# # File:: %RS_TOOLSLIB%/util/generate_makefiles.rb # Description:: ironruby script that recursively scans for source files # pulls up a treeview control for you to edit # and finally produces a local makefile.txt file for use with PG3 # - it also reads the csproj (& vcxproj files in the case of project references ) to produce the makefile. # - it also produces guid files, project references and assembly references. # # Author:: Derek Ward # Date:: 15th July 2013 # #----------------------------------------------------------------------------- # Uses #----------------------------------------------------------------------------- require 'RSG.Base.dll' require 'mscorlib' require 'System.Core' require 'System.Xml' require 'System.Drawing' require 'System.Windows.Forms' require 'pathname' require 'RSG.SourceControl.Perforce.dll' require 'RSG.Base.Configuration.dll' include System::IO include System::Windows::Forms include System::Xml include RSG::Base::Configuration include RSG::Base::Logging include RSG::Base::Logging::Universal include RSG::Base::OS require 'pipeline/os/options' include Pipeline include RSG::SourceControl::Perforce using_clr_extensions RSG::Base::Extensions module Generate_makefiles #----------------------------------------------------------------------------- # Constants #----------------------------------------------------------------------------- SCRIPT = __FILE__ AUTHOR = 'Derek Ward' EMAIL = 'RSGEDI Tools <*tools@rockstarnorth.com>' ICON = "Rockstar_North_Logo.ico" # just serves as a default, the treeview allows you to add, remove these. VALID_SOURCE_FILETYPES = [ ".c", ".cpp", ".h", ".ico", ".config", ".cs",".res", ".resx", ".xaml", ".frag", ".job", ".pmjob", ".task", ".inl", ".sch", ".psc", ".dtx", ".spa", ".inc", ".fxh", ".fx", ".rc", ".png", ".appxmanifest", ".bat", ".datasource" ] # just serves as a default, the treeview allows you to add, remove these. INVALID_DIRECTORIES = ["obj", "bin"] OUT_FILENAME = ".makefile" FORM_WIDTH = 800 FORM_HEIGHT = 500 FORM_TITLE = "PG3 Makefile Generator" VERSION = 1.0 COMPILE_XPATH = "//*[local-name() = 'Compile']/@Include" GUID_XPATH = "//*[local-name() = 'ProjectGuid']" NO_TASK_XPATH = "//*[local-name() = 'None']/@Include" CONTENT_XPATH = "//*[local-name() = 'Content']/@Include" ASSEMBLY_REF_XPATH = "//*[local-name() = 'Reference']/@Include" PROJECT_REF_XPATH = "//*[local-name() = 'ProjectReference']/@Include" OUTPUT_TYPE_XPATH = "//*[local-name() = 'OutputType']" FRAMEWORK_VERSION_XPATH = "//*[local-name() = 'TargetFrameworkVersion']" OUTPUT_PATH_XPATH = "//*[local-name() = 'OutputPath']" OPTIONS = [] #----------------------------------------------------------------------------- # Helper for guids. #----------------------------------------------------------------------------- class GuidUtility # # Read a project file and write out its guid ( if required ) # def GuidUtility::WriteProjectGuid(csproj_filename, guid_filename, log, p4) doc = XmlDocument.new log.Message "Loading #{csproj_filename}" doc.Load(csproj_filename) guids = doc.SelectNodes(GUID_XPATH) guid = guids.Item(0).InnerText.ToLower guid = guid.Replace("{","").Replace("}","") if (guid != nil) if (File.exist? guid_filename) File.open(guid_filename, 'r') do |f| read_guid = f.read.downcase.sub("\n","") if read_guid == guid log.Message "guid #{guid} already in #{guid_filename}" return else puts guid log.Warning "guid #{guid} not found, #{read_guid} was found in #{guid_filename}" end end if (!File.writable?(guid_filename)) log.Warning "#{guid_filename} is not writable." return end end File.open(guid_filename, 'w') do |f| f.puts(guid) log.Message "Wrote #{guid} => #{guid_filename}" end # Add to p4 for convenience begin g_P4.Run( "add", false, guid_filename) g_Log.Message "*** #{fullPath} added to default CL ***" rescue Exception => ex # Oh look I'm using expection catching to silence any issues, damn lazing, but beacuse I had a load of inexplicable issues with FileState class end end end end #----------------------------------------------------------------------------- # Helper to recurse directories and build a treeview # and Serialisation of the created treeview. #----------------------------------------------------------------------------- class TreeViewUtility # # Create treeview of passed path # def TreeViewUtility::Create(path) treeview = System::Windows::Forms::TreeView.new() treeview.Nodes.Clear() rootDirectoryInfo = DirectoryInfo.new(path) treeview.Nodes.Add(CreateDirectoryNode(rootDirectoryInfo)) treeview end # # Create treeview of passed paths # def TreeViewUtility::CreateFromPaths(paths) treeview_local = System::Windows::Forms::TreeView.new() treeview_local.Nodes.Clear() # just a thought #rootDirectoryInfo = DirectoryInfo.new(Dir.pwd) #treeview_local.Nodes.Add(CreateDirectoryNode(rootDirectoryInfo)) treeview_csproj = System::Windows::Forms::TreeView.new() treeview_csproj.Nodes.Clear() treeview_csproj.PathSeparator = "\\"; PopulateTreeView(treeview_csproj, paths, "\\"); treeview = treeview_csproj # Merge(treeview_local, treeview_csproj) end # # Creates the treeview for the paths passed, sperated by the path separator # def TreeViewUtility::PopulateTreeView(treeview, paths, path_separator) last_node = nil paths.each do |path| sub_path_aggregate = "" split_paths = path.split(path_separator) split_paths.each do |sub_path| sub_path_aggregate += sub_path + path_separator nodes = treeview.Nodes.Find(sub_path_aggregate, true) if nodes.Length == 0 if last_node == nil last_node = treeview.Nodes.Add(sub_path_aggregate, sub_path) else last_node = last_node.Nodes.Add(sub_path_aggregate, sub_path) end else last_node = nodes[0] end last_node.checked = true end end end # # Resursive helper to construct treeview, it unchecks files you are likely uninterested in. # def TreeViewUtility::CreateDirectoryNode(directoryInfo) directoryNode = TreeNode.new(directoryInfo.Name) puts directoryInfo.Name directoryInfo.GetDirectories().each do |directory| dir_node = TreeViewUtility::CreateDirectoryNode(directory) dir_node.checked = true INVALID_DIRECTORIES.each do |invalid_dir| dir_node.checked = false if (directory.Name.ToString() == invalid_dir) end directoryNode.Nodes.Add(dir_node) end directoryInfo.GetFiles().each do |file| file_node = TreeNode.new(file.Name) file_node.checked = false VALID_SOURCE_FILETYPES.each do |sft| file_node.checked = true if (file.Name.EndsWith(sft)) end directoryNode.Nodes.Add(file_node) end directoryNode end # # Serialise treeview of checked treenodes. # def TreeViewUtility::Serialise(treeViewNode, depth, file) tabs = "\t" depth.times { tabs += "\t" } is_dir = false # sorry I can't work out why the name is mangled like this?! filename = treeViewNode.ToString().Replace("TreeNode:","").strip() if (not treeViewNode.ToString().include? ".") if (treeViewNode.checked) file.puts "#{tabs}Directory #{filename} {" is_dir = true depth += 1 end else file.puts "#{tabs}#{filename}" if (treeViewNode.checked) end if (treeViewNode.checked) treeViewNode.Nodes.each do |node| Serialise(node, depth + 1, file) if (node.checked) end end file.puts "#{tabs}}" if is_dir end end #----------------------------------------------------------------------------- # Implementation & main entry point. #----------------------------------------------------------------------------- if ( __FILE__ == $0 ) then # Initialise log and console output. LogFactory.Initialize(); g_Log = LogFactory.method(:CreateUniversal).of(UniversalLog).call( SCRIPT ) LogFactory.CreateApplicationConsoleLogTarget( ) g_Options = OS::Options::new( OPTIONS ) g_P4 = P4::new( ) g_P4.CallingProgram = __FILE__ g_P4.Connect( ) begin if ( g_Options.is_enabled?( 'help' ) ) g_Log.Message "#{__FILE__}" g_Log.Message "Usage:" g_Log.Message g_Options.usage() exit( 1 ) end g_Log.Message "-----------------------------------------------------------------------------------------" #g_Log.Message "Creating treeview of local filesystem..." #g_Log.Message "Searching within #{Dir.pwd}..." #treeview = TreeViewUtility::Create(Dir.pwd) g_Log.Message "-----------------------------------------------------------------------------------------" dirname = File.basename(Dir.getwd) # assume the working dir is the name of the csproj file project_name = dirname #treeview.Nodes[0].FullPath # Write guid file csproj_filename = "#{project_name}.csproj" source_filenames = [] no_task_filenames = [] content_filenames = [] assembly_references = [] project_references_filenames = [] output_types = [] framework_versions = [] framework_version = nil output_paths = [] output_path = nil if (not File.exist? csproj_filename) g_Log.Message "Expected proj filename didnt exist #{csproj_filename}..." files = Dir.entries(Dir.getwd) files.each do |file| if (File.extname(file).include?("csproj")) project_name = File.basename(file).chomp(File.extname(file)) csproj_filename = "#{project_name}.csproj" end end end output_filename = "#{project_name}#{OUT_FILENAME}" g_Log.Message "Project name is '#{project_name}', Output filename will be '#{output_filename}'" is_exe = false g_Log.Message "-----------------------------------------------------------------------------------------" g_Log.Message "Opening csproj (#{csproj_filename})" if (File.exist? csproj_filename) guid_filename = "#{project_name}.guid" g_Log.Message "-----------------------------------------------------------------------------------------" g_Log.Message "Creating Guid file... #{guid_filename}" GuidUtility::WriteProjectGuid(csproj_filename, guid_filename, g_Log, g_P4); g_Log.Message "-----------------------------------------------------------------------------------------" g_Log.Message "Reading csproj" g_Log.Warning "if you ran a previous conversion and the file is edit you may wish to revert." if File.writable?(csproj_filename) doc = XmlDocument.new doc.Load(csproj_filename) g_Log.Message "Reading Source files found in csproj." i = 0 source_files = doc.SelectNodes(COMPILE_XPATH) source_files.Count.times { source_filenames << project_name + "\\" + source_files.Item(i).Value; i+= 1 } g_Log.Message "#{source_filenames.length} read" g_Log.Message "Reading 'no task' source files found in csproj." i = 0 no_task_files = doc.SelectNodes(NO_TASK_XPATH) no_task_files.Count.times { no_task_filenames << project_name + "\\" + no_task_files.Item(i).Value; i+= 1 } source_filenames += no_task_filenames g_Log.Message "#{no_task_filenames.length} read" g_Log.Message "Reading 'content' source files found in csproj." i = 0 content_files = doc.SelectNodes(CONTENT_XPATH) content_files.Count.times { content_filenames << project_name + "\\" + content_files.Item(i).Value; i+= 1 } source_filenames += content_filenames g_Log.Message "#{content_filenames.length} read" g_Log.Message "Reading Assembly references found in csproj." i = 0 assembly_refs = doc.SelectNodes(ASSEMBLY_REF_XPATH) assembly_refs.Count.times { assembly_references << assembly_refs.Item(i).Value; i+=1 } g_Log.Message "#{assembly_references.length} read" g_Log.Message "Reading Project references found in csproj." i = 0 project_references = doc.SelectNodes(PROJECT_REF_XPATH) project_references.Count.times { project_references_filenames << project_references.Item(i).Value; i+=1 } g_Log.Message "#{project_references_filenames.length} read" g_Log.Message "Reading outputtype in csproj." i = 0 output_type_nodes = doc.SelectNodes(OUTPUT_TYPE_XPATH) output_type_nodes.Count.times { output_types << output_type_nodes.Item(i).InnerText; i+=1 } if (output_types.length == 1) is_exe = output_types.last.downcase.include? "exe" end g_Log.Message "#{output_types.length} read (#{output_types.last})" g_Log.Message "Reading frameworkVersion in csproj." framework_version_nodes = doc.SelectNodes(FRAMEWORK_VERSION_XPATH) i = 0 if (framework_version_nodes != nil) framework_version_nodes.Count.times { framework_versions << framework_version_nodes.Item(i).InnerText; i+=1 } if (framework_versions.length == 1) framework_version = framework_versions.last end g_Log.Message "#{framework_versions.length} read (#{framework_versions.last})" end g_Log.Message "Reading outputpath in csproj." output_path_nodes = doc.SelectNodes(OUTPUT_PATH_XPATH) g_Log.Message "Read #{output_path_nodes.Count} nodes" i = 0 if (output_path_nodes != nil) output_path_nodes.Count.times { output_paths << output_path_nodes.Item(i).InnerText; i+=1 } if (output_paths.length > 0) output_path = output_paths.first end g_Log.Message "#{output_paths.length} read (#{output_paths.last})" end else g_Log.Warning "Csproj (#{csproj_filename}) does not exist" end g_Log.Message "-----------------------------------------------------------------------------------------" g_Log.Message "Creating guid files for all the project references we discovered in csproj." if (project_references_filenames.length > 0) project_references_filenames.each do |prf| full_path = File.expand_path(prf) dirname = File.dirname(full_path) basename = File.basename(full_path, ".*" ) guid_filename = "#{dirname}\\#{basename}.guid" GuidUtility::WriteProjectGuid(prf, guid_filename, g_Log, g_P4) end end g_Log.Message "-----------------------------------------------------------------------------------------" if (source_filenames.length > 0 ) g_Log.Message "Creating treeview from paths read from csproj." treeview = TreeViewUtility::CreateFromPaths(source_filenames) else g_Log.Message "Creating treeview from paths read in filesystem." treeview = TreeViewUtility::Create(project_name) end g_Log.Message "-----------------------------------------------------------------------------------------" g_Log.Message "- Please edit treeview checking the checkboxes of the files you desire then close window.".upcase form = Form.new form.width = FORM_WIDTH form.height = FORM_HEIGHT form.text = "#{FORM_TITLE} v#{VERSION} : #{SCRIPT}"; form.icon = System::Drawing::Icon.new(File.join(ENV["RS_TOOLSROOT"], "data", ICON)) treeview.dock = System::Windows::Forms::DockStyle.Fill treeview.nodes[0].expand treeview.nodes[0].checked = true treeview.check_boxes = true form.controls.add treeview # comment in if you wish to see the treeview? #form.show_dialog current_dir = Pathname.new(Dir.pwd) assembly_references_filename = "#{project_name}.references" project_references_filename = "#{project_name}.projectReferences" =begin if (assembly_references.length > 0) g_Log.Message "-----------------------------------------------------------------------------------------" g_Log.Message "Writing #{assembly_references_filename}" File.open(assembly_references_filename, 'w') do |f| f.puts("# - Initally generated by #{SCRIPT}") f.puts("References {") assembly_references.each do |ar| f.puts("\t#{ar}") end f.puts("}") end end if (project_references_filenames.length > 0) g_Log.Message "-----------------------------------------------------------------------------------------" g_Log.Message "Writing #{project_references_filename}" File.open(project_references_filename, 'w') do |f| f.puts("# - Initally generated by #{SCRIPT}") f.puts("ProjectReferences {") project_references_filenames.each do |pr| f.puts("\t#{pr}") end f.puts("}") end end =end fullPath = "#{Dir.pwd}\\#{output_filename}" if (File.exist?(fullPath)) if (not File.writable?(fullPath)) g_Log.Error "#{fullPath} is not writable" exit(-1) end end g_Log.Message "-----------------------------------------------------------------------------------------" g_Log.Message "Writing #{fullPath}" File.open(fullPath, 'w') do |f| f.puts("#") f.puts("# #{output_filename}") f.puts("# - Initally generated by #{SCRIPT}") f.puts("# - Edit as required.") f.puts("#") f.puts(" ") f.puts("Project #{project_name}\n") f.puts(" ") if (is_exe) f.puts("ConfigurationType exe") f.puts(" ") end if (framework_version != nil) f.puts("FrameworkVersion #{framework_version.gsub("v","")}") f.puts(" ") g_Log.Message("FrameworkVersion #{framework_version.gsub("v","")}") else g_Log.Warning("No framework version found in csproj") end if (output_path != nil) f.puts("OutputPath #{output_path}") f.puts(" ") g_Log.Message("OutputPath #{output_path}") else g_Log.Warning("No output path found in csproj") end f.puts("Files {") if (treeview.Nodes[0].checked) treeview.Nodes[0].nodes.each do |nodes| TreeViewUtility::Serialise(nodes, 0, f) end end f.puts("}") f.puts(" ") if (project_references_filenames.length > 0) f.puts("ProjectReferences {") project_references_filenames.each do |pr| f.puts("\t#{pr}") end f.puts("}") end if (assembly_references.length > 0) f.puts("References {") assembly_references.each do |ar| f.puts("\t#{ar}") end f.puts("}") end #f.puts("Include #{project_name}.References") if (assembly_references.length > 0) #f.puts("Include #{project_name}.ProjectReferences") if (project_references_filenames.length > 0) end # Add to p4 for convenience begin g_P4.Run( "add", false, fullPath) g_Log.Message "*** #{fullPath} added to default CL ***" rescue Exception => ex # Oh look I'm using expection catching to silence any issues, damn lazing, but beacuse I had a load of inexplicable issues with FileState class end rescue SystemExit => ex exit( ex.status ) rescue Exception => ex ConsoleLog::new( ) Log::Log__Error( "Unhandled error in #{SCRIPT}" ) Log::Log__Error( ex.backtrace.join("\n" ) + "\n" + ex.Message ) end end end # module