1use crate::models::{DatasourceId, FileInfo, Package, TopLevelDependency};
2use strum::EnumIter;
3
4use super::{
5 AssemblerConfig, AssemblyMode, DirectoryMergeOutput, cargo_resource_assign,
6 cargo_workspace_merge, composer_resource_assign, conda_rootfs_merge, file_ref_resolve,
7 hackage_merge, npm_resource_assign, npm_workspace_merge, nuget_cpm_resolve,
8 ruby_resource_assign, swift_merge,
9};
10
11#[derive(Clone, Copy)]
12pub(super) enum SpecialDirectoryMergerKind {
13 Skip,
14 Hackage,
15}
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIter)]
18pub(super) enum PostAssemblyPassKind {
19 SwiftMerge,
20 CondaRootfsMerge,
21 NpmResourceAssign,
22 FileReferenceResolve,
23 RpmYumdbMerge,
24 NpmWorkspaceMerge,
25 CargoWorkspaceMerge,
26 NugetCpmResolve,
27 CargoResourceAssign,
28 ComposerResourceAssign,
29 RubyResourceAssign,
30}
31
32pub(super) fn special_directory_merger_for(
33 config_key: DatasourceId,
34) -> Option<SpecialDirectoryMergerKind> {
35 match config_key {
36 DatasourceId::HackageCabal => Some(SpecialDirectoryMergerKind::Hackage),
37 DatasourceId::SwiftPackageManifestJson => Some(SpecialDirectoryMergerKind::Skip),
38 _ => None,
39 }
40}
41
42pub(super) static POST_ASSEMBLY_PASSES: &[PostAssemblyPassKind] = &[
43 PostAssemblyPassKind::SwiftMerge,
44 PostAssemblyPassKind::CondaRootfsMerge,
45 PostAssemblyPassKind::NpmResourceAssign,
46 PostAssemblyPassKind::FileReferenceResolve,
47 PostAssemblyPassKind::RpmYumdbMerge,
48 PostAssemblyPassKind::NpmWorkspaceMerge,
49 PostAssemblyPassKind::CargoWorkspaceMerge,
50 PostAssemblyPassKind::NugetCpmResolve,
51 PostAssemblyPassKind::CargoResourceAssign,
52 PostAssemblyPassKind::ComposerResourceAssign,
53 PostAssemblyPassKind::RubyResourceAssign,
54];
55
56pub(super) fn run_post_assembly_passes(
57 files: &mut [FileInfo],
58 packages: &mut Vec<Package>,
59 dependencies: &mut Vec<TopLevelDependency>,
60) {
61 for pass in POST_ASSEMBLY_PASSES {
62 pass.run(files, packages, dependencies);
63 }
64}
65
66impl SpecialDirectoryMergerKind {
67 pub(super) fn run(
68 self,
69 files: &[FileInfo],
70 file_indices: &[usize],
71 ) -> Vec<DirectoryMergeOutput> {
72 match self {
73 Self::Skip => Vec::new(),
74 Self::Hackage => hackage_merge::assemble_hackage_packages(files, file_indices),
75 }
76 }
77}
78
79impl PostAssemblyPassKind {
80 fn run(
81 self,
82 files: &mut [FileInfo],
83 packages: &mut Vec<Package>,
84 dependencies: &mut Vec<TopLevelDependency>,
85 ) {
86 match self {
87 Self::SwiftMerge => swift_merge::assemble_swift_packages(files, packages, dependencies),
88 Self::CondaRootfsMerge => {
89 conda_rootfs_merge::merge_conda_rootfs_metadata(files, packages, dependencies)
90 }
91 Self::NpmResourceAssign => {
92 npm_resource_assign::assign_npm_package_resources(files, packages)
93 }
94 Self::FileReferenceResolve => {
95 file_ref_resolve::resolve_file_references(files, packages, dependencies)
96 }
97 Self::RpmYumdbMerge => file_ref_resolve::merge_rpm_yumdb_metadata(files, packages),
98 Self::NpmWorkspaceMerge => {
99 npm_workspace_merge::assemble_npm_workspaces(files, packages, dependencies)
100 }
101 Self::CargoWorkspaceMerge => {
102 cargo_workspace_merge::assemble_cargo_workspaces(files, packages, dependencies)
103 }
104 Self::NugetCpmResolve => {
105 nuget_cpm_resolve::resolve_nuget_cpm_versions(files, dependencies)
106 }
107 Self::CargoResourceAssign => {
108 cargo_resource_assign::assign_cargo_package_resources(files, packages)
109 }
110 Self::ComposerResourceAssign => {
111 composer_resource_assign::assign_composer_package_resources(files, packages)
112 }
113 Self::RubyResourceAssign => {
114 ruby_resource_assign::assign_ruby_package_resources(files, packages)
115 }
116 }
117 }
118}
119
120pub static ASSEMBLERS: &[AssemblerConfig] = &[
121 AssemblerConfig {
127 datasource_ids: &[
128 DatasourceId::BunLock,
129 DatasourceId::BunLockb,
130 DatasourceId::NpmPackageJson,
131 DatasourceId::NpmPackageLockJson,
132 DatasourceId::YarnLock,
133 DatasourceId::PnpmLockYaml,
134 DatasourceId::PnpmWorkspaceYaml,
135 ],
136 sibling_file_patterns: &[
137 "package.json",
138 "bun.lock",
139 "bun.lockb",
140 "package-lock.json",
141 "npm-shrinkwrap.json",
142 "yarn.lock",
143 "pnpm-lock.yaml",
144 "pnpm-workspace.yaml",
145 ],
146 mode: AssemblyMode::SiblingMerge,
147 },
148 AssemblerConfig {
150 datasource_ids: &[DatasourceId::CargoToml, DatasourceId::CargoLock],
151 sibling_file_patterns: &["Cargo.toml", "Cargo.lock"],
152 mode: AssemblyMode::SiblingMerge,
153 },
154 AssemblerConfig {
156 datasource_ids: &[
157 DatasourceId::CocoapodsPodspec,
158 DatasourceId::CocoapodsPodspecJson,
159 DatasourceId::CocoapodsPodfile,
160 DatasourceId::CocoapodsPodfileLock,
161 ],
162 sibling_file_patterns: &["*.podspec", "*.podspec.json", "Podfile", "Podfile.lock"],
163 mode: AssemblyMode::SiblingMerge,
164 },
165 AssemblerConfig {
167 datasource_ids: &[DatasourceId::PhpComposerJson, DatasourceId::PhpComposerLock],
168 sibling_file_patterns: &[
169 "*composer.json",
170 "composer.*.json",
171 "*composer.lock",
172 "composer.*.lock",
173 ],
174 mode: AssemblyMode::SiblingMerge,
175 },
176 AssemblerConfig {
178 datasource_ids: &[
179 DatasourceId::GoMod,
180 DatasourceId::GoModGraph,
181 DatasourceId::GoSum,
182 DatasourceId::GoWork,
183 DatasourceId::Godeps,
184 ],
185 sibling_file_patterns: &[
186 "go.mod",
187 "go.work",
188 "go.mod.graph",
189 "go.modgraph",
190 "go.sum",
191 "Godeps.json",
192 ],
193 mode: AssemblyMode::SiblingMerge,
194 },
195 AssemblerConfig {
197 datasource_ids: &[DatasourceId::PubspecYaml, DatasourceId::PubspecLock],
198 sibling_file_patterns: &["pubspec.yaml", "pubspec.lock"],
199 mode: AssemblyMode::SiblingMerge,
200 },
201 AssemblerConfig {
203 datasource_ids: &[DatasourceId::PixiToml, DatasourceId::PixiLock],
204 sibling_file_patterns: &["pixi.toml", "pixi.lock"],
205 mode: AssemblyMode::SiblingMerge,
206 },
207 AssemblerConfig {
209 datasource_ids: &[DatasourceId::HelmChartYaml, DatasourceId::HelmChartLock],
210 sibling_file_patterns: &["Chart.yaml", "Chart.lock"],
211 mode: AssemblyMode::SiblingMerge,
212 },
213 AssemblerConfig {
214 datasource_ids: &[
215 DatasourceId::HackageCabal,
216 DatasourceId::HackageCabalProject,
217 DatasourceId::HackageStackYaml,
218 ],
219 sibling_file_patterns: &["*.cabal", "cabal.project", "stack.yaml"],
220 mode: AssemblyMode::SiblingMerge,
221 },
222 AssemblerConfig {
224 datasource_ids: &[
225 DatasourceId::ChefCookbookMetadataJson,
226 DatasourceId::ChefCookbookMetadataRb,
227 ],
228 sibling_file_patterns: &["metadata.json", "metadata.rb"],
229 mode: AssemblyMode::SiblingMerge,
230 },
231 AssemblerConfig {
233 datasource_ids: &[
234 DatasourceId::ConanConanFilePy,
235 DatasourceId::ConanConanFileTxt,
236 DatasourceId::ConanLock,
237 DatasourceId::ConanConanDataYml,
238 ],
239 sibling_file_patterns: &[
240 "conanfile.py",
241 "conanfile.txt",
242 "conan.lock",
243 "conandata.yml",
244 ],
245 mode: AssemblyMode::SiblingMerge,
246 },
247 AssemblerConfig {
249 datasource_ids: &[
250 DatasourceId::MavenPom,
251 DatasourceId::MavenPomProperties,
252 DatasourceId::JavaJarManifest,
253 DatasourceId::JavaOsgiManifest,
254 ],
255 sibling_file_patterns: &["pom.xml", "pom.properties", "**/META-INF/MANIFEST.MF"],
256 mode: AssemblyMode::SiblingMerge,
257 },
258 AssemblerConfig {
259 datasource_ids: &[DatasourceId::PypiWheel, DatasourceId::PypiPipOriginJson],
260 sibling_file_patterns: &["*.whl", "origin.json"],
261 mode: AssemblyMode::SiblingMerge,
262 },
263 AssemblerConfig {
265 datasource_ids: &[
266 DatasourceId::PypiPyprojectToml,
267 DatasourceId::PypiSetupPy,
268 DatasourceId::PypiSetupCfg,
269 DatasourceId::PypiWheel,
270 DatasourceId::PypiWheelMetadata,
271 DatasourceId::PypiEgg,
272 DatasourceId::PypiJson,
273 DatasourceId::PypiSdistPkginfo,
274 DatasourceId::PypiInspectDeplock,
275 DatasourceId::PipRequirements,
276 DatasourceId::PypiPoetryLock,
277 DatasourceId::PypiPylockToml,
278 DatasourceId::PypiUvLock,
279 DatasourceId::Pipfile,
280 DatasourceId::PipfileLock,
281 ],
282 sibling_file_patterns: &[
283 "pyproject.toml",
284 "setup.py",
285 "setup.cfg",
286 "PKG-INFO",
287 "METADATA",
288 "pypi.json",
289 "requirements*.txt",
290 "Pipfile",
291 "Pipfile.lock",
292 "poetry.lock",
293 "pylock.toml",
294 "pylock.*.toml",
295 "uv.lock",
296 ],
297 mode: AssemblyMode::SiblingMerge,
298 },
299 AssemblerConfig {
300 datasource_ids: &[DatasourceId::DenoJson, DatasourceId::DenoLock],
301 sibling_file_patterns: &["deno.json", "deno.jsonc", "deno.lock"],
302 mode: AssemblyMode::SiblingMerge,
303 },
304 AssemblerConfig {
306 datasource_ids: &[
307 DatasourceId::GemArchiveExtracted,
308 DatasourceId::Gemspec,
309 DatasourceId::Gemfile,
310 DatasourceId::GemfileLock,
311 DatasourceId::GemArchive,
312 ],
313 sibling_file_patterns: &[
314 "metadata.gz-extract",
315 "**/data.gz-extract/*.gemspec",
316 "**/data.gz-extract/Gemfile",
317 "**/data.gz-extract/Gemfile.lock",
318 "*.gemspec",
319 "Gemfile",
320 "Gemfile.lock",
321 ],
322 mode: AssemblyMode::SiblingMerge,
323 },
324 AssemblerConfig {
326 datasource_ids: &[
327 DatasourceId::CondaMetaYaml,
328 DatasourceId::CondaYaml,
329 DatasourceId::CondaMetaJson,
330 ],
331 sibling_file_patterns: &[
332 "meta.yaml",
333 "meta.yml",
334 "environment.yml",
335 "environment.yaml",
336 "conda.yaml",
337 "env.yaml",
338 "*.json",
339 ],
340 mode: AssemblyMode::SiblingMerge,
341 },
342 AssemblerConfig {
344 datasource_ids: &[DatasourceId::RpmSpecfile],
345 sibling_file_patterns: &["*.spec"],
346 mode: AssemblyMode::SiblingMerge,
347 },
348 AssemblerConfig {
350 datasource_ids: &[
351 DatasourceId::DebianControlInSource,
352 DatasourceId::DebianCopyright,
353 ],
354 sibling_file_patterns: &["**/debian/control", "**/debian/copyright"],
355 mode: AssemblyMode::SiblingMerge,
356 },
357 AssemblerConfig {
359 datasource_ids: &[DatasourceId::BuildGradle, DatasourceId::GradleLockfile],
360 sibling_file_patterns: &["build.gradle", "build.gradle.kts", "gradle.lockfile"],
361 mode: AssemblyMode::SiblingMerge,
362 },
363 AssemblerConfig {
364 datasource_ids: &[DatasourceId::GradleModule],
365 sibling_file_patterns: &["*.module"],
366 mode: AssemblyMode::OnePerPackageData,
367 },
368 AssemblerConfig {
370 datasource_ids: &[
371 DatasourceId::CpanMetaJson,
372 DatasourceId::CpanMetaYml,
373 DatasourceId::CpanManifest,
374 DatasourceId::CpanDistIni,
375 DatasourceId::CpanMakefile,
376 ],
377 sibling_file_patterns: &[
378 "META.json",
379 "META.yml",
380 "MANIFEST",
381 "dist.ini",
382 "Makefile.PL",
383 ],
384 mode: AssemblyMode::SiblingMerge,
385 },
386 AssemblerConfig {
388 datasource_ids: &[
389 DatasourceId::NugetCsproj,
390 DatasourceId::NugetFsproj,
391 DatasourceId::NugetNuspec,
392 DatasourceId::NugetNupkg,
393 DatasourceId::NugetProjectJson,
394 DatasourceId::NugetProjectLockJson,
395 DatasourceId::NugetPackagesConfig,
396 DatasourceId::NugetPackagesLock,
397 DatasourceId::NugetVbproj,
398 ],
399 sibling_file_patterns: &[
400 "*.csproj",
401 "*.fsproj",
402 "*.nuspec",
403 "*.nupkg",
404 "project.json",
405 "project.lock.json",
406 "packages.config",
407 "packages.lock.json",
408 "*.vbproj",
409 ],
410 mode: AssemblyMode::SiblingMerge,
411 },
412 AssemblerConfig {
413 datasource_ids: &[DatasourceId::NugetDepsJson],
414 sibling_file_patterns: &["*.deps.json"],
415 mode: AssemblyMode::OnePerPackageData,
416 },
417 AssemblerConfig {
419 datasource_ids: &[
420 DatasourceId::SwiftPackageManifestJson,
421 DatasourceId::SwiftPackageResolved,
422 DatasourceId::SwiftPackageShowDependencies,
423 ],
424 sibling_file_patterns: &["Package.swift", "Package.resolved"],
425 mode: AssemblyMode::SiblingMerge,
426 },
427 AssemblerConfig {
434 datasource_ids: &[DatasourceId::BowerJson],
435 sibling_file_patterns: &["bower.json"],
436 mode: AssemblyMode::SiblingMerge,
437 },
438 AssemblerConfig {
440 datasource_ids: &[DatasourceId::CranDescription],
441 sibling_file_patterns: &["DESCRIPTION"],
442 mode: AssemblyMode::SiblingMerge,
443 },
444 AssemblerConfig {
446 datasource_ids: &[DatasourceId::FreebsdCompactManifest],
447 sibling_file_patterns: &["+COMPACT_MANIFEST"],
448 mode: AssemblyMode::SiblingMerge,
449 },
450 AssemblerConfig {
452 datasource_ids: &[DatasourceId::HaxelibJson],
453 sibling_file_patterns: &["haxelib.json"],
454 mode: AssemblyMode::SiblingMerge,
455 },
456 AssemblerConfig {
458 datasource_ids: &[DatasourceId::OpamFile],
459 sibling_file_patterns: &["opam"],
460 mode: AssemblyMode::SiblingMerge,
461 },
462 AssemblerConfig {
464 datasource_ids: &[DatasourceId::RpmMarinerManifest],
465 sibling_file_patterns: &["*.rpm.manifest"],
466 mode: AssemblyMode::SiblingMerge,
467 },
468 AssemblerConfig {
469 datasource_ids: &[DatasourceId::RpmYumdb],
470 sibling_file_patterns: &["**/var/lib/yum/yumdb/*/*/from_repo"],
471 mode: AssemblyMode::OnePerPackageData,
472 },
473 AssemblerConfig {
475 datasource_ids: &[DatasourceId::MicrosoftUpdateManifestMum],
476 sibling_file_patterns: &["*.mum"],
477 mode: AssemblyMode::SiblingMerge,
478 },
479 AssemblerConfig {
481 datasource_ids: &[DatasourceId::AutotoolsConfigure],
482 sibling_file_patterns: &["configure", "configure.ac"],
483 mode: AssemblyMode::SiblingMerge,
484 },
485 AssemblerConfig {
487 datasource_ids: &[DatasourceId::BazelBuild],
488 sibling_file_patterns: &["BUILD"],
489 mode: AssemblyMode::SiblingMerge,
490 },
491 AssemblerConfig {
492 datasource_ids: &[DatasourceId::BazelModule],
493 sibling_file_patterns: &["MODULE.bazel"],
494 mode: AssemblyMode::OnePerPackageData,
495 },
496 AssemblerConfig {
498 datasource_ids: &[DatasourceId::BuckFile, DatasourceId::BuckMetadata],
499 sibling_file_patterns: &["BUCK", ".buckconfig"],
500 mode: AssemblyMode::SiblingMerge,
501 },
502 AssemblerConfig {
504 datasource_ids: &[DatasourceId::AntIvyXml],
505 sibling_file_patterns: &["ivy.xml"],
506 mode: AssemblyMode::SiblingMerge,
507 },
508 AssemblerConfig {
510 datasource_ids: &[DatasourceId::MeteorPackage],
511 sibling_file_patterns: &["package.js"],
512 mode: AssemblyMode::SiblingMerge,
513 },
514 AssemblerConfig {
518 datasource_ids: &[DatasourceId::AlpineInstalledDb],
519 sibling_file_patterns: &["installed"],
520 mode: AssemblyMode::OnePerPackageData,
521 },
522 AssemblerConfig {
523 datasource_ids: &[DatasourceId::AlpineApkbuild],
524 sibling_file_patterns: &["APKBUILD"],
525 mode: AssemblyMode::SiblingMerge,
526 },
527 AssemblerConfig {
529 datasource_ids: &[
530 DatasourceId::RpmInstalledDatabaseBdb,
531 DatasourceId::RpmInstalledDatabaseNdb,
532 DatasourceId::RpmInstalledDatabaseSqlite,
533 ],
534 sibling_file_patterns: &["Packages", "Packages.db", "rpmdb.sqlite"],
535 mode: AssemblyMode::OnePerPackageData,
536 },
537 AssemblerConfig {
539 datasource_ids: &[
540 DatasourceId::DebianInstalledStatusDb,
541 DatasourceId::DebianDistrolessInstalledDb,
542 ],
543 sibling_file_patterns: &["status"],
544 mode: AssemblyMode::OnePerPackageData,
545 },
546 AssemblerConfig {
547 datasource_ids: &[DatasourceId::AboutFile],
548 sibling_file_patterns: &["*.ABOUT"],
549 mode: AssemblyMode::OnePerPackageData,
550 },
551];
552
553#[allow(dead_code)] pub static UNASSEMBLED_DATASOURCE_IDS: &[DatasourceId] = &[
563 DatasourceId::Readme,
565 DatasourceId::EtcOsRelease,
566 DatasourceId::Gitmodules,
567 DatasourceId::AlpineApkArchive,
569 DatasourceId::AndroidAarLibrary,
570 DatasourceId::AndroidApk,
571 DatasourceId::AppleDmg,
572 DatasourceId::Axis2Mar,
573 DatasourceId::ChromeCrx,
574 DatasourceId::DebianDeb,
575 DatasourceId::DebianOriginalSourceTarball,
576 DatasourceId::DebianSourceMetadataTarball,
577 DatasourceId::InstallshieldInstaller,
578 DatasourceId::IosIpa,
579 DatasourceId::IsoDiskImage,
580 DatasourceId::JavaEarArchive,
581 DatasourceId::JavaJar,
582 DatasourceId::JavaWarArchive,
583 DatasourceId::JbossSar,
584 DatasourceId::MicrosoftCabinet,
585 DatasourceId::MozillaXpi,
586 DatasourceId::NsisInstaller,
587 DatasourceId::RpmArchive,
588 DatasourceId::SharShellArchive,
589 DatasourceId::SquashfsDiskImage,
590 DatasourceId::ArchAurinfo,
592 DatasourceId::ArchPkginfo,
593 DatasourceId::ArchSrcinfo,
594 DatasourceId::Axis2ModuleXml,
595 DatasourceId::ClojureDepsEdn,
596 DatasourceId::ClojureProjectClj,
597 DatasourceId::DebianControlExtractedDeb,
598 DatasourceId::DebianInstalledFilesList,
599 DatasourceId::DebianInstalledMd5Sums,
600 DatasourceId::DebianMd5SumsInExtractedDeb,
601 DatasourceId::DebianSourceControlDsc,
602 DatasourceId::Dockerfile,
603 DatasourceId::HexMixLock,
604 DatasourceId::JavaEarApplicationXml,
605 DatasourceId::JavaWarWebXml,
606 DatasourceId::JbossServiceXml,
607 DatasourceId::MesonBuild,
608 DatasourceId::NugetDirectoryBuildProps,
609 DatasourceId::NugetDirectoryPackagesProps,
610 DatasourceId::RpmPackageLicenses,
611 DatasourceId::SbtBuildSbt,
612 DatasourceId::VcpkgJson,
613];
614
615#[cfg(test)]
616mod tests {
617 use super::*;
618 use std::collections::HashSet;
619 use strum::IntoEnumIterator;
620
621 #[test]
622 fn test_every_datasource_id_is_accounted_for() {
623 let mut assembled: HashSet<DatasourceId> = HashSet::new();
624 for config in ASSEMBLERS {
625 for &dsid in config.datasource_ids {
626 assembled.insert(dsid);
627 }
628 }
629
630 let unassembled: HashSet<DatasourceId> =
631 UNASSEMBLED_DATASOURCE_IDS.iter().copied().collect();
632
633 let overlap: Vec<_> = assembled.intersection(&unassembled).collect();
634 assert!(
635 overlap.is_empty(),
636 "Datasource IDs in BOTH ASSEMBLERS and UNASSEMBLED: {overlap:?}"
637 );
638
639 let missing: Vec<_> = DatasourceId::iter()
640 .filter(|dsid| !assembled.contains(dsid) && !unassembled.contains(dsid))
641 .collect();
642
643 assert!(
644 missing.is_empty(),
645 "Datasource IDs in NEITHER ASSEMBLERS nor UNASSEMBLED: {missing:?}\n\
646 Add each to an AssemblerConfig in ASSEMBLERS, or to UNASSEMBLED_DATASOURCE_IDS."
647 );
648 }
649
650 #[test]
651 fn test_post_assembly_passes_are_unique() {
652 let unique: HashSet<PostAssemblyPassKind> = POST_ASSEMBLY_PASSES.iter().copied().collect();
653
654 assert_eq!(
655 unique.len(),
656 POST_ASSEMBLY_PASSES.len(),
657 "POST_ASSEMBLY_PASSES contains duplicate entries"
658 );
659 }
660
661 #[test]
662 fn test_every_post_assembly_pass_kind_is_registered_once() {
663 let registered: HashSet<PostAssemblyPassKind> =
664 POST_ASSEMBLY_PASSES.iter().copied().collect();
665
666 let missing: Vec<_> = PostAssemblyPassKind::iter()
667 .filter(|pass| !registered.contains(pass))
668 .collect();
669
670 assert!(
671 missing.is_empty(),
672 "Post-assembly pass variants not registered in POST_ASSEMBLY_PASSES: {missing:?}"
673 );
674
675 for pass in PostAssemblyPassKind::iter() {
676 let count = POST_ASSEMBLY_PASSES
677 .iter()
678 .filter(|registered| **registered == pass)
679 .count();
680 assert_eq!(
681 count, 1,
682 "Post-assembly pass {pass:?} should be registered exactly once"
683 );
684 }
685 }
686}