taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

build.rs (4085B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use std::{
     18     env, fs,
     19     path::{Path, PathBuf},
     20     process::Command,
     21 };
     22 
     23 fn git(root: &Path, args: &[&str]) -> Result<String, String> {
     24     let output = Command::new("git")
     25         .args(args)
     26         .current_dir(root)
     27         .env_remove("GIT_DIR")
     28         .env_remove("GIT_WORK_TREE")
     29         .env_remove("GIT_COMMON_DIR")
     30         .output()
     31         .map_err(|e| format!("could not run git: {e}"))?;
     32     if !output.status.success() {
     33         return Err(format!(
     34             "git {}: {}",
     35             args.join(" "),
     36             String::from_utf8_lossy(&output.stderr).trim()
     37         ));
     38     }
     39     String::from_utf8(output.stdout)
     40         .map(|value| value.trim().to_owned())
     41         .map_err(|e| format!("invalid Git output: {e}"))
     42 }
     43 
     44 fn watch(path: &Path) {
     45     println!("cargo:rerun-if-changed={}", path.display());
     46 }
     47 
     48 fn git_version(root: &Path) -> Result<String, String> {
     49     // An archive can be unpacked inside another checkout. Only use our own Git
     50     // metadata, including the .git file used by linked worktrees/submodules.
     51     let marker = root.join(".git");
     52     if !marker.exists() {
     53         return Err("source tree has no Git metadata".into());
     54     }
     55     if marker.is_file() {
     56         watch(&marker);
     57     }
     58     let toplevel = git(root, &["rev-parse", "--show-toplevel"])?;
     59     if Path::new(&toplevel)
     60         .canonicalize()
     61         .map_err(|e| e.to_string())?
     62         != root
     63     {
     64         return Err("Git metadata belongs to another source tree".into());
     65     }
     66 
     67     // Resolve paths through Git: worktree HEAD is private, while refs and
     68     // packed-refs live in the common Git directory. Watching all loose refs
     69     // also catches new/deleted tags, even when HEAD itself does not move.
     70     for name in ["HEAD", "refs", "packed-refs", "shallow"] {
     71         let path = PathBuf::from(git(
     72             root,
     73             &["rev-parse", "--path-format=absolute", "--git-path", name],
     74         )?);
     75         // refs exists even when empty. Packing refs removes loose files there,
     76         // which reruns this script and adds the newly created packed-refs file.
     77         if path.exists() || name == "HEAD" {
     78             watch(&path);
     79         }
     80     }
     81     git(root, &["describe", "--tags", "--always", "--abbrev=8"])
     82 }
     83 
     84 fn main() -> Result<(), String> {
     85     let manifest =
     86         PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").ok_or("CARGO_MANIFEST_DIR is missing")?);
     87     let root = manifest
     88         .join("../..")
     89         .canonicalize()
     90         .map_err(|e| format!("could not locate source root: {e}"))?;
     91     watch(&manifest.join("build.rs"));
     92     let version = match git_version(&root) {
     93         Ok(version) => version,
     94         Err(git_error) => {
     95             let path = root.join(".version");
     96             watch(&path);
     97             // Recheck Git availability on later builds of an extracted tree.
     98             watch(&root.join(".git"));
     99             fs::read_to_string(&path).map_err(|e| format!(
    100                 "cannot determine program version: {git_error}; could not read {}: {e}. Source archives must contain a .version file",
    101                 path.display()
    102             ))?
    103         }
    104     };
    105     let version = version.trim();
    106     if version.is_empty() || version.contains(['\n', '\r', '\0']) {
    107         return Err("program version must be a nonempty single line".into());
    108     }
    109     println!("cargo:rustc-env=BUILD_VERSION={version}");
    110     Ok(())
    111 }