1
2
3
4
5
6
7 from Ganga.GPIDev.Adapters.IApplication import IApplication
8 from Ganga.GPIDev.Adapters.IRuntimeHandler import IRuntimeHandler
9 from Ganga.GPIDev.Schema import FileItem, Schema, SimpleItem, Version
10 from Ganga.GPIDev.Lib.File import File
11
12 from Ganga.Utility.Config import makeConfig, getConfig, ConfigError
13 from Ganga.Utility.root import getrootsys,getpythonhome
14
15 import Ganga.Utility.logging
16 logger = Ganga.Utility.logging.getLogger()
17
18
19
20 import sys
21 config = makeConfig('ROOT',"Options for Root backend")
22 config.addOption('arch','slc4_ia32_gcc34','Architecture of ROOT')
23 config.addOption('location','/afs/cern.ch/sw/lcg/external/root','Location of ROOT')
24 config.addOption('path','','Set to a specific ROOT version. Will override other options.')
25 config.addOption('pythonhome','${location}/../Python/${pythonversion}/${arch}/','Location of the python used for execution of PyROOT script')
26 config.addOption('pythonversion','',"Version number of python used for execution python ROOT script")
27 config.addOption('version','5.18.00','Version of ROOT')
28
29 import os
30
31 -class Root(IApplication):
32 """
33 Root application -- running ROOT
34
35 To run a job in ROOT you need to specify the CINT script to be
36 executed. Additional files required at run time (shared libraries,
37 source files, other scripts, Ntuples) should be placed in the
38 inputsandbox of the job. Arguments can be passed onto the script using
39 the 'args' field of the application.
40
41 Defining a Simple Job:
42
43 As an example the script analysis.C in the directory ~/abc might
44 contain:
45
46 void analysis(const char* type, int events) {
47 std::cout << type << " " << events << std::endl;
48 }
49
50 To define an LCG job on the Ganga command line with this script,
51 running in ROOT version 5.14.00b with the arguments 'MinBias'
52 and 10, you would do the following:
53
54 r = Root()
55 r.version = '5.14.00b'
56 r.script = '~/abc/analysis.C'
57 r.args = ['Minbias', 10]
58
59 j = Job(application=r, backend=LCG())
60
61 Using Shared Libraries:
62
63 If you have private shared libraries that should be loaded you need to
64 include them in the inputsandbox. Files you want back as a result of
65 running your job should be placed in your outputsandbox.
66
67 The shared library mechanism is particularly useful in order to create
68 a thin wrapper around code that uses precompiled libraries, or
69 that has not been designed to work in the CINT environment.
70
71 **For more detailed instructions, see the following Wiki page:**
72
73 https://twiki.cern.ch/twiki/bin/view/ArdaGrid/HowToRootJobsSharedObject
74
75 A summary of this page is given below:
76
77 Consider the follow in CINT script, runMain.C, that makes use of a ROOT
78 compatible shared library:
79
80 void runMain(){
81 //set up main, eg command line opts
82 char* argv[] = {"runMain.C","--muons","100"};
83 int argc = 3;
84
85 //load the shared library
86 gSystem->Load("libMain");
87
88 //run the code
89 Main m(argv,argc);
90 int returnCode = m.run();
91 }
92
93 The class Main is as follows and has been compiled into a shared
94 library, libMain.so.
95
96 Main.h:
97
98 #ifndef MAIN_H
99 #define MAIN_H
100 #include "TObject.h"
101
102 class Main : public TObject {
103
104 public:
105 Main(){}//needed by Root IO
106 Main(char* argv[], int argc);
107 int run();
108
109 ClassDef(Main,1)//Needed for CINT
110 };
111 #endif
112
113 Main.cpp:
114
115 #include <iostream>
116 using std::cout;
117 using std::endl;
118 #include "Main.h"
119
120 ClassImp(Main)//needed for CINT
121 Main::Main(char* arvv[], int argc){
122 //do some setup, command line opts etc
123 }
124
125 int Main::run(){
126 cout << "Running Main..." << endl;
127 return 0;
128 }
129
130 To run this on LCG, a Job could be created as follows:
131
132 r = Root()
133 r.version = '5.12.00' #version must be on LCG external site
134 r.script = 'runMain.C'
135
136 j = Job(application=r,backend=LCG())
137 j.inputsandbox = ['libMain.so']
138
139 It is a requirement that your script contains a function with the same
140 name as the script itself and that the shared library file is built to
141 be binary compatible with the Grid environment (e.g. same architecture
142 and version of gcc). As shown above, the wrapper class must be made CINT
143 compatible. This restriction does not, however, apply to classes used by
144 the wrapper class. When running remote (e.g. LCG) jobs, the architecture
145 used is 'slc3_ia32_gcc323' if the Root version is 5.16 or earlier and
146 'slc4_ia32_gcc34' otherwise. This reflects the availability of builds
147 on the SPI server:
148
149 http://service-spi.web.cern.ch/service-spi/external/distribution/
150
151
152 For backends that use a local installation of ROOT the location should
153 be set correctly in the [Root] section of the configuration.
154
155 Using Python and Root:
156
157 The Root project provides bindings for Python, the language supported by
158 the Ganga command line interface. These bindings are referred to as PyRoot.
159 A job is run using PyRoot if the script has the '.py' extension or the
160 usepython flag is set to True.
161
162 There are many example PyRoot scripts available in the Root tutorials.
163 A short example is given below:
164
165 gengaus.py:
166
167 if __name__ == '__main__':
168 from ROOT import gRandom
169
170 output = file('gaus.txt','w')
171 try:
172 for i in range(100):
173 print >>output, gRandom.Gaus()
174 finally:
175 output.close()
176
177 The above script could be run in Ganga as follows:
178
179 r = Root()
180 r.version = '5.14.00'
181 r.script = '~/gengaus.py'
182 r.usepython = True #set automatically for '.py' scripts
183
184 j = Job(application=r,backend=Local())
185 j.outputsandbox = ['gaus.txt']
186 j.submit()
187
188 When running locally, the python interpreter used for running PyRoot jobs
189 will default to the one being used in the current Ganga session.
190 The Root binaries selected must be binary compatible with this version.
191
192 The pythonhome variable in the [Root] section of .gangarc controls which
193 interpreter will be used for PyRoot jobs.
194
195 When using PyRoot on a remote backend, e.g. LCG, the python version that
196 is used will depend on that used to build the Root version requested.
197
198 """
199 _schema = Schema(Version(1,1), {
200 'script' : FileItem(defvalue=File(),doc='A File object specifying the script to execute when Root starts',checkset='_checkset_script'),
201 'args' : SimpleItem(defvalue=[],typelist=['str','int'],sequence=1,doc="List of arguments for the script. Accepted types are numerics and strings"),
202 'version' : SimpleItem(defvalue='5.18.00',doc="The version of Root to run"),
203 'usepython' : SimpleItem(defvalue = False, doc="Execute 'script' using Python. The PyRoot libraries are added to the PYTHONPATH.")
204 } )
205 _category = 'applications'
206 _name = 'Root'
207 _GUIPrefs = [ { 'attribute' : 'script', 'widget' : 'FileOrString' },
208 { 'attribute' : 'args', 'widget' : 'String_List' },
209 { 'attribute' : 'version', 'widget' : 'String' },
210 { 'attribute' : 'usepython', 'widget' : 'Bool' } ]
211
212 _GUIAdvancedPrefs = [ { 'attribute' : 'script', 'widget' : 'FileOrString' },
213 { 'attribute' : 'args', 'widget' : 'String_List' },
214 { 'attribute' : 'usepython', 'widget' : 'Bool' },
215 { 'attribute' : 'version', 'widget' : 'String' } ]
216
219
222
224 """Callback used to set usepython to 1 if the script name has a *.py or *.PY extention."""
225 from os.path import splitext
226 (_,ext) = splitext(str(value.name))
227
228 if('.py' == ext.lower()):
229 logger.debug('Setting usepython to True')
230 self.usepython = True
231
232
234
236 return '\\"%s\\"' % cintArg
237
239 """Returns an environment suitable for running Root and sometimes Python."""
240 from os.path import join
241 from os import environ
242
243 from Ganga.Lib.Root.shared import setEnvironment,findPythonVersion
244
245 rootsys = getrootsys(version)
246
247 rootenv = {}
248
249 if environ.has_key('PATH'):
250 setEnvironment('PATH',environ['PATH'],update=True,environment=rootenv)
251 if environ.has_key('LD_LIBRARY_PATH'):
252 setEnvironment('LD_LIBRARY_PATH',environ['LD_LIBRARY_PATH'],update=True,environment=rootenv)
253
254 setEnvironment('LD_LIBRARY_PATH',join(rootsys,'lib'),update=True,environment=rootenv)
255 setEnvironment('PATH',join(rootsys,'bin'),update=True,environment=rootenv)
256 setEnvironment('ROOTSYS',rootsys,update=False,environment=rootenv)
257 logger.debug('Have set Root variables. rootenv is now %s', str(rootenv))
258
259 if usepython:
260
261 python_version = ''
262 try:
263 python_version = getConfig('ROOT')['pythonversion']
264 except ConfigError, e:
265 logger.debug('There was a problem trying to get [ROOT]pythonversion: %s.', e)
266
267
268 if not python_version:
269 python_version = findPythonVersion(rootsys)
270
271 if (python_version is None):
272 logger.warn('Unable to find the Python version needed for Root version %s. See the [ROOT] section of your .gangarc file.', version)
273 else:
274 logger.debug('Python version found was %s', python_version)
275 python_home = getpythonhome(pythonversion=python_version)
276 logger.debug('PYTHONHOME is being set to %s',python_home)
277
278 python_bin = join(python_home,'bin')
279 setEnvironment('PATH',python_bin,update=True,environment=rootenv)
280 setEnvironment('PYTHONPATH',join(rootsys,'lib'),update=True,environment=rootenv)
281 logger.debug('Added PYTHONPATH. rootenv is now %s', str(rootenv))
282
283 if join(python_bin,'python') != sys.executable:
284
285 logger.debug('Using a different Python - %s.', python_home)
286 python_lib = join(python_home,'lib')
287
288 import os.path
289 if not os.path.exists(python_bin) or not os.path.exists(python_lib):
290 logger.error('The PYTHONHOME specified does not have the expected structure. See the [ROOT] section of your .gangarc file.')
291
292 setEnvironment('LD_LIBRARY_PATH',python_lib,update=True,environment=rootenv)
293 setEnvironment('PYTHONHOME',python_home,update=False,environment=rootenv)
294 setEnvironment('PYTHONPATH',python_lib,update=True,environment=rootenv)
295
296 return (rootenv, rootsys)
297
299 """JobConfig for executing a Root script using CINT."""
300 from Ganga.GPIDev.Adapters.StandardJobConfig import StandardJobConfig
301 from os.path import join, split
302 import string
303
304
305
306 arglist = []
307 for arg in app.args:
308 if type(arg)==type('str'):
309 arglist.append(self.quoteCintArgString(arg))
310 else:
311 arglist.append(arg)
312 rootarg='('+string.join([str(s) for s in arglist],',')+')'
313
314 script=app.script
315 if script==File():
316 script=File(defaultScript())
317
318
319
320 arguments=['-b', '-q', join('.',script.subdir,
321 split(script.name)[1])+rootarg]
322 inputsandbox=app._getParent().inputsandbox+[script]
323
324 (rootenv, _) = self._getRootEnvSys(app.version)
325 logger.debug( "ROOT environment:\n %s: ", str(rootenv) )
326
327 return StandardJobConfig('root.exe',inputsandbox,arguments,
328 app._getParent().outputsandbox,rootenv)
329
331 """JobConfig for executing a Root script using CINT."""
332 from Ganga.GPIDev.Adapters.StandardJobConfig import StandardJobConfig
333 from os.path import join, split
334
335 script=app.script
336 if script==File():
337 script=File(defaultPyRootScript())
338
339 arguments = [join('.',script.subdir,split(script.name)[1])]
340 arguments.extend([str(s) for s in app.args])
341 arguments.append('-b')
342
343 inputsandbox=app._getParent().inputsandbox+[script]
344
345 (rootenv, _) = self._getRootEnvSys(app.version,usepython=True)
346 logger.debug( "PyRoot environment:\n %s: ", str(rootenv) )
347
348 return StandardJobConfig('python',inputsandbox,arguments,
349 app._getParent().outputsandbox,rootenv)
350
351 - def prepare(self,app,appconfig,appmasterconfig,jobmasterconfig):
352 """The default prepare method. Used to select scripting backend."""
353 if(app.usepython):
354 return self._preparePyRootJobConfig(app,appconfig,
355 appmasterconfig,jobmasterconfig)
356 else:
357 return self._prepareCintJobConfig(app,appconfig,
358 appmasterconfig,jobmasterconfig)
359
361 """Same as RootRTHander, but slight difference in string quoting used."""
363 return '"%s"' % cintArg
364
366 - def prepare(self,app,appconfig,appmasterconfig,jobmasterconfig):
375
376
378 - def prepare(self,app,appconfig,appmasterconfig,jobmasterconfig):
388
389 from Ganga.GPIDev.Adapters.ApplicationRuntimeHandlers import allHandlers
390
391 allHandlers.add('Root','Cronus',RootRTHandler)
392 allHandlers.add('Root','LSF', RootLocalRTHandler)
393 allHandlers.add('Root','Local', RootLocalRTHandler)
394 allHandlers.add('Root','Interactive', RootRTHandler)
395
396
397 allHandlers.add('Root','PBS', RootLocalRTHandler)
398 allHandlers.add('Root','SGE', RootLocalRTHandler)
399 allHandlers.add('Root','Condor', RootRTHandler)
400 allHandlers.add('Root','LCG', RootLCGRTHandler)
401 allHandlers.add('Root','TestSubmitter', RootRTHandler)
402 allHandlers.add('Root','Remote', RootLCGRTHandler)
403
405 from os.path import join,split
406 from Ganga.GPIDev.Lib.File import FileBuffer
407 import string
408
409 rootsys=join('.','root')
410 rootenv={'ROOTSYS':rootsys}
411
412 script=app.script
413 if script==File():
414 if not app.usepython:
415 script=File(defaultScript())
416 else:
417 script=File(defaultPyRootScript())
418
419 commandline = ''
420 scriptPath = join('.',script.subdir,split(script.name)[1])
421 if not app.usepython:
422
423
424 arglist = []
425 for arg in app.args:
426 if type(arg)==type('str'):
427 arglist.append('\\\\"'+arg+'\\\\"')
428 else:
429 arglist.append(arg)
430 rootarg='\('+string.join([str(s) for s in arglist],',')+'\)'
431
432
433 commandline='\'root.exe -b -q '+ scriptPath + \
434 rootarg + '\''
435 else:
436
437 pyarg = string.join([str(s) for s in app.args],' ')
438 commandline = '\'%(PYTHONCMD)s ' + scriptPath + ' ' + pyarg + ' -b \''
439
440 logger.debug( "Command line: %s: ", commandline )
441
442
443 wrapperscript= """#!/usr/bin/env python
444 '''Script to run root with cint or python.'''
445 def downloadAndUnTar(fileName, url):
446 '''Downloads and untars a file with tar xfz'''
447 from shutil import copyfileobj
448 from urllib2 import urlopen
449
450 urlFileIn = urlopen(url)
451 urlFileOut = file(fileName,'w')
452 copyfileobj(urlFileIn,urlFileOut)
453 urlFileOut.close
454
455 status = 0
456 cmd = 'tar xzf %s' % fileName
457
458 from commands import getstatusoutput, getoutput
459
460 #to check whether the folder name is 'root' or 'ROOT'
461 folderName = getoutput('tar --list --file ROOT*.tar.gz').split('/')[0]
462
463 try:#do this in try as module is only unix
464 #commmand approach removes ugly tar error
465 (status,output) = getstatusoutput(cmd)
466 except ImportError:
467 import os
468 status = os.system(cmd)
469
470 return status, folderName
471
472 def setEnvironment(key, value, update=False):
473 '''Sets an environment variable. If update=True, it preends it to
474 the current value with os.pathsep as the seperator.'''
475 from os import environ,pathsep
476 if update and environ.has_key(key):
477 value += (pathsep + environ[key])#prepend
478 environ[key] = value
479
480 def findPythonVersion(arch,rootsys):
481 '''Digs around in rootsys for config files and then greps
482 the version of python used'''
483 import os
484
485 def lookInFile(config):
486 '''Looks in the specified file for the build config
487 and picks out the python version'''
488 version = None
489 if os.path.exists(config):
490 configFile = file(config)
491 for line in configFile:#loop through the file looking for #define
492 if line.startswith('#define R__CONFIGUREOPTION'):
493 for arg in line.split(' '):#look at value of #define
494 if arg.startswith('PYTHONDIR'):
495 arglist = arg.split('/')
496 if arglist[-1] == arch:
497 version = arglist[-2]
498 return version
499
500 def useRootConfig(rootsys):
501 '''Use the new root-config features to find the python version'''
502 version = None
503 root_config = os.path.join(rootsys,'bin','root-config')
504 if os.path.exists(root_config):
505 import subprocess
506
507 args = [root_config,'--python-version']
508
509 run = subprocess.Popen(' '.join(args), shell = True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
510 out, err = [ e.splitlines() for e in run.communicate() ]
511 code = run.returncode
512 if code == 0 and out and not err and len(out) == 1:
513 split = out[0].split('.')
514 if len(out) != len(split):
515 version = '.'.join(split)
516 return version
517
518
519 version = None
520 for f in ['config.h','RConfigure.h']:
521 version = lookInFile(os.path.join(rootsys,'include',f))
522 if version is not None:
523 break
524 if version is None:
525 version = useRootConfig(rootsys)
526 return version
527
528 def greaterThanVersion(version_string, version_tuple):
529 '''Checks whether a version string is greater than a specific version'''
530 result = False
531 version_split = version_string.split('.')
532 if len(version_split) == 3:
533 try:
534 major = int(version_split[0])
535 minor = int(version_split[1])
536 if major >= version_tuple[0] and minor > version_tuple[1]:
537 result = True
538 except:
539 pass
540 return result
541
542 def findArch(version):
543 '''Method stub. In the future we might look at the
544 environment to determin the arch we are running on.'''
545
546 #SPI achitectures changed in Root > 5.16
547 if greaterThanVersion(version, (5,16) ):
548 return 'slc4_ia32_gcc34'
549 return 'slc3_ia32_gcc323'
550
551 def findURL(version, arch):
552
553 if greaterThanVersion(version, (5,16) ):
554 fname = 'ROOT_%s__LCG_%s.tar.gz' % (version,arch)
555 else:
556 fname = 'root_%s__LCG_%s.tar.gz' % (version,arch)
557
558 return fname
559
560 # Main
561 if __name__ == '__main__':
562
563 from os import curdir, system, environ, pathsep, sep
564 from os.path import join
565 import sys
566
567 commandline = ###COMMANDLINE###
568 scriptPath = '###SCRIPTPATH###'
569 usepython = ###USEPYTHON###
570
571 version = '###ROOTVERSION###'
572 arch = findArch(version)
573 fname = findURL(version,arch)
574
575 spiURL = 'http://service-spi.web.cern.ch/service-spi/external/distribution/'
576 url = spiURL + fname
577
578 print 'Downloading ROOT version %s from %s.' % (version,url)
579 (status, folderName) = downloadAndUnTar(fname,url)
580 sys.stdout.flush()
581 sys.stderr.flush()
582
583 #see HowtoPyroot in the root docs
584 import os
585 pwd = os.environ['PWD']
586 rootsys=join(pwd,folderName,version,arch,'root')
587 setEnvironment('LD_LIBRARY_PATH',curdir,True)
588 setEnvironment('LD_LIBRARY_PATH',join(rootsys,'lib'),True)
589 setEnvironment('ROOTSYS',rootsys)
590 setEnvironment('PATH',join(rootsys,'bin'),True)
591
592 if usepython:
593
594 pythonVersion = findPythonVersion(arch,rootsys)
595 if not pythonVersion:
596 print >>sys.stderr, 'Failed to find the correct version of python to use. Exiting'
597 sys.exit(-1)
598
599 tarFileName = 'Python_%s__LCG_%s.tar.gz' % (pythonVersion, arch)
600 url = spiURL + tarFileName
601
602 print 'Downloading Python version %s from %s.' % (pythonVersion,url)
603 downloadAndUnTar(tarFileName,url)
604
605 pythonDir = join('.','Python',pythonVersion,arch)
606 pythonCmd = join(pythonDir,'bin','python')
607 commandline = commandline % {'PYTHONCMD':pythonCmd}
608
609 setEnvironment('LD_LIBRARY_PATH',join(pythonDir,'lib'),True)
610 setEnvironment('PYTHONDIR',pythonDir)
611 setEnvironment('PYTHONPATH',join(rootsys,'lib'),True)
612
613 #exec the script
614 print 'Executing ',commandline
615 sys.stdout.flush()
616 sys.stderr.flush()
617 sys.exit(system(commandline)>>8)
618
619 """
620
621 wrapperscript = wrapperscript.replace('###COMMANDLINE###',commandline)
622 wrapperscript = wrapperscript.replace('###ROOTVERSION###',app.version)
623 wrapperscript = wrapperscript.replace('###SCRIPTPATH###',scriptPath)
624 wrapperscript = wrapperscript.replace('###USEPYTHON###',str(app.usepython))
625
626 logger.debug('Script to run on worker node\n'+wrapperscript)
627 scriptName = "rootwrapper_generated_%s.py" % randomString()
628 runScript = FileBuffer( scriptName, wrapperscript, executable = 1 )
629
630 inputsandbox=app._getParent().inputsandbox+[script]
631 return runScript,inputsandbox,rootenv
632
634 import tempfile,os
635 tmpdir = tempfile.mktemp()
636 os.mkdir(tmpdir)
637 fname = os.path.join(tmpdir,'test.C')
638 f = open(fname,'w')
639 f.write("""
640 void test() {
641 cout << "Hello World from ROOT" << endl;
642 cout << "Load Path : " << gSystem->GetDynamicPath() << endl;
643 gSystem->Load("libTree");
644 gSystem->Exit(0);
645 }
646 """)
647 f.close()
648 return fname
649
651 import tempfile,os
652 tmpdir = tempfile.mktemp()
653 os.mkdir(tmpdir)
654 fname = os.path.join(tmpdir,'test.py')
655 f = open(fname,'w')
656 try:
657 f.write("""#!/usr/bin/env python
658 class Main(object):
659 def run(self):
660 '''Prints out some PyRoot debug info.'''
661 print 'Hello from PyRoot. Importing ROOT...'
662 import ROOT
663 print 'Root Load Path', ROOT.gSystem.GetDynamicPath()
664
665 from os.path import pathsep
666 import string
667 import sys
668
669 print 'Python Load Path', string.join([str(s) for s in sys.path],pathsep)
670
671 print 'Loading libTree:', ROOT.gSystem.Load('libTree')
672
673 print 'Goodbye...'
674
675 if __name__ == '__main__':
676
677 m = Main()
678 m.run()
679
680 import sys
681 sys.exit(0)
682
683 """)
684 finally:
685 f.close()
686 return fname
687
689 """Simple method to generate a random string"""
690 from random import randint
691 from string import ascii_uppercase,join
692
693 def addToSample(sample,ascii_length):
694 """Basically random.select but python2.2"""
695 a = ascii_uppercase[randint(0,ascii_length-1)]
696 if not a in sample:
697 sample.append(a)
698 else:
699
700 addToSample(sample,ascii_length)
701
702 ascii_length = len(ascii_uppercase)
703 sample = []
704 for _ in range(6):
705 addToSample(sample,ascii_length)
706 assert(len(sample) == 6)
707
708
709 return join([str(a) for a in sample],'')
710