Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion bolt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ Run `bolt` with one of the commands described below:

```
Usage:
bolt make <target|target.bolt.json> [--install] [--force-install] [--sbom[=full|with-gpl-sources|optimized]] [--no-sstate] [--key=<key.pem>] [--cert=<cert.pem>]
bolt make <target|target.bolt.json> [--install] [--force-install]
[--sbom[=full|with-gpl-sources|optimized]] [--no-sstate]
[--release] [--key=<key.pem>] [--cert=<cert.pem>]
Build a bolt package using <target>.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
Expand All @@ -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=<key.pem> Sign the package using the given private key (PEM format)
--cert=<cert.pem> Store the given certificate together with the signature (requires --key)

Expand Down
32 changes: 31 additions & 1 deletion bolt/docs/make.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ by using instructions defined in `bolt.json` configuration files.
## Usage

```
bolt make <target|target.bolt.json> [--install] [--force-install] [--sbom[=full|with-gpl-sources|optimized]] [--no-sstate] [--key=<key.pem>] [--cert=<cert.pem>]
bolt make <target|target.bolt.json> [--install] [--force-install]
[--sbom[=full|with-gpl-sources|optimized]] [--no-sstate]
[--release] [--key=<key.pem>] [--cert=<cert.pem>]
```

- `<target>` corresponds to a file named `<target>.bolt.json`. Example: `bolt make myapp` looks for `myapp.bolt.json`, located as described in the [locating bolt.json files](#locating-boltjson-files) section.
Expand All @@ -28,6 +30,7 @@ bolt make <target|target.bolt.json> [--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=\<key.pem\> | 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=\<cert.pem\> | Stores the given certificate together with the signature. The certificate must match the private key. Requires `--key`. |

Expand Down Expand Up @@ -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`
Expand Down
7 changes: 6 additions & 1 deletion bolt/src/PackageConfigBuilder.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

const { writeFileSync } = require('node:fs');
const { exec } = require('./utils.cjs');
const { PackageConfig } = require('./PackageConfig.cjs');

const RELEASE_BRANCH_PREFIX = 'release/';

Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -139,6 +140,10 @@ class PackageConfigBuilder {
return this;
}

create() {
return new PackageConfig(this.data);
}

store(path) {
writeFileSync(path, JSON.stringify(this.data, null, 2));

Expand Down
6 changes: 5 additions & 1 deletion bolt/src/bolt.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ const { fetch, fetchOptions } = require('./fetch.cjs');
function help() {
console.log(`
Usage:
bolt make <target|target.bolt.json> [--install] [--force-install] [--sbom[=full|with-gpl-sources|optimized]] [--no-sstate] [--key=<key.pem>] [--cert=<cert.pem>]
bolt make <target|target.bolt.json> [--install] [--force-install]
[--sbom[=full|with-gpl-sources|optimized]] [--no-sstate]
[--release] [--key=<key.pem>] [--cert=<cert.pem>]
Build a bolt package using <target>.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
Expand All @@ -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=<key.pem> Sign the package using the given private key (PEM format)
--cert=<cert.pem> Store the given certificate together with the signature (requires --key)

Expand Down
25 changes: 22 additions & 3 deletions bolt/src/make.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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`;
Expand All @@ -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(
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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, {
Expand Down
Loading