autoproj 1.9.6 → 1.9.7.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ module Autoproj
2
+ # A set of packages that can be referred to by name
3
+ class Metapackage
4
+ # The metapackage name
5
+ attr_reader :name
6
+ # The packages listed in this metapackage
7
+ attr_reader :packages
8
+ # The normal dependency handling behaviour is to generate an error if a
9
+ # metapackage is selected for the build but some of its dependencies
10
+ # cannot be built. This modifies the behaviour to simply ignore the
11
+ # problematic packages.
12
+ attr_writer :weak_dependencies
13
+
14
+ # @return [Boolean] whether the dependencies from this metapackage are
15
+ # weak or not
16
+ # @see #weak_dependencies
17
+ def weak_dependencies?
18
+ !!@weak_dependencies
19
+ end
20
+
21
+ def initialize(name)
22
+ @name = name
23
+ @packages = []
24
+ @weak_dependencies = false
25
+ end
26
+
27
+ # Adds a package to this metapackage
28
+ #
29
+ # @param [Autobuild::Package] pkg
30
+ def add(pkg)
31
+ @packages << pkg
32
+ end
33
+
34
+ # Lists the packages contained in this metapackage
35
+ #
36
+ # @yieldparam [Autobuild::Package] pkg
37
+ def each_package(&block)
38
+ @packages.each(&block)
39
+ end
40
+
41
+ # Tests if the given package is included in this metapackage
42
+ #
43
+ # @param [String,#name] pkg the package or package name
44
+ def include?(pkg)
45
+ if !pkg.respond_to?(:to_str)
46
+ pkg = pkg.name
47
+ end
48
+ @packages.any? { |p| p.name == pkg }
49
+ end
50
+ end
51
+ end
@@ -1,6 +1,20 @@
1
1
  module Autoproj
2
2
  class InputError < RuntimeError; end
3
3
 
4
+ class << self
5
+ # Programatically overriden autoproj options
6
+ #
7
+ # @see override_option
8
+ attr_reader :option_overrides
9
+ end
10
+ @option_overrides = Hash.new
11
+
12
+ # Programatically override a user-selected option without changing the
13
+ # configuration file
14
+ def self.override_option(option_name, value)
15
+ @option_overrides[option_name] = value
16
+ end
17
+
4
18
  class BuildOption
5
19
  attr_reader :name
6
20
  attr_reader :type
@@ -88,11 +88,11 @@ module Autoproj
88
88
  end
89
89
  end
90
90
  end
91
-
92
-
91
+
92
+ sudo = Autobuild.tool_in_path('sudo')
93
93
  Tempfile.open('osdeps_sh') do |io|
94
94
  io.puts "#! /bin/bash"
95
- io.puts GAIN_ROOT_ACCESS
95
+ io.puts GAIN_ROOT_ACCESS % [sudo]
96
96
  io.write script
97
97
  io.flush
98
98
  Autobuild::Subprocess.run 'autoproj', 'osdeps', '/bin/bash', io.path
@@ -102,7 +102,7 @@ module Autoproj
102
102
  GAIN_ROOT_ACCESS = <<-EOSCRIPT
103
103
  # Gain root access using sudo
104
104
  if test `id -u` != "0"; then
105
- exec sudo /bin/bash $0 "$@"
105
+ exec %s /bin/bash $0 "$@"
106
106
 
107
107
  fi
108
108
  EOSCRIPT
@@ -214,6 +214,15 @@ fi
214
214
  end
215
215
  end
216
216
 
217
+ #Package manger for OpenSuse and Suse (untested)
218
+ class ZypperManager < ShellScriptManager
219
+ def initialize
220
+ super(['zypper'], true,
221
+ "zypper -n install '%s'",
222
+ "zypper -n install '%s'")
223
+ end
224
+ end
225
+
217
226
  # Package manager interface for systems that use yum
218
227
  class YumManager < ShellScriptManager
219
228
  def initialize
@@ -389,10 +398,18 @@ fi
389
398
  end
390
399
  end
391
400
 
401
+ def reinstall
402
+ base_cmdline = [Autobuild.tool_in_path('ruby'), '-S', Autobuild.tool('gem')]
403
+ Autobuild::Subprocess.run 'autoproj', 'osdeps', 'reinstall', *base_cmdline,
404
+ 'clean'
405
+ Autobuild::Subprocess.run 'autoproj', 'osdeps', 'reinstall', *base_cmdline,
406
+ 'pristine', '--all', '--extensions'
407
+ end
408
+
392
409
  def install(gems)
393
410
  guess_gem_program
394
411
 
395
- base_cmdline = [Autobuild.tool('gem'), 'install']
412
+ base_cmdline = [Autobuild.tool_in_path('ruby'), '-S', Autobuild.tool('gem'), 'install']
396
413
  if !GemManager.with_doc
397
414
  base_cmdline << '--no-rdoc' << '--no-ri'
398
415
  end
@@ -690,6 +707,7 @@ fi
690
707
  PackageManagers::PacmanManager,
691
708
  PackageManagers::YumManager,
692
709
  PackageManagers::PortManager,
710
+ PackageManagers::ZypperManager,
693
711
  PackageManagers::PipManager]
694
712
  OS_PACKAGE_HANDLERS = {
695
713
  'debian' => 'apt-dpkg',
@@ -697,7 +715,8 @@ fi
697
715
  'arch' => 'pacman',
698
716
  'manjarolinux' => 'pacman',
699
717
  'fedora' => 'yum',
700
- 'darwin' => 'port'
718
+ 'darwin' => 'port',
719
+ 'opensuse' => 'zypper'
701
720
  }
702
721
 
703
722
  # The information contained in the OSdeps files, as a hash
@@ -888,6 +907,8 @@ fi
888
907
  @operating_system = [[lsb_name, "debian"], lsb_versions]
889
908
  elsif lsb_name != 'arch' && File.exists?("/etc/arch-release")
890
909
  @operating_system = [[lsb_name, "arch"], lsb_versions]
910
+ elsif lsb_name != 'opensuse' && File.exists?("/etc/SuSE-release")
911
+ @operating_system = [[lsb_name, "opensuse"], lsb_versions]
891
912
  elsif lsb_name != 'debian'
892
913
  # Debian unstable cannot be detected with lsb_release,
893
914
  # so debian has its own detection logic
@@ -925,6 +946,11 @@ fi
925
946
  [['darwin'], [version.strip]]
926
947
  elsif Autobuild.windows?
927
948
  [['windows'], []]
949
+ elsif File.exists?('/etc/SuSE-release')
950
+ version = File.read('/etc/SuSE-release').strip
951
+ version =~/.*VERSION\s+=\s+([^\s]+)/
952
+ version = $1
953
+ [['opensuse'], [version.strip]]
928
954
  end
929
955
  end
930
956
 
@@ -1466,8 +1492,18 @@ So, what do you want ? (all, none or a comma-separated list of: os gem pip)
1466
1492
  # The set of packages that have already been installed
1467
1493
  attr_reader :installed_packages
1468
1494
 
1469
- # Requests the installation of the given set of packages
1470
- def install(packages, options = Hash.new)
1495
+ # Set up the registered package handlers according to the specified osdeps mode
1496
+ #
1497
+ # It enables/disables package handlers based on either the value
1498
+ # returned by {#osdeps_mode} or the value passed as option (the latter
1499
+ # takes precedence). Moreover, sets the handler's silent flag using
1500
+ # {#silent?}
1501
+ #
1502
+ # @option options [Array<String>] the package handlers that should be
1503
+ # enabled. The default value is returned by {#osdeps_mode}
1504
+ # @return [Array<PackageManagers::Manager>] the set of enabled package
1505
+ # managers
1506
+ def setup_package_handlers(options = Hash.new)
1471
1507
  options =
1472
1508
  if Kernel.respond_to?(:validate_options)
1473
1509
  Kernel.validate_options options, :osdeps_mode => osdeps_mode
@@ -1495,10 +1531,26 @@ So, what do you want ? (all, none or a comma-separated list of: os gem pip)
1495
1531
  v.silent = self.silent?
1496
1532
  end
1497
1533
 
1534
+ enabled_handlers = []
1535
+ if os_package_handler.enabled?
1536
+ enabled_handlers << os_package_handler
1537
+ end
1538
+ package_handlers.each_value do |v|
1539
+ if v.enabled?
1540
+ enabled_handlers << v
1541
+ end
1542
+ end
1543
+ enabled_handlers
1544
+ end
1545
+
1546
+ # Requests the installation of the given set of packages
1547
+ def install(packages, options = Hash.new)
1498
1548
  # Remove the set of packages that have already been installed
1499
1549
  packages = packages.to_set - installed_packages
1500
1550
  return false if packages.empty?
1501
1551
 
1552
+ setup_package_handlers(options)
1553
+
1502
1554
  packages = resolve_os_dependencies(packages)
1503
1555
  packages = packages.map do |handler, list|
1504
1556
  if filter_uptodate_packages? && handler.respond_to?(:filter_uptodate_packages)
@@ -0,0 +1,58 @@
1
+ module Autoproj
2
+ # Autoproj-specific information about a package definition
3
+ #
4
+ # This stores the information that goes in addition to the autobuild package
5
+ # definitions
6
+ class PackageDefinition
7
+ # @return [Autobuild::Package] the autobuild package definitins
8
+ attr_reader :autobuild
9
+ # @return [Array<#call>] the set of blocks that should be called to
10
+ # prepare the package. These are called before any operation has been
11
+ # performed on the package iself.
12
+ attr_reader :user_blocks
13
+ # @return [PackageSet] the package set that defined this package
14
+ attr_reader :package_set
15
+ # @return [String] path to the file that contains this package's
16
+ # definition
17
+ attr_reader :file
18
+ # Whether this package is completely setup
19
+ #
20
+ # If the package is set up, its importer as well as all target
21
+ # directories are properly set, and all {user_blocks} have been called.
22
+ def setup?; !!@setup end
23
+
24
+ # Sets the {setup?} flag
25
+ attr_writer :setup
26
+
27
+ # @return [VCSDefinition] the version control information associated
28
+ # with this package
29
+ attr_accessor :vcs
30
+
31
+ def initialize(autobuild, package_set, file)
32
+ @autobuild, @package_set, @file =
33
+ autobuild, package_set, file
34
+ @user_blocks = []
35
+ end
36
+
37
+ # The package name
38
+ # @return [String]
39
+ def name
40
+ autobuild.name
41
+ end
42
+
43
+ # Registers a setup block
44
+ #
45
+ # The block will be called when the setup phase is finished, or
46
+ # immediately if it is already finished (i.e. if {setup?} returns true)
47
+ #
48
+ # @param [#call] block the block that should be registered
49
+ # @yieldparam [Autobuild::Package] pkg the autobuild package object
50
+ # @see {user_blocks}
51
+ def add_setup_block(block)
52
+ user_blocks << block
53
+ if setup?
54
+ block.call(autobuild)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,155 @@
1
+ module Autoproj
2
+ # Access to the information contained in a package's manifest.xml file
3
+ #
4
+ # Use PackageManifest.load to create
5
+ class PackageManifest
6
+ # Load a manifest.xml file and returns the corresponding
7
+ # PackageManifest object
8
+ 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
14
+
15
+ PackageManifest.new(package, doc)
16
+ end
17
+
18
+ # The Autobuild::Package instance this manifest applies on
19
+ 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
33
+ end
34
+
35
+ def documentation
36
+ xml.elements.each('package/description') do |node|
37
+ doc = (node.text || "").strip
38
+ if !doc.empty?
39
+ return doc
40
+ end
41
+ end
42
+ return short_documentation
43
+ end
44
+
45
+ def short_documentation
46
+ xml.elements.each('package/description') do |node|
47
+ doc = node.attributes['brief']
48
+ if doc
49
+ doc = doc.to_s.strip
50
+ end
51
+ if doc && !doc.empty?
52
+ return doc.to_s
53
+ end
54
+ end
55
+ "no documentation available for #{package.name} in its manifest.xml file"
56
+ end
57
+
58
+ def initialize(package, doc = REXML::Document.new)
59
+ @package = package
60
+ @xml = doc
61
+ end
62
+
63
+ def each_dependency(&block)
64
+ if block_given?
65
+ each_os_dependency(&block)
66
+ each_package_dependency(&block)
67
+ else
68
+ enum_for(:each_dependency, &block)
69
+ end
70
+ end
71
+
72
+ def each_os_dependency
73
+ if block_given?
74
+ xml.elements.each('package/rosdep') do |node|
75
+ yield(node.attributes['name'], false)
76
+ end
77
+ package.os_packages.each do |name|
78
+ yield(name, false)
79
+ end
80
+ else
81
+ enum_for :each_os_dependency
82
+ end
83
+ end
84
+
85
+ def each_package_dependency
86
+ if block_given?
87
+ depend_nodes = xml.elements.to_a('package/depend') +
88
+ xml.elements.to_a('package/depend_optional')
89
+
90
+ depend_nodes.each do |node|
91
+ dependency = node.attributes['package']
92
+ optional = (node.attributes['optional'].to_s == '1' || node.name == "depend_optional")
93
+
94
+ if dependency
95
+ yield(dependency, optional)
96
+ else
97
+ raise ConfigError.new, "manifest of #{package.name} has a <depend> tag without a 'package' attribute"
98
+ end
99
+ end
100
+ else
101
+ enum_for :each_package_dependency
102
+ end
103
+ end
104
+
105
+ # Enumerates the name and email of each author. If no email is present,
106
+ # yields (name, nil)
107
+ def each_author
108
+ if !block_given?
109
+ return enum_for(:each_author)
110
+ end
111
+
112
+ xml.elements.each('package/author') do |author|
113
+ (author.text || "").strip.split(',').each do |str|
114
+ name, email = str.split('/').map(&:strip)
115
+ email = nil if email && email.empty?
116
+ yield(name, email)
117
+ end
118
+ end
119
+ end
120
+
121
+ # If +name+ points to a text element in the XML document, returns the
122
+ # content of that element. If no element matches +name+, or if the
123
+ # content is empty, returns nil
124
+ def text_node(name)
125
+ xml.elements.each(name) do |str|
126
+ str = (str.text || "").strip
127
+ if !str.empty?
128
+ return str
129
+ end
130
+ end
131
+ nil
132
+ end
133
+
134
+ # The package associated URL, usually meant to direct to a website
135
+ #
136
+ # Returns nil if there is none
137
+ def url
138
+ return text_node('package/url')
139
+ end
140
+
141
+ # The package license name
142
+ #
143
+ # Returns nil if there is none
144
+ def license
145
+ return text_node('package/license')
146
+ end
147
+
148
+ # The package version number
149
+ #
150
+ # Returns 0 if none is declared
151
+ def version
152
+ return text_node("version")
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,153 @@
1
+ module Autoproj
2
+ # Exception raised by
3
+ # PackageSelection#filter_excluded_and_ignored_packages when a given
4
+ # selection is completely excluded
5
+ class ExcludedSelection < ConfigError
6
+ attr_reader :selection
7
+ def initialize(selection)
8
+ @selection = selection
9
+ end
10
+ end
11
+
12
+ # Class holding information about which packages have been selected, and
13
+ # why. It is used to decide whether some non-availability of packages
14
+ # are errors or simply warnings (i.e. if the user really wants a given
15
+ # package, or merely might be adding it by accident)
16
+ class PackageSelection
17
+ # The set of matches, i.e. a mapping from a user-provided string to
18
+ # the set of packages it selected
19
+ attr_reader :matches
20
+ # The set of selected packages, as a hash of the package name to the
21
+ # set of user-provided strings that caused that package to be
22
+ # selected
23
+ attr_reader :selection
24
+ # The set of osdeps matches, i.e. a mapping from a user-provided string to
25
+ # the set of packages it selected
26
+ attr_reader :osdeps_matches
27
+ # The set of selected osdeps packages, as a hash of the package name
28
+ # to the set of user-provided strings that caused that package to be
29
+ # selected
30
+ attr_reader :osdeps_selection
31
+ # A flag that tells #filter_excluded_and_ignored_packages whether
32
+ # the a given package selection is weak or not.
33
+ #
34
+ # If true, a selection that have some excluded packages will not
35
+ # generate an error. Otherwise (the default), an error is generated
36
+ attr_reader :weak_dependencies
37
+ # After a call to #filter_excluded_and_ignored_packages, this
38
+ # contains the set of package exclusions that have been ignored
39
+ # because the corresponding metapackage has a weak dependency policy
40
+ attr_reader :exclusions
41
+ # After a call to #filter_excluded_and_ignored_packages, this
42
+ # contains the set of package ignores that have been ignored because
43
+ # the corresponding metapackage has a weak dependency policy
44
+ attr_reader :ignores
45
+
46
+ def initialize
47
+ @selection = Hash.new { |h, k| h[k] = Set.new }
48
+ @matches = Hash.new { |h, k| h[k] = Set.new }
49
+ @osdeps_matches = Hash.new { |h, k| h[k] = Set.new }
50
+ @osdeps_selection = Hash.new { |h, k| h[k] = Set.new }
51
+ @weak_dependencies = Hash.new
52
+ @ignores = Hash.new { |h, k| h[k] = Set.new }
53
+ @exclusions = Hash.new { |h, k| h[k] = Set.new }
54
+ end
55
+
56
+ # The set of packages that have been selected
57
+ def packages
58
+ selection.keys
59
+ end
60
+
61
+ # The set of packages that have been selected
62
+ def osdeps_packages
63
+ osdeps_selection.keys
64
+ end
65
+
66
+ def include?(pkg_name)
67
+ selection.has_key?(pkg_name) || osdeps_selection.has_key?(pkg_name)
68
+ end
69
+
70
+ def empty?
71
+ selection.empty?
72
+ end
73
+
74
+ def each(&block)
75
+ selection.each_key(&block)
76
+ end
77
+
78
+ def select(sel, packages, weak = false)
79
+ packages = Array(packages)
80
+ matches[sel] |= packages.to_set.dup
81
+ packages.each do |pkg_name|
82
+ selection[pkg_name] << sel
83
+ end
84
+ weak_dependencies[sel] = weak
85
+ end
86
+
87
+ def initialize_copy(old)
88
+ old.selection.each do |pkg_name, set|
89
+ @selection[pkg_name] = set.dup
90
+ end
91
+ old.matches.each do |sel, set|
92
+ @matches[sel] = set.dup
93
+ end
94
+ end
95
+
96
+ def has_match_for?(sel)
97
+ matches.has_key?(sel)
98
+ end
99
+
100
+ # Remove packages that are explicitely excluded and/or ignored
101
+ #
102
+ # Raise an error if an explicit selection expands only to an
103
+ # excluded package, and display a warning for ignored packages
104
+ def filter_excluded_and_ignored_packages(manifest)
105
+ matches.each do |sel, expansion|
106
+ excluded, other = expansion.partition { |pkg_name| manifest.excluded?(pkg_name) }
107
+ ignored, ok = other.partition { |pkg_name| manifest.ignored?(pkg_name) }
108
+
109
+ if !excluded.empty? && (!weak_dependencies[sel] || (ok.empty? && ignored.empty?))
110
+ exclusions = excluded.map do |pkg_name|
111
+ [pkg_name, manifest.exclusion_reason(pkg_name)]
112
+ end
113
+ base_msg = "#{sel} is selected in the manifest or on the command line"
114
+ if exclusions.size == 1
115
+ reason = exclusions[0][1]
116
+ if sel == exclusions[0][0]
117
+ raise ExcludedSelection.new(sel), "#{base_msg}, but it is excluded from the build: #{reason}"
118
+ elsif weak_dependencies[sel]
119
+ raise ExcludedSelection.new(sel), "#{base_msg}, but it expands to #{exclusions.map(&:first).join(", ")}, which is excluded from the build: #{reason}"
120
+ else
121
+ raise ExcludedSelection.new(sel), "#{base_msg}, but its dependency #{exclusions.map(&:first).join(", ")} is excluded from the build: #{reason}"
122
+ end
123
+ elsif weak_dependencies[sel]
124
+ raise ExcludedSelection.new(sel), "#{base_msg}, but expands to #{exclusions.map(&:first).join(", ")}, and all these packages are excluded from the build:\n #{exclusions.map { |name, reason| "#{name}: #{reason}" }.join("\n ")}"
125
+ else
126
+ raise ExcludedSelection.new(sel), "#{base_msg}, but it requires #{exclusions.map(&:first).join(", ")}, and all these packages are excluded from the build:\n #{exclusions.map { |name, reason| "#{name}: #{reason}" }.join("\n ")}"
127
+ end
128
+ else
129
+ self.exclusions[sel] |= excluded.to_set.dup
130
+ self.ignores[sel] |= ignored.to_set.dup
131
+ end
132
+
133
+ excluded = excluded.to_set
134
+ ignored = ignored.to_set
135
+ expansion.delete_if do |pkg_name|
136
+ ignored.include?(pkg_name) || excluded.include?(pkg_name)
137
+ end
138
+ end
139
+
140
+ selection.keys.sort.each do |pkg_name|
141
+ if manifest.excluded?(pkg_name)
142
+ selection.delete(pkg_name)
143
+ elsif manifest.ignored?(pkg_name)
144
+ selection.delete(pkg_name)
145
+ end
146
+ end
147
+ matches.delete_if do |key, sel|
148
+ sel.empty?
149
+ end
150
+ end
151
+ end
152
+ end
153
+