Skip to main content

provenant/assembly/
assemblers.rs

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    python_requirements_assign, 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    PythonRequirementsAssign,
23    FileReferenceResolve,
24    RpmYumdbMerge,
25    NpmWorkspaceMerge,
26    CargoWorkspaceMerge,
27    NugetCpmResolve,
28    CargoResourceAssign,
29    ComposerResourceAssign,
30    RubyResourceAssign,
31}
32
33pub(super) fn special_directory_merger_for(
34    config_key: DatasourceId,
35) -> Option<SpecialDirectoryMergerKind> {
36    match config_key {
37        DatasourceId::HackageCabal => Some(SpecialDirectoryMergerKind::Hackage),
38        DatasourceId::SwiftPackageManifestJson => Some(SpecialDirectoryMergerKind::Skip),
39        _ => None,
40    }
41}
42
43pub(super) static POST_ASSEMBLY_PASSES: &[PostAssemblyPassKind] = &[
44    PostAssemblyPassKind::SwiftMerge,
45    PostAssemblyPassKind::CondaRootfsMerge,
46    PostAssemblyPassKind::NpmResourceAssign,
47    PostAssemblyPassKind::PythonRequirementsAssign,
48    PostAssemblyPassKind::FileReferenceResolve,
49    PostAssemblyPassKind::RpmYumdbMerge,
50    PostAssemblyPassKind::NpmWorkspaceMerge,
51    PostAssemblyPassKind::CargoWorkspaceMerge,
52    PostAssemblyPassKind::NugetCpmResolve,
53    PostAssemblyPassKind::CargoResourceAssign,
54    PostAssemblyPassKind::ComposerResourceAssign,
55    PostAssemblyPassKind::RubyResourceAssign,
56];
57
58pub(super) fn run_post_assembly_passes(
59    files: &mut [FileInfo],
60    packages: &mut Vec<Package>,
61    dependencies: &mut Vec<TopLevelDependency>,
62) {
63    for pass in POST_ASSEMBLY_PASSES {
64        pass.run(files, packages, dependencies);
65    }
66}
67
68impl SpecialDirectoryMergerKind {
69    pub(super) fn run(
70        self,
71        files: &[FileInfo],
72        file_indices: &[usize],
73    ) -> Vec<DirectoryMergeOutput> {
74        match self {
75            Self::Skip => Vec::new(),
76            Self::Hackage => hackage_merge::assemble_hackage_packages(files, file_indices),
77        }
78    }
79}
80
81impl PostAssemblyPassKind {
82    fn run(
83        self,
84        files: &mut [FileInfo],
85        packages: &mut Vec<Package>,
86        dependencies: &mut Vec<TopLevelDependency>,
87    ) {
88        match self {
89            Self::SwiftMerge => swift_merge::assemble_swift_packages(files, packages, dependencies),
90            Self::CondaRootfsMerge => {
91                conda_rootfs_merge::merge_conda_rootfs_metadata(files, packages, dependencies)
92            }
93            Self::NpmResourceAssign => {
94                npm_resource_assign::assign_npm_package_resources(files, packages)
95            }
96            Self::PythonRequirementsAssign => {
97                python_requirements_assign::assign_python_requirements_to_projects(
98                    files,
99                    packages,
100                    dependencies,
101                )
102            }
103            Self::FileReferenceResolve => {
104                file_ref_resolve::resolve_file_references(files, packages, dependencies)
105            }
106            Self::RpmYumdbMerge => file_ref_resolve::merge_rpm_yumdb_metadata(files, packages),
107            Self::NpmWorkspaceMerge => {
108                npm_workspace_merge::assemble_npm_workspaces(files, packages, dependencies)
109            }
110            Self::CargoWorkspaceMerge => {
111                cargo_workspace_merge::assemble_cargo_workspaces(files, packages, dependencies)
112            }
113            Self::NugetCpmResolve => {
114                nuget_cpm_resolve::resolve_nuget_cpm_versions(files, dependencies)
115            }
116            Self::CargoResourceAssign => {
117                cargo_resource_assign::assign_cargo_package_resources(files, packages)
118            }
119            Self::ComposerResourceAssign => {
120                composer_resource_assign::assign_composer_package_resources(files, packages)
121            }
122            Self::RubyResourceAssign => {
123                ruby_resource_assign::assign_ruby_package_resources(files, packages)
124            }
125        }
126    }
127}
128
129pub static ASSEMBLERS: &[AssemblerConfig] = &[
130    // ── Sibling-merge assemblers ──
131    //
132    // npm ecosystem: package.json + lockfiles in same directory.
133    // NOTE: npm-shrinkwrap.json emits "npm_package_lock_json" as its datasource_id,
134    // so "npm_shrinkwrap_json" is NOT a real datasource_id.
135    AssemblerConfig {
136        datasource_ids: &[
137            DatasourceId::BunLock,
138            DatasourceId::BunLockb,
139            DatasourceId::NpmPackageJson,
140            DatasourceId::NpmPackageLockJson,
141            DatasourceId::YarnLock,
142            DatasourceId::PnpmLockYaml,
143            DatasourceId::PnpmWorkspaceYaml,
144        ],
145        sibling_file_patterns: &[
146            "package.json",
147            "bun.lock",
148            "bun.lockb",
149            ".package-lock.json",
150            "package-lock.json",
151            ".npm-shrinkwrap.json",
152            "npm-shrinkwrap.json",
153            "yarn.lock",
154            "pnpm-lock.yaml",
155            "shrinkwrap.yaml",
156            "pnpm-workspace.yaml",
157        ],
158        mode: AssemblyMode::SiblingMerge,
159    },
160    // Rust/Cargo ecosystem
161    AssemblerConfig {
162        datasource_ids: &[DatasourceId::CargoToml, DatasourceId::CargoLock],
163        sibling_file_patterns: &["Cargo.toml", "Cargo.lock"],
164        mode: AssemblyMode::SiblingMerge,
165    },
166    // CocoaPods ecosystem
167    AssemblerConfig {
168        datasource_ids: &[
169            DatasourceId::CocoapodsPodspec,
170            DatasourceId::CocoapodsPodspecJson,
171            DatasourceId::CocoapodsPodfile,
172            DatasourceId::CocoapodsPodfileLock,
173        ],
174        sibling_file_patterns: &["*.podspec", "*.podspec.json", "Podfile", "Podfile.lock"],
175        mode: AssemblyMode::SiblingMerge,
176    },
177    // PHP Composer ecosystem
178    AssemblerConfig {
179        datasource_ids: &[DatasourceId::PhpComposerJson, DatasourceId::PhpComposerLock],
180        sibling_file_patterns: &[
181            "*composer.json",
182            "composer.*.json",
183            "*composer.lock",
184            "composer.*.lock",
185        ],
186        mode: AssemblyMode::SiblingMerge,
187    },
188    // Go ecosystem (includes legacy Godeps)
189    AssemblerConfig {
190        datasource_ids: &[
191            DatasourceId::GoMod,
192            DatasourceId::GoModGraph,
193            DatasourceId::GoSum,
194            DatasourceId::GoWork,
195            DatasourceId::Godeps,
196        ],
197        sibling_file_patterns: &[
198            "go.mod",
199            "go.work",
200            "go.mod.graph",
201            "go.modgraph",
202            "go.sum",
203            "Godeps.json",
204        ],
205        mode: AssemblyMode::SiblingMerge,
206    },
207    // Dart/Flutter ecosystem
208    AssemblerConfig {
209        datasource_ids: &[DatasourceId::PubspecYaml, DatasourceId::PubspecLock],
210        sibling_file_patterns: &["pubspec.yaml", "pubspec.lock"],
211        mode: AssemblyMode::SiblingMerge,
212    },
213    // Pixi ecosystem
214    AssemblerConfig {
215        datasource_ids: &[DatasourceId::PixiToml, DatasourceId::PixiLock],
216        sibling_file_patterns: &["pixi.toml", "pixi.lock"],
217        mode: AssemblyMode::SiblingMerge,
218    },
219    AssemblerConfig {
220        datasource_ids: &[DatasourceId::NixFlakeNix, DatasourceId::NixFlakeLock],
221        sibling_file_patterns: &["flake.nix", "flake.lock"],
222        mode: AssemblyMode::SiblingMerge,
223    },
224    AssemblerConfig {
225        datasource_ids: &[DatasourceId::NixDefaultNix],
226        sibling_file_patterns: &["default.nix"],
227        mode: AssemblyMode::OnePerPackageData,
228    },
229    // Helm chart ecosystem
230    AssemblerConfig {
231        datasource_ids: &[DatasourceId::HelmChartYaml, DatasourceId::HelmChartLock],
232        sibling_file_patterns: &["Chart.yaml", "Chart.lock"],
233        mode: AssemblyMode::SiblingMerge,
234    },
235    AssemblerConfig {
236        datasource_ids: &[
237            DatasourceId::HackageCabal,
238            DatasourceId::HackageCabalProject,
239            DatasourceId::HackageStackYaml,
240        ],
241        sibling_file_patterns: &["*.cabal", "cabal.project", "stack.yaml"],
242        mode: AssemblyMode::SiblingMerge,
243    },
244    // Chef ecosystem
245    AssemblerConfig {
246        datasource_ids: &[
247            DatasourceId::ChefCookbookMetadataJson,
248            DatasourceId::ChefCookbookMetadataRb,
249        ],
250        sibling_file_patterns: &["metadata.json", "metadata.rb"],
251        mode: AssemblyMode::SiblingMerge,
252    },
253    // Conan (C/C++) ecosystem
254    AssemblerConfig {
255        datasource_ids: &[
256            DatasourceId::ConanConanFilePy,
257            DatasourceId::ConanConanFileTxt,
258            DatasourceId::ConanLock,
259            DatasourceId::ConanConanDataYml,
260        ],
261        sibling_file_patterns: &[
262            "conanfile.py",
263            "conanfile.txt",
264            "conan.lock",
265            "conandata.yml",
266        ],
267        mode: AssemblyMode::SiblingMerge,
268    },
269    // Maven/Java ecosystem (nested merge via META-INF)
270    AssemblerConfig {
271        datasource_ids: &[
272            DatasourceId::MavenPom,
273            DatasourceId::MavenPomProperties,
274            DatasourceId::JavaJarManifest,
275            DatasourceId::JavaOsgiManifest,
276        ],
277        sibling_file_patterns: &[
278            "pom.xml",
279            "*.pom",
280            "pom.properties",
281            "**/META-INF/MANIFEST.MF",
282        ],
283        mode: AssemblyMode::SiblingMerge,
284    },
285    AssemblerConfig {
286        datasource_ids: &[DatasourceId::PypiWheel, DatasourceId::PypiPipOriginJson],
287        sibling_file_patterns: &["*.whl", "origin.json"],
288        mode: AssemblyMode::SiblingMerge,
289    },
290    // Python/PyPI ecosystem
291    AssemblerConfig {
292        datasource_ids: &[
293            DatasourceId::PypiPyprojectToml,
294            DatasourceId::PypiSetupPy,
295            DatasourceId::PypiSetupCfg,
296            DatasourceId::PypiWheel,
297            DatasourceId::PypiWheelMetadata,
298            DatasourceId::PypiEgg,
299            DatasourceId::PypiJson,
300            DatasourceId::PypiSdistPkginfo,
301            DatasourceId::PypiInspectDeplock,
302            DatasourceId::PipRequirements,
303            DatasourceId::PypiPoetryLock,
304            DatasourceId::PypiPylockToml,
305            DatasourceId::PypiUvLock,
306            DatasourceId::Pipfile,
307            DatasourceId::PipfileLock,
308        ],
309        sibling_file_patterns: &[
310            "pyproject.toml",
311            "setup.py",
312            "setup.cfg",
313            "PKG-INFO",
314            "METADATA",
315            "pypi.json",
316            "pip-inspect.deplock",
317            "requirements*.txt",
318            "Pipfile",
319            "Pipfile.lock",
320            "poetry.lock",
321            "pylock.toml",
322            "pylock.*.toml",
323            "uv.lock",
324        ],
325        mode: AssemblyMode::SiblingMerge,
326    },
327    AssemblerConfig {
328        datasource_ids: &[DatasourceId::DenoJson, DatasourceId::DenoLock],
329        sibling_file_patterns: &["deno.json", "deno.jsonc", "deno.lock"],
330        mode: AssemblyMode::SiblingMerge,
331    },
332    // Ruby/RubyGems ecosystem
333    AssemblerConfig {
334        datasource_ids: &[
335            DatasourceId::GemArchiveExtracted,
336            DatasourceId::Gemspec,
337            DatasourceId::Gemfile,
338            DatasourceId::GemfileLock,
339            DatasourceId::GemArchive,
340        ],
341        sibling_file_patterns: &[
342            "metadata.gz-extract",
343            "**/data.gz-extract/*.gemspec",
344            "**/data.gz-extract/Gemfile",
345            "**/data.gz-extract/Gemfile.lock",
346            "*.gemspec",
347            "Gemfile",
348            "Gemfile.lock",
349        ],
350        mode: AssemblyMode::SiblingMerge,
351    },
352    // Conda ecosystem
353    AssemblerConfig {
354        datasource_ids: &[
355            DatasourceId::CondaMetaYaml,
356            DatasourceId::CondaYaml,
357            DatasourceId::CondaMetaJson,
358        ],
359        sibling_file_patterns: &[
360            "meta.yaml",
361            "meta.yml",
362            "environment.yml",
363            "environment.yaml",
364            "conda.yaml",
365            "conda.yml",
366            "*conda*.yaml",
367            "*conda*.yml",
368            "env.yaml",
369            "env.yml",
370            "*env*.yaml",
371            "*env*.yml",
372            "*environment*.yaml",
373            "*environment*.yml",
374            "*.json",
375        ],
376        mode: AssemblyMode::SiblingMerge,
377    },
378    // RPM specfile (source packages)
379    AssemblerConfig {
380        datasource_ids: &[DatasourceId::RpmSpecfile],
381        sibling_file_patterns: &["*.spec"],
382        mode: AssemblyMode::SiblingMerge,
383    },
384    // Debian source packages (nested merge via debian/ directory)
385    AssemblerConfig {
386        datasource_ids: &[
387            DatasourceId::DebianControlInSource,
388            DatasourceId::DebianCopyright,
389        ],
390        sibling_file_patterns: &["**/debian/control", "**/debian/copyright"],
391        mode: AssemblyMode::SiblingMerge,
392    },
393    // Gradle/Android ecosystem
394    AssemblerConfig {
395        datasource_ids: &[DatasourceId::BuildGradle, DatasourceId::GradleLockfile],
396        sibling_file_patterns: &["build.gradle", "build.gradle.kts", "gradle.lockfile"],
397        mode: AssemblyMode::SiblingMerge,
398    },
399    AssemblerConfig {
400        datasource_ids: &[DatasourceId::GradleModule],
401        sibling_file_patterns: &["*.module"],
402        mode: AssemblyMode::OnePerPackageData,
403    },
404    // CPAN/Perl ecosystem
405    AssemblerConfig {
406        datasource_ids: &[
407            DatasourceId::CpanMetaJson,
408            DatasourceId::CpanMetaYml,
409            DatasourceId::CpanManifest,
410            DatasourceId::CpanDistIni,
411            DatasourceId::CpanMakefile,
412        ],
413        sibling_file_patterns: &[
414            "META.json",
415            "META.yml",
416            "MANIFEST",
417            "dist.ini",
418            "Makefile.PL",
419        ],
420        mode: AssemblyMode::SiblingMerge,
421    },
422    // NuGet/.NET ecosystem
423    AssemblerConfig {
424        datasource_ids: &[
425            DatasourceId::NugetCsproj,
426            DatasourceId::NugetFsproj,
427            DatasourceId::NugetNuspec,
428            DatasourceId::NugetNupkg,
429            DatasourceId::NugetProjectJson,
430            DatasourceId::NugetProjectLockJson,
431            DatasourceId::NugetPackagesConfig,
432            DatasourceId::NugetPackagesLock,
433            DatasourceId::NugetVbproj,
434        ],
435        sibling_file_patterns: &[
436            "*.csproj",
437            "*.fsproj",
438            "*.nuspec",
439            "*.nupkg",
440            "project.json",
441            "project.lock.json",
442            "packages.config",
443            "packages.lock.json",
444            "*.packages.lock.json",
445            "*.vbproj",
446        ],
447        mode: AssemblyMode::SiblingMerge,
448    },
449    AssemblerConfig {
450        datasource_ids: &[DatasourceId::NugetDepsJson],
451        sibling_file_patterns: &["*.deps.json"],
452        mode: AssemblyMode::OnePerPackageData,
453    },
454    // Swift/SPM ecosystem
455    AssemblerConfig {
456        datasource_ids: &[
457            DatasourceId::SwiftPackageManifestJson,
458            DatasourceId::SwiftPackageResolved,
459            DatasourceId::SwiftPackageShowDependencies,
460        ],
461        sibling_file_patterns: &[
462            "Package.swift.json",
463            "Package.swift.deplock",
464            "Package.resolved",
465            ".package.resolved",
466            "swift-show-dependencies.deplock",
467        ],
468        mode: AssemblyMode::SiblingMerge,
469    },
470    // ── Standalone assemblers (single file → single package) ──
471    //
472    // These ecosystems have only one manifest file type with no sibling merging.
473    // They still need configs so their datasource_ids are recognized by the assembler.
474    //
475    // Bower (JavaScript)
476    AssemblerConfig {
477        datasource_ids: &[DatasourceId::BowerJson],
478        sibling_file_patterns: &["bower.json"],
479        mode: AssemblyMode::SiblingMerge,
480    },
481    // CRAN (R language)
482    AssemblerConfig {
483        datasource_ids: &[DatasourceId::CranDescription],
484        sibling_file_patterns: &["DESCRIPTION"],
485        mode: AssemblyMode::SiblingMerge,
486    },
487    // FreeBSD packages
488    AssemblerConfig {
489        datasource_ids: &[DatasourceId::FreebsdCompactManifest],
490        sibling_file_patterns: &["+COMPACT_MANIFEST"],
491        mode: AssemblyMode::SiblingMerge,
492    },
493    // Haxe ecosystem
494    AssemblerConfig {
495        datasource_ids: &[DatasourceId::HaxelibJson],
496        sibling_file_patterns: &["haxelib.json"],
497        mode: AssemblyMode::SiblingMerge,
498    },
499    AssemblerConfig {
500        datasource_ids: &[DatasourceId::Gitmodules],
501        sibling_file_patterns: &[".gitmodules"],
502        mode: AssemblyMode::SiblingMerge,
503    },
504    // OCaml/opam ecosystem
505    AssemblerConfig {
506        datasource_ids: &[DatasourceId::OpamFile],
507        sibling_file_patterns: &["opam", "*.opam"],
508        mode: AssemblyMode::SiblingMerge,
509    },
510    // RPM Mariner manifest
511    AssemblerConfig {
512        datasource_ids: &[DatasourceId::RpmMarinerManifest],
513        sibling_file_patterns: &["*.rpm.manifest"],
514        mode: AssemblyMode::SiblingMerge,
515    },
516    AssemblerConfig {
517        datasource_ids: &[DatasourceId::RpmYumdb],
518        sibling_file_patterns: &["**/var/lib/yum/yumdb/*/*/from_repo"],
519        mode: AssemblyMode::OnePerPackageData,
520    },
521    // Microsoft Update Manifest
522    AssemblerConfig {
523        datasource_ids: &[DatasourceId::MicrosoftUpdateManifestMum],
524        sibling_file_patterns: &["*.mum"],
525        mode: AssemblyMode::SiblingMerge,
526    },
527    // Autotools (C/C++ build system)
528    AssemblerConfig {
529        datasource_ids: &[DatasourceId::AutotoolsConfigure],
530        sibling_file_patterns: &["configure", "configure.ac"],
531        mode: AssemblyMode::SiblingMerge,
532    },
533    // Bazel (build system)
534    AssemblerConfig {
535        datasource_ids: &[DatasourceId::BazelBuild],
536        sibling_file_patterns: &["BUILD"],
537        mode: AssemblyMode::SiblingMerge,
538    },
539    AssemblerConfig {
540        datasource_ids: &[DatasourceId::BazelModule],
541        sibling_file_patterns: &["MODULE.bazel"],
542        mode: AssemblyMode::OnePerPackageData,
543    },
544    // Buck (build system)
545    AssemblerConfig {
546        datasource_ids: &[DatasourceId::BuckFile, DatasourceId::BuckMetadata],
547        sibling_file_patterns: &["BUCK", "METADATA.bzl", ".buckconfig"],
548        mode: AssemblyMode::SiblingMerge,
549    },
550    // Ant/Ivy (Java dependency management)
551    AssemblerConfig {
552        datasource_ids: &[DatasourceId::AntIvyXml],
553        sibling_file_patterns: &["ivy.xml"],
554        mode: AssemblyMode::SiblingMerge,
555    },
556    // Meteor (JavaScript platform)
557    AssemblerConfig {
558        datasource_ids: &[DatasourceId::MeteorPackage],
559        sibling_file_patterns: &["package.js"],
560        mode: AssemblyMode::SiblingMerge,
561    },
562    // ── One-per-PackageData assemblers (database files with many packages) ──
563    //
564    // Alpine installed package database
565    AssemblerConfig {
566        datasource_ids: &[DatasourceId::AlpineInstalledDb],
567        sibling_file_patterns: &["installed"],
568        mode: AssemblyMode::OnePerPackageData,
569    },
570    AssemblerConfig {
571        datasource_ids: &[DatasourceId::AlpineApkbuild],
572        sibling_file_patterns: &["APKBUILD"],
573        mode: AssemblyMode::SiblingMerge,
574    },
575    // RPM installed package databases (BDB, NDB, SQLite)
576    AssemblerConfig {
577        datasource_ids: &[
578            DatasourceId::RpmInstalledDatabaseBdb,
579            DatasourceId::RpmInstalledDatabaseNdb,
580            DatasourceId::RpmInstalledDatabaseSqlite,
581        ],
582        sibling_file_patterns: &["Packages", "Packages.db", "rpmdb.sqlite"],
583        mode: AssemblyMode::OnePerPackageData,
584    },
585    // Debian installed package databases
586    AssemblerConfig {
587        datasource_ids: &[
588            DatasourceId::DebianInstalledStatusDb,
589            DatasourceId::DebianDistrolessInstalledDb,
590        ],
591        sibling_file_patterns: &["status"],
592        mode: AssemblyMode::OnePerPackageData,
593    },
594    AssemblerConfig {
595        datasource_ids: &[
596            DatasourceId::DebianControlExtractedDeb,
597            DatasourceId::DebianMd5SumsInExtractedDeb,
598        ],
599        sibling_file_patterns: &["control", "md5sums"],
600        mode: AssemblyMode::SiblingMerge,
601    },
602    AssemblerConfig {
603        datasource_ids: &[DatasourceId::AboutFile],
604        sibling_file_patterns: &["*.ABOUT"],
605        mode: AssemblyMode::OnePerPackageData,
606    },
607];
608
609// Datasource IDs intentionally excluded from package assembly.
610//
611// This list is runtime-significant: files with these datasource IDs may remain
612// unowned by any Package, while their dependencies are still eligible for
613// top-level hoisting. Tests also use it to enforce explicit assembly accounting.
614pub static UNASSEMBLED_DATASOURCE_IDS: &[DatasourceId] = &[
615    // Non-package metadata
616    DatasourceId::Readme,
617    DatasourceId::EtcOsRelease,
618    // Binary archives (require external extraction via ExtractCode before scanning)
619    DatasourceId::AlpineApkArchive,
620    DatasourceId::AndroidAarLibrary,
621    DatasourceId::AndroidApk,
622    DatasourceId::AppleDmg,
623    DatasourceId::Axis2Mar,
624    DatasourceId::ChromeCrx,
625    DatasourceId::DebianDeb,
626    DatasourceId::DebianOriginalSourceTarball,
627    DatasourceId::DebianSourceMetadataTarball,
628    DatasourceId::InstallshieldInstaller,
629    DatasourceId::IosIpa,
630    DatasourceId::IsoDiskImage,
631    DatasourceId::JavaEarArchive,
632    DatasourceId::JavaJar,
633    DatasourceId::JavaWarArchive,
634    DatasourceId::JbossSar,
635    DatasourceId::MicrosoftCabinet,
636    DatasourceId::MozillaXpi,
637    DatasourceId::NsisInstaller,
638    DatasourceId::RpmArchive,
639    DatasourceId::SharShellArchive,
640    DatasourceId::SquashfsDiskImage,
641    // Supplementary metadata (not primary package definitions)
642    DatasourceId::ArchAurinfo,
643    DatasourceId::ArchPkginfo,
644    DatasourceId::ArchSrcinfo,
645    DatasourceId::Axis2ModuleXml,
646    DatasourceId::ClojureDepsEdn,
647    DatasourceId::ClojureProjectClj,
648    DatasourceId::DebianInstalledFilesList,
649    DatasourceId::DebianInstalledMd5Sums,
650    DatasourceId::DebianSourceControlDsc,
651    DatasourceId::Dockerfile,
652    DatasourceId::HexMixLock,
653    DatasourceId::JavaEarApplicationXml,
654    DatasourceId::JavaWarWebXml,
655    DatasourceId::JbossServiceXml,
656    DatasourceId::MesonBuild,
657    DatasourceId::NugetDirectoryBuildProps,
658    DatasourceId::NugetDirectoryPackagesProps,
659    DatasourceId::RpmPackageLicenses,
660    DatasourceId::SbtBuildSbt,
661    DatasourceId::VcpkgJson,
662];
663
664#[cfg(test)]
665mod tests {
666    use super::*;
667    use std::collections::HashSet;
668    use strum::IntoEnumIterator;
669
670    #[test]
671    fn test_every_datasource_id_is_accounted_for() {
672        let mut assembled: HashSet<DatasourceId> = HashSet::new();
673        for config in ASSEMBLERS {
674            for &dsid in config.datasource_ids {
675                assembled.insert(dsid);
676            }
677        }
678
679        let unassembled: HashSet<DatasourceId> =
680            UNASSEMBLED_DATASOURCE_IDS.iter().copied().collect();
681
682        let overlap: Vec<_> = assembled.intersection(&unassembled).collect();
683        assert!(
684            overlap.is_empty(),
685            "Datasource IDs in BOTH ASSEMBLERS and UNASSEMBLED: {overlap:?}"
686        );
687
688        let missing: Vec<_> = DatasourceId::iter()
689            .filter(|dsid| !assembled.contains(dsid) && !unassembled.contains(dsid))
690            .collect();
691
692        assert!(
693            missing.is_empty(),
694            "Datasource IDs in NEITHER ASSEMBLERS nor UNASSEMBLED: {missing:?}\n\
695             Add each to an AssemblerConfig in ASSEMBLERS, or to UNASSEMBLED_DATASOURCE_IDS."
696        );
697    }
698
699    #[test]
700    fn test_post_assembly_passes_are_unique() {
701        let unique: HashSet<PostAssemblyPassKind> = POST_ASSEMBLY_PASSES.iter().copied().collect();
702
703        assert_eq!(
704            unique.len(),
705            POST_ASSEMBLY_PASSES.len(),
706            "POST_ASSEMBLY_PASSES contains duplicate entries"
707        );
708    }
709
710    #[test]
711    fn test_every_post_assembly_pass_kind_is_registered_once() {
712        let registered: HashSet<PostAssemblyPassKind> =
713            POST_ASSEMBLY_PASSES.iter().copied().collect();
714
715        let missing: Vec<_> = PostAssemblyPassKind::iter()
716            .filter(|pass| !registered.contains(pass))
717            .collect();
718
719        assert!(
720            missing.is_empty(),
721            "Post-assembly pass variants not registered in POST_ASSEMBLY_PASSES: {missing:?}"
722        );
723
724        for pass in PostAssemblyPassKind::iter() {
725            let count = POST_ASSEMBLY_PASSES
726                .iter()
727                .filter(|registered| **registered == pass)
728                .count();
729            assert_eq!(
730                count, 1,
731                "Post-assembly pass {pass:?} should be registered exactly once"
732            );
733        }
734    }
735}