From 173193df7128473db99d39d9065555b1edd851f6 Mon Sep 17 00:00:00 2001 From: Adam Stolcenburg Date: Fri, 12 Jun 2026 17:03:27 +0200 Subject: [PATCH] Add --release option to make command The versionName is now derived using: git describe --tags --dirty --always Ref: #RDKEAPPRT-840 --- bolt/README.md | 6 +++++- bolt/docs/make.md | 32 ++++++++++++++++++++++++++++++- bolt/src/PackageConfigBuilder.cjs | 7 ++++++- bolt/src/bolt.cjs | 6 +++++- bolt/src/make.cjs | 25 +++++++++++++++++++++--- 5 files changed, 69 insertions(+), 7 deletions(-) diff --git a/bolt/README.md b/bolt/README.md index 61355b8..2583594 100644 --- a/bolt/README.md +++ b/bolt/README.md @@ -35,7 +35,9 @@ Run `bolt` with one of the commands described below: ``` Usage: - bolt make [--install] [--force-install] [--sbom[=full|with-gpl-sources|optimized]] [--no-sstate] [--key=] [--cert=] + bolt make [--install] [--force-install] + [--sbom[=full|with-gpl-sources|optimized]] [--no-sstate] + [--release] [--key=] [--cert=] Build a bolt package using .bolt.json, or the given .bolt.json file --install Also installs the package into the Local Package Store --force-install Installs the package, overwriting any existing package with the same name @@ -52,6 +54,8 @@ Usage: Requires SPDX 2.2 format; if problems arise, use with-gpl-sources instead. --no-sstate Disable sstate cache restoration; forces a full rebuild (bitbake targets only) + --release Fail when the repository is not in a release state or any dependency + is not a release package version --key= Sign the package using the given private key (PEM format) --cert= Store the given certificate together with the signature (requires --key) diff --git a/bolt/docs/make.md b/bolt/docs/make.md index e879b5e..50f7d40 100644 --- a/bolt/docs/make.md +++ b/bolt/docs/make.md @@ -8,7 +8,9 @@ by using instructions defined in `bolt.json` configuration files. ## Usage ``` -bolt make [--install] [--force-install] [--sbom[=full|with-gpl-sources|optimized]] [--no-sstate] [--key=] [--cert=] +bolt make [--install] [--force-install] + [--sbom[=full|with-gpl-sources|optimized]] [--no-sstate] + [--release] [--key=] [--cert=] ``` - `` corresponds to a file named `.bolt.json`. Example: `bolt make myapp` looks for `myapp.bolt.json`, located as described in the [locating bolt.json files](#locating-boltjson-files) section. @@ -28,6 +30,7 @@ bolt make [--install] [--force-install] [--sbom[=full| | --force-install | Same as `--install`, but overwrites any existing package with the same name. | | --sbom\[=MODE\] | Generates a SPDX SBOM during the bitbake build. `MODE` is one of `full` (default when no value is given), `with-gpl-sources`, or `optimized`. See [SBOM Generation](#sbom-generation). Only valid for [`bitbake`](#bitbake) targets; using it with a [`direct`](#direct) target causes `bolt make` to fail. | | --no-sstate | Passes `--no-setscene` to `bitbake`, disabling sstate cache restoration so every task is re-executed. Only valid for [`bitbake`](#bitbake) targets; using it with a [`direct`](#direct) target causes `bolt make` to fail. | +| --release | Fails the build when the repository is not in a release state or when any dependency package is not a release version. See [Release Builds](#release-builds). | | --key=\ | Signs the package using the specified RSA private key (PEM format). Produces a [cosign-compatible](https://github.com/rdkcentral/oci-package-spec/blob/main/format.md#signature-manifest) signature manifest inside the bolt package. | | --cert=\ | Stores the given certificate together with the signature. The certificate must match the private key. Requires `--key`. | @@ -163,6 +166,33 @@ Example config using automatic versioning: } ``` +## Release Builds + +When `--release` is passed, `bolt make` verifies that the package being built and all of its +dependencies are proper release versions, and fails the build when they are not. + +A package is considered a release version when the `versionName` in its package config equals +its `version`. For the package being built, `bolt make` derives the `versionName` from +`git describe --tags --dirty --always` when it is not specified explicitly (or is set to +`develop`), so it equals the version exactly when the package is built from a clean working tree +checked out at the version tag. When no tag is reachable, the abbreviated commit hash is used +instead (with a `-dirty` suffix when the working tree has uncommitted changes), and `develop` is +used only when the `.bolt.json` file is not inside a git repository (or the repository has no +commits yet). For dependency packages, the `versionName` embedded in the package is compared; +a package without any `versionName` is treated as a release version. + +The following is verified: + +1. **The repository is in a release state** — the `versionName` of the package being built must + equal its resolved `version`. In practice this means the repository containing the + `.bolt.json` file is checked out at a tag matching the package version and has no + uncommitted changes. +2. **All dependencies are release versions** — every package from the + [dependency list](#dependency-handling) must be a release version. The error reports the + first detected non-release package together with its version name. This check applies to + [`bitbake`](#bitbake) targets only; [`direct`](#direct) targets do not resolve dependencies + during the build, so only the repository state is verified for them. + ## SBOM Generation When `--sbom` is passed (bitbake targets only), `bolt make` writes a single `sbom.conf` diff --git a/bolt/src/PackageConfigBuilder.cjs b/bolt/src/PackageConfigBuilder.cjs index 45cf017..c2b6002 100644 --- a/bolt/src/PackageConfigBuilder.cjs +++ b/bolt/src/PackageConfigBuilder.cjs @@ -19,6 +19,7 @@ const { writeFileSync } = require('node:fs'); const { exec } = require('./utils.cjs'); +const { PackageConfig } = require('./PackageConfig.cjs'); const RELEASE_BRANCH_PREFIX = 'release/'; @@ -66,7 +67,7 @@ class PackageConfigBuilder { updateVersionNameIfNotSpecified(repoPath) { if (!this.originalVersionName || this.originalVersionName === "develop") { - this.versionName = exec('git describe --dirty 2>/dev/null || echo develop', { cwd: repoPath }).trim(); + this.versionName = exec('git describe --tags --dirty --always 2>/dev/null || echo develop', { cwd: repoPath }).trim(); this.updateVersionName(); } @@ -139,6 +140,10 @@ class PackageConfigBuilder { return this; } + create() { + return new PackageConfig(this.data); + } + store(path) { writeFileSync(path, JSON.stringify(this.data, null, 2)); diff --git a/bolt/src/bolt.cjs b/bolt/src/bolt.cjs index 410b902..e0487f1 100644 --- a/bolt/src/bolt.cjs +++ b/bolt/src/bolt.cjs @@ -33,7 +33,9 @@ const { fetch, fetchOptions } = require('./fetch.cjs'); function help() { console.log(` Usage: - bolt make [--install] [--force-install] [--sbom[=full|with-gpl-sources|optimized]] [--no-sstate] [--key=] [--cert=] + bolt make [--install] [--force-install] + [--sbom[=full|with-gpl-sources|optimized]] [--no-sstate] + [--release] [--key=] [--cert=] Build a bolt package using .bolt.json, or the given .bolt.json file --install Also installs the package into the Local Package Store --force-install Installs the package, overwriting any existing package with the same name @@ -50,6 +52,8 @@ Usage: Requires SPDX 2.2 format; if problems arise, use with-gpl-sources instead. --no-sstate Disable sstate cache restoration; forces a full rebuild (bitbake targets only) + --release Fail when the repository is not in a release state or any dependency + is not a release package version --key= Sign the package using the given private key (PEM format) --cert= Store the given certificate together with the signature (requires --key) diff --git a/bolt/src/make.cjs b/bolt/src/make.cjs index d9c0bf5..0d14527 100644 --- a/bolt/src/make.cjs +++ b/bolt/src/make.cjs @@ -146,6 +146,7 @@ async function makeCommand(packageAlias, workDir, options) { const packageConfigBuilder = new PackageConfigBuilder(packageConfig); packageConfigBuilder.resolveAutoValues(packageConfigStore.getPath()); + packageConfigBuilder.updateVersionNameIfNotSpecified(packageConfigStore.getPath()); const searchedStoreDirs = []; const packageStore = PackageStore.find(workDir, searchedStoreDirs); @@ -164,6 +165,14 @@ async function makeCommand(packageAlias, workDir, options) { throw new Error(`--no-sstate is only supported for bitbake targets`); } + if (options.release) { + const updatedPackageConfig = packageConfigBuilder.create(); + if (!updatedPackageConfig.isReleaseVersion()) { + throw new Error(`Repository ${packageConfigStore.getPath()} is not in a release state: ` + + `package version is ${updatedPackageConfig.getVersion()} but version name is ${updatedPackageConfig.getVersionName()}`); + } + } + if (packageBoltConfig?.bitbake?.image) { const packageRootfsDir = `${workDir}/${packageConfig.getFullName()}-rootfs`; const packageLayerArchive = `${workDir}/${packageConfig.getFullName()}-layer.tgz`; @@ -177,6 +186,14 @@ async function makeCommand(packageAlias, workDir, options) { assert(last.getFullName() === packageConfig.getFullName()); + if (options.release) { + for (const pkg of packages) { + if (!pkg.isReleaseVersion()) { + throw new Error(`Non-release dependency detected: ${pkg.getFullName()} (version name: ${pkg.getVersionName()})`); + } + } + } + contentFile = packageRootfsDir + ".tgz"; imageTarPath = bitbakeMakeOCIImage(packageBoltConfig.bitbake, options, workDir); const platform = PackageConfig.makePlatformConfigFromOCIImageConfig( @@ -221,9 +238,7 @@ async function makeCommand(packageAlias, workDir, options) { if (contentFile) { const packageConfigPath = `${workDir}/${packageConfig.getFullName()}.json`; - packageConfigBuilder - .updateVersionNameIfNotSpecified(packageConfigStore.getPath()) - .store(packageConfigPath); + packageConfigBuilder.store(packageConfigPath); if (options.sbom) { const imageName = packageBoltConfig.bitbake.image; @@ -323,6 +338,10 @@ exports.makeOptions = { return (result.noSstate = (params.options["no-sstate"] === '')); }, + release(params, result) { + return (result.release = (params.options.release === '')); + }, + "force-install"(params, result) { if (params.options["force-install"] === "") { Object.assign(result, {