1616import json
1717import array as arr
1818
19- parser = argparse .ArgumentParser (description = 'Create a MC simulation workflow' )
19+ parser = argparse .ArgumentParser (description = 'Create an ALICE (Run3) MC simulation workflow' )
2020
2121parser .add_argument ('-ns' ,help = 'number of signal events / timeframe' , default = 20 )
2222parser .add_argument ('-gen' ,help = 'generator: pythia8, extgen' , default = 'pythia8' )
3535parser .add_argument ('-ptTrigMin' ,help = 'generated pT trigger minimum' , default = 0 )
3636parser .add_argument ('-ptTrigMax' ,help = 'generated pT trigger maximum' , default = - 1 )
3737
38- parser .add_argument ('--embedding' ,help = 'whether to embedd into background' , default = False )
38+ parser .add_argument ('--embedding' ,action = 'store_true' , help = 'With embedding into background' )
3939parser .add_argument ('-nb' ,help = 'number of background events / timeframe' , default = 20 )
4040parser .add_argument ('-genBkg' ,help = 'generator' , default = 'pythia8hi' )
4141parser .add_argument ('-iniBkg' ,help = 'generator init parameters file' , default = '${O2DPG_ROOT}/MC/config/common/ini/basic.ini' )
4646parser .add_argument ('-mod' ,help = 'Active modules' , default = '--skipModules ZDC' )
4747parser .add_argument ('-seed' ,help = 'random seed number' , default = 0 )
4848parser .add_argument ('-o' ,help = 'output workflow file' , default = 'workflow.json' )
49- parser .add_argument ('--noIPC' ,help = 'disable shared memory in DPL' )
49+ parser .add_argument ('--noIPC' ,help = 'disable shared memory in DPL' )
50+
51+ # arguments for background event caching
52+ parser .add_argument ('--upload-bkg-to' ,help = 'where to upload background event files (alien path)' )
53+ parser .add_argument ('--use-bkg-from' ,help = 'take background event from given alien path' )
54+ # power feature (for playing) --> does not appear in help message
55+ # help='Treat smaller sensors in a single digitization')
56+ parser .add_argument ('--combine-smaller-digi' , action = 'store_true' , help = argparse .SUPPRESS )
57+
5058args = parser .parse_args ()
5159print (args )
5260
@@ -89,15 +97,59 @@ def getDPL_global_options(bigshm=False,nosmallrate=False):
8997 return "-b --run --session " + str (taskcounter ) + ' --driver-client-backend ws://' + (' --rate 1' ,'' )[nosmallrate ]
9098
9199doembedding = True if args .embedding == 'True' or args .embedding == True else False
100+ usebkgcache = args .use_bkg_from != None
92101
93102if doembedding :
94- # ---- background transport task -------
95- NBKGEVENTS = args .nb
96- GENBKG = args .genBkg
97- INIBKG = args .iniBkg
98- BKGtask = createTask (name = 'bkgsim' , lab = ["GEANT" ], cpu = '8' )
99- BKGtask ['cmd' ]= 'o2-sim -e ' + SIMENGINE + ' -j ' + str (NWORKERS ) + ' -n ' + str (NBKGEVENTS ) + ' -g ' + str (GENBKG ) + str (MODULES ) + ' -o bkg --configFile ' + str (INIBKG )
100- workflow ['stages' ].append (BKGtask )
103+ if not usebkgcache :
104+ # ---- do background transport task -------
105+ NBKGEVENTS = args .nb
106+ GENBKG = args .genBkg
107+ INIBKG = args .iniBkg
108+ BKGtask = createTask (name = 'bkgsim' , lab = ["GEANT" ], cpu = '8' )
109+ BKGtask ['cmd' ]= 'o2-sim -e ' + SIMENGINE + ' -j ' + str (NWORKERS ) + ' -n ' + str (NBKGEVENTS ) + ' -g ' + str (GENBKG ) + str (MODULES ) + ' -o bkg --configFile ' + str (INIBKG )
110+ workflow ['stages' ].append (BKGtask )
111+
112+ # check if we should upload background event
113+ if args .upload_bkg_to != None :
114+ BKGuploadtask = createTask (name = 'bkgupload' , needs = [BKGtask ['name' ]], cpu = '0' )
115+ BKGuploadtask ['cmd' ]= 'alien.py mkdir ' + args .upload_bkg_to + ';'
116+ BKGuploadtask ['cmd' ]+= 'alien.py cp -f bkg* ' + args .upload_bkg_to + ';'
117+ workflow ['stages' ].append (BKGuploadtask )
118+
119+ else :
120+ # here we are reusing existing background events from ALIEN
121+
122+ # when using background caches, we have multiple smaller tasks
123+ # this split makes sense as they are needed at different stages
124+ # 1: --> download bkg_MCHeader.root + grp + geometry
125+ # 2: --> download bkg_Hit files (individually)
126+ # 3: --> download bkg_Kinematics
127+ # (A problem with individual copying might be higher error probability but
128+ # we can introduce a "retry" feature in the copy process)
129+
130+ # Step 1: header and link files
131+ BKG_HEADER_task = createTask (name = 'bkgdownloadheader' , cpu = '0' , lab = ['BKGCACHE' ])
132+ BKG_HEADER_task ['cmd' ]= 'alien.py cp ' + args .use_bkg_from + 'bkg_MCHeader.root .'
133+ BKG_HEADER_task ['cmd' ]= BKG_HEADER_task ['cmd' ] + ';alien.py cp ' + args .use_bkg_from + 'bkg_geometry.root .'
134+ BKG_HEADER_task ['cmd' ]= BKG_HEADER_task ['cmd' ] + ';alien.py cp ' + args .use_bkg_from + 'bkg_grp.root .'
135+ workflow ['stages' ].append (BKG_HEADER_task )
136+
137+ # a list of smaller sensors (used to construct digitization tasks in a parametrized way)
138+ smallsensorlist = [ "ITS" , "TOF" , "FT0" , "FV0" , "FDD" , "MCH" , "MID" , "MFT" , "HMP" , "EMC" , "PHS" , "CPV" ]
139+
140+ BKG_HITDOWNLOADER_TASKS = {}
141+ for det in [ 'TPC' , 'TRD' ] + smallsensorlist :
142+ if usebkgcache :
143+ BKG_HITDOWNLOADER_TASKS [det ] = createTask (str (det ) + 'hitdownload' , cpu = '0' , lab = ['BKGCACHE' ])
144+ BKG_HITDOWNLOADER_TASKS [det ]['cmd' ] = 'alien.py cp ' + args .use_bkg_from + 'bkg_Hits' + str (det ) + '.root .'
145+ workflow ['stages' ].append (BKG_HITDOWNLOADER_TASKS [det ])
146+ else :
147+ BKG_HITDOWNLOADER_TASKS [det ] = None
148+
149+ if usebkgcache :
150+ BKG_KINEDOWNLOADER_TASK = createTask (name = 'bkgkinedownload' , cpu = '0' , lab = ['BKGCACHE' ])
151+ BKG_KINEDOWNLOADER_TASK ['cmd' ] = 'alien.py cp ' + args .use_bkg_from + 'bkg_Kine.root .'
152+ workflow ['stages' ].append (BKG_KINEDOWNLOADER_TASK )
101153
102154# loop over timeframes
103155for tf in range (1 , NTIMEFRAMES + 1 ):
@@ -175,7 +227,7 @@ def getDPL_global_options(bigshm=False,nosmallrate=False):
175227 PDGB = 2212 # proton
176228
177229 if COLTYPE == 'PbPb' :
178- PDGA = 2212 # Pb????
230+ PDGA = 2212 # Pb???? #---> to be checked (seems same as pp case)
179231 PDGB = 2212 # Pb????
180232
181233 if COLTYPE == 'pPb' :
@@ -202,81 +254,120 @@ def getDPL_global_options(bigshm=False,nosmallrate=False):
202254 SGN_CONFIG_task ['cmd' ] = SGN_CONFIG_task ['cmd' ] + ' --weightPow=' + str (WEIGHTPOW )
203255 workflow ['stages' ].append (SGN_CONFIG_task )
204256 # elif GENERATOR == 'extgen': what do we do if generator is not pythia8?
205-
206- if doembedding :
207- # link background files to current working dir for this timeframe
208- LinkBKGtask = createTask (name = 'linkbkg_' + str (tf ), needs = [BKGtask ['name' ]], tf = tf , cwd = timeframeworkdir )
209- LinkBKGtask ['cmd' ]= 'ln -nsf ../bkg*.root .'
210- workflow ['stages' ].append (LinkBKGtask )
257+ # NOTE: Generator setup might be handled in a different file or different files (one per
258+ # possible generator)
211259
260+ # -----------------
212261 # transport signals
262+ # -----------------
213263 signalprefix = 'sgn_' + str (tf )
214264 signalneeds = [ SGN_CONFIG_task ['name' ] ]
215- embeddinto = "--embedIntoFile bkg_Kine .root" if doembedding else ""
265+ embeddinto = "--embedIntoFile ../bkg_MCHeader .root" if doembedding else ""
216266 if doembedding :
217- signalneeds = signalneeds + [ BKGtask ['name' ], LinkBKGtask ['name' ] ]
267+ if not usebkgcache :
268+ signalneeds = signalneeds + [ BKGtask ['name' ] ]
269+ else :
270+ signalneeds = signalneeds + [ BKG_HEADER_task ['name' ] ]
218271 SGNtask = createTask (name = 'sgnsim_' + str (tf ), needs = signalneeds , tf = tf , cwd = 'tf' + str (tf ), lab = ["GEANT" ], cpu = '5.' )
219- SGNtask ['cmd' ]= 'o2-sim -e ' + str (SIMENGINE ) + ' ' + str (MODULES ) + ' -n ' + str (NSIGEVENTS ) + ' -j ' + str (NWORKERS ) + ' -g ' + str (GENERATOR ) + ' ' + str (TRIGGER )+ ' ' + str (CONFKEY ) + ' ' + str (INIFILE ) + ' -o ' + signalprefix + ' ' + embeddinto
272+ SGNtask ['cmd' ]= 'o2-sim -e ' + str (SIMENGINE ) + ' ' + str (MODULES ) + ' -n ' + str (NSIGEVENTS ) + ' -j ' \
273+ + str (NWORKERS ) + ' -g ' + str (GENERATOR ) + ' ' + str (TRIGGER )+ ' ' + str (CONFKEY ) \
274+ + ' ' + str (INIFILE ) + ' -o ' + signalprefix + ' ' + embeddinto
220275 workflow ['stages' ].append (SGNtask )
221276
222277 # some tasks further below still want geometry + grp in fixed names, so we provide it here
223278 # Alternatively, since we have timeframe isolation, we could just work with standard o2sim_ files
224279 # We need to be careful here and distinguish between embedding and non-embedding cases
225280 # (otherwise it can confuse itstpcmatching, see O2-2026). This is because only one of the GRPs is updated during digitization.
226281 if doembedding :
227- LinkGRPFileTask = createTask (name = 'linkGRP_' + str (tf ), needs = [BKGtask ['name' ]], tf = tf , cwd = timeframeworkdir )
228- LinkGRPFileTask ['cmd' ]= 'ln -nsf bkg_grp.root o2sim_grp.root ; ln -nsf bkg_geometry.root o2sim_geometry.root'
282+ LinkGRPFileTask = createTask (name = 'linkGRP_' + str (tf ), needs = [BKG_HEADER_task ['name' ] if usebkgcache else BKGtask ['name' ] ], tf = tf , cwd = timeframeworkdir )
283+ LinkGRPFileTask ['cmd' ]= '''
284+ ln -nsf ../bkg_grp.root o2sim_grp.root;
285+ ln -nsf ../bkg_geometry.root o2sim_geometry.root;
286+ ln -nsf ../bkg_geometry.root bkg_geometry.root;
287+ ln -nsf ../bkg_grp.root bkg_grp.root
288+ '''
229289 else :
230290 LinkGRPFileTask = createTask (name = 'linkGRP_' + str (tf ), needs = [SGNtask ['name' ]], tf = tf , cwd = timeframeworkdir )
231291 LinkGRPFileTask ['cmd' ]= 'ln -nsf ' + signalprefix + '_grp.root o2sim_grp.root ; ln -nsf ' + signalprefix + '_geometry.root o2sim_geometry.root'
232292 workflow ['stages' ].append (LinkGRPFileTask )
233293
234-
294+ # ------------------
295+ # digitization steps
296+ # ------------------
235297 CONTEXTFILE = 'collisioncontext.root'
236298
237299 simsoption = ' --sims ' + ('bkg,' + signalprefix if doembedding else signalprefix )
238300
239- ContextTask = createTask (name = 'digicontext_' + str (tf ), needs = [SGNtask ['name' ], LinkGRPFileTask ['name' ]], tf = tf , cwd = timeframeworkdir , lab = [ "DIGI" ], cpu = '8' )
240-
301+ ContextTask = createTask (name = 'digicontext_' + str (tf ), needs = [SGNtask ['name' ], LinkGRPFileTask ['name' ]], tf = tf ,
302+ cwd = timeframeworkdir , lab = [ "DIGI" ], cpu = '1' )
241303 ContextTask ['cmd' ] = 'o2-sim-digitizer-workflow --only-context --interactionRate 50000 ' + getDPL_global_options () + ' -n ' + str (args .ns ) + simsoption
242304 workflow ['stages' ].append (ContextTask )
243305
244- TPCDigitask = createTask (name = 'tpcdigi_' + str (tf ), needs = [ContextTask ['name' ], LinkGRPFileTask ['name' ]],
245- tf = tf , cwd = timeframeworkdir , lab = ["DIGI" ], cpu = '8' , mem = '16000' )
246- TPCDigitask ['cmd' ] = 'o2-sim-digitizer-workflow ' + getDPL_global_options (bigshm = True ) + ' -n ' + str (args .ns ) + simsoption + ' --onlyDet TPC --interactionRate 50000 --tpc-lanes ' + str (NWORKERS ) + ' --incontext ' + str (CONTEXTFILE )
306+ tpcdigineeds = [ContextTask ['name' ], LinkGRPFileTask ['name' ]]
307+ if usebkgcache :
308+ tpcdigineeds += [ BKG_HITDOWNLOADER_TASKS ['TPC' ]['name' ] ]
309+
310+ TPCDigitask = createTask (name = 'tpcdigi_' + str (tf ), needs = tpcdigineeds ,
311+ tf = tf , cwd = timeframeworkdir , lab = ["DIGI" ], cpu = '8' , mem = '9000' )
312+ TPCDigitask ['cmd' ] = ('' ,'ln -nfs ../bkg_HitsTPC.root . ;' )[doembedding ]
313+ TPCDigitask ['cmd' ] += 'o2-sim-digitizer-workflow ' + getDPL_global_options () + ' -n ' + str (args .ns ) + simsoption + ' --onlyDet TPC --interactionRate 50000 --tpc-lanes ' + str (NWORKERS ) + ' --incontext ' + str (CONTEXTFILE ) + ' --tpc-chunked-writer'
247314 workflow ['stages' ].append (TPCDigitask )
248315
249- TRDDigitask = createTask (name = 'trddigi_' + str (tf ), needs = [ContextTask ['name' ]], tf = tf , cwd = timeframeworkdir , lab = ["DIGI" ], cpu = '8' , mem = '8000' )
250- TRDDigitask ['cmd' ] = 'o2-sim-digitizer-workflow ' + getDPL_global_options () + ' -n ' + str (args .ns ) + simsoption + ' --onlyDet TRD --interactionRate 50000 --configKeyValues \" TRDSimParams.digithreads=' + str (NWORKERS ) + '\" --incontext ' + str (CONTEXTFILE )
316+ trddigineeds = [ContextTask ['name' ]]
317+ if usebkgcache :
318+ trddigineeds += [ BKG_HITDOWNLOADER_TASKS ['TRD' ]['name' ] ]
319+ TRDDigitask = createTask (name = 'trddigi_' + str (tf ), needs = trddigineeds ,
320+ tf = tf , cwd = timeframeworkdir , lab = ["DIGI" ], cpu = '8' , mem = '8000' )
321+ TRDDigitask ['cmd' ] = ('' ,'ln -nfs ../bkg_HitsTRD.root . ;' )[doembedding ]
322+ TRDDigitask ['cmd' ] += 'o2-sim-digitizer-workflow ' + getDPL_global_options () + ' -n ' + str (args .ns ) + simsoption + ' --onlyDet TRD --interactionRate 50000 --configKeyValues \" TRDSimParams.digithreads=' + str (NWORKERS ) + '\" --incontext ' + str (CONTEXTFILE )
251323 workflow ['stages' ].append (TRDDigitask )
252324
253- # RESTDigitask=createTask(name='restdigi_'+str(tf), needs=[ContextTask['name'], LinkGRPFileTask['name']], tf=tf, cwd=timeframeworkdir, lab=["DIGI"], cpu='medium', mem='8000')
254- # RESTDigitask['cmd'] = 'o2-sim-digitizer-workflow ' + getDPL_global_options() + ' -n ' + str(args.ns) + simsoption + ' --skipDet TRD,TPC --interactionRate 50000 --incontext ' + str(CONTEXTFILE)
255- # workflow['stages'].append(RESTDigitask)
256-
257- # we split the digitizers for improved load balancing --> the precise list needs to be made consistent with geometry and active sensors
258- sensorlist = [ "ITS" , "TOF" , "FT0" , "FV0" , "FDD" , "MCH" , "MID" , "MFT" , "HMP" , "EMC" , "PHS" , "CPV" ]
259325 # these are digitizers which are single threaded
260- def createRestDigiTask (name ):
261- t = createTask (name = name , needs = [ContextTask ['name' ]], tf = tf , cwd = timeframeworkdir , lab = ["DIGI" ,"SMALLDIGI" ], cpu = '1' )
262- t ['cmd' ] = 'o2-sim-digitizer-workflow ' + getDPL_global_options () + ' -n ' + str (args .ns ) + simsoption + ' --onlyDet ' + str (det ) + ' --interactionRate 50000 --incontext ' + str (CONTEXTFILE )
263- workflow ['stages' ].append (t )
264- return t
326+ def createRestDigiTask (name , det = 'ALLSMALLER' ):
327+ tneeds = needs = [ContextTask ['name' ]]
328+ if det == 'ALLSMALLER' :
329+ if usebkgcache :
330+ for d in smallsensorlist :
331+ tneeds += [ BKG_HITDOWNLOADER_TASKS [d ]['name' ] ]
332+ t = createTask (name = name , needs = tneeds ,
333+ tf = tf , cwd = timeframeworkdir , lab = ["DIGI" ,"SMALLDIGI" ], cpu = '8' )
334+ t ['cmd' ] = ('' ,'ln -nfs ../bkg_Hits*.root . ;' )[doembedding ]
335+ t ['cmd' ] += 'o2-sim-digitizer-workflow ' + getDPL_global_options (nosmallrate = True ) + ' -n ' + str (args .ns ) + simsoption + ' --skipDet TPC,TRD --interactionRate 50000 --incontext ' + str (CONTEXTFILE )
336+ workflow ['stages' ].append (t )
337+ return t
338+
339+ else :
340+ if usebkgcache :
341+ tneeds += [ BKG_HITDOWNLOADER_TASKS [det ]['name' ] ]
342+ t = createTask (name = name , needs = tneeds ,
343+ tf = tf , cwd = timeframeworkdir , lab = ["DIGI" ,"SMALLDIGI" ], cpu = '1' )
344+ t ['cmd' ] = ('' ,'ln -nfs ../bkg_Hits' + str (det ) + '.root . ;' )[doembedding ]
345+ t ['cmd' ] += 'o2-sim-digitizer-workflow ' + getDPL_global_options () + ' -n ' + str (args .ns ) + simsoption + ' --onlyDet ' + str (det ) + ' --interactionRate 50000 --incontext ' + str (CONTEXTFILE )
346+ workflow ['stages' ].append (t )
347+ return t
265348
266349 det_to_digitask = {}
267350
268- for det in sensorlist :
269- name = str (det ).lower () + "digi_" + str (tf )
270- t = createRestDigiTask (name )
351+ if args .combine_smaller_digi == True :
352+ det_to_digitask ['ALLSMALLER' ]= createRestDigiTask ("restdigi_" + str (tf ))
353+
354+ for det in smallsensorlist :
355+ name = str (det ).lower () + "digi_" + str (tf )
356+ t = det_to_digitask ['ALLSMALLER' ] if args .combine_smaller_digi == True else createRestDigiTask (name , det )
271357 det_to_digitask [det ]= t
272358
273359 # -----------
274360 # reco
275361 # -----------
276362
277- # TODO: check value for MaxTimeBin; A large value had to be set tmp in order to avoid crashes bases on "exceeding timeframe limit"
278- TPCRECOtask = createTask (name = 'tpcreco_' + str (tf ), needs = [TPCDigitask ['name' ]], tf = tf , cwd = timeframeworkdir , lab = ["RECO" ], cpu = '3' , mem = '16000' )
279- TPCRECOtask ['cmd' ] = 'o2-tpc-reco-workflow ' + getDPL_global_options (bigshm = True , nosmallrate = True ) + ' --tpc-digit-reader "--infile tpcdigits.root" --input-type digits --output-type clusters,tracks,send-clusters-per-sector --configKeyValues "GPU_global.continuousMaxTimeBin=100000;GPU_proc.ompThreads=' + str (NWORKERS )+ '"'
363+ # TODO: check value for MaxTimeBin; A large value had to be set tmp in order to avoid crashes based on "exceeding timeframe limit"
364+ TPCRECOtask1 = createTask (name = 'tpccluster_' + str (tf ), needs = [TPCDigitask ['name' ]], tf = tf , cwd = timeframeworkdir , lab = ["RECO" ], cpu = '3' , mem = '16000' )
365+ TPCRECOtask1 ['cmd' ] = 'o2-tpc-chunkeddigit-merger --rate 1 --tpc-lanes ' + str (NWORKERS ) + ' --session ' + str (taskcounter )
366+ TPCRECOtask1 ['cmd' ] += ' | o2-tpc-reco-workflow ' + getDPL_global_options (bigshm = True , nosmallrate = True ) + ' --input-type digitizer --output-type clusters,send-clusters-per-sector --configKeyValues "GPU_global.continuousMaxTimeBin=100000;GPU_proc.ompThreads=' + str (NWORKERS )+ '"'
367+ workflow ['stages' ].append (TPCRECOtask1 )
368+
369+ TPCRECOtask = createTask (name = 'tpcreco_' + str (tf ), needs = [TPCRECOtask1 ['name' ]], tf = tf , cwd = timeframeworkdir , lab = ["RECO" ], cpu = '3' , mem = '16000' )
370+ TPCRECOtask ['cmd' ] = 'o2-tpc-reco-workflow ' + getDPL_global_options (bigshm = True , nosmallrate = True ) + ' --input-type clusters --output-type tracks,send-clusters-per-sector --configKeyValues "GPU_global.continuousMaxTimeBin=100000;GPU_proc.ompThreads=' + str (NWORKERS )+ '"'
280371 workflow ['stages' ].append (TPCRECOtask )
281372
282373 ITSRECOtask = createTask (name = 'itsreco_' + str (tf ), needs = [det_to_digitask ["ITS" ]['name' ]], tf = tf , cwd = timeframeworkdir , lab = ["RECO" ], cpu = '1' , mem = '2000' )
@@ -315,12 +406,16 @@ def createRestDigiTask(name):
315406 # -----------
316407 # produce AOD
317408 # -----------
318-
319- AODtask = createTask (name = 'aod_' + str (tf ), needs = [PVFINDERtask ['name' ], TOFRECOtask ['name' ], TRDTRACKINGtask ['name' ]], tf = tf , cwd = timeframeworkdir , lab = ["AOD" ])
320- AODtask ['cmd' ] = 'o2-aod-producer-workflow --aod-writer-keep dangling --aod-writer-resfile \" AO2D\" --aod-writer-resmode UPDATE --aod-timeframe-id ' + str (tf ) + ' ' + getDPL_global_options (bigshm = True )
409+ aodneeds = [PVFINDERtask ['name' ], TOFRECOtask ['name' ], TRDTRACKINGtask ['name' ]]
410+ if usebkgcache :
411+ aodneeds += [ BKG_KINEDOWNLOADER_TASK ['name' ] ]
412+
413+ AODtask = createTask (name = 'aod_' + str (tf ), needs = aodneeds , tf = tf , cwd = timeframeworkdir , lab = ["AOD" ], mem = '16000' , cpu = '1' )
414+ AODtask ['cmd' ] = ('' ,'ln -nfs ../bkg_Kine.root . ;' )[doembedding ]
415+ AODtask ['cmd' ] += 'o2-aod-producer-workflow --aod-writer-keep dangling --aod-writer-resfile \" AO2D\" --aod-writer-resmode UPDATE --aod-timeframe-id ' + str (tf ) + ' ' + getDPL_global_options (bigshm = True )
416+ AODtask ['cmd' ] = 'echo \" hello\" ' #-> skipping for moment since not optimized
321417 workflow ['stages' ].append (AODtask )
322418
323-
324419def trimString (cmd ):
325420 return ' ' .join (cmd .split ())
326421
0 commit comments