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