Skip to main content

leo_package/
dependency.rs

1// Copyright (C) 2019-2026 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use crate::Location;
18use std::fmt::Display;
19
20use serde::{Deserialize, Serialize};
21use std::path::PathBuf;
22
23/// Information about a dependency, as represented in the `program.json` manifest.
24#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
25pub struct Dependency {
26    /// The name of the program. As this corresponds to what appears in `program.json`,
27    /// it should have the ".aleo" suffix.
28    pub name: String,
29    pub location: Location,
30    /// For a local dependency, where is its package? Or, for a test, where is its source file?
31    pub path: Option<PathBuf>,
32    /// For a network dependency, what is its edition?
33    pub edition: Option<u16>,
34    /// For a git dependency, the repository URL and the reference to track.
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub git: Option<GitSource>,
37}
38
39/// The `git` entry of a git dependency: a repository URL and at most one of `branch`/`tag`/`rev`.
40#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
41pub struct GitSource {
42    pub url: String,
43    /// Git branch to track (exclusive with `tag`/`rev`).
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub branch: Option<String>,
46    /// Git tag to pin (exclusive with `branch`/`rev`).
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub tag: Option<String>,
49    /// Git revision to pin (exclusive with `branch`/`tag`).
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub rev: Option<String>,
52}
53
54impl GitSource {
55    /// The [`GitReference`] this source tracks (see [`GitReference::from_opts`]).
56    pub fn reference(&self) -> Result<GitReference, &'static str> {
57        GitReference::from_opts(&self.branch, &self.tag, &self.rev)
58    }
59}
60
61/// The git reference a dependency tracks, derived from its `branch`/`tag`/`rev` fields.
62#[derive(Debug, Clone, Eq, PartialEq, Hash)]
63pub enum GitReference {
64    /// Tip of a named branch. Mutable: re-resolved online each build.
65    Branch(String),
66    /// A tag. Immutable: reused from cache once fetched.
67    Tag(String),
68    /// A revision (commit-ish). Immutable: reused from cache once fetched.
69    Rev(String),
70    /// The repository's default branch. Mutable, like `Branch`.
71    DefaultBranch,
72}
73
74impl GitReference {
75    /// Whether the reference tracks a moving target (a branch tip) and must be re-resolved online.
76    pub fn is_mutable(&self) -> bool {
77        matches!(self, GitReference::Branch(_) | GitReference::DefaultBranch)
78    }
79
80    /// Stable string form stored in `leo.lock`; a change here forces re-resolution.
81    pub fn lock_string(&self) -> String {
82        match self {
83            GitReference::Branch(b) => format!("branch={b}"),
84            GitReference::Tag(t) => format!("tag={t}"),
85            GitReference::Rev(r) => format!("rev={r}"),
86            GitReference::DefaultBranch => "default".to_string(),
87        }
88    }
89
90    /// Build a reference from `branch`/`tag`/`rev`, defaulting to `DefaultBranch`. Errors if more than
91    /// one is set, or if `rev` isn't a commit hash (a symbolic revspec like `HEAD` is not a stable pin).
92    pub fn from_opts(
93        branch: &Option<String>,
94        tag: &Option<String>,
95        rev: &Option<String>,
96    ) -> Result<GitReference, &'static str> {
97        match (branch, tag, rev) {
98            (Some(branch), None, None) => Ok(GitReference::Branch(branch.clone())),
99            (None, Some(tag), None) => Ok(GitReference::Tag(tag.clone())),
100            (None, None, Some(rev)) if is_commit_hash(rev) => Ok(GitReference::Rev(rev.clone())),
101            (None, None, Some(_)) => {
102                Err("a git `rev` must be a commit hash; use `branch` or `tag` for a named reference")
103            }
104            (None, None, None) => Ok(GitReference::DefaultBranch),
105            _ => Err("a git dependency may specify at most one of `branch`, `tag`, or `rev`"),
106        }
107    }
108}
109
110/// Whether `s` looks like a commit hash (hex, 4–64 chars) rather than a symbolic revspec.
111fn is_commit_hash(s: &str) -> bool {
112    (4..=64).contains(&s.len()) && s.bytes().all(|b| b.is_ascii_hexdigit())
113}
114
115impl Display for Dependency {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        write!(f, "{} (on {:?})", self.name, self.location)?;
118        if let Some(path) = &self.path {
119            write!(f, " (at {})", path.display())?;
120        }
121        if let Some(edition) = self.edition {
122            write!(f, " (edition {edition})")?;
123        }
124        if let Some(git) = &self.git {
125            write!(f, " (git {})", git.url)?;
126            if let Some(branch) = &git.branch {
127                write!(f, " (branch {branch})")?;
128            }
129            if let Some(tag) = &git.tag {
130                write!(f, " (tag {tag})")?;
131            }
132            if let Some(rev) = &git.rev {
133                write!(f, " (rev {rev})")?;
134            }
135        }
136        Ok(())
137    }
138}