assemble_core/dependencies/
project_dependency.rs1use crate::dependencies::{
4 AcquisitionError, Dependency, DependencyType, Registry, ResolvedDependency,
5 ResolvedDependencyBuilder,
6};
7use crate::flow::shared::Artifact;
8use crate::identifier::{Id, InvalidId};
9use crate::plugins::Plugin;
10use crate::prelude::ProjectId;
11use crate::project::buildable::{Buildable, BuildableObject, GetBuildable};
12use crate::project::error::ProjectResult;
13use crate::project::GetProjectId;
14use crate::resources::{ProjectResourceExt, ResourceLocation};
15use crate::Project;
16use crate::__export::TaskId;
17use crate::plugins::PluginAware;
18
19use itertools::Itertools;
20use once_cell::sync::Lazy;
21use regex::Regex;
22use std::collections::HashSet;
23use std::fmt::Debug;
24use std::path::Path;
25
26use crate::project::shared::SharedProject;
27use url::Url;
28
29pub trait CreateProjectDependencies {
31 fn project<S: AsRef<str>>(&self, path: S) -> ProjectDependency;
33 fn project_with<P: AsRef<str>, C: AsRef<str>>(&self, path: P, config: C) -> ProjectDependency;
35}
36
37impl CreateProjectDependencies for Project {
38 fn project<S: AsRef<str>>(&self, path: S) -> ProjectDependency {
39 ProjectDependency {
40 parent: self.as_shared(),
41 location: ResourceLocation::find(self.id(), path.as_ref(), None)
42 .expect("no project found"),
43 }
44 }
45
46 fn project_with<P: AsRef<str>, C: AsRef<str>>(&self, path: P, config: C) -> ProjectDependency {
47 ProjectDependency {
48 parent: self.as_shared(),
49 location: ResourceLocation::find(self.id(), path.as_ref(), config.as_ref())
50 .expect("no project found"),
51 }
52 }
53}
54
55impl CreateProjectDependencies for SharedProject {
56 fn project<S: AsRef<str>>(&self, path: S) -> ProjectDependency {
57 ProjectDependency {
58 parent: self.clone(),
59 location: ResourceLocation::find(&self.project_id(), path.as_ref(), None)
60 .expect("no project found"),
61 }
62 }
63
64 fn project_with<P: AsRef<str>, C: AsRef<str>>(&self, path: P, config: C) -> ProjectDependency {
65 ProjectDependency {
66 parent: self.clone(),
67 location: ResourceLocation::find(&self.project_id(), path.as_ref(), config.as_ref())
68 .expect("no project found"),
69 }
70 }
71}
72
73#[derive(Debug)]
74pub struct ProjectDependency {
75 parent: SharedProject,
76 location: ResourceLocation,
77}
78
79impl Buildable for ProjectDependency {
80 fn get_dependencies(&self, project: &Project) -> ProjectResult<HashSet<TaskId>> {
81 let location = self.location.clone();
82 self.parent.with(|p| {
83 let resource = p
84 .get_resource(location)
85 .map_err(|e| AcquisitionError::custom(e.to_string()))
86 .unwrap();
87
88 match resource.buildable() {
89 None => Ok(HashSet::new()),
90 Some(buildable) => buildable.get_dependencies(project),
91 }
92 })
93 }
94}
95
96impl GetBuildable for ProjectDependency {
97 fn as_buildable(&self) -> BuildableObject {
98 let location = self.location.clone();
99 self.parent.with(|p| {
100 let resource = p
101 .get_resource(location)
102 .map_err(|e| AcquisitionError::custom(e.to_string()))
103 .unwrap();
104
105 match resource.buildable() {
106 None => BuildableObject::None,
107 Some(buildable) => BuildableObject::from(buildable),
108 }
109 })
110 }
111}
112
113impl Dependency for ProjectDependency {
114 fn id(&self) -> String {
115 format!("{}", self.location.project())
116 }
117
118 fn dep_type(&self) -> DependencyType {
119 PROJECT_DEPENDENCY_TYPE.clone()
120 }
121
122 fn try_resolve(
123 &self,
124 _: &dyn Registry,
125 _: &Path,
126 ) -> Result<ResolvedDependency, AcquisitionError> {
127 let location = self.location.clone();
128 self.parent.with(|p| {
129 let resource = p
130 .get_resource(location)
131 .map_err(|e| AcquisitionError::custom(e.to_string()))?;
132
133 Ok(ResolvedDependencyBuilder::new(resource).finish())
134 })
135 }
136
137 }
149
150pub static PROJECT_DEPENDENCY_TYPE: Lazy<DependencyType> =
152 Lazy::new(|| DependencyType::new("project", "project_variant_artifact", vec!["*"]));
153
154pub struct ProjectRegistry;
156
157impl ProjectRegistry {
158 fn new() -> Self {
159 Self
160 }
161}
162
163impl Registry for ProjectRegistry {
164 fn url(&self) -> Url {
165 Url::parse("https://localhost:80/").unwrap()
166 }
167
168 fn supported(&self) -> Vec<DependencyType> {
169 vec![PROJECT_DEPENDENCY_TYPE.clone()]
170 }
171}
172
173#[derive(Debug, Default)]
174pub struct ProjectDependencyPlugin;
175
176impl Plugin<Project> for ProjectDependencyPlugin {
177 fn apply_to(&self, project: &mut Project) -> ProjectResult {
178 for sub in project.subprojects() {
179 sub.apply_plugin::<Self>()?;
180 }
181 project.registries_mut(|reg| {
182 reg.add_registry(ProjectRegistry::new());
183 Ok(())
184 })?;
185 Ok(())
186 }
187}
188
189pub static PROJECT_SCHEME: &str = "assemble";
190
191pub fn project_url<P: GetProjectId>(project: &P) -> Url {
192 let id = project.project_id();
193 _project_url(id)
194}
195
196fn _project_url(id: ProjectId) -> Url {
197 let project_as_path = id.iter().join("/");
198 let host = "project.assemble.rs";
199 Url::parse(&format!(
200 "{scheme}://{host}/{path}/",
201 scheme = PROJECT_SCHEME,
202 path = project_as_path
203 ))
204 .unwrap()
205}
206
207pub fn subproject_url<P: GetProjectId>(
208 base_project: &P,
209 path: &str,
210 configuration: impl Into<Option<String>>,
211) -> Result<Url, ProjectUrlError> {
212 static PROJECT_LOCATOR_REGEX: Lazy<Regex> =
213 Lazy::new(|| Regex::new(r"(:{0,2})([a-zA-Z]\w*)").unwrap());
214
215 let project_ptr = if path.is_empty() {
216 base_project.project_id()
217 } else {
218 let mut project_ptr: Option<Id> = None;
219
220 for captures in PROJECT_LOCATOR_REGEX.captures_iter(path) {
221 let mechanism = &captures[1];
222 let id = &captures[2];
223
224 match mechanism {
225 ":" => match project_ptr {
226 None => {
227 project_ptr = Some(Id::new(id)?);
228 }
229 Some(s) => {
230 project_ptr = Some(s.join(id)?);
231 }
232 },
233 "::" => match project_ptr {
234 None => {
235 project_ptr = Some(
236 base_project
237 .parent_id()
238 .ok_or(InvalidId::new("No parent id"))
239 .and_then(|parent| parent.join(id))?,
240 )
241 }
242 Some(s) => {
243 project_ptr = Some(
244 s.parent()
245 .ok_or(InvalidId::new("No parent id"))
246 .and_then(|parent| parent.join(id))?,
247 )
248 }
249 },
250 "" => {
251 match project_ptr {
252 None => project_ptr = Some(base_project.project_id().join(id)?),
253 Some(_) => {
254 panic!("Shouldn't be possible to access a non :: or : access after the first")
255 }
256 }
257 }
258 s => {
259 panic!("{:?} should not be matchable", s)
260 }
261 }
262 }
263 project_ptr.unwrap().into()
264 };
265
266 let output: Url = project_url(&project_ptr);
267
268 if let Some(configuration) = configuration.into() {
269 Ok(output.join(&configuration)?)
270 } else {
271 Ok(output)
272 }
273}
274
275impl GetProjectId for Url {
276 fn project_id(&self) -> ProjectId {
277 if self.scheme() != PROJECT_SCHEME {
278 panic!("only assemble: scheme supported for project id urls")
279 }
280 ProjectId::from_path(self.path()).expect("url is not valid project id")
281 }
282
283 fn parent_id(&self) -> Option<ProjectId> {
284 self.project_id().parent().cloned().map(ProjectId::from)
285 }
286
287 fn root_id(&self) -> ProjectId {
288 let id = self.project_id();
289 id.ancestors().last().cloned().map(ProjectId::from).unwrap()
290 }
291}
292
293#[derive(Debug, thiserror::Error)]
294pub enum ProjectUrlError {
295 #[error("No parent project to resolve non-absolute project path (path = {0:?})")]
296 NoParentProject(String),
297 #[error(transparent)]
298 ParseUrlError(#[from] url::ParseError),
299 #[error(transparent)]
300 InvalidId(#[from] InvalidId),
301 #[error("No project was found")]
302 NoProjectFound,
303}
304
305impl GetProjectId for ProjectId {
306 fn project_id(&self) -> ProjectId {
307 self.clone()
308 }
309
310 fn parent_id(&self) -> Option<ProjectId> {
311 self.parent().cloned().map(ProjectId::from)
312 }
313
314 fn root_id(&self) -> ProjectId {
315 self.ancestors()
316 .last()
317 .cloned()
318 .map(ProjectId::from)
319 .unwrap()
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use std::str::FromStr;
327
328 #[test]
329 fn create_project_url() {
330 let project = Project::temp("root");
331 let url = project_url(&project);
332 println!("url = {}", url);
333
334 assert_eq!(url.scheme(), PROJECT_SCHEME);
335 assert_eq!(url.path(), "/root/");
336 }
337
338 #[test]
339 fn url_as_assemble_project() {
340 let child1 = ProjectId::from_str(":root:child1").unwrap();
341 let _child2 = ProjectId::from_str(":root:child2").unwrap();
342
343 let url = subproject_url(&child1, ":root:child1::child2", None).unwrap();
344
345 println!("url = {}", url);
346 }
347}