1use std::collections::HashSet;
8
9use crate::models::PackageType;
10use crate::models::{DatasourceId, FileInfo, Package, TopLevelDependency};
11use strum::EnumIter;
12
13use super::{
14 AssemblerConfig, AssemblyMode, DirectoryMergeOutput, bazel_merge, bazel_prune,
15 cargo_resource_assign, composer_resource_assign, conda_rootfs_merge, debian_source_merge,
16 file_ref_resolve, hackage_merge, nix_flake_compat_merge, npm_resource_assign,
17 nuget_cpm_resolve, python_requirements_assign, ruby_resource_assign, swift_merge, topology,
18};
19
20#[derive(Clone, Copy)]
21pub(super) enum SpecialDirectoryMergerKind {
22 Skip,
23 Bazel,
24 DebianSource,
25 Hackage,
26 Huggingface,
27 WindowsUpdate,
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIter)]
31pub(super) enum PostAssemblyPassKind {
32 SwiftMerge,
33 CondaRootfsMerge,
34 NpmResourceAssign,
35 PythonRequirementsAssign,
36 FileReferenceResolve,
37 RpmYumdbMerge,
38 NpmWorkspaceMerge,
39 CargoWorkspaceMerge,
40 NugetCpmResolve,
41 CargoResourceAssign,
42 ComposerResourceAssign,
43 RubyResourceAssign,
44 NixFlakeCompatMerge,
45 BazelPrune,
46}
47
48pub(super) fn special_directory_merger_for(
49 config_key: DatasourceId,
50) -> Option<SpecialDirectoryMergerKind> {
51 match config_key {
52 DatasourceId::BazelBuild => Some(SpecialDirectoryMergerKind::Bazel),
53 DatasourceId::DebianControlInSource => Some(SpecialDirectoryMergerKind::DebianSource),
54 DatasourceId::HackageCabal => Some(SpecialDirectoryMergerKind::Hackage),
55 DatasourceId::HuggingfaceModelCard => Some(SpecialDirectoryMergerKind::Huggingface),
56 DatasourceId::MicrosoftUpdateManifestMum => Some(SpecialDirectoryMergerKind::WindowsUpdate),
57 DatasourceId::SwiftPackageManifestJson => Some(SpecialDirectoryMergerKind::Skip),
58 _ => None,
59 }
60}
61
62pub(super) static POST_ASSEMBLY_PASSES: &[PostAssemblyPassKind] = &[
63 PostAssemblyPassKind::SwiftMerge,
64 PostAssemblyPassKind::CondaRootfsMerge,
65 PostAssemblyPassKind::NpmResourceAssign,
66 PostAssemblyPassKind::PythonRequirementsAssign,
67 PostAssemblyPassKind::FileReferenceResolve,
68 PostAssemblyPassKind::RpmYumdbMerge,
69 PostAssemblyPassKind::NpmWorkspaceMerge,
70 PostAssemblyPassKind::CargoWorkspaceMerge,
71 PostAssemblyPassKind::NugetCpmResolve,
72 PostAssemblyPassKind::CargoResourceAssign,
73 PostAssemblyPassKind::ComposerResourceAssign,
74 PostAssemblyPassKind::RubyResourceAssign,
75 PostAssemblyPassKind::NixFlakeCompatMerge,
76 PostAssemblyPassKind::BazelPrune,
77];
78
79const SWIFT_POST_ASSEMBLY_DATASOURCE_IDS: &[DatasourceId] = &[
80 DatasourceId::SwiftPackageManifestJson,
81 DatasourceId::SwiftPackageResolved,
82 DatasourceId::SwiftPackageShowDependencies,
83];
84
85const CONDA_ROOTFS_POST_ASSEMBLY_DATASOURCE_IDS: &[DatasourceId] =
86 &[DatasourceId::CondaMetaJson, DatasourceId::CondaMetaYaml];
87
88const RPM_INSTALLED_DATABASE_DATASOURCE_IDS: &[DatasourceId] = &[
89 DatasourceId::RpmInstalledDatabaseBdb,
90 DatasourceId::RpmInstalledDatabaseNdb,
91 DatasourceId::RpmInstalledDatabaseSqlite,
92];
93
94const NUGET_CPM_CONFIG_DATASOURCE_IDS: &[DatasourceId] = &[
95 DatasourceId::NugetDirectoryBuildProps,
96 DatasourceId::NugetDirectoryPackagesProps,
97];
98
99const NUGET_CPM_PROJECT_DATASOURCE_IDS: &[DatasourceId] = &[
100 DatasourceId::NugetCsproj,
101 DatasourceId::NugetFsproj,
102 DatasourceId::NugetVbproj,
103];
104
105#[derive(Default)]
106struct PostAssemblyInputs {
107 package_types: HashSet<PackageType>,
108 file_datasource_ids: HashSet<DatasourceId>,
109 has_npm_workspace_markers: bool,
110 has_cargo_workspace_markers: bool,
111}
112
113pub(super) fn run_post_assembly_passes(
114 files: &mut [FileInfo],
115 packages: &mut Vec<Package>,
116 dependencies: &mut Vec<TopLevelDependency>,
117 topology_plan: &topology::TopologyPlan,
118) {
119 let inputs = PostAssemblyInputs::collect(files, packages);
120
121 for pass in POST_ASSEMBLY_PASSES {
122 if !pass.should_run(&inputs) {
123 continue;
124 }
125
126 pass.run(files, packages, dependencies, topology_plan);
127 }
128}
129
130impl PostAssemblyInputs {
131 fn collect(files: &[FileInfo], packages: &[Package]) -> Self {
132 let mut inputs = Self {
133 package_types: packages
134 .iter()
135 .filter_map(|package| package.package_type)
136 .collect(),
137 ..Self::default()
138 };
139
140 for file in files {
141 for package_data in &file.package_data {
142 let Some(datasource_id) = package_data.datasource_id else {
143 continue;
144 };
145
146 inputs.file_datasource_ids.insert(datasource_id);
147
148 if matches!(
149 datasource_id,
150 DatasourceId::NpmPackageJson | DatasourceId::PnpmWorkspaceYaml
151 ) && package_data
152 .extra_data
153 .as_ref()
154 .is_some_and(|extra_data| extra_data.contains_key("workspaces"))
155 {
156 inputs.has_npm_workspace_markers = true;
157 }
158
159 if datasource_id == DatasourceId::CargoToml
160 && package_data
161 .extra_data
162 .as_ref()
163 .and_then(|extra_data| extra_data.get("workspace"))
164 .and_then(|workspace| workspace.get("members"))
165 .and_then(|members| members.as_array())
166 .is_some_and(|members| !members.is_empty())
167 {
168 inputs.has_cargo_workspace_markers = true;
169 }
170 }
171 }
172
173 inputs
174 }
175
176 fn has_package_type(&self, package_type: PackageType) -> bool {
177 self.package_types.contains(&package_type)
178 }
179
180 fn has_any_file_datasource(&self, datasource_ids: &[DatasourceId]) -> bool {
181 datasource_ids
182 .iter()
183 .any(|datasource_id| self.file_datasource_ids.contains(datasource_id))
184 }
185
186 fn has_all_file_datasources(&self, datasource_ids: &[DatasourceId]) -> bool {
187 datasource_ids
188 .iter()
189 .all(|datasource_id| self.file_datasource_ids.contains(datasource_id))
190 }
191}
192
193impl SpecialDirectoryMergerKind {
194 pub(super) fn run(
195 self,
196 config: &AssemblerConfig,
197 files: &[FileInfo],
198 file_indices: &[usize],
199 ) -> Vec<DirectoryMergeOutput> {
200 match self {
201 Self::Skip => Vec::new(),
202 Self::Bazel => bazel_merge::assemble_bazel_packages(config, files, file_indices),
203 Self::DebianSource => {
204 debian_source_merge::assemble_debian_source_packages(config, files, file_indices)
205 }
206 Self::Hackage => hackage_merge::assemble_hackage_packages(files, file_indices),
207 Self::Huggingface => {
208 super::huggingface_merge::assemble_huggingface_packages(files, file_indices)
209 }
210 Self::WindowsUpdate => super::windows_update_merge::assemble_windows_update_packages(
211 config,
212 files,
213 file_indices,
214 ),
215 }
216 }
217}
218
219impl PostAssemblyPassKind {
220 fn should_run(self, inputs: &PostAssemblyInputs) -> bool {
221 match self {
222 Self::SwiftMerge => inputs.has_any_file_datasource(SWIFT_POST_ASSEMBLY_DATASOURCE_IDS),
223 Self::CondaRootfsMerge => {
224 inputs.has_all_file_datasources(CONDA_ROOTFS_POST_ASSEMBLY_DATASOURCE_IDS)
225 }
226 Self::NpmResourceAssign => inputs.has_package_type(PackageType::Npm),
227 Self::PythonRequirementsAssign => {
228 inputs.has_package_type(PackageType::Pypi)
229 && inputs.has_any_file_datasource(&[DatasourceId::PipRequirements])
230 }
231 Self::FileReferenceResolve => {
232 file_ref_resolve::has_relevant_file_reference_datasource_ids(
233 &inputs.file_datasource_ids,
234 )
235 }
236 Self::RpmYumdbMerge => {
237 inputs.has_any_file_datasource(&[DatasourceId::RpmYumdb])
238 && inputs.has_any_file_datasource(RPM_INSTALLED_DATABASE_DATASOURCE_IDS)
239 }
240 Self::NpmWorkspaceMerge => inputs.has_npm_workspace_markers,
241 Self::CargoWorkspaceMerge => inputs.has_cargo_workspace_markers,
242 Self::NugetCpmResolve => {
243 inputs.has_any_file_datasource(NUGET_CPM_CONFIG_DATASOURCE_IDS)
244 && inputs.has_any_file_datasource(NUGET_CPM_PROJECT_DATASOURCE_IDS)
245 }
246 Self::CargoResourceAssign => inputs.has_package_type(PackageType::Cargo),
247 Self::ComposerResourceAssign => inputs.has_package_type(PackageType::Composer),
248 Self::RubyResourceAssign => inputs.has_package_type(PackageType::Gem),
249 Self::NixFlakeCompatMerge => {
250 inputs.has_any_file_datasource(&[DatasourceId::NixDefaultNix])
251 && inputs.has_any_file_datasource(&[
252 DatasourceId::NixFlakeNix,
253 DatasourceId::NixFlakeLock,
254 ])
255 }
256 Self::BazelPrune => inputs.has_package_type(PackageType::Bazel),
257 }
258 }
259
260 fn run(
261 self,
262 files: &mut [FileInfo],
263 packages: &mut Vec<Package>,
264 dependencies: &mut Vec<TopLevelDependency>,
265 topology_plan: &topology::TopologyPlan,
266 ) {
267 match self {
268 Self::SwiftMerge => swift_merge::assemble_swift_packages(files, packages, dependencies),
269 Self::CondaRootfsMerge => {
270 conda_rootfs_merge::merge_conda_rootfs_metadata(files, packages, dependencies)
271 }
272 Self::NpmResourceAssign => {
273 npm_resource_assign::assign_npm_package_resources(files, packages)
274 }
275 Self::PythonRequirementsAssign => {
276 python_requirements_assign::assign_python_requirements_to_projects(
277 files,
278 packages,
279 dependencies,
280 )
281 }
282 Self::FileReferenceResolve => {
283 file_ref_resolve::resolve_file_references(files, packages, dependencies)
284 }
285 Self::RpmYumdbMerge => file_ref_resolve::merge_rpm_yumdb_metadata(files, packages),
286 Self::NpmWorkspaceMerge => {
287 topology_plan.apply_npm_workspace_domains(files, packages, dependencies)
288 }
289 Self::CargoWorkspaceMerge => {
290 topology_plan.apply_cargo_workspace_domains(files, packages, dependencies)
291 }
292 Self::NugetCpmResolve => {
293 nuget_cpm_resolve::resolve_nuget_cpm_versions(files, dependencies)
294 }
295 Self::CargoResourceAssign => {
296 cargo_resource_assign::assign_cargo_package_resources(files, packages)
297 }
298 Self::ComposerResourceAssign => {
299 composer_resource_assign::assign_composer_package_resources(files, packages)
300 }
301 Self::RubyResourceAssign => {
302 ruby_resource_assign::assign_ruby_package_resources(files, packages)
303 }
304 Self::NixFlakeCompatMerge => {
305 nix_flake_compat_merge::attach_flake_compat_default_files(files, packages)
306 }
307 Self::BazelPrune => {
308 bazel_prune::prune_unused_bazel_packages(files, packages, dependencies)
309 }
310 }
311 }
312}
313
314pub static ASSEMBLERS: &[AssemblerConfig] = &[
315 AssemblerConfig {
321 datasource_ids: &[
322 DatasourceId::BunLock,
323 DatasourceId::BunLockb,
324 DatasourceId::NpmPackageJson,
325 DatasourceId::NpmPackageLockJson,
326 DatasourceId::YarnLock,
327 DatasourceId::YarnLockV1,
328 DatasourceId::YarnLockV2,
329 DatasourceId::YarnPnpCjs,
330 DatasourceId::PnpmLockYaml,
331 DatasourceId::PnpmWorkspaceYaml,
332 ],
333 sibling_file_patterns: &[
334 "package.json",
335 "bun.lock",
336 "bun.lockb",
337 ".package-lock.json",
338 "package-lock.json",
339 ".npm-shrinkwrap.json",
340 "npm-shrinkwrap.json",
341 "yarn.lock",
342 ".pnp.cjs",
343 "pnpm-lock.yaml",
344 "shrinkwrap.yaml",
345 "pnpm-workspace.yaml",
346 ],
347 mode: AssemblyMode::SiblingMerge,
348 },
349 AssemblerConfig {
351 datasource_ids: &[DatasourceId::CargoToml, DatasourceId::CargoLock],
352 sibling_file_patterns: &["Cargo.toml", "Cargo.lock"],
353 mode: AssemblyMode::SiblingMerge,
354 },
355 AssemblerConfig {
357 datasource_ids: &[
358 DatasourceId::JuliaProjectToml,
359 DatasourceId::JuliaManifestToml,
360 ],
361 sibling_file_patterns: &["Project.toml", "Manifest.toml"],
362 mode: AssemblyMode::SiblingMerge,
363 },
364 AssemblerConfig {
366 datasource_ids: &[DatasourceId::RebarConfig, DatasourceId::RebarLock],
367 sibling_file_patterns: &["rebar.config", "rebar.lock"],
368 mode: AssemblyMode::SiblingMerge,
369 },
370 AssemblerConfig {
372 datasource_ids: &[
373 DatasourceId::CarthageCartfile,
374 DatasourceId::CarthageCartfileResolved,
375 ],
376 sibling_file_patterns: &["Cartfile", "Cartfile.private", "Cartfile.resolved"],
377 mode: AssemblyMode::SiblingMerge,
378 },
379 AssemblerConfig {
381 datasource_ids: &[
382 DatasourceId::CocoapodsPodspec,
383 DatasourceId::CocoapodsPodspecJson,
384 DatasourceId::CocoapodsPodfile,
385 DatasourceId::CocoapodsPodfileLock,
386 ],
387 sibling_file_patterns: &["*.podspec", "*.podspec.json", "Podfile", "Podfile.lock"],
388 mode: AssemblyMode::SiblingMerge,
389 },
390 AssemblerConfig {
392 datasource_ids: &[DatasourceId::PhpComposerJson, DatasourceId::PhpComposerLock],
393 sibling_file_patterns: &[
394 "*composer.json",
395 "composer.*.json",
396 "*composer.lock",
397 "composer.*.lock",
398 ],
399 mode: AssemblyMode::SiblingMerge,
400 },
401 AssemblerConfig {
403 datasource_ids: &[
404 DatasourceId::GoMod,
405 DatasourceId::GoModGraph,
406 DatasourceId::GoSum,
407 DatasourceId::GoWork,
408 DatasourceId::Godeps,
409 ],
410 sibling_file_patterns: &[
411 "go.mod",
412 "go.work",
413 "go.mod.graph",
414 "go.modgraph",
415 "go.sum",
416 "Godeps.json",
417 ],
418 mode: AssemblyMode::SiblingMerge,
419 },
420 AssemblerConfig {
422 datasource_ids: &[DatasourceId::PubspecYaml, DatasourceId::PubspecLock],
423 sibling_file_patterns: &["pubspec.yaml", "pubspec.lock"],
424 mode: AssemblyMode::SiblingMerge,
425 },
426 AssemblerConfig {
428 datasource_ids: &[DatasourceId::PixiToml, DatasourceId::PixiLock],
429 sibling_file_patterns: &["pixi.toml", "pixi.lock"],
430 mode: AssemblyMode::SiblingMerge,
431 },
432 AssemblerConfig {
433 datasource_ids: &[DatasourceId::NixFlakeNix, DatasourceId::NixFlakeLock],
434 sibling_file_patterns: &["flake.nix", "flake.lock"],
435 mode: AssemblyMode::SiblingMerge,
436 },
437 AssemblerConfig {
438 datasource_ids: &[DatasourceId::NixDefaultNix],
439 sibling_file_patterns: &["default.nix"],
440 mode: AssemblyMode::OnePerPackageData,
441 },
442 AssemblerConfig {
444 datasource_ids: &[DatasourceId::HelmChartYaml, DatasourceId::HelmChartLock],
445 sibling_file_patterns: &["Chart.yaml", "Chart.lock"],
446 mode: AssemblyMode::SiblingMerge,
447 },
448 AssemblerConfig {
449 datasource_ids: &[
450 DatasourceId::HackageCabal,
451 DatasourceId::HackageCabalProject,
452 DatasourceId::HackageStackYaml,
453 ],
454 sibling_file_patterns: &["*.cabal", "cabal.project", "stack.yaml"],
455 mode: AssemblyMode::SiblingMerge,
456 },
457 AssemblerConfig {
459 datasource_ids: &[
460 DatasourceId::ChefCookbookMetadataJson,
461 DatasourceId::ChefCookbookMetadataRb,
462 ],
463 sibling_file_patterns: &["metadata.json", "metadata.rb"],
464 mode: AssemblyMode::SiblingMerge,
465 },
466 AssemblerConfig {
468 datasource_ids: &[
469 DatasourceId::ConanConanFilePy,
470 DatasourceId::ConanConanFileTxt,
471 DatasourceId::ConanLock,
472 DatasourceId::ConanConanDataYml,
473 ],
474 sibling_file_patterns: &[
475 "conanfile.py",
476 "conanfile.txt",
477 "conan.lock",
478 "conandata.yml",
479 ],
480 mode: AssemblyMode::SiblingMerge,
481 },
482 AssemblerConfig {
484 datasource_ids: &[
485 DatasourceId::MavenPom,
486 DatasourceId::MavenPomProperties,
487 DatasourceId::JavaJarManifest,
488 DatasourceId::JavaOsgiManifest,
489 ],
490 sibling_file_patterns: &[
491 "pom.xml",
492 "*.pom",
493 "pom.properties",
494 "**/META-INF/MANIFEST.MF",
495 ],
496 mode: AssemblyMode::SiblingMerge,
497 },
498 AssemblerConfig {
499 datasource_ids: &[DatasourceId::PypiWheel, DatasourceId::PypiPipOriginJson],
500 sibling_file_patterns: &["*.whl", "origin.json"],
501 mode: AssemblyMode::SiblingMerge,
502 },
503 AssemblerConfig {
505 datasource_ids: &[
506 DatasourceId::PypiPyprojectToml,
507 DatasourceId::PypiPoetryPyprojectToml,
508 DatasourceId::PypiSetupPy,
509 DatasourceId::PypiSetupCfg,
510 DatasourceId::PypiWheelMetadata,
511 DatasourceId::PypiEgg,
512 DatasourceId::PypiEggPkginfo,
513 DatasourceId::PypiEditableEggPkginfo,
514 DatasourceId::PypiJson,
515 DatasourceId::PypiSdist,
516 DatasourceId::PypiSdistPkginfo,
517 DatasourceId::PypiInspectDeplock,
518 DatasourceId::PipRequirements,
519 DatasourceId::PypiPoetryLock,
520 DatasourceId::PypiPylockToml,
521 DatasourceId::PypiUvLock,
522 DatasourceId::Pipfile,
523 DatasourceId::PipfileLock,
524 ],
525 sibling_file_patterns: &[
526 "pyproject.toml",
527 "setup.py",
528 "setup.cfg",
529 "PKG-INFO",
530 "METADATA",
531 "pypi.json",
532 "pip-inspect.deplock",
533 "*.tar.gz",
534 "*.tgz",
535 "*.tar.bz2",
536 "*.tar.xz",
537 "*.zip",
538 "requirements*.txt",
539 "Pipfile",
540 "Pipfile.lock",
541 "poetry.lock",
542 "pylock.toml",
543 "pylock.*.toml",
544 "uv.lock",
545 ],
546 mode: AssemblyMode::SiblingMerge,
547 },
548 AssemblerConfig {
549 datasource_ids: &[DatasourceId::DenoJson, DatasourceId::DenoLock],
550 sibling_file_patterns: &["deno.json", "deno.jsonc", "deno.lock"],
551 mode: AssemblyMode::SiblingMerge,
552 },
553 AssemblerConfig {
555 datasource_ids: &[
556 DatasourceId::GemArchiveExtracted,
557 DatasourceId::Gemspec,
558 DatasourceId::GemspecExtracted,
559 DatasourceId::Gemfile,
560 DatasourceId::GemfileExtracted,
561 DatasourceId::GemfileLock,
562 DatasourceId::GemfileLockExtracted,
563 ],
564 sibling_file_patterns: &[
565 "metadata.gz-extract",
566 "**/data.gz-extract/*.gemspec",
567 "**/data.gz-extract/Gemfile",
568 "**/data.gz-extract/Gemfile.lock",
569 "*.gemspec",
570 "Gemfile",
571 "Gemfile.lock",
572 ],
573 mode: AssemblyMode::SiblingMerge,
574 },
575 AssemblerConfig {
576 datasource_ids: &[DatasourceId::GemArchive],
577 sibling_file_patterns: &["*.gem"],
578 mode: AssemblyMode::OnePerPackageData,
579 },
580 AssemblerConfig {
582 datasource_ids: &[
583 DatasourceId::CondaMetaYaml,
584 DatasourceId::CondaYaml,
585 DatasourceId::CondaMetaJson,
586 ],
587 sibling_file_patterns: &[
588 "meta.yaml",
589 "meta.yml",
590 "recipe.yaml",
591 "recipe.yml",
592 "environment.yml",
593 "environment.yaml",
594 "conda.yaml",
595 "conda.yml",
596 "*conda*.yaml",
597 "*conda*.yml",
598 "env.yaml",
599 "env.yml",
600 "*env*.yaml",
601 "*env*.yml",
602 "*environment*.yaml",
603 "*environment*.yml",
604 "*.json",
605 ],
606 mode: AssemblyMode::SiblingMerge,
607 },
608 AssemblerConfig {
610 datasource_ids: &[DatasourceId::RpmSpecfile],
611 sibling_file_patterns: &["*.spec"],
612 mode: AssemblyMode::OnePerPackageData,
613 },
614 AssemblerConfig {
616 datasource_ids: &[
617 DatasourceId::DebianControlInSource,
618 DatasourceId::DebianCopyrightInSource,
619 ],
620 sibling_file_patterns: &["control", "copyright"],
621 mode: AssemblyMode::SiblingMerge,
622 },
623 AssemblerConfig {
625 datasource_ids: &[DatasourceId::BuildGradle, DatasourceId::GradleLockfile],
626 sibling_file_patterns: &["build.gradle", "build.gradle.kts", "gradle.lockfile"],
627 mode: AssemblyMode::SiblingMerge,
628 },
629 AssemblerConfig {
630 datasource_ids: &[DatasourceId::GradleModule],
631 sibling_file_patterns: &["*.module"],
632 mode: AssemblyMode::OnePerPackageData,
633 },
634 AssemblerConfig {
641 datasource_ids: &[
642 DatasourceId::HuggingfaceModelCard,
643 DatasourceId::HuggingfaceConfigJson,
644 DatasourceId::HuggingfaceModelIndexJson,
645 ],
646 sibling_file_patterns: &["README.md", "config.json", "model_index.json"],
647 mode: AssemblyMode::SiblingMerge,
648 },
649 AssemblerConfig {
651 datasource_ids: &[
652 DatasourceId::CpanMetaJson,
653 DatasourceId::CpanMetaYml,
654 DatasourceId::CpanManifest,
655 DatasourceId::CpanDistIni,
656 DatasourceId::CpanMakefile,
657 ],
658 sibling_file_patterns: &[
659 "META.json",
660 "META.yml",
661 "MANIFEST",
662 "dist.ini",
663 "Makefile.PL",
664 ],
665 mode: AssemblyMode::SiblingMerge,
666 },
667 AssemblerConfig {
669 datasource_ids: &[
670 DatasourceId::NugetCsproj,
671 DatasourceId::NugetFsproj,
672 DatasourceId::NugetNuspec,
673 DatasourceId::NugetNupkg,
674 DatasourceId::NugetProjectJson,
675 DatasourceId::NugetProjectLockJson,
676 DatasourceId::NugetPackagesConfig,
677 DatasourceId::NugetPackagesLock,
678 DatasourceId::NugetVbproj,
679 ],
680 sibling_file_patterns: &[
681 "*.csproj",
682 "*.fsproj",
683 "*.nuspec",
684 "*.nupkg",
685 "project.json",
686 "project.lock.json",
687 "packages.config",
688 "packages.lock.json",
689 "*.packages.lock.json",
690 "*.vbproj",
691 ],
692 mode: AssemblyMode::SiblingMerge,
693 },
694 AssemblerConfig {
695 datasource_ids: &[DatasourceId::NugetDepsJson],
696 sibling_file_patterns: &["*.deps.json"],
697 mode: AssemblyMode::OnePerPackageData,
698 },
699 AssemblerConfig {
701 datasource_ids: &[
702 DatasourceId::SwiftPackageManifestJson,
703 DatasourceId::SwiftPackageResolved,
704 DatasourceId::SwiftPackageShowDependencies,
705 ],
706 sibling_file_patterns: &[
707 "Package.swift.json",
708 "Package.swift.deplock",
709 "Package.resolved",
710 ".package.resolved",
711 "swift-show-dependencies.deplock",
712 ],
713 mode: AssemblyMode::SiblingMerge,
714 },
715 AssemblerConfig {
722 datasource_ids: &[DatasourceId::BowerJson],
723 sibling_file_patterns: &["bower.json"],
724 mode: AssemblyMode::SiblingMerge,
725 },
726 AssemblerConfig {
728 datasource_ids: &[DatasourceId::CranDescription],
729 sibling_file_patterns: &["DESCRIPTION"],
730 mode: AssemblyMode::SiblingMerge,
731 },
732 AssemblerConfig {
734 datasource_ids: &[DatasourceId::FreebsdCompactManifest],
735 sibling_file_patterns: &["+COMPACT_MANIFEST"],
736 mode: AssemblyMode::SiblingMerge,
737 },
738 AssemblerConfig {
740 datasource_ids: &[DatasourceId::HaxelibJson],
741 sibling_file_patterns: &["haxelib.json"],
742 mode: AssemblyMode::SiblingMerge,
743 },
744 AssemblerConfig {
745 datasource_ids: &[DatasourceId::Gitmodules],
746 sibling_file_patterns: &[".gitmodules"],
747 mode: AssemblyMode::SiblingMerge,
748 },
749 AssemblerConfig {
751 datasource_ids: &[DatasourceId::OpamFile],
752 sibling_file_patterns: &["opam", "*.opam"],
753 mode: AssemblyMode::SiblingMerge,
754 },
755 AssemblerConfig {
756 datasource_ids: &[DatasourceId::RpmYumdb],
757 sibling_file_patterns: &["**/var/lib/yum/yumdb/*/*/from_repo"],
758 mode: AssemblyMode::OnePerPackageData,
759 },
760 AssemblerConfig {
762 datasource_ids: &[DatasourceId::MicrosoftUpdateManifestMum],
763 sibling_file_patterns: &["*.mum"],
764 mode: AssemblyMode::SiblingMerge,
765 },
766 AssemblerConfig {
768 datasource_ids: &[DatasourceId::AutotoolsConfigure],
769 sibling_file_patterns: &["configure", "configure.ac"],
770 mode: AssemblyMode::SiblingMerge,
771 },
772 AssemblerConfig {
774 datasource_ids: &[DatasourceId::BazelBuild],
775 sibling_file_patterns: &["BUILD"],
776 mode: AssemblyMode::SiblingMerge,
777 },
778 AssemblerConfig {
779 datasource_ids: &[DatasourceId::BazelModule],
780 sibling_file_patterns: &["MODULE.bazel"],
781 mode: AssemblyMode::OnePerPackageData,
782 },
783 AssemblerConfig {
785 datasource_ids: &[DatasourceId::BuckFile, DatasourceId::BuckMetadata],
786 sibling_file_patterns: &["BUCK", "METADATA.bzl", ".buckconfig"],
787 mode: AssemblyMode::SiblingMerge,
788 },
789 AssemblerConfig {
791 datasource_ids: &[DatasourceId::AntIvyXml],
792 sibling_file_patterns: &["ivy.xml"],
793 mode: AssemblyMode::SiblingMerge,
794 },
795 AssemblerConfig {
797 datasource_ids: &[
798 DatasourceId::JavaJar,
799 DatasourceId::JavaWarArchive,
800 DatasourceId::AndroidAarLibrary,
801 ],
802 sibling_file_patterns: &["*.jar", "*.war", "*.aar"],
803 mode: AssemblyMode::OnePerPackageData,
804 },
805 AssemblerConfig {
807 datasource_ids: &[DatasourceId::MeteorPackage],
808 sibling_file_patterns: &["package.js"],
809 mode: AssemblyMode::SiblingMerge,
810 },
811 AssemblerConfig {
815 datasource_ids: &[DatasourceId::AlpineInstalledDb],
816 sibling_file_patterns: &["installed"],
817 mode: AssemblyMode::OnePerPackageData,
818 },
819 AssemblerConfig {
820 datasource_ids: &[DatasourceId::AlpineApkbuild],
821 sibling_file_patterns: &["APKBUILD"],
822 mode: AssemblyMode::SiblingMerge,
823 },
824 AssemblerConfig {
826 datasource_ids: &[
827 DatasourceId::RpmInstalledDatabaseBdb,
828 DatasourceId::RpmInstalledDatabaseNdb,
829 DatasourceId::RpmInstalledDatabaseSqlite,
830 DatasourceId::RpmMarinerManifest,
831 ],
832 sibling_file_patterns: &[
833 "Packages",
834 "Packages.db",
835 "rpmdb.sqlite",
836 "container-manifest-2",
837 ],
838 mode: AssemblyMode::OnePerPackageData,
839 },
840 AssemblerConfig {
841 datasource_ids: &[DatasourceId::RpmArchive],
842 sibling_file_patterns: &["*.rpm", "*.srpm"],
843 mode: AssemblyMode::OnePerPackageData,
844 },
845 AssemblerConfig {
847 datasource_ids: &[DatasourceId::DebianDeb],
848 sibling_file_patterns: &["*.deb"],
849 mode: AssemblyMode::OnePerPackageData,
850 },
851 AssemblerConfig {
852 datasource_ids: &[
853 DatasourceId::DebianInstalledStatusDb,
854 DatasourceId::DebianDistrolessInstalledDb,
855 ],
856 sibling_file_patterns: &["status"],
857 mode: AssemblyMode::OnePerPackageData,
858 },
859 AssemblerConfig {
860 datasource_ids: &[
861 DatasourceId::DebianControlExtractedDeb,
862 DatasourceId::DebianMd5SumsInExtractedDeb,
863 ],
864 sibling_file_patterns: &["control", "md5sums"],
865 mode: AssemblyMode::SiblingMerge,
866 },
867 AssemblerConfig {
868 datasource_ids: &[DatasourceId::DebianSourceControlDsc],
869 sibling_file_patterns: &["*.dsc"],
870 mode: AssemblyMode::OnePerPackageData,
871 },
872 AssemblerConfig {
873 datasource_ids: &[DatasourceId::AboutFile],
874 sibling_file_patterns: &["*.ABOUT"],
875 mode: AssemblyMode::OnePerPackageData,
876 },
877 AssemblerConfig {
878 datasource_ids: &[
879 DatasourceId::BitbakeRecipe,
880 DatasourceId::BitbakeRecipeAppend,
881 ],
882 sibling_file_patterns: &["*.bb", "*.bbappend"],
883 mode: AssemblyMode::SiblingMerge,
884 },
885];
886
887pub static UNASSEMBLED_DATASOURCE_IDS: &[DatasourceId] = &[
893 DatasourceId::Readme,
895 DatasourceId::EtcOsRelease,
896 DatasourceId::AlpineApkArchive,
898 DatasourceId::AndroidAab,
899 DatasourceId::AndroidApk,
900 DatasourceId::AndroidManifestXml,
901 DatasourceId::AndroidSoongMetadata,
902 DatasourceId::AppleDmg,
903 DatasourceId::Axis2Mar,
904 DatasourceId::ChromeCrx,
905 DatasourceId::DebianOriginalSourceTarball,
906 DatasourceId::DebianSourceMetadataTarball,
907 DatasourceId::InstallshieldInstaller,
908 DatasourceId::IosIpa,
909 DatasourceId::IsoDiskImage,
910 DatasourceId::JavaEarArchive,
911 DatasourceId::JbossSar,
912 DatasourceId::MicrosoftCabinet,
913 DatasourceId::MozillaXpi,
914 DatasourceId::NsisInstaller,
915 DatasourceId::SharShellArchive,
916 DatasourceId::SquashfsDiskImage,
917 DatasourceId::ArchAurinfo,
919 DatasourceId::ArchPkginfo,
920 DatasourceId::ArchSrcinfo,
921 DatasourceId::Axis2ModuleXml,
922 DatasourceId::ClojureDepsEdn,
923 DatasourceId::ClojureProjectClj,
924 DatasourceId::DebianInstalledFilesList,
925 DatasourceId::DebianInstalledMd5Sums,
926 DatasourceId::DebianCopyright,
927 DatasourceId::DebianCopyrightInPackage,
928 DatasourceId::DebianCopyrightStandalone,
929 DatasourceId::GoBinary,
930 DatasourceId::WindowsExecutable,
931 DatasourceId::Dockerfile,
932 DatasourceId::OciImageIndex,
933 DatasourceId::OciImageManifest,
934 DatasourceId::ErlangOtpAppSrc,
935 DatasourceId::HexMixLock,
936 DatasourceId::JavaEarApplicationXml,
937 DatasourceId::JavaWarWebXml,
938 DatasourceId::JbossServiceXml,
939 DatasourceId::MesonBuild,
940 DatasourceId::GemGemspecInstalledSpecifications,
941 DatasourceId::NugetDirectoryBuildProps,
942 DatasourceId::NugetDirectoryPackagesProps,
943 DatasourceId::CitationCff,
944 DatasourceId::PubliccodeYaml,
945 DatasourceId::RpmPackageLicenses,
946 DatasourceId::RustBinary,
947 DatasourceId::SbtBuildSbt,
948 DatasourceId::VcpkgJson,
949];
950
951#[cfg(test)]
952mod tests {
953 use super::*;
954 use std::collections::HashSet;
955 use strum::IntoEnumIterator;
956
957 #[test]
958 fn test_every_datasource_id_is_accounted_for() {
959 let mut assembled: HashSet<DatasourceId> = HashSet::new();
960 for config in ASSEMBLERS {
961 for &dsid in config.datasource_ids {
962 assembled.insert(dsid);
963 }
964 }
965
966 let unassembled: HashSet<DatasourceId> =
967 UNASSEMBLED_DATASOURCE_IDS.iter().copied().collect();
968
969 let overlap: Vec<_> = assembled.intersection(&unassembled).collect();
970 assert!(
971 overlap.is_empty(),
972 "Datasource IDs in BOTH ASSEMBLERS and UNASSEMBLED: {overlap:?}"
973 );
974
975 let missing: Vec<_> = DatasourceId::iter()
976 .filter(|dsid| !assembled.contains(dsid) && !unassembled.contains(dsid))
977 .collect();
978
979 assert!(
980 missing.is_empty(),
981 "Datasource IDs in NEITHER ASSEMBLERS nor UNASSEMBLED: {missing:?}\n\
982 Add each to an AssemblerConfig in ASSEMBLERS, or to UNASSEMBLED_DATASOURCE_IDS."
983 );
984 }
985
986 #[test]
987 fn test_post_assembly_passes_are_unique() {
988 let unique: HashSet<PostAssemblyPassKind> = POST_ASSEMBLY_PASSES.iter().copied().collect();
989
990 assert_eq!(
991 unique.len(),
992 POST_ASSEMBLY_PASSES.len(),
993 "POST_ASSEMBLY_PASSES contains duplicate entries"
994 );
995 }
996
997 #[test]
998 fn test_every_post_assembly_pass_kind_is_registered_once() {
999 let registered: HashSet<PostAssemblyPassKind> =
1000 POST_ASSEMBLY_PASSES.iter().copied().collect();
1001
1002 let missing: Vec<_> = PostAssemblyPassKind::iter()
1003 .filter(|pass| !registered.contains(pass))
1004 .collect();
1005
1006 assert!(
1007 missing.is_empty(),
1008 "Post-assembly pass variants not registered in POST_ASSEMBLY_PASSES: {missing:?}"
1009 );
1010
1011 for pass in PostAssemblyPassKind::iter() {
1012 let count = POST_ASSEMBLY_PASSES
1013 .iter()
1014 .filter(|registered| **registered == pass)
1015 .count();
1016 assert_eq!(
1017 count, 1,
1018 "Post-assembly pass {pass:?} should be registered exactly once"
1019 );
1020 }
1021 }
1022
1023 #[test]
1024 fn test_post_assembly_passes_skip_irrelevant_inputs() {
1025 let inputs = PostAssemblyInputs::default();
1026
1027 for pass in PostAssemblyPassKind::iter() {
1028 assert!(
1029 !pass.should_run(&inputs),
1030 "{pass:?} should skip when no relevant inputs are present"
1031 );
1032 }
1033 }
1034
1035 #[test]
1036 fn test_npm_workspace_inputs_only_run_npm_passes() {
1037 let inputs = PostAssemblyInputs {
1038 package_types: HashSet::from([PackageType::Npm]),
1039 file_datasource_ids: HashSet::from([DatasourceId::NpmPackageJson]),
1040 has_npm_workspace_markers: true,
1041 has_cargo_workspace_markers: false,
1042 };
1043
1044 let runnable: HashSet<_> = PostAssemblyPassKind::iter()
1045 .filter(|pass| pass.should_run(&inputs))
1046 .collect();
1047
1048 assert_eq!(
1049 runnable,
1050 HashSet::from([
1051 PostAssemblyPassKind::NpmResourceAssign,
1052 PostAssemblyPassKind::NpmWorkspaceMerge,
1053 ])
1054 );
1055 }
1056
1057 #[test]
1058 fn test_cargo_workspace_merge_requires_workspace_markers() {
1059 let without_markers = PostAssemblyInputs {
1060 package_types: HashSet::from([PackageType::Cargo]),
1061 file_datasource_ids: HashSet::from([DatasourceId::CargoToml]),
1062 has_npm_workspace_markers: false,
1063 has_cargo_workspace_markers: false,
1064 };
1065
1066 assert!(!PostAssemblyPassKind::CargoWorkspaceMerge.should_run(&without_markers));
1067
1068 let with_markers = PostAssemblyInputs {
1069 has_cargo_workspace_markers: true,
1070 ..without_markers
1071 };
1072
1073 assert!(PostAssemblyPassKind::CargoWorkspaceMerge.should_run(&with_markers));
1074 }
1075}