Skip to content
Open
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
15 changes: 15 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ jobs:
- name: Build
run: nix build ".#packages.${{ matrix.system }}.${{ matrix.package }}"

- name: Verify reported version
if: matrix.package == 'default'
run: |
set -euo pipefail
expected="brainpod $(tr -d '[:space:]' < VERSION)"
if ! reported="$(./result/bin/brainpod --version 2>&1)"; then
echo "::error::could not run the ${{ matrix.system }} binary on ${{ matrix.runner }}: $reported"
exit 1
fi
if [ "$reported" != "$expected" ]; then
echo "::error::binary reports '$reported' but VERSION expects '$expected'"
exit 1
fi
echo "$reported"

- name: Package Unix binary
if: matrix.package == 'default'
run: |
Expand Down
34 changes: 34 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Resolve version from release tag
id: version
env:
TAG: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail
if [ -z "${TAG:-}" ]; then
echo "::error::release tag_name is empty, refusing to build an unversioned release"
exit 1
fi
version="${TAG#v}"
if ! printf '%s' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$'; then
echo "::error::release tag '$TAG' is not a semver version, refusing to ship a wrong version"
exit 1
fi
printf '%s\n' "$version" > VERSION
echo "version=$version" >> "$GITHUB_OUTPUT"

- uses: cachix/install-nix-action@v31
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -59,6 +77,22 @@ jobs:
- name: Build
run: nix build ".#packages.${{ matrix.system }}.${{ matrix.package }}"

- name: Verify reported version
if: matrix.package == 'default'
env:
EXPECTED: brainpod ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
if ! reported="$(./result/bin/brainpod --version 2>&1)"; then
echo "::error::could not run the ${{ matrix.system }} binary on ${{ matrix.runner }}: $reported"
exit 1
fi
if [ "$reported" != "$EXPECTED" ]; then
echo "::error::binary reports '$reported' but the release tag expects '$EXPECTED'"
exit 1
fi
echo "$reported"

- name: Package Unix binary
if: matrix.package == 'default'
run: |
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "brainpod-cli"
version = "0.1.0"
version = "0.0.0"
edition = "2024"

[[bin]]
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,21 @@ Create and deploy:
brainpod resource create --file resources.json --json
brainpod deploy --summary "Configure application resources" --json
```

## Versioning

`brainpod --version` reports the release the binary was built from. The release workflow derives that version from the published git tag, so no version field has to be bumped by hand:

```sh
brainpod --version
brainpod 0.0.6
```

The tag is written to `VERSION`, read by `flake.nix`, and passed to the build as `BRAINPOD_VERSION`. Builds that the release workflow did not stamp report `0.0.0-dev`, suffixed with the commit they were built from when git metadata is available:

```sh
brainpod --version
brainpod 0.0.0-dev+d4ac667a1b2c
```

`Cargo.toml` deliberately stays at `0.0.0`; it is not the release version. Both the build and release workflows run the binary they just built and fail if its reported version disagrees.
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.0.0-dev
63 changes: 63 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
use std::fs;
use std::path::Path;
use std::process::Command;

fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-changed=proto");
println!("cargo:rerun-if-env-changed=BRAINPOD_VERSION");

let version = std::env::var("BRAINPOD_VERSION")
.ok()
.map(|value| value.trim().to_owned())
.filter(|value| !value.is_empty())
.unwrap_or_else(development_version);
println!("cargo:rustc-env=BRAINPOD_VERSION={version}");

let descriptors = protox::compile(
[
"proto/brainpod/tunnel/v1/broker.proto",
Expand All @@ -14,3 +28,52 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

Ok(())
}

/// Version for builds the release workflow did not stamp, carrying the commit
/// it was built from when git metadata is reachable, so a local build is never
/// mistaken for a published release.
fn development_version() -> String {
let base = format!(
"{}-dev",
std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.0.0".to_owned())
);

match commit_sha() {
Some(sha) => format!("{base}+{sha}"),
None => base,
}
}

fn commit_sha() -> Option<String> {
watch_git_head(Path::new(".git"));

let output = Command::new("git")
.args(["rev-parse", "--short=12", "HEAD"])
.output()
.ok()?;
if !output.status.success() {
return None;
}

let sha = String::from_utf8(output.stdout).ok()?.trim().to_owned();
(!sha.is_empty()).then_some(sha)
}

fn watch_git_head(git_dir: &Path) {
let head = git_dir.join("HEAD");
if !head.exists() {
return;
}

println!("cargo:rerun-if-changed={}", head.display());

let Ok(contents) = fs::read_to_string(&head) else {
return;
};
if let Some(reference) = contents.strip_prefix("ref: ") {
println!(
"cargo:rerun-if-changed={}",
git_dir.join(reference.trim()).display()
);
}
}
8 changes: 7 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
overlays = [ (import rust-overlay) ];
eachSystem = nixpkgs.lib.genAttrs supportedSystems;

# The release workflow rewrites ./VERSION from the release tag before
# `nix build`. It is deliberately outside the crane source filter below,
# so stamping a release does not invalidate cargoArtifacts.
version = nixpkgs.lib.head (nixpkgs.lib.splitString "\n" (builtins.readFile ./VERSION));

rustTargetFor =
pkgs:
if pkgs.stdenv.hostPlatform.isWindows then
Expand Down Expand Up @@ -108,7 +113,8 @@
craneLib.buildPackage (
craneArgs
// {
inherit cargoArtifacts;
inherit cargoArtifacts version;
BRAINPOD_VERSION = version;
meta.mainProgram = if pkgs.stdenv.hostPlatform.isWindows then "brainpod.exe" else "brainpod";
}
// pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
Expand Down
2 changes: 1 addition & 1 deletion src/describe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub fn generate(mut root: Command, path: &[String]) -> Result<Value> {

Ok(json!({
"schemaVersion": 1,
"cliVersion": root.get_version().unwrap_or(env!("CARGO_PKG_VERSION")),
"cliVersion": root.get_version().unwrap_or(env!("BRAINPOD_VERSION")),
"command": describe_command(command, &resolved_path),
"globalArguments": global_arguments,
"outputContract": {
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const UPGRADE_URL: &str = "https://console.brainpod.io/onboarding?upgrade=1";
#[derive(Debug, Parser)]
#[command(
name = "brainpod",
version,
version = env!("BRAINPOD_VERSION"),
about = "Manage Brainpod deployments, images, resources, and database tunnels",
after_help = "For machine-readable command metadata, run `brainpod describe --json`."
)]
Expand Down
Loading