Pastebin launched a little side project called VERYVIRAL.com, check it out ;-) Want more features on Pastebin? Sign Up, it's FREE!
Guest

vproj.py - Valve project switching module

By: a guest on Aug 5th, 2012  |  syntax: Python  |  size: 67.23 KB  |  views: 11,124  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. ###WWW.VALVETIME.NET###
  2. ###Uploader comment: This file was found in "SourceFilmmaker/game/sdktools/python/global/lib/site-packages/vproj/vproj.py"###
  3.  
  4. #========= Copyright (c) Valve Corporation. All Rights Reserved. ============#
  5.  
  6. '''
  7. Valve project switching module.
  8.  
  9. Nomenclature:
  10. projectConfig, project: repesents a single game's or project's configuration data
  11. gameConfig: file containing a list of projects
  12. '''
  13.  
  14. from _winreg import *
  15. import codecs
  16. import errno
  17. import hashlib
  18. import os
  19. import pickle
  20. import subprocess
  21. import sys
  22. from copy import deepcopy
  23. from imp import is_frozen
  24. import ctypes, pythoncom, pywintypes, win32api, win32con, win32gui
  25. from win32com.shell import shell
  26. from time import localtime, strftime
  27. import P4
  28.  
  29. # filesystem needs at least VPROJECT to be set,
  30. # so try and set it top something if it's not there.
  31. if not 'VPROJECT' in os.environ:
  32.         toolsPath = os.path.split( os.path.split( sys.executable )[0] )[0]
  33.         basePath = os.path.split( toolsPath )[0]
  34.         os.environ[ 'VPROJECT' ] = os.path.join( basePath, 'game\tmp' )
  35. try:
  36.         import filesystem
  37. except ImportError:
  38.         toolsBin = os.path.split( os.path.split( sys.executable )[0] )[0]
  39.         toolsPython = os.path.join( toolsBin, 'python' )
  40.         os.environ[ 'PATH' ] = os.environ[ 'PATH' ] +';'+ toolsBin
  41.         sys.path.append( toolsPython )
  42.  
  43. #from filesystem import content, game, mod, platform, removeDupes, tools
  44. #from filesystem import *
  45.  
  46. from filesystem.misc import removeDupes, findInFile, removeLineInFileThatContains
  47. from filesystem.path import PathError, Path
  48. from filesystem.valve import ( content, game, mod, platform, project, tools, Chunk,
  49.                                parseLine, KeyValueFile, GameInfoFile, GameInfoException )
  50. from filesystem.perforce import P4ConfigFile
  51.  
  52. import utils, fileAssociations
  53.  
  54. # Import vproj constants and default settings
  55. # Variable and settings should be added/changed here
  56. from constants import *
  57. from settings import EnvarSetting
  58.  
  59. __author__ = 'jeff@valvesoftware.com'
  60. __toolName__ = 'vproj'
  61.  
  62. def main_is_frozen():
  63.         '''
  64.         Returns True when running via .exe, False if running via script.
  65.         '''
  66.         return ( hasattr( sys, "frozen" ) or # new py2exe
  67.                  hasattr( sys, "importers" ) # old py2exe
  68.                  or is_frozen( "__main__" ) # tools/freeze
  69.                  )
  70.  
  71. def get_main_dir():
  72.         '''
  73.         Returns the directory name of the script or the directory name of the exe.
  74.         '''
  75.         if main_is_frozen():
  76.                 return os.path.dirname( sys.executable )
  77.         return os.path.dirname( sys.argv[0] )
  78.  
  79. # guesses for the tools dir, different if run directly from the exe or from the script
  80. if main_is_frozen():
  81.         kBASE_DIR = Path(__file__).up( 4 )
  82.         kTOOLS_DIR = Path(__file__).up( 2 )
  83. else:
  84.         kBASE_DIR = Path(__file__).up( 8 )
  85.         kTOOLS_DIR = Path(__file__).up( 6 )
  86.        
  87. def getEnvKeys( useSysKeys=True, asString=False ):
  88.         '''
  89.         Returns the proper root and subkeys for the given environment space
  90.         '''
  91.         if useSysKeys:
  92.                 regKeyRoot = 'HKEY_LOCAL_MACHINE'
  93.                 regSubKey = r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
  94.         else:
  95.                 regKeyRoot = 'HKEY_CURRENT_USER'
  96.                 regSubKey = r"Environment"
  97.         if not asString:
  98.                 regKeyRoot = globals()[ regKeyRoot ]
  99.                
  100.         return regKeyRoot, regSubKey
  101.  
  102. def getEnv( varname, default=False, expandStrings=False, useSysKeys=True ):
  103.         '''
  104.         Returns the value of a given environment variable from the given space.
  105.         False by default if variable doesn't exist, and an empty string if the variable exists but has no value set.
  106.         '''
  107.         v = default
  108.         regKeyRoot, regSubKey = getEnvKeys( useSysKeys )
  109.  
  110.         try:
  111.                 regKey = OpenKey( regKeyRoot, regSubKey, 0, KEY_READ )
  112.                 try:
  113.                         v = str( win32api.RegQueryValueEx( regKey, varname )[0] )
  114.                         if expandStrings:
  115.                                 v = win32api.ExpandEnvironmentStrings( v )
  116.                 except:
  117.                         pass
  118.         finally:
  119.                 try:
  120.                         win32api.RegCloseKey( regKey )
  121.                 except:
  122.                         raise RegistryError( 'Error attempting to read an environment variable: {0}'.format( varname ) )
  123.         return v
  124.  
  125. def setRegKey( regKeyRoot, regSubKey, varName, varValue, reg_value_type=REG_SZ ):
  126.         '''
  127.         Set a registry key. Returns True if successful.
  128.         '''
  129.         try:
  130.                 regKey = OpenKey( regKeyRoot, regSubKey, 0, KEY_WRITE )
  131.                 SetValueEx( regKey, varName, 0, reg_value_type, varValue )
  132.                 result = True
  133.         except WindowsError:
  134.                 result = False
  135.         finally:
  136.                 try:
  137.                         CloseKey( regKey )
  138.                 except:
  139.                         raise RegistryError( 'Error attempting to set a registry key: {0}{1}'.format( regKeyRoot, regSubKey ) )
  140.         return result  
  141.  
  142. def setEnv( varName, varValue, reg_value_type=REG_SZ, useSysKeys=True ):
  143.         '''
  144.         Sets the given environment variable in the given space. Returns True if successful.
  145.         '''
  146.         regKeyRoot, regSubKey = getEnvKeys( useSysKeys )
  147.         return setRegKey( regKeyRoot, regSubKey, varName, varValue, reg_value_type )
  148.  
  149. def deleteEnv( varName, useSysKeys=True ):
  150.         '''
  151.         Deletes the given environment variable in the given space. Returns True if successful.
  152.         '''
  153.         regKeyRoot, regSubKey = getEnvKeys( useSysKeys )
  154.  
  155.         try:
  156.                 regKey = OpenKey( regKeyRoot, regSubKey, 0, KEY_ALL_ACCESS )
  157.         except:
  158.                 return False
  159.                
  160.         try:
  161.                 DeleteValue( regKey, varName )
  162.                 return True
  163.         except:
  164.                 return False
  165.         finally:
  166.                 try:
  167.                         CloseKey( regKey )
  168.                 except:
  169.                         raise RegistryError( 'Error attempting to delete a environment variable: {0}'.format( varName ) )
  170. def checkForVars( useSysKeys=True ):
  171.         '''
  172.         Returns a list of known envars in the given environment
  173.         TODO: return a dict with sys and user vars?
  174.         '''
  175.         checkVars = ENVARS.BASE.keys()
  176.         checkVars.extend( ENVARS.ADD.keys() )
  177.         for e in ENVARS.DEPRECATED.itervalues():
  178.                 checkVars.extend( e )
  179.  
  180.         existingVars = []
  181.         for var in checkVars:
  182.                 if getEnv( var, useSysKeys=useSysKeys ):
  183.                         existingVars.append( var )
  184.         if existingVars: return existingVars
  185.         return False
  186.  
  187. def checkForPath( useSysKeys=True ):
  188.         '''
  189.         Returns True if there are Valve paths in the PATH envar.
  190.         '''
  191.         curPath = getEnv( 'PATH', useSysKeys=useSysKeys )
  192.         if curPath:
  193.                 # check the path for valve paths
  194.                 newSysPath = removeProjectPathsFromPATH( curPath )
  195.                 sysPathValue = kSEPARATOR.join( newSysPath ).replace('/', '\\')
  196.                 if sysPathValue == curPath:
  197.                         return False
  198.                 return True
  199.         return False
  200.  
  201. def deletePath( useSysKeys=True ):
  202.         '''
  203.         Remove Valve paths from PATH
  204.         '''
  205.         curPath = getEnv( 'PATH', useSysKeys=useSysKeys )
  206.         if curPath:
  207.                 try:
  208.                         # Clear the path of valve paths
  209.                         newPath = removeProjectPathsFromPATH( curPath )
  210.                         # TODO: check to see if newPath == curPath (no work to do?)
  211.                         PathValue = kSEPARATOR.join( newPath ).replace('/', '\\')
  212.                         # Check to see if final path is empty. If so, remove it.
  213.                         if PathValue == '':
  214.                                 deleteEnv( 'PATH', useSysKeys=useSysKeys )
  215.                         else:
  216.                                 setEnv( 'PATH', PathValue, REG_EXPAND_SZ, useSysKeys=useSysKeys )
  217.                         return True
  218.                 except:
  219.                         return False
  220.  
  221. def deleteVars( useSysKeys=True ):
  222.         '''
  223.         Removes known environment variables and returns a list of deleted vars.
  224.         '''
  225.         varList = checkForVars( useSysKeys=useSysKeys )
  226.  
  227.         if varList:
  228.                 if not checkAdmin() and ( useSysKeys==True ):
  229.                         raise PermissionError( 'Current user does not have admin privileges.' )
  230.  
  231.                 deletedVars = []               
  232.                 for var in varList:
  233.                         try:
  234.                                 deleteEnv( var, useSysKeys=useSysKeys )
  235.                                 deletedVars.append( var )
  236.                         except WindowsError, e:
  237.                                 if e.errno == 2:
  238.                                         # skip if key cannot be found
  239.                                         pass
  240.                 return deletedVars
  241.         else:
  242.                 return False
  243.  
  244. def make_NT_SYMBOL_PATH( useExtLinks=True, useP4Paths=True, userAdd=None ):
  245.         '''
  246.         Return a default _NT_SYMBOL_PATH string given the options.
  247.         '''
  248.         envPath = r'%VGAME%\bin\win64;' + \
  249.                 r'%VGAME%\bin\win32;' + \
  250.                 r'%VGAME%\%VMOD%\bin\win64;' + \
  251.                 r'%VGAME%\%VMOD%\bin\win32;' + \
  252.                 r'%VGAME%\..\devtools\bin\win64;' + \
  253.                 r'%VGAME%\..\devtools\bin\win32;' + \
  254.                 r'%VGAME%\sdktools\bin\win64;' + \
  255.                 r'%VGAME%\sdktools\bin\win32;' + \
  256.                 r'%VGAME%\bin;' + \
  257.                 r'%VGAME%\%VMOD%\bin;' + \
  258.                 r'%VGAME%\..\devtools\bin;' + \
  259.                 r'%VGAME%\sdktools\bin;'
  260.        
  261.         ## Updated path, commented out for now due since it will currently trigger a false warning that
  262.         ## the path has changed behind VProj's back.
  263.         #envPath = r'%VGAME%\bin\win64;'+ \
  264.                 #r'%VGAME%\bin\win32;'+ \
  265.                 #r'%VGAME%\%VMOD%\bin\win64;'+ \
  266.                 #r'%VGAME%\%VMOD%\bin\win32;'+ \
  267.                 #r'%VGAME%\..\src\devtools\bin\win64;'+ \
  268.                 #r'%VGAME%\..\src\devtools\bin\win32;'+ \
  269.                 #r'%VGAME%\sdktools\bin\win64;'+ \
  270.                 #r'%VGAME%\sdktools\bin\win32;'+ \
  271.                 #r'%VGAME%\bin;'+ \
  272.                 #r'%VGAME%\%VMOD%\bin;'+ \
  273.                 #r'%VGAME%\..\src\devtools\bin;'+ \
  274.                 #r'%VGAME%\sdktools\bin;'
  275.         p4Path = ''
  276.         extPath = ''
  277.         userPath = ''
  278.         if useP4Paths:
  279.                 p4Path = r'*\\perforce\symbols'
  280.         if useExtLinks:
  281.                 extPath =  '*http://msdl.microsoft.com/download/symbols'
  282.         if userAdd:
  283.                 userPath = userAdd + ';'
  284.                 userPath.replace( ';;', ';' )
  285.  
  286.         result = 'cache*;' + envPath + userPath + 'SRV' + p4Path + extPath
  287.         return result
  288.  
  289. def getP4RegistryVar( p4var='p4config' ):
  290.         '''
  291.         Check the dedicated Perforce registry space for the presence of a p4 setting.
  292.         Return the value if it's found, or False if not.
  293.         '''
  294.         regKeyRoot = HKEY_CURRENT_USER
  295.         regSubKey = r"Software\Perforce\environment"
  296.         v = False
  297.  
  298.         try:
  299.                 regKey = OpenKey( regKeyRoot, regSubKey, 0, KEY_READ )
  300.                 try:
  301.                         v = str( win32api.RegQueryValueEx( regKey, p4var )[0] )
  302.                 except:
  303.                         pass
  304.         finally:
  305.                 try:
  306.                         win32api.RegCloseKey( regKey )
  307.                 except:
  308.                         pass
  309.         return v
  310.  
  311. def checkp4Environ( p4var='p4config' ):
  312.         '''
  313.         Returns the value of a p4 setting and returns None if it doesn't exist.
  314.         Order is checked in the priority order that perforce uses: user environment, sys environment, registry.
  315.         '''
  316.         p4 = P4.P4()
  317.         return p4.env( p4var )
  318.  
  319.         ## Manual method commented out
  320.         ## First check user environment space
  321.         #if ( getEnv( p4var.upper(), useSysKeys=False ) ):
  322.                 #return getEnv( p4var, useSysKeys=False )
  323.         ## Check system environment space
  324.         #elif  ( getEnv( p4var.upper(), useSysKeys=True ) ):
  325.                 #return getEnv( p4var, useSysKeys=True )
  326.         ## Last check registry
  327.         #elif ( getP4RegistryVar( p4var ) ):
  328.                 #return getP4RegistryVar( p4var )
  329.         #else:
  330.                 #return False
  331.                
  332. def setP4RegistryVar( p4var='p4config', p4value='p4.cfg' ):
  333.         '''
  334.         Set a P4 variable in the registry
  335.         Return False if it could not be set.
  336.         '''
  337.         regKeyRoot = HKEY_CURRENT_USER
  338.         regSubKey = r"Software\Perforce\environment"
  339.  
  340.         return setRegKey( regKeyRoot, regSubKey, p4var, p4value )
  341.  
  342. def deleteP4RegistryVar( p4var='p4config' ):
  343.         '''
  344.         Check the registry for the presence of a p4 setting.
  345.         Return the True if it's deleted, or False if it is not.
  346.         '''
  347.         regKeyRoot = HKEY_CURRENT_USER
  348.         regSubKey = r"Software\Perforce\environment"
  349.  
  350.         try:
  351.                 regKey = OpenKey( regKeyRoot, regSubKey, 0, KEY_ALL_ACCESS )
  352.         except:
  353.                 return False
  354.        
  355.         try:
  356.                 DeleteValue( regKey, p4var )
  357.                 return True
  358.         except:
  359.                 return False
  360.         finally:
  361.                 try:
  362.                         CloseKey( regKey )
  363.                 except:
  364.                         return False
  365.                
  366.         return result  
  367.  
  368. def getP4Workspaces():
  369.         '''
  370.         Returns the current workspace and a list of Perforce workspaces (clientspecs) for the current user and workstation
  371.         '''
  372.         p4 = P4.P4() # Create the P4 instance
  373.         host = os.environ[ 'COMPUTERNAME' ]
  374.         workspaces = []
  375.         curWorkspace = None
  376.        
  377.         try:
  378.                 p4.connect()
  379.                 info = p4.run( "info" )
  380.                
  381.                 # Get the workspaces for the current user and host
  382.                 for client in p4.run( 'workspaces', '-u', p4.user ):
  383.                         if ( client['Host'] == host.lower() ):
  384.                                 workspaces.append( client['client'] )
  385.                 p4.disconnect()
  386.                 curWorkspace = info[0][ 'clientName' ]
  387.         except P4.P4Exception:
  388.                 raise
  389.        
  390.         return curWorkspace, workspaces
  391.                
  392. def rotateBackupFiles( backupPath, fileName, maxBackups=4 ):
  393.         '''
  394.         Rotates through a set up backup files in the given backup path.
  395.         The highest number file is deleted and the fileName becomes the first.
  396.         Normally the backups would be rotated, then a new "root" backup is created to replace the first.
  397.        
  398.         backupPath = where the backup files should be created
  399.         fileName = name of the "root" backup file. Needs to exist in the backup path.
  400.         maxBackups = number of backup files to keep
  401.         '''
  402.        
  403.         fileBaseName, ext = os.path.splitext( fileName )
  404.        
  405.         # Remove last backup
  406.         try:
  407.                 os.remove( '{0}\\{1}.{2}{3}'.format( backupPath, fileBaseName, maxBackups, ext ) )
  408.         except WindowsError, e:
  409.                 # Raise all error except file not found
  410.                 if not ( e.errno == errno.ENOENT ):
  411.                         raise WindowsError, e
  412.        
  413.         # Increment backup filenames
  414.         for i in range( maxBackups - 1, 0, -1 ):
  415.                 try:
  416.                         os.rename( '{0}\\{1}.{2}{3}'.format( backupPath, fileBaseName, i, ext ),
  417.                                    '{0}\\{1}.{2}{3}'.format( backupPath, fileBaseName, i+1, ext ) )
  418.                 except WindowsError, e:
  419.                         # Raise all error except file not found
  420.                         if not ( e.errno == errno.ENOENT ):
  421.                                 raise WindowsError, e
  422.        
  423.         # Rename filename backup to 1 slot
  424.         try:
  425.                 os.rename( '{0}\\{1}{2}'.format( backupPath, fileBaseName, ext ),
  426.                            '{0}\\{1}.1{2}'.format( backupPath, fileBaseName, ext ) )
  427.         except WindowsError, e:
  428.                 if not ( e.errno == errno.ENOENT ):
  429.                         # Raise all error except file not found
  430.                         raise WindowsError, e
  431.  
  432. def backupEnvironment( filePath, fileName, useSysKeys=True ):
  433.         '''
  434.         Make a backup copy of the current environment variables
  435.         '''
  436.         # Make the backup directory if it doesn't exist
  437.         if not os.path.exists( filePath ):
  438.                 os.makedirs( filePath )
  439.        
  440.         TEMP = os.path.expandvars( '%TEMP%' )
  441.  
  442.         # Read the system and user environment to temp files
  443.         tmpRegFileSys = '{0}\\{1}'.format( os.path.expandvars( '%TEMP%' ), 'environment_backup_sys_tmp.reg' )
  444.         regKeyRoot, regSubKey = getEnvKeys( useSysKeys=True, asString=True )
  445.         regPath = '{0}\{1}'.format( regKeyRoot, regSubKey )
  446.         regedit = subprocess.Popen( 'regedit.exe /e "{0}" "{1}"'.format( tmpRegFileSys, regPath ) )
  447.         regedit.wait()
  448.  
  449.         tmpRegFileUsr = '{0}\\{1}'.format( os.path.expandvars( '%TEMP%' ), 'environment_backup_usr_tmp.reg' )
  450.         regKeyRoot, regSubKey = getEnvKeys( useSysKeys=False, asString=True )
  451.         regPath = '{0}\{1}'.format( regKeyRoot, regSubKey )
  452.         regedit = subprocess.Popen( 'regedit.exe /e "{0}" "{1}"'.format( tmpRegFileUsr, regPath ) )
  453.         regedit.wait()
  454.        
  455.         # Merge the two files into the target file
  456.         addRegistryFiles( tmpRegFileUsr, tmpRegFileSys, '{0}\{1}'.format( filePath, fileName ) )
  457.        
  458.         # Delete the temp files
  459.         os.remove( tmpRegFileUsr )
  460.         os.remove( tmpRegFileSys )
  461.  
  462. def backupEnvironmentRotated( filePath=None, fileName=None, useSysKeys=True ):
  463.         '''
  464.         Makes a backup copy of the current environment variables and rotates through 5 backups.
  465.         '''
  466.         if not filePath:
  467.                 filePath = '{0}\\Backups'.format( os.path.dirname( os.path.expandvars( CONFIG_PATH ) ) )
  468.         if not fileName:
  469.                 fileName = 'environment_vars.reg'
  470.                
  471.         # Rename existing backups if the directory exists
  472.         if os.path.exists( filePath ):
  473.                 rotateBackupFiles( filePath, fileName )
  474.                
  475.         backupEnvironment( filePath, fileName, useSysKeys=useSysKeys )
  476.  
  477. def addRegistryFiles( filePathA, filePathB, targetFile ):
  478.         '''
  479.         Merge the contents of two unicode registry files into a third file.
  480.        
  481.         Note: there is no handling of duplicates. It simply adds the contents of fileB to fileB,
  482.         only removing the header line from the 2nd file.
  483.         '''
  484.         # Read data from source files
  485.         fileA = codecs.open( filePathA, 'r', 'utf-16' )
  486.         fileAData = fileA.readlines()
  487.         fileA.close()
  488.         fileB = codecs.open( filePathB, 'r', 'utf-16' )
  489.         fileBData = fileB.readlines()
  490.         fileA.close()
  491.        
  492.         # Write out the new merged file
  493.         newFileData = fileAData + fileBData[1:]  # Skip the header line in 2nd file
  494.         newFile = codecs.open( targetFile, 'w', 'utf-16' )
  495.         newFile.writelines( newFileData )
  496.         newFile.close()
  497.  
  498. def checkAdmin():
  499.         '''
  500.         Returns True if the user has admin privileges, False if not.
  501.         '''
  502.         return shell.IsUserAnAdmin()
  503.  
  504. class RegistryError( Exception ):
  505.         '''
  506.         Exception to handle registry access errors.
  507.         '''
  508.         def __init__( self, message, errno=None ):
  509.                 Exception.__init__( self, message )
  510.                 self.errno = None
  511.                 self.strerror = message
  512.  
  513. class PermissionError( Exception ):
  514.         '''
  515.         Exception to handle when user does not have admin permissions.
  516.         '''
  517.         def __init__( self, message, errno=None ):
  518.                 Exception.__init__( self, message )
  519.                 self.errno = None
  520.                 self.strerror = message
  521.  
  522. def removeProjectPathsFromPATH( PATH_str, gameConfig=None ):
  523.         '''
  524.         Removes all project paths from a given %PATH% string, and returns the new, clean %PATH% as a list
  525.         Returns a list of the original Path if there were no paths to remove
  526.         '''
  527.         #remove any duplicate separators from the path - sometimes there are double separators
  528.         while PATH_str.find(kSEPARATOR*2) != -1:
  529.                 PATH_str = PATH_str.replace(kSEPARATOR*2, kSEPARATOR)
  530.  
  531.         pathToks = PATH_str.split(kSEPARATOR)
  532.         #pathToks = map( Path, pathToks ) # make them all Path instances
  533.         pathToks = utils.removeDupes( pathToks ) # Remove duplicates...
  534.  
  535.         # so now what we want to do is remove all references to paths from other projects - we look
  536.         # for any and all paths that exist as
  537.         if gameConfig is None:
  538.                 gameConfig = GameConfigFile()
  539.  
  540.         toksToRemove = set()
  541.         for c in gameConfig.getProjectsCached().itervalues():
  542.                 # Use expanded paths to find all matches
  543.                 cPaths = c.getRequiredPathsExpanded()
  544.                 for p in PATHS.DEPRECATED[ c.getEngine() ]:
  545.                         p = p.replace('/', '\\')
  546.                         cPaths.append( os.path.expandvars( p ) )
  547.                 assert cPaths
  548.  
  549.                 for n, pathTok in enumerate( pathToks ):
  550.                         if os.path.expandvars( pathTok ) in cPaths:
  551.                                 toksToRemove.add( n )
  552.         if toksToRemove:
  553.                 toksToRemove = list( sorted( toksToRemove ) )
  554.                 toksToRemove.reverse()
  555.  
  556.                 for n in toksToRemove:
  557.                         pathToks.pop( n )      
  558.         else:
  559.                 pathToks = PATH_str.split( kSEPARATOR )
  560.         return pathToks
  561.  
  562. def sendEnvironmentUpdate():
  563.         '''
  564.         Tells Windows to update the environment.
  565.         '''
  566.         TIMEOUT = 150  #in milliseconds
  567.         win32gui.SendMessageTimeout( win32con.HWND_BROADCAST, win32con.WM_SETTINGCHANGE, 0, 'Environment', win32con.SMTO_ABORTIFHUNG, TIMEOUT )
  568.  
  569. def executablesInPath( paths, excludeList=[] ):
  570.         '''
  571.         Given a list of paths, return a list of .exe files in them, without duplicates.
  572.         Optionally give a list of processing to exclude in the check.
  573.         '''
  574.         exeList = []
  575.         for p in paths:
  576.                 try:
  577.                         for fname in os.listdir( Path( p ) ):
  578.                                 if ( fname.endswith( '.exe' ) ) and ( fname not in exeList ) and ( fname not in excludeList ):
  579.                                         exeList.append( fname )
  580.                 except WindowsError:
  581.                         pass
  582.         return exeList
  583.  
  584. TH32CS_SNAPPROCESS = 0x00000002
  585. class PROCESSENTRY32( ctypes.Structure ):
  586.         _fields_ = [( "dwSize", ctypes.c_ulong ),
  587.                     ( "cntUsage", ctypes.c_ulong ),
  588.                     ( "th32ProcessID", ctypes.c_ulong ),
  589.                     ( "th32DefaultHeapID", ctypes.c_ulong ),
  590.                     ( "th32ModuleID", ctypes.c_ulong ),
  591.                     ( "cntThreads", ctypes.c_ulong ),
  592.                     ( "th32ParentProcessID", ctypes.c_ulong ),
  593.                     ( "pcPriClassBase", ctypes.c_ulong ),
  594.                     ( "dwFlags", ctypes.c_ulong ),
  595.                     ( "szExeFile", ctypes.c_char * 260 )]
  596.  
  597. def isRunningProcess( *procs ):
  598.         '''
  599.         Returns a list of the given processes that are currently running.
  600.         '''
  601.         running = []
  602.         # If we got a list as an argument, just use that.
  603.         if isinstance( procs[0], list ):
  604.                 procs = procs[0]
  605.  
  606.         allProc = []
  607.         # See http://msdn2.microsoft.com/en-us/library/ms686701.aspx
  608.         hSnapProcess = ctypes.windll.kernel32.CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 )
  609.         ps32 = PROCESSENTRY32()
  610.         ps32.dwSize = ctypes.sizeof( PROCESSENTRY32 )
  611.         if ctypes.windll.kernel32.Process32First( hSnapProcess,
  612.                                                   ctypes.byref( ps32 ) ) == win32con.FALSE:
  613.                 raise WindowsError( 'Could not get a list of processes!' )
  614.         while True:
  615.                 allProc.append( ps32.szExeFile )
  616.                 if ctypes.windll.kernel32.Process32Next( hSnapProcess,
  617.                                                          ctypes.byref( ps32 ) ) == win32con.FALSE:
  618.                         break
  619.         ctypes.windll.kernel32.CloseHandle( hSnapProcess )
  620.  
  621.         for p in procs:
  622.                 if p in allProc:
  623.                         if p not in running:
  624.                                 running.append( p )
  625.         if running:
  626.                 return running
  627.         else:
  628.                 return False
  629.  
  630. def unSony_ify_PATH( envRegKey ):
  631.         '''
  632.         Fix up Sony paths
  633.         '''
  634.         # Get the actual PATH value and tokenize it
  635.         try:
  636.                 pathValue = QueryValueEx( envRegKey, 'PATH' )[ 0 ]
  637.                 # cast the path values to Path instances (makes comparing them easy)
  638.                 # but also store the original string to that env variable embedding is preserved
  639.                 paths = [ (Path( p ), p) for p in pathValue.split( ';' ) ]
  640.         except WindowsError:
  641.                 paths = None
  642.  
  643.         if paths:
  644.                 # constructs a list of tokens that if found in a path token, will cause the path to get demoted to the tail of the new PATH value
  645.                 demotionTokens = [ Path( r'C:\msys\1.0\bin' ) ]
  646.                 for envName in ('SCE_PS3_ROOT', 'SN_COMMON_PATH', 'SN_PS3_PATH'):
  647.                         try:
  648.                                 value, _tmp = QueryValueEx( envRegKey, envName )
  649.                         except WindowsError: pass
  650.                         else:
  651.                                 demotionTokens.append( Path( value ) )
  652.  
  653.                 #find the paths that contain demotion tokens
  654.                 pathsToDemote = []
  655.                 for tok in demotionTokens:
  656.                         toPop = []
  657.                         for n, (path, orgPath) in enumerate( paths ):
  658.                                 if path.isUnder( tok ):
  659.                                         pathsToDemote.append( (path, orgPath) )
  660.                                         toPop.append( n )
  661.  
  662.                         #remove the path values to demote from the paths list
  663.                         toPop.sort()
  664.                         for n in reversed( toPop ):
  665.                                 paths.pop( n )
  666.  
  667.                 #bump them to the end and set the new env var
  668.                 paths += pathsToDemote
  669.                 paths = [ p[1] for p in paths ]
  670.                 pathValue = ';'.join( paths )
  671.                 SetValueEx( envRegKey, 'PATH', 0, REG_EXPAND_SZ, pathValue )
  672.  
  673. def removeConfigVars( project=None, update=False, useSysKeys=True ):
  674.         '''
  675.         Removes any variables set by config, and cleans out the PATH variable.
  676.         If update is set to False, Windows is not notified of the changes, if you want to do it later.
  677.         '''
  678.         try:
  679.                 # Remove envars and clear PATHs
  680.                 deleteVars( useSysKeys=True )
  681.                 deleteVars( useSysKeys=False )
  682.                 deletePath( useSysKeys=True )
  683.                 deletePath( useSysKeys=False )
  684.         finally:       
  685.                 if update:
  686.                         sendEnvironmentUpdate()
  687.         return True
  688.  
  689.         #TODO:
  690.         # - report if data was removed, and what it was.
  691.  
  692. def readPickle( filePath ):
  693.         with open( filePath, 'r' ) as f:
  694.                 return pickle.load( f )
  695.  
  696. def getPythonPaths():
  697.         '''
  698.         Gets the current Python path and executable using a temporary file and returns
  699.         sys.path, sys.executable, PYTHONHOME as a list.
  700.         '''
  701.         # Create a temporary python script object
  702.         pyScriptFile = TempPythonFile()
  703.  
  704.         # Generate a temp filename for the output of the script
  705.         timestamp = strftime( "%H%M%S-%m%d%Y", localtime() )
  706.         tempPath = os.path.expandvars( '%temp%' )
  707.         tempFile = '{0}\\temp_{1}.txt'.format( tempPath, timestamp )
  708.                
  709.         # Code for the temp script     
  710.         code = """
  711. import sys, os
  712. from time import localtime, strftime
  713. import pickle
  714.  
  715. def writePicklePaths( filePath ):
  716.  
  717.         pList = []
  718.        
  719.         paths = []
  720.         for p in sys.path[1:]:
  721.                 paths.append( p )
  722.        
  723.         pList.append( paths )
  724.         pList.append( sys.executable )
  725.        
  726.         try:
  727.                 curPYTHONHOME = os.getenv( 'PYTHONHOME' )
  728.         except KeyError:
  729.                 curPYTHONHOME = False
  730.         pList.append( curPYTHONHOME )
  731.        
  732.         try:
  733.                 f = open( filePath, 'w' )
  734.                 pickle.dump( pList, f )
  735.                 f.close()
  736.         except:
  737.                 raise
  738.  
  739. writePicklePaths( '{0}' )
  740. """.format( tempFile.replace( '\\', '\\\\' ) )
  741.  
  742.         # Write the code and run it
  743.         try:
  744.                 pyScriptFile.setCode( code )
  745.                 pyScriptFile.writeCode()
  746.                
  747.                 # Check the SHA1 digest string to make sure we're running the same code
  748.                 # This should always be true if we've gotten this far.
  749.                 assert pyScriptFile.checkSHA1()
  750.                 pyScriptFile.run()
  751.                        
  752.         except ( WindowsError, ImportError ) as error:
  753.                 raise WindowsError
  754.         finally:
  755.                 pyScriptFile.delete()
  756.  
  757.         if os.path.exists( tempFile ):
  758.                 curSyspath, curPythonExe, curPYTHONHOME = readPickle( tempFile )
  759.                 os.remove( tempFile )
  760.                 return curSyspath, curPythonExe, curPYTHONHOME
  761.         else:
  762.                 return None, None, None
  763.        
  764. class TempPythonFile( file ):
  765.         '''
  766.         A simple file object to create semi-unique python files in the %temp% directory.
  767.         Filenames use the current timestamp and an optional prefix, ie. "%temp%\<prefix><timestamp>.py"
  768.         The checkSHA1() method can be used to verify the written file matches the generated code string.
  769.         '''
  770.         def __init__( self, prefix='tmp_' ):
  771.                
  772.                 timestamp = strftime( "%H%M%S-%m%d%Y", localtime() )
  773.                 self.filePath = os.path.expandvars( '%temp%\{0}{1}.py'.format( prefix, timestamp ) )
  774.                 self.codeStr = ''
  775.                 self.output = ''
  776.        
  777.                 super( file, self).__init__()
  778.  
  779.         def setCode( self, codeStr ):
  780.                 '''
  781.                 Set the string that will be used when the python file is run.
  782.                 '''
  783.                 self.codeStr = codeStr
  784.                 # calculate a new SHA1 string for checksum
  785.                 self.setSHA1digest()
  786.         def writeCode( self ):
  787.                 '''
  788.                 Write the current code string to the file.
  789.                 '''
  790.                 try:
  791.                         f = open( self.filePath, 'w' )
  792.                         f.write( self.codeStr )
  793.                         f.close()
  794.                 except IOError as e:
  795.                         raise IOError( 'Could not create temporary python file.\n\n{0}'.format( e ) )
  796.         def getFilename( self ):
  797.                 '''
  798.                 Get the autogenerated filename of the target file.
  799.                 '''
  800.                 return self.filePath
  801.         filename = property( getFilename )
  802.         def delete( self ):
  803.                 '''
  804.                 Delete the target temp script file.
  805.                 '''
  806.                 try:
  807.                         self.close()
  808.                         if os.path.exists( self.filePath ):
  809.                                 os.remove( self.filePath )
  810.                 except:
  811.                         pass
  812.         def run( self ):
  813.                 '''
  814.                 Run the temp script and wait for it to complete.
  815.                 '''
  816.                 subprocess.Popen( [ 'python', self.filename ], shell=False, env=os.environ ).wait()
  817.         def runPiped( self ):
  818.                 '''
  819.                 Run the script with a piped command prompt to retrieve the output
  820.                 This is a bit hacky -- due to how environments are inherited, it will only work properly if the environment is the same as the parent.
  821.                 '''
  822.                 startupinfo = subprocess.STARTUPINFO()
  823.                 startupinfo.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW             
  824.                 startupinfo.wShowWindow = win32con.SW_HIDE
  825.                 # TODO: test adding .wait() method to this line:
  826.                 process = subprocess.Popen( 'python.exe %s' % ( self.filename ), shell=False, startupinfo=startupinfo, stdout=subprocess.PIPE, creationflags=win32con.CREATE_NO_WINDOW )
  827.                 self.output = process.communicate()[0]
  828.  
  829.                 return self.output
  830.         def setSHA1digest( self ):
  831.                 '''
  832.                 Set the SHA1 digest string for the currently set code string.
  833.                 '''
  834.                 self.sha1 = hashlib.sha1( self.codeStr ).hexdigest()
  835.         def readSHA1digest( self ):
  836.                 '''
  837.                 Return a SHA1 digest string for the contents of the written file.
  838.                 '''
  839.                 try:
  840.                         return hashlib.sha1( file( self.filename, 'r' ).read() ).hexdigest()
  841.                 except IOError as e:
  842.                         raise IOError( 'Could not read temporary python file.\n\n{0}'.format( e ) )
  843.         def checkSHA1( self ):
  844.                 '''
  845.                 Compare the current SHA1 string to the SHA1 for the written file
  846.                 '''
  847.                 if self.sha1 == self.readSHA1digest():
  848.                         return True
  849.                 else:
  850.                         return False
  851. def listKeyValues( key ):
  852.         '''
  853.         lists all the values under a given key
  854.         '''
  855.         values = []
  856.         n=0
  857.         while True:
  858.                 try:
  859.                         values.append( EnumValue( key, n ) )
  860.                         n+=1
  861.                 except EnvironmentError:
  862.                         break
  863.         return values
  864.  
  865. class ProjectConfig( object ):
  866.         '''
  867.         All the configuration data for a single game project.
  868.         '''
  869.         def __init__( self, chunk ):
  870.                 self.__chunk = chunk
  871.                
  872.                 self.updateProjectSettings()
  873.  
  874.         @classmethod
  875.         def Create( cls, name, mod, gamePath, contentPath ):
  876.                 '''
  877.                 This is used to create a "naked" config object (with the default config data populated) instead of creating an object directly.
  878.                 '''
  879.                 if gamePath:
  880.                         game = Path( gamePath ).asfile()
  881.                         project = ( game + '\\' + mod ).resolve().asNative()
  882.                 else:
  883.                         project = ''
  884.                
  885.                 # Build the key value data
  886.                 gameChunk = Chunk( str(name), [] )
  887.                 gameChunk.append( Chunk( 'GameDir', str( project ), gameChunk ) )
  888.  
  889.                 # Now turn the new chunk into a projectConfig object, and set the data
  890.                 projConfig = cls( gameChunk )
  891.                 projConfig.setName( name )
  892.                
  893.                 projConfig.setContent( Path( contentPath ).asfile() )
  894.                                
  895.                 return projConfig
  896.         @classmethod
  897.         def CreateFromGameInfo( cls, gameInfoPath ):
  898.                 '''
  899.                 Sets up a project object by reading the contents of a gameInfo file
  900.                 '''
  901.                 try:
  902.                         gameInfo = GameInfoFile( gameInfoPath.asNative() )
  903.                 except TypeError:
  904.                         raise GameInfoException( 'The selected gameinfo file is not valid.' )
  905.  
  906.                 try:
  907.                         name = gameInfo.game.value
  908.                 except AttributeError:
  909.                         raise GameInfoException( 'A valid "name" keyvalue could not be found in the given gameinfo file.' )
  910.  
  911.                 mod = gameInfoPath.up().name()
  912.                 gamePath = gameInfoPath.up( 2 )
  913.                 contentPath = gameInfoPath.up( 3 ) + r'\content'
  914.  
  915.                 proj = ProjectConfig.Create( name, mod, gamePath, contentPath )
  916.  
  917.                 try:
  918.                         if gameInfo.Engine2.Capable64Bit.value == '1':
  919.                                 proj.setRunIn32Bit( supported=False )
  920.                 except AttributeError:
  921.                         pass
  922.  
  923.                 try:
  924.                         proj.setEngine( gameInfo.ToolsEnvironment.Engine.value )
  925.                 except AttributeError:
  926.                         pass
  927.  
  928.                 try:
  929.                         # Override the calculated default tools directory with the one from
  930.                         # the GameInfo file if it's present
  931.                         proj.setToolsChunk( gamePath + mod + gameInfo.ToolsEnvironment.ToolsDir.value )
  932.                 except AttributeError:
  933.                         pass
  934.  
  935.                 # add the engine and platform to the friendly name
  936.                 if proj.getRunIn32Bit():
  937.                         proj.setName( '{0}'.format( name ) )
  938.                 else:
  939.                         proj.setName( '{0} ({1})'.format( name, proj.getPlatform() ) )
  940.  
  941.                 return proj
  942.         def __str__( self ):
  943.                 return self.getName()
  944.         __repr__ = __str__
  945.         def getChunk( self ):
  946.                 return self.__chunk
  947.         def getName( self ):
  948.                 '''
  949.                 Get the friendly name of the project config. This is the name that shows up in the vprojUI.
  950.                 '''
  951.                 return self.getChunk().key
  952.         def setName( self, name ):
  953.                 '''
  954.                 sets this config's name to a given value - does a uniqeness check on the name, and appends
  955.                 '_copy' until the name is unique
  956.                 '''
  957.  
  958.                 # Grab the parent - if it exists
  959.                 gameChunk = self.getChunk().parent
  960.  
  961.                 # Try to ensure uniqueness - this may not be possible to do, as it is possible to create a Config object that doesn't
  962.                 # belong to a GameConfigFile (ie the Config object has no parent Chunk). If the object has no parent, the gameChunk
  963.                 # is None - hence the try statement.
  964.                 try:
  965.                         names = set( [ProjectConfig(c).getName() for c in gameChunk if c is not self] )
  966.                         names.remove( self.getName() )
  967.                         while name in names:
  968.                                 name += '_copy'
  969.                 except TypeError: pass
  970.  
  971.                 self.getChunk().key = name
  972.                 self.name = name
  973.         def getProjectProfile( self ):
  974.                 '''
  975.                 Returns a list of basic project attributes useful for comparasions
  976.                 '''
  977.                 try:
  978.                         profile = [ self.vMod,
  979.                                     os.path.normpath( self.vGame.asNative() ),
  980.                                     os.path.normpath( self.vContent.asNative() ),
  981.                                     os.path.normpath( self.vTools.asNative() ),
  982.                                     self.platform
  983.                                     ]
  984.                 except ( AttributeError, ValueError, PathError ):
  985.                         return None
  986.                 try:
  987.                         profile.append( os.path.normpath( self.getPYTHONHOME() ) )
  988.                 except:
  989.                         profile.append( None )
  990.                 try:
  991.                         profile.append( os.path.normpath( self.getMAYA_SCRIPT_PATH() ) )
  992.                 except:
  993.                         profile.append( None )
  994.                        
  995.                 return profile
  996.                            
  997.         def getMod( self ):
  998.                 '''
  999.                 Get the mode name, i.e. %VMOD%
  1000.                 '''
  1001.                 return self.getProjectDir().name()
  1002.         def setMod( self, mod ):
  1003.                 '''
  1004.                 Set the mod name, i.e. %VMOD%
  1005.                 '''
  1006.                 self.setProjectDir( self.getGame() + '\\' + mod )
  1007.                 self.vMod = mod
  1008.         def getGame( self ):
  1009.                 '''
  1010.                 Get the game directory Path, i.e. %VGAME%
  1011.                 '''
  1012.                 return self.getProjectDir().up()
  1013.         def setGame( self, path ):
  1014.                 '''
  1015.                 Set the game directory path, i.e. %VGAME%
  1016.                 '''
  1017.                 path = Path( path )
  1018.                 mod = self.getMod()
  1019.                 vproject = '{0}\\{1}'.format( path, mod )
  1020.                 self.setProjectDir( vproject )
  1021.                 self.vGame = path
  1022.         def getContent( self ):
  1023.                 '''
  1024.                 Get the content directory Path, i.e. %VCONTENT%
  1025.                 '''
  1026.                 try:
  1027.                         return Path( self.getChunk().ContentDir.value )
  1028.                 except AttributeError:
  1029.                         try:
  1030.                                 return self.getGame().replace('game', 'content')
  1031.                         except ValueError: return self.getGame()
  1032.         def setContent( self, path ):
  1033.                 '''
  1034.                 Set the content directory Path, i.e. %VCONTENT%
  1035.                 '''
  1036.                 path = Path(path)
  1037.                 pathStr = path.resolve().asNative()
  1038.  
  1039.                 try:
  1040.                         self.getChunk().ContentDir.value = pathStr
  1041.                         self.vContent = pathStr
  1042.                 except AttributeError:
  1043.                         c = Chunk( 'ContentDir', pathStr )
  1044.                         self.getChunk().append( c )
  1045.         def getPlatform( self ):
  1046.                 '''
  1047.                 Return a string with the current platform. If invalid, assume win32.
  1048.                 '''
  1049.                 try:
  1050.                         return PLATFORM.BY_INDEX[ self.getRunIn32Bit() ]
  1051.                 except KeyError:
  1052.                         return PLATFORM.WIN_32_SUFFIX
  1053.         def getDefaultToolsDir( self, engine=None ):
  1054.                 '''
  1055.                 Try to determine the default tools path.
  1056.                 '''
  1057.                 try:
  1058.                         if not engine:
  1059.                                 engine = self.getEngine()
  1060.                         if  ( engine in ( ENGINE.SOURCE2, ENGINE.HYBRID, ENGINE.SFM ) ):
  1061.                                 return Path( self.getGame() + r'\sdktools' )
  1062.                         else:
  1063.                                 # First try using location of vproj
  1064.                                 exeTools = Path( sys.executable ).up( 2 ) + 'tools'
  1065.                                 if os.path.exists( exeTools ):
  1066.                                         return Path( exeTools )
  1067.                                
  1068.                                 # Initial guess
  1069.                                 initialTools = Path( self.getGame().up( 2 ) + r'\tools' )
  1070.                                 if initialTools.exists:
  1071.                                         return initialTools
  1072.                                 else:
  1073.                                         # If the root is valid, try going up from gamedir until we find an existing one
  1074.                                         toolTest = self.vGame.up() + r'\tools'
  1075.                                         if os.path.exists( toolTest.split()[0] ):
  1076.                                                 while toolTest != toolTest.split()[0]:
  1077.                                                         toolTest = toolTest.up()
  1078.                                                         if ( toolTest + r'\tools\bin\vproj.exe' ).exists:
  1079.                                                                 return toolTest + r'\tools'
  1080.                                         # Last try deriving it using the perforce workspace
  1081.                                         wsTools = self.getToolsFromP4Workspace()
  1082.                                         if wsTools:
  1083.                                                 return Path( wsTools )
  1084.                                         else:
  1085.                                                 # Give up and return the default
  1086.                                                 return initialTools
  1087.                 except AttributeError:
  1088.                         return kTOOLS_DIR
  1089.  
  1090.         def getToolsFromP4Workspace( self, workspace=None ):
  1091.                 '''
  1092.                 Return the tools path extracted from the workspace for the current project
  1093.                 Return None if is not present or couldn't be determined.
  1094.                 '''
  1095.                 # First determine the workspace
  1096.                 if not workspace:
  1097.                         # First check for a stored workspace in the project
  1098.                         workspace = self.getVersionControlWorkspace()
  1099.                         if not workspace:
  1100.                                 # Next try finding it in the closest p4config file
  1101.                                 p4CfgLoc = self.findP4Config()
  1102.                                 if p4CfgLoc:
  1103.                                         p4Cfg = P4ConfigFile( p4CfgLoc )
  1104.                                         p4Cfg.read()
  1105.                                         workspace = p4Cfg.P4CLIENT
  1106.                 if not workspace:
  1107.                         # workspace couldn't be determined.
  1108.                         return None
  1109.                
  1110.                 p4 = P4.P4()
  1111.                 try:
  1112.                         p4.connect()
  1113.                         ws = p4.fetch_client( workspace )
  1114.                 except P4.P4Exception:
  1115.                         return None
  1116.                 localTools = None
  1117.                 for line in ws[ 'View' ]:
  1118.                         if line.startswith( '//ValveGames/tools/...' ):
  1119.                                 toolsMap = line.split( ' ' )[1]
  1120.                                 # Construct a local version of the directory
  1121.                                 root = os.path.normpath( ws[ 'Root' ] ) + '\\'
  1122.                                 localTools = toolsMap.replace( '//{0}/'.format( ws[ 'Client' ] ), root ).rstrip( '/...' )
  1123.                 return localTools
  1124.         def getTools( self ):
  1125.                 '''
  1126.                 Get the tools directory Path, i.e. %VTOOLS% from the project config,
  1127.                 or try to determine it if it isn't present there.
  1128.                 '''
  1129.                 # First look for existing value
  1130.                 tools = self.getToolsChunk()           
  1131.                 if not tools:
  1132.                         # If not, get a new default and save it
  1133.                         tools = self.getDefaultToolsDir()
  1134.                         self.setToolsChunk( tools )
  1135.                 return tools
  1136.         def getToolsChunk( self ):
  1137.                 '''
  1138.                 Get the calculated tools directory stored in the project config.
  1139.                 '''
  1140.                 try:
  1141.                         return Path( self.getChunk().Tools.value )
  1142.                 except AttributeError:
  1143.                         return None
  1144.         def getToolsDirOverrideChunk( self ):
  1145.                 '''
  1146.                 Get the override tools directory Path, i.e. %VTOOLS% from the project config,
  1147.                 '''
  1148.                 try:
  1149.                         return Path( self.getChunk().ToolsDirOverride.value )
  1150.                 except AttributeError:
  1151.                         # Next try the old variable version
  1152.                         try:
  1153.                                 return Path( self.getChunk().ToolsDir.value )
  1154.                         except AttributeError:
  1155.                                 return None
  1156.         def setToolsDirOverrideChunk( self, path ):
  1157.                 '''
  1158.                 Set the tools directory manual override.
  1159.                 '''
  1160.                 try: path = path.resolve().asNative()
  1161.                 except AttributeError: pass
  1162.  
  1163.                 # First remove the old variable
  1164.                 try:
  1165.                         t = self.getChunk().ToolsDir
  1166.                         t.parent.value.remove( t )
  1167.                 except AttributeError:
  1168.                         pass
  1169.  
  1170.                 try:
  1171.                         self.getChunk().ToolsDirOverride.value = str( path )
  1172.                 except AttributeError:
  1173.                         c = Chunk( 'ToolsDirOverride', str( path ) )
  1174.                         self.getChunk().append( c )
  1175.         def setToolsChunk( self, path ):
  1176.                 '''
  1177.                 Set the calculated tools directory Path, i.e. %VTOOLS%
  1178.                 '''
  1179.                 try: path = path.resolve().asNative()
  1180.                 except AttributeError: pass
  1181.                                
  1182.                 # Now add the new one
  1183.                 try:
  1184.                         self.getChunk().Tools.value = str( path )
  1185.                         self.vTools = path
  1186.                 except AttributeError:
  1187.                         c = Chunk( 'Tools', str( path ) )
  1188.                         self.getChunk().append( c )
  1189.         def removeToolsDirOverrideChunk( self ):
  1190.                 '''
  1191.                 Delete the ToolsDir Chunk from the project
  1192.                 '''
  1193.                 try:
  1194.                         t = self.getChunk().ToolsDirOverride
  1195.                         t.parent.value.remove( t )
  1196.                 except AttributeError:
  1197.                         pass
  1198.  
  1199.         def getProjectDir( self ):
  1200.                 '''
  1201.                 Get the project directory Path, i.e. %VPROJECT%
  1202.                 '''
  1203.                 projDir = Path( self.getChunk().GameDir.value )
  1204.                 if projDir == '///':
  1205.                         # If directory is empty, return empty Path
  1206.                         return Path( '' )
  1207.                 else:
  1208.                         return projDir
  1209.         def setProjectDir( self, path ):
  1210.                 '''
  1211.                 Set the project directory Path, i.e. %VPROJECT%
  1212.                 '''
  1213.                 path = Path(path)
  1214.                 pathStr = path.resolve().asNative()
  1215.  
  1216.                 self.getChunk().GameDir.value = pathStr
  1217.         def getVersionControlWorkspace( self ):
  1218.                 '''
  1219.                 Get the version control (Perforce) workspace associated with the project
  1220.                 Returns None if no workspace is set for the project.
  1221.                 '''
  1222.                 try:
  1223.                         return self.getChunk().VerControlWorkspace.value
  1224.                 except AttributeError:
  1225.                         return None
  1226.         def setVersionControlWorkspace( self, workspace ):
  1227.                 '''
  1228.                 Set the version control (Perforce) workspace associated with the project
  1229.                 '''
  1230.                 if workspace:
  1231.                         try:
  1232.                                 self.getChunk().VerControlWorkspace.value = str( workspace )
  1233.                         except AttributeError:
  1234.                                 c = Chunk( 'VerControlWorkspace', str( workspace ) )
  1235.                                 self.getChunk().append( c )
  1236.                 # If None, remove it from the config
  1237.                 else:
  1238.                         try:
  1239.                                 v = self.getChunk().VerControlWorkspace
  1240.                                 v.parent.value.remove( v )
  1241.                         except AttributeError:
  1242.                                 pass
  1243.         def findP4Config( self ):
  1244.                 '''
  1245.                 Look for a Perforce configuration file relative to the project and return the filepath.
  1246.                 '''
  1247.                 p4cfg = checkp4Environ( 'p4config' )
  1248.                 if not p4cfg:
  1249.                         p4cfg = 'p4config'
  1250.                 p4cfgDir = self.getProjectDir().asNative()
  1251.                 rootDir = p4cfgDir.split( '\\' )[0]
  1252.                 p4cfgFile = '{0}\\{1}'.format( p4cfgDir, p4cfg )
  1253.                
  1254.                 if os.path.exists( p4cfgFile ):
  1255.                         return p4cfgFile
  1256.                 else:
  1257.                         # Now go up the until we find an existing cfg file
  1258.                         while p4cfgDir != rootDir:
  1259.                                 p4cfgDir = '\\'.join( p4cfgDir.split( '\\' )[:-1] )
  1260.                                 p4cfgFile = '{0}\\{1}'.format( p4cfgDir, p4cfg )
  1261.                                 if os.path.exists( p4cfgFile ):
  1262.                                         return p4cfgFile
  1263.                 return False
  1264.                        
  1265.         def getRunIn32Bit( self ):
  1266.                 '''
  1267.                 Get the RunIn32Bit value, and return 1 (win32) if it doesn't exist at all.
  1268.                 This is used to set various paths to the correct binaries.
  1269.                 '''
  1270.                 try:
  1271.                         return int( self.getChunk().RunIn32Bit.value )
  1272.                 except AttributeError:
  1273.                         return 1
  1274.         def setRunIn32Bit( self, supported=False ):
  1275.                 '''
  1276.                 Set whether the project should run in 32-bit. This is used to set various paths to the correct binaries.
  1277.                 '''
  1278.                 try:
  1279.                         self.getChunk().RunIn32Bit.value = int( supported )
  1280.                 except AttributeError:
  1281.                         c = Chunk( 'RunIn32Bit', int( supported ) )
  1282.                         self.getChunk().append( c )
  1283.         def set_use_NT_SYMBOL_PATH( self, supported=True ):
  1284.                 '''
  1285.                 Set whether the _NT_SYMBOL_PATH option should be use for the project. Default is False.
  1286.                 '''
  1287.                 try:
  1288.                         self.getChunk().use_NT_SYMBOL_PATH.value = int( supported )
  1289.                 except AttributeError:
  1290.                         c = Chunk( 'use_NT_SYMBOL_PATH', int( supported ) )
  1291.                         self.getChunk().append( c )
  1292.         def get_use_NT_SYMBOL_PATH( self ):
  1293.                 '''
  1294.                 Get whether the _NT_SYMBOL_PATH option should be use for the project. Returns False if it doesn't exist.
  1295.                 '''
  1296.                 try:
  1297.                         return int( self.getChunk().use_NT_SYMBOL_PATH.value )
  1298.                 except AttributeError:
  1299.                         return False
  1300.         def remove_use_NT_SYMBOL_PATH( self ):
  1301.                 '''
  1302.                 Delete the NT_SYMBOL_PATH Chunk from the project
  1303.                 '''
  1304.                 try:
  1305.                         c = self.getChunk().use_NT_SYMBOL_PATH
  1306.                         c.parent.value.remove( c )
  1307.                 except AttributeError:
  1308.                         pass
  1309.         def getMAYA_SCRIPT_PATH( self ):
  1310.                 '''
  1311.                 Returns the MAYA_SCRIPT_PATH override, or the default value for the project
  1312.                 '''
  1313.                 override = self.getMAYA_SCRIPT_PATHOverride()
  1314.                 if override:
  1315.                         return override
  1316.                 else:
  1317.                         maya_script_path = self.cleanEnvarPath( ENVARS.getCombined( self.engine )[ 'MAYA_SCRIPT_PATH' ][0] )
  1318.                         return Path( maya_script_path ).asNative()
  1319.         def getMAYA_SCRIPT_PATHOverride( self ):
  1320.                 '''
  1321.                 Returns a Path for the custom MAYA_SCRIPT_PATH for the project. Returns False if it doesn't exist.
  1322.                 '''
  1323.                 try:
  1324.                         return Path( self.getChunk().MAYA_SCRIPT_PATH.value ).unresolved()
  1325.                 except AttributeError:
  1326.                         return False
  1327.         def setMAYA_SCRIPT_PATHOverride( self, mayaPath ):
  1328.                 '''
  1329.                 Set a custom MAYA_SCRIPT_PATH envar for the project.
  1330.                 '''
  1331.                 try:
  1332.                         self.getChunk().MAYA_SCRIPT_PATH.value = str( mayaPath )
  1333.                 except AttributeError:
  1334.                         c = Chunk( 'MAYA_SCRIPT_PATH', str( mayaPath ) )
  1335.                         self.getChunk().append( c )
  1336.         def removeMAYA_SCRIPT_PATHOverride( self ):
  1337.                 '''
  1338.                 Delete the MAYA_SCRIPT_PATH Chunk from the project
  1339.                 '''
  1340.                 try:
  1341.                         c = self.getChunk().MAYA_SCRIPT_PATH
  1342.                         c.parent.value.remove( c )
  1343.                 except AttributeError:
  1344.                         pass
  1345.         def getPYTHONHOME( self ):
  1346.                 '''
  1347.                 Returns the PYTHONHOME override, or the default value
  1348.                 '''
  1349.                 override = self.getPYTHONHOMEOverride()
  1350.                 if override:
  1351.                         return override                        
  1352.                 else:
  1353.                         try:
  1354.                                 pythonhome = self.cleanEnvarPath( ENVARS.getCombined( self.engine )[ 'PYTHONHOME' ][0] )
  1355.                                 return Path( pythonhome ).asNative()
  1356.                         except KeyError:
  1357.                                 return None
  1358.  
  1359.         def getPYTHONHOMEOverride( self ):
  1360.                 '''
  1361.                 Returns a Path for the custom PYTHONHOME for the project. Returns False if it doesn't exist.
  1362.                 '''
  1363.                 try:
  1364.                         return Path( self.getChunk().PYTHONHOME.value ).unresolved()
  1365.                 except AttributeError:
  1366.                         return False
  1367.         def setPYTHONHOMEOverride( self, pythonPath ):
  1368.                 '''
  1369.                 Set a custom PYTHONHOME envar that should be used for the project.
  1370.                 '''
  1371.                 try:
  1372.                         self.getChunk().PYTHONHOME.value = str( pythonPath )
  1373.                 except AttributeError:
  1374.                         c = Chunk( 'PYTHONHOME', str( pythonPath ) )
  1375.                         self.getChunk().append( c )
  1376.         def removePYTHONHOMEOverride( self ):
  1377.                 '''
  1378.                 Delete the PYTHONHOME Chunk from the project
  1379.                 '''
  1380.                 try:
  1381.                         c = self.getChunk().PYTHONHOME
  1382.                         c.parent.value.remove( c )
  1383.                 except AttributeError:
  1384.                         pass
  1385.         def getEngine( self ):
  1386.                 '''
  1387.                 Return an str with the current engine version.
  1388.                 If key doesn't doesn't exist, assume 'Source', otherwise invalid -- assume next-gen 'Source 2'.
  1389.                 '''
  1390.                 try:
  1391.                         eng = unicode( self.getChunk().Engine.value )
  1392.                         if eng not in ENGINE.ALL_ENGINES:
  1393.                                 return ENGINE.SOURCE2
  1394.                         return eng
  1395.                 except ( AttributeError, ValueError ):
  1396.                         try:
  1397.                                 # HACK: check vproject for 'source2' or 'hybrid' and guess the engine.
  1398.                                 projDir = str.lower( self.getProjectDir() )
  1399.                                 if 'source2' in projDir:
  1400.                                         return ENGINE.SOURCE2
  1401.                                 elif ( 'hybrid' in projDir ) or ( 'l4d2port' in projDir ):
  1402.                                         return ENGINE.HYBRID
  1403.                                 else:
  1404.                                         return ENGINE.SOURCE
  1405.                         except AttributeError:
  1406.                                 return ENGINE.SOURCE
  1407.         def setEngine( self, version=ENGINE.SOURCE ):
  1408.                 '''
  1409.                 Set the engine version for the project, i.e. 'Source 2'
  1410.                 '''
  1411.                 try:
  1412.                         self.getChunk().Engine.value = version
  1413.                         self.engine = version
  1414.                 except AttributeError:
  1415.                         c = Chunk( 'Engine', version )
  1416.                         self.getChunk().append( c )
  1417.         def getRequiredPaths( self ):
  1418.                 '''
  1419.                 Get a list of the required paths for a project
  1420.                 '''
  1421.                 engine = self.getEngine()
  1422.                 return PATHS.BY_ENGINE[ engine ]
  1423.         def getRequiredPathsExpanded( self ):
  1424.                 '''
  1425.                 Get a list of the required paths for a project, with expanded envars
  1426.                 '''
  1427.                 expPaths = []
  1428.                 for p in self.getRequiredPaths():
  1429.                         # Expand using the project data first
  1430.                         p = self.expandEnvarPath( p )
  1431.                         # Then append the path using system environment
  1432.                         expPaths.append( os.path.expandvars( p ) )
  1433.                 return expPaths
  1434.         def getOldEnvVars( self ):
  1435.                 '''
  1436.                 Returns a list of deprecated envars for this project
  1437.                 '''
  1438.                 depEnvars = ENVARS.DEPRECATED[ ENGINE.ALL ]
  1439.                 try:
  1440.                         depEnvars.extend( ENVARS.DEPRECATED[ self.getEngine() ] )
  1441.                 except KeyError:
  1442.                         pass
  1443.                
  1444.                 return depEnvars
  1445.         def checkForOldVars( self, useSysKeys=True ):
  1446.                 '''
  1447.                 Returns a list of deprecated envars for this project
  1448.                 '''
  1449.                 oldVars = []
  1450.  
  1451.                 for var in self.getOldEnvVars():
  1452.                         if getEnv( var ):
  1453.                                 oldVars.append( var )
  1454.                 if oldVars: return oldVars
  1455.  
  1456.                 return False
  1457.         def deleteOldVars( self, useSysKeys=True ):
  1458.                 '''
  1459.                 Removes deprecated environment variables for this project and returns a list of deleted vars.
  1460.                 '''
  1461.                 varList = self.checkForOldVars()
  1462.                 deletedVars = []
  1463.  
  1464.                 for var in varList:
  1465.                         try:
  1466.                                 deleteEnv( var, useSysKeys=useSysKeys )
  1467.                                 deletedVars.append( var )
  1468.                         except WindowsError: pass
  1469.                 return deletedVars
  1470.  
  1471.         def createHammerData( self ):
  1472.                 '''
  1473.                 creates a hammer data chunk - most of the data in this chunk gets populated by hammer on startup, but the one thing that
  1474.                 hammer doesn't seem to find is fgd files.  so try to create a sensible fgd file to point hammer to
  1475.                 '''
  1476.                 #build the hammer data chunk
  1477.                 hammerChunk = Chunk( 'Hammer', [] )
  1478.                 self.getChunk().append( hammerChunk )
  1479.  
  1480.                 #walk up the mod hierarchy and look for an fgd that has the same name as the mod name and use it
  1481.                 fgdDir = self.getGame() + r'\bin'
  1482.  
  1483.                 engine = self.getEngine()
  1484.                 if ( engine == ENGINE.SOURCE2 ):
  1485.                         configGameInfo = self.getProjectDir() + r'\gameInfo.gi'
  1486.                 else:
  1487.                         configGameInfo = self.getProjectDir() + r'\GameInfo.txt'                       
  1488.  
  1489.                 if configGameInfo.exists:
  1490.                         configGameInfo = GameInfoFile( configGameInfo )
  1491.                         for mod in configGameInfo.getSearchMods():
  1492.                                 modFgd = ( fgdDir + '\\' + mod ).setExtension( 'fgd' )
  1493.                                 if modFgd.exists:
  1494.                                         hammerChunk.append( Chunk( 'GameData0', str( modFgd ) ) )
  1495.                                         return
  1496.                 #if there is still no fgd found, then add the base fgd file
  1497.                 hammerChunk.append( Chunk( 'GameData0', str( fgdDir + r'\base.fgd' ) ) )
  1498.  
  1499.         def verifyProject( self ):
  1500.                 '''
  1501.                 Some sanity checks to make sure a config is valid. Returns a list of missing files.
  1502.                 '''
  1503.                 game = self.getGame()
  1504.                 engine = self.getEngine()
  1505.                 platform = self.getPlatform()
  1506.                 tools = self.getTools()
  1507.  
  1508.                 if engine in ENGINE.SOURCE2_FAMILY:
  1509.                         requiredFiles = [ game + r'\bin\{0}\tier0.dll'.format( platform ),
  1510.                                           game + r'\platform\Resource\platform_english.txt',
  1511.                                           game + r'\sdktools\perl\bin\perl.exe',
  1512.                                           game + r'\sdktools\python\{0}\{1}\python.exe'.format( PY_VERSION, platform ) ]
  1513.                         requiredLocsToSync = [ game + r'\bin\{0}'.format( platform ),
  1514.                                                game + r'\platform',
  1515.                                                game + r'\sdktools',
  1516.                                                game + r'\sdktools' ]
  1517.                 elif engine == ENGINE.SFM:
  1518.                         requiredFiles = [ game + r'\bin\tier0.dll',
  1519.                                           game + r'\platform\Resource\platform_english.txt',
  1520.                                           game + r'\sdktools\perl\bin\perl.exe',
  1521.                                           game + r'\sdktools\python\{0}\{1}\python.exe'.format( PY_VERSION, platform ) ]
  1522.                         requiredLocsToSync = [ game + r'\bin',
  1523.                                                game + r'\platform',
  1524.                                                game + r'\sdktools',
  1525.                                                game + r'\sdktools' ]                   
  1526.                 else:
  1527.                         requiredFiles = [ game + r'\bin\tier0.dll',
  1528.                                           game + r'\platform\Resource\platform_english.txt',
  1529.                                           game + r'\sdktools\perl\bin\perl.exe',
  1530.                                           tools + r'\bin\python.exe' ]
  1531.                         requiredLocsToSync = [ game + r'\bin',
  1532.                                                game + r'\platform',
  1533.                                                game + r'\sdktools',
  1534.                                                tools + r'\bin' ]                       
  1535.                 missing = []
  1536.                 for f, syncLoc in zip( requiredFiles, requiredLocsToSync ):
  1537.                         if not f.exists:
  1538.                                 missing.append( (f, syncLoc) )
  1539.                 return missing
  1540.  
  1541.         def verifyToolsDir( self, tPath=None ):
  1542.                 '''
  1543.                 Do a simple verification if the tools directory will be set to a valid location
  1544.                 '''
  1545.                 if not tPath:
  1546.                         tPath = self.getTools()
  1547.                 engine = self.getEngine()
  1548.                
  1549.                 # First check if it even exists
  1550.                 if not os.path.exists( tPath ):
  1551.                         return False
  1552.                
  1553.                 # Now check to see if vproj.exe is there
  1554.                 return os.path.exists( '{0}\\bin\\vproj.exe'.format( tPath ) )
  1555.        
  1556.         def authorPATHstr( self, PATH_str=None ):
  1557.                 '''
  1558.                 Generates a new PATH string, removing old paths adding the required paths.
  1559.                 '''
  1560.                 pathToks = []
  1561.                 if PATH_str:
  1562.                         pathToks = removeProjectPathsFromPATH( PATH_str )
  1563.                 else:
  1564.                         # Use the default path
  1565.                         pathToks = removeProjectPathsFromPATH( getEnv( 'PATH' ) )
  1566.  
  1567.                 requiredPaths = self.getRequiredPaths()
  1568.                 engine = self.getEngine()
  1569.                 newPath = []
  1570.                 for p in requiredPaths:
  1571.                         p_exp = self.expandEnvarPath( p )
  1572.                         newPath.append( p )
  1573.                         #try:
  1574.                                 ## generate path with project envars expanded
  1575.                                 #p_exp = self.expandEnvarPath( p )
  1576.                                 ## only add paths that exist
  1577.                                 #if os.path.exists( p_exp ):
  1578.                                         #newPath.append( p )
  1579.                                 #else:
  1580.                                         ## HACK: Source1 vproj would add even non-existant paths, so we'll do that here
  1581.                                        
  1582.                                         #if ( engine in ENGINE.SOURCE_FAMILY ) or ( engine == ENGINE.HYBRID ):
  1583.                                                 #newPath.append( p )
  1584.                         ## skip any paths that don't validate properly
  1585.                         #except PathError:
  1586.                                 #pass
  1587.  
  1588.                 # now add the required paths
  1589.                 pathToks = newPath + pathToks
  1590.  
  1591.                 # TODO: check that it's always unresolved paths going in here
  1592.                 return kSEPARATOR.join( [ p for p in pathToks ] ).replace('/', '\\')
  1593.        
  1594.         def expandEnvarPath( self, path ):
  1595.                 '''
  1596.                 Expand all known envars in a path string with project data
  1597.                 '''
  1598.                 for e in self.envars:
  1599.                         path = path.replace( '%{0}%'.format( e ), self.envars[ e ].value )
  1600.                 return path
  1601.        
  1602.         def cleanEnvarPath( self, path ):
  1603.                 '''
  1604.                 Replaces known path tokens in a path
  1605.                 '''
  1606.                 path = path.replace( '<game>', self.vGame )
  1607.                 path = path.replace( '<content>', self.vContent )
  1608.                 path = path.replace( '<mod>', self.vMod )
  1609.                 path = path.replace( '<platform>', self.platform )
  1610.                 path = path.replace( '<tools>', self.vTools )
  1611.                 path = path.replace( "/", "\\" )
  1612.                 # TODO: Support UNC paths?
  1613.                 return path
  1614.                
  1615.         def updateProjectSettings( self, useSysKeys=True ):
  1616.                 '''
  1617.                 Adds various settings as attributes to the project.
  1618.                 '''
  1619.                 self.name = self.getName()
  1620.                 self.platform = self.getPlatform()
  1621.                 self.engine = self.getEngine()
  1622.                 self.vGame = self.getGame()
  1623.                 self.vMod = self.getMod()
  1624.                 self.vContent = self.getContent()
  1625.                 self.vTools = self.getTools()
  1626.  
  1627.                 # Set defaults for miscellaneous settings
  1628.                 self.setPythonInstallKeys = True
  1629.                 self.setPythonFileAssociations = True
  1630.                 self.setPerlFileAssociations = True
  1631.                 self.setPerlPATHEXT = True
  1632.  
  1633.                 # Set P4CONFIG if it is not already defined or set to something other than the default:
  1634.                 self.setP4CONFIG =  ( not checkp4Environ( 'p4config' ) and  \
  1635.                                          ( getP4RegistryVar( 'p4config' ) != kP4CONFIG )
  1636.                                          )
  1637.                 # Set P4PORT if it is not already defined or if it is set to the default perforce value:
  1638.                 self.setP4PORT =  ( not checkp4Environ( 'p4port' ) or  \
  1639.                                          ( getP4RegistryVar( 'p4port' ) == 'perforce:1666' )
  1640.                                          )
  1641.                
  1642.                 self.envars = {}
  1643.                 # Add envars for this project
  1644.                 envars = ENVARS.getCombined( self.engine )
  1645.                 for e in envars.iterkeys():
  1646.                         # Clean the path for envars with the flag set
  1647.                         if envars[ e ][ 2 ]:
  1648.                                 value = self.cleanEnvarPath( envars[ e ][ 0 ] )
  1649.                         else:
  1650.                                 value =  envars[ e ][ 0 ]
  1651.                         setting = EnvarSetting( e, value, envars[ e ][ 1 ], self.engine )
  1652.                         # Variable overrides
  1653.                         if self.getMAYA_SCRIPT_PATHOverride() and setting.name == 'MAYA_SCRIPT_PATH':
  1654.                                 setting.value = os.path.normpath( self.getMAYA_SCRIPT_PATHOverride() )
  1655.                         if self.getPYTHONHOMEOverride() and setting.name == 'PYTHONHOME':
  1656.                                 setting.value = os.path.normpath( self.getPYTHONHOMEOverride() )
  1657.                         self.envars[ setting.name ] = setting
  1658.                 ## Select matching engine-specific envars for this project
  1659.                 #for e in ENVARS.ADD.iterkeys():
  1660.                         #if self.engine in ENVARS.ADD[ e ].keys():
  1661.                                 #setting = EnvarSetting( e, self.cleanEnvarPath( ENVARS.ADD[ e ][ self.engine ][0] ), ENVARS.ADD[ e ][ self.engine ][ 1 ], self.engine )
  1662.                                 ## Variable overrides
  1663.                                 #if self.getMAYA_SCRIPT_PATHOverride() and setting.name == 'MAYA_SCRIPT_PATH':
  1664.                                         #setting.value = os.path.normpath( self.getMAYA_SCRIPT_PATHOverride() )
  1665.                                 #if self.getPYTHONHOMEOverride() and setting.name == 'PYTHONHOME':
  1666.                                         #setting.value = os.path.normpath( self.getPYTHONHOMEOverride() )
  1667.                                 #self.envars[ setting.name ] = setting
  1668.                 # Select matching deprecated envars for this project
  1669.                 self.envarsToRemove = []
  1670.                 for e in ENVARS.DEPRECATED.iterkeys():
  1671.                         # Match on the exact name, 'All', or if envar is for an engine family the project is a part of.
  1672.                         if e in ( self.engine, ENGINE.ALL ) or \
  1673.                            self.engine in ( e ):
  1674.                                 for evar in ENVARS.DEPRECATED[ e ]:
  1675.                                         self.envarsToRemove.append( evar )
  1676.                                
  1677.                 # Only set debugging variables if global settings have been applied to the project.
  1678.                 if hasattr( self, 'n_NT_SYMBOL_PATH_enabled' ):
  1679.                         if self.n_NT_SYMBOL_PATH_enabled:
  1680.                                 if self.n_NT_SYMBOL_PATH_mode == 1:
  1681.                                         self.envars[ '_NT_SYMBOL_PATH' ] = EnvarSetting( '_NT_SYMBOL_PATH', self.s_NT_SYMBOL_PATH_override, True )
  1682.                                 else:
  1683.                                         userAdd = None
  1684.                                         if self.d_NT_SYMBOL_PATH_opts['userdef']:
  1685.                                                 userAdd = self.s_NT_SYMBOL_PATH_useradd
  1686.                                         p = make_NT_SYMBOL_PATH( self.d_NT_SYMBOL_PATH_opts['extlinks'],
  1687.                                                                                                  self.d_NT_SYMBOL_PATH_opts['perforce'],
  1688.                                                                                                  userAdd )
  1689.                                         self.envars[ '_NT_SYMBOL_PATH' ] = EnvarSetting( '_NT_SYMBOL_PATH', p, True )
  1690.                         else:
  1691.                                 # Remove the envar if the setting has been disabled
  1692.                                 self.envarsToRemove.append( '_NT_SYMBOL_PATH' )
  1693.                 if hasattr( self, 'n_NT_SYMCACHE_PATH_enabled' ):
  1694.                         if self.n_NT_SYMCACHE_PATH_enabled:
  1695.                                 if self.n_NT_SYMCACHE_PATH_mode == 1:
  1696.                                         self.envars[ '_NT_SYMCACHE_PATH' ] = EnvarSetting( '_NT_SYMCACHE_PATH', self.s_NT_SYMCACHE_PATH_override, True )
  1697.                                 else:
  1698.                                         self.envars[ '_NT_SYMCACHE_PATH' ] = EnvarSetting( '_NT_SYMCACHE_PATH', 'C:\SymbolCache', True )
  1699.                         else:
  1700.                                 # Remove the envar if the setting has been disabled
  1701.                                 self.envarsToRemove.append( '_NT_SYMCACHE_PATH' )
  1702.  
  1703.                 self.envVarsToMakeExpand = []
  1704.                 if '_vcee_autoexp' in self.envars.keys():
  1705.                         self.envVarsToMakeExpand.append( '_vcee_autoexp' )
  1706.                
  1707.         def apply( self, useSysKeys=True , update=True ):
  1708.                 '''
  1709.                 Applies this config to the system - ie performs the env var setup, and all other actions.
  1710.                 If update is False, don't update the windows environment at end, usually because it is being handled elsewhere.
  1711.                 '''
  1712.                 # Update project settings data
  1713.                 self.updateProjectSettings()
  1714.  
  1715.                 # Add the path envars
  1716.                 for name, setting in self.envars.iteritems():
  1717.                         reg_value_type = REG_SZ
  1718.                         if ( self.envVarsToMakeExpand ) and ( name in self.envVarsToMakeExpand ):
  1719.                                 reg_value_type = REG_EXPAND_SZ
  1720.  
  1721.                         setEnv( name, setting.value, reg_value_type, useSysKeys=useSysKeys )
  1722.                         os.environ[ name ] = setting.value
  1723.  
  1724.                 ## Set the P4CONFIG variable to a useful default if it is not set
  1725.                 if self.setP4CONFIG:
  1726.                         setP4RegistryVar( 'P4CONFIG', kP4CONFIG )
  1727.                 if self.setP4PORT:
  1728.                         setP4RegistryVar( 'P4PORT', kP4PORT )
  1729.  
  1730.                 ###  DEAL WITH THE %PATH% ENV VAR  ###
  1731.                 # Adds the Valve paths to the beginning of the PATH variable
  1732.                 # It uses System or User depending on what is globally set and passed to Apply.
  1733.                 pathKeyRoot, pathSubKey = getEnvKeys( useSysKeys )
  1734.                 curPATH = getEnv( 'PATH', useSysKeys=useSysKeys )
  1735.  
  1736.                 if not curPATH:
  1737.                         # If PATH doesn't exist, create it
  1738.                         try:
  1739.                                 pathKey = OpenKey( pathKeyRoot, pathSubKey, 0, KEY_ALL_ACCESS )
  1740.                                 CreateKey( pathKey, 'PATH' )
  1741.                         except WindowsError: pass
  1742.                         finally:
  1743.                                 CloseKey( pathKey )            
  1744.  
  1745.                 pathValue = self.authorPATHstr( curPATH )
  1746.                 setEnv( 'PATH', pathValue, REG_EXPAND_SZ, useSysKeys=useSysKeys )
  1747.  
  1748.                 ### SETUP FILE ASSOCIATIONS ###
  1749.                 # TODO: add some exception handling?
  1750.                 if self.setPythonInstallKeys:
  1751.                         fileAssociations.setupPythonInstallKeys( self.platform, engine=self.engine )
  1752.                 if self.setPythonFileAssociations:
  1753.                         fileAssociations.setupPythonAssociations( self.platform, engine=self.engine )
  1754.                 if self.setPerlFileAssociations:
  1755.                         fileAssociations.removePerlAssociations( includeUserChoice=False )             
  1756.                         fileAssociations.setupPerlAssociations( self.platform, engine=self.engine )
  1757.                
  1758.                 # Set Perl in PATHEXT
  1759.                 if self.setPerlPATHEXT:
  1760.                         pathExtValue = getEnv( 'PATHEXT' )
  1761.                         if ( not pathExtValue.upper().endswith( ';.PL' ) ) and ('.PL;' not in pathExtValue ):
  1762.                                 pathExtValue += ';.PL'
  1763.                                 setEnv( 'PATHEXT', pathExtValue )
  1764.  
  1765.                 # Leaving Python out for now, will probably cause problems as it is before \bin in the path
  1766.                 #pathExtValue = getEnv( 'PATHEXT' )
  1767.                 #if ( not pathExtValue.upper().endswith( ';.PY' ) ) or ('.PY;' not in pathExtValue ):
  1768.                         #pathExtValue += ';.PY'
  1769.                         #setEnv( 'PATHEXT', pathExtValue )
  1770.  
  1771.                 ### FIX UP SONY PATHS ###
  1772.                 try:
  1773.                         pathKey = OpenKey( pathKeyRoot, pathSubKey, 0, KEY_ALL_ACCESS )
  1774.                         unSony_ify_PATH( pathKey )
  1775.                 finally:
  1776.                         CloseKey( pathKey )            
  1777.  
  1778.                 # change the PATH in the current env
  1779.                 try:
  1780.                         os.environ[ 'path' ] = os.path.expandvars( '{0};{1}'.format( getEnv( 'PATH', useSysKeys=True ), getEnv( 'PATH', useSysKeys=False ) ) )
  1781.                 except WindowsError, RegistryError: pass
  1782.  
  1783.                 ###  SEND THE ENVIRONMENT CHANGED MESSAGE TO THE SYSTEM  ###
  1784.                 if update:
  1785.                         sendEnvironmentUpdate()
  1786.  
  1787.                 ### Hammer game configuration file ###
  1788.                 if self.engine in ENGINE.SOURCE_FAMILY:
  1789.                         # make sure there is a gameconfig.txt file in the game\bin directory has the config so hammer
  1790.                         # doesn't complain when it starts...
  1791.                         gcPath = self.vGame + r'\bin\gameconfig.txt'
  1792.                         projectGameConfig = HammerGameConfigFile( gcPath )
  1793.                         if gcPath.exists:
  1794.                                 for c in projectGameConfig.getProjectsCached().itervalues():
  1795.                                         if c.vGame == self.vGame:
  1796.                                                 return
  1797.  
  1798.                         # If we've come this far, then create a new project and add it to the config file
  1799.                         contentDir = self.vContent + '\\' + self.getMod()
  1800.                         # create the project
  1801.                         newProject = projectGameConfig.addNew(self.getName(), self.vMod, self.vGame, contentDir )
  1802.                         # add Hammer data to the project
  1803.                         newProject.createHammerData()
  1804.                         projectGameConfig.write()
  1805.  
  1806. def generateDefaultGameConfig():
  1807.         '''
  1808.         Generates a default gameconfig file
  1809.         '''
  1810.         default = Path( CONFIG_PATH )
  1811.  
  1812.         gameCfg = Chunk( 'Configs', [] )
  1813.         version = Chunk( 'Version', CONFIG_VERSION )
  1814.         games = Chunk( 'Games', [] )
  1815.  
  1816.         gameCfg.append( version )
  1817.         gameCfg.append( games )
  1818.  
  1819.         #gamePath = kBASE_DIR / 'game'
  1820.         #contentPath = kBASE_DIR / 'content'
  1821.         #for name, mod in kDEFAULT_GAMES:
  1822.                 #config = ProjectConfig.Create( name, mod, gamePath, contentPath )
  1823.                 #b.append( config.getChunk() )
  1824.  
  1825.         try:
  1826.                 default.write( str( gameCfg ), False )
  1827.         except: pass
  1828.  
  1829.         return default
  1830.  
  1831. class HammerGameConfigFile( KeyValueFile ):
  1832.         '''
  1833.         This is the file that contains all of the Hammer data/preferences for each project.
  1834.         Preserves the gameConfig file as it is written - the GameConfigFile class below is what is primarily used to interact with the gameConfig
  1835.         file used by the vproj tool. The main difference is that all the Hammer data is stripped out of the file when working with GameConfigFile
  1836.         TODO: Try flipping this relationship. Shouldn't this just be a subclass of GameConfigFile with the extra info added, instead of removed?
  1837.         '''
  1838.         def __init__( self, filepath, lineParser=None, chunkClass=Chunk, readCallback=None ):
  1839.                 KeyValueFile.__init__(self, filepath, parseLine, chunkClass, readCallback, True)
  1840.  
  1841.                 # Create cached version of config data as ProjectConfig objects
  1842.                 self.createProjectCache()
  1843.                
  1844.         def createProjectCache( self ):
  1845.                 '''
  1846.                 Regenerate the project cache
  1847.                 '''
  1848.                 # Create cached version of config data
  1849.                 self.configCache = {}
  1850.                 for c in self.getGamesChunk():
  1851.                         #if a chunk doesnt' have the GameDir key, bail - its not a valid config
  1852.                         try:
  1853.                                 c.GameDir
  1854.                         except AttributeError: continue
  1855.                         self.configCache[ c.key ] = ProjectConfig( c )
  1856.  
  1857.         def updateProjectCache( self, projectName ):
  1858.                 '''
  1859.                 Update a named project's data in the cache
  1860.                 '''
  1861.                 self.configCache[ projectName ] = self.getNamedProject( projectName )
  1862.  
  1863.         def removeFromProjectCache( self, project ):
  1864.                 '''
  1865.                 Remove a named project from the cache
  1866.                 '''
  1867.                 try:
  1868.                         del self.configCache[ project ]
  1869.                         return True
  1870.                 except AttributeError:
  1871.                         return False
  1872.                
  1873.         def getGamesChunk( self ):
  1874.                 '''
  1875.                 Returns a Chunk of valid games in the config
  1876.                 '''
  1877.                 try:
  1878.                         gamesChunk = self.findKey( 'Games' )[ 0 ]
  1879.                         if not gamesChunk.hasLen:
  1880.                                 gamesChunk.value = []
  1881.  
  1882.                         return gamesChunk
  1883.                 except IndexError:
  1884.                         #in this case, build it out...
  1885.                         configChunk = Chunk( 'Configs', [] )
  1886.                         gameChunk = Chunk( 'Games', [] )
  1887.  
  1888.                         self.append( configChunk )
  1889.                         configChunk.append( gameChunk )
  1890.  
  1891.                         return gameChunk
  1892.         def getProjectsCached( self ):
  1893.                 '''
  1894.                 Returns a dict of projects as ProjectConfig objects keyed by project name
  1895.                 '''
  1896.                 return self.configCache
  1897.         def getProjects( self ):
  1898.                 '''
  1899.                 Returns a list of projects as ProjectConfig objects
  1900.                 '''
  1901.                 configs = []
  1902.                 for c in self.getGamesChunk():
  1903.                         #if a chunk doesnt' have the GameDir key, bail - its not a valid config
  1904.                         try:
  1905.                                 c.GameDir
  1906.                         except AttributeError: continue
  1907.                         configs.append( ProjectConfig( c ) )
  1908.  
  1909.                 return configs         
  1910.         def getNames( self ):
  1911.                 return utils.removeDupes( [c.getName() for c in self.getProjects()] )
  1912.         def getNamesCached( self ):
  1913.                 return utils.removeDupes( self.configCache.keys() )
  1914.         def getCurrentProject( self ):
  1915.                 '''
  1916.                 Returns the projectConfig object corresponding to the current project.
  1917.                 None is returned if the project cannot be determined.
  1918.                 '''
  1919.                 # Get values from current environment
  1920.                 try:
  1921.                         curMod = os.path.normpath( os.environ[ 'VMOD' ] )
  1922.                         curGame = os.path.normpath( os.environ[ 'VGAME' ] )
  1923.                         curContent = os.path.normpath( os.environ[ 'VCONTENT' ] )
  1924.                         curTools =  os.path.normpath( os.environ[ 'VTOOLS' ] )
  1925.                         curPlatform = filesystem.platform()
  1926.                 except AttributeError, KeyError:
  1927.                         return None
  1928.                
  1929.                 try:
  1930.                         curPYTHON_HOME = os.path.normpath( os.getenv( 'PYTHONHOME' ) )
  1931.                 except AttributeError, KeyError:
  1932.                         curPYTHON_HOME = None
  1933.  
  1934.                 try:
  1935.                         curMAYA_SCRIPT_PATH = os.path.normpath( os.getenv( 'MAYA_SCRIPT_PATH' ) )
  1936.                 except AttributeError, KeyError:
  1937.                         curPythonHome = None
  1938.                        
  1939.                 curProj = [ curMod,
  1940.                             curGame,
  1941.                             curContent,
  1942.                             curTools,
  1943.                             curPlatform,
  1944.                             curPYTHON_HOME,
  1945.                             curMAYA_SCRIPT_PATH
  1946.                             ]
  1947.                 # Compare against cached project profiles to find a match
  1948.                 for c in self.getProjectsCached().itervalues():
  1949.                         if c.getProjectProfile() == curProj:
  1950.                                 return c
  1951.                 return None
  1952.         def getNameOfCurrent( self ):
  1953.                 try:
  1954.                         return self.getCurrentProject().getName()
  1955.                 except ( AttributeError, KeyError, PathError ):
  1956.                         return None
  1957.         def getNamedProject( self, theName ):
  1958.                 '''
  1959.                 Finds the named project and returns it as a ProjectConfig object
  1960.                 '''
  1961.                 configs = self.getProjects()
  1962.                 for c in configs:
  1963.                         if c.getName() == theName:
  1964.                                 return c
  1965.                 return None
  1966.         def getNamedProjectCached( self, theName ):
  1967.                 '''
  1968.                 Returns the named project from the cache as a ProjectConfig object
  1969.                 '''
  1970.                 try:
  1971.                         return self.configCache[ theName ]
  1972.                 except KeyError:
  1973.                         return None
  1974.         def getNamedChunk( self, theName ):
  1975.                 '''
  1976.                 returns the mod, game, content tuple for a named project
  1977.                 '''
  1978.                 for pData in self.getGamesChunk():
  1979.                         if theName == pData.key:
  1980.                                 return pData
  1981.                 return None
  1982.         def addNew( self, name, mod, gamePath, contentPath=None):
  1983.                 '''
  1984.                 Creates a new project object and appends it to this instance's config list. The new project is returned.
  1985.                 '''
  1986.                 project = ProjectConfig.Create( name, mod, gamePath, contentPath )
  1987.                 self.add( project )
  1988.                 return project
  1989.         def add( self, project ):
  1990.                 '''
  1991.                 Appends a project to this instance's config list.
  1992.                 '''
  1993.                 self.getGamesChunk().append( project.getChunk() )
  1994.                 # Update the project cache
  1995.                 self.configCache[ project.name ] = project
  1996.         def rename( self, oldProjectName, newProjectName ):
  1997.                 config = self.getNamedProject( oldProjectName )
  1998.                 config.setName( newProjectName )
  1999.                 # Update the project cache
  2000.                 self.configCache[ newProjectName ] = self.configCache[ oldProjectName ]
  2001.                 del self.configCache[ oldProjectName ]
  2002.                 return config.getName()
  2003.         def remove( self, projectName ):
  2004.                 '''
  2005.                 given a projectName, will remove it from the config
  2006.                 '''
  2007.                 config = self.getNamedProject( projectName )
  2008.                 try:
  2009.                         chunk = config.getChunk()
  2010.                         chunk.parent.value.remove( chunk )
  2011.                         # Update the project cache
  2012.                         del self.configCache[ projectName ]
  2013.                 except AttributeError: raise AttributeError('no such project')
  2014.         def write( self, filepath=None, *a ):
  2015.                 '''
  2016.                 gameConfigs generally don't want to be added to perforce, so override the base class's method to never add to perforce
  2017.                 '''
  2018.                 KeyValueFile.write( self, filepath, False )
  2019.  
  2020. class GameConfigFile( HammerGameConfigFile ):
  2021.         def __init__( self, filepath=None, lineParser=None, chunkClass=Chunk, readCallback=None ):
  2022.                
  2023.                 self.isFirstRun = False
  2024.                 self.isImportGameConfig = False
  2025.                
  2026.                 if filepath is None:
  2027.                         filepath = Path( CONFIG_PATH )
  2028.                         if not filepath.exists:
  2029.                                 # If the default gameconfig location doesn't exist, first try looking in the old location, the user's home dir.
  2030.                                 # If that doesn't work, try and copy any project's gameconfig we can find.
  2031.                                 # This is unlikely, as the user has most likely run this tool from a new machine, but we'll try anyway
  2032.                                 # TODO: Should probably check that the default location is not set to the either of these.
  2033.                                 self.isFirstRun = True
  2034.                                 try:
  2035.                                         userpath = Path( '%USERPROFILE%\gameconfig.txt' )
  2036.  
  2037.                                         # If the user config file exists, copy it to the config location
  2038.                                         if userpath.exists:
  2039.                                                 # if the config directory doesn't exist, create it
  2040.                                                 if not filepath.up(1).exists:
  2041.                                                         os.mkdir( filepath.up(1) )
  2042.                                                 userpath.copy( CONFIG_PATH )
  2043.                                                 self.isImportGameConfig = True
  2044.                                         # Otherwise, try using the project gameconfig
  2045.                                         else:
  2046.                                                 gamepath = Path( '%VGAME%\bin\gameconfig.txt' )
  2047.  
  2048.                                                 # if the branch based config file exists, copy it to config location
  2049.                                                 if gamepath.exists:
  2050.                                                         # if the config directory doesn't exist, create it
  2051.                                                         if not filepath.up(1).exists:
  2052.                                                                 os.mkdir( filepath.up(1) )
  2053.                                                         gamepath.copy( CONFIG_PATH )
  2054.                                                         self.isImportGameConfig = True
  2055.                                                 # otherwise, create a default one
  2056.                                                 else:
  2057.                                                         gamepath = generateDefaultGameConfig()
  2058.                                 except IOError:
  2059.                                         filepath = generateDefaultGameConfig()
  2060.  
  2061.                 HammerGameConfigFile.__init__( self, filepath, parseLine, chunkClass, readCallback )
  2062.  
  2063.                 # Remove any Hammer chunks if they exist - all the hammer config data is handled by the gameconfig file in the
  2064.                 # game\bin directory.  GameConfigFile simply handles the actual project configuations
  2065.                 gc = self.getGamesChunk()
  2066.                 for c in gc:
  2067.                         try:
  2068.                                 hammerChunk = c.Hammer
  2069.                                 c.value.remove( hammerChunk )
  2070.                         except AttributeError: pass
  2071.                
  2072.         def addCurrentVersion( self ):
  2073.                 '''
  2074.                 Add the current version to the beginning of the gameconfig.txt file
  2075.                 '''                    
  2076.                 configChunk = self.findKey( 'Configs' )[ 0 ]
  2077.                 versionChunk = Chunk( 'Version', CONFIG_VERSION )
  2078.                 configChunk.insert( 0, versionChunk )
  2079.         def setVersion( self ):
  2080.                 '''
  2081.                 Set the gameconfig.txt file version
  2082.                 '''
  2083.                 try:
  2084.                         configChunk = self.findKey( 'Configs' )[ 0 ]
  2085.                         versionChunk = self.findKey( 'Version' )[ 0 ]
  2086.                         configChunk.value.remove( versionChunk )
  2087.                         self.addCurrentVersion()
  2088.                 except IndexError:
  2089.                         return None
  2090.         def getVersion( self ):
  2091.                 '''
  2092.                 Get the gameconfig.txt file version or return None
  2093.                 '''
  2094.                 try:
  2095.                         versionChunk = self.findKey( 'Version' )[ 0 ]
  2096.                         return versionChunk.value
  2097.                 except IndexError:
  2098.                         return None
  2099.         version = property( getVersion )
  2100.                        
  2101.                
  2102. if __name__ == "__main__":
  2103.         pass
  2104.  
  2105. #end