502 lines
17 KiB
Ruby
Executable File
502 lines
17 KiB
Ruby
Executable File
#
|
|
# File:: %RS_TOOLSLIB%/pipeline/config/project.rb
|
|
# Description:: Project config.xml wrapper
|
|
#
|
|
# Author:: David Muir <david.muir@rockstarnorth.com>
|
|
# Author:: Greg Smith <greg@rockstarnorth.com>
|
|
# Date:: June 2008
|
|
#
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Uses
|
|
#-----------------------------------------------------------------------------
|
|
require 'pipeline/config/branch'
|
|
require 'pipeline/config/globals'
|
|
require 'pipeline/config/targets'
|
|
require 'pipeline/content/parser'
|
|
require 'pipeline/os/path'
|
|
require 'pipeline/util/environment'
|
|
require 'pipeline/util/float'
|
|
require 'pipeline/util/time'
|
|
include Pipeline
|
|
|
|
require 'rexml/document'
|
|
include REXML
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Implementation
|
|
#-----------------------------------------------------------------------------
|
|
module Pipeline
|
|
|
|
#
|
|
# == Description
|
|
#
|
|
# The Project class abstracts a Project project.xml file, providing read-only
|
|
# access to the XML's attributes including the available project targets and
|
|
# their platform directory through the ProjectTarget class.
|
|
#
|
|
# == Example Usage
|
|
#
|
|
# p = Config.instance.project["gta4"]
|
|
# puts "Project name: #{p.name}"
|
|
# puts "Project root: #{p.root}"
|
|
# puts "Project targetdir: #{p.targetdir}"
|
|
# puts "Number of branches: #{p.branches.size}"
|
|
# p.branches.each_pair do |name, branch|
|
|
# puts "Branch: #{name}"
|
|
# puts "Number of targets: #{branch.targets.size}"
|
|
# puts "Targets:"
|
|
# branch.targets.each do |platform, target|
|
|
# puts "Target: #{target.platform} #{target.target}"
|
|
# end
|
|
# end
|
|
#
|
|
class Project
|
|
|
|
#---------------------------------------------------------------------
|
|
# Attributes
|
|
#---------------------------------------------------------------------
|
|
|
|
attr_reader :name # Project name
|
|
|
|
# PATHS
|
|
attr_reader :sc_proj # Project source control path
|
|
attr_reader :root # Project root path
|
|
attr_reader :local # Project local export path
|
|
attr_reader :cache # Project cache path
|
|
attr_reader :netstream # Stream Directory
|
|
attr_reader :netgenstream # Generic stream direcotry (xrefs etc)
|
|
|
|
# PROJECT FLAGS
|
|
attr_reader :is_episodic # Is the project episodic
|
|
attr_reader :has_levels # Is the project levelled e.g. Jimmy
|
|
|
|
# BRANCHES
|
|
attr_reader :branches # Project branches
|
|
attr_reader :default_branch # Default branch (string key)
|
|
attr_reader :config # Reference to global config.
|
|
attr_reader :labels #Project labels
|
|
|
|
# CONFIG LOADED STATUS
|
|
attr_reader :loaded_config # Project config loaded status
|
|
attr_reader :loaded_content # Project content loaded status
|
|
|
|
# CONTENT
|
|
attr_reader :content # Project content tree root
|
|
|
|
# GLOBAL / LOCAL CONFIGURATION
|
|
attr_accessor :uiname # Project UI-friendly name
|
|
attr_accessor :root # Project root path
|
|
|
|
# DEFAULT BRANCH
|
|
attr_reader :audio # Branch audio data root path
|
|
attr_reader :build # Branch build data root path
|
|
attr_reader :export # Branch export data path
|
|
attr_reader :processed # Branch processed data path
|
|
attr_reader :metadata # Branch metadata data path
|
|
attr_reader :independent # Branch independent data path (OBSOLETE)
|
|
attr_reader :common # Branch common data path
|
|
attr_reader :shaders # Branch shaders path
|
|
attr_reader :code # Branch code path
|
|
attr_reader :ragecode # Branch RAGE code path
|
|
attr_reader :script # Branch script code path
|
|
attr_reader :art # Branch art path
|
|
attr_reader :assets # Branch assets path
|
|
attr_reader :ind_target # Branch independent target object
|
|
attr_reader :targets # Branch targets Array
|
|
attr_reader :builders # Branch builders Hash
|
|
attr_reader :preview # Branch preview directory
|
|
attr_reader :codeconfigs # Code configurations
|
|
|
|
#---------------------------------------------------------------------
|
|
# Utility Classes and Modules
|
|
#---------------------------------------------------------------------
|
|
|
|
#
|
|
# == Description
|
|
# Class wrapping a single content XML file to be loaded.
|
|
#
|
|
class ContentFile
|
|
attr_reader :name, :filename
|
|
|
|
def initialize( name, filename )
|
|
@name = name
|
|
@filename = filename
|
|
end
|
|
end
|
|
#
|
|
# == Description
|
|
# Project XMLParseError exception class.
|
|
#
|
|
class XMLParseError < Exception; end
|
|
|
|
#---------------------------------------------------------------------
|
|
# Public Methods
|
|
#---------------------------------------------------------------------
|
|
|
|
def initialize( name, uiname, root, config, sc_proj )
|
|
@name = name
|
|
@uiname = uiname
|
|
Pipeline::Globals.instance().in_env do |e|
|
|
@root = e.subst( root )
|
|
@config = e.subst( config )
|
|
@sc_proj = e.subst( sc_proj )
|
|
end
|
|
reset()
|
|
end
|
|
|
|
#
|
|
# Returns the source control object, making sure it's initialised for
|
|
# this project.
|
|
#
|
|
def scm
|
|
|
|
Config::instance().scm()
|
|
end
|
|
|
|
#
|
|
# Returns the source control object, making sure it's initialised for
|
|
# this project.
|
|
#
|
|
def scm_connected
|
|
|
|
Config::instance().scm_connected()
|
|
end
|
|
|
|
|
|
#
|
|
# Compare this project against another, just uses the name currently
|
|
#
|
|
def <=>(otherEntry)
|
|
|
|
( @name <=> otherEntry.name )
|
|
end
|
|
|
|
#
|
|
# Unload all information for this project
|
|
#
|
|
def reset( )
|
|
@loaded_config = false
|
|
@loaded_content = false
|
|
@targets = {}
|
|
@branches = {}
|
|
@default_branch = nil
|
|
@content = nil
|
|
@contentfiles = []
|
|
@labels = {}
|
|
end
|
|
|
|
#
|
|
# Return simple string representation of this object.
|
|
#
|
|
def to_s( )
|
|
"#{@name}: #{@uiname}"
|
|
end
|
|
|
|
#
|
|
#
|
|
#
|
|
def to_local_xml( )
|
|
|
|
root = Element.new( 'local' )
|
|
branches_elem = root.add_element( 'branches' )
|
|
branches_elem.add_attribute( 'default', @default_branch )
|
|
@branches.each_pair do |name, branch|
|
|
|
|
branches_elem.add_element( branch.to_local_xml() )
|
|
end
|
|
root
|
|
end
|
|
|
|
#
|
|
# Fill an environment object from this project's state using the
|
|
# default branch aswell.
|
|
#
|
|
def fill_env( env, include_default_branch_env = true )
|
|
# Global configuration environment
|
|
Pipeline::Globals.instance().fill_env( env )
|
|
|
|
# Import Config instance variables that are of class String.
|
|
c = Pipeline::Config::instance()
|
|
c.instance_variables.each do |var|
|
|
next unless ( c.instance_variable_get( var ).is_a?( String ) )
|
|
|
|
sym = var.sub( '@', '' )
|
|
begin
|
|
env.lookup( sym )
|
|
rescue EnvironmentException
|
|
# Import into our environment.
|
|
val = c.instance_variable_get( var )
|
|
env.add( sym, val.to_s )
|
|
end
|
|
end
|
|
|
|
# Project environment
|
|
env.add( 'uiname', env.subst( @uiname ) )
|
|
env.add( 'name', env.subst( @name ) )
|
|
env.add( 'root', env.subst( @root ) )
|
|
env.add( 'content', env.subst( '$(toolsconfig)/content' ) )
|
|
env.add( 'cache', env.subst( @cache ) )
|
|
|
|
# Project default branch environment
|
|
if ( include_default_branch_env and ( not @default_branch.nil? ) ) then
|
|
branch = @branches[@default_branch]
|
|
branch.fill_env( env ) unless ( branch.nil? )
|
|
end
|
|
end
|
|
|
|
#
|
|
# Run a code block setting up an environment from this object.
|
|
#
|
|
def in_env( &block )
|
|
env = Environment.new()
|
|
fill_env( env )
|
|
yield( env ) if ( block_given? )
|
|
end
|
|
|
|
#
|
|
# Load in configuration information for this project.
|
|
#
|
|
def load_config( force = false )
|
|
return true if ( ( @loaded_config ) and ( not force ) )
|
|
|
|
reset( )
|
|
env = Environment.new()
|
|
env.add( 'root', @root )
|
|
env.add( 'filepath', OS::Path.remove_filename(@config) )
|
|
@content = Content::Group.new( 'root' )
|
|
|
|
# Load project global configuration file
|
|
begin
|
|
File.open( @config ) do |file|
|
|
doc = Document.new( file )
|
|
@loaded_config = load_from( doc, env, 'project' )
|
|
end
|
|
|
|
rescue Exception => ex
|
|
Project::log().exception( ex, "Unhandled exception loading project config" )
|
|
@loaded_config = false
|
|
end
|
|
|
|
# Load project local configuration file
|
|
@localconfig = OS::Path.combine( OS::Path.remove_filename(@config), DEFAULT_LOCAL_CONFIG )
|
|
return ( @loaded_config ) unless ( File::exists?( @localconfig ) )
|
|
|
|
begin
|
|
File.open( @localconfig ) do |file|
|
|
doc = Document.new( file )
|
|
@loaded_config = load_from( doc, env, 'local' )
|
|
end
|
|
rescue Exception => ex
|
|
Project::log().exception( ex, "Unhandled exception loading project local config" )
|
|
end
|
|
|
|
@loaded_config
|
|
end
|
|
|
|
|
|
#
|
|
# Load project's content tree.
|
|
#
|
|
def load_content( branch = nil, force = false, filter = nil )
|
|
|
|
return ( true ) if ( ( @loaded_content ) and ( not force ) )
|
|
throw Exception.new( "can't load content, project not enabled" ) \
|
|
unless ( load_config() )
|
|
throw Exception.new( "Invalid branch specified: #{branch}." ) \
|
|
unless ( branch.nil? or @branches.has_key?( branch ) )
|
|
|
|
branch = @default_branch if ( branch.nil? )
|
|
cp = Content::Parser.new( self, branch )
|
|
|
|
# Leave existing functionality for the default type (now ALL not DEFAULT)
|
|
# Pre-pass and store required content. Need to keep content order (output before platform)
|
|
contentfilelist = []
|
|
@content = Content::Group.new( 'root' )
|
|
@contentfiles.each do |c|
|
|
next unless ( File::exists?( c.filename ) )
|
|
|
|
elapsed_time = Util::time() do
|
|
newgroup = Content::Group.new( c.name )
|
|
newgroup.add_child( cp.parse_xml( c.filename, nil, filter ) )
|
|
@content.add_child( newgroup )
|
|
end
|
|
Project::log().info( "Loaded content: #{c.filename} [#{elapsed_time.to_s( 3 )}s]\n" )
|
|
end
|
|
|
|
@loaded_content = true
|
|
@loaded_content
|
|
end
|
|
|
|
#
|
|
# Save out any local project information.
|
|
#
|
|
def save_locals()
|
|
return unless ( @loaded_config )
|
|
|
|
File.open( @localconfig, 'w+' ) do |file|
|
|
|
|
outputXML = REXML::Document.new()
|
|
outputXML << XMLDecl.new()
|
|
outputXML << to_local_xml( )
|
|
|
|
fmt = REXML::Formatters::Pretty.new()
|
|
fmt.write( outputXML, file )
|
|
end
|
|
end
|
|
|
|
def default_branch=( branch_name )
|
|
|
|
@default_branch = branch_name
|
|
initialise_default_branch()
|
|
end
|
|
|
|
# Return Project class logging object.
|
|
def Project::log( )
|
|
@@log = Log.new( 'project' ) if @@log.nil?
|
|
@@log
|
|
end
|
|
|
|
#
|
|
# Return true iff the passed in filename is within this project's
|
|
# cache directory structure.
|
|
#
|
|
def is_cache_file?( filename )
|
|
( filename.starts_with( cache ) )
|
|
end
|
|
|
|
#---------------------------------------------------------------------
|
|
# Private Methods
|
|
#---------------------------------------------------------------------
|
|
private
|
|
@@log = nil
|
|
|
|
# Return the XML attribute value or the project member variable,
|
|
# XML overridding the project member variable.
|
|
def get_override_attr_or_member( xml_node, project, member )
|
|
return xml_node.attributes[member] unless xml_node.attributes[member].nil?
|
|
return project.send(member) if project.methods.include?( member )
|
|
throw ArgumentError.new( "#{member} not found in XML or project object." )
|
|
end
|
|
|
|
#
|
|
# Parse project data from an XML node, updating member data as
|
|
# required.
|
|
#
|
|
# This allows us to have local.xml overwrite anything in project.xml.
|
|
#
|
|
def load_from( xml_node, env, root = 'project' )
|
|
env.push( )
|
|
|
|
Pipeline::Globals.instance().fill_env( env )
|
|
|
|
env.add( 'root', @root )
|
|
@cache = env.subst( get_override_attr_or_member( xml_node.root, self, 'cache' ) )
|
|
env.add( 'cache', @cache )
|
|
|
|
# PROJECT FLAGS
|
|
@is_episodic = ( 'true' == get_override_attr_or_member( xml_node.root, self, 'is_episodic' ) ) ? true : false
|
|
@has_levels = ( 'true' == get_override_attr_or_member( xml_node.root, self, 'has_levels' ) ) ? true : false
|
|
|
|
# BRANCHES
|
|
xml_node.elements.each( "#{root}/branches" ) do |branches|
|
|
default_name = branches.attributes['default'] unless branches.attributes['default'].nil?
|
|
@default_branch = default_name unless default_name.nil?
|
|
end
|
|
xml_node.elements.each( "#{root}/branches/branch" ) do |branch|
|
|
branch_name = branch.attributes['name']
|
|
|
|
throw XMLParseError.new( 'Branch does not specify name. Fix project configuration #{root}.' ) \
|
|
if ( branch_name.nil? )
|
|
|
|
project_branch = nil
|
|
project_branch = @branches[branch_name] if ( @branches.has_key?( branch_name ) )
|
|
|
|
# Prevent loading config data for branches in local config that we don't
|
|
# have a definition for.
|
|
if ( 'local' == root and project_branch.nil? ) then
|
|
Project::log().warn( "Not loading local branch '#{branch_name}' as there is no definition for it." )
|
|
next
|
|
end
|
|
project_branch = Branch::from_xml( branch, env, self, project_branch )
|
|
@branches[branch_name] = project_branch
|
|
end
|
|
# Initialise default branch, setting our compatibility member data.
|
|
initialise_default_branch( )
|
|
|
|
# CONTENT FILES
|
|
xml_node.elements.each( "#{root}/content/content" ) do |content|
|
|
throw XMLParseError.new( 'Content files must be defined in a Group node.' ) \
|
|
unless ( content.attributes['type'] == 'group' )
|
|
throw XMLParseError.new( 'Content files must have a defined name.' ) \
|
|
if ( content.attributes['name'].nil? )
|
|
|
|
name = content.attributes['name']
|
|
filename = env.subst( content.children[1].attributes['href'] )
|
|
@contentfiles << ContentFile.new( name, filename )
|
|
end
|
|
|
|
xml_node.elements.each( "#{root}/labels/label" ) do |label|
|
|
|
|
label_type = label.attributes['type']
|
|
name = label.attributes['name']
|
|
|
|
@labels[label_type] = name
|
|
end
|
|
|
|
env.pop( )
|
|
true
|
|
end
|
|
|
|
#
|
|
# Initialise our member data to the default branch (see
|
|
# @default_branch).
|
|
#
|
|
def initialise_default_branch( )
|
|
return if ( @default_branch.nil? )
|
|
|
|
throw XMLParseError.new( "Invalid default branch, key '#{@default_branch}' not found." ) \
|
|
unless ( @branches.has_key?( @default_branch ) )
|
|
|
|
branch = @branches[@default_branch]
|
|
env = Environment.new()
|
|
fill_env( env )
|
|
env.push( )
|
|
branch.fill_env( env )
|
|
|
|
@audio = env.subst( branch.audio )
|
|
@build = env.subst( branch.build )
|
|
@export = env.subst( branch.export )
|
|
@processed = env.subst( branch.processed )
|
|
@metadata = env.subst( branch.metadata )
|
|
@independent = env.subst( branch.independent )
|
|
@common = env.subst( branch.common )
|
|
@shaders = env.subst( branch.shaders )
|
|
@code = env.subst( branch.code )
|
|
@ragecode = env.subst( branch.ragecode )
|
|
@script = env.subst( branch.script )
|
|
@art = env.subst( branch.art )
|
|
@assets = env.subst( branch.assets )
|
|
@preview = env.subst( branch.preview )
|
|
@codeconfigs = branch.codeconfigs
|
|
|
|
env.pop( )
|
|
|
|
# Init targets
|
|
@ind_target = branch.ind_target
|
|
@targets = branch.targets
|
|
|
|
@builders = branch.builders
|
|
end
|
|
|
|
#---------------------------------------------------------------------
|
|
# Private Constants
|
|
#---------------------------------------------------------------------
|
|
private
|
|
DEFAULT_LOCAL_CONFIG = "local.xml"
|
|
end
|
|
|
|
end # Pipeline module
|
|
|
|
# %RS_TOOLSLIB%/pipeline/config/project.rb
|