wasm_pkg_core/manifest/
workspace.rs1use crate::manifest::paths::{expand_globs, normalize_path};
2
3use super::*;
4use std::path::{Path, PathBuf};
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10#[serde(untagged)]
11pub enum WorkspaceConfig {
12 Root(WorkspaceRootConfig),
13 Member { root: Option<String> },
17}
18impl Default for WorkspaceConfig {
19 fn default() -> Self {
20 Self::Root(WorkspaceRootConfig::default())
22 }
23}
24
25impl WorkspaceConfig {
26 pub fn as_root(&self) -> Option<&WorkspaceRootConfig> {
28 match self {
29 WorkspaceConfig::Root(r) => Some(r),
30 WorkspaceConfig::Member { .. } => None,
31 }
32 }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
40pub struct WorkspaceRootConfig {
41 #[serde(default, skip)]
42 pub(super) root_dir: PathBuf,
43 pub members: Vec<PathBuf>,
44 pub metadata: Option<Metadata>,
45}
46
47impl WorkspaceRootConfig {
48 pub fn root_dir(&self) -> &Path {
50 &self.root_dir
51 }
52
53 pub fn out_dir(&self) -> PathBuf {
55 self.root_dir.join(PathBuf::from(WORKSPACE_OUT_DIR))
56 }
57
58 pub(super) fn is_explicitly_listed_member(&self, manifest_path: &Path) -> bool {
71 let root_manifest = self.root_dir.join("Cargo.toml");
72 if manifest_path == root_manifest {
73 return true;
74 }
75
76 let manifest_path = normalize_path(manifest_path);
77 self.members.iter().any(|member| member == &manifest_path)
78 }
79 pub(super) fn resolve_members(members: &[PathBuf], root_dir: &Path) -> Vec<PathBuf> {
81 members
82 .iter()
83 .flat_map(|entry| expand_globs(entry, root_dir))
84 .flatten()
85 .map(|p| normalize_path(&p))
86 .collect()
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use std::fs;
93
94 use super::*;
95
96 fn touch(path: impl AsRef<Path>) {
98 let path = path.as_ref();
99 fs::create_dir_all(path.parent().unwrap()).unwrap();
100 fs::write(path, "").unwrap();
101 }
102
103 #[test]
104 fn find_root_manifest_for_wd_first_ancestor() {
105 let tempdir = tempfile::tempdir().unwrap();
110 let root_dir = tempdir.path();
111 touch(root_dir.join("wkg.toml"));
112 touch(root_dir.join("pkg/wkg.toml"));
113 fs::create_dir_all(root_dir.join("pkg/wit")).unwrap();
114
115 let found = find_root_manifest_for_wd(root_dir.join("pkg/wit")).unwrap();
116 assert_eq!(found, root_dir.join("pkg/wkg.toml"));
117 }
118
119 #[test]
120 fn find_root_manifest_for_wd_none() {
121 let tempdir = tempfile::tempdir().unwrap();
122 assert!(find_root_manifest_for_wd(tempdir.path()).is_none());
123 }
124
125 #[test]
126 fn find_root_iter_ancestors() {
127 let tempdir = tempfile::tempdir().unwrap();
132 let root_dir = tempdir.path();
133 touch(root_dir.join("wkg.toml"));
134 let member = root_dir.join("pkg/wkg.toml");
135 touch(&member);
136
137 let roots: Vec<PathBuf> = find_root_iter(&member).collect();
138 assert_eq!(roots, vec![root_dir.join("wkg.toml")]);
139 }
140
141 #[test]
142 fn resolve_members_skip_non_dirs() {
143 let tempdir = tempfile::tempdir().unwrap();
155 let root_dir = tempdir.path();
156 let member_wit = ["example-a/wit", "example-b/wit", "example-c/wit"];
157 for dir in member_wit {
158 fs::create_dir_all(root_dir.join(dir)).unwrap();
159 fs::write(root_dir.join(dir).join("pkg.wit"), "").unwrap();
160 }
161 fs::write(root_dir.join("example-d"), "").unwrap();
162
163 let members = vec![PathBuf::from("example-*/wit")];
164 let mut result = WorkspaceRootConfig::resolve_members(&members, root_dir);
165 result.sort();
166 let mut expected: Vec<_> = member_wit.iter().map(|p| root_dir.join(p)).collect();
167 expected.sort();
168 assert_eq!(result, expected, "glob should expand to matching dirs only");
169 }
170
171 #[test]
173 fn resolve_members_skip_non_wit_dirs() {
174 let tempdir = tempfile::tempdir().unwrap();
180 let root_dir = tempdir.path();
181 let core = root_dir.join("pkg/core");
182 let filesystem = root_dir.join("pkg/filesystem");
183 fs::create_dir_all(&core).unwrap();
184 fs::write(core.join("types.wit"), "package foo:core;\n").unwrap();
185 fs::create_dir_all(filesystem).unwrap();
186
187 let members = vec![PathBuf::from("pkg/*")];
188 let result = WorkspaceRootConfig::resolve_members(&members, root_dir);
189 assert_eq!(result, vec![core]);
190 }
191
192 #[test]
193 fn resolved_members_keeps_literal_even_when_empty() {
194 let tempdir = tempfile::tempdir().unwrap();
195 let root_dir = tempdir.path();
196 fs::create_dir_all(root_dir.join("empty-pkg")).unwrap();
199
200 let members = vec![PathBuf::from("empty-pkg")];
201 let result = WorkspaceRootConfig::resolve_members(&members, root_dir);
202 assert_eq!(result, vec![root_dir.join("empty-pkg")]);
203 }
204
205 #[test]
206 fn workspace_root() {
207 let toml = r#"
208[workspace]
209members = ["a/wit", "b/wit"]
210"#;
211 let cfg = Manifest::from_toml(toml).unwrap();
212 let root = cfg.workspace.unwrap();
213 assert_eq!(
214 root.as_root().unwrap().members,
215 vec![PathBuf::from("a/wit"), PathBuf::from("b/wit")],
216 );
217 assert!(cfg.overrides.is_none() && cfg.metadata.is_none());
218 }
219
220 #[test]
221 fn workspace_metadata() {
222 let toml = r#"
223[workspace]
224members = ["a/wit"]
225
226[workspace.metadata]
227authors = "Webster Assembler"
228"#;
229 let cfg = Manifest::from_toml(toml).unwrap();
230 let root = cfg.root().unwrap();
231 let meta = root.metadata.as_ref().unwrap();
232 assert_eq!(meta.authors.as_ref().unwrap(), "Webster Assembler");
233 }
234
235 #[test]
236 fn workspace_overrides_incompatible() {
237 let toml = r#"
238[workspace]
239members = ["a/wit"]
240
241[overrides."foo:bar"]
242path = "oo-bar"
243"#;
244 Manifest::from_toml(toml).unwrap_err();
245 }
246
247 fn write(path: impl AsRef<Path>, contents: &str) {
248 let path = path.as_ref();
249 fs::create_dir_all(path.parent().unwrap()).unwrap();
250 fs::write(path, contents).unwrap();
251 }
252
253 #[tokio::test]
254 async fn load_workspace_from_root_and_member() {
255 let tempdir = tempfile::tempdir().unwrap();
256 let root_dir = tempdir.path();
257 write(
262 root_dir.join("wkg.toml"),
263 r#"
264[workspace]
265members = ["pkg-a"]
266"#,
267 );
268 fs::create_dir_all(root_dir.join("pkg-a/wit")).unwrap();
269
270 let expected = root_dir.canonicalize().unwrap();
271 let from_root = Manifest::load_root_workspace(root_dir)
272 .await
273 .unwrap()
274 .unwrap();
275 assert_eq!(from_root.root_dir.canonicalize().unwrap(), expected);
276
277 let from_member = Manifest::load_root_workspace(&root_dir.join("pkg-a/wit"))
278 .await
279 .unwrap()
280 .unwrap();
281 assert_eq!(from_member.root_dir.canonicalize().unwrap(), expected);
282 }
283
284 #[tokio::test]
285 async fn load_workspace_no_root() {
286 let tempdir = tempfile::tempdir().unwrap();
288 let root_dir = tempdir.path();
289 write(
290 root_dir.join("wkg.toml"),
291 r#"
292[metadata]
293authors = "Webster Assembler"
294"#,
295 );
296 assert!(
297 Manifest::load_root_workspace(root_dir)
298 .await
299 .unwrap()
300 .is_none()
301 );
302 }
303}