Files
gtav-src/tools_ng/lib/util/parcodegen_ci/parcodegen_ci.rb
T
2025-09-29 00:52:08 +02:00

740 lines
27 KiB
Ruby
Executable File

#
# File:: parcodegen_ci.rb
# Description:: Continuous Integration of ParCodeGen psc files.
#
# This script takes a modifications xml file as produced by cruise control,
# It updates p4 with the associated .PSC file.
#
# Pseudo-Pseudo-Code
# ==================
# - For all the files modified they are converted by parcodegen if they are sourcecode files (cpp, c)
# - If there are psc files they are just copied.
# - They all go to a tmp directory.
# - Then for all files we processed
# - if an associated psc file doesn't exist in the tmp dir delete the psc file from the p4 build/metadata dir.
# - if it does exist it will be added/edited to the build/metadata directory.
#
# Author:: Derek Ward <derek.ward@rockstarnorth.com>
# Date:: 08th April 2011
#
# Passed in :- dest directory, filename/widcards or modification.xml(cruise control) to parse.
# Passed out :- stderr contains all errors
# stdout for all other output.
# Returns :- returns non zero upon detecting any errors
#-----------------------------------------------------------------------------
# Uses / Requires
#-----------------------------------------------------------------------------
require 'pipeline/config/projects'
require 'pipeline/os/getopt'
require 'rexml/document'
include Pipeline
require 'systemu'
require 'fileutils'
#-----------------------------------------------------------------------------
# Constants
#-----------------------------------------------------------------------------
OPTIONS = [
[ "--help", "-h", OS::Getopt::BOOLEAN, "display usage information." ],
[ '--project', '-p', OS::Getopt::REQUIRED, 'specify project key (e.g. gta5, jimmy)' ],
[ '--branch', '-b', OS::Getopt::REQUIRED, 'specify project branch (e.g. dev, dev_migrate)' ],
[ '--dest_folder', '-d', OS::Getopt::REQUIRED, 'dest folder eg. x/gta5/build/dev/metadata' ],
[ '--disablecheckin', '-c', OS::Getopt::BOOLEAN, 'prevent checkin ( for development )' ],
[ '--disable_revert_unchanged', '-r', OS::Getopt::BOOLEAN, 'prevent revert unchanged files ( for development )' ],
[ '--pre_convert_script', '-ps', OS::Getopt::BOOLEAN, 'execute script on each file eg. blah.rb, this will be called like this; blah.rb file.cpp' ],
]
TRAILING_DESC = 'File paths separated by spaces (e.g. x:/gta5/src/dev). and/or a cruise control modifications.xml filename.'
INFO = "" # comment back in for a verbose report "[colourise=grey]INFO_MSG: "
INFO_BLACK = "[colourise=black]INFO_MSG: "
INFO_BLUE = "[colourise=blue]INFO_MSG: "
INFO_GREEN = "[colourise=green]INFO_MSG: "
PAR_CODE_GEN_CI_VERSION = "1.0"
PAR_CODE_GEN_COMMAND = "$(toolsroot)\\bin\\coding\\python\\parCodeGen.exe"
PRE_CONVERT_SCRIPT_COMMAND = "$(toolsroot)\\lib\\util\\parcodegen_ci\\parcodegen_ci_pre_convert.rb"
TMP_FOLDER = "$(toolsroot)\\tmp\\parcodegen_ci"
PAR_CODE_GEN_NO_XML_TOKEN = "No XML metadata"
COPY_EXTENSIONS = [ "psc" ]
CONVERT_EXTENSIONS = [ ]
PAR_CODE_GEN_EXTENSION = "psc"
#-----------------------------------------------------------------------------
# General helper methods
# - log files added in retrospect of the fact they could be useful!
#-----------------------------------------------------------------------------
$gLog = Log.new( 'parcodegenci' )
def info(msg)
$gLog.info("#{INFO}#{msg}")
end
def info_black(msg)
$gLog.info("#{INFO_BLACK}#{msg}")
end
def info_blue(msg)
$gLog.info("#{INFO_BLUE}#{msg}")
end
def info_green(msg)
$gLog.info("#{INFO_GREEN}#{msg}")
end
def warning(msg)
$gLog.warn("#{INFO}Warning: #{msg}")
end
def error(msg)
$gLog.error("Error: #{msg}")
end
#-----------------------------------------------------------------------------
# ParCodeGen helper class
#-----------------------------------------------------------------------------
class ParCodeGenCi
def initialize(pre_convert_script = false)
@p4 = Pipeline::Config::instance().scm
@p4.connect()
@p4_rage = Pipeline::Config::instance().ragescm
@p4_rage.connect()
@pre_convert_script = false # DW - removed for now, not working... @pre_convert_script = pre_convert_script
end
#-----------------------------------------------------------------------------
# Parse the Cruise Control XML file for modificatons.
# Return an array of p4 filespecs.
#
# FYI - the modifications.xml 'schema'
#
#<!-- Start of the group of modifications (even if just one). -->
#<ArrayOfModification xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
# <!-- Start of one modification. -->
# <Modification>
# <!-- The change number. -->--filename=
# <ChangeNumber>... value ...</ChangeNumber>
# <!-- The comment. -->
# <Comment>... value ...</Comment>
# <!-- The user's email address. -->
# <EmailAddress>... value ...</EmailAddress>
# <!-- The affected file name. -->
# <FileName>... value ...</FileName>
# <!-- The affect file's folder name. -->
# <FolderName>... value ...</FolderName>
# <!-- The change timestamp, in yyyy-mm-ddThh:mm:ss.nnnn-hhmm format -->
# <ModifiedTime>... value ...</ModifiedTime>
# <!-- The operation type. -->
# <Type>... value ...</Type>
# <!-- The user name. -->
# <UserName>... value ...</UserName>
# <!-- The related URL. -->
# <Url>... value ...</Url>
# <!-- The file version. -->
# <Version>... value ...</Version>
# <!-- End of modification. -->
# </Modification>
# <!-- End of the group of modifications. -->
#</ArrayOfModification>
#Pasted from <http://confluence.public.thoughtworks.org/display/CCNET/Modification+Writer+Task>
#-----------------------------------------------------------------------------
def parse_cc_modifications( filename, project, branchname)
info "Parsing #{filename}"
files = []
begin
if not File.exists?(filename)
error "#{filename} does not exist."
return nil
end
#----------- Open Source xml ---------------------
src_file = File.new( filename )
if not src_file
error "#{filename} could not open."
return nil
end
#----------- Read XML doc ------------------------
xmldoc = REXML::Document.new( src_file )
if not xmldoc
error "#{filename} could not be opened as an XML document."
return nil
end
xmldoc.elements.each( 'ArrayOfModification/Modification' ) do |modification|
folder_name = nil
file_name = nil
revision = nil
type = nil
modification.elements.each('FolderName') { |element| folder_name = element.text }
modification.elements.each('FileName') { |element| file_name = element.text }
modification.elements.each('Version') { |element| revision = element.text }
modification.elements.each('Type') { |element| type = element.text }
if folder_name.nil? or file_name.nil? or revision.nil? or type.nil?
error "#{filename} Invalid XML."
return nil
end
local_file = OS::Path.normalise(@p4.depot2local( "#{folder_name}/#{file_name}" ))
local_file = OS::Path.normalise(@p4_rage.depot2local( "#{folder_name}/#{file_name}" )) if ( local_file.nil? or local_file.length==0 )
# its ok for files not to be resolved - might be outside the workspace.
#error "Can't resolve #{folder_name}/#{file_name} locally" if local_file.nil? or local_file.length==0
# there is no requirement for the file to exist though since this may be a delete operation
# but the result will be that the file is processed and finally deleted - we need to process it in order to
# know that it needs deleting from perforce.
if ( local_file and local_file.length > 0)
info "\t#{type} Local file parsed #{local_file} "
files << local_file
else
warning "#{folder_name}/#{file_name} will not be converted."
end
end
rescue Exception => ex
error "Unexpected exception parsing modifications: "
error "\t#{ex.message}"
ex.backtrace.each { |m| error "\t#{m}"; }
Process.exit -1
end
files
end
#--------------------------------------------------------------------------------
# Kick off the conversion/copy of files - they will be built/copied to tmp folder
#--------------------------------------------------------------------------------
def process_files( env, files, error_count, dst_folder_root )
begin
processed_files = []
# --- report ---
report = { "copied" => 0, "copied_ok" => 0, "not_copied_ok" => 0, "converted" => 0, "converted_ok" => 0, "skipped" => 0, "non_existent" => 0, "deleted" => 0 }
# --- copy directly / convert ---
# --- first strip the files down to more concise list ---
stripped_files = []
files.uniq.each do |file|
if (not file.is_a?( String ))
error_count += 1
error "file is a #{file.class} #{file.to_s}"
next
end
if file.nil?
error_count += 1
error "nil file encountered."
next
end
if not File.exist?(file)
report["non_existent"] += 1
else
filename = OS::Path.get_filename(file)
ext = OS::Path::get_extension(filename)
is_copy = COPY_EXTENSIONS.include?(ext)
is_convert = CONVERT_EXTENSIONS.include?(ext)
if (not is_copy and not is_convert)
report["skipped"] += 1
info "\tSkipping #{file}"# as #{ext} not in #{CONVERT_EXTENSIONS.join(" ")} or #{COPY_EXTENSIONS.join(" ")}"
next
end
end
stripped_files << file
end
src_path = OS::Path.normalise( ENV['RS_CODEBRANCH'] )
# --- now copy or convert ---
stripped_files.each do |file|
filename = OS::Path.get_filename(file)
ext = OS::Path::get_extension(filename)
is_copy, is_convert, is_delete = false, false, false
if (File.exists?(file))
is_copy = COPY_EXTENSIONS.include?(ext)
is_convert = CONVERT_EXTENSIONS.include?(ext)
else
is_delete = true
end
# --- compute subfolder in tmp dir and make it---
sub_folder = OS::Path.normalise(OS::Path.get_directory(file))
if (sub_folder.sub!(src_path,"") == nil)
report["skipped"] += 1
info "\tSkipping #{file} as #{src_path} is not in #{sub_folder}"
next
end
dst_folder = OS::Path.combine(dst_folder_root,sub_folder)
if not File.exists?(dst_folder)
info "\tMaking directory #{dst_folder}"
FileUtils::mkdir_p(dst_folder)
end
dst = OS::Path.combine(dst_folder,filename)
dst = OS::Path.replace_ext(dst,PAR_CODE_GEN_EXTENSION)
# --- choose to copy, convert or skip. ---
if ( is_copy )
report["copied"] += 1
info"\tCopying #{file} to #{dst}"
FileUtils::cp(file, dst)
# --- did a file get copied ok? ---
if File.exist?(dst)
report["copied_ok"] += 1
# --- register as a file that was processed --
processed_files << { "filename" => filename, "tmp_filename" => dst }
else
report["not_copied_ok"] += 1
end
elsif ( is_convert )
report["converted"] += 1
# --- build cmd ---
info"\tConverting #{file}"
cmd = OS::Path::normalise( env.subst("#{PAR_CODE_GEN_COMMAND} --rebuild --savemetadata #{dst_folder} #{file}"))
info("\t#{cmd}")
if (@pre_convert_script)
# -- execute pre convert script cmd ---
cmd = OS::Path::normalise(env.subst("#{PRE_CONVERT_SCRIPT_COMMAND} #{file}"))
info"\t----------------> PRE CONVERT #{cmd}"
status, stdout, stderr = systemu(cmd)
# --- interpret output ---
error "\n\n\n#{stderr.length} ERRORS FOUND!\n\n\n" if (stderr.length > 0 || status != 0)
stderr.each { |err| error "#{err}"; error_count += 1 }
stdout.each do |out|
info("\t\t#{cmd} : #{out}")
end
else
info"\t----------------> NO PRE CONVERT #{cmd}"
end
# -- execute convert cmd ---
status, stdout, stderr = systemu(cmd)
generated_file = true
# --- interpret output ---
error "\n\n\n#{stderr.length} ERRORS FOUND!\n\n\n" if (stderr.length > 0 || status != 0)
stderr.each { |err| error "#{err}"; error_count += 1 }
stdout.each do |out|
info("\t\t#{cmd} : #{out}")
if out.include?(PAR_CODE_GEN_NO_XML_TOKEN)
generated_file = false
end
end
info("\t\treturned #{status}")
if (status != 0)
error "A parcodegen error occured"
error "Parcodegen didn't write the error to stderr" if stderr.length==0
error_count += 1
elsif (status==0 && stderr.length>0)
error "A parcodegen error occured : Parcodegen didn't return a non zero return code though"
end
# --- did a file get created? ---
if generated_file and File.exist?(dst)
report["converted_ok"] += 1
# --- register as a file that was processed --
processed_files << { "filename" => filename, "tmp_filename" => dst }
info_black("\t\tCreated #{dst}")
else
info("\t\tDest file not created or invalid #{dst}")
end
elsif is_delete
report["deleted"] += 1
processed_files << { "filename" => filename, "tmp_filename" => dst }
end
end
info ""
info "\tProcess completed. #{Time.now}"
info ""
report.sort.each { |key,val| info_black "\t*** Report: #{key} #{val}" }
# if any files have not been copied this may indicate a disk fault - out of diskspace etc
# we signal to abort - no files should be checked in.
abort = false
if (report["not_copied_ok"] > 0)
abort = true
error "A file did not get copied ok"
error_count += 1
end
rescue Exception => ex
error "Unexpected exception converting or copying files: "
error "\t#{ex.message}"
ex.backtrace.each { |m| error "\t#{m}"; }
Process.exit -1
end
return processed_files, error_count, abort
end
#------------------------------------------------------------------------------------------
# Build a changelist of the final destination files to submit.
#------------------------------------------------------------------------------------------
def build_changelist(env, processed_files, dst_folder, error_count )
info "\t\t========build_changelist============"
# --- create a changelist ---
change_id = nil
@p4.connect()
raise Exception if not @p4.connected?
change_id = @p4.create_changelist( comment() )
raise Exception if change_id.nil?
# --- sync on the dst folder to prevent possible sync/resolve issues ---
@p4.run_sync( "#{dst_folder}/*.#{PAR_CODE_GEN_EXTENSION}" )
# --- For each determine the equivalent filename and add it to the CL, but if it doesn't exist delete it ---
# * this means that if we change the RULES of the equivalent mapping there would be work in perforce to do by hand.
# i.e. a stale file could remain in p4.
info "\t\t========build_changelist : #{processed_files.length} processed_files"
processed_files.each do |processed_file|
tmp_filename = processed_file["tmp_filename"]
dest_filename = get_dest_filename(env, tmp_filename, dst_folder)
if (File.exist?(tmp_filename) )
info "\t\tProcessed file exists #{tmp_filename}"
add_file( tmp_filename, dest_filename, change_id)
else
info "\t\tProcessed file missing #{tmp_filename} if it exists in p4 then it needs deleted."
delete_file( tmp_filename, dest_filename, change_id)
end
end
change_id
end
#------------------------------------------------------------------------------------------
# comment for changelist
#------------------------------------------------------------------------------------------
def comment()
"Automatically generated by #{__FILE__} v#{PAR_CODE_GEN_CI_VERSION}\n"
end
#------------------------------------------------------------------------------------------
# delete a file in p4 and add into CL
#------------------------------------------------------------------------------------------
def delete_file( tmp_filename, filename, change_id)
info "Filename #{filename} deleted in CL"
@p4.run_revert( filename )
@p4.run_sync( filename )
@p4.run_delete( '-c', change_id.to_s, filename )
end
#------------------------------------------------------------------------------------------
# add a file to the CL
#------------------------------------------------------------------------------------------
def add_file( tmp_filename, dest_filename, change_id)
# -- create the dir so we can copy the file here if required --
dst_dir = OS::Path.get_directory(dest_filename)
FileUtils::mkdir_p(dst_dir) if (not File.exists?(dst_dir) )
fstat = @p4.run_fstat( dest_filename ).shift
# --- edit or add file to CL ---
if (fstat.nil? or (fstat['headAction'] and fstat['headAction'].include?("delete")))
info "\t\t\tCopy #{tmp_filename} to #{dest_filename}"
FileUtils.rm(dest_filename, :force => true) if File.exists?(dest_filename)
FileUtils::cp(tmp_filename, dest_filename)
info "\t\t\t#{dest_filename} added to CL"
@p4.run_add( '-c', change_id.to_s, dest_filename )
else
info "\t\t\tDest_filename #{dest_filename} edited in CL"
@p4.run_revert( dest_filename )
@p4.run_sync( dest_filename )
@p4.run_edit( '-c', change_id.to_s, dest_filename )
info "\t\t\tCopy #{tmp_filename} to #{dest_filename}"
FileUtils.rm(dest_filename, :force => true) if File.exists?(dest_filename)
FileUtils.cp(tmp_filename, dest_filename)
end
end
#------------------------------------------------------------------------------------------
# submit the changelist
#------------------------------------------------------------------------------------------
def submit_changelist( change_id, enable_checkin, enable_revert_unchanged, error_count )
files_pre_revert = @p4.run_opened( '-c', change_id.to_s )
info "\t#{files_pre_revert.length} files before revert"
if (enable_revert_unchanged)
info "\tReverting unchanged files #{change_id}"
@p4.run_revert( '-a', '-c', change_id.to_s, '//...')
end
files = @p4.run_opened( '-c', change_id.to_s )
raise Exception if files.nil?
num_reverted = files_pre_revert.length - files.length
info "\t#{num_reverted} files reverted"
info_black "\tThere are #{files.size} files to submit."
files.each { |file| info_black "\t\t#{file['depotFile']}" }
if ( enable_checkin )
if ( files.size > 0 )
info_black "\tSubmitting file currently in #{change_id}"
submit_result = @p4.run_submit( '-c', change_id.to_s )
elsif ( 0 == files.size )
info "\tDeleting #{change_id} no files changed."
@p4.run_change('-d', change_id.to_s)
end
else
info "\tCheckin is disabled the CL #{change_id} is pending."
end
end
#------------------------------------------------------------------------------------------
# get the destination filename - based on the name of the file
# some criteria is followed to compute an appropriate destination file destination.
# *** IF THIS MAPPING CHANGES - A CLEAN UP OF PERFORCE METADATA FILES MAY BE REQUIRED! ***
#------------------------------------------------------------------------------------------
def get_dest_filename(env, tmp_filename, dst_folder )
filename = OS::Path.get_filename(tmp_filename)
sub_folder = OS::Path.normalise(OS::Path.get_directory(tmp_filename))
tmp_path = OS::Path.normalise( env.subst(TMP_FOLDER))
sub_folder.sub!(tmp_path,"")
ret = OS::Path::normalise( env.subst(OS::Path.combine(dst_folder, sub_folder, filename)))
info "\t#{filename} => #{ret}"
ret
end
end # class parcodegen_ci
#-----------------------------------------------------------------------------
# Application entry point
#-----------------------------------------------------------------------------
if __FILE__ == $0
info "======================================================================================================="
info_green "Running #{__FILE__} #{ARGV.join(" ")}"
info_green "https://devstar.rockstargames.com/wiki/index.php/Par_Code_Gen_Continuous_Integration"
error_count = 0
warning_count = 0
begin
g_AppName = File::basename( __FILE__, '.rb' )
g_ProjectName = ''
g_BranchName = ''
g_Project = nil
g_Config = Pipeline::Config.instance()
#---------------------------------------------------------------------
# Parse Command Line.
#---------------------------------------------------------------------
opts, trailing = OS::Getopt.getopts( OPTIONS )
if ( opts['help'] )
puts OS::Getopt.usage( OPTIONS )
puts ("Press Enter to continue...")
$stdin.getc( )
Process.exit! 1
end
if ( ( 0 == trailing.size ) ) then
error 'No trailing file arguments specified. Exiting.'
puts OS::Getopt.usage( OPTIONS, { 'files' => TRAILING_DESC } )
Process.exit( 2 )
end
g_ProjectName = ( nil == opts['project'] ) ? '' : opts['project']
project_exists = ( g_Config.projects.has_key?( g_ProjectName ) )
if ( not project_exists ) then
puts OS::Getopt.usage( OPTIONS )
error "#{g_ProjectName} does not exist or its configuration is unreadable."
Process.exit! 3
end
g_Project = g_Config.projects[ g_ProjectName ]
g_Project.load_config( )
if ( not g_Project.enabled ) then
error "#{g_ProjectName} is not enabled on this machine. Re-run installer."
Process.exit! 4
end
g_BranchName = ( nil == opts['branch'] ) ? g_Project.default_branch : opts['branch']
if ( not g_Project.branches.has_key?( g_BranchName ) ) then
error "#{g_ProjectName} does not have branch #{g_BranchName} defined."
Process.exit! 5
end
g_Branch = g_Project.branches[ g_BranchName ]
g_DestFolder = ( nil == opts['dest_folder'] ) ? nil : opts['dest_folder']
if ( g_DestFolder.nil? ) then
error "no dest folder."
Process.exit! 6
end
g_enable_checkin = ( nil == opts['disablecheckin'] ) ? true : false
g_enable_revert_unchanged = ( nil == opts['disable_revert_unchanged'] ) ? true : false
g_pre_convert_script = ( nil == opts['pre_convert_script'] ) ? false : true
# --- build environment ---
env = Environment.new()
g_Branch.fill_env( env ) unless ( g_Branch.nil? )
g_Project.fill_env( env ) if ( g_Branch.nil? )
info "g_pre_convert_script is #{g_pre_convert_script}"
parcodegen_ci = ParCodeGenCi.new(g_pre_convert_script)
#-------------------------------------------------------------------------
# Parse the modifications xml file - get a list of LOCAL files to process
# - some may not exist on the client
# - this is ok and needs handled.
# - also recurse for wildcards in filesystem
#-------------------------------------------------------------------------
files = []
trailing.each do |wildcard|
if (wildcard.downcase.include?(".xml"))
info ""
info_blue "*** Parsing modifications #{wildcard} \t#{Time.now}"
files += parcodegen_ci.parse_cc_modifications( wildcard, g_Project, g_BranchName )
info_blue "*** Finished parsing #{files.length} modifications \t#{Time.now}"
else
info ""
info_blue "*** Parsing wildcard #{wildcard}\t#{Time.now}"
files_found = OS::FindEx::find_files_recurse( wildcard )
info "\tFiles wildcard: #{wildcard}, #{files_found.size} files found."
files_found.each do |filename|
info "\t\t#{filename}\n"
files << filename
end
info_blue "*** Finished parsing wildcard \t#{Time.now}"
end
end
if ( files.length > 0 )
#---------------------------------------------------------------------
# Process files
#---------------------------------------------------------------------
info ""
info_blue "*** Processing #{files.length} files \t#{Time.now}"
copy_files = []
convert_files = []
files.each do |filename|
ext = OS::Path::get_extension(filename)
copy_files << filename if COPY_EXTENSIONS.include?(ext)
convert_files << filename if CONVERT_EXTENSIONS.include?(ext)
end
copy_files.each do |cf|
error "#{cf} is considered for copy AND convert, it cannot be BOTH" if (convert_files.include? cf)
end
#
# DW: convert needs to happen before copy otherwise it may delete the copied file when it converts ( pcg quirk )
#
# --- blat tmp folder and ensure it exists. ---
dst_folder_root = OS::Path::normalise( env.subst(TMP_FOLDER) )
FileUtils.rm_rf(dst_folder_root) if (File.exists?(dst_folder_root) )
FileUtils::mkdir_p(dst_folder_root)
if not File.exists?dst_folder_root
error_count += 1
error "#{dst_folder_root} doesn't exist"
else
info_blue "*** Started converting #{convert_files.length} files \t#{Time.now}"
processed_convert_files, error_count, abort = parcodegen_ci.process_files( env, convert_files, error_count, dst_folder_root )
info_blue "*** Finished converting #{processed_convert_files.length} files \t#{Time.now}"
info_blue "*** Started copying #{copy_files.length} files \t#{Time.now}"
processed_copy_files, error_count, abort = parcodegen_ci.process_files( env, copy_files, error_count, dst_folder_root ) if (!abort)
info_blue "*** Finished copy #{processed_copy_files.length} files \t#{Time.now}"
end
processed_files = processed_convert_files + processed_copy_files
processed_files = processed_files.uniq
info_blue "*** Total #{processed_files.length} unique files \t#{Time.now}"
if (abort==false)
#---------------------------------------------------------------------
# Build Changelist
#---------------------------------------------------------------------
info ""
info_blue "*** Build CL files \t#{Time.now}"
changelist = parcodegen_ci.build_changelist(env, processed_files, g_DestFolder, error_count )
info_blue "*** Build CL complete.\t#{Time.now}"
#---------------------------------------------------------------------
# Submit Changelist
#---------------------------------------------------------------------
info ""
info_blue "*** Submit CL \t#{Time.now}"
parcodegen_ci.submit_changelist( changelist, g_enable_checkin, g_enable_revert_unchanged, error_count )
info_blue "*** Submit CL complete.\t#{Time.now}"
info ""
end
else
info_blue " Warning: no files where processed"
end
rescue Exception => ex
error "Unhandled exception: #{ex.message}"
error "Backtrace:"
ex.backtrace.each { |m| error "\t#{m}" }
Process.exit -1
end
ret_code = ( error_count > 0 ) ? -1 : 0
info_blue "*** Script exiting with #{ret_code} since error_count is #{error_count} \t#{Time.now}"
info "======================================================================================================="
info ""
info ""
info ""
Process.exit! ret_code
end #if __FILE__ == $0