autoproj 2.8.8 → 2.9.0

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