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 {
208 datasource_ids: &[DatasourceId::NixFlakeNix, DatasourceId::NixFlakeLock],
209 sibling_file_patterns: &["flake.nix", "flake.lock"],
210 mode: AssemblyMode::SiblingMerge,
211 },
212 AssemblerConfig {
213 datasource_ids: &[DatasourceId::NixDefaultNix],
214 sibling_file_patterns: &["default.nix"],
215 mode: AssemblyMode::OnePerPackageData,
216 },
217 AssemblerConfig {
219 datasource_ids: &[DatasourceId::HelmChartYaml, DatasourceId::HelmChartLock],
220 sibling_file_patterns: &["Chart.yaml", "Chart.lock"],
221 mode: AssemblyMode::SiblingMerge,
222 },
223 AssemblerConfig {
224 datasource_ids: &[
225 DatasourceId::HackageCabal,
226 DatasourceId::HackageCabalProject,
227 DatasourceId::HackageStackYaml,
228 ],
229 sibling_file_patterns: &["*.cabal", "cabal.project", "stack.yaml"],
230 mode: AssemblyMode::SiblingMerge,
231 },
232 AssemblerConfig {
234 datasource_ids: &[
235 DatasourceId::ChefCookbookMetadataJson,
236 DatasourceId::ChefCookbookMetadataRb,
237 ],
238 sibling_file_patterns: &["metadata.json", "metadata.rb"],
239 mode: AssemblyMode::SiblingMerge,
240 },
241 AssemblerConfig {
243 datasource_ids: &[
244 DatasourceId::ConanConanFilePy,
245 DatasourceId::ConanConanFileTxt,
246 DatasourceId::ConanLock,
247 DatasourceId::ConanConanDataYml,
248 ],
249 sibling_file_patterns: &[
250 "conanfile.py",
251 "conanfile.txt",
252 "conan.lock",
253 "conandata.yml",
254 ],
255 mode: AssemblyMode::SiblingMerge,
256 },
257 AssemblerConfig {
259 datasource_ids: &[
260 DatasourceId::MavenPom,
261 DatasourceId::MavenPomProperties,
262 DatasourceId::JavaJarManifest,
263 DatasourceId::JavaOsgiManifest,
264 ],
265 sibling_file_patterns: &["pom.xml", "pom.properties", "**/META-INF/MANIFEST.MF"],
266 mode: AssemblyMode::SiblingMerge,
267 },
268 AssemblerConfig {
269 datasource_ids: &[DatasourceId::PypiWheel, DatasourceId::PypiPipOriginJson],
270 sibling_file_patterns: &["*.whl", "origin.json"],
271 mode: AssemblyMode::SiblingMerge,
272 },
273 AssemblerConfig {
275 datasource_ids: &[
276 DatasourceId::PypiPyprojectToml,
277 DatasourceId::PypiSetupPy,
278 DatasourceId::PypiSetupCfg,
279 DatasourceId::PypiWheel,
280 DatasourceId::PypiWheelMetadata,
281 DatasourceId::PypiEgg,
282 DatasourceId::PypiJson,
283 DatasourceId::PypiSdistPkginfo,
284 DatasourceId::PypiInspectDeplock,
285 DatasourceId::PipRequirements,
286 DatasourceId::PypiPoetryLock,
287 DatasourceId::PypiPylockToml,
288 DatasourceId::PypiUvLock,
289 DatasourceId::Pipfile,
290 DatasourceId::PipfileLock,
291 ],
292 sibling_file_patterns: &[
293 "pyproject.toml",
294 "setup.py",
295 "setup.cfg",
296 "PKG-INFO",
297 "METADATA",
298 "pypi.json",
299 "requirements*.txt",
300 "Pipfile",
301 "Pipfile.lock",
302 "poetry.lock",
303 "pylock.toml",
304 "pylock.*.toml",
305 "uv.lock",
306 ],
307 mode: AssemblyMode::SiblingMerge,
308 },
309 AssemblerConfig {
310 datasource_ids: &[DatasourceId::DenoJson, DatasourceId::DenoLock],
311 sibling_file_patterns: &["deno.json", "deno.jsonc", "deno.lock"],
312 mode: AssemblyMode::SiblingMerge,
313 },
314 AssemblerConfig {
316 datasource_ids: &[
317 DatasourceId::GemArchiveExtracted,
318 DatasourceId::Gemspec,
319 DatasourceId::Gemfile,
320 DatasourceId::GemfileLock,
321 DatasourceId::GemArchive,
322 ],
323 sibling_file_patterns: &[
324 "metadata.gz-extract",
325 "**/data.gz-extract/*.gemspec",
326 "**/data.gz-extract/Gemfile",
327 "**/data.gz-extract/Gemfile.lock",
328 "*.gemspec",
329 "Gemfile",
330 "Gemfile.lock",
331 ],
332 mode: AssemblyMode::SiblingMerge,
333 },
334 AssemblerConfig {
336 datasource_ids: &[
337 DatasourceId::CondaMetaYaml,
338 DatasourceId::CondaYaml,
339 DatasourceId::CondaMetaJson,
340 ],
341 sibling_file_patterns: &[
342 "meta.yaml",
343 "meta.yml",
344 "environment.yml",
345 "environment.yaml",
346 "conda.yaml",
347 "env.yaml",
348 "*.json",
349 ],
350 mode: AssemblyMode::SiblingMerge,
351 },
352 AssemblerConfig {
354 datasource_ids: &[DatasourceId::RpmSpecfile],
355 sibling_file_patterns: &["*.spec"],
356 mode: AssemblyMode::SiblingMerge,
357 },
358 AssemblerConfig {
360 datasource_ids: &[
361 DatasourceId::DebianControlInSource,
362 DatasourceId::DebianCopyright,
363 ],
364 sibling_file_patterns: &["**/debian/control", "**/debian/copyright"],
365 mode: AssemblyMode::SiblingMerge,
366 },
367 AssemblerConfig {
369 datasource_ids: &[DatasourceId::BuildGradle, DatasourceId::GradleLockfile],
370 sibling_file_patterns: &["build.gradle", "build.gradle.kts", "gradle.lockfile"],
371 mode: AssemblyMode::SiblingMerge,
372 },
373 AssemblerConfig {
374 datasource_ids: &[DatasourceId::GradleModule],
375 sibling_file_patterns: &["*.module"],
376 mode: AssemblyMode::OnePerPackageData,
377 },
378 AssemblerConfig {
380 datasource_ids: &[
381 DatasourceId::CpanMetaJson,
382 DatasourceId::CpanMetaYml,
383 DatasourceId::CpanManifest,
384 DatasourceId::CpanDistIni,
385 DatasourceId::CpanMakefile,
386 ],
387 sibling_file_patterns: &[
388 "META.json",
389 "META.yml",
390 "MANIFEST",
391 "dist.ini",
392 "Makefile.PL",
393 ],
394 mode: AssemblyMode::SiblingMerge,
395 },
396 AssemblerConfig {
398 datasource_ids: &[
399 DatasourceId::NugetCsproj,
400 DatasourceId::NugetFsproj,
401 DatasourceId::NugetNuspec,
402 DatasourceId::NugetNupkg,
403 DatasourceId::NugetProjectJson,
404 DatasourceId::NugetProjectLockJson,
405 DatasourceId::NugetPackagesConfig,
406 DatasourceId::NugetPackagesLock,
407 DatasourceId::NugetVbproj,
408 ],
409 sibling_file_patterns: &[
410 "*.csproj",
411 "*.fsproj",
412 "*.nuspec",
413 "*.nupkg",
414 "project.json",
415 "project.lock.json",
416 "packages.config",
417 "packages.lock.json",
418 "*.vbproj",
419 ],
420 mode: AssemblyMode::SiblingMerge,
421 },
422 AssemblerConfig {
423 datasource_ids: &[DatasourceId::NugetDepsJson],
424 sibling_file_patterns: &["*.deps.json"],
425 mode: AssemblyMode::OnePerPackageData,
426 },
427 AssemblerConfig {
429 datasource_ids: &[
430 DatasourceId::SwiftPackageManifestJson,
431 DatasourceId::SwiftPackageResolved,
432 DatasourceId::SwiftPackageShowDependencies,
433 ],
434 sibling_file_patterns: &["Package.swift", "Package.resolved"],
435 mode: AssemblyMode::SiblingMerge,
436 },
437 AssemblerConfig {
444 datasource_ids: &[DatasourceId::BowerJson],
445 sibling_file_patterns: &["bower.json"],
446 mode: AssemblyMode::SiblingMerge,
447 },
448 AssemblerConfig {
450 datasource_ids: &[DatasourceId::CranDescription],
451 sibling_file_patterns: &["DESCRIPTION"],
452 mode: AssemblyMode::SiblingMerge,
453 },
454 AssemblerConfig {
456 datasource_ids: &[DatasourceId::FreebsdCompactManifest],
457 sibling_file_patterns: &["+COMPACT_MANIFEST"],
458 mode: AssemblyMode::SiblingMerge,
459 },
460 AssemblerConfig {
462 datasource_ids: &[DatasourceId::HaxelibJson],
463 sibling_file_patterns: &["haxelib.json"],
464 mode: AssemblyMode::SiblingMerge,
465 },
466 AssemblerConfig {
468 datasource_ids: &[DatasourceId::OpamFile],
469 sibling_file_patterns: &["opam"],
470 mode: AssemblyMode::SiblingMerge,
471 },
472 AssemblerConfig {
474 datasource_ids: &[DatasourceId::RpmMarinerManifest],
475 sibling_file_patterns: &["*.rpm.manifest"],
476 mode: AssemblyMode::SiblingMerge,
477 },
478 AssemblerConfig {
479 datasource_ids: &[DatasourceId::RpmYumdb],
480 sibling_file_patterns: &["**/var/lib/yum/yumdb/*/*/from_repo"],
481 mode: AssemblyMode::OnePerPackageData,
482 },
483 AssemblerConfig {
485 datasource_ids: &[DatasourceId::MicrosoftUpdateManifestMum],
486 sibling_file_patterns: &["*.mum"],
487 mode: AssemblyMode::SiblingMerge,
488 },
489 AssemblerConfig {
491 datasource_ids: &[DatasourceId::AutotoolsConfigure],
492 sibling_file_patterns: &["configure", "configure.ac"],
493 mode: AssemblyMode::SiblingMerge,
494 },
495 AssemblerConfig {
497 datasource_ids: &[DatasourceId::BazelBuild],
498 sibling_file_patterns: &["BUILD"],
499 mode: AssemblyMode::SiblingMerge,
500 },
501 AssemblerConfig {
502 datasource_ids: &[DatasourceId::BazelModule],
503 sibling_file_patterns: &["MODULE.bazel"],
504 mode: AssemblyMode::OnePerPackageData,
505 },
506 AssemblerConfig {
508 datasource_ids: &[DatasourceId::BuckFile, DatasourceId::BuckMetadata],
509 sibling_file_patterns: &["BUCK", ".buckconfig"],
510 mode: AssemblyMode::SiblingMerge,
511 },
512 AssemblerConfig {
514 datasource_ids: &[DatasourceId::AntIvyXml],
515 sibling_file_patterns: &["ivy.xml"],
516 mode: AssemblyMode::SiblingMerge,
517 },
518 AssemblerConfig {
520 datasource_ids: &[DatasourceId::MeteorPackage],
521 sibling_file_patterns: &["package.js"],
522 mode: AssemblyMode::SiblingMerge,
523 },
524 AssemblerConfig {
528 datasource_ids: &[DatasourceId::AlpineInstalledDb],
529 sibling_file_patterns: &["installed"],
530 mode: AssemblyMode::OnePerPackageData,
531 },
532 AssemblerConfig {
533 datasource_ids: &[DatasourceId::AlpineApkbuild],
534 sibling_file_patterns: &["APKBUILD"],
535 mode: AssemblyMode::SiblingMerge,
536 },
537 AssemblerConfig {
539 datasource_ids: &[
540 DatasourceId::RpmInstalledDatabaseBdb,
541 DatasourceId::RpmInstalledDatabaseNdb,
542 DatasourceId::RpmInstalledDatabaseSqlite,
543 ],
544 sibling_file_patterns: &["Packages", "Packages.db", "rpmdb.sqlite"],
545 mode: AssemblyMode::OnePerPackageData,
546 },
547 AssemblerConfig {
549 datasource_ids: &[
550 DatasourceId::DebianInstalledStatusDb,
551 DatasourceId::DebianDistrolessInstalledDb,
552 ],
553 sibling_file_patterns: &["status"],
554 mode: AssemblyMode::OnePerPackageData,
555 },
556 AssemblerConfig {
557 datasource_ids: &[DatasourceId::AboutFile],
558 sibling_file_patterns: &["*.ABOUT"],
559 mode: AssemblyMode::OnePerPackageData,
560 },
561];
562
563#[cfg(test)]
572pub static UNASSEMBLED_DATASOURCE_IDS: &[DatasourceId] = &[
573 DatasourceId::Readme,
575 DatasourceId::EtcOsRelease,
576 DatasourceId::Gitmodules,
577 DatasourceId::AlpineApkArchive,
579 DatasourceId::AndroidAarLibrary,
580 DatasourceId::AndroidApk,
581 DatasourceId::AppleDmg,
582 DatasourceId::Axis2Mar,
583 DatasourceId::ChromeCrx,
584 DatasourceId::DebianDeb,
585 DatasourceId::DebianOriginalSourceTarball,
586 DatasourceId::DebianSourceMetadataTarball,
587 DatasourceId::InstallshieldInstaller,
588 DatasourceId::IosIpa,
589 DatasourceId::IsoDiskImage,
590 DatasourceId::JavaEarArchive,
591 DatasourceId::JavaJar,
592 DatasourceId::JavaWarArchive,
593 DatasourceId::JbossSar,
594 DatasourceId::MicrosoftCabinet,
595 DatasourceId::MozillaXpi,
596 DatasourceId::NsisInstaller,
597 DatasourceId::RpmArchive,
598 DatasourceId::SharShellArchive,
599 DatasourceId::SquashfsDiskImage,
600 DatasourceId::ArchAurinfo,
602 DatasourceId::ArchPkginfo,
603 DatasourceId::ArchSrcinfo,
604 DatasourceId::Axis2ModuleXml,
605 DatasourceId::ClojureDepsEdn,
606 DatasourceId::ClojureProjectClj,
607 DatasourceId::DebianControlExtractedDeb,
608 DatasourceId::DebianInstalledFilesList,
609 DatasourceId::DebianInstalledMd5Sums,
610 DatasourceId::DebianMd5SumsInExtractedDeb,
611 DatasourceId::DebianSourceControlDsc,
612 DatasourceId::Dockerfile,
613 DatasourceId::HexMixLock,
614 DatasourceId::JavaEarApplicationXml,
615 DatasourceId::JavaWarWebXml,
616 DatasourceId::JbossServiceXml,
617 DatasourceId::MesonBuild,
618 DatasourceId::NugetDirectoryBuildProps,
619 DatasourceId::NugetDirectoryPackagesProps,
620 DatasourceId::RpmPackageLicenses,
621 DatasourceId::SbtBuildSbt,
622 DatasourceId::VcpkgJson,
623];
624
625#[cfg(test)]
626mod tests {
627 use super::*;
628 use std::collections::HashSet;
629 use strum::IntoEnumIterator;
630
631 #[test]
632 fn test_every_datasource_id_is_accounted_for() {
633 let mut assembled: HashSet<DatasourceId> = HashSet::new();
634 for config in ASSEMBLERS {
635 for &dsid in config.datasource_ids {
636 assembled.insert(dsid);
637 }
638 }
639
640 let unassembled: HashSet<DatasourceId> =
641 UNASSEMBLED_DATASOURCE_IDS.iter().copied().collect();
642
643 let overlap: Vec<_> = assembled.intersection(&unassembled).collect();
644 assert!(
645 overlap.is_empty(),
646 "Datasource IDs in BOTH ASSEMBLERS and UNASSEMBLED: {overlap:?}"
647 );
648
649 let missing: Vec<_> = DatasourceId::iter()
650 .filter(|dsid| !assembled.contains(dsid) && !unassembled.contains(dsid))
651 .collect();
652
653 assert!(
654 missing.is_empty(),
655 "Datasource IDs in NEITHER ASSEMBLERS nor UNASSEMBLED: {missing:?}\n\
656 Add each to an AssemblerConfig in ASSEMBLERS, or to UNASSEMBLED_DATASOURCE_IDS."
657 );
658 }
659
660 #[test]
661 fn test_post_assembly_passes_are_unique() {
662 let unique: HashSet<PostAssemblyPassKind> = POST_ASSEMBLY_PASSES.iter().copied().collect();
663
664 assert_eq!(
665 unique.len(),
666 POST_ASSEMBLY_PASSES.len(),
667 "POST_ASSEMBLY_PASSES contains duplicate entries"
668 );
669 }
670
671 #[test]
672 fn test_every_post_assembly_pass_kind_is_registered_once() {
673 let registered: HashSet<PostAssemblyPassKind> =
674 POST_ASSEMBLY_PASSES.iter().copied().collect();
675
676 let missing: Vec<_> = PostAssemblyPassKind::iter()
677 .filter(|pass| !registered.contains(pass))
678 .collect();
679
680 assert!(
681 missing.is_empty(),
682 "Post-assembly pass variants not registered in POST_ASSEMBLY_PASSES: {missing:?}"
683 );
684
685 for pass in PostAssemblyPassKind::iter() {
686 let count = POST_ASSEMBLY_PASSES
687 .iter()
688 .filter(|registered| **registered == pass)
689 .count();
690 assert_eq!(
691 count, 1,
692 "Post-assembly pass {pass:?} should be registered exactly once"
693 );
694 }
695 }
696}