Move the git hash lookup to mold-common

elf/build.rs looked up the commit mold is built from and made the ELF
linker's version string from it. The Mach-O linker needs the hash as
well, so mold-common's build script now looks it up and exports it as
mold_common::GIT_HASH. The ELF linker's version string is unchanged.
This commit is contained in:
Rui Ueyama committed 2026-10-09 18:36:53 +09:00
1 parent afd7b70851
commit 96406a27e1
6 files changed
+89 -71

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
//! Finds the git commit that mold is built from, for the linkers' version
//! strings.
use std::path::{Path, PathBuf};
use std::process::Command;
// Embed the current git commit hash into the mold executable. We ask git
// instead of parsing files under .git because the repository format varies
// (e.g. packed refs, reftable). When building from a source tarball, there's
// no .git, and the hash is simply omitted.
fn git_hash(source_dir: &Path) -> Option<String> {
let dot_git = source_dir.join(".git");
if !dot_git.exists() {
// Source archives use the package version. If Git metadata is added
// later, a clean rebuild is needed to embed the new commit hash.
return None;
}
if dot_git.is_file() {
println!("cargo:rerun-if-changed={}", dot_git.display());
}
let git_path = |name: &str| -> Option<PathBuf> {
let output = Command::new("git")
.arg("-C")
.arg(source_dir)
.args(["rev-parse", "--git-path", name])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let path = PathBuf::from(String::from_utf8(output.stdout).ok()?.trim());
Some(if path.is_absolute() { path } else { source_dir.join(path) })
};
let reftable = git_path("reftable/tables.list").filter(|p| p.exists());
if let Some(head) = git_path("HEAD").filter(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", head.display());
if let Ok(contents) = std::fs::read_to_string(&head)
&& let Some(reference) =
contents.strip_prefix("ref: ").map(str::trim).filter(|_| reftable.is_none())
&& let Some(path) = git_path(reference)
{
// A packed reference may acquire a loose file later.
// Watch its nearest existing directory until that happens.
if let Some(path) = path.ancestors().find(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", path.display());
}
}
}
for path in git_path("packed-refs").into_iter().chain(reftable).filter(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", path.display());
}
let output =
Command::new("git").arg("-C").arg(source_dir).args(["rev-parse", "HEAD"]).output().ok()?;
output.status.success().then(|| String::from_utf8_lossy(&output.stdout).trim().to_string())
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// This crate is in common/ of the source tree.
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
if let Some(hash) = git_hash(manifest_dir.parent().unwrap()) {
println!("cargo:rustc-env=MOLD_GIT_HASH={hash}");
}
}
+4
View File
@@ -22,3 +22,7 @@ pub mod util;
pub mod worker_local;
pub use prefetch::prefetch;
/// The git commit that mold is built from, or None if the source tree has
/// no git metadata, as in a source tarball.
pub const GIT_HASH: Option<&str> = option_env!("MOLD_GIT_HASH");
+1 -64
View File
@@ -8,73 +8,10 @@
//! `lto-message.c` adapts the LTO plugin's printf-like diagnostics
//! callback, which is C-variadic as well, and is linked into mold.
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::process::Command;
// Embed the current git commit hash into the mold executable. We ask git
// instead of parsing files under .git because the repository format varies
// (e.g. packed refs, reftable). When building from a source tarball, there's
// no .git, and the hash is simply omitted.
fn git_hash(source_dir: &Path) -> Option<String> {
let dot_git = source_dir.join(".git");
if !dot_git.exists() {
// Source archives use the package version. If Git metadata is added
// later, a clean rebuild is needed to embed the new commit hash.
return None;
}
if dot_git.is_file() {
println!("cargo:rerun-if-changed={}", dot_git.display());
}
let git_path = |name: &str| -> Option<PathBuf> {
let output = Command::new("git")
.arg("-C")
.arg(source_dir)
.args(["rev-parse", "--git-path", name])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let path = PathBuf::from(String::from_utf8(output.stdout).ok()?.trim());
Some(if path.is_absolute() { path } else { source_dir.join(path) })
};
let reftable = git_path("reftable/tables.list").filter(|p| p.exists());
if let Some(head) = git_path("HEAD").filter(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", head.display());
if let Ok(contents) = std::fs::read_to_string(&head)
&& let Some(reference) =
contents.strip_prefix("ref: ").map(str::trim).filter(|_| reftable.is_none())
&& let Some(path) = git_path(reference)
{
// A packed reference may acquire a loose file later.
// Watch its nearest existing directory until that happens.
if let Some(path) = path.ancestors().find(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", path.display());
}
}
}
for path in git_path("packed-refs").into_iter().chain(reftable).filter(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", path.display());
}
let output =
Command::new("git").arg("-C").arg(source_dir).args(["rev-parse", "HEAD"]).output().ok()?;
output.status.success().then(|| String::from_utf8_lossy(&output.stdout).trim().to_string())
}
fn main() {
// This crate is in elf/ of the source tree.
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let source_dir = manifest_dir.parent().unwrap();
let version = std::env::var("CARGO_PKG_VERSION").unwrap();
let version = match git_hash(source_dir) {
Some(hash) => format!("mold {version} ({hash}; compatible with GNU ld)"),
None => format!("mold {version} (compatible with GNU ld)"),
};
println!("cargo:rustc-env=MOLD_VERSION={version}");
println!("cargo:rerun-if-changed=c/mold-wrapper.c");
println!("cargo:rerun-if-changed=c/lto-message.c");
+13 -5
View File
@@ -5,6 +5,7 @@ use std::collections::{HashMap, HashSet};
use std::ffi::{OsStr, OsString};
use std::io::{IsTerminal, Write};
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use bstr::{ByteSlice, ByteVec};
use mold_common::error::strerror;
@@ -237,7 +238,13 @@ Options:
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf64-bigaarch64 elf32-littleriscv elf32-bigriscv elf64-littleriscv elf64-bigriscv elf32-powerpc elf64-powerpc elf64-powerpc elf64-powerpcle elf64-s390 elf64-sparc elf32-m68k elf32-sh-linux elf64-loongarch elf32-loongarch
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64elf aarch64linux aarch64elfb aarch64linuxb elf32lriscv elf32briscv elf64lriscv elf64briscv elf32ppc elf32ppclinux elf64ppc elf64lppc elf64_s390 elf64_sparc m68kelf shlelf_linux shelf_linux elf64loongarch elf32loongarch";
pub const VERSION: &str = env!("MOLD_VERSION");
pub static VERSION: LazyLock<String> = LazyLock::new(|| {
let version = env!("CARGO_PKG_VERSION");
match mold_common::GIT_HASH {
Some(hash) => format!("mold {version} ({hash}; compatible with GNU ld)"),
None => format!("mold {version} (compatible with GNU ld)"),
}
});
#[derive(Clone, Debug, Default)]
pub enum BuildId {
@@ -1326,18 +1333,19 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[Cow<'_, OsStr>]) -> Pars
} else if cursor.read_flag("no-dynamic-linker") {
a.dynamic_linker.clear();
} else if cursor.read_flag("v") {
out!("{VERSION}");
out!("{}", *VERSION);
version_shown = true;
} else if cursor.read_flag("version") {
out!("{VERSION}");
out!("{}", *VERSION);
std::process::exit(0);
} else if cursor.read_flag("V") {
out!(
"{VERSION}\n Supported emulations:\n elf_x86_64\n elf_i386\n aarch64elf\n \
"{}\n Supported emulations:\n elf_x86_64\n elf_i386\n aarch64elf\n \
aarch64linux\n aarch64elfb\n aarch64linuxb\n armelf_linux_eabi\n elf64lriscv\n \
elf64briscv\n elf32lriscv\n elf32briscv\n elf32ppc\n elf64ppc\n elf64lppc\n \
elf64_s390\n elf64_sparc\n m68kelf\n shlelf_linux\n shelf_linux\n \
elf64loongarch\n elf32loongarch"
elf64loongarch\n elf32loongarch",
*VERSION
);
version_shown = true;
} else if read_arg!("mllvm", true) {
+2 -1
View File
@@ -544,7 +544,8 @@ unsafe extern "C" fn get_api_version(
static LINKER_VERSION: OnceLock<CString> = OnceLock::new();
unsafe {
*linker_identifier = c"mold".as_ptr();
*linker_version = LINKER_VERSION.get_or_init(|| CString::new(VERSION).unwrap()).as_ptr();
*linker_version =
LINKER_VERSION.get_or_init(|| CString::new(VERSION.as_str()).unwrap()).as_ptr();
}
if LAPI_V1 <= maximal_api_supported {
HOOKS.lock().unwrap().gcc_api_v1 = true;
+1 -1
View File
@@ -1707,7 +1707,7 @@ pub fn write_repro_file<E: Target>(ctx: &Context<E>) {
write(
&mut tar,
std::path::Path::new("version.txt"),
format!("{}\n", crate::cmdline::VERSION).as_bytes(),
format!("{}\n", *crate::cmdline::VERSION).as_bytes(),
);
// Input files are archived from memory. Their contents are still those