Skip to content

Commit

Permalink
#3 Replaced ar with tool object.
Browse files Browse the repository at this point in the history
  • Loading branch information
hiker committed May 10, 2024
1 parent 87bce71 commit 672bb9c
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 27 deletions.
4 changes: 3 additions & 1 deletion source/fab/newtools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
'''A simple init file to make it shorter to import tools.
'''

from fab.newtools.ar import Ar
from fab.newtools.categories import Categories
from fab.newtools.compiler import (CCompiler, Compiler, FortranCompiler, Gcc,
Gfortran, Icc, Ifort)
Expand All @@ -19,7 +20,8 @@
from fab.newtools.tool_box import ToolBox
from fab.newtools.versioning import Fcm, Git, Subversion, Versioning

__all__ = ["Categories",
__all__ = ["Ar",
"Categories",
"CCompiler",
"Compiler",
"Cpp",
Expand Down
46 changes: 46 additions & 0 deletions source/fab/newtools/ar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
##############################################################################
# (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
##############################################################################

"""This file contains the base class for any preprocessor, and two derived
classes for cpp and fpp.
"""

from pathlib import Path
from typing import List, Union

from fab.newtools.categories import Categories
from fab.newtools.tool import Tool


class Ar(Tool):
'''This is the base class for `ar`.
'''

def __init__(self):
super().__init__("ar", "ar", Categories.AR)

def check_available(self):
'''Checks if the compiler is available. We do this by requesting the
compiler version.
'''
try:
self.run("--version")
except (RuntimeError, FileNotFoundError):
return False
return True

def create(self, output_fpath: Path,
members: List[Union[Path, str]]):
'''Create the archive with the specified name, containing the
listed members.
:param output_fpath: the output path.
:param members: the list of objects to be added to the archive.
'''
print("XX", type(members), type(map(str, members)))
parameters = ["cr", str(output_fpath)]
parameters.extend(map(str, members))
return self.run(additional_parameters=parameters)
1 change: 1 addition & 0 deletions source/fab/newtools/categories.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Categories(Enum):
FCM = auto()
GIT = auto()
SUBVERSION = auto()
AR = auto()

def __str__(self):
'''Simplify the str output by using only the name (e.g. `C_COMPILER`
Expand Down
4 changes: 2 additions & 2 deletions source/fab/newtools/tool_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import logging
from typing import Any, Type

from fab.newtools import (Categories, Cpp, CppFortran, Gcc, Gfortran,
from fab.newtools import (Ar, Categories, Cpp, CppFortran, Gcc, Gfortran,
Icc, Ifort, Linker)
from fab.newtools.versioning import Fcm, Git, Subversion

Expand Down Expand Up @@ -56,7 +56,7 @@ def __init__(self):
# TODO: sort the defaults so that they actually work (since not all
# tools FAB knows about are available). For now, disable Fpp:
for cls in [Gcc, Icc, Gfortran, Ifort, Cpp, CppFortran,
Fcm, Git, Subversion]:
Fcm, Git, Subversion, Ar]:
self.add_tool(cls)

def add_tool(self, cls: Type[Any]):
Expand Down
24 changes: 11 additions & 13 deletions source/fab/steps/archive_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from fab.constants import OBJECT_FILES, OBJECT_ARCHIVES
from fab.steps import step
from fab.util import log_or_dot
from fab.tools import run_command
from fab.newtools import Categories
from fab.artefacts import ArtefactsGetter, CollectionGetter

logger = logging.getLogger(__name__)
Expand All @@ -30,8 +30,10 @@
# todo: all this documentation for such a simple step - should we split it up somehow?

@step
def archive_objects(config: BuildConfig, source: Optional[ArtefactsGetter] = None, archiver='ar',
output_fpath=None, output_collection=OBJECT_ARCHIVES):
def archive_objects(config: BuildConfig,
source: Optional[ArtefactsGetter] = None,
output_fpath=None,
output_collection=OBJECT_ARCHIVES):
"""
Create an object archive for every build target, from their object files.
Expand Down Expand Up @@ -91,9 +93,8 @@ def archive_objects(config: BuildConfig, source: Optional[ArtefactsGetter] = Non
# todo: the output path should not be an abs fpath, it should be relative to the proj folder

source_getter = source or DEFAULT_SOURCE_GETTER
archiver = archiver
ar = config.tool_box[Categories.AR]
output_fpath = str(output_fpath) if output_fpath else None
output_collection = output_collection

target_objects = source_getter(config.artefact_store)
assert target_objects.keys()
Expand All @@ -114,14 +115,11 @@ def archive_objects(config: BuildConfig, source: Optional[ArtefactsGetter] = Non
output_fpath = Template(str(output_fpath)).substitute(
output=config.build_output)

command = [archiver]
command.extend(['cr', output_fpath])
command.extend(map(str, sorted(objects)))

log_or_dot(logger, 'CreateObjectArchive running command: ' + ' '.join(command))
log_or_dot(logger, f"CreateObjectArchive running archiver for "
f"'{output_fpath}'.")
try:
run_command(command)
except Exception as err:
raise Exception(f"error creating object archive:\n{err}")
ar.create(output_fpath, sorted(objects))
except RuntimeError as err:
raise RuntimeError(f"error creating object archive:\n{err}") from err

output_archives[root] = [output_fpath]
44 changes: 33 additions & 11 deletions tests/unit_tests/steps/test_archive_objects.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
##############################################################################
# (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
##############################################################################
"""
Test for the archive step.
"""

from unittest import mock
from unittest.mock import call

Expand All @@ -9,22 +18,31 @@
import pytest


class Test_archive_objects(object):
class TestArchiveObjects():
'''Test the achive step.
'''

def test_for_exes(self):
# as used when archiving before linking exes
'''As used when archiving before linking exes.
'''
targets = ['prog1', 'prog2']

config = BuildConfig('proj', ToolBox())
config._artefact_store = {OBJECT_FILES: {target: [f'{target}.o', 'util.o'] for target in targets}}
config._artefact_store = {OBJECT_FILES: {target: [f'{target}.o', 'util.o']
for target in targets}}

with mock.patch('fab.steps.archive_objects.run_command') as mock_run_command, \
pytest.warns(UserWarning, match="_metric_send_conn not set, cannot send metrics"):
mock_result = mock.Mock(returncode=0, return_value=123)
with mock.patch('fab.newtools.tool.subprocess.run',
return_value=mock_result) as mock_run_command, \
pytest.warns(UserWarning, match="_metric_send_conn not set, "
"cannot send metrics"):
archive_objects(config=config)

# ensure the correct command line calls were made
expected_calls = [
call(['ar', 'cr', str(config.build_output / f'{target}.a'), f'{target}.o', 'util.o'])
call(['ar', 'cr', str(config.build_output / f'{target}.a'),
f'{target}.o', 'util.o'],
capture_output=True, env=None, cwd=None, check=False)
for target in targets
]
mock_run_command.assert_has_calls(expected_calls)
Expand All @@ -34,19 +52,23 @@ def test_for_exes(self):
target: [str(config.build_output / f'{target}.a')] for target in targets}

def test_for_library(self):
# as used when building an object archive or archiving before linking a shared library
pass
'''As used when building an object archive or archiving before linking
a shared library.
'''

config = BuildConfig('proj', ToolBox())
config._artefact_store = {OBJECT_FILES: {None: ['util1.o', 'util2.o']}}

with mock.patch('fab.steps.archive_objects.run_command') as mock_run_command, \
pytest.warns(UserWarning, match="_metric_send_conn not set, cannot send metrics"):
mock_result = mock.Mock(returncode=0, return_value=123)
with mock.patch('fab.newtools.tool.subprocess.run',
return_value=mock_result) as mock_run_command, \
pytest.warns(UserWarning, match="_metric_send_conn not set, cannot send metrics"):
archive_objects(config=config, output_fpath=config.build_output / 'mylib.a')

# ensure the correct command line calls were made
mock_run_command.assert_called_once_with([
'ar', 'cr', str(config.build_output / 'mylib.a'), 'util1.o', 'util2.o'])
'ar', 'cr', str(config.build_output / 'mylib.a'), 'util1.o', 'util2.o'],
capture_output=True, env=None, cwd=None, check=False)

# ensure the correct artefacts were created
assert config.artefact_store[OBJECT_ARCHIVES] == {
Expand Down

0 comments on commit 672bb9c

Please sign in to comment.