autoproj 2.4.0 → 2.5.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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