require 'highline' require 'utilrb/module/attr_predicate' module Autoproj class << self attr_accessor :verbose attr_reader :console attr_predicate :silent?, true end @silent = false @verbose = false @console = HighLine.new def self.progress(*args) if !silent? if args.empty? puts else STDERR.puts console.color(*args) end end end def self.color(*args) console.color(*args) end # Displays a warning message def self.warn(message) Autoproj.progress(" WARN: #{message}", :magenta) end module CmdLine def self.handle_automatic_osdeps if !Autoproj.has_config_key?('automatic_osdeps') && ENV['AUTOPROJ_AUTOMATIC_OSDEPS'] mode = ENV['AUTOPROJ_AUTOMATIC_OSDEPS'] mode = if mode == 'true' then Autoproj::OSDependencies::AUTOMATIC elsif mode == 'false' then Autoproj::OSDependencies::MANUAL elsif mode == 'wait' then Autoproj::OSDependencies::WAIT else Autoproj::OSDependencies::ASK end Autoproj.change_option('automatic_osdeps', mode, true) end short_doc = "Should autoproj handle the OS package installation automatically (yes, no, wait or ask) ?" long_doc =<<-EOT * if you say "yes", the OS dependencies will be handled by autoproj. * if you say "no", the list of OS dependencies that need to be installed will be listed, and autoproj will go on, assuming that you have installed them yourself. * if you say "ask", you will be prompted each time a package needs to be installed. * if you say "wait", autoproj will simply wait for you to press ENTER to continue after it prompted you for the dependencies. This value can be changed anytime by calling an autoproj operation with the --reconfigure option (e.g. autoproj update --reconfigure). Moreover, the "autoproj osdeps" call will always allow you to install OS dependencies through autoproj. EOT long_doc = long_doc.strip Autoproj.configuration_option 'automatic_osdeps', 'string', :default => 'yes', :doc => [short_doc, long_doc] do |value| begin Autoproj::BuildOption.validate_boolean(value, Hash.new) rescue Autoproj::InputError if value.to_s == "ask" :ask elsif value.to_s == "wait" :wait else raise Autoproj::InputError, "invalid value. Please answer 'yes', 'no', 'wait' or 'ask' -- without the quotes" end end end Autoproj.user_config('automatic_osdeps') end def self.initialize Autobuild::Reporting << Autoproj::Reporter.new if mail_config[:to] Autobuild::Reporting << Autobuild::MailReporter.new(mail_config) end Autoproj.load_config if Autoproj.has_config_key?('prefix') Autoproj.prefix = Autoproj.user_config('prefix') end # If we are under rubygems, check that the GEM_HOME is right ... if $LOADED_FEATURES.any? { |l| l =~ /rubygems/ } if ENV['GEM_HOME'] != Autoproj.gem_home raise ConfigError, "RubyGems is already loaded with a different GEM_HOME, make sure you are loading the right env.sh script !" end end # Set up some important autobuild parameters Autoproj.env_inherit 'PATH', 'PKG_CONFIG_PATH', 'RUBYLIB', 'LD_LIBRARY_PATH' Autoproj.env_set 'GEM_HOME', Autoproj.gem_home Autoproj.env_add 'PATH', File.join(Autoproj.gem_home, 'bin') Autoproj.env_set 'RUBYOPT', "-rubygems" Autobuild.prefix = Autoproj.build_dir Autobuild.srcdir = Autoproj.root_dir Autobuild.logdir = File.join(Autobuild.prefix, 'log') handle_automatic_osdeps ruby = RbConfig::CONFIG['RUBY_INSTALL_NAME'] if ruby != 'ruby' bindir = File.join(Autoproj.build_dir, 'bin') FileUtils.mkdir_p bindir File.open(File.join(bindir, 'ruby'), 'w') do |io| io.puts "#! /bin/sh" io.puts "exec #{ruby} \"$@\"" end FileUtils.chmod 0755, File.join(bindir, 'ruby') Autoproj.env_add 'PATH', bindir end # We load the local init.rb first so that the manifest loading # process can use options defined there for the autoproj version # control information (for instance) local_source = LocalSource.new Autoproj.load_if_present(local_source, local_source.local_dir, "init.rb") manifest_path = File.join(Autoproj.config_dir, 'manifest') Autoproj.manifest = Manifest.load(manifest_path) # Once thing left to do: handle the Autoproj.auto_update # configuration parameter. This has to be done here as the rest of # the configuration update/loading procedure rely on it. # # Namely, we must check if Autobuild.do_update has been explicitely # set to true or false. If that is the case, don't do anything. # Otherwise, set it to the value of auto_update (set in the # manifest) if Autobuild.do_update.nil? Autobuild.do_update = manifest.auto_update? end if Autoproj::CmdLine.update_os_dependencies.nil? Autoproj::CmdLine.update_os_dependencies = manifest.auto_update? end # Initialize the Autoproj.osdeps object by loading the default. The # rest is loaded later Autoproj.osdeps = Autoproj::OSDependencies.load_default end def self.update_myself return if !Autoproj::CmdLine.update_os_dependencies? # First things first, see if we need to update ourselves if Autoproj.osdeps.install(%w{autobuild autoproj}) # We updated autobuild or autoproj themselves ... Restart ! # # ...But first save the configuration (!) Autoproj.save_config require 'rbconfig' ruby = RbConfig::CONFIG['RUBY_INSTALL_NAME'] exec(ruby, $0, *ARGV) end end def self.load_configuration(silent = false) manifest = Autoproj.manifest # Load init.rb files. each_source must not load the source.yml file, as # init.rb may define configuration options that are used there manifest.each_source(false) do |source| Autoproj.load_if_present(source, source.local_dir, "init.rb") end # Load the required autobuild definitions if !silent Autoproj.progress("autoproj: loading ...", :bold) if !Autoproj.reconfigure? Autoproj.progress("run 'autoproj --reconfigure' to change configuration values", :bold) end end manifest.each_autobuild_file do |source, name| Autoproj.import_autobuild_file source, name end # Now, load the package's importer configurations (from the various # source.yml files) manifest.load_importers # We finished loading the configuration files. Not all configuration # is done (since we need to process the package setup blocks), but # save the current state of the configuration anyway. Autoproj.save_config # Loads OS package definitions once and for all Autoproj.load_osdeps_from_package_sets end def self.update_configuration manifest = Autoproj.manifest # Load the installation's manifest a first time, to check if we should # update it ... We assume that the OS dependencies for this VCS is already # installed (i.e. that the user did not remove it) if manifest.vcs manifest.update_yourself manifest_path = File.join(Autoproj.config_dir, 'manifest') Autoproj.manifest = manifest = Manifest.load(manifest_path) end source_os_dependencies = manifest.each_remote_source(false). inject(Set.new) do |set, source| set << source.vcs.type if !source.local? end # Update the remote sources if there are any if manifest.has_remote_sources? Autoproj.progress("autoproj: updating remote definitions of package sets", :bold) # If we need to install some packages to import our remote sources, do it if update_os_dependencies? Autoproj.osdeps.install(source_os_dependencies) end manifest.update_remote_sources Autoproj.progress end end def self.initial_package_setup manifest = Autoproj.manifest # Now starts a different stage of the whole build. Until now, we were # working on the whole package set. Starting from now, we need to build the # package sets based on the layout file # # First, we allow to user to specify packages based on disk paths, so # resolve those seen = Set.new manifest.each_package_set do |name, packages, enabled_packages| packages -= seen srcdir = File.join(Autoproj.root_dir, name) prefix = File.join(Autoproj.build_dir, name) logdir = File.join(prefix, "log") packages.each do |pkg_name| pkg = Autobuild::Package[pkg_name] pkg.srcdir = File.join(srcdir, pkg_name) pkg.prefix = prefix pkg.doc_target_dir = File.join(Autoproj.build_dir, 'doc', name, pkg_name) pkg.logdir = logdir end seen |= packages end # Now call the blocks that the user defined in the autobuild files. We do it # now so that the various package directories are properly setup manifest.packages.each_value do |pkg| if pkg.user_block pkg.user_block[pkg.autobuild] end end # Load the package's override files. each_source must not load the # source.yml file, as init.rb may define configuration options that are used # there manifest.each_source(false).to_a.reverse.each do |source| Autoproj.load_if_present(source, source.local_dir, "overrides.rb") end # We now have processed the process setup blocks. All configuration # should be done and we can save the configuration data. Autoproj.save_config end def self.display_sources(manifest) # We can't have the Manifest class load the source.yml file, as it # cannot resolve all constants. So we need to do it ourselves to get # the name ... sources = manifest.each_source(false).to_a if sources.empty? Autoproj.progress("autoproj: no package sets defined in autoproj/manifest", :bold, :red) else Autoproj.progress("autoproj: available package sets", :bold) manifest.each_source(false) do |source| source_yml = source.raw_description_file Autoproj.progress " #{source_yml['name']}" if source.local? Autoproj.progress " local source in #{source.local_dir}" else Autoproj.progress " from: #{source.vcs}" Autoproj.progress " local: #{source.local_dir}" end lines = [] source.each_package. map { |pkg| [pkg.name, manifest.package_manifests[pkg.name]] }. sort_by { |name, _| name }. each do |name, source_manifest| vcs_def = manifest.importer_definition_for(name) if source_manifest lines << [name, source_manifest.short_documentation] lines << ["", vcs_def.to_s] else lines << [name, vcs_def.to_s] end end w_col1, w_col2 = nil lines.each do |col1, col2| w_col1 = col1.size if !w_col1 || col1.size > w_col1 w_col2 = col2.size if !w_col2 || col2.size > w_col2 end puts " packages:" format = " | %-#{w_col1}s | %-#{w_col2}s |" lines.each do |col1, col2| puts(format % [col1, col2]) end end end end # Returns the set of packages that are actually selected based on what # the user gave on the command line def self.resolve_user_selection(selected_packages) manifest = Autoproj.manifest if selected_packages.empty? return manifest.default_packages end if selected_packages.empty? # no packages, terminate Autoproj.progress Autoproj.progress("autoproj: no packages defined", :red) exit 0 end selected_packages = selected_packages.to_set selected_packages = manifest.expand_package_selection(selected_packages) if selected_packages.empty? Autoproj.progress("autoproj: wrong package selection on command line", :red) exit 1 elsif Autoproj.verbose Autoproj.progress "will install #{selected_packages.to_a.join(", ")}" end selected_packages end def self.verify_package_availability(pkg_name) if reason = Autoproj.manifest.exclusion_reason(pkg_name) raise ConfigError, "#{pkg_name} is excluded from the build: #{reason}" end pkg = Autobuild::Package[pkg_name] if !pkg raise ConfigError, "#{pkg_name} does not seem to exist" end # Verify that its dependencies are there, and add # them to the selected_packages set so that they get # imported as well pkg.dependencies.each do |dep_name| begin verify_package_availability(dep_name) rescue ConfigError => e raise e, "#{pkg_name} depends on #{dep_name}, but #{e.message}" end end end def self.import_packages(selected_packages) selected_packages = selected_packages.dup. map do |pkg_name| pkg = Autobuild::Package[pkg_name] if !pkg raise ConfigError, "selected package #{pkg_name} does not exist" end pkg end.to_set # First, import all packages that are already there to make # automatic dependency discovery possible old_update_flag = Autobuild.do_update begin Autobuild.do_update = false packages = Autobuild::Package.each. find_all { |pkg_name, pkg| File.directory?(pkg.srcdir) }. delete_if { |pkg_name, pkg| Autoproj.manifest.excluded?(pkg_name) || Autoproj.manifest.ignored?(pkg_name) } packages.each do |_, pkg| pkg.import end ensure Autobuild.do_update = old_update_flag end known_available_packages = Set.new all_enabled_packages = Set.new package_queue = selected_packages.dup while !package_queue.empty? current_packages, package_queue = package_queue, Set.new current_packages = current_packages.sort_by(&:name) current_packages. delete_if { |pkg| all_enabled_packages.include?(pkg.name) } all_enabled_packages |= current_packages.map(&:name).to_set # Recursively check that no package in the selection depend on # excluded packages current_packages.each do |pkg| verify_package_availability(pkg.name) end # We import first so that all packages can export the # additional targets they provide. current_packages.each do |pkg| # If the package has no importer, the source directory must # be there if !pkg.importer && !File.directory?(pkg.srcdir) raise ConfigError, "#{pkg.name} has no VCS, but is not checked out in #{pkg.srcdir}" end Rake::Task["#{pkg.name}-import"].invoke manifest.load_package_manifest(pkg.name) end current_packages.each do |pkg| verify_package_availability(pkg.name) Rake::Task["#{pkg.name}-prepare"].invoke # Verify that its dependencies are there, and add # them to the selected_packages set so that they get # imported as well pkg.dependencies.each do |dep_name| package_queue << Autobuild::Package[dep_name] end end end if Autoproj.verbose Autoproj.progress "autoproj: finished importing packages" end if Autoproj::CmdLine.list_newest? fields = [] Rake::Task.tasks.each do |task| if task.kind_of?(Autobuild::SourceTreeTask) task.timestamp fields << ["#{task.name}:", task.newest_file, task.newest_time.to_s] end end field_sizes = fields.inject([0, 0, 0]) do |sizes, line| 3.times do |i| sizes[i] = [sizes[i], line[i].length].max end sizes end format = " %-#{field_sizes[0]}s %-#{field_sizes[1]}s at %-#{field_sizes[2]}s" fields.each do |line| Autoproj.progress(format % line) end end return all_enabled_packages end def self.build_packages(selected_packages, all_enabled_packages) if Autoproj::CmdLine.doc? Autoproj.progress("autoproj: building and installing documentation", :bold) else Autoproj.progress("autoproj: building and installing packages", :bold) end if Autoproj::CmdLine.update_os_dependencies? manifest.install_os_dependencies(all_enabled_packages) end if !selected_packages.empty? && !force_re_build_with_depends? if Autobuild.do_rebuild selected_packages.each do |pkg_name| Autobuild::Package[pkg_name].prepare_for_rebuild end Autobuild.do_rebuild = false elsif Autobuild.do_forced_build selected_packages.each do |pkg_name| Autobuild::Package[pkg_name].prepare_for_forced_build end Autobuild.do_forced_build = false end end Autobuild.apply(all_enabled_packages, "autoproj-build") end def self.manifest; Autoproj.manifest end def self.bootstrap?; !!@bootstrap end def self.only_status?; !!@only_status end def self.check?; !!@check end def self.manifest_update?; !!@manifest_update end def self.only_config?; !!@only_config end def self.update_os_dependencies?; !!@update_os_dependencies end class << self attr_accessor :update_os_dependencies attr_accessor :snapshot_dir attr_writer :list_newest end def self.display_configuration?; !!@display_configuration end def self.force_re_build_with_depends?; !!@force_re_build_with_depends end def self.partial_build?; !!@partial_build end def self.mail_config; @mail_config || Hash.new end def self.update_packages?; @mode == "update" || @mode == "envsh" || build? end def self.build?; @mode =~ /build/ end def self.doc?; @mode == "doc" end def self.snapshot?; @mode == "snapshot" end def self.list_newest?; @list_newest end def self.parse_arguments(args) @only_status = false @check = false @manifest_update = false @display_configuration = false @update_os_dependencies = true update_os_dependencies = nil @force_re_build_with_depends = false force_re_build_with_depends = nil @only_config = false @partial_build = false Autobuild.doc_errors = false Autobuild.do_doc = false Autobuild.only_doc = false Autobuild.do_update = nil do_update = nil mail_config = Hash.new # Parse the configuration options parser = OptionParser.new do |opts| opts.banner = <<-EOBANNER autoproj mode [options] where 'mode' is one of: -- Build build: import, build and install all packages that need it. A package or package set name can be given, in which case only this package and its dependencies will be taken into account. Example: autoproj build drivers/hokuyo fast-build: builds without updating and without considering OS dependencies full-build: updates packages and OS dependencies, and then builds force-build: triggers all build commands, i.e. don't be lazy like in "build". If packages are selected on the command line, only those packages will be affected unless the --with-depends option is used. rebuild: clean and then rebuild. If packages are selected on the command line, only those packages will be affected unless the --with-depends option is used. doc: generate and install documentation for packages that have some -- Status & Update envsh: update the env.sh script status: displays the state of the packages w.r.t. their source VCS list: list all available packages update: only import/update packages, do not build them update-config: only update the configuration -- Autoproj Configuration bootstrap: starts a new autoproj installation. Usage: autoproj bootstrap [manifest_url|source_vcs source_url opt1=value1 opt2=value2 ...] switch-config: change where the configuration should be taken from. Syntax: autoproj switch-config source_vcs source_url opt1=value1 opt2=value2 ... For example: autoproj switch-config git git://github.com/doudou/rubim-all.git branch=all -- Additional options: EOBANNER opts.on("--reconfigure", "re-ask all configuration options (build modes only)") do Autoproj.reconfigure = true end opts.on("--version", "displays the version and then exits") do STDERR.puts "autoproj v#{Autoproj::VERSION}" exit(0) end opts.on("--[no-]update", "[do not] update already checked-out packages (build modes only)") do |value| do_update = value end opts.on("--os", "displays the operating system as detected by autoproj") do os = OSDependencies.operating_system if !os puts "no information about that OS" else puts "name: #{os[0]}" puts "version:" os[1].each do |version_name| puts " #{version_name}" end end exit 0 end opts.on("--[no-]osdeps", "[do not] install prepackaged dependencies (build and update modes only)") do |value| update_os_dependencies = value end opts.on("--with-depends", "apply rebuild and force-build to both packages selected on the command line and their dependencies") do force_re_build_with_depends = true end opts.on("--list-newest", "for each source directory, list what is the newest file used by autoproj for dependency tracking") do Autoproj::CmdLine.list_newest = true end opts.on("--verbose", "verbose output") do Autoproj.verbose = true Autobuild.verbose = true Rake.application.options.trace = false end opts.on("--debug", "debugging output") do Autoproj.verbose = true Autobuild.verbose = true Rake.application.options.trace = true Autobuild.debug = true end opts.on('--nice NICE', Integer, 'nice the subprocesses to the given value') do |value| Autobuild.nice = value end opts.on("-h", "--help", "Show this message") do puts opts exit end opts.on("--mail-from EMAIL", String, "From: field of the sent mails") do |from_email| mail_config[:from] = from_email end opts.on("--mail-to EMAILS", String, "comma-separated list of emails to which the reports should be sent") do |emails| mail_config[:to] ||= [] mail_config[:to] += emails.split(',') end opts.on("--mail-subject SUBJECT", String, "Subject: field of the sent mails") do |subject_email| mail_config[:subject] = subject_email end opts.on("--mail-smtp HOSTNAME", String, " address of the mail server written as hostname[:port]") do |smtp| raise "invalid SMTP specification #{smtp}" unless smtp =~ /^([^:]+)(?::(\d+))?$/ mail_config[:smtp] = $1 mail_config[:port] = Integer($2) if $2 && !$2.empty? end opts.on("--mail-only-errors", "send mail only on errors") do mail_config[:only_errors] = true end end parser.parse!(args) @mail_config = mail_config @mode = args.shift unknown_mode = catch(:unknown) do handle_mode(@mode, args) end if unknown_mode STDERR.puts "unknown mode #{@mode}" STDERR.puts "run autoproj --help for more documentation" exit(1) end selection = args.dup @partial_build = !selection.empty? @update_os_dependencies = update_os_dependencies if !update_os_dependencies.nil? @force_re_build_with_depends = force_re_build_with_depends if !force_re_build_with_depends.nil? Autobuild.do_update = do_update if !do_update.nil? selection end def self.handle_mode(mode, remaining_args) case mode when "update-sets" Autoproj.warn("update-sets is deprecated. Use update-config instead") mode = "update-config" when "list-sets" Autoproj.warn("list-sets is deprecated. Use list-config instead") mode = "list-config" end case mode when "bootstrap" @bootstrap = true bootstrap(*remaining_args) remaining_args.clear @display_configuration = true Autobuild.do_build = false Autobuild.do_update = false @update_os_dependencies = false @only_config = true when "switch-config" # We must switch to the root dir first, as it is required by the # configuration switch code. This is acceptable as long as we # quit just after the switch Dir.chdir(Autoproj.root_dir) switch_config(*remaining_args) exit 0 when "build" when "force-build" Autobuild.do_forced_build = true when "rebuild" Autobuild.do_rebuild = true when "fast-build" Autobuild.do_update = false @update_os_dependencies = false when "snapshot" @snapshot_dir = remaining_args.shift Autobuild.do_update = false Autobuild.do_build = false @update_os_dependencies = false when "full-build" Autobuild.do_update = true @update_os_dependencies = true when "update" Autobuild.do_update = true @update_os_dependencies = true Autobuild.do_build = false when "check" Autobuild.do_update = false @update_os_dependencies = false Autobuild.do_build = false @check = true when "manifest-update" Autobuild.do_update = false @update_os_dependencies = false Autobuild.do_build = false @manifest_update = true when "osdeps" Autobuild.do_update = false @update_os_dependencies = true Autobuild.do_build = false Autoproj::OSDependencies.force_osdeps = true when "status" @only_status = true Autobuild.do_update = false @update_os_dependencies = false when "envsh" Autobuild.do_build = false Autobuild.do_update = false @update_os_dependencies = false when "update-config" @only_config = true Autobuild.do_update = true @update_os_dependencies = false Autobuild.do_build = false when "list-config" @only_config = true @display_configuration = true Autobuild.do_update = false @update_os_dependencies = false when "doc" Autobuild.do_update = false @update_os_dependencies = false Autobuild.do_doc = true Autobuild.only_doc = true else throw :unknown, true end nil end def self.display_status(packages) last_was_in_sync = false packages.each do |pkg| lines = [] pkg_name = if pkg.respond_to?(:text_name) pkg.text_name else pkg.autoproj_name end if !pkg.importer.respond_to?(:status) lines << Autoproj.color(" the #{pkg.importer.class.name.gsub(/.*::/, '')} importer does not support status display", :bold, :red) elsif !File.directory?(pkg.srcdir) lines << Autoproj.color(" is not imported yet", :magenta) else status = pkg.importer.status(pkg) if status.uncommitted_code lines << Autoproj.color(" contains uncommitted modifications", :red) end case status.status when Autobuild::Importer::Status::UP_TO_DATE if !status.uncommitted_code if last_was_in_sync STDERR.print ", #{pkg_name}" else STDERR.print pkg_name end last_was_in_sync = true next else lines << Autoproj.color(" local and remote are in sync", :green) end when Autobuild::Importer::Status::ADVANCED lines << Autoproj.color(" local contains #{status.local_commits.size} commit that remote does not have:", :magenta) status.local_commits.each do |line| lines << Autoproj.color(" #{line}", :magenta) end when Autobuild::Importer::Status::SIMPLE_UPDATE lines << Autoproj.color(" remote contains #{status.remote_commits.size} commit that local does not have:", :magenta) status.remote_commits.each do |line| lines << Autoproj.color(" #{line}", :magenta) end when Autobuild::Importer::Status::NEEDS_MERGE lines << Autoproj.color(" local and remote have diverged with respectively #{status.local_commits.size} and #{status.remote_commits.size} commits each", :magenta) lines << " -- local commits --" status.local_commits.each do |line| lines << Autoproj.color(" #{line}", :magenta) end lines << " -- remote commits --" status.remote_commits.each do |line| lines << Autoproj.color(" #{line}", :magenta) end end end if last_was_in_sync Autoproj.progress(": local and remote are in sync", :green) end last_was_in_sync = false STDERR.print "#{pkg_name}:" if lines.size == 1 Autoproj.progress lines.first else Autoproj.progress Autoproj.progress lines.join("\n") end end if last_was_in_sync Autoproj.progress(": local and remote are in sync", :green) end end def self.status(packages) console = Autoproj.console sources = Autoproj.manifest.each_configuration_source. map do |vcs, text_name, pkg_name, local_dir| Autoproj::Manifest.create_autobuild_package(vcs, text_name, pkg_name, local_dir) end if !sources.empty? Autoproj.progress("autoproj: displaying status of configuration", :bold) display_status(sources) STDERR.puts end Autoproj.progress("autoproj: displaying status of packages", :bold) packages = packages.sort.map do |pkg_name| Autobuild::Package[pkg_name] end display_status(packages) end def self.switch_config(*args) Autoproj.load_config if Autoproj.has_config_key?('manifest_source') vcs = Autoproj.normalize_vcs_definition(Autoproj.user_config('manifest_source')) end if args.first =~ /^(\w+)=/ # First argument is an option string, we are simply setting the # options without changing the type/url type, url = vcs.type, vcs.url else type, url = args.shift, args.shift end options = args url = VCSDefinition.to_absolute_url(url) if vcs && (vcs.type == type && vcs.url == url) # Don't need to do much: simply change the options and save the config # file, the VCS handler will take care of the actual switching else # We will have to delete the current autoproj directory. Ask the user. opt = Autoproj::BuildOption.new("delete current config", "boolean", Hash[:default => "false", :doc => "delete the current configuration ? (required to switch)"], nil) return if !opt.ask(nil) Dir.chdir(Autoproj.root_dir) do do_switch_config(true, type, url, *options) end end # And now save the options: note that we keep the current option set even # though we switched configuration. This is not a problem as undefined # options will not be reused # # TODO: cleanup the options to only keep the relevant ones vcs_def = Hash['type' => type, 'url' => url] options.each do |opt| opt_name, opt_val = opt.split '=' vcs_def[opt_name] = opt_val end # Validate the option hash, just in case Autoproj.normalize_vcs_definition(vcs_def) # Save the new options Autoproj.change_option('manifest_source', vcs_def, true) Autoproj.save_config end def self.do_switch_config(delete_current, type, url, *options) vcs_def = Hash.new vcs_def[:type] = type vcs_def[:url] = VCSDefinition.to_absolute_url(url) while !options.empty? name, value = options.shift.split("=") vcs_def[name] = value end vcs = Autoproj.normalize_vcs_definition(vcs_def) # Install the OS dependencies required for this VCS handle_automatic_osdeps osdeps = Autoproj::OSDependencies.load_default osdeps.install([vcs.type]) # Now check out the actual configuration config_dir = File.join(Dir.pwd, "autoproj") if delete_current # Find a backup name for it backup_base_name = backup_name = "#{config_dir}.bak" index = 0 while File.directory?(backup_name) backup_name = "#{backup_base_name}-#{index}.bak" index += 1 end FileUtils.mv config_dir, backup_name end Autoproj::Manifest.update_source(vcs, "autoproj main configuration", 'autoproj_config', config_dir) # Now write it in the config file File.open(File.join(Autoproj.config_dir, "config.yml"), "a") do |io| io.puts <<-EOTEXT manifest_source: type: #{vcs_def.delete(:type)} url: #{vcs_def.delete(:url)} #{vcs_def.map { |k, v| "#{k}: #{v}" }.join("\n ")} EOTEXT end rescue Exception if backup_name FileUtils.rm_rf config_dir if config_dir FileUtils.mv backup_name, config_dir end raise ensure if backup_name FileUtils.rm_rf backup_name end end def self.bootstrap(*args) if File.exists?(File.join("autoproj", "manifest")) raise ConfigError, "this installation is already bootstrapped. Remove the autoproj directory if it is not the case" end Autobuild.logdir = File.join('build', 'log') # Check if GEM_HOME is set. If it is the case, assume that we are # bootstrapping from another installation directory and start by # copying the .gems directory # # We don't use Autoproj.gem_home there as we might not be in an # autoproj directory at all gem_home = ENV['AUTOPROJ_GEM_HOME'] || File.join(Dir.pwd, ".gems") if ENV['GEM_HOME'] && Autoproj.in_autoproj_installation?(ENV['GEM_HOME']) && ENV['GEM_HOME'] != gem_home if !File.exists?(gem_home) Autoproj.progress "autoproj: reusing bootstrap from #{File.dirname(ENV['GEM_HOME'])}" FileUtils.cp_r ENV['GEM_HOME'], gem_home end ENV['GEM_HOME'] = gem_home Autoproj.progress "restarting bootstrapping from #{Dir.pwd}" require 'rbconfig' ruby = RbConfig::CONFIG['RUBY_INSTALL_NAME'] exec ruby, $0, *ARGV end # If we are not getting the installation setup from a VCS, copy the template # files if args.empty? || args.size == 1 sample_dir = File.expand_path(File.join("..", "..", "samples"), File.dirname(__FILE__)) FileUtils.cp_r File.join(sample_dir, "autoproj"), "autoproj" end if args.size == 1 # the user asks us to download a manifest manifest_url = args.first Autoproj.progress("autoproj: downloading manifest file #{manifest_url}", :bold) manifest_data = begin open(manifest_url) { |file| file.read } rescue raise ConfigError, "cannot read #{manifest_url}, did you mean 'autoproj bootstrap VCSTYPE #{manifest_url}' ?" end File.open(File.join(Autoproj.config_dir, "manifest"), "w") do |io| io.write(manifest_data) end elsif args.size >= 2 # is a VCS definition for the manifest itself ... type, url, *options = *args url = VCSDefinition.to_absolute_url(url, Dir.pwd) do_switch_config(false, type, url, *options) end # Finally, generate an env.sh script File.open('env.sh', 'w') do |io| io.write <<-EOSHELL export RUBYOPT=-rubygems export GEM_HOME=#{Autoproj.gem_home} export PATH=$GEM_HOME/bin:$PATH EOSHELL end end def self.missing_dependencies(pkg) manifest = Autoproj.manifest.package_manifests[pkg.name] all_deps = pkg.dependencies.map do |dep_name| dep_pkg = Autobuild::Package[dep_name] if dep_pkg then dep_pkg.name else dep_name end end if manifest declared_deps = manifest.each_dependency.to_a missing = all_deps - declared_deps else missing = all_deps end missing.to_set.to_a.sort end def self.check(packages) packages.sort.each do |pkg_name| result = [] pkg = Autobuild::Package[pkg_name] manifest = Autoproj.manifest.package_manifests[pkg.name] # Check if the manifest contains rosdep tags # if manifest && !manifest.each_os_dependency.to_a.empty? # result << "uses rosdep tags, convert them to normal tags" # end missing = missing_dependencies(pkg) if !missing.empty? result << "missing dependency tags for: #{missing.join(", ")}" end if !result.empty? Autoproj.progress pkg.name Autoproj.progress " #{result.join("\n ")}" end end end def self.manifest_update(packages) packages.sort.each do |pkg_name| pkg = Autobuild::Package[pkg_name] manifest = Autoproj.manifest.package_manifests[pkg.name] xml = if !manifest Nokogiri::XML::Document.parse("") do |c| c.noblanks end else manifest.xml.dup end # Add missing dependencies missing = missing_dependencies(pkg) if !missing.empty? package_node = xml.xpath('/package').to_a.first missing.each do |pkg_name| node = Nokogiri::XML::Node.new("depend", xml) node['package'] = pkg_name package_node.add_child(node) end modified = true end # Save the manifest back if modified path = File.join(pkg.srcdir, 'manifest.xml') File.open(path, 'w') do |io| io.write xml.to_xml end if !manifest Autoproj.progress "created #{path}" else Autoproj.progress "modified #{path}" end end end end def self.snapshot(target_dir, packages) # First, copy the configuration directory to create target_dir if File.exists?(target_dir) raise ArgumentError, "#{target_dir} already exists" end FileUtils.cp_r Autoproj.config_dir, target_dir # Now, create snapshot information for each of the packages version_control = [] packages.each do |package_name| package = Autobuild::Package[package_name] importer = package.importer if !importer Autoproj.progress "cannot snapshot #{package_name} as it has no importer" next elsif !importer.respond_to?(:snapshot) Autoproj.progress "cannot snapshot #{package_name} as the #{importer.class} importer does not support it" next end vcs_info = importer.snapshot(package, target_dir) if vcs_info version_control << Hash[package_name, vcs_info] end end overrides_path = File.join(target_dir, 'overrides.yml') overrides = if File.exists?(overrides_path) YAML.load(File.read(overrides_path)) else Hash.new end if overrides['overrides'] overrides['overrides'].concat(version_control) else overrides['overrides'] = version_control end File.open(overrides_path, 'w') do |io| io.write YAML.dump(overrides) end end end end