cargo_gitv/
build_context.rs

1use std::time::SystemTime;
2
3use anyhow::{anyhow, Context, Result};
4use cargo_metadata::Metadata;
5use chrono::{DateTime, NaiveDateTime, Utc};
6use git2::Repository;
7use semver::Version;
8
9pub struct BuildContext {
10    pub git_repository: Repository,
11    pub cargo_metadata: Metadata,
12    pub git_sha: String,
13    pub git_latest_version: Option<Version>,
14    pub git_commit_timestamp: Option<DateTime<Utc>>,
15    pub cargo_version: Version,
16}
17
18impl BuildContext {
19    pub fn dev_version(&self) -> Result<Version> {
20        let timestamp: DateTime<Utc> = self
21            .git_commit_timestamp
22            .unwrap_or_else(|| SystemTime::now().into());
23        let timestamp_formatted = timestamp.format("%Y%m%d%H%M%S");
24
25        let dev_version = format!(
26            "{}-dev.{}+{}",
27            self.cargo_version, timestamp_formatted, self.git_sha
28        );
29
30        Version::parse(&dev_version).with_context(|| "Failed to generate valid dev version.")
31    }
32}
33
34pub fn load_build_context() -> Result<BuildContext> {
35    let mut cmd = cargo_metadata::MetadataCommand::new();
36    cmd.manifest_path("./Cargo.toml");
37    let cargo_metadata = cmd
38        .exec()
39        .context("Could not find the cargo metadata. Tried ./Cargo.toml")?;
40
41    let git_repository = Repository::open("./")
42      .context("Could not find a git repository. Please run from the top-level folder of a git repository.")?;
43
44    let git_sha = get_git_sha(&git_repository)?;
45    let git_latest_version = get_git_latest_version(&git_repository)?;
46    let cargo_version = get_cargo_version(&cargo_metadata)?;
47    let git_commit_timestamp = get_git_commit_timestamp(&git_repository).ok();
48
49    let build_context = BuildContext {
50        git_repository,
51        cargo_metadata,
52        git_sha,
53        git_latest_version,
54        git_commit_timestamp,
55        cargo_version,
56    };
57
58    Ok(build_context)
59}
60
61fn get_git_sha(git_repository: &Repository) -> Result<String> {
62    let mut sha = git_repository
63        .head()?
64        .target()
65        .ok_or_else(|| anyhow!("Could not determin git commit SHA!"))?
66        .to_string();
67    sha.truncate(7);
68
69    Ok(sha)
70}
71
72fn get_git_latest_version(git_repository: &Repository) -> Result<Option<Version>> {
73    let mut tags: Vec<Version> = git_repository
74        .tag_names(Some("v*"))?
75        .into_iter()
76        .flatten()
77        .flat_map(|v| v.strip_prefix('v'))
78        .flat_map(semver::Version::parse)
79        .collect();
80    tags.sort();
81    tags.reverse();
82
83    let current_release_version = tags.first().cloned();
84
85    Ok(current_release_version)
86}
87
88fn get_git_commit_timestamp(git_repository: &Repository) -> Result<DateTime<Utc>> {
89    let head_ref = git_repository.find_reference("HEAD")?;
90    let head_direct = head_ref.resolve()?;
91    let head_refspec = head_direct.name().expect("invalid name");
92    let head_oid = git_repository.refname_to_id(head_refspec)?;
93    let commit = git_repository.find_commit(head_oid)?;
94    let commit_timestamp = commit.time().seconds();
95    let timestamp: DateTime<Utc> =
96        DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(commit_timestamp, 0), Utc);
97
98    Ok(timestamp)
99}
100
101fn get_cargo_version(cargo_metadata: &Metadata) -> Result<Version> {
102    // TODO: handle cargo workspaces correctly.
103    let root = cargo_metadata.workspace_members.first().unwrap();
104    let package = cargo_metadata
105        .packages
106        .iter()
107        .find(|p| p.id == *root)
108        .ok_or_else(|| anyhow!("Could not determine cargo package version."))?;
109    Ok(package.version.clone())
110}