1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use cargo_metadata::MetadataCommand;
use kinetics_parser::Package;
use std::path::{Path, PathBuf};
use crate::project::ConfigFile;
// Workspace definition for a project.
//
// A non-workspace project would have one member
// with it's path identical to workspace_root.
//
// Otherwise it's a real workspace.
#[derive(Debug, Default, Clone)]
pub struct Workspace {
pub root_path: PathBuf,
pub packages: Vec<Package>,
}
impl Workspace {
pub fn from_path(path: &Path) -> eyre::Result<Self> {
let metadata = MetadataCommand::new().current_dir(path).exec()?;
// Convert [cargo_metadata::Package] into a simpler representation
// keeping only necessary data (in order to avoid these transforms at consuming code):
// - the name from Cargo.toml
// - the relative path from workspace root to the package dir.
//
// The closure is used instead of impl From
// in order to access metadata from outer scope.
let convert_package = |pkg: &cargo_metadata::Package| -> Option<Package> {
Some(Package {
name: ConfigFile::cargo_toml_name(pkg.manifest_path.parent()?.as_std_path())
.ok()?,
relative_path: pkg
.manifest_path
.strip_prefix(&metadata.workspace_root)
.ok()?
.parent()? // Remove filename and keep only the dir name.
.into(),
})
};
// Validate workspace rules for kinetics:
// 1. Workspace as a single kinetics project:
// - the workspace root MUST contain kinetics.toml;
// - workspace members cannot have kinetics.toml - throw an error.
// 2. Standalone kinetics project:
// - the project can contain kinetics.toml;
// - for the name fall back to Cargo.toml.
// 3. Workspace member is a kinetics project:
// - workspace member from where the command is called MUST have kinetics.toml.
// - the workspace root cannot have kinetics.toml - throw an error.
let workspace_config = metadata.workspace_root.join("kinetics.toml");
// Option 3. A call within a workspace member which is a kinetics project
if metadata.workspace_root != path && path.join("kinetics.toml").exists() {
if workspace_config.exists() {
eyre::bail!("Workspace is not allowed to have `kinetics.toml` within its root and within its members at the same time.");
}
// Return `Workspace` with only one package in the `packages` list - current project.
let cwd_manifest = path.join("Cargo.toml");
return Ok(Self {
packages: metadata
.workspace_members
.into_iter()
.filter_map(|member| {
metadata
.packages
.iter()
.find(|pkg| pkg.id == member)
.and_then(|pkg| {
// Take only the member corresponding to cwd - current project
// and discard all other members.
if pkg.manifest_path == cwd_manifest {
convert_package(pkg)
} else {
None
}
})
})
.collect(),
root_path: metadata.workspace_root.into_std_path_buf(),
});
}
// Options 1 and 2. A call from root.
let members_configs: Vec<_> = metadata
.workspace_members
.iter()
.filter_map(|member| {
metadata
.packages
.iter()
.find(|pkg| pkg.id == *member)
.and_then(|pkg| pkg.manifest_path.parent())
.and_then(|dir| {
let config = dir.join("kinetics.toml");
if config.exists() {
Some(config)
} else {
None
}
})
})
.collect();
if workspace_config.exists() && !members_configs.is_empty() {
eyre::bail!("Workspace is not allowed to have `kinetics.toml` within its root and within its members at the same time.");
}
Ok(Self {
packages: metadata
.workspace_members
.into_iter()
.filter_map(|member| {
metadata
.packages
.iter()
.find(|pkg| pkg.id == member)
.and_then(convert_package)
})
.collect(),
root_path: metadata.workspace_root.into_std_path_buf(),
})
}
}