Module houdiniBackEnd
[hide private]
[frames] | no frames]

Source Code for Module houdiniBackEnd

  1  ''' 
  2  A class that allows for a dynamic allocation model for Houdini 
  3  ''' 
  4  #====================================== 
  5  #  $Revision: #8 $ 
  6  #  $Change: 15708 $ 
  7  #====================================== 
  8   
  9  import sys 
 10  import os 
 11  import re 
 12  import glob 
 13  import pprint 
 14   
 15  import qb.backend.pythonChildBackEnd 
 16  import qb.backend.utils as backendUtils 
 17   
18 -class HoudiniBackEnd(qb.backend.pythonChildBackEnd.PythonChildBackEnd):
19 ''' 20 A backend for a jobtype that runs an instance of Houdini in a child process. 21 22 This will allow for the Houdini session to persist for the duration of the subjob. 23 '''
24 - def __init__(self, job):
25 super(HoudiniBackEnd, self).__init__(job) 26 27 # let the back-end auto-discover the Houdini installation if it needs to 28 (self.job['package']['HFS'], self.pyExecutable) = self.findHoudini()
29
30 - def getSubprocessArgs(self, port):
31 ''' 32 Get the arguments to start the child python process. 33 34 @param port: the port on which the PyCmdDispatcher's backchannel instance is listening, usually 35 passed as a parameter to the child_bootstrapper.py script which starts up the pyCmdExecutor 36 inside child process started by the PyCmdDispatcher 37 38 @type port: C{int} 39 40 @return: Return a tuple of a list of args to start the python interpreter, 41 and a list of python commands to initialize the python working environment. 42 43 @rtype: C{tuple} C{([childArgs], [pyInitCmds])} 44 ''' 45 46 args = [] 47 if sys.platform == 'win32': 48 pyCmdLine = '"%s\\bin\\hcmd.exe" /c ""%s" -u "%s" --port %s --kind %s"' % (self.job['package']['HFS'], self.pyExecutable, self.childBootstrapper, port, self.job['kind']) 49 else: 50 args.extend(['/bin/tcsh', '-c']) 51 pyCmdLine = 'cd %s ; source houdini_setup; %s -u %s --port %s --kind %s' % (self.job['package']['HFS'], self.pyExecutable, self.childBootstrapper, port, self.job['kind']) 52 53 if sys.platform == 'darwin': 54 pyCmdLine = 'setenv VERSIONER_PYTHON_VERSION 2.7; setenv VERSIONER_PYTHON_PREFER_32_BIT no; %s' % pyCmdLine 55 56 args.append(pyCmdLine) 57 58 pyInitCmds = [ 59 'import sys, os', 60 61 ''' 62 def enableHouModule(): 63 import sys,os 64 65 if sys.platform == 'linux2' and hasattr(sys, "setdlopenflags"): 66 old_dlopen_flags = sys.getdlopenflags() 67 import DLFCN 68 sys.setdlopenflags(old_dlopen_flags | DLFCN.RTLD_GLOBAL) 69 70 try: 71 import hou 72 except ImportError: 73 # Add $HFS/houdini/python2.xlibs to sys.path so Python can find the 74 # hou module. 75 sys.path.append(os.environ['HFS'] + "/houdini/python%d.%dlibs" % sys.version_info[:2]) 76 77 if sys.platform == 'darwin': 78 lib_path = '%s/Libraries' % os.environ['HFS'].replace('/Resources', '') 79 if 'DYLD_LIBRARY_PATH' in os.environ: 80 os.environ['DYLD_LIBRARY_PATH'] = '%s:%s' % (os.environ.get('DYLD_LIBRARY_PATH', ''), lib_path) 81 else: 82 os.environ['DYLD_LIBRARY_PATH'] = lib_path 83 84 import hou 85 finally: 86 if sys.platform == 'linux2' and hasattr(sys, "setdlopenflags"): 87 sys.setdlopenflags(old_dlopen_flags) 88 ''', 89 90 'enableHouModule()', 91 'import hou', 92 93 ] 94 95 return (args, pyInitCmds)
96
97 - def findHoudini(self):
98 ''' 99 If HFS is passed in from the job, and is valid on this host, use it. 100 Otherwise, get a clue from job.conf and search. 101 ''' 102 HFS = '' 103 hVers = {} 104 pythonExec = '' 105 106 HFS_BLOCK_NAME = 'HFS_PATHS' 107 osPathRGX = re.compile('^(\w+):\s*"(.*)"') 108 hfsVerRGX = re.compile('(\d+)\.(\d+)\.(\d+)') 109 110 # --------------------------------------- 111 # determine HFS 112 # --------------------------------------- 113 114 # catch case where pkg['HFS'] is None 115 if self.job['package']['HFS'] is None: 116 del self.job['package']['HFS'] 117 118 if os.path.isdir(self.job['package'].get('HFS', '')): 119 HFS = self.job['package']['HFS'] 120 backendUtils.flushPrint('INFO: Using HFS value from job submission') 121 else: 122 # --------------------------------------------------------- 123 # look for job.conf in the same directory as this module. 124 # --------------------------------------------------------- 125 thisDir = os.path.dirname(backendUtils.getModulePath()) 126 jobConfPath = '%s/job.conf' % thisDir 127 backendUtils.flushPrint('INFO: Scanning %s for HFS possibilities' % jobConfPath) 128 129 paths = backendUtils.scanConfForPaths(jobConfPath, HFS_BLOCK_NAME) 130 131 # ------------------------------------------------------------ 132 # test and see if any of the paths in the job.conf are a 133 # real directory, and if so, use it 134 # ------------------------------------------------------------ 135 for hfsPath in paths[sys.platform]: 136 if os.path.isdir(hfsPath): 137 # found a real path, not a path template... 138 HFS = hfsPath 139 backendUtils.flushPrint('INFO: using HFS path from job.conf - %s' % hfsPath) 140 break 141 142 if HFS == '': 143 # --------------------------------------- 144 # Find the houdini version from the job 145 # --------------------------------------- 146 hVerString = '' 147 (hVerMajor, hVerMinor, hVerBuild) = (0,0,0) 148 if 'houdiniVersion' in self.job['package']: 149 (hVerMajor, hVerMinor, hVerBuild) = self.job['package'].get('houdiniVersion', (0,0,0))[0:3] 150 else: 151 # search the job's HFS value for a version number 152 m = hfsVerRGX.search(self.job['package'].get('HFS','')) 153 try: 154 (hVerMajor, hVerMinor, hVerBuild) = m.groups() 155 except (AttributeError, ValueError): 156 errMsg = 'Unable to determine Houdini version from job\'s HFS value "%s" or houdiniVersion %s\n' % (self.job['package'].get('HFS', '<HFS unspecified>'), self.job['package'].get('houdiniVersion', '<hVer unspecified>').__repr__() ) 157 backendUtils.flushPrint(errMsg, fhList=[sys.stderr]) 158 return ('','') 159 160 (hVerMajor, hVerMinor, hVerBuild) = tuple([int(x) for x in (hVerMajor, hVerMinor, hVerBuild)]) 161 hVerString = '%s.%s.%s' % (hVerMajor, hVerMinor, hVerBuild) 162 backendUtils.flushPrint('INFO: Trying to find a match for Houdini v%s' % hVerString) 163 164 # first look for an exact version match (preferred behavior) 165 testedPaths = [] 166 for appPath in paths[sys.platform]: 167 # foreach line in the path block for this OS, replace the placeholder 0's with the 168 # desired version number and test to see if it's a valid path 169 # 170 # also test to see if the value in the job.conf is an actual directory with no 171 # verison number; can happen when someone symlinks hfs1.2.3 -> houdini, for example 172 testPath = re.sub('0.0.0', hVerString, appPath) 173 if testPath == appPath and not os.path.isdir(testPath): 174 # we were NOT able to do the version substitution 175 errMsg = 'WARNING: job.conf file %s: %s\n' % jobConfPath 176 errMsg += 'WARNING: The application path template does not contain a version placeholder of all 0\'s\n' 177 errMsg += 'WANRING: The invalid line looks like: %s\n' % appPath 178 backendUtils.flushPrint(errMsg, fhList=[sys.stderr]) 179 else: 180 if os.path.isdir(testPath): 181 HFS = testPath 182 backendUtils.flushPrint('INFO: Found a version match: HFS = %s' % HFS) 183 break 184 else: 185 testedPaths.append(testPath) 186 187 hVerMatchType = self.job['package'].get('hVerMatchType', 'bestEffort') 188 hVerMajorMustMatch = self.job['package'].get('hVerMajorMustMatch') 189 190 if HFS == '': 191 if hVerMatchType == 'exact': 192 # If we got here, we never found an exact version match 193 backendUtils.flushPrint('ERROR: An exact Houdini version match is required but not found for version: %s' % hVerString, fhList=[sys.stdout, sys.stderr]) 194 errMsg = ['ERROR: Paths tested but not found:'] 195 errMsg.extend(['ERROR: \t%s' % x for x in testedPaths]) 196 errMsg.append('ERROR: subjob exiting...') 197 backendUtils.flushPrint('\n'.join(errMsg)) 198 return ('','') 199 200 # now scan for all versions, see if we can find one suitable 201 202 # build a dict of {major: {minor: {build:path}}} 203 for appPath in paths[sys.platform]: 204 globPath = re.sub('0.0.0', '*.*.*', appPath) 205 if globPath != appPath: 206 for foundHFS in glob.glob(globPath): 207 (major, minor, build) = [int(x) for x in hfsVerRGX.search(foundHFS).groups()] 208 hVers.setdefault(major, {}).setdefault(minor, {})[build] = foundHFS 209 210 if len(hVers.keys()) == 0: 211 # we never found any install locations with version numbers 212 backendUtils.flushPrint('ERROR: An Houdini installation was not found in any of the paths tested', fhList=[sys.stdout, sys.stderr]) 213 errMsg = ['ERROR: Paths tested but not found:'] 214 errMsg.extend(['ERROR: \t%s' % x for x in testedPaths]) 215 errMsg.append('ERROR: subjob exiting...') 216 backendUtils.flushPrint('\n'.join(errMsg)) 217 return ('','') 218 # ------------------------------------------------------------------- 219 # now find an appropriate version according to the match type rule 220 # ------------------------------------------------------------------- 221 # 222 # minorMatchLaterBuild 223 # minorMatchFuzzyBuild 224 # 225 # majorMatchLaterMinor 226 # majorMatchFuzzyMinor 227 # 228 # bestEffort 229 #======================= 230 if hVerMatchType.startswith('minorMatch'): 231 # Try and match major & minor, maybe allow for later builds, but find the 232 # closest later build 233 if hVerMinor in hVers.get(hVerMajor, {}): 234 # insert the desired buildVer into the list, and use it to split it 235 builds = hVers[hVerMajor][hVerMinor].keys() 236 builds.append(hVerBuild) 237 builds.sort() 238 if builds[-1] == hVerBuild: 239 # our hVerBuild is later than all of them, so pick the 2nd to last 240 if hVerMatchType.count('Fuzzy'): 241 bestBuildVer = builds[-2] 242 else: 243 backendUtils.flushPrint('ERROR: Unable to find a build version later than %s for Houdini v%s.%s' % (hVerBuild, hVerMajor, hVerMinor), fhList=[sys.stdout, sys.stderr]) 244 errMsg = ['ERROR: builds found:'] 245 errMsg.extend(['ERROR: \t%s' % x for x in hVers[hVerMajor][hVerMinor].values()]) 246 errMsg.append('ERROR: subjob exiting...') 247 backendUtils.flushPrint('\n'.join(errMsg)) 248 return ('','') 249 else: 250 # toss the inserted hVerBuild, then get the earliest build that's later 251 # than our hVerBuild 252 bestBuildVer = builds[builds.index(hVerBuild):][1] 253 254 HFS = hVers[hVerMajor][hVerMinor][bestBuildVer] 255 256 elif hVerMatchType.startswith('majorMatch'): 257 # Try and match major, maybe allow for later minors, but find the 258 # closest minor build 259 if hVerMajor in hVers: 260 # insert the desired minor into the list, and use it to split it 261 minors = hVers[hVerMajor].keys() 262 minors.append(hVerMinor) 263 minors.sort() 264 if minors[-1] == hVerMinor: 265 # our hVerMinor is later than all of them, so pick the 2nd to last 266 if hVerMatchType.count('Fuzzy'): 267 bestMinorVer = minors[-2] 268 else: 269 backendUtils.flushPrint('ERROR: Unable to find a minor version later than %s for Houdini v%s' % (hVerMinor, hVerMajor), fhList=[sys.stdout, sys.stderr]) 270 errMsg = ['ERROR: builds found:'] 271 errMsg.extend(['ERROR: \t%s' % x for x in hVers[hVerMajor][hVerMinor].values()]) 272 errMsg.append('ERROR: subjob exiting...') 273 backendUtils.flushPrint('\n'.join(errMsg)) 274 return ('','') 275 else: 276 # toss the inserted hVerMinor, then get the earliest build that's later 277 # than our hVerMinor 278 bestMinorVer = minors[minors.index(hVerMinor):][1] 279 280 bestBuildVer = max(hVers[hVerMajor][bestMinorVer].keys()) 281 282 HFS = hVers[hVerMajor][bestMinorVer][bestBuildVer] 283 284 elif hVerMatchType == 'bestEffort': 285 # find the best major version 286 if hVerMajor in hVers: 287 bestMajorVer = hVerMajor 288 elif hVerMajorMustMatch: 289 backendUtils.flushPrint('ERROR: An exact Houdini major version match is required but not found for version: %s' % hVerMajor, fhList=[sys.stdout, sys.stderr]) 290 errMsg = ['ERROR: builds found:'] 291 errMsg.append(pprint.pformat(hVers)) 292 errMsg.append('ERROR: subjob exiting...') 293 backendUtils.flushPrint('\n'.join(errMsg)) 294 return ('','') 295 else: 296 bestMajorVer = max(hVers.keys()) 297 298 bestMinorVer = max(hVers[bestMajorVer].keys()) 299 bestBuildVer = max(hVers[bestMajorVer][bestMinorVer].keys()) 300 301 HFS = hVers[bestMajorVer][bestMinorVer][bestBuildVer] 302 303 backendUtils.flushPrint('INFO: using HFS set to %s' % HFS) 304 305 # ------------------------------------------------------------------------------------ 306 # Now look below HFS for the a version of python; if the job doesn't specify a python 307 # version, use the latest. 308 # ------------------------------------------------------------------------------------ 309 (pyVerMajor, pyVerMinor) = (0,0) 310 if self.job['package'].get('pythonVersion'): 311 (pyVerMajor, pyVerMinor) = self.job['package']['pythonVersion'] 312 313 python_location = { 314 'darwin': '%s/python/bin/python*' % HFS, 315 'linux2': '%s/python/bin/python*' % HFS, 316 'win32': '%s/python*/python.exe' % HFS 317 }[sys.platform] 318 319 # ----------------------------------------------- 320 # houdini uses the system python on linux and 321 # OS X since houdini 14 322 # ----------------------------------------------- 323 hou_ver_major = 0 324 hou_vers = hfsVerRGX.search(HFS) 325 if hou_vers: 326 try: 327 hou_ver_major = hou_vers.group(1) 328 except: 329 pass 330 331 if hou_ver_major >= 14 and sys.platform != 'win32': 332 pythonExec = '/usr/bin/python' 333 334 if not os.path.isfile(pythonExec): 335 pyVers = [x for x in glob.glob(python_location) if os.path.isfile(x) and not os.path.islink(x)] 336 if pyVerMajor == 0: 337 # unspecified, so pick the highest major version 338 for py in pyVers: 339 pVMaj = re.search('python(\d+)', os.path.basename(py)).group(1) 340 pyVerMajor = max(pVMaj, pyVerMajor) 341 342 # now pick the highest minor version from that major version 343 for py in [x for x in pyVers if x.count('python%s' % pyVerMajor)]: 344 pVMin = re.search('python\d+.(\d+)',os.path.basename(py)).group(1) 345 pyVerMinor = max(pVMin, pyVerMinor) 346 347 if sys.platform == 'win32': 348 pyGlobPat = '%s*' % python_location 349 else: 350 pyGlobPat = '%s%s.%s*' % (python_location, pyVerMajor, pyVerMinor) 351 pyName = pyGlobPat.replace('*', '') 352 353 pyBins = [x for x in glob.glob(pyGlobPat) if not x.endswith('-bin')] 354 if len(pyBins) == 1: 355 pythonExec = pyBins[0] 356 else: 357 # looks like we have to pick between word-size, 358 pyWordSize = 0 359 for py in pyBins: 360 m = re.search('python%s.%s-(\d{2})' % (pyVerMajor, pyVerMinor), py) 361 if m: 362 pyWordSize = max(pyWordSize, int(m.group(1))) 363 364 pythonExec = '%s-%s' % (pyName, pyWordSize) 365 366 return (HFS, pythonExec)
367