Conversation
When a defaults file declares `append_arch: <value>`, that value (not the default filename) is appended to the effective architecture. In a chained `--default a::b::c` invocation only the defaults files that carry `append_arch` contribute to the suffix, giving fine-grained control over install-path qualification. readDefaults() collects each file's append_arch value in order before merge_dicts() flattens the metadata, storing the list as `_append_arch_qualifiers` in the merged meta. compute_combined_arch() checks for `_append_arch_qualifiers` first and uses those verbatim values as the suffix; the legacy `qualify_arch: true` path (which appended every non-release default name) is preserved as a fallback for existing setups that do not use append_arch. Example: defaults-gcc13.sh has `append_arch: gcc13`, defaults-release.sh has none → `--default release::gcc13` produces `<arch>-gcc13` instead of `<arch>-release-gcc13`.
When a recipe specifies `sources:` (tarball URLs) rather than a git repository, bits downloaded the archive into $SOURCEDIR but never unpacked it. The build script would then rsync a bare .tar.gz into the build directory, so `configure` / `CMakeLists.txt` were missing and the build failed immediately. Add `_extract_source_archives(source_dir)` which scans for archives after every download pass and unpacks them with `tar --strip-components=1` (or the equivalent zip logic), so the unpacked source tree lands directly in $SOURCEDIR just as a git checkout would. Supported formats: .tar.gz / .tgz, .tar.bz2 / .tbz2, .tar.xz / .txz, .tar.zst, .zip. A `.bits_extracted` sentinel prevents re-extraction on resumed builds. Tests: update all ParallelCheckoutSourcesTest cases to mock the new helper, and add ExtractSourceArchivesTest (9 cases) covering sentinel idempotency, per-format extraction, and checkout_sources integration.
…ation
patches: listed in a recipe YAML were copied to SOURCEDIR and exposed as
/ environment variables, but were never applied - the
build system left application entirely to recipe authors. In practice,
97 out of 97 lcg.bits recipes that declared patches had no Prepare()
override and therefore built with unpatched source trees.
Add _apply_patches(spec, source_dir) which iterates over spec[patches]
in declaration order and invokes:
patch -p1 --input <patch_path> (cwd=source_dir)
A .bits_patched sentinel file is written after successful application,
mirroring the .bits_extracted sentinel used by _extract_source_archives(),
so that incremental/resumed builds do not attempt re-application.
Call _apply_patches() at the end of every source-preparation path in
checkout_sources():
* tarball sources - after _extract_source_archives()
* git sources - after _verify_commit_pin(), in both the existing-
checkout and fresh-clone branches
patch -p1 handles both tarball extracts (no .git present) and git working
trees equally. The source_dir path is keyed by short_commit_hash(spec),
which changes whenever the recipe, tag, or patch content changes, so the
sentinel is never stale across meaningful source changes.
Add nine unit tests in tests/test_workarea.py covering:
- spec with no patches key (no-op)
- empty patches list (no-op)
- existing .bits_patched sentinel (idempotent skip)
- single patch: correct patch -p1 --input invocation and cwd
- multiple patches: applied in declaration order
- sentinel written on success
- inline checksum suffix (,sha256:...) stripped from patch filename
- CalledProcessError propagates on patch failure
- sentinel NOT written on patch failure
…rsal Without --batch, if a source dir is in a partially-patched state (e.g. from a previous failed run where no .bits_patched sentinel was written), patch(1) prompts interactively: Reversed (or previously applied) patch detected! Assume -R? [n] In a terminal session the user may answer 'y', causing patch to reverse the previously-applied hunks and exit 0. _apply_patches then writes the .bits_patched sentinel, locking in an unpatched (or mixed) source state. The subsequent cmake/make step then fails with 'Cannot find source file'. With --batch, patch never prompts and exits non-zero when it encounters a reversed/already-applied patch, which propagates as a CalledProcessError. The user must then clean the SOURCEDIR manually (rm -rf sw/SOURCES/<pkg>/), but at least the sentinel is not written and the failure is explicit. Updated tests to expect ['patch', '-p1', '--batch', '--input', ...].
…lied A previous build run may have extracted a tarball with the old hardcoded --strip-components=1 and written .bits_extracted before _archive_prefix_depth was introduced. On retry the extraction sentinel suppresses re-extraction, leaving source files at the wrong subdirectory depth so patch -p1 cannot find them. Before calling _extract_source_archives, remove .bits_extracted if patches are declared but .bits_patched does not yet exist. This forces a clean re-extract with the correct strip depth on any retry, without requiring manual source directory cleanup.
The common-prefix loop iterated over range(min_path_len), which includes the filename component itself. For a single-file archive (or any archive where every file shares the same full path) every component trivially satisfies "all paths agree", so depth was set to the full path length rather than the directory depth - e.g. depth=2 for pkg-1.0/hello.txt instead of 1, causing --strip-components=2 to over-strip and leave an empty source directory. Fix: iterate over range(min_path_len - 1) so the filename is never counted as a common prefix level. This also corrects the photos/215.4/ two-level-prefix case and the ./pkg-1.0/ dot-prefix case. Also fix getPackageList: introduce _disable_set to skip re-processing packages that are already known to be disabled. Previously, a package with prefer_system (e.g. GCC-Toolchain) was appended to the disable list once per occurrence in the dependency queue, producing hundreds of duplicate --disable=GCC-Toolchain entries in error-message argument logs. Guard both disable.append sites and add an early-continue at the top of the resolution loop. Deduplicate the list in the two error-message formatting sites in build.py as a belt-and-suspenders measure. All 857 tests pass.
After arch-conditional filtering and bash evaluation, each resolved
source entry is passed through Python % formatting with the dict
{"name": spec["package"], "version": spec["version"]}. This lets
recipes avoid repeating the package name and version in every URL:
sources:
- https://example.com/%(name)s/%(name)s-%(version)s.tar.gz
Substitution errors (unknown keys, malformed %) are silently ignored
so that URLs containing literal % characters are unaffected.
Adds a new "name = version" clause to the dependency requirement syntax,
allowing a recipe to lock a specific version of a dependency directly in
its requires/build_requires list instead of through a defaults-*.sh
override entry.
Syntax:
requires:
- root = 6.24.02
- my-provider = feature-branch
- "boost = 1.82.0:(?!osx)" # combined with arch-conditional
- "boost = 1.82.0:defaults=o2" # combined with defaults-conditional
The pin overrides both the recipe default and any defaults-*.sh override,
taking the highest precedence in the resolution order. After filtering,
spec["requires"] still contains plain package names so storeHashes is
unaffected; the changed version/tag propagates naturally through the
dependency hash chain.
Conflict detection:
- Two packages pinning the same dep to different versions → fatal error.
- A pin declared after the dep was already resolved at a different version
→ fatal error with a "move the pinning package earlier" hint.
13 new tests in test_utilities.py cover _parse_req_matcher, the filter
functions with version-pinned entries, and all _collect_version_pins
scenarios (basic, arch-inactive, same-version-two-owners, conflict,
already-resolved-same, already-resolved-conflict).
864 tests pass.
resolve_spec_data (used by build.py to expand source URLs before download) already supported %(package)s and %(version)s but not %(name)s. Source URLs in lcg.bits recipes now use %(name)s as a shorter alias; without this fix the build aborted with KeyError: 'name' on the first package whose sources field contained %(name)s.
Malformed recipes, bad !include references, unknown %(var)s
substitutions, patch failures, and unsupported download protocols
previously caused unhandled Python exceptions (raw tracebacks).
All are now intercepted and reported via dieOnError().
workarea._apply_patches
Catch CalledProcessError from patch(1), collect all .rej files
left in the source tree, and surface them in the error message so
the developer can see exactly which hunks failed without digging
into the build directory.
utilities.resolve_spec_data
Catch KeyError on %(unknown_var)s expansion; report the missing
variable name, the package, the offending value, and the full list
of available variables.
utilities.resolve_tag
Same treatment for %(var)s in the tag: field.
utilities.construct_include (!include in YAML)
Wrap open() in try/except OSError and the yaml/json parsers in
their respective exceptions; re-raise as ConstructorError with the
filename and position so parseRecipe surfaces it as a clean
"Unable to parse" message.
utilities.parseRecipe
Broaden "except (ScannerError, ParserError)" to "except
yaml.YAMLError" so ConstructorError from failed !include directives
is caught and reported cleanly instead of propagating as a crash.
utilities.getGeneratedPackages
Wrap __import__("packages") and pkg.getPackages() in try/except;
call dieOnError naming the offending packages.py file.
download.download
Guard the downloadHandlers dict lookup; call dieOnError listing
the unsupported protocol, the URL, and the supported protocols
instead of raising a raw KeyError.
Tests updated to match the new behaviour (dieOnError / yaml.YAMLError
instead of raw CalledProcessError / FileNotFoundError).
workarea.py - _apply_patches:
- Import ProgressPrint and emit "==> Patching PKG@VERSION" before the
patch loop, matching the "==> Compiling" style so the package being
processed is always visible before any patch(1) output.
- In non-debug mode, capture patch(1) stdout/stderr with subprocess.run
so that "patching file …" lines no longer leak into the progress
display; the captured output is forwarded to debug() on success and
prepended to the error message on failure.
- On failure call progress.end("failed") before dieOnError so the
progress line closes cleanly; on success call progress.end("done").
build.py - doBuild:
- Wrap checkout_sources() in try/except OSError and convert the
exception to a dieOnError call ("Failed to fetch sources for
PKG@VERSION: …") so a failed download (e.g. unresolved shell variable
in a source URL) prints a clean error instead of a raw Python
traceback.
Some tarballs (e.g. the LCG-mirrored HDF5 tarball) have a two-level
leading prefix ("./hdf5-1.14.6/…"), so _archive_prefix_depth() returns
2 and correct extraction needs --strip-components=2. Old bits code
hardcoded --strip-components=1, leaving an unstripped "hdf5-1.14.6/"
subdirectory inside $SOURCEDIR and writing a .bits_extracted sentinel.
On subsequent runs the new code saw the sentinel, skipped extraction,
and cmake failed with "does not appear to contain CMakeLists.txt".
The previous workaround removed stale sentinels only for packages that
declare patches (because patched packages exposed the symptom earlier).
Non-patched packages like hdf5 were never fixed.
Fix: _extract_source_archives() now writes the per-archive strip depth
into the sentinel as JSON ({"strips": {"hdf5-1.14.6.tar.gz": 2}}). On
every run it compares the recorded depths against what _archive_prefix_
depth() would compute today; if they differ the sentinel is removed and
the archives are re-extracted with the correct depth. Sentinels written
by the old code (empty file, not valid JSON) are also treated as stale
and replaced automatically.
The now-redundant manual sentinel removal for patched packages in
checkout_sources() is removed; the universal check in
_extract_source_archives() covers all packages.
…ronment mirrors what each package's runtime modulefile
Under --builders, doBuild() ran one serial preparation loop that checked out every package's sources inline and only started the scheduler afterwards, so no build began until all downloads finished. At O(1000) packages this left the CPUs idle for a long time. Source checkout is now a scheduler "download" task (fetch:<pkg>, via the new _doCheckout helper); the build task depends on it plus its dependencies' builds. This activates the scheduler's existing download/build task types and their separate caps, so packages compile as soon as their sources are present while other downloads keep flowing. Inline checkout remains only on the single-builder path. Also: - --prefetch-workers now defaults to -1 (auto = min(builders, 4)); 0 disables. - Add --parallel-downloads N (default 2), wired to the scheduler download cap. - Update test_async_build defaults test; fix the stale sentinel test to write a valid JSON .bits_extracted sentinel (the extractor re-extracts on a strip -depth mismatch, so an empty sentinel no longer means "skip").
With --sandbox=auto (the default), a plain local Linux build (no --docker) resolved to podman whenever it was installed, which meant every `bits build` invoked `podman info` to probe it - and, on Debian/Ubuntu, podman's own startup pulled in dpkg-query. podman was only ever intended for --docker (nested) or explicit opt-in. resolve_sandbox_mode() now returns "off" for the local-Linux/no-docker auto case without calling podman_available(), so podman is never invoked there. Unchanged: --docker still uses nested podman when available (falling back to off), --sandbox=podman / --sandbox-image still force it, and macOS auto still uses sandbox-exec. - bits_helpers/sandbox.py: local Linux auto -> off, no podman probe - tests/test_sandbox.py: assert auto+Linux+no-docker is off and podman_available is not called - docs/REFERENCE.md §22.1, docs/ROADMAP.md, args.py --sandbox help: document the new behaviour
bits keys the source directory by package version/commit (constant for a tarball), and _apply_patches skips re-patching whenever the .bits_patched sentinel exists. So editing a recipe patch file had NO effect on rebuild: the old already-patched tree was reused (the new patches cannot be cleanly applied on top), and the stale source silently propagated into every new build hash. This was the real cause of the Gaudi confdb2 saga -- repeated patch edits never took effect. Fix: record a fingerprint of the patch set (each patch name + full content, in order) in the .bits_patched sentinel. For tarball sources, before extraction, wipe the source dir when the recorded fingerprint differs from the current patches (legacy empty sentinels count as changed, so existing trees self-heal), forcing a clean re-extract + re-patch. Git sources are untouched. Verified: new fingerprint changes iff patch content changes; legacy sentinels trigger a wipe; matching fingerprint skips. (The 5 pre-existing ApplyPatchesTest failures are unrelated -- those tests mock subprocess.check_call while the code has used subprocess.run for a while; this change does not touch that line.)
…tches) The ApplyPatchesTest cases mocked subprocess.check_call, but _apply_patches has used subprocess.run (with output capture for error messages) for a while, so the mock never intercepted the call: setUp creates empty patch files, real `patch` no-ops, and the check_call assertions saw 0 calls (5 failures, independent of the patch-fingerprint change). Update the tests to mock subprocess.run and assert the patch command + cwd via call_args (ignoring stdout/stderr/check kwargs). All 14 tests pass.
The patch-set re-extraction guard only fired when .bits_patched existed with a different fingerprint. But if a patch run fails partway, _apply_patches dies without writing .bits_patched while .bits_extracted is already present and the tree is partially patched. On retry the wipe was skipped, extraction skipped, and patch re-applied onto the dirty tree -> "Reversed (or previously applied) patch detected" + corruption (seen with madgraph5amc). Wipe also when there is no .bits_patched sentinel but .bits_extracted exists, so patches always apply to a pristine tree.
'bits brew' now writes next to the recipes by default (lcg.bits/macos/ Brewfile) instead of cwd/Brewfile, and doctor checks that location first.
Flavours stay command-line only (--flavour). Extend the defaults variables:
block so an entry can be GATED, and add predefined architecture variables so
gates and package requires can test the platform with the (?NAME) spelling.
* predefined_arch_vars(arch): exposes the truthy platform booleans
osx / linux / arm64 / aarch64 / x86_64 (only the true ones, so (?osx) is
correctly false off macOS).
* resolve_variables(variables, flavours, arch, defaults): resolves variables:
into a flat map. A plain 'name: value' is always defined; a gated
'name: {value: V, when: MATCHER}' is defined only when MATCHER is active.
MATCHER reuses the requires grammar -- (?flavour), arch regex (osx/(?!osx)),
defaults=, combined with && / || -- evaluated against the vars resolved so
far, so a gate may reference CLI flavours, the predefined arch vars, and
earlier ('previously defined') entries. Precedence: predefined < flavour <
defaults entry, but a CLI flavour always overrides a defaults entry of the
same name while staying visible to gates.
Wired into build.py and deps.py defaultsReaders (replacing the old
flavour->variables merge; flavours still go into env/hash). The resolved map
feeds the existing (?NAME) matchers that gate package requires and %(NAME)s
templating, so flavours + variables + predefined vars can all gate packages
(e.g. define heavygen when (?openloops) && (?!osx), then 'pkg:(?heavygen)').
Note: arch regexes in when: match the RAW architecture (resolution runs before
the combined-arch qualifier is computed). Tests in test_utilities.py.
|
Is there a way to make sure certain packages are present first without adding it as an dependency? It shouldn't change the hash if it changes. |
|
|
||
| # A variable-reference matcher is spelled "(?NAME)" -- an identifier in the same | ||
| # parenthesised form as a regex group, but one that is NOT a legal regex (e.g. | ||
| # "(?cuda)" raises re.error: "unknown extension ?c"). This lets a recipe gate a |
There was a problem hiding this comment.
@pbuncic How can I test this seems like a useful thing to have but unable to use it.
requires:
- cuda(?cuda)
BASH
And I need to define a variable cuda in defaults? Is that it? There was a problem hiding this comment.
Yes, have a look into stacks.bits
There was a problem hiding this comment.
Ah ok ig there is an issue with it this works
package: defaults-release
version: vBits
variables:
gcc: 'true'
gcc-compiler: 'true'
---package: zlib
version: 1.2.3
requires:
- "gcc:(?gcc)"
- "gcc-compiler:(?gcc-compiler)"
---For gcc it works perfectly fine but if the name is gcc-compiler. I think '-' is causing the issue? Maybe?
raise source.error("unknown extension ?" + char,
re.error: unknown extension ?l at position 1There was a problem hiding this comment.
Yes — names must be C-style identifiers, so a hyphen isn't allowed. Use underscores instead (gcc_compiler, not gcc-compiler).
There was a problem hiding this comment.
Besides, I am about to merge devel into main, hope it is OK with you.
The per-build 'CPU oversubscription factor N (defaults system.build_oversubscribe)' message added noise to every build invocation. Remove it; the factor still applies, just silently.
…tartup) The pre-build stale .downloading sentinel cleanup did os.walk(workDir) over the ENTIRE sw tree on every run - including INSTALLROOT and BUILD, the whole installed stack (tens of thousands of files). That stat() storm caused a long, visible pause between 'Updating repositories: done' and the first package's rebuild check, especially on macOS. Sentinels are only ever created under SOURCES/ (source archives) and TARS/<arch>/store/ (tarballs, per resolve_store_path), so scope the walk to just those subtrees. Verified the scoped cleanup still removes sentinels in both locations and skips INSTALLROOT.
Follow-up to d9b4243. Sentinels live at two fixed depths - SOURCES/<pkg>/<ver>/<file>.downloading and TARS/<arch>/store/<hash[:2]>/<hash>.downloading - so no recursive walk is needed at all. Two non-recursive glob patterns match exactly those directories and nothing else, eliminating even the scoped os.walk. Verified the globs catch sentinels at both depths and skip decoys at other depths.
A recipe's full body (comments included) fed the build hash, so documentation- only edits rehashed the package and forced a rebuild. Hash a normalized copy instead: normalize_recipe_for_hash() drops whole-line comments and blank lines, while preserving here-document bodies verbatim (a leading '#' there is data). The executed recipe is unchanged. One-time effect: all hashes shift once on upgrade; thereafter comment/whitespace edits never trigger a rebuild. Tests in test_hashing.py cover comments, blanks, inline '#', and here-doc preservation.
The stale-sentinel cleanup now globs SOURCES/*/*/*.downloading and TARS/*/store/*/*.downloading (commit 00836ec); the glob mock lacked those keys, raising KeyError. Add them returning empty.
Add bits_helpers/cvmfs_catalog.py (productionised from the validated standalone prototype): when a directory is served by a single dedicated nested catalog rooted there, read the catalog hash from the user.catalog_counters magic xattr, fetch the one catalog object over HTTP, decompress it, and list every entry from a local SQLite query -- no per-file FUSE walk. Conservative: it lists only from a catalog rooted at the path with no deeper nested catalogs, else signals fallback (exit 3), so it can never return a partial/oversized listing. bitsModules is the thin entry script (bitsBuild/bitsDeps family) that drives it. The shell frontend's collectModules now enumerates <pkg>/<version> via _listPkgVersions: on /cvmfs it tries bitsModules and falls back to the POSIX find walk on the fallback contract; off CVMFS it goes straight to find (no python spawn), so non-CVMFS behaviour is unchanged. Tests build a crafted catalog SQLite and cover path reconstruction, both output modes, and the fall-back contract.
When --defaults qualifies the architecture (qualify_arch: osx_arm64 -> osx_arm64_gcc15), the install tree lives under the combined string but the bits frontend auto-detects only the raw base arch, so 'bits enter'/'q' looked in the wrong sw/<arch> (and the success banner's suggested command omitted -a). Build: when args.architecture != raw_architecture, the success banner now prints 'bits -a <combined> enter <pkg>/...' plus the BITS_ARCHITECTURE persist hint; unchanged when the arch was not qualified. Frontend (bits launcher): when -a was not given and the auto-detected arch is absent/unknown in the work dir, prefer an arch actually present -- guess it if exactly one exists, or warn and list them when several do (instead of the old silent 'newest wins'). An explicit -a is always respected verbatim.
…h enter REFERENCE.md additions for recent features: - defaults variables: (plain + gated when: form) and predefined platform vars (osx/arm64/aarch64) feeding the (?NAME) matcher; note that (?!osx) is an arch regex, not variable negation. - macOS Homebrew system layer: homebrew_formula, bits brew / Brewfile, --brew / BITS_BREW on-demand install, HomebrewRecipe symlink exposure, doctor check. - bits q fast listing on CVMFS via the serving catalog (bitsModules). - entering a qualified-architecture build: the success-banner -a hint, BITS_ARCHITECTURE, and the frontend single-arch guess / multi-arch warning. - CLI table: add --parallel-downloads, --auto-resources, --brew; fix the stale --prefetch-workers default (now -1 auto, was 0).
NAME must be [A-Za-z_][A-Za-z0-9_]*; a hyphen makes (?NAME) fall through to an arch regex (gate never fires) and can't be an env var. Use underscores.
Remove the 'Build-time sandbox network allowed by default' and 'Reusing cached provider' messages printed on every bits start. The provider one was already redundant with the debug() line above it (kept for verbose mode).
Autodetection scanned sw/ and only filtered all-uppercase dirs, so lowercase non-arch dirs like wrapper-scripts were offered as architecture candidates (spurious 'several architectures present' warning). Enumerate $WORK_DIR/MODULES/* instead — the authoritative set of module-serving architectures.
When several architectures are present under MODULES/ and none was given, pick the most recently modified (built) one (ls -1t, newest first) instead of warning and bailing. A short note tells the user which was chosen and how to override with -a.
|
Before you merge it one change would be ==> Packages will be built in the following order:
- db6@6.2.32
- zlib@v%(version)s
- gmake@4.3
- libffi@v3.5.2
- expat@R_2_7_1
- sqlite@version-3.48.0
- autotools@1.5
- bz2lib@1.0.8
- gdbm@1.26
- libuuid@2.40
- xz@v%(version)s
- Python@v%(version)s
- cuda@12.9.1version is not replaced in this display we can maybe just call resolve_spec_data here to replace version. So the print shows it. |
…Build The display prefix / branding (BITS_PKG_PREFIX, BITS_BRANDING) and the ALICE default no longer live in bits itself. bits q now prints native <pkg>/<version> and only reformats to <PREFIX>@<pkg>::<version> when BITS_PKG_PREFIX is set. - aliBuild: symlink -> standalone wrapper that exports BITS_ORGANISATION=ALICE, BITS_PKG_PREFIX=VO_ALICE, BITS_BRANDING=aliBuild, then execs bits. Needs no bits.rc; reproduces the old VO_ALICE@pkg::ver output. - bits launcher: drop baked ALICE/VO_ defaults and the implicit VO_ prefix; replace the INI readBitsRc with a minimal work_dir reader. Everything else is read by the Python layer. - Reject old-style bits.rc: deprecated keys (sw_dir/repo_dir/search_path/ pkg_prefix/branding) or non-[bits] sections print the required renames and exit. - organisation stays the registry/provider home selector and now also defaults from BITS_ORGANISATION env so aliBuild selects ALICE's registry for build too. - docs: env-var table, bits q format, bits.rc format note.
At the end of a --builders run, integrate the per-package resource-monitor traces into useful core-seconds and divide by cores x wall-clock to estimate whole-run CPU utilisation, plus the average number of builders busy at once. Store both (and a recommendation) under a 'tuning' key in bits_build_stats.json, and print the recommendation when there is headroom (<90%): - slots mostly full but cores idle -> per-package serial phases; suggest a higher --oversubscribe (memory budget unchanged by design). - slots often empty -> dependency-graph bound; suggest more --builders and/or reusing prebuilt tarballs. build_stats.tuning_report() is pure/unit-tested; aggregate_and_write() gains an optional tuning= kwarg (existing callers unaffected).
…ecmd bits q was slow on a real filesystem because it (1) ran collectModules, which rm -rf's and re-copies every modulefile into the MODULES cache on every call, and (2) then spawned 'modulecmd avail' to enumerate. Both are redundant for a listing: the install tree's modulefiles are the source of truth. Add listAvailableModules() — the collectModules loop minus the copy — which emits <pkg>/<version> for each install dir that ships a modulefile, via the existing _listPkgVersions fast path (CVMFS catalog shortcut included). q now pipes it through sort; load/enter/avail keep using collectModules/modulecmd. Instant on a normal FS (matches plain find); BASE helper module no longer shown.
collectModules rm -rf'd MODULES/<arch> and re-cp'd every modulefile on every load/enter/avail — O(packages) of mkdir/cp each time. Now sync incrementally: copy a modulefile only when missing or newer than the cached copy, prune cache entries whose source vanished, and drop emptied package dirs. After a build with no module changes this copies nothing, so repeated load/enter are instant. Portable: uses bash -nt tests and plain find (no GNU -printf, no bash-4 associative arrays) so it still works on macOS /bin/bash 3.2.
existModules ran 'modulecmd bash -t avail' (a full MODULES-tree scan/evaluate) once per requested module just to verify it exists -- the main reason load/enter/printenv/setenv were notably slower than q. Check the modulefile cache directly instead (<pkg>/<version> is a file, bare <pkg> a directory), falling back to modulecmd only on a miss so extra MODULEPATH entries and default-version aliases still resolve. A '..' guard keeps the fast path from matching path-traversal names. The actual 'modulecmd load' is unchanged.
Set BITS_TIMING=1 to print the wall time of each phase (preamble, collectModules, existModules, modulecmd) to stderr, to localize where load/enter/printenv spend their time. Uses bash EPOCHREALTIME (>=5) or GNU date; no overhead when unset.
BITS_TIMING showed collectModules was the whole cost of load/enter/printenv (~4.6s) -- even incremental, its O(N) per-package stat walk + prune + rmdir is slow on a filesystem with high per-stat latency (install dirs are symlinks into the hashed store). It ran on every load even when no package had changed. Add a stamp file (.bits_sync_stamp) written after each successful sync, and skip the entire walk when a single shallow 'find -newer' (the same ~0.02s traversal as a bare find) reports nothing newer under the install tree. -maxdepth 2 covers the arch dir and per-package dirs, so package add/remove and version re-symlinks all bump a checked mtime and re-trigger the sync. Repeat load/enter/printenv now ~0.003s instead of 4.6s; the first call after a build still does one full sync. Also adds startup/archdetect BITS_TIMING marks.
…utput Arch autodetection is now MODULES-first: when -a is not given, pick from the installed MODULES/<arch> trees directly. A single installed arch (the common case) is used with no bitsBuild python spawn, reclaiming the ~0.29s archdetect phase seen under BITS_TIMING. Python is only spawned to disambiguate several installed arches (prefer the host's) or when nothing is installed yet. -q (which already unsets VERBOSE) now also exports MODULES_VERBOSITY=silent, so setenv/printenv/load/enter no longer emit environment-modules' 'Loading <pkg>' / 'Loading requirement:' lines -- output can be captured/parsed cleanly. Help updated for enter/setenv.
|
Sorry, @akritkbehera, I didn't see your comment before merging. I do not see such a problem in my builds. I normally derive the version from the tag, like: package: ROOT Where do you set the version in your recipe? |
This I do not understand, can you clarify? |
bits: concurrent-build scheduling, graceful CPU prioritisation, repository providers, recipe/provenance features, and a docs overhaul
Summary
This branch brings
bitsfrom serial, single-recipe-repo builds to a parallel, resource-aware build system with pluggable recipe repositories, richer recipe/provenance features, and a rewritten documentation set.44 commits; ~5k lines added across
bits_helpers/with matching test coverage.Highlights
1. Concurrent build scheduling and resource awareness (
--builders)--builders Nbuilds N packages at once through the built-in scheduler, respecting the dependency graph.$JOBS: each package's parallel-compile budget is divided across the builders (effective_jobs) so the concurrent jobs together never oversubscribe the machine's CPU/load.mem_per_job(peak RSS per compile process) andmem_utilisation; the scheduler'sResourceManageradmits builds so total memory stays within budget.--resource-monitoring) and auto-loaded on the next run to schedule with up-to-date estimates.--parallel-sourcesfetches multiplesources:URLs concurrently.2. Graceful CPU prioritisation:
--build-niceladder + straggler watchdog--build-nice(on by default for--builders > 1; opt out with--no-build-nice) staggers concurrent builds across OS priority: one runs at top priority (full speed), the rest are progressively backed off, and the freed top slot is taken over as builds finish — so contention degrades gracefully instead of all builds fighting equally. Native builds usenice;--docker/podman builds use the cgroup-equivalent--cpu-shares.--build-nice-boost-afterseconds (default 600):--dockerbuilds: each build runs in a named container; the watchdog finds the long-running compiler back-end withdocker exec … psand renices it viadocker exec --user 0 … renice(run as root inside the container, so it can raise priority without host privilege). If the image lacksps, it warns once and disables that path — see operational notes.3. Scheduler robustness
SystemExit(asdieOnErrordoes) was not caught by the worker'sexcept Exception, killing the worker silently and leaving the job stuck "running" forever so the build never finished and printed no summary. Workers now catchBaseException, and the master loop has a bounded wait + watchdog that fails stuck jobs if all workers exit — guaranteeing the build always finishes with a summary.4. Build-error interception and diagnostics
$PIPESTATUSafter the test had clobbered it, so every failure exited1. It now captures${PIPESTATUS[0]}first, preserving the real failing code at all three recipe-run sites.BUILD FAILEDnow includes the matched high-signal error lines (compiler/linker/cmake/make/python) plus the log tail, so triage no longer means opening and grepping the full log.5. Repository providers
provides_repository: truepulls an entire additional recipe repository from git at dependency-resolution time and adds it toBITS_PATH, enabling modular and nested recipe sets.always_load: trueclones it unconditionally at startup;repository_positionand--provider-policycontrol search order (with prepend downgraded unless explicitly granted, to prevent recipe-controlled PATH hijacking). The provider's commit hash folds into every dependent's build hash for reproducibility.bits build PKGwith no local recipe dir clonesbits-providers(default or--organisation <org>) and the recipe repo it points to. Provider repo version is selectable via the recipe'stag:or an@tagsuffix on the providers URL.6. Recipe authoring and defaults-profile features
%(VAR)s-style placeholders resolved from the active defaults profile, with soft expansion so unknown placeholders don't hard-fail.requires(name = version, with optional:matcher), in addition to the existingdefaults-*.shoverrides:mechanism (which can also settag/source).%(name)s/%(version)ssubstitution insources:URLs and patch names.PKG_CONFIG_PATHso the build environment mirrors each package's runtime modulefile; shared arch-detection helper; per-defaultappend_arch.7. Source checkout and patching (
workarea)patches:after source preparation (patch --batchto avoid interactive reversal).--strip-components) bugs.8. Provenance and integrity
sources,patches, and expandedvariablesfor each package; builds can be replayed with--from-manifest.--check/enforce/print/write-checksumsplus per-profile/per-recipe checksum policy; store integrity verification of recalled tarballs.9. Sandbox and miscellaneous robustness
==> Patching PKG@VERSIONprogress; downloadOSErrorinterception; guard against accidental deletes.10. Documentation
docs/: USERGUIDE, REFERENCE, COOKBOOK, WORKFLOWS, ROADMAP; obsolete mkdocs site and stale pages removed. New material covers--builders/--build-nice, the three version-pinning mechanisms, repository providers, and recipe error-handling pitfalls.New CLI flags (selected)
--builders N--build-nice/--no-build-nice--builders > 1.--build-nice-step N--build-nice-boost-after SECONDS--parallel-sources Nsources:downloads per checkout.--prefetch-workers N--resource-monitoring/--resources FILE--from-manifest FILE--provider-policyBITS_PATHinsertion order.New recipe / defaults fields (selected)
mem_per_job,mem_utilisation,provides_repository,always_load,repository_position;requires:name = version[:matcher]pins;overrides:may setversion/tag/source;%(name)s/%(version)s/profile%(VAR)sexpansion in sources, patches, and recipe bodies.Backward compatibility
--buildersdefaults to 1 (serial), preserving prior behaviour;--build-niceonly engages for--builders > 1.Operational notes
--dockerbuilds requireps(theprocpspackage) in the build image so the watchdog can find the in-container compile; if absent, it warns once and disables in-container renicing for the run (the build is unaffected). Build images used withbits build --dockershould includeprocps.Testing
test_scheduler.py(incl. the SystemExit-hang regression),test_nice_ladder.py(ladder, native renice, dockerexec renice,ps-missing fallback),test_memory.py/test_build_stats.py(builder-aware jobs, resource stats),test_repo_provider.py/test_always_on_providers.py/test_provider_staleness.py,test_manifest.py(schema v3),test_workarea.py(patching/re-extraction),test_utilities.py(version pins, variable expansion).test_sync.pyS3 tests require the optionalbotocoredependency, andtest_build.py::test_coverDoBuildis a slow, fully-mocked coverage test independent of these changes.