# # File:: pipeline/config/projects.rb # Description:: Global tools configuration classes # # Author:: David Muir # Author:: Greg Smith # Author:: Luke Openshaw # # 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 /config.xml) attr_accessor :user_version # Local version on current machine (from /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