Files
gtav-src/tools_ng/ironlib/util/projGen/generate_makefiles.rb
T
2025-09-29 00:52:08 +02:00

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