autoproj 2.8.8 → 2.9.0

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.
@@ -50,6 +50,35 @@ def empty?
50
50
  selection.empty?
51
51
  end
52
52
 
53
+ # Returns the source packages selected explicitely or through
54
+ # dependencies
55
+ #
56
+ # @param [Manifest] manifest
57
+ # @return [Array<PackageDefinition>]
58
+ def all_selected_source_packages(manifest)
59
+ names = Set.new
60
+ roots = each_source_package_name.to_set
61
+ roots.each do |pkg_name|
62
+ manifest.find_autobuild_package(pkg_name).all_dependencies(names)
63
+ end
64
+ names.merge(roots).map do |pkg_name|
65
+ manifest.find_package_definition(pkg_name)
66
+ end
67
+ end
68
+
69
+ # Returns the source packages selected explicitely or through
70
+ # dependencies
71
+ #
72
+ # @param [Manifest] manifest
73
+ # @return [Array<String>]
74
+ def all_selected_osdep_packages(manifest)
75
+ all_sources = all_selected_source_packages(manifest)
76
+ from_source = all_sources.each_with_object(Set.new) do |pkg, s|
77
+ s.merge(pkg.autobuild.os_packages)
78
+ end
79
+ from_source | osdeps
80
+ end
81
+
53
82
  def each(&block)
54
83
  Autoproj.warn_deprecated "PackageSelection#each", "use PackageSelection#each_source_package_name instead", 0
55
84
  each_source_package_name(&block)
@@ -78,27 +107,24 @@ def packages
78
107
  source_packages
79
108
  end
80
109
 
81
- def select(sel, packages, options = Hash.new)
82
- if !options.kind_of?(Hash)
110
+ def select(sel, packages, *_backward, weak: false, osdep: false)
111
+ unless _backward.empty?
83
112
  Autoproj.warn_deprecated "calling PackageSelection#select with a boolean as third argument", "use e.g. weak: true instead", 0
84
- options = Hash[weak: options]
113
+ weak = _backward.first
85
114
  end
86
- options = Kernel.validate_options options,
87
- weak: false,
88
- osdep: false
89
115
 
90
116
  packages = Array(packages).to_set
91
117
  matches[sel].merge(packages)
92
118
  packages.each do |pkg_name|
93
119
  selection[pkg_name] << sel
94
120
  end
95
- if options[:osdep]
121
+ if osdep
96
122
  osdeps.merge(packages)
97
123
  else
98
124
  source_packages.merge(packages)
99
125
  end
100
126
 
101
- weak_dependencies[sel] = options[:weak]
127
+ weak_dependencies[sel] = weak
102
128
  end
103
129
 
104
130
  def initialize_copy(old)
@@ -160,10 +186,10 @@ def filter_excluded_and_ignored_packages(manifest)
160
186
  end
161
187
  end
162
188
 
163
- source_packages.delete_if do |pkg_name|
189
+ source_packages.delete_if do |pkg_name|
164
190
  manifest.excluded?(pkg_name) || manifest.ignored?(pkg_name)
165
191
  end
166
- osdeps.delete_if do |pkg_name|
192
+ osdeps.delete_if do |pkg_name|
167
193
  manifest.excluded?(pkg_name) || manifest.ignored?(pkg_name)
168
194
  end
169
195
  selection.delete_if do |pkg_name, _|
@@ -5,7 +5,7 @@ module Autoproj
5
5
  # dependencies that are provided by the operating system (.osdeps file).
6
6
  class PackageSet
7
7
  # Exception raised when an operation that needs the source.yml to be
8
- # loaded is called before {PackageSet#load_description_file} is called
8
+ # loaded is called before {PackageSet#load_description_file} is called
9
9
  class NotLoaded < RuntimeError
10
10
  attr_reader :package_set
11
11
  def initialize(package_set)
@@ -34,7 +34,7 @@ def add_source_file(name)
34
34
 
35
35
  # The manifest this package set is registered on
36
36
  #
37
- # @return [Manifest]
37
+ # @return [Manifest]
38
38
  def manifest
39
39
  ws.manifest
40
40
  end
@@ -64,10 +64,21 @@ def manifest
64
64
  # and the corresponding OSPackageResolver object
65
65
  attr_reader :all_osdeps
66
66
 
67
+ # The set of OSRepositoryResolver object that represent the osrepos files
68
+ # available in this package set
69
+ #
70
+ # @return [Array<(String,OSRepositoryResolver)>] the list of osdep files
71
+ # and the corresponding OSRepositoryResolver object
72
+ attr_reader :all_osrepos
73
+
67
74
  # The OSPackageResolver which is a merged version of all OSdeps in
68
75
  # {#all_osdeps}
69
76
  attr_reader :os_package_resolver
70
77
 
78
+ # The OSRepositoryResolver which is a merged version of all OSrepos in
79
+ # {#all_osrepos}
80
+ attr_reader :os_repository_resolver
81
+
71
82
  # If this package set has been imported from another package set, this
72
83
  # is the other package set object
73
84
  attr_accessor :imported_from
@@ -144,12 +155,14 @@ def initialize(
144
155
  raise ArgumentError, "cannot create a package set with a nil vcs, create a null VCS using VCSDefinition.none"
145
156
  end
146
157
  @name = name
158
+ @os_repository_resolver = OSRepositoryResolver.new
147
159
  @os_package_resolver = OSPackageResolver.new(
148
160
  operating_system: ws.os_package_resolver.operating_system,
149
161
  package_managers: ws.os_package_resolver.package_managers,
150
162
  os_package_manager: ws.os_package_resolver.os_package_manager)
151
163
  @importer_definitions_cache = Hash.new
152
164
  @all_osdeps = []
165
+ @all_osrepos = []
153
166
  @constants_definitions = Hash.new
154
167
  @required_autoproj_version = '0'
155
168
  @version_control = Array.new
@@ -175,11 +188,24 @@ def load_osdeps(file, **options)
175
188
  new_osdeps
176
189
  end
177
190
 
191
+ # Load a new osrepos file for this package set
192
+ def load_osrepos(file)
193
+ new_osrepos = OSRepositoryResolver.load(file)
194
+ all_osrepos << new_osrepos
195
+ os_repository_resolver.merge(all_osrepos.last)
196
+ new_osrepos
197
+ end
198
+
178
199
  # Enumerate all osdeps package names from this package set
179
200
  def each_osdep(&block)
180
201
  os_package_resolver.all_package_names.each(&block)
181
202
  end
182
203
 
204
+ # Enumerate all osrepos entries from this package set
205
+ def each_osrepo(&block)
206
+ os_repository_resolver.all_entries.each(&block)
207
+ end
208
+
183
209
  # True if this source has already been checked out on the local autoproj
184
210
  # installation
185
211
  def present?; File.directory?(raw_local_dir) end
@@ -302,7 +328,7 @@ def repository_id
302
328
  # For local sources, is simply returns the path to the source directory.
303
329
  def user_local_dir
304
330
  if local?
305
- return vcs.url
331
+ return vcs.url
306
332
  else
307
333
  File.join(ws.config_dir, 'remotes', name)
308
334
  end
@@ -714,7 +740,7 @@ def overrides_for(package, vcs, require_existing: true)
714
740
  # This is a helper for {#overrides_for}
715
741
  def resolve_overrides(key, vcs)
716
742
  overrides.each do |file, file_overrides|
717
- new_spec, new_raw_entry =
743
+ new_spec, new_raw_entry =
718
744
  Autoproj.in_file file do
719
745
  version_control_field(key, file_overrides, validate: false, file: file)
720
746
  end
@@ -762,6 +788,15 @@ def each_osdeps_file
762
788
  yield(file)
763
789
  end
764
790
  end
791
+
792
+ # Yields each osdeps definition files that are present in this package
793
+ # set
794
+ def each_osrepos_file
795
+ return enum_for(__method__) if !block_given?
796
+ Dir.glob(File.join(local_dir, "*.osrepos")).each do |file|
797
+ yield(file)
798
+ end
799
+ end
765
800
  end
766
801
  end
767
802
 
@@ -0,0 +1,289 @@
1
+ # frozen_string_literal: false
2
+
3
+ require 'open3'
4
+ require 'pathname'
5
+ require 'open-uri'
6
+
7
+ module Autoproj
8
+ module RepositoryManagers
9
+ # Apt repository manager class
10
+ class APT < Manager
11
+ attr_reader :source_files
12
+ attr_reader :source_entries
13
+ attr_reader :sources_dir
14
+ attr_reader :autoproj_sources
15
+
16
+ SOURCES_DIR = '/etc/apt'.freeze
17
+ SOURCE_TYPES = ['deb', 'deb-src'].freeze
18
+ AUTOPROJ_SOURCES = '/etc/apt/sources.list.d/autoproj.list'.freeze
19
+
20
+ def initialize(ws, sources_dir: SOURCES_DIR, autoproj_sources: AUTOPROJ_SOURCES)
21
+ @sources_dir = sources_dir
22
+ @autoproj_sources = autoproj_sources
23
+ @source_files = Dir[File.join(sources_dir, '**', '*.list')]
24
+ @source_entries = {}
25
+
26
+ source_files.each { |file| load_sources_from_file(file) }
27
+ super(ws)
28
+ end
29
+
30
+ def os_dependencies
31
+ super + ['archive-keyring', 'gnupg', 'apt-transport-https']
32
+ end
33
+
34
+ def load_sources_from_file(file)
35
+ contents = File.open(file).read
36
+ contents.gsub!(/\r\n?/, "\n")
37
+
38
+ contents.each_line do |line|
39
+ @source_entries[file] ||= []
40
+ @source_entries[file] << parse_source_line(line, raise_if_invalid: false)
41
+ end
42
+ end
43
+
44
+ def parse_source_line(line, raise_if_invalid: true)
45
+ entry = {}
46
+ entry[:valid] = false
47
+ entry[:enabled] = true
48
+ entry[:source] = ''
49
+ entry[:comment] = ''
50
+
51
+ line.strip!
52
+ if line.start_with?('#')
53
+ entry[:enabled] = false
54
+ line = line[1..-1]
55
+ end
56
+
57
+ i = line.index('#')
58
+ if i&.positive?
59
+ entry[:comment] = line[(i + 1)..-1].strip
60
+ line = line[0..(i - 1)]
61
+ end
62
+
63
+ entry[:source] = line.strip
64
+ chunks = entry[:source].split
65
+ entry[:valid] = true if SOURCE_TYPES.include?(chunks[0])
66
+ entry[:source] = chunks.join(' ')
67
+
68
+ if raise_if_invalid && (!entry[:valid] || !entry[:enabled])
69
+ raise ConfigError, "Invalid source line: #{entry[:source]}"
70
+ end
71
+
72
+ entry
73
+ end
74
+
75
+ def add_source(source, file = nil)
76
+ file = if file
77
+ File.join(sources_dir, 'sources.list.d', file)
78
+ else
79
+ autoproj_sources
80
+ end
81
+
82
+ new_entry = parse_source_line(source)
83
+ found = entry_exist?(new_entry)
84
+
85
+ if found
86
+ file = found.first
87
+ entry = found.last
88
+ return false if entry[:enabled]
89
+
90
+ enable_entry_in_file(file, entry)
91
+ else
92
+ add_entry_to_file(file, new_entry)
93
+ end
94
+ end
95
+
96
+ def append_entry(contents, entry)
97
+ unless entry[:enabled]
98
+ contents << '#'
99
+ contents << ' ' unless entry[:source].start_with?('#')
100
+ end
101
+
102
+ contents << entry[:source]
103
+ contents << "# #{entry[:comment]}" unless entry[:comment].empty?
104
+ contents << "\n"
105
+ end
106
+
107
+ def enable_entry_in_file(file, enable_entry)
108
+ contents = ''
109
+ source_entries[file].each do |entry|
110
+ entry[:enabled] = true if enable_entry[:source] == entry[:source]
111
+ append_entry(contents, entry)
112
+ end
113
+ run_tee_command(['sudo', 'tee', file], contents)
114
+ true
115
+ end
116
+
117
+ def add_entry_to_file(file, entry)
118
+ run_tee_command(['sudo', 'tee', '-a', file], entry[:source])
119
+ @source_entries[file] ||= []
120
+ @source_entries[file] << entry
121
+ true
122
+ end
123
+
124
+ def run_tee_command(command, contents)
125
+ contents = StringIO.new("#{contents}\n")
126
+ Autobuild::Subprocess.run('autoproj', 'osrepos', *command, input_streams: [contents])
127
+ end
128
+
129
+ def entry_exist?(new_entry)
130
+ source_entries.each_pair do |file, entries|
131
+ entry = entries.find { |e| e[:source] == new_entry[:source] }
132
+ return [file, entry] if entry
133
+ end
134
+ nil
135
+ end
136
+
137
+ def source_exist?(source)
138
+ entry_exist?(parse_source_line(source))
139
+ end
140
+
141
+ def key_exist?(key)
142
+ exist = false
143
+ Open3.popen3("apt-key export #{key}") do |_, _, stderr, wait_thr|
144
+ exist = true if wait_thr.value.success? && stderr.read.empty?
145
+ end
146
+ exist
147
+ end
148
+
149
+ def apt_update
150
+ Autobuild::Subprocess.run(
151
+ 'autoproj',
152
+ 'osrepos',
153
+ 'sudo',
154
+ 'apt-get',
155
+ 'update'
156
+ )
157
+ end
158
+
159
+ def add_apt_key(id, origin, type: :keyserver)
160
+ if type == :keyserver
161
+ Autobuild::Subprocess.run(
162
+ 'autoproj',
163
+ 'osrepos',
164
+ 'sudo',
165
+ 'apt-key',
166
+ 'adv',
167
+ '--keyserver',
168
+ origin,
169
+ '--recv-key',
170
+ id
171
+ )
172
+ else
173
+ open(origin) do |io|
174
+ Autobuild::Subprocess.run(
175
+ 'autoproj',
176
+ 'osrepos',
177
+ 'sudo',
178
+ 'apt-key',
179
+ 'add',
180
+ '-',
181
+ input_streams: [io]
182
+ )
183
+ end
184
+ end
185
+ rescue Errno::ENOENT, SocketError => e
186
+ raise ConfigError, e.message
187
+ end
188
+
189
+ def filter_installed_definitions(definitions)
190
+ definitions = definitions.dup.reject do |definition|
191
+ if definition['type'] == 'repo'
192
+ _, entry = source_exist?(definition['repo'])
193
+ entry && entry[:enabled]
194
+ else
195
+ key_exist?(definition['id'])
196
+ end
197
+ end
198
+ definitions
199
+ end
200
+
201
+ def print_installing_definitions(definitions)
202
+ repos = definitions.select { |definition| definition['type'] == 'repo' }
203
+ keys = definitions.select { |definition| definition['type'] == 'key' }
204
+
205
+ unless repos.empty?
206
+ Autoproj.message ' adding apt repositories:'
207
+ repos.each do |repo|
208
+ if repo['file']
209
+ Autoproj.message " #{repo['repo']}, file: #{repo['file']}"
210
+ else
211
+ Autoproj.message " #{repo['repo']}"
212
+ end
213
+ end
214
+ end
215
+ return if keys.empty?
216
+
217
+ Autoproj.message ' adding apt keys:'
218
+ keys.each do |key|
219
+ if key['keyserver']
220
+ Autoproj.message " id: #{key['id']}, keyserver: #{key['keyserver']}"
221
+ else
222
+ Autoproj.message " id: #{key['id']}, url: #{key['url']}"
223
+ end
224
+ end
225
+ end
226
+
227
+ # Validates repositories definitions from .osrepos files
228
+ #
229
+ # Examples:
230
+ #
231
+ # - ubuntu:
232
+ # - xenial:
233
+ # type: repo
234
+ # repo: 'deb http://archive.ubuntu.com/ubuntu/ xenial main restricted'
235
+ #
236
+ # - ubuntu:
237
+ # - xenial:
238
+ # type: key
239
+ # id: 630239CC130E1A7FD81A27B140976EAF437D05B5
240
+ # keyserver: 'hkp://ha.pool.sks-keyservers.net:80'
241
+ #
242
+ # - ubuntu:
243
+ # - xenial:
244
+ # type: key
245
+ # id: D2486D2DD83DB69272AFE98867170598AF249743
246
+ # url: 'http://packages.osrfoundation.org/gazebo.key'
247
+ #
248
+ def validate_definitions(definitions)
249
+ invalid_string = 'Invalid apt repository definition'
250
+ definitions.each do |definition|
251
+ raise ConfigError, "#{invalid_string} type: #{definition['type']}" unless %w[repo key].include?(definition['type'])
252
+
253
+ if definition['type'] == 'repo'
254
+ raise ConfigError, "#{invalid_string}: 'repo' key missing" if definition['repo'].nil?
255
+ raise ConfigError, "#{invalid_string}: 'repo' should be a String" unless definition['repo'].is_a?(String)
256
+ raise ConfigError, "#{invalid_string}: 'file' should be a String" if definition['file'] && !definition['file'].is_a?(String)
257
+ if definition['file'] && Pathname.new(definition['file']).absolute?
258
+ raise ConfigError, "#{invalid_string}: 'file' should be a relative to #{File.join(SOURCES_DIR, 'sources.list.d')}"
259
+ end
260
+ else
261
+ raise ConfigError, "#{invalid_string}: 'id' key missing" if definition['id'].nil?
262
+ raise ConfigError, "#{invalid_string}: 'id' should be a String" unless definition['id'].is_a?(String)
263
+ raise ConfigError, "#{invalid_string}: 'url' conflicts with 'keyserver'" if definition['url'] && definition['keyserver']
264
+ raise ConfigError, "#{invalid_string}: 'url' should be a String" if definition['url'] && !definition['url'].is_a?(String)
265
+ raise ConfigError, "#{invalid_string}: 'keyserver' should be a String" if definition['keyserver'] && !definition['keyserver'].is_a?(String)
266
+ end
267
+ end
268
+ end
269
+
270
+ def install(definitions)
271
+ super
272
+ validate_definitions(definitions)
273
+ definitions = filter_installed_definitions(definitions)
274
+ print_installing_definitions(definitions)
275
+
276
+ definitions.each do |definition|
277
+ if definition['type'] == 'repo'
278
+ add_source(definition['repo'], definition['file'])
279
+ else
280
+ type = definition['url'] ? 'url' : 'keyserver'
281
+ origin = definition[type]
282
+ add_apt_key(definition['id'], origin, type: type.to_sym)
283
+ end
284
+ end
285
+ apt_update unless definitions.empty?
286
+ end
287
+ end
288
+ end
289
+ end