From d3535488567d3b8ae3e76ccf7cd59c0f1e7c09cb Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Fri, 25 Sep 2026 08:27:02 +0100 Subject: [PATCH 1/3] Force components to be generated after seeding/updating. When a repository is seeded, for all configured components a dummy placeholder file is created -- this is necessary because the components are imported by the edit file, so they need to exist and be importable. When seeding a new repository, it can be safely assumed that the edit file will be edited at some point after seeding, and this will naturally cause the generated-at-seed-time components to be considered outdated and therefore automatically rebuilt when calling `make all_components` (or any other target that depends on components, such as `prepare_release`). But when a pre-existing ontology is updated in place to add a new component, the user may expect that the automatically geneated placeholder file will be automatically replaced by the actual component simply by running `make all_components`. This will not happen, however, because from Make's point of view the component file is up-to-date. Make is strictly speaking correct, and the appropriate command to use is rather `make recreate-components`, which will force rebuilding (in fact in this case, build for the first time) the newly added component. But it is difficult to argue that such a behaviour is correct from the ODK's point of view. The component file may be up-to-date as far as Make is concerned, but it is in fact not -- it has never been built once. The workaround for this is to automatically create the `$(TMPDIR)/stamp-component-{{ component.filename }}` file when seeding, and to do that _after_ the placeholder component file is itself created. This will force Make to consider that the component file is outdated, since the stamp file will be more recent. See https://github.com/information-artifact-ontology/ontology-metadata/pull/210#issuecomment-5828219422 --- src/incatools/odk/templates/_dynamic_files.jinja2 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/incatools/odk/templates/_dynamic_files.jinja2 b/src/incatools/odk/templates/_dynamic_files.jinja2 index 1b5c71a..bd45952 100644 --- a/src/incatools/odk/templates/_dynamic_files.jinja2 +++ b/src/incatools/odk/templates/_dynamic_files.jinja2 @@ -605,6 +605,8 @@ Documentation of the Default DOSDP Pipeline +^^^ src/ontology/tmp/stamp-component-{{ component.filename }} + {% endfor -%} {% endif -%} {% if 'basic' in project.release_artefacts or project.primary_release == 'basic' -%} From df3309486b9c75e33d8e4b2096d47d472e6c725f Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Fri, 25 Sep 2026 09:09:26 +0100 Subject: [PATCH 2/3] Do not attempt to commit autogenerated stamp files. The seeding process will now create stamp files in the src/ontology/tmp directory, but such files must not be committed when the repository is seeded for the first time. --- src/incatools/odk/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/incatools/odk/cli.py b/src/incatools/odk/cli.py index afb4606..806f55d 100644 --- a/src/incatools/odk/cli.py +++ b/src/incatools/odk/cli.py @@ -348,11 +348,14 @@ def seed( if gitemail is not None: os.environ["GIT_AUTHOR_EMAIL"] = gitemail os.environ["GIT_COMMITTER_EMAIL"] = gitemail + files_to_commit = [ + t for t in tgts if not t.startswith(outdir + "/src/ontology/tmp/stamp-") + ] runcmd( "cd {dir} && git init -b {branch} && git add {files} && git commit -m 'initial commit'".format( dir=outdir, branch=project.git_main_branch, - files=" ".join([t.replace(outdir, ".", 1) for t in tgts]), + files=" ".join([t.replace(outdir, ".", 1) for t in files_to_commit]), ) ) runcmd( From 12c973404da3c02b80e8c74d08cf7f17cad062a7 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Sun, 27 Sep 2026 21:16:20 +0100 Subject: [PATCH 3/3] Make the components explicitly look "older" than the stamp files. Use a different approach for ensuring that newly introduced components are rebuild. Instead of having the template create both the component file and the stamp file, and relying on the stamp file being created after the component file, we forcefully set the modification time of the component file one minute in the past. Also, we do that _only_ when updating, not when seeding the repository initially. And we only do that for _newly added_ components (those that still have the "placeholder" marker in them), not for those that were already existing and that have been built before. --- src/incatools/odk/cli.py | 7 ++-- src/incatools/odk/template.py | 33 ++++++++++++++++++- .../odk/templates/_dynamic_files.jinja2 | 2 -- src/incatools/odk/util.py | 17 ++++++++++ 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/incatools/odk/cli.py b/src/incatools/odk/cli.py index 806f55d..cdcac9b 100644 --- a/src/incatools/odk/cli.py +++ b/src/incatools/odk/cli.py @@ -184,6 +184,8 @@ def update(templatedir): # may have been manually edited. mg.update_gitignore(templatedir + "/.gitignore.jinja2", "../../.gitignore") + mg.post_update("../..") + if project.manage_import_declarations: mg.update_xml_catalog( templatedir + "/src/ontology/catalog-v001.xml.jinja2", "catalog-v001.xml" @@ -348,14 +350,11 @@ def seed( if gitemail is not None: os.environ["GIT_AUTHOR_EMAIL"] = gitemail os.environ["GIT_COMMITTER_EMAIL"] = gitemail - files_to_commit = [ - t for t in tgts if not t.startswith(outdir + "/src/ontology/tmp/stamp-") - ] runcmd( "cd {dir} && git init -b {branch} && git add {files} && git commit -m 'initial commit'".format( dir=outdir, branch=project.git_main_branch, - files=" ".join([t.replace(outdir, ".", 1) for t in files_to_commit]), + files=" ".join([t.replace(outdir, ".", 1) for t in tgts]), ) ) runcmd( diff --git a/src/incatools/odk/template.py b/src/incatools/odk/template.py index a4910e8..9d7737a 100644 --- a/src/incatools/odk/template.py +++ b/src/incatools/odk/template.py @@ -8,6 +8,7 @@ import fnmatch import logging import os +import time from dataclasses import dataclass from enum import Enum from pathlib import Path @@ -19,7 +20,7 @@ from jinja2 import Template from .model import OntologyProject -from .util import runcmd +from .util import grep, runcmd TEMPLATE_SUFFIX = ".jinja2" DEFAULT_TEMPLATE_DIR = Path(__file__).parent.resolve() / "templates" @@ -397,3 +398,33 @@ def update_import_declarations(self) -> None: else: cmd += f" convert --check false -o {self.project.id}-edit.obo" runcmd(cmd) + + def post_update(self, root: str) -> None: + """Performs various tasks after updating a repository. + + For now, this means ensuring that newly added components will be + built after the update, without requiring the user to explicitly + run `make recreate-components`. + + :param root: Path to the root directory of the repository. + """ + rootdir = Path(root) + if self.project.components is not None: + compdir = rootdir / "src/ontology/components" + tmpdir = rootdir / "src/ontology/tmp" + now = time.time() + for component in self.project.components.products: + compfile = compdir / component.filename + if not grep( + compfile, " -^^^ src/ontology/tmp/stamp-component-{{ component.filename }} - {% endfor -%} {% endif -%} {% if 'basic' in project.release_artefacts or project.primary_release == 'basic' -%} diff --git a/src/incatools/odk/util.py b/src/incatools/odk/util.py index 7e31c9c..a3d64d4 100644 --- a/src/incatools/odk/util.py +++ b/src/incatools/odk/util.py @@ -6,7 +6,9 @@ # for the detailed conditions. import logging +import re import subprocess +from pathlib import Path def runcmd(cmd: str) -> None: @@ -30,3 +32,18 @@ def runcmd(cmd: str) -> None: logging.error(err) if p.returncode != 0: raise Exception("Failed: {}".format(cmd)) + + +def grep(file: Path, needle: str) -> bool: + """Searches for a pattern anywhere in a file, + + :param file: The file to grep into. + :param needle: The pattern to look for. + :returns: True if the file contains the pattern. + """ + pattern = re.compile(needle) + with file.open("r") as fd: + for line in fd: + if pattern.search(line): + return True + return False