Skip to content

Commit 3adffd3

Browse files
hikerlukehoffmannjasonjunweilyu
authored
More linker wrapper improvements (#383)
* Support new and old style of PSyclone command line (no more nemo api etc) * Fix mypy errors. * Added missing tests for calling psyclone, and converting old style to new stle arguments and vice versa. * Updated comment. * Removed mixing, use a simple regex instead. * Added support for ifx/icx compiler as intel-llvm class. * Added support for nvidia compiler. * Add preliminary support for Cray compiler. * Added Cray compiler wrapper ftn and cc. * Follow a more consistent naming scheme for crays, even though the native compiler names are longer (crayftn-cray, craycc-cray). * Changed names again. * Renamed cray compiler wrapper to be CrayCcWrapper and CrayFtnWrapper, to avoid confusion with Craycc. * Fixed incorrect name in comments. * Additional compilers (#349) * Moved OBJECT_ARCHIVES from constants to ArtefactSet. * Moved PRAGMAD_C from constants to ArtefactSet. * Turned 'all_source' into an enum. * Allow integer as revision. * Fixed flake8 error. * Removed specific functions to add/get fortran source files etc. * Removed non-existing and unneccessary collections. * Try to fix all run_configs. * Fixed rebase issues. * Added replace functionality to ArtefactStore, updated test_artefacts to cover all lines in that file. * Started to replace artefacts when files are pre-processed. * Removed linker argument from linking step in all examples. * Try to get jules to link. * Fixed build_jules. * Fixed other issues raised in reviews. * Try to get jules to link. * Fixed other issues raised in reviews. * Simplify handling of X90 files by replacing the X90 with x90, meaning only one artefact set is involved when running PSyclone. * Make OBJECT_ARCHIVES also a dict, migrate more code to replace/add files to the default build artefact collections. * Fixed some examples. * Fix flake8 error. * Fixed failing tests. * Support empty comments. * Fix preprocessor to not unnecessary remove and add files that are already in the output directory. * Allow find_soure_files to be called more than once by adding files (not replacing artefact). * Updated lfric_common so that files created by configurator are written in build (not source). * Use c_build_files instead of pragmad_c. * Removed unnecessary str. * Documented the new artefact set handling. * Fixed typo. * Make the PSyclone API configurable. * Fixed formatting of documentation, properly used ArtefactSet names. * Support .f and .F Fortran files. * Removed setter for tool.is_available, which was only used for testing. * #3 Fix documentation and coding style issues from review. * Renamed Categories into Category. * Minor coding style cleanup. * Removed more unnecessary (). * Re-added (invalid) grab_pre_build call. * Fixed typo. * Renamed set_default_vendor to set_default_compiler_suite. * Renamed VendorTool to CompilerSuiteTool. * Also accept a Path as exec_name specification for a tool. * Move the check_available function into the base class. * Fixed some types and documentation. * Fix typing error. * Added explanation for meta-compiler. * Improved error handling and documentation. * Replace mpiifort with mpifort to be a tiny bit more portable. * Use classes to group tests for git/svn/fcm together. * Fixed issue in get_transformation script, and moved script into lfric_common to remove code duplication. * Code improvement as suggested by review. * Fixed run config * Added reference to ticket. * Updated type information. * More typing fixes. * Fixed typing warnings. * As requested by reviewer removed is_working_copy functionality. * Issue a warning (which can be silenced) when a tool in a toolbox is replaced. * Fixed flake8. * Fixed flake8. * Fixed failing test. * Addressed issues raised in review. * Removed now unnecessary operations. * Updated some type information. * Fixed all references to APIs to be consistent with PSyclone 2.5. * Added api to the checksum computation. * Fixed type information. * Added test to verify that changing the api changes the checksum. * Make compiler version a tuple of integers * Update some tests to use tuple versions * Explicitly test handling of bad version format * Fix formatting * Tidying up * Make compiler raise an error for any invalid version string Assume these compilers don't need to be hashed. Saves dealing with empty tuples. * Check compiler version string for compiler name * Fix formatting * Add compiler.get_version_string() method Includes other cleanup from PR comments * Add mpi and openmp settings to BuildConfig, made compiler MPI aware. * Looks like the circular dependency has been fixed. * Revert "Looks like the circular dependency has been fixed." ... while it works with the tests, a real application still triggered it. This reverts commit 150dc37. * Don't even try to find a C compiler if no C files are to be compiled. * Updated gitignore to ignore (recently renamed) documentation. * Fixed failing test. * Return from compile Fortran early if there are no files to compiles. Fixed coding style. * Add MPI enables wrapper for intel and gnu compiler. * Fixed test. * Automatically add openmp flag to compiler and linker based on BuildConfig. * Removed enforcement of keyword parameters, which is not supported in python 3.7. * Fixed failing test. * Support more than one tool of a given suite by sorting them. * Use different version checkout for each compiler vendor with mixins * Refactoring, remove unittest compiler class * Fix some mypy errors * Use 'Union' type hint to fix build checks * Added option to add flags to a tool. * Introduce proper compiler wrapper, used this to implement properly wrapper MPI compiler. * Fixed typo in types. * Return run_version_command to base Compiler class Provides default version command that can be overridden for other compilers. Also fix some incorrect tests Other tidying * Add a missing type hint * Added (somewhat stupid) 'test' to reach 100% coverage of PSyclone tool. * Simplified MPI support in wrapper. * More compiler wrapper coverage. * Removed duplicated function. * Removed debug print. * Removed permanently changing compiler attributes, which can cause test failures later. * More test for C compiler wrapper. * More work on compiler wrapper tests. * Fixed version and availability handling, added missing tests for 100% coverage. * Fixed typing error. * Try to fix python 3.7. * Tried to fix failing tests. * Remove inheritance from mixins and use protocol * Simplify compiler inheritance Mixins have static methods with unique names, overrides only happen in concrete classes * Updated wrapper and tests to handle error raised in get_version. * Simplified regular expressions (now tests cover detection of version numbers with only a major version). * Test for missing mixin. * Use the parsing mixing from the compiler in a compiler wrapper. * Use setattr instead of assignment to make mypy happy. * Simplify usage of compiler-specific parsing mixins. * Minor code cleanup. * Updated documentation. * Simplify usage of compiler-specific parsing mixins. * Test for missing mixin. * Fixed test. * Added missing openmp_flag property to compiler wrapper. * Don't use isinstance for consistency check, which does not work for CompilerWrappers. * Fixed isinstance test for C compilation which doesn't work with a CompilerWrapper. * Use a linker's compiler to determine MPI support. Removed mpi property from CompilerSuite. * Added more tests for invalid version numbers. * Added more test cases for invalid version number, improved regex to work as expected. * Fixed typo in test. * Fixed flake/mypy errors. * Combine wrapper flags with flags from wrapped compiler. * Made mypy happy. * Fixed test. * Split tests into smaller individual ones, fixed missing asssert in test. * Parameterised compiler version tests to also test wrapper. * Added missing MPI parameter when getting the compiler. * Fixed comments. * Order parameters to be in same order for various compiler classes. * Remove stray character * Added getter for wrapped compiler. * Fixed small error that would prevent nested compiler wrappers from being used. * Added a cast to make mypy happy. * Add simple getter for linker library flags * Add getter for linker flags by library * Fix formatting * Add optional libs argument to link function * Reorder and clean up linker tests * Make sure `Linker.link()` raises for unknown lib * Add missing type * Fix typing error * Add 'libs' argument to link_exe function * Try to add documentation for the linker libs feature * Use correct list type in link_exe hint * Add silent replace option to linker.add_lib_flags * Fixed spelling mistake in option. * Clarified documentation. * Removed unnecessary functions in CompilerWrapper. * Fixed failing test triggered by executing them in specific order (tools then steps) * Fixed line lengths. * Add tests for linker LDFLAG * Add pre- and post- lib flags to link function * Fix syntax in built-in lib flags * Remove netcdf as a built-in linker library Bash-style substitution is not currently handled * Configure pre- and post-lib flags on the Linker object Previously they were passed into the Linker.link() function * Use more realistic linker lib flags * Formatting fix * Removed mixing, use a simple regex instead. * Added support for ifx/icx compiler as intel-llvm class. * Added support for nvidia compiler. * Add preliminary support for Cray compiler. * Added Cray compiler wrapper ftn and cc. * Made mpi and openmp default to False in the BuildConfig constructor. * Removed white space. * Follow a more consistent naming scheme for crays, even though the native compiler names are longer (crayftn-cray, craycc-cray). * Changed names again. * Support compilers that do not support OpenMP. * Added documentation for openmp parameter. * Renamed cray compiler wrapper to be CrayCcWrapper and CrayFtnWrapper, to avoid confusion with Craycc. * Fixed incorrect name in comments. --------- Co-authored-by: jasonjunweilyu <[email protected]> Co-authored-by: Luke Hoffmann <[email protected]> Co-authored-by: Luke Hoffmann <[email protected]> * Support new and old style of PSyclone command line (no more nemo api etc) * Fix mypy errors. * Added missing tests for calling psyclone, and converting old style to new stle arguments and vice versa. * Added shell tool. * Try to make mypy happy. * Removed debug code. * ToolRepository now only returns default that are available. Updated tests to make tools as available. * Fixed typos and coding style. * Support new and old style of PSyclone command line (no more nemo api etc) * Fix mypy errors. * Added missing tests for calling psyclone, and converting old style to new stle arguments and vice versa. * Updated comment. * Fixed failing tests. * Updated fparser dependency to version 0.2. * Replace old code for handling sentinels with triggering this behaviour in fparser. Require config in constructor of Analyser classes. * Fixed tests for latest changes. * Removed invalid openmp continuation line - since now fparser fails when trying to parse this line. * Added test for disabled openmp parsing. Updated test to work with new test file. * Coding style changes. * Fix flake issues. * Fixed double _. * Make Linker inherit CompilerWrapper * Fix up tests for new Linker inheritence * Fix a flake error * Use linker wrapping to combine flags from the wrapped linker with the linker wrapper. * Minor code cleanup. * Created linker wrapper in ToolRepository. * Try making linker a CompilerSuiteTool instead of a CompilerWrapper. * Updated tests. * Fix support for post-libs. * Fixed mypy. * Removed more accesses to private members. * Added missing type hint. * Make flake8 happy. * Added missing openmp handling in linker. * Addressed issues raised in review. * Forgot that file in previous commit. * Updated linker to always require a compiler (previously it was actually never checked if the wrapped compiler actually exists, it only checked the linker). * Remove unreliable test, since there is no guarantee that a C linker is returned, see issue #379. * Fixed bug that a wrapper would not report openmp status based on the compiler. * Added more description for this test. --------- Co-authored-by: Luke Hoffmann <[email protected]> Co-authored-by: jasonjunweilyu <[email protected]> Co-authored-by: Luke Hoffmann <[email protected]>
1 parent c585ff7 commit 3adffd3

File tree

6 files changed

+53
-96
lines changed

6 files changed

+53
-96
lines changed

Diff for: source/fab/tools/compiler.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ def mpi(self) -> bool:
7676
def openmp(self) -> bool:
7777
''':returns: if the compiler supports openmp or not
7878
'''
79-
return self._openmp_flag != ""
79+
# It is important not to use `_openmp_flag` directly, since a compiler
80+
# wrapper overwrites `openmp_flag`.
81+
return self.openmp_flag != ""
8082

8183
@property
8284
def openmp_flag(self) -> str:

Diff for: source/fab/tools/linker.py

+24-64
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import os
1313
from pathlib import Path
14-
from typing import cast, Dict, List, Optional, Union
14+
from typing import Dict, List, Optional, Union
1515
import warnings
1616

1717
from fab.tools.category import Category
@@ -20,47 +20,35 @@
2020

2121

2222
class Linker(CompilerSuiteTool):
23-
'''This is the base class for any Linker. It takes either another linker
24-
instance, or a compiler instance as parameter in the constructor. Exactly
25-
one of these must be provided.
26-
27-
:param compiler: an optional compiler instance
23+
'''This is the base class for any Linker. It takes an existing compiler
24+
instance as parameter, and optional another linker. The latter is used
25+
to get linker settings - for example, linker-mpif90-gfortran will use
26+
mpif90-gfortran as compiler (i.e. to test if it is available and get
27+
compilation flags), and linker-gfortran as linker. This way a user
28+
only has to specify linker flags in the most basic class (gfortran),
29+
all other linker wrapper will inherit the settings.
30+
31+
:param compiler: a compiler instance
2832
:param linker: an optional linker instance
2933
:param name: name of the linker
3034
3135
:raises RuntimeError: if both compiler and linker are specified.
3236
:raises RuntimeError: if neither compiler nor linker is specified.
3337
'''
3438

35-
def __init__(self, compiler: Optional[Compiler] = None,
39+
def __init__(self, compiler: Compiler,
3640
linker: Optional[Linker] = None,
37-
exec_name: Optional[str] = None,
3841
name: Optional[str] = None):
3942

40-
if linker and compiler:
41-
raise RuntimeError("Both compiler and linker is specified in "
42-
"linker constructor.")
43-
if not linker and not compiler:
44-
raise RuntimeError("Neither compiler nor linker is specified in "
45-
"linker constructor.")
4643
self._compiler = compiler
4744
self._linker = linker
4845

49-
search_linker = self
50-
while search_linker._linker:
51-
search_linker = search_linker._linker
52-
final_compiler = search_linker._compiler
5346
if not name:
54-
assert final_compiler # make mypy happy
55-
name = f"linker-{final_compiler.name}"
56-
57-
if not exec_name:
58-
# This will search for the name in linker or compiler
59-
exec_name = self.get_exec_name()
47+
name = f"linker-{compiler.name}"
6048

6149
super().__init__(
6250
name=name,
63-
exec_name=exec_name,
51+
exec_name=compiler.exec_name,
6452
suite=self.suite,
6553
category=Category.LINKER)
6654

@@ -76,51 +64,31 @@ def check_available(self) -> bool:
7664
''':returns: whether this linker is available by asking the wrapped
7765
linker or compiler.
7866
'''
79-
if self._compiler:
80-
return self._compiler.check_available()
81-
assert self._linker # make mypy happy
82-
return self._linker.check_available()
83-
84-
def get_exec_name(self) -> str:
85-
''':returns: the name of the executable by asking the wrapped
86-
linker or compiler.'''
87-
if self._compiler:
88-
return self._compiler.exec_name
89-
assert self._linker # make mypy happy
90-
return self._linker.exec_name
67+
return self._compiler.check_available()
9168

9269
@property
9370
def suite(self) -> str:
9471
''':returns: the suite this linker belongs to by getting it from
95-
the wrapper compiler or linker.'''
96-
return cast(CompilerSuiteTool, (self._compiler or self._linker)).suite
72+
the wrapped compiler.'''
73+
return self._compiler.suite
9774

9875
@property
9976
def mpi(self) -> bool:
10077
''':returns" whether this linker supports MPI or not by checking
101-
with the wrapper compiler or linker.'''
102-
if self._compiler:
103-
return self._compiler.mpi
104-
assert self._linker # make mypy happy
105-
return self._linker.mpi
78+
with the wrapped compiler.'''
79+
return self._compiler.mpi
10680

10781
@property
10882
def openmp(self) -> bool:
109-
''':returns" whether this linker supports OpenMP or not by checking
110-
with the wrapper compiler or linker.'''
111-
if self._compiler:
112-
return self._compiler.openmp
113-
assert self._linker # make mypy happy
114-
return self._linker.openmp
83+
''':returns: whether this linker supports OpenMP or not by checking
84+
with the wrapped compiler.'''
85+
return self._compiler.openmp
11586

11687
@property
11788
def output_flag(self) -> str:
11889
''':returns: the flag that is used to specify the output name.
11990
'''
120-
if self._compiler:
121-
return self._compiler.output_flag
122-
assert self._linker # make mypy happy
123-
return self._linker.output_flag
91+
return self._compiler.output_flag
12492

12593
def get_lib_flags(self, lib: str) -> List[str]:
12694
'''Gets the standard flags for a standard library
@@ -238,18 +206,10 @@ def link(self, input_files: List[Path], output_file: Path,
238206

239207
params: List[Union[str, Path]] = []
240208

241-
# Find the compiler by following the (potentially
242-
# layered) linker wrapper.
243-
linker = self
244-
while linker._linker:
245-
linker = linker._linker
246-
# Now we must have a compiler
247-
compiler = linker._compiler
248-
assert compiler # make mypy happy
249-
params.extend(compiler.flags)
209+
params.extend(self._compiler.flags)
250210

251211
if openmp:
252-
params.append(compiler.openmp_flag)
212+
params.append(self._compiler.openmp_flag)
253213

254214
# TODO: why are the .o files sorted? That shouldn't matter
255215
params.extend(sorted(map(str, input_files)))

Diff for: source/fab/tools/tool_repository.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -117,19 +117,19 @@ def add_tool(self, tool: Tool):
117117
compiler = cast(Compiler, tool)
118118
if isinstance(compiler, CompilerWrapper):
119119
# If we have a compiler wrapper, create a new linker using
120-
# the linker based on the wrappped compiler. For example, when
120+
# the linker based on the wrapped compiler. For example, when
121121
# creating linker-mpif90-gfortran, we want this to be based on
122-
# linker-gfortran (and not on the compiler mpif90-gfortran),
123-
# since the linker-gfortran might have library definitions
124-
# that should be reused. So we first get the existing linker
125-
# (since the compiler exists, a linker for this compiler was
126-
# already created and must exist).
122+
# linker-gfortran. The compiler mpif90-gfortran will be the
123+
# wrapper compiler. Reason is that e.g. linker-gfortran might
124+
# have library definitions that should be reused. So we first
125+
# get the existing linker (since the compiler exists, a linker
126+
# for this compiler was already created and must exist).
127127
other_linker = self.get_tool(
128128
category=Category.LINKER,
129129
name=f"linker-{compiler.compiler.name}")
130130
other_linker = cast(Linker, other_linker)
131-
linker = Linker(linker=other_linker,
132-
exec_name=compiler.exec_name,
131+
linker = Linker(compiler,
132+
linker=other_linker,
133133
name=f"linker-{compiler.name}")
134134
self[linker.category].append(linker)
135135
else:

Diff for: tests/unit_tests/tools/test_compiler_wrapper.py

+12
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,18 @@ def test_compiler_wrapper_flags_independent():
257257
assert mpicc.flags == ["-a", "-b"]
258258
assert mpicc.openmp_flag == gcc.openmp_flag
259259

260+
# Test a compiler wrapper correctly queries the wrapper compiler for
261+
# openmp flag: Set the wrapper to have no _openmp_flag (which is
262+
# actually the default, since the wrapper never sets its own flag), but
263+
# gcc does have a flag, so mpicc should report that is supports openmp.
264+
# mpicc.openmp calls openmp of its base class (Compiler), which queries
265+
# if an openmp flag is defined. This query must go to the openmp property,
266+
# since the wrapper overwrites this property to return the wrapped
267+
# compiler's flag (and not the wrapper's flag, which would not be defined)
268+
with mock.patch.object(mpicc, "_openmp_flag", ""):
269+
assert mpicc._openmp_flag == ""
270+
assert mpicc.openmp
271+
260272
# Adding flags to the wrapper should not affect the wrapped compiler:
261273
mpicc.add_flags(["-d", "-e"])
262274
assert gcc.flags == ["-a", "-b"]

Diff for: tests/unit_tests/tools/test_linker.py

+5-18
Original file line numberDiff line numberDiff line change
@@ -41,28 +41,15 @@ def test_linker(mock_c_compiler, mock_fortran_compiler):
4141
assert linker.flags == []
4242

4343

44-
def test_linker_constructor_error(mock_c_compiler):
45-
'''Test the linker constructor with invalid parameters.'''
46-
47-
with pytest.raises(RuntimeError) as err:
48-
Linker()
49-
assert ("Neither compiler nor linker is specified in linker constructor."
50-
in str(err.value))
51-
with pytest.raises(RuntimeError) as err:
52-
Linker(compiler=mock_c_compiler, linker=mock_c_compiler)
53-
assert ("Both compiler and linker is specified in linker constructor."
54-
in str(err.value))
55-
56-
5744
@pytest.mark.parametrize("mpi", [True, False])
5845
def test_linker_mpi(mock_c_compiler, mpi):
5946
'''Test that linker wrappers handle MPI as expected.'''
6047

6148
mock_c_compiler._mpi = mpi
62-
linker = Linker(compiler=mock_c_compiler)
49+
linker = Linker(mock_c_compiler)
6350
assert linker.mpi == mpi
6451

65-
wrapped_linker = Linker(linker=linker)
52+
wrapped_linker = Linker(mock_c_compiler, linker=linker)
6653
assert wrapped_linker.mpi == mpi
6754

6855

@@ -80,7 +67,7 @@ def test_linker_openmp(mock_c_compiler, openmp):
8067
linker = Linker(compiler=mock_c_compiler)
8168
assert linker.openmp == openmp
8269

83-
wrapped_linker = Linker(linker=linker)
70+
wrapped_linker = Linker(mock_c_compiler, linker=linker)
8471
assert wrapped_linker.openmp == openmp
8572

8673

@@ -103,7 +90,7 @@ def test_linker_check_available(mock_c_compiler):
10390

10491
# Then test the usage of a linker wrapper. The linker will call the
10592
# corresponding function in the wrapper linker:
106-
wrapped_linker = Linker(linker=linker)
93+
wrapped_linker = Linker(mock_c_compiler, linker=linker)
10794
with mock.patch('fab.tools.compiler.Compiler.get_version',
10895
return_value=(1, 2, 3)):
10996
assert wrapped_linker.check_available()
@@ -342,7 +329,7 @@ def test_linker_nesting(mock_c_compiler):
342329
linker1.add_lib_flags("lib_a", ["a_from_1"])
343330
linker1.add_lib_flags("lib_c", ["c_from_1"])
344331
linker1.add_post_lib_flags(["post_lib1"])
345-
linker2 = Linker(linker=linker1)
332+
linker2 = Linker(mock_c_compiler, linker=linker1)
346333
linker2.add_pre_lib_flags(["pre_lib2"])
347334
linker2.add_lib_flags("lib_b", ["b_from_2"])
348335
linker2.add_lib_flags("lib_c", ["c_from_2"])

Diff for: tests/unit_tests/tools/test_tool_repository.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import pytest
1212

1313
from fab.tools import (Ar, Category, FortranCompiler, Gcc, Gfortran, Ifort,
14-
Linker, ToolRepository)
14+
ToolRepository)
1515

1616

1717
def test_tool_repository_get_singleton_new():
@@ -62,10 +62,6 @@ def test_tool_repository_get_default():
6262
openmp=False)
6363
assert isinstance(gfortran, Gfortran)
6464

65-
gcc_linker = tr.get_default(Category.LINKER, mpi=False, openmp=False)
66-
assert isinstance(gcc_linker, Linker)
67-
assert gcc_linker.name == "linker-gcc"
68-
6965
gcc = tr.get_default(Category.C_COMPILER, mpi=False, openmp=False)
7066
assert isinstance(gcc, Gcc)
7167

0 commit comments

Comments
 (0)