# # File:: cc_make_assetbuilder_rebuild.rb # Description:: makes an assetbuilder rebuild file for inclusion in cruise control configs. # # Author:: Derek Ward # Date:: 02nd September 2011 # # Passed in :- see OPTIONS ... # Passed out :- # # Returns :- #----------------------------------------------------------------------------- # Uses / Requires #----------------------------------------------------------------------------- require 'pipeline/os/path' require 'pipeline/os/getopt' require 'pipeline/log/log' require 'xml' require "rexml/document" require 'find' include REXML include Pipeline #----------------------------------------------------------------------------- # Constants #----------------------------------------------------------------------------- INFO_BLUE = "[colourise=blue]INFO_MSG: " HELP_URL = "https://devstar.rockstargames.com/wiki/index.php/Asset_Builder_-_Rebuilds" # todo - add wiki REBUILD_OUTPUT_FILE = "assetbuilder_rebuilds" NAMESPACE = "urn:ccnet.config.builder" CB_SCOPE = 'cb:scope' CB_NAMESPACE = 'xmlns:cb' CB_INCLUDE = "cb:include" MACHINE = ENV["COMPUTERNAME"] REMOTING_PORT = "21234" REMOTING_FILE = "CruiseManager.rem" SERVER_URL = "tcp://#{MACHINE}:#{REMOTING_PORT}/#{REMOTING_FILE}" CCTRAY_PROJECTS_XPATH = "Configuration/Projects" CCTRAY_ALL_PROJECTS_XPATH = 'Configuration/Projects/Project' KEEP_CCTRAY_PROJECT_REGEXP = /^(assetbuilder)/ ENV_SYMBOLS = [ "RS_EXPORT", "RAGE_DIR", "RS_TOOLSSRC", "RS_TOOLSCONFIG", "RS_CODEBRANCH", "RAGE_3RDPARTY", "RAGE_3RDPARTY", "RS_PROJECT" ] EXCLUSIONS_XPATH = "//rebuilds/exclusion" FOLDERS_XPATH = "//rebuilds/folder" DEFAULT_BRANCH = "dev" REBUILD_CLASS = "auto" CCTRAY_TO_RUN = "%RS_TOOLSROOT%/script/util/CruiseControl/CCTray/cctray_assetbuild.bat" NO_RECURSE_CHARACTER = "<" # TODO: DW - ideally this should be declared in only one place! # # Rebuild class class Rebuild attr_accessor :folders_with_underbars, :folders, :wildcard, :full_rebuild_name def initialize(folder, variant="") puts "create rebuild #{folder}" folder_names = folder.split("/") @folders_with_underbars = folder_names.join("_") @folders ="#{NO_RECURSE_CHARACTER}#{folder}" @full_rebuild_name = "assetbuilder_#{ENV["RS_PROJECT"]}_#{DEFAULT_BRANCH}_rebuild_#{REBUILD_CLASS}_#{@folders_with_underbars}#{variant}" @wildcard ="*.*" end def to_s() "#{@folders_with_underbars.ljust(40)} #{@folders.ljust(40)} #{@wildcard.ljust(5)}" end end # CC make class CCMakerAssetbuildRebuild def initialize(variant) @config = Pipeline::Config.instance @config.project.load_config @env = Environment.new() @config.project.fill_env( @env ) ENV_SYMBOLS.each { |sym| @env.add(sym,ENV[sym]) } @p4 = SCM::Perforce.new() @p4.port = @config.sc_server @p4.client = @config.sc_workspace @p4.user = @config.sc_username @p4.connect() @folders = [] @exclusions = [] @rebuilds = [] @variant = variant end # # main control process def process(filenames, targetDir, cctray_file, submit ) make( filenames ) save( targetDir ) update_cctray( cctray_file ) submit_or_revert(submit) true end # # process exclusions & folders def make( filenames ) filenames.each do |filename| xmldoc = LibXML::XML::Document::file( filename ) exclusion_nodes = xmldoc.find(EXCLUSIONS_XPATH) exclusion_nodes.each do |node| exclusion = @env.subst(node.attributes["path"]) exclusion = OS::Path::normalise(exclusion) @exclusions << exclusion puts "#{INFO_BLUE} Exclusion registered #{exclusion}" end folder_nodes = xmldoc.find(FOLDERS_XPATH) folder_nodes.each do |node| folder = @env.subst(node.attributes["path"]) folder = OS::Path::normalise(folder) @folders << folder puts "#{INFO_BLUE} Folder registered #{folder}" end end end # # save files def save( target_dir ) #FileUtils::mkdir_p( OS::Path::get_directory( filename ) ) unless ( File::directory?( filename ) ) @change_id = @p4.create_changelist( "Automatically generated by cc_make_assetbuilder_rebuild.rb" ) save_target_list( target_dir ) end # submit or revert files def submit_or_revert( submit ) @p4.run_revert( '-a', '-c', @change_id.to_s, '//...') files = @p4.run_opened( '-c', @change_id.to_s ) if ( files.size > 0 ) if (submit) puts "#{INFO_BLUE} Submitting #{files.size} files in CL #{@change_id}" puts "Warning: #{CCTRAY_TO_RUN} should be run to update your cctray." files.each {|file| puts "#{INFO_BLUE} #{file['depotFile']}" } submit_result = @p4.run_submit( '-c', @change_id.to_s ) else puts "#{INFO_BLUE} Pending #{files.size} files in CL #{@change_id}" end elsif ( 0 == files.size ) puts "#{INFO_BLUE} No files changed - CL #{@change_id} deleted" @p4.run_change('-d', @change_id.to_s) end end # # Update the CCTray config file def update_cctray( cctray_file ) @p4.run_sync( cctray_file ) fstat = @p4.run_fstat( cctray_file ).shift basetype, modifiers = fstat['headType'].split( '+' ) if (fstat.key?('headType')) # perforce drives me dippy. basetype, modifiers = fstat['type'].split( '+' ) if (fstat.key?('type')) @p4.run_edit_or_add( '-c', @change_id.to_s, cctray_file ) @p4.run_reopen( '-c', @change_id, cctray_file ) @p4.run_reopen( '-t', "#{basetype}+w", '-c', @change_id.to_s, cctray_file ) xmldoc = REXML::Document.new File.open( cctray_file, 'r' ) # new working element store new_projects_elem = Element.new( "Projects" ) # keep some existing elements xmldoc.elements.each( CCTRAY_ALL_PROJECTS_XPATH ) do |elem| if elem.attributes["projectName"] =~ KEEP_CCTRAY_PROJECT_REGEXP if not elem.attributes["projectName"].include?("_rebuild_#{REBUILD_CLASS}") puts "#{INFO_BLUE} Keeping CCtray project #{elem.attributes["projectName"]}" new_projects_elem.add_element(elem.clone) end end end # add all elements as defined by solutions @rebuilds.each do |rebuild| found = false project_name = rebuild.full_rebuild_name #puts "#{INFO_BLUE} Adding CCtray project #{project_name}" elem = Element.new( "Project" ) elem.add_attribute("serverUrl", SERVER_URL) elem.add_attribute("projectName", project_name) elem.add_attribute("showProject", "true") new_projects_elem.add_element(elem) end # switch to the new element in the xmldoc xmldoc.delete_element(CCTRAY_PROJECTS_XPATH) xmldoc.root.add_element(new_projects_elem) File.open( cctray_file, 'w' ) do |file| fmt = REXML::Formatters::Default.new() fmt.write(xmldoc,file) end end private # # Save a list of targets def save_target_list( target_dir ) puts "#{INFO_BLUE} Generating Rebuilds" generate_rebuilds() puts "#{INFO_BLUE} Rebuilds to XML" xmldoc = rebuilds_to_xml( ) dest_file = OS::Path.combine(target_dir, "#{REBUILD_OUTPUT_FILE}#{@variant}.xml") puts "#{INFO_BLUE} Writing #{dest_file}" puts "#{INFO_BLUE} Sync #{dest_file}" @p4.run_sync( dest_file ) puts "#{INFO_BLUE} Edit or add #{dest_file}" @p4.run_edit_or_add( '-c', @change_id.to_s, dest_file ) # puts "#{INFO_BLUE} Reopen #{dest_file}" @p4.run_reopen( '-c', @change_id.to_s, dest_file ) xmldoc.save( dest_file ) end # # Discover folders and files and make rebuild specifications def generate_rebuilds( ) # for each folder - find folders that have filenames and add just these files to a rebuild, this will also catch leaf directories and non leaf directories with files in them rebuild_folders = [] @folders.each do |folder| puts "#{INFO_BLUE} Processing folder #{folder}" Find.find(folder) do |path| if FileTest.directory?(path) prune = false @exclusions.each do |exclusion| prune = true if OS::Path.normalise(path).include?(exclusion) end if (prune) puts "#{INFO_BLUE} Pruned #{path}" Find.prune # Don't look any further into this directory. else next end else path_to_add = OS::Path.normalise("#{OS::Path::remove_filename(path)}".downcase) path_to_add = path_to_add.sub(OS::Path.normalise("#{ENV["RS_EXPORT"].downcase}/"), "") next if path_to_add.include?(":")# DW - I don;t understand this bug for some reason the RS_EXPORT gsub doesn't work in one occassion rebuild_folders << path_to_add unless rebuild_folders.include?(path_to_add) end end end rebuild_folders.each do |rebuild_folder| rebuild = Rebuild.new(rebuild_folder,@variant) puts "#{INFO_BLUE} Rebuild : #{rebuild}" @rebuilds << rebuild end end # # Get xml doc for target file def rebuilds_to_xml( ) xmldoc = XML::Document.new xmldoc.encoding = XML::Encoding::UTF_8 xmldoc.root = XML::Node.new( CB_SCOPE ) xmldoc.root.attributes['xmlns:cb'] = NAMESPACE @rebuilds.each do |rebuild| node = XML::Node.new( CB_SCOPE ) node.attributes['subvariant'] = "_#{REBUILD_CLASS}_#{rebuild.folders_with_underbars}" node.attributes['wildcard'] = "#{rebuild.folders}\\#{rebuild.wildcard}" include = XML::Node.new( CB_INCLUDE ) include.attributes['href'] = "$(base_file)" node << include xmldoc.root << node end xmldoc end # log def self.log( ) @@log = Log.new( 'cc_make_assetbuilder_rebuild' ) if ( @@log.nil? ) @@log end @@log = nil end #---------------------------------------------------------------------------- # Implementation #---------------------------------------------------------------------------- if ( __FILE__ == $0 ) then OPTIONS = [ [ "--help", "-h", OS::Getopt::BOOLEAN, "display usage information." ], [ '--target_dir', '-t', OS::Getopt::REQUIRED, "directory to output results to" ], [ '--cctray' , '-c', OS::Getopt::OPTIONAL, "cctray file to update" ], [ '--submit' , '-s', OS::Getopt::BOOLEAN, "submit or revert" ], [ '--variant' , '-v', OS::Getopt::OPTIONAL, "type of assetbuilder we are making rebuilds for eg. _hw " ], ] begin g_AppName = File::basename( __FILE__, '.rb' ) #--------------------------------------------------------------------- # Parse Command Line #--------------------------------------------------------------------- opts, g_Trailing = OS::Getopt.getopts( OPTIONS ) if ( opts['help'] ) then puts OS::Getopt.usage( OPTIONS ) exit( 1 ) end if ( not opts['target_dir'] ) then puts OS::Getopt.usage( OPTIONS ) exit( 1 ) end if (g_Trailing.length <= 0) puts OS::Getopt.usage( OPTIONS ) puts "Supply files to read" exit( 1 ) end g_TargetDir = opts['target_dir'] g_CCtrayFile = opts['cctray'] g_Submit = opts["submit"] g_Variant = opts["variant"] ? opts["variant"] : "" if (g_CCtrayFile) filename = File::expand_path( g_CCtrayFile ) if (File.exist? filename) g_CCtrayFile = filename puts "#{INFO_BLUE} CCtray file found #{g_CCtrayFile}" else g_CCtrayFile = nil $stderr.puts("Error: filename #{filename} not found") end end g_Filenames = [] Pipeline::OS::Path::set_downcase_on_normalise( false ) g_Trailing.each_with_index do |filename, index| filename = File::expand_path( filename ) if (File.exist? filename) g_Filenames << filename else $stderr.puts("Error: filename #{filename} not found") end end Pipeline::OS::Path::set_downcase_on_normalise( true ) # Make puts "#{INFO_BLUE} #{HELP_URL}" cc_maker = CCMakerAssetbuildRebuild.new(g_Variant) ret = cc_maker.process( g_Filenames, g_TargetDir, g_CCtrayFile, g_Submit ) Process.exit! -2 unless ret rescue Exception => ex $stderr.puts "Error: Unhandled exception: #{ex.message}" puts ex.backtrace().join("\n") Process.exit! -1 end Process.exit! 0 end # %RS_TOOLSLIB%/util/CruiseControl/cc_make.rb