bin/autoproj_bootstrap in autoproj-1.10.2 vs bin/autoproj_bootstrap in autoproj-1.11.0.b1

- old
+ new

@@ -123,10 +123,283 @@ end end end end +module Autoproj + class InputError < RuntimeError; end + + # Definition of an autoproj option as defined by + # {Configuration#configuration_option} + class BuildOption + attr_reader :name + attr_reader :type + attr_reader :options + + attr_reader :validator + + TRUE_STRINGS = %w{on yes y true} + FALSE_STRINGS = %w{off no n false} + def initialize(name, type, options, validator) + @name, @type, @options = name.to_str, type.to_str, options.to_hash + @validator = validator.to_proc if validator + if !BuildOption.respond_to?("validate_#{type}") + raise ConfigError.new, "invalid option type #{type}" + end + end + + def short_doc + if short_doc = options[:short_doc] + short_doc + elsif doc = options[:doc] + if doc.respond_to?(:to_ary) then doc.first + else doc + end + else "#{name} (no documentation for this option)" + end + end + + def doc + doc = (options[:doc] || "#{name} (no documentation for this option)") + if doc.respond_to?(:to_ary) # multi-line + first_line = doc[0] + remaining = doc[1..-1] + if remaining.empty? + first_line + else + remaining = remaining.join("\n").split("\n").join("\n ") + Autoproj.color(first_line, :bold) + "\n " + remaining + end + else + doc + end + end + + def ask(current_value, doc = nil) + default_value = + if !current_value.nil? then current_value.to_s + elsif options[:default] then options[:default].to_str + else '' + end + + STDOUT.print " #{doc || self.doc} [#{default_value}] " + STDOUT.flush + answer = STDIN.readline.chomp + if answer == '' + answer = default_value + end + validate(answer) + + rescue InputError => e + Autoproj.message("invalid value: #{e.message}", :red) + retry + end + + def validate(value) + value = BuildOption.send("validate_#{type}", value, options) + if validator + value = validator[value] + end + value + end + + def self.validate_boolean(value, options) + if TRUE_STRINGS.include?(value.downcase) + true + elsif FALSE_STRINGS.include?(value.downcase) + false + else + raise InputError, "invalid boolean value '#{value}', accepted values are '#{TRUE_STRINGS.join(", ")}' for true, and '#{FALSE_STRINGS.join(", ")} for false" + end + end + + def self.validate_string(value, options) + if possible_values = options[:possible_values] + if options[:lowercase] + value = value.downcase + elsif options[:uppercase] + value = value.upcase + end + + if !possible_values.include?(value) + raise InputError, "invalid value '#{value}', accepted values are '#{possible_values.join("', '")}' (without the quotes)" + end + end + value + end + end +end + + +module Autoproj + # Class that does the handling of configuration options as well as + # loading/saving on disk + class Configuration + # Set of currently known options + # + # These are the values that are going to be saved on disk. Use + # {override} to change a value without changing the saved configuration + # file. + attr_reader :config + # Set of overriden option values that won't get written to file + attr_reader :overrides + # Set of options that have been declared with {declare} + attr_reader :declared_options + # The options that have already been shown to the user + attr_reader :displayed_options + + def initialize + @config = Hash.new + @overrides = Hash.new + @declared_options = Hash.new + @displayed_options = Hash.new + end + + # Deletes the current value for an option + # + # The user will be asked for a new value next time the option is needed + # + # @param [String] the option name + # @return the deleted value + def reset(name) + config.delete(name) + end + + # Sets a configuration option + # + # @param [String] key the option name + # @param [Object] value the option value + # @param [Boolean] user_validated if true, autoproj will not ask the + # user about this value next time it is needed. Otherwise, it will be + # asked about it, the new value being used as default + def set(key, value, user_validated = false) + config[key] = [value, user_validated] + end + + # Override a known option value + # + # The new value will not be saved to disk, unlike with {set} + def override(option_name, value) + overrides[option_name] = value + end + + # Tests whether a value is set for the given option name + # + # @return [Boolean] + def has_value_for?(name) + config.has_key?(name) || overrides.has_key?(name) + end + + # Get the value for a given option + def get(key) + if overrides.has_key?(key) + return overrides[key] + end + + value, validated = config[key] + if value.nil? || (declared?(key) && !validated) + value = configure(key) + else + if declared?(key) && (displayed_options[key] != value) + doc = declared_options[key].short_doc + if doc[-1, 1] != "?" + doc = "#{doc}:" + end + Autoproj.message " #{doc} #{value}" + displayed_options[key] = value + end + value + end + end + + # Returns the option's name-value pairs for the options that do not + # require user input + def validated_values + config.inject(Hash.new) do |h, (k, v)| + h[k] = + if overrides.has_key?(k) then overrides[k] + elsif v.last || !declared?(k) then v.first + end + h + end + end + + # Declare an option + # + # This declares a given option, thus allowing to ask the user about it + # + # @param [String] name the option name + # @param [String] type the option type (can be 'boolean' or 'string') + # @option options [String] :short_doc the one-line documentation string + # that is displayed when the user does not have to be queried. It + # defaults to the first line of :doc if not given + # @option options [String] :doc the full option documentation. It is + # displayed to the user when he is explicitly asked about the option's + # value + # @option options [Object] :default the default value this option should + # take + # @option options [Array] :possible_values list of possible values (only + # if the option type is 'string') + # @option options [Boolean] :lowercase (false) whether the user's input + # should be converted to lowercase before it gets validated / saved. + # @option options [Boolean] :uppercase (false) whether the user's input + # should be converted to uppercase before it gets validated / saved. + def declare(name, type, options, &validator) + declared_options[name] = BuildOption.new(name, type, options, validator) + end + + # Checks if an option exists + # @return [Boolean] + def declared?(name) + declared_options.has_key?(name) + end + + # Configures a given option by asking the user about its desired value + # + # @return [Object] the new option value + # @raise ConfigError if the option is not declared + def configure(option_name) + if opt = declared_options[option_name] + if current_value = config[option_name] + current_value = current_value.first + end + value = opt.ask(current_value) + config[option_name] = [value, true] + displayed_options[option_name] = value + value + else + raise ConfigError.new, "undeclared option '#{option_name}'" + end + end + + def load(path, reconfigure = false) + if h = YAML.load(File.read(path)) + h.each do |key, value| + set(key, value, !reconfigure) + end + end + end + + def save(path) + File.open(path, "w") do |io| + h = Hash.new + config.each do |key, value| + h[key] = value.first + end + + io.write YAML.dump(h) + end + end + end +end + +module Autoproj + def self.config + @config ||= Configuration.new + end +end + require 'tempfile' require 'json' module Autoproj # Module that contains the package manager implementations for the # OSDependencies class @@ -545,10 +818,29 @@ installed_packages << package_name end end new_packages end + + def install(packages) + patterns, packages = packages.partition { |pkg| pkg =~ /^@/ } + patterns = patterns.map { |str| str[1..-1] } + result = false + if !patterns.empty? + result |= super(patterns, + :auto_install_cmd => "yum groupinstall -y '%s'", + :user_install_cmd => "yum groupinstall '%s'") + end + if !packages.empty? + result |= super(packages) + end + if result + # Invalidate caching of installed packages, as we just + # installed new packages ! + @installed_packages = nil + end + end end # Package manager interface for systems that use APT and dpkg for # package management class AptDpkgManager < ShellScriptManager @@ -699,10 +991,11 @@ Autobuild.programs['gem'] = "gem" end end def reinstall + Autoproj.message "reinstalling all RubyGems" base_cmdline = [Autobuild.tool_in_path('ruby'), '-S', Autobuild.tool('gem')] Autobuild::Subprocess.run 'autoproj', 'osdeps', 'reinstall', *base_cmdline, 'clean' Autobuild::Subprocess.run 'autoproj', 'osdeps', 'reinstall', *base_cmdline, 'pristine', '--all', '--extensions' @@ -736,11 +1029,11 @@ Autobuild::Subprocess.run 'autoproj', 'osdeps', *c end gems.each do |name, v| installed_gems << name end - did_something = true + true end end # Returns the set of RubyGem packages in +packages+ that are not already # installed, or that can be upgraded @@ -1123,11 +1416,24 @@ @definitions = definitions.merge(info.definitions) do |h, v1, v2| if v1 != v2 root_dir ||= "#{Autoproj.root_dir}/" old = source_of(h).gsub(root_dir, '') new = info.source_of(h).gsub(root_dir, '') - Autoproj.warn("osdeps definition for #{h}, previously defined in #{old} overridden by #{new}") + + # Warn if the new osdep definition resolves to a different + # set of packages than the old one + old_resolved = resolve_package(h).inject(Hash.new) do |osdep_h, (handler, status, list)| + osdep_h[handler.name] = [status, list] + h + end + new_resolved = info.resolve_package(h).inject(Hash.new) do |osdep_h, (handler, status, list)| + osdep_h[handler.name] = [status, list] + h + end + if old_resolved != new_resolved + Autoproj.warn("osdeps definition for #{h}, previously defined in #{old} overridden by #{new}") + end end v2 end @sources = sources.merge(info.sources) @all_definitions = all_definitions.merge(info.all_definitions) do |package_name, all_defs, new_all_defs| @@ -1279,22 +1585,25 @@ Kernel.validate_options options, :force => false else options.dup end - if options[:force] - @operating_system = nil - elsif !@operating_system.nil? # @operating_system can be set to false to simulate an unknown OS - return @operating_system - elsif user_os = ENV['AUTOPROJ_OS'] + if user_os = ENV['AUTOPROJ_OS'] @operating_system = if user_os.empty? then false else names, versions = user_os.split(':') normalize_os_representation(names.split(','), versions.split(',')) end return @operating_system + end + + + if options[:force] + @operating_system = nil + elsif !@operating_system.nil? # @operating_system can be set to false to simulate an unknown OS + return @operating_system elsif Autoproj.has_config_key?('operating_system') os = Autoproj.user_config('operating_system') if os.respond_to?(:to_ary) if os[0].respond_to?(:to_ary) && os[0].all? { |s| s.respond_to?(:to_str) } && os[1].respond_to?(:to_ary) && os[1].all? { |s| s.respond_to?(:to_str) } @@ -1586,12 +1895,16 @@ class MissingOSDep < ConfigError; end # Resolves the given OS dependencies into the actual packages that need # to be installed on this particular OS. # - # Raises ConfigError if some packages can't be found or if the - # nonexistent keyword was found for some of them + # @param [Array<String>] dependencies the list of osdep names that should be resolved + # @return [Array<#install,Array<String>>] the set of packages, grouped + # by the package handlers that should be used to install them + # + # @raise MissingOSDep if some packages can't be found or if the + # nonexistent keyword was found for some of them def resolve_os_dependencies(dependencies) all_packages = [] dependencies.each do |name| result = resolve_package(name) if !result @@ -2017,209 +2330,57 @@ @programs_in_path = Hash.new end module Autoproj - class InputError < RuntimeError; end - - class << self - # Programatically overriden autoproj options - # - # @see override_option - attr_reader :option_overrides - end - @option_overrides = Hash.new - - # Programatically override a user-selected option without changing the - # configuration file + # @deprecated use config.override instead def self.override_option(option_name, value) - @option_overrides[option_name] = value + config.override(option_name, value) end - - class BuildOption - attr_reader :name - attr_reader :type - attr_reader :options - - attr_reader :validator - - TRUE_STRINGS = %w{on yes y true} - FALSE_STRINGS = %w{off no n false} - def initialize(name, type, options, validator) - @name, @type, @options = name.to_str, type.to_str, options.to_hash - @validator = validator.to_proc if validator - if !BuildOption.respond_to?("validate_#{type}") - raise ConfigError.new, "invalid option type #{type}" - end - end - - def short_doc - if short_doc = options[:short_doc] - short_doc - elsif doc = options[:doc] - if doc.respond_to?(:to_ary) then doc.first - else doc - end - else "#{name} (no documentation for this option)" - end - end - - def doc - doc = (options[:doc] || "#{name} (no documentation for this option)") - if doc.respond_to?(:to_ary) # multi-line - first_line = doc[0] - remaining = doc[1..-1] - if remaining.empty? - first_line - else - remaining = remaining.join("\n").split("\n").join("\n ") - Autoproj.color(first_line, :bold) + "\n " + remaining - end - else - doc - end - end - - def ask(current_value, doc = nil) - default_value = - if !current_value.nil? then current_value.to_s - elsif options[:default] then options[:default].to_str - else '' - end - - STDOUT.print " #{doc || self.doc} [#{default_value}] " - STDOUT.flush - answer = STDIN.readline.chomp - if answer == '' - answer = default_value - end - validate(answer) - - rescue InputError => e - Autoproj.message("invalid value: #{e.message}", :red) - retry - end - - def validate(value) - value = BuildOption.send("validate_#{type}", value, options) - if validator - value = validator[value] - end - value - end - - def self.validate_boolean(value, options) - if TRUE_STRINGS.include?(value.downcase) - true - elsif FALSE_STRINGS.include?(value.downcase) - false - else - raise InputError, "invalid boolean value '#{value}', accepted values are '#{TRUE_STRINGS.join(", ")}' for true, and '#{FALSE_STRINGS.join(", ")} for false" - end - end - - def self.validate_string(value, options) - if possible_values = options[:possible_values] - if options[:lowercase] - value = value.downcase - elsif options[:uppercase] - value = value.upcase - end - - if !possible_values.include?(value) - raise InputError, "invalid value '#{value}', accepted values are '#{possible_values.join("', '")}' (without the quotes)" - end - end - value - end - end - - @user_config = Hash.new - - def self.option_set - @user_config.inject(Hash.new) do |h, (k, v)| - h[k] = v.first - h - end - end - + # @deprecated use config.reset instead def self.reset_option(key) - @user_config.delete(key) + config.reset(key) end - + # @deprecated use config.set(key, value, user_validated) instead def self.change_option(key, value, user_validated = false) - @user_config[key] = [value, user_validated] + config.set(key, value, user_validated) end - + # @deprecated use config.validated_values instead + def self.option_set + config.validated_values + end + # @deprecated use config.get(key) instead def self.user_config(key) - value, seen = @user_config[key] - # All non-user options are always considered as "seen" - seen ||= !@declared_options.has_key?(key) - - if value.nil? || (!seen && Autoproj.reconfigure?) - value = configure(key) - else - if !seen - doc = @declared_options[key].short_doc - if doc[-1, 1] != "?" - doc = "#{doc}:" - end - Autoproj.message " #{doc} #{value}" - @user_config[key] = [value, true] - end - value - end + config.get(key) end - - @declared_options = Hash.new + # @deprecated use config.declare(name, type, options, &validator) instead def self.configuration_option(name, type, options, &validator) - @declared_options[name] = BuildOption.new(name, type, options, validator) + config.declare(name, type, options, &validator) end - + # @deprecated use config.declared?(name, type, options, &validator) instead def self.declared_option?(name) - @declared_options.has_key?(name) + config.declared?(name) end - + # @deprecated use config.configure(option_name) instead def self.configure(option_name) - if opt = @declared_options[option_name] - if current_value = @user_config[option_name] - current_value = current_value.first - end - value = opt.ask(current_value) - @user_config[option_name] = [value, true] - value - else - raise ConfigError.new, "undeclared option '#{option_name}'" - end + config.configure(option_name) end + # @deprecated use config.has_value_for?(name) + def self.has_config_key?(name) + config.has_value_for?(name) + end def self.save_config - File.open(File.join(Autoproj.config_dir, "config.yml"), "w") do |io| - config = Hash.new - @user_config.each_key do |key| - config[key] = @user_config[key].first - end - - io.write YAML.dump(config) - end + config.save(File.join(Autoproj.config_dir, "config.yml")) end - def self.has_config_key?(name) - @user_config.has_key?(name) - end - def self.load_config + @config ||= Configuration.new + config_file = File.join(Autoproj.config_dir, "config.yml") if File.exists?(config_file) - config = YAML.load(File.read(config_file)) - if !config - return - end - - config.each do |key, value| - @user_config[key] = [value, false] - end + config.load(config_file, reconfigure?) end end class << self attr_accessor :reconfigure @@ -2430,36 +2591,19 @@ end Autobuild.export_env_sh(io) end end - # Load a definition file given at +path+. +source+ is the package set from - # which the file is taken. - # - # If any error is detected, the backtrace will be filtered so that it is - # easier to understand by the user. Moreover, if +source+ is non-nil, the - # package set name will be mentionned. + # @deprecated use Ops.loader.load or add a proper Loader object to your + # class def self.load(package_set, *path) - path = File.join(*path) - in_package_set(package_set, File.expand_path(path).gsub(/^#{Regexp.quote(Autoproj.root_dir)}\//, '')) do - begin - Kernel.load path - rescue Interrupt - raise - rescue ConfigError => e - raise - rescue Exception => e - filter_load_exception(e, package_set, path) - end - end + Ops.loader.load(package_set, *path) end - # Same as #load, but runs only if the file exists. + # @deprecated use Ops.loader.load_if_present or add a proper Loader object + # to your class def self.load_if_present(package_set, *path) - path = File.join(*path) - if File.file?(path) - self.load(package_set, *path) - end + Ops.loader.load_if_present(package_set, *path) end # Look into +dir+, searching for shared libraries. For each library, display # a warning message if this library has undefined symbols. def self.validate_solib_dependencies(dir, exclude_paths = [])