123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- using Invercargill;
- namespace Usm {
- public class Transaction {
- public Paths paths { get; set; }
- public SystemState state { get; set; }
- public Set<CachedPackage> to_install { get; set; }
- public Set<CachedPackage> to_remove { get; set; }
- public signal void progress_updated(TransactionTask task_type, string subject, int current_task, int total_tasks, float task_progress);
- private int task_count = 0;
- private int current_task = 0;
- private string current_subject = "transaction";
- private Vector<Vector<CachedPackage>> install_lots;
- private Vector<CachedPackage> remove_order;
- public void run() throws TransactionError {
- // 1. Verify the transaction is valid
- strategise();
- var all_packages = to_remove.concat(to_install);
- // 2. Unpack packages
- do_for(all_packages, unpack_package, TransactionTask.UNPACKING);
- // 3. Remove packages
- do_for(remove_order, remove_package, TransactionTask.REMOVING);
- foreach (var lot in install_lots) {
- // 3. Build packages
- do_for(lot, build_package, TransactionTask.BUILDING);
-
- // 5. Install packages
- do_for(lot, install_package, TransactionTask.INSTALLING);
- }
- // 6. Clean up
- do_for(all_packages, cleanup_package, TransactionTask.CLEANING_UP);
- }
- private string previous_key = "";
- public void print_progress(TransactionTask task_type, string subject, int current_task, int total_tasks, float task_progress) {
- var verb = task_type.get_verb();
- verb = verb[0].toupper().to_string() + verb.substring(1);
- var percent = (int)(task_progress * 100.0f);
- var key = @"$(task_type.get_verb())_$(subject)_$current_task";
- var prefix = previous_key == key ? "\x1b[1F\x1b[2K" : "";
- previous_key = key;
- printerr(@"$prefix[$(current_task+1)/$total_tasks] $verb $subject ($percent%)\n");
- }
- public void print_progress_simple(TransactionTask task_type, string subject, int current_task, int total_tasks, float task_progress) {
- var verb = task_type.get_verb();
- verb = verb[0].toupper().to_string() + verb.substring(1);
- var percent = (int)(task_progress * 100.0f);
- printerr(@"[$(current_task+1)/$total_tasks] $verb $subject ($percent%)\n");
- }
- public void strategise() throws TransactionError {
- task_count = (to_install.count() * 4) + (to_remove.count() * 3) + 1;
- var strategise_worst_case_task_count = (to_remove.count() * to_remove.count()) + (to_install.count() * to_install.count());
- var strategise_current_task = 0;
- report_progress(TransactionTask.STRATEGISING, 0.0f);
-
- // Installation strategy
- install_lots = new Vector<Vector<CachedPackage>>();
- var touched = new Set<CachedPackage>();
- var available_resources = new Set<ResourceRef>();
- var round = 0;
- while(true) {
- strategise_current_task = round * to_install.count();
- var lot = new Vector<CachedPackage>();
- var remaining = to_install.difference(touched);
- if(remaining.count() == 0) {
- break;
- }
- foreach (var package in to_install.difference(touched)) {
- report_progress(TransactionTask.STRATEGISING, (float)strategise_current_task / (float)strategise_worst_case_task_count);
- try {
- var manifest = package.get_manifest();
- var installtime_dependencies = manifest.dependencies.manage.concat(manifest.dependencies.build);
- if(installtime_dependencies.all(d => d.is_satisfied() || available_resources.any(r => d.satisfied_by(r)))) {
- lot.add(package);
- touched.add(package);
- available_resources.add_all(manifest.provides.select<ResourceRef>(p => p.key));
- }
- strategise_current_task++;
- }
- catch(Error e) {
- throw new TransactionError.UNKNOWN_ERROR(@"Failed to read manifest for package \"$(package.package_name)\": $(e.message)");
- }
- }
- if(lot.count() == 0) {
- var packages = to_install.difference(touched).to_string(p => p.package_name, ", ");
- throw new TransactionError.INVALID_TRANSACTION(@"Could not build a transaction strategy, packages $(packages) have unmet or cyclical dependencies");
- }
- install_lots.add(lot);
- round++;
- }
- var current_task_baseline = (to_install.count() * to_install.count());
- strategise_current_task = current_task_baseline;
- report_progress(TransactionTask.STRATEGISING, (float)strategise_current_task / (float)strategise_worst_case_task_count);
- // Removal strategy
- remove_order = new Vector<CachedPackage>();
- touched = new Set<CachedPackage>();
- Set<CachedPackageManifest> remaining_to_remove;
- try {
- remaining_to_remove = to_remove
- .attempt_select<CachedPackageManifest>(p => new CachedPackageManifest(p))
- .to_set();
- }
- catch(Error e) {
- throw new TransactionError.UNKNOWN_ERROR(@"Failed to read manifest: $(e.message)");
- }
- round = 0;
- while(true) {
- strategise_current_task = current_task_baseline + (round * to_remove.count());
- if(remaining_to_remove.count() == 0) {
- break;
- }
- foreach (var package in remaining_to_remove) {
- report_progress(TransactionTask.STRATEGISING, (float)strategise_current_task / (float)strategise_worst_case_task_count);
- if(remaining_to_remove.no(p => p.manifest.dependencies.manage.any(d => package.manifest.provides.any(r => d.satisfied_by(r.key))))) {
- remove_order.add(package.package);
- touched.add(package.package);
- remaining_to_remove.remove(package);
- strategise_current_task++;
- round++;
- break;
- }
- strategise_current_task++;
- }
- var packages = remaining_to_remove.to_string(p => p.package.package_name, ", ");
- throw new TransactionError.INVALID_TRANSACTION(@"Could not build a transaction strategy, packages $(packages) have unmet or cyclical dependencies");
- }
- report_progress(TransactionTask.STRATEGISING, 1.0f);
- current_task++;
- }
- private delegate void PackageDelegate(CachedPackage package) throws Error;
- private void do_for(Enumerable<CachedPackage> packages, PackageDelegate func, TransactionTask task_type) throws TransactionError {
- foreach (var package in packages) {
- try {
- current_subject = package.package_name;
- report_progress(task_type, 0.0f);
- func(package);
- current_task++;
- }
- catch (TransactionError e) {
- throw e;
- }
- catch(Error e) {
- throw new TransactionError.UNKNOWN_ERROR(@"Error $(task_type.get_verb()) package $(package.package_name): $(e.message)");
- }
- }
- }
- private void report_progress(TransactionTask task, float progress) {
- progress_updated(task, current_subject, current_task, task_count, progress);
- }
- public void unpack_package(CachedPackage package) throws Error {
- var will_remove = to_remove.has(package);
- // Get a clean copy of the sources
- package.clean_source();
- report_progress(TransactionTask.UNPACKING, will_remove ? 0.25f : 0.5f);
- package.get_source_directory();
- report_progress(TransactionTask.UNPACKING, will_remove ? 0.5f : 1.0f);
- if(will_remove) {
- // Get a clean copy of the build artifact
- package.clean_build_directory();
- report_progress(TransactionTask.UNPACKING, 0.75f);
- package.get_build_directory();
- report_progress(TransactionTask.UNPACKING, 1.0f);
- }
- }
- private void build_package(CachedPackage package) throws Error {
- // Get source directory, and create build directory
- var source_dir = package.get_source_directory();
- var build_dir = package.create_build_directory();
-
- // Change directory to sources
- Environment.set_current_dir(source_dir);
- var manifest = new Usm.Manifest.from_file("MANIFEST.usm");
- // Build package
- var build_proc = manifest.run_build(build_dir, paths, SubprocessFlags.STDOUT_SILENCE);
- build_proc.wait_check();
- report_progress(TransactionTask.BUILDING, 1.0f);
- }
- private void remove_package(CachedPackage package) throws Error {
- // Get source and build directories
- var source_dir = package.get_source_directory();
- // "cd" into the source directory and read the manifest
- Environment.set_current_dir(source_dir);
- var manifest = new Usm.Manifest.from_file("MANIFEST.usm");
- // Run remove process if present
- var build_proc = manifest.run_remove(RemoveType.FINAL, SubprocessFlags.STDOUT_SILENCE);
- if(build_proc != null)
- build_proc.wait_check();
- manifest.remove_resources( paths, (r, cr, tr, f) => report_progress(TransactionTask.INSTALLING, ((float)cr + (float)f) / (float)tr));
- state.unmark_installed(package);
- }
- private void install_package(CachedPackage package) throws Error {
- var source_dir = package.get_source_directory();
- var build_dir = package.get_build_directory();
- // "cd" into the source directory and read the manifest
- Environment.set_current_dir(source_dir);
- var manifest = new Usm.Manifest.from_file("MANIFEST.usm");
- report_progress(TransactionTask.INSTALLING, 0.0f);
- string? install_dir = null;
- // Run install process if present
- if(manifest.executables.install != null) {
- install_dir = package.create_install_directory();
- var install_proc = manifest.run_install(build_dir, install_dir, paths, InstallType.FRESH, SubprocessFlags.STDOUT_SILENCE);
- install_proc.wait_check();
- }
- // Install the package's resources
- manifest.install_resources(source_dir, build_dir, install_dir, paths, (r, cr, tr, f) => report_progress(TransactionTask.INSTALLING, ((float)cr + (float)f) / (float)tr));
- // Run post install process if present
- report_progress(TransactionTask.INSTALLING, 1.0f);
- var post_install_proc = manifest.run_post_install(build_dir, InstallType.FRESH, SubprocessFlags.STDOUT_SILENCE);
- if(post_install_proc != null)
- post_install_proc.wait_check();
- // Update the system state, and cleanup
- state.mark_installed(package);
- }
- private void cleanup_package(CachedPackage package) throws Error {
- package.archive_build();
- report_progress(TransactionTask.CLEANING_UP, 0.33334f);
- package.clean_source();
- report_progress(TransactionTask.CLEANING_UP, 0.66667f);
- package.clean_install();
- report_progress(TransactionTask.CLEANING_UP, 1.0f);
- }
- }
- public enum TransactionTask {
- STRATEGISING,
- UNPACKING,
- BUILDING,
- REMOVING,
- INSTALLING,
- CLEANING_UP;
- public string get_verb() {
- switch (this) {
- case STRATEGISING:
- return "preparing a strategy for";
- case UNPACKING:
- return "unpacking";
- case BUILDING:
- return "building";
- case REMOVING:
- return "removing";
- case INSTALLING:
- return "installing";
- case CLEANING_UP:
- return "cleaning up";
- default:
- assert_not_reached();
- }
- }
- }
- public errordomain TransactionError {
- INVALID_TRANSACTION,
- UNKNOWN_ERROR,
- BUILD_ERROR,
- INSTALL_ERROR
- }
- }
|