Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ###WWW.VALVETIME.NET###
- ###Uploader comment: This file was found in "SourceFilmmaker/game/sdktools/python/global/lib/site-packages/vproj/vproj.py"###
- #========= Copyright (c) Valve Corporation. All Rights Reserved. ============#
- '''
- Valve project switching module.
- Nomenclature:
- projectConfig, project: repesents a single game's or project's configuration data
- gameConfig: file containing a list of projects
- '''
- from _winreg import *
- import codecs
- import errno
- import hashlib
- import os
- import pickle
- import subprocess
- import sys
- from copy import deepcopy
- from imp import is_frozen
- import ctypes, pythoncom, pywintypes, win32api, win32con, win32gui
- from win32com.shell import shell
- from time import localtime, strftime
- import P4
- # filesystem needs at least VPROJECT to be set,
- # so try and set it top something if it's not there.
- if not 'VPROJECT' in os.environ:
- toolsPath = os.path.split( os.path.split( sys.executable )[0] )[0]
- basePath = os.path.split( toolsPath )[0]
- os.environ[ 'VPROJECT' ] = os.path.join( basePath, 'game\tmp' )
- try:
- import filesystem
- except ImportError:
- toolsBin = os.path.split( os.path.split( sys.executable )[0] )[0]
- toolsPython = os.path.join( toolsBin, 'python' )
- os.environ[ 'PATH' ] = os.environ[ 'PATH' ] +';'+ toolsBin
- sys.path.append( toolsPython )
- #from filesystem import content, game, mod, platform, removeDupes, tools
- #from filesystem import *
- from filesystem.misc import removeDupes, findInFile, removeLineInFileThatContains
- from filesystem.path import PathError, Path
- from filesystem.valve import ( content, game, mod, platform, project, tools, Chunk,
- parseLine, KeyValueFile, GameInfoFile, GameInfoException )
- from filesystem.perforce import P4ConfigFile
- import utils, fileAssociations
- # Import vproj constants and default settings
- # Variable and settings should be added/changed here
- from constants import *
- from settings import EnvarSetting
- __author__ = 'jeff@valvesoftware.com'
- __toolName__ = 'vproj'
- def main_is_frozen():
- '''
- Returns True when running via .exe, False if running via script.
- '''
- return ( hasattr( sys, "frozen" ) or # new py2exe
- hasattr( sys, "importers" ) # old py2exe
- or is_frozen( "__main__" ) # tools/freeze
- )
- def get_main_dir():
- '''
- Returns the directory name of the script or the directory name of the exe.
- '''
- if main_is_frozen():
- return os.path.dirname( sys.executable )
- return os.path.dirname( sys.argv[0] )
- # guesses for the tools dir, different if run directly from the exe or from the script
- if main_is_frozen():
- kBASE_DIR = Path(__file__).up( 4 )
- kTOOLS_DIR = Path(__file__).up( 2 )
- else:
- kBASE_DIR = Path(__file__).up( 8 )
- kTOOLS_DIR = Path(__file__).up( 6 )
- def getEnvKeys( useSysKeys=True, asString=False ):
- '''
- Returns the proper root and subkeys for the given environment space
- '''
- if useSysKeys:
- regKeyRoot = 'HKEY_LOCAL_MACHINE'
- regSubKey = r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
- else:
- regKeyRoot = 'HKEY_CURRENT_USER'
- regSubKey = r"Environment"
- if not asString:
- regKeyRoot = globals()[ regKeyRoot ]
- return regKeyRoot, regSubKey
- def getEnv( varname, default=False, expandStrings=False, useSysKeys=True ):
- '''
- Returns the value of a given environment variable from the given space.
- False by default if variable doesn't exist, and an empty string if the variable exists but has no value set.
- '''
- v = default
- regKeyRoot, regSubKey = getEnvKeys( useSysKeys )
- try:
- regKey = OpenKey( regKeyRoot, regSubKey, 0, KEY_READ )
- try:
- v = str( win32api.RegQueryValueEx( regKey, varname )[0] )
- if expandStrings:
- v = win32api.ExpandEnvironmentStrings( v )
- except:
- pass
- finally:
- try:
- win32api.RegCloseKey( regKey )
- except:
- raise RegistryError( 'Error attempting to read an environment variable: {0}'.format( varname ) )
- return v
- def setRegKey( regKeyRoot, regSubKey, varName, varValue, reg_value_type=REG_SZ ):
- '''
- Set a registry key. Returns True if successful.
- '''
- try:
- regKey = OpenKey( regKeyRoot, regSubKey, 0, KEY_WRITE )
- SetValueEx( regKey, varName, 0, reg_value_type, varValue )
- result = True
- except WindowsError:
- result = False
- finally:
- try:
- CloseKey( regKey )
- except:
- raise RegistryError( 'Error attempting to set a registry key: {0}{1}'.format( regKeyRoot, regSubKey ) )
- return result
- def setEnv( varName, varValue, reg_value_type=REG_SZ, useSysKeys=True ):
- '''
- Sets the given environment variable in the given space. Returns True if successful.
- '''
- regKeyRoot, regSubKey = getEnvKeys( useSysKeys )
- return setRegKey( regKeyRoot, regSubKey, varName, varValue, reg_value_type )
- def deleteEnv( varName, useSysKeys=True ):
- '''
- Deletes the given environment variable in the given space. Returns True if successful.
- '''
- regKeyRoot, regSubKey = getEnvKeys( useSysKeys )
- try:
- regKey = OpenKey( regKeyRoot, regSubKey, 0, KEY_ALL_ACCESS )
- except:
- return False
- try:
- DeleteValue( regKey, varName )
- return True
- except:
- return False
- finally:
- try:
- CloseKey( regKey )
- except:
- raise RegistryError( 'Error attempting to delete a environment variable: {0}'.format( varName ) )
- def checkForVars( useSysKeys=True ):
- '''
- Returns a list of known envars in the given environment
- TODO: return a dict with sys and user vars?
- '''
- checkVars = ENVARS.BASE.keys()
- checkVars.extend( ENVARS.ADD.keys() )
- for e in ENVARS.DEPRECATED.itervalues():
- checkVars.extend( e )
- existingVars = []
- for var in checkVars:
- if getEnv( var, useSysKeys=useSysKeys ):
- existingVars.append( var )
- if existingVars: return existingVars
- return False
- def checkForPath( useSysKeys=True ):
- '''
- Returns True if there are Valve paths in the PATH envar.
- '''
- curPath = getEnv( 'PATH', useSysKeys=useSysKeys )
- if curPath:
- # check the path for valve paths
- newSysPath = removeProjectPathsFromPATH( curPath )
- sysPathValue = kSEPARATOR.join( newSysPath ).replace('/', '\\')
- if sysPathValue == curPath:
- return False
- return True
- return False
- def deletePath( useSysKeys=True ):
- '''
- Remove Valve paths from PATH
- '''
- curPath = getEnv( 'PATH', useSysKeys=useSysKeys )
- if curPath:
- try:
- # Clear the path of valve paths
- newPath = removeProjectPathsFromPATH( curPath )
- # TODO: check to see if newPath == curPath (no work to do?)
- PathValue = kSEPARATOR.join( newPath ).replace('/', '\\')
- # Check to see if final path is empty. If so, remove it.
- if PathValue == '':
- deleteEnv( 'PATH', useSysKeys=useSysKeys )
- else:
- setEnv( 'PATH', PathValue, REG_EXPAND_SZ, useSysKeys=useSysKeys )
- return True
- except:
- return False
- def deleteVars( useSysKeys=True ):
- '''
- Removes known environment variables and returns a list of deleted vars.
- '''
- varList = checkForVars( useSysKeys=useSysKeys )
- if varList:
- if not checkAdmin() and ( useSysKeys==True ):
- raise PermissionError( 'Current user does not have admin privileges.' )
- deletedVars = []
- for var in varList:
- try:
- deleteEnv( var, useSysKeys=useSysKeys )
- deletedVars.append( var )
- except WindowsError, e:
- if e.errno == 2:
- # skip if key cannot be found
- pass
- return deletedVars
- else:
- return False
- def make_NT_SYMBOL_PATH( useExtLinks=True, useP4Paths=True, userAdd=None ):
- '''
- Return a default _NT_SYMBOL_PATH string given the options.
- '''
- envPath = r'%VGAME%\bin\win64;' + \
- r'%VGAME%\bin\win32;' + \
- r'%VGAME%\%VMOD%\bin\win64;' + \
- r'%VGAME%\%VMOD%\bin\win32;' + \
- r'%VGAME%\..\devtools\bin\win64;' + \
- r'%VGAME%\..\devtools\bin\win32;' + \
- r'%VGAME%\sdktools\bin\win64;' + \
- r'%VGAME%\sdktools\bin\win32;' + \
- r'%VGAME%\bin;' + \
- r'%VGAME%\%VMOD%\bin;' + \
- r'%VGAME%\..\devtools\bin;' + \
- r'%VGAME%\sdktools\bin;'
- ## Updated path, commented out for now due since it will currently trigger a false warning that
- ## the path has changed behind VProj's back.
- #envPath = r'%VGAME%\bin\win64;'+ \
- #r'%VGAME%\bin\win32;'+ \
- #r'%VGAME%\%VMOD%\bin\win64;'+ \
- #r'%VGAME%\%VMOD%\bin\win32;'+ \
- #r'%VGAME%\..\src\devtools\bin\win64;'+ \
- #r'%VGAME%\..\src\devtools\bin\win32;'+ \
- #r'%VGAME%\sdktools\bin\win64;'+ \
- #r'%VGAME%\sdktools\bin\win32;'+ \
- #r'%VGAME%\bin;'+ \
- #r'%VGAME%\%VMOD%\bin;'+ \
- #r'%VGAME%\..\src\devtools\bin;'+ \
- #r'%VGAME%\sdktools\bin;'
- p4Path = ''
- extPath = ''
- userPath = ''
- if useP4Paths:
- p4Path = r'*\\perforce\symbols'
- if useExtLinks:
- extPath = '*http://msdl.microsoft.com/download/symbols'
- if userAdd:
- userPath = userAdd + ';'
- userPath.replace( ';;', ';' )
- result = 'cache*;' + envPath + userPath + 'SRV' + p4Path + extPath
- return result
- def getP4RegistryVar( p4var='p4config' ):
- '''
- Check the dedicated Perforce registry space for the presence of a p4 setting.
- Return the value if it's found, or False if not.
- '''
- regKeyRoot = HKEY_CURRENT_USER
- regSubKey = r"Software\Perforce\environment"
- v = False
- try:
- regKey = OpenKey( regKeyRoot, regSubKey, 0, KEY_READ )
- try:
- v = str( win32api.RegQueryValueEx( regKey, p4var )[0] )
- except:
- pass
- finally:
- try:
- win32api.RegCloseKey( regKey )
- except:
- pass
- return v
- def checkp4Environ( p4var='p4config' ):
- '''
- Returns the value of a p4 setting and returns None if it doesn't exist.
- Order is checked in the priority order that perforce uses: user environment, sys environment, registry.
- '''
- p4 = P4.P4()
- return p4.env( p4var )
- ## Manual method commented out
- ## First check user environment space
- #if ( getEnv( p4var.upper(), useSysKeys=False ) ):
- #return getEnv( p4var, useSysKeys=False )
- ## Check system environment space
- #elif ( getEnv( p4var.upper(), useSysKeys=True ) ):
- #return getEnv( p4var, useSysKeys=True )
- ## Last check registry
- #elif ( getP4RegistryVar( p4var ) ):
- #return getP4RegistryVar( p4var )
- #else:
- #return False
- def setP4RegistryVar( p4var='p4config', p4value='p4.cfg' ):
- '''
- Set a P4 variable in the registry
- Return False if it could not be set.
- '''
- regKeyRoot = HKEY_CURRENT_USER
- regSubKey = r"Software\Perforce\environment"
- return setRegKey( regKeyRoot, regSubKey, p4var, p4value )
- def deleteP4RegistryVar( p4var='p4config' ):
- '''
- Check the registry for the presence of a p4 setting.
- Return the True if it's deleted, or False if it is not.
- '''
- regKeyRoot = HKEY_CURRENT_USER
- regSubKey = r"Software\Perforce\environment"
- try:
- regKey = OpenKey( regKeyRoot, regSubKey, 0, KEY_ALL_ACCESS )
- except:
- return False
- try:
- DeleteValue( regKey, p4var )
- return True
- except:
- return False
- finally:
- try:
- CloseKey( regKey )
- except:
- return False
- return result
- def getP4Workspaces():
- '''
- Returns the current workspace and a list of Perforce workspaces (clientspecs) for the current user and workstation
- '''
- p4 = P4.P4() # Create the P4 instance
- host = os.environ[ 'COMPUTERNAME' ]
- workspaces = []
- curWorkspace = None
- try:
- p4.connect()
- info = p4.run( "info" )
- # Get the workspaces for the current user and host
- for client in p4.run( 'workspaces', '-u', p4.user ):
- if ( client['Host'] == host.lower() ):
- workspaces.append( client['client'] )
- p4.disconnect()
- curWorkspace = info[0][ 'clientName' ]
- except P4.P4Exception:
- raise
- return curWorkspace, workspaces
- def rotateBackupFiles( backupPath, fileName, maxBackups=4 ):
- '''
- Rotates through a set up backup files in the given backup path.
- The highest number file is deleted and the fileName becomes the first.
- Normally the backups would be rotated, then a new "root" backup is created to replace the first.
- backupPath = where the backup files should be created
- fileName = name of the "root" backup file. Needs to exist in the backup path.
- maxBackups = number of backup files to keep
- '''
- fileBaseName, ext = os.path.splitext( fileName )
- # Remove last backup
- try:
- os.remove( '{0}\\{1}.{2}{3}'.format( backupPath, fileBaseName, maxBackups, ext ) )
- except WindowsError, e:
- # Raise all error except file not found
- if not ( e.errno == errno.ENOENT ):
- raise WindowsError, e
- # Increment backup filenames
- for i in range( maxBackups - 1, 0, -1 ):
- try:
- os.rename( '{0}\\{1}.{2}{3}'.format( backupPath, fileBaseName, i, ext ),
- '{0}\\{1}.{2}{3}'.format( backupPath, fileBaseName, i+1, ext ) )
- except WindowsError, e:
- # Raise all error except file not found
- if not ( e.errno == errno.ENOENT ):
- raise WindowsError, e
- # Rename filename backup to 1 slot
- try:
- os.rename( '{0}\\{1}{2}'.format( backupPath, fileBaseName, ext ),
- '{0}\\{1}.1{2}'.format( backupPath, fileBaseName, ext ) )
- except WindowsError, e:
- if not ( e.errno == errno.ENOENT ):
- # Raise all error except file not found
- raise WindowsError, e
- def backupEnvironment( filePath, fileName, useSysKeys=True ):
- '''
- Make a backup copy of the current environment variables
- '''
- # Make the backup directory if it doesn't exist
- if not os.path.exists( filePath ):
- os.makedirs( filePath )
- TEMP = os.path.expandvars( '%TEMP%' )
- # Read the system and user environment to temp files
- tmpRegFileSys = '{0}\\{1}'.format( os.path.expandvars( '%TEMP%' ), 'environment_backup_sys_tmp.reg' )
- regKeyRoot, regSubKey = getEnvKeys( useSysKeys=True, asString=True )
- regPath = '{0}\{1}'.format( regKeyRoot, regSubKey )
- regedit = subprocess.Popen( 'regedit.exe /e "{0}" "{1}"'.format( tmpRegFileSys, regPath ) )
- regedit.wait()
- tmpRegFileUsr = '{0}\\{1}'.format( os.path.expandvars( '%TEMP%' ), 'environment_backup_usr_tmp.reg' )
- regKeyRoot, regSubKey = getEnvKeys( useSysKeys=False, asString=True )
- regPath = '{0}\{1}'.format( regKeyRoot, regSubKey )
- regedit = subprocess.Popen( 'regedit.exe /e "{0}" "{1}"'.format( tmpRegFileUsr, regPath ) )
- regedit.wait()
- # Merge the two files into the target file
- addRegistryFiles( tmpRegFileUsr, tmpRegFileSys, '{0}\{1}'.format( filePath, fileName ) )
- # Delete the temp files
- os.remove( tmpRegFileUsr )
- os.remove( tmpRegFileSys )
- def backupEnvironmentRotated( filePath=None, fileName=None, useSysKeys=True ):
- '''
- Makes a backup copy of the current environment variables and rotates through 5 backups.
- '''
- if not filePath:
- filePath = '{0}\\Backups'.format( os.path.dirname( os.path.expandvars( CONFIG_PATH ) ) )
- if not fileName:
- fileName = 'environment_vars.reg'
- # Rename existing backups if the directory exists
- if os.path.exists( filePath ):
- rotateBackupFiles( filePath, fileName )
- backupEnvironment( filePath, fileName, useSysKeys=useSysKeys )
- def addRegistryFiles( filePathA, filePathB, targetFile ):
- '''
- Merge the contents of two unicode registry files into a third file.
- Note: there is no handling of duplicates. It simply adds the contents of fileB to fileB,
- only removing the header line from the 2nd file.
- '''
- # Read data from source files
- fileA = codecs.open( filePathA, 'r', 'utf-16' )
- fileAData = fileA.readlines()
- fileA.close()
- fileB = codecs.open( filePathB, 'r', 'utf-16' )
- fileBData = fileB.readlines()
- fileA.close()
- # Write out the new merged file
- newFileData = fileAData + fileBData[1:] # Skip the header line in 2nd file
- newFile = codecs.open( targetFile, 'w', 'utf-16' )
- newFile.writelines( newFileData )
- newFile.close()
- def checkAdmin():
- '''
- Returns True if the user has admin privileges, False if not.
- '''
- return shell.IsUserAnAdmin()
- class RegistryError( Exception ):
- '''
- Exception to handle registry access errors.
- '''
- def __init__( self, message, errno=None ):
- Exception.__init__( self, message )
- self.errno = None
- self.strerror = message
- class PermissionError( Exception ):
- '''
- Exception to handle when user does not have admin permissions.
- '''
- def __init__( self, message, errno=None ):
- Exception.__init__( self, message )
- self.errno = None
- self.strerror = message
- def removeProjectPathsFromPATH( PATH_str, gameConfig=None ):
- '''
- Removes all project paths from a given %PATH% string, and returns the new, clean %PATH% as a list
- Returns a list of the original Path if there were no paths to remove
- '''
- #remove any duplicate separators from the path - sometimes there are double separators
- while PATH_str.find(kSEPARATOR*2) != -1:
- PATH_str = PATH_str.replace(kSEPARATOR*2, kSEPARATOR)
- pathToks = PATH_str.split(kSEPARATOR)
- #pathToks = map( Path, pathToks ) # make them all Path instances
- pathToks = utils.removeDupes( pathToks ) # Remove duplicates...
- # so now what we want to do is remove all references to paths from other projects - we look
- # for any and all paths that exist as
- if gameConfig is None:
- gameConfig = GameConfigFile()
- toksToRemove = set()
- for c in gameConfig.getProjectsCached().itervalues():
- # Use expanded paths to find all matches
- cPaths = c.getRequiredPathsExpanded()
- for p in PATHS.DEPRECATED[ c.getEngine() ]:
- p = p.replace('/', '\\')
- cPaths.append( os.path.expandvars( p ) )
- assert cPaths
- for n, pathTok in enumerate( pathToks ):
- if os.path.expandvars( pathTok ) in cPaths:
- toksToRemove.add( n )
- if toksToRemove:
- toksToRemove = list( sorted( toksToRemove ) )
- toksToRemove.reverse()
- for n in toksToRemove:
- pathToks.pop( n )
- else:
- pathToks = PATH_str.split( kSEPARATOR )
- return pathToks
- def sendEnvironmentUpdate():
- '''
- Tells Windows to update the environment.
- '''
- TIMEOUT = 150 #in milliseconds
- win32gui.SendMessageTimeout( win32con.HWND_BROADCAST, win32con.WM_SETTINGCHANGE, 0, 'Environment', win32con.SMTO_ABORTIFHUNG, TIMEOUT )
- def executablesInPath( paths, excludeList=[] ):
- '''
- Given a list of paths, return a list of .exe files in them, without duplicates.
- Optionally give a list of processing to exclude in the check.
- '''
- exeList = []
- for p in paths:
- try:
- for fname in os.listdir( Path( p ) ):
- if ( fname.endswith( '.exe' ) ) and ( fname not in exeList ) and ( fname not in excludeList ):
- exeList.append( fname )
- except WindowsError:
- pass
- return exeList
- TH32CS_SNAPPROCESS = 0x00000002
- class PROCESSENTRY32( ctypes.Structure ):
- _fields_ = [( "dwSize", ctypes.c_ulong ),
- ( "cntUsage", ctypes.c_ulong ),
- ( "th32ProcessID", ctypes.c_ulong ),
- ( "th32DefaultHeapID", ctypes.c_ulong ),
- ( "th32ModuleID", ctypes.c_ulong ),
- ( "cntThreads", ctypes.c_ulong ),
- ( "th32ParentProcessID", ctypes.c_ulong ),
- ( "pcPriClassBase", ctypes.c_ulong ),
- ( "dwFlags", ctypes.c_ulong ),
- ( "szExeFile", ctypes.c_char * 260 )]
- def isRunningProcess( *procs ):
- '''
- Returns a list of the given processes that are currently running.
- '''
- running = []
- # If we got a list as an argument, just use that.
- if isinstance( procs[0], list ):
- procs = procs[0]
- allProc = []
- # See http://msdn2.microsoft.com/en-us/library/ms686701.aspx
- hSnapProcess = ctypes.windll.kernel32.CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 )
- ps32 = PROCESSENTRY32()
- ps32.dwSize = ctypes.sizeof( PROCESSENTRY32 )
- if ctypes.windll.kernel32.Process32First( hSnapProcess,
- ctypes.byref( ps32 ) ) == win32con.FALSE:
- raise WindowsError( 'Could not get a list of processes!' )
- while True:
- allProc.append( ps32.szExeFile )
- if ctypes.windll.kernel32.Process32Next( hSnapProcess,
- ctypes.byref( ps32 ) ) == win32con.FALSE:
- break
- ctypes.windll.kernel32.CloseHandle( hSnapProcess )
- for p in procs:
- if p in allProc:
- if p not in running:
- running.append( p )
- if running:
- return running
- else:
- return False
- def unSony_ify_PATH( envRegKey ):
- '''
- Fix up Sony paths
- '''
- # Get the actual PATH value and tokenize it
- try:
- pathValue = QueryValueEx( envRegKey, 'PATH' )[ 0 ]
- # cast the path values to Path instances (makes comparing them easy)
- # but also store the original string to that env variable embedding is preserved
- paths = [ (Path( p ), p) for p in pathValue.split( ';' ) ]
- except WindowsError:
- paths = None
- if paths:
- # 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
- demotionTokens = [ Path( r'C:\msys\1.0\bin' ) ]
- for envName in ('SCE_PS3_ROOT', 'SN_COMMON_PATH', 'SN_PS3_PATH'):
- try:
- value, _tmp = QueryValueEx( envRegKey, envName )
- except WindowsError: pass
- else:
- demotionTokens.append( Path( value ) )
- #find the paths that contain demotion tokens
- pathsToDemote = []
- for tok in demotionTokens:
- toPop = []
- for n, (path, orgPath) in enumerate( paths ):
- if path.isUnder( tok ):
- pathsToDemote.append( (path, orgPath) )
- toPop.append( n )
- #remove the path values to demote from the paths list
- toPop.sort()
- for n in reversed( toPop ):
- paths.pop( n )
- #bump them to the end and set the new env var
- paths += pathsToDemote
- paths = [ p[1] for p in paths ]
- pathValue = ';'.join( paths )
- SetValueEx( envRegKey, 'PATH', 0, REG_EXPAND_SZ, pathValue )
- def removeConfigVars( project=None, update=False, useSysKeys=True ):
- '''
- Removes any variables set by config, and cleans out the PATH variable.
- If update is set to False, Windows is not notified of the changes, if you want to do it later.
- '''
- try:
- # Remove envars and clear PATHs
- deleteVars( useSysKeys=True )
- deleteVars( useSysKeys=False )
- deletePath( useSysKeys=True )
- deletePath( useSysKeys=False )
- finally:
- if update:
- sendEnvironmentUpdate()
- return True
- #TODO:
- # - report if data was removed, and what it was.
- def readPickle( filePath ):
- with open( filePath, 'r' ) as f:
- return pickle.load( f )
- def getPythonPaths():
- '''
- Gets the current Python path and executable using a temporary file and returns
- sys.path, sys.executable, PYTHONHOME as a list.
- '''
- # Create a temporary python script object
- pyScriptFile = TempPythonFile()
- # Generate a temp filename for the output of the script
- timestamp = strftime( "%H%M%S-%m%d%Y", localtime() )
- tempPath = os.path.expandvars( '%temp%' )
- tempFile = '{0}\\temp_{1}.txt'.format( tempPath, timestamp )
- # Code for the temp script
- code = """
- import sys, os
- from time import localtime, strftime
- import pickle
- def writePicklePaths( filePath ):
- pList = []
- paths = []
- for p in sys.path[1:]:
- paths.append( p )
- pList.append( paths )
- pList.append( sys.executable )
- try:
- curPYTHONHOME = os.getenv( 'PYTHONHOME' )
- except KeyError:
- curPYTHONHOME = False
- pList.append( curPYTHONHOME )
- try:
- f = open( filePath, 'w' )
- pickle.dump( pList, f )
- f.close()
- except:
- raise
- writePicklePaths( '{0}' )
- """.format( tempFile.replace( '\\', '\\\\' ) )
- # Write the code and run it
- try:
- pyScriptFile.setCode( code )
- pyScriptFile.writeCode()
- # Check the SHA1 digest string to make sure we're running the same code
- # This should always be true if we've gotten this far.
- assert pyScriptFile.checkSHA1()
- pyScriptFile.run()
- except ( WindowsError, ImportError ) as error:
- raise WindowsError
- finally:
- pyScriptFile.delete()
- if os.path.exists( tempFile ):
- curSyspath, curPythonExe, curPYTHONHOME = readPickle( tempFile )
- os.remove( tempFile )
- return curSyspath, curPythonExe, curPYTHONHOME
- else:
- return None, None, None
- class TempPythonFile( file ):
- '''
- A simple file object to create semi-unique python files in the %temp% directory.
- Filenames use the current timestamp and an optional prefix, ie. "%temp%\<prefix><timestamp>.py"
- The checkSHA1() method can be used to verify the written file matches the generated code string.
- '''
- def __init__( self, prefix='tmp_' ):
- timestamp = strftime( "%H%M%S-%m%d%Y", localtime() )
- self.filePath = os.path.expandvars( '%temp%\{0}{1}.py'.format( prefix, timestamp ) )
- self.codeStr = ''
- self.output = ''
- super( file, self).__init__()
- def setCode( self, codeStr ):
- '''
- Set the string that will be used when the python file is run.
- '''
- self.codeStr = codeStr
- # calculate a new SHA1 string for checksum
- self.setSHA1digest()
- def writeCode( self ):
- '''
- Write the current code string to the file.
- '''
- try:
- f = open( self.filePath, 'w' )
- f.write( self.codeStr )
- f.close()
- except IOError as e:
- raise IOError( 'Could not create temporary python file.\n\n{0}'.format( e ) )
- def getFilename( self ):
- '''
- Get the autogenerated filename of the target file.
- '''
- return self.filePath
- filename = property( getFilename )
- def delete( self ):
- '''
- Delete the target temp script file.
- '''
- try:
- self.close()
- if os.path.exists( self.filePath ):
- os.remove( self.filePath )
- except:
- pass
- def run( self ):
- '''
- Run the temp script and wait for it to complete.
- '''
- subprocess.Popen( [ 'python', self.filename ], shell=False, env=os.environ ).wait()
- def runPiped( self ):
- '''
- Run the script with a piped command prompt to retrieve the output
- 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.
- '''
- startupinfo = subprocess.STARTUPINFO()
- startupinfo.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW
- startupinfo.wShowWindow = win32con.SW_HIDE
- # TODO: test adding .wait() method to this line:
- process = subprocess.Popen( 'python.exe %s' % ( self.filename ), shell=False, startupinfo=startupinfo, stdout=subprocess.PIPE, creationflags=win32con.CREATE_NO_WINDOW )
- self.output = process.communicate()[0]
- return self.output
- def setSHA1digest( self ):
- '''
- Set the SHA1 digest string for the currently set code string.
- '''
- self.sha1 = hashlib.sha1( self.codeStr ).hexdigest()
- def readSHA1digest( self ):
- '''
- Return a SHA1 digest string for the contents of the written file.
- '''
- try:
- return hashlib.sha1( file( self.filename, 'r' ).read() ).hexdigest()
- except IOError as e:
- raise IOError( 'Could not read temporary python file.\n\n{0}'.format( e ) )
- def checkSHA1( self ):
- '''
- Compare the current SHA1 string to the SHA1 for the written file
- '''
- if self.sha1 == self.readSHA1digest():
- return True
- else:
- return False
- def listKeyValues( key ):
- '''
- lists all the values under a given key
- '''
- values = []
- n=0
- while True:
- try:
- values.append( EnumValue( key, n ) )
- n+=1
- except EnvironmentError:
- break
- return values
- class ProjectConfig( object ):
- '''
- All the configuration data for a single game project.
- '''
- def __init__( self, chunk ):
- self.__chunk = chunk
- self.updateProjectSettings()
- @classmethod
- def Create( cls, name, mod, gamePath, contentPath ):
- '''
- This is used to create a "naked" config object (with the default config data populated) instead of creating an object directly.
- '''
- if gamePath:
- game = Path( gamePath ).asfile()
- project = ( game + '\\' + mod ).resolve().asNative()
- else:
- project = ''
- # Build the key value data
- gameChunk = Chunk( str(name), [] )
- gameChunk.append( Chunk( 'GameDir', str( project ), gameChunk ) )
- # Now turn the new chunk into a projectConfig object, and set the data
- projConfig = cls( gameChunk )
- projConfig.setName( name )
- projConfig.setContent( Path( contentPath ).asfile() )
- return projConfig
- @classmethod
- def CreateFromGameInfo( cls, gameInfoPath ):
- '''
- Sets up a project object by reading the contents of a gameInfo file
- '''
- try:
- gameInfo = GameInfoFile( gameInfoPath.asNative() )
- except TypeError:
- raise GameInfoException( 'The selected gameinfo file is not valid.' )
- try:
- name = gameInfo.game.value
- except AttributeError:
- raise GameInfoException( 'A valid "name" keyvalue could not be found in the given gameinfo file.' )
- mod = gameInfoPath.up().name()
- gamePath = gameInfoPath.up( 2 )
- contentPath = gameInfoPath.up( 3 ) + r'\content'
- proj = ProjectConfig.Create( name, mod, gamePath, contentPath )
- try:
- if gameInfo.Engine2.Capable64Bit.value == '1':
- proj.setRunIn32Bit( supported=False )
- except AttributeError:
- pass
- try:
- proj.setEngine( gameInfo.ToolsEnvironment.Engine.value )
- except AttributeError:
- pass
- try:
- # Override the calculated default tools directory with the one from
- # the GameInfo file if it's present
- proj.setToolsChunk( gamePath + mod + gameInfo.ToolsEnvironment.ToolsDir.value )
- except AttributeError:
- pass
- # add the engine and platform to the friendly name
- if proj.getRunIn32Bit():
- proj.setName( '{0}'.format( name ) )
- else:
- proj.setName( '{0} ({1})'.format( name, proj.getPlatform() ) )
- return proj
- def __str__( self ):
- return self.getName()
- __repr__ = __str__
- def getChunk( self ):
- return self.__chunk
- def getName( self ):
- '''
- Get the friendly name of the project config. This is the name that shows up in the vprojUI.
- '''
- return self.getChunk().key
- def setName( self, name ):
- '''
- sets this config's name to a given value - does a uniqeness check on the name, and appends
- '_copy' until the name is unique
- '''
- # Grab the parent - if it exists
- gameChunk = self.getChunk().parent
- # Try to ensure uniqueness - this may not be possible to do, as it is possible to create a Config object that doesn't
- # belong to a GameConfigFile (ie the Config object has no parent Chunk). If the object has no parent, the gameChunk
- # is None - hence the try statement.
- try:
- names = set( [ProjectConfig(c).getName() for c in gameChunk if c is not self] )
- names.remove( self.getName() )
- while name in names:
- name += '_copy'
- except TypeError: pass
- self.getChunk().key = name
- self.name = name
- def getProjectProfile( self ):
- '''
- Returns a list of basic project attributes useful for comparasions
- '''
- try:
- profile = [ self.vMod,
- os.path.normpath( self.vGame.asNative() ),
- os.path.normpath( self.vContent.asNative() ),
- os.path.normpath( self.vTools.asNative() ),
- self.platform
- ]
- except ( AttributeError, ValueError, PathError ):
- return None
- try:
- profile.append( os.path.normpath( self.getPYTHONHOME() ) )
- except:
- profile.append( None )
- try:
- profile.append( os.path.normpath( self.getMAYA_SCRIPT_PATH() ) )
- except:
- profile.append( None )
- return profile
- def getMod( self ):
- '''
- Get the mode name, i.e. %VMOD%
- '''
- return self.getProjectDir().name()
- def setMod( self, mod ):
- '''
- Set the mod name, i.e. %VMOD%
- '''
- self.setProjectDir( self.getGame() + '\\' + mod )
- self.vMod = mod
- def getGame( self ):
- '''
- Get the game directory Path, i.e. %VGAME%
- '''
- return self.getProjectDir().up()
- def setGame( self, path ):
- '''
- Set the game directory path, i.e. %VGAME%
- '''
- path = Path( path )
- mod = self.getMod()
- vproject = '{0}\\{1}'.format( path, mod )
- self.setProjectDir( vproject )
- self.vGame = path
- def getContent( self ):
- '''
- Get the content directory Path, i.e. %VCONTENT%
- '''
- try:
- return Path( self.getChunk().ContentDir.value )
- except AttributeError:
- try:
- return self.getGame().replace('game', 'content')
- except ValueError: return self.getGame()
- def setContent( self, path ):
- '''
- Set the content directory Path, i.e. %VCONTENT%
- '''
- path = Path(path)
- pathStr = path.resolve().asNative()
- try:
- self.getChunk().ContentDir.value = pathStr
- self.vContent = pathStr
- except AttributeError:
- c = Chunk( 'ContentDir', pathStr )
- self.getChunk().append( c )
- def getPlatform( self ):
- '''
- Return a string with the current platform. If invalid, assume win32.
- '''
- try:
- return PLATFORM.BY_INDEX[ self.getRunIn32Bit() ]
- except KeyError:
- return PLATFORM.WIN_32_SUFFIX
- def getDefaultToolsDir( self, engine=None ):
- '''
- Try to determine the default tools path.
- '''
- try:
- if not engine:
- engine = self.getEngine()
- if ( engine in ( ENGINE.SOURCE2, ENGINE.HYBRID, ENGINE.SFM ) ):
- return Path( self.getGame() + r'\sdktools' )
- else:
- # First try using location of vproj
- exeTools = Path( sys.executable ).up( 2 ) + 'tools'
- if os.path.exists( exeTools ):
- return Path( exeTools )
- # Initial guess
- initialTools = Path( self.getGame().up( 2 ) + r'\tools' )
- if initialTools.exists:
- return initialTools
- else:
- # If the root is valid, try going up from gamedir until we find an existing one
- toolTest = self.vGame.up() + r'\tools'
- if os.path.exists( toolTest.split()[0] ):
- while toolTest != toolTest.split()[0]:
- toolTest = toolTest.up()
- if ( toolTest + r'\tools\bin\vproj.exe' ).exists:
- return toolTest + r'\tools'
- # Last try deriving it using the perforce workspace
- wsTools = self.getToolsFromP4Workspace()
- if wsTools:
- return Path( wsTools )
- else:
- # Give up and return the default
- return initialTools
- except AttributeError:
- return kTOOLS_DIR
- def getToolsFromP4Workspace( self, workspace=None ):
- '''
- Return the tools path extracted from the workspace for the current project
- Return None if is not present or couldn't be determined.
- '''
- # First determine the workspace
- if not workspace:
- # First check for a stored workspace in the project
- workspace = self.getVersionControlWorkspace()
- if not workspace:
- # Next try finding it in the closest p4config file
- p4CfgLoc = self.findP4Config()
- if p4CfgLoc:
- p4Cfg = P4ConfigFile( p4CfgLoc )
- p4Cfg.read()
- workspace = p4Cfg.P4CLIENT
- if not workspace:
- # workspace couldn't be determined.
- return None
- p4 = P4.P4()
- try:
- p4.connect()
- ws = p4.fetch_client( workspace )
- except P4.P4Exception:
- return None
- localTools = None
- for line in ws[ 'View' ]:
- if line.startswith( '//ValveGames/tools/...' ):
- toolsMap = line.split( ' ' )[1]
- # Construct a local version of the directory
- root = os.path.normpath( ws[ 'Root' ] ) + '\\'
- localTools = toolsMap.replace( '//{0}/'.format( ws[ 'Client' ] ), root ).rstrip( '/...' )
- return localTools
- def getTools( self ):
- '''
- Get the tools directory Path, i.e. %VTOOLS% from the project config,
- or try to determine it if it isn't present there.
- '''
- # First look for existing value
- tools = self.getToolsChunk()
- if not tools:
- # If not, get a new default and save it
- tools = self.getDefaultToolsDir()
- self.setToolsChunk( tools )
- return tools
- def getToolsChunk( self ):
- '''
- Get the calculated tools directory stored in the project config.
- '''
- try:
- return Path( self.getChunk().Tools.value )
- except AttributeError:
- return None
- def getToolsDirOverrideChunk( self ):
- '''
- Get the override tools directory Path, i.e. %VTOOLS% from the project config,
- '''
- try:
- return Path( self.getChunk().ToolsDirOverride.value )
- except AttributeError:
- # Next try the old variable version
- try:
- return Path( self.getChunk().ToolsDir.value )
- except AttributeError:
- return None
- def setToolsDirOverrideChunk( self, path ):
- '''
- Set the tools directory manual override.
- '''
- try: path = path.resolve().asNative()
- except AttributeError: pass
- # First remove the old variable
- try:
- t = self.getChunk().ToolsDir
- t.parent.value.remove( t )
- except AttributeError:
- pass
- try:
- self.getChunk().ToolsDirOverride.value = str( path )
- except AttributeError:
- c = Chunk( 'ToolsDirOverride', str( path ) )
- self.getChunk().append( c )
- def setToolsChunk( self, path ):
- '''
- Set the calculated tools directory Path, i.e. %VTOOLS%
- '''
- try: path = path.resolve().asNative()
- except AttributeError: pass
- # Now add the new one
- try:
- self.getChunk().Tools.value = str( path )
- self.vTools = path
- except AttributeError:
- c = Chunk( 'Tools', str( path ) )
- self.getChunk().append( c )
- def removeToolsDirOverrideChunk( self ):
- '''
- Delete the ToolsDir Chunk from the project
- '''
- try:
- t = self.getChunk().ToolsDirOverride
- t.parent.value.remove( t )
- except AttributeError:
- pass
- def getProjectDir( self ):
- '''
- Get the project directory Path, i.e. %VPROJECT%
- '''
- projDir = Path( self.getChunk().GameDir.value )
- if projDir == '///':
- # If directory is empty, return empty Path
- return Path( '' )
- else:
- return projDir
- def setProjectDir( self, path ):
- '''
- Set the project directory Path, i.e. %VPROJECT%
- '''
- path = Path(path)
- pathStr = path.resolve().asNative()
- self.getChunk().GameDir.value = pathStr
- def getVersionControlWorkspace( self ):
- '''
- Get the version control (Perforce) workspace associated with the project
- Returns None if no workspace is set for the project.
- '''
- try:
- return self.getChunk().VerControlWorkspace.value
- except AttributeError:
- return None
- def setVersionControlWorkspace( self, workspace ):
- '''
- Set the version control (Perforce) workspace associated with the project
- '''
- if workspace:
- try:
- self.getChunk().VerControlWorkspace.value = str( workspace )
- except AttributeError:
- c = Chunk( 'VerControlWorkspace', str( workspace ) )
- self.getChunk().append( c )
- # If None, remove it from the config
- else:
- try:
- v = self.getChunk().VerControlWorkspace
- v.parent.value.remove( v )
- except AttributeError:
- pass
- def findP4Config( self ):
- '''
- Look for a Perforce configuration file relative to the project and return the filepath.
- '''
- p4cfg = checkp4Environ( 'p4config' )
- if not p4cfg:
- p4cfg = 'p4config'
- p4cfgDir = self.getProjectDir().asNative()
- rootDir = p4cfgDir.split( '\\' )[0]
- p4cfgFile = '{0}\\{1}'.format( p4cfgDir, p4cfg )
- if os.path.exists( p4cfgFile ):
- return p4cfgFile
- else:
- # Now go up the until we find an existing cfg file
- while p4cfgDir != rootDir:
- p4cfgDir = '\\'.join( p4cfgDir.split( '\\' )[:-1] )
- p4cfgFile = '{0}\\{1}'.format( p4cfgDir, p4cfg )
- if os.path.exists( p4cfgFile ):
- return p4cfgFile
- return False
- def getRunIn32Bit( self ):
- '''
- Get the RunIn32Bit value, and return 1 (win32) if it doesn't exist at all.
- This is used to set various paths to the correct binaries.
- '''
- try:
- return int( self.getChunk().RunIn32Bit.value )
- except AttributeError:
- return 1
- def setRunIn32Bit( self, supported=False ):
- '''
- Set whether the project should run in 32-bit. This is used to set various paths to the correct binaries.
- '''
- try:
- self.getChunk().RunIn32Bit.value = int( supported )
- except AttributeError:
- c = Chunk( 'RunIn32Bit', int( supported ) )
- self.getChunk().append( c )
- def set_use_NT_SYMBOL_PATH( self, supported=True ):
- '''
- Set whether the _NT_SYMBOL_PATH option should be use for the project. Default is False.
- '''
- try:
- self.getChunk().use_NT_SYMBOL_PATH.value = int( supported )
- except AttributeError:
- c = Chunk( 'use_NT_SYMBOL_PATH', int( supported ) )
- self.getChunk().append( c )
- def get_use_NT_SYMBOL_PATH( self ):
- '''
- Get whether the _NT_SYMBOL_PATH option should be use for the project. Returns False if it doesn't exist.
- '''
- try:
- return int( self.getChunk().use_NT_SYMBOL_PATH.value )
- except AttributeError:
- return False
- def remove_use_NT_SYMBOL_PATH( self ):
- '''
- Delete the NT_SYMBOL_PATH Chunk from the project
- '''
- try:
- c = self.getChunk().use_NT_SYMBOL_PATH
- c.parent.value.remove( c )
- except AttributeError:
- pass
- def getMAYA_SCRIPT_PATH( self ):
- '''
- Returns the MAYA_SCRIPT_PATH override, or the default value for the project
- '''
- override = self.getMAYA_SCRIPT_PATHOverride()
- if override:
- return override
- else:
- maya_script_path = self.cleanEnvarPath( ENVARS.getCombined( self.engine )[ 'MAYA_SCRIPT_PATH' ][0] )
- return Path( maya_script_path ).asNative()
- def getMAYA_SCRIPT_PATHOverride( self ):
- '''
- Returns a Path for the custom MAYA_SCRIPT_PATH for the project. Returns False if it doesn't exist.
- '''
- try:
- return Path( self.getChunk().MAYA_SCRIPT_PATH.value ).unresolved()
- except AttributeError:
- return False
- def setMAYA_SCRIPT_PATHOverride( self, mayaPath ):
- '''
- Set a custom MAYA_SCRIPT_PATH envar for the project.
- '''
- try:
- self.getChunk().MAYA_SCRIPT_PATH.value = str( mayaPath )
- except AttributeError:
- c = Chunk( 'MAYA_SCRIPT_PATH', str( mayaPath ) )
- self.getChunk().append( c )
- def removeMAYA_SCRIPT_PATHOverride( self ):
- '''
- Delete the MAYA_SCRIPT_PATH Chunk from the project
- '''
- try:
- c = self.getChunk().MAYA_SCRIPT_PATH
- c.parent.value.remove( c )
- except AttributeError:
- pass
- def getPYTHONHOME( self ):
- '''
- Returns the PYTHONHOME override, or the default value
- '''
- override = self.getPYTHONHOMEOverride()
- if override:
- return override
- else:
- try:
- pythonhome = self.cleanEnvarPath( ENVARS.getCombined( self.engine )[ 'PYTHONHOME' ][0] )
- return Path( pythonhome ).asNative()
- except KeyError:
- return None
- def getPYTHONHOMEOverride( self ):
- '''
- Returns a Path for the custom PYTHONHOME for the project. Returns False if it doesn't exist.
- '''
- try:
- return Path( self.getChunk().PYTHONHOME.value ).unresolved()
- except AttributeError:
- return False
- def setPYTHONHOMEOverride( self, pythonPath ):
- '''
- Set a custom PYTHONHOME envar that should be used for the project.
- '''
- try:
- self.getChunk().PYTHONHOME.value = str( pythonPath )
- except AttributeError:
- c = Chunk( 'PYTHONHOME', str( pythonPath ) )
- self.getChunk().append( c )
- def removePYTHONHOMEOverride( self ):
- '''
- Delete the PYTHONHOME Chunk from the project
- '''
- try:
- c = self.getChunk().PYTHONHOME
- c.parent.value.remove( c )
- except AttributeError:
- pass
- def getEngine( self ):
- '''
- Return an str with the current engine version.
- If key doesn't doesn't exist, assume 'Source', otherwise invalid -- assume next-gen 'Source 2'.
- '''
- try:
- eng = unicode( self.getChunk().Engine.value )
- if eng not in ENGINE.ALL_ENGINES:
- return ENGINE.SOURCE2
- return eng
- except ( AttributeError, ValueError ):
- try:
- # HACK: check vproject for 'source2' or 'hybrid' and guess the engine.
- projDir = str.lower( self.getProjectDir() )
- if 'source2' in projDir:
- return ENGINE.SOURCE2
- elif ( 'hybrid' in projDir ) or ( 'l4d2port' in projDir ):
- return ENGINE.HYBRID
- else:
- return ENGINE.SOURCE
- except AttributeError:
- return ENGINE.SOURCE
- def setEngine( self, version=ENGINE.SOURCE ):
- '''
- Set the engine version for the project, i.e. 'Source 2'
- '''
- try:
- self.getChunk().Engine.value = version
- self.engine = version
- except AttributeError:
- c = Chunk( 'Engine', version )
- self.getChunk().append( c )
- def getRequiredPaths( self ):
- '''
- Get a list of the required paths for a project
- '''
- engine = self.getEngine()
- return PATHS.BY_ENGINE[ engine ]
- def getRequiredPathsExpanded( self ):
- '''
- Get a list of the required paths for a project, with expanded envars
- '''
- expPaths = []
- for p in self.getRequiredPaths():
- # Expand using the project data first
- p = self.expandEnvarPath( p )
- # Then append the path using system environment
- expPaths.append( os.path.expandvars( p ) )
- return expPaths
- def getOldEnvVars( self ):
- '''
- Returns a list of deprecated envars for this project
- '''
- depEnvars = ENVARS.DEPRECATED[ ENGINE.ALL ]
- try:
- depEnvars.extend( ENVARS.DEPRECATED[ self.getEngine() ] )
- except KeyError:
- pass
- return depEnvars
- def checkForOldVars( self, useSysKeys=True ):
- '''
- Returns a list of deprecated envars for this project
- '''
- oldVars = []
- for var in self.getOldEnvVars():
- if getEnv( var ):
- oldVars.append( var )
- if oldVars: return oldVars
- return False
- def deleteOldVars( self, useSysKeys=True ):
- '''
- Removes deprecated environment variables for this project and returns a list of deleted vars.
- '''
- varList = self.checkForOldVars()
- deletedVars = []
- for var in varList:
- try:
- deleteEnv( var, useSysKeys=useSysKeys )
- deletedVars.append( var )
- except WindowsError: pass
- return deletedVars
- def createHammerData( self ):
- '''
- creates a hammer data chunk - most of the data in this chunk gets populated by hammer on startup, but the one thing that
- hammer doesn't seem to find is fgd files. so try to create a sensible fgd file to point hammer to
- '''
- #build the hammer data chunk
- hammerChunk = Chunk( 'Hammer', [] )
- self.getChunk().append( hammerChunk )
- #walk up the mod hierarchy and look for an fgd that has the same name as the mod name and use it
- fgdDir = self.getGame() + r'\bin'
- engine = self.getEngine()
- if ( engine == ENGINE.SOURCE2 ):
- configGameInfo = self.getProjectDir() + r'\gameInfo.gi'
- else:
- configGameInfo = self.getProjectDir() + r'\GameInfo.txt'
- if configGameInfo.exists:
- configGameInfo = GameInfoFile( configGameInfo )
- for mod in configGameInfo.getSearchMods():
- modFgd = ( fgdDir + '\\' + mod ).setExtension( 'fgd' )
- if modFgd.exists:
- hammerChunk.append( Chunk( 'GameData0', str( modFgd ) ) )
- return
- #if there is still no fgd found, then add the base fgd file
- hammerChunk.append( Chunk( 'GameData0', str( fgdDir + r'\base.fgd' ) ) )
- def verifyProject( self ):
- '''
- Some sanity checks to make sure a config is valid. Returns a list of missing files.
- '''
- game = self.getGame()
- engine = self.getEngine()
- platform = self.getPlatform()
- tools = self.getTools()
- if engine in ENGINE.SOURCE2_FAMILY:
- requiredFiles = [ game + r'\bin\{0}\tier0.dll'.format( platform ),
- game + r'\platform\Resource\platform_english.txt',
- game + r'\sdktools\perl\bin\perl.exe',
- game + r'\sdktools\python\{0}\{1}\python.exe'.format( PY_VERSION, platform ) ]
- requiredLocsToSync = [ game + r'\bin\{0}'.format( platform ),
- game + r'\platform',
- game + r'\sdktools',
- game + r'\sdktools' ]
- elif engine == ENGINE.SFM:
- requiredFiles = [ game + r'\bin\tier0.dll',
- game + r'\platform\Resource\platform_english.txt',
- game + r'\sdktools\perl\bin\perl.exe',
- game + r'\sdktools\python\{0}\{1}\python.exe'.format( PY_VERSION, platform ) ]
- requiredLocsToSync = [ game + r'\bin',
- game + r'\platform',
- game + r'\sdktools',
- game + r'\sdktools' ]
- else:
- requiredFiles = [ game + r'\bin\tier0.dll',
- game + r'\platform\Resource\platform_english.txt',
- game + r'\sdktools\perl\bin\perl.exe',
- tools + r'\bin\python.exe' ]
- requiredLocsToSync = [ game + r'\bin',
- game + r'\platform',
- game + r'\sdktools',
- tools + r'\bin' ]
- missing = []
- for f, syncLoc in zip( requiredFiles, requiredLocsToSync ):
- if not f.exists:
- missing.append( (f, syncLoc) )
- return missing
- def verifyToolsDir( self, tPath=None ):
- '''
- Do a simple verification if the tools directory will be set to a valid location
- '''
- if not tPath:
- tPath = self.getTools()
- engine = self.getEngine()
- # First check if it even exists
- if not os.path.exists( tPath ):
- return False
- # Now check to see if vproj.exe is there
- return os.path.exists( '{0}\\bin\\vproj.exe'.format( tPath ) )
- def authorPATHstr( self, PATH_str=None ):
- '''
- Generates a new PATH string, removing old paths adding the required paths.
- '''
- pathToks = []
- if PATH_str:
- pathToks = removeProjectPathsFromPATH( PATH_str )
- else:
- # Use the default path
- pathToks = removeProjectPathsFromPATH( getEnv( 'PATH' ) )
- requiredPaths = self.getRequiredPaths()
- engine = self.getEngine()
- newPath = []
- for p in requiredPaths:
- p_exp = self.expandEnvarPath( p )
- newPath.append( p )
- #try:
- ## generate path with project envars expanded
- #p_exp = self.expandEnvarPath( p )
- ## only add paths that exist
- #if os.path.exists( p_exp ):
- #newPath.append( p )
- #else:
- ## HACK: Source1 vproj would add even non-existant paths, so we'll do that here
- #if ( engine in ENGINE.SOURCE_FAMILY ) or ( engine == ENGINE.HYBRID ):
- #newPath.append( p )
- ## skip any paths that don't validate properly
- #except PathError:
- #pass
- # now add the required paths
- pathToks = newPath + pathToks
- # TODO: check that it's always unresolved paths going in here
- return kSEPARATOR.join( [ p for p in pathToks ] ).replace('/', '\\')
- def expandEnvarPath( self, path ):
- '''
- Expand all known envars in a path string with project data
- '''
- for e in self.envars:
- path = path.replace( '%{0}%'.format( e ), self.envars[ e ].value )
- return path
- def cleanEnvarPath( self, path ):
- '''
- Replaces known path tokens in a path
- '''
- path = path.replace( '<game>', self.vGame )
- path = path.replace( '<content>', self.vContent )
- path = path.replace( '<mod>', self.vMod )
- path = path.replace( '<platform>', self.platform )
- path = path.replace( '<tools>', self.vTools )
- path = path.replace( "/", "\\" )
- # TODO: Support UNC paths?
- return path
- def updateProjectSettings( self, useSysKeys=True ):
- '''
- Adds various settings as attributes to the project.
- '''
- self.name = self.getName()
- self.platform = self.getPlatform()
- self.engine = self.getEngine()
- self.vGame = self.getGame()
- self.vMod = self.getMod()
- self.vContent = self.getContent()
- self.vTools = self.getTools()
- # Set defaults for miscellaneous settings
- self.setPythonInstallKeys = True
- self.setPythonFileAssociations = True
- self.setPerlFileAssociations = True
- self.setPerlPATHEXT = True
- # Set P4CONFIG if it is not already defined or set to something other than the default:
- self.setP4CONFIG = ( not checkp4Environ( 'p4config' ) and \
- ( getP4RegistryVar( 'p4config' ) != kP4CONFIG )
- )
- # Set P4PORT if it is not already defined or if it is set to the default perforce value:
- self.setP4PORT = ( not checkp4Environ( 'p4port' ) or \
- ( getP4RegistryVar( 'p4port' ) == 'perforce:1666' )
- )
- self.envars = {}
- # Add envars for this project
- envars = ENVARS.getCombined( self.engine )
- for e in envars.iterkeys():
- # Clean the path for envars with the flag set
- if envars[ e ][ 2 ]:
- value = self.cleanEnvarPath( envars[ e ][ 0 ] )
- else:
- value = envars[ e ][ 0 ]
- setting = EnvarSetting( e, value, envars[ e ][ 1 ], self.engine )
- # Variable overrides
- if self.getMAYA_SCRIPT_PATHOverride() and setting.name == 'MAYA_SCRIPT_PATH':
- setting.value = os.path.normpath( self.getMAYA_SCRIPT_PATHOverride() )
- if self.getPYTHONHOMEOverride() and setting.name == 'PYTHONHOME':
- setting.value = os.path.normpath( self.getPYTHONHOMEOverride() )
- self.envars[ setting.name ] = setting
- ## Select matching engine-specific envars for this project
- #for e in ENVARS.ADD.iterkeys():
- #if self.engine in ENVARS.ADD[ e ].keys():
- #setting = EnvarSetting( e, self.cleanEnvarPath( ENVARS.ADD[ e ][ self.engine ][0] ), ENVARS.ADD[ e ][ self.engine ][ 1 ], self.engine )
- ## Variable overrides
- #if self.getMAYA_SCRIPT_PATHOverride() and setting.name == 'MAYA_SCRIPT_PATH':
- #setting.value = os.path.normpath( self.getMAYA_SCRIPT_PATHOverride() )
- #if self.getPYTHONHOMEOverride() and setting.name == 'PYTHONHOME':
- #setting.value = os.path.normpath( self.getPYTHONHOMEOverride() )
- #self.envars[ setting.name ] = setting
- # Select matching deprecated envars for this project
- self.envarsToRemove = []
- for e in ENVARS.DEPRECATED.iterkeys():
- # Match on the exact name, 'All', or if envar is for an engine family the project is a part of.
- if e in ( self.engine, ENGINE.ALL ) or \
- self.engine in ( e ):
- for evar in ENVARS.DEPRECATED[ e ]:
- self.envarsToRemove.append( evar )
- # Only set debugging variables if global settings have been applied to the project.
- if hasattr( self, 'n_NT_SYMBOL_PATH_enabled' ):
- if self.n_NT_SYMBOL_PATH_enabled:
- if self.n_NT_SYMBOL_PATH_mode == 1:
- self.envars[ '_NT_SYMBOL_PATH' ] = EnvarSetting( '_NT_SYMBOL_PATH', self.s_NT_SYMBOL_PATH_override, True )
- else:
- userAdd = None
- if self.d_NT_SYMBOL_PATH_opts['userdef']:
- userAdd = self.s_NT_SYMBOL_PATH_useradd
- p = make_NT_SYMBOL_PATH( self.d_NT_SYMBOL_PATH_opts['extlinks'],
- self.d_NT_SYMBOL_PATH_opts['perforce'],
- userAdd )
- self.envars[ '_NT_SYMBOL_PATH' ] = EnvarSetting( '_NT_SYMBOL_PATH', p, True )
- else:
- # Remove the envar if the setting has been disabled
- self.envarsToRemove.append( '_NT_SYMBOL_PATH' )
- if hasattr( self, 'n_NT_SYMCACHE_PATH_enabled' ):
- if self.n_NT_SYMCACHE_PATH_enabled:
- if self.n_NT_SYMCACHE_PATH_mode == 1:
- self.envars[ '_NT_SYMCACHE_PATH' ] = EnvarSetting( '_NT_SYMCACHE_PATH', self.s_NT_SYMCACHE_PATH_override, True )
- else:
- self.envars[ '_NT_SYMCACHE_PATH' ] = EnvarSetting( '_NT_SYMCACHE_PATH', 'C:\SymbolCache', True )
- else:
- # Remove the envar if the setting has been disabled
- self.envarsToRemove.append( '_NT_SYMCACHE_PATH' )
- self.envVarsToMakeExpand = []
- if '_vcee_autoexp' in self.envars.keys():
- self.envVarsToMakeExpand.append( '_vcee_autoexp' )
- def apply( self, useSysKeys=True , update=True ):
- '''
- Applies this config to the system - ie performs the env var setup, and all other actions.
- If update is False, don't update the windows environment at end, usually because it is being handled elsewhere.
- '''
- # Update project settings data
- self.updateProjectSettings()
- # Add the path envars
- for name, setting in self.envars.iteritems():
- reg_value_type = REG_SZ
- if ( self.envVarsToMakeExpand ) and ( name in self.envVarsToMakeExpand ):
- reg_value_type = REG_EXPAND_SZ
- setEnv( name, setting.value, reg_value_type, useSysKeys=useSysKeys )
- os.environ[ name ] = setting.value
- ## Set the P4CONFIG variable to a useful default if it is not set
- if self.setP4CONFIG:
- setP4RegistryVar( 'P4CONFIG', kP4CONFIG )
- if self.setP4PORT:
- setP4RegistryVar( 'P4PORT', kP4PORT )
- ### DEAL WITH THE %PATH% ENV VAR ###
- # Adds the Valve paths to the beginning of the PATH variable
- # It uses System or User depending on what is globally set and passed to Apply.
- pathKeyRoot, pathSubKey = getEnvKeys( useSysKeys )
- curPATH = getEnv( 'PATH', useSysKeys=useSysKeys )
- if not curPATH:
- # If PATH doesn't exist, create it
- try:
- pathKey = OpenKey( pathKeyRoot, pathSubKey, 0, KEY_ALL_ACCESS )
- CreateKey( pathKey, 'PATH' )
- except WindowsError: pass
- finally:
- CloseKey( pathKey )
- pathValue = self.authorPATHstr( curPATH )
- setEnv( 'PATH', pathValue, REG_EXPAND_SZ, useSysKeys=useSysKeys )
- ### SETUP FILE ASSOCIATIONS ###
- # TODO: add some exception handling?
- if self.setPythonInstallKeys:
- fileAssociations.setupPythonInstallKeys( self.platform, engine=self.engine )
- if self.setPythonFileAssociations:
- fileAssociations.setupPythonAssociations( self.platform, engine=self.engine )
- if self.setPerlFileAssociations:
- fileAssociations.removePerlAssociations( includeUserChoice=False )
- fileAssociations.setupPerlAssociations( self.platform, engine=self.engine )
- # Set Perl in PATHEXT
- if self.setPerlPATHEXT:
- pathExtValue = getEnv( 'PATHEXT' )
- if ( not pathExtValue.upper().endswith( ';.PL' ) ) and ('.PL;' not in pathExtValue ):
- pathExtValue += ';.PL'
- setEnv( 'PATHEXT', pathExtValue )
- # Leaving Python out for now, will probably cause problems as it is before \bin in the path
- #pathExtValue = getEnv( 'PATHEXT' )
- #if ( not pathExtValue.upper().endswith( ';.PY' ) ) or ('.PY;' not in pathExtValue ):
- #pathExtValue += ';.PY'
- #setEnv( 'PATHEXT', pathExtValue )
- ### FIX UP SONY PATHS ###
- try:
- pathKey = OpenKey( pathKeyRoot, pathSubKey, 0, KEY_ALL_ACCESS )
- unSony_ify_PATH( pathKey )
- finally:
- CloseKey( pathKey )
- # change the PATH in the current env
- try:
- os.environ[ 'path' ] = os.path.expandvars( '{0};{1}'.format( getEnv( 'PATH', useSysKeys=True ), getEnv( 'PATH', useSysKeys=False ) ) )
- except WindowsError, RegistryError: pass
- ### SEND THE ENVIRONMENT CHANGED MESSAGE TO THE SYSTEM ###
- if update:
- sendEnvironmentUpdate()
- ### Hammer game configuration file ###
- if self.engine in ENGINE.SOURCE_FAMILY:
- # make sure there is a gameconfig.txt file in the game\bin directory has the config so hammer
- # doesn't complain when it starts...
- gcPath = self.vGame + r'\bin\gameconfig.txt'
- projectGameConfig = HammerGameConfigFile( gcPath )
- if gcPath.exists:
- for c in projectGameConfig.getProjectsCached().itervalues():
- if c.vGame == self.vGame:
- return
- # If we've come this far, then create a new project and add it to the config file
- contentDir = self.vContent + '\\' + self.getMod()
- # create the project
- newProject = projectGameConfig.addNew(self.getName(), self.vMod, self.vGame, contentDir )
- # add Hammer data to the project
- newProject.createHammerData()
- projectGameConfig.write()
- def generateDefaultGameConfig():
- '''
- Generates a default gameconfig file
- '''
- default = Path( CONFIG_PATH )
- gameCfg = Chunk( 'Configs', [] )
- version = Chunk( 'Version', CONFIG_VERSION )
- games = Chunk( 'Games', [] )
- gameCfg.append( version )
- gameCfg.append( games )
- #gamePath = kBASE_DIR / 'game'
- #contentPath = kBASE_DIR / 'content'
- #for name, mod in kDEFAULT_GAMES:
- #config = ProjectConfig.Create( name, mod, gamePath, contentPath )
- #b.append( config.getChunk() )
- try:
- default.write( str( gameCfg ), False )
- except: pass
- return default
- class HammerGameConfigFile( KeyValueFile ):
- '''
- This is the file that contains all of the Hammer data/preferences for each project.
- Preserves the gameConfig file as it is written - the GameConfigFile class below is what is primarily used to interact with the gameConfig
- 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
- TODO: Try flipping this relationship. Shouldn't this just be a subclass of GameConfigFile with the extra info added, instead of removed?
- '''
- def __init__( self, filepath, lineParser=None, chunkClass=Chunk, readCallback=None ):
- KeyValueFile.__init__(self, filepath, parseLine, chunkClass, readCallback, True)
- # Create cached version of config data as ProjectConfig objects
- self.createProjectCache()
- def createProjectCache( self ):
- '''
- Regenerate the project cache
- '''
- # Create cached version of config data
- self.configCache = {}
- for c in self.getGamesChunk():
- #if a chunk doesnt' have the GameDir key, bail - its not a valid config
- try:
- c.GameDir
- except AttributeError: continue
- self.configCache[ c.key ] = ProjectConfig( c )
- def updateProjectCache( self, projectName ):
- '''
- Update a named project's data in the cache
- '''
- self.configCache[ projectName ] = self.getNamedProject( projectName )
- def removeFromProjectCache( self, project ):
- '''
- Remove a named project from the cache
- '''
- try:
- del self.configCache[ project ]
- return True
- except AttributeError:
- return False
- def getGamesChunk( self ):
- '''
- Returns a Chunk of valid games in the config
- '''
- try:
- gamesChunk = self.findKey( 'Games' )[ 0 ]
- if not gamesChunk.hasLen:
- gamesChunk.value = []
- return gamesChunk
- except IndexError:
- #in this case, build it out...
- configChunk = Chunk( 'Configs', [] )
- gameChunk = Chunk( 'Games', [] )
- self.append( configChunk )
- configChunk.append( gameChunk )
- return gameChunk
- def getProjectsCached( self ):
- '''
- Returns a dict of projects as ProjectConfig objects keyed by project name
- '''
- return self.configCache
- def getProjects( self ):
- '''
- Returns a list of projects as ProjectConfig objects
- '''
- configs = []
- for c in self.getGamesChunk():
- #if a chunk doesnt' have the GameDir key, bail - its not a valid config
- try:
- c.GameDir
- except AttributeError: continue
- configs.append( ProjectConfig( c ) )
- return configs
- def getNames( self ):
- return utils.removeDupes( [c.getName() for c in self.getProjects()] )
- def getNamesCached( self ):
- return utils.removeDupes( self.configCache.keys() )
- def getCurrentProject( self ):
- '''
- Returns the projectConfig object corresponding to the current project.
- None is returned if the project cannot be determined.
- '''
- # Get values from current environment
- try:
- curMod = os.path.normpath( os.environ[ 'VMOD' ] )
- curGame = os.path.normpath( os.environ[ 'VGAME' ] )
- curContent = os.path.normpath( os.environ[ 'VCONTENT' ] )
- curTools = os.path.normpath( os.environ[ 'VTOOLS' ] )
- curPlatform = filesystem.platform()
- except AttributeError, KeyError:
- return None
- try:
- curPYTHON_HOME = os.path.normpath( os.getenv( 'PYTHONHOME' ) )
- except AttributeError, KeyError:
- curPYTHON_HOME = None
- try:
- curMAYA_SCRIPT_PATH = os.path.normpath( os.getenv( 'MAYA_SCRIPT_PATH' ) )
- except AttributeError, KeyError:
- curPythonHome = None
- curProj = [ curMod,
- curGame,
- curContent,
- curTools,
- curPlatform,
- curPYTHON_HOME,
- curMAYA_SCRIPT_PATH
- ]
- # Compare against cached project profiles to find a match
- for c in self.getProjectsCached().itervalues():
- if c.getProjectProfile() == curProj:
- return c
- return None
- def getNameOfCurrent( self ):
- try:
- return self.getCurrentProject().getName()
- except ( AttributeError, KeyError, PathError ):
- return None
- def getNamedProject( self, theName ):
- '''
- Finds the named project and returns it as a ProjectConfig object
- '''
- configs = self.getProjects()
- for c in configs:
- if c.getName() == theName:
- return c
- return None
- def getNamedProjectCached( self, theName ):
- '''
- Returns the named project from the cache as a ProjectConfig object
- '''
- try:
- return self.configCache[ theName ]
- except KeyError:
- return None
- def getNamedChunk( self, theName ):
- '''
- returns the mod, game, content tuple for a named project
- '''
- for pData in self.getGamesChunk():
- if theName == pData.key:
- return pData
- return None
- def addNew( self, name, mod, gamePath, contentPath=None):
- '''
- Creates a new project object and appends it to this instance's config list. The new project is returned.
- '''
- project = ProjectConfig.Create( name, mod, gamePath, contentPath )
- self.add( project )
- return project
- def add( self, project ):
- '''
- Appends a project to this instance's config list.
- '''
- self.getGamesChunk().append( project.getChunk() )
- # Update the project cache
- self.configCache[ project.name ] = project
- def rename( self, oldProjectName, newProjectName ):
- config = self.getNamedProject( oldProjectName )
- config.setName( newProjectName )
- # Update the project cache
- self.configCache[ newProjectName ] = self.configCache[ oldProjectName ]
- del self.configCache[ oldProjectName ]
- return config.getName()
- def remove( self, projectName ):
- '''
- given a projectName, will remove it from the config
- '''
- config = self.getNamedProject( projectName )
- try:
- chunk = config.getChunk()
- chunk.parent.value.remove( chunk )
- # Update the project cache
- del self.configCache[ projectName ]
- except AttributeError: raise AttributeError('no such project')
- def write( self, filepath=None, *a ):
- '''
- gameConfigs generally don't want to be added to perforce, so override the base class's method to never add to perforce
- '''
- KeyValueFile.write( self, filepath, False )
- class GameConfigFile( HammerGameConfigFile ):
- def __init__( self, filepath=None, lineParser=None, chunkClass=Chunk, readCallback=None ):
- self.isFirstRun = False
- self.isImportGameConfig = False
- if filepath is None:
- filepath = Path( CONFIG_PATH )
- if not filepath.exists:
- # If the default gameconfig location doesn't exist, first try looking in the old location, the user's home dir.
- # If that doesn't work, try and copy any project's gameconfig we can find.
- # This is unlikely, as the user has most likely run this tool from a new machine, but we'll try anyway
- # TODO: Should probably check that the default location is not set to the either of these.
- self.isFirstRun = True
- try:
- userpath = Path( '%USERPROFILE%\gameconfig.txt' )
- # If the user config file exists, copy it to the config location
- if userpath.exists:
- # if the config directory doesn't exist, create it
- if not filepath.up(1).exists:
- os.mkdir( filepath.up(1) )
- userpath.copy( CONFIG_PATH )
- self.isImportGameConfig = True
- # Otherwise, try using the project gameconfig
- else:
- gamepath = Path( '%VGAME%\bin\gameconfig.txt' )
- # if the branch based config file exists, copy it to config location
- if gamepath.exists:
- # if the config directory doesn't exist, create it
- if not filepath.up(1).exists:
- os.mkdir( filepath.up(1) )
- gamepath.copy( CONFIG_PATH )
- self.isImportGameConfig = True
- # otherwise, create a default one
- else:
- gamepath = generateDefaultGameConfig()
- except IOError:
- filepath = generateDefaultGameConfig()
- HammerGameConfigFile.__init__( self, filepath, parseLine, chunkClass, readCallback )
- # Remove any Hammer chunks if they exist - all the hammer config data is handled by the gameconfig file in the
- # game\bin directory. GameConfigFile simply handles the actual project configuations
- gc = self.getGamesChunk()
- for c in gc:
- try:
- hammerChunk = c.Hammer
- c.value.remove( hammerChunk )
- except AttributeError: pass
- def addCurrentVersion( self ):
- '''
- Add the current version to the beginning of the gameconfig.txt file
- '''
- configChunk = self.findKey( 'Configs' )[ 0 ]
- versionChunk = Chunk( 'Version', CONFIG_VERSION )
- configChunk.insert( 0, versionChunk )
- def setVersion( self ):
- '''
- Set the gameconfig.txt file version
- '''
- try:
- configChunk = self.findKey( 'Configs' )[ 0 ]
- versionChunk = self.findKey( 'Version' )[ 0 ]
- configChunk.value.remove( versionChunk )
- self.addCurrentVersion()
- except IndexError:
- return None
- def getVersion( self ):
- '''
- Get the gameconfig.txt file version or return None
- '''
- try:
- versionChunk = self.findKey( 'Version' )[ 0 ]
- return versionChunk.value
- except IndexError:
- return None
- version = property( getVersion )
- if __name__ == "__main__":
- pass
- #end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement