1use std::collections::HashSet;
8
9use crate::models::PackageType;
10use crate::models::{DatasourceId, FileInfo, Package, TopLevelDependency};
11use strum::EnumIter;
12
13use super::{
14 AssemblerConfig, AssemblyMode, DirectoryMergeOutput, bazel_prune, cargo_resource_assign,
15 composer_resource_assign, conda_rootfs_merge, debian_source_merge, file_ref_resolve,
16 hackage_merge, nix_flake_compat_merge, npm_resource_assign, nuget_cpm_resolve,
17 python_requirements_assign, ruby_resource_assign, swift_merge, topology,
18};
19
20#[derive(Clone, Copy)]
21pub(super) enum SpecialDirectoryMergerKind {
22 Skip,
23 Cocoapods,
24 DebianSource,
25 Hackage,
26 Huggingface,
27 WindowsUpdate,
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIter)]
31pub(super) enum PostAssemblyPassKind {
32 SwiftMerge,
33 CondaRootfsMerge,
34 NpmResourceAssign,
35 PythonRequirementsAssign,
36 FileReferenceResolve,
37 RpmYumdbMerge,
38 NpmWorkspaceMerge,
39 CargoWorkspaceMerge,
40 NugetCpmResolve,
41 CargoResourceAssign,
42 ComposerResourceAssign,
43 RubyResourceAssign,
44 NixFlakeCompatMerge,
45 BazelPrune,
46}
47
48pub(super) fn special_directory_merger_for(
49 config_key: DatasourceId,
50) -> Option<SpecialDirectoryMergerKind> {
51 match config_key {
52 DatasourceId::CocoapodsPodspec => Some(SpecialDirectoryMergerKind::Cocoapods),
53 DatasourceId::DebianControlInSource => Some(SpecialDirectoryMergerKind::DebianSource),
54 DatasourceId::HackageCabal => Some(SpecialDirectoryMergerKind::Hackage),
55 DatasourceId::HuggingfaceModelCard => Some(SpecialDirectoryMergerKind::Huggingface),
56 DatasourceId::MicrosoftUpdateManifestMum => Some(SpecialDirectoryMergerKind::WindowsUpdate),
57 DatasourceId::SwiftPackageManifestJson => Some(SpecialDirectoryMergerKind::Skip),
58 _ => None,
59 }
60}
61
62pub(super) static POST_ASSEMBLY_PASSES: &[PostAssemblyPassKind] = &[
63 PostAssemblyPassKind::SwiftMerge,
64 PostAssemblyPassKind::CondaRootfsMerge,
65 PostAssemblyPassKind::NpmResourceAssign,
66 PostAssemblyPassKind::PythonRequirementsAssign,
67 PostAssemblyPassKind::FileReferenceResolve,
68 PostAssemblyPassKind::RpmYumdbMerge,
69 PostAssemblyPassKind::NpmWorkspaceMerge,
70 PostAssemblyPassKind::CargoWorkspaceMerge,
71 PostAssemblyPassKind::NugetCpmResolve,
72 PostAssemblyPassKind::CargoResourceAssign,
73 PostAssemblyPassKind::ComposerResourceAssign,
74 PostAssemblyPassKind::RubyResourceAssign,
75 PostAssemblyPassKind::NixFlakeCompatMerge,
76 PostAssemblyPassKind::BazelPrune,
77];
78
79const SWIFT_POST_ASSEMBLY_DATASOURCE_IDS: &[DatasourceId] = &[
80 DatasourceId::SwiftPackageManifestJson,
81 DatasourceId::SwiftPackageResolved,
82 DatasourceId::SwiftPackageShowDependencies,
83];
84
85const CONDA_ROOTFS_POST_ASSEMBLY_DATASOURCE_IDS: &[DatasourceId] =
86 &[DatasourceId::CondaMetaJson, DatasourceId::CondaMetaYaml];
87
88const RPM_INSTALLED_DATABASE_DATASOURCE_IDS: &[DatasourceId] = &[
89 DatasourceId::RpmInstalledDatabaseBdb,
90 DatasourceId::RpmInstalledDatabaseNdb,
91 DatasourceId::RpmInstalledDatabaseSqlite,
92];
93
94const NUGET_CPM_CONFIG_DATASOURCE_IDS: &[DatasourceId] = &[
95 DatasourceId::NugetDirectoryBuildProps,
96 DatasourceId::NugetDirectoryPackagesProps,
97];
98
99const NUGET_CPM_PROJECT_DATASOURCE_IDS: &[DatasourceId] = &[
100 DatasourceId::NugetCsproj,
101 DatasourceId::NugetFsproj,
102 DatasourceId::NugetVbproj,
103];
104
105#[derive(Default)]
106struct PostAssemblyInputs {
107 package_types: HashSet<PackageType>,
108 file_datasource_ids: HashSet<DatasourceId>,
109 has_npm_workspace_markers: bool,
110 has_cargo_workspace_markers: bool,
111}
112
113pub(super) fn run_post_assembly_passes(
114 files: &mut [FileInfo],
115 packages: &mut Vec<Package>,
116 dependencies: &mut Vec<TopLevelDependency>,
117 topology_plan: &topology::TopologyPlan,
118) {
119 let inputs = PostAssemblyInputs::collect(files, packages);
120
121 for pass in POST_ASSEMBLY_PASSES {
122 if !pass.should_run(&inputs) {
123 continue;
124 }
125
126 pass.run(files, packages, dependencies, topology_plan);
127 }
128}
129
130impl PostAssemblyInputs {
131 fn collect(files: &[FileInfo], packages: &[Package]) -> Self {
132 let mut inputs = Self {
133 package_types: packages
134 .iter()
135 .filter_map(|package| package.package_type)
136 .collect(),
137 ..Self::default()
138 };
139
140 for file in files {
141 for package_data in &file.package_data {
142 let Some(datasource_id) = package_data.datasource_id else {
143 continue;
144 };
145
146 inputs.file_datasource_ids.insert(datasource_id);
147
148 if matches!(
149 datasource_id,
150 DatasourceId::NpmPackageJson | DatasourceId::PnpmWorkspaceYaml
151 ) && package_data
152 .extra_data
153 .as_ref()
154 .is_some_and(|extra_data| extra_data.contains_key("workspaces"))
155 {
156 inputs.has_npm_workspace_markers = true;
157 }
158
159 if datasource_id == DatasourceId::CargoToml
160 && package_data
161 .extra_data
162 .as_ref()
163 .and_then(|extra_data| extra_data.get("workspace"))
164 .and_then(|workspace| workspace.get("members"))
165 .and_then(|members| members.as_array())
166 .is_some_and(|members| !members.is_empty())
167 {
168 inputs.has_cargo_workspace_markers = true;
169 }
170 }
171 }
172
173 inputs
174 }
175
176 fn has_package_type(&self, package_type: PackageType) -> bool {
177 self.package_types.contains(&package_type)
178 }
179
180 fn has_any_file_datasource(&self, datasource_ids: &[DatasourceId]) -> bool {
181 datasource_ids
182 .iter()
183 .any(|datasource_id| self.file_datasource_ids.contains(datasource_id))
184 }
185
186 fn has_all_file_datasources(&self, datasource_ids: &[DatasourceId]) -> bool {
187 datasource_ids
188 .iter()
189 .all(|datasource_id| self.file_datasource_ids.contains(datasource_id))
190 }
191}
192
193impl SpecialDirectoryMergerKind {
194 pub(super) fn run(
195 self,
196 config: &AssemblerConfig,
197 files: &[FileInfo],
198 file_indices: &[usize],
199 ) -> Vec<DirectoryMergeOutput> {
200 match self {
201 Self::Skip => Vec::new(),
202 Self::Cocoapods => {
203 super::cocoapods_merge::assemble_cocoapods_packages(config, files, file_indices)
204 }
205 Self::DebianSource => {
206 debian_source_merge::assemble_debian_source_packages(config, files, file_indices)
207 }
208 Self::Hackage => hackage_merge::assemble_hackage_packages(files, file_indices),
209 Self::Huggingface => {
210 super::huggingface_merge::assemble_huggingface_packages(files, file_indices)
211 }
212 Self::WindowsUpdate => super::windows_update_merge::assemble_windows_update_packages(
213 config,
214 files,
215 file_indices,
216 ),
217 }
218 }
219}
220
221impl PostAssemblyPassKind {
222 fn should_run(self, inputs: &PostAssemblyInputs) -> bool {
223 match self {
224 Self::SwiftMerge => inputs.has_any_file_datasource(SWIFT_POST_ASSEMBLY_DATASOURCE_IDS),
225 Self::CondaRootfsMerge => {
226 inputs.has_all_file_datasources(CONDA_ROOTFS_POST_ASSEMBLY_DATASOURCE_IDS)
227 }
228 Self::NpmResourceAssign => inputs.has_package_type(PackageType::Npm),
229 Self::PythonRequirementsAssign => {
230 inputs.has_package_type(PackageType::Pypi)
231 && inputs.has_any_file_datasource(&[DatasourceId::PipRequirements])
232 }
233 Self::FileReferenceResolve => {
234 file_ref_resolve::has_relevant_file_reference_datasource_ids(
235 &inputs.file_datasource_ids,
236 )
237 }
238 Self::RpmYumdbMerge => {
239 inputs.has_any_file_datasource(&[DatasourceId::RpmYumdb])
240 && inputs.has_any_file_datasource(RPM_INSTALLED_DATABASE_DATASOURCE_IDS)
241 }
242 Self::NpmWorkspaceMerge => inputs.has_npm_workspace_markers,
243 Self::CargoWorkspaceMerge => inputs.has_cargo_workspace_markers,
244 Self::NugetCpmResolve => {
245 inputs.has_any_file_datasource(NUGET_CPM_CONFIG_DATASOURCE_IDS)
246 && inputs.has_any_file_datasource(NUGET_CPM_PROJECT_DATASOURCE_IDS)
247 }
248 Self::CargoResourceAssign => inputs.has_package_type(PackageType::Cargo),
249 Self::ComposerResourceAssign => inputs.has_package_type(PackageType::Composer),
250 Self::RubyResourceAssign => inputs.has_package_type(PackageType::Gem),
251 Self::NixFlakeCompatMerge => {
252 inputs.has_any_file_datasource(&[DatasourceId::NixDefaultNix])
253 && inputs.has_any_file_datasource(&[
254 DatasourceId::NixFlakeNix,
255 DatasourceId::NixFlakeLock,
256 ])
257 }
258 Self::BazelPrune => inputs.has_package_type(PackageType::Bazel),
259 }
260 }
261
262 fn run(
263 self,
264 files: &mut [FileInfo],
265 packages: &mut Vec<Package>,
266 dependencies: &mut Vec<TopLevelDependency>,
267 topology_plan: &topology::TopologyPlan,
268 ) {
269 match self {
270 Self::SwiftMerge => swift_merge::assemble_swift_packages(files, packages, dependencies),
271 Self::CondaRootfsMerge => {
272 conda_rootfs_merge::merge_conda_rootfs_metadata(files, packages, dependencies)
273 }
274 Self::NpmResourceAssign => {
275 npm_resource_assign::assign_npm_package_resources(files, packages)
276 }
277 Self::PythonRequirementsAssign => {
278 python_requirements_assign::assign_python_requirements_to_projects(
279 files,
280 packages,
281 dependencies,
282 )
283 }
284 Self::FileReferenceResolve => {
285 file_ref_resolve::resolve_file_references(files, packages, dependencies)
286 }
287 Self::RpmYumdbMerge => file_ref_resolve::merge_rpm_yumdb_metadata(files, packages),
288 Self::NpmWorkspaceMerge => {
289 topology_plan.apply_npm_workspace_domains(files, packages, dependencies)
290 }
291 Self::CargoWorkspaceMerge => {
292 topology_plan.apply_cargo_workspace_domains(files, packages, dependencies)
293 }
294 Self::NugetCpmResolve => {
295 nuget_cpm_resolve::resolve_nuget_cpm_versions(files, dependencies)
296 }
297 Self::CargoResourceAssign => {
298 cargo_resource_assign::assign_cargo_package_resources(files, packages)
299 }
300 Self::ComposerResourceAssign => {
301 composer_resource_assign::assign_composer_package_resources(files, packages)
302 }
303 Self::RubyResourceAssign => {
304 ruby_resource_assign::assign_ruby_package_resources(files, packages)
305 }
306 Self::NixFlakeCompatMerge => {
307 nix_flake_compat_merge::attach_flake_compat_default_files(files, packages)
308 }
309 Self::BazelPrune => {
310 bazel_prune::prune_unused_bazel_packages(files, packages, dependencies)
311 }
312 }
313 }
314}
315
316pub static ASSEMBLERS: &[AssemblerConfig] = &[
317 AssemblerConfig {
323 datasource_ids: &[
324 DatasourceId::BunLock,
325 DatasourceId::BunLockb,
326 DatasourceId::NpmPackageJson,
327 DatasourceId::NpmPackageLockJson,
328 DatasourceId::YarnLock,
329 DatasourceId::YarnLockV1,
330 DatasourceId::YarnLockV2,
331 DatasourceId::YarnPnpCjs,
332 DatasourceId::PnpmLockYaml,
333 DatasourceId::PnpmWorkspaceYaml,
334 ],
335 sibling_file_patterns: &[
336 "package.json",
337 "bun.lock",
338 "bun.lockb",
339 ".package-lock.json",
340 "package-lock.json",
341 ".npm-shrinkwrap.json",
342 "npm-shrinkwrap.json",
343 "yarn.lock",
344 ".pnp.cjs",
345 "pnpm-lock.yaml",
346 "shrinkwrap.yaml",
347 "pnpm-workspace.yaml",
348 ],
349 mode: AssemblyMode::SiblingMerge,
350 },
351 AssemblerConfig {
353 datasource_ids: &[DatasourceId::CargoToml, DatasourceId::CargoLock],
354 sibling_file_patterns: &["Cargo.toml", "Cargo.lock"],
355 mode: AssemblyMode::SiblingMerge,
356 },
357 AssemblerConfig {
359 datasource_ids: &[
360 DatasourceId::JuliaProjectToml,
361 DatasourceId::JuliaManifestToml,
362 ],
363 sibling_file_patterns: &["Project.toml", "Manifest.toml"],
364 mode: AssemblyMode::SiblingMerge,
365 },
366 AssemblerConfig {
368 datasource_ids: &[DatasourceId::RebarConfig, DatasourceId::RebarLock],
369 sibling_file_patterns: &["rebar.config", "rebar.lock"],
370 mode: AssemblyMode::SiblingMerge,
371 },
372 AssemblerConfig {
378 datasource_ids: &[DatasourceId::HexMixExs, DatasourceId::HexMixLock],
379 sibling_file_patterns: &["mix.exs", "mix.lock"],
380 mode: AssemblyMode::SiblingMerge,
381 },
382 AssemblerConfig {
387 datasource_ids: &[DatasourceId::ErlangOtpAppSrc],
388 sibling_file_patterns: &["*.app.src"],
389 mode: AssemblyMode::OnePerPackageData,
390 },
391 AssemblerConfig {
393 datasource_ids: &[
394 DatasourceId::CarthageCartfile,
395 DatasourceId::CarthageCartfileResolved,
396 ],
397 sibling_file_patterns: &["Cartfile", "Cartfile.private", "Cartfile.resolved"],
398 mode: AssemblyMode::SiblingMerge,
399 },
400 AssemblerConfig {
402 datasource_ids: &[
403 DatasourceId::CocoapodsPodspec,
404 DatasourceId::CocoapodsPodspecJson,
405 DatasourceId::CocoapodsPodfile,
406 DatasourceId::CocoapodsPodfileLock,
407 ],
408 sibling_file_patterns: &["*.podspec", "*.podspec.json", "Podfile", "Podfile.lock"],
409 mode: AssemblyMode::SiblingMerge,
410 },
411 AssemblerConfig {
413 datasource_ids: &[DatasourceId::PhpComposerJson, DatasourceId::PhpComposerLock],
414 sibling_file_patterns: &[
415 "*composer.json",
416 "composer.*.json",
417 "*composer.lock",
418 "composer.*.lock",
419 ],
420 mode: AssemblyMode::SiblingMerge,
421 },
422 AssemblerConfig {
424 datasource_ids: &[
425 DatasourceId::GoMod,
426 DatasourceId::GoModGraph,
427 DatasourceId::GoSum,
428 DatasourceId::GoWork,
429 DatasourceId::Godeps,
430 ],
431 sibling_file_patterns: &[
432 "go.mod",
433 "go.work",
434 "go.mod.graph",
435 "go.modgraph",
436 "go.sum",
437 "Godeps.json",
438 ],
439 mode: AssemblyMode::SiblingMerge,
440 },
441 AssemblerConfig {
443 datasource_ids: &[DatasourceId::PubspecYaml, DatasourceId::PubspecLock],
444 sibling_file_patterns: &["pubspec.yaml", "pubspec.lock"],
445 mode: AssemblyMode::SiblingMerge,
446 },
447 AssemblerConfig {
449 datasource_ids: &[DatasourceId::PixiToml, DatasourceId::PixiLock],
450 sibling_file_patterns: &["pixi.toml", "pixi.lock"],
451 mode: AssemblyMode::SiblingMerge,
452 },
453 AssemblerConfig {
454 datasource_ids: &[DatasourceId::NixFlakeNix, DatasourceId::NixFlakeLock],
455 sibling_file_patterns: &["flake.nix", "flake.lock"],
456 mode: AssemblyMode::SiblingMerge,
457 },
458 AssemblerConfig {
459 datasource_ids: &[DatasourceId::NixDefaultNix],
460 sibling_file_patterns: &["default.nix"],
461 mode: AssemblyMode::OnePerPackageData,
462 },
463 AssemblerConfig {
465 datasource_ids: &[DatasourceId::HelmChartYaml, DatasourceId::HelmChartLock],
466 sibling_file_patterns: &["Chart.yaml", "Chart.lock"],
467 mode: AssemblyMode::SiblingMerge,
468 },
469 AssemblerConfig {
470 datasource_ids: &[
471 DatasourceId::HackageCabal,
472 DatasourceId::HackageCabalProject,
473 DatasourceId::HackageStackYaml,
474 ],
475 sibling_file_patterns: &["*.cabal", "cabal.project", "stack.yaml"],
476 mode: AssemblyMode::SiblingMerge,
477 },
478 AssemblerConfig {
480 datasource_ids: &[
481 DatasourceId::ChefCookbookMetadataJson,
482 DatasourceId::ChefCookbookMetadataRb,
483 ],
484 sibling_file_patterns: &["metadata.json", "metadata.rb"],
485 mode: AssemblyMode::SiblingMerge,
486 },
487 AssemblerConfig {
489 datasource_ids: &[
490 DatasourceId::ConanConanFilePy,
491 DatasourceId::ConanConanFileTxt,
492 DatasourceId::ConanLock,
493 DatasourceId::ConanConanDataYml,
494 ],
495 sibling_file_patterns: &[
496 "conanfile.py",
497 "conanfile.txt",
498 "conan.lock",
499 "conandata.yml",
500 ],
501 mode: AssemblyMode::SiblingMerge,
502 },
503 AssemblerConfig {
510 datasource_ids: &[DatasourceId::VcpkgControl, DatasourceId::VcpkgJson],
511 sibling_file_patterns: &["CONTROL", "vcpkg.json"],
512 mode: AssemblyMode::OnePerPackageData,
513 },
514 AssemblerConfig {
516 datasource_ids: &[
517 DatasourceId::MavenPom,
518 DatasourceId::MavenPomProperties,
519 DatasourceId::JavaJarManifest,
520 DatasourceId::JavaOsgiManifest,
521 ],
522 sibling_file_patterns: &[
523 "pom.xml",
524 "*.pom",
525 "pom.properties",
526 "**/META-INF/MANIFEST.MF",
527 ],
528 mode: AssemblyMode::SiblingMergePerIdentity,
531 },
532 AssemblerConfig {
537 datasource_ids: &[DatasourceId::ClojureProjectClj],
538 sibling_file_patterns: &["project.clj"],
539 mode: AssemblyMode::OnePerPackageData,
540 },
541 AssemblerConfig {
545 datasource_ids: &[DatasourceId::SbtBuildSbt],
546 sibling_file_patterns: &["build.sbt"],
547 mode: AssemblyMode::OnePerPackageData,
548 },
549 AssemblerConfig {
550 datasource_ids: &[DatasourceId::PypiWheel, DatasourceId::PypiPipOriginJson],
551 sibling_file_patterns: &["*.whl", "origin.json"],
552 mode: AssemblyMode::SiblingMergePerIdentity,
557 },
558 AssemblerConfig {
560 datasource_ids: &[
561 DatasourceId::PypiPyprojectToml,
562 DatasourceId::PypiPoetryPyprojectToml,
563 DatasourceId::PypiSetupPy,
564 DatasourceId::PypiSetupCfg,
565 DatasourceId::PypiWheelMetadata,
566 DatasourceId::PypiEgg,
567 DatasourceId::PypiEggPkginfo,
568 DatasourceId::PypiEditableEggPkginfo,
569 DatasourceId::PypiJson,
570 DatasourceId::PypiSdist,
571 DatasourceId::PypiSdistPkginfo,
572 DatasourceId::PypiInspectDeplock,
573 DatasourceId::PipRequirements,
574 DatasourceId::PypiPoetryLock,
575 DatasourceId::PypiPylockToml,
576 DatasourceId::PypiUvLock,
577 DatasourceId::Pipfile,
578 DatasourceId::PipfileLock,
579 ],
580 sibling_file_patterns: &[
581 "pyproject.toml",
582 "setup.py",
583 "setup.cfg",
584 "PKG-INFO",
585 "METADATA",
586 "pypi.json",
587 "pip-inspect.deplock",
588 "*.tar.gz",
589 "*.tgz",
590 "*.tar.bz2",
591 "*.tar.xz",
592 "*.zip",
593 "requirements*.txt",
594 "Pipfile",
595 "Pipfile.lock",
596 "poetry.lock",
597 "pylock.toml",
598 "pylock.*.toml",
599 "uv.lock",
600 ],
601 mode: AssemblyMode::SiblingMerge,
602 },
603 AssemblerConfig {
604 datasource_ids: &[DatasourceId::DenoJson, DatasourceId::DenoLock],
605 sibling_file_patterns: &["deno.json", "deno.jsonc", "deno.lock"],
606 mode: AssemblyMode::SiblingMerge,
607 },
608 AssemblerConfig {
610 datasource_ids: &[
611 DatasourceId::GemArchiveExtracted,
612 DatasourceId::Gemspec,
613 DatasourceId::GemspecExtracted,
614 DatasourceId::Gemfile,
615 DatasourceId::GemfileExtracted,
616 DatasourceId::GemfileLock,
617 DatasourceId::GemfileLockExtracted,
618 ],
619 sibling_file_patterns: &[
620 "metadata.gz-extract",
621 "**/data.gz-extract/*.gemspec",
622 "**/data.gz-extract/Gemfile",
623 "**/data.gz-extract/Gemfile.lock",
624 "*.gemspec",
625 "Gemfile",
626 "Gemfile.lock",
627 ],
628 mode: AssemblyMode::SiblingMergePerIdentity,
634 },
635 AssemblerConfig {
640 datasource_ids: &[DatasourceId::GemGemspecInstalledSpecifications],
641 sibling_file_patterns: &["**/specifications/*.gemspec"],
642 mode: AssemblyMode::OnePerPackageData,
643 },
644 AssemblerConfig {
645 datasource_ids: &[DatasourceId::GemArchive],
646 sibling_file_patterns: &["*.gem"],
647 mode: AssemblyMode::OnePerPackageData,
648 },
649 AssemblerConfig {
652 datasource_ids: &[DatasourceId::CondaMetaYaml, DatasourceId::CondaYaml],
653 sibling_file_patterns: &[
654 "meta.yaml",
655 "meta.yml",
656 "recipe.yaml",
657 "recipe.yml",
658 "environment.yml",
659 "environment.yaml",
660 "conda.yaml",
661 "conda.yml",
662 "*conda*.yaml",
663 "*conda*.yml",
664 "env.yaml",
665 "env.yml",
666 "*env*.yaml",
667 "*env*.yml",
668 "*environment*.yaml",
669 "*environment*.yml",
670 "*.json",
671 ],
672 mode: AssemblyMode::SiblingMerge,
673 },
674 AssemblerConfig {
679 datasource_ids: &[DatasourceId::CondaMetaJson],
680 sibling_file_patterns: &["*.json"],
681 mode: AssemblyMode::OnePerPackageData,
682 },
683 AssemblerConfig {
685 datasource_ids: &[DatasourceId::RpmSpecfile],
686 sibling_file_patterns: &["*.spec"],
687 mode: AssemblyMode::OnePerPackageData,
688 },
689 AssemblerConfig {
691 datasource_ids: &[
692 DatasourceId::DebianControlInSource,
693 DatasourceId::DebianCopyrightInSource,
694 ],
695 sibling_file_patterns: &["control", "copyright"],
696 mode: AssemblyMode::SiblingMerge,
697 },
698 AssemblerConfig {
700 datasource_ids: &[DatasourceId::BuildGradle, DatasourceId::GradleLockfile],
701 sibling_file_patterns: &["build.gradle", "build.gradle.kts", "gradle.lockfile"],
702 mode: AssemblyMode::SiblingMerge,
703 },
704 AssemblerConfig {
705 datasource_ids: &[DatasourceId::GradleModule],
706 sibling_file_patterns: &["*.module"],
707 mode: AssemblyMode::OnePerPackageData,
708 },
709 AssemblerConfig {
716 datasource_ids: &[
717 DatasourceId::HuggingfaceModelCard,
718 DatasourceId::HuggingfaceConfigJson,
719 DatasourceId::HuggingfaceModelIndexJson,
720 ],
721 sibling_file_patterns: &["README.md", "config.json", "model_index.json"],
722 mode: AssemblyMode::SiblingMerge,
723 },
724 AssemblerConfig {
726 datasource_ids: &[
727 DatasourceId::CpanMetaJson,
728 DatasourceId::CpanMetaYml,
729 DatasourceId::CpanManifest,
730 DatasourceId::CpanDistIni,
731 DatasourceId::CpanMakefile,
732 ],
733 sibling_file_patterns: &[
734 "META.json",
735 "META.yml",
736 "MANIFEST",
737 "dist.ini",
738 "Makefile.PL",
739 ],
740 mode: AssemblyMode::SiblingMerge,
741 },
742 AssemblerConfig {
744 datasource_ids: &[
745 DatasourceId::NugetCsproj,
746 DatasourceId::NugetFsproj,
747 DatasourceId::NugetNuspec,
748 DatasourceId::NugetNupkg,
749 DatasourceId::NugetProjectJson,
750 DatasourceId::NugetProjectLockJson,
751 DatasourceId::NugetPackagesConfig,
752 DatasourceId::NugetPackagesLock,
753 DatasourceId::NugetVbproj,
754 ],
755 sibling_file_patterns: &[
756 "*.csproj",
757 "*.fsproj",
758 "*.nuspec",
759 "*.nupkg",
760 "project.json",
761 "project.lock.json",
762 "packages.config",
763 "packages.lock.json",
764 "*.packages.lock.json",
765 "*.vbproj",
766 ],
767 mode: AssemblyMode::SiblingMergePerIdentity,
773 },
774 AssemblerConfig {
775 datasource_ids: &[DatasourceId::NugetDepsJson],
776 sibling_file_patterns: &["*.deps.json"],
777 mode: AssemblyMode::OnePerPackageData,
778 },
779 AssemblerConfig {
781 datasource_ids: &[
782 DatasourceId::SwiftPackageManifestJson,
783 DatasourceId::SwiftPackageResolved,
784 DatasourceId::SwiftPackageShowDependencies,
785 ],
786 sibling_file_patterns: &[
787 "Package.swift.json",
788 "Package.swift.deplock",
789 "Package.resolved",
790 ".package.resolved",
791 "swift-show-dependencies.deplock",
792 ],
793 mode: AssemblyMode::SiblingMerge,
794 },
795 AssemblerConfig {
802 datasource_ids: &[DatasourceId::BowerJson],
803 sibling_file_patterns: &["bower.json"],
804 mode: AssemblyMode::SiblingMerge,
805 },
806 AssemblerConfig {
808 datasource_ids: &[DatasourceId::CranDescription],
809 sibling_file_patterns: &["DESCRIPTION"],
810 mode: AssemblyMode::SiblingMerge,
811 },
812 AssemblerConfig {
814 datasource_ids: &[DatasourceId::FreebsdCompactManifest],
815 sibling_file_patterns: &["+COMPACT_MANIFEST"],
816 mode: AssemblyMode::SiblingMerge,
817 },
818 AssemblerConfig {
820 datasource_ids: &[DatasourceId::HaxelibJson],
821 sibling_file_patterns: &["haxelib.json"],
822 mode: AssemblyMode::SiblingMerge,
823 },
824 AssemblerConfig {
825 datasource_ids: &[DatasourceId::Gitmodules],
826 sibling_file_patterns: &[".gitmodules"],
827 mode: AssemblyMode::SiblingMerge,
828 },
829 AssemblerConfig {
834 datasource_ids: &[DatasourceId::OpamFile],
835 sibling_file_patterns: &["opam", "*.opam"],
836 mode: AssemblyMode::SiblingMergePerIdentity,
837 },
838 AssemblerConfig {
839 datasource_ids: &[DatasourceId::RpmYumdb],
840 sibling_file_patterns: &["**/var/lib/yum/yumdb/*/*/from_repo"],
841 mode: AssemblyMode::OnePerPackageData,
842 },
843 AssemblerConfig {
845 datasource_ids: &[DatasourceId::MicrosoftUpdateManifestMum],
846 sibling_file_patterns: &["*.mum"],
847 mode: AssemblyMode::SiblingMerge,
848 },
849 AssemblerConfig {
851 datasource_ids: &[DatasourceId::AutotoolsConfigure],
852 sibling_file_patterns: &["configure", "configure.ac"],
853 mode: AssemblyMode::SiblingMerge,
854 },
855 AssemblerConfig {
861 datasource_ids: &[DatasourceId::BazelBuild],
862 sibling_file_patterns: &["BUILD"],
863 mode: AssemblyMode::SiblingMerge,
864 },
865 AssemblerConfig {
866 datasource_ids: &[DatasourceId::BazelModule],
867 sibling_file_patterns: &["MODULE.bazel"],
868 mode: AssemblyMode::OnePerPackageData,
869 },
870 AssemblerConfig {
872 datasource_ids: &[DatasourceId::BuckFile, DatasourceId::BuckMetadata],
873 sibling_file_patterns: &["BUCK", "METADATA.bzl", ".buckconfig"],
874 mode: AssemblyMode::SiblingMerge,
875 },
876 AssemblerConfig {
884 datasource_ids: &[DatasourceId::MesonBuild],
885 sibling_file_patterns: &["meson.build"],
886 mode: AssemblyMode::OnePerPackageData,
887 },
888 AssemblerConfig {
890 datasource_ids: &[DatasourceId::AntIvyXml],
891 sibling_file_patterns: &["ivy.xml"],
892 mode: AssemblyMode::SiblingMerge,
893 },
894 AssemblerConfig {
896 datasource_ids: &[
897 DatasourceId::JavaJar,
898 DatasourceId::JavaWarArchive,
899 DatasourceId::AndroidAarLibrary,
900 ],
901 sibling_file_patterns: &["*.jar", "*.war", "*.aar"],
902 mode: AssemblyMode::OnePerPackageData,
903 },
904 AssemblerConfig {
906 datasource_ids: &[DatasourceId::MeteorPackage],
907 sibling_file_patterns: &["package.js"],
908 mode: AssemblyMode::SiblingMerge,
909 },
910 AssemblerConfig {
914 datasource_ids: &[DatasourceId::AlpineInstalledDb],
915 sibling_file_patterns: &["installed"],
916 mode: AssemblyMode::OnePerPackageData,
917 },
918 AssemblerConfig {
919 datasource_ids: &[DatasourceId::AlpineApkbuild],
920 sibling_file_patterns: &["APKBUILD"],
921 mode: AssemblyMode::SiblingMerge,
922 },
923 AssemblerConfig {
927 datasource_ids: &[
928 DatasourceId::ArchSrcinfo,
929 DatasourceId::ArchPkginfo,
930 DatasourceId::ArchAurinfo,
931 ],
932 sibling_file_patterns: &[".SRCINFO", ".PKGINFO", ".AURINFO"],
933 mode: AssemblyMode::OnePerPackageData,
934 },
935 AssemblerConfig {
937 datasource_ids: &[
938 DatasourceId::RpmInstalledDatabaseBdb,
939 DatasourceId::RpmInstalledDatabaseNdb,
940 DatasourceId::RpmInstalledDatabaseSqlite,
941 DatasourceId::RpmMarinerManifest,
942 ],
943 sibling_file_patterns: &[
944 "Packages",
945 "Packages.db",
946 "rpmdb.sqlite",
947 "container-manifest-2",
948 ],
949 mode: AssemblyMode::OnePerPackageData,
950 },
951 AssemblerConfig {
952 datasource_ids: &[DatasourceId::RpmArchive],
953 sibling_file_patterns: &["*.rpm", "*.srpm"],
954 mode: AssemblyMode::OnePerPackageData,
955 },
956 AssemblerConfig {
958 datasource_ids: &[DatasourceId::DebianDeb],
959 sibling_file_patterns: &["*.deb"],
960 mode: AssemblyMode::OnePerPackageData,
961 },
962 AssemblerConfig {
963 datasource_ids: &[
964 DatasourceId::DebianInstalledStatusDb,
965 DatasourceId::DebianDistrolessInstalledDb,
966 ],
967 sibling_file_patterns: &["status"],
968 mode: AssemblyMode::OnePerPackageData,
969 },
970 AssemblerConfig {
971 datasource_ids: &[
972 DatasourceId::DebianControlExtractedDeb,
973 DatasourceId::DebianMd5SumsInExtractedDeb,
974 ],
975 sibling_file_patterns: &["control", "md5sums"],
976 mode: AssemblyMode::SiblingMerge,
977 },
978 AssemblerConfig {
979 datasource_ids: &[DatasourceId::DebianSourceControlDsc],
980 sibling_file_patterns: &["*.dsc"],
981 mode: AssemblyMode::OnePerPackageData,
982 },
983 AssemblerConfig {
984 datasource_ids: &[DatasourceId::AboutFile],
985 sibling_file_patterns: &["*.ABOUT"],
986 mode: AssemblyMode::OnePerPackageData,
987 },
988 AssemblerConfig {
989 datasource_ids: &[
990 DatasourceId::BitbakeRecipe,
991 DatasourceId::BitbakeRecipeAppend,
992 ],
993 sibling_file_patterns: &["*.bb", "*.bbappend"],
994 mode: AssemblyMode::SiblingMergePerIdentity,
1001 },
1002];
1003
1004#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1017pub(super) enum UnassembledReason {
1018 NotAPackage,
1021 BinaryArtifact,
1024 SupplementaryMetadata,
1027 DependenciesOnlyNoIdentity,
1030}
1031
1032pub(super) static UNASSEMBLED_DATASOURCE_IDS: &[(DatasourceId, UnassembledReason)] = &[
1033 (DatasourceId::Readme, UnassembledReason::NotAPackage),
1034 (DatasourceId::EtcOsRelease, UnassembledReason::NotAPackage),
1035 (
1036 DatasourceId::AndroidManifestXml,
1037 UnassembledReason::NotAPackage,
1038 ),
1039 (
1040 DatasourceId::AndroidSoongMetadata,
1041 UnassembledReason::NotAPackage,
1042 ),
1043 (DatasourceId::Dockerfile, UnassembledReason::NotAPackage),
1044 (DatasourceId::OciImageIndex, UnassembledReason::NotAPackage),
1045 (
1046 DatasourceId::OciImageManifest,
1047 UnassembledReason::NotAPackage,
1048 ),
1049 (DatasourceId::Axis2ModuleXml, UnassembledReason::NotAPackage),
1050 (
1051 DatasourceId::JavaEarApplicationXml,
1052 UnassembledReason::NotAPackage,
1053 ),
1054 (DatasourceId::JavaWarWebXml, UnassembledReason::NotAPackage),
1055 (
1056 DatasourceId::JbossServiceXml,
1057 UnassembledReason::NotAPackage,
1058 ),
1059 (
1061 DatasourceId::AlpineApkArchive,
1062 UnassembledReason::BinaryArtifact,
1063 ),
1064 (DatasourceId::AndroidAab, UnassembledReason::BinaryArtifact),
1065 (DatasourceId::AndroidApk, UnassembledReason::BinaryArtifact),
1066 (DatasourceId::AppleDmg, UnassembledReason::BinaryArtifact),
1067 (DatasourceId::Axis2Mar, UnassembledReason::BinaryArtifact),
1068 (DatasourceId::ChromeCrx, UnassembledReason::BinaryArtifact),
1069 (
1070 DatasourceId::DebianOriginalSourceTarball,
1071 UnassembledReason::BinaryArtifact,
1072 ),
1073 (
1074 DatasourceId::DebianSourceMetadataTarball,
1075 UnassembledReason::BinaryArtifact,
1076 ),
1077 (
1078 DatasourceId::InstallshieldInstaller,
1079 UnassembledReason::BinaryArtifact,
1080 ),
1081 (DatasourceId::IosIpa, UnassembledReason::BinaryArtifact),
1082 (
1083 DatasourceId::IsoDiskImage,
1084 UnassembledReason::BinaryArtifact,
1085 ),
1086 (
1087 DatasourceId::JavaEarArchive,
1088 UnassembledReason::BinaryArtifact,
1089 ),
1090 (DatasourceId::JbossSar, UnassembledReason::BinaryArtifact),
1091 (
1092 DatasourceId::MicrosoftCabinet,
1093 UnassembledReason::BinaryArtifact,
1094 ),
1095 (DatasourceId::MozillaXpi, UnassembledReason::BinaryArtifact),
1096 (
1097 DatasourceId::NsisInstaller,
1098 UnassembledReason::BinaryArtifact,
1099 ),
1100 (
1101 DatasourceId::SharShellArchive,
1102 UnassembledReason::BinaryArtifact,
1103 ),
1104 (
1105 DatasourceId::SquashfsDiskImage,
1106 UnassembledReason::BinaryArtifact,
1107 ),
1108 (DatasourceId::GoBinary, UnassembledReason::BinaryArtifact),
1109 (
1110 DatasourceId::WindowsExecutable,
1111 UnassembledReason::BinaryArtifact,
1112 ),
1113 (DatasourceId::RustBinary, UnassembledReason::BinaryArtifact),
1114 (
1116 DatasourceId::DebianInstalledFilesList,
1117 UnassembledReason::SupplementaryMetadata,
1118 ),
1119 (
1120 DatasourceId::DebianInstalledMd5Sums,
1121 UnassembledReason::SupplementaryMetadata,
1122 ),
1123 (
1124 DatasourceId::DebianCopyright,
1125 UnassembledReason::SupplementaryMetadata,
1126 ),
1127 (
1128 DatasourceId::DebianCopyrightInPackage,
1129 UnassembledReason::SupplementaryMetadata,
1130 ),
1131 (
1132 DatasourceId::DebianCopyrightStandalone,
1133 UnassembledReason::SupplementaryMetadata,
1134 ),
1135 (
1136 DatasourceId::AntIvyDependenciesProperties,
1137 UnassembledReason::SupplementaryMetadata,
1138 ),
1139 (
1140 DatasourceId::NugetDirectoryBuildProps,
1141 UnassembledReason::SupplementaryMetadata,
1142 ),
1143 (
1144 DatasourceId::NugetDirectoryPackagesProps,
1145 UnassembledReason::SupplementaryMetadata,
1146 ),
1147 (
1148 DatasourceId::CitationCff,
1149 UnassembledReason::SupplementaryMetadata,
1150 ),
1151 (
1152 DatasourceId::PubliccodeYaml,
1153 UnassembledReason::SupplementaryMetadata,
1154 ),
1155 (
1156 DatasourceId::RpmPackageLicenses,
1157 UnassembledReason::SupplementaryMetadata,
1158 ),
1159 (
1160 DatasourceId::VcpkgConfigurationJson,
1161 UnassembledReason::SupplementaryMetadata,
1162 ),
1163 (
1164 DatasourceId::VcpkgLockJson,
1165 UnassembledReason::SupplementaryMetadata,
1166 ),
1167 (
1169 DatasourceId::ClojureDepsEdn,
1170 UnassembledReason::DependenciesOnlyNoIdentity,
1171 ),
1172];
1173
1174pub(super) fn is_unassembled_datasource(datasource_id: DatasourceId) -> bool {
1177 UNASSEMBLED_DATASOURCE_IDS
1178 .iter()
1179 .any(|(dsid, _)| *dsid == datasource_id)
1180}
1181
1182#[cfg(test)]
1183mod tests {
1184 use super::*;
1185 use std::collections::HashSet;
1186 use strum::IntoEnumIterator;
1187
1188 #[test]
1189 fn test_every_datasource_id_is_accounted_for() {
1190 let mut assembled: HashSet<DatasourceId> = HashSet::new();
1191 for config in ASSEMBLERS {
1192 for &dsid in config.datasource_ids {
1193 assembled.insert(dsid);
1194 }
1195 }
1196
1197 let unassembled: HashSet<DatasourceId> =
1198 UNASSEMBLED_DATASOURCE_IDS.iter().map(|(d, _)| *d).collect();
1199 assert_eq!(
1200 unassembled.len(),
1201 UNASSEMBLED_DATASOURCE_IDS.len(),
1202 "UNASSEMBLED_DATASOURCE_IDS lists a datasource more than once"
1203 );
1204
1205 let overlap: Vec<_> = assembled.intersection(&unassembled).collect();
1206 assert!(
1207 overlap.is_empty(),
1208 "Datasource IDs in BOTH ASSEMBLERS and UNASSEMBLED: {overlap:?}"
1209 );
1210
1211 let missing: Vec<_> = DatasourceId::iter()
1212 .filter(|dsid| !assembled.contains(dsid) && !unassembled.contains(dsid))
1213 .collect();
1214
1215 assert!(
1216 missing.is_empty(),
1217 "Datasource IDs in NEITHER ASSEMBLERS nor UNASSEMBLED: {missing:?}\n\
1218 Add each to an AssemblerConfig in ASSEMBLERS, or to UNASSEMBLED_DATASOURCE_IDS."
1219 );
1220 }
1221
1222 #[test]
1223 fn test_post_assembly_passes_are_unique() {
1224 let unique: HashSet<PostAssemblyPassKind> = POST_ASSEMBLY_PASSES.iter().copied().collect();
1225
1226 assert_eq!(
1227 unique.len(),
1228 POST_ASSEMBLY_PASSES.len(),
1229 "POST_ASSEMBLY_PASSES contains duplicate entries"
1230 );
1231 }
1232
1233 #[test]
1234 fn test_every_post_assembly_pass_kind_is_registered_once() {
1235 let registered: HashSet<PostAssemblyPassKind> =
1236 POST_ASSEMBLY_PASSES.iter().copied().collect();
1237
1238 let missing: Vec<_> = PostAssemblyPassKind::iter()
1239 .filter(|pass| !registered.contains(pass))
1240 .collect();
1241
1242 assert!(
1243 missing.is_empty(),
1244 "Post-assembly pass variants not registered in POST_ASSEMBLY_PASSES: {missing:?}"
1245 );
1246
1247 for pass in PostAssemblyPassKind::iter() {
1248 let count = POST_ASSEMBLY_PASSES
1249 .iter()
1250 .filter(|registered| **registered == pass)
1251 .count();
1252 assert_eq!(
1253 count, 1,
1254 "Post-assembly pass {pass:?} should be registered exactly once"
1255 );
1256 }
1257 }
1258
1259 #[test]
1260 fn test_post_assembly_passes_skip_irrelevant_inputs() {
1261 let inputs = PostAssemblyInputs::default();
1262
1263 for pass in PostAssemblyPassKind::iter() {
1264 assert!(
1265 !pass.should_run(&inputs),
1266 "{pass:?} should skip when no relevant inputs are present"
1267 );
1268 }
1269 }
1270
1271 #[test]
1272 fn test_npm_workspace_inputs_only_run_npm_passes() {
1273 let inputs = PostAssemblyInputs {
1274 package_types: HashSet::from([PackageType::Npm]),
1275 file_datasource_ids: HashSet::from([DatasourceId::NpmPackageJson]),
1276 has_npm_workspace_markers: true,
1277 has_cargo_workspace_markers: false,
1278 };
1279
1280 let runnable: HashSet<_> = PostAssemblyPassKind::iter()
1281 .filter(|pass| pass.should_run(&inputs))
1282 .collect();
1283
1284 assert_eq!(
1285 runnable,
1286 HashSet::from([
1287 PostAssemblyPassKind::NpmResourceAssign,
1288 PostAssemblyPassKind::NpmWorkspaceMerge,
1289 ])
1290 );
1291 }
1292
1293 #[test]
1294 fn test_cargo_workspace_merge_requires_workspace_markers() {
1295 let without_markers = PostAssemblyInputs {
1296 package_types: HashSet::from([PackageType::Cargo]),
1297 file_datasource_ids: HashSet::from([DatasourceId::CargoToml]),
1298 has_npm_workspace_markers: false,
1299 has_cargo_workspace_markers: false,
1300 };
1301
1302 assert!(!PostAssemblyPassKind::CargoWorkspaceMerge.should_run(&without_markers));
1303
1304 let with_markers = PostAssemblyInputs {
1305 has_cargo_workspace_markers: true,
1306 ..without_markers
1307 };
1308
1309 assert!(PostAssemblyPassKind::CargoWorkspaceMerge.should_run(&with_markers));
1310 }
1311}