1 #-------------------------------------------------------------------------------
3 # | ____| _| |_ / __ \ /\ | \/ |
4 # | |__ _ __ ___ ___ / \| | | | / \ | \ / |
5 # | __| '__/ _ \/ _ ( (| |) ) | | |/ /\ \ | |\/| |
6 # | | | | | __/ __/\_ _/| |__| / ____ \| | | |
7 # |_| |_| \___|\___| |_| \____/_/ \_\_| |_|
9 # FreeFOAM: The Cross-Platform CFD Toolkit
11 # Copyright (C) 2008-2011 Michael Wild <themiwi@users.sf.net>
12 # Gerber van der Graaf <gerber_graaf@users.sf.net>
13 #-------------------------------------------------------------------------------
15 # This file is part of FreeFOAM.
17 # FreeFOAM is free software; you can redistribute it and/or modify it
18 # under the terms of the GNU General Public License as published by the
19 # Free Software Foundation; either version 2 of the License, or (at your
20 # option) any later version.
22 # FreeFOAM is distributed in the hope that it will be useful, but WITHOUT
23 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
24 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
27 # You should have received a copy of the GNU General Public License
28 # along with FreeFOAM; if not, write to the Free Software Foundation,
29 # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
32 # Useful Python functionality to run FreeFOAM tutorial cases
34 #-------------------------------------------------------------------------------
36 """Classes and functions useful for running FreeFOAM tutorial applications."""
38 # want to be future proof
39 from FreeFOAM
.compat
import *
44 import FreeFOAM
.run
as _fr
45 import FreeFOAM
.util
as _fu
47 compile_config
= _f
.BUILD_TYPE
49 class ConfiguringFailedError(Exception):
50 """Thrown if configuring a tutorial application failed."""
51 def __init__(self
, bindir
, logfile
):
52 """Initialize the exception object.
56 bindir : The binary directory.
57 logfile : Name of the logfile which marks the case as run.
60 Exception.__init
__(self
, bindir
, logfile
)
63 """Return string representation of the error."""
64 return ('*** ERROR *** Configuring application %s failed.\n' +
65 'Refer to the logfile %s for more information.')%self
.args
67 class CompilingFailedError(Exception):
68 """Thrown if the compilation of a tutorial application failed."""
69 def __init__(self
, bindir
, logfile
):
70 """Initialize the exception object.
74 bindir : The binary directory where the compilation should have run.
75 logfile : Name of the logfile which marks the case as run.
78 Exception.__init
__(self
, bindir
, logfile
)
81 """Return string representation of the error."""
82 return ('*** ERROR *** Cleaning application %s failed.\n' +
83 'Refer to the logfile %s for more information.')%self
.args
85 class CleaningFailedError(Exception):
86 """Thrown if the cleaning of a tutorial application failed."""
87 def __init__(self
, bindir
):
88 """Initialize the exception object.
92 app : The application that was already run.
93 logfile : Name of the logfile which marks the case as run.
96 Exception.__init
__(self
, bindir
)
99 """Return string representation of the error."""
100 return ('*** ERROR *** Cleaning application %s failed.'%self
.args
)
102 class CaseRunner(object):
107 name : Name of the case.
108 case_dir : Directory containing the case.
109 test_dir : Directory to use in test runs.
110 default_schemes : Use the default finite volume schemes in test runs.
114 def __init__(self
, name
, case_dir
='.', test_dir
=None, skip_test
=False):
115 """Initializes a new CaseRunner instance.
119 name : Name of the case.
120 case_dir : Directory containing the case. Defaults to
122 test_dir : Directory to create when cloning the case for the test
123 run. Defaults to `case_dir-test`.
124 skip_test : Default value to use when adding a new step.
128 self
.case_dir
= _op
.abspath(case_dir
)
130 test_dir
= self
.case_dir
+'-test'
131 self
.test_dir
= _op
.abspath(test_dir
)
132 self
.skip_test
= skip_test
133 self
.default_schemes
= False
138 """Returns the name of this tutorial."""
142 """Retrieves the name of all registered steps."""
143 return self
._stepnames
145 def add_step(self
, stepname
, cmd
, stamp_file
=None, skip_test
=None):
146 """Registers a callable object to be run as a tutorial step.
150 stepname : Name of the step.
151 cmd : Callable (function or object with __call__ function).
152 stamp_file : File to use as a stamp. If absolute, relative path to
153 `self.case_dir` will be used, and then appended to either
154 `self.case_dir` or `self.test_dir`. Defaults to
156 skip_test : Skip this step in test mode. Defaults to `self.skip_test`.
160 `cmd` must have the following signature:
161 def command(case_dir, stamp_file, test_mode):
165 where `case_dir` is the the absolute path to where the case is contained
166 in. `stamp_file` is a `file` object the command should write its log to.
167 The last line must either read `REPORT: SUCCESS` or `REPORT: FAILURE` (as
168 e.g. created by `RunApp.__call__()`). Upon success, the function returns
169 True, False otherwise.
172 assert stepname
not in self
._stepnames
, (
173 "Step "+stepname
+" already exists")
174 if stamp_file
== None:
175 stamp_file
= 'log.'+stepname
176 if _op
.isabs(stamp_file
):
177 stamp_file
= _op
.relpath(stamp_file
, self
.case_dir
)
178 if skip_test
== None:
179 skip_test
= self
.skip_test
180 self
._stepnames
.append(stepname
)
181 self
._steps
[stepname
] = {
183 'stamp' : stamp_file
,
184 'skip_test' : skip_test
,
187 def add_app_step(self
, stepname
, app
=None, parallel
=False, args
=[],
188 stamp_file
=None, skip_test
=None):
189 """Convenience function to add a step running a FreeFOAM application.
193 stepname : Name of the step.
194 app : Name of the application to run. Defaults to `stepname`.
195 parallel : If True, run the application in parallel.
196 args : Additional arguments to be passed to the application.
197 stamp_file : File to use as a stamp. Relative to `self.case_dir`.
198 Defaults to `'log.'+app`.
199 skip_test : Skip this step in test mode.
204 if stamp_file
== None:
205 stamp_file
= 'log.'+_op
.basename(stepname
)
206 self
.add_step(stepname
, RunApp(app
, parallel
, args
),
207 stamp_file
=stamp_file
, skip_test
=skip_test
)
209 def run(self
, step
=None, test_mode
=False, verbose
=False):
210 """Run the case as a whole or a specific step.
212 A step is only run if at least one of the following applies:
213 * `test_mode` is True.
214 * The stamp file of the test (usually the log file) does not exist.
215 * The stamp file of the preceeding step is newer than the one for this
218 Every step is assumed to depend on the preceeding step, and above
219 criteria propagate up to the first step if required (except for
220 `test_mode` which only applies to the specified step, or to all steps if
221 `step` is None). This means that if you specify a step to be run and any
222 of the preceeding steps has not been run or is out of date, that step and
223 all the ones in between will also be run.
227 step : Name of the step to be run. If `None`, try to run all steps.
228 test_mode : Run in testing mode.
229 verbose : Echo the output to standard output.
233 True if the steps all ran successfully, False otherwise.
237 assert len(self
._stepnames
) > 0, "No steps registered"
240 step
= self
._stepnames
[-1]
241 assert step
in self
._stepnames
, "Step "+step
+" not registered"
245 case_dir
= self
.test_dir
247 case_dir
= self
.case_dir
248 case_dir
= _op
.abspath(case_dir
)
250 echo('Running case '+case_dir
)
253 fmt
= ' %%-%ds ${HIDE_CURSOR}'%max(
254 30, max(map(len, self
._stepnames
)))
255 last
= self
._stepnames
.index(step
)
257 for i
, s
in enumerate(self
._stepnames
):
259 _fu
.cecho(fmt
%s, end
='')
261 stamp
= _op
.join(case_dir
, self
._steps
[s
]['stamp'])
263 if test_mode
and self
._steps
[s
]['skip_test']:
268 elif _op
.isfile(stamp
):
271 old_s
= self
._stepnames
[i
-1]
272 old_stamp
= _op
.join(
273 case_dir
, self
._steps
[old_s
]['stamp'])
274 if _op
.getmtime(old_stamp
) > _op
.getmtime(stamp
):
278 _fu
.cecho('${YELLOW}[ SKIP ]${NORMAL}')
283 stamp_file
= _fu
.Tee(stamp
, 'wt')
285 stamp_file
= open(stamp
, 'wt')
286 stat
= self
._steps
[s
]['cmd'](case_dir
, stamp_file
, test_mode
)
289 _fu
.cecho('${GREEN}[ OK ]${NORMAL}')
291 _fu
.cecho('${RED}${BOLD}[FAILED]${NORMAL}')
292 except BaseException
, e
:
294 _fu
.cecho('${RED}${BOLD}[FAILED]${NORMAL}')
295 _fu
.cerror("Step %s in case %s raised an exception:\n%s"%
296 (s
, case_dir
, str(e
)), file=sys
.stderr
)
302 _fu
.cecho('${SHOW_CURSOR}', end
='')
308 This function is supposed to be overridden by tutorials requiring more
309 cleaning up than `FreeFOAM.util.clean_case` provides.
311 Note that this implementation also removes `test_dir` if it exist
312 and is not equal to the `case_dir`.
315 _fu
.clean_case(self
.case_dir
)
316 if self
.test_dir
!= self
.case_dir
and _op
.isdir(self
.test_dir
):
317 _fu
.remove_case(self
.test_dir
)
319 def clone_test(self
):
320 """Creates a testing clone.
322 This function is supposed to be overridden by tutorials requiring more
323 cloning work than `FreeFOAM.util.clone()` and `modify_for_test()`
327 if _op
.isdir(self
.test_dir
):
328 _fu
.remove_case(self
.test_dir
)
329 _fu
.clone_case(self
.case_dir
, self
.test_dir
)
330 self
.modify_for_test()
332 def modify_for_test(self
):
333 """Modifies the case `test_dir` for use as a test case."""
334 self
.modify_global_controlDict_for_test(self
.test_dir
)
335 self
.modify_case_for_test(self
.test_dir
)
337 def modify_global_controlDict_for_test(self
, test_dir
):
338 """Copies the global etc directory and modifies if for testing."""
339 ctrld
= _op
.join(test_dir
, 'etc', 'controlDict')
340 if not _op
.isdir(_op
.dirname(ctrld
)):
341 _os
.makedirs(_op
.dirname(ctrld
))
342 _fu
.copytree(_op
.join(_f
.CONFIG_DIR
, 'controlDict'), ctrld
)
344 _op
.join(_f
.CONFIG_DIR
, 'cellModels'),
345 _op
.join(test_dir
, 'etc', 'cellModels'))
347 _op
.join(_f
.CONFIG_DIR
, 'thermoData'),
348 _op
.join(test_dir
, 'etc', 'thermoData'))
350 (r
'(fvSchemes\s*)([0-9]);', r
'\g<1>1;'),
351 (r
'(solution\s*)([0-9]);', r
'\g<1>1;') ])
353 def modify_case_for_test(self
, test_dir
):
354 "Modifies system/{controlDict,fvSchemes,snappyHexMeshDict} for testing."
356 # modify system/controlDict
357 for e
in ['', '.1st']:
358 ctrld
= _op
.join(test_dir
, 'system', 'controlDict'+e
)
359 if _op
.isfile(ctrld
):
361 (r
'(startFrom\s+)([a-zA-Z]+);', r
'\g<1>latestTime;'),
362 (r
'(stopAt\s+)([a-zA-Z]*);', r
'\g<1>nextWrite;'),
363 (r
'(writeControl\s+)([a-zA-Z]+);', r
'\g<1>timeStep;'),
364 (r
'(writeInterval\s+)([0-9a-zA-Z.-]+);', r
'\g<1>1;'),
366 # modify system/fvSchemes
367 if self
.default_schemes
:
372 'interpolationScheme',
376 for parent
, dirs
, files
in _os
.walk(_op
.join(test_dir
,'system')):
377 if 'fvSchemes' in files
:
378 fvs
= _op
.join(parent
, 'fvSchemes')
379 lines
= open(fvs
, 'rt').readlines()
384 for i
, l
in enumerate(lines
):
385 m
= re
.search('|'.join(fv_schemes
), l
)
386 if n_brace
> 0 or search_start
or m
:
392 if search_start
or search_end
:
393 n_brace
+= l
.count('{')
397 n_brace
-= l
.count('}')
398 if search_end
and n_brace
< 1:
400 for i
in reversed(kill_lines
):
405 gradSchemes { default Gauss linear; }
408 default Gauss linear;
409 div(phi,fu_ft_h) Gauss multivariateSelection
415 div(phi,ft_b_h_hu) Gauss multivariateSelection
424 laplacianSchemes { default Gauss linear corrected; }
425 interpolationSchemes { default linear; }
426 snGradSchemes { default corrected; }
427 fluxRequired { default yes; }
431 class ClonedCaseRunner(CaseRunner
):
432 """A runner for cases that need to be cloned from another case."""
433 def __init__(self
, name
, parent_runner
, case_dir
='.',
434 test_dir
=None, skip_test
=False):
435 CaseRunner
.__init
__(self
, name
, case_dir
=case_dir
,
436 test_dir
=test_dir
, skip_test
=skip_test
)
437 self
._parent
_runner
= parent_runner
439 def clone_from_parent(self
, case_dir
):
440 """Clones the case from its parent case."""
442 parent_dir
= self
._parent
_runner
.case_dir
443 if not _op
.isdir(_op
.join(case_dir
, 'system')):
444 _fu
.clone_case(parent_dir
, case_dir
, verbose
=False)
448 _fu
.cerror("Failed to clone", parent_dir
, ":", str(e
),
452 def run(self
, step
=None, test_mode
=False, verbose
=False):
453 """Refer to `CaseRunner.run`."""
455 self
.clone_from_parent(self
.case_dir
)
456 return CaseRunner
.run(self
, step
=step
, test_mode
=test_mode
,
460 """Removes the cloned case and, if it exists, the test-case."""
461 _fu
.remove_case(self
.case_dir
)
462 if _op
.isdir(self
.test_dir
):
463 _fu
.remove_case(self
.test_dir
)
465 def clone_test(self
):
466 """Refer to `CaseRunner.clean`."""
467 if _op
.isdir(self
.test_dir
):
468 _fu
.remove_case(self
.test_dir
)
469 self
.clone_from_parent(self
.test_dir
)
470 self
.modify_for_test()
472 class TutorialRunner(object):
473 """A tutorial case runner.
477 default_schemes : Use the default finite volume schemes in test runs.
479 keep_going : Do not abort after a failed case. Defaults to False.
480 test_mode : Run in testing mode. Defaults to False.
481 verbose : Echo the output to standard output. Defaults to False.
484 def __init__(self
, default_schemes
=False, keep_going
=False, test_mode
=False,
486 """Initializes a new TutorialRunner instance."""
487 self
.default_schemes
= default_schemes
488 self
.keep_going
= keep_going
489 self
.test_mode
= test_mode
490 self
.verbose
= verbose
495 """Returns a tuple of all registered case names."""
496 return tuple([c
.name
for c
in self
._cases
])
499 """Returns a tuple of all registered step names."""
501 for c
in self
._cases
:
503 steps
.append('.'.join((c
.name
,s
)))
506 def add_case(self
, case
):
508 assert case
.name
not in self
.cases(), (
509 "Trying to re-register case "+case
.name
)
510 self
._cases
.append(case
)
512 def run(self
, step
=None):
513 """Run the case as a whole or a specific step.
515 A step is only run if at least one of the following applies:
516 * `self.test_mode` is True.
517 * The stamp file of the test (usually the log file) does not exist.
518 * The stamp file of the preceeding step of the same case is newer than
519 the one for this step.
521 Every step is assumed to depend on the preceeding step of the same case,
522 and above criteria propagate up to the first step if required (except for
523 `self.test_mode` which only applies to the specified step, or to all
524 steps if `step` is None). This means that if you specify a step to be run
525 and any of the preceeding steps of the same case has not been run or is
526 out of date, that step and all the ones in between will also be run.
528 In test mode all steps belonging to the same case are run (up to the
529 specified one), except in the default case, where all steps in all cases
532 The names of the steps are composed of the case name and the step name,
533 separated by a dot. Only specifying the case name is equivalent to
534 running all the steps belonging to that case.
538 step : Name of the step to be run. If `None`, try to run all steps.
542 True if the steps all ran successfully, False otherwise.
548 case_and_step
= step
.split('.', 1)
549 case
= case_and_step
[0]
550 if len(case_and_step
) > 1:
551 substep
= case_and_step
[1]
556 for c
in filter(lambda c
: c
.name
in cases
, self
._cases
):
557 c
.default_schemes
= self
.default_schemes
560 stat
= c
.run(substep
, self
.test_mode
, self
.verbose
)
562 if not self
.keep_going
:
564 if not stat
and not self
.keep_going
:
566 return stat
or self
.keep_going
569 """Clean all cases."""
570 for c
in self
._cases
:
574 """Run as the main function in a script.
575 <step> Optionally, a step can be specifiedo which should be
576 run. By default all steps are run. Refer to run() for
582 parser
= optparse
.OptionParser(usage
='Usage: %prog [options] [step]')
583 parser
.add_option('-t', '--test', dest
='testmode', action
='store_true',
584 default
=False, help=('Run in testing mode. This causes the case ' +
585 'to be cloned to a test directory.'))
586 parser
.add_option('-k', '--keep-going', dest
='keep_going',
587 action
='store_true', default
=False,
588 help='Don\'t abort after a failed case.')
589 parser
.add_option('-d', '--default-schemes', dest
='default_schemes',
590 action
='store_true', default
=False,
591 help='Run using a set of default fvSchemes.')
592 parser
.add_option('-l', '--list', dest
='list', action
='store_true',
593 default
=False, help='List the individual steps in this tutorial.')
594 parser
.add_option('-c', '--cases', dest
='cases', action
='store_true',
595 default
=False, help='Print the cases in this tutorial.')
596 parser
.add_option('--clean', dest
='clean', action
='store_true',
597 default
=False, help='Clean this tutorial case.')
598 parser
.add_option('-v', '--verbose', dest
='verbose', action
='store_true',
599 default
=False, help=('Echo the output to standard output'))
601 (opts
, args
) = parser
.parse_args()
602 if opts
.list and (opts
.testmode
or opts
.default_schemes
or
603 opts
.keep_going
or opts
.cases
or opts
.clean
or opts
.verbose
or
606 'Option --list may not be used with arguments and other options')
607 if opts
.cases
and (opts
.testmode
or opts
.default_schemes
or
608 opts
.keep_going
or opts
.list or opts
.clean
or opts
.verbose
or
611 'Option --cases may not be used with arguments and other options')
612 if opts
.clean
and (opts
.testmode
or opts
.default_schemes
or
613 opts
.keep_going
or opts
.list or opts
.cases
or opts
.verbose
or
616 'Option --clean may not be used with arguments and other options')
617 if opts
.default_schemes
and not opts
.testmode
:
619 'Option --default-schemes can only be used with --test')
622 'May not be called with more than one argument')
625 echo('\n'.join(self
.steps()))
628 echo('\n'.join(self
.cases()))
634 self
.default_schemes
= opts
.default_schemes
635 self
.keep_going
= opts
.keep_going
636 self
.test_mode
= opts
.testmode
637 self
.verbose
= opts
.verbose
642 return int(not self
.run(step
))
644 class RunApp(object):
645 """Functor to run a FreeFOAM application."""
646 def __init__(self
, app
, parallel
=False, args
=[]):
647 """Creates a new application runner.
651 app : Name or full/relative path to the application to run.
652 parallel : If True, the '-parallel' flag will be passed to the
654 args : Additional arguments and options to be passed to the
659 self
._parallel
= parallel
661 self
._runner
= _fr
.ParallelRunner()
663 def __call__(self
, case_dir
, stamp_file
, testmode
):
664 """Runs the FreeFOAM application.
666 The function redirects the output to `self.logfile`. Before running
667 the application, it writes a short header to the log, listing the
668 application name, the case directory and the command line used to
669 invoke the application. At the end of the run it writes a `REPORT:
670 SUCCESS` if the application returned 0, or `REPORT: FAILURE` for all
671 other return values or if an exception was caught.
675 True on success, False otherwise.
681 stamp_file
.write(''.join([80*'='+'\nAPPLICATION: ', self
._app
, '\n']))
682 stamp_file
.write(''.join(['CASE: ', case_dir
, '\n']))
683 stamp_file
.write(''.join(['COMMAND LINE: ', self
._runner
.command_str(
684 self
._app
, case
=case_dir
, parallel
=self
._parallel
,
685 args
=self
._args
), '\n'+80*'='+'\n\n']))
688 # this is ridiculous, but python 2.4 doesn't support try-except-finally
691 _os
.environ
['FREEFOAM_CONFIG_DIR'] = _op
.join(case_dir
, 'etc')
692 self
._runner
.run(self
._app
, case
=case_dir
,
693 parallel
=self
._parallel
, stdout
=subprocess
.PIPE
,
694 stderr
=subprocess
.STDOUT
, args
=self
._args
, background
=True) == 0
695 # retrieve the output and write it to the log file as long as the thing runs
697 lines
= self
._runner
.getProcess().stdout
.readlines()
698 if len(lines
) and not isinstance(lines
[0], str):
699 lines
= list(map(lambda l
: l
.decode(), lines
))
700 stamp_file
.writelines(lines
)
701 stat
= self
._runner
.poll()
705 except KeyboardInterrupt:
706 stamp_file
.write('\n*** Error *** KeyboardInterrupt\n')
710 stamp_file
.write('\n*** Error *** '+str(e
)+'\n')
714 # write final verdict to the log and clean up
715 stamp_file
.write('\n'+80*'='+'\nREPORT: ')
717 stamp_file
.write('SUCCESS\n')
719 stamp_file
.write('FAILURE\n')
722 class CompileApp(object):
723 """Functor to compile a FreeFOAM tutorial application.
727 srcdir : The top-level source directory.
728 bindir : The top-level binary directory.
729 app : Absolute path to the binary application.
732 def __init__(self
, srcdir
, bindir
=None, name
=None):
733 """Initializes a new CompileApp instance.
737 srcdir : The top-level source directory.
738 bindir : The top-level binary directory (i.e. where to build the
739 application). If set to None, defaults to 'srcdir+"-build"'.
740 name : Name of the application. Defaults to `os.path.basename(srcdir)`.
743 self
.srcdir
= _op
.abspath(srcdir
)
745 bindir
= self
.srcdir
+'-build'
747 name
= _op
.basename(self
.srcdir
)
749 self
.app
= _op
.join(self
.bindir
, _f
.EXE_PREFIX
+name
+_f
.EXE_SUFFIX
)
751 def __call__(self
, case_dir
, stamp_file
, testmode
):
752 """Compile a FreeFOAM application.
754 Configures and compiles a FreeFOAM tutorial application located in the
755 source directory `self.srcdir`.
759 # get the current working directory
761 # create the binary directory and cd to it
762 if not _op
.exists(self
.bindir
):
763 _os
.makedirs(self
.bindir
)
764 elif not _op
.isdir(self
.bindir
):
765 raise BinDirCreationError(self
.bindir
,
766 'The path exists, but is not a directory')
769 _os
.chdir(self
.bindir
)
770 # configure the project
773 '-DCMAKE_C_COMPILER='+_f
.CMAKE_C_COMPILER
,
774 '-DCMAKE_CXX_COMPILER='+_f
.CMAKE_CXX_COMPILER
,
775 '-DCMAKE_BUILD_TYPE='+compile_config
,
776 '-G', _f
.CMAKE_GENERATOR
,
779 stamp_file
.write(''.join(['CONFIGURING: ', ' '.join(cmd
),
780 '\n'+80*'='+'\n\n']))
783 process
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
,
784 stderr
=subprocess
.STDOUT
)
786 lines
= process
.stdout
.readlines()
787 if len(lines
) and not isinstance(lines
[0], str):
788 lines
= list(map(lambda l
: l
.decode(), lines
))
789 stamp_file
.writelines(lines
)
790 returncode
= process
.poll()
791 if returncode
!= None:
793 stamp_file
.write('\n'+80*'='+'\nREPORT: ')
795 msg
= 'Failed to configure the project "%s" in "%s".'%(
796 self
.srcdir
, self
.bindir
)
797 stamp_file
.write('FAILURE\n')
800 stamp_file
.write('SUCCESS\n')
802 cmd
= [_f
.CMAKE_COMMAND
, '--build', '.',
803 '--config', compile_config
]
804 stamp_file
.write(''.join(['COMPILING: ', ' '.join(cmd
),
805 '\n'+80*'='+'\n\n']))
807 returncode
= subprocess
.call(
808 cmd
, stdout
=stamp_file
, stderr
=stamp_file
)
809 stamp_file
.write('\n'+80*'='+'\nREPORT: ')
811 msg
= 'Failed to build the application "%s" in "%s".'%(
812 self
.srcdir
, self
.bindir
)
813 stamp_file
.write('FAILURE\n')
816 stamp_file
.write('SUCCESS\n')
822 def clean_application(bindir
):
823 """Cleans a FreeFOAM application.
825 Cleans a FreeFOAM tutorial application located in the binary directory
830 bindir : The top-level binary directory (i.e. where the build directory is.
834 bindir
= _op
.normpath(bindir
)
836 if _op
.isdir(bindir
):
837 cmd
= [_f
.CMAKE_COMMAND
, '--build', bindir
, '--target', 'clean']
838 returncode
= subprocess
.call(cmd
)
840 raise CleaningFailedError(bindir
)
843 def modify_file(fname
, regSub
, backup
=None):
844 """Modifies file `fname` by applying the regular expression/substitution
845 pairs in the iterable `regSub`. If `backup` is a string and not None, a
846 backup file with the given suffix will be created before the modification
849 fname
= _op
.abspath(fname
)
850 if backup
and type(backup
) == str:
852 shutil
.copy2(fname
, fname
+backup
)
853 lines
=open(fname
, 'rt').readlines()
854 for i
, l
in enumerate(lines
):
857 lines
[i
] = re
.sub(p
, s
, l
)
858 open(fname
, 'wt').writelines(lines
)
860 # ------------------------- vim: set sw=3 sts=3 et: --------------- end-of-file