Skip to content

Commit a077f50

Browse files
committed
fix: align build and release versioning
1 parent 3106009 commit a077f50

5 files changed

Lines changed: 180 additions & 31 deletions

File tree

.github/workflows/ci.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ jobs:
4242
with:
4343
target: ${{ matrix.target }}
4444

45+
- name: Derive build version
46+
id: version
47+
shell: bash
48+
run: |
49+
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
50+
VERSION="${GITHUB_REF#refs/tags/}"
51+
else
52+
VERSION="${GITHUB_SHA::7}"
53+
fi
54+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
55+
echo "DSVIEW_BUILD_VERSION=$VERSION" >> "$GITHUB_ENV"
56+
4557
- name: Build workspace
4658
run: cargo build --release --target ${{ matrix.target }}
4759

@@ -75,6 +87,6 @@ jobs:
7587
uses: ./.github/actions/package-and-validate
7688
with:
7789
target: ${{ matrix.target }}
78-
version: v0.1.0-ci
90+
version: ${{ steps.version.outputs.version }}
7991
exe-path: ${{ steps.exe-path.outputs.exe }}
8092
runtime-path: ${{ steps.find-runtime.outputs.runtime-lib }}

.github/workflows/release.yml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ jobs:
4545
- name: Extract version from tag
4646
id: version
4747
shell: bash
48-
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
48+
run: |
49+
VERSION="${GITHUB_REF#refs/tags/}"
50+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
51+
echo "DSVIEW_BUILD_VERSION=$VERSION" >> "$GITHUB_ENV"
4952
5053
- name: Build workspace
5154
run: cargo build --release --target ${{ matrix.target }}
@@ -83,14 +86,6 @@ jobs:
8386
version: ${{ steps.version.outputs.version }}
8487
exe-path: ${{ steps.exe-path.outputs.exe }}
8588
runtime-path: ${{ steps.find-runtime.outputs.runtime-lib }}
86-
87-
- name: Upload release bundle
88-
uses: actions/upload-artifact@v4
89-
with:
90-
name: dsview-cli-${{ steps.version.outputs.version }}-${{ matrix.target }}
91-
path: dsview-cli-${{ steps.version.outputs.version }}-${{ matrix.target }}.tar.gz
92-
retention-days: 90
93-
9489
publish-release:
9590
name: publish-release
9691
needs: build-release

crates/dsview-cli/build.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use std::env;
2+
use std::fs;
3+
use std::path::{Path, PathBuf};
4+
use std::process::Command;
5+
6+
fn main() {
7+
println!("cargo:rerun-if-env-changed=DSVIEW_BUILD_VERSION");
8+
9+
if let Some(workspace_root) = workspace_root() {
10+
emit_git_rerun_hints(&workspace_root);
11+
}
12+
13+
let version = env::var("DSVIEW_BUILD_VERSION")
14+
.ok()
15+
.filter(|value| !value.trim().is_empty())
16+
.or_else(detect_git_version)
17+
.unwrap_or_else(|| env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION should be set"));
18+
19+
println!("cargo:rustc-env=DSVIEW_BUILD_VERSION={version}");
20+
}
21+
22+
fn workspace_root() -> Option<PathBuf> {
23+
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").ok()?);
24+
manifest_dir.parent()?.parent().map(Path::to_path_buf)
25+
}
26+
27+
fn emit_git_rerun_hints(workspace_root: &Path) {
28+
let git_metadata = workspace_root.join(".git");
29+
println!("cargo:rerun-if-changed={}", git_metadata.display());
30+
31+
if git_metadata.is_dir() {
32+
emit_git_dir_rerun_hints(&git_metadata);
33+
return;
34+
}
35+
36+
if git_metadata.is_file() {
37+
if let Ok(contents) = fs::read_to_string(&git_metadata) {
38+
if let Some(git_dir) = parse_gitdir_path(&git_metadata, &contents) {
39+
emit_git_dir_rerun_hints(&git_dir);
40+
}
41+
}
42+
}
43+
}
44+
45+
fn emit_git_dir_rerun_hints(git_dir: &Path) {
46+
let head_path = git_dir.join("HEAD");
47+
println!("cargo:rerun-if-changed={}", head_path.display());
48+
49+
let refs_path = git_dir.join("refs");
50+
println!("cargo:rerun-if-changed={}", refs_path.display());
51+
52+
let packed_refs_path = git_dir.join("packed-refs");
53+
println!("cargo:rerun-if-changed={}", packed_refs_path.display());
54+
55+
if let Ok(head_contents) = fs::read_to_string(&head_path) {
56+
if let Some(reference) = head_contents.strip_prefix("ref: ") {
57+
println!(
58+
"cargo:rerun-if-changed={}",
59+
git_dir.join(reference.trim()).display()
60+
);
61+
}
62+
}
63+
}
64+
65+
fn parse_gitdir_path(git_metadata: &Path, contents: &str) -> Option<PathBuf> {
66+
let git_dir = contents.strip_prefix("gitdir: ")?.trim();
67+
let git_dir_path = Path::new(git_dir);
68+
69+
if git_dir_path.is_absolute() {
70+
Some(git_dir_path.to_path_buf())
71+
} else {
72+
git_metadata
73+
.parent()
74+
.map(|parent| parent.join(git_dir_path))
75+
}
76+
}
77+
78+
fn detect_git_version() -> Option<String> {
79+
let workspace_root = workspace_root()?;
80+
81+
git_output(&workspace_root, &["describe", "--tags", "--exact-match"])
82+
.or_else(|| git_output(&workspace_root, &["rev-parse", "--short", "HEAD"]))
83+
}
84+
85+
fn git_output(workspace_root: &Path, args: &[&str]) -> Option<String> {
86+
let output = Command::new("git")
87+
.args(args)
88+
.current_dir(workspace_root)
89+
.output()
90+
.ok()?;
91+
92+
if !output.status.success() {
93+
return None;
94+
}
95+
96+
let value = String::from_utf8(output.stdout).ok()?;
97+
let trimmed = value.trim();
98+
if trimmed.is_empty() {
99+
None
100+
} else {
101+
Some(trimmed.to_string())
102+
}
103+
}

crates/dsview-cli/src/main.rs

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@ use std::time::Duration;
55

66
use clap::{Args, Parser, Subcommand, ValueEnum};
77
use dsview_core::{
8-
resolve_capture_artifact_paths, AcquisitionSummary, AcquisitionTerminalEvent,
9-
BringUpError, CaptureArtifactPathError, CaptureCleanup, CaptureCompletion,
10-
CaptureConfigRequest, CaptureExportError, CaptureRunError, CaptureRunRequest, Discovery,
11-
NativeErrorCode, RuntimeError, SelectionHandle, SupportedDevice, describe_native_error,
8+
describe_native_error, resolve_capture_artifact_paths, AcquisitionSummary,
9+
AcquisitionTerminalEvent, BringUpError, CaptureArtifactPathError, CaptureCleanup,
10+
CaptureCompletion, CaptureConfigRequest, CaptureExportError, CaptureRunError,
11+
CaptureRunRequest, Discovery, NativeErrorCode, RuntimeError, SelectionHandle, SupportedDevice,
1212
};
1313
use serde::Serialize;
1414

15+
const BUILD_VERSION: &str = match option_env!("DSVIEW_BUILD_VERSION") {
16+
Some(version) => version,
17+
None => env!("CARGO_PKG_VERSION"),
18+
};
19+
1520
#[derive(Parser, Debug)]
21+
#[command(version = BUILD_VERSION)]
1622
#[command(name = "dsview-cli")]
1723
#[command(about = "Scriptable DSLogic bring-up CLI")]
1824
struct Cli {
@@ -223,8 +229,10 @@ fn run_open(args: OpenArgs) -> Result<(), FailedCommand> {
223229
}
224230

225231
fn run_capture(args: CaptureArgs) -> Result<(), FailedCommand> {
226-
let artifact_paths = resolve_capture_artifact_paths(&args.output, args.metadata_output.as_ref())
227-
.map_err(|error| command_error(args.runtime.format, classify_artifact_path_error(&error)))?;
232+
let artifact_paths =
233+
resolve_capture_artifact_paths(&args.output, args.metadata_output.as_ref()).map_err(
234+
|error| command_error(args.runtime.format, classify_artifact_path_error(&error)),
235+
)?;
228236
let discovery = connect_runtime(&args.runtime)?;
229237
let handle = SelectionHandle::new(args.handle)
230238
.ok_or_else(|| command_error(args.runtime.format, invalid_handle_error()))?;
@@ -239,12 +247,14 @@ fn run_capture(args: CaptureArgs) -> Result<(), FailedCommand> {
239247
wait_timeout: Duration::from_millis(args.wait_timeout_ms),
240248
poll_interval: Duration::from_millis(args.poll_interval_ms),
241249
};
242-
let validated_config = discovery.validate_capture_config(&run_request.config).map_err(|error| {
243-
command_error(
244-
args.runtime.format,
245-
classify_runtime_error(&RuntimeError::InvalidArgument(error.to_string())),
246-
)
247-
})?;
250+
let validated_config = discovery
251+
.validate_capture_config(&run_request.config)
252+
.map_err(|error| {
253+
command_error(
254+
args.runtime.format,
255+
classify_runtime_error(&RuntimeError::InvalidArgument(error.to_string())),
256+
)
257+
})?;
248258
let result = discovery
249259
.run_capture(&run_request)
250260
.map_err(|error| command_error(args.runtime.format, classify_capture_error(&error)))?;
@@ -255,7 +265,7 @@ fn run_capture(args: CaptureArgs) -> Result<(), FailedCommand> {
255265
vcd_path: artifact_paths.vcd_path,
256266
metadata_path: Some(artifact_paths.metadata_path),
257267
tool_name: env!("CARGO_PKG_NAME").to_string(),
258-
tool_version: env!("CARGO_PKG_VERSION").to_string(),
268+
tool_version: BUILD_VERSION.to_string(),
259269
capture_started_at: std::time::SystemTime::now(),
260270
device_model: "DSLogic Plus".to_string(),
261271
device_stable_id: "dslogic-plus".to_string(),
@@ -297,7 +307,10 @@ fn classify_artifact_path_error(error: &CaptureArtifactPathError) -> ErrorRespon
297307
match error {
298308
CaptureArtifactPathError::InvalidVcdExtension { path } => ErrorResponse {
299309
code: "capture_output_path_invalid",
300-
message: format!("VCD output path `{}` must use the .vcd extension", path.display()),
310+
message: format!(
311+
"VCD output path `{}` must use the .vcd extension",
312+
path.display()
313+
),
301314
detail: None,
302315
native_error: None,
303316
terminal_event: None,
@@ -726,9 +739,7 @@ pub(crate) fn capture_success_text(
726739
vcd_path: &str,
727740
metadata_path: &str,
728741
) -> String {
729-
format!(
730-
"capture {completion}\nvcd {vcd_path}\nmetadata {metadata_path}"
731-
)
742+
format!("capture {completion}\nvcd {vcd_path}\nmetadata {metadata_path}")
732743
}
733744

734745
fn render_error(format: OutputFormat, error: &ErrorResponse) {
@@ -997,7 +1008,10 @@ mod tests {
9971008
completion_name(CaptureCompletion::CleanSuccess),
9981009
"clean_success"
9991010
);
1000-
assert_eq!(completion_name(CaptureCompletion::RunFailure), "run_failure");
1011+
assert_eq!(
1012+
completion_name(CaptureCompletion::RunFailure),
1013+
"run_failure"
1014+
);
10011015
assert_eq!(completion_name(CaptureCompletion::Detached), "detach");
10021016
assert_eq!(completion_name(CaptureCompletion::Timeout), "timeout");
10031017
}

crates/dsview-cli/tests/devices_cli.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ fn cli_command() -> Command {
55
Command::cargo_bin("dsview-cli").expect("dsview-cli binary should build for CLI tests")
66
}
77

8+
fn expected_build_version() -> &'static str {
9+
match option_env!("DSVIEW_BUILD_VERSION") {
10+
Some(version) => version,
11+
None => env!("CARGO_PKG_VERSION"),
12+
}
13+
}
14+
15+
#[test]
16+
fn version_flag_reports_the_resolved_build_version() {
17+
cli_command()
18+
.arg("--version")
19+
.assert()
20+
.success()
21+
.stdout(predicate::str::contains(expected_build_version()));
22+
}
23+
824
#[test]
925
fn devices_help_does_not_expose_runtime_selection_flags() {
1026
cli_command()
@@ -25,7 +41,9 @@ fn devices_list_help_keeps_resource_override_only() {
2541
.assert()
2642
.success()
2743
.stdout(predicate::str::contains("--resource-dir <PATH>"))
28-
.stdout(predicate::str::contains("bundled resources are used by default"))
44+
.stdout(predicate::str::contains(
45+
"bundled resources are used by default",
46+
))
2947
.stdout(predicate::str::contains("--library").not())
3048
.stdout(predicate::str::contains("--use-source-runtime").not());
3149
}
@@ -45,10 +63,17 @@ fn devices_open_help_keeps_resource_override_only() {
4563
#[test]
4664
fn devices_list_rejects_removed_library_flag() {
4765
cli_command()
48-
.args(["devices", "list", "--library", "runtime/libdsview_runtime.so"])
66+
.args([
67+
"devices",
68+
"list",
69+
"--library",
70+
"runtime/libdsview_runtime.so",
71+
])
4972
.assert()
5073
.failure()
51-
.stderr(predicate::str::contains("unexpected argument '--library' found"));
74+
.stderr(predicate::str::contains(
75+
"unexpected argument '--library' found",
76+
));
5277
}
5378

5479
#[test]

0 commit comments

Comments
 (0)