diff --git a/lib/autoproj/cli/main.rb b/lib/autoproj/cli/main.rb index 17da3dda..a7a00b71 100644 --- a/lib/autoproj/cli/main.rb +++ b/lib/autoproj/cli/main.rb @@ -229,6 +229,8 @@ def status(*packages) desc: "compare to the given baseline. if 'true', the comparison will ignore any override, otherwise it will take into account overrides only up to the given package set" option :auto_exclude, type: :boolean, desc: 'if true, packages that fail to import will be excluded from the build' + option :ask, type: :boolean, default: false, + desc: 'ask whether each package should or should not be updated' def update(*packages) report_options = Hash[silent: false, on_package_failures: default_report_on_package_failures] if options[:auto_exclude] diff --git a/lib/autoproj/cli/status.rb b/lib/autoproj/cli/status.rb index 33da46d2..85a3fe64 100644 --- a/lib/autoproj/cli/status.rb +++ b/lib/autoproj/cli/status.rb @@ -73,7 +73,7 @@ def snapshot_overrides_vcs?(importer, vcs, snapshot) end end - def report_exception(package_status, msg, e) + def self.report_exception(package_status, msg, e) package_status.msg << Autoproj.color(" #{msg} (#{e})", :red) if Autobuild.debug package_status.msg.concat(e.backtrace.map do |line| @@ -82,8 +82,8 @@ def report_exception(package_status, msg, e) end end - PackageStatus = Struct.new :msg, :sync, :uncommitted, :local, :remote - def status_of_package(package_description, only_local: false, snapshot: false) + PackageStatus = Struct.new :msg, :sync, :unexpected, :uncommitted, :local, :remote + def self.status_of_package(package_description, only_local: false, snapshot: false) pkg = package_description.autobuild importer = pkg.importer package_status = PackageStatus.new(Array.new, false, false, false, false) @@ -96,7 +96,7 @@ def status_of_package(package_description, only_local: false, snapshot: false) else begin status = importer.status(pkg, only_local: only_local) rescue StandardError => e - report_exception(package_status, "failed to fetch status information", e) + self.report_exception(package_status, "failed to fetch status information", e) return package_status end @@ -108,7 +108,7 @@ def status_of_package(package_description, only_local: false, snapshot: false) rescue Autobuild::PackageException Hash.new rescue StandardError => e - report_exception(package_status, "failed to fetch snapshotting information", e) + self.report_exception(package_status, "failed to fetch snapshotting information", e) return package_status end if snapshot_overrides_vcs?(importer, package_description.vcs, snapshot_version) @@ -122,6 +122,7 @@ def status_of_package(package_description, only_local: false, snapshot: false) end status.unexpected_working_copy_state.each do |msg| + package_status.unexpected = true package_status.msg << Autoproj.color(" #{msg}", :red, :bold) end @@ -173,13 +174,15 @@ def each_package_status(packages, parallel: ws.config.parallel_import_level, sna end noninteractive = noninteractive.map do |pkg| future = Concurrent::Future.execute(executor: executor) do - status_of_package(pkg, snapshot: snapshot, only_local: only_local) + Status.status_of_package( + pkg, snapshot: snapshot, only_local: only_local + ) end [pkg, future] end (noninteractive + interactive).each do |pkg, future| - if future + if future if progress wait_timeout = 1 while true @@ -196,7 +199,10 @@ def each_package_status(packages, parallel: ws.config.parallel_import_level, sna if !(status = future.value) raise future.reason end - else status = status_of_package(pkg, snapshot: snapshot, only_local: only_local) + else + status = Status.status_of_package( + pkg, snapshot: snapshot, only_local: only_local + ) end result.uncommitted ||= status.uncommitted @@ -263,7 +269,7 @@ def display_status(packages, parallel: ws.config.parallel_import_level, snapshot sync_packages = "" end - STDERR.print + STDERR.print if status.msg.size == 1 Autoproj.message "#{pkg_name}: #{status.msg.first}" diff --git a/lib/autoproj/cli/update.rb b/lib/autoproj/cli/update.rb index e6d6c9b9..56171617 100644 --- a/lib/autoproj/cli/update.rb +++ b/lib/autoproj/cli/update.rb @@ -1,5 +1,6 @@ require 'autoproj/cli' require 'autoproj/cli/base' +require 'autoproj/cli/status' require 'autoproj/ops/import' module Autoproj @@ -66,17 +67,20 @@ def validate_options(selection, options) return selection, options end - def run(selected_packages, run_hook: false, report: true, **options) + def run(selected_packages, run_hook: false, report: true, ask: false, **options) ws.manifest.accept_unavailable_osdeps = !options[:osdeps] ws.setup ws.autodetect_operating_system(force: true) - if options[:bundler] - ws.update_bundler - end - if options[:autoproj] - ws.update_autoproj + + if ask + prompt = TTY::Prompt.new + options[:bundler] &&= prompt.yes?("Update bundler ?") + options[:autoproj] &&= prompt.yes?("Update autoproj ?") end + ws.update_bundler if options[:bundler] + ws.update_autoproj if options[:autoproj] + begin ws.load_package_sets( mainline: options[:mainline], @@ -84,7 +88,8 @@ def run(selected_packages, run_hook: false, report: true, **options) checkout_only: !options[:config] || options[:checkout_only], reset: options[:reset], keep_going: options[:keep_going], - retry_count: options[:retry_count]) + retry_count: options[:retry_count] + ) rescue ImportFailed => configuration_import_failure if !options[:keep_going] raise @@ -122,6 +127,7 @@ def run(selected_packages, run_hook: false, report: true, **options) parallel: options[:parallel] || ws.config.parallel_import_level, retry_count: options[:retry_count], auto_exclude: options[:auto_exclude], + ask: ask, report: report) ws.finalize_setup @@ -193,14 +199,63 @@ def normalize_osdeps_options( osdeps_options end + class AskUpdateFilter + def initialize(prompt, parallel: 1, only_local: false) + @prompt = prompt + @only_local = only_local + @executor = Concurrent::FixedThreadPool.new(parallel, max_length: 0) + + @parallel = parallel + @futures = {} + @lookahead_queue = [] + end + + def call(pkg) + unless (status = @futures.delete(pkg).value) + raise v.reason + end + + clean = !status.unexpected && + (status.sync || (status.local && !status.remote)) + if clean + msg = Autobuild.color('already up-to-date', :green) + pkg.autobuild.message "#{msg} %s" + return false + end + + Autobuild.progress_display_synchronize do + status.msg.each { |m| puts m } + @prompt.yes?("Update #{pkg.name} ?") + end + end + + def lookahead(pkg) + @futures[pkg] = Concurrent::Future.execute(executor: @executor) do + Status.status_of_package( + pkg, snapshot: false, only_local: @only_local + ) + end + end + end + def update_packages(selected_packages, from: nil, checkout_only: false, only_local: false, reset: false, deps: true, keep_going: false, parallel: 1, retry_count: 0, osdeps: true, auto_exclude: false, osdeps_options: Hash.new, - report: true) + report: true, ask: false) setup_update_from(from) if from + filter = + if ask + prompt = TTY::Prompt.new + filter = AskUpdateFilter.new( + prompt, parallel: parallel, only_local: only_local + ) + else + ->(pkg) { true } + end + ops = Autoproj::Ops::Import.new( ws, report_path: (ws.import_report_path if report)) source_packages, osdep_packages = @@ -213,7 +268,8 @@ def update_packages(selected_packages, parallel: parallel, retry_count: retry_count, install_vcs_packages: (osdeps_options if osdeps), - auto_exclude: auto_exclude) + auto_exclude: auto_exclude, + filter: filter) [source_packages, osdep_packages, nil] rescue ExcludedSelection => e raise CLIInvalidSelection, e.message, e.backtrace diff --git a/lib/autoproj/ops/import.rb b/lib/autoproj/ops/import.rb index e8a8797a..f8e3bcf6 100644 --- a/lib/autoproj/ops/import.rb +++ b/lib/autoproj/ops/import.rb @@ -199,6 +199,7 @@ def import_selected_packages(selection, install_vcs_packages: Hash.new, non_imported_packages: :checkout, auto_exclude: auto_exclude?, + filter: ->(package) { true }, **import_options) unless %i[checkout ignore return].include?(non_imported_packages) @@ -235,6 +236,11 @@ def import_selected_packages(selection, missing_vcs = Array.new installed_vcs_packages = Set['none', 'local'] while failures.empty? || keep_going + # Allow 'filter' to parallelize as well + if filter.respond_to?(:lookahead) + package_queue.each { |pkg| filter.lookahead(pkg) } + end + # Queue work for all packages in the queue package_queue.each do |pkg| # Remove packages that have already been processed @@ -255,6 +261,11 @@ def import_selected_packages(selection, end all_processed_packages << pkg + unless filter.call(pkg) + completion_queue << [pkg, Time.now] + next + end + importer = pkg.autobuild.importer if !pre_package_import(selection, manifest, pkg.autobuild, reverse_dependencies) @@ -272,8 +283,8 @@ def import_selected_packages(selection, next end - pending_packages << pkg begin + pending_packages << pkg queue_import_work( executor, completion_queue, pkg, retry_count: retry_count, @@ -432,6 +443,7 @@ def import_packages(selection, keep_going: false, install_vcs_packages: Hash.new, auto_exclude: auto_exclude?, + filter: ->(pkg) { true }, **import_options) manifest = ws.manifest @@ -443,6 +455,7 @@ def import_packages(selection, recursive: recursive, install_vcs_packages: install_vcs_packages, auto_exclude: auto_exclude, + filter: filter, **import_options) raise failures.first if !keep_going && !failures.empty? diff --git a/lib/autoproj/ops/tools.rb b/lib/autoproj/ops/tools.rb index c0ff972a..305b8115 100644 --- a/lib/autoproj/ops/tools.rb +++ b/lib/autoproj/ops/tools.rb @@ -24,8 +24,8 @@ def initialize(text_name, srcdir, importer = nil) @@packages.delete(text_name) end - def import(options = Hash.new) - importer.import(self, options) + def import(**options) + importer.import(self, **options) end def add_stat(*args) diff --git a/lib/autoproj/package_set.rb b/lib/autoproj/package_set.rb index 456c9418..584dbe59 100644 --- a/lib/autoproj/package_set.rb +++ b/lib/autoproj/package_set.rb @@ -239,7 +239,7 @@ def snapshot(target_dir, options = Hash.new) else package = create_autobuild_package if package.importer.respond_to?(:snapshot) - package.importer.snapshot(package, target_dir, options) + package.importer.snapshot(package, target_dir, **options) end end end