autoproj 2.0.0.rc37 → 2.0.0.rc38

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -2
  3. data/Rakefile +1 -1
  4. data/bin/autoproj_bootstrap +34 -2
  5. data/bin/autoproj_bootstrap.in +4 -2
  6. data/bin/autoproj_install +34 -2
  7. data/bin/autoproj_install.in +4 -2
  8. data/lib/autoproj.rb +9 -2
  9. data/lib/autoproj/autobuild.rb +13 -742
  10. data/lib/autoproj/autobuild_extensions/archive_importer.rb +44 -0
  11. data/lib/autoproj/autobuild_extensions/dsl.rb +439 -0
  12. data/lib/autoproj/autobuild_extensions/git.rb +116 -0
  13. data/lib/autoproj/autobuild_extensions/package.rb +159 -0
  14. data/lib/autoproj/autobuild_extensions/svn.rb +11 -0
  15. data/lib/autoproj/cli/base.rb +17 -18
  16. data/lib/autoproj/cli/clean.rb +1 -2
  17. data/lib/autoproj/cli/envsh.rb +1 -2
  18. data/lib/autoproj/cli/inspection_tool.rb +12 -21
  19. data/lib/autoproj/cli/locate.rb +130 -73
  20. data/lib/autoproj/cli/main.rb +31 -5
  21. data/lib/autoproj/cli/main_plugin.rb +79 -0
  22. data/lib/autoproj/cli/main_test.rb +19 -5
  23. data/lib/autoproj/cli/osdeps.rb +1 -2
  24. data/lib/autoproj/cli/patcher.rb +21 -0
  25. data/lib/autoproj/cli/query.rb +34 -41
  26. data/lib/autoproj/cli/show.rb +121 -52
  27. data/lib/autoproj/cli/status.rb +4 -5
  28. data/lib/autoproj/cli/tag.rb +1 -1
  29. data/lib/autoproj/cli/test.rb +7 -6
  30. data/lib/autoproj/cli/update.rb +8 -22
  31. data/lib/autoproj/cli/versions.rb +1 -2
  32. data/lib/autoproj/configuration.rb +1 -1
  33. data/lib/autoproj/environment.rb +2 -7
  34. data/lib/autoproj/exceptions.rb +10 -8
  35. data/lib/autoproj/find_workspace.rb +46 -12
  36. data/lib/autoproj/installation_manifest.rb +34 -25
  37. data/lib/autoproj/local_package_set.rb +86 -0
  38. data/lib/autoproj/manifest.rb +448 -503
  39. data/lib/autoproj/metapackage.rb +31 -5
  40. data/lib/autoproj/ops/configuration.rb +46 -45
  41. data/lib/autoproj/ops/import.rb +150 -60
  42. data/lib/autoproj/ops/install.rb +25 -1
  43. data/lib/autoproj/ops/loader.rb +4 -1
  44. data/lib/autoproj/ops/main_config_switcher.rb +4 -4
  45. data/lib/autoproj/ops/snapshot.rb +4 -3
  46. data/lib/autoproj/os_package_installer.rb +105 -46
  47. data/lib/autoproj/os_package_resolver.rb +63 -36
  48. data/lib/autoproj/package_definition.rb +1 -0
  49. data/lib/autoproj/package_managers/apt_dpkg_manager.rb +30 -27
  50. data/lib/autoproj/package_managers/bundler_manager.rb +64 -18
  51. data/lib/autoproj/package_managers/gem_manager.rb +4 -2
  52. data/lib/autoproj/package_managers/manager.rb +26 -7
  53. data/lib/autoproj/package_managers/shell_script_manager.rb +4 -4
  54. data/lib/autoproj/package_managers/zypper_manager.rb +1 -1
  55. data/lib/autoproj/package_manifest.rb +154 -137
  56. data/lib/autoproj/package_selection.rb +16 -2
  57. data/lib/autoproj/package_set.rb +352 -309
  58. data/lib/autoproj/query.rb +13 -1
  59. data/lib/autoproj/system.rb +2 -2
  60. data/lib/autoproj/test.rb +164 -11
  61. data/lib/autoproj/variable_expansion.rb +15 -42
  62. data/lib/autoproj/vcs_definition.rb +93 -76
  63. data/lib/autoproj/version.rb +1 -1
  64. data/lib/autoproj/workspace.rb +116 -80
  65. metadata +10 -2
@@ -34,6 +34,7 @@ def initialize(autobuild, package_set, file)
34
34
  @user_blocks = []
35
35
  @modes = ['import', 'build']
36
36
  @setup = false
37
+ @vcs = VCSDefinition.none
37
38
  end
38
39
 
39
40
  # The modes in which this package will be used
@@ -13,36 +13,39 @@ def initialize(ws, status_file = "/var/lib/dpkg/status")
13
13
  %w{DEBIAN_FRONTEND=noninteractive apt-get install -y})
14
14
  end
15
15
 
16
+ def self.parse_package_status(installed_packages, paragraph)
17
+ if paragraph =~ /^Status: install ok installed$/
18
+ if paragraph =~ /^Package: (.*)$/
19
+ installed_packages << $1
20
+ end
21
+ if paragraph =~ /^Provides: (.*)$/
22
+ installed_packages.merge($1.split(',').map(&:strip))
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.parse_dpkg_status(status_file)
28
+ installed_packages = Set.new
29
+ dpkg_status = File.read(status_file)
30
+ dpkg_status << "\n"
31
+
32
+ dpkg_status = StringScanner.new(dpkg_status)
33
+ if !dpkg_status.scan(/Package: /)
34
+ raise ArgumentError, "expected #{status_file} to have Package: lines but found none"
35
+ end
36
+
37
+ while paragraph_end = dpkg_status.scan_until(/Package: /)
38
+ paragraph = "Package: #{paragraph_end[0..-10]}"
39
+ parse_package_status(installed_packages, paragraph)
40
+ end
41
+ parse_package_status(installed_packages, "Package: #{dpkg_status.rest}")
42
+ installed_packages
43
+ end
44
+
16
45
  # On a dpkg-enabled system, checks if the provided package is installed
17
46
  # and returns true if it is the case
18
47
  def installed?(package_name, filter_uptodate_packages: false, install_only: false)
19
- if !@installed_packages
20
- @installed_packages = Set.new
21
- dpkg_status = File.readlines(status_file)
22
- dpkg_status << ""
23
-
24
- current_packages = []
25
- is_installed = false
26
- dpkg_status.each do |line|
27
- line = line.chomp
28
- line = line.encode( "UTF-8", "binary", :invalid => :replace, :undef => :replace)
29
- if line == ""
30
- if is_installed
31
- current_packages.each do |pkg|
32
- @installed_packages << pkg
33
- end
34
- is_installed = false
35
- end
36
- current_packages.clear
37
- elsif line =~ /Package: (.*)$/
38
- current_packages << $1
39
- elsif line =~ /Provides: (.*)$/
40
- current_packages.concat($1.split(',').map(&:strip))
41
- elsif line == "Status: install ok installed"
42
- is_installed = true
43
- end
44
- end
45
- end
48
+ @installed_packages ||= AptDpkgManager.parse_dpkg_status(status_file)
46
49
 
47
50
  if package_name =~ /^(\w[a-z0-9+-.]+)/
48
51
  @installed_packages.include?($1)
@@ -24,13 +24,18 @@ def self.with_prerelease(*value)
24
24
  end
25
25
  end
26
26
 
27
- def initialize(ws)
28
- super
29
- self.call_while_empty = true
27
+ # (see Manager#call_while_empty?)
28
+ def call_while_empty?
29
+ !workspace_configuration_gemfiles.empty?
30
30
  end
31
31
 
32
- # Filters all paths that come from other autoproj installations out
33
- # of GEM_PATH
32
+ # (see Manager#strict?)
33
+ def strict?
34
+ true
35
+ end
36
+
37
+ # Set up the workspace environment to work with the bundler-managed
38
+ # gems
34
39
  def initialize_environment
35
40
  env = ws.env
36
41
 
@@ -87,6 +92,15 @@ def initialize_environment
87
92
  end
88
93
  end
89
94
 
95
+ # @api private
96
+ #
97
+ # Update RUBYLIB to add the gems that are part of the bundler
98
+ # install
99
+ #
100
+ # @param [Array<String>] bundle_rubylib the rubylib entries reported
101
+ # by bundler
102
+ # @param [Array<String>] system_rubylib the rubylib entries that are
103
+ # set by the underlying ruby interpreter itself
90
104
  def update_env_rubylib(bundle_rubylib, system_rubylib = discover_rubylib)
91
105
  current = (ws.env.resolved_env['RUBYLIB'] || '').split(File::PATH_SEPARATOR) + system_rubylib
92
106
  (bundle_rubylib - current).each do |p|
@@ -94,6 +108,16 @@ def update_env_rubylib(bundle_rubylib, system_rubylib = discover_rubylib)
94
108
  end
95
109
  end
96
110
 
111
+ # @api private
112
+ #
113
+ # Parse an osdep entry into a gem name and gem version
114
+ #
115
+ # The 'gem' entries in the osdep files can contain a version
116
+ # specification. This method parses the two parts and return them
117
+ #
118
+ # @param [String] entry the osdep entry
119
+ # @return [(String,String),(String,nil)] the gem name, and an
120
+ # optional version specification
97
121
  def parse_package_entry(entry)
98
122
  if entry =~ /^([^><=~]*)([><=~]+.*)$/
99
123
  [$1.strip, $2.strip]
@@ -103,7 +127,14 @@ def parse_package_entry(entry)
103
127
  end
104
128
 
105
129
  class NotCleanState < RuntimeError; end
106
-
130
+
131
+ # @api private
132
+ #
133
+ # Create backup files matching a certain file mapping
134
+ #
135
+ # @param [Hash<String,String>] mapping a mapping from the original
136
+ # file to the file into which it should be backed up. The source
137
+ # file might not exist.
107
138
  def backup_files(mapping)
108
139
  mapping.each do |file, backup_file|
109
140
  if File.file?(file)
@@ -112,6 +143,11 @@ def backup_files(mapping)
112
143
  end
113
144
  end
114
145
 
146
+ # @api private
147
+ #
148
+ # Restore backups saved by {#backup_file}
149
+ #
150
+ # @param (see #backup_file)
115
151
  def backup_restore(mapping)
116
152
  mapping.each do |file, backup_file|
117
153
  if File.file?(backup_file)
@@ -120,6 +156,11 @@ def backup_restore(mapping)
120
156
  end
121
157
  end
122
158
 
159
+ # @api private
160
+ #
161
+ # Remove backups created by {#backup_files}
162
+ #
163
+ # @param (see #backup_file)
123
164
  def backup_clean(mapping)
124
165
  mapping.each do |file, backup_file|
125
166
  if File.file?(backup_file)
@@ -183,8 +224,8 @@ def self.run_bundler(ws, *commandline, gemfile: nil)
183
224
  def merge_gemfiles(*path, unlock: [])
184
225
  gems_remotes = Set.new
185
226
  dependencies = Hash.new do |h, k|
186
- h[k] = Hash.new do |h, k|
187
- h[k] = Hash.new do |a, b|
227
+ h[k] = Hash.new do |i, j|
228
+ i[j] = Hash.new do |a, b|
188
229
  a[b] = Array.new
189
230
  end
190
231
  end
@@ -239,6 +280,20 @@ def merge_gemfiles(*path, unlock: [])
239
280
  contents.join("\n")
240
281
  end
241
282
 
283
+ def workspace_configuration_gemfiles
284
+ gemfiles = []
285
+ ws.manifest.each_package_set do |source|
286
+ if source.local_dir && File.file?(pkg_set_gemfile = File.join(source.local_dir, 'Gemfile'))
287
+ gemfiles << pkg_set_gemfile
288
+ end
289
+ end
290
+ # In addition, look into overrides.d
291
+ Dir.glob(File.join(ws.overrides_dir, "*.gemfile")) do |overrides_gemfile_path|
292
+ gemfiles << overrides_gemfile_path
293
+ end
294
+ gemfiles
295
+ end
296
+
242
297
  def install(gems, filter_uptodate_packages: false, install_only: false)
243
298
  root_dir = File.join(ws.prefix_dir, 'gems')
244
299
  gemfile_path = File.join(root_dir, 'Gemfile')
@@ -257,16 +312,7 @@ def install(gems, filter_uptodate_packages: false, install_only: false)
257
312
  end
258
313
  end
259
314
 
260
- gemfiles = []
261
- ws.manifest.each_package_set do |source|
262
- if source.local_dir && File.file?(pkg_set_gemfile = File.join(source.local_dir, 'Gemfile'))
263
- gemfiles << pkg_set_gemfile
264
- end
265
- end
266
- # In addition, look into overrides.d
267
- Dir.glob(File.join(ws.overrides_dir, "*.gemfile")) do |gemfile_path|
268
- gemfiles << gemfile_path
269
- end
315
+ gemfiles = workspace_configuration_gemfiles
270
316
  gemfiles << File.join(ws.dot_autoproj_dir, 'Gemfile')
271
317
 
272
318
  # Save the osdeps entries in a temporary gemfile and finally
@@ -90,6 +90,8 @@ def self.use_cache_dir
90
90
  def self.gem_home
91
91
  @gem_home
92
92
  end
93
+
94
+ @gem_home = nil
93
95
 
94
96
  # Returns the set of default options that are added to gem
95
97
  #
@@ -158,7 +160,7 @@ def build_gem_cmdlines(gems)
158
160
 
159
161
  def pristine(gems)
160
162
  guess_gem_program
161
- base_cmdline = [Autobuild.tool_in_path('ruby'), '-S', Autobuild.tool('gem')]
163
+ base_cmdline = [Autobuild.tool_in_path('ruby', env: ws.env), '-S', Autobuild.tool('gem')]
162
164
  cmdlines = [
163
165
  [*base_cmdline, 'clean'],
164
166
  ]
@@ -176,7 +178,7 @@ def pristine(gems)
176
178
  def install(gems)
177
179
  guess_gem_program
178
180
 
179
- base_cmdline = [Autobuild.tool_in_path('ruby'), '-S', Autobuild.tool('gem'), 'install', *GemManager.default_install_options]
181
+ base_cmdline = [Autobuild.tool_in_path('ruby', env: ws.env), '-S', Autobuild.tool('gem'), 'install', *GemManager.default_install_options]
180
182
  if !GemManager.with_doc
181
183
  base_cmdline << '--no-rdoc' << '--no-ri'
182
184
  end
@@ -16,19 +16,38 @@ def enabled?; !!@enabled end
16
16
  attr_writer :silent
17
17
  def silent?; !!@silent end
18
18
 
19
- attr_writer :call_while_empty
20
- def call_while_empty?; !!@call_while_empty end
19
+ # Whether this package manager should be called even if no packages
20
+ # should be installed
21
+ #
22
+ # This is needed if the installer has ways to get specifications of
23
+ # packages to install through other means than the osdep system, as
24
+ # e.g. {BundlerManager} that would install gems listed in
25
+ # autoproj/Gemfile
26
+ def call_while_empty?
27
+ false
28
+ end
29
+
30
+ # Whether this package manager needs to maintain a list of all the
31
+ # packages that are needed for the whole installation (true), or
32
+ # needs only to be called with packages to install
33
+ #
34
+ # OS package managers are generally non-strict (once a package is
35
+ # installed, it's available to all). Package managers like
36
+ # {BundlerManager} are strict as they maintain a list of gems that
37
+ # are then made available to the whole installation
38
+ #
39
+ # The default is false, reimplement in subclasses to return true
40
+ def strict?
41
+ false
42
+ end
21
43
 
22
- # Create a package manager registered with various names
44
+ # Create a package manager
23
45
  #
24
- # @param [Array<String>] names the package manager names. It MUST be
25
- # different from the OS names that autoproj uses. See the comment
26
- # for OS_PACKAGE_HANDLERS for an explanation
46
+ # @param [Workspace] ws the underlying workspace
27
47
  def initialize(ws)
28
48
  @ws = ws
29
49
  @enabled = true
30
50
  @silent = true
31
- @call_while_empty = false
32
51
  end
33
52
 
34
53
  # Overload to perform initialization of environment variables in
@@ -3,7 +3,7 @@ module PackageManagers
3
3
  # Base class for all package managers that simply require the call of a
4
4
  # shell script to install packages (e.g. yum, apt, ...)
5
5
  class ShellScriptManager < Manager
6
- def self.execute(command_line, with_locking, with_root)
6
+ def self.execute(command_line, with_locking, with_root, env: Autobuild.env)
7
7
  if with_locking
8
8
  File.open('/tmp/autoproj_osdeps_lock', 'w') do |lock_io|
9
9
  begin
@@ -11,7 +11,7 @@ def self.execute(command_line, with_locking, with_root)
11
11
  Autoproj.message " waiting for other autoproj instances to finish their osdeps installation"
12
12
  sleep 5
13
13
  end
14
- return execute(command_line, false, with_root)
14
+ return execute(command_line, false, with_root, env: env)
15
15
  ensure
16
16
  lock_io.flock(File::LOCK_UN)
17
17
  end
@@ -19,7 +19,7 @@ def self.execute(command_line, with_locking, with_root)
19
19
  end
20
20
 
21
21
  if with_root
22
- sudo = Autobuild.tool_in_path('sudo')
22
+ sudo = Autobuild.tool_in_path('sudo', env: env)
23
23
  command_line = [sudo, *command_line]
24
24
  end
25
25
 
@@ -188,7 +188,7 @@ def install(packages, filter_uptodate_packages: false, install_only: false,
188
188
  Autoproj.message shell_script
189
189
  end
190
190
 
191
- ShellScriptManager.execute([*auto_install_cmd, *packages], needs_locking?, needs_root?)
191
+ ShellScriptManager.execute([*auto_install_cmd, *packages], needs_locking?, needs_root?, env: ws.env)
192
192
  return true
193
193
  end
194
194
  false
@@ -9,7 +9,7 @@ def initialize(ws)
9
9
  end
10
10
 
11
11
  def filter_uptodate_packages(packages)
12
- result = `LANG=C rpm -q --whatprovides '#{packages.join("' '")}'`
12
+ `LANG=C rpm -q --whatprovides '#{packages.join("' '")}'`
13
13
  has_all_pkgs = $?.success?
14
14
 
15
15
  if !has_all_pkgs
@@ -3,201 +3,218 @@ module Autoproj
3
3
  #
4
4
  # Use PackageManifest.load to create
5
5
  class PackageManifest
6
- # Load a manifest.xml file and returns the corresponding
7
- # PackageManifest object
6
+ # Create a null manifest for the given package
7
+ def self.null(package)
8
+ new(package, null: true)
9
+ end
10
+
11
+ # Load a manifest.xml file and returns the corresponding PackageManifest
12
+ # object
13
+ #
14
+ # @param [PackageDescription] the package we're loading it for
15
+ # @param [String] file the path to the manifest.xml file
16
+ # @return [PackageManifest]
17
+ # @see parse
8
18
  def self.load(package, file)
9
- doc =
10
- begin REXML::Document.new(File.read(file))
11
- rescue REXML::ParseException => e
12
- raise Autobuild::PackageException.new(package.name, 'prepare'), "invalid #{file}: #{e.message}"
13
- end
19
+ parse(package, File.read(file), path: file)
20
+ end
14
21
 
15
- PackageManifest.new(package, doc)
22
+ # Create a PackageManifest object from the XML content provided as a
23
+ # string
24
+ #
25
+ # @param [PackageDescription] the package we're loading it for
26
+ # @param [String] contents the manifest.xml contents as a string
27
+ # @param [String] path the file path, used for error reporting
28
+ # @return [PackageManifest]
29
+ # @see load
30
+ def self.parse(package, contents, path: '<loaded from string>')
31
+ manifest = PackageManifest.new(package)
32
+ loader = Loader.new(path, manifest)
33
+ begin
34
+ REXML::Document.parse_stream(contents, loader)
35
+ rescue REXML::ParseException => e
36
+ raise Autobuild::PackageException.new(package.name, 'prepare'), "invalid #{file}: #{e.message}"
37
+ end
38
+ manifest
16
39
  end
17
40
 
41
+ ContactInfo = Struct.new :name, :email
42
+ Dependency = Struct.new :name, :optional, :modes
43
+
18
44
  # The Autobuild::Package instance this manifest applies on
19
45
  attr_reader :package
20
- # The raw XML data as a Nokogiri document
21
- attr_reader :xml
22
-
23
- # The list of tags defined for this package
24
- #
25
- # Tags are defined as multiple <tags></tags> blocks, each of which can
26
- # contain multiple comma-separated tags
27
- def tags
28
- result = []
29
- xml.elements.each('package/tags') do |node|
30
- result.concat((node.text || "").strip.split(','))
31
- end
32
- result
46
+ attr_accessor :description
47
+ attr_accessor :brief_description
48
+ attr_reader :dependencies
49
+ attr_accessor :tags
50
+ attr_accessor :url
51
+ attr_accessor :license
52
+ attr_accessor :version
53
+ attr_accessor :authors
54
+ attr_accessor :maintainers
55
+ attr_accessor :rock_maintainers
56
+
57
+ # Add a declared dependency to this package
58
+ def add_dependency(name, optional: false, modes: [])
59
+ dependencies << Dependency.new(name, optional, modes)
33
60
  end
34
61
 
35
62
  def has_documentation?
36
- xml.elements.each('package/description') do |node|
37
- doc = (node.text || "").strip
38
- if !doc.empty?
39
- return true
40
- end
41
- end
42
- return false
63
+ !!description
43
64
  end
44
65
 
45
66
  def documentation
46
- xml.elements.each('package/description') do |node|
47
- doc = (node.text || "").strip
48
- if !doc.empty?
49
- return doc
50
- end
51
- end
52
- return short_documentation
67
+ description || short_documentation
53
68
  end
54
69
 
55
70
  def has_short_documentation?
56
- xml.elements.each('package/description') do |node|
57
- doc = node.attributes['brief']
58
- if doc
59
- doc = doc.to_s.strip
60
- end
61
- if doc && !doc.empty?
62
- return true
63
- end
64
- end
65
- false
71
+ !!brief_description
66
72
  end
67
73
 
68
74
  def short_documentation
69
- xml.elements.each('package/description') do |node|
70
- doc = node.attributes['brief']
71
- if doc
72
- doc = doc.to_s.strip
73
- end
74
- if doc && !doc.empty?
75
- return doc.to_s
76
- end
77
- end
78
- "no documentation available for #{package.name} in its manifest.xml file"
75
+ brief_description ||
76
+ "no documentation available for package '#{package.name}' in its manifest.xml file"
79
77
  end
80
78
 
81
- def initialize(package, doc = REXML::Document.new)
79
+ def initialize(package, null: false)
82
80
  @package = package
83
- @xml = doc
81
+ @dependencies = []
82
+ @authors = []
83
+ @maintainers = []
84
+ @rock_maintainers = []
85
+ @tags = []
86
+ @null = null
84
87
  end
85
88
 
86
- def each_dependency(in_modes = Array.new, &block)
87
- return enum_for(__method__) if !block_given?
88
-
89
- depend_nodes = xml.elements.to_a('package/depend') +
90
- xml.elements.to_a('package/depend_optional') +
91
- xml.elements.to_a('package/rosdep')
92
- in_modes.each do |m|
93
- depend_nodes += xml.elements.to_a("package/#{m}_depend")
94
- end
95
-
96
- depend_nodes.each do |node|
97
- dependency = node.attributes['package'] || node.attributes['name']
98
- optional = (node.attributes['optional'].to_s == '1' || node.name == "depend_optional")
99
- modes = node.attributes['modes'].to_s.split(',')
100
- if node.name =~ /^(\w+)_depend$/
101
- modes << $1
102
- end
103
- if !modes.empty? && modes.none? { |m| in_modes.include?(m) }
104
- next
105
- end
89
+ # Whether this is a null manifest (used for packages that have actually
90
+ # no manifest) or not
91
+ def null?
92
+ !!@null
93
+ end
106
94
 
107
- if dependency
108
- yield(dependency, optional)
109
- elsif node.name == 'rosdep'
110
- raise ConfigError.new, "manifest of #{package.name} has a <rosdep> tag without a 'name' attribute"
111
- else
112
- raise ConfigError.new, "manifest of #{package.name} has a <#{node.name}> tag without a 'package' attribute"
95
+ def each_dependency(in_modes = Array.new, &block)
96
+ return enum_for(__method__, in_modes) if !block_given?
97
+ dependencies.each do |dep|
98
+ if dep.modes.empty? || in_modes.any? { |m| dep.modes.include?(m) }
99
+ yield(dep.name, dep.optional)
113
100
  end
114
101
  end
115
-
116
- package.os_packages.each do |name|
117
- yield(name, false)
118
- end
119
102
  end
120
103
 
121
104
  def each_os_dependency(modes = Array.new, &block)
122
- Autoproj.warn "PackageManifest#each_os_dependency called, fix your code"
105
+ Autoproj.warn_deprecated "#{self.class}##{__method__}", "call #each_dependency instead"
123
106
  return each_dependency(modes, &block)
124
107
  end
125
108
 
126
109
  def each_package_dependency(modes = Array.new, &block)
127
- Autoproj.warn "PackageManifest#each_os_dependency called, fix your code"
110
+ Autoproj.warn_deprecated "#{self.class}##{__method__}", "call #each_dependency instead"
128
111
  return each_dependency(modes, &block)
129
112
  end
130
113
 
131
114
  def each_rock_maintainer
132
115
  return enum_for(__method__) if !block_given?
133
- xml.elements.each('package/rock_maintainer') do |maintainer|
134
- (maintainer.text || "").strip.split(',').each do |str|
135
- name, email = str.split('/').map(&:strip)
136
- email = nil if email && email.empty?
137
- yield(name, email)
138
- end
116
+ rock_maintainers.each do |m|
117
+ yield(m.name, m.email)
139
118
  end
140
119
  end
141
120
 
142
121
  def each_maintainer
143
122
  return enum_for(__method__) if !block_given?
144
- xml.elements.each('package/maintainer') do |maintainer|
145
- (maintainer.text || "").strip.split(',').each do |str|
146
- name, email = str.split('/').map(&:strip)
147
- email = nil if email && email.empty?
148
- yield(name, email)
149
- end
123
+ maintainers.each do |m|
124
+ yield(m.name, m.email)
150
125
  end
151
126
  end
152
127
 
153
128
  # Enumerates the name and email of each author. If no email is present,
154
129
  # yields (name, nil)
155
130
  def each_author
156
- if !block_given?
157
- return enum_for(:each_author)
131
+ return enum_for(__method__) if !block_given?
132
+ authors.each do |m|
133
+ yield(m.name, m.email)
158
134
  end
135
+ end
136
+
137
+ # @api private
138
+ #
139
+ # REXML stream parser object used to load the XML contents into a
140
+ # {PackageManifest} object
141
+ class Loader
142
+ include REXML::StreamListener
159
143
 
160
- xml.elements.each('package/author') do |author|
161
- (author.text || "").strip.split(',').each do |str|
144
+ attr_reader :path, :manifest
145
+
146
+ def initialize(path, manifest)
147
+ @path = path
148
+ @manifest = manifest
149
+ end
150
+
151
+ def parse_depend_tag(tag_name, attributes, modes: [], optional: false)
152
+ package = attributes['package'] || attributes['name']
153
+ if !package
154
+ raise InvalidPackageManifest, "found '#{tag_name}' tag in #{path} without a 'package' attribute"
155
+ end
156
+
157
+ if tag_modes = attributes['modes']
158
+ modes += tag_modes.split(',')
159
+ end
160
+
161
+ manifest.add_dependency(
162
+ package,
163
+ optional: optional || (attributes['optional'] == '1'),
164
+ modes: modes)
165
+ end
166
+
167
+ def parse_contact_field(text)
168
+ text.strip.split(',').map do |str|
162
169
  name, email = str.split('/').map(&:strip)
163
170
  email = nil if email && email.empty?
164
- yield(name, email)
171
+ ContactInfo.new(name, email)
165
172
  end
166
173
  end
167
- end
168
174
 
169
- # If +name+ points to a text element in the XML document, returns the
170
- # content of that element. If no element matches +name+, or if the
171
- # content is empty, returns nil
172
- def text_node(name)
173
- xml.elements.each(name) do |str|
174
- str = (str.text || "").strip
175
- if !str.empty?
176
- return str
175
+ TEXT_FIELDS = Set['url', 'license', 'version', 'description']
176
+ AUTHOR_FIELDS = Set['author', 'maintainer', 'rock_maintainer']
177
+
178
+ def tag_start(name, attributes)
179
+ if name == 'depend'
180
+ parse_depend_tag(name, attributes)
181
+ elsif name == 'depend_optional'
182
+ parse_depend_tag(name, attributes, optional: true)
183
+ elsif name == 'rosdep'
184
+ parse_depend_tag(name, attributes)
185
+ elsif name =~ /^(\w+)_depend$/
186
+ parse_depend_tag(name, attributes, modes: [$1])
187
+ elsif name == 'description'
188
+ if brief = attributes['brief']
189
+ manifest.brief_description = brief
190
+ end
191
+ @tag_text = ''
192
+ elsif TEXT_FIELDS.include?(name) || AUTHOR_FIELDS.include?(name)
193
+ @tag_text = ''
194
+ elsif name == 'tags'
195
+ @tag_text = ''
196
+ else
197
+ @tag_text = nil
177
198
  end
178
199
  end
179
- nil
180
- end
181
-
182
- # The package associated URL, usually meant to direct to a website
183
- #
184
- # Returns nil if there is none
185
- def url
186
- return text_node('package/url')
187
- end
188
-
189
- # The package license name
190
- #
191
- # Returns nil if there is none
192
- def license
193
- return text_node('package/license')
194
- end
195
-
196
- # The package version number
197
- #
198
- # Returns 0 if none is declared
199
- def version
200
- return text_node("version")
200
+ def text(text)
201
+ if @tag_text
202
+ @tag_text << text
203
+ end
204
+ end
205
+ def tag_end(name)
206
+ if AUTHOR_FIELDS.include?(name)
207
+ manifest.send("#{name}s").concat(parse_contact_field(@tag_text))
208
+ elsif TEXT_FIELDS.include?(name)
209
+ field = @tag_text.strip
210
+ if !field.empty?
211
+ manifest.send("#{name}=", field)
212
+ end
213
+ elsif name == 'tags'
214
+ manifest.tags.concat(@tag_text.strip.split(',').map(&:strip))
215
+ end
216
+ @tag_text = nil
217
+ end
201
218
  end
202
219
  end
203
220
  end