Skip to content

Commit

Permalink
Try making linker a CompilerSuiteTool instead of a CompilerWrapper.
Browse files Browse the repository at this point in the history
  • Loading branch information
hiker committed Dec 2, 2024
1 parent d06c9ce commit 39a8204
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 42 deletions.
115 changes: 79 additions & 36 deletions source/fab/tools/linker.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,58 @@
"""This file contains the base class for any Linker.
"""

from __future__ import annotations

import os
from pathlib import Path
from typing import cast, Dict, List, Optional, Union
import warnings

from fab.tools.category import Category
from fab.tools.compiler import Compiler
from fab.tools.compiler_wrapper import CompilerWrapper
from fab.tools.tool import CompilerSuiteTool


class Linker(CompilerWrapper):
'''This is the base class for any Linker.
class Linker(CompilerSuiteTool):
'''This is the base class for any Linker. It takes either another linker
instance, or a compiler instance as parameter in the constructor. Exactly
one of these must be provided.
:param compiler: a compiler or linker instance
:param compiler: an optional compiler instance
:param linker: an optional linker instance
:param name: name of the linker
:param output_flag: flag to use to specify the output name.
:raises RuntimeError: if both compiler and linker are specified.
:raises RuntimeError: if neither compiler nor linker is specified.
'''

def __init__(self, compiler: Compiler, name: Optional[str] = None,
output_flag: Optional[str] = None):
def __init__(self, compiler: Optional[Compiler] = None,
linker: Optional[Linker] = None,
name: Optional[str] = None):

if linker and compiler:
raise RuntimeError("Both compiler and linker is specified in "
"linker constructor.")
if not linker and not compiler:
raise RuntimeError("Neither compiler nor linker is specified in "
"linker constructor.")
self._compiler = compiler
self._linker = linker

search_linker = self
while search_linker._linker:
search_linker = search_linker._linker
final_compiler = search_linker._compiler
if not name:
assert final_compiler
name = f"linker-{final_compiler.name}"

super().__init__(
name=name or f"linker-{compiler.name}",
exec_name=compiler.exec_name,
compiler=compiler,
category=Category.LINKER,
mpi=compiler.mpi)
name=name or f"linker-{name}",
exec_name=self.exec_name,
suite=self.suite,
category=Category.LINKER)

self._output_flag = output_flag or ""
self.add_flags(os.getenv("LDFLAGS", "").split())

# Maintain a set of flags for common libraries.
Expand All @@ -44,16 +67,35 @@ def __init__(self, compiler: Compiler, name: Optional[str] = None,
self._pre_lib_flags: List[str] = []
self._post_lib_flags: List[str] = []

def get_output_flag(self) -> str:
def check_available(self):
return (self._compiler or self._linker).check_available()

@property
def exec_name(self) -> str:
if self._compiler:
return self._compiler.exec_name
assert self._linker # make mypy happy
return self._linker.exec_name

@property
def suite(self) -> str:
return cast(CompilerSuiteTool, (self._compiler or self._linker)).suite

@property
def mpi(self) -> bool:
if self._compiler:
return self._compiler.mpi
assert self._linker
return self._linker.mpi

@property
def output_flag(self) -> str:
''':returns: the flag that is used to specify the output name.
'''
if self._output_flag:
return self._output_flag
if not self.compiler.category == Category.LINKER:
return self.compiler.output_flag

linker = cast(Linker, self.compiler)
return linker.get_output_flag()
if self._compiler:
return self._compiler.output_flag
assert self._linker
return self._linker.output_flag

def get_lib_flags(self, lib: str) -> List[str]:
'''Gets the standard flags for a standard library
Expand All @@ -69,9 +111,8 @@ def get_lib_flags(self, lib: str) -> List[str]:
except KeyError:
# If a lib is not defined here, but this is a wrapper around
# another linker, return the result from the wrapped linker
if self.compiler.category is Category.LINKER:
linker = cast(Linker, self.compiler)
return linker.get_lib_flags(lib)
if self._linker:
return self._linker.get_lib_flags(lib)
raise RuntimeError(f"Unknown library name: '{lib}'")

def add_lib_flags(self, lib: str, flags: List[str],
Expand Down Expand Up @@ -127,16 +168,15 @@ def get_pre_link_flags(self) -> List[str]:
wrapped linkers
'''
params: List[str] = []

print("gplf", self._pre_lib_flags, self, self._linker)
if self._pre_lib_flags:
params.extend(self._pre_lib_flags)
if self.compiler.category == Category.LINKER:
if self._linker:
# If we are wrapping a linker, get the wrapped linker's
# pre-link flags and append them to the end (so the linker
# wrapper's settings come before the setting from the
# wrapped linker).
linker = cast(Linker, self.compiler)
params.extend(linker.get_pre_link_flags())
params.extend(self._linker.get_pre_link_flags())
return params

def link(self, input_files: List[Path], output_file: Path,
Expand All @@ -155,14 +195,17 @@ def link(self, input_files: List[Path], output_file: Path,

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

if openmp:
# Find the compiler by following the (potentially
# layered) linker wrapper.
compiler = self.compiler
while compiler.category == Category.LINKER:
# Make mypy happy
compiler = cast(Linker, compiler).compiler
# Find the compiler by following the (potentially
# layered) linker wrapper.
linker = self
while linker._linker:
linker = linker._linker
# Now we must have a compiler
compiler = linker._compiler
assert compiler
params.extend(compiler.flags)

if openmp:
params.append(compiler.openmp_flag)

# TODO: why are the .o files sorted? That shouldn't matter
Expand All @@ -174,6 +217,6 @@ def link(self, input_files: List[Path], output_file: Path,

if self._post_lib_flags:
params.extend(self._post_lib_flags)
params.extend([self.get_output_flag(), str(output_file)])
params.extend([self.output_flag, str(output_file)])

return self.run(params)
2 changes: 1 addition & 1 deletion source/fab/tools/tool_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def add_tool(self, tool: Tool):
category=Category.LINKER,
name=f"linker-{compiler.compiler.name}")
other_linker = cast(Linker, other_linker)
linker = Linker(compiler=other_linker,
linker = Linker(linker=other_linker,
name=f"linker-{compiler.name}")
self[linker.category].append(linker)
else:
Expand Down
2 changes: 1 addition & 1 deletion tests/unit_tests/steps/test_link.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def test_run(self, tool_box, mock_fortran_compiler):

with mock.patch.dict("os.environ", {"FFLAGS": "-L/foo1/lib -L/foo2/lib"}):
# We need to create a linker here to pick up the env var:
linker = Linker(mock_fortran_compiler)
linker = Linker(compiler=mock_fortran_compiler)
# Mark the linker as available to it can be added to the tool box
linker._is_available = True

Expand Down
8 changes: 4 additions & 4 deletions tests/unit_tests/tools/test_linker.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ def test_linker(mock_c_compiler, mock_fortran_compiler):
assert mock_c_compiler.category == Category.C_COMPILER
assert mock_c_compiler.name == "mock_c_compiler"

linker = Linker(mock_c_compiler, output_flag="-o")
linker = Linker(mock_c_compiler)
assert linker.category == Category.LINKER
assert linker.name == "linker-mock_c_compiler"
assert linker.exec_name == "mock_c_compiler.exe"
assert linker.suite == "suite"
assert linker.flags == []
assert linker.get_output_flag() == "-o"
assert linker.output_flag == "-o"

assert mock_fortran_compiler.category == Category.FORTRAN_COMPILER
assert mock_fortran_compiler.name == "mock_fortran_compiler"
Expand Down Expand Up @@ -277,8 +277,8 @@ def test_linker_all_flag_types(mock_c_compiler):

tool_run.assert_called_with([
"mock_c_compiler.exe",
"-compiler-flag1", "-compiler-flag2", "-fflag",
"-ldflag", "-linker-flag1", "-linker-flag2",
"-compiler-flag1", "-compiler-flag2",
"-fopenmp",
"a.o",
"-prelibflag1", "-prelibflag2",
Expand All @@ -297,7 +297,7 @@ def test_linker_nesting(mock_c_compiler):
linker1.add_pre_lib_flags(["pre_lib1"])
linker1.add_lib_flags("lib_a", ["a_from_1"])
linker1.add_lib_flags("lib_c", ["c_from_1"])
linker2 = Linker(compiler=linker1)
linker2 = Linker(linker=linker1)
linker2.add_pre_lib_flags(["pre_lib2"])
linker2.add_lib_flags("lib_b", ["b_from_2"])
linker2.add_lib_flags("lib_c", ["c_from_2"])
Expand Down

0 comments on commit 39a8204

Please sign in to comment.