Resolver.vala 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. using Invercargill;
  2. using Invercargill.DataStructures;
  3. namespace Usm {
  4. /**
  5. * Errors thrown while resolving a package set.
  6. *
  7. * Every member's message is a fully itemised report safe to print
  8. * verbatim by a CLI.
  9. */
  10. public errordomain ResolverError {
  11. /** A required resource ref could not be satisfied; the message itemises every failing ref and why. */
  12. UNSATISFIABLE,
  13. /** No candidate group in a grouped dependency phase is satisfiable; the message carries a per-group itemised report. */
  14. NO_VIABLE_GROUP,
  15. /** The resolved package graph contains a dependency cycle; the message names the packages in the cycle. */
  16. CYCLE,
  17. /** The configured system package manager query failed. */
  18. SYSTEM_QUERY_FAILED
  19. }
  20. /**
  21. * Why one {@link ResourceRef} could not be satisfied during resolution:
  22. * it was neither present locally nor provided by any system or USM
  23. * package candidate.
  24. */
  25. public class RefFailure : Object {
  26. /** The ref that could not be satisfied. */
  27. public ResourceRef resource { get; set; }
  28. /** The name of the package whose dependency phase required it. */
  29. public string origin { get; set; }
  30. /** Whether a system package manager was consulted for this ref. */
  31. public bool spm_configured { get; set; }
  32. /**
  33. * One-line description used in itemised reports, e.g.
  34. * `bin:foo (required by "app") — not present locally; no system package provides it; no USM package provides it`.
  35. */
  36. public string describe(string reported_for) {
  37. var origin_note = origin != reported_for ? @" (required by \"$origin\")" : "";
  38. var spm_reason = spm_configured ? "no system package provides it" : "no system package manager is configured";
  39. return @"$(resource.to_string())$origin_note — not present locally; $spm_reason; no USM package provides it";
  40. }
  41. }
  42. /**
  43. * One candidate group that could not be satisfied during group selection,
  44. * carrying the failure for each unresolvable member.
  45. */
  46. public class GroupFailure : Object {
  47. /** The 1-based position of the group within its phase. */
  48. public uint group_index { get; set; }
  49. /** The members of the group, deterministically ordered. */
  50. public Vector<ResourceRef> members { get; set; }
  51. /** Why each unresolvable member failed. */
  52. public Vector<RefFailure> failures { get; set; }
  53. /** Indented multi-line description used in itemised reports. */
  54. public string describe(string reported_for) {
  55. var builder = new StringBuilder();
  56. builder.append_printf(" Group %u (%s):\n", group_index, members.to_string(r => r.to_string(), ", "));
  57. foreach(var failure in failures) {
  58. builder.append_printf(" %s\n", failure.describe(reported_for));
  59. }
  60. return builder.str;
  61. }
  62. }
  63. /**
  64. * The outcome of a successful {@link Resolver.resolve}: the resolved
  65. * package set, the chosen system packages and the topological
  66. * install/removal orders derived from the resolved package graph.
  67. */
  68. public class ResolutionResult : Object {
  69. /** Every chosen USM package: the roots plus their transitive provider closure. */
  70. public PackageSet packages { get; set; }
  71. /** System packages chosen to satisfy missing resources, ordered by name; install them as one transaction before any USM package. */
  72. public Vector<SystemPackageCandidate> system_packages { get; set; }
  73. /** The chosen USM packages ordered dependencies-before-dependents (Kahn's algorithm, package-name tie-break). */
  74. public Vector<AbstractPackage> install_order { get; set; }
  75. /** The exact reverse of {@link install_order}: dependents are removed before their providers. */
  76. public Vector<AbstractPackage> removal_order { get; set; }
  77. }
  78. /**
  79. * Mutable working state for one resolution run.
  80. *
  81. * Snapshots are deep (packages, processed markers, chosen system
  82. * packages and per-package chosen refs are all copied) so a group
  83. * evaluation can be rolled back by restoring a snapshot without sharing
  84. * any mutable collection with the state it was taken from.
  85. */
  86. internal class ResolutionState {
  87. public PackageSet chosen = new PackageSet();
  88. public HashSet<AbstractPackage> processed = new HashSet<AbstractPackage>();
  89. public Dictionary<string, SystemPackageCandidate> spm_chosen = new Dictionary<string, SystemPackageCandidate>();
  90. public Dictionary<string, Set<ResourceRef>> refs_by_package = new Dictionary<string, Set<ResourceRef>>();
  91. public ResolutionState snapshot() {
  92. var copy = new ResolutionState();
  93. copy.chosen.union_with(chosen);
  94. copy.processed.union_with(processed);
  95. foreach(var pair in spm_chosen) {
  96. copy.spm_chosen.set(pair.key, pair.value);
  97. }
  98. foreach(var pair in refs_by_package) {
  99. var refs = new HashSet<ResourceRef>();
  100. refs.union_with(pair.value);
  101. copy.refs_by_package.set(pair.key, refs);
  102. }
  103. return copy;
  104. }
  105. }
  106. public class Resolver {
  107. private Dictionary<Repository, RepositoryListing> listings = new Dictionary<Repository, RepositoryListing>();
  108. private Set<AbstractPackage> supplied = new HashSet<AbstractPackage>();
  109. private ResourceFinder resource_finder;
  110. private Vector<AbstractPackage>? catalog_cache = null;
  111. // Per-resolution state, reset at the start of every {@link resolve} call
  112. private ResolutionState state = new ResolutionState();
  113. private Dictionary<string, Vector<SystemPackageCandidate>> spm_index = new Dictionary<string, Vector<SystemPackageCandidate>>();
  114. private Dictionary<string, bool> local_presence = new Dictionary<string, bool>();
  115. private bool spm_configured = false;
  116. /**
  117. * Optional progress callback fired once per resolved package as
  118. * `resolved_count / estimated_total`, where the estimate is the
  119. * number of roots times two (a rough dependency multiplier) and
  120. * the final report is clamped to 1.0 when resolution completes.
  121. * Null (the default) disables reporting.
  122. */
  123. public owned ProgressDelegate? resolution_progress { get; set; }
  124. /** Estimated package count for {@link resolution_progress}: roots x 2, at least 1. */
  125. private uint resolution_estimated_total = 1;
  126. /** The lifecycle phases resolution considers, in processing order. */
  127. private const string[] PHASES = { "manage", "build", "runtime" };
  128. public Resolver(ResourceFinder local_resource_finder) {
  129. this.resource_finder = local_resource_finder;
  130. }
  131. public void load_listing(Repository repo, RepositoryListing listing) {
  132. listings.set(repo, listing);
  133. catalog_cache = null;
  134. }
  135. public void supply_package(string path) throws Error {
  136. supplied.add(new AbstractPackage.from_package(path));
  137. catalog_cache = null;
  138. }
  139. /**
  140. * Adds every readable cached package as a supplied package so the
  141. * cache can satisfy resources during resolution. Unreadable cache
  142. * entries (for example a half-finished download) are skipped with a
  143. * warning.
  144. */
  145. public void load_cache(Paths paths) throws Error {
  146. var cache_state = new SystemState(paths);
  147. foreach (var package in cache_state.get_cached_packages()) {
  148. if(!File.new_for_path(package.package_path).query_exists()) {
  149. continue;
  150. }
  151. try {
  152. supply_package(package.package_path);
  153. }
  154. catch(Error e) {
  155. warning(@"[Usm] Skipping unreadable cached package \"$(package.package_name)\": $(e.message)");
  156. }
  157. }
  158. }
  159. /**
  160. * Finds a package by exact name, preferring repository packages over
  161. * supplied (cached) ones so installs pull a fresh copy. Deterministic:
  162. * repository packages are preferred, then the highest version, then
  163. * repository name.
  164. */
  165. public AbstractPackage? find_package(string search) {
  166. AbstractPackage? match = null;
  167. foreach(var package in catalog()) {
  168. if(package.manifest.name != search) {
  169. continue;
  170. }
  171. if(package.repository != null) {
  172. match = package;
  173. }
  174. else if(match == null) {
  175. match = package;
  176. }
  177. }
  178. return match;
  179. }
  180. /**
  181. * Finds a package providing the given resource, deterministically
  182. * choosing the alphabetically-first package name (then version,
  183. * repository name, package path).
  184. */
  185. public AbstractPackage? find_resource(ResourceRef resource) {
  186. foreach(var package in catalog()) {
  187. if(package.manifest.provides.any(r => resource.satisfied_by(r.key))) {
  188. return package;
  189. }
  190. }
  191. return null;
  192. }
  193. /**
  194. * Resolves the full dependency closure for the given root packages.
  195. *
  196. * A missing resource resolves in order: (a) already present locally,
  197. * (b) system packages — via ONE batched query covering every ref any
  198. * candidate group could need, choosing per ref the candidate
  199. * minimising new installs (dependency-count − installed-dependency-count),
  200. * deduplicating packages chosen for multiple resources — then (c) USM
  201. * packages from repositories, the cache or supplied packages. Without
  202. * a configured manager (b) is skipped. Grouped phases select, in
  203. * manifest order, the viable group minimising total new installs
  204. * (USM packages count with their transitive closure; resources
  205. * already satisfied cost 0); ties keep manifest order.
  206. *
  207. * Throws {@link ResolverError} with an itemised report when a flat
  208. * ref is unsatisfiable, when no group is viable, or when the resolved
  209. * graph contains a cycle.
  210. */
  211. public ResolutionResult resolve(Lot<AbstractPackage> roots, SystemPackageManager? spm = null) throws Error {
  212. state = new ResolutionState();
  213. spm_index = new Dictionary<string, Vector<SystemPackageCandidate>>();
  214. local_presence = new Dictionary<string, bool>();
  215. spm_configured = spm != null && spm.enabled;
  216. resolution_estimated_total = roots.length * 2;
  217. if(resolution_estimated_total < 1) {
  218. resolution_estimated_total = 1;
  219. }
  220. var ordered_roots = roots.sort(compare_packages).to_vector();
  221. foreach(var root in ordered_roots) {
  222. state.chosen.add(root);
  223. }
  224. if(spm_configured) {
  225. batch_query(roots, (!)spm);
  226. }
  227. foreach(var root in ordered_roots) {
  228. process_package(root);
  229. }
  230. var install_order = topological_order(state.chosen, state.refs_by_package);
  231. var removal_order = new Vector<AbstractPackage>();
  232. for(uint index = install_order.length; index > 0; index--) {
  233. removal_order.add(install_order[index - 1]);
  234. }
  235. var system_packages = new Vector<SystemPackageCandidate>();
  236. foreach(var pair in state.spm_chosen) {
  237. system_packages.add(pair.value);
  238. }
  239. system_packages = system_packages.sort((a, b) => a.name.collate(b.name)).to_vector();
  240. var packages = new PackageSet();
  241. packages.union_with(state.chosen);
  242. if(resolution_progress != null) {
  243. resolution_progress(1.0f);
  244. }
  245. return new ResolutionResult() {
  246. packages = packages,
  247. system_packages = system_packages,
  248. install_order = install_order,
  249. removal_order = removal_order
  250. };
  251. }
  252. /**
  253. * Orders packages dependencies-before-dependents with Kahn's
  254. * algorithm and a deterministic package-name tie-break; a cycle is a
  255. * hard error naming the packages in it.
  256. *
  257. * {@link chosen_refs} maps a package name to the refs chosen for it
  258. * during resolution (flat refs plus the chosen group's members); when
  259. * null the requirements are derived from each manifest's flat
  260. * phases plus every candidate group's refs. Only refs provided by
  261. * another package in the set create ordering edges.
  262. */
  263. public static Vector<AbstractPackage> topological_order(Enumerable<AbstractPackage> packages, ReadOnlyAssociative<string, Set<ResourceRef>>? chosen_refs = null) throws ResolverError {
  264. var nodes = packages.sort(compare_packages).to_vector();
  265. var by_name = new Dictionary<string, Vector<AbstractPackage>>();
  266. foreach(var node in nodes) {
  267. var name = node.manifest.name;
  268. Vector<AbstractPackage> named;
  269. if(!by_name.try_get(name, out named)) {
  270. named = new Vector<AbstractPackage>();
  271. by_name.set(name, named);
  272. }
  273. named.add(node);
  274. }
  275. var required = new Dictionary<string, Vector<ResourceRef>>();
  276. foreach(var node in nodes) {
  277. var refs = new Vector<ResourceRef>();
  278. Set<ResourceRef>? chosen = null;
  279. if(chosen_refs != null && chosen_refs.try_get(node.manifest.name, out chosen)) {
  280. foreach(var resource in chosen) {
  281. refs.add(resource);
  282. }
  283. }
  284. else {
  285. foreach(var phase in resolution_phases(node.manifest)) {
  286. foreach(var resource in phase.ordered_all_refs()) {
  287. refs.add(resource);
  288. }
  289. }
  290. }
  291. required.set(node.manifest.name, refs.sort((a, b) => a.to_string().collate(b.to_string())).to_vector());
  292. }
  293. // Edges provider → dependent; one edge per (provider, dependent) pair
  294. var dependents = new Dictionary<string, Vector<string>>();
  295. var indegree = new Dictionary<string, uint>();
  296. foreach(var node in nodes) {
  297. indegree.set(node.manifest.name, 0);
  298. }
  299. foreach(var dependent in nodes) {
  300. var dependent_name = dependent.manifest.name;
  301. var linked_providers = new HashSet<string>();
  302. Vector<ResourceRef> dependent_refs;
  303. if(!required.try_get(dependent_name, out dependent_refs)) {
  304. continue;
  305. }
  306. foreach(var resource in dependent_refs) {
  307. foreach(var provider in nodes) {
  308. var provider_name = provider.manifest.name;
  309. if(provider_name == dependent_name || linked_providers.has(provider_name)) {
  310. continue;
  311. }
  312. if(provider.manifest.provides.any(p => resource.satisfied_by(p.key))) {
  313. linked_providers.add(provider_name);
  314. Vector<string> follower_names;
  315. if(!dependents.try_get(provider_name, out follower_names)) {
  316. follower_names = new Vector<string>();
  317. dependents.set(provider_name, follower_names);
  318. }
  319. follower_names.add(dependent_name);
  320. uint degree;
  321. indegree.try_get(dependent_name, out degree);
  322. indegree.set(dependent_name, degree + 1);
  323. }
  324. }
  325. }
  326. }
  327. var order = new Vector<AbstractPackage>();
  328. var remaining = new HashSet<string>();
  329. foreach(var node in nodes) {
  330. remaining.add(node.manifest.name);
  331. }
  332. while(remaining.any()) {
  333. string? next = null;
  334. foreach(var name in remaining) {
  335. uint degree;
  336. if(indegree.try_get(name, out degree) && degree == 0) {
  337. if(next == null || name.collate(next) < 0) {
  338. next = name;
  339. }
  340. }
  341. }
  342. if(next == null) {
  343. throw new ResolverError.CYCLE(
  344. @"The resolved package graph contains a dependency cycle: $(describe_cycle(remaining, required, nodes))"
  345. );
  346. }
  347. remaining.remove(next);
  348. Vector<AbstractPackage> named;
  349. if(by_name.try_get(next, out named)) {
  350. foreach(var package in named) {
  351. order.add(package);
  352. }
  353. }
  354. Vector<string> follower_names;
  355. if(dependents.try_get(next, out follower_names)) {
  356. foreach(var follower in follower_names) {
  357. if(remaining.has(follower)) {
  358. uint degree;
  359. indegree.try_get(follower, out degree);
  360. indegree.set(follower, degree - 1);
  361. }
  362. }
  363. }
  364. }
  365. return order;
  366. }
  367. /** Deterministic catalog order: (name, version, repository name, package path). */
  368. private static int compare_packages(AbstractPackage a, AbstractPackage b) {
  369. var by_name = a.manifest.name.collate(b.manifest.name);
  370. if(by_name != 0) {
  371. return by_name;
  372. }
  373. var by_version = a.manifest.version.compare(b.manifest.version);
  374. if(by_version != 0) {
  375. return by_version;
  376. }
  377. var by_repository = (a.repository?.name ?? "").collate(b.repository?.name ?? "");
  378. if(by_repository != 0) {
  379. return by_repository;
  380. }
  381. return (a.package_path ?? "").collate(b.package_path ?? "");
  382. }
  383. /** The lifecycle phases resolution considers, in processing order. */
  384. private static Vector<DependencyPhase> resolution_phases(Manifest manifest) {
  385. var phases = new Vector<DependencyPhase>();
  386. phases.add(manifest.dependencies.manage);
  387. phases.add(manifest.dependencies.build);
  388. phases.add(manifest.dependencies.runtime);
  389. return phases;
  390. }
  391. private static Vector<AbstractPackage> catalog_of(Set<AbstractPackage> supplied, Dictionary<Repository, RepositoryListing> listings) {
  392. return supplied.concat(
  393. listings.select_many<Pair<Repository, RepositoryListingEntry>>(l => l.value.entries.select_pairs<Repository, RepositoryListingEntry>(e => l.key, e => e))
  394. .select<AbstractPackage>(p => new AbstractPackage.from_repository(p.value1, p.value2)))
  395. .sort(compare_packages)
  396. .to_vector();
  397. }
  398. private Vector<AbstractPackage> catalog() {
  399. if(catalog_cache == null) {
  400. catalog_cache = catalog_of(supplied, listings);
  401. }
  402. return catalog_cache;
  403. }
  404. /**
  405. * Collects every ref resolution could possibly consult — all refs of
  406. * all candidate groups of every package reachable through USM
  407. * providers — then asks the system package manager about the locally
  408. * missing ones in ONE batched query.
  409. */
  410. private void batch_query(Lot<AbstractPackage> roots, SystemPackageManager spm) throws Error {
  411. var refs = new HashSet<ResourceRef>();
  412. var visited = new HashSet<AbstractPackage>();
  413. var pending = new Series<AbstractPackage>();
  414. foreach(var root in roots) {
  415. pending.add(root);
  416. }
  417. while(pending.length > 0) {
  418. var package = pending.pop_start();
  419. if(visited.has(package)) {
  420. continue;
  421. }
  422. visited.add(package);
  423. foreach(var phase in resolution_phases(package.manifest)) {
  424. foreach(var resource in phase.ordered_all_refs()) {
  425. refs.add(resource);
  426. if(!has_local(resource)) {
  427. var provider = find_resource(resource);
  428. if(provider != null) {
  429. pending.add(provider);
  430. }
  431. }
  432. }
  433. }
  434. }
  435. var missing = new Vector<ResourceRef>();
  436. foreach(var resource in refs.sort((a, b) => a.to_string().collate(b.to_string()))) {
  437. if(!has_local(resource)) {
  438. missing.add(resource);
  439. }
  440. }
  441. if(missing.length == 0) {
  442. return;
  443. }
  444. SystemQueryResult? result = null;
  445. try {
  446. result = spm.query_sync(missing);
  447. }
  448. catch(Error e) {
  449. throw new ResolverError.SYSTEM_QUERY_FAILED(@"$(e.message)");
  450. }
  451. if(result == null) {
  452. spm_configured = false;
  453. return;
  454. }
  455. foreach(var candidate in result.packages) {
  456. foreach(var resource in candidate.resources) {
  457. var key = resource.to_string();
  458. Vector<SystemPackageCandidate> candidates;
  459. if(!spm_index.try_get(key, out candidates)) {
  460. candidates = new Vector<SystemPackageCandidate>();
  461. spm_index.set(key, candidates);
  462. }
  463. candidates.add(candidate);
  464. }
  465. }
  466. }
  467. private bool has_local(ResourceRef resource) {
  468. var key = resource.to_string();
  469. bool present;
  470. if(local_presence.try_get(key, out present)) {
  471. return present;
  472. }
  473. present = resource_finder.has_resource(resource);
  474. local_presence.set(key, present);
  475. return present;
  476. }
  477. /** Cost ordering for system package candidates: new installs first, then name. */
  478. private static int compare_spm_candidates(SystemPackageCandidate a, SystemPackageCandidate b) {
  479. var by_cost = (a.dependency_count - a.installed_dependency_count) - (b.dependency_count - b.installed_dependency_count);
  480. if(by_cost != 0) {
  481. return by_cost;
  482. }
  483. return a.name.collate(b.name);
  484. }
  485. /**
  486. * The best candidate providing the resource: one already chosen for
  487. * another resource (cost 0, smallest name) when possible, otherwise
  488. * the cheapest by (new installs, name).
  489. */
  490. private SystemPackageCandidate? best_spm_candidate(ResourceRef resource) {
  491. Vector<SystemPackageCandidate> candidates;
  492. if(!spm_index.try_get(resource.to_string(), out candidates)) {
  493. return null;
  494. }
  495. SystemPackageCandidate? already_chosen = null;
  496. SystemPackageCandidate? best = null;
  497. foreach(var candidate in candidates) {
  498. if(state.spm_chosen.has(candidate.name)) {
  499. if(already_chosen == null || candidate.name.collate(already_chosen.name) < 0) {
  500. already_chosen = candidate;
  501. }
  502. }
  503. else if(best == null || compare_spm_candidates(candidate, best) < 0) {
  504. best = candidate;
  505. }
  506. }
  507. return already_chosen ?? best;
  508. }
  509. /**
  510. * Resolves one resource ref for {@link origin} under the
  511. * local → system-package → USM-package precedence, mutating the
  512. * current {@link state}; returns the failure when unsatisfiable.
  513. */
  514. private RefFailure? resolve_ref(AbstractPackage origin, ResourceRef resource) throws Error {
  515. if(state.chosen.provides(resource) || has_local(resource)) {
  516. return null;
  517. }
  518. if(spm_configured) {
  519. var candidate = best_spm_candidate(resource);
  520. if(candidate != null) {
  521. state.spm_chosen.set(candidate.name, candidate);
  522. return null;
  523. }
  524. }
  525. var provider = find_resource(resource);
  526. if(provider != null) {
  527. if(!state.chosen.has(provider)) {
  528. state.chosen.add(provider);
  529. }
  530. process_package(provider);
  531. return null;
  532. }
  533. return new RefFailure() {
  534. resource = resource,
  535. origin = origin.manifest.name,
  536. spm_configured = spm_configured
  537. };
  538. }
  539. private void process_package(AbstractPackage package) throws Error {
  540. if(state.processed.has(package)) {
  541. return;
  542. }
  543. state.processed.add(package);
  544. report_resolution_progress();
  545. var manifest = package.manifest;
  546. for(int phase_index = 0; phase_index < PHASES.length; phase_index++) {
  547. var phase_name = PHASES[phase_index];
  548. var phase = manifest_phase(manifest, phase_name);
  549. if(phase.is_grouped) {
  550. select_group(package, phase_name, phase);
  551. }
  552. else {
  553. var failures = new Vector<RefFailure>();
  554. foreach(var resource in phase.ordered_required()) {
  555. var failure = resolve_ref(package, resource);
  556. if(failure != null) {
  557. failures.add(failure);
  558. }
  559. }
  560. if(failures.any()) {
  561. var builder = new StringBuilder();
  562. builder.append_printf(
  563. "Could not resolve package \"%s\" (phase \"%s\"): %u unresolvable dependenc%s:\n",
  564. manifest.name, phase_name, failures.length, failures.length == 1 ? "y" : "ies"
  565. );
  566. foreach(var failure in failures) {
  567. builder.append_printf(" %s\n", failure.describe(manifest.name));
  568. }
  569. throw new ResolverError.UNSATISFIABLE(builder.str);
  570. }
  571. record_refs(package, phase.ordered_required());
  572. }
  573. }
  574. }
  575. /**
  576. * Fires {@link resolution_progress} for the packages processed
  577. * so far, clamped to 1.0; the estimate deliberately
  578. * over-counts shallow closures so the bar never sits full
  579. * before resolution is done.
  580. */
  581. private void report_resolution_progress() {
  582. if(resolution_progress == null) {
  583. return;
  584. }
  585. var fraction = (float)state.processed.count() / (float)resolution_estimated_total;
  586. resolution_progress(fraction > 1.0f ? 1.0f : fraction);
  587. }
  588. private static DependencyPhase manifest_phase(Manifest manifest, string phase_name) {
  589. switch(phase_name) {
  590. case "manage":
  591. return manifest.dependencies.manage;
  592. case "build":
  593. return manifest.dependencies.build;
  594. case "runtime":
  595. return manifest.dependencies.runtime;
  596. default:
  597. assert_not_reached();
  598. }
  599. }
  600. /**
  601. * Evaluates the phase's candidate groups in manifest order on a
  602. * snapshot of the choices already made: a group is viable when every
  603. * member resolves. The viable group minimising total new installs
  604. * (USM packages with their transitive closure plus system packages by
  605. * their cost fields) is committed; ties keep manifest order. When no
  606. * group is viable an itemised per-group report is thrown.
  607. */
  608. private void select_group(AbstractPackage package, string phase_name, DependencyPhase phase) throws Error {
  609. var base_state = state.snapshot();
  610. var groups = phase.ordered_groups();
  611. var group_failures = new Vector<GroupFailure>();
  612. ResolutionState? best_state = null;
  613. uint best_index = 0;
  614. int best_cost = 0;
  615. for(uint index = 0; index < groups.length; index++) {
  616. // A fresh copy per evaluation: state must never alias
  617. // base_state, or every group's additions would look
  618. // pre-existing and cost nothing
  619. state = base_state.snapshot();
  620. var failures = new Vector<RefFailure>();
  621. foreach(var resource in groups[index]) {
  622. var failure = resolve_ref(package, resource);
  623. if(failure != null) {
  624. failures.add(failure);
  625. }
  626. }
  627. if(failures.any()) {
  628. group_failures.add(new GroupFailure() {
  629. group_index = index + 1,
  630. members = groups[index],
  631. failures = failures
  632. });
  633. continue;
  634. }
  635. var cost = new_install_cost(base_state);
  636. if(best_state == null || cost < best_cost) {
  637. best_cost = cost;
  638. best_index = index;
  639. best_state = state.snapshot();
  640. }
  641. }
  642. if(best_state == null) {
  643. var builder = new StringBuilder();
  644. builder.append_printf(
  645. "Could not resolve package \"%s\" (phase \"%s\"): no candidate dependency group is satisfiable.\n",
  646. package.manifest.name, phase_name
  647. );
  648. foreach(var failure in group_failures) {
  649. builder.append(failure.describe(package.manifest.name));
  650. }
  651. throw new ResolverError.NO_VIABLE_GROUP(builder.str);
  652. }
  653. state = best_state;
  654. record_refs(package, groups[best_index]);
  655. }
  656. /** Total new installs the current state adds over {@link base_state}: new USM packages plus system-package new-install costs. */
  657. private int new_install_cost(ResolutionState base_state) {
  658. var cost = (int)(state.chosen.count() - base_state.chosen.count());
  659. foreach(var pair in state.spm_chosen) {
  660. if(!base_state.spm_chosen.has(pair.key)) {
  661. cost += pair.value.dependency_count - pair.value.installed_dependency_count;
  662. }
  663. }
  664. return cost;
  665. }
  666. private void record_refs(AbstractPackage package, Vector<ResourceRef> refs) {
  667. Set<ResourceRef> recorded;
  668. if(!state.refs_by_package.try_get(package.manifest.name, out recorded)) {
  669. recorded = new HashSet<ResourceRef>();
  670. state.refs_by_package.set(package.manifest.name, recorded);
  671. }
  672. foreach(var resource in refs) {
  673. recorded.add(resource);
  674. }
  675. }
  676. /** Walks provider edges among {@link remaining} from its smallest name until a node repeats, naming the cycle found. */
  677. private static string describe_cycle(HashSet<string> remaining, Dictionary<string, Vector<ResourceRef>> required, Vector<AbstractPackage> nodes) {
  678. var providers_of = new Dictionary<string, Vector<string>>();
  679. foreach(var dependent in nodes) {
  680. var dependent_name = dependent.manifest.name;
  681. foreach(var provider in nodes) {
  682. var provider_name = provider.manifest.name;
  683. if(provider_name == dependent_name || !remaining.has(provider_name)) {
  684. continue;
  685. }
  686. Vector<ResourceRef> refs;
  687. if(!required.try_get(dependent_name, out refs)) {
  688. continue;
  689. }
  690. var provided = false;
  691. foreach(var resource in refs) {
  692. if(provider.manifest.provides.any(p => resource.satisfied_by(p.key))) {
  693. provided = true;
  694. break;
  695. }
  696. }
  697. if(provided) {
  698. Vector<string> providers;
  699. if(!providers_of.try_get(dependent_name, out providers)) {
  700. providers = new Vector<string>();
  701. providers_of.set(dependent_name, providers);
  702. }
  703. providers.add(provider_name);
  704. }
  705. }
  706. }
  707. string? smallest = null;
  708. foreach(var name in remaining) {
  709. if(smallest == null || name.collate(smallest) < 0) {
  710. smallest = name;
  711. }
  712. }
  713. var path = new Series<string>();
  714. var seen = new HashSet<string>();
  715. var current = smallest;
  716. while(current != null && !seen.has(current)) {
  717. seen.add(current);
  718. path.add(current);
  719. string? next = null;
  720. Vector<string> providers;
  721. if(providers_of.try_get(current, out providers)) {
  722. foreach(var provider in providers) {
  723. if(next == null || provider.collate(next) < 0) {
  724. next = provider;
  725. }
  726. }
  727. }
  728. current = next;
  729. }
  730. if(current == null) {
  731. return remaining.to_string(n => n, " -> ");
  732. }
  733. var cycle = new Series<string>();
  734. var started = false;
  735. foreach(var name in path) {
  736. if(name == current) {
  737. started = true;
  738. }
  739. if(started) {
  740. cycle.add(name);
  741. }
  742. }
  743. cycle.add(current);
  744. return cycle.to_string(n => n, " -> ");
  745. }
  746. }
  747. public class PackageSet : HashSet<AbstractPackage> {
  748. public void add_from_file(string path) throws Error {
  749. add(new AbstractPackage.from_package(path));
  750. }
  751. public void add_from_repo(Repository repository, RepositoryListingEntry entry) throws Error {
  752. add(new AbstractPackage.from_repository(repository, entry));
  753. }
  754. /**
  755. * Whether every package's required phases hold: flat phases need all
  756. * refs present (provided by the set or locally), grouped phases need
  757. * one fully-satisfied group.
  758. */
  759. public bool is_satisfied(ResourceFinder local_resource_finder) {
  760. return all(package => {
  761. foreach(var phase_name in new string[] { "manage", "build", "runtime" }) {
  762. var phase = manifest_phase_of(package.manifest, phase_name);
  763. if(!phase.is_satisfied(d => provides(d) || local_resource_finder.has_resource(d))) {
  764. return false;
  765. }
  766. }
  767. return true;
  768. });
  769. }
  770. public bool provides(ResourceRef resource) {
  771. return any(p => p.manifest.provides.any(r => resource.satisfied_by(r.key)));
  772. }
  773. private static DependencyPhase manifest_phase_of(Manifest manifest, string phase_name) {
  774. switch(phase_name) {
  775. case "manage":
  776. return manifest.dependencies.manage;
  777. case "build":
  778. return manifest.dependencies.build;
  779. case "runtime":
  780. return manifest.dependencies.runtime;
  781. default:
  782. assert_not_reached();
  783. }
  784. }
  785. }
  786. public class AbstractPackage {
  787. public Manifest manifest { get; set; }
  788. public Repository? repository { get; set; }
  789. public RepositoryListingEntry? repository_entry { get; set; }
  790. public string? package_path { get; set; }
  791. public CachedPackage? cached_package { get; set; }
  792. public AbstractPackage.from_package(string path) throws Error {
  793. package_path = path;
  794. manifest = new Manifest.from_package(path);
  795. }
  796. public AbstractPackage.from_repository(Repository repository, RepositoryListingEntry entry) {
  797. this.repository = repository;
  798. this.repository_entry = entry;
  799. this.manifest = entry.manifest;
  800. }
  801. /** Wraps an already-parsed {@link Manifest} with no repository or package source behind it; used by tooling and tests. */
  802. public AbstractPackage.from_manifest(Manifest manifest) {
  803. this.manifest = manifest;
  804. }
  805. }
  806. }