574 lines
19 KiB
Ruby
Executable File
574 lines
19 KiB
Ruby
Executable File
#
|
|
# 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 <derek.ward@rockstarnorth.com>
|
|
# 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 |