summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--mullvad-version/build.rs126
1 files changed, 85 insertions, 41 deletions
diff --git a/mullvad-version/build.rs b/mullvad-version/build.rs
index 08145b0924..d5f3859e98 100644
--- a/mullvad-version/build.rs
+++ b/mullvad-version/build.rs
@@ -44,76 +44,120 @@ fn main() {
.unwrap();
}
-/// Returns the Mullvad product version from the corresponding metadata files,
-/// depending on target platform.
+/// Computes the Mullvad product version using the latest release on the given platform and the git
+/// hash pointed to by `HEAD`. Also triggers a rebuild of this crate when the information becomes
+/// outdated.
fn get_product_version(target: Target) -> String {
let version_file_path = match target {
Target::Android => ANDROID_VERSION_FILE_PATH,
Target::Desktop => DESKTOP_VERSION_FILE_PATH,
};
println!("cargo:rerun-if-changed={version_file_path}");
- let version = fs::read_to_string(version_file_path)
+
+ let release_version = fs::read_to_string(version_file_path)
.unwrap_or_else(|_| panic!("Failed to read {version_file_path}"))
.trim()
.to_owned();
- if let Some(dev_suffix) = get_dev_suffix(target, &version) {
- format!("{version}{dev_suffix}")
- } else {
- version
- }
-}
-
-/// Returns the development suffix for the current build. A build has a development
-/// suffix if the build is not done on a git tag named `product_version`.
-/// This also returns `None` if the `git` command can't run, or the code does
-/// not live in a git repository.
-fn get_dev_suffix(target: Target, product_version: &str) -> Option<String> {
// Compute the expected tag name for the release named `product_version`
let release_tag = match target {
- Target::Android => format!("android/{product_version}"),
- Target::Desktop => product_version.to_owned(),
+ Target::Android => format!("android/{release_version}"),
+ Target::Desktop => release_version.to_owned(),
};
- // Get the git commit hashes for the latest release and current HEAD
- // Return `None` if unable to find the hash for HEAD.
- let head_commit_hash = git_rev_parse_commit_hash("HEAD")?;
- let product_version_commit_hash = git_rev_parse_commit_hash(&release_tag);
+ format!("{release_version}{}", get_suffix(&release_tag))
+}
+
+/// Returns the suffix for the current build. If the build is done on a git tag named
+/// `product_version` or a git repository cannot be found, the suffix is empty. Otherwise,
+/// `-dev-$hash` is appended to the release version.
+fn get_suffix(release_tag: &str) -> String {
+ if !valid_git_repo() {
+ return String::new();
+ };
+ // Rerun this build script on changes to the git ref that affects the build version.
+ // NOTE: This must be kept up to date with the behavior of `git_rev_parse_commit_hash`.
+ rerun_if_git_ref_changed(release_tag);
+ let head_commit_hash =
+ git_rev_parse_commit_hash("HEAD").expect("Failed to run `git rev-parse HEAD^{{commit}}`");
+ let product_version_commit_hash = git_rev_parse_commit_hash(release_tag);
// If we are currently building the release tag, there is no dev suffix
if Some(&head_commit_hash) == product_version_commit_hash.as_ref() {
- return None;
+ String::new()
+ } else {
+ format!("-dev-{}", &head_commit_hash[..GIT_HASH_DEV_SUFFIX_LEN])
}
- Some(format!(
- "-dev-{}",
- &head_commit_hash[..GIT_HASH_DEV_SUFFIX_LEN]
- ))
}
-/// Returns the commit hash for the commit that `git_ref` is pointing to.
+fn valid_git_repo() -> bool {
+ matches!(Command::new("git").arg("status").status(), Ok(status) if status.success())
+}
+
+/// Trigger rebuild of `mullvad-version` on changing branch (`.git/HEAD`), on changes to the ref of
+/// the current branch (`.git/refs/heads/$current_branch`) and on changes to the ref of the current
+/// release tag (`.git/refs/tags/$current_release_tag`).
///
-/// Returns `None` if executing the `git rev-parse` command fails for some reason.
-fn git_rev_parse_commit_hash(git_ref: &str) -> Option<String> {
+/// Returns an error if not in a git repository, or the git binary is not in `PATH`.
+fn rerun_if_git_ref_changed(release_tag: &str) {
let git_dir = Path::new("..").join(".git");
- // If we build our output on information about HEAD we need to re-run if HEAD moves
- if git_ref == "HEAD" {
- let head_path = git_dir.join("HEAD");
- if head_path.exists() {
- println!("cargo:rerun-if-changed={}", head_path.display());
- }
+
+ // The `.git/HEAD` file contains the position of the current head. If in 'detached HEAD' state,
+ // this will be the ref of the current commit. If on a branch it will just point to it, e.g.
+ // `ref: refs/heads/main`. Tracking changes to this file will tell us if we change branch, or
+ // modify the current detached HEAD state (e.g. committing or rebasing).
+ let head_path = git_dir.join("HEAD");
+ if head_path.exists() {
+ println!("cargo:rerun-if-changed={}", head_path.display());
}
- // If we build our output on information about a git reference, we need to re-run
- // if it moves. Instead of trying to be smart, just re-run if any git reference moves.
- let git_refs_dir = git_dir.join("refs");
- if git_refs_dir.exists() {
- println!("cargo:rerun-if-changed={}", git_refs_dir.display());
+
+ // The above check will not cause a rebuild when modifying commits on a currently checked out
+ // branch. To catch this, we need to track the `.git/refs/heads/$current_branch` file.
+ let output = Command::new("git")
+ .arg("branch")
+ .arg("--show-current")
+ .output()
+ .expect("Failed to execute `git branch --show-current`");
+
+ let current_branch = String::from_utf8(output.stdout).unwrap();
+ let current_branch = current_branch.trim();
+
+ // When in 'detached HEAD' state, the output will be empty. However, in that case we already get
+ // the ref from `.git/HEAD`, so we can safely skip this part.
+ if !current_branch.is_empty() {
+ let git_current_branch_ref = git_dir.join("refs").join("heads").join(current_branch);
+ if git_current_branch_ref.exists() {
+ println!(
+ "cargo:rerun-if-changed={}",
+ git_current_branch_ref.display()
+ );
+ }
}
+ // Since the product version depends on if the build is done on the commit with the
+ // corresponding release tag or not, we must track creation of/changes to said tag
+ let git_release_tag_ref = git_dir.join("refs").join("tags").join(release_tag);
+ if git_release_tag_ref.exists() {
+ println!("cargo:rerun-if-changed={}", git_release_tag_ref.display());
+ };
+
+ // NOTE: As the repository has gotten quite large, you may find the contents of the
+ // `.git/refs/heads` and `.git/refs/tags` empty. This happens because `git pack-refs` compresses
+ // and moves the information into the `.git/packed-refs` file to save storage. We do not have to
+ // track this file, however, as any changes to the current branch, 'detached HEAD' state
+ // or tags will update the corresponding `.git/refs` file we are tracking, even if it had
+ // previously been pruned.
+}
+
+/// Returns the commit hash for the commit that `git_ref` is pointing to.
+///
+/// Returns `None` if the git reference cannot be found.
+fn git_rev_parse_commit_hash(git_ref: &str) -> Option<String> {
let output = Command::new("git")
.arg("rev-parse")
.arg(format!("{git_ref}^{{commit}}"))
.output()
- .ok()?;
+ .expect("Failed to run `git rev-parse`");
if !output.status.success() {
return None;
}