fix: git sha for cli
This commit is contained in:
1 parent
d1ead872b1
commit
bfcc111817
3 files changed
+151
-25
No files matched your search
@@ -70,6 +70,18 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
# Build from the tag we're releasing, not the branch the workflow
|
||||||
|
# was dispatched from. Without this the binary's Cargo.toml version
|
||||||
|
# and embedded SHA reflect whatever was on the dispatch branch.
|
||||||
|
ref: refs/tags/${{ github.event.inputs.post }}
|
||||||
|
|
||||||
|
- name: Resolve release commit SHA
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
sha=$(git rev-parse HEAD)
|
||||||
|
echo "DIOXUS_CLI_GIT_SHA=$sha" >> "$GITHUB_ENV"
|
||||||
|
echo "DIOXUS_CLI_GIT_SHA_SHORT=${sha:0:7}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Install openssl on macos
|
- name: Install openssl on macos
|
||||||
if: matrix.platform.os == 'macos-latest'
|
if: matrix.platform.os == 'macos-latest'
|
||||||
|
|||||||
@@ -98,12 +98,19 @@
|
|||||||
);
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages.dioxus-cli = (
|
packages.dioxus-cli =
|
||||||
rustPackage "dioxus-cli" {
|
(rustPackage "dioxus-cli" {
|
||||||
binary = "dx";
|
binary = "dx";
|
||||||
features = [ "no-downloads" ];
|
features = [ "no-downloads" ];
|
||||||
}
|
}).overrideAttrs
|
||||||
);
|
(_: {
|
||||||
|
# The nix sandbox has no .git and no .cargo_vcs_info.json, so
|
||||||
|
# build.rs has nothing to derive the commit from. Pass the
|
||||||
|
# flake's own input rev through the env-var override path.
|
||||||
|
# `self.rev` is set on a clean tree; `dirtyRev` on a dirty one
|
||||||
|
# (and includes a `-dirty` suffix, which build.rs accepts).
|
||||||
|
DIOXUS_CLI_GIT_SHA = inputs.self.rev or inputs.self.dirtyRev or "";
|
||||||
|
});
|
||||||
packages.default = self'.packages.dioxus-cli;
|
packages.default = self'.packages.dioxus-cli;
|
||||||
checks.dioxus-cli = self'.packages.dioxus-cli;
|
checks.dioxus-cli = self'.packages.dioxus-cli;
|
||||||
|
|
||||||
|
|||||||
+128
-21
@@ -1,38 +1,145 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo:rerun-if-env-changed=DIOXUS_CLI_GIT_SHA");
|
println!("cargo:rerun-if-env-changed=DIOXUS_CLI_GIT_SHA");
|
||||||
println!("cargo:rerun-if-env-changed=DIOXUS_CLI_GIT_SHA_SHORT");
|
println!("cargo:rerun-if-env-changed=DIOXUS_CLI_GIT_SHA_SHORT");
|
||||||
println!("cargo:rerun-if-changed=.git/HEAD");
|
|
||||||
println!("cargo:rerun-if-changed=.git/refs");
|
|
||||||
|
|
||||||
let full_hash = std::env::var("DIOXUS_CLI_GIT_SHA").ok().or_else(|| {
|
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
|
||||||
let output = Command::new("git")
|
let git_dir = find_git_dir(&manifest_dir);
|
||||||
.args(["rev-parse", "HEAD"])
|
|
||||||
.output()
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
// Track the files that determine the current commit. Absolute paths are
|
||||||
return None;
|
// required because the build script runs from `packages/cli/`, not the
|
||||||
|
// workspace root where `.git` lives.
|
||||||
|
if let Some(git_dir) = git_dir.as_deref() {
|
||||||
|
emit_rerun(&git_dir.join("HEAD"));
|
||||||
|
emit_rerun(&git_dir.join("packed-refs"));
|
||||||
|
if let Some(target) = head_ref_target(git_dir) {
|
||||||
|
emit_rerun(&git_dir.join(target));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
emit_rerun(&manifest_dir.join(".cargo_vcs_info.json"));
|
||||||
|
|
||||||
let hash = String::from_utf8(output.stdout).ok()?;
|
let full_hash = env_nonempty("DIOXUS_CLI_GIT_SHA")
|
||||||
let hash = hash.trim().to_string();
|
.or_else(|| git_dir.as_deref().and_then(read_head_sha))
|
||||||
|
.or_else(|| read_cargo_vcs_info(&manifest_dir))
|
||||||
if hash.is_empty() {
|
.or_else(|| git_cli_sha(&manifest_dir));
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(hash)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(full_hash) = full_hash {
|
if let Some(full_hash) = full_hash {
|
||||||
|
let full_hash = full_hash.trim();
|
||||||
println!("cargo:rustc-env=DIOXUS_CLI_GIT_SHA={full_hash}");
|
println!("cargo:rustc-env=DIOXUS_CLI_GIT_SHA={full_hash}");
|
||||||
|
|
||||||
let short_hash = std::env::var("DIOXUS_CLI_GIT_SHA_SHORT")
|
let short_hash = env_nonempty("DIOXUS_CLI_GIT_SHA_SHORT")
|
||||||
.ok()
|
|
||||||
.unwrap_or_else(|| full_hash.chars().take(7).collect());
|
.unwrap_or_else(|| full_hash.chars().take(7).collect());
|
||||||
|
|
||||||
println!("cargo:rustc-env=DIOXUS_CLI_GIT_SHA_SHORT={short_hash}");
|
println!("cargo:rustc-env=DIOXUS_CLI_GIT_SHA_SHORT={short_hash}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn env_nonempty(key: &str) -> Option<String> {
|
||||||
|
env::var(key).ok().filter(|s| !s.trim().is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_rerun(path: &Path) {
|
||||||
|
println!("cargo:rerun-if-changed={}", path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walk up from `start` looking for `.git`. Handles the file form used by
|
||||||
|
/// git worktrees and submodules, where `.git` contains `gitdir: <path>`.
|
||||||
|
fn find_git_dir(start: &Path) -> Option<PathBuf> {
|
||||||
|
for dir in start.ancestors() {
|
||||||
|
let candidate = dir.join(".git");
|
||||||
|
let meta = match fs::metadata(&candidate) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
if meta.is_dir() {
|
||||||
|
return Some(candidate);
|
||||||
|
}
|
||||||
|
if meta.is_file() {
|
||||||
|
let contents = fs::read_to_string(&candidate).ok()?;
|
||||||
|
let pointer = contents
|
||||||
|
.lines()
|
||||||
|
.find_map(|l| l.strip_prefix("gitdir:"))?
|
||||||
|
.trim();
|
||||||
|
let resolved = if Path::new(pointer).is_absolute() {
|
||||||
|
PathBuf::from(pointer)
|
||||||
|
} else {
|
||||||
|
dir.join(pointer)
|
||||||
|
};
|
||||||
|
return Some(resolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If HEAD is a symbolic ref (`ref: refs/heads/foo`), return the target path.
|
||||||
|
fn head_ref_target(git_dir: &Path) -> Option<String> {
|
||||||
|
let head = fs::read_to_string(git_dir.join("HEAD")).ok()?;
|
||||||
|
head.strip_prefix("ref:").map(|s| s.trim().to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_head_sha(git_dir: &Path) -> Option<String> {
|
||||||
|
let head = fs::read_to_string(git_dir.join("HEAD")).ok()?;
|
||||||
|
let head = head.trim();
|
||||||
|
|
||||||
|
let Some(target) = head.strip_prefix("ref:").map(str::trim) else {
|
||||||
|
// Detached HEAD: the file holds the SHA directly.
|
||||||
|
return is_sha(head).then(|| head.to_owned());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Loose ref file.
|
||||||
|
if let Ok(sha) = fs::read_to_string(git_dir.join(target)) {
|
||||||
|
let sha = sha.trim();
|
||||||
|
if is_sha(sha) {
|
||||||
|
return Some(sha.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packed refs (after `git gc` / shallow clones, refs only live here).
|
||||||
|
let packed = fs::read_to_string(git_dir.join("packed-refs")).ok()?;
|
||||||
|
for line in packed.lines() {
|
||||||
|
if line.starts_with('#') || line.starts_with('^') || line.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (sha, name) = line.split_once(' ')?;
|
||||||
|
if name == target && is_sha(sha) {
|
||||||
|
return Some(sha.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cargo writes `.cargo_vcs_info.json` next to `Cargo.toml` when publishing,
|
||||||
|
/// recording the source SHA at publish time. This is how `cargo install`
|
||||||
|
/// from crates.io can still report a real commit despite the `.crate`
|
||||||
|
/// tarball having no `.git/`.
|
||||||
|
fn read_cargo_vcs_info(manifest_dir: &Path) -> Option<String> {
|
||||||
|
let contents = fs::read_to_string(manifest_dir.join(".cargo_vcs_info.json")).ok()?;
|
||||||
|
// Cheap targeted parse so we don't pull serde into the build script.
|
||||||
|
// The file is generated by cargo and the field name is stable.
|
||||||
|
let after = contents.split("\"sha1\"").nth(1)?;
|
||||||
|
let sha = after.split('"').nth(1)?.trim();
|
||||||
|
is_sha(sha).then(|| sha.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn git_cli_sha(manifest_dir: &Path) -> Option<String> {
|
||||||
|
// Scope `git` to the manifest dir so we never accidentally pick up a
|
||||||
|
// SHA from a parent repo (e.g. someone's `~` is a git repo).
|
||||||
|
let output = Command::new("git")
|
||||||
|
.arg("-C")
|
||||||
|
.arg(manifest_dir)
|
||||||
|
.args(["rev-parse", "HEAD"])
|
||||||
|
.output()
|
||||||
|
.ok()?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let sha = String::from_utf8(output.stdout).ok()?.trim().to_owned();
|
||||||
|
is_sha(&sha).then_some(sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_sha(s: &str) -> bool {
|
||||||
|
matches!(s.len(), 40 | 64) && s.chars().all(|c| c.is_ascii_hexdigit())
|
||||||
|
}
|
||||||
Reference in new issue
Block a user