From b23c4a9a3bc5eea462d6b311c600be8c152cbc6f Mon Sep 17 00:00:00 2001 From: bblay Date: Fri, 10 Feb 2023 12:31:47 +0000 Subject: [PATCH 01/15] zero config --- setup.py | 7 +++- source/fab/cli.py | 83 +++++++++++++++++++++++++++++++++++++ source/fab/parse/fortran.py | 15 ++++++- source/fab/steps/analyse.py | 20 +++++++-- 4 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 source/fab/cli.py diff --git a/setup.py b/setup.py index 3e636952..f3eec633 100644 --- a/setup.py +++ b/setup.py @@ -54,5 +54,10 @@ 'tests': tests, 'docs': docs, 'dev': [*tests, *docs, *features], - } + }, + entry_points={ + 'console_scripts': [ + 'fab=fab.cli:cli_fab' + ] + }, ) diff --git a/source/fab/cli.py b/source/fab/cli.py new file mode 100644 index 00000000..30b5c19a --- /dev/null +++ b/source/fab/cli.py @@ -0,0 +1,83 @@ +# ############################################################################## +# (c) Crown copyright Met Office. All rights reserved. +# For further details please refer to the file COPYRIGHT +# which you should have received as part of this distribution +# ############################################################################## +from argparse import ArgumentParser +from pathlib import Path + +from fab.steps.link import LinkExe + +from fab.steps.compile_c import CompileC + +from fab.steps.compile_fortran import CompileFortran, get_fortran_compiler + +from fab.steps.analyse import Analyse + +from fab.constants import PRAGMAD_C + +from fab.artefacts import CollectionGetter +from fab.steps.c_pragma_injector import CPragmaInjector + +from fab.steps.preprocess import c_preprocessor, fortran_preprocessor + +from fab.steps.root_inc_files import RootIncFiles + +from fab.steps.find_source_files import FindSourceFiles + +from fab.build_config import BuildConfig +from fab.steps.grab.folder import GrabFolder + + +def generic_build_config(folder: Path) -> BuildConfig: + folder = folder.resolve() + + # Within the fab workspace, we'll create a project workspace. + # Ideally we'd just use folder.name, but to avoid clashes, we'll use the full absolute path. + label = '/'.join(folder.parts[1:]) + + # which compiler is set in the environment? + # we need to know because there are some linker flags that we'll need + compiler = get_fortran_compiler()[0] + if compiler == 'gfortran': + link_step = LinkExe(linker='gcc', flags=['-lgfortran']) + else: + raise NotImplementedError(f"Fab's zero config not yet configured for your compiler: '{compiler}'") + + config = BuildConfig( + project_label=label, + steps=[ + GrabFolder(folder), + FindSourceFiles(), + + RootIncFiles(), # JULES helper, get rid of this eventually + + fortran_preprocessor(common_flags=['-P']), + + CPragmaInjector(), + c_preprocessor(source=CollectionGetter(PRAGMAD_C)), + + Analyse(find_fortran_programs=True), + + CompileFortran(), + CompileC(), + + link_step, + ] + ) + + return config + + +def cli_fab(): + """ + Running Fab from the command line will attempt to build the project in the current or given folder. + + """ + arg_parser = ArgumentParser() + arg_parser.add_argument('folder', nargs='?', default='.', type=Path) + args = arg_parser.parse_args() + + config = generic_build_config(args.folder) + + config.run() diff --git a/source/fab/parse/fortran.py b/source/fab/parse/fortran.py index 72fe3bd0..dc55f017 100644 --- a/source/fab/parse/fortran.py +++ b/source/fab/parse/fortran.py @@ -37,6 +37,7 @@ class AnalysedFortran(AnalysedDependent): """ def __init__(self, fpath: Union[str, Path], file_hash: Optional[int] = None, + program_defs: Optional[Iterable[str]] = None, module_defs: Optional[Iterable[str]] = None, symbol_defs: Optional[Iterable[str]] = None, module_deps: Optional[Iterable[str]] = None, symbol_deps: Optional[Iterable[str]] = None, mo_commented_file_deps: Optional[Iterable[str]] = None, file_deps: Optional[Iterable[Path]] = None, @@ -46,6 +47,8 @@ def __init__(self, fpath: Union[str, Path], file_hash: Optional[int] = None, The source file that was analysed. :param file_hash: The hash of the source. If omitted, Fab will evaluate lazily. + :param program_defs: + Set of program names defined by this source file. :param module_defs: Set of module names defined by this source file. A subset of symbol_defs @@ -69,6 +72,7 @@ def __init__(self, fpath: Union[str, Path], file_hash: Optional[int] = None, super().__init__(fpath=fpath, file_hash=file_hash, symbol_defs=symbol_defs, symbol_deps=symbol_deps, file_deps=file_deps) + self.program_defs: Set[str] = set(program_defs or []) self.module_defs: Set[str] = set(module_defs or []) self.module_deps: Set[str] = set(module_deps or []) self.mo_commented_file_deps: Set[str] = set(mo_commented_file_deps or []) @@ -79,6 +83,10 @@ def __init__(self, fpath: Union[str, Path], file_hash: Optional[int] = None, self.validate() + def add_program_def(self, name): + self.program_defs.add(name.lower()) + self.add_symbol_def(name) + def add_module_def(self, name): self.module_defs.add(name.lower()) self.add_symbol_def(name) @@ -97,6 +105,7 @@ def field_names(cls): # we're not using the super class because we want to insert, not append the order of our attributes return [ 'fpath', 'file_hash', + 'program_defs', 'module_defs', 'symbol_defs', 'module_deps', 'symbol_deps', 'mo_commented_file_deps', @@ -109,6 +118,7 @@ def to_dict(self) -> Dict[str, Any]: # We sort the lists for reproducibility in testing. result = super().to_dict() result.update({ + "program_defs": list(sorted(self.program_defs)), "module_defs": list(sorted(self.module_defs)), "module_deps": list(sorted(self.module_deps)), "mo_commented_file_deps": list(sorted(self.mo_commented_file_deps)), @@ -122,6 +132,7 @@ def from_dict(cls, d): result = cls( fpath=Path(d["fpath"]), file_hash=d["file_hash"], + program_defs=set(d["program_defs"]), module_defs=set(d["module_defs"]), symbol_defs=set(d["symbol_defs"]), module_deps=set(d["module_deps"]), @@ -137,12 +148,14 @@ def from_dict(cls, d): def validate(self): assert self.file_hash is not None + assert all([d and len(d) for d in self.program_defs]), "bad program definitions" assert all([d and len(d) for d in self.module_defs]), "bad module definitions" assert all([d and len(d) for d in self.symbol_defs]), "bad symbol definitions" assert all([d and len(d) for d in self.module_deps]), "bad module dependencies" assert all([d and len(d) for d in self.symbol_deps]), "bad symbol dependencies" # todo: this feels a little clanky. + assert self.program_defs <= self.symbol_defs, "programs definitions must also be symbol definitions" assert self.module_defs <= self.symbol_defs, "modules definitions must also be symbol definitions" assert self.module_deps <= self.symbol_deps, "modules dependencies must also be symbol dependencies" @@ -185,7 +198,7 @@ def walk_nodes(self, fpath, file_hash, node_tree) -> AnalysedFortran: analysed_fortran.add_symbol_dep(called_name.string) elif obj_type == Program_Stmt: - analysed_fortran.add_symbol_def(str(obj.get_name())) + analysed_fortran.add_program_def(str(obj.get_name())) elif obj_type == Module_Stmt: analysed_fortran.add_module_def(str(obj.get_name())) diff --git a/source/fab/steps/analyse.py b/source/fab/steps/analyse.py index 0585ea64..5b84f43a 100644 --- a/source/fab/steps/analyse.py +++ b/source/fab/steps/analyse.py @@ -33,7 +33,7 @@ You'll have to manually read the file to determine which symbol definitions and dependencies it contains. """ - +from itertools import chain import logging import sys import warnings @@ -42,7 +42,7 @@ from fab.parse.c import CAnalyser -from fab.parse.fortran import FortranParserWorkaround, FortranAnalyser +from fab.parse.fortran import AnalysedFortran, FortranParserWorkaround, FortranAnalyser from fab.constants import BUILD_TREES, CURRENT_PREBUILDS from fab.dep_tree import extract_sub_tree, validate_dependencies, AnalysedDependent @@ -79,7 +79,8 @@ class Analyse(Step): # todo: allow the user to specify a different output artefact collection name? def __init__(self, source: Optional[ArtefactsGetter] = None, - root_symbol: Optional[Union[str, List[str]]] = None, # todo: iterable is more correct + root_symbol: Optional[Union[str, List[str]]] = None, + find_fortran_programs: bool = False, std: str = "f2008", special_measure_analysis_results: Optional[Iterable[FortranParserWorkaround]] = None, unreferenced_deps: Optional[Iterable[str]] = None, @@ -96,6 +97,9 @@ def __init__(self, :param source: An :class:`~fab.util.ArtefactsGetter` to get the source files. + :param find_fortran_programs: + Instructs the analyser to automatically identify program definitions in the source. + Alternatively, the required programs can be specified with the root_symbol argument. :param root_symbol: When building an executable, provide the Fortran Program name(s), or 'main' for C. If None, build tree extraction will not be performed and the entire source will be used @@ -122,8 +126,12 @@ def __init__(self, # because the files they refer to probably don't exist yet, # because we're just creating steps at this point, so there's been no grab... + if find_fortran_programs and root_symbol: + raise ValueError("find_fortran_programs and root_symbol can't be used together") + super().__init__(name) self.source_getter = source or DEFAULT_SOURCE_GETTER + self.find_fortran_programs = find_fortran_programs self.root_symbols: Optional[List[str]] = [root_symbol] if isinstance(root_symbol, str) else root_symbol self.special_measure_analysis_results: List[FortranParserWorkaround] = \ list(special_measure_analysis_results or []) @@ -167,6 +175,12 @@ def run(self, artefact_store: Dict, config): analysed_files = self._parse_files(files=files) self._add_manual_results(analysed_files) + # shall we search the results for fortran programs? + if self.find_fortran_programs: + sets_of_programs = [af.program_defs for af in by_type(analysed_files, AnalysedFortran)] + self.root_symbols = set(chain(*sets_of_programs)) + logger.info(f'automatically found the following programs to build: {", ".join(self.root_symbols)}') + # analyse project_source_tree, symbols = self._analyse_dependencies(analysed_files) From 6341e3322b80cd274d6f65619fa960eeb8254123 Mon Sep 17 00:00:00 2001 From: bblay Date: Fri, 10 Feb 2023 12:38:16 +0000 Subject: [PATCH 02/15] tidy imports --- source/fab/cli.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/source/fab/cli.py b/source/fab/cli.py index 30b5c19a..e61c5a25 100644 --- a/source/fab/cli.py +++ b/source/fab/cli.py @@ -6,27 +6,18 @@ from argparse import ArgumentParser from pathlib import Path -from fab.steps.link import LinkExe - -from fab.steps.compile_c import CompileC - -from fab.steps.compile_fortran import CompileFortran, get_fortran_compiler - -from fab.steps.analyse import Analyse - -from fab.constants import PRAGMAD_C - from fab.artefacts import CollectionGetter +from fab.build_config import BuildConfig +from fab.constants import PRAGMAD_C +from fab.steps.analyse import Analyse from fab.steps.c_pragma_injector import CPragmaInjector - -from fab.steps.preprocess import c_preprocessor, fortran_preprocessor - -from fab.steps.root_inc_files import RootIncFiles - +from fab.steps.compile_c import CompileC +from fab.steps.compile_fortran import CompileFortran, get_fortran_compiler from fab.steps.find_source_files import FindSourceFiles - -from fab.build_config import BuildConfig from fab.steps.grab.folder import GrabFolder +from fab.steps.link import LinkExe +from fab.steps.preprocess import c_preprocessor, fortran_preprocessor +from fab.steps.root_inc_files import RootIncFiles def generic_build_config(folder: Path) -> BuildConfig: From ee61d24258d348d57af6b9265572fe3e645983f9 Mon Sep 17 00:00:00 2001 From: bblay Date: Fri, 10 Feb 2023 13:49:31 +0000 Subject: [PATCH 03/15] detect c main --- source/fab/cli.py | 8 +++-- source/fab/steps/analyse.py | 29 +++++++++++++------ .../zero_config/test_zero_config.py | 19 ++++++++++++ 3 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 system-tests-new/zero_config/test_zero_config.py diff --git a/source/fab/cli.py b/source/fab/cli.py index e61c5a25..0ee6065d 100644 --- a/source/fab/cli.py +++ b/source/fab/cli.py @@ -5,6 +5,7 @@ # ############################################################################## from argparse import ArgumentParser from pathlib import Path +from typing import Optional from fab.artefacts import CollectionGetter from fab.build_config import BuildConfig @@ -20,7 +21,7 @@ from fab.steps.root_inc_files import RootIncFiles -def generic_build_config(folder: Path) -> BuildConfig: +def _generic_build_config(folder: Path, fab_workspace: Optional[Path] = None) -> BuildConfig: folder = folder.resolve() # Within the fab workspace, we'll create a project workspace. @@ -37,6 +38,7 @@ def generic_build_config(folder: Path) -> BuildConfig: config = BuildConfig( project_label=label, + fab_workspace=fab_workspace, steps=[ GrabFolder(folder), FindSourceFiles(), @@ -48,7 +50,7 @@ def generic_build_config(folder: Path) -> BuildConfig: CPragmaInjector(), c_preprocessor(source=CollectionGetter(PRAGMAD_C)), - Analyse(find_fortran_programs=True), + Analyse(find_programs=True), CompileFortran(), CompileC(), @@ -69,6 +71,6 @@ def cli_fab(): arg_parser.add_argument('folder', nargs='?', default='.', type=Path) args = arg_parser.parse_args() - config = generic_build_config(args.folder) + config = _generic_build_config(args.folder) config.run() diff --git a/source/fab/steps/analyse.py b/source/fab/steps/analyse.py index 5b84f43a..30213499 100644 --- a/source/fab/steps/analyse.py +++ b/source/fab/steps/analyse.py @@ -40,7 +40,9 @@ from pathlib import Path from typing import Dict, List, Iterable, Set, Optional, Union -from fab.parse.c import CAnalyser +from fab import FabException + +from fab.parse.c import AnalysedC, CAnalyser from fab.parse.fortran import AnalysedFortran, FortranParserWorkaround, FortranAnalyser @@ -80,7 +82,7 @@ class Analyse(Step): def __init__(self, source: Optional[ArtefactsGetter] = None, root_symbol: Optional[Union[str, List[str]]] = None, - find_fortran_programs: bool = False, + find_programs: bool = False, std: str = "f2008", special_measure_analysis_results: Optional[Iterable[FortranParserWorkaround]] = None, unreferenced_deps: Optional[Iterable[str]] = None, @@ -97,7 +99,7 @@ def __init__(self, :param source: An :class:`~fab.util.ArtefactsGetter` to get the source files. - :param find_fortran_programs: + :param find_programs: Instructs the analyser to automatically identify program definitions in the source. Alternatively, the required programs can be specified with the root_symbol argument. :param root_symbol: @@ -126,12 +128,12 @@ def __init__(self, # because the files they refer to probably don't exist yet, # because we're just creating steps at this point, so there's been no grab... - if find_fortran_programs and root_symbol: - raise ValueError("find_fortran_programs and root_symbol can't be used together") + if find_programs and root_symbol: + raise ValueError("find_programs and root_symbol can't be used together") super().__init__(name) self.source_getter = source or DEFAULT_SOURCE_GETTER - self.find_fortran_programs = find_fortran_programs + self.find_programs = find_programs self.root_symbols: Optional[List[str]] = [root_symbol] if isinstance(root_symbol, str) else root_symbol self.special_measure_analysis_results: List[FortranParserWorkaround] = \ list(special_measure_analysis_results or []) @@ -175,10 +177,19 @@ def run(self, artefact_store: Dict, config): analysed_files = self._parse_files(files=files) self._add_manual_results(analysed_files) - # shall we search the results for fortran programs? - if self.find_fortran_programs: + # shall we search the results for fortran programs and a c function called main? + if self.find_programs: + # find fortran programs sets_of_programs = [af.program_defs for af in by_type(analysed_files, AnalysedFortran)] - self.root_symbols = set(chain(*sets_of_programs)) + self.root_symbols = list(chain(*sets_of_programs)) + + # find c main() + c_with_main = list(filter(lambda c: 'main' in c.symbol_defs, by_type(analysed_files, AnalysedC))) + if c_with_main: + self.root_symbols.append('main') + if len(c_with_main) > 1: + raise FabException("multiple c main() functions found") + logger.info(f'automatically found the following programs to build: {", ".join(self.root_symbols)}') # analyse diff --git a/system-tests-new/zero_config/test_zero_config.py b/system-tests-new/zero_config/test_zero_config.py new file mode 100644 index 00000000..217807f7 --- /dev/null +++ b/system-tests-new/zero_config/test_zero_config.py @@ -0,0 +1,19 @@ +from pathlib import Path + +from fab.cli import _generic_build_config + + +class TestZeroConfig(object): + + def test_fortran_dependencies(self, tmp_path): + # test the sample project in the fortran dependencies system test + config = _generic_build_config(Path(__file__).parent.parent / 'FortranDependencies', fab_workspace=tmp_path) + config.run() + assert (config.project_workspace / 'first.exe').exists() + assert (config.project_workspace / 'second.exe').exists() + + def test_c_fortran_interop(self, tmp_path): + # test the sample project in the fortran dependencies system test + config = _generic_build_config(Path(__file__).parent.parent / 'CFortranInterop', fab_workspace=tmp_path) + config.run() + assert (config.project_workspace / 'main.exe').exists() From c5574c95cb996bf22cd6dbe3cd26671e16b095f9 Mon Sep 17 00:00:00 2001 From: bblay Date: Fri, 10 Feb 2023 14:03:22 +0000 Subject: [PATCH 04/15] tidy --- source/fab/cli.py | 9 +++++---- .../FortranDependencies/test_FortranDependencies.py | 2 ++ system-tests-new/zero_config/test_zero_config.py | 8 ++++++-- tests/unit_tests/parse/fortran/test_fortran.py | 1 + tests/unit_tests/parse/fortran/test_fortran_analyser.py | 1 + 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/source/fab/cli.py b/source/fab/cli.py index 0ee6065d..ff4bd1b7 100644 --- a/source/fab/cli.py +++ b/source/fab/cli.py @@ -5,7 +5,7 @@ # ############################################################################## from argparse import ArgumentParser from pathlib import Path -from typing import Optional +from typing import Dict, Optional from fab.artefacts import CollectionGetter from fab.build_config import BuildConfig @@ -21,8 +21,9 @@ from fab.steps.root_inc_files import RootIncFiles -def _generic_build_config(folder: Path, fab_workspace: Optional[Path] = None) -> BuildConfig: +def _generic_build_config(folder: Path, kwargs: Optional[Dict] = None) -> BuildConfig: folder = folder.resolve() + kwargs = kwargs or {} # Within the fab workspace, we'll create a project workspace. # Ideally we'd just use folder.name, but to avoid clashes, we'll use the full absolute path. @@ -38,7 +39,6 @@ def _generic_build_config(folder: Path, fab_workspace: Optional[Path] = None) -> config = BuildConfig( project_label=label, - fab_workspace=fab_workspace, steps=[ GrabFolder(folder), FindSourceFiles(), @@ -56,7 +56,8 @@ def _generic_build_config(folder: Path, fab_workspace: Optional[Path] = None) -> CompileC(), link_step, - ] + ], + **kwargs, ) return config diff --git a/system-tests-new/FortranDependencies/test_FortranDependencies.py b/system-tests-new/FortranDependencies/test_FortranDependencies.py index 50f803b3..f2e2a102 100644 --- a/system-tests-new/FortranDependencies/test_FortranDependencies.py +++ b/system-tests-new/FortranDependencies/test_FortranDependencies.py @@ -54,11 +54,13 @@ def test_FortranDependencies(tmp_path): # check the analysis results assert AnalysedFortran.load(config.prebuild_folder / 'first.193489053.an') == AnalysedFortran( fpath=config.source_root / 'first.f90', file_hash=193489053, + program_defs={'first'}, module_defs=None, symbol_defs={'first'}, module_deps={'greeting_mod', 'constants_mod'}, symbol_deps={'greeting_mod', 'constants_mod', 'greet'}) assert AnalysedFortran.load(config.prebuild_folder / 'two.2557739057.an') == AnalysedFortran( fpath=config.source_root / 'two.f90', file_hash=2557739057, + program_defs={'second'}, module_defs=None, symbol_defs={'second'}, module_deps={'constants_mod', 'bye_mod'}, symbol_deps={'constants_mod', 'bye_mod', 'farewell'}) diff --git a/system-tests-new/zero_config/test_zero_config.py b/system-tests-new/zero_config/test_zero_config.py index 217807f7..0114ad6a 100644 --- a/system-tests-new/zero_config/test_zero_config.py +++ b/system-tests-new/zero_config/test_zero_config.py @@ -7,13 +7,17 @@ class TestZeroConfig(object): def test_fortran_dependencies(self, tmp_path): # test the sample project in the fortran dependencies system test - config = _generic_build_config(Path(__file__).parent.parent / 'FortranDependencies', fab_workspace=tmp_path) + config = _generic_build_config( + Path(__file__).parent.parent / 'FortranDependencies', + kwargs={'fab_workspace': tmp_path, 'multiprocessing': False}) config.run() assert (config.project_workspace / 'first.exe').exists() assert (config.project_workspace / 'second.exe').exists() def test_c_fortran_interop(self, tmp_path): # test the sample project in the fortran dependencies system test - config = _generic_build_config(Path(__file__).parent.parent / 'CFortranInterop', fab_workspace=tmp_path) + config = _generic_build_config( + Path(__file__).parent.parent / 'CFortranInterop', + kwargs={'fab_workspace': tmp_path, 'multiprocessing': False}) config.run() assert (config.project_workspace / 'main.exe').exists() diff --git a/tests/unit_tests/parse/fortran/test_fortran.py b/tests/unit_tests/parse/fortran/test_fortran.py index aabea95e..5765be6d 100644 --- a/tests/unit_tests/parse/fortran/test_fortran.py +++ b/tests/unit_tests/parse/fortran/test_fortran.py @@ -138,6 +138,7 @@ def as_dict(self): return { 'fpath': 'foo.f90', 'file_hash': 123, + 'program_defs': [], 'module_defs': ['my_mod1', 'my_mod2'], 'symbol_defs': ['my_func1', 'my_func2', 'my_mod1', 'my_mod2'], 'module_deps': ['other_mod1', 'other_mod2'], diff --git a/tests/unit_tests/parse/fortran/test_fortran_analyser.py b/tests/unit_tests/parse/fortran/test_fortran_analyser.py index a29f897a..11dcd9fc 100644 --- a/tests/unit_tests/parse/fortran/test_fortran_analyser.py +++ b/tests/unit_tests/parse/fortran/test_fortran_analyser.py @@ -73,6 +73,7 @@ def test_program_file(self, fortran_analyser, module_fpath, module_expected): module_expected.fpath = Path(tmp_file.name) module_expected._file_hash = 768896775 + module_expected.program_defs = {'foo_mod'} module_expected.module_defs = set() module_expected.symbol_defs.update({'internal_sub', 'internal_func'}) From 67a27fb1807777cc8374ee9045ff01184354a795 Mon Sep 17 00:00:00 2001 From: bblay Date: Fri, 10 Feb 2023 14:14:07 +0000 Subject: [PATCH 05/15] handle unspecified compiler --- source/fab/steps/compile_fortran.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/source/fab/steps/compile_fortran.py b/source/fab/steps/compile_fortran.py index ddaa5a20..aecba83e 100644 --- a/source/fab/steps/compile_fortran.py +++ b/source/fab/steps/compile_fortran.py @@ -357,7 +357,33 @@ def get_fortran_compiler(compiler: Optional[str] = None): Use this string instead of the $FC environment variable. """ - return get_tool(compiler or os.getenv('FC', '')) # type: ignore + fortran_compiler = None + try: + fortran_compiler = get_tool(compiler or os.getenv('FC', '')) # type: ignore + except ValueError: + # tool not specified + pass + + if not fortran_compiler: + try: + run_command(['gfortran', '--help']) + fortran_compiler = 'gfortran' + except RuntimeError: + # gfortran not available + pass + + if not fortran_compiler: + try: + run_command(['ifort', '--help']) + fortran_compiler = 'ifort' + except RuntimeError: + # gfortran not available + pass + + if not fortran_compiler: + raise RuntimeError('no fortran compiler specified or discovered') + + return fortran_compiler def get_mod_hashes(analysed_files: Set[AnalysedFortran], config) -> Dict[str, int]: From d6d2d02012d2b16c584dea57c1b6b8d98da76f22 Mon Sep 17 00:00:00 2001 From: bblay Date: Fri, 10 Feb 2023 14:15:50 +0000 Subject: [PATCH 06/15] default flags --- source/fab/steps/compile_fortran.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/fab/steps/compile_fortran.py b/source/fab/steps/compile_fortran.py index aecba83e..2cff6a8b 100644 --- a/source/fab/steps/compile_fortran.py +++ b/source/fab/steps/compile_fortran.py @@ -356,6 +356,8 @@ def get_fortran_compiler(compiler: Optional[str] = None): :param compiler: Use this string instead of the $FC environment variable. + Returns the tool and a list of flags. + """ fortran_compiler = None try: @@ -367,7 +369,7 @@ def get_fortran_compiler(compiler: Optional[str] = None): if not fortran_compiler: try: run_command(['gfortran', '--help']) - fortran_compiler = 'gfortran' + fortran_compiler = 'gfortran', [] except RuntimeError: # gfortran not available pass @@ -375,7 +377,7 @@ def get_fortran_compiler(compiler: Optional[str] = None): if not fortran_compiler: try: run_command(['ifort', '--help']) - fortran_compiler = 'ifort' + fortran_compiler = 'ifort', [] except RuntimeError: # gfortran not available pass From 4aca57dc0fc011071b83cb9842699d096c9647ae Mon Sep 17 00:00:00 2001 From: bblay Date: Fri, 10 Feb 2023 15:23:30 +0000 Subject: [PATCH 07/15] fix getting fpp --- source/fab/cli.py | 16 ++++---- source/fab/steps/compile_fortran.py | 37 +++++++++++++++++++ .../zero_config/test_zero_config.py | 2 +- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/source/fab/cli.py b/source/fab/cli.py index ff4bd1b7..29ef83cf 100644 --- a/source/fab/cli.py +++ b/source/fab/cli.py @@ -13,7 +13,7 @@ from fab.steps.analyse import Analyse from fab.steps.c_pragma_injector import CPragmaInjector from fab.steps.compile_c import CompileC -from fab.steps.compile_fortran import CompileFortran, get_fortran_compiler +from fab.steps.compile_fortran import CompileFortran, get_fortran_compiler, get_fortran_preprocessor from fab.steps.find_source_files import FindSourceFiles from fab.steps.grab.folder import GrabFolder from fab.steps.link import LinkExe @@ -29,13 +29,13 @@ def _generic_build_config(folder: Path, kwargs: Optional[Dict] = None) -> BuildC # Ideally we'd just use folder.name, but to avoid clashes, we'll use the full absolute path. label = '/'.join(folder.parts[1:]) - # which compiler is set in the environment? - # we need to know because there are some linker flags that we'll need - compiler = get_fortran_compiler()[0] - if compiler == 'gfortran': + fpp, fpp_flags = get_fortran_preprocessor() + fc, fc_flags = get_fortran_compiler() + + if fc == 'gfortran': link_step = LinkExe(linker='gcc', flags=['-lgfortran']) else: - raise NotImplementedError(f"Fab's zero config not yet configured for your compiler: '{compiler}'") + raise NotImplementedError(f"Fab's zero config not yet configured for compiler: '{fc}'") config = BuildConfig( project_label=label, @@ -45,14 +45,14 @@ def _generic_build_config(folder: Path, kwargs: Optional[Dict] = None) -> BuildC RootIncFiles(), # JULES helper, get rid of this eventually - fortran_preprocessor(common_flags=['-P']), + fortran_preprocessor(preprocessor=fpp, common_flags=fpp_flags), CPragmaInjector(), c_preprocessor(source=CollectionGetter(PRAGMAD_C)), Analyse(find_programs=True), - CompileFortran(), + CompileFortran(compiler=fc, common_flags=fc_flags), CompileC(), link_step, diff --git a/source/fab/steps/compile_fortran.py b/source/fab/steps/compile_fortran.py index 2cff6a8b..d48d6042 100644 --- a/source/fab/steps/compile_fortran.py +++ b/source/fab/steps/compile_fortran.py @@ -346,6 +346,41 @@ def compile_file(self, analysed_file, flags, output_fpath): value={'time_taken': timer.taken, 'start': timer.start}) +def get_fortran_preprocessor(): + fpp, fpp_flags = None, None + + try: + fpp, fpp_flags = get_tool(os.getenv('FPP')) + except ValueError: + pass + + if not fpp: + try: + run_command(['echo', '"#define foo"', '|', 'fpp']) + fpp, fpp_flags = 'fpp', ['-P'] + logger.info('detected fpp') + except RuntimeError: + # fpp not available + pass + + if not fpp: + try: + run_command(['echo', '"#define foo"', '|', 'cpp']) + fpp, fpp_flags = 'cpp', ['-traditional-cpp', '-P'] + logger.info('detected cpp') + except RuntimeError: + # fpp not available + pass + + if not fpp: + raise RuntimeError('no fortran preprocessor specified or discovered') + + if '-P' not in fpp_flags: + fpp_flags.append('-P') + + return fpp, fpp_flags + + def get_fortran_compiler(compiler: Optional[str] = None): """ Get the fortran compiler specified by the `$FC` environment variable, @@ -370,6 +405,7 @@ def get_fortran_compiler(compiler: Optional[str] = None): try: run_command(['gfortran', '--help']) fortran_compiler = 'gfortran', [] + logger.info('detected gfortran') except RuntimeError: # gfortran not available pass @@ -378,6 +414,7 @@ def get_fortran_compiler(compiler: Optional[str] = None): try: run_command(['ifort', '--help']) fortran_compiler = 'ifort', [] + logger.info('detected ifort') except RuntimeError: # gfortran not available pass diff --git a/system-tests-new/zero_config/test_zero_config.py b/system-tests-new/zero_config/test_zero_config.py index 0114ad6a..23d3657c 100644 --- a/system-tests-new/zero_config/test_zero_config.py +++ b/system-tests-new/zero_config/test_zero_config.py @@ -18,6 +18,6 @@ def test_c_fortran_interop(self, tmp_path): # test the sample project in the fortran dependencies system test config = _generic_build_config( Path(__file__).parent.parent / 'CFortranInterop', - kwargs={'fab_workspace': tmp_path, 'multiprocessing': False}) + kwargs={'fab_workspace': tmp_path, 'multiprocessing': False, 'verbose': True}) config.run() assert (config.project_workspace / 'main.exe').exists() From da81e139ee0e3186ecdda34a3fadcba1632202e1 Mon Sep 17 00:00:00 2001 From: bblay Date: Fri, 10 Feb 2023 15:52:23 +0000 Subject: [PATCH 08/15] none handling --- source/fab/tools.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/fab/tools.py b/source/fab/tools.py index dc87860b..d50dde78 100644 --- a/source/fab/tools.py +++ b/source/fab/tools.py @@ -9,7 +9,7 @@ """ import subprocess import warnings -from typing import Dict, List, Tuple +from typing import Dict, List, Optional, Tuple from fab.util import logger, string_checksum @@ -104,7 +104,7 @@ def run_command(command, env=None, cwd=None, capture_output=True): return res.stdout.decode() -def get_tool(tool_str: str = '') -> Tuple[str, List[str]]: +def get_tool(tool_str: Optional[str] = None) -> Tuple[str, List[str]]: """ Get the compiler, preprocessor, etc, from the given string. @@ -116,6 +116,8 @@ def get_tool(tool_str: str = '') -> Tuple[str, List[str]]: The environment variable from which to find the tool. """ + tool_str = tool_str or '' + tool_split = tool_str.split() if not tool_split: raise ValueError(f"Tool not specified in '{tool_str}'. Cannot continue.") From b86feb778b2212d284fb0b087bf667015b745d67 Mon Sep 17 00:00:00 2001 From: bblay Date: Fri, 10 Feb 2023 16:42:23 +0000 Subject: [PATCH 09/15] unit tests --- source/fab/steps/compile_fortran.py | 1 + .../zero_config/test_zero_config.py | 2 + .../unit_tests/steps/test_compile_fortran.py | 72 ++++++++++++++++++- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/source/fab/steps/compile_fortran.py b/source/fab/steps/compile_fortran.py index d48d6042..41c044fc 100644 --- a/source/fab/steps/compile_fortran.py +++ b/source/fab/steps/compile_fortran.py @@ -351,6 +351,7 @@ def get_fortran_preprocessor(): try: fpp, fpp_flags = get_tool(os.getenv('FPP')) + logger.info(f'env defined fpp as "{fpp}"') except ValueError: pass diff --git a/system-tests-new/zero_config/test_zero_config.py b/system-tests-new/zero_config/test_zero_config.py index 23d3657c..230e0fc1 100644 --- a/system-tests-new/zero_config/test_zero_config.py +++ b/system-tests-new/zero_config/test_zero_config.py @@ -1,4 +1,6 @@ +import os from pathlib import Path +from unittest import mock from fab.cli import _generic_build_config diff --git a/tests/unit_tests/steps/test_compile_fortran.py b/tests/unit_tests/steps/test_compile_fortran.py index 033eaecf..7c35b6ba 100644 --- a/tests/unit_tests/steps/test_compile_fortran.py +++ b/tests/unit_tests/steps/test_compile_fortran.py @@ -4,11 +4,12 @@ from unittest.mock import call import pytest + from fab.parse.fortran import AnalysedFortran from fab.build_config import BuildConfig from fab.constants import BUILD_TREES, OBJECT_FILES -from fab.steps.compile_fortran import CompileFortran, get_mod_hashes +from fab.steps.compile_fortran import CompileFortran, get_fortran_compiler, get_fortran_preprocessor, get_mod_hashes from fab.util import CompiledFile @@ -457,3 +458,72 @@ def test_vanilla(self): result = get_mod_hashes(analysed_files=analysed_files, config=config) assert result == {'foo': 123, 'bar': 456} + + +class Test_get_fortran_preprocessor(object): + + def test_from_env(self): + with mock.patch.dict(os.environ, values={'FPP': 'foo_pp --foo'}): + fpp, fpp_flags = get_fortran_preprocessor() + + assert fpp == 'foo_pp' + assert fpp_flags == ['--foo', '-P'] + + def test_empty_env_fpp(self): + def mock_run_command(command): + if 'fpp' not in command: + raise RuntimeError('foo') + + with mock.patch.dict(os.environ, values={'FPP': ''}): + with mock.patch('fab.steps.compile_fortran.run_command', side_effect=mock_run_command): + fpp, fpp_flags = get_fortran_preprocessor() + + assert fpp == 'fpp' + assert fpp_flags == ['-P'] + + def test_empty_env_cpp(self): + def mock_run_command(command): + if 'cpp' not in command: + raise RuntimeError('foo') + + with mock.patch.dict(os.environ, values={'FPP': ''}): + with mock.patch('fab.steps.compile_fortran.run_command', side_effect=mock_run_command): + fpp, fpp_flags = get_fortran_preprocessor() + + assert fpp == 'cpp' + assert fpp_flags == ['-traditional-cpp', '-P'] + + +class Test_get_fortran_compiler(object): + + def test_from_env(self): + with mock.patch.dict(os.environ, values={'FC': 'foo_c --foo'}): + fc, fc_flags = get_fortran_compiler() + + assert fc == 'foo_c' + assert fc_flags == ['--foo'] + + def test_empty_env_gfortran(self): + def mock_run_command(command): + if 'gfortran' not in command: + raise RuntimeError('foo') + + with mock.patch.dict(os.environ, values={'FC': ''}): + with mock.patch('fab.steps.compile_fortran.run_command', side_effect=mock_run_command): + fc, fc_flags = get_fortran_compiler() + + assert fc == 'gfortran' + assert fc_flags == [] + + def test_empty_env_ifort(self): + def mock_run_command(command): + if 'ifort' not in command: + raise RuntimeError('foo') + + with mock.patch.dict(os.environ, values={'FC': ''}): + with mock.patch('fab.steps.compile_fortran.run_command', side_effect=mock_run_command): + fc, fc_flags = get_fortran_compiler() + + assert fc == 'ifort' + assert fc_flags == [] + From db04b052de8c702be91a82e8a380fa6ec5f83cfe Mon Sep 17 00:00:00 2001 From: bblay Date: Fri, 10 Feb 2023 16:43:10 +0000 Subject: [PATCH 10/15] flake8 --- system-tests-new/zero_config/test_zero_config.py | 2 -- tests/unit_tests/steps/test_compile_fortran.py | 1 - 2 files changed, 3 deletions(-) diff --git a/system-tests-new/zero_config/test_zero_config.py b/system-tests-new/zero_config/test_zero_config.py index 230e0fc1..23d3657c 100644 --- a/system-tests-new/zero_config/test_zero_config.py +++ b/system-tests-new/zero_config/test_zero_config.py @@ -1,6 +1,4 @@ -import os from pathlib import Path -from unittest import mock from fab.cli import _generic_build_config diff --git a/tests/unit_tests/steps/test_compile_fortran.py b/tests/unit_tests/steps/test_compile_fortran.py index 7c35b6ba..e196ea7f 100644 --- a/tests/unit_tests/steps/test_compile_fortran.py +++ b/tests/unit_tests/steps/test_compile_fortran.py @@ -526,4 +526,3 @@ def mock_run_command(command): assert fc == 'ifort' assert fc_flags == [] - From 3d973c4a1fcb302a8f7b7dd637797924a69045de Mon Sep 17 00:00:00 2001 From: bblay Date: Mon, 13 Feb 2023 16:15:18 +0000 Subject: [PATCH 11/15] fix missing fpp handling using docker without fpp --- envs/docker/Dockerfile | 11 +++++++++-- source/fab/steps/compile_fortran.py | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/envs/docker/Dockerfile b/envs/docker/Dockerfile index 472de4c1..d2051fe0 100644 --- a/envs/docker/Dockerfile +++ b/envs/docker/Dockerfile @@ -1,10 +1,17 @@ +# Usage: +# docker build -t fab . +# docker run --env PYTHONPATH=/fab -v /home/byron/git/fab/source:/fab -v /home/byron:/home/byron -it fab bash + FROM ubuntu:20.04 -RUN apt update && apt install -y gcc gfortran libclang-dev python-clang python3-pip rsync +RUN apt update && apt install -y gcc gfortran libclang-dev python-clang python3-pip rsync git RUN mkdir -p ~/.local/lib/python3.8/site-packages RUN cp -vr /usr/lib/python3/dist-packages/clang ~/.local/lib/python3.8/site-packages/ -RUN pip install flake8 fparser matplotlib mypy pytest sphinx sphinx_rtd_theme +RUN pip install pytest pytest-cov pytest-mock flake8 mypy +RUN pip install sphinx sphinx_rtd_theme sphinx-autodoc-typehints +RUN pip install svn GitPython matplotlib +RUN pip install fparser CMD [ "python3", "--version" ] diff --git a/source/fab/steps/compile_fortran.py b/source/fab/steps/compile_fortran.py index 41c044fc..9eb829f8 100644 --- a/source/fab/steps/compile_fortran.py +++ b/source/fab/steps/compile_fortran.py @@ -357,7 +357,7 @@ def get_fortran_preprocessor(): if not fpp: try: - run_command(['echo', '"#define foo"', '|', 'fpp']) + run_command(['which', 'fpp']) fpp, fpp_flags = 'fpp', ['-P'] logger.info('detected fpp') except RuntimeError: @@ -366,7 +366,7 @@ def get_fortran_preprocessor(): if not fpp: try: - run_command(['echo', '"#define foo"', '|', 'cpp']) + run_command(['which', 'cpp']) fpp, fpp_flags = 'cpp', ['-traditional-cpp', '-P'] logger.info('detected cpp') except RuntimeError: From 631bcedaf8110019e14d8767730787b12eccede0 Mon Sep 17 00:00:00 2001 From: bblay Date: Tue, 14 Feb 2023 11:27:16 +0000 Subject: [PATCH 12/15] fab version --- envs/docker/Dockerfile | 2 +- source/fab/cli.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/envs/docker/Dockerfile b/envs/docker/Dockerfile index d2051fe0..1da416c8 100644 --- a/envs/docker/Dockerfile +++ b/envs/docker/Dockerfile @@ -1,6 +1,6 @@ # Usage: # docker build -t fab . -# docker run --env PYTHONPATH=/fab -v /home/byron/git/fab/source:/fab -v /home/byron:/home/byron -it fab bash +# docker run --env PYTHONPATH=/fab/source -v /home/byron/git/fab:/fab -v /home/byron:/home/byron -it fab bash FROM ubuntu:20.04 diff --git a/source/fab/cli.py b/source/fab/cli.py index 29ef83cf..e5f6c774 100644 --- a/source/fab/cli.py +++ b/source/fab/cli.py @@ -7,6 +7,7 @@ from pathlib import Path from typing import Dict, Optional +import fab from fab.artefacts import CollectionGetter from fab.build_config import BuildConfig from fab.constants import PRAGMAD_C @@ -70,8 +71,13 @@ def cli_fab(): """ arg_parser = ArgumentParser() arg_parser.add_argument('folder', nargs='?', default='.', type=Path) + arg_parser.add_argument('-v', '--version', action='store_true') args = arg_parser.parse_args() + if args.version: + print('Fab', fab.__version__) + exit(0) + config = _generic_build_config(args.folder) config.run() From b4a8cec82238ef5bb8bf10f7e79006e31dabc867 Mon Sep 17 00:00:00 2001 From: bblay Date: Tue, 21 Feb 2023 16:46:29 +0000 Subject: [PATCH 13/15] review actions, thank you --- source/fab/cli.py | 21 ++++++++++--------- source/fab/steps/compile_fortran.py | 2 +- source/fab/steps/preprocess.py | 9 ++++---- .../unit_tests/steps/test_compile_fortran.py | 2 +- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/source/fab/cli.py b/source/fab/cli.py index e5f6c774..43463d73 100644 --- a/source/fab/cli.py +++ b/source/fab/cli.py @@ -33,10 +33,15 @@ def _generic_build_config(folder: Path, kwargs: Optional[Dict] = None) -> BuildC fpp, fpp_flags = get_fortran_preprocessor() fc, fc_flags = get_fortran_compiler() - if fc == 'gfortran': - link_step = LinkExe(linker='gcc', flags=['-lgfortran']) - else: - raise NotImplementedError(f"Fab's zero config not yet configured for compiler: '{fc}'") + # linker and flags depend on compiler + linkers = { + 'gfortran': ('gcc', ['-lgfortran']), + # 'ifort': (..., [...]) + } + try: + linker, linker_flags = linkers[fc] + except KeyError: + raise NotImplementedError(f"Fab's zero configuration mode does not yet work with compiler '{fc}'") config = BuildConfig( project_label=label, @@ -56,7 +61,7 @@ def _generic_build_config(folder: Path, kwargs: Optional[Dict] = None) -> BuildC CompileFortran(compiler=fc, common_flags=fc_flags), CompileC(), - link_step, + LinkExe(linker=linker, flags=linker_flags), ], **kwargs, ) @@ -71,13 +76,9 @@ def cli_fab(): """ arg_parser = ArgumentParser() arg_parser.add_argument('folder', nargs='?', default='.', type=Path) - arg_parser.add_argument('-v', '--version', action='store_true') + arg_parser.add_argument('--version', action='version', version=f'%(prog)s {fab.__version__}') args = arg_parser.parse_args() - if args.version: - print('Fab', fab.__version__) - exit(0) - config = _generic_build_config(args.folder) config.run() diff --git a/source/fab/steps/compile_fortran.py b/source/fab/steps/compile_fortran.py index 9eb829f8..b07114c2 100644 --- a/source/fab/steps/compile_fortran.py +++ b/source/fab/steps/compile_fortran.py @@ -351,7 +351,7 @@ def get_fortran_preprocessor(): try: fpp, fpp_flags = get_tool(os.getenv('FPP')) - logger.info(f'env defined fpp as "{fpp}"') + logger.info(f"The environment defined FPP as '{fpp}'") except ValueError: pass diff --git a/source/fab/steps/preprocess.py b/source/fab/steps/preprocess.py index dc239652..5b791322 100644 --- a/source/fab/steps/preprocess.py +++ b/source/fab/steps/preprocess.py @@ -35,18 +35,19 @@ class PreProcessor(Step): LABEL: str def __init__(self, + preprocessor: str, source: Optional[ArtefactsGetter] = None, output_collection=None, output_suffix=None, - preprocessor: Optional[str] = None, common_flags: Optional[List[str]] = None, + common_flags: Optional[List[str]] = None, path_flags: Optional[List] = None, name=None): """ + :param preprocessor: + The preprocessor executable. :param source: Defines the files to preprocess. Defaults to DEFAULT_SOURCE. :param output_collection: The name of the output artefact collection, defaulting to DEFAULT_OUTPUT_NAME. :param output_suffix: Defaults to DEFAULT_OUTPUT_SUFFIX. - :param preprocessor: - The preprocessor executable. :param common_flags: Used to construct a :class:`~fab.config.FlagsConfig` object. :param path_flags: @@ -60,7 +61,7 @@ def __init__(self, # todo: should we manage known preprocessors like we do compilers, so we can ensure the -P flag is added? # Command line tools are sometimes specified with flags attached, e.g 'cpp -traditional-cpp' - preprocessor_split = (preprocessor or os.getenv('FPP', 'fpp -P')).split() # type: ignore + preprocessor_split = preprocessor.split() self.preprocessor = preprocessor_split[0] common_flags = preprocessor_split[1:] + (common_flags or []) diff --git a/tests/unit_tests/steps/test_compile_fortran.py b/tests/unit_tests/steps/test_compile_fortran.py index e196ea7f..3d02b902 100644 --- a/tests/unit_tests/steps/test_compile_fortran.py +++ b/tests/unit_tests/steps/test_compile_fortran.py @@ -474,7 +474,7 @@ def mock_run_command(command): if 'fpp' not in command: raise RuntimeError('foo') - with mock.patch.dict(os.environ, values={'FPP': ''}): + with mock.patch.dict(os.environ, clear=True): with mock.patch('fab.steps.compile_fortran.run_command', side_effect=mock_run_command): fpp, fpp_flags = get_fortran_preprocessor() From acd10a457acdfd207de0f127570f980a99b36f2d Mon Sep 17 00:00:00 2001 From: bblay Date: Fri, 24 Feb 2023 12:06:18 +0000 Subject: [PATCH 14/15] review actions --- source/fab/steps/compile_fortran.py | 11 +++++++++++ system-tests-new/prebuild/test_prebuild.py | 6 +++++- tests/unit_tests/steps/test_compile_fortran.py | 10 +++++----- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/source/fab/steps/compile_fortran.py b/source/fab/steps/compile_fortran.py index b07114c2..dbf49d5f 100644 --- a/source/fab/steps/compile_fortran.py +++ b/source/fab/steps/compile_fortran.py @@ -347,6 +347,17 @@ def compile_file(self, analysed_file, flags, output_fpath): def get_fortran_preprocessor(): + """ + Identify the fortran preprocessor and any flags from the environment. + + Initially looks for the `FPP` environment variable, then tries to call the `fpp` and `cpp` command line tools. + + Returns the executable and flags. + + The returned flags will always include `-P` to suppress line numbers. + This fparser ticket requests line number handling https://github.com/stfc/fparser/issues/390 . + + """ fpp, fpp_flags = None, None try: diff --git a/system-tests-new/prebuild/test_prebuild.py b/system-tests-new/prebuild/test_prebuild.py index 9fbcc493..b578c362 100644 --- a/system-tests-new/prebuild/test_prebuild.py +++ b/system-tests-new/prebuild/test_prebuild.py @@ -15,10 +15,14 @@ from fab.steps.find_source_files import FindSourceFiles -@mock.patch.dict(os.environ, {'FFLAGS': ''}) +@mock.patch.dict(os.environ) class TestFortranPrebuild(object): def build_config(self, fab_workspace, grab_prebuild_folder=None): + # remove FFLAGS from the *mocked*, i.e copy of, the environment variables + if os.getenv('FFLAGS'): + del os.environ['FFLAGS'] + logging.getLogger('fab').setLevel(logging.WARNING) build_config = BuildConfig( diff --git a/tests/unit_tests/steps/test_compile_fortran.py b/tests/unit_tests/steps/test_compile_fortran.py index 3d02b902..2992e4e3 100644 --- a/tests/unit_tests/steps/test_compile_fortran.py +++ b/tests/unit_tests/steps/test_compile_fortran.py @@ -396,7 +396,7 @@ def test_obj_missing(self): class test_constructor(object): def test_bare(self): - with mock.patch.dict(os.environ, FC='foofc', FFLAGS=''): + with mock.patch.dict(os.environ, FC='foofc', clear=True): cf = CompileFortran() assert cf.compiler == 'foofc' assert cf.flags.common_flags == [] @@ -430,7 +430,7 @@ def test_precedence(self): assert cf.compiler == 'barfc' def test_no_compiler(self): - with mock.patch.dict(os.environ, FC=''): + with mock.patch.dict(os.environ, clear=True): with pytest.raises(ValueError): CompileFortran() @@ -486,7 +486,7 @@ def mock_run_command(command): if 'cpp' not in command: raise RuntimeError('foo') - with mock.patch.dict(os.environ, values={'FPP': ''}): + with mock.patch.dict(os.environ, clear=True): with mock.patch('fab.steps.compile_fortran.run_command', side_effect=mock_run_command): fpp, fpp_flags = get_fortran_preprocessor() @@ -508,7 +508,7 @@ def mock_run_command(command): if 'gfortran' not in command: raise RuntimeError('foo') - with mock.patch.dict(os.environ, values={'FC': ''}): + with mock.patch.dict(os.environ, clear=True): with mock.patch('fab.steps.compile_fortran.run_command', side_effect=mock_run_command): fc, fc_flags = get_fortran_compiler() @@ -520,7 +520,7 @@ def mock_run_command(command): if 'ifort' not in command: raise RuntimeError('foo') - with mock.patch.dict(os.environ, values={'FC': ''}): + with mock.patch.dict(os.environ, clear=True): with mock.patch('fab.steps.compile_fortran.run_command', side_effect=mock_run_command): fc, fc_flags = get_fortran_compiler() From c1ce7b1ce0ff22f2d736d4207db66312ef34a04e Mon Sep 17 00:00:00 2001 From: bblay Date: Fri, 24 Feb 2023 14:12:24 +0000 Subject: [PATCH 15/15] post merge import tidy --- source/fab/parse/fortran.py | 2 +- source/fab/steps/analyse.py | 12 ++---------- tests/unit_tests/steps/test_compile_fortran.py | 2 -- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/source/fab/parse/fortran.py b/source/fab/parse/fortran.py index 1b8bb23c..dc55f017 100644 --- a/source/fab/parse/fortran.py +++ b/source/fab/parse/fortran.py @@ -11,7 +11,6 @@ from pathlib import Path from typing import Union, Optional, Iterable, Dict, Any, Set -from fab.dep_tree import AnalysedDependent from fparser.two.Fortran2003 import ( # type: ignore Use_Stmt, Module_Stmt, Program_Stmt, Subroutine_Stmt, Function_Stmt, Language_Binding_Spec, Char_Literal_Constant, Interface_Block, Name, Comment, Module, Call_Stmt, Derived_Type_Def, Derived_Type_Stmt, @@ -21,6 +20,7 @@ from fparser.two.Fortran2008 import ( # type: ignore Type_Declaration_Stmt, Attr_Spec_List, Entity_Decl_List) +from fab.dep_tree import AnalysedDependent from fab.parse.fortran_common import iter_content, _has_ancestor_type, _typed_child, FortranAnalyserBase from fab.util import file_checksum, string_checksum diff --git a/source/fab/steps/analyse.py b/source/fab/steps/analyse.py index e5b223be..bcf3c46b 100644 --- a/source/fab/steps/analyse.py +++ b/source/fab/steps/analyse.py @@ -40,22 +40,14 @@ from pathlib import Path from typing import Dict, List, Iterable, Set, Optional, Union +from fab import FabException from fab.artefacts import ArtefactsGetter, CollectionConcat, SuffixFilter from fab.constants import BUILD_TREES -from fab.dep_tree import AnalysedDependent, extract_sub_tree, validate_dependencies +from fab.dep_tree import extract_sub_tree, validate_dependencies, AnalysedDependent from fab.mo import add_mo_commented_file_deps from fab.parse import AnalysedFile, EmptySourceFile -from fab.parse.c import CAnalyser -from fab.parse.fortran import FortranParserWorkaround, FortranAnalyser -from fab import FabException - from fab.parse.c import AnalysedC, CAnalyser - from fab.parse.fortran import AnalysedFortran, FortranParserWorkaround, FortranAnalyser - -from fab.constants import BUILD_TREES, CURRENT_PREBUILDS -from fab.dep_tree import extract_sub_tree, validate_dependencies, AnalysedDependent -from fab.mo import add_mo_commented_file_deps from fab.steps import Step from fab.util import TimerLogger, by_type diff --git a/tests/unit_tests/steps/test_compile_fortran.py b/tests/unit_tests/steps/test_compile_fortran.py index 8e81a426..ce487a1c 100644 --- a/tests/unit_tests/steps/test_compile_fortran.py +++ b/tests/unit_tests/steps/test_compile_fortran.py @@ -5,8 +5,6 @@ import pytest -from fab.parse.fortran import AnalysedFortran - from fab.build_config import BuildConfig from fab.constants import BUILD_TREES, OBJECT_FILES from fab.parse.fortran import AnalysedFortran