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