bin/autoproj_install in autoproj-2.11.0 vs bin/autoproj_install in autoproj-2.12.0

- old
+ new

@@ -6,14 +6,17 @@ elsif ENV['AUTOPROJ_CURRENT_ROOT'] && (ENV['AUTOPROJ_CURRENT_ROOT'] != Dir.pwd) STDERR.puts "it seems that you've already loaded an env.sh script in this console, open a new console and try again" exit 1 end +# frozen_string_literal: true + require 'pathname' require 'optparse' require 'fileutils' require 'yaml' +require 'English' module Autoproj module Ops # This class contains the functionality necessary to install autoproj in a # clean root @@ -21,10 +24,40 @@ # It can be required standalone (i.e. does not depend on anything else than # ruby and the ruby standard library) class Install class UnexpectedBinstub < RuntimeError; end + RUBYLIB_REINIT = <<~RUBY + if defined?(Bundler) + if Bundler.respond_to?(:with_unbundled_env) + Bundler.with_unbundled_env do + exec(Hash['RUBYLIB' => nil], $0, *ARGV) + end + else + Bundler.with_clean_env do + exec(Hash['RUBYLIB' => nil], $0, *ARGV) + end + end + elsif ENV['RUBYLIB'] + exec(Hash['RUBYLIB' => nil], $0, *ARGV) + end + RUBY + + WITHOUT_BUNDLER = <<~RUBY + if defined?(Bundler) + if Bundler.respond_to?(:with_unbundled_env) + Bundler.with_unbundled_env do + exec($0, *ARGV) + end + else + Bundler.with_clean_env do + exec($0, *ARGV) + end + end + end + RUBY + # The created workspace's root directory 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 @@ -228,10 +261,14 @@ "ruby \"#{RUBY_VERSION}\" if respond_to?(:ruby)", "gem \"autoproj\", \"#{autoproj_version}\"", "gem \"utilrb\", \">= 3.0.1\""].join("\n") end + def add_seed_config(path) + @config.merge!(YAML.safe_load(File.read(path))) + end + # Parse the provided command line options and returns the non-options def parse_options(args = ARGV) options = OptionParser.new do |opt| opt.on '--local', 'do not access the network (may fail)' do @local = true @@ -251,10 +288,14 @@ self.gems_install_path = path end opt.on '--public-gems', "install gems in the default gem location" do self.install_gems_in_gem_user_dir end + opt.on '--bundler-version=VERSION_CONSTRAINT', String, 'use the provided '\ + 'string as a version constraint for bundler' do |version| + @config['bundler_version'] = version + end opt.on '--version=VERSION_CONSTRAINT', String, 'use the provided '\ 'string as a version constraint for autoproj' do |version| if @gemfile raise "cannot give both --version and --gemfile" end @@ -265,13 +306,18 @@ if @gemfile raise "cannot give both --version and --gemfile" end @gemfile = File.read(path) end + opt.on '--no-seed-config', + 'when reinstalling an existing autoproj workspace, do not '\ + 'use the config in .autoproj/ as seed' do + @config.clear + end opt.on '--seed-config=PATH', String, 'path to a seed file that '\ 'should be used to initialize the configuration' do |path| - @config.merge!(YAML.load(File.read(path))) + add_seed_config(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 @@ -297,60 +343,91 @@ end args = options.parse(ARGV) @autoproj_options + args end - def find_bundler(gem_program) + def bundler_version + @config['bundler_version'] + end + + def find_bundler(gem_program, version: nil) + bundler_path = File.join(gems_gem_home, 'bin', 'bundle') + return unless File.exist?(bundler_path) + setup_paths = - IO.popen([env_for_child, Gem.ruby, gem_program, 'which','-a', 'bundler/setup']) do |io| - io.read + if version + find_versioned_bundler_setup(gem_program, version) + else + find_unversioned_bundler_setup(gem_program) end - return unless $?.success? - bundler_path = File.join(gems_gem_home, 'bin', 'bundle') - setup_paths.each_line do |setup_path| - if File.exist?(bundler_path) && setup_path.start_with?(gems_gem_home) - return bundler_path - end + + setup_paths.each do |setup_path| + return bundler_path if setup_path.start_with?(gems_gem_home) end - return + nil end - def install_bundler(gem_program, silent: false) + def find_versioned_bundler_setup(gem_program, version) + contents = IO.popen( + [env_for_child, Gem.ruby, gem_program, + 'contents', '-v', version, 'bundler'], + &:readlines + ) + return [] unless $CHILD_STATUS.success? + + contents.grep(%r{bundler/setup.rb$}) + end + + def find_unversioned_bundler_setup(gem_program) + setup_paths = IO.popen( + [env_for_child, Gem.ruby, gem_program, + 'which', '-a', 'bundler/setup'], + &:readlines + ) + return [] unless $CHILD_STATUS.success? + + setup_paths + end + + def install_bundler(gem_program, version: nil, silent: false) local = ['--local'] if local? redirection = Hash.new if silent redirection = Hash[out: :close] end + version_args = [] + version_args << '-v' << version if version + # Shut up the bundler warning about 'bin' not being in PATH env = self.env env['PATH'] += [File.join(gems_gem_home, 'bin')] result = system( env_for_child(env), Gem.ruby, gem_program, 'install', '--env-shebang', '--no-document', '--no-format-executable', '--clear-sources', '--source', gem_source, '--no-user-install', '--install-dir', gems_gem_home, *local, "--bindir=#{File.join(gems_gem_home, 'bin')}", - 'bundler', **redirection) + 'bundler', *version_args, **redirection) if !result STDERR.puts "FATAL: failed to install bundler in #{gems_gem_home}" nil end - if (bundler_path = find_bundler(gem_program)) + if (bundler_path = find_bundler(gem_program, version: version)) bundler_path else STDERR.puts "gem install bundler returned successfully, but still "\ "cannot find bundler in #{gems_gem_home}" nil end end - def install_autoproj(bundler) + def install_autoproj(bundler, bundler_version: self.bundler_version) # Force bundler to update. If the user does not want this, let # him specify a Gemfile with tighter version constraints lockfile = File.join(dot_autoproj, 'Gemfile.lock') if File.exist?(lockfile) FileUtils.rm lockfile @@ -361,27 +438,32 @@ opts = Array.new opts << '--local' if local? opts << "--path=#{gems_install_path}" shims_path = File.join(dot_autoproj, 'bin') - result = system(clean_env, - Gem.ruby, bundler, 'install', - "--gemfile=#{autoproj_gemfile_path}", - "--shebang=#{Gem.ruby}", - "--binstubs=#{shims_path}", - *opts, chdir: dot_autoproj) + version_arg = [] + version_arg << "_#{bundler_version}_" if bundler_version - if !result + result = system( + clean_env, + Gem.ruby, bundler, *version_arg, 'install', + "--gemfile=#{autoproj_gemfile_path}", + "--shebang=#{Gem.ruby}", + "--binstubs=#{shims_path}", + *opts, chdir: dot_autoproj + ) + + unless result STDERR.puts "FATAL: failed to install autoproj in #{dot_autoproj}" exit 1 end ensure self.class.rewrite_shims(shims_path, ruby_executable, root_dir, autoproj_gemfile_path, gems_gem_home) end - EXCLUDED_FROM_SHIMS = %w{rake thor} + EXCLUDED_FROM_SHIMS = %w[rake thor].freeze def self.rewrite_shims(shim_path, ruby_executable, root_dir, autoproj_gemfile_path, gems_gem_home) FileUtils.mkdir_p shim_path File.open(File.join(shim_path, 'ruby'), 'w') do |io| @@ -433,15 +515,11 @@ # # This file was generated by Bundler. # # Autoproj generated preamble -if defined?(Bundler) - Bundler.with_clean_env do - exec($0, *ARGV) - end -end +#{WITHOUT_BUNDLER} ENV['BUNDLE_GEMFILE'] ||= '#{autoproj_gemfile_path}' ENV['GEM_HOME'] = '#{gems_gem_home}' ENV.delete('GEM_PATH') Gem.paths = Hash['GEM_HOME' => '#{gems_gem_home}', 'GEM_PATH' => ''] RESTART_BUNDLER @@ -449,16 +527,11 @@ end def self.shim_bundler_old(ruby_executable, autoproj_gemfile_path, gems_gem_home) "#! #{ruby_executable} -if defined?(Bundler) - Bundler.with_clean_env do - exec($0, *ARGV) - end -end - +#{WITHOUT_BUNDLER} ENV['BUNDLE_GEMFILE'] ||= '#{autoproj_gemfile_path}' ENV['GEM_HOME'] = '#{gems_gem_home}' ENV.delete('GEM_PATH') Gem.paths = Hash['GEM_HOME' => '#{gems_gem_home}', 'GEM_PATH' => ''] @@ -479,18 +552,11 @@ # # This file was generated by Bundler. # # Autoproj generated preamble, v1 -if defined?(Bundler) - Bundler.with_clean_env do - exec(Hash['RUBYLIB' => nil], $0, *ARGV) - end -elsif ENV['RUBYLIB'] - exec(Hash['RUBYLIB' => nil], $0, *ARGV) -end - +#{RUBYLIB_REINIT} ENV['BUNDLE_GEMFILE'] = '#{autoproj_gemfile_path}' ENV['AUTOPROJ_CURRENT_ROOT'] = '#{root_dir}' Gem.paths = Hash['GEM_HOME' => '#{gems_gem_home}', 'GEM_PATH' => ''] AUTOPROJ_PREAMBLE return script_lines.join @@ -498,18 +564,11 @@ def self.shim_script_old(ruby_executable, root_dir, autoproj_gemfile_path, gems_gem_home, load_line) "#! #{ruby_executable} -if defined?(Bundler) - Bundler.with_clean_env do - exec(Hash['RUBYLIB' => nil], $0, *ARGV) - end -elsif ENV['RUBYLIB'] - exec(Hash['RUBYLIB' => nil], $0, *ARGV) -end - +#{RUBYLIB_REINIT} ENV['BUNDLE_GEMFILE'] = '#{autoproj_gemfile_path}' ENV['AUTOPROJ_CURRENT_ROOT'] = '#{root_dir}' require 'rubygems' Gem.paths = Hash['GEM_HOME' => '#{gems_gem_home}', 'GEM_PATH' => ''] require 'bundler/setup' @@ -599,38 +658,40 @@ # 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(env_for_child, - [Gem.ruby, '-e', 'puts "#{Gem.user_dir}/bin"']).read + bindir = IO.popen( + env_for_child, + [Gem.ruby, '-e', 'puts "#{Gem.user_dir}/bin"'], # rubocop:disable Lint/InterpolationCheck + &:read + ) if bindir @gem_bindir = bindir.chomp else raise "FATAL: cannot run #{Gem.ruby} -e 'puts Gem.bindir'" end end - def install + def install(bundler_version: self.bundler_version) if ENV['BUNDLER_GEMFILE'] raise "cannot run autoproj_install or autoproj_bootstrap while "\ - "under a 'bundler exec' subcommand or having loaded an env.sh. "\ - "Open a new console and try again" + "under a 'bundler exec' subcommand or having loaded an "\ + "env.sh. Open a new console and try again" end gem_program = self.class.guess_gem_program puts "Detected 'gem' to be #{gem_program}" env['GEM_HOME'] = [gems_gem_home] env['GEM_PATH'] = [gems_gem_home] - if bundler = find_bundler(gem_program) + if (bundler = find_bundler(gem_program, version: bundler_version)) puts "Detected bundler at #{bundler}" else puts "Installing bundler in #{gems_gem_home}" - if !(bundler = install_bundler(gem_program)) - exit 1 - end + bundler = install_bundler(gem_program, version: bundler_version) + exit(1) unless bundler end self.class.rewrite_shims( File.join(dot_autoproj, 'bin'), ruby_executable, root_dir, @@ -638,11 +699,11 @@ gems_gem_home) env['PATH'].unshift File.join(dot_autoproj, 'bin') save_gemfile puts "Installing autoproj in #{gems_gem_home}" - install_autoproj(bundler) + install_autoproj(bundler, bundler_version: bundler_version) end def load_config v1_config_path = File.join(root_dir, 'autoproj', 'config.yml') @@ -740,10 +801,15 @@ ENV.delete('BUNDLE_GEMFILE') ENV.delete('RUBYLIB') ops = Autoproj::Ops::Install.new(Dir.pwd) + +existing_config = File.join(Dir.pwd, '.autoproj', 'config.yml') +if File.file?(existing_config) + puts "Found existing configuration, using it as seed" + puts "use --no-seed-config to avoid this behavior" + ops.add_seed_config(existing_config) +end ops.parse_options(ARGV) ops.stage1 -if !ops.skip_stage2? - ops.call_stage2 -end +ops.call_stage2 unless ops.skip_stage2?