Skip to content

Commit 5e01424

Browse files
authored
Merge pull request #4697 from lexming/search_path_lib
add option `search-path-linker` to control linker options at build time
2 parents b07033b + 27dd317 commit 5e01424

File tree

6 files changed

+105
-42
lines changed

6 files changed

+105
-42
lines changed

easybuild/tools/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
266266
'rpath_override_dirs',
267267
'required_linked_shared_libs',
268268
'search_path_cpp_headers',
269+
'search_path_linker',
269270
'skip',
270271
'software_commit',
271272
'stop',

easybuild/tools/options.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
from easybuild.tools.run import run_shell_cmd
103103
from easybuild.tools.package.utilities import avail_package_naming_schemes
104104
from easybuild.tools.toolchain.compiler import DEFAULT_OPT_LEVEL, OPTARCH_MAP_CHAR, OPTARCH_SEP, Compiler
105-
from easybuild.tools.toolchain.toolchain import SEARCH_PATH_CPP_HEADERS, DEFAULT_SEARCH_PATH_CPP_HEADERS
105+
from easybuild.tools.toolchain.toolchain import DEFAULT_SEARCH_PATH_CPP_HEADERS, DEFAULT_SEARCH_PATH_LINKER, SEARCH_PATH
106106
from easybuild.tools.toolchain.toolchain import SYSTEM_TOOLCHAIN_NAME
107107
from easybuild.tools.repository.repository import avail_repositories
108108
from easybuild.tools.systemtools import DARWIN, UNKNOWN, check_python_version, get_cpu_architecture, get_cpu_family
@@ -639,7 +639,9 @@ def config_options(self):
639639
"For more info, use --avail-repositories."),
640640
'strlist', 'store', self.default_repositorypath),
641641
'search-path-cpp-headers': ("Search path used at build time for include directories", 'choice',
642-
'store', DEFAULT_SEARCH_PATH_CPP_HEADERS, [*SEARCH_PATH_CPP_HEADERS]),
642+
'store', DEFAULT_SEARCH_PATH_CPP_HEADERS, [*SEARCH_PATH["cpp_headers"]]),
643+
'search-path-linker': ("Search path used at build time by the linker for libraries", 'choice',
644+
'store', DEFAULT_SEARCH_PATH_LINKER, [*SEARCH_PATH["linker"]]),
643645
'sourcepath': ("Path(s) to where sources should be downloaded (string, colon-separated)",
644646
None, 'store', mk_full_default_path('sourcepath')),
645647
'subdir-modules': ("Installpath subdir for modules", None, 'store', DEFAULT_PATH_SUBDIRS['subdir_modules']),

easybuild/tools/toolchain/compiler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class Compiler(Toolchain):
9494
'packed-linker-options': (False, "Pack the linker options as comma separated list"), # ScaLAPACK mainly
9595
'rpath': (True, "Use RPATH wrappers when --rpath is enabled in EasyBuild configuration"),
9696
'search-path-cpp-headers': (None, "Search path used at build time for include directories"),
97+
'search-path-linker': (None, "Search path used at build time by the linker for libraries"),
9798
'extra_cflags': (None, "Specify extra CFLAGS options."),
9899
'extra_cxxflags': (None, "Specify extra CXXFLAGS options."),
99100
'extra_fflags': (None, "Specify extra FFLAGS options."),

easybuild/tools/toolchain/constants.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@
5858
('PRECFLAGS', 'FP precision flags'),
5959
] + COMPILER_FLAGS,
6060
LibraryList: [
61-
('LIBS', 'Libraries'), # TODO: where are these used? ld?
62-
('FLIBS', 'Fortran libraries'), # TODO: where are these used? gfortran only?
61+
('LIBS', 'Libraries'), # -l options to pass to the linker (C/C++/Fortran)
62+
('FLIBS', 'Fortran libraries'), # linker flags (e.g. -L and -l) for Fortran libraries
6363
],
6464
LinkLibraryPaths: [
65-
('LDFLAGS', 'Flags passed to linker'), # TODO: overridden by command line?
65+
('LDFLAGS', 'Linker flags'),
6666
],
6767
IncludePaths: [
6868
('CPPFLAGS', 'Preprocessor flags'),
@@ -72,6 +72,7 @@
7272
('C_INCLUDE_PATH', 'Location of C header files'),
7373
('CPLUS_INCLUDE_PATH', 'Location of C++ header files'),
7474
('OBJC_INCLUDE_PATH', 'Location of Objective C header files'),
75+
('LIBRARY_PATH', 'Location of linker files'),
7576
],
7677
CommandFlagList: COMPILER_VARIABLES,
7778
}

easybuild/tools/toolchain/toolchain.py

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,22 @@
9595
TOOLCHAIN_CAPABILITY_LAPACK_FAMILY,
9696
TOOLCHAIN_CAPABILITY_MPI_FAMILY,
9797
]
98-
# modes to handle CPP header search paths
98+
# modes to handle header and linker search paths
9999
# see: https://gcc.gnu.org/onlinedocs/cpp/Environment-Variables.html
100-
SEARCH_PATH_CPP_HEADERS_FLAGS = "CPPFLAGS"
101-
SEARCH_PATH_CPP_HEADERS_CPATH = "CPATH"
102-
SEARCH_PATH_CPP_HEADERS_INCLUDE = "INCLUDE_PATHS"
103-
SEARCH_PATH_CPP_HEADERS = {
104-
SEARCH_PATH_CPP_HEADERS_FLAGS: ["CPPFLAGS"],
105-
SEARCH_PATH_CPP_HEADERS_CPATH: ["CPATH"],
106-
SEARCH_PATH_CPP_HEADERS_INCLUDE: ["C_INCLUDE_PATH", "CPLUS_INCLUDE_PATH", "OBJC_INCLUDE_PATH"],
100+
# supported on Linux by: GCC, GFortran, oneAPI C/C++ Compilers, oneAPI Fortran Compiler, LLVM-based
101+
SEARCH_PATH = {
102+
"cpp_headers": {
103+
"flags": ["CPPFLAGS"],
104+
"cpath": ["CPATH"],
105+
"include_paths": ["C_INCLUDE_PATH", "CPLUS_INCLUDE_PATH", "OBJC_INCLUDE_PATH"],
106+
},
107+
"linker": {
108+
"flags": ["LDFLAGS"],
109+
"library_path": ["LIBRARY_PATH"],
110+
},
107111
}
108-
DEFAULT_SEARCH_PATH_CPP_HEADERS = SEARCH_PATH_CPP_HEADERS_FLAGS
112+
DEFAULT_SEARCH_PATH_CPP_HEADERS = "flags"
113+
DEFAULT_SEARCH_PATH_LINKER = "flags"
109114

110115

111116
def is_system_toolchain(tc_name):
@@ -225,6 +230,11 @@ def __init__(self, name=None, version=None, mns=None, class_constants=None, tcde
225230

226231
self.use_rpath = False
227232

233+
self.search_path = {
234+
"cpp_headers": DEFAULT_SEARCH_PATH_CPP_HEADERS,
235+
"linker": DEFAULT_SEARCH_PATH_LINKER,
236+
}
237+
228238
self.mns = mns
229239
self.mod_full_name = None
230240
self.mod_short_name = None
@@ -372,9 +382,11 @@ def get_variable(self, name, typ=str):
372382
return res
373383

374384
def set_variables(self):
375-
"""Do nothing? Everything should have been set by others
376-
Needs to be defined for super() relations
377385
"""
386+
No generic toolchain variables set.
387+
Post-process variables set by child Toolchain classes.
388+
"""
389+
378390
if self.options.option('packed-linker-options'):
379391
self.log.devel("set_variables: toolchain variables. packed-linker-options.")
380392
self.variables.try_function_on_element('set_packed_linker_options')
@@ -759,6 +771,27 @@ def _verify_toolchain(self):
759771
raise EasyBuildError("List of toolchain dependency modules and toolchain definition do not match "
760772
"(found %s vs expected %s)", self.toolchain_dep_mods, toolchain_definition)
761773

774+
def _validate_search_path(self):
775+
"""
776+
Validate search path toolchain options.
777+
Toolchain option has precedence over build option
778+
"""
779+
for search_path in self.search_path:
780+
sp_build_opt = f"search_path_{search_path}"
781+
sp_toolchain_opt = sp_build_opt.replace("_", "-")
782+
if self.options.get(sp_toolchain_opt) is not None:
783+
self.search_path[search_path] = self.options.option(sp_toolchain_opt)
784+
elif build_option(sp_build_opt) is not None:
785+
self.search_path[search_path] = build_option(sp_build_opt)
786+
787+
if self.search_path[search_path] not in SEARCH_PATH[search_path]:
788+
raise EasyBuildError(
789+
"Unknown value selected for toolchain option %s: %s. Choose one of: %s",
790+
sp_toolchain_opt, self.search_path[search_path], ", ".join(SEARCH_PATH[search_path])
791+
)
792+
793+
self.log.debug("%s toolchain option set to: %s", sp_toolchain_opt, self.search_path[search_path])
794+
762795
def symlink_commands(self, paths):
763796
"""
764797
Create a symlink for each command to binary/script at specified path.
@@ -838,7 +871,6 @@ def prepare(self, onlymod=None, deps=None, silent=False, loadmod=True,
838871
self._load_modules(silent=silent)
839872

840873
if self.is_system_toolchain():
841-
842874
# define minimal build environment when using system toolchain;
843875
# this is mostly done to try controlling which compiler commands are being used,
844876
# cfr. https://github.com/easybuilders/easybuild-framework/issues/3398
@@ -851,6 +883,7 @@ def prepare(self, onlymod=None, deps=None, silent=False, loadmod=True,
851883
self._verify_toolchain()
852884

853885
# Generate the variables to be set
886+
self._validate_search_path()
854887
self.set_variables()
855888

856889
# set the variables
@@ -1091,24 +1124,7 @@ def _add_dependency_cpp_headers(self, dep_root, extra_dirs=None):
10911124
header_dirs = ["include"]
10921125
header_dirs = unique_ordered_extend(header_dirs, extra_dirs)
10931126

1094-
# mode of operation is defined by search-path-cpp-headers option
1095-
# toolchain option has precedence over build option
1096-
cpp_headers_mode = DEFAULT_SEARCH_PATH_CPP_HEADERS
1097-
build_opt = build_option("search_path_cpp_headers")
1098-
if self.options.get("search-path-cpp-headers") is not None:
1099-
cpp_headers_mode = self.options.option("search-path-cpp-headers")
1100-
self.log.debug("search-path-cpp-headers set by toolchain option: %s", cpp_headers_mode)
1101-
elif build_opt is not None:
1102-
cpp_headers_mode = build_opt
1103-
self.log.debug("search-path-cpp-headers set by build option: %s", cpp_headers_mode)
1104-
1105-
if cpp_headers_mode not in SEARCH_PATH_CPP_HEADERS:
1106-
raise EasyBuildError(
1107-
"Unknown value selected for option search-path-cpp-headers: %s. Choose one of: %s",
1108-
cpp_headers_mode, ", ".join(SEARCH_PATH_CPP_HEADERS)
1109-
)
1110-
1111-
for env_var in SEARCH_PATH_CPP_HEADERS[cpp_headers_mode]:
1127+
for env_var in SEARCH_PATH["cpp_headers"][self.search_path["cpp_headers"]]:
11121128
self.log.debug("Adding header paths to toolchain variable '%s': %s", env_var, dep_root)
11131129
self.variables.append_subdirs(env_var, dep_root, subdirs=header_dirs)
11141130

@@ -1122,9 +1138,9 @@ def _add_dependency_linker_paths(self, dep_root, extra_dirs=None):
11221138
lib_dirs = ["lib64", "lib"]
11231139
lib_dirs = unique_ordered_extend(lib_dirs, extra_dirs)
11241140

1125-
env_var = "LDFLAGS"
1126-
self.log.debug("Adding lib paths to toolchain variable '%s': %s", env_var, dep_root)
1127-
self.variables.append_subdirs(env_var, dep_root, subdirs=lib_dirs)
1141+
for env_var in SEARCH_PATH["linker"][self.search_path["linker"]]:
1142+
self.log.debug("Adding lib paths to toolchain variable '%s': %s", env_var, dep_root)
1143+
self.variables.append_subdirs(env_var, dep_root, subdirs=lib_dirs)
11281144

11291145
def _setenv_variables(self, donotset=None, verbose=True):
11301146
"""Actually set the environment variables"""

test/framework/toolchain.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -958,9 +958,9 @@ def test_precision_flags(self):
958958
def test_search_path_cpp_headers(self):
959959
"""Test functionality behind search-path-cpp-headers option"""
960960
cpp_headers_mode = {
961-
"CPPFLAGS": ["CPPFLAGS"],
962-
"CPATH": ["CPATH"],
963-
"INCLUDE_PATHS": ["C_INCLUDE_PATH", "CPLUS_INCLUDE_PATH", "OBJC_INCLUDE_PATH"],
961+
"flags": ["CPPFLAGS"],
962+
"cpath": ["CPATH"],
963+
"include_paths": ["C_INCLUDE_PATH", "CPLUS_INCLUDE_PATH", "OBJC_INCLUDE_PATH"],
964964
}
965965
# test without toolchain option
966966
for build_opt in cpp_headers_mode:
@@ -994,7 +994,49 @@ def test_search_path_cpp_headers(self):
994994
tc = self.get_toolchain("foss", version="2018a")
995995
tc.set_options({"search-path-cpp-headers": "WRONG_MODE"})
996996
with self.mocked_stdout_stderr():
997-
error_pattern = "Unknown value selected for option search-path-cpp-headers"
997+
error_pattern = "Unknown value selected for toolchain option search-path-cpp-headers"
998+
self.assertErrorRegex(EasyBuildError, error_pattern, tc.prepare)
999+
self.modtool.purge()
1000+
1001+
def test_search_path_linker(self):
1002+
"""Test functionality behind search-path-linker option"""
1003+
linker_mode = {
1004+
"flags": ["LDFLAGS"],
1005+
"library_path": ["LIBRARY_PATH"],
1006+
}
1007+
# test without toolchain option
1008+
for build_opt in linker_mode:
1009+
init_config(build_options={"search_path_linker": build_opt, "silent": True})
1010+
tc = self.get_toolchain("foss", version="2018a")
1011+
with self.mocked_stdout_stderr():
1012+
tc.prepare()
1013+
for env_var in linker_mode[build_opt]:
1014+
assert_fail_msg = (
1015+
f"Variable {env_var} required by search-path-linker build option '{build_opt}' "
1016+
"not found in toolchain environment"
1017+
)
1018+
self.assertIn(env_var, tc.variables, assert_fail_msg)
1019+
self.modtool.purge()
1020+
# test with toolchain option
1021+
for build_opt in linker_mode:
1022+
init_config(build_options={"search_path_linker": build_opt, "silent": True})
1023+
for tc_opt in linker_mode:
1024+
tc = self.get_toolchain("foss", version="2018a")
1025+
tc.set_options({"search-path-linker": tc_opt})
1026+
with self.mocked_stdout_stderr():
1027+
tc.prepare()
1028+
for env_var in linker_mode[tc_opt]:
1029+
assert_fail_msg = (
1030+
f"Variable {env_var} required by search-path-linker toolchain option '{tc_opt}' "
1031+
"not found in toolchain environment"
1032+
)
1033+
self.assertIn(env_var, tc.variables, assert_fail_msg)
1034+
self.modtool.purge()
1035+
# test wrong toolchain option
1036+
tc = self.get_toolchain("foss", version="2018a")
1037+
tc.set_options({"search-path-linker": "WRONG_MODE"})
1038+
with self.mocked_stdout_stderr():
1039+
error_pattern = "Unknown value selected for toolchain option search-path-linker"
9981040
self.assertErrorRegex(EasyBuildError, error_pattern, tc.prepare)
9991041
self.modtool.purge()
10001042

0 commit comments

Comments
 (0)