[Rock-dev] TESTING NEEDED: autobuild and autoproj RC

Sylvain Joyeux sylvain.joyeux at dfki.de
Wed Apr 24 10:03:07 CEST 2013


= Version 1.9.3
  * close #245: wrong local variable name
  * close #218: fix error generated when a metapackage is empty
  * close #239: metapackages are now back to having "hard" dependencies, 
i.e. the
    build will fail if any of the metapackage's dependencies (or their
    dependencies) cannot be built for any reason. Individual 
metapackages (e.g. the
    rock metapackage) can be marked as using "weak" dependencies to 
retain the
    behaviour of "build as much as you can"
  * Ruby 2.0 / RubyGems 2.0 compatibility fix
  * re-add 'default' in the osdep OS names. This allows to fall back to
    os-independent handlers (e.g. 'gem') when not on a known OS
  * fixes for the MacPort OSdep handler
  * fix issues with the Ruby shims (gem, irb, ...) being shell scripts
  * archives are now gracefully updated by asking the user whether he wants
    the folder to be deleted
  * early support for having importdir != srcdir. This allows to import 
and build
    packages that are somewhere within another repository. It is still 
not very
    well integrated, though.
  * fix issue with autotools packages being rebuilt unnecessarily

Testing Instructions
--------------------------
In an already existing autoproj installation, source the env.sh and
simply run

gem install --prerelease autoproj autobuild

To test the bootstrapping as well, save the autoproj_bootstrap file attached
to this mail and use it as your bootstrapping script.

-- 
Sylvain Joyeux (Dr.Ing.)
Senior Researcher

Space & Security Robotics
Underwater Robotics

!!! Achtung, neue Telefonnummer!!!

Standort Bremen:
DFKI GmbH
Robotics Innovation Center
Robert-Hooke-Straße 5
28359 Bremen, Germany

Phone: +49 (0)421 178-454136
Fax:   +49 (0)421 218-454150
E-Mail: robotik at dfki.de

Weitere Informationen: http://www.dfki.de/robotik
-----------------------------------------------------------------------
Deutsches Forschungszentrum fuer Kuenstliche Intelligenz GmbH
Firmensitz: Trippstadter Straße 122, D-67663 Kaiserslautern
Geschaeftsfuehrung: Prof. Dr. Dr. h.c. mult. Wolfgang Wahlster
(Vorsitzender) Dr. Walter Olthoff
Vorsitzender des Aufsichtsrats: Prof. Dr. h.c. Hans A. Aukes
Amtsgericht Kaiserslautern, HRB 2313
Sitz der Gesellschaft: Kaiserslautern (HRB 2313)
USt-Id.Nr.:    DE 148646973
Steuernummer:  19/673/0060/3
-----------------------------------------------------------------------

-------------- nächster Teil --------------
#! /usr/bin/ruby

if RUBY_VERSION < "1.8.7"
    STDERR.puts "autoproj requires Ruby >= 1.8.7"
    exit 1
end

if defined? Encoding # This is a 1.9-only thing
    Encoding.default_internal = Encoding::UTF_8
    Encoding.default_external = Encoding::UTF_8
end

require 'rbconfig'
module Autobuild
    @windows = RbConfig::CONFIG["host_os"] =~%r!(msdos|mswin|djgpp|mingw|[Ww]indows)!
    def self.windows?
        @windows
    end

    @macos =  RbConfig::CONFIG["host_os"] = ~%r!([Dd]arwin)!
    def self.macos?
        @macos
    end
end

require 'set'
curdir_entries = Dir.entries('.').to_set - [".", "..", "autoproj_bootstrap", ".gems", 'env.sh'].to_set
if !curdir_entries.empty? && ENV['AUTOPROJ_BOOTSTRAP_IGNORE_NONEMPTY_DIR'] != '1'
    while true
        print "The current directory is not empty, continue bootstrapping anyway ? [yes] "
        STDOUT.flush 
        answer = STDIN.readline.chomp
        if answer == "no"
            exit
        elsif answer == "" || answer == "yes"
            # Set the environment variable since we might restart the
            # autoproj_bootstrap script and -- anyway -- will run "autoproj
            # bootstrap" later on
            break
        else
            STDOUT.puts "invalid answer. Please answer 'yes' or 'no'"
            STDOUT.flush
        end
    end
end

# Environment is clean, so just mark it as so unconditionally
ENV['AUTOPROJ_BOOTSTRAP_IGNORE_NONEMPTY_DIR'] = '1'

needed_gem_home = ENV['AUTOPROJ_GEM_HOME'] || "#{Dir.pwd}/.gems"
if $LOADED_FEATURES.find { |str| str =~ /bygems/ }
    if ENV['GEM_HOME'] != needed_gem_home
        require 'rbconfig'
        RUBY = RbConfig::CONFIG['RUBY_INSTALL_NAME']

        ENV['GEM_HOME'] = needed_gem_home
        ENV.delete 'GEM_PATH'
        exec RUBY, __FILE__, *ARGV
    end
end


ENV['GEM_HOME'] = needed_gem_home
ENV['PATH']     = "#{ENV['GEM_HOME']}/bin:#{ENV['PATH']}"

require 'yaml'
require 'set'

module Autoproj
    class ConfigError < RuntimeError; end
    class << self
        attr_reader :verbose
    end

    def self.color(string, *args)
        string
    end

    def self.message(str)
        STDERR.puts "  #{str}"
    end
end

module Autobuild
    def self.do_update
        true
    end
    def self.message(str)
        STDERR.puts "  #{str}"
    end

    class << self
        attr_reader :programs
    end
    @programs = Hash.new
    def self.tool(name)
        # Let the ability to set programs[name] to nil to make sure we don't use
        # that program. This is used later on in this file to make sure we
        # aren't using the wrong rubygems binary
        if programs.has_key?(name)
            programs[name]
        else
            name
        end
    end

    module Subprocess
        def self.run(name, phase, *cmd)
            output = `#{cmd.join(" ")}`
            if $?.exitstatus != 0
                STDERR.puts "ERROR: failed to run #{cmd.join(" ")}"
                STDERR.puts "ERROR: command output is: #{output}"
                exit 1
            end
        end
    end
end

require 'tempfile'
module Autoproj
    # Module that contains the package manager implementations for the
    # OSDependencies class
    module PackageManagers
        # Base class for all package managers. Subclasses must add the
        # #install(packages) method and may add the
        # #filter_uptodate_packages(packages) method
        class Manager
            attr_reader :names

            attr_writer :enabled
            def enabled?; !!@enabled end

            attr_writer :silent
            def silent?; !!@silent end

            def initialize(names = [])
                @names = names.dup
                @enabled = true
                @silent = true
            end

            def name
                names.first
            end
        end

        # Dummy package manager used for unknown OSes. It simply displays a
        # message to the user when packages are needed
        class UnknownOSManager < Manager
            def initialize
                super(['unknown'])
                @installed_osdeps = Set.new
            end

            def osdeps_interaction_unknown_os(osdeps)
                puts <<-EOMSG
  #{Autoproj.color("The build process requires some other software packages to be installed on our operating system", :bold)}
  #{Autoproj.color("If they are already installed, simply ignore this message", :red)}

    #{osdeps.to_a.sort.join("\n    ")}

                EOMSG
                print Autoproj.color("Press ENTER to continue", :bold)
                STDOUT.flush
                STDIN.readline
                puts
                nil
            end

            def install(osdeps)
                if silent?
                    return false
                else
                    osdeps = osdeps.to_set
                    osdeps -= @installed_osdeps
                    if !osdeps.empty?
                        result = osdeps_interaction_unknown_os(osdeps)
                    end
                    @installed_osdeps |= osdeps
                    return result
                end
            end
        end

        # Base class for all package managers that simply require the call of a
        # shell script to install packages (e.g. yum, apt, ...)
        class ShellScriptManager < Manager
            def self.execute_as_root(script, with_locking)
                if with_locking
                    File.open('/tmp/autoproj_osdeps_lock', 'w') do |lock_io|
                        begin
                            while !lock_io.flock(File::LOCK_EX | File::LOCK_NB)
                                Autoproj.message "  waiting for other autoproj instances to finish their osdeps installation"
                                sleep 5
                            end
                            return execute_as_root(script, false)
                        ensure
                            lock_io.flock(File::LOCK_UN)
                        end
                    end
                end


                Tempfile.open('osdeps_sh') do |io|
                    io.puts "#! /bin/bash"
                    io.puts GAIN_ROOT_ACCESS
                    io.write script
                    io.flush
                    Autobuild::Subprocess.run 'autoproj', 'osdeps', '/bin/bash', io.path
                end
            end

            GAIN_ROOT_ACCESS = <<-EOSCRIPT
# Gain root access using sudo
if test `id -u` != "0"; then
    exec sudo /bin/bash $0 "$@"

fi
            EOSCRIPT

            attr_writer :needs_locking
            def needs_locking?; !!@needs_locking end

            attr_reader :auto_install_cmd
            attr_reader :user_install_cmd

            def initialize(names, needs_locking, user_install_cmd, auto_install_cmd)
                super(names)
                @needs_locking, @user_install_cmd, @auto_install_cmd =
                    needs_locking, user_install_cmd, auto_install_cmd
            end

            def generate_user_os_script(os_packages)
                if user_install_cmd
                    (user_install_cmd % [os_packages.join("' '")])
                else generate_auto_os_script(os_packages)
                end
            end

            def generate_auto_os_script(os_packages)
                (auto_install_cmd % [os_packages.join("' '")])
            end

            def osdeps_interaction(os_packages, shell_script)
                if OSDependencies.force_osdeps
                    return true
                elsif enabled?
                    return true
                elsif silent?
                    return false
                end

                # We're asked to not install the OS packages but to display them
                # anyway, do so now
                puts <<-EOMSG

                #{Autoproj.color("The build process and/or the packages require some other software to be installed", :bold)}
                #{Autoproj.color("and you required autoproj to not install them itself", :bold)}
                #{Autoproj.color("\nIf these packages are already installed, simply ignore this message\n", :red) if !respond_to?(:filter_uptodate_packages)}
    The following packages are available as OS dependencies, i.e. as prebuilt
    packages provided by your distribution / operating system. You will have to
    install them manually if they are not already installed

                #{os_packages.sort.join("\n      ")}

    the following command line(s) can be run as root to install them:

                #{shell_script.split("\n").join("\n|   ")}

            EOMSG
                print "    #{Autoproj.color("Press ENTER to continue ", :bold)}"
                STDOUT.flush
                STDIN.readline
                puts
                false
            end

            def install(packages)
                handled_os = OSDependencies.supported_operating_system?
                if handled_os
                    shell_script = generate_auto_os_script(packages)
                    user_shell_script = generate_user_os_script(packages)
                end
                if osdeps_interaction(packages, user_shell_script)
                    Autoproj.message "  installing OS packages: #{packages.sort.join(", ")}"

                    if Autoproj.verbose
                        Autoproj.message "Generating installation script for non-ruby OS dependencies"
                        Autoproj.message shell_script
                    end
                    ShellScriptManager.execute_as_root(shell_script, needs_locking?)
                    return true
                end
                false
            end
        end

        # Package manager interface for systems that use port (i.e. MacPorts/Darwin) as
        # their package manager
        class PortManager < ShellScriptManager
            def initialize
                super(['port'], true,
                        "port install '%s'",
                        "port install '%s'")
            end
        end

        # Package manager interface for systems that use pacman (i.e. arch) as
        # their package manager
        class PacmanManager < ShellScriptManager
            def initialize
                super(['pacman'], true,
                        "pacman '%s'",
                        "pacman -Sy --noconfirm '%s'")
            end
        end

        # Package manager interface for systems that use emerge (i.e. gentoo) as
        # their package manager
        class EmergeManager < ShellScriptManager
            def initialize
                super(['emerge'], true,
                        "emerge '%s'",
                        "emerge --noreplace '%s'")
            end
        end

        # Package manager interface for systems that use yum
        class YumManager < ShellScriptManager
            def initialize
                super(['yum'], true,
                      "yum install '%s'",
                      "yum install -y '%s'")
            end

            def filter_uptodate_packages(packages)
                result = `LANG=C rpm -q --queryformat "%{NAME}\n" '#{packages.join("' '")}'`

                installed_packages = []
		new_packages = []
                result.split("\n").each_with_index do |line, index|
		    line = line.strip
                    if line =~ /package (.*) is not installed/
			package_name = $1
			if !packages.include?(package_name) # something is wrong, fallback to installing everything
			    return packages
			end
                        new_packages << package_name
		    else 
			package_name = line.strip
			if !packages.include?(package_name) # something is wrong, fallback to installing everything
			    return packages
			end
		        installed_packages << package_name
                    end
                end
                new_packages
            end
        end

        # Package manager interface for systems that use APT and dpkg for
        # package management
        class AptDpkgManager < ShellScriptManager
            attr_accessor :status_file

            def initialize(status_file = "/var/lib/dpkg/status")
                @status_file = status_file
                super(['apt-dpkg'], true,
                      "apt-get install '%s'",
                      "export DEBIAN_FRONTEND=noninteractive; apt-get install -y '%s'")
            end

            # On a dpkg-enabled system, checks if the provided package is installed
            # and returns true if it is the case
            def installed?(package_name)
                if !@installed_packages
                    @installed_packages = Set.new
                    dpkg_status = File.readlines(status_file)
                    dpkg_status << ""

                    current_packages = []
                    is_installed = false
                    dpkg_status.each do |line|
                        line = line.chomp
                        if line == ""
                            if is_installed
                                current_packages.each do |pkg|
                                    @installed_packages << pkg
                                end
                                is_installed = false
                            end
                            current_packages.clear
                        elsif line =~ /Package: (.*)$/
                            current_packages << $1
                        elsif line =~ /Provides: (.*)$/
                            current_packages.concat($1.split(',').map(&:strip))
                        elsif line == "Status: install ok installed"
                            is_installed = true
                        end
                    end
                end
                
                if package_name =~ /^(\w[a-z0-9+-.]+)/
                    @installed_packages.include?($1)
                else
                    Autoproj.warn "#{package_name} is not a valid Debian package name"
                    false
                end
            end
            
            def install(packages)
                if super
                    # Invalidate caching of installed packages, as we just
                    # installed new packages !
                    @installed_packages = nil
                end
            end
            
            def filter_uptodate_packages(packages)
                packages.find_all do |package_name|
                    !installed?(package_name)
                end
            end
        end

        # Package manager interface for the RubyGems system
        class GemManager < Manager
            class << self
                attr_accessor :with_prerelease
                attr_accessor :with_doc
            end
            @with_prerelease = false
            @with_doc = false

            def initialize
                super(['gem'])
                @installed_gems = Set.new
            end

            # Used to override the Gem::SpecFetcher object used by this gem
            # manager. Useful mainly for testing
            attr_writer :gem_fetcher

            # The set of gems installed during this autoproj session
            attr_reader :installed_gems

            def gem_fetcher
                if !@gem_fetcher
                    Autoproj.message "  looking for RubyGems updates"
                    @gem_fetcher = Gem::SpecFetcher.fetcher
                end
                @gem_fetcher
            end

            def guess_gem_program
                if Autobuild.programs['gem']
                    return Autobuild.programs['gem']
                end

                ruby_bin = RbConfig::CONFIG['RUBY_INSTALL_NAME']
                if ruby_bin =~ /^ruby(.+)$/
                    Autobuild.programs['gem'] = "gem#{$1}"
                else
                    Autobuild.programs['gem'] = "gem"
                end
            end

            def install(gems)
                guess_gem_program

                base_cmdline = [Autobuild.tool('gem'), 'install']
                if !GemManager.with_doc
                    base_cmdline << '--no-rdoc' << '--no-ri'
                end

                if GemManager.with_prerelease
                    base_cmdline << "--prerelease"
                end
                with_version, without_version = gems.partition { |name, v| v }

                cmdlines = []
                if !without_version.empty?
                    cmdlines << (base_cmdline + without_version.flatten)
                end
                with_version.each do |name, v|
                    cmdlines << base_cmdline + [name, "-v", v]
                end

                if gems_interaction(gems, cmdlines)
                    Autoproj.message "  installing/updating RubyGems dependencies: #{gems.map { |g| g.join(" ") }.sort.join(", ")}"

                    cmdlines.each do |c|
                        Autobuild::Subprocess.run 'autoproj', 'osdeps', *c
                    end
                    gems.each do |name, v|
                        installed_gems << name
                    end
                    did_something = true
                end
            end

            # Returns the set of RubyGem packages in +packages+ that are not already
            # installed, or that can be upgraded
            def filter_uptodate_packages(gems)
                # Don't install gems that are already there ...
                gems = gems.dup
                gems.delete_if do |name, version|
                    next(true) if installed_gems.include?(name)

                    version_requirements = Gem::Requirement.new(version || '>= 0')
                    installed =
                        if Gem::Specification.respond_to?(:find_by_name)
                            begin
                                [Gem::Specification.find_by_name(name, version_requirements)]
                            rescue Gem::LoadError
                                []
                            end
                        else
                            Gem.source_index.find_name(name, version_requirements)
                        end

                    if !installed.empty? && Autobuild.do_update
                        # Look if we can update the package ...
                        dep = Gem::Dependency.new(name, version_requirements)
                        available =
                            if gem_fetcher.respond_to?(:find_matching)
                                gem_fetcher.find_matching(dep, true, true, GemManager.with_prerelease).map(&:first)
                            else # Post RubyGems-2.0
                                type = if GemManager.with_prerelease then :prerelease
                                       else :complete
                                       end

                                gem_fetcher.detect(type) do |tuple|
                                    tuple.name == name && dep.match?(tuple)
                                end.map { |tuple, _| [tuple.name, tuple.version] }
                            end
                        installed_version = installed.map(&:version).max
                        available_version = available.map { |name, v| v }.max
                        if !available_version
                            if version
                                raise ConfigError.new, "cannot find any gem with the name '#{name}' and version #{version}"
                            else
                                raise ConfigError.new, "cannot find any gem with the name '#{name}'"
                            end
                        end
                        needs_update = (available_version > installed_version)
                        !needs_update
                    else
                        !installed.empty?
                    end
                end
                gems
            end

            def parse_package_entry(entry)
                if entry =~ /^([^><=~]*)([><=~]+.*)$/
                    [$1.strip, $2.strip]
                else
                    [entry]
                end
            end

            def gems_interaction(gems, cmdlines)
                if OSDependencies.force_osdeps
                    return true
                elsif enabled?
                    return true
                elsif silent?
                    return false
                end

                # We're not supposed to install rubygem packages but silent is not
                # set, so display information about them anyway
                puts <<-EOMSG
      #{Autoproj.color("The build process and/or the packages require some Ruby Gems to be installed", :bold)}
      #{Autoproj.color("and you required autoproj to not do it itself", :bold)}
        You can use the --all or --ruby options to autoproj osdeps to install these
        packages anyway, and/or change to the osdeps handling mode by running an
        autoproj operation with the --reconfigure option as for instance
        autoproj build --reconfigure
        
        The following command line can be used to install them manually
        
          #{cmdlines.map { |c| c.join(" ") }.join("\n      ")}
        
        Autoproj expects these Gems to be installed in #{Autoproj.gem_home} This can
        be overridden by setting the AUTOPROJ_GEM_HOME environment variable manually

                EOMSG
                print "    #{Autoproj.color("Press ENTER to continue ", :bold)}"

                STDOUT.flush
                STDIN.readline
                puts
                false
            end
        end
    end

    # Manager for packages provided by external package managers
    class OSDependencies
	class << self
	    # When requested to load a file called X.Y, the osdeps code will
	    # also look for files called X-suffix.Y, where 'suffix' is an
	    # element in +osdeps_suffixes+
	    #
	    # A usage of this functionality is to make loading conditional to
	    # the available version of certain tools, namely Ruby. Autoproj for
	    # instance adds ruby18 when started on Ruby 1.8 and ruby19 when
	    # started on Ruby 1.9
	    attr_reader :suffixes
	end
	@suffixes = []

        def self.load(file)
	    if !File.file?(file)
		raise ArgumentError, "no such file or directory #{file}"
	    end

	    candidates = [file]
	    candidates.concat(suffixes.map { |s| "#{file}-#{s}" })

            error_t = if defined? Psych::SyntaxError then [ArgumentError, Psych::SyntaxError]
                      else ArgumentError
                      end

	    result = OSDependencies.new
	    candidates.each do |file|
                next if !File.file?(file)
                file = File.expand_path(file)
                begin
                    data = YAML.load(File.read(file)) || Hash.new
                    verify_definitions(data)
                rescue *error_t => e
                    raise ConfigError.new, "error in #{file}: #{e.message}", e.backtrace
                end

                result.merge(OSDependencies.new(data, file))
	    end
	    result
        end

        class << self
            attr_reader :aliases
            attr_accessor :force_osdeps
        end
        @aliases = Hash.new

        attr_writer :silent
        def silent?; @silent end

        def self.alias(old_name, new_name)
            @aliases[new_name] = old_name
        end

	def self.ruby_version_keyword
            "ruby#{RUBY_VERSION.split('.')[0, 2].join("")}"
        end

        def self.autodetect_ruby
            self.alias(ruby_version_keyword, "ruby")
        end
	self.suffixes << ruby_version_keyword
        autodetect_ruby

        AUTOPROJ_OSDEPS = File.join(File.expand_path(File.dirname(__FILE__)), 'default.osdeps')
        def self.load_default
            file = ENV['AUTOPROJ_DEFAULT_OSDEPS'] || AUTOPROJ_OSDEPS
            if !File.file?(file)
                Autoproj.warn "#{file} (from AUTOPROJ_DEFAULT_OSDEPS) is not a file, falling back to #{AUTOPROJ_OSDEPS}"
                file = AUTOPROJ_OSDEPS
            end
            OSDependencies.load(file)
        end

        PACKAGE_HANDLERS = [PackageManagers::AptDpkgManager,
            PackageManagers::GemManager,
            PackageManagers::EmergeManager,
            PackageManagers::PacmanManager,
            PackageManagers::YumManager,
            PackageManagers::PortManager]
        OS_PACKAGE_HANDLERS = {
            'debian' => 'apt-dpkg',
            'gentoo' => 'emerge',
            'arch' => 'pacman',
            'fedora' => 'yum',
            'darwin' => 'port'
        }

        # The information contained in the OSdeps files, as a hash
        attr_reader :definitions
        # All the information contained in all the OSdeps files, as a mapping
        # from the OSdeps package name to [osdeps_file, definition] pairs
        attr_reader :all_definitions
        # The information as to from which osdeps file the current package
        # information in +definitions+ originates. It is a mapping from the
        # package name to the osdeps file' full path
        attr_reader :sources

        # Use to override the autodetected OS-specific package handler
        attr_writer :os_package_handler

        # Returns the package manager object for the current OS
        def os_package_handler
            if @os_package_handler.nil?
                os_names, _ = OSDependencies.operating_system
                if os_names && (key = os_names.find { |name| OS_PACKAGE_HANDLERS[name] })
                    @os_package_handler = package_handlers[OS_PACKAGE_HANDLERS[key]]
                    if !@os_package_handler
                        raise ArgumentError, "found #{OS_PACKAGE_HANDLERS[name]} as the required package handler for #{os_names.join(", ")}, but it is not registered"
                    end
                else
                    @os_package_handler = PackageManagers::UnknownOSManager.new
                end
            end
            return @os_package_handler
        end

        # Returns the set of package managers
        def package_handlers
            if !@package_handlers
                @package_handlers = Hash.new
                PACKAGE_HANDLERS.each do |klass|
                    obj = klass.new
                    obj.names.each do |n|
                        @package_handlers[n] = obj
                    end
                end
            end
            @package_handlers
        end

        # The Gem::SpecFetcher object that should be used to query RubyGems, and
        # install RubyGems packages
        def initialize(defs = Hash.new, file = nil)
            @definitions = defs.to_hash
            @all_definitions = Hash.new { |h, k| h[k] = Array.new }

            @sources     = Hash.new
            @installed_packages = Set.new
            if file
                defs.each_key do |package_name|
                    sources[package_name] = file
                    all_definitions[package_name] << [[file], defs[package_name]]
                end
            end
            @silent = true
            @filter_uptodate_packages = true
        end

        # Returns the full path to the osdeps file from which the package
        # definition for +package_name+ has been taken
        def source_of(package_name)
            sources[package_name]
        end

        # Merges the osdeps information of +info+ into +self+. If packages are
        # defined in both OSDependencies objects, the information in +info+
        # takes precedence
        def merge(info)
            root_dir = nil
            @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}")
                end
                v2
            end
            @sources = sources.merge(info.sources)
            @all_definitions = all_definitions.merge(info.all_definitions) do |package_name, all_defs, new_all_defs|
                all_defs = all_defs.dup
                new_all_defs = new_all_defs.dup
                new_all_defs.delete_if do |files, data|
                    if entry = all_defs.find { |_, d| d == data }
                        entry[0] |= files
                    end
                end
                all_defs.concat(new_all_defs)
            end
        end

        # Perform some sanity checks on the given osdeps definitions
        def self.verify_definitions(hash, path = [])
            hash.each do |key, value|
                if value && !key.kind_of?(String)
                    raise ArgumentError, "invalid osdeps definition: found an #{key.class} as a key in #{path.join("/")}. Don't forget to put quotes around numbers"
                elsif !value && (key.kind_of?(Hash) || key.kind_of?(Array))
                    verify_definitions(key)
                end
                next if !value

                if value.kind_of?(Array) || value.kind_of?(Hash)
                    verify_definitions(value, (path + [key]))
                else
                    if !value.kind_of?(String)
                        raise ArgumentError, "invalid osdeps definition: found an #{value.class} as a value in #{path.join("/")}. Don't forget to put quotes around numbers"
                    end
                end
            end
        end

        # Returns true if it is possible to install packages for the operating
        # system on which we are installed
        def self.supported_operating_system?
            if @supported_operating_system.nil?
                os_names, _ = operating_system
                @supported_operating_system =
                    if !os_names then false
                    else
                        os_names.any? { |os_name| OS_PACKAGE_HANDLERS.has_key?(os_name) }
                    end
            end
            return @supported_operating_system
        end

        # Used mainly during testing to bypass the operating system
        # autodetection
        def self.operating_system=(values)
            @supported_operating_system = nil
            @operating_system = values
        end

        # Autodetects the operating system name and version
        #
        # +osname+ is the operating system name, all in lowercase (e.g. ubuntu,
        # arch, gentoo, debian)
        #
        # +versions+ is a set of names that describe the OS version. It includes
        # both the version number (as a string) and/or the codename if there is
        # one.
        #
        # Examples: ['debian', ['sid', 'unstable']] or ['ubuntu', ['lucid lynx', '10.04']]
        def self.operating_system(options = Hash.new)
            # Validate the options. We check on the availability of
            # validate_options as to not break autoproj_bootstrap (in which
            # validate_options is not available)
            options =
                if Kernel.respond_to?(:validate_options)
                    Kernel.validate_options options, :force => false
                else
                    options.dup
                end

            if options[:force]
                @operating_system = nil
            else
                if !@operating_system.nil?
                    return @operating_system
                elsif Autoproj.has_config_key?('operating_system') && !(user_os = ENV['AUTOPROJ_OS'])
                    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) }
                           @operating_system = os
                           return os
                        end
                    end
                end
            end

            if user_os = ENV['AUTOPROJ_OS']
                if user_os.empty?
                    @operating_system = false
                else
                    names, versions = user_os.split(':')
                    @operating_system = [names.split(','), versions.split(',')]
                end
            else
                Autoproj.message "  autodetecting the operating system"
                lsb_name, lsb_versions = os_from_lsb
                if lsb_name
                    if lsb_name != "debian"
                        if File.exists?("/etc/debian_version")
                            @operating_system = [[lsb_name, "debian"], lsb_versions]
                        else
                            @operating_system = [[lsb_name], lsb_versions]
                        end
                    end
                end
            end

            if @operating_system.nil?
                # Need to do some heuristics unfortunately
                @operating_system =
                    if File.exists?('/etc/debian_version')
                        versions = [File.read('/etc/debian_version').strip]
                        if versions.first =~ /sid/
                            versions = ["unstable", "sid"]
                        end
                        if lsb_versions
                            lsb_versions.each { |v| versions << v if !versions.include?(v) }
                        end
                        [['debian'], versions]
                    elsif File.exists?('/etc/fedora-release')
                        release_string = File.read('/etc/fedora-release').strip
                        release_string =~ /Fedora release (\d+)/
                        version = $1
                        [['fedora'], [version]]
                    elsif File.exists?('/etc/gentoo-release')
                        release_string = File.read('/etc/gentoo-release').strip
                        release_string =~ /^.*([^\s]+)$/
                            version = $1
                        [['gentoo'], [version]]
                    elsif File.exists?('/etc/arch-release')
                        [['arch'], []]
                    elsif Autobuild.macos? 
                        version=`sw_vers | head -2 | tail -1`.split(":")[1]
                        [['darwin'], [version.strip]]
                    elsif Autobuild.windows?
                        [['windows'], []]
                    end
            end

            if !@operating_system
                return
            end

            # Normalize the names to lowercase
            names, versions = @operating_system[0], @operating_system[1]
            names    = names.map(&:downcase)
            versions = versions.map(&:downcase)
            if !versions.include?('default')
                versions += ['default']
            end

            @operating_system = [names, versions]
            Autoproj.change_option('operating_system', @operating_system, true)
            @operating_system
        end

        def self.os_from_lsb
            has_lsb_release = nil
            begin
                has_lsb_release = `which lsb_release`
                return unless $?.success?
            rescue Exception => e
                #seems which is not installes (e.g. on windows)
                return
            end

            distributor = `lsb_release -i -s`
            distributor = distributor.strip.downcase
            codename    = `lsb_release -c -s`.strip.downcase
            version     = `lsb_release -r -s`.strip.downcase

            return [distributor, [codename, version]]
        end

        # Return the list of packages that should be installed for +name+
        #
        # The following two simple return values are possible:
        #
        # nil:: +name+ has no definition
        # []:: +name+ has no definition on this OS and/or for this specific OS
        #      version
        #
        # In all other cases, the method returns an array of triples:
        #
        #   [package_handler, status, package_list]
        #
        # where status is FOUND_PACKAGES if +package_list+ is the list of
        # packages that should be installed with +package_handler+ for +name+,
        # and FOUND_NONEXISTENT if the nonexistent keyword is used for this OS
        # name and version. The package list might be empty even if status ==
        # FOUND_PACKAGES, for instance if the ignore keyword is used.
        def resolve_package(name)
            while OSDependencies.aliases.has_key?(name)
                name = OSDependencies.aliases[name]
            end

            os_names, os_versions = OSDependencies.operating_system
            os_names = os_names.dup
            os_names << 'default'

            dep_def = definitions[name]
            if !dep_def
                return nil
            end

            # Partition the found definition in all entries that are interesting
            # for us: toplevel os-independent package managers, os-dependent
            # package managers and os-independent package managers selected by
            # OS or version
            if !os_names
                os_names = ['default']
                os_versions = ['default']
            end

            package_handler_names = package_handlers.keys

            result = []
            found, pkg = partition_osdep_entry(name, dep_def, nil, (package_handler_names - os_package_handler.names), os_names, os_versions)
            if found
                result << [os_package_handler, found, pkg]
            end

            # NOTE: package_handlers might contain the same handler multiple
            # times (when a package manager has multiple names). That's why we
            # do a to_set.each
            package_handlers.each_value.to_set.each do |handler|
                found, pkg = partition_osdep_entry(name, dep_def, handler.names, [], os_names, os_versions)
                if found
                    result << [handler, found, pkg]
                end
            end

            # Recursive resolutions
            found, pkg = partition_osdep_entry(name, dep_def, ['osdep'], [], os_names, os_versions)
            if found
                pkg.each do |pkg_name|
                    result.concat(resolve_package(pkg_name))
                end
            end

            result.map do |handler, status, entries|
                if handler.respond_to?(:parse_package_entry)
                    [handler, status, entries.map { |s| handler.parse_package_entry(s) }]
                else
                    [handler, status, entries]
                end
            end
        end

        # Value returned by #resolve_package and #partition_osdep_entry in
        # the status field. See the documentation of these methods for more
        # information
        FOUND_PACKAGES = 0
        # Value returned by #resolve_package and #partition_osdep_entry in
        # the status field. See the documentation of these methods for more
        # information
        FOUND_NONEXISTENT = 1

        # Helper method that parses the osdep definition to split between the
        # parts needed for this OS and specific package handlers.
        #
        # +osdep_name+ is the name of the osdep. It is used to resolve explicit
        # mentions of a package handler, i.e. so that:
        #
        #   pkg: gem
        #
        # is resolved as the 'pkg' package to be installed by the 'gem' handler
        #
        # +dep_def+ is the content to parse. It can be a string, array or hash
        #
        # +handler_names+ is a list of entries that we are looking for. If it is
        # not nil, only entries that explicitely refer to +handler_names+ will
        # be browsed, i.e. in:
        #
        #   pkg:
        #       - test: 1
        #       - [a, list, of, packages]
        #
        #   partition_osdep_entry('osdep_name', data, ['test'], [])
        #
        # will ignore the toplevel list of packages, while
        #
        #   partition_osdep_entry('osdep_name', data, nil, [])
        #
        # will return it.
        #
        # +excluded+ is a list of branches that should be ignored during
        # parsing. It is used to e.g. ignore 'gem' when browsing for the main OS
        # package list. For instance, in
        #
        #   pkg:
        #       - test
        #       - [a, list, of, packages]
        #
        #   partition_osdep_entry('osdep_name', data, nil, ['test'])
        #
        # the returned value will only include the list of packages (and not
        # 'test')
        #
        # The rest of the arguments are array of strings that contain list of
        # keys to browse for (usually, the OS names and version)
        #
        # The return value is either nil if no packages were found, or a pair
        # [status, package_list] where status is FOUND_NONEXISTENT if the
        # nonexistent keyword was found, and FOUND_PACKAGES if either packages
        # or the ignore keyword were found.
        #
        def partition_osdep_entry(osdep_name, dep_def, handler_names, excluded, *keys)
            keys, *additional_keys = *keys
            keys ||= []
            found = false
            nonexistent = false
            result = []
            found_keys = Hash.new
            Array(dep_def).each do |names, values|
                if !values
                    # Raw array of packages. Possible only if we are not at toplevel
                    # (i.e. if we already have a handler)
                    if names == 'ignore'
                        found = true if !handler_names
                    elsif names == 'nonexistent'
                        nonexistent = true if !handler_names
                    elsif !handler_names && names.kind_of?(Array)
                        result.concat(result)
                        found = true
                    elsif names.respond_to?(:to_str)
                        if excluded.include?(names)
                        elsif handler_names && handler_names.include?(names)
                            result << osdep_name
                            found = true
                        elsif !handler_names
                            result << names
                            found = true
                        end
                    elsif names.respond_to?(:to_hash)
                        rec_found, rec_result = partition_osdep_entry(osdep_name, names, handler_names, excluded, keys, *additional_keys)
                        if rec_found == FOUND_NONEXISTENT then nonexistent = true
                        elsif rec_found == FOUND_PACKAGES then found = true
                        end
                        result.concat(rec_result)
                    end
                else
                    if names.respond_to?(:to_str) # names could be an array already
                        names = names.split(',')
                    end

                    if handler_names
                        if matching_name = handler_names.find { |k| names.any? { |name_tag| k == name_tag.downcase } }
                            rec_found, rec_result = partition_osdep_entry(osdep_name, values, nil, excluded, keys, *additional_keys)
                            if rec_found == FOUND_NONEXISTENT then nonexistent = true
                            elsif rec_found == FOUND_PACKAGES then found = true
                            end
                            result.concat(rec_result)
                        end
                    end

                    matching_name = keys.find { |k| names.any? { |name_tag| k == name_tag.downcase } }
                    if matching_name
                        rec_found, rec_result = partition_osdep_entry(osdep_name, values, handler_names, excluded, *additional_keys)
                        # We only consider the first highest-priority entry,
                        # regardless of whether it has some packages for us or
                        # not
                        idx = keys.index(matching_name)
                        if !rec_found
                            if !found_keys.has_key?(idx)
                                found_keys[idx] = nil
                            end
                        else
                            found_keys[idx] ||= [0, []]
                            found_keys[idx][0] += rec_found
                            found_keys[idx][1].concat(rec_result)
                        end
                    end
                end
            end
            first_entry = found_keys.keys.sort.first
            found_keys = found_keys[first_entry]
            if found_keys
                if found_keys[0] > 0
                    nonexistent = true
                else
                    found = true
                end
                result.concat(found_keys[1])
            end

            found =
                if nonexistent then FOUND_NONEXISTENT
                elsif found then FOUND_PACKAGES
                else false
                end

            return found, result
        end

        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
        def resolve_os_dependencies(dependencies)
            all_packages = []
            dependencies.each do |name|
                result = resolve_package(name)
                if !result
                    raise MissingOSDep.new, "there is no osdeps definition for #{name}"
                end

                if result.empty?
                    if OSDependencies.supported_operating_system?
                        os_names, os_versions = OSDependencies.operating_system
                        raise MissingOSDep.new, "there is an osdeps definition for #{name}, but not for this operating system and version (resp. #{os_names.join(", ")} and #{os_versions.join(", ")})"
                    end
                    result = [[os_package_handler, FOUND_PACKAGES, [name]]]
                end

                result.each do |handler, status, packages|
                    if status == FOUND_NONEXISTENT
                        raise MissingOSDep.new, "there is an osdep definition for #{name}, and it explicitely states that this package does not exist on your OS"
                    end
                    if entry = all_packages.find { |h, _| h == handler }
                        entry[1].concat(packages)
                    else
                        all_packages << [handler, packages]
                    end
                end
            end

            all_packages.delete_if do |handler, pkg|
                pkg.empty?
            end
            return all_packages
        end


        # Returns true if +name+ is an acceptable OS package for this OS and
        # version
        def has?(name)
            status = availability_of(name)
            status == AVAILABLE || status == IGNORE
        end

        # Value returned by #availability_of if the required package has no
        # definition
        NO_PACKAGE       = 0
        # Value returned by #availability_of if the required package has
        # definitions, but not for this OS name or version
        WRONG_OS         = 1
        # Value returned by #availability_of if the required package has
        # definitions, but the local OS is unknown
        UNKNOWN_OS       = 2
        # Value returned by #availability_of if the required package has
        # definitions, but the nonexistent keyword was used for this OS
        NONEXISTENT      = 3
        # Value returned by #availability_of if the required package is
        # available
        AVAILABLE        = 4
        # Value returned by #availability_of if the required package is
        # available, but no package needs to be installed to have it
        IGNORE           = 5

        # If +name+ is an osdeps that is available for this operating system,
        # returns AVAILABLE. Otherwise, returns one of:
        #
        # NO_PACKAGE:: the package has no definitions
        # WRONG_OS:: the package has a definition, but not for this OS
        # UNKNOWN_OS:: the package has a definition, but the local OS is unknown
        # NONEXISTENT:: the package has a definition, but the 'nonexistent'
        #               keyword was found for this OS
        # AVAILABLE:: the package is available for this OS
        # IGNORE:: the package is available for this OS, but no packages need to
        #          be installed for it
        def availability_of(name)
            resolved = resolve_package(name)
            if !resolved
                return NO_PACKAGE
            end

            if resolved.empty?
                if !OSDependencies.operating_system
                    return UNKNOWN_OS
                elsif !OSDependencies.supported_operating_system?
                    return AVAILABLE
                else return WRONG_OS
                end
            end

            resolved = resolved.delete_if { |_, status, list| status == FOUND_PACKAGES && list.empty? }
            failed = resolved.find_all do |handler, status, list|
                status == FOUND_NONEXISTENT
            end
            if failed.empty?
                if resolved.empty?
                    return IGNORE
                else
                    return AVAILABLE
                end
            else
                return NONEXISTENT
            end
        end

        HANDLE_ALL  = 'all'
        HANDLE_RUBY = 'ruby'
        HANDLE_OS   = 'os'
        HANDLE_NONE = 'none'

        def self.osdeps_mode_option_unsupported_os
            long_doc =<<-EOT
The software packages that autoproj will have to build may require other
prepackaged softwares (a.k.a. OS dependencies) to be installed (RubyGems
packages, packages from your operating system/distribution, ...). Autoproj is
usually able to install those automatically, but unfortunately your operating
system is not (yet) supported by autoproj's osdeps mechanism, it can only offer
you some limited support.

RubyGem packages are a cross-platform mechanism, and are therefore supported.
However, you will have to install the kind of OS dependencies (so-called OS
packages)

This option is meant to allow you to control autoproj's behaviour while handling
OS dependencies.

* if you say "ruby", the RubyGem packages will be installed.
* if you say "none", autoproj will not do anything related to the OS
  dependencies.

As any configuration value, the mode can be changed anytime by calling
an autoproj operation with the --reconfigure option (e.g. autoproj update
--reconfigure).

Finally, OS dependencies can be installed by calling "autoproj osdeps"
with the corresponding option (--all, --ruby, --os or --none). Calling
"autoproj osdeps" without arguments will also give you information as
to what you should install to compile the software successfully.

So, what do you want ? (ruby or none)
            EOT
            message = [ "Which prepackaged software (a.k.a. 'osdeps') should autoproj install automatically (ruby, none) ?", long_doc.strip ]

	    Autoproj.configuration_option 'osdeps_mode', 'string',
		:default => 'ruby',
		:doc => message,
                :possible_values => %w{ruby none},
                :lowercase => true
        end

        def self.osdeps_mode_option_supported_os
            long_doc =<<-EOT
The software packages that autoproj will have to build may require other
prepackaged softwares (a.k.a. OS dependencies) to be installed (RubyGems
packages, packages from your operating system/distribution, ...). Autoproj
is able to install those automatically for you.

Advanced users may want to control this behaviour. Additionally, the
installation of some packages require administration rights, which you may
not have. This option is meant to allow you to control autoproj's behaviour
while handling OS dependencies.

* if you say "all", it will install all packages automatically.
  This requires root access thru 'sudo'
* if you say "ruby", only the Ruby packages will be installed.
  Installing these packages does not require root access.
* if you say "os", only the OS-provided packages will be installed.
  Installing these packages requires root access.
* if you say "none", autoproj will not do anything related to the
  OS dependencies.

As any configuration value, the mode can be changed anytime by calling
an autoproj operation with the --reconfigure option (e.g. autoproj update
--reconfigure).

Finally, OS dependencies can be installed by calling "autoproj osdeps"
with the corresponding option (--all, --ruby, --os or --none).

So, what do you want ? (all, ruby, os or none)
            EOT
            message = [ "Which prepackaged software (a.k.a. 'osdeps') should autoproj install automatically (all, ruby, os, none) ?", long_doc.strip ]

	    Autoproj.configuration_option 'osdeps_mode', 'string',
		:default => 'all',
		:doc => message,
                :possible_values => %w{all ruby os none},
                :lowercase => true
        end

        def self.define_osdeps_mode_option
            if supported_operating_system?
                osdeps_mode_option_supported_os
            else
                osdeps_mode_option_unsupported_os
            end
        end

        def self.osdeps_mode_string_to_value(string)
            string = string.to_s.downcase
            case string
            when 'all'  then HANDLE_ALL
            when 'ruby' then HANDLE_RUBY
            when 'os'   then HANDLE_OS
            when 'none' then HANDLE_NONE
            else raise ArgumentError, "invalid osdeps mode string '#{string}'"
            end
        end

        # If set to true (the default), #install will try to remove the list of
        # already uptodate packages from the installed packages. Set to false to
        # install all packages regardless of their status
        attr_writer :filter_uptodate_packages

        # If set to true (the default), #install will try to remove the list of
        # already uptodate packages from the installed packages. Use
        # #filter_uptodate_packages= to set it to false to install all packages
        # regardless of their status
        def filter_uptodate_packages?
            !!@filter_uptodate_packages
        end

        # Override the osdeps mode
        def osdeps_mode=(value)
            @osdeps_mode = OSDependencies.osdeps_mode_string_to_value(value)
        end

        # Returns the osdeps mode chosen by the user
        def osdeps_mode
            # This has two uses. It caches the value extracted from the
            # AUTOPROJ_OSDEPS_MODE and/or configuration file. Moreover, it
            # allows to override the osdeps mode by using
            # OSDependencies#osdeps_mode=
            if @osdeps_mode
                return @osdeps_mode
            end

            @osdeps_mode = OSDependencies.osdeps_mode
        end

        def self.osdeps_mode
            while true
                mode =
                    if !Autoproj.has_config_key?('osdeps_mode') &&
                        mode_name = ENV['AUTOPROJ_OSDEPS_MODE']
                        begin OSDependencies.osdeps_mode_string_to_value(mode_name)
                        rescue ArgumentError
                            Autoproj.warn "invalid osdeps mode given through AUTOPROJ_OSDEPS_MODE (#{mode})"
                            nil
                        end
                    else
                        mode_name = Autoproj.user_config('osdeps_mode')
                        begin OSDependencies.osdeps_mode_string_to_value(mode_name)
                        rescue ArgumentError
                            Autoproj.warn "invalid osdeps mode stored in configuration file"
                            nil
                        end
                    end

                if mode
                    @osdeps_mode = mode
                    Autoproj.change_option('osdeps_mode', mode_name, true)
                    return mode
                end

                # Invalid configuration values. Retry
                Autoproj.reset_option('osdeps_mode')
                ENV['AUTOPROJ_OSDEPS_MODE'] = nil
            end
        end

        # The set of packages that have already been installed
        attr_reader :installed_packages

        def installs_os_packages?
            osdeps_mode == HANDLE_ALL || osdeps_mode == HANDLE_OS
        end

        def installs_ruby_packages?
            osdeps_mode == HANDLE_ALL || osdeps_mode == HANDLE_RUBY
        end


        # Requests the installation of the given set of packages
        def install(packages, package_osdeps = Hash.new)
            os_package_handler.enabled = installs_os_packages?
            os_package_handler.silent = self.silent?
            package_handlers['gem'].enabled = installs_ruby_packages?
            package_handlers.each_value do |v|
                v.silent = self.silent?
            end

            # Remove the set of packages that have already been installed 
            packages = packages.to_set - installed_packages
            return false if packages.empty?

            packages = resolve_os_dependencies(packages)
            packages = packages.map do |handler, list|
                if filter_uptodate_packages? && handler.respond_to?(:filter_uptodate_packages)
                    list = handler.filter_uptodate_packages(list)
                end

                if !list.empty?
                    [handler, list]
                end
            end.compact
            return false if packages.empty?

            # Install OS packages first, as the other package handlers might
            # depend on OS packages
            os_packages, other_packages = packages.partition { |handler, list| handler == os_package_handler }
            [os_packages, other_packages].each do |packages|
                packages.each do |handler, list|
                    handler.install(list)
                    @installed_packages |= list.to_set
                end
            end
            true
        end
    end
end


module Autoproj
    class InputError < RuntimeError; 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

    def self.reset_option(key)
        @user_config.delete(key)
    end

    def self.change_option(key, value, user_validated = false)
        @user_config[key] = [value, user_validated]
    end

    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
    end

    @declared_options = Hash.new
    def self.configuration_option(name, type, options, &validator)
        @declared_options[name] = BuildOption.new(name, type, options, validator)
    end

    def self.declared_option?(name)
	@declared_options.has_key?(name)
    end

    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
    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
    end

    def self.has_config_key?(name)
        @user_config.has_key?(name)
    end

    def self.load_config
        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
        end
    end

    class << self
        attr_accessor :reconfigure
    end
    def self.reconfigure?; @reconfigure end
end


module Autoproj
    class UserError < RuntimeError; end

    # OS-independent creation of symbolic links. Note that on windows, it only
    # works for directories
    def create_symlink(from, to)
        if Autobuild.windows?
            Dir.create_junction(to, from)
        else
            FileUtils.ln_sf from, to
        end
    end

    # Returns true if +path+ is part of an autoproj installation
    def self.in_autoproj_installation?(path)
        root_dir(File.expand_path(path))
        true
    rescue UserError
        false
    end

    # Forcefully sets the root directory
    #
    # This is mostly useful during bootstrapping (i.e. when the search would
    # fail)
    def self.root_dir=(dir)
        @root_dir = dir
    end

    # Returns the root directory of the current autoproj installation.
    #
    # If the current directory is not in an autoproj installation,
    # raises UserError.
    def self.root_dir(dir = Dir.pwd)
        if @root_dir
            return @root_dir
        end

        root_dir_rx =
            if Autobuild.windows? then /^[a-zA-Z]:\\\\$/
            else /^\/$/
            end

        while root_dir_rx !~ dir && !File.directory?(File.join(dir, "autoproj"))
            dir = File.dirname(dir)
        end
        if root_dir_rx =~ dir
            raise UserError, "not in a Autoproj installation"
        end

        #Preventing backslashed in path, that might be confusing on some path compares
        if Autobuild.windows?
            dir = dir.gsub(/\\/,'/')
        end
        dir
    end

    # Returns the configuration directory for this autoproj installation.
    #
    # If the current directory is not in an autoproj installation,
    # raises UserError.
    def self.config_dir
        File.join(root_dir, "autoproj")
    end

    class << self
        # The directory in which packages will be installed.
        #
        # If it is a relative path, it is relative to the root dir of the
        # installation.
        #
        # The default is "install"
        attr_reader :prefix

        # Change the value of 'prefix'
        def prefix=(new_path)
            @prefix = new_path
            Autoproj.change_option('prefix', new_path, true)
        end
    end
    @prefix = "install"

    # Returns the build directory (prefix) for this autoproj installation.
    #
    # If the current directory is not in an autoproj installation, raises
    # UserError.
    def self.build_dir
        File.expand_path(Autoproj.prefix, root_dir)
    end

    # Returns the path to the provided configuration file.
    #
    # If the current directory is not in an autoproj installation, raises
    # UserError.
    def self.config_file(file)
        File.join(config_dir, file)
    end

    # Run the provided command as user
    def self.run_as_user(*args)
        if !system(*args)
            raise "failed to run #{args.join(" ")}"
        end
    end

    # Run the provided command as root, using sudo to gain root access
    def self.run_as_root(*args)
        if !system('sudo', *args)
            raise "failed to run #{args.join(" ")} as root"
        end
    end

    # Return the directory in which remote package set definition should be
    # checked out
    def self.remotes_dir
        File.join(root_dir, ".remotes")
    end

    # Return the directory in which RubyGems package should be installed
    def self.gem_home
        ENV['AUTOPROJ_GEM_HOME'] || File.join(root_dir, ".gems")
    end

    def self.env_inherit(*names)
        Autobuild.env_inherit(*names)
    end

    # Find the given program in PATH. It raises ArgumentError if the program
    # can't be found
    def self.find_in_path(name)
        result = ENV['PATH'].split(':').find { |dir| File.file?(File.join(dir, name)) }
        if !result
            raise ArgumentError, "#{name} can not be found in PATH (#{ENV['PATH']})"
        end
        File.join(result, name)
    end

    # Initializes the environment variables to a "sane default"
    #
    # Use this in autoproj/init.rb to make sure that the environment will not
    # get polluted during the build.
    def self.set_initial_env
        Autobuild.env_inherit = false
        Autoproj.env_set 'RUBYOPT', "-rubygems"
        Autobuild.env_push_path 'GEM_PATH', Autoproj.gem_home
        Autobuild.env_push_path 'PATH', "#{Autoproj.gem_home}/bin", "/usr/local/bin", "/usr/bin", "/bin"
    end

    class << self
        attr_writer :shell_helpers
        def shell_helpers?; !!@shell_helpers end
    end
    @shell_helpers = true

    # Create the env.sh script in +subdir+. In general, +subdir+ should be nil.
    def self.export_env_sh(subdir = nil)
        # Make sure that we have the environment of all selected packages
        if Autoproj.manifest # we don't have a manifest if we are bootstrapping
            Autoproj::CmdLine.update_environment
        end

        filename = if subdir
               File.join(Autoproj.root_dir, subdir, ENV_FILENAME)
           else
               File.join(Autoproj.root_dir, ENV_FILENAME)
           end

        shell_dir = File.expand_path(File.join("..", "..", "shell"), File.dirname(__FILE__))
        if Autoproj.shell_helpers? && shell = ENV['SHELL']
            shell_kind = File.basename(shell)
            if shell_kind =~ /^\w+$/
                shell_file = File.join(shell_dir, "autoproj_#{shell_kind}")
                if File.exists?(shell_file)
                    Autoproj.message
                    Autoproj.message "autodetected the shell to be #{shell_kind}, sourcing autoproj shell helpers"
                    Autoproj.message "add \"Autoproj.shell_helpers = false\" in autoproj/init.rb to disable"
                    Autobuild.env_source_after(shell_file)
                end
            end
        end

        File.open(filename, "w") do |io|
            Autobuild.export_env_sh(io)
            if Autobuild.environment.has_key?('GEM_PATH')
                io.puts "export GEM_PATH=$GEM_PATH:#{Gem.default_path.join(":")}"
            end
        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.
    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
    end

    # Same as #load, but runs only if the file exists.
    def self.load_if_present(package_set, *path)
        path = File.join(*path)
        if File.file?(path)
            self.load(package_set, *path)
        end
    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 = [])
        Find.find(File.expand_path(dir)) do |name|
            next unless name =~ /\.so$/
            next if exclude_paths.find { |p| name =~ p }

            output = `ldd -r #{name} 2>&1`
            if output =~ /undefined symbol/
                Autoproj.message("  WARN: #{name} has undefined symbols", :magenta)
            end
        end
    end
end



# Override Autoproj.root_dir
module Autoproj
    def self.root_dir
        @root_dir
    end
    @root_dir = Dir.pwd
end

DEFS = <<EODEFS
---
none: ignore
ruby18:
  debian,ubuntu:
  - ruby1.8-dev
  - ruby1.8
  - rubygems1.8
  - ri1.8
  - libopenssl-ruby1.8
  - rake
  gentoo:
  - dev-lang/ruby:1.8
  - rake
  fedora:
    '15,16':
    - ruby
    - rubygems
  darwin:
  - ruby
  - rb-rake
  default: nonexistent
ruby19:
  debian:
  - ruby1.9.1
  - ruby1.9.1-dev
  - rubygems1.9.1
  - rake
  - rubygems-integration
  ubuntu:
  - ruby1.9.1
  - ruby1.9.1-dev
  - rubygems1.9.1
  - ri1.9.1
  - libopenssl-ruby1.9.1
  - rake
  gentoo:
  - dev-lang/ruby:1.9
  - rake
  arch:
  - ruby
  - rake
  fedora:
    '17':
    - ruby
    - rubygems
  darwin:
  - ruby19
  - rake
  default: nonexistent
ruby20: ignore
build-essential:
  debian,ubuntu: build-essential
  gentoo: ignore
  arch: ignore
  fedora: ignore
  darwin: ignore
autobuild: gem
autoproj: gem
git:
  debian:
    lenny: git
    default: git-core
  ubuntu: git-core
  gentoo: dev-vcs/git
  arch: git
  fedora: git
  darwin: git-core
svn:
  debian,ubuntu: subversion
  gentoo: dev-util/subversion
  arch: subversion
  fedora: subversion
  darwin: subversion
cmake:
  debian,ubuntu: cmake
  gentoo: dev-util/cmake
  arch: cmake
  fedora: cmake
  darwin: cmake
autotools:
  debian,ubuntu:
  - automake1.9
  - autoconf
  gentoo:
  - sys-devel/automake:1.9
  - sys-devel/autoconf
  arch: automake autoconf
  fedora:
  - automake
  - autoconf
  darwin:
  - automake
  - autoconf
lsb_release:
  debian,ubuntu: lsb-release
  gentoo: sys-apps/lsb-release
  arch: ignore
  fedora: redhat-lsb
  darwin: ignore
archive:
  debian,ubuntu:
  - tar
  - unzip
  gentoo:
  - app-arch/tar
  - app-arch/unzip
  arch:
  - tar
  - unzip
  fedora:
  - tar
  - unzip
  darwin:
  - gnutar
  - unzip
cvs:
  debian,ubuntu: cvs
  fedora: cvs
  darwin: cvs

EODEFS

Autoproj::OSDependencies.define_osdeps_mode_option
osdeps_mode = Autoproj::OSDependencies.osdeps_mode
ENV['AUTOPROJ_OSDEPS_MODE'] = osdeps_mode

# First thing we do is install a proper ruby environment. We make sure that we
# aren't installing any gems for now (as we need to choose the right gem
# binary) by setting Autobuild.programs['gem'] to nil
Autobuild.programs['gem'] = nil
Autoproj::OSDependencies.autodetect_ruby

osdeps_management = 
    if ENV['AUTOPROJ_DEFAULT_OSDEPS']
        Autoproj::OSDependencies.load(ENV['AUTOPROJ_DEFAULT_OSDEPS'])
    else
        Autoproj::OSDependencies.new(YAML.load(DEFS))
    end
osdeps_management.silent = false

begin
    STDERR.puts "autoproj: installing a proper Ruby environment (this can take a long time)"
    osdeps_management.install(['ruby'])
rescue Autoproj::ConfigError => e
    STDERR.puts "failed: #{e.message}"
    exit(1)
end

# Now try to find out the name of the gem binary
PACKAGES      = %w{lsb_release}

ENV['RUBYOPT']  = "-rubygems"
require 'rubygems'

STDERR.puts "autoproj: installing autoproj and its dependencies (this can take a long time)"
# First install the dependencies of autoproj, as we don't want them to be
# affected by the prerelease flag
begin
    osdeps_management.install(PACKAGES)
rescue Autoproj::ConfigError => e
    STDERR.puts "failed: #{e.message}"
    exit(1)
end

File.open('env.sh', 'w') do |io|
    io.write <<-EOSHELL
export RUBYOPT=-rubygems
export GEM_PATH=#{needed_gem_home}:$GEM_PATH
export GEM_HOME=#{needed_gem_home}
export PATH=$GEM_HOME/bin:$PATH
    EOSHELL
end

# If the user specifies "dev" on the command line, install the prerelease
# version of autoproj. If it is "localdev", expect him to install autoproj and
# run autoproj bootstrap manually.
if ARGV.first != "localdev"
    if ARGV.first == "dev"
        Autoproj::PackageManagers::GemManager.with_prerelease = true
        ARGV.shift
    end
    begin
        osdeps_management.install(['build-essential'])
        osdeps_management.install(['autobuild'])
        osdeps_management.install(['autoproj'])
    rescue Autoproj::ConfigError => e
        STDERR.puts "failed: #{e.message}"
        exit(1)
    end
    Autoproj::PackageManagers::GemManager.with_prerelease = false

    if !system('autoproj', 'bootstrap', *ARGV)
        STDERR.puts "ERROR: failed to run autoproj bootstrap #{ARGV.join(", ")}"
        exit 1
    end
end



More information about the Rock-dev mailing list