taler-rust

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

build.rs (4095B)


      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::env;
     18 use std::fs;
     19 use std::path::{Path, PathBuf};
     20 use std::process::Command;
     21 
     22 fn git(root: &Path, args: &[&str]) -> Result<String, String> {
     23     let output = Command::new("git")
     24         .args(args)
     25         .current_dir(root)
     26         .env_remove("GIT_DIR")
     27         .env_remove("GIT_WORK_TREE")
     28         .env_remove("GIT_COMMON_DIR")
     29         .output()
     30         .map_err(|e| format!("could not run git: {e}"))?;
     31     if !output.status.success() {
     32         return Err(format!(
     33             "git {}: {}",
     34             args.join(" "),
     35             String::from_utf8_lossy(&output.stderr).trim()
     36         ));
     37     }
     38     String::from_utf8(output.stdout)
     39         .map(|value| value.trim().to_owned())
     40         .map_err(|e| format!("invalid Git output: {e}"))
     41 }
     42 
     43 fn watch(path: &Path) {
     44     println!("cargo:rerun-if-changed={}", path.display());
     45 }
     46 
     47 fn git_version(root: &Path) -> Result<String, String> {
     48     // An archive can be unpacked inside another checkout. Only use our own Git
     49     // metadata, including the .git file used by linked worktrees/submodules.
     50     let marker = root.join(".git");
     51     if !marker.exists() {
     52         return Err("source tree has no Git metadata".into());
     53     }
     54     if marker.is_file() {
     55         watch(&marker);
     56     }
     57     let toplevel = git(root, &["rev-parse", "--show-toplevel"])?;
     58     if Path::new(&toplevel)
     59         .canonicalize()
     60         .map_err(|e| e.to_string())?
     61         != root
     62     {
     63         return Err("Git metadata belongs to another source tree".into());
     64     }
     65 
     66     // Resolve paths through Git: worktree HEAD is private, while refs and
     67     // packed-refs live in the common Git directory. Watching all loose refs
     68     // also catches new/deleted tags, even when HEAD itself does not move.
     69     for name in ["HEAD", "refs", "packed-refs", "shallow"] {
     70         let path = PathBuf::from(git(
     71             root,
     72             &["rev-parse", "--path-format=absolute", "--git-path", name],
     73         )?);
     74         // refs exists even when empty. Packing refs removes loose files there,
     75         // which reruns this script and adds the newly created packed-refs file.
     76         if path.exists() || name == "HEAD" {
     77             watch(&path);
     78         }
     79     }
     80     git(root, &["describe", "--tags", "--always", "--abbrev=8"])
     81 }
     82 
     83 fn main() -> Result<(), String> {
     84     let manifest =
     85         PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").ok_or("CARGO_MANIFEST_DIR is missing")?);
     86     let root = manifest
     87         .join("../..")
     88         .canonicalize()
     89         .map_err(|e| format!("could not locate source root: {e}"))?;
     90     watch(&manifest.join("build.rs"));
     91     let version = match git_version(&root) {
     92         Ok(version) => version,
     93         Err(git_error) => {
     94             let path = root.join(".version");
     95             watch(&path);
     96             // Recheck Git availability on later builds of an extracted tree.
     97             watch(&root.join(".git"));
     98             fs::read_to_string(&path).map_err(|e| format!(
     99                 "cannot determine program version: {git_error}; could not read {}: {e}. Source archives must contain a .version file",
    100                 path.display()
    101             ))?
    102         }
    103     };
    104     let version = version.trim();
    105     if version.is_empty() || version.contains(['\n', '\r', '\0']) {
    106         return Err("program version must be a nonempty single line".into());
    107     }
    108     println!("cargo:rustc-env=BUILD_VERSION={version}");
    109     Ok(())
    110 }