autoproj 2.4.0 → 2.5.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@
2
2
  require 'tty/color'
3
3
  require 'autoproj/cli/main_test'
4
4
  require 'autoproj/cli/main_plugin'
5
+ require 'autoproj/reporter'
5
6
 
6
7
  module Autoproj
7
8
  module CLI
@@ -34,13 +35,13 @@ def default_report_on_package_failures
34
35
  end
35
36
  end
36
37
 
37
- def run_autoproj_cli(filename, classname, report_options, *args, **extra_options)
38
+ def run_autoproj_cli(filename, classname, report_options, *args, tool_failure_mode: :exit_silent, **extra_options)
38
39
  require "autoproj/cli/#{filename}"
39
40
  if Autobuild::Subprocess.transparent_mode = options[:tool]
40
41
  Autobuild.silent = true
41
42
  Autobuild.color = false
42
43
  report_options[:silent] = true
43
- report_options[:on_package_failures] = :exit_silent
44
+ report_options[:on_package_failures] = tool_failure_mode
44
45
  extra_options[:silent] = true
45
46
  end
46
47
 
@@ -83,6 +84,13 @@ def envsh
83
84
  run_autoproj_cli(:envsh, :Envsh, Hash[])
84
85
  end
85
86
 
87
+ desc 'watch', 'watch workspace for changes', hide: true
88
+ option :show_events, type: :boolean, default: false,
89
+ desc: "whether detected events should be displayed"
90
+ def watch
91
+ run_autoproj_cli(:watch, :Watch, Hash[])
92
+ end
93
+
86
94
  desc 'status [PACKAGES]', 'displays synchronization status between this workspace and the package(s) source'
87
95
  option :local, type: :boolean, default: false,
88
96
  desc: 'only use locally available information (mainly for distributed version control systems such as git)'
@@ -195,7 +203,21 @@ def build(*packages)
195
203
  report_options[:on_package_failures] = :report
196
204
  end
197
205
 
198
- run_autoproj_cli(:build, :Build, Hash[silent: false].merge(report_options), *packages)
206
+ failures = run_autoproj_cli(:build, :Build, report_options, *packages,
207
+ tool_failure_mode: :report_silent)
208
+ if !failures.empty?
209
+ Autobuild.silent = false
210
+ packages_failed = failures.
211
+ map do |e|
212
+ if e.respond_to?(:target) && e.target.respond_to?(:name)
213
+ e.target.name
214
+ end
215
+ end.compact
216
+ if !packages_failed.empty?
217
+ Autobuild.error "#{packages_failed.size} packages failed: #{packages_failed.sort.join(", ")}"
218
+ end
219
+ exit 1
220
+ end
199
221
  end
200
222
 
201
223
  desc 'cache CACHE_DIR', 'create or update a cache directory that can be given to AUTOBUILD_CACHE_DIR'
@@ -343,12 +365,12 @@ def tag(tag_name = nil, *packages)
343
365
  run_autoproj_cli(:tag, :Tag, Hash[], tag_name, *packages)
344
366
  end
345
367
 
346
- desc 'commit [PACKAGES]', 'save the package current versions as a new commit in the main build configuration'
368
+ desc 'commit [TAG_NAME] [PACKAGES]', 'save the package current versions as a new commit in the main build configuration'
347
369
  long_desc <<-EOD
348
370
  The commit subcommand stores the state of all packages (or of the packages
349
371
  selected on the command line) into a new commit in the currently checked-out
350
372
  branch of the build configuration. This state can be retrieved later on by using
351
- "autoproj reset"
373
+ "autoproj reset". If a TAG_NAME is provided, the commit will be tagged.
352
374
 
353
375
  If given no arguments, will list the existing tags
354
376
  EOD
@@ -356,6 +378,8 @@ def tag(tag_name = nil, *packages)
356
378
  desc: 'commit the package set state as well (enabled by default)'
357
379
  option :keep_going, aliases: :k, type: :boolean, banner: '',
358
380
  desc: 'do not stop on build or checkout errors'
381
+ option :tag, aliases: :t, type: :string,
382
+ desc: 'the tag name to use'
359
383
  option :message, aliases: :m, type: :string,
360
384
  desc: 'the message to use for the new commit (the default is to mention the creation of the tag)'
361
385
  def commit(*packages)
@@ -453,15 +477,36 @@ def manifest(*name)
453
477
  end
454
478
 
455
479
  desc 'exec', "runs a command, applying the workspace's environment first"
480
+ option :use_cache, type: :boolean, default: nil,
481
+ desc: "use the cached environment instead of "\
482
+ "loading the whole configuration"
456
483
  def exec(*args)
457
484
  require 'autoproj/cli/exec'
458
- CLI::Exec.new.run(*args)
485
+ Autoproj.report(on_package_failures: default_report_on_package_failures, debug: options[:debug], silent: true) do
486
+ opts = Hash.new
487
+ use_cache = options[:use_cache]
488
+ if !use_cache.nil?
489
+ opts[:use_cached_env] = use_cache
490
+ end
491
+ CLI::Exec.new.run(*args, **opts)
492
+ end
459
493
  end
460
494
 
461
- desc 'which', "resolves the full path to a command within the Autoproj workspace"
495
+ desc 'which', "resolves the full path to a command "\
496
+ " within the Autoproj workspace"
497
+ option :use_cache, type: :boolean, default: nil,
498
+ desc: "use the cached environment instead of "\
499
+ "loading the whole configuration"
462
500
  def which(cmd)
463
501
  require 'autoproj/cli/which'
464
- CLI::Which.new.run(cmd)
502
+ Autoproj.report(on_package_failures: default_report_on_package_failures, debug: options[:debug], silent: true) do
503
+ opts = Hash.new
504
+ use_cache = options[:use_cache]
505
+ if !use_cache.nil?
506
+ opts[:use_cached_env] = use_cache
507
+ end
508
+ CLI::Which.new.run(cmd, **opts)
509
+ end
465
510
  end
466
511
  end
467
512
  end
@@ -7,12 +7,13 @@ module Autoproj
7
7
  module CLI
8
8
  class Reset < InspectionTool
9
9
  def run(ref_name, options)
10
- pkg = manifest.main_package_set.create_autobuild_package
10
+ ws.load_config
11
+ pkg = ws.manifest.main_package_set.create_autobuild_package
11
12
  importer = pkg.importer
12
13
  if !importer || !importer.kind_of?(Autobuild::Git)
13
14
  raise CLIInvalidArguments, "cannot use autoproj reset if the main configuration is not managed by git"
14
15
  end
15
-
16
+
16
17
  # Check if the reflog entry exists
17
18
  begin
18
19
  importer.rev_parse(pkg, ref_name)
@@ -90,6 +90,9 @@ def status_of_package(package_description, only_local: false, snapshot: false)
90
90
  begin importer.snapshot(pkg, nil, exact_state: false, only_local: only_local)
91
91
  rescue Autobuild::PackageException
92
92
  Hash.new
93
+ rescue Exception => e
94
+ package_status.msg << Autoproj.color(" failed to fetch snapshotting information (#{e})", :red)
95
+ return package_status
93
96
  end
94
97
  if snapshot_overrides_vcs?(importer, package_description.vcs, snapshot_version)
95
98
  non_nil_values = snapshot_version.delete_if { |k, v| !v }
@@ -198,7 +201,7 @@ def each_package_status(packages, parallel: ws.config.parallel_import_level, sna
198
201
  Autoproj.warn "Interrupted, waiting for pending jobs to finish"
199
202
  raise
200
203
  rescue Exception => e
201
- Autoproj.error "internal error: #{e}, waiting for pending jobs to finish"
204
+ Autoproj.error "internal error (#{e.class}): #{e}, waiting for pending jobs to finish"
202
205
  raise
203
206
  ensure
204
207
  executor.shutdown
@@ -9,7 +9,7 @@ class Versions < InspectionTool
9
9
  DEFAULT_VERSIONS_FILE_BASENAME = Ops::Snapshot::DEFAULT_VERSIONS_FILE_BASENAME
10
10
 
11
11
  def default_versions_file
12
- File.join( Autoproj.overrides_dir, DEFAULT_VERSIONS_FILE_BASENAME )
12
+ File.join( ws.overrides_dir, DEFAULT_VERSIONS_FILE_BASENAME )
13
13
  end
14
14
 
15
15
  def validate_options(packages, options = Hash.new)
@@ -32,9 +32,9 @@ def run(user_selection, options)
32
32
  packages, *, config_selected =
33
33
  finalize_setup(user_selection,
34
34
  recursive: options[:deps])
35
-
35
+
36
36
  ops = Ops::Snapshot.new(ws.manifest, keep_going: options[:keep_going])
37
-
37
+
38
38
  if user_selection.empty?
39
39
  snapshot_package_sets = (options[:config] != false)
40
40
  snapshot_packages = !options[:config]
@@ -0,0 +1,159 @@
1
+ require 'rbconfig'
2
+ require 'autoproj/cli/inspection_tool'
3
+ require 'autoproj/ops/watch'
4
+ module Autoproj
5
+ module CLI
6
+ class Watch < InspectionTool
7
+ attr_reader :notifier
8
+
9
+ def initialize(*args)
10
+ super(*args)
11
+ @show_events = false
12
+ end
13
+
14
+ def validate_options(unused, options = {})
15
+ _, options = super(unused, options)
16
+ @show_events = options[:show_events]
17
+ nil
18
+ end
19
+
20
+ def show_events?
21
+ @show_events
22
+ end
23
+
24
+ def update_workspace
25
+ initialize_and_load
26
+
27
+ source_packages, _ = finalize_setup([])
28
+ @source_packages_dirs = source_packages.map do |pkg_name|
29
+ ws.manifest.find_autobuild_package(pkg_name).srcdir
30
+ end
31
+ @pkg_sets_dirs = ws.manifest.each_package_set.map do |pkg_set|
32
+ pkg_set.raw_local_dir
33
+ end
34
+ export_env_sh(shell_helpers: ws.config.shell_helpers?)
35
+ end
36
+
37
+ def load_info_from_installation_manifest
38
+ installation_manifest =
39
+ begin
40
+ Autoproj::InstallationManifest.from_workspace_root(ws.root_dir)
41
+ rescue ConfigError
42
+ end
43
+
44
+ @source_packages_dirs = []
45
+ @package_sets = []
46
+
47
+ @source_packages_dirs = installation_manifest.each_package.
48
+ map(&:srcdir)
49
+ @package_sets = installation_manifest.each_package_set.
50
+ map(&:raw_local_dir)
51
+ end
52
+
53
+ def callback
54
+ notifier.stop
55
+ end
56
+
57
+ def create_file_watcher(file)
58
+ notifier.watch(file, :modify) do |e|
59
+ Autobuild.message "#{e.absolute_name} modified" if show_events?
60
+ callback
61
+ end
62
+ end
63
+
64
+ def create_dir_watcher(dir, included_paths: [], excluded_paths: [], inotify_flags: [])
65
+ strip_dir_range = ((dir.size + 1)..-1)
66
+ notifier.watch(dir, :move, :create, :delete, :modify, :dont_follow, *inotify_flags) do |e|
67
+ file_name = e.absolute_name[strip_dir_range]
68
+ included = included_paths.empty? ||
69
+ included_paths.any? { |rx| rx === file_name }
70
+ if included
71
+ included = !excluded_paths.any? { |rx| rx === file_name }
72
+ end
73
+ next if !included
74
+ Autobuild.message "#{e.absolute_name} changed" if show_events?
75
+ callback
76
+ end
77
+ end
78
+
79
+ def create_src_pkg_watchers
80
+ @source_packages_dirs.each do |pkg_srcdir|
81
+ next unless File.exist? pkg_srcdir
82
+ create_dir_watcher(pkg_srcdir, included_paths: ["manifest.xml"])
83
+
84
+ manifest_file = File.join(pkg_srcdir, 'manifest.xml')
85
+ next unless File.exist? manifest_file
86
+ create_file_watcher(manifest_file)
87
+ end
88
+ end
89
+
90
+ def start_watchers
91
+ create_file_watcher(ws.config.path)
92
+ create_src_pkg_watchers
93
+ create_dir_watcher(ws.config_dir,
94
+ excluded_paths: [/(^|#{File::SEPARATOR})\./],
95
+ inotify_flags: [:recursive])
96
+ FileUtils.mkdir_p ws.remotes_dir
97
+ create_dir_watcher(ws.remotes_dir,
98
+ excluded_paths: [/(^|#{File::SEPARATOR})\./],
99
+ inotify_flags: [:recursive])
100
+ end
101
+
102
+ def cleanup_notifier
103
+ notifier.watchers.dup.each_value(&:close)
104
+ notifier.close
105
+ end
106
+
107
+ def assert_watchers_available
108
+ return if RbConfig::CONFIG['target_os'] =~ /linux/
109
+ puts 'error: Workspace watching not available on this platform'
110
+ exit 1
111
+ end
112
+
113
+ def setup_notifier
114
+ assert_watchers_available
115
+
116
+ require 'rb-inotify'
117
+ @notifier = INotify::Notifier.new
118
+ end
119
+
120
+ def cleanup
121
+ if @marker_io
122
+ Ops.watch_cleanup_marker(@marker_io)
123
+ end
124
+ if @notifier
125
+ cleanup_notifier
126
+ end
127
+ end
128
+
129
+ def restart
130
+ cleanup
131
+ args = []
132
+ args << "--show-events" if show_events?
133
+ exec($PROGRAM_NAME, 'watch', *args)
134
+ end
135
+
136
+ def run(**)
137
+ @marker_io = Ops.watch_create_marker(ws.root_dir)
138
+ begin
139
+ update_workspace
140
+ rescue Exception => e
141
+ puts "ERROR: #{e.message}"
142
+ load_info_from_installation_manifest
143
+ end
144
+ setup_notifier
145
+ start_watchers
146
+
147
+ puts 'Watching workspace, press ^C to quit...'
148
+ notifier.run
149
+
150
+ puts 'Workspace changed...'
151
+ restart
152
+ rescue Interrupt
153
+ puts 'Exiting...'
154
+ ensure
155
+ cleanup
156
+ end
157
+ end
158
+ end
159
+ end
@@ -1,14 +1,52 @@
1
- require 'autoproj/cli/inspection_tool'
1
+ require 'autoproj'
2
+ require 'autoproj/ops/cached_env'
3
+ require 'autoproj/ops/which'
4
+ require 'autoproj/ops/watch'
5
+
2
6
  module Autoproj
3
7
  module CLI
4
- class Which < InspectionTool
5
- def run(cmd)
6
- initialize_and_load
7
- finalize_setup(Array.new)
8
+ class Which
9
+ def initialize
10
+ @root_dir = Autoproj.find_workspace_dir
11
+ if !@root_dir
12
+ require 'autoproj/workspace'
13
+ # Will do all sorts of error reporting,
14
+ # or may be able to resolve
15
+ @root_dir = Workspace.default.root_dir
16
+ end
17
+ end
18
+
19
+ def load_cached_env
20
+ env = Ops.load_cached_env(@root_dir)
21
+ return if !env
22
+
23
+ Autobuild::Environment.
24
+ environment_from_export(env, ENV)
25
+ end
26
+
27
+ def run(cmd, use_cached_env: Ops.watch_running?(@root_dir))
28
+ if use_cached_env
29
+ env = load_cached_env
30
+ end
31
+
32
+ if !env
33
+ require 'autoproj'
34
+ require 'autoproj/cli/inspection_tool'
35
+ ws = Workspace.from_dir(@root_dir)
36
+ loader = InspectionTool.new(ws)
37
+ loader.initialize_and_load
38
+ loader.finalize_setup(Array.new)
39
+ env = ws.full_env.resolved_env
40
+ end
8
41
 
9
- puts ws.which(cmd)
10
- rescue Workspace::ExecutableNotFound => e
42
+ path = env['PATH'].split(File::PATH_SEPARATOR)
43
+ puts Ops.which(cmd, path_entries: path)
44
+ rescue ExecutableNotFound => e
45
+ require 'autoproj' # make sure everything is available for error reporting
11
46
  raise CLIInvalidArguments, e.message, e.backtrace
47
+ rescue Exception
48
+ require 'autoproj' # make sure everything is available for error reporting
49
+ raise
12
50
  end
13
51
  end
14
52
  end
@@ -1,3 +1,9 @@
1
+ require 'backports/2.4.0/float/dup'
2
+ require 'backports/2.4.0/fixnum/dup'
3
+ require 'backports/2.4.0/nil_class/dup'
4
+ require 'backports/2.4.0/false_class/dup'
5
+ require 'backports/2.4.0/true_class/dup'
6
+
1
7
  module Autoproj
2
8
  # Class that does the handling of configuration options as well as
3
9
  # loading/saving on disk
@@ -23,6 +29,18 @@ def initialize(path = nil)
23
29
  @declared_options = Hash.new
24
30
  @displayed_options = Hash.new
25
31
  @path = path
32
+ @modified = false
33
+ end
34
+
35
+ # Whether the configuration was changed since the last call to {#load}
36
+ # or {#save}
37
+ def modified?
38
+ @modified
39
+ end
40
+
41
+ # Resets the modified? flag to false
42
+ def reset_modified
43
+ @modified = false
26
44
  end
27
45
 
28
46
  # Deletes the current value for an option
@@ -32,7 +50,9 @@ def initialize(path = nil)
32
50
  # @param [String] the option name
33
51
  # @return the deleted value
34
52
  def reset(name)
53
+ @modified = config.has_key?(name)
35
54
  config.delete(name)
55
+ overrides.delete(name)
36
56
  end
37
57
 
38
58
  # Sets a configuration option
@@ -43,7 +63,12 @@ def reset(name)
43
63
  # user about this value next time it is needed. Otherwise, it will be
44
64
  # asked about it, the new value being used as default
45
65
  def set(key, value, user_validated = false)
46
- config[key] = [value, user_validated]
66
+ if config.has_key?(key)
67
+ @modified = (config[key][0] != value)
68
+ else
69
+ @modified = true
70
+ end
71
+ config[key] = [value.dup, user_validated]
47
72
  end
48
73
 
49
74
  # Override a known option value
@@ -53,6 +78,16 @@ def override(option_name, value)
53
78
  overrides[option_name] = value
54
79
  end
55
80
 
81
+ # Remove a specific override
82
+ def reset_overrides(name)
83
+ @overrides.delete(name)
84
+ end
85
+
86
+ # Remove all overrides
87
+ def reset_overrides
88
+ @overrides.clear
89
+ end
90
+
56
91
  # Tests whether a value is set for the given option name
57
92
  #
58
93
  # @return [Boolean]
@@ -63,23 +98,31 @@ def has_value_for?(name)
63
98
  # Get the value for a given option
64
99
  def get(key, *default_value)
65
100
  if overrides.has_key?(key)
66
- return overrides[key]
101
+ return overrides[key].dup
67
102
  end
68
103
 
104
+ has_value = config.has_key?(key)
69
105
  value, validated = config[key]
70
- if value.nil? && !declared?(key) && !default_value.empty?
71
- default_value.first
72
- elsif value.nil? || (declared?(key) && !validated)
73
- value = configure(key)
106
+
107
+ if !declared?(key)
108
+ if has_value
109
+ return value.dup
110
+ elsif default_value.empty?
111
+ raise ConfigError, "undeclared option '#{key}'"
112
+ else
113
+ default_value.first.dup
114
+ end
74
115
  else
75
- if declared?(key) && (displayed_options[key] != value)
116
+ if validated
76
117
  doc = declared_options[key].short_doc
77
118
  if doc[-1, 1] != "?"
78
119
  doc = "#{doc}:"
79
120
  end
80
121
  displayed_options[key] = value
122
+ value.dup
123
+ else
124
+ configure(key).dup
81
125
  end
82
- value
83
126
  end
84
127
  end
85
128
 
@@ -135,6 +178,7 @@ def configure(option_name)
135
178
  current_value = current_value.first
136
179
  end
137
180
  value = opt.ask(current_value)
181
+ @modified = true
138
182
  config[option_name] = [value, true]
139
183
  displayed_options[option_name] = value
140
184
  value
@@ -143,14 +187,15 @@ def configure(option_name)
143
187
  end
144
188
  end
145
189
 
146
- def load(options = Hash.new)
147
- options = validate_options options,
148
- path: self.path,
149
- reconfigure: false
150
-
151
- if h = YAML.load(File.read(options[:path]))
190
+ def load(path: self.path, reconfigure: false)
191
+ current_keys = @config.keys
192
+ if h = YAML.load(File.read(path))
152
193
  h.each do |key, value|
153
- set(key, value, !options[:reconfigure])
194
+ current_keys.delete(key)
195
+ set(key, value, !reconfigure)
196
+ end
197
+ if current_keys.empty?
198
+ @modified = false
154
199
  end
155
200
  end
156
201
  end
@@ -160,11 +205,14 @@ def reconfigure!
160
205
  config.each do |key, (value, _user_validated)|
161
206
  new_config[key] = [value, false]
162
207
  end
208
+ @modified = true
163
209
  @config = new_config
164
210
  end
165
211
 
166
- def save(path = self.path)
167
- File.open(path, "w") do |io|
212
+ def save(path = self.path, force: false)
213
+ return if !modified? && !force
214
+
215
+ Ops.atomic_write(path) do |io|
168
216
  h = Hash.new
169
217
  config.each do |key, value|
170
218
  h[key] = value.first
@@ -172,6 +220,7 @@ def save(path = self.path)
172
220
 
173
221
  io.write YAML.dump(h)
174
222
  end
223
+ @modified = false
175
224
  end
176
225
 
177
226
  def each_reused_autoproj_installation
@@ -474,6 +523,9 @@ def to_hash
474
523
  @config.each do |key, (value, _)|
475
524
  result[key] = value
476
525
  end
526
+ overrides.each do |key, value|
527
+ result[key] = value
528
+ end
477
529
  result
478
530
  end
479
531
  end