1use std::collections::{HashMap, HashSet, VecDeque};
2
3use daml_grpc::DamlGrpcClient;
4use daml_grpc::data::package::DamlPackage;
5use daml_grpc::data::{DamlError, DamlResult};
6use daml_lf::element::{
7 DamlAbsoluteTyCon, DamlElementVisitor, DamlNonLocalTyCon, DamlNonLocalValueName, DamlPackage as DamlLfPackage,
8 DamlVisitableElement,
9};
10use daml_lf::{DamlLfArchive, DamlLfArchivePayload, DamlLfHashFunction, DarFile, DarManifest};
11use futures::StreamExt;
12use futures::stream::FuturesUnordered;
13use uuid::Uuid;
14
15#[derive(Debug)]
34pub struct DamlPackages {
35 packages: Vec<DamlPackage>,
36}
37
38impl DamlPackages {
39 pub fn new(packages: Vec<DamlPackage>) -> Self {
40 Self {
41 packages,
42 }
43 }
44
45 pub async fn from_ledger(ledger_client: &DamlGrpcClient) -> DamlResult<Self> {
47 let packages = ledger_client.package_service().list_packages().await?;
48 let handles = packages
49 .iter()
50 .map(|pd| async move { ledger_client.package_service().get_package(pd).await })
51 .collect::<FuturesUnordered<_>>();
52 let all_packages =
53 handles.collect::<Vec<DamlResult<_>>>().await.into_iter().collect::<DamlResult<Vec<DamlPackage>>>()?;
54 Ok(Self::new(all_packages))
55 }
56
57 pub fn find_module(self, module_name: &str) -> DamlResult<String> {
61 self.into_payloads()?
62 .iter()
63 .find(|(_, payload)| payload.contains_module(module_name))
64 .map_or_else(|| Err("package could not be found".into()), |(package_id, _)| Ok((*package_id).clone()))
65 }
66
67 pub fn into_dar(
80 self,
81 main_package_id: Option<&str>,
82 filter_deps: bool,
83 auto_naming_style: ArchiveAutoNamingStyle,
84 ) -> DamlResult<DarFile> {
85 let main_id = self.resolve_main_id(main_package_id)?;
86 let keep = if filter_deps {
87 let payloads: HashMap<String, DamlLfArchivePayload> = self.payloads_by_id()?;
88 let reachable = Self::reachable_from(&main_id, &payloads)?;
89 self.packages.into_iter().filter(|p| reachable.contains(p.hash())).collect()
90 } else {
91 self.packages
92 };
93 let all_archives = Self::packages_to_archives(keep, auto_naming_style)?;
94 Self::archives_to_dar(all_archives, &main_id)
95 }
96
97 pub fn into_archives(self, auto_naming_style: ArchiveAutoNamingStyle) -> DamlResult<Vec<DamlLfArchive>> {
101 Self::packages_to_archives(self.packages, auto_naming_style)
102 }
103
104 fn packages_to_archives(
105 packages: Vec<DamlPackage>,
106 auto_naming_style: ArchiveAutoNamingStyle,
107 ) -> DamlResult<Vec<DamlLfArchive>> {
108 packages
109 .into_iter()
110 .map(|p| {
111 let hash = p.hash().to_owned();
112 let payload = Self::package_into_payload(p)?;
113 let name = match auto_naming_style {
114 ArchiveAutoNamingStyle::Empty => String::default(),
115 ArchiveAutoNamingStyle::Hash => hash.clone(),
116 ArchiveAutoNamingStyle::Uuid => Uuid::new_v4().to_string(),
117 };
118 Ok(DamlLfArchive::new(name, payload, DamlLfHashFunction::Sha256, hash))
119 })
120 .collect()
121 }
122
123 pub fn into_payloads(self) -> DamlResult<Vec<(String, DamlLfArchivePayload)>> {
125 self.packages
126 .into_iter()
127 .map(|p| {
128 let hash = p.hash().to_owned();
129 Self::package_into_payload(p).map(|pl| (hash, pl))
130 })
131 .collect::<DamlResult<Vec<_>>>()
132 }
133
134 fn package_into_payload(package: DamlPackage) -> DamlResult<DamlLfArchivePayload> {
135 DamlLfArchivePayload::from_bytes(package.take_payload()).map_err(|e| DamlError::Other(e.to_string()))
136 }
137
138 fn archives_to_dar(mut all_packages: Vec<DamlLfArchive>, main_id: &str) -> DamlResult<DarFile> {
139 if all_packages.is_empty() {
140 return Err("expected at least one archive".into());
141 }
142 let main_idx = all_packages
143 .iter()
144 .position(|a| a.hash == main_id)
145 .ok_or_else(|| DamlError::Other(format!("main package {main_id} not present in archive set")))?;
146 let first = all_packages.swap_remove(main_idx);
147 let rest = all_packages;
148 let manifest = DarManifest::new_implied(first.name.clone(), rest.iter().map(|n| n.name.clone()).collect());
149 Ok(DarFile::new(manifest, first, rest))
150 }
151
152 fn resolve_main_id(&self, hint: Option<&str>) -> DamlResult<String> {
156 match hint {
157 Some(id) => {
158 if self.packages.iter().any(|p| p.hash() == id) {
159 Ok(id.to_owned())
160 } else {
161 Err(DamlError::Other(format!("main package {id} not present in package set")))
162 }
163 },
164 None => self
165 .packages
166 .first()
167 .map(|p| p.hash().to_owned())
168 .ok_or_else(|| DamlError::Other("expected at least one package".to_owned())),
169 }
170 }
171
172 fn payloads_by_id(&self) -> DamlResult<HashMap<String, DamlLfArchivePayload>> {
173 self.packages
174 .iter()
175 .map(|p| {
176 let hash = p.hash().to_owned();
177 let payload = DamlLfArchivePayload::from_bytes(p.payload().to_vec())
178 .map_err(|e| DamlError::Other(e.to_string()))?;
179 Ok((hash, payload))
180 })
181 .collect()
182 }
183
184 fn reachable_from(root: &str, payloads: &HashMap<String, DamlLfArchivePayload>) -> DamlResult<HashSet<String>> {
191 let mut visited: HashSet<String> = HashSet::new();
192 let mut queue: VecDeque<String> = VecDeque::new();
193 queue.push_back(root.to_owned());
194 while let Some(id) = queue.pop_front() {
195 if !visited.insert(id.clone()) {
196 continue;
197 }
198 let Some(payload) = payloads.get(&id) else {
199 continue;
200 };
201 let refs = collect_referenced_package_ids(payload).map_err(|e| DamlError::Other(e.to_string()))?;
202 for r in refs {
203 if !visited.contains(&r) {
204 queue.push_back(r);
205 }
206 }
207 }
208 Ok(visited)
209 }
210}
211
212fn collect_referenced_package_ids(payload: &DamlLfArchivePayload) -> daml_lf::DamlLfResult<HashSet<String>> {
221 payload.clone().apply(|package: &DamlLfPackage<'_>| {
222 let mut visitor = ReferencedPackagesVisitor {
223 self_package_id: package.package_id().to_owned(),
224 referenced: HashSet::new(),
225 };
226 package.accept(&mut visitor);
227 visitor.referenced
228 })
229}
230
231struct ReferencedPackagesVisitor {
232 self_package_id: String,
233 referenced: HashSet<String>,
234}
235
236impl ReferencedPackagesVisitor {
237 fn record(&mut self, pkg: &str) {
238 if !pkg.is_empty() && pkg != self.self_package_id {
239 self.referenced.insert(pkg.to_owned());
240 }
241 }
242}
243
244impl DamlElementVisitor for ReferencedPackagesVisitor {
245 fn pre_visit_absolute_tycon<'a>(&mut self, abs: &'a DamlAbsoluteTyCon<'a>) {
246 self.record(abs.package_id());
247 }
248
249 fn pre_visit_non_local_tycon<'a>(&mut self, non_local: &'a DamlNonLocalTyCon<'a>) {
250 self.record(non_local.target_package_id());
251 }
252
253 fn pre_visit_non_local_value_name<'a>(&mut self, non_local: &'a DamlNonLocalValueName<'a>) {
254 self.record(non_local.target_package_id());
255 }
256}
257
258#[derive(Clone, Copy, Debug)]
260pub enum ArchiveAutoNamingStyle {
261 Empty,
263 Hash,
265 Uuid,
267}
268
269pub async fn find_module_package_id(ledger_client: &DamlGrpcClient, module_name: &str) -> DamlResult<String> {
273 DamlPackages::from_ledger(ledger_client).await?.find_module(module_name)
274}