1 '''
2 A class that allows for a dynamic allocation model for Houdini
3 '''
4
5
6
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 '''
29
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
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
112
113
114
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
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
133
134
135 for hfsPath in paths[sys.platform]:
136 if os.path.isdir(hfsPath):
137
138 HFS = hfsPath
139 backendUtils.flushPrint('INFO: using HFS path from job.conf - %s' % hfsPath)
140 break
141
142 if HFS == '':
143
144
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
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
165 testedPaths = []
166 for appPath in paths[sys.platform]:
167
168
169
170
171
172 testPath = re.sub('0.0.0', hVerString, appPath)
173 if testPath == appPath and not os.path.isdir(testPath):
174
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
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
201
202
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
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
220
221
222
223
224
225
226
227
228
229
230 if hVerMatchType.startswith('minorMatch'):
231
232
233 if hVerMinor in hVers.get(hVerMajor, {}):
234
235 builds = hVers[hVerMajor][hVerMinor].keys()
236 builds.append(hVerBuild)
237 builds.sort()
238 if builds[-1] == hVerBuild:
239
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
251
252 bestBuildVer = builds[builds.index(hVerBuild):][1]
253
254 HFS = hVers[hVerMajor][hVerMinor][bestBuildVer]
255
256 elif hVerMatchType.startswith('majorMatch'):
257
258
259 if hVerMajor in hVers:
260
261 minors = hVers[hVerMajor].keys()
262 minors.append(hVerMinor)
263 minors.sort()
264 if minors[-1] == hVerMinor:
265
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
277
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
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
307
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
321
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
338 for py in pyVers:
339 pVMaj = re.search('python(\d+)', os.path.basename(py)).group(1)
340 pyVerMajor = max(pVMaj, pyVerMajor)
341
342
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
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