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

Create artefact store #285

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f7ad442
#278 Use absolute path for source file when compiling Fortran.
hiker Feb 29, 2024
378b47f
Fixed errors in the psyclone system tests - Issue #4
Mar 1, 2024
6b8f73b
Also don't change directory so we can be sure all paths are absolute.
hiker Mar 2, 2024
071d1a2
Minor documentation update for #277.
hiker Mar 2, 2024
06b0da0
Remove useless statement, the same value was computed earlier.
hiker Mar 2, 2024
ce0890f
Introduced an ArtefactStore objects and getter in build_config.
hiker Mar 2, 2024
14a3483
Avoid circular import.
hiker Mar 2, 2024
1e16da4
Updated documentation.
hiker Mar 2, 2024
c7dc9d3
hiker#5 Test warnings are either filtered or changed to be alerted wh…
Mar 11, 2024
34853c4
hiker#5, hiker#4: Renamed duplicated filenames in test subdirectories…
Mar 11, 2024
d0fb208
Merge pull request #6 from hiker/small_documentation_update
jasonjunweilyu Mar 12, 2024
14d0e59
Merge pull request #7 from hiker/278_absolute_paths_in_compilation
jasonjunweilyu Mar 12, 2024
7791733
hiker#4: Improved the test error fix to be easier to understand
Mar 12, 2024
63d2ddc
Merge pull request #9 from hiker/fix_test_error
hiker Mar 12, 2024
ea7b813
Removed absolute path patch, since this breaks a test.
hiker Mar 12, 2024
d4b5d8a
Fixed failing tests.
hiker Mar 12, 2024
b8a45c3
Resolve merge conflict with branch fix_test_error by incorporating bo…
Mar 13, 2024
2268191
Fix styling problems reported by Flake8
Mar 13, 2024
2cfcb1a
Fix "_metric_send_conn not set, cannot send metrics" warning not issu…
Mar 13, 2024
e080c44
Merge pull request #10 from hiker/fix_test_warning
hiker Mar 14, 2024
ab9245c
Merge branch 'bom_master' into create_artefact_store
hiker Mar 14, 2024
6b290c2
Remove cosmetic change done to follow other coding standards.
hiker Mar 14, 2024
71e1b4a
Remove more cosmetic changes (which pylint required).
hiker Mar 14, 2024
ea16694
More removal of cosmetic changes, and some useless code.
hiker Mar 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions docs/source/advanced_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,20 @@ import your grab configuration to find out where it put the source.

if __name__ == '__main__':
with BuildConfig(project_label='<project_label>') as state:
grab_folder(state, src=grab_config.source_root),
grab_folder(state, src=my_grab_config.source_root),


Housekeeping
============

Fab will remove old files from the prebuilds folder. It will remove all prebuild files that are not part of the current build by default.

If you add a :func:`~fab.steps.cleanup_prebuilds.cleanup_prebuilds` step, you
can keep prebuild files for longer. This may be useful, for example, if you
often switch between two versions of your code and want to keep the prebuild
speed benefits when building both.
You can add a :func:`~fab.steps.cleanup_prebuilds.cleanup_prebuilds`
step, where you can explicitly control how long to keep prebuild files.
This may be useful, for example, if you often switch between two versions
of your code and want to keep the prebuild speed benefits when building
both. If you do not add your own cleanup_prebuild step, Fab will
automatically run a default step which will remove old files from the
prebuilds folder. It will remove all prebuild files that are not part of
the current build by default.


Sharing Prebuilds
Expand Down Expand Up @@ -311,7 +313,7 @@ which most Fab steps accept. (See :ref:`Overriding default collections`)

@step
def custom_step(state):
state._artefact_store['custom_artefacts'] = do_something(state._artefact_store['step 1 artefacts'])
state.artefact_store['custom_artefacts'] = do_something(state.artefact_store['step 1 artefacts'])


with BuildConfig(project_label='<project label>') as state:
Expand All @@ -328,7 +330,7 @@ Steps have access to multiprocessing methods through the

@step
def custom_step(state):
input_files = artefact_store['custom_artefacts']
input_files = state.artefact_store['custom_artefacts']
results = run_mp(state, items=input_files, func=do_something)


Expand Down
4 changes: 2 additions & 2 deletions docs/source/environment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
Environment
***********

Fab requires a suitible Python environment in which to run. This page outlines
Fab requires a suitable Python environment in which to run. This page outlines
some routes to achieving such an environment.

This page contains general instructions, there are additional instructions for
:ref:`Met Office<MetOffice>` users elsewhere.
:ref:`Met Office<MetOfficeUsage>` users elsewhere.


.. _Requirements:
Expand Down
4 changes: 2 additions & 2 deletions docs/source/writing_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,14 @@ However preprocessing C currently requires a preceding step called the
into the C code so Fab is able to deduce which inclusions are user code and
which are system code. This allows system dependencies to be ignored.

See also :ref:`Advanced C Code<Advanced C Code>`
See also :ref:`Advanced C Code<C Pragma Injector>`


Further Reading
===============

More advanced configuration topics are discussed in
:ref:`Advanced Configuration`.
:ref:`Advanced Config`.

You can see more complicated configurations in the
`developer testing directory <https://github.com/metomi/fab/tree/master/run_configs>`_.
18 changes: 16 additions & 2 deletions source/fab/artefacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from pathlib import Path
from typing import Iterable, Union, Dict, List

from fab.constants import BUILD_TREES
from fab.constants import BUILD_TREES, CURRENT_PREBUILDS
from fab.dep_tree import filter_source_tree, AnalysedDependent
from fab.util import suffix_filter

Expand All @@ -32,7 +32,6 @@ def __call__(self, artefact_store):
The artefact store from which to retrieve.

"""
pass


class CollectionGetter(ArtefactsGetter):
Expand Down Expand Up @@ -158,3 +157,18 @@ def __call__(self, artefact_store):
build_lists[root] = filter_source_tree(source_tree=tree, suffixes=self.suffixes)

return build_lists


class ArtefactStore(dict):
'''This object stores artefacts (which can be of any type). Each artefact
is index by a string.
'''
def __init__(self):
super().__init__()
self.reset()

def reset(self):
'''Clears the artefact store (but does not delete any files).
'''
self.clear()
self[CURRENT_PREBUILDS] = set()
39 changes: 22 additions & 17 deletions source/fab/build_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@
from multiprocessing import cpu_count
from pathlib import Path
from string import Template
from typing import List, Optional, Dict, Any, Iterable
from typing import List, Optional, Iterable

from fab.artefacts import ArtefactStore
from fab.constants import BUILD_OUTPUT, SOURCE_ROOT, PREBUILD, CURRENT_PREBUILDS
from fab.metrics import send_metric, init_metrics, stop_metrics, metrics_summary
from fab.steps.cleanup_prebuilds import CLEANUP_COUNT, cleanup_prebuilds
from fab.util import TimerLogger, by_type, get_fab_workspace

logger = logging.getLogger(__name__)


class BuildConfig(object):
class BuildConfig():
"""
Contains and runs a list of build steps.

Expand Down Expand Up @@ -105,9 +107,10 @@ def __init__(self, project_label: str, multiprocessing: bool = True, n_procs: Op

# todo: should probably pull the artefact store out of the config
# runtime
# todo: either make this public, add get/setters, or extract into a class.
self._artefact_store: Dict[str, Any] = {}
self.init_artefact_store() # note: the artefact store is reset with every call to run()
self._artefact_store: ArtefactStore = ArtefactStore()

self._build_timer = None
self._start_time = None

def __enter__(self):

Expand All @@ -130,8 +133,7 @@ def __enter__(self):
def __exit__(self, exc_type, exc_val, exc_tb):

if not exc_type: # None if there's no error.
from fab.steps.cleanup_prebuilds import CLEANUP_COUNT, cleanup_prebuilds
if CLEANUP_COUNT not in self._artefact_store:
if CLEANUP_COUNT not in self.artefact_store:
logger.info("no housekeeping step was run, using a default hard cleanup")
cleanup_prebuilds(config=self, all_unused=True)

Expand All @@ -142,19 +144,23 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self._finalise_logging()

@property
def build_output(self):
return self.project_workspace / BUILD_OUTPUT
def artefact_store(self) -> ArtefactStore:
''':returns: the Artefact instance for this configuration.
'''
return self._artefact_store

def init_artefact_store(self):
# there's no point writing to this from a child process of Step.run_mp() because you'll be modifying a copy.
self._artefact_store = {CURRENT_PREBUILDS: set()}
@property
def build_output(self) -> Path:
''':returns: the build output path.
'''
return self.project_workspace / BUILD_OUTPUT

def add_current_prebuilds(self, artefacts: Iterable[Path]):
"""
Mark the given file paths as being current prebuilds, not to be cleaned during housekeeping.

"""
self._artefact_store[CURRENT_PREBUILDS].update(artefacts)
self.artefact_store[CURRENT_PREBUILDS].update(artefacts)

def _run_prep(self):
self._init_logging()
Expand All @@ -168,7 +174,7 @@ def _run_prep(self):
init_metrics(metrics_folder=self.metrics_folder)

# note: initialising here gives a new set of artefacts each run
self.init_artefact_store()
self.artefact_store.reset()

def _prep_folders(self):
self.source_root.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -210,7 +216,7 @@ def _finalise_metrics(self, start_time, steps_timer):


# todo: better name? perhaps PathFlags?
class AddFlags(object):
class AddFlags():
"""
Add command-line flags when our path filter matches.
Generally used inside a :class:`~fab.build_config.FlagsConfig`.
Expand Down Expand Up @@ -265,14 +271,13 @@ def run(self, fpath: Path, input_flags: List[str], config):
input_flags += add_flags


class FlagsConfig(object):
class FlagsConfig():
"""
Return command-line flags for a given path.

Simply allows appending flags but may evolve to also replace and remove flags.

"""

def __init__(self, common_flags: Optional[List[str]] = None, path_flags: Optional[List[AddFlags]] = None):
"""
:param common_flags:
Expand Down
2 changes: 0 additions & 2 deletions source/fab/parse/fortran_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,6 @@ def run(self, fpath: Path) \

# find things in the node tree
analysed_file = self.walk_nodes(fpath=fpath, file_hash=file_hash, node_tree=node_tree)

analysis_fpath = self._get_analysis_fpath(fpath, file_hash)
analysed_file.save(analysis_fpath)

return analysed_file, analysis_fpath
Expand Down
4 changes: 2 additions & 2 deletions source/fab/steps/analyse.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def analyse(
c_analyser._config = config

# parse
files: List[Path] = source_getter(config._artefact_store)
files: List[Path] = source_getter(config.artefact_store)
analysed_files = _parse_files(config, files=files, fortran_analyser=fortran_analyser, c_analyser=c_analyser)
_add_manual_results(special_measure_analysis_results, analysed_files)

Expand Down Expand Up @@ -206,7 +206,7 @@ def analyse(
_add_unreferenced_deps(unreferenced_deps, symbol_table, project_source_tree, build_tree)
validate_dependencies(build_tree)

config._artefact_store[BUILD_TREES] = build_trees
config.artefact_store[BUILD_TREES] = build_trees


def _analyse_dependencies(analysed_files: Iterable[AnalysedDependent]):
Expand Down
4 changes: 2 additions & 2 deletions source/fab/steps/archive_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@ def archive_objects(config: BuildConfig, source: Optional[ArtefactsGetter] = Non
output_fpath = str(output_fpath) if output_fpath else None
output_collection = output_collection

target_objects = source_getter(config._artefact_store)
target_objects = source_getter(config.artefact_store)
assert target_objects.keys()
if output_fpath and list(target_objects.keys()) != [None]:
raise ValueError("You must not specify an output path (library) when there are root symbols (exes)")
if not output_fpath and list(target_objects.keys()) == [None]:
raise ValueError("You must specify an output path when building a library.")

output_archives = config._artefact_store.setdefault(output_collection, {})
output_archives = config.artefact_store.setdefault(output_collection, {})
for root, objects in target_objects.items():

if root:
Expand Down
4 changes: 2 additions & 2 deletions source/fab/steps/c_pragma_injector.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ def c_pragma_injector(config, source: Optional[ArtefactsGetter] = None, output_n
source_getter = source or DEFAULT_SOURCE_GETTER
output_name = output_name or PRAGMAD_C

files = source_getter(config._artefact_store)
files = source_getter(config.artefact_store)
results = run_mp(config, items=files, func=_process_artefact)
config._artefact_store[output_name] = list(results)
config.artefact_store[output_name] = list(results)


def _process_artefact(fpath: Path):
Expand Down
8 changes: 4 additions & 4 deletions source/fab/steps/cleanup_prebuilds.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,23 @@ def cleanup_prebuilds(

elif all_unused:
num_removed = remove_all_unused(
found_files=prebuild_files, current_files=config._artefact_store[CURRENT_PREBUILDS])
found_files=prebuild_files, current_files=config.artefact_store[CURRENT_PREBUILDS])

else:
# get the file access time for every artefact
prebuilds_ts = \
dict(zip(prebuild_files, run_mp(config, prebuild_files, get_access_time))) # type: ignore

# work out what to delete
to_delete = by_age(older_than, prebuilds_ts, current_files=config._artefact_store[CURRENT_PREBUILDS])
to_delete |= by_version_age(n_versions, prebuilds_ts, current_files=config._artefact_store[CURRENT_PREBUILDS])
to_delete = by_age(older_than, prebuilds_ts, current_files=config.artefact_store[CURRENT_PREBUILDS])
to_delete |= by_version_age(n_versions, prebuilds_ts, current_files=config.artefact_store[CURRENT_PREBUILDS])

# delete them all
run_mp(config, to_delete, os.remove)
num_removed = len(to_delete)

logger.info(f'removed {num_removed} prebuild files')
config._artefact_store[CLEANUP_COUNT] = num_removed
config.artefact_store[CLEANUP_COUNT] = num_removed


def by_age(older_than: Optional[timedelta],
Expand Down
4 changes: 2 additions & 2 deletions source/fab/steps/compile_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def compile_c(config, common_flags: Optional[List[str]] = None,
source_getter = source or DEFAULT_SOURCE_GETTER

# gather all the source to compile, for all build trees, into one big lump
build_lists: Dict = source_getter(config._artefact_store)
build_lists: Dict = source_getter(config.artefact_store)
to_compile: list = sum(build_lists.values(), [])
logger.info(f"compiling {len(to_compile)} c files")

Expand All @@ -101,7 +101,7 @@ def compile_c(config, common_flags: Optional[List[str]] = None,
config.add_current_prebuilds(prebuild_files)

# record the compilation results for the next step
store_artefacts(compiled_c, build_lists, config._artefact_store)
store_artefacts(compiled_c, build_lists, config.artefact_store)


# todo: very similar code in fortran compiler
Expand Down
6 changes: 3 additions & 3 deletions source/fab/steps/compile_fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def compile_fortran(config: BuildConfig, common_flags: Optional[List[str]] = Non
A list of :class:`~fab.build_config.AddFlags`, defining flags to be included in the command line call
for selected files.
:param source:
An :class:`~fab.artefacts.ArtefactsGetter` which give us our c files to process.
An :class:`~fab.artefacts.ArtefactsGetter` which gives us our Fortran files to process.

"""

Expand All @@ -84,7 +84,7 @@ def compile_fortran(config: BuildConfig, common_flags: Optional[List[str]] = Non
mod_hashes: Dict[str, int] = {}

# get all the source to compile, for all build trees, into one big lump
build_lists: Dict[str, List] = source_getter(config._artefact_store)
build_lists: Dict[str, List] = source_getter(config.artefact_store)

# build the arguments passed to the multiprocessing function
mp_common_args = MpCommonArgs(
Expand Down Expand Up @@ -119,7 +119,7 @@ def compile_fortran(config: BuildConfig, common_flags: Optional[List[str]] = Non
logger.info(f"stage 2 compiled {len(compiled_this_pass)} files")

# record the compilation results for the next step
store_artefacts(compiled, build_lists, config._artefact_store)
store_artefacts(compiled, build_lists, config.artefact_store)


def handle_compiler_args(common_flags=None, path_flags=None):
Expand Down
2 changes: 1 addition & 1 deletion source/fab/steps/find_source_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,4 @@ def find_source_files(config, source_root=None, output_collection="all_source",
if not filtered_fpaths:
raise RuntimeError("no source files found after filtering")

config._artefact_store[output_collection] = filtered_fpaths
config.artefact_store[output_collection] = filtered_fpaths
6 changes: 3 additions & 3 deletions source/fab/steps/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ def link_exe(config, linker: Optional[str] = None, flags=None, source: Optional[
flags = flags or []
source_getter = source or DefaultLinkerSource()

target_objects = source_getter(config._artefact_store)
target_objects = source_getter(config.artefact_store)
for root, objects in target_objects.items():
exe_path = config.project_workspace / f'{root}'
call_linker(linker=linker, flags=flags, filename=str(exe_path), objects=objects)
config._artefact_store.setdefault(EXECUTABLES, []).append(exe_path)
config.artefact_store.setdefault(EXECUTABLES, []).append(exe_path)


# todo: the bit about Dict[None, object_files] seems too obscure - try to rethink this.
Expand Down Expand Up @@ -123,7 +123,7 @@ def link_shared_object(config, output_fpath: str, linker: Optional[str] = None,
flags.append(f)

# We expect a single build target containing the whole codebase, with no name (as it's not a root symbol).
target_objects = source_getter(config._artefact_store)
target_objects = source_getter(config.artefact_store)
assert list(target_objects.keys()) == [None]

objects = target_objects[None]
Expand Down
Loading