Transaction.vala 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273
  1. using Invercargill;
  2. using Invercargill.DataStructures;
  3. namespace Usm {
  4. public class Transaction {
  5. public Paths paths { get; set; }
  6. public ResourceFinder resource_finder { get; set; }
  7. /**
  8. * Resources guaranteed present on the destination before the first
  9. * install lot runs: the system packages chosen during dependency
  10. * resolution install ahead of the USM transaction (see
  11. * `Install.run`). The lot scheduler treats them as locally
  12. * present — without this, every system-provided build dependency
  13. * (compilers, pkg-config files, ...) looks unmet at planning time,
  14. * because strategise runs before anything is installed.
  15. */
  16. public Set<ResourceRef> preinstalled_resources { get; set; default = new HashSet<ResourceRef>(); }
  17. public SystemState state { get; set; }
  18. public Set<CachedPackage> to_install { get; set; default = new HashSet<CachedPackage>(); }
  19. public Set<CachedPackage> to_remove { get; set; default = new HashSet<CachedPackage>(); }
  20. /**
  21. * Optional install order (package names) taken from a
  22. * {@link ResolutionResult}'s {@link ResolutionResult.install_order};
  23. * when set, packages build and install in exactly this order.
  24. */
  25. public Vector<string>? install_order { get; set; }
  26. /**
  27. * Optional removal order (package names) — the reverse of a
  28. * {@link ResolutionResult}'s install order; when set, removals follow
  29. * it exactly (dependents before their providers).
  30. */
  31. public Vector<string>? remove_order { get; set; }
  32. /**
  33. * Manifest names of the packages the user asked for by name (the
  34. * `usm install` arguments): each install records
  35. * {@link OriginInformation.explicitly_installed} true for these and
  36. * false for everything the resolver pulled in as a dependency.
  37. * Null — every command but `usm install` — marks the whole install
  38. * set implicit.
  39. */
  40. public Vector<string>? explicit_packages { get; set; }
  41. /**
  42. * Version downgrades executed by this transaction: each entry
  43. * removes {@link DowngradeEntry.current}'s installed resources —
  44. * keeping its state directory for a later downgrade back — and
  45. * installs {@link DowngradeEntry.target} in its place.
  46. */
  47. public Vector<DowngradeEntry> to_downgrade { get; set; default = new Vector<DowngradeEntry>(); }
  48. /**
  49. * The system package manager the {@link system_packages} install
  50. * through, as one native transaction that runs BEFORE the first
  51. * USM install lot — providing the toolchain and libraries the
  52. * builds depend on. Null (or an empty {@link system_packages})
  53. * makes the transaction USM-only, exactly as before.
  54. */
  55. public SystemPackageManager? system_package_manager { get; set; }
  56. /**
  57. * System (native) packages chosen during dependency resolution
  58. * that this transaction installs itself, each as one
  59. * {@link TransactionTask.INSTALLING_SYSTEM} action: they appear
  60. * in the plan preview, drive the shared progress display, and
  61. * count towards the transaction's overall progress. The lot
  62. * scheduler also treats their resources as present (see
  63. * {@link preinstalled_resources}).
  64. */
  65. public Vector<SystemPackageCandidate> system_packages { get; set; default = new Vector<SystemPackageCandidate>(); }
  66. /**
  67. * The material system install set — every native package the
  68. * manager would install for {@link system_packages}, chosen
  69. * names plus every dependency of a dependency, in the manager's
  70. * own transaction order (from
  71. * {@link SystemPackageManager.plan_install}). Each name in this
  72. * list is one {@link TransactionTask.INSTALLING_SYSTEM} action
  73. * with its own progress and completion log, so the overall
  74. * percentage covers the whole native transaction. Empty (helpers
  75. * without a plan mode) falls back to one action per chosen
  76. * {@link system_packages} entry.
  77. */
  78. public Vector<string> system_install_plan { get; set; default = new Vector<string>(); }
  79. /**
  80. * The names of the system-phase actions, plan first: every
  81. * material package when a plan exists, otherwise the chosen
  82. * candidates alone.
  83. */
  84. private Vector<string> effective_system_names() {
  85. var names = new Vector<string>();
  86. if(system_install_plan.length > 0) {
  87. foreach(var name in system_install_plan) {
  88. names.add(name);
  89. }
  90. return names;
  91. }
  92. foreach(var candidate in system_packages) {
  93. names.add(candidate.name);
  94. }
  95. return names;
  96. }
  97. public signal void progress_updated(TransactionTask task_type, string subject, uint current_task, uint total_tasks, float task_progress);
  98. private uint task_count = 0;
  99. private uint current_task = 0;
  100. private string current_subject = "transaction";
  101. private TransactionTask current_task_type = TransactionTask.STRATEGISING;
  102. /**
  103. * Active rollback journal, non-null only for the duration of
  104. * {@link run} and {@link rebuild_package}: every install and
  105. * remove records its changes before executing them so a failure
  106. * can be undone in reverse (see {@link Journal}).
  107. */
  108. private Journal? journal = null;
  109. /**
  110. * Working sets computed by {@link strategise}: {@link to_install}
  111. * plus downgrade targets, and {@link to_remove} plus downgrade
  112. * currents — the whole transaction runs against these.
  113. */
  114. private Set<CachedPackage> planned_install = new HashSet<CachedPackage>();
  115. private Set<CachedPackage> planned_removal = new HashSet<CachedPackage>();
  116. /** Downgrade targets (installed with {@link InstallType.DOWNGRADE}) and currents (removed with {@link RemoveType.DOWNGRADE}). */
  117. private Set<CachedPackage> downgrade_targets = new HashSet<CachedPackage>();
  118. private Set<CachedPackage> downgrade_currents = new HashSet<CachedPackage>();
  119. /** Removals whose package name this transaction reinstalls — update removals ({@link RemoveType.UPGRADE}). */
  120. private Set<CachedPackage> upgrade_removals = new HashSet<CachedPackage>();
  121. /**
  122. * Lots computed by {@link strategise}: each lot builds, tests and
  123. * installs together, and lots run in dependency order. A package
  124. * joins a lot only when every BUILD-phase ref is already present or
  125. * provided by an earlier lot, so downstream builds always see the
  126. * upstream package's installed `pc:`/`vapi:` artifacts.
  127. */
  128. public Vector<Vector<CachedPackage>> install_lots { get; private set; }
  129. /** Removal order computed by {@link strategise}: dependents before their providers. */
  130. public Vector<CachedPackage> removal_order { get; private set; }
  131. /**
  132. * Rebuild entries planned by {@link strategise} for packages in
  133. * {@link to_install} flagged
  134. * {@link ManifestFlag.REBUILD_DEPENDANTS}: each entry rebuilds its
  135. * installed {@link RebuildEntry.package} — same CachedPackage,
  136. * same version, same state path — through the standard pipeline,
  137. * tagged {@link TransactionTask.REBUILDING} in progress reports.
  138. * Empty when nothing is flagged or {@link state} is unset.
  139. */
  140. public Vector<RebuildEntry> rebuilds { get; private set; default = new Vector<RebuildEntry>(); }
  141. /**
  142. * Emitted when a Ctrl+C lands mid-transaction: a true return
  143. * keeps the transaction going (the interrupted operation
  144. * re-runs), false rolls the journaled transaction back. No
  145. * handler is connected by the CLI — a Ctrl+C always rolls back
  146. * — leaving this as the programmatic opt-out for embedders.
  147. */
  148. public signal bool interrupt_decided(string subject, string phase_verb);
  149. public void run() throws TransactionError {
  150. // 1. Verify the transaction is valid
  151. strategise();
  152. journal = new Journal(journal_backup_root());
  153. TransactionInterrupt.install();
  154. try {
  155. journal.begin();
  156. var all_packages = planned_removal.concat(planned_install);
  157. var rebuild_packages = rebuilds.select<CachedPackage>(r => r.package).to_vector();
  158. // 2. System packages install first, as one native
  159. // transaction, providing the toolchain and libraries the
  160. // builds depend on
  161. install_system_packages();
  162. // 3. Unpack packages
  163. do_for(all_packages, unpack_package, TransactionTask.UNPACKING);
  164. do_for(rebuild_packages, unpack_package, TransactionTask.REBUILDING);
  165. // 4. Remove packages
  166. do_for(removal_order, remove_package, TransactionTask.REMOVING);
  167. foreach (var lot in install_lots) {
  168. // 5. Build packages
  169. do_for(lot, build_package, TransactionTask.BUILDING);
  170. // 6. Test packages
  171. do_for(lot, test_package, TransactionTask.TESTING);
  172. // 7. Install packages
  173. do_for(lot, install_package, TransactionTask.INSTALLING);
  174. }
  175. // Rebuilds run the same pipeline once every flagged package is
  176. // installed, so dependants build against the fresh artifacts
  177. do_for(rebuild_packages, unpack_package, TransactionTask.UNPACKING);
  178. do_for(rebuild_packages, build_package, TransactionTask.REBUILDING);
  179. do_for(rebuild_packages, test_package, TransactionTask.TESTING);
  180. do_for(rebuild_packages, install_package, TransactionTask.INSTALLING);
  181. // 8. Clean up
  182. do_for(all_packages, cleanup_package, TransactionTask.CLEANING_UP);
  183. do_for(rebuild_packages, cleanup_package, TransactionTask.CLEANING_UP);
  184. journal.finish();
  185. }
  186. catch(TransactionError e) {
  187. rollback_and_report();
  188. throw e;
  189. }
  190. catch(Error e) {
  191. rollback_and_report();
  192. throw new TransactionError.UNKNOWN_ERROR(@"Error running transaction: $(e.message)");
  193. }
  194. finally {
  195. TransactionInterrupt.restore();
  196. journal = null;
  197. }
  198. }
  199. /**
  200. * Rebuilds one already-installed package through the standard
  201. * pipeline — build-cache restore, clean-retry on failure,
  202. * reinstall of its resources — tagged
  203. * {@link TransactionTask.REBUILDING} throughout; the `usm
  204. * rebuild` entry point. No confirmation is needed: nothing
  205. * installed changes version.
  206. */
  207. public void rebuild_package(CachedPackage package) throws TransactionError {
  208. var packages = new Vector<CachedPackage>();
  209. packages.add(package);
  210. task_count = 5;
  211. current_task = 0;
  212. current_subject = package.package_name;
  213. current_task_type = TransactionTask.REBUILDING;
  214. journal = new Journal(journal_backup_root());
  215. TransactionInterrupt.install();
  216. try {
  217. journal.begin();
  218. do_for(packages, unpack_package, TransactionTask.UNPACKING);
  219. do_for(packages, build_package, TransactionTask.REBUILDING);
  220. do_for(packages, test_package, TransactionTask.TESTING);
  221. do_for(packages, install_package, TransactionTask.INSTALLING);
  222. do_for(packages, cleanup_package, TransactionTask.CLEANING_UP);
  223. journal.finish();
  224. }
  225. catch(TransactionError e) {
  226. rollback_and_report();
  227. throw e;
  228. }
  229. catch(Error e) {
  230. rollback_and_report();
  231. throw new TransactionError.UNKNOWN_ERROR(@"Error rebuilding package $(package.package_name): $(e.message)");
  232. }
  233. finally {
  234. TransactionInterrupt.restore();
  235. journal = null;
  236. }
  237. }
  238. /**
  239. * Rolls the active journal back after a failure and prints what
  240. * was undone; a rollback failure is reported by the journal
  241. * itself and never masks the original error.
  242. */
  243. private void rollback_and_report() {
  244. if(journal == null) {
  245. return;
  246. }
  247. journal.rollback();
  248. printerr(journal.describe_rollback() + "\n");
  249. }
  250. /**
  251. * Installs the system phase through {@link
  252. * system_package_manager} as the transaction's first actions:
  253. * one native transaction whose streaming events drive one
  254. * {@link TransactionTask.INSTALLING_SYSTEM} action per package
  255. * of {@link effective_system_names} — in the plan preview, on
  256. * the shared progress display and in the overall task count.
  257. *
  258. * Action slots are claimed IN COMPLETION ORDER, never by the
  259. * helper's own positions or the plan's ordering: downloads and
  260. * the rpm transaction each stream `package` events in their own
  261. * order, neither matching the plan, so a fixed mapping would
  262. * hand the display out-of-order indexes that the stale-report
  263. * guard (correctly) drops. Completions are strictly sequential
  264. * in the native transaction, so each one claims the next slot
  265. * and logs; a fraction event for a package that has not yet
  266. * completed draws on the next slot to be claimed — the running
  267. * package — without claiming it. Names outside the plan (a
  268. * differently-resolved run-time transaction) share the bound of
  269. * {@link effective_system_names}.length slots and fold silently
  270. * once the bound is reached.
  271. *
  272. * A native package cannot be rolled back by the journal; on
  273. * failure the thrown {@link TransactionError.INSTALL_ERROR} still
  274. * unwinds and rolls back every USM action (none has run yet). A
  275. * no-op when no manager is set or the package list is empty.
  276. */
  277. private void install_system_packages() throws TransactionError {
  278. var action_names = effective_system_names();
  279. if(system_package_manager == null || (system_packages.length == 0 && system_install_plan.length == 0)) {
  280. return;
  281. }
  282. var planned = new HashSet<string>();
  283. foreach(var name in action_names) {
  284. planned.add(name);
  285. }
  286. var install_names = new Vector<string>();
  287. foreach(var candidate in system_packages) {
  288. if(!install_names.any(n => n == candidate.name)) {
  289. install_names.add(candidate.name);
  290. }
  291. }
  292. string failure = "";
  293. var claimed_slot = new Dictionary<string, int>();
  294. int next_slot = 0;
  295. var loop = new MainLoop();
  296. system_package_manager.install.begin(install_names, event => {
  297. switch(event.event_type) {
  298. case SystemInstallEventType.BEGIN:
  299. break;
  300. case SystemInstallEventType.PACKAGE:
  301. if(event.name != null && next_slot < action_names.length) {
  302. int slot;
  303. if(!claimed_slot.try_get(event.name, out slot)) {
  304. // The running package targets the slot the
  305. // next completion will claim
  306. slot = next_slot;
  307. }
  308. begin_action(TransactionTask.INSTALLING_SYSTEM, event.name, 1 + slot);
  309. report_progress(TransactionTask.INSTALLING_SYSTEM, (float)event.progress);
  310. }
  311. break;
  312. case SystemInstallEventType.PACKAGE_COMPLETE:
  313. if(event.name != null && next_slot < action_names.length) {
  314. int slot;
  315. if(!claimed_slot.try_get(event.name, out slot)) {
  316. slot = next_slot++;
  317. claimed_slot.set(event.name, slot);
  318. }
  319. begin_action(TransactionTask.INSTALLING_SYSTEM, event.name, 1 + slot);
  320. report_progress(TransactionTask.INSTALLING_SYSTEM, 1.0f);
  321. }
  322. break;
  323. case SystemInstallEventType.COMPLETE:
  324. break;
  325. case SystemInstallEventType.ERROR:
  326. failure = event.message ?? "unknown error";
  327. break;
  328. }
  329. }, (obj, res) => {
  330. try {
  331. if(!system_package_manager.install.end(res)) {
  332. failure = failure.length > 0 ? failure : "the system package manager reported a failure";
  333. }
  334. }
  335. catch(Error e) {
  336. failure = e.message;
  337. }
  338. loop.quit();
  339. });
  340. loop.run();
  341. // Whatever the helper streamed, the USM actions start right
  342. // after the last system action's slot
  343. begin_action(TransactionTask.STRATEGISING, "transaction", 1 + action_names.length);
  344. if(failure.length > 0) {
  345. throw new TransactionError.INSTALL_ERROR(@"System package installation failed: $failure");
  346. }
  347. }
  348. /**
  349. * `<destination>/<local-state>/usm/backup` — the journal's home,
  350. * rooted like every other destination this transaction writes
  351. * (`/var/usm/backup` on an unredirected system).
  352. */
  353. private string journal_backup_root() {
  354. return Path.build_filename(paths.destination, paths.local_state, "usm", "backup");
  355. }
  356. private string previous_key = "";
  357. public void print_progress(TransactionTask task_type, string subject, uint current_task, uint total_tasks, float task_progress) {
  358. var verb = task_type.get_verb();
  359. verb = verb[0].toupper().to_string() + verb.substring(1);
  360. var percent = (int)(task_progress * 100.0f);
  361. var key = @"$(task_type.get_verb())_$(subject)_$current_task";
  362. var prefix = previous_key == key ? "\x1b[1F\x1b[2K" : "";
  363. previous_key = key;
  364. printerr(@"$prefix[$(current_task+1)/$total_tasks] $verb $subject ($percent%)\n");
  365. }
  366. public void print_progress_simple(TransactionTask task_type, string subject, uint current_task, uint total_tasks, float task_progress) {
  367. var verb = task_type.get_verb();
  368. verb = verb[0].toupper().to_string() + verb.substring(1);
  369. var percent = (int)(task_progress * 100.0f);
  370. printerr(@"[$(current_task+1)/$total_tasks] $verb $subject ($percent%)\n");
  371. }
  372. public void strategise() throws TransactionError {
  373. // Reset the task counters — strategise may be called twice
  374. // (once for the summary/confirm, once inside run())
  375. current_task = 0;
  376. task_count = 0;
  377. planned_install = new HashSet<CachedPackage>();
  378. planned_install.union_with(to_install);
  379. planned_removal = new HashSet<CachedPackage>();
  380. planned_removal.union_with(to_remove);
  381. downgrade_targets = new HashSet<CachedPackage>();
  382. downgrade_currents = new HashSet<CachedPackage>();
  383. foreach(var entry in to_downgrade) {
  384. planned_install.add(entry.target);
  385. downgrade_targets.add(entry.target);
  386. planned_removal.add(entry.current);
  387. downgrade_currents.add(entry.current);
  388. }
  389. // A removal whose package name this transaction reinstalls is
  390. // an update removal, not a final one
  391. upgrade_removals = new HashSet<CachedPackage>();
  392. try {
  393. var install_names = new HashSet<string>();
  394. foreach(var package in planned_install) {
  395. install_names.add(package.get_manifest().name);
  396. }
  397. foreach(var package in planned_removal) {
  398. if(install_names.contains(package.get_manifest().name)) {
  399. upgrade_removals.add(package);
  400. }
  401. }
  402. }
  403. catch(Error e) {
  404. throw new TransactionError.UNKNOWN_ERROR(@"Failed to read manifest while planning removals: $(e.message)");
  405. }
  406. task_count = (planned_install.count() * 5) + (planned_removal.count() * 3) + effective_system_names().length + 1;
  407. uint strategise_worst_case_task_count = (planned_removal.count() * planned_removal.count()) + (planned_install.count() * planned_install.count()) + effective_system_names().length;
  408. uint strategise_current_task = 0;
  409. report_progress(TransactionTask.STRATEGISING, 0.0f);
  410. // Installation strategy
  411. install_lots = new Vector<Vector<CachedPackage>>();
  412. var touched = new HashSet<CachedPackage>();
  413. var installed_by_earlier_lots = new HashSet<ResourceRef>();
  414. var ordered_install = ordered_by_names(planned_install, install_order);
  415. var round = 0;
  416. while(true) {
  417. strategise_current_task = round * planned_install.count();
  418. var lot = new Vector<CachedPackage>();
  419. var installed_by_this_lot = new HashSet<ResourceRef>();
  420. var remaining = ordered_install.exclude(touched);
  421. if(remaining.count() == 0) {
  422. break;
  423. }
  424. foreach (var package in ordered_install.exclude(touched)) {
  425. report_progress(TransactionTask.STRATEGISING, (float)strategise_current_task / (float)strategise_worst_case_task_count);
  426. try {
  427. var manifest = package.get_manifest();
  428. // Resources provided by packages this transaction
  429. // REMOVES are gone before the first build runs —
  430. // stale on disk at planning time, they satisfy
  431. // nothing (an update pair's replacement counts only
  432. // from the lot that installs it, so the dependency
  433. // lands in an earlier lot and the dependent builds
  434. // against the fresh artifact)
  435. PredicateDelegate<ResourceRef> removed_by_plan = d => planned_removal.any(p => {
  436. try {
  437. return p.get_manifest().satisfies(d);
  438. }
  439. catch(Error e) {
  440. return false;
  441. }
  442. });
  443. // Build-phase refs must be satisfied by resources
  444. // that are in place when this package builds:
  445. // either installed by an EARLIER lot (the
  446. // replacement of an updated pair — this dominates,
  447. // the removal is already undone by then) or
  448. // present on the destination and NOT provided by
  449. // a package this transaction removes (those go
  450. // away before the first build, so the stale
  451. // on-disk state satisfies nothing). A same-lot
  452. // provide is not installed yet and defers the
  453. // package to the next lot.
  454. PredicateDelegate<ResourceRef> buildtime_satisfied = d =>
  455. installed_by_earlier_lots.any(r => d.satisfied_by(r))
  456. || (!removed_by_plan(d) && (
  457. resource_finder.has_resource(d)
  458. || preinstalled_resources.any(r => d.satisfied_by(r))));
  459. // Manage-phase executables run at install time,
  460. // when everything this lot has already admitted
  461. // (in topological order) is installed — the same
  462. // dominance and poison rules as the build phase
  463. PredicateDelegate<ResourceRef> installtime_satisfied = d =>
  464. installed_by_earlier_lots.any(r => d.satisfied_by(r))
  465. || installed_by_this_lot.any(r => d.satisfied_by(r))
  466. || (!removed_by_plan(d) && (
  467. resource_finder.has_resource(d)
  468. || preinstalled_resources.any(r => d.satisfied_by(r))));
  469. if(manifest.dependencies.manage.is_satisfied(installtime_satisfied) &&
  470. manifest.dependencies.build.is_satisfied(buildtime_satisfied)) {
  471. lot.add(package);
  472. touched.add(package);
  473. installed_by_this_lot.union_with(manifest.effective_provides());
  474. }
  475. strategise_current_task++;
  476. }
  477. catch(Error e) {
  478. throw new TransactionError.UNKNOWN_ERROR(@"Failed to read manifest for package \"$(package.package_name)\": $(e.message)");
  479. }
  480. }
  481. if(lot.count() == 0) {
  482. throw new TransactionError.INVALID_TRANSACTION(
  483. describe_install_deadlock(ordered_install.exclude(touched), installed_by_earlier_lots, planned_install)
  484. );
  485. }
  486. installed_by_earlier_lots.union_with(installed_by_this_lot);
  487. install_lots.add(lot);
  488. round++;
  489. }
  490. var current_task_baseline = (planned_install.count() * planned_install.count());
  491. strategise_current_task = current_task_baseline;
  492. report_progress(TransactionTask.STRATEGISING, (float)strategise_current_task / (float)strategise_worst_case_task_count);
  493. // Removal strategy
  494. removal_order = new Vector<CachedPackage>();
  495. if(remove_order != null) {
  496. removal_order = ordered_by_names(planned_removal, remove_order);
  497. }
  498. else {
  499. Set<CachedPackageManifest> remaining_to_remove;
  500. try {
  501. remaining_to_remove = planned_removal
  502. .attempt_select<CachedPackageManifest>(p => new CachedPackageManifest(p))
  503. .to_set();
  504. }
  505. catch(Error e) {
  506. throw new TransactionError.UNKNOWN_ERROR(@"Failed to read manifest: $(e.message)");
  507. }
  508. round = 0;
  509. while(true) {
  510. strategise_current_task = current_task_baseline + (round * planned_removal.count());
  511. if(remaining_to_remove.count() == 0) {
  512. break;
  513. }
  514. var removed_this_round = false;
  515. foreach (var package in remaining_to_remove.sort((a, b) => a.package.package_name.collate(b.package.package_name))) {
  516. report_progress(TransactionTask.STRATEGISING, (float)strategise_current_task / (float)strategise_worst_case_task_count);
  517. if(remaining_to_remove.no(p => p.manifest.dependencies.manage.all_refs().any(d => package.manifest.satisfies(d)))) {
  518. removal_order.add(package.package);
  519. remaining_to_remove.remove(package);
  520. strategise_current_task++;
  521. round++;
  522. removed_this_round = true;
  523. break;
  524. }
  525. strategise_current_task++;
  526. }
  527. if(!removed_this_round) {
  528. throw new TransactionError.INVALID_TRANSACTION(describe_removal_deadlock(remaining_to_remove));
  529. }
  530. }
  531. }
  532. plan_rebuilds();
  533. task_count += rebuilds.length * 5;
  534. report_progress(TransactionTask.STRATEGISING, 1.0f);
  535. current_task++;
  536. }
  537. /**
  538. * Whether a resource is satisfiable without the packages this
  539. * explainer deals with: present on the destination, provided by a
  540. * preinstalled (system) package, or provided by a lot scheduled
  541. * before the stall.
  542. */
  543. /**
  544. * Explains why the install lot scheduler stalled: for every
  545. * unscheduled package, each blocking dependency is reported with
  546. * its exact cause — unmet (nothing present or planned provides it)
  547. * or cyclical (only packages that are themselves unscheduled
  548. * provide it) — naming the providers, the phase carrying the ref,
  549. * and the lot rule that applies (build-phase refs must come from
  550. * an EARLIER lot; manage-phase refs may share the lot).
  551. */
  552. private string describe_install_deadlock(Enumerable<CachedPackage> stuck, Set<ResourceRef> installed_by_earlier_lots, Set<CachedPackage> planned_install) {
  553. var stuck_sorted = stuck.sort((a, b) => a.package_name.collate(b.package_name)).to_vector();
  554. // The same satisfier the scheduler used, poison included: a
  555. // resource provided by a package this transaction removes is
  556. // going away before the first build, so explaining a stall
  557. // against the stale on-disk state would clear every blocker
  558. // and produce a dead diagnosis
  559. PredicateDelegate<ResourceRef> removed_by_plan = r => planned_removal.any(p => {
  560. try {
  561. return p.get_manifest().satisfies(r);
  562. }
  563. catch(Error e) {
  564. return false;
  565. }
  566. });
  567. PredicateDelegate<ResourceRef> satisfier = r =>
  568. installed_by_earlier_lots.any(p => r.satisfied_by(p))
  569. || (!removed_by_plan(r) && (
  570. resource_finder.has_resource(r)
  571. || preinstalled_resources.any(p => r.satisfied_by(p))));
  572. var builder = new StringBuilder();
  573. builder.append_printf("Could not build a transaction strategy: %u package(s) could not be scheduled for installation", stuck_sorted.length);
  574. if(preinstalled_resources.any()) {
  575. builder.append_printf(" (%u resource(s) already planned via system packages are counted as present)", preinstalled_resources.count());
  576. }
  577. builder.append(":\n");
  578. foreach(var package in stuck_sorted) {
  579. builder.append_printf(" %s:\n", package.package_name);
  580. Manifest manifest;
  581. try {
  582. manifest = package.get_manifest();
  583. }
  584. catch(Error e) {
  585. builder.append_printf(" (manifest could not be read: %s)\n", e.message);
  586. continue;
  587. }
  588. append_blocking_refs(builder, manifest.dependencies.manage, "manage", satisfier, removed_by_plan, planned_install, package);
  589. append_blocking_refs(builder, manifest.dependencies.build, "build", satisfier, removed_by_plan, planned_install, package);
  590. }
  591. return builder.str.chomp();
  592. }
  593. /**
  594. * Appends the refs of one dependency phase that nothing schedulable
  595. * provides. Flat phases list their refs directly; grouped phases
  596. * report every candidate group that is not viable.
  597. */
  598. private void append_blocking_refs(StringBuilder builder, DependencyPhase phase, string phase_name, PredicateDelegate<ResourceRef> satisfier, PredicateDelegate<ResourceRef> removed_by_plan, Set<CachedPackage> planned_install, CachedPackage package) {
  599. if(phase == null) {
  600. return;
  601. }
  602. if(!phase.is_grouped) {
  603. foreach(var resource in phase.ordered_required()) {
  604. if(!satisfier(resource)) {
  605. append_blocking_ref(builder, phase_name, resource, removed_by_plan, planned_install, package);
  606. }
  607. }
  608. return;
  609. }
  610. var groups = phase.ordered_groups();
  611. for(uint index = 0; index < groups.length; index++) {
  612. var blocking = groups[index].where(r => !satisfier(r)).to_vector();
  613. if(blocking.length == 0) {
  614. return;
  615. }
  616. builder.append_printf(" [%s phase, candidate group %u of %u:]\n", phase_name, index + 1, groups.length);
  617. foreach(var resource in blocking) {
  618. append_blocking_ref(builder, phase_name, resource, removed_by_plan, planned_install, package);
  619. }
  620. }
  621. }
  622. /**
  623. * Appends one blocking ref with its exact cause and, when other
  624. * transaction members provide it, their names — distinguishing
  625. * unmet dependencies from dependency cycles.
  626. */
  627. private void append_blocking_ref(StringBuilder builder, string phase_name, ResourceRef resource, PredicateDelegate<ResourceRef> removed_by_plan, Set<CachedPackage> planned_install, CachedPackage package) {
  628. var providers = new Vector<string>();
  629. foreach(var candidate in planned_install) {
  630. if(candidate == package) {
  631. continue;
  632. }
  633. try {
  634. if(candidate.get_manifest().satisfies(resource)) {
  635. providers.add(candidate.package_name);
  636. }
  637. }
  638. catch(Error e) {
  639. builder.append_printf(" %s (%s phase): provider check failed for \"%s\": %s\n", resource.to_string(), phase_name, candidate.package_name, e.message);
  640. }
  641. }
  642. if(providers.length == 0) {
  643. if(removed_by_plan(resource)) {
  644. builder.append_printf(
  645. " %s (%s phase) is going away: a package this transaction removes currently provides it, and nothing in this transaction replaces it\n",
  646. resource.to_string(), phase_name
  647. );
  648. return;
  649. }
  650. builder.append_printf(" %s (%s phase) is unmet: it is not present on this system, no planned system package provides it, and no package in this transaction provides it\n", resource.to_string(), phase_name);
  651. return;
  652. }
  653. var self_cycle = false;
  654. try {
  655. self_cycle = package.get_manifest().satisfies(resource);
  656. }
  657. catch(Error e) {
  658. }
  659. if(self_cycle) {
  660. providers.add(@"$(package.package_name) (itself)");
  661. }
  662. builder.append_printf(
  663. " %s (%s phase) is part of a dependency cycle: only %s provide%s it, and none of them can be scheduled before \"%s\"\n",
  664. resource.to_string(), phase_name, providers.to_string(p => p, ", "), providers.length == 1 ? "s" : "", package.package_name
  665. );
  666. }
  667. /**
  668. * Explains why no remaining package can be removed: a package is
  669. * stuck when another still-to-be-removed package's manage-phase
  670. * dependencies are satisfied by its provides, so each blocking
  671. * (remover, blocked-by, ref) pair is reported — a pure cycle when
  672. * every remaining package is blocked.
  673. */
  674. private string describe_removal_deadlock(Set<CachedPackageManifest> remaining) {
  675. var builder = new StringBuilder();
  676. builder.append_printf("Could not build a transaction strategy: %u package(s) could not be scheduled for removal:\n", remaining.count());
  677. foreach(var entry in remaining.sort((a, b) => a.package.package_name.collate(b.package.package_name))) {
  678. var blockers = new Vector<string>();
  679. foreach(var other in remaining) {
  680. if(other == entry) {
  681. continue;
  682. }
  683. foreach(var dependency in other.manifest.dependencies.manage.all_refs()) {
  684. if(entry.manifest.satisfies(dependency)) {
  685. blockers.add(@"\"$(other.package.package_name)\" still needs $(dependency.to_string()) (manage phase)");
  686. break;
  687. }
  688. }
  689. }
  690. if(blockers.length == 0) {
  691. builder.append_printf(" %s: no remaining package depends on it, but nothing else could be scheduled either\n", entry.package.package_name);
  692. }
  693. else {
  694. builder.append_printf(" %s: %s\n", entry.package.package_name, blockers.to_string(b => b, "; "));
  695. }
  696. }
  697. return builder.str.chomp();
  698. }
  699. /**
  700. * Plans rebuildDependants rebuilds: every package in
  701. * {@link to_install} flagged
  702. * {@link ManifestFlag.REBUILD_DEPENDANTS} names its installed
  703. * dependants via {@link SystemState.find_dependant_names}, and each
  704. * one neither installed nor removed by this transaction rebuilds
  705. * exactly once — dependants the transaction already handles are
  706. * skipped, and two flagged packages sharing a dependant rebuild it
  707. * once only. Without a {@link state} there is nothing to scan.
  708. */
  709. private void plan_rebuilds() throws TransactionError {
  710. rebuilds = new Vector<RebuildEntry>();
  711. if(state == null) {
  712. return;
  713. }
  714. try {
  715. var installed_by_name = new Dictionary<string, CachedPackage>();
  716. foreach(var installed in state.get_installed_packages()) {
  717. // Resolve through the installed symlink so a rebuild
  718. // carries the cache path: reinstalling through the
  719. // installed path itself would make mark_installed
  720. // relink the symlink onto itself
  721. var info = File.new_for_path(installed.state_path).query_info(FileAttribute.STANDARD_SYMLINK_TARGET, FileQueryInfoFlags.NOFOLLOW_SYMLINKS);
  722. var target = info.get_symlink_target();
  723. installed_by_name.set(installed.package_name, target != null ? new CachedPackage(target) : installed);
  724. }
  725. var planned = new HashSet<string>();
  726. foreach(var package in planned_install) {
  727. planned.add(package.get_manifest().name);
  728. }
  729. foreach(var package in planned_removal) {
  730. planned.add(package.get_manifest().name);
  731. }
  732. foreach(var package in planned_install) {
  733. var manifest = package.get_manifest();
  734. if(manifest.flags == null || !manifest.flags.has(ManifestFlag.REBUILD_DEPENDANTS)) {
  735. continue;
  736. }
  737. foreach(var name in state.find_dependant_names(package)) {
  738. CachedPackage dependant;
  739. if(!installed_by_name.try_get(name, out dependant)) {
  740. continue;
  741. }
  742. var dependant_name = dependant.get_manifest().name;
  743. if(planned.has(dependant_name)) {
  744. continue;
  745. }
  746. planned.add(dependant_name);
  747. rebuilds.add(new RebuildEntry() {
  748. package = dependant,
  749. trigger = package
  750. });
  751. }
  752. }
  753. }
  754. catch(Error e) {
  755. throw new TransactionError.UNKNOWN_ERROR(@"Failed to plan dependant rebuilds: $(e.message)");
  756. }
  757. }
  758. /**
  759. * Orders a transaction package set by the given manifest-name order
  760. * (from a {@link ResolutionResult}); names with no package in the set
  761. * are skipped (for example supplied cache packages filtered out by
  762. * the caller). Without an order the set is sorted by name for
  763. * determinism. Throws when an order covers none of a package's set
  764. * entries — every package must be covered.
  765. */
  766. private Vector<CachedPackage> ordered_by_names(Set<CachedPackage> packages, Vector<string>? names) throws TransactionError {
  767. if(names == null) {
  768. return packages.sort((a, b) => a.package_name.collate(b.package_name)).to_vector();
  769. }
  770. var by_name = new Dictionary<string, CachedPackage>();
  771. foreach(var package in packages) {
  772. try {
  773. by_name.set(package.get_manifest().name, package);
  774. }
  775. catch(Error e) {
  776. throw new TransactionError.UNKNOWN_ERROR(@"Failed to read manifest for package \"$(package.package_name)\": $(e.message)");
  777. }
  778. }
  779. var ordered = new Vector<CachedPackage>();
  780. var covered = new HashSet<CachedPackage>();
  781. foreach(var name in names) {
  782. CachedPackage package;
  783. if(by_name.try_get(name, out package)) {
  784. ordered.add(package);
  785. covered.add(package);
  786. }
  787. }
  788. var uncovered_names = packages.exclude(covered).to_string(p => p.package_name, ", ");
  789. if(uncovered_names.length > 0) {
  790. throw new TransactionError.INVALID_TRANSACTION(@"The supplied package order does not cover: $(uncovered_names)");
  791. }
  792. return ordered;
  793. }
  794. private delegate void PackageDelegate(CachedPackage package) throws Error;
  795. /**
  796. * Serialises progress reporting: the ninja progress reader
  797. * thread of a `ninjaStyleProgress` build emits fractions
  798. * concurrently with the main thread advancing between actions,
  799. * so the reporting fields and the {@link progress_updated}
  800. * emissions (and the display behind them) must never interleave.
  801. */
  802. private Object progress_lock = new Object();
  803. /**
  804. * Moves the action cursor under {@link progress_lock}: the next
  805. * {@link report_progress} carries this task, subject and index.
  806. */
  807. private void begin_action(TransactionTask task, string subject, uint index) {
  808. lock(progress_lock) {
  809. current_task_type = task;
  810. current_subject = subject;
  811. current_task = index;
  812. }
  813. }
  814. private void do_for(Enumerable<CachedPackage> packages, PackageDelegate func, TransactionTask task_type) throws TransactionError {
  815. foreach (var package in packages) {
  816. // A Ctrl+C that arrived between operations resolves before
  817. // this one starts: keep going (clearing the flag) or roll
  818. // the transaction back
  819. if(TransactionInterrupt.requested && !resolve_interrupt(package.package_name, task_type)) {
  820. throw new TransactionError.INTERRUPTED(@"Interrupted before $(task_type.get_verb()) $(package.package_name)");
  821. }
  822. while(true) {
  823. try {
  824. begin_action(task_type, package.package_name, current_task);
  825. report_progress(task_type, 0.0f);
  826. func(package);
  827. lock(progress_lock) {
  828. current_task++;
  829. }
  830. break;
  831. }
  832. catch (TransactionError e) {
  833. // The operation failed with a Ctrl+C pending — the
  834. // terminal signalled the child too. Ask: roll back,
  835. // or clear the flag and re-run this operation
  836. if(TransactionInterrupt.requested && resolve_interrupt(package.package_name, task_type)) {
  837. continue;
  838. }
  839. throw e;
  840. }
  841. catch(Error e) {
  842. if(TransactionInterrupt.requested && resolve_interrupt(package.package_name, task_type)) {
  843. continue;
  844. }
  845. throw new TransactionError.UNKNOWN_ERROR(@"Error $(task_type.get_verb()) package $(package.package_name): $(e.message)");
  846. }
  847. }
  848. }
  849. }
  850. /**
  851. * Resolves a pending interrupt for {@link subject} in the phase
  852. * {@link verb}: emits {@link interrupt_decided}. A "keep going"
  853. * answer clears the flag so the retried operation runs
  854. * uninterrupted; a rollback answer (or no handler — the signal's
  855. * default) leaves it set and the caller unwinds to the journaled
  856. * rollback.
  857. */
  858. private bool resolve_interrupt(string subject, TransactionTask task_type) {
  859. bool keep_going = interrupt_decided(subject, task_type.get_verb());
  860. if(keep_going) {
  861. TransactionInterrupt.requested = false;
  862. }
  863. return keep_going;
  864. }
  865. private void report_progress(TransactionTask task, float progress) {
  866. lock(progress_lock) {
  867. progress_updated(task, current_subject, current_task, task_count, progress);
  868. }
  869. }
  870. /**
  871. * Reports one progress fraction for an action whose identity was
  872. * captured when it started — used by the ninja progress reader
  873. * thread, whose emissions may lag behind the main thread
  874. * advancing the cursor; carrying the captured identity (instead
  875. * of reading the live fields) keeps a late emission from
  876. * masquerading as whatever action happens to be current.
  877. */
  878. private void report_action_progress(TransactionTask task, string subject, uint index, float progress) {
  879. lock(progress_lock) {
  880. progress_updated(task, subject, index, task_count, progress);
  881. }
  882. }
  883. public void unpack_package(CachedPackage package) throws Error {
  884. // A clean copy of the sources is all any phase needs — builds,
  885. // tests, installs and remove scripts all run from the source
  886. // directory — so removals must not require a build artifact
  887. // to exist (see {@link cleanup_package})
  888. package.clean_source();
  889. report_progress(current_task_type, 0.5f);
  890. package.get_source_directory((fraction) => {
  891. report_progress(current_task_type, 0.5f + (fraction * 0.5f));
  892. });
  893. report_progress(current_task_type, 1.0f);
  894. }
  895. /**
  896. * Builds a package through the unified build-cache path: an
  897. * existing build directory or a build archive (restored by
  898. * {@link CachedPackage.get_build_directory}) makes the build
  899. * incremental, and a failure atop either is retried once from
  900. * extracted sources — a stale cache is the likeliest culprit, and
  901. * the clean-retry error propagates when that also fails. A failure
  902. * on an already-clean build propagates directly.
  903. */
  904. private void build_package(CachedPackage package) throws Error {
  905. var reused = package.has_build_directory() || package.has_build_archive();
  906. try {
  907. attempt_build(package);
  908. }
  909. catch(Error e) {
  910. if(!reused) {
  911. throw e;
  912. }
  913. package.clean_build_directory();
  914. package.clean_source();
  915. package.get_source_directory();
  916. package.create_build_directory();
  917. attempt_build(package);
  918. }
  919. report_progress(current_task_type, 1.0f);
  920. }
  921. /**
  922. * Runs the package's build script against the unified
  923. * build-directory path: an existing build tree is reused and a
  924. * build archive is restored in place (both incremental), and only
  925. * with neither does a fresh build directory appear — a missing
  926. * cache is fine, the user may have cleaned it up. The caller owns
  927. * the clean-retry decision (see {@link build_package}).
  928. */
  929. private void attempt_build(CachedPackage package) throws Error {
  930. // Get source directory, and reuse, restore or create the build directory
  931. string build_dir;
  932. if(package.has_build_directory() || package.has_build_archive()) {
  933. build_dir = package.get_build_directory();
  934. }
  935. else {
  936. build_dir = package.create_build_directory();
  937. }
  938. var source_dir = package.get_source_directory();
  939. // Change directory to sources
  940. Environment.set_current_dir(source_dir);
  941. var manifest = new Usm.Manifest.from_file("MANIFEST.usm");
  942. // The ninja progress reader thread outlives the build process
  943. // by a moment; snapshot the action's identity so its late
  944. // emissions carry what they meant when written, never
  945. // whatever action the cursor has since moved to (see
  946. // {@link report_action_progress})
  947. TransactionTask action_task = current_task_type;
  948. string action_subject = current_subject;
  949. uint action_index = current_task;
  950. var build_proc = manifest.run_build(build_dir, paths, SubprocessFlags.STDOUT_SILENCE, (progress) => {
  951. report_action_progress(action_task, action_subject, action_index, progress);
  952. });
  953. build_proc.wait_check();
  954. }
  955. private void test_package(CachedPackage package) throws Error {
  956. var source_dir = package.get_source_directory();
  957. var build_dir = package.get_build_directory();
  958. // "cd" into the source directory and read the manifest
  959. Environment.set_current_dir(source_dir);
  960. var manifest = new Usm.Manifest.from_file("MANIFEST.usm");
  961. // Run test process if present
  962. var test_proc = manifest.run_test(build_dir, SubprocessFlags.STDOUT_SILENCE);
  963. if(test_proc != null) {
  964. test_proc.wait_check();
  965. }
  966. // A package with no test executable still transitions through
  967. // the testing task, so completion is reported either way
  968. report_progress(current_task_type, 1.0f);
  969. }
  970. private void remove_package(CachedPackage package) throws Error {
  971. // Get source and build directories
  972. var source_dir = package.get_source_directory();
  973. // "cd" into the source directory and read the manifest
  974. Environment.set_current_dir(source_dir);
  975. var manifest = new Usm.Manifest.from_file("MANIFEST.usm");
  976. // A downgrade removal keeps the state directory for the way
  977. // back; an update removal is followed by a reinstall; anything
  978. // else is final
  979. var removal_type = downgrade_currents.has(package)
  980. ? RemoveType.DOWNGRADE
  981. : upgrade_removals.has(package) ? RemoveType.UPGRADE : RemoveType.FINAL;
  982. // Journal before anything is deleted: every existing resource
  983. // file is backed up and the installed mark's target recorded,
  984. // so a later failure puts the package back exactly as it was
  985. var files_removed = manifest.report_remove_resources(paths);
  986. journal.entry_remove(package, files_removed.to_array(), state.installed_mark_path(package), state.read_installed_target(package));
  987. foreach(var path in files_removed) {
  988. journal.backup_file(path, package.package_name);
  989. }
  990. // Run remove process if present
  991. var build_proc = manifest.run_remove(removal_type, SubprocessFlags.STDOUT_SILENCE);
  992. if(build_proc != null)
  993. build_proc.wait_check();
  994. manifest.remove_resources( paths, (r, cr, tr, f) => report_progress(current_task_type, ((float)cr + (float)f) / (float)tr));
  995. state.unmark_installed(package);
  996. journal.complete(package.package_name);
  997. }
  998. private void install_package(CachedPackage package) throws Error {
  999. var source_dir = package.get_source_directory();
  1000. var build_dir = package.get_build_directory();
  1001. // "cd" into the source directory and read the manifest
  1002. Environment.set_current_dir(source_dir);
  1003. var manifest = new Usm.Manifest.from_file("MANIFEST.usm");
  1004. report_progress(current_task_type, 0.0f);
  1005. string? install_dir = null;
  1006. // Downgrade targets install over their current version rather
  1007. // than fresh
  1008. var install_type = downgrade_targets.has(package) ? InstallType.DOWNGRADE : InstallType.FRESH;
  1009. // Journal before anything changes on disk: the destination
  1010. // set comes from the manifest's provides, every file about to
  1011. // be overwritten is backed up, and the installed mark's
  1012. // previous target is recorded so the rollback restores it
  1013. Vector<string> files_created;
  1014. Vector<string> files_overwritten;
  1015. manifest.report_install_resources(paths, out files_created, out files_overwritten);
  1016. journal.entry_install(package, files_created.to_array(), files_overwritten.to_array(),
  1017. state.installed_mark_path(package), state.read_installed_target(package));
  1018. foreach(var path in files_overwritten) {
  1019. journal.backup_file(path, package.package_name);
  1020. }
  1021. // Run install process if present
  1022. if(manifest.executables.install != null) {
  1023. install_dir = package.create_install_directory();
  1024. // Only a ninjaStyleProgress install script streams
  1025. // parseable Installing lines; everything else stays
  1026. // silenced (see {@link Manifest.run_install})
  1027. ProgressDelegate? install_progress = null;
  1028. if(manifest.flags != null && manifest.flags.has(ManifestFlag.NINJA_STYLE_PROGRESS)) {
  1029. install_progress = (fraction) => {
  1030. report_progress(current_task_type, fraction);
  1031. };
  1032. }
  1033. var install_proc = manifest.run_install(build_dir, install_dir, paths, install_type, SubprocessFlags.STDOUT_SILENCE, install_progress);
  1034. install_proc.wait_check();
  1035. }
  1036. // Install the package's resources
  1037. manifest.install_resources(source_dir, build_dir, install_dir, paths, (r, cr, tr, f) => report_progress(current_task_type, ((float)cr + (float)f) / (float)tr));
  1038. // Run post install process if present
  1039. report_progress(current_task_type, 1.0f);
  1040. var post_install_proc = manifest.run_post_install(build_dir, install_type, SubprocessFlags.STDOUT_SILENCE);
  1041. if(post_install_proc != null)
  1042. post_install_proc.wait_check();
  1043. // Update the system state, and cleanup
  1044. state.mark_installed(package);
  1045. record_origin(package, manifest);
  1046. journal.complete(package.package_name);
  1047. }
  1048. /**
  1049. * Writes the package's origin record after install: a record
  1050. * already in this cache directory (a rebuild reinstalls the same
  1051. * one) keeps its provenance and its explicit mark — a rebuild
  1052. * must not demote an explicitly installed package to
  1053. * orphan-removable — while a fresh directory starts a record
  1054. * whose only known field is the mark, set from whether the user
  1055. * named the package in this transaction.
  1056. */
  1057. private void record_origin(CachedPackage package, Manifest manifest) throws Error {
  1058. OriginInformation origin;
  1059. try {
  1060. origin = package.get_origin_information();
  1061. }
  1062. catch(Error e) {
  1063. origin = new OriginInformation();
  1064. }
  1065. var named = explicit_packages != null && explicit_packages.contains(manifest.name);
  1066. origin.explicitly_installed = named || origin.explicitly_installed;
  1067. package.update_origin_information(origin);
  1068. }
  1069. private void cleanup_package(CachedPackage package) throws Error {
  1070. // A package with no build artifact (for example after `usm
  1071. // clean builds`) has nothing to archive; its cache stays as-is
  1072. if(package.has_build_directory() || package.has_build_archive()) {
  1073. package.archive_build((compression) => {
  1074. report_progress(TransactionTask.CLEANING_UP, compression * 0.25f);
  1075. });
  1076. }
  1077. report_progress(TransactionTask.CLEANING_UP, 0.25f);
  1078. package.clean_source();
  1079. report_progress(TransactionTask.CLEANING_UP, 0.5f);
  1080. package.clean_install();
  1081. report_progress(TransactionTask.CLEANING_UP, 0.75f);
  1082. report_progress(TransactionTask.CLEANING_UP, 1.0f);
  1083. }
  1084. }
  1085. /**
  1086. * One planned rebuildDependants rebuild: {@link package} is the
  1087. * installed {@link CachedPackage} — same version, same state path —
  1088. * that rebuilds through the standard pipeline tagged
  1089. * {@link TransactionTask.REBUILDING}, and {@link trigger} is the
  1090. * flagged package whose install/update pulled it in.
  1091. */
  1092. public class RebuildEntry {
  1093. public CachedPackage package { get; set; }
  1094. public CachedPackage trigger { get; set; }
  1095. }
  1096. /**
  1097. * One planned version downgrade: {@link current} is the installed
  1098. * {@link CachedPackage} whose resources are removed — its state
  1099. * directory is kept, enabling the downgrade back — and {@link target}
  1100. * is the older version installed in its place.
  1101. */
  1102. public class DowngradeEntry {
  1103. public CachedPackage target { get; set; }
  1104. public CachedPackage current { get; set; }
  1105. }
  1106. public enum TransactionTask {
  1107. STRATEGISING,
  1108. UNPACKING,
  1109. BUILDING,
  1110. TESTING,
  1111. REMOVING,
  1112. INSTALLING,
  1113. CLEANING_UP,
  1114. /** Installing one chosen system (native) package; runs ahead of the first USM lot. */
  1115. INSTALLING_SYSTEM,
  1116. /** The rebuildDependants pipeline for an already-installed package (see {@link RebuildEntry}). */
  1117. REBUILDING;
  1118. public string get_verb() {
  1119. switch (this) {
  1120. case STRATEGISING:
  1121. return "preparing a strategy for";
  1122. case UNPACKING:
  1123. return "unpacking";
  1124. case BUILDING:
  1125. return "building";
  1126. case TESTING:
  1127. return "testing";
  1128. case REMOVING:
  1129. return "removing";
  1130. case INSTALLING:
  1131. return "installing";
  1132. case INSTALLING_SYSTEM:
  1133. return "installing system package";
  1134. case CLEANING_UP:
  1135. return "cleaning up";
  1136. case REBUILDING:
  1137. return "rebuilding";
  1138. default:
  1139. assert_not_reached();
  1140. }
  1141. }
  1142. }
  1143. public errordomain TransactionError {
  1144. INVALID_TRANSACTION,
  1145. UNKNOWN_ERROR,
  1146. BUILD_ERROR,
  1147. INSTALL_ERROR,
  1148. /** A Ctrl+C the interrupt decision answered with "roll back". */
  1149. INTERRUPTED
  1150. }
  1151. }