|
@@ -8,8 +8,8 @@ namespace Usm {
|
|
|
public Paths paths { get; set; }
|
|
public Paths paths { get; set; }
|
|
|
public ResourceFinder resource_finder { get; set; }
|
|
public ResourceFinder resource_finder { get; set; }
|
|
|
public SystemState state { get; set; }
|
|
public SystemState state { get; set; }
|
|
|
- public Set<CachedPackage> to_install { get; set; }
|
|
|
|
|
- public Set<CachedPackage> to_remove { get; set; }
|
|
|
|
|
|
|
+ public Set<CachedPackage> to_install { get; set; default = new HashSet<CachedPackage>(); }
|
|
|
|
|
+ public Set<CachedPackage> to_remove { get; set; default = new HashSet<CachedPackage>(); }
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Optional install order (package names) taken from a
|
|
* Optional install order (package names) taken from a
|
|
@@ -25,6 +25,14 @@ namespace Usm {
|
|
|
*/
|
|
*/
|
|
|
public Vector<string>? remove_order { get; set; }
|
|
public Vector<string>? remove_order { get; set; }
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Version downgrades executed by this transaction: each entry
|
|
|
|
|
+ * removes {@link DowngradeEntry.current}'s installed resources —
|
|
|
|
|
+ * keeping its state directory for a later downgrade back — and
|
|
|
|
|
+ * installs {@link DowngradeEntry.target} in its place.
|
|
|
|
|
+ */
|
|
|
|
|
+ public Vector<DowngradeEntry> to_downgrade { get; set; default = new Vector<DowngradeEntry>(); }
|
|
|
|
|
+
|
|
|
public signal void progress_updated(TransactionTask task_type, string subject, uint current_task, uint total_tasks, float task_progress);
|
|
public signal void progress_updated(TransactionTask task_type, string subject, uint current_task, uint total_tasks, float task_progress);
|
|
|
|
|
|
|
|
private uint task_count = 0;
|
|
private uint task_count = 0;
|
|
@@ -32,6 +40,19 @@ namespace Usm {
|
|
|
private string current_subject = "transaction";
|
|
private string current_subject = "transaction";
|
|
|
private TransactionTask current_task_type = TransactionTask.STRATEGISING;
|
|
private TransactionTask current_task_type = TransactionTask.STRATEGISING;
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Working sets computed by {@link strategise}: {@link to_install}
|
|
|
|
|
+ * plus downgrade targets, and {@link to_remove} plus downgrade
|
|
|
|
|
+ * currents — the whole transaction runs against these.
|
|
|
|
|
+ */
|
|
|
|
|
+ private Set<CachedPackage> planned_install = new HashSet<CachedPackage>();
|
|
|
|
|
+ private Set<CachedPackage> planned_removal = new HashSet<CachedPackage>();
|
|
|
|
|
+ /** Downgrade targets (installed with {@link InstallType.DOWNGRADE}) and currents (removed with {@link RemoveType.DOWNGRADE}). */
|
|
|
|
|
+ private Set<CachedPackage> downgrade_targets = new HashSet<CachedPackage>();
|
|
|
|
|
+ private Set<CachedPackage> downgrade_currents = new HashSet<CachedPackage>();
|
|
|
|
|
+ /** Removals whose package name this transaction reinstalls — update removals ({@link RemoveType.UPGRADE}). */
|
|
|
|
|
+ private Set<CachedPackage> upgrade_removals = new HashSet<CachedPackage>();
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Lots computed by {@link strategise}: each lot builds, tests and
|
|
* Lots computed by {@link strategise}: each lot builds, tests and
|
|
|
* installs together, and lots run in dependency order. A package
|
|
* installs together, and lots run in dependency order. A package
|
|
@@ -60,7 +81,7 @@ namespace Usm {
|
|
|
// 1. Verify the transaction is valid
|
|
// 1. Verify the transaction is valid
|
|
|
strategise();
|
|
strategise();
|
|
|
|
|
|
|
|
- var all_packages = to_remove.concat(to_install);
|
|
|
|
|
|
|
+ var all_packages = planned_removal.concat(planned_install);
|
|
|
var rebuild_packages = rebuilds.select<CachedPackage>(r => r.package).to_vector();
|
|
var rebuild_packages = rebuilds.select<CachedPackage>(r => r.package).to_vector();
|
|
|
|
|
|
|
|
// 2. Unpack packages
|
|
// 2. Unpack packages
|
|
@@ -73,10 +94,10 @@ namespace Usm {
|
|
|
foreach (var lot in install_lots) {
|
|
foreach (var lot in install_lots) {
|
|
|
// 3. Build packages
|
|
// 3. Build packages
|
|
|
do_for(lot, build_package, TransactionTask.BUILDING);
|
|
do_for(lot, build_package, TransactionTask.BUILDING);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 4. Test packages
|
|
// 4. Test packages
|
|
|
do_for(lot, test_package, TransactionTask.TESTING);
|
|
do_for(lot, test_package, TransactionTask.TESTING);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 5. Install packages
|
|
// 5. Install packages
|
|
|
do_for(lot, install_package, TransactionTask.INSTALLING);
|
|
do_for(lot, install_package, TransactionTask.INSTALLING);
|
|
|
}
|
|
}
|
|
@@ -93,6 +114,30 @@ namespace Usm {
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Rebuilds one already-installed package through the standard
|
|
|
|
|
+ * pipeline — build-cache restore, clean-retry on failure,
|
|
|
|
|
+ * reinstall of its resources — tagged
|
|
|
|
|
+ * {@link TransactionTask.REBUILDING} throughout; the `usm
|
|
|
|
|
+ * rebuild` entry point. No confirmation is needed: nothing
|
|
|
|
|
+ * installed changes version.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void rebuild_package(CachedPackage package) throws TransactionError {
|
|
|
|
|
+ var packages = new Vector<CachedPackage>();
|
|
|
|
|
+ packages.add(package);
|
|
|
|
|
+
|
|
|
|
|
+ task_count = 5;
|
|
|
|
|
+ current_task = 0;
|
|
|
|
|
+ current_subject = package.package_name;
|
|
|
|
|
+ current_task_type = TransactionTask.REBUILDING;
|
|
|
|
|
+
|
|
|
|
|
+ do_for(packages, unpack_package, TransactionTask.REBUILDING);
|
|
|
|
|
+ do_for(packages, build_package, TransactionTask.REBUILDING);
|
|
|
|
|
+ do_for(packages, test_package, TransactionTask.REBUILDING);
|
|
|
|
|
+ do_for(packages, install_package, TransactionTask.REBUILDING);
|
|
|
|
|
+ do_for(packages, cleanup_package, TransactionTask.CLEANING_UP);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private string previous_key = "";
|
|
private string previous_key = "";
|
|
|
public void print_progress(TransactionTask task_type, string subject, uint current_task, uint total_tasks, float task_progress) {
|
|
public void print_progress(TransactionTask task_type, string subject, uint current_task, uint total_tasks, float task_progress) {
|
|
|
var verb = task_type.get_verb();
|
|
var verb = task_type.get_verb();
|
|
@@ -114,8 +159,39 @@ namespace Usm {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public void strategise() throws TransactionError {
|
|
public void strategise() throws TransactionError {
|
|
|
- task_count = (to_install.count() * 5) + (to_remove.count() * 3) + 1;
|
|
|
|
|
- uint strategise_worst_case_task_count = (to_remove.count() * to_remove.count()) + (to_install.count() * to_install.count());
|
|
|
|
|
|
|
+ planned_install = new HashSet<CachedPackage>();
|
|
|
|
|
+ planned_install.union_with(to_install);
|
|
|
|
|
+ planned_removal = new HashSet<CachedPackage>();
|
|
|
|
|
+ planned_removal.union_with(to_remove);
|
|
|
|
|
+ downgrade_targets = new HashSet<CachedPackage>();
|
|
|
|
|
+ downgrade_currents = new HashSet<CachedPackage>();
|
|
|
|
|
+ foreach(var entry in to_downgrade) {
|
|
|
|
|
+ planned_install.add(entry.target);
|
|
|
|
|
+ downgrade_targets.add(entry.target);
|
|
|
|
|
+ planned_removal.add(entry.current);
|
|
|
|
|
+ downgrade_currents.add(entry.current);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // A removal whose package name this transaction reinstalls is
|
|
|
|
|
+ // an update removal, not a final one
|
|
|
|
|
+ upgrade_removals = new HashSet<CachedPackage>();
|
|
|
|
|
+ try {
|
|
|
|
|
+ var install_names = new HashSet<string>();
|
|
|
|
|
+ foreach(var package in planned_install) {
|
|
|
|
|
+ install_names.add(package.get_manifest().name);
|
|
|
|
|
+ }
|
|
|
|
|
+ foreach(var package in planned_removal) {
|
|
|
|
|
+ if(install_names.contains(package.get_manifest().name)) {
|
|
|
|
|
+ upgrade_removals.add(package);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ catch(Error e) {
|
|
|
|
|
+ throw new TransactionError.UNKNOWN_ERROR(@"Failed to read manifest while planning removals: $(e.message)");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ task_count = (planned_install.count() * 5) + (planned_removal.count() * 3) + 1;
|
|
|
|
|
+ uint strategise_worst_case_task_count = (planned_removal.count() * planned_removal.count()) + (planned_install.count() * planned_install.count());
|
|
|
uint strategise_current_task = 0;
|
|
uint strategise_current_task = 0;
|
|
|
|
|
|
|
|
report_progress(TransactionTask.STRATEGISING, 0.0f);
|
|
report_progress(TransactionTask.STRATEGISING, 0.0f);
|
|
@@ -124,10 +200,10 @@ namespace Usm {
|
|
|
install_lots = new Vector<Vector<CachedPackage>>();
|
|
install_lots = new Vector<Vector<CachedPackage>>();
|
|
|
var touched = new HashSet<CachedPackage>();
|
|
var touched = new HashSet<CachedPackage>();
|
|
|
var installed_by_earlier_lots = new HashSet<ResourceRef>();
|
|
var installed_by_earlier_lots = new HashSet<ResourceRef>();
|
|
|
- var ordered_install = ordered_by_names(to_install, install_order);
|
|
|
|
|
|
|
+ var ordered_install = ordered_by_names(planned_install, install_order);
|
|
|
var round = 0;
|
|
var round = 0;
|
|
|
while(true) {
|
|
while(true) {
|
|
|
- strategise_current_task = round * to_install.count();
|
|
|
|
|
|
|
+ strategise_current_task = round * planned_install.count();
|
|
|
var lot = new Vector<CachedPackage>();
|
|
var lot = new Vector<CachedPackage>();
|
|
|
var installed_by_this_lot = new HashSet<ResourceRef>();
|
|
var installed_by_this_lot = new HashSet<ResourceRef>();
|
|
|
var remaining = ordered_install.exclude(touched);
|
|
var remaining = ordered_install.exclude(touched);
|
|
@@ -174,19 +250,19 @@ namespace Usm {
|
|
|
install_lots.add(lot);
|
|
install_lots.add(lot);
|
|
|
round++;
|
|
round++;
|
|
|
}
|
|
}
|
|
|
- var current_task_baseline = (to_install.count() * to_install.count());
|
|
|
|
|
|
|
+ var current_task_baseline = (planned_install.count() * planned_install.count());
|
|
|
strategise_current_task = current_task_baseline;
|
|
strategise_current_task = current_task_baseline;
|
|
|
report_progress(TransactionTask.STRATEGISING, (float)strategise_current_task / (float)strategise_worst_case_task_count);
|
|
report_progress(TransactionTask.STRATEGISING, (float)strategise_current_task / (float)strategise_worst_case_task_count);
|
|
|
|
|
|
|
|
// Removal strategy
|
|
// Removal strategy
|
|
|
removal_order = new Vector<CachedPackage>();
|
|
removal_order = new Vector<CachedPackage>();
|
|
|
if(remove_order != null) {
|
|
if(remove_order != null) {
|
|
|
- removal_order = ordered_by_names(to_remove, remove_order);
|
|
|
|
|
|
|
+ removal_order = ordered_by_names(planned_removal, remove_order);
|
|
|
}
|
|
}
|
|
|
else {
|
|
else {
|
|
|
Set<CachedPackageManifest> remaining_to_remove;
|
|
Set<CachedPackageManifest> remaining_to_remove;
|
|
|
try {
|
|
try {
|
|
|
- remaining_to_remove = to_remove
|
|
|
|
|
|
|
+ remaining_to_remove = planned_removal
|
|
|
.attempt_select<CachedPackageManifest>(p => new CachedPackageManifest(p))
|
|
.attempt_select<CachedPackageManifest>(p => new CachedPackageManifest(p))
|
|
|
.to_set();
|
|
.to_set();
|
|
|
|
|
|
|
@@ -197,7 +273,7 @@ namespace Usm {
|
|
|
|
|
|
|
|
round = 0;
|
|
round = 0;
|
|
|
while(true) {
|
|
while(true) {
|
|
|
- strategise_current_task = current_task_baseline + (round * to_remove.count());
|
|
|
|
|
|
|
+ strategise_current_task = current_task_baseline + (round * planned_removal.count());
|
|
|
if(remaining_to_remove.count() == 0) {
|
|
if(remaining_to_remove.count() == 0) {
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
@@ -259,14 +335,14 @@ namespace Usm {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var planned = new HashSet<string>();
|
|
var planned = new HashSet<string>();
|
|
|
- foreach(var package in to_install) {
|
|
|
|
|
|
|
+ foreach(var package in planned_install) {
|
|
|
planned.add(package.get_manifest().name);
|
|
planned.add(package.get_manifest().name);
|
|
|
}
|
|
}
|
|
|
- foreach(var package in to_remove) {
|
|
|
|
|
|
|
+ foreach(var package in planned_removal) {
|
|
|
planned.add(package.get_manifest().name);
|
|
planned.add(package.get_manifest().name);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- foreach(var package in to_install) {
|
|
|
|
|
|
|
+ foreach(var package in planned_install) {
|
|
|
var manifest = package.get_manifest();
|
|
var manifest = package.get_manifest();
|
|
|
if(manifest.flags == null || !manifest.flags.has(ManifestFlag.REBUILD_DEPENDANTS)) {
|
|
if(manifest.flags == null || !manifest.flags.has(ManifestFlag.REBUILD_DEPENDANTS)) {
|
|
|
continue;
|
|
continue;
|
|
@@ -357,21 +433,14 @@ namespace Usm {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public void unpack_package(CachedPackage package) throws Error {
|
|
public void unpack_package(CachedPackage package) throws Error {
|
|
|
- var will_remove = to_remove.has(package);
|
|
|
|
|
-
|
|
|
|
|
- // Get a clean copy of the sources
|
|
|
|
|
|
|
+ // A clean copy of the sources is all any phase needs — builds,
|
|
|
|
|
+ // tests, installs and remove scripts all run from the source
|
|
|
|
|
+ // directory — so removals must not require a build artifact
|
|
|
|
|
+ // to exist (see {@link cleanup_package})
|
|
|
package.clean_source();
|
|
package.clean_source();
|
|
|
- report_progress(current_task_type, will_remove ? 0.25f : 0.5f);
|
|
|
|
|
|
|
+ report_progress(current_task_type, 0.5f);
|
|
|
package.get_source_directory();
|
|
package.get_source_directory();
|
|
|
- report_progress(current_task_type, will_remove ? 0.5f : 1.0f);
|
|
|
|
|
-
|
|
|
|
|
- if(will_remove) {
|
|
|
|
|
- // Get a clean copy of the build artifact
|
|
|
|
|
- package.clean_build_directory();
|
|
|
|
|
- report_progress(current_task_type, 0.75f);
|
|
|
|
|
- package.get_build_directory();
|
|
|
|
|
- report_progress(current_task_type, 1.0f);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ report_progress(current_task_type, 1.0f);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -453,8 +522,15 @@ namespace Usm {
|
|
|
Environment.set_current_dir(source_dir);
|
|
Environment.set_current_dir(source_dir);
|
|
|
var manifest = new Usm.Manifest.from_file("MANIFEST.usm");
|
|
var manifest = new Usm.Manifest.from_file("MANIFEST.usm");
|
|
|
|
|
|
|
|
|
|
+ // A downgrade removal keeps the state directory for the way
|
|
|
|
|
+ // back; an update removal is followed by a reinstall; anything
|
|
|
|
|
+ // else is final
|
|
|
|
|
+ var removal_type = downgrade_currents.has(package)
|
|
|
|
|
+ ? RemoveType.DOWNGRADE
|
|
|
|
|
+ : upgrade_removals.has(package) ? RemoveType.UPGRADE : RemoveType.FINAL;
|
|
|
|
|
+
|
|
|
// Run remove process if present
|
|
// Run remove process if present
|
|
|
- var build_proc = manifest.run_remove(RemoveType.FINAL, SubprocessFlags.STDOUT_SILENCE);
|
|
|
|
|
|
|
+ var build_proc = manifest.run_remove(removal_type, SubprocessFlags.STDOUT_SILENCE);
|
|
|
if(build_proc != null)
|
|
if(build_proc != null)
|
|
|
build_proc.wait_check();
|
|
build_proc.wait_check();
|
|
|
|
|
|
|
@@ -473,10 +549,14 @@ namespace Usm {
|
|
|
report_progress(current_task_type, 0.0f);
|
|
report_progress(current_task_type, 0.0f);
|
|
|
|
|
|
|
|
string? install_dir = null;
|
|
string? install_dir = null;
|
|
|
|
|
+ // Downgrade targets install over their current version rather
|
|
|
|
|
+ // than fresh
|
|
|
|
|
+ var install_type = downgrade_targets.has(package) ? InstallType.DOWNGRADE : InstallType.FRESH;
|
|
|
|
|
+
|
|
|
// Run install process if present
|
|
// Run install process if present
|
|
|
if(manifest.executables.install != null) {
|
|
if(manifest.executables.install != null) {
|
|
|
install_dir = package.create_install_directory();
|
|
install_dir = package.create_install_directory();
|
|
|
- var install_proc = manifest.run_install(build_dir, install_dir, paths, InstallType.FRESH, SubprocessFlags.STDOUT_SILENCE);
|
|
|
|
|
|
|
+ var install_proc = manifest.run_install(build_dir, install_dir, paths, install_type, SubprocessFlags.STDOUT_SILENCE);
|
|
|
install_proc.wait_check();
|
|
install_proc.wait_check();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -485,7 +565,7 @@ namespace Usm {
|
|
|
|
|
|
|
|
// Run post install process if present
|
|
// Run post install process if present
|
|
|
report_progress(current_task_type, 1.0f);
|
|
report_progress(current_task_type, 1.0f);
|
|
|
- var post_install_proc = manifest.run_post_install(build_dir, InstallType.FRESH, SubprocessFlags.STDOUT_SILENCE);
|
|
|
|
|
|
|
+ var post_install_proc = manifest.run_post_install(build_dir, install_type, SubprocessFlags.STDOUT_SILENCE);
|
|
|
if(post_install_proc != null)
|
|
if(post_install_proc != null)
|
|
|
post_install_proc.wait_check();
|
|
post_install_proc.wait_check();
|
|
|
|
|
|
|
@@ -494,7 +574,11 @@ namespace Usm {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void cleanup_package(CachedPackage package) throws Error {
|
|
private void cleanup_package(CachedPackage package) throws Error {
|
|
|
- package.archive_build();
|
|
|
|
|
|
|
+ // A package with no build artifact (for example after `usm
|
|
|
|
|
+ // clean builds`) has nothing to archive; its cache stays as-is
|
|
|
|
|
+ if(package.has_build_directory() || package.has_build_archive()) {
|
|
|
|
|
+ package.archive_build();
|
|
|
|
|
+ }
|
|
|
report_progress(TransactionTask.CLEANING_UP, 0.33334f);
|
|
report_progress(TransactionTask.CLEANING_UP, 0.33334f);
|
|
|
package.clean_source();
|
|
package.clean_source();
|
|
|
report_progress(TransactionTask.CLEANING_UP, 0.66667f);
|
|
report_progress(TransactionTask.CLEANING_UP, 0.66667f);
|
|
@@ -515,6 +599,17 @@ namespace Usm {
|
|
|
public CachedPackage trigger { get; set; }
|
|
public CachedPackage trigger { get; set; }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * One planned version downgrade: {@link current} is the installed
|
|
|
|
|
+ * {@link CachedPackage} whose resources are removed — its state
|
|
|
|
|
+ * directory is kept, enabling the downgrade back — and {@link target}
|
|
|
|
|
+ * is the older version installed in its place.
|
|
|
|
|
+ */
|
|
|
|
|
+ public class DowngradeEntry {
|
|
|
|
|
+ public CachedPackage target { get; set; }
|
|
|
|
|
+ public CachedPackage current { get; set; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public enum TransactionTask {
|
|
public enum TransactionTask {
|
|
|
STRATEGISING,
|
|
STRATEGISING,
|
|
|
UNPACKING,
|
|
UNPACKING,
|