autoproj 2.0.0.rc37 → 2.0.0.rc38

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