1use crate::defaults::plugins::BasePlugin;
2use std::cell::RefCell;
3
4use crate::dependencies::dependency_container::ConfigurationHandler;
5
6use crate::dependencies::RegistryContainer;
7
8use crate::file::RegularFile;
9
10use crate::flow::output::VariantHandler;
11use crate::flow::shared::ConfigurableArtifact;
12use crate::identifier::{Id, InvalidId, ProjectId, TaskId, TaskIdFactory};
13use crate::lazy_evaluation::{Prop, Provider};
14use crate::logging::LOGGING_CONTROL;
15use crate::plugins::extensions::{ExtensionAware, ExtensionContainer};
16use crate::plugins::{Plugin, PluginAware, PluginManager};
17
18use crate::task::task_container::TaskContainer;
19use crate::task::AnyTaskHandle;
20use crate::task::{Task, TaskHandle};
21use crate::workspace::WorkspaceDirectory;
22use crate::Workspace;
23use log::debug;
24use once_cell::sync::OnceCell;
25
26use std::collections::HashMap;
27
28use std::fmt::{Debug, Display, Formatter};
29
30use std::ops::{Deref, DerefMut, Not};
31use std::path::{Path, PathBuf};
32
33use parking_lot::{ReentrantMutex, ReentrantMutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
34use std::sync::{Arc, Mutex, TryLockError, Weak};
35use tempfile::TempDir;
36
37pub mod buildable;
38#[cfg(test)]
39pub mod dev;
40pub mod error;
41pub mod finder;
42pub mod requests;
43pub mod shared;
44pub mod variant;
45
46use crate::error::PayloadError;
47use crate::prelude::{Settings, SettingsAware};
48use crate::project::finder::TaskPath;
49pub use error::*;
50use shared::{SharedProject, TrueSharableProject, WeakSharedProject};
51
52pub mod prelude {
53 pub use super::Project;
54 pub use crate::dependencies::project_dependency::CreateProjectDependencies;
55}
56
57#[derive(Debug)]
81pub struct Project {
82 settings: Option<Weak<RwLock<Settings>>>,
83 project_id: ProjectId,
84 task_id_factory: TaskIdFactory,
85 task_container: TaskContainer,
86 workspace: Workspace,
87 build_dir: Prop<PathBuf>,
88 applied_plugins: Vec<String>,
89 variants: VariantHandler,
90 self_reference: OnceCell<WeakSharedProject>,
91 properties: HashMap<String, Option<String>>,
92 default_tasks: Vec<TaskId>,
93 registries: Arc<Mutex<RegistryContainer>>,
94 configurations: ConfigurationHandler,
95 subprojects: HashMap<ProjectId, SharedProject>,
96 parent_project: OnceCell<WeakSharedProject>,
97 root_project: OnceCell<WeakSharedProject>,
98 extensions: ExtensionContainer,
99 plugin_manager: PluginManager<Project>,
100
101 is_root: bool,
102}
103
104impl Drop for Project {
105 fn drop(&mut self) {
106 warn!("dropping project {}", self.project_id);
107 }
108}
109impl Display for Project {
117 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
118 write!(f, "Project {}", self.project_id)
119 }
120}
121
122impl Project {
123 #[doc(hidden)]
124 pub fn temp<'a, I: Into<Option<&'a str>>>(id: I) -> SharedProject {
125 Self::in_dir_with_id(TempDir::new().unwrap().path(), id.into().unwrap_or("root")).unwrap()
126 }
127
128 pub fn new() -> error::Result<SharedProject> {
130 let file = std::env::current_dir().unwrap();
131 let name = file.file_name().unwrap().to_str().unwrap().to_string();
132 Self::in_dir_with_id(file, ProjectId::new(&name).map_err(PayloadError::new)?)
133 }
134
135 pub fn in_dir(path: impl AsRef<Path>) -> error::Result<SharedProject> {
137 let path = path.as_ref();
138 Self::in_dir_with_id(path, path)
139 }
140
141 pub fn with_id<I: TryInto<ProjectId>>(id: I) -> error::Result<SharedProject>
143 where
144 ProjectError: From<<I as TryInto<ProjectId>>::Error>,
145 {
146 Self::in_dir_with_id(std::env::current_dir().unwrap(), id)
147 }
148
149 pub fn in_dir_with_id<Id: TryInto<ProjectId>, P: AsRef<Path>>(
151 path: P,
152 id: Id,
153 ) -> error::Result<SharedProject>
154 where
155 ProjectError: From<<Id as TryInto<ProjectId>>::Error>,
156 {
157 Self::in_dir_with_id_and_root(path, id, None, None)
158 }
159
160 pub fn in_dir_with_id_and_root<Id: TryInto<ProjectId>, P: AsRef<Path>>(
162 path: P,
163 id: Id,
164 root: Option<&SharedProject>,
165 settings: Option<Weak<RwLock<Settings>>>,
166 ) -> error::Result<SharedProject>
167 where
168 ProjectError: From<<Id as TryInto<ProjectId>>::Error>,
169 {
170 let id = id.try_into().map_err(PayloadError::new)?;
171 LOGGING_CONTROL.in_project(id.clone());
172 let factory = TaskIdFactory::new(id.clone());
173 let mut build_dir = Prop::new(id.join("buildDir").map_err(PayloadError::new)?);
174 build_dir
175 .set(path.as_ref().join("build"))
176 .map_err(PayloadError::new)?;
177 let registries = Arc::new(Mutex::new(Default::default()));
178 let dependencies = ConfigurationHandler::new(id.clone(), ®istries);
179
180 let project = SharedProject::new_cyclic(|cycle| {
181 let mut project = Self {
182 settings: settings.clone(),
183 project_id: id,
184 task_id_factory: factory.clone(),
185 task_container: TaskContainer::new(factory),
186 workspace: Workspace::new(path),
187 build_dir,
188 applied_plugins: Default::default(),
189 variants: VariantHandler::new(),
190 self_reference: OnceCell::new(),
191 properties: Default::default(),
192 default_tasks: vec![],
193 registries,
194 configurations: dependencies,
195 subprojects: Default::default(),
196 parent_project: OnceCell::new(),
197 root_project: OnceCell::new(),
198 extensions: ExtensionContainer::default(),
199 plugin_manager: PluginManager::default(),
200 is_root: root.is_none(),
201 };
202
203 project.task_container.init(cycle);
204 project.self_reference.set(cycle.clone()).unwrap();
205 if let Some(root) = root {
206 project.root_project.set(root.weak()).unwrap();
207 } else {
208 project.root_project.set(cycle.clone()).unwrap();
209 }
210
211 project
212 });
213
214 project.apply_plugin::<BasePlugin>()?;
215
216 LOGGING_CONTROL.reset();
217 Ok(project)
218 }
219
220 pub fn id(&self) -> &ProjectId {
222 &self.project_id
223 }
224
225 pub fn build_dir(&self) -> impl Provider<PathBuf> + Clone {
227 self.build_dir.clone()
228 }
229
230 pub fn set_build_dir(&mut self, dir: &str) {
232 let dir = self.workspace.dir(dir).unwrap();
233 let path = dir.rel_path();
234 self.build_dir.set(path).unwrap();
235 }
236
237 pub fn registered_tasks(&self) -> Vec<TaskId> {
239 self.task_container
240 .get_tasks()
241 .into_iter()
242 .cloned()
243 .collect()
244 }
245
246 pub fn file<T: AsRef<Path>>(&self, any_value: T) -> error::Result<RegularFile> {
254 let path = any_value.as_ref();
255 debug!("trying to create/get file {:?}", path);
256 self.workspace.create_file(path).map_err(PayloadError::new)
257 }
258
259 pub fn visitor<R, V: VisitProject<R>>(&self, visitor: &mut V) -> R {
261 visitor.visit(self)
262 }
263
264 pub fn visitor_mut<R, V: VisitMutProject<R>>(&mut self, visitor: &mut V) -> R {
266 visitor.visit_mut(self)
267 }
268
269 pub fn project_dir(&self) -> PathBuf {
271 self.workspace.absolute_path()
272 }
273
274 pub fn root_dir(&self) -> PathBuf {
276 self.root_project().with(|p| p.project_dir())
277 }
278
279 pub fn task_container(&self) -> &TaskContainer {
281 &self.task_container
282 }
283
284 pub fn task_container_mut(&mut self) -> &mut TaskContainer {
286 &mut self.task_container
287 }
288
289 pub fn variant(&self, variant: &str) -> Option<impl Provider<ConfigurableArtifact>> {
291 self.variants.get_artifact(variant)
292 }
293
294 pub fn as_shared(&self) -> SharedProject {
296 SharedProject::try_from(self.self_reference.get().unwrap().clone()).unwrap()
297 }
298
299 pub fn task_id_factory(&self) -> &TaskIdFactory {
301 &self.task_id_factory
302 }
303
304 pub fn properties(&self) -> &HashMap<String, Option<String>> {
305 &self.properties
306 }
307
308 pub fn set_property(&mut self, key: String, value: impl Into<Option<String>>) {
309 self.properties.insert(key, value.into());
310 }
311
312 pub fn get_property(&self, key: &str) -> Option<&Option<String>> {
313 self.properties.get(key)
314 }
315
316 pub fn has_property(&self, key: &str) -> bool {
317 self.properties.contains_key(key)
318 }
319
320 pub fn subprojects(&self) -> Vec<&SharedProject> {
322 self.subprojects.values().collect()
323 }
324
325 pub fn default_tasks(&self) -> &Vec<TaskId> {
329 &self.default_tasks
330 }
331
332 pub fn set_default_tasks<I: IntoIterator<Item = TaskId>>(&mut self, iter: I) {
334 self.default_tasks = Vec::from_iter(iter);
335 }
336
337 pub fn registries_mut<F: FnOnce(&mut RegistryContainer) -> ProjectResult>(
339 &mut self,
340 configure: F,
341 ) -> ProjectResult {
342 let mut registries = self.registries.lock().map_err(PayloadError::new)?;
343 configure(registries.deref_mut())
344 }
345
346 pub fn registries<R, F: FnOnce(&RegistryContainer) -> ProjectResult<R>>(
348 &self,
349 configure: F,
350 ) -> ProjectResult<R> {
351 let registries = self.registries.lock().map_err(PayloadError::new)?;
352 configure(registries.deref())
353 }
354
355 pub fn configurations(&self) -> &ConfigurationHandler {
357 &self.configurations
358 }
359
360 pub fn configurations_mut(&mut self) -> &mut ConfigurationHandler {
362 &mut self.configurations
363 }
364
365 pub fn get_subproject<P: AsRef<str>>(&self, project: P) -> error::Result<&SharedProject> {
366 let id = ProjectId::from(self.project_id().join(project).map_err(PayloadError::new)?);
367 self.subprojects
368 .get(&id)
369 .ok_or(ProjectError::NoIdentifiersFound(id.to_string()).into())
370 }
371
372 pub fn subproject<F>(&mut self, name: &str, configure: F) -> ProjectResult
374 where
375 F: FnOnce(&mut Project) -> ProjectResult,
376 {
377 let path = self.project_dir().join(name);
378 self.subproject_in(name, path, configure)
379 }
380
381 pub fn subproject_in<P, F>(&mut self, name: &str, path: P, configure: F) -> ProjectResult
383 where
384 F: FnOnce(&mut Project) -> ProjectResult,
385 P: AsRef<Path>,
386 {
387 let root_shared = self.root_project();
388 let self_shared = self.as_shared();
389 let id = ProjectId::from(self.project_id.join(name).map_err(PayloadError::new)?);
390 let shared = self.subprojects.entry(id.clone()).or_insert_with(|| {
391 Project::in_dir_with_id_and_root(
392 path,
393 id.clone(),
394 Some(&root_shared),
395 self.settings.clone(),
396 )
397 .unwrap()
398 });
399 shared.with_mut(|p| p.parent_project.set(self_shared.weak()).unwrap());
400 shared.with_mut(configure)
401 }
402
403 pub fn root_project(&self) -> SharedProject {
405 SharedProject::try_from(self.root_project.get().unwrap().clone()).unwrap()
406 }
407
408 pub fn parent_project(&self) -> Option<SharedProject> {
410 self.parent_project
411 .get()
412 .and_then(|p| p.clone().upgrade().ok())
413 .map(|s| s.into())
414 }
415
416 pub fn variants(&self) -> &VariantHandler {
418 &self.variants
419 }
420
421 pub fn variants_mut(&mut self) -> &mut VariantHandler {
423 &mut self.variants
424 }
425
426 fn settings(&self) -> Arc<RwLock<Settings>> {
428 self.settings
429 .as_ref()
430 .and_then(|weak| weak.upgrade())
431 .expect("settings not set")
432 }
433}
434
435impl PluginAware for Project {
436 fn plugin_manager(&self) -> &PluginManager<Self> {
437 &self.plugin_manager
438 }
439
440 fn plugin_manager_mut(&mut self) -> &mut PluginManager<Self> {
441 &mut self.plugin_manager
442 }
443}
444
445impl ExtensionAware for Project {
446 fn extensions(&self) -> &ExtensionContainer {
447 &self.extensions
448 }
449
450 fn extensions_mut(&mut self) -> &mut ExtensionContainer {
451 &mut self.extensions
452 }
453}
454
455pub trait VisitProject<R = ()> {
457 fn visit(&mut self, project: &Project) -> R;
459}
460
461pub trait VisitMutProject<R = ()> {
463 fn visit_mut(&mut self, project: &mut Project) -> R;
465}
466
467impl SettingsAware for Project {
468 fn with_settings<F: FnOnce(&Settings) -> R, R>(&self, func: F) -> R {
469 self.settings().with_settings(func)
470 }
471
472 fn with_settings_mut<F: FnOnce(&mut Settings) -> R, R>(&mut self, func: F) -> R {
473 self.settings().with_settings_mut(func)
474 }
475}
476
477pub trait GetProjectId {
478 fn project_id(&self) -> ProjectId;
479 fn parent_id(&self) -> Option<ProjectId>;
480 fn root_id(&self) -> ProjectId;
481
482 fn is_root(&self) -> bool {
484 self.root_id() == self.project_id()
485 }
486}
487
488impl GetProjectId for Project {
489 fn project_id(&self) -> ProjectId {
490 self.id().clone()
491 }
492
493 fn parent_id(&self) -> Option<ProjectId> {
494 self.parent_project().as_ref().map(GetProjectId::project_id)
495 }
496
497 fn root_id(&self) -> ProjectId {
498 self.root_project().project_id()
499 }
500
501 fn is_root(&self) -> bool {
502 self.is_root
503 }
504}
505
506#[cfg(test)]
507mod test {
508 use crate::defaults::tasks::Empty;
509 use crate::logging::init_root_log;
510 use crate::project::Project;
511
512 use crate::project::shared::SharedProject;
513 use crate::workspace::WorkspaceDirectory;
514 use log::LevelFilter;
515 use std::env;
516 use std::path::PathBuf;
517 use tempfile::TempDir;
518
519 #[test]
520 fn create_tasks() {
521 let project = SharedProject::default();
522
523 let mut provider = project.tasks().register_task::<Empty>("arbitrary").unwrap();
524 provider.configure_with(|_, _| Ok(())).unwrap();
525 }
526
527 #[test]
528 fn project_name_based_on_directory() {
529 let path = PathBuf::from("parent_dir/ProjectName");
530 let project = Project::in_dir(path).unwrap();
531
532 assert_eq!(
533 project.with(|p| p.id().clone()).to_string(),
534 ":parent_dir:ProjectName"
535 );
536 }
537
538 #[test]
539 fn create_files_in_project() {
540 init_root_log(LevelFilter::Debug, None);
541 let temp_dir = TempDir::new_in(env::current_dir().unwrap()).unwrap();
542 assert!(temp_dir.path().exists());
543 let project = Project::temp(None);
544 project.with(|project| {
545 let file = project.file("test1").expect("Couldn't make file from &str");
546 assert_eq!(file.path(), project.project_dir().join("test1"));
547 let file = project
548 .file("test2")
549 .expect("Couldn't make file from String");
550 assert_eq!(file.path(), project.project_dir().join("test2"));
551 })
552 }
553}