autoproj 2.11.0 → 2.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -8
  3. data/.travis.yml +5 -3
  4. data/autoproj.gemspec +6 -6
  5. data/bin/alog +1 -0
  6. data/bin/autoproj +1 -1
  7. data/bin/autoproj_bootstrap +130 -67
  8. data/bin/autoproj_bootstrap.in +9 -7
  9. data/bin/autoproj_install +129 -63
  10. data/bin/autoproj_install.in +8 -3
  11. data/lib/autoproj/autobuild_extensions/dsl.rb +27 -12
  12. data/lib/autoproj/base.rb +18 -0
  13. data/lib/autoproj/cli/base.rb +1 -1
  14. data/lib/autoproj/cli/build.rb +8 -3
  15. data/lib/autoproj/cli/cache.rb +79 -7
  16. data/lib/autoproj/cli/inspection_tool.rb +5 -6
  17. data/lib/autoproj/cli/main.rb +33 -9
  18. data/lib/autoproj/cli/show.rb +12 -18
  19. data/lib/autoproj/cli/status.rb +15 -9
  20. data/lib/autoproj/cli/test.rb +1 -1
  21. data/lib/autoproj/cli/update.rb +72 -17
  22. data/lib/autoproj/cli/utility.rb +25 -28
  23. data/lib/autoproj/configuration.rb +15 -4
  24. data/lib/autoproj/default.osdeps +29 -3
  25. data/lib/autoproj/environment.rb +17 -13
  26. data/lib/autoproj/installation_manifest.rb +7 -5
  27. data/lib/autoproj/manifest.rb +14 -6
  28. data/lib/autoproj/ops/build.rb +23 -21
  29. data/lib/autoproj/ops/cache.rb +151 -33
  30. data/lib/autoproj/ops/cached_env.rb +2 -2
  31. data/lib/autoproj/ops/import.rb +23 -4
  32. data/lib/autoproj/ops/install.rb +121 -60
  33. data/lib/autoproj/ops/phase_reporting.rb +49 -0
  34. data/lib/autoproj/ops/snapshot.rb +2 -1
  35. data/lib/autoproj/ops/tools.rb +2 -2
  36. data/lib/autoproj/os_package_installer.rb +19 -11
  37. data/lib/autoproj/package_definition.rb +1 -1
  38. data/lib/autoproj/package_managers/apt_dpkg_manager.rb +49 -28
  39. data/lib/autoproj/package_managers/bundler_manager.rb +102 -19
  40. data/lib/autoproj/package_managers/homebrew_manager.rb +2 -2
  41. data/lib/autoproj/package_managers/pip_manager.rb +34 -22
  42. data/lib/autoproj/package_managers/shell_script_manager.rb +44 -24
  43. data/lib/autoproj/package_manifest.rb +43 -31
  44. data/lib/autoproj/package_set.rb +2 -2
  45. data/lib/autoproj/python.rb +285 -0
  46. data/lib/autoproj/test.rb +26 -10
  47. data/lib/autoproj/variable_expansion.rb +3 -1
  48. data/lib/autoproj/vcs_definition.rb +25 -12
  49. data/lib/autoproj/version.rb +1 -1
  50. data/lib/autoproj/workspace.rb +60 -16
  51. data/lib/autoproj.rb +3 -0
  52. metadata +17 -28
@@ -50,12 +50,19 @@ module Autoproj
50
50
 
51
51
  # Load the manifest data contained in +file+
52
52
  def load(file)
53
- if !File.exist?(file)
54
- raise ConfigError.new(File.dirname(file)), "expected an autoproj configuration in #{File.dirname(file)}, but #{file} does not exist"
53
+ unless File.exist?(file)
54
+ raise ConfigError.new(File.dirname(file)),
55
+ "expected an autoproj configuration in #{File.dirname(file)}, "\
56
+ "but #{file} does not exist"
55
57
  end
56
58
 
57
59
  data = Autoproj.in_file(file, Autoproj::YAML_LOAD_ERROR) do
58
- YAML.load(File.read(file)) || Hash.new
60
+ YAML.safe_load(File.read(file)) || {}
61
+ end
62
+
63
+ if data["layout"]&.member?(nil)
64
+ Autoproj.warn "There is an empty entry in your layout in #{file}. All empty entries are ignored."
65
+ data["layout"] = data["layout"].compact
59
66
  end
60
67
 
61
68
  @file = file
@@ -81,7 +88,8 @@ module Autoproj
81
88
  @has_layout = !!data['layout']
82
89
 
83
90
  if data['constants']
84
- @constant_definitions = Autoproj.resolve_constant_definitions(data['constants'])
91
+ @constant_definitions =
92
+ Autoproj.resolve_constant_definitions(data['constants'])
85
93
  end
86
94
  end
87
95
 
@@ -1234,7 +1242,7 @@ module Autoproj
1234
1242
  workspace.config
1235
1243
  end
1236
1244
 
1237
- def self.add_osdeps_overrides(*args, &block)
1238
- manifest.add_osdeps_overrides(*args, &block)
1245
+ def self.add_osdeps_overrides(*args, **kw, &block)
1246
+ manifest.add_osdeps_overrides(*args, **kw, &block)
1239
1247
  end
1240
1248
  end
@@ -87,34 +87,36 @@ module Autoproj
87
87
  # names of the packages that should be rebuilt
88
88
  # @return [void]
89
89
  def build_packages(all_enabled_packages, options = Hash.new)
90
+ if @report_path
91
+ reporting = Ops::PhaseReporting.new(
92
+ 'build', @report_path, method(:package_metadata)
93
+ )
94
+ end
95
+
90
96
  Autobuild.do_rebuild = false
91
97
  Autobuild.do_forced_build = false
98
+ reporting&.initialize_incremental_report
92
99
  begin
93
- Autobuild.apply(all_enabled_packages, "autoproj-build", ['build'], options)
100
+ Autobuild.apply(
101
+ all_enabled_packages,
102
+ "autoproj-build", ['build'], options
103
+ ) do |pkg, phase|
104
+ reporting&.report_incremental(pkg) if phase == 'build'
105
+ end
106
+
94
107
  ensure
95
- create_report(all_enabled_packages) if @report_path
108
+ packages = all_enabled_packages.map do |name|
109
+ @manifest.find_autobuild_package(name)
110
+ end
111
+ reporting&.create_report(packages)
96
112
  end
97
113
  end
98
114
 
99
- def create_report(package_list)
100
- FileUtils.mkdir_p File.dirname(@report_path)
101
-
102
- packages = package_list.each_with_object({}) do |pkg_name, h|
103
- pkg = manifest.find_autobuild_package(pkg_name)
104
-
105
- h[pkg.name] = {
106
- invoked: !!pkg.install_invoked?,
107
- success: !!pkg.installed?
108
- }
109
- end
110
-
111
- report = JSON.pretty_generate({
112
- build_report: {
113
- timestamp: Time.now,
114
- packages: packages
115
- }
116
- })
117
- IO.write(@report_path, report)
115
+ def package_metadata(autobuild_package)
116
+ {
117
+ invoked: !!autobuild_package.install_invoked?,
118
+ success: !!autobuild_package.installed?
119
+ }
118
120
  end
119
121
  end
120
122
  end
@@ -4,9 +4,10 @@ module Autoproj
4
4
  attr_reader :cache_dir
5
5
  attr_reader :manifest
6
6
 
7
- def initialize(cache_dir, manifest)
7
+ def initialize(cache_dir, ws)
8
8
  @cache_dir = cache_dir
9
- @manifest = manifest
9
+ @ws = ws
10
+ @manifest = ws.manifest
10
11
  end
11
12
 
12
13
  def with_retry(count)
@@ -27,29 +28,39 @@ module Autoproj
27
28
  File.join(cache_dir, 'git')
28
29
  end
29
30
 
30
- def cache_git(pkg, options = Hash.new)
31
+ def cache_git(pkg, checkout_only: false)
31
32
  pkg.importdir = File.join(git_cache_dir, pkg.name)
32
- if options[:checkout_only] && File.directory?(pkg.importdir)
33
- return
34
- end
33
+ return if checkout_only && File.directory?(pkg.importdir)
35
34
 
36
35
  pkg.importer.local_branch = nil
37
36
  pkg.importer.remote_branch = nil
38
37
  pkg.importer.remote_name = 'autobuild'
39
38
 
40
- if !File.directory?(pkg.importdir)
39
+ unless File.directory?(pkg.importdir)
41
40
  FileUtils.mkdir_p File.dirname(pkg.importdir)
42
- Autobuild::Subprocess.run("autoproj-cache", "import", Autobuild.tool(:git), "--git-dir", pkg.importdir, 'init', "--bare")
41
+ Autobuild::Subprocess.run(
42
+ "autoproj-cache", "import", Autobuild.tool(:git),
43
+ "--git-dir", pkg.importdir, 'init', "--bare"
44
+ )
43
45
  end
44
46
  pkg.importer.update_remotes_configuration(pkg)
45
47
 
46
48
  with_retry(10) do
47
- Autobuild::Subprocess.run('autoproj-cache', :import, Autobuild.tool('git'), '--git-dir', pkg.importdir, 'remote', 'update', 'autobuild')
49
+ Autobuild::Subprocess.run(
50
+ 'autoproj-cache', :import, Autobuild.tool('git'),
51
+ '--git-dir', pkg.importdir, 'remote', 'update', 'autobuild'
52
+ )
48
53
  end
49
54
  with_retry(10) do
50
- Autobuild::Subprocess.run('autoproj-cache', :import, Autobuild.tool('git'), '--git-dir', pkg.importdir, 'fetch', 'autobuild', '--tags')
55
+ Autobuild::Subprocess.run(
56
+ 'autoproj-cache', :import, Autobuild.tool('git'),
57
+ '--git-dir', pkg.importdir, 'fetch', 'autobuild', '--tags'
58
+ )
51
59
  end
52
- Autobuild::Subprocess.run('autoproj-cache', :import, Autobuild.tool('git'), '--git-dir', pkg.importdir, 'gc', '--prune=all')
60
+ Autobuild::Subprocess.run(
61
+ 'autoproj-cache', :import, Autobuild.tool('git'),
62
+ '--git-dir', pkg.importdir, 'gc', '--prune=all'
63
+ )
53
64
  end
54
65
 
55
66
  def archive_cache_dir
@@ -63,7 +74,8 @@ module Autoproj
63
74
  end
64
75
  end
65
76
 
66
- def create_or_update(*package_names, all: true, keep_going: false, checkout_only: false)
77
+ def create_or_update(*package_names, all: true, keep_going: false,
78
+ checkout_only: false)
67
79
  FileUtils.mkdir_p cache_dir
68
80
 
69
81
  if package_names.empty?
@@ -75,7 +87,7 @@ module Autoproj
75
87
  end
76
88
  else
77
89
  packages = package_names.map do |name|
78
- if pkg = manifest.find_autobuild_package(name)
90
+ if (pkg = manifest.find_autobuild_package(name))
79
91
  pkg
80
92
  else
81
93
  raise PackageNotFound, "no package named #{name}"
@@ -88,42 +100,148 @@ module Autoproj
88
100
  total = packages.size
89
101
  Autoproj.message "Handling #{total} packages"
90
102
  packages.each_with_index do |pkg, i|
91
- next if pkg.srcdir != pkg.importdir # No need to process this one, it is uses another package's import
103
+ # No need to process this one, it is uses another package's
104
+ # import
105
+ next if pkg.srcdir != pkg.importdir
106
+
92
107
  begin
93
108
  case pkg.importer
94
109
  when Autobuild::Git
95
- Autoproj.message " [#{i}/#{total}] caching #{pkg.name} (git)"
110
+ Autoproj.message(
111
+ " [#{i}/#{total}] caching #{pkg.name} (git)"
112
+ )
96
113
  cache_git(pkg, checkout_only: checkout_only)
97
114
  when Autobuild::ArchiveImporter
98
- Autoproj.message " [#{i}/#{total}] caching #{pkg.name} (archive)"
115
+ Autoproj.message(
116
+ " [#{i}/#{total}] caching #{pkg.name} (archive)"
117
+ )
99
118
  cache_archive(pkg)
100
119
  else
101
- Autoproj.message " [#{i}/#{total}] not caching #{pkg.name} (cannot cache #{pkg.importer.class})"
120
+ Autoproj.message(
121
+ " [#{i}/#{total}] not caching #{pkg.name} "\
122
+ "(cannot cache #{pkg.importer.class})"
123
+ )
102
124
  end
103
125
  rescue Interrupt
104
126
  raise
105
127
  rescue ::Exception => e
106
- if keep_going
107
- pkg.error " failed to cache #{pkg.name}, but going on as requested"
108
- lines = e.to_s.split("\n")
109
- if lines.empty?
110
- lines = e.message.split("\n")
111
- end
112
- if lines.empty?
113
- lines = ["unknown error"]
114
- end
115
- pkg.message(lines.shift, :red, :bold)
116
- lines.each do |line|
117
- pkg.message(line)
128
+ raise unless keep_going
129
+
130
+ pkg.error " failed to cache #{pkg.name}, "\
131
+ 'but going on as requested'
132
+ lines = e.to_s.split('\n')
133
+ lines = e.message.split('\n') if lines.empty?
134
+ lines = ['unknown error'] if lines.empty?
135
+ pkg.message(lines.shift, :red, :bold)
136
+ lines.each do |line|
137
+ pkg.message(line)
138
+ end
139
+ nil
140
+ end
141
+ end
142
+ end
143
+
144
+ def gems_cache_dir
145
+ File.join(cache_dir, 'package_managers', 'gem')
146
+ end
147
+
148
+ def create_or_update_gems(keep_going: true, compile_force: false, compile: [])
149
+ # Note: this might directly copy into the cache directoy, and
150
+ # we support it later
151
+ cache_dir = File.join(@ws.prefix_dir, 'gems', 'vendor', 'cache')
152
+ PackageManagers::BundlerManager.run_bundler(
153
+ @ws, 'cache'
154
+ )
155
+
156
+ FileUtils.mkdir_p(gems_cache_dir) unless File.exist?(gems_cache_dir)
157
+
158
+ needs_copy =
159
+ if File.exist?(cache_dir)
160
+ real_cache_dir = File.realpath(cache_dir)
161
+ real_target_dir = File.realpath(gems_cache_dir)
162
+ (real_cache_dir != real_target_dir)
163
+ end
164
+
165
+ synchronize_gems_cache_dirs(real_cache_dir, real_target_dir) if needs_copy
166
+
167
+ platform_suffix = "-#{Gem::Platform.local}.gem"
168
+ failed = []
169
+ compile.each do |gem_name, artifacts: []|
170
+ Dir.glob(File.join(cache_dir, "#{gem_name}*.gem")) do |gem|
171
+ next if gem.end_with?(platform_suffix)
172
+
173
+ gem_basename = File.basename(gem, ".gem")
174
+ expected_platform_gem = File.join(
175
+ real_target_dir, "#{gem_basename}#{platform_suffix}"
176
+ )
177
+ next if !compile_force && File.file?(expected_platform_gem)
178
+
179
+ begin
180
+ compile_gem(
181
+ gem, artifacts: artifacts, output: real_target_dir
182
+ )
183
+ rescue CompilationFailed
184
+ unless keep_going
185
+ raise CompilationFailed, "#{gem} failed to compile"
118
186
  end
119
- nil
120
- else
121
- raise
187
+
188
+ failed << gem
122
189
  end
123
190
  end
124
191
  end
192
+
193
+ unless failed.empty?
194
+ raise CompilationFailed, "#{failed.sort.join(', ')} failed to compile"
195
+ end
196
+ end
197
+
198
+ class CompilationFailed < RuntimeError; end
199
+
200
+ def synchronize_gems_cache_dirs(source, target)
201
+ Dir.glob(File.join(source, "*.gem")) do |source_file|
202
+ basename = File.basename(source_file)
203
+ target_file = File.join(target, basename)
204
+ next if File.file?(target_file)
205
+
206
+ Autoproj.message "gems: caching #{basename}"
207
+ FileUtils.cp source_file, target_file
208
+ end
209
+ end
210
+
211
+ def guess_gem_program
212
+ return Autobuild.programs['gem'] if Autobuild.programs['gem']
213
+
214
+ ruby_bin = RbConfig::CONFIG['RUBY_INSTALL_NAME']
215
+ ruby_bindir = RbConfig::CONFIG['bindir']
216
+
217
+ candidates = ['gem']
218
+ candidates << "gem#{$1}" if ruby_bin =~ /^ruby(.+)$/
219
+
220
+ candidates.each do |gem_name|
221
+ if File.file?(gem_full_path = File.join(ruby_bindir, gem_name))
222
+ Autobuild.programs['gem'] = gem_full_path
223
+ return Autobuild.programs['gem']
224
+ end
225
+ end
226
+
227
+ raise ArgumentError,
228
+ 'cannot find a gem program (tried '\
229
+ "#{candidates.sort.join(', ')} in #{ruby_bindir})"
230
+ end
231
+
232
+ private def compile_gem(gem_path, output:, artifacts: [])
233
+ artifacts = artifacts.flat_map do |include, n|
234
+ if include
235
+ ["--include", n]
236
+ else
237
+ ["--exclude", n]
238
+ end
239
+ end
240
+ unless system(Autobuild.tool('ruby'), '-S', guess_gem_program,
241
+ 'compile', '--output', output, *artifacts, gem_path)
242
+ raise CompilationFailed, "#{gem_path} failed to compile"
243
+ end
125
244
  end
126
245
  end
127
246
  end
128
247
  end
129
-
@@ -9,7 +9,7 @@ module Autoproj
9
9
  def self.load_cached_env(root_dir)
10
10
  path = cached_env_path(root_dir)
11
11
  if File.file?(path)
12
- env = YAML.load(File.read(path))
12
+ env = YAML.safe_load(File.read(path))
13
13
  Autobuild::Environment::ExportedEnvironment.new(
14
14
  env['set'], env['unset'], env['update'])
15
15
  end
@@ -20,7 +20,7 @@ module Autoproj
20
20
  path = cached_env_path(root_dir)
21
21
  existing =
22
22
  begin
23
- YAML.load(File.read(path))
23
+ YAML.safe_load(File.read(path))
24
24
  rescue Exception
25
25
  end
26
26
 
@@ -199,6 +199,7 @@ module Autoproj
199
199
  install_vcs_packages: Hash.new,
200
200
  non_imported_packages: :checkout,
201
201
  auto_exclude: auto_exclude?,
202
+ filter: ->(package) { true },
202
203
  **import_options)
203
204
 
204
205
  unless %i[checkout ignore return].include?(non_imported_packages)
@@ -235,6 +236,11 @@ module Autoproj
235
236
  missing_vcs = Array.new
236
237
  installed_vcs_packages = Set['none', 'local']
237
238
  while failures.empty? || keep_going
239
+ # Allow 'filter' to parallelize as well
240
+ if filter.respond_to?(:lookahead)
241
+ package_queue.each { |pkg| filter.lookahead(pkg) }
242
+ end
243
+
238
244
  # Queue work for all packages in the queue
239
245
  package_queue.each do |pkg|
240
246
  # Remove packages that have already been processed
@@ -255,6 +261,11 @@ module Autoproj
255
261
  end
256
262
  all_processed_packages << pkg
257
263
 
264
+ unless filter.call(pkg)
265
+ completion_queue << [pkg, Time.now]
266
+ next
267
+ end
268
+
258
269
  importer = pkg.autobuild.importer
259
270
  if !pre_package_import(selection, manifest, pkg.autobuild,
260
271
  reverse_dependencies)
@@ -272,8 +283,8 @@ module Autoproj
272
283
  next
273
284
  end
274
285
 
275
- pending_packages << pkg
276
286
  begin
287
+ pending_packages << pkg
277
288
  queue_import_work(
278
289
  executor, completion_queue, pkg,
279
290
  retry_count: retry_count,
@@ -288,8 +299,12 @@ module Autoproj
288
299
  if completion_queue.empty? && pending_packages.empty?
289
300
  unless missing_vcs.empty?
290
301
  installed_vcs_packages.merge(
291
- install_vcs_packages_for(*missing_vcs,
292
- **install_vcs_packages))
302
+ install_vcs_packages_for(
303
+ *missing_vcs,
304
+ install_only: import_options[:checkout_only],
305
+ **install_vcs_packages
306
+ )
307
+ )
293
308
  package_queue.concat(missing_vcs)
294
309
  missing_vcs.clear
295
310
  next
@@ -366,7 +381,9 @@ module Autoproj
366
381
  end
367
382
  end
368
383
 
369
- def finalize_package_load(processed_packages, auto_exclude: auto_exclude?)
384
+ def finalize_package_load(processed_packages,
385
+ ignore_optional_dependencies: false,
386
+ auto_exclude: auto_exclude?)
370
387
  manifest = ws.manifest
371
388
 
372
389
  all = Set.new
@@ -426,6 +443,7 @@ module Autoproj
426
443
  keep_going: false,
427
444
  install_vcs_packages: Hash.new,
428
445
  auto_exclude: auto_exclude?,
446
+ filter: ->(pkg) { true },
429
447
  **import_options)
430
448
 
431
449
  manifest = ws.manifest
@@ -437,6 +455,7 @@ module Autoproj
437
455
  recursive: recursive,
438
456
  install_vcs_packages: install_vcs_packages,
439
457
  auto_exclude: auto_exclude,
458
+ filter: filter,
440
459
  **import_options)
441
460
 
442
461
  raise failures.first if !keep_going && !failures.empty?
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pathname'
2
4
  require 'optparse'
3
5
  require 'fileutils'
4
6
  require 'yaml'
7
+ require 'English'
5
8
 
6
9
  module Autoproj
7
10
  module Ops
@@ -13,6 +16,36 @@ module Autoproj
13
16
  class Install
14
17
  class UnexpectedBinstub < RuntimeError; end
15
18
 
19
+ RUBYLIB_REINIT = <<~RUBY
20
+ if defined?(Bundler)
21
+ if Bundler.respond_to?(:with_unbundled_env)
22
+ Bundler.with_unbundled_env do
23
+ exec(Hash['RUBYLIB' => nil], $0, *ARGV)
24
+ end
25
+ else
26
+ Bundler.with_clean_env do
27
+ exec(Hash['RUBYLIB' => nil], $0, *ARGV)
28
+ end
29
+ end
30
+ elsif ENV['RUBYLIB']
31
+ exec(Hash['RUBYLIB' => nil], $0, *ARGV)
32
+ end
33
+ RUBY
34
+
35
+ WITHOUT_BUNDLER = <<~RUBY
36
+ if defined?(Bundler)
37
+ if Bundler.respond_to?(:with_unbundled_env)
38
+ Bundler.with_unbundled_env do
39
+ exec($0, *ARGV)
40
+ end
41
+ else
42
+ Bundler.with_clean_env do
43
+ exec($0, *ARGV)
44
+ end
45
+ end
46
+ end
47
+ RUBY
48
+
16
49
  # The created workspace's root directory
17
50
  attr_reader :root_dir
18
51
  # Content of the Gemfile generated to install autoproj itself
@@ -220,6 +253,10 @@ module Autoproj
220
253
  "gem \"utilrb\", \">= 3.0.1\""].join("\n")
221
254
  end
222
255
 
256
+ def add_seed_config(path)
257
+ @config.merge!(YAML.safe_load(File.read(path), [Symbol]))
258
+ end
259
+
223
260
  # Parse the provided command line options and returns the non-options
224
261
  def parse_options(args = ARGV)
225
262
  options = OptionParser.new do |opt|
@@ -243,6 +280,10 @@ module Autoproj
243
280
  opt.on '--public-gems', "install gems in the default gem location" do
244
281
  self.install_gems_in_gem_user_dir
245
282
  end
283
+ opt.on '--bundler-version=VERSION_CONSTRAINT', String, 'use the provided '\
284
+ 'string as a version constraint for bundler' do |version|
285
+ @config['bundler_version'] = version
286
+ end
246
287
  opt.on '--version=VERSION_CONSTRAINT', String, 'use the provided '\
247
288
  'string as a version constraint for autoproj' do |version|
248
289
  if @gemfile
@@ -257,9 +298,14 @@ module Autoproj
257
298
  end
258
299
  @gemfile = File.read(path)
259
300
  end
301
+ opt.on '--no-seed-config',
302
+ 'when reinstalling an existing autoproj workspace, do not '\
303
+ 'use the config in .autoproj/ as seed' do
304
+ @config.clear
305
+ end
260
306
  opt.on '--seed-config=PATH', String, 'path to a seed file that '\
261
307
  'should be used to initialize the configuration' do |path|
262
- @config.merge!(YAML.load(File.read(path)))
308
+ add_seed_config(path)
263
309
  end
264
310
  opt.on '--prefer-os-independent-packages', 'prefer OS-independent '\
265
311
  'packages (such as a RubyGem) over their OS-packaged equivalent '\
@@ -289,22 +335,50 @@ module Autoproj
289
335
  @autoproj_options + args
290
336
  end
291
337
 
292
- def find_bundler(gem_program)
293
- setup_paths =
294
- IO.popen([env_for_child, Gem.ruby, gem_program, 'which','-a', 'bundler/setup']) do |io|
295
- io.read
296
- end
297
- return unless $?.success?
338
+ def bundler_version
339
+ @config['bundler_version']
340
+ end
341
+
342
+ def find_bundler(gem_program, version: nil)
298
343
  bundler_path = File.join(gems_gem_home, 'bin', 'bundle')
299
- setup_paths.each_line do |setup_path|
300
- if File.exist?(bundler_path) && setup_path.start_with?(gems_gem_home)
301
- return bundler_path
344
+ return unless File.exist?(bundler_path)
345
+
346
+ setup_paths =
347
+ if version
348
+ find_versioned_bundler_setup(gem_program, version)
349
+ else
350
+ find_unversioned_bundler_setup(gem_program)
302
351
  end
352
+
353
+ setup_paths.each do |setup_path|
354
+ return bundler_path if setup_path.start_with?(gems_gem_home)
303
355
  end
304
- return
356
+ nil
357
+ end
358
+
359
+ def find_versioned_bundler_setup(gem_program, version)
360
+ contents = IO.popen(
361
+ [env_for_child, Gem.ruby, gem_program,
362
+ 'contents', '-v', version, 'bundler'],
363
+ &:readlines
364
+ )
365
+ return [] unless $CHILD_STATUS.success?
366
+
367
+ contents.grep(%r{bundler/setup.rb$})
368
+ end
369
+
370
+ def find_unversioned_bundler_setup(gem_program)
371
+ setup_paths = IO.popen(
372
+ [env_for_child, Gem.ruby, gem_program,
373
+ 'which', '-a', 'bundler/setup'],
374
+ &:readlines
375
+ )
376
+ return [] unless $CHILD_STATUS.success?
377
+
378
+ setup_paths
305
379
  end
306
380
 
307
- def install_bundler(gem_program, silent: false)
381
+ def install_bundler(gem_program, version: nil, silent: false)
308
382
  local = ['--local'] if local?
309
383
 
310
384
  redirection = Hash.new
@@ -312,6 +386,9 @@ module Autoproj
312
386
  redirection = Hash[out: :close]
313
387
  end
314
388
 
389
+ version_args = []
390
+ version_args << '-v' << version if version
391
+
315
392
  # Shut up the bundler warning about 'bin' not being in PATH
316
393
  env = self.env
317
394
  env['PATH'] += [File.join(gems_gem_home, 'bin')]
@@ -322,14 +399,14 @@ module Autoproj
322
399
  '--clear-sources', '--source', gem_source,
323
400
  '--no-user-install', '--install-dir', gems_gem_home,
324
401
  *local, "--bindir=#{File.join(gems_gem_home, 'bin')}",
325
- 'bundler', **redirection)
402
+ 'bundler', *version_args, **redirection)
326
403
 
327
404
  if !result
328
405
  STDERR.puts "FATAL: failed to install bundler in #{gems_gem_home}"
329
406
  nil
330
407
  end
331
408
 
332
- if (bundler_path = find_bundler(gem_program))
409
+ if (bundler_path = find_bundler(gem_program, version: version))
333
410
  bundler_path
334
411
  else
335
412
  STDERR.puts "gem install bundler returned successfully, but still "\
@@ -338,7 +415,7 @@ module Autoproj
338
415
  end
339
416
  end
340
417
 
341
- def install_autoproj(bundler)
418
+ def install_autoproj(bundler, bundler_version: self.bundler_version)
342
419
  # Force bundler to update. If the user does not want this, let
343
420
  # him specify a Gemfile with tighter version constraints
344
421
  lockfile = File.join(dot_autoproj, 'Gemfile.lock')
@@ -353,14 +430,19 @@ module Autoproj
353
430
  opts << "--path=#{gems_install_path}"
354
431
  shims_path = File.join(dot_autoproj, 'bin')
355
432
 
356
- result = system(clean_env,
357
- Gem.ruby, bundler, 'install',
358
- "--gemfile=#{autoproj_gemfile_path}",
359
- "--shebang=#{Gem.ruby}",
360
- "--binstubs=#{shims_path}",
361
- *opts, chdir: dot_autoproj)
433
+ version_arg = []
434
+ version_arg << "_#{bundler_version}_" if bundler_version
362
435
 
363
- if !result
436
+ result = system(
437
+ clean_env,
438
+ Gem.ruby, bundler, *version_arg, 'install',
439
+ "--gemfile=#{autoproj_gemfile_path}",
440
+ "--shebang=#{Gem.ruby}",
441
+ "--binstubs=#{shims_path}",
442
+ *opts, chdir: dot_autoproj
443
+ )
444
+
445
+ unless result
364
446
  STDERR.puts "FATAL: failed to install autoproj in #{dot_autoproj}"
365
447
  exit 1
366
448
  end
@@ -369,7 +451,7 @@ module Autoproj
369
451
  root_dir, autoproj_gemfile_path, gems_gem_home)
370
452
  end
371
453
 
372
- EXCLUDED_FROM_SHIMS = %w{rake thor}
454
+ EXCLUDED_FROM_SHIMS = %w[rake thor].freeze
373
455
 
374
456
  def self.rewrite_shims(shim_path, ruby_executable,
375
457
  root_dir, autoproj_gemfile_path, gems_gem_home)
@@ -425,11 +507,7 @@ module Autoproj
425
507
  #
426
508
 
427
509
  # Autoproj generated preamble
428
- if defined?(Bundler)
429
- Bundler.with_clean_env do
430
- exec($0, *ARGV)
431
- end
432
- end
510
+ #{WITHOUT_BUNDLER}
433
511
  ENV['BUNDLE_GEMFILE'] ||= '#{autoproj_gemfile_path}'
434
512
  ENV['GEM_HOME'] = '#{gems_gem_home}'
435
513
  ENV.delete('GEM_PATH')
@@ -441,12 +519,7 @@ Gem.paths = Hash['GEM_HOME' => '#{gems_gem_home}', 'GEM_PATH' => '']
441
519
  def self.shim_bundler_old(ruby_executable, autoproj_gemfile_path, gems_gem_home)
442
520
  "#! #{ruby_executable}
443
521
 
444
- if defined?(Bundler)
445
- Bundler.with_clean_env do
446
- exec($0, *ARGV)
447
- end
448
- end
449
-
522
+ #{WITHOUT_BUNDLER}
450
523
  ENV['BUNDLE_GEMFILE'] ||= '#{autoproj_gemfile_path}'
451
524
  ENV['GEM_HOME'] = '#{gems_gem_home}'
452
525
  ENV.delete('GEM_PATH')
@@ -471,14 +544,7 @@ load Gem.bin_path('bundler', 'bundler')"
471
544
  #
472
545
 
473
546
  # Autoproj generated preamble, v1
474
- if defined?(Bundler)
475
- Bundler.with_clean_env do
476
- exec(Hash['RUBYLIB' => nil], $0, *ARGV)
477
- end
478
- elsif ENV['RUBYLIB']
479
- exec(Hash['RUBYLIB' => nil], $0, *ARGV)
480
- end
481
-
547
+ #{RUBYLIB_REINIT}
482
548
  ENV['BUNDLE_GEMFILE'] = '#{autoproj_gemfile_path}'
483
549
  ENV['AUTOPROJ_CURRENT_ROOT'] = '#{root_dir}'
484
550
  Gem.paths = Hash['GEM_HOME' => '#{gems_gem_home}', 'GEM_PATH' => '']
@@ -490,14 +556,7 @@ Gem.paths = Hash['GEM_HOME' => '#{gems_gem_home}', 'GEM_PATH' => '']
490
556
  gems_gem_home, load_line)
491
557
  "#! #{ruby_executable}
492
558
 
493
- if defined?(Bundler)
494
- Bundler.with_clean_env do
495
- exec(Hash['RUBYLIB' => nil], $0, *ARGV)
496
- end
497
- elsif ENV['RUBYLIB']
498
- exec(Hash['RUBYLIB' => nil], $0, *ARGV)
499
- end
500
-
559
+ #{RUBYLIB_REINIT}
501
560
  ENV['BUNDLE_GEMFILE'] = '#{autoproj_gemfile_path}'
502
561
  ENV['AUTOPROJ_CURRENT_ROOT'] = '#{root_dir}'
503
562
  require 'rubygems'
@@ -591,8 +650,11 @@ require 'bundler/setup'
591
650
  #
592
651
  # So, we're calling 'gem' as a subcommand to discovery the
593
652
  # actual bindir
594
- bindir = IO.popen(env_for_child,
595
- [Gem.ruby, '-e', 'puts "#{Gem.user_dir}/bin"']).read
653
+ bindir = IO.popen(
654
+ env_for_child,
655
+ [Gem.ruby, '-e', 'puts "#{Gem.user_dir}/bin"'], # rubocop:disable Lint/InterpolationCheck
656
+ &:read
657
+ )
596
658
  if bindir
597
659
  @gem_bindir = bindir.chomp
598
660
  else
@@ -600,11 +662,11 @@ require 'bundler/setup'
600
662
  end
601
663
  end
602
664
 
603
- def install
665
+ def install(bundler_version: self.bundler_version)
604
666
  if ENV['BUNDLER_GEMFILE']
605
667
  raise "cannot run autoproj_install or autoproj_bootstrap while "\
606
- "under a 'bundler exec' subcommand or having loaded an env.sh. "\
607
- "Open a new console and try again"
668
+ "under a 'bundler exec' subcommand or having loaded an "\
669
+ "env.sh. Open a new console and try again"
608
670
  end
609
671
 
610
672
  gem_program = self.class.guess_gem_program
@@ -612,13 +674,12 @@ require 'bundler/setup'
612
674
  env['GEM_HOME'] = [gems_gem_home]
613
675
  env['GEM_PATH'] = [gems_gem_home]
614
676
 
615
- if bundler = find_bundler(gem_program)
677
+ if (bundler = find_bundler(gem_program, version: bundler_version))
616
678
  puts "Detected bundler at #{bundler}"
617
679
  else
618
680
  puts "Installing bundler in #{gems_gem_home}"
619
- if !(bundler = install_bundler(gem_program))
620
- exit 1
621
- end
681
+ bundler = install_bundler(gem_program, version: bundler_version)
682
+ exit(1) unless bundler
622
683
  end
623
684
  self.class.rewrite_shims(
624
685
  File.join(dot_autoproj, 'bin'),
@@ -630,7 +691,7 @@ require 'bundler/setup'
630
691
  save_gemfile
631
692
 
632
693
  puts "Installing autoproj in #{gems_gem_home}"
633
- install_autoproj(bundler)
694
+ install_autoproj(bundler, bundler_version: bundler_version)
634
695
  end
635
696
 
636
697
  def load_config