|
| 1 | +/* MIT License |
| 2 | +
|
| 3 | +Copyright (c) 2023 Astral Software Inc. |
| 4 | +
|
| 5 | +Permission is hereby granted, free of charge, to any person obtaining a copy |
| 6 | +of this software and associated documentation files (the "Software"), to deal |
| 7 | +in the Software without restriction, including without limitation the rights |
| 8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 9 | +copies of the Software, and to permit persons to whom the Software is |
| 10 | +furnished to do so, subject to the following conditions: |
| 11 | +
|
| 12 | +The above copyright notice and this permission notice shall be included in all |
| 13 | +copies or substantial portions of the Software. |
| 14 | +
|
| 15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 21 | +SOFTWARE. |
| 22 | +*/ |
| 23 | + |
| 24 | +use std::{ |
| 25 | + path::{Path, PathBuf}, |
| 26 | + process::Command, |
| 27 | +}; |
| 28 | + |
| 29 | +use fs_err as fs; |
| 30 | + |
| 31 | +fn main() { |
| 32 | + // The workspace root directory is not available without walking up the tree |
| 33 | + // https://github.com/rust-lang/cargo/issues/3946 |
| 34 | + let workspace_root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).to_path_buf(); |
| 35 | + |
| 36 | + commit_info(&workspace_root); |
| 37 | +} |
| 38 | + |
| 39 | +fn commit_info(workspace_root: &Path) { |
| 40 | + // If not in a git repository, do not attempt to retrieve commit information |
| 41 | + let git_dir = workspace_root.join(".git"); |
| 42 | + if !git_dir.exists() { |
| 43 | + return; |
| 44 | + } |
| 45 | + |
| 46 | + if let Some(git_head_path) = git_head(&git_dir) { |
| 47 | + println!("cargo:rerun-if-changed={}", git_head_path.display()); |
| 48 | + |
| 49 | + let git_head_contents = fs::read_to_string(git_head_path); |
| 50 | + if let Ok(git_head_contents) = git_head_contents { |
| 51 | + // The contents are either a commit or a reference in the following formats |
| 52 | + // - "<commit>" when the head is detached |
| 53 | + // - "ref <ref>" when working on a branch |
| 54 | + // If a commit, checking if the HEAD file has changed is sufficient |
| 55 | + // If a ref, we need to add the head file for that ref to rebuild on commit |
| 56 | + let mut git_ref_parts = git_head_contents.split_whitespace(); |
| 57 | + git_ref_parts.next(); |
| 58 | + if let Some(git_ref) = git_ref_parts.next() { |
| 59 | + let git_ref_path = git_dir.join(git_ref); |
| 60 | + println!("cargo:rerun-if-changed={}", git_ref_path.display()); |
| 61 | + } |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + let output = match Command::new("git") |
| 66 | + .arg("log") |
| 67 | + .arg("-1") |
| 68 | + .arg("--date=short") |
| 69 | + .arg("--abbrev=9") |
| 70 | + .arg("--format=%H %h %cd %(describe)") |
| 71 | + .output() |
| 72 | + { |
| 73 | + Ok(output) if output.status.success() => output, |
| 74 | + _ => return, |
| 75 | + }; |
| 76 | + let stdout = String::from_utf8(output.stdout).unwrap(); |
| 77 | + let mut parts = stdout.split_whitespace(); |
| 78 | + let mut next = || parts.next().unwrap(); |
| 79 | + println!("cargo:rustc-env=PREFLIGIT_COMMIT_HASH={}", next()); |
| 80 | + println!("cargo:rustc-env=PREFLIGIT_COMMIT_SHORT_HASH={}", next()); |
| 81 | + println!("cargo:rustc-env=PREFLIGIT_COMMIT_DATE={}", next()); |
| 82 | + |
| 83 | + // Describe can fail for some commits |
| 84 | + // https://git-scm.com/docs/pretty-formats#Documentation/pretty-formats.txt-emdescribeoptionsem |
| 85 | + if let Some(describe) = parts.next() { |
| 86 | + let mut describe_parts = describe.split('-'); |
| 87 | + println!( |
| 88 | + "cargo:rustc-env=PREFLIGIT_LAST_TAG={}", |
| 89 | + describe_parts.next().unwrap() |
| 90 | + ); |
| 91 | + // If this is the tagged commit, this component will be missing |
| 92 | + println!( |
| 93 | + "cargo:rustc-env=PREFLIGIT_LAST_TAG_DISTANCE={}", |
| 94 | + describe_parts.next().unwrap_or("0") |
| 95 | + ); |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +fn git_head(git_dir: &Path) -> Option<PathBuf> { |
| 100 | + // The typical case is a standard git repository. |
| 101 | + let git_head_path = git_dir.join("HEAD"); |
| 102 | + if git_head_path.exists() { |
| 103 | + return Some(git_head_path); |
| 104 | + } |
| 105 | + if !git_dir.is_file() { |
| 106 | + return None; |
| 107 | + } |
| 108 | + // If `.git/HEAD` doesn't exist and `.git` is actually a file, |
| 109 | + // then let's try to attempt to read it as a worktree. If it's |
| 110 | + // a worktree, then its contents will look like this, e.g.: |
| 111 | + // |
| 112 | + // gitdir: /home/andrew/astral/uv/main/.git/worktrees/pr2 |
| 113 | + // |
| 114 | + // And the HEAD file we want to watch will be at: |
| 115 | + // |
| 116 | + // /home/andrew/astral/uv/main/.git/worktrees/pr2/HEAD |
| 117 | + let contents = fs::read_to_string(git_dir).ok()?; |
| 118 | + let (label, worktree_path) = contents.split_once(':')?; |
| 119 | + if label != "gitdir" { |
| 120 | + return None; |
| 121 | + } |
| 122 | + let worktree_path = worktree_path.trim(); |
| 123 | + Some(PathBuf::from(worktree_path)) |
| 124 | +} |
0 commit comments