Skip to content

Latest commit

 

History

History
195 lines (154 loc) · 9.09 KB

EXTERNAL-CONTRIBUTIONS.md

File metadata and controls

195 lines (154 loc) · 9.09 KB

External contributions (*.ext)

Some components, library snippets and example instruments shipped with McCode are maintained in their own repositories. Rather than copying them into this tree (where they drift) or hiding the fetch inside a bespoke CMake module (where nobody finds it), each such contribution declares itself with a small JSON manifest named <something>.ext, placed in the directory the files are populated into.

So a developer browsing mcstas-comps/contrib/ for a component finds either the .comp itself or an .ext file naming the upstream repository, the release it is pinned to, and the SHA256 of every file taken from it. The record lives where you would look for the thing it describes.

The CMake side is cmake/Modules/External.cmake. The manifests are written and kept honest by mcext and the GitHub Action around it, both in mccode-dev/external-registrar; .github/workflows/external-contributions.yml runs them here.

Where the manifests live

mcstas-chopper-lib contributes files to three destinations, so it has a manifest in each:

Manifest Populates
mcstas-comps/share/chopper-lib.ext chopper-lib.c, chopper-lib.h — %include-d into generated instrument C, like the other *-lib.c snippets beside it
mcstas-comps/contrib/chopper-lib.ext NXdisk_chopper.comp, Masked_ESS_butterfly.comp
mcstas-comps/examples/Tests_optics/<Instrument>/<Instrument>.ext that one example instrument

Manifests are not installed: the install( DIRECTORY ... ) rules that cover those directories carry PATTERN "*.ext" EXCLUDE.

Sources that are compiled: cif2hkl

mcstas-comps/libs/cif2hkl/cif2hkl.ext (McXtrace: a symlink to it) pins the SOLEIL cif2hkl Fortran sources. Its mccode_install_externals() call has no DESTINATION, so the files are only staged, never installed; OUTPUT_VARIABLE hands them to add_executable( cif2hkl ... ). cif2hkl lives on GitLab, which the poll job below does not query, so move it to a new release by hand with mcext update mcstas-comps/libs/cif2hkl/cif2hkl.ext -v <tag> (and bump the cif2hkl >= conda and Debian pins to match).

Manifest format

Short form — a JSON array of fully self-describing file entries:

[
  { "name": "chopper-lib.h",
    "url": "https://raw.githubusercontent.com/<owner>/<repo>/<tag>/chopper-lib.h",
    "sha256": "0965f6660850acce12d665d7a57064389ccb570e218f21c1dbad9155ee7d8ef9" }
]

Long form — an object of contribution-wide defaults plus a files array. Any key understood in a file entry may also appear at the top level, where it applies to every entry that does not override it:

{
  "name": "mcstas-chopper-lib",
  "description": "Chopper opening/closing times and (inverse velocity, time) masks.",
  "homepage": "https://github.com/<owner>/<repo>",
  "git": "https://github.com/<owner>/<repo>.git",
  "version": "<tag>",
  "base": "https://raw.githubusercontent.com/<owner>/<repo>/<tag>/",
  "files": [
    { "name": "chopper-lib.h", "sha256": "0965f666..." },
    { "name": "chopper-lib.c", "sha256": "d914a307..." }
  ]
}

Prefer the long form: it is the one that records what the contribution is, not just where some bytes live.

For a real, current manifest see mcstas-comps/share/chopper-lib.ext.

File-entry keys

Key
name required — file name upstream, and the default installed name
sha256 required — SHA256 of the contents
as install path relative to the manifest's own directory; may name a subdirectory, e.g. "Test_Foo/Test_Foo.instr". Default: name
url explicit download URL for this one file
from path of the file inside the release archive. Default: name
base URL prefix; the file is fetched from <base><from>
git upstream repository — informational, but see below
version upstream tag/ref

Contribution-wide keys

Key
files required in the long form
archive { "url", "sha256", "strip" } — a release tarball or zip, downloaded and unpacked once and shared across every manifest that names the same URL. strip leading path components are dropped; the default of 1 matches GitHub's generated source archives
license, description, homepage recorded in configure output

A single file resolves through url, then base, then archive; failing all three, a github.com git plus a version is enough to derive a raw base URL. Mixing is fine — a manifest may take most of its files from a release tarball and one from a direct URL.

Why the hashes are mandatory

The hash is the point. A pinned tag says what should be fetched; the SHA256 is what makes a build fail loudly when the bytes behind that tag change — a moved tag, a regenerated release archive, a compromised host. Without it, "pinned to v4.1.0" is a statement of intent rather than a fact.

This matters more than usual for GitHub's auto-generated source archives (/archive/refs/tags/*.tar.gz), which are produced on demand and have changed byte-for-byte across GitHub compression changes in the past. A release asset that the maintainer uploads is stable and is the better thing to point archive.url at where one exists.

A manifest may omit sha256 only if the build is configured with -DMCCODE_EXTERNALS_ALLOW_UNVERIFIED=ON, which exists for bisecting an upstream problem and should not appear in a manifest's normal life.

Keeping contributions current

.github/workflows/external-contributions.yml runs once a day, and on demand from the Actions tab:

  • poll looks up the latest release of every repository a manifest's git names. When it finds one newer than McCode records, it opens a pull request from external/OWNER-REPO that moves every manifest for that repository to the new tag, with each hash computed from the files as published. A repository can shape those manifests by keeping templates in a .mccode/ directory laid out like this tree; see the external-registrar README. Closing such a PR unmerged declines that release; the next release is proposed as usual.
  • check re-verifies every recorded hash against upstream and fails on any mismatch: a moved tag, a regenerated release archive, a compromised host.

The poll job needs the registrar GitHub App (REGISTRAR_CLIENT_ID and REGISTRAR_PRIVATE_KEY) and is skipped until it is configured; check needs no credentials.

Adding a contribution, or changing one by hand

mcext needs only Python 3.9+. Without installing anything:

pipx run --spec https://github.com/mccode-dev/external-registrar/archive/refs/tags/v1.tar.gz mcext --help

or pip install https://github.com/mccode-dev/external-registrar/archive/refs/tags/v1.tar.gz to put mcext on your path. The steps below assume the latter.

  1. Create the directory the files belong in, if it does not exist.
  2. Write the manifest there with name/git/version/base (or archive) and the list of files, leaving the hashes out.
  3. Fill them in: mcext update path/to/thing.ext
  4. Add a mccode_install_externals() call for the containing directory if one does not already exist — see mcstas-comps/CMakeLists.txt, where each call mirrors the install( DIRECTORY ... ) rule for the same directory.

Once that is merged, the poll job keeps the contribution current. To move to a release by hand instead, e.g. one the poll would not choose:

mcext update mcstas-comps/contrib/chopper-lib.ext -v v4.2.1

which repoints every recorded URL at the new tag and recomputes every hash. mcext check [PATH ...] runs the same verification as the check job, over the current directory by default.

Build options

Option Default
ENABLE_EXTERNALS ON populate external contributions at all
MCCODE_EXTERNALS_CACHE <build>/externals-cache content-addressed download cache; point it somewhere persistent to survive a wiped build directory
MCCODE_EXTERNALS_LOCAL (empty) directory of pre-fetched files, searched by name before any download and still hash-verified
MCCODE_EXTERNALS_OFFLINE OFF never download: any file not in the cache or MCCODE_EXTERNALS_LOCAL is a hard error rather than a silent fetch
MCCODE_EXTERNALS_ALLOW_UNVERIFIED OFF accept entries with no sha256
MCCODE_EXTERNALS_TIMEOUT 60 per-file download timeout, seconds

Distribution packagers who may not reach the network during a build have two routes: pre-populate MCCODE_EXTERNALS_CACHE, or drop the files into MCCODE_EXTERNALS_LOCAL. Either way, set MCCODE_EXTERNALS_OFFLINE=ON so a gap in the provided set fails the configure instead of quietly downloading.

The manifest machinery uses string( JSON ) and file( ARCHIVE_EXTRACT ), which is why McCode's cmake_minimum_required is 3.19. The cmake_policy( VERSION 3.17.0 ) pins in the tools subdirectories were deliberately left alone: raising the floor should not quietly change policy behaviour anywhere it was explicitly fixed.