bin/autoproj_bootstrap in autoproj-2.0.0.rc5 vs bin/autoproj_bootstrap in autoproj-2.0.0.rc6

- old
+ new

@@ -24,22 +24,22 @@ attr_reader :root_dir # Content of the Gemfile generated to install autoproj itself attr_accessor :gemfile # The environment that is passed to the bundler installs attr_reader :env + # The configuration hash + attr_reader :config def initialize(root_dir) @root_dir = root_dir if File.file?(autoproj_gemfile_path) @gemfile = File.read(autoproj_gemfile_path) else @gemfile = default_gemfile_contents end - @private_bundler = false - @private_autoproj = false - @private_gems = false + load_config @local = false @env = self.class.clean_env end def self.clean_env @@ -47,21 +47,31 @@ env['RUBYLIB'] = [] env['GEM_PATH'] = [] %w{PATH GEM_HOME}.each do |name| env[name] = sanitize_env(ENV[name] || "") end - env['BUNDLE_GEMFILE'] = nil + env['BUNDLE_GEMFILE'] = [] env end def env_for_child env.inject(Hash.new) do |h, (k, v)| h[k] = (v.join(File::PATH_SEPARATOR) if v && !v.empty?) h end end + def apply_env(env) + env.each do |k, v| + if v + ENV[k] = v + else + ENV.delete(k) + end + end + end + def self.sanitize_env(value) value.split(File::PATH_SEPARATOR). find_all { |p| !in_workspace?(p) } end @@ -76,11 +86,10 @@ return false end def dot_autoproj; File.join(root_dir, '.autoproj') end - def bin_dir; File.join(dot_autoproj, 'bin') end def bundler_install_dir; File.join(dot_autoproj, 'bundler') end def autoproj_install_dir; File.join(dot_autoproj, 'autoproj') end # The path to the gemfile used to install autoproj def autoproj_gemfile_path; File.join(autoproj_install_dir, 'Gemfile') end def autoproj_config_path; File.join(dot_autoproj, 'config.yml') end @@ -91,20 +100,26 @@ def local=(flag); @local = flag end # Whether bundler should be installed locally in {#dot_autoproj} def private_bundler?; @private_bundler end # (see #private_bundler?) - def private_bundler=(flag); @private_bundler = flag end + def private_bundler=(flag); @private_bundler = !!flag end # Whether autoproj should be installed locally in {#dot_autoproj} def private_autoproj?; @private_autoproj end # (see #private_autoproj?) - def private_autoproj=(flag); @private_autoproj = flag end + def private_autoproj=(flag); @private_autoproj = !!flag end # Whether bundler should be installed locally in the workspace # prefix directory def private_gems?; @private_gems end # (see #private_gems?) - def private_gems=(flag); @private_gems = flag end + def private_gems=(flag); @private_gems = !!flag end + # Whether autoproj should prefer OS-independent packages over their + # OS-packaged equivalents (e.g. the thor gem vs. the ruby-thor + # Debian package) + def prefer_indep_over_os_packages?; @prefer_indep_over_os_packages end + # (see #private_gems?) + def prefer_indep_over_os_packages=(flag); @prefer_indep_over_os_packages = !!flag end def guess_gem_program ruby_bin = RbConfig::CONFIG['RUBY_INSTALL_NAME'] ruby_bindir = RbConfig::CONFIG['bindir'] @@ -156,10 +171,13 @@ @gemfile = default_gemfile_contents(version) end opt.on '--gemfile=PATH', String, 'use the given Gemfile to install autoproj instead of the default' do |path| @gemfile = File.read(path) end + opt.on '--prefer-os-independent-packages', 'prefer OS-independent packages (such as a RubyGem) over their OS-packaged equivalent (e.g. the thor gem vs. the ruby-thor debian package)' do + @prefer_indep_over_os_packages = true + end end options.parse(ARGV) end def install_bundler @@ -176,95 +194,86 @@ if !result STDERR.puts "FATAL: failed to install bundler in #{dot_autoproj}" exit 1 end - env['GEM_PATH'] << bundler_install_dir - env['PATH'] << File.join(bundler_install_dir, 'bin') - File.join(bin_dir, 'bundler') + env['PATH'].unshift File.join(bundler_install_dir, 'bin') + File.join(bundler_install_dir, 'bin', 'bundler') end - def save_env_sh - env = Autobuild::Environment.new - env.prepare + def find_bundler + self.env['PATH'].unshift gem_bindir + clean_env = env_for_child + Gem.paths = Hash[ + 'GEM_HOME' => clean_env['GEM_HOME'] || Gem.default_dir, + 'GEM_PATH' => clean_env['GEM_PATH'] || nil + ] - %w{GEM_HOME GEM_PATH}.each do |name| - value = self.env[name] - if value.empty? - env.unset name - else - env.set name, *value + bundler = find_in_clean_path('bundler') + if !bundler + clean_path = env_for_child['PATH'] + STDERR.puts "cannot find 'bundler' in PATH=#{clean_path}" + STDERR.puts "installing it now ..." + result = system(clean_env, Gem.ruby, '-S', 'gem', 'install', 'bundler') + if !result + if ENV['PATH'] != clean_path + STDERR.puts " it appears that you already have some autoproj-generated env.sh loaded" + STDERR.puts " - if you are running 'autoproj upgrade', please contact the autoproj author at https://github.com/rock-core/autoproj/issues/new" + STDERR.puts " - if you are running an install, try again in a console where the env.sh is not loaded" + exit 1 + else + STDERR.puts " the recommended action is to install it manually first by running 'gem install bundler'" + STDERR.puts " or call this command again with --private-bundler to have it installed in the workspace" + exit 1 + end end - end - env.push_path 'PATH', File.join(autoproj_install_dir, 'bin') - if private_autoproj? - env.push_path 'GEM_PATH', autoproj_install_dir + bundler = find_in_clean_path('bundler') + if !bundler + STDERR.puts "FATAL: gem install bundler returned successfully, but still cannot find bundler" + STDERR.puts "FATAL: in #{clean_path}" + end end - # Generate environment files right now, we can at least use bundler - File.open(File.join(dot_autoproj, 'env.sh'), 'w') do |io| - env.export_env_sh(io) - end - - File.open(File.join(root_dir, 'env.sh'), 'w') do |io| - io.write <<-EOSHELL -source "#{File.join(dot_autoproj, 'env.sh')}" -export AUTOPROJ_CURRENT_ROOT=#{root_dir} - EOSHELL - end + bundler end - def save_gemfile - FileUtils.mkdir_p File.dirname(autoproj_gemfile_path) - File.open(autoproj_gemfile_path, 'w') do |io| - io.write gemfile - end - end - - ENV_BUNDLE_GEMFILE_RX = /^(\s*ENV\[['"]BUNDLE_GEMFILE['"]\]\s*)(?:\|\|)?=/ - def install_autoproj(bundler) # Force bundler to update. If the user does not want this, let him specify a # Gemfile with tighter version constraints - lockfile = File.join(File.dirname(autoproj_gemfile_path), 'Gemfile.lock') + lockfile = File.join(File.dirname(autoproj_install_dir), 'Gemfile.lock') if File.exist?(lockfile) FileUtils.rm lockfile end opts = Array.new - opts << '--local' if local? + clean_env = env_for_child.merge('BUNDLE_GEMFILE' => nil) - env = env_for_child + opts << '--local' if local? if private_autoproj? - env = env.merge( - 'GEM_PATH' => bundler_install_dir, - 'GEM_HOME' => nil) + clean_env['GEM_PATH'] = (bundler_install_dir if private_bundler?) + clean_env['GEM_HOME'] = nil opts << "--clean" << "--path=#{autoproj_install_dir}" end - binstubs_path = File.join(autoproj_install_dir, 'bin') - - result = system(env, + result = system(clean_env, Gem.ruby, bundler, 'install', "--gemfile=#{autoproj_gemfile_path}", + "--shebang=#{Gem.ruby}", "--binstubs=#{binstubs_path}", - *opts) + *opts, chdir: autoproj_install_dir) + if !result STDERR.puts "FATAL: failed to install autoproj in #{dot_autoproj}" exit 1 end # Now tune the binstubs to force the usage of the autoproj # gemfile. Otherwise, they get BUNDLE_GEMFILE from the # environment by default Dir.glob(File.join(binstubs_path, '*')) do |path| next if !File.file?(path) - # Do NOT do that for bundler, otherwise it will fail with an - # "already loaded gemfile" message once we e.g. try to do - # 'bundler install --gemfile=NEW_GEMFILE' - next if File.basename(path) == 'bundler' lines = File.readlines(path) matched = false filtered = lines.map do |l| matched ||= (ENV_BUNDLE_GEMFILE_RX === l) @@ -276,30 +285,57 @@ File.open(path, 'w') do |io| io.write filtered.join("") end end - env['PATH'] << File.join(autoproj_install_dir, 'bin') + env['PATH'].unshift File.join(autoproj_install_dir, 'bin') if private_autoproj? - env['GEM_PATH'] << autoproj_install_dir + env['GEM_PATH'].unshift autoproj_install_dir end + ensure + if binstubs_path + FileUtils.rm_f File.join(binstubs_path, 'bundler') + end end - def update_configuration - if File.exist?(autoproj_config_path) - config = YAML.load(File.read(autoproj_config_path)) || Hash.new - else - config = Hash.new + def save_env_sh(*vars) + env = Autobuild::Environment.new + env.prepare + vars.each do |kv| + k, *v = kv.split("=") + v = v.join("=") + + if v.empty? + env.unset k + else + env.set k, *v.split(File::PATH_SEPARATOR) + end end - config['private_bundler'] = private_bundler? - config['private_autoproj'] = private_autoproj? - config['private_gems'] = private_gems? - File.open(autoproj_config_path, 'w') do |io| - YAML.dump(config, io) + # Generate environment files right now, we can at least use bundler + File.open(File.join(dot_autoproj, 'env.sh'), 'w') do |io| + env.export_env_sh(io) end + + # And now the root envsh + env = Autobuild::Environment.new + env.source_before File.join(dot_autoproj, 'env.sh') + env.set('AUTOPROJ_CURRENT_ROOT', root_dir) + File.open(File.join(root_dir, 'env.sh'), 'w') do |io| + env.export_env_sh(io) + end end + def save_gemfile + FileUtils.mkdir_p File.dirname(autoproj_gemfile_path) + File.open(autoproj_gemfile_path, 'w') do |io| + io.write gemfile + end + end + + ENV_BUNDLE_GEMFILE_RX = /^(\s*ENV\[['"]BUNDLE_GEMFILE['"]\]\s*)(?:\|\|)?=/ + + def find_in_clean_path(command) clean_path = env_for_child['PATH'].split(File::PATH_SEPARATOR) clean_path.each do |p| full_path = File.join(p, command) if File.file?(full_path) @@ -307,50 +343,30 @@ end end nil end - def find_bundler - clean_env = env_for_child - Gem.paths = Hash[ - 'GEM_HOME' => clean_env['GEM_HOME'] || Gem.default_dir, - 'GEM_PATH' => clean_env['GEM_PATH'] || nil - ] + # The path of the bin/ folder for installed gems + def gem_bindir + return @gem_bindir if @gem_bindir + # Here, we're getting into the esotheric # # The problem is that e.g. Ubuntu and Debian install an # operating_system.rb file that sets proper OS defaults. Some # autoproj installs have it in their RUBYLIB but should not # because of limitations of autoproj 1.x. This leads to # Gem.bindir being *not* valid for subprocesses # # So, we're calling 'gem' as a subcommand to discovery the # actual bindir - bindir = IO.popen(clean_env, [Gem.ruby, '-e', 'puts Gem.bindir']).read + bindir = IO.popen(env_for_child, [Gem.ruby, '-e', 'puts Gem.bindir']).read if bindir - env['PATH'].unshift bindir.chomp + @gem_bindir = bindir.chomp else - STDERR.puts "FATAL: cannot run #{Gem.ruby} -e 'puts Gem.bindir'" - exit 1 + raise "FATAL: cannot run #{Gem.ruby} -e 'puts Gem.bindir'" end - - bundler = find_in_clean_path('bundler') - if !bundler - clean_path = env_for_child['PATH'] - STDERR.puts "FATAL: cannot find 'bundler' in PATH=#{clean_path}" - if ENV['PATH'] != clean_path - STDERR.puts " it appears that you already have some autoproj-generated env.sh loaded" - STDERR.puts " - if you are running 'autoproj upgrade', please contact the autoproj author at https://github.com/rock-core/autoproj/issues/new" - STDERR.puts " - if you are running an install, try again in a console where the env.sh is not loaded" - exit 1 - else - STDERR.puts " the recommended action is to install it manually first by running 'gem install bundler'" - STDERR.puts " or call this command again with --private-bundler to have it installed in the workspace" - exit 1 - end - end - bundler end def install if private_bundler? puts "Installing bundler in #{bundler_install_dir}" @@ -362,29 +378,56 @@ save_gemfile puts "Installing autoproj in #{dot_autoproj}" install_autoproj(bundler) end - # Actually perform the install - def run(stage2: false) - if stage2 - require 'autobuild' - save_env_sh - else - install + def load_config + v1_config_path = File.join(root_dir, 'autoproj', 'config.yml') + + config = Hash.new + if File.file?(v1_config_path) + config.merge!(YAML.load(File.read(v1_config_path))) + end + if File.file?(autoproj_config_path) + config.merge!(YAML.load(File.read(autoproj_config_path))) + end - env_for_child.each do |k, v| - if v - ENV[k] = v - else - ENV.delete(k) - end - end - ENV['BUNDLE_GEMFILE'] = autoproj_gemfile_path - update_configuration - exec Gem.ruby, File.join(autoproj_install_dir, 'bin', 'autoproj'), - 'install-stage2', root_dir + @config = config + %w{private_bundler private_gems private_autoproj prefer_indep_over_os_packages}.each do |flag| + instance_variable_set "@#{flag}", config.fetch(flag, false) end + end + + def save_config + config['private_bundler'] = private_bundler? + config['private_autoproj'] = private_autoproj? + config['private_gems'] = private_gems? + config['prefer_indep_over_os_packages'] = prefer_indep_over_os_packages? + File.open(autoproj_config_path, 'w') { |io| YAML.dump(config, io) } + end + + def stage1 + FileUtils.mkdir_p dot_autoproj + save_config + install + + clean_env = env_for_child + stage2_vars = clean_env.map { |k, v| "#{k}=#{v}" } + puts "starting the newly installed autoproj for stage2 install" + puts [clean_env.merge('BUNDLE_GEMFILE' => autoproj_gemfile_path), + Gem.ruby, File.join(autoproj_install_dir, 'bin', 'autoproj'), + 'install-stage2', root_dir, *stage2_vars].inspect + exec clean_env.merge('BUNDLE_GEMFILE' => autoproj_gemfile_path), + Gem.ruby, File.join(autoproj_install_dir, 'bin', 'autoproj'), + 'install-stage2', root_dir, *stage2_vars + end + + def stage2(*vars) + require 'autobuild' + puts "saving env.sh and .autoproj/env.sh" + save_env_sh(*vars) + puts "calling autoproj envsh" + system(Gem.ruby, $0, 'envsh') end end end end