1 #-------------------------------------------------------------------------------
3 # | ____| _| |_ / __ \ /\ | \/ |
4 # | |__ _ __ ___ ___ / \| | | | / \ | \ / |
5 # | __| '__/ _ \/ _ ( (| |) ) | | |/ /\ \ | |\/| |
6 # | | | | | __/ __/\_ _/| |__| / ____ \| | | |
7 # |_| |_| \___|\___| |_| \____/_/ \_\_| |_|
9 # FreeFOAM: The Cross-Platform CFD Toolkit
11 # Copyright (C) 2008-2012 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 3 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, see <http://www.gnu.org/licenses/>.
31 # Useful Python functionality to run FreeFOAM tutorial cases
33 #-------------------------------------------------------------------------------
35 """Classes and functions useful for running FreeFOAM tutorial applications."""
37 # want to be future proof
38 from FreeFOAM
.compat
import *
43 import FreeFOAM
.run
as _fr
44 import FreeFOAM
.util
as _fu
46 compile_config
= _f
.BUILD_TYPE
48 class ConfiguringFailedError(Exception):
49 """Thrown if configuring a tutorial application failed."""
50 def __init__(self
, bindir
, logfile
):
51 """Initialize the exception object.
55 bindir : The binary directory.
56 logfile : Name of the logfile which marks the case as run.
59 Exception.__init
__(self
, bindir
, logfile
)
62 """Return string representation of the error."""
63 return ('*** ERROR *** Configuring application %s failed.\n' +
64 'Refer to the logfile %s for more information.')%self
.args
66 class CompilingFailedError(Exception):
67 """Thrown if the compilation of a tutorial application failed."""
68 def __init__(self
, bindir
, logfile
):
69 """Initialize the exception object.
73 bindir : The binary directory where the compilation should have run.
74 logfile : Name of the logfile which marks the case as run.
77 Exception.__init
__(self
, bindir
, logfile
)
80 """Return string representation of the error."""
81 return ('*** ERROR *** Cleaning application %s failed.\n' +
82 'Refer to the logfile %s for more information.')%self
.args
84 class CleaningFailedError(Exception):
85 """Thrown if the cleaning of a tutorial application failed."""
86 def __init__(self
, bindir
):
87 """Initialize the exception object.
91 app : The application that was already run.
92 logfile : Name of the logfile which marks the case as run.
95 Exception.__init
__(self
, bindir
)
98 """Return string representation of the error."""
99 return ('*** ERROR *** Cleaning application %s failed.'%self
.args
)
101 class CaseRunner(object):
106 name : Name of the case.
107 case_dir : Directory containing the case.
108 test_dir : Directory to use in test runs.
109 default_schemes : Use the default finite volume schemes in test runs.
113 def __init__(self
, name
, case_dir
='.', test_dir
=None, skip_test
=False):
114 """Initializes a new CaseRunner instance.
118 name : Name of the case.
119 case_dir : Directory containing the case. Defaults to
121 test_dir : Directory to create when cloning the case for the test
122 run. Defaults to `case_dir-test`.
123 skip_test : Default value to use when adding a new step.
127 self
.case_dir
= _op
.abspath(case_dir
)
129 test_dir
= self
.case_dir
+'-test'
130 self
.test_dir
= _op
.abspath(test_dir
)
131 self
.skip_test
= skip_test
132 self
.default_schemes
= False
137 """Returns the name of this tutorial."""
141 """Retrieves the name of all registered steps."""
142 return self
._stepnames
144 def add_step(self
, stepname
, cmd
, stamp_file
=None, skip_test
=None):
145 """Registers a callable object to be run as a tutorial step.
149 stepname : Name of the step.
150 cmd : Callable (function or object with __call__ function).
151 stamp_file : File to use as a stamp. If absolute, relative path to
152 `self.case_dir` will be used, and then appended to either
153 `self.case_dir` or `self.test_dir`. Defaults to
155 skip_test : Skip this step in test mode. Defaults to `self.skip_test`.
159 `cmd` must have the following signature:
160 def command(case_dir, stamp_file, test_mode):
164 where `case_dir` is the the absolute path to where the case is contained
165 in. `stamp_file` is a `file` object the command should write its log to.
166 The last line must either read `REPORT: SUCCESS` or `REPORT: FAILURE` (as
167 e.g. created by `RunApp.__call__()`). Upon success, the function returns
168 True, False otherwise.
171 assert stepname
not in self
._stepnames
, (
172 "Step "+stepname
+" already exists")
173 if stamp_file
== None:
174 stamp_file
= 'log.'+stepname
175 if _op
.isabs(stamp_file
):
176 stamp_file
= _op
.relpath(stamp_file
, self
.case_dir
)
177 if skip_test
== None:
178 skip_test
= self
.skip_test
179 self
._stepnames
.append(stepname
)
180 self
._steps
[stepname
] = {
182 'stamp' : stamp_file
,
183 'skip_test' : skip_test
,
186 def add_app_step(self
, stepname
, app
=None, parallel
=False, args
=[],
187 stamp_file
=None, skip_test
=None):
188 """Convenience function to add a step running a FreeFOAM application.
192 stepname : Name of the step.
193 app : Name of the application to run. Defaults to `stepname`.
194 parallel : If True, run the application in parallel.
195 args : Additional arguments to be passed to the application.
196 stamp_file : File to use as a stamp. Relative to `self.case_dir`.
197 Defaults to `'log.'+app`.
198 skip_test : Skip this step in test mode.
203 if stamp_file
== None:
204 stamp_file
= 'log.'+_op
.basename(stepname
)
205 self
.add_step(stepname
, RunApp(app
, parallel
, args
),
206 stamp_file
=stamp_file
, skip_test
=skip_test
)
208 def run(self
, step
=None, test_mode
=False, verbose
=False):
209 """Run the case as a whole or a specific step.
211 A step is only run if at least one of the following applies:
212 * `test_mode` is True.
213 * The stamp file of the test (usually the log file) does not exist.
214 * The stamp file of the preceeding step is newer than the one for this
217 Every step is assumed to depend on the preceeding step, and above
218 criteria propagate up to the first step if required (except for
219 `test_mode` which only applies to the specified step, or to all steps if
220 `step` is None). This means that if you specify a step to be run and any
221 of the preceeding steps has not been run or is out of date, that step and
222 all the ones in between will also be run.
226 step : Name of the step to be run. If `None`, try to run all steps.
227 test_mode : Run in testing mode.
228 verbose : Echo the output to standard output.
232 True if the steps all ran successfully, False otherwise.
236 assert len(self
._stepnames
) > 0, "No steps registered"
239 step
= self
._stepnames
[-1]
240 assert step
in self
._stepnames
, "Step "+step
+" not registered"
244 case_dir
= self
.test_dir
246 case_dir
= self
.case_dir
247 case_dir
= _op
.abspath(case_dir
)
249 echo('Running case '+case_dir
)
252 fmt
= ' %%-%ds ${HIDE_CURSOR}'%max(
253 30, max(map(len, self
._stepnames
)))
254 last
= self
._stepnames
.index(step
)
256 for i
, s
in enumerate(self
._stepnames
):
258 _fu
.cecho(fmt
%s, end
='')
260 stamp
= _op
.join(case_dir
, self
._steps
[s
]['stamp'])
262 if test_mode
and self
._steps
[s
]['skip_test']:
267 elif _op
.isfile(stamp
):
270 old_s
= self
._stepnames
[i
-1]
271 old_stamp
= _op
.join(
272 case_dir
, self
._steps
[old_s
]['stamp'])
273 if _op
.getmtime(old_stamp
) > _op
.getmtime(stamp
):
277 _fu
.cecho('${YELLOW}[ SKIP ]${NORMAL}')
282 stamp_file
= _fu
.Tee(stamp
, 'wt')
284 stamp_file
= open(stamp
, 'wt')
285 stat
= self
._steps
[s
]['cmd'](case_dir
, stamp_file
, test_mode
)
288 _fu
.cecho('${GREEN}[ OK ]${NORMAL}')
290 _fu
.cecho('${RED}${BOLD}[FAILED]${NORMAL}')
291 except BaseException
, e
:
293 _fu
.cecho('${RED}${BOLD}[FAILED]${NORMAL}')
294 _fu
.cerror("Step %s in case %s raised an exception:\n%s"%
295 (s
, case_dir
, str(e
)), file=sys
.stderr
)
301 _fu
.cecho('${SHOW_CURSOR}', end
='')
307 This function is supposed to be overridden by tutorials requiring more
308 cleaning up than `FreeFOAM.util.clean_case` provides.
310 Note that this implementation also removes `test_dir` if it exist
311 and is not equal to the `case_dir`.
314 _fu
.clean_case(self
.case_dir
)
315 if self
.test_dir
!= self
.case_dir
and _op
.isdir(self
.test_dir
):
316 _fu
.remove_case(self
.test_dir
)
318 def clone_test(self
):
319 """Creates a testing clone.
321 This function is supposed to be overridden by tutorials requiring more
322 cloning work than `FreeFOAM.util.clone()` and `modify_for_test()`
326 if _op
.isdir(self
.test_dir
):
327 _fu
.remove_case(self
.test_dir
)
328 _fu
.clone_case(self
.case_dir
, self
.test_dir
)
329 self
.modify_for_test()
331 def modify_for_test(self
):
332 """Modifies the case `test_dir` for use as a test case."""
333 self
.modify_global_controlDict_for_test(self
.test_dir
)
334 self
.modify_case_for_test(self
.test_dir
)
336 def modify_global_controlDict_for_test(self
, test_dir
):
337 """Copies the global etc directory and modifies if for testing."""
338 ctrld
= _op
.join(test_dir
, 'etc', 'controlDict')
339 if not _op
.isdir(_op
.dirname(ctrld
)):
340 _os
.makedirs(_op
.dirname(ctrld
))
341 _fu
.copytree(_op
.join(_f
.CONFIG_DIR
, 'controlDict'), ctrld
)
343 _op
.join(_f
.CONFIG_DIR
, 'cellModels'),
344 _op
.join(test_dir
, 'etc', 'cellModels'))
346 _op
.join(_f
.CONFIG_DIR
, 'thermoData'),
347 _op
.join(test_dir
, 'etc', 'thermoData'))
349 (r
'(fvSchemes\s*)([0-9]);', r
'\g<1>1;'),
350 (r
'(solution\s*)([0-9]);', r
'\g<1>1;') ])
352 def modify_case_for_test(self
, test_dir
):
353 "Modifies system/{controlDict,fvSchemes,snappyHexMeshDict} for testing."
355 # modify system/controlDict
356 for e
in ['', '.1st']:
357 ctrld
= _op
.join(test_dir
, 'system', 'controlDict'+e
)
358 if _op
.isfile(ctrld
):
360 (r
'(startFrom\s+)([a-zA-Z]+);', r
'\g<1>latestTime;'),
361 (r
'(stopAt\s+)([a-zA-Z]*);', r
'\g<1>nextWrite;'),
362 (r
'(writeControl\s+)([a-zA-Z]+);', r
'\g<1>timeStep;'),
363 (r
'(writeInterval\s+)([0-9a-zA-Z.-]+);', r
'\g<1>1;'),
365 # modify system/fvSchemes
366 if self
.default_schemes
:
371 'interpolationScheme',
375 for parent
, dirs
, files
in _os
.walk(_op
.join(test_dir
,'system')):
376 if 'fvSchemes' in files
:
377 fvs
= _op
.join(parent
, 'fvSchemes')
378 lines
= open(fvs
, 'rt').readlines()
383 for i
, l
in enumerate(lines
):
384 m
= re
.search('|'.join(fv_schemes
), l
)
385 if n_brace
> 0 or search_start
or m
:
391 if search_start
or search_end
:
392 n_brace
+= l
.count('{')
396 n_brace
-= l
.count('}')
397 if search_end
and n_brace
< 1:
399 for i
in reversed(kill_lines
):
404 gradSchemes { default Gauss linear; }
407 default Gauss linear;
408 div(phi,fu_ft_h) Gauss multivariateSelection
414 div(phi,ft_b_h_hu) Gauss multivariateSelection
423 laplacianSchemes { default Gauss linear corrected; }
424 interpolationSchemes { default linear; }
425 snGradSchemes { default corrected; }
426 fluxRequired { default yes; }
430 class ClonedCaseRunner(CaseRunner
):
431 """A runner for cases that need to be cloned from another case."""
432 def __init__(self
, name
, parent_runner
, case_dir
='.',
433 test_dir
=None, skip_test
=False):
434 CaseRunner
.__init
__(self
, name
, case_dir
=case_dir
,
435 test_dir
=test_dir
, skip_test
=skip_test
)
436 self
._parent
_runner
= parent_runner
438 def clone_from_parent(self
, case_dir
):
439 """Clones the case from its parent case."""
441 parent_dir
= self
._parent
_runner
.case_dir
442 if not _op
.isdir(_op
.join(case_dir
, 'system')):
443 _fu
.clone_case(parent_dir
, case_dir
, verbose
=False)
447 _fu
.cerror("Failed to clone", parent_dir
, ":", str(e
),
451 def run(self
, step
=None, test_mode
=False, verbose
=False):
452 """Refer to `CaseRunner.run`."""
454 self
.clone_from_parent(self
.case_dir
)
455 return CaseRunner
.run(self
, step
=step
, test_mode
=test_mode
,
459 """Removes the cloned case and, if it exists, the test-case."""
460 _fu
.remove_case(self
.case_dir
)
461 if _op
.isdir(self
.test_dir
):
462 _fu
.remove_case(self
.test_dir
)
464 def clone_test(self
):
465 """Refer to `CaseRunner.clean`."""
466 if _op
.isdir(self
.test_dir
):
467 _fu
.remove_case(self
.test_dir
)
468 self
.clone_from_parent(self
.test_dir
)
469 self
.modify_for_test()
471 class TutorialRunner(object):
472 """A tutorial case runner.
476 default_schemes : Use the default finite volume schemes in test runs.
478 keep_going : Do not abort after a failed case. Defaults to False.
479 test_mode : Run in testing mode. Defaults to False.
480 verbose : Echo the output to standard output. Defaults to False.
483 def __init__(self
, default_schemes
=False, keep_going
=False, test_mode
=False,
485 """Initializes a new TutorialRunner instance."""
486 self
.default_schemes
= default_schemes
487 self
.keep_going
= keep_going
488 self
.test_mode
= test_mode
489 self
.verbose
= verbose
494 """Returns a tuple of all registered case names."""
495 return tuple([c
.name
for c
in self
._cases
])
498 """Returns a tuple of all registered step names."""
500 for c
in self
._cases
:
502 steps
.append('.'.join((c
.name
,s
)))
505 def add_case(self
, case
):
507 assert case
.name
not in self
.cases(), (
508 "Trying to re-register case "+case
.name
)
509 self
._cases
.append(case
)
511 def run(self
, step
=None):
512 """Run the case as a whole or a specific step.
514 A step is only run if at least one of the following applies:
515 * `self.test_mode` is True.
516 * The stamp file of the test (usually the log file) does not exist.
517 * The stamp file of the preceeding step of the same case is newer than
518 the one for this step.
520 Every step is assumed to depend on the preceeding step of the same case,
521 and above criteria propagate up to the first step if required (except for
522 `self.test_mode` which only applies to the specified step, or to all
523 steps if `step` is None). This means that if you specify a step to be run
524 and any of the preceeding steps of the same case has not been run or is
525 out of date, that step and all the ones in between will also be run.
527 In test mode all steps belonging to the same case are run (up to the
528 specified one), except in the default case, where all steps in all cases
531 The names of the steps are composed of the case name and the step name,
532 separated by a dot. Only specifying the case name is equivalent to
533 running all the steps belonging to that case.
537 step : Name of the step to be run. If `None`, try to run all steps.
541 True if the steps all ran successfully, False otherwise.
547 case_and_step
= step
.split('.', 1)
548 case
= case_and_step
[0]
549 if len(case_and_step
) > 1:
550 substep
= case_and_step
[1]
555 for c
in filter(lambda c
: c
.name
in cases
, self
._cases
):
556 c
.default_schemes
= self
.default_schemes
559 stat
= c
.run(substep
, self
.test_mode
, self
.verbose
)
561 if not self
.keep_going
:
563 if not stat
and not self
.keep_going
:
565 return stat
or self
.keep_going
568 """Clean all cases."""
569 for c
in self
._cases
:
573 """Run as the main function in a script.
574 <step> Optionally, a step can be specifiedo which should be
575 run. By default all steps are run. Refer to run() for
581 parser
= optparse
.OptionParser(usage
='Usage: %prog [options] [step]')
582 parser
.add_option('-t', '--test', dest
='testmode', action
='store_true',
583 default
=False, help=('Run in testing mode. This causes the case ' +
584 'to be cloned to a test directory.'))
585 parser
.add_option('-k', '--keep-going', dest
='keep_going',
586 action
='store_true', default
=False,
587 help='Don\'t abort after a failed case.')
588 parser
.add_option('-d', '--default-schemes', dest
='default_schemes',
589 action
='store_true', default
=False,
590 help='Run using a set of default fvSchemes.')
591 parser
.add_option('-l', '--list', dest
='list', action
='store_true',
592 default
=False, help='List the individual steps in this tutorial.')
593 parser
.add_option('-c', '--cases', dest
='cases', action
='store_true',
594 default
=False, help='Print the cases in this tutorial.')
595 parser
.add_option('--clean', dest
='clean', action
='store_true',
596 default
=False, help='Clean this tutorial case.')
597 parser
.add_option('-v', '--verbose', dest
='verbose', action
='store_true',
598 default
=False, help=('Echo the output to standard output'))
600 (opts
, args
) = parser
.parse_args()
601 if opts
.list and (opts
.testmode
or opts
.default_schemes
or
602 opts
.keep_going
or opts
.cases
or opts
.clean
or opts
.verbose
or
605 'Option --list may not be used with arguments and other options')
606 if opts
.cases
and (opts
.testmode
or opts
.default_schemes
or
607 opts
.keep_going
or opts
.list or opts
.clean
or opts
.verbose
or
610 'Option --cases may not be used with arguments and other options')
611 if opts
.clean
and (opts
.testmode
or opts
.default_schemes
or
612 opts
.keep_going
or opts
.list or opts
.cases
or opts
.verbose
or
615 'Option --clean may not be used with arguments and other options')
616 if opts
.default_schemes
and not opts
.testmode
:
618 'Option --default-schemes can only be used with --test')
621 'May not be called with more than one argument')
624 echo('\n'.join(self
.steps()))
627 echo('\n'.join(self
.cases()))
633 self
.default_schemes
= opts
.default_schemes
634 self
.keep_going
= opts
.keep_going
635 self
.test_mode
= opts
.testmode
636 self
.verbose
= opts
.verbose
641 return int(not self
.run(step
))
643 class RunApp(object):
644 """Functor to run a FreeFOAM application."""
645 def __init__(self
, app
, parallel
=False, args
=[]):
646 """Creates a new application runner.
650 app : Name or full/relative path to the application to run.
651 parallel : If True, the '-parallel' flag will be passed to the
653 args : Additional arguments and options to be passed to the
658 self
._parallel
= parallel
660 self
._runner
= _fr
.ParallelRunner()
662 def __call__(self
, case_dir
, stamp_file
, testmode
):
663 """Runs the FreeFOAM application.
665 The function redirects the output to `self.logfile`. Before running
666 the application, it writes a short header to the log, listing the
667 application name, the case directory and the command line used to
668 invoke the application. At the end of the run it writes a `REPORT:
669 SUCCESS` if the application returned 0, or `REPORT: FAILURE` for all
670 other return values or if an exception was caught.
674 True on success, False otherwise.
680 stamp_file
.write(''.join([80*'='+'\nAPPLICATION: ', self
._app
, '\n']))
681 stamp_file
.write(''.join(['CASE: ', case_dir
, '\n']))
682 stamp_file
.write(''.join(['COMMAND LINE: ', self
._runner
.command_str(
683 self
._app
, case
=case_dir
, parallel
=self
._parallel
,
684 args
=self
._args
), '\n'+80*'='+'\n\n']))
687 # this is ridiculous, but python 2.4 doesn't support try-except-finally
690 _os
.environ
['FREEFOAM_CONFIG_DIR'] = _op
.join(case_dir
, 'etc')
691 self
._runner
.run(self
._app
, case
=case_dir
,
692 parallel
=self
._parallel
, stdout
=subprocess
.PIPE
,
693 stderr
=subprocess
.STDOUT
, args
=self
._args
, background
=True) == 0
694 # retrieve the output and write it to the log file as long as the thing runs
696 lines
= self
._runner
.getProcess().stdout
.readlines()
697 if len(lines
) and not isinstance(lines
[0], str):
698 lines
= list(map(lambda l
: l
.decode(), lines
))
699 stamp_file
.writelines(lines
)
700 stat
= self
._runner
.poll()
704 except KeyboardInterrupt:
705 stamp_file
.write('\n*** Error *** KeyboardInterrupt\n')
709 stamp_file
.write('\n*** Error *** '+str(e
)+'\n')
713 # write final verdict to the log and clean up
714 stamp_file
.write('\n'+80*'='+'\nREPORT: ')
716 stamp_file
.write('SUCCESS\n')
718 stamp_file
.write('FAILURE\n')
721 class CompileApp(object):
722 """Functor to compile a FreeFOAM tutorial application.
726 srcdir : The top-level source directory.
727 bindir : The top-level binary directory.
728 app : Absolute path to the binary application.
731 def __init__(self
, srcdir
, bindir
=None, name
=None):
732 """Initializes a new CompileApp instance.
736 srcdir : The top-level source directory.
737 bindir : The top-level binary directory (i.e. where to build the
738 application). If set to None, defaults to 'srcdir+"-build"'.
739 name : Name of the application. Defaults to `os.path.basename(srcdir)`.
742 self
.srcdir
= _op
.abspath(srcdir
)
744 bindir
= self
.srcdir
+'-build'
746 name
= _op
.basename(self
.srcdir
)
748 self
.app
= _op
.join(self
.bindir
, _f
.EXE_PREFIX
+name
+_f
.EXE_SUFFIX
)
750 def __call__(self
, case_dir
, stamp_file
, testmode
):
751 """Compile a FreeFOAM application.
753 Configures and compiles a FreeFOAM tutorial application located in the
754 source directory `self.srcdir`.
758 # get the current working directory
760 # create the binary directory and cd to it
761 if not _op
.exists(self
.bindir
):
762 _os
.makedirs(self
.bindir
)
763 elif not _op
.isdir(self
.bindir
):
764 raise BinDirCreationError(self
.bindir
,
765 'The path exists, but is not a directory')
768 _os
.chdir(self
.bindir
)
769 # configure the project
772 '-DCMAKE_C_COMPILER='+_f
.CMAKE_C_COMPILER
,
773 '-DCMAKE_CXX_COMPILER='+_f
.CMAKE_CXX_COMPILER
,
774 '-DCMAKE_BUILD_TYPE='+compile_config
,
775 '-G', _f
.CMAKE_GENERATOR
,
778 stamp_file
.write(''.join(['CONFIGURING: ', ' '.join(cmd
),
779 '\n'+80*'='+'\n\n']))
782 process
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
,
783 stderr
=subprocess
.STDOUT
)
785 lines
= process
.stdout
.readlines()
786 if len(lines
) and not isinstance(lines
[0], str):
787 lines
= list(map(lambda l
: l
.decode(), lines
))
788 stamp_file
.writelines(lines
)
789 returncode
= process
.poll()
790 if returncode
!= None:
792 stamp_file
.write('\n'+80*'='+'\nREPORT: ')
794 msg
= 'Failed to configure the project "%s" in "%s".'%(
795 self
.srcdir
, self
.bindir
)
796 stamp_file
.write('FAILURE\n')
799 stamp_file
.write('SUCCESS\n')
801 cmd
= [_f
.CMAKE_COMMAND
, '--build', '.',
802 '--config', compile_config
]
803 stamp_file
.write(''.join(['COMPILING: ', ' '.join(cmd
),
804 '\n'+80*'='+'\n\n']))
806 returncode
= subprocess
.call(
807 cmd
, stdout
=stamp_file
, stderr
=stamp_file
)
808 stamp_file
.write('\n'+80*'='+'\nREPORT: ')
810 msg
= 'Failed to build the application "%s" in "%s".'%(
811 self
.srcdir
, self
.bindir
)
812 stamp_file
.write('FAILURE\n')
815 stamp_file
.write('SUCCESS\n')
821 def clean_application(bindir
):
822 """Cleans a FreeFOAM application.
824 Cleans a FreeFOAM tutorial application located in the binary directory
829 bindir : The top-level binary directory (i.e. where the build directory is.
833 bindir
= _op
.normpath(bindir
)
835 if _op
.isdir(bindir
):
836 cmd
= [_f
.CMAKE_COMMAND
, '--build', bindir
, '--target', 'clean']
837 returncode
= subprocess
.call(cmd
)
839 raise CleaningFailedError(bindir
)
842 def modify_file(fname
, regSub
, backup
=None):
843 """Modifies file `fname` by applying the regular expression/substitution
844 pairs in the iterable `regSub`. If `backup` is a string and not None, a
845 backup file with the given suffix will be created before the modification
848 fname
= _op
.abspath(fname
)
849 if backup
and type(backup
) == str:
851 shutil
.copy2(fname
, fname
+backup
)
852 lines
=open(fname
, 'rt').readlines()
853 for i
, l
in enumerate(lines
):
856 lines
[i
] = re
.sub(p
, s
, l
)
857 open(fname
, 'wt').writelines(lines
)
859 # ------------------------- vim: set sw=3 sts=3 et: --------------- end-of-file