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