Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linker flags for common libraries #19

Merged
merged 22 commits into from
Sep 24, 2024
Merged

Linker flags for common libraries #19

merged 22 commits into from
Sep 24, 2024

Conversation

lukehoffmann
Copy link
Collaborator

@lukehoffmann lukehoffmann commented Aug 22, 2024

Add support for libraries in the Linker class. As per issue #7:

In order to better support different paths on different sites:

  • The linker base class should provide a dictionary that maps strings (which are 'standard' library names) to a list of linker flags. E.g.
    'xios' -> ["-L", "/bla/bla", "-l", "morebla"] 
    'netcdf' -> ['$(nf-config --flibs)', '($nc-config --libs)`]`,
    
  • An application than only specifies which libraries it needs to link with (and in what order), and the linker then adds the correct flags during the linking stage.
  • If a library is specified that is unknown, raise an error.
  • There must be function to add and remove libraries (and Fab as default would likely provide none? Or Maybe just say netcdf as an example, since this is reasonable portable). Site-specific scripts will want to modify the settings (e.g.

@@ -65,7 +65,7 @@ def link_exe(config, flags=None, source: Optional[ArtefactsGetter] = None):
target_objects = source_getter(config.artefact_store)
for root, objects in target_objects.items():
exe_path = config.project_workspace / f'{root}'
linker.link(objects, exe_path, openmp=config.openmp, add_libs=flags)
linker.link(objects, exe_path, openmp=config.openmp, add_flags=flags)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I include support for libs up at this level?

Copy link
Owner

@hiker hiker Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes please. Keep the flags parameter, while I can't think of a real good use case, you never know if an app might want/need to provide additional flags (hmm - maybe ask for linker mapping file or so ... there are enough linker options :) )

@lukehoffmann
Copy link
Collaborator Author

@hiker please let me know if I've misunderstood what you had in mind, or if not can you review please?

@lukehoffmann lukehoffmann added enhancement New feature or request Ready for review labels Aug 22, 2024
@hiker
Copy link
Owner

hiker commented Aug 27, 2024

Looks perfect. Bring it up to the link_exe (and link_shared_object), and this is ready to go.

except KeyError:
raise RuntimeError(f"Unknown library name: '{lib}'")

def add_lib_flags(self, lib: str, flags: List[str]):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm - I just remembered: during the code review of the tool box, I was asked to add a silent_replace option: if there is already an entry defined that is overwritten, do a warnings.warn(...) to indicate that library settings get replaced (but if there is no entry, just add it as well).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't sure if you meant both the warning and the silent option, but I've added both since it's fairly simple

@hiker
Copy link
Owner

hiker commented Sep 5, 2024

I've merged bom_masters into this and resolved the conflicts. I detected one test failure only triggered when the tests were executed in a certain order (in steps/archive) - I switched this test to use mock_c_compiler (which is always available), instead of the default in the tool box (which somehow changed when things were executed in a certain oder).

I also noticed that linesn66 and 147 are not covered by a test in test_linker:

~/work/fab/tests/unit_tests/tools$ pytest --cov-report term-missing --cov fab.tools.linker ./test_linker.py 
/home/joerg/.local/lib/python3.10/site-packages/pep8.py:110: FutureWarning: Possible nested set at position 1
  EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]')
============================================================================== test session starts ===============================================================================
platform linux -- Python 3.10.12, pytest-7.4.4, pluggy-1.0.0
rootdir: /home/joerg/work/fab
configfile: pyproject.toml
plugins: pylint-0.21.0, pep8-1.0.6, flakes-4.0.5, xdist-3.2.1, anyio-3.6.2, cov-4.0.0
collected 15 items                                                                                                                                                               

test_linker.py ...............                                                                                                                                             [100%]

---------- coverage: platform linux, python 3.10.12-final-0 ----------
Name                                              Stmts   Miss  Cover   Missing
-------------------------------------------------------------------------------
/home/joerg/work/fab/source/fab/tools/linker.py      59      2    97%   66, 147
-------------------------------------------------------------------------------
TOTAL                                                59      2    97%

I don't think this is an official requirement (line 66 is covered if we run all tests in unit_tests/tools, and line 147 somewhere from unit_tests/steps), but imho this is quite useful.

Now, line 66 is actually my fault I think (unless it was a side-effect of conflicts when merging??), and looking at it, this code is actually wrong (it relies on linker._compiler to exist, which atm does not have to be the case ... though de-facto it is). With #20 this will actually be fixed, so ... just ignore line 66 for now, but covering 147 looks useful

@hiker
Copy link
Owner

hiker commented Sep 5, 2024

I think we need two extensions:

  1. We need to have a list of flags that are always added before the library names (use case: all my libraries come from spack, but for whatever reasons I need to add their path using -L ... the only option to do this atm is to add this to every library, since I can't know in which order the future applications will add the library names). For this case, I can just add the paths once, and it will always be added.
  2. Similarly, it might be good to have a list of flags to be added after the libraries. Use case: when linking xios with Fortran, you need to link explicitly with the stdc++ libraries. Since there could be more than one library that is based on c++, and a linker will link a library only once, being able to always specify this at the end is very useful

@lukehoffmann
Copy link
Collaborator Author

lukehoffmann commented Sep 11, 2024

@hiker

  1. We need to have a list of flags that are always added before the library names
  2. Similarly, it might be good to have a list of flags to be added after the libraries

Currently the libs part of the link() function call.

Do you want to also put these pre/post flags into the link() function call, e.g.

linker.link(..., prelib_flags=[..], libs=[..], postlib_flags[..], ...)

Or should the be properties of the linker,

linker.prelib_flags = [ ... ]
linker.postlib_flags = [ ... ]
...
linker.link(..., libs=[..], ...)

Or do you want both options?

Currently we can technically do both with

  • linker.flags - always goes before libs (but also before the source files)
  • the add_flags argument - these flags always go after the libs flags

But the first is configured on the linker object, while the second is passed into link().

@lukehoffmann
Copy link
Collaborator Author

I decided to go with the option of adding both arguments to the link function. So these will be in addition to linker.flags. See what you think.

@hiker
Copy link
Owner

hiker commented Sep 12, 2024

I decided to go with the option of adding both arguments to the link function. So these will be in addition to linker.flags. See what you think.

I admit I can't decide, and am happy with either (as long as these flags are all optional, since typically you won't need them). The only thing I did notice: flags that go before the source code (linker.flags) seems pretty useless. Any 'generic' linker flag can go anywhere (e.g. to ask for a symbol map file or so, or adding a search path), but any library must come after the .o files.

@lukehoffmann
Copy link
Collaborator Author

lukehoffmann commented Sep 12, 2024

Yeah the linker.flags is just built-in behaviour of the Tool baseclass, which adds them in the run method. We will probably not use them, but I thought I'd better add a test for them since they exist

# Include netcdf as an example, since it is reasonable portable
self.add_lib_flags(
'netcdf',
['$(nf-config --flibs)', '($nc-config --libs)']
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake, but the second entry should be $(nc-config...) (i.e. switch $ and (.
But even worse: this actually does not work, because atm we don't use a shell when starting a process, we only fork ... and as a result the (shell) $(...) is not executed.
It's easy enough to add that flat (and instead of a list of parameters, we supply a string), but I better check that with Matthew. It might break something subtly (because parameters that contain a string now need quoting ... not that I think we have any spaces anywhere :) ).
So for now, just remove the whole setting of netcdf. I will add this using a proper shell script in the site-specific/default config. and discuss this with Matthew.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner

@hiker hiker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Back to you due to the misunderstanding of where to add the pre/post flag.

def test_linker_add_lib_flags(mock_c_compiler):
"""Linker should provide a way to add a new set of flags for a library"""
linker = Linker(compiler=mock_c_compiler)
linker.add_lib_flags("xios", ["-L", "xios/lib", "-I", "xios/inc"])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit picking, but -I is not required for linking (it's the include path for compilation). Instead use "-l", "xios" together with -L...).

linker = Linker(compiler=mock_c_compiler)
# netcdf is built in to the linker, since it is reasonable portable
result = linker.get_lib_flags("netcdf")
assert result == ["$(nf-config --flibs)", "($nc-config --libs)"]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will affect quite a few tests, so I'll mention it only here: As mentioned above, remove netcdf lib. Instead modify the mock_linker in conftest.py to add some libraries there.

# Replace them with another set of flags.
warn_message = 'Replacing existing flags for library netcdf'
with pytest.warns(UserWarning, match=warn_message):
linker.add_lib_flags("netcdf", ["-L", "netcdf/lib", "-I", "netcdf/inc"])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, replace with -l netcdf or so

def link(self, input_files: List[Path], output_file: Path,
openmp: bool,
add_libs: Optional[List[str]] = None) -> str:
pre_lib_flags: Optional[List[str]] = None,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we had a misunderstanding :( I want to have the pre- and post-flags for the linker object, not the link step (since the link step ideally should be as independent from the linker and site used as possible, we now would have to specify site- and linker/compiler-specific paths here.

I'll add more details to the actual ticket #7 (it's just more convenient to write there :) )

@lukehoffmann
Copy link
Collaborator Author

No problem, as I said I was weighing up both options, and just picked one option to implement for you to review. I've switched to configure the additional flags on the Linker itself now.

@hiker hiker added under review The review is in progress and removed Ready for review labels Sep 23, 2024
@hiker
Copy link
Owner

hiker commented Sep 23, 2024

That looks good now, thanks. I'll port our lfric build to see if any other issue pops up :)

Copy link
Owner

@hiker hiker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks nice, and works perfectly with our LFRic_atm-fab builds.

@hiker hiker merged commit 282f068 into bom_master Sep 24, 2024
8 checks passed
@hiker hiker deleted the linker-lib-flags branch September 24, 2024 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request under review The review is in progress
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants