1091 lines
39 KiB
Ruby
Executable File
1091 lines
39 KiB
Ruby
Executable File
#
|
|
# File:: pipeline/config/projects.rb
|
|
# Description:: Global tools configuration classes
|
|
#
|
|
# Author:: David Muir <david.muir@rockstarnorth.com>
|
|
# Author:: Greg Smith <greg@rockstarnorth.com>
|
|
# Author:: Luke Openshaw <luke.openshaw@rockstarnorth.com>
|
|
#
|
|
# Date:: 3 December 2007
|
|
#
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Uses
|
|
#-----------------------------------------------------------------------------
|
|
require 'pipeline/config/project'
|
|
require 'pipeline/config/globals'
|
|
require 'pipeline/config/runlines'
|
|
require 'pipeline/config/user'
|
|
require 'pipeline/gui/messagebox'
|
|
require 'pipeline/os/path'
|
|
require 'pipeline/scm/perforce'
|
|
require 'pipeline/util/autodesk3dsmax'
|
|
require 'pipeline/util/autodeskMotionbuilder'
|
|
require 'pipeline/os/sysinfo'
|
|
|
|
require 'singleton'
|
|
require 'rexml/document'
|
|
require 'ping'
|
|
include REXML
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Implementation
|
|
#-----------------------------------------------------------------------------
|
|
module Pipeline
|
|
|
|
#
|
|
# == Description
|
|
# Platform abstraction class.
|
|
#
|
|
class Platform
|
|
attr_reader :name, :uiname
|
|
|
|
def initialize( name, uiname )
|
|
@name = name
|
|
@uiname = uiname
|
|
end
|
|
|
|
def <=>(otherEntry)
|
|
return (@name <=> otherEntry.name)
|
|
end
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Options per max version
|
|
#
|
|
class MaxVersionOptions
|
|
|
|
attr_reader :version, :check_for_install
|
|
|
|
def initialize( version, check_for_install )
|
|
|
|
@version = version
|
|
@check_for_install = check_for_install
|
|
end
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Overall options for all versions of max
|
|
#
|
|
class MaxOptions
|
|
|
|
attr_reader :version_options
|
|
attr_reader :toolset_mode
|
|
attr_reader :toolset_modes
|
|
|
|
attr_writer :toolset_mode
|
|
|
|
def initialize()
|
|
|
|
@toolset_modes = Array.new()
|
|
@toolset_modes << :current
|
|
@toolset_modes << :debug
|
|
@toolset_modes << :outsource
|
|
|
|
@version_options = Hash.new()
|
|
@toolset_mode = :current
|
|
end
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# The Config class is used to fetch information from the overall Pipeline
|
|
# configuration file. This class is a singleton to allow easy access
|
|
# throughout the pipeline scripts and is initialised automatically on
|
|
# first access. See Example Usage below.
|
|
#
|
|
# == Example Usage
|
|
#
|
|
# @p = Pipeline::Config.instance
|
|
# @p.projects.each_value do |proj|
|
|
#
|
|
# # Load project configs for those that exist
|
|
# proj.loadConfig() if File.exists?( OS::Path.combine( proj.root, "bin/config.xml" ) )
|
|
# next unless proj.loadedConfig
|
|
#
|
|
# puts "Loaded project: #{proj.uiname}."
|
|
#
|
|
# puts "Name: #{proj.uiname}"
|
|
# puts "Project root: #{proj.root}"
|
|
# puts "Number of targets: #{proj.targets.length}"
|
|
# puts "Targets:"
|
|
# proj.targets.each_value { |target| puts "Target: #{target.platform} #{target.directory}" }
|
|
# end
|
|
#
|
|
class Config
|
|
|
|
include Singleton
|
|
|
|
#---------------------------------------------------------------------
|
|
# Classes
|
|
#---------------------------------------------------------------------
|
|
|
|
#
|
|
# == Description
|
|
# Project XMLParseError exception class.
|
|
#
|
|
class XMLParseError < Exception; end
|
|
|
|
#---------------------------------------------------------------------
|
|
# Attributes
|
|
#---------------------------------------------------------------------
|
|
|
|
# Array of Project objects
|
|
attr_reader :project
|
|
attr_reader :projects
|
|
attr_reader :platforms
|
|
attr_reader :logmailtos
|
|
|
|
attr_reader :mailserver
|
|
attr_reader :mailport
|
|
attr_reader :maildomain
|
|
|
|
# Local user information
|
|
attr_accessor :user
|
|
attr_accessor :sc_enabled
|
|
attr_accessor :sc_server, :sc_workspace, :sc_username, :sc_replicaserver
|
|
attr_accessor :sc_rage_server, :sc_rage_workspace, :sc_rage_username
|
|
attr_accessor :sc_tools_server, :sc_tools_workspace, :sc_tools_username
|
|
|
|
# Log options
|
|
attr_accessor :logmailerrors, :logtostdout, :log_trace, :log_level
|
|
attr_accessor :log_generate_html
|
|
|
|
# Max options
|
|
attr_accessor :max_options
|
|
|
|
# Application Settings (really should abstract this, using IApplication interface or somesuch...)
|
|
attr_accessor :use_xge
|
|
|
|
# File installation
|
|
attr_reader :network_version # Version user should be at (from network file)
|
|
attr_reader :version # Global version on current machine (from <toolsroot>/config.xml)
|
|
attr_accessor :user_version # Local version on current machine (from <toolsroot>/local.xml)
|
|
attr_reader :install_lines # Array of RunLine objects to call during install
|
|
attr_reader :uninstall_lines # Array of RunLine objects to call during uninstall
|
|
|
|
attr_reader :sc
|
|
|
|
#---------------------------------------------------------------------
|
|
# Public Methods
|
|
#---------------------------------------------------------------------
|
|
|
|
# Define public methods for all Pipeline::Globals instance variables.
|
|
Pipeline::Globals::instance( ).instance_variables.each do |var|
|
|
# Add getter method to self.
|
|
sym = var.sub( '@', '')
|
|
define_method( sym ) {
|
|
self.instance_variable_get( var )
|
|
}
|
|
end
|
|
|
|
#
|
|
# Class constructor. Parses our global configuration and environment
|
|
# so we get an entry point to our project configuration. Individual
|
|
# project's configuration must be loaded manually.
|
|
#
|
|
def initialize( )
|
|
|
|
# Initialise environment
|
|
@environment = Environment.new( )
|
|
import_globals( )
|
|
|
|
# Initialise defaults (to be overwritten later by the XML parser)
|
|
@user = nil
|
|
|
|
#@max_version_options = Hash.new()
|
|
@max_options = MaxOptions.new()
|
|
|
|
@sc_enabled = true
|
|
@sc_server = ""
|
|
@sc_workspace = ""
|
|
@sc_username = ENV['USERNAME']
|
|
@sc_rage_server = ""
|
|
@sc_rage_username = ENV['USERNAME']
|
|
@sc_rage_workspace = ""
|
|
@sc_tools_server = ""
|
|
@sc_tools_username = ENV['USERNAME']
|
|
@sc_tools_workspace = ""
|
|
@version = 0
|
|
@network_version = 0
|
|
@user_version = 0
|
|
@use_xge = true
|
|
@install_time = Time.new
|
|
|
|
config = OS::Path.combine( @toolsroot, DEFAULT_CONFIG )
|
|
localconfig = OS::Path.combine( @toolsroot, DEFAULT_LOCAL_CONFIG )
|
|
reset()
|
|
|
|
# Parse main config XML file
|
|
begin
|
|
File.open( config ) do |file|
|
|
doc = Document.new( file )
|
|
load_from( doc, @environment, 'config' )
|
|
end
|
|
rescue Exception => ex
|
|
puts "Exception parsing main config XML file: #{ex.message}"
|
|
puts ex.backtrace.join( "\n" )
|
|
end
|
|
|
|
|
|
# Parse local config XML file (if it exists)
|
|
if ( ::File::exists?( localconfig ) ) then
|
|
begin
|
|
File.open( localconfig ) do |file|
|
|
doc = Document.new( file )
|
|
load_from( doc, @environment, 'local' )
|
|
end
|
|
rescue Exception => ex
|
|
puts "Exception parsing local config XML file: #{ex.message}"
|
|
puts ex.backtrace.join( "\n" )
|
|
end
|
|
end
|
|
|
|
# Parse configuration against to acquire the overriding proxy server.
|
|
# This serves as a workaround for studios using the forwarding replica with
|
|
# this version of the P4Ruby library. (url:bugstar:1149226)
|
|
begin
|
|
File.open( config ) do |file|
|
|
doc = Document.new( file )
|
|
|
|
doc.elements.each( "config/studios/studio" ) do |studio_line|
|
|
|
|
if ( @local_studio == studio_line.attributes['name'] ) then
|
|
if ( not studio_line.attributes['perforcereplica'].nil? ) then
|
|
@sc_replicaserver = studio_line.attributes['perforcereplica']
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => ex
|
|
puts "Exception parsing main config XML file: #{ex.message}"
|
|
puts ex.backtrace.join( "\n" )
|
|
end
|
|
|
|
if ( @sc_replicaserver != nil and @sc_replicaserver.empty? == false ) then
|
|
puts "Overriding main server with replica server #{@sc_replicaserver}. Forwarding replica workaround for P4Ruby library incompatibility.\n"
|
|
@sc_server = @sc_replicaserver
|
|
end
|
|
|
|
perforce_connect = true
|
|
|
|
# Validate that the local version of the framework is up-to date.
|
|
# Only attempt to do this when the framework has already been installed.
|
|
# If it has not been installed yet, the user/local version check will fail.
|
|
if ( not Globals::instance().skip_version_check and @sc_enabled)
|
|
if @sc_server != nil and @sc_server.empty? == false \
|
|
and @sc_workspace != nil and @sc_workspace.empty? == false \
|
|
and @sc_username != nil and @sc_username.empty? == false then
|
|
|
|
config_filename = "$(toolsroot)/" + DEFAULT_CONFIG
|
|
config_filename = envsubst(config_filename)
|
|
|
|
tokens = @sc_server.split(":")
|
|
check_server = tokens[0]
|
|
check_port = 1666
|
|
|
|
if tokens.count > 1 then
|
|
|
|
check_port = tokens[1].to_i
|
|
end
|
|
|
|
if Ping.pingecho(check_server,3,check_port) == true then
|
|
|
|
p4 = SCM::Perforce.new( )
|
|
p4.user = @sc_username
|
|
p4.client = @sc_workspace
|
|
p4.port = @sc_server
|
|
if ( p4.connect(true) == false )
|
|
puts "Unable to connect to Perforce to determine network configuration version status.\n"
|
|
else
|
|
|
|
fstat = p4.run_fstat( config_filename ).shift
|
|
network_config = p4.run("print", "-q", "#{config_filename}##{fstat['headRev']}")
|
|
|
|
# Parse main config XML file
|
|
begin
|
|
doc = Document.new( network_config.to_s )
|
|
load_from( doc, @environment, 'network' )
|
|
rescue Exception => ex
|
|
puts "Exception parsing main config XML file: #{ex.message}"
|
|
puts ex.backtrace.join( "\n" )
|
|
end
|
|
end
|
|
else
|
|
|
|
puts("*** tools version check: failed to connect to perforce to do at #{check_server} on port #{check_port} ***" )
|
|
perforce_connect = false
|
|
end
|
|
end
|
|
end
|
|
|
|
install_app = "#{OS::Path::combine( @toolsroot, 'install.bat' )}"
|
|
builder = @user.nil? ? false : ( @user.is_builder_client or @user.is_builder_server )
|
|
tools = @user.nil? ? false : @user.is_tools
|
|
|
|
# Validate we running in the right toolset for the configured
|
|
# project.
|
|
if ( not OS::Path::normalise( __FILE__.downcase ).starts_with( Globals::instance().toolsroot.downcase ) ) then
|
|
|
|
msg = "You are running a script from a different project's toolchain\n"
|
|
msg += "compared to the one you currently have installed.\n\n"
|
|
msg += "Please run the installer at #{install_app} or run the\n"
|
|
msg += "equivalent script under #{Globals::instance().toolsroot}.\n\n"
|
|
msg += "%RS_TOOLSROOT%: #{Globals::instance().toolsroot}\n"
|
|
msg += "__FILE__: #{OS::Path::normalise( __FILE__ )}"
|
|
GUI::MessageBox::critical( TITLE, msg )
|
|
abort( )
|
|
end
|
|
|
|
# Verify network and config versions; unless you are one of the
|
|
# build machines.
|
|
if (perforce_connect and ( ( not builder ) or ( tools ) ) ) then
|
|
if ( ( not defined? $no_network_check ) or $no_network_check.nil? or $no_network_check == false) then
|
|
|
|
if ( @network_version > @version ) then
|
|
msg = "An important update has been made to the tool chain.\n\n"
|
|
msg += "Fetch latest or labelled tools and run #{install_app}."
|
|
GUI::MessageBox::critical( TITLE, msg )
|
|
abort( )
|
|
end
|
|
end
|
|
|
|
# Verify local versions are up-to-date.
|
|
if ( ( not defined? $no_project_check ) or $no_project_check.nil? or $no_project_check == false) then
|
|
|
|
# Verify user and config versions.
|
|
if ( @user_version < @version ) then
|
|
msg = "An important update has been made to the tool chain.\n\n"
|
|
msg += "Fetch latest or labelled tools and run #{install_app}."
|
|
GUI::MessageBox::critical( TITLE, msg )
|
|
abort( )
|
|
end
|
|
end
|
|
end
|
|
|
|
@sc = nil
|
|
end
|
|
|
|
#
|
|
# Returns true if there is a local.xml for the pipeline
|
|
#
|
|
def local_config_exists?()
|
|
|
|
localconfig = OS::Path.combine( @toolsbin, DEFAULT_LOCAL_CONFIG )
|
|
( ::File.exists?( localconfig ) )
|
|
end
|
|
|
|
#
|
|
# Connect and return SCM::Perforce object for the configured server.
|
|
#
|
|
def scm()
|
|
# Already connected, then return.
|
|
return ( @sc ) unless ( @sc.nil? )
|
|
|
|
# Otherwise create a new connection.
|
|
@sc = SCM::Perforce.new( )
|
|
@sc.user = @sc_username
|
|
@sc.client = @sc_workspace
|
|
@sc.port = @sc_server
|
|
@sc.connect()
|
|
|
|
@sc
|
|
end
|
|
|
|
#
|
|
# Connect and return SCM::Perforce object for the configured rage server.
|
|
#
|
|
def ragescm()
|
|
# Already connected, then return.
|
|
return ( @ragesc ) unless ( @ragesc.nil? )
|
|
|
|
# Otherwise create a new connection.
|
|
@ragesc = SCM::Perforce.new( )
|
|
@ragesc.user = @sc_rage_username
|
|
@ragesc.client = @sc_rage_workspace
|
|
@ragesc.port = @sc_rage_server
|
|
@ragesc.connect()
|
|
|
|
@ragesc
|
|
end
|
|
|
|
#
|
|
# Connect and return SCM::Perforce object for the configured server.
|
|
# This is a 'connected' version. See SCM::Perforce_Connected
|
|
#
|
|
def scm_connected()
|
|
# Already connected, then return.
|
|
return ( @sc ) unless ( @sc.nil? )
|
|
|
|
# Otherwise create a new connection.
|
|
@sc = SCM::Perforce_Connected.new( )
|
|
@sc.user = @sc_username
|
|
@sc.client = @sc_workspace
|
|
@sc.port = @sc_server
|
|
@sc.connect()
|
|
|
|
@sc
|
|
end
|
|
|
|
#
|
|
# Pipeline temp folder
|
|
#
|
|
def temp()
|
|
OS::Path.combine( @toolsroot, "tmp" )
|
|
end
|
|
|
|
#
|
|
# Use our local project environment to substitute any environment
|
|
# variable values and return the resultant string.
|
|
#
|
|
def envsubst( string )
|
|
@environment.subst( string )
|
|
end
|
|
|
|
#
|
|
# Save local global configuration data XML.
|
|
#
|
|
def save_locals( )
|
|
|
|
localconfig = OS::Path.combine( @toolsroot, DEFAULT_LOCAL_CONFIG )
|
|
|
|
File.open( localconfig, "w+" ) do |file|
|
|
|
|
outputXML = REXML::Document.new()
|
|
root = outputXML.add_element("local")
|
|
root.attributes["version"] = @user_version.to_s
|
|
root.attributes["installtime"] = @install_time.strftime("%Y/%m/%d %H:%M:%S")
|
|
|
|
# USER
|
|
root.add_element( @user.to_local_xml() )
|
|
|
|
# SCM
|
|
scmNode = root.add_element('scm')
|
|
alienbrainNode = scmNode.add_element('build')
|
|
alienbrainNode.attributes['workspace'] = @sc_workspace
|
|
alienbrainNode.attributes['username'] = @sc_username
|
|
alienbrainNode.attributes['server'] = @sc_server
|
|
|
|
# RAGE SCM Server
|
|
if ( @user.is_programmer() or @user.is_tools() or
|
|
@user.is_builder_client() or @user.is_builder_server() ) then
|
|
|
|
rageNode = scmNode.add_element( 'rage' )
|
|
rageNode.attributes['workspace'] = @sc_rage_workspace
|
|
rageNode.attributes['username'] = @sc_rage_username
|
|
rageNode.attributes['server'] = @sc_rage_server
|
|
end
|
|
|
|
# TOOLS SCM Server
|
|
toolsNode = scmNode.add_element( 'tools' )
|
|
|
|
# There is no doubt a better way of determining the p4 server for tools when in tools development / tools release.
|
|
if (@toolsroot.include?("rage"))
|
|
toolsNode.attributes['workspace'] = @sc_rage_workspace
|
|
toolsNode.attributes['username'] = @sc_rage_username
|
|
toolsNode.attributes['server'] = @sc_rage_server
|
|
else
|
|
toolsNode.attributes['workspace'] = @sc_workspace
|
|
toolsNode.attributes['username'] = @sc_username
|
|
toolsNode.attributes['server'] = @sc_server
|
|
end
|
|
|
|
# PROJECTS
|
|
projectsNode = root.add_element("projects")
|
|
projects.each_value do |project|
|
|
|
|
projectNode = projectsNode.add_element("project")
|
|
projectNode.attributes["name"] = project.name
|
|
end
|
|
|
|
# LOGGING
|
|
loggingNode = root.add_element("logging")
|
|
loggingNode.attributes["stdout"] = @logtostdout.to_s
|
|
loggingNode.attributes["level"] = @log_level.to_s
|
|
loggingNode.attributes["trace"] = @log_trace.to_s
|
|
loggingNode.attributes["generate_html"] = @log_generate_html.to_s
|
|
logMailNode = loggingNode.add_element("logmails")
|
|
logMailNode.attributes["enabled"] = @logmailerrors.to_s
|
|
|
|
# APPLICATION
|
|
applicationsNode = root.add_element( "applications" )
|
|
xgeNode = applicationsNode.add_element( "app" )
|
|
xgeNode.attributes['name'] = 'xge'
|
|
xgeNode.attributes['enabled'] = @use_xge.to_s
|
|
|
|
# MAX
|
|
maxNode = applicationsNode.add_element( "app" )
|
|
maxNode.attributes['name'] = 'max'
|
|
maxNode.attributes['toolset_mode'] = @max_options.toolset_mode.to_s
|
|
|
|
# For tools and artists user we try and find their 3dsmax
|
|
# install location. If it exists then we
|
|
if ( Autodesk3dsmax::instance().is_installed?() ) then
|
|
|
|
save_locals_artist( root )
|
|
save_locals_3dsmax( )
|
|
end
|
|
|
|
# If Motionbuilder is installed.
|
|
autodeskMotionbuilder = AutodeskMotionbuilder::instance()
|
|
if ( autodeskMotionbuilder.is_installed?() ) then
|
|
moboNode = applicationsNode.add_element( "app" )
|
|
moboNode.attributes['name'] = 'motionbuilder'
|
|
autodeskMotionbuilder.installdirs.each_pair do |platform, dirs|
|
|
dirs.each_pair do |version, installdir|
|
|
instanceNode = moboNode.add_element( "instance" )
|
|
instanceNode.attributes['version'] = version
|
|
instanceNode.attributes['platform'] = platform
|
|
instanceNode.attributes['path'] = installdir
|
|
end
|
|
end
|
|
end
|
|
|
|
fmt = REXML::Formatters::Pretty.new()
|
|
fmt.write( outputXML, file )
|
|
end
|
|
|
|
projects.each_value do |project|
|
|
|
|
project.save_locals()
|
|
end
|
|
end
|
|
|
|
def can_install()
|
|
|
|
(( self.user.nil? or
|
|
self.user.username.nil? or self.user.username.empty? or
|
|
self.sc_server.nil? or self.sc_username.nil? or self.sc_workspace.nil? or
|
|
self.sc_server.empty? or self.sc_username.empty? or self.sc_workspace.empty? ) == false)
|
|
end
|
|
|
|
# Execute our simple install process; run the configured runlines
|
|
# and then save our local data to XML.
|
|
def do_install( reinstall = false )
|
|
autodesk3dsmax = Autodesk3dsmax::instance()
|
|
|
|
maxuser = autodesk3dsmax.is_installed?()
|
|
dirty = ( reinstall or @user_version < @version )
|
|
success = true
|
|
|
|
if ( reinstall ) then
|
|
uninstall_lines.each do |run_line|
|
|
success = false if run_line.run() == false
|
|
end
|
|
@user_version = 0
|
|
end
|
|
|
|
# Always execute our runlines. This was initially done to always
|
|
# copy across the 3dsmax plugins as we were continually having to
|
|
# fixup machines when artists got new c: drives or a new 3dsmax
|
|
# install.
|
|
install_lines.each do |run_line|
|
|
# Skip if it was a one of and we ain't dirty.
|
|
next if ( run_line.run_once and not dirty )
|
|
next if ( run_line.maxonly and not maxuser )
|
|
|
|
success = false if run_line.run() == false
|
|
end
|
|
|
|
if ( success ) then
|
|
@user_version = @version
|
|
save_locals( )
|
|
end
|
|
success
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# Copy the core max plugins that need to be in max's root directory
|
|
#
|
|
def copy_core_max_plugins( uninstall = false, force_max_run = false )
|
|
|
|
autodesk3dsmax = Autodesk3dsmax::instance()
|
|
|
|
if ( autodesk3dsmax.is_installed?() ) then
|
|
installed_dirs = autodesk3dsmax.installdirs( )
|
|
isX64 = ( :x64 == OS::WinSystemInfo::SysType( ) )
|
|
|
|
Autodesk3dsmax::log().info( "Processing installed Autodesk 3dsmax versions..." )
|
|
installed_dirs[:x64].each_pair do |key, value|
|
|
next if ( key.to_f < 11.0 ) # skip < 3dsmax 2009
|
|
setup_3dsmax_version( key.to_f, value, true, uninstall, force_max_run )
|
|
end
|
|
installed_dirs[:x86].each_pair do |key, value|
|
|
next if ( key.to_f < 11.0 ) # skip < 3dsmax 2009
|
|
setup_3dsmax_version( key.to_f, value, false, uninstall, force_max_run )
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
#--------------------------------------------------------------------
|
|
# Private Constants
|
|
#--------------------------------------------------------------------
|
|
private
|
|
TITLE = 'Rockstar Games Tools Framework'
|
|
DEFAULT_CONFIG = "config.xml"
|
|
DEFAULT_LOCAL_CONFIG = "local.xml"
|
|
|
|
#--------------------------------------------------------------------
|
|
# Private Methods
|
|
#--------------------------------------------------------------------
|
|
private
|
|
# Function to import all definitions in the Globals class into this
|
|
# Config class, and its Environment table. This saves us having to
|
|
# maintain this class for additional Global attributes.
|
|
def import_globals( )
|
|
g = Pipeline::Globals::instance( )
|
|
g.instance_variables.each do |var|
|
|
sym = var.sub( '@', '')
|
|
val = g.instance_variable_get( var )
|
|
self.instance_variable_set( var, val )
|
|
|
|
# Import into our environment.
|
|
@environment.add( sym, val.to_s )
|
|
end
|
|
end
|
|
|
|
# Return the XML attribute value or the project member variable,
|
|
# XML overridding the project member variable.
|
|
def get_override_attr_or_member( xml_node, config, member )
|
|
return xml_node.attributes[member] unless xml_node.attributes[member].nil?
|
|
return config.send(member) if config.methods.include?( member )
|
|
throw ArgumentError.new( "#{member} not found in XML or project object." )
|
|
end
|
|
|
|
public
|
|
#
|
|
# Parse project data from an XML node, updating member data as
|
|
# required.
|
|
#
|
|
# This allows us to have local.xml overwrite anything in config.xml.
|
|
#
|
|
def load_from( xml_node, env, root = 'config' )
|
|
|
|
# Parse our version numbers and network configuration file
|
|
# depending on what type of configuration file we are parsing.
|
|
if ( 'config' == root ) then
|
|
@version = xml_node.root.attributes['version'].to_i \
|
|
unless ( xml_node.root.attributes['version'].nil? )
|
|
elsif ( 'network' == root ) then
|
|
@network_version = xml_node.root.attributes['version'].to_i \
|
|
unless ( xml_node.root.attributes['version'].nil? )
|
|
elsif ( 'local' == root ) then
|
|
@user_version = xml_node.root.attributes['version'].to_i \
|
|
unless ( xml_node.root.attributes['version'].nil? )
|
|
end
|
|
|
|
#-----------------------------------------------------------------
|
|
# GLOBAL-SPECIFIC CONFIGURATION
|
|
#-----------------------------------------------------------------
|
|
if ( 'config' == root ) then
|
|
|
|
# USERTYPES CONFIGURATION
|
|
pseudoUserMask = 0
|
|
xml_node.elements.each( "#{root}/usertypes/usertype" ) do |usertype|
|
|
|
|
if("true" == usertype.attributes['pseudo'])
|
|
pseudoUserMask |= usertype.attributes['flags'].hex
|
|
end
|
|
|
|
end
|
|
#invert
|
|
pseudoUserMask = pseudoUserMask ^ 0xFFFFFFFF
|
|
User::set_pseudoUserMask(pseudoUserMask)
|
|
|
|
xml_node.elements.each( "#{root}/usertypes/usertype" ) do |usertype|
|
|
|
|
name = usertype.attributes['name']
|
|
uiname = usertype.attributes['uiname']
|
|
flags = usertype.attributes['flags'].hex
|
|
pseudo = usertype.attributes['pseudo']
|
|
|
|
User::add_usertype( name, Usertype.new( name, uiname, flags, pseudo ) )
|
|
end
|
|
|
|
# PLATFORM CONFIGURATION
|
|
xml_node.elements.each( "#{root}/platforms/platform" ) do |platform|
|
|
name = platform.attributes['name']
|
|
uiname = platform.attributes['uiname']
|
|
|
|
if ( @platforms.has_key?( name ) ) then
|
|
|
|
# Don't permit updating platforms in local.xml.
|
|
else
|
|
@platforms[name] = Platform.new( name, uiname )
|
|
end
|
|
end
|
|
|
|
# MAX INSTALL CONFIGURATION
|
|
xml_node.elements.each( "#{root}/install/maxinstalls/max" ) do |runline|
|
|
|
|
check = true
|
|
next if runline.attributes['version'].nil?
|
|
|
|
version = runline.attributes['version'].to_f
|
|
check = false if ((not runline.attributes['check'].nil?) and (runline.attributes['check'].downcase == "false"))
|
|
|
|
@max_options.version_options[version] = MaxVersionOptions.new(version,check)
|
|
end
|
|
|
|
# RUNLINES CONFIGURATION
|
|
xml_node.elements.each( "#{root}/install/runlines/runline" ) do |runline|
|
|
|
|
if ( not runline.attributes['cmd'].nil? ) then
|
|
@install_lines << RunLineCmd::from_xml( runline, @environment )
|
|
elsif ( not runline.attributes['scriptcmd'].nil? ) then
|
|
@install_lines << RunLineScript::from_xml( runline, @environment )
|
|
else
|
|
RunLine::log().error( "Unrecognised runline XML: #{runline.to_s}." )
|
|
end
|
|
end
|
|
xml_node.elements.each( 'uninstall/runlines/runline' ) do |runline|
|
|
|
|
if ( not runline.attributes['cmd'].nil? ) then
|
|
@uninstall_lines << RunLineCmd::from_xml( runline, @environment )
|
|
elsif ( not runline.attrubutes['scriptcmd'].nil? ) then
|
|
@uninstall_lines << RunLineScript::from_xml( runline, @environment )
|
|
else
|
|
RunLine::log().error( "Unrecognised runline XML: #{runline.to_s}." )
|
|
end
|
|
end
|
|
end
|
|
|
|
# SCM CONFIGURATION
|
|
xml_node.elements.each( "#{root}/scm" ) do |scm|
|
|
@sc_enabled = scm.attributes['toolchain_integration'] unless scm.attributes['toolchain_integration'].nil?
|
|
end
|
|
|
|
xml_node.elements.each( "#{root}/scm/build" ) do |scm|
|
|
|
|
@sc_workspace = scm.attributes['workspace'] unless scm.attributes['workspace'].nil?
|
|
@sc_username = scm.attributes['username'] unless scm.attributes['username'].nil?
|
|
@sc_server = scm.attributes['server'] unless scm.attributes['server'].nil?
|
|
end
|
|
# RAGE SCM CONFIGURATION
|
|
xml_node.elements.each( "#{root}/scm/rage" ) do |scm|
|
|
|
|
@sc_rage_workspace = scm.attributes["workspace"] unless scm.attributes["workspace"].nil?
|
|
@sc_rage_username = scm.attributes["username"] unless scm.attributes["username"].nil?
|
|
@sc_rage_server = scm.attributes["server"] unless scm.attributes["server"].nil?
|
|
end
|
|
# TOOLS SCM CONFIGURATION
|
|
xml_node.elements.each( "#{root}/scm/tools" ) do |scm|
|
|
|
|
@sc_tools_workspace = scm.attributes["workspace"] unless scm.attributes["workspace"].nil?
|
|
@sc_tools_username = scm.attributes["username"] unless scm.attributes["username"].nil?
|
|
@sc_tools_server = scm.attributes["server"] unless scm.attributes["server"].nil?
|
|
end
|
|
|
|
# PROJECTS CONFIGURATION
|
|
xml_node.elements.each( "#{root}/projects/project" ) do |project|
|
|
|
|
# Parse all XML attributes
|
|
name = project.attributes['name']
|
|
throw XmlParseError.new( "No project name attribute." ) \
|
|
if ( name.nil? )
|
|
|
|
uiname = project.attributes['uiname'] unless project.attributes['uiname'].nil?
|
|
rootpath = project.attributes['root'] unless project.attributes['root'].nil?
|
|
config = project.attributes['config'] unless project.attributes['config'].nil?
|
|
sc_proj = name # DHM; obsolete attribute 'sc'
|
|
|
|
if ( @projects.has_key?( name ) ) then
|
|
# UPDATE PROJECT OBJECT
|
|
# name and config cannot be changed
|
|
@projects[name].uiname = uiname unless uiname.nil?
|
|
@projects[name].root = rootpath unless rootpath.nil?
|
|
elsif ( 'config' == root )
|
|
# CREATE PROJECT OBJECT
|
|
# Only if we are parsing the root config. So we can
|
|
# safely remove projects from the root even when they
|
|
# may exist in users local XML data.
|
|
@projects[name] = Project.new( name, uiname, rootpath, config, sc_proj )
|
|
end
|
|
end
|
|
|
|
#Local Studio information.
|
|
xml_node.elements.each( "#{root}/studio" ) do |studio|
|
|
|
|
@local_studio = studio.attributes['name']
|
|
end
|
|
|
|
# DLC PROJECTS CONFIGURATION
|
|
# DHM: no current support, all support in Asset Pipeline 3.
|
|
|
|
@project = @projects.values[0]
|
|
|
|
# LOGGING CONFIGURATION
|
|
xml_node.elements.each( "#{root}/logging" ) do |logging|
|
|
|
|
# Parse all XML attributes
|
|
@log_level = logging.attributes['level'].to_i unless logging.attributes['level'].nil?
|
|
@logtostdout = ( 'true' == logging.attributes['stdout'] ) unless logging.attributes['stdout'].nil?
|
|
@log_trace = ( 'true' == logging.attributes['trace'] ) unless logging.attributes['trace'].nil?
|
|
@log_generate_html = ( 'true' == logging.attributes['generate_html'] ) unless logging.attributes['generate_html'].nil?
|
|
end
|
|
xml_node.elements.each( "#{root}/logging/logmails" ) do |logmails|
|
|
|
|
# Parse all XML attributes
|
|
@mailserver = logmails.attributes['server'] unless logmails.attributes['server'].nil?
|
|
@mailport = logmails.attributes['port'] unless logmails.attributes['port'].nil?
|
|
@maildomain = logmails.attributes['domain'] unless logmails.attributes['domain'].nil?
|
|
@logmailerrors = ( 'true' == logmails.attributes["enabled"] ) unless logmails.attributes['enabled'].nil?
|
|
end
|
|
xml_node.elements.each( "#{root}/logging/logmails/logmail" ) do |logmail|
|
|
|
|
# Parse all XML attributes
|
|
name = logmail.attributes['name']
|
|
email = logmail.attributes['email']
|
|
|
|
@logmailtos[name] = email
|
|
end
|
|
|
|
#MAX
|
|
|
|
xml_node.elements.each( "#{root}/max") do |max|
|
|
|
|
if max.attributes['toolset_mode'].nil? == false then
|
|
|
|
@max_options.toolset_mode = max.attributes['toolset_mode'].clone.intern
|
|
end
|
|
end
|
|
|
|
#-----------------------------------------------------------------
|
|
# LOCAL-SPECIFIC CONFIGURATION
|
|
#-----------------------------------------------------------------
|
|
if ( 'local' == root ) then
|
|
|
|
xml_node.elements.each( "#{root}/user" ) do |user|
|
|
|
|
@user = User::from_xml( user )
|
|
end
|
|
end
|
|
|
|
#-----------------------------------------------------------------
|
|
# USER-SPECIFIC CONFIGURATION
|
|
#-----------------------------------------------------------------
|
|
|
|
# APPLICATIONS CONFIGURATION
|
|
xml_node.elements.each( "#{root}/applications/app" ) do |app|
|
|
if ( 'xge' == app.attributes['name'] ) then
|
|
|
|
@use_xge = ( 'true' == app.attributes['enabled'] )
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Reset member variables to their default values. Private as only used
|
|
# internally.
|
|
#
|
|
def reset( )
|
|
@environment.clear( )
|
|
import_globals( )
|
|
|
|
@projects = {}
|
|
@platforms = {}
|
|
@usertypes = {}
|
|
@logmailtos = {}
|
|
@install_lines = []
|
|
@uninstall_lines = []
|
|
|
|
# LOGGING DEFAULTS
|
|
@log_level = 2
|
|
@logmailerrors = true
|
|
@logtostdout = false
|
|
@log_trace = false
|
|
@log_generate_html = false
|
|
|
|
#MAX
|
|
@max_options = MaxOptions.new()
|
|
end
|
|
|
|
#
|
|
# == Description
|
|
# This method is invoked from the public +save_locals+ method and it
|
|
# updates the 3dsmax plugcfg directory with a small settings file
|
|
# so our 3dsmax plugins/script tools can determine the path locations
|
|
# for project/global configuration settings.
|
|
#
|
|
def save_locals_3dsmax( )
|
|
# Fix issue with some artists not having 3dsmax installed.
|
|
autodesk3dsmax = Autodesk3dsmax::instance( )
|
|
return unless ( autodesk3dsmax.is_installed? )
|
|
|
|
begin
|
|
autodesk3dsmax.installdirs.each_pair do |platform, installdirs|
|
|
|
|
installdirs.each_pair do |max_version, max_root_dir|
|
|
dcc_root_dir = autodesk3dsmax.get_root_dir()
|
|
|
|
next if (@max_options.version_options[max_version.to_f].nil? == false) and (@max_options.version_options[max_version.to_f].check_for_install == false)
|
|
|
|
max_plugcfg_dir = OS::Path.combine( @toolsroot, dcc_root_dir, Autodesk3dsmax::VERSION_DIRS[max_version.to_f], 'plugcfg' )
|
|
|
|
pipeline_file = OS::Path.combine( max_plugcfg_dir, 'pipeline.xml' )
|
|
|
|
if ( File.directory?( max_plugcfg_dir ) ) then
|
|
File.open( pipeline_file, 'w' ) do |file|
|
|
Autodesk3dsmax::log().info( "Creating pipeline.xml file in #{max_root_dir}" )
|
|
max_settings = Document.new
|
|
root_elem = max_settings.add_element( 'max_pipeline' )
|
|
max_settings.root.add_attribute( 'toolsroot', Globals.instance.toolsroot )
|
|
max_settings.root.add_attribute( 'toolsbin', Globals.instance.toolsbin )
|
|
|
|
# Rather than hardcode which environment variables
|
|
# go into pipeline.xml we just take all of the root
|
|
# pipeline globals.
|
|
g = Pipeline::Globals::instance( )
|
|
g.instance_variables.each do |var|
|
|
sym = var.sub( '@', '')
|
|
val = g.instance_variable_get( var )
|
|
root_elem.add_attribute( sym.to_s, val )
|
|
end
|
|
# Write out the XML.
|
|
fmt = REXML::Formatters::Pretty.new( )
|
|
fmt.write( max_settings, file )
|
|
end
|
|
else
|
|
Autodesk3dsmax::log().error( "No pipeline.xml file created for #{max_root_dir}. Path #{max_plugcfg_dir} does not exist." )
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => ex
|
|
Autodesk3dsmax::log().exception(ex, "Max setup failed" )
|
|
end
|
|
end
|
|
|
|
#
|
|
# This method is invoked from the public +save_locals+ method and it
|
|
# updates the artist configuration XML node (storing active project
|
|
# for 3dsmax etc.)
|
|
#
|
|
def save_locals_artist( root )
|
|
isX64 = ( :x64 == OS::WinSystemInfo::SysType( ) )
|
|
latest_version = Autodesk3dsmax::instance( ).latest_version
|
|
installdirs = Autodesk3dsmax::instance( ).installdirs( )
|
|
max_install_dir = ''
|
|
if ( isX64 and installdirs.has_key?( :x64 ) ) then
|
|
max_install_dir = installdirs[:x64][latest_version]
|
|
elsif ( installdirs.has_key?( :x86 ) ) then
|
|
max_install_dir = installdirs[:x86][latest_version]
|
|
end
|
|
|
|
artistNode = root.add_element( 'artist' )
|
|
artistNode.attributes['activeproject'] = ''
|
|
artistNode.attributes['maxinstalldir'] = max_install_dir
|
|
artistNode.attributes['maxversion'] = latest_version.to_s
|
|
end
|
|
|
|
#
|
|
# Setup specific Autodesk 3dsmax version.
|
|
#
|
|
def setup_3dsmax_version( max_version, installdir, isX64, uninstall, force_max_run )
|
|
|
|
Autodesk3dsmax::log().info( 'Autodesk 3dsmax Initialisation' )
|
|
Autodesk3dsmax::log().info( "Version: #{max_version} 64bit:#{isX64}" )
|
|
Autodesk3dsmax::log().info( "Install Dir: #{installdir}" )
|
|
|
|
begin
|
|
autodesk3dsmax = Autodesk3dsmax::instance( )
|
|
dcc_root_dir = autodesk3dsmax.get_root_dir( )
|
|
|
|
if ( Autodesk3dsmax::VERSION_DIRS[max_version].nil? ) then
|
|
|
|
GUI::MessageBox::warning( "Unsupported 3dsmax Version", "3dsmax version #{max_version} 32-bit is not yet supported by the tool chain and will not be setup." ) unless isX64
|
|
GUI::MessageBox::warning( "Unsupported 3dsmax Version", "3dsmax version #{max_version} 64-bit is not yet supported by the tool chain and will not be setup." ) unless isX64
|
|
elsif ( (@max_options.version_options[max_version].nil? == false) and (@max_options.version_options[max_version].check_for_install == false) )
|
|
|
|
Autodesk3dsmax::log().info( "ignoring: #{max_version.to_s}" )
|
|
else
|
|
|
|
plugin_src_dir = OS::Path.combine( @toolsroot, dcc_root_dir, Autodesk3dsmax::VERSION_DIRS[max_version])
|
|
plugin_src_dir = OS::Path.combine( plugin_src_dir, 'x64' ) if isX64
|
|
|
|
filelist = OS::FindEx.find_files( OS::Path::combine( plugin_src_dir, '*.*' ), false )
|
|
filelist.each do |file|
|
|
fname = OS::Path.get_filename( file )
|
|
dst_file = OS::Path.combine( installdir, fname )
|
|
|
|
Autodesk3dsmax::log().info( "Copying file #{file}" )
|
|
Autodesk3dsmax::log().info( "\tDestination: #{dst_file}" )
|
|
|
|
if uninstall then
|
|
|
|
OS::FileUtilsEx.delete_files( dst_file ) if File.exist?( dst_file )
|
|
else
|
|
|
|
OS::FileUtilsEx.copy_file( file, dst_file ) if File.exist?( file )
|
|
end
|
|
end
|
|
|
|
# This could do with it's own function for clarity but for now it will live here
|
|
startup_script_dest_dir = OS::Path.combine( installdir, 'scripts', 'startup' )
|
|
OS::FileUtilsEx::delete_files( OS::Path::combine( startup_script_dest_dir, '*.*' ) )
|
|
initFilename = OS::Path::combine( startup_script_dest_dir, 'rockstar_max_init.ms' )
|
|
|
|
config_string = ''
|
|
if uninstall then
|
|
|
|
config_string = "RsUpdateMaxSystemPaths \"UNUSED\" versionSubdir:\"#{Autodesk3dsmax::VERSION_DIRS[0.0]}\" isX64:#{isX64}"
|
|
else
|
|
|
|
config_string = "RsUpdateMaxSystemPaths \"UNUSED\" versionSubdir:\"#{Autodesk3dsmax::VERSION_DIRS[max_version]}\" isX64:#{isX64}"
|
|
end
|
|
|
|
config_string = config_string + " toolset:\"#{@max_options.toolset_mode.to_s}\""
|
|
|
|
c = Pipeline::Config::instance()
|
|
fh = File.new(initFilename, 'w')
|
|
fh.write( "filein \"#{OS::Path::normalise( OS::Path::combine( c.toolsroot, 'dcc', 'common', 'configswitch_clean.ms' ) )}\"\n\n" )
|
|
fh.write( config_string )
|
|
fh.close( )
|
|
|
|
max_run = false
|
|
|
|
if force_max_run then
|
|
|
|
max_run = true
|
|
else
|
|
|
|
ver = Autodesk3dsmax::VERSION_DIRS[max_version].sub( 'max', '' )
|
|
msg = "Do you want to start 3dsmax #{ver} 32-bit to complete plugin setup?\n\n" unless isX64
|
|
msg = "Do you want to start 3dsmax #{ver} 64-bit to complete plugin setup?\n\n" if isX64
|
|
msg += "This only needs to be done the first time the installer is run, when you switch project or when you experience problems with "
|
|
msg += "3dsmax #{ver} 32-bit." unless isX64
|
|
msg += "3dsmax #{ver} 64-bit." if isX64
|
|
res = GUI::MessageBox::question( '3dsmax Plugin Setup', msg )
|
|
case res
|
|
when GUI::MessageBox::YES
|
|
max_run = true
|
|
end
|
|
end
|
|
|
|
if max_run then
|
|
|
|
Autodesk3dsmax::log().info( "Running 3dsmax #{max_version} 32-bit..." ) unless isX64
|
|
Autodesk3dsmax::log().info( "Running 3dsmax #{max_version} 64-bit..." ) if isX64
|
|
commandline = "#{OS::Path::combine( installdir, '3dsmax.exe')} -U MAXScript \"#{initFilename}\" -mxs \"quitMax #noPrompt\" -silent"
|
|
Autodesk3dsmax::log().info( "Command line: #{commandline}" )
|
|
system( commandline )
|
|
else
|
|
|
|
Autodesk3dsmax::log().info( "3dsmax #{max_version} 32-bit not setup." ) unless isX64
|
|
Autodesk3dsmax::log().info( "3dsmax #{max_version} 64-bit not setup." ) if isX64
|
|
end
|
|
end
|
|
rescue Exception => ex
|
|
Autodesk3dsmax::log().exception( ex, "Unhandled exception during 3dsmax #{max_version} 32-bit install" ) unless isX64
|
|
Autodesk3dsmax::log().exception( ex, "Unhandled exception during 3dsmax #{max_version} 64-bit install" ) if isX64
|
|
end
|
|
end
|
|
end
|
|
|
|
end # Pipeline
|
|
|
|
# pipeline/config/projects.rb
|