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,497 @@
1
+ module Autoproj
2
+ # A package set is a version control repository which contains general
3
+ # information with package version control information (source.yml file),
4
+ # package definitions (.autobuild files), and finally definition of
5
+ # dependencies that are provided by the operating system (.osdeps file).
6
+ class PackageSet
7
+ attr_reader :manifest
8
+ # The VCSDefinition object that defines the version control holding
9
+ # information for this source. Local package sets (i.e. the ones that are not
10
+ # under version control) use the 'local' version control name. For them,
11
+ # local? returns true.
12
+ attr_accessor :vcs
13
+
14
+ # The set of OSDependencies object that represent the osdeps files
15
+ # available in this package set
16
+ attr_reader :all_osdeps
17
+
18
+ # The OSDependencies which is a merged version of all OSdeps in
19
+ # #all_osdeps
20
+ attr_reader :osdeps
21
+
22
+ # If this package set has been imported from another package set, this
23
+ # is the other package set object
24
+ attr_accessor :imported_from
25
+
26
+ # If true, this package set has been loaded because another set imports
27
+ # it. If false, it is loaded explicitely by the user
28
+ def explicit?; !@imported_from end
29
+
30
+ attr_reader :source_definition
31
+ attr_reader :constants_definitions
32
+
33
+ # Sets the auto_imports flag. See #auto_imports?
34
+ attr_writer :auto_imports
35
+ # If true (the default), imports listed in this package set will be
36
+ # automatically loaded by autoproj
37
+ def auto_imports?; !!@auto_imports end
38
+
39
+ # Returns the Metapackage object that has the same name than this
40
+ # package set
41
+ def metapackage
42
+ manifest.metapackage(name)
43
+ end
44
+
45
+ # List of the packages that are built if the package set is selected in
46
+ # the layout
47
+ def default_packages
48
+ metapackage.packages
49
+ end
50
+
51
+ # Create this source from a VCSDefinition object
52
+ def initialize(manifest, vcs)
53
+ @manifest = manifest
54
+ @vcs = vcs
55
+ @osdeps = OSDependencies.new
56
+ @all_osdeps = []
57
+
58
+ @provides = Set.new
59
+ @imports = Array.new
60
+ @auto_imports = true
61
+ end
62
+
63
+ # Load a new osdeps file for this package set
64
+ def load_osdeps(file)
65
+ new_osdeps = OSDependencies.load(file)
66
+ @all_osdeps << new_osdeps
67
+ @osdeps.merge(@all_osdeps.last)
68
+ new_osdeps
69
+ end
70
+
71
+ # Enumerate all osdeps package names from this package set
72
+ def each_osdep(&block)
73
+ @osdeps.definitions.each_key(&block)
74
+ end
75
+
76
+ # True if this source has already been checked out on the local autoproj
77
+ # installation
78
+ def present?; File.directory?(raw_local_dir) end
79
+ # True if this source is local, i.e. is not under a version control
80
+ def local?; vcs.local? end
81
+ # True if this source defines nothing
82
+ def empty?
83
+ !source_definition['version_control'] && !source_definition['overrides']
84
+ !each_package.find { true } &&
85
+ !File.exists?(File.join(raw_local_dir, "overrides.rb")) &&
86
+ !File.exists?(File.join(raw_local_dir, "init.rb"))
87
+ end
88
+
89
+ def snapshot(target_dir)
90
+ if local?
91
+ Hash.new
92
+ else
93
+ package = Manifest.create_autobuild_package(vcs, name, raw_local_dir)
94
+ package.importer.snapshot(package, target_dir)
95
+ end
96
+ end
97
+
98
+ # Create a PackageSet instance from its description as found in YAML
99
+ # configuration files
100
+ def self.from_spec(manifest, raw_spec, load_description)
101
+ if raw_spec.respond_to?(:to_str)
102
+ local_path = File.join(Autoproj.config_dir, raw_spec)
103
+ if File.directory?(local_path)
104
+ raw_spec = { :type => 'local', :url => local_path }
105
+ end
106
+ end
107
+ spec = VCSDefinition.vcs_definition_to_hash(raw_spec)
108
+ options, vcs_spec = Kernel.filter_options spec, :auto_imports => true
109
+
110
+ # Look up for short notation (i.e. not an explicit hash). It is
111
+ # either vcs_type:url or just url. In the latter case, we expect
112
+ # 'url' to be a path to a local directory
113
+ vcs_spec = Autoproj.expand(vcs_spec, manifest.constant_definitions)
114
+ vcs_def = VCSDefinition.from_raw(vcs_spec, [[nil, raw_spec]])
115
+
116
+ source = PackageSet.new(manifest, vcs_def)
117
+ source.auto_imports = options[:auto_imports]
118
+ if load_description
119
+ if source.present?
120
+ source.load_description_file
121
+ else
122
+ raise InternalError, "cannot load description file as it has not been checked out yet"
123
+ end
124
+ else
125
+ # Try to load just the name from the source.yml file
126
+ source.load_minimal
127
+ end
128
+
129
+ source
130
+ end
131
+
132
+ # Returns a string that uniquely represents the version control
133
+ # information for this package set.
134
+ #
135
+ # I.e. for two package sets set1 and set2, if set1.repository_id ==
136
+ # set2.repository_id, it means that both package sets are checked out
137
+ # from exactly the same source.
138
+ def repository_id
139
+ if local?
140
+ local_dir
141
+ else
142
+ importer = vcs.create_autobuild_importer
143
+ if importer.respond_to?(:repository_id)
144
+ importer.repository_id
145
+ else
146
+ vcs.to_s
147
+ end
148
+ end
149
+ end
150
+
151
+ # Remote sources can be accessed through a hidden directory in
152
+ # $AUTOPROJ_ROOT/.remotes, or through a symbolic link in
153
+ # autoproj/remotes/
154
+ #
155
+ # This returns the former. See #user_local_dir for the latter.
156
+ #
157
+ # For local sources, is simply returns the path to the source directory.
158
+ def raw_local_dir
159
+ if local?
160
+ File.expand_path(vcs.url)
161
+ else
162
+ File.expand_path(File.join(Autoproj.remotes_dir, vcs.create_autobuild_importer.repository_id.gsub(/[^\w]/, '_')))
163
+ end
164
+ end
165
+
166
+ # Remote sources can be accessed through a hidden directory in
167
+ # $AUTOPROJ_ROOT/.remotes, or through a symbolic link in
168
+ # autoproj/remotes/
169
+ #
170
+ # This returns the latter. See #raw_local_dir for the former.
171
+ #
172
+ # For local sources, is simply returns the path to the source directory.
173
+ def user_local_dir
174
+ if local?
175
+ return vcs.url
176
+ else
177
+ File.join(Autoproj.config_dir, 'remotes', name)
178
+ end
179
+ end
180
+
181
+ # The directory in which data for this source will be checked out
182
+ def local_dir
183
+ ugly_dir = raw_local_dir
184
+ pretty_dir = user_local_dir
185
+ if File.symlink?(pretty_dir) && File.readlink(pretty_dir) == ugly_dir
186
+ pretty_dir
187
+ else
188
+ ugly_dir
189
+ end
190
+ end
191
+
192
+ def required_autoproj_version
193
+ definition = @source_definition || raw_description_file
194
+ definition['required_autoproj_version'] || '0'
195
+ end
196
+
197
+ # Returns the source name
198
+ def name
199
+ if @name
200
+ @name
201
+ else
202
+ vcs.to_s
203
+ end
204
+ end
205
+
206
+ # Loads the source.yml file, validates it and returns it as a hash
207
+ #
208
+ # Raises InternalError if the source has not been checked out yet (it
209
+ # should have), and ConfigError if the source.yml file is not valid.
210
+ def raw_description_file
211
+ if !present?
212
+ raise InternalError, "source #{vcs} has not been fetched yet, cannot load description for it"
213
+ end
214
+
215
+ source_file = File.join(raw_local_dir, "source.yml")
216
+ if !File.exists?(source_file)
217
+ raise ConfigError.new, "source #{vcs.type}:#{vcs.url} should have a source.yml file, but does not"
218
+ end
219
+
220
+ source_definition = Autoproj.in_file(source_file, Autoproj::YAML_LOAD_ERROR) do
221
+ YAML.load(File.read(source_file))
222
+ end
223
+
224
+ if !source_definition || !source_definition['name']
225
+ raise ConfigError.new(source_file), "in #{source_file}: missing a 'name' field"
226
+ end
227
+
228
+ source_definition
229
+ end
230
+
231
+ # Load and validate the self-contained information from the YAML hash
232
+ def load_minimal
233
+ # If @source_definition is set, it means that load_description_file
234
+ # has been called and that therefore all information has already
235
+ # been parsed
236
+ definition = @source_definition || raw_description_file
237
+ @name = definition['name']
238
+
239
+ if @name !~ /^[\w_\.-]+$/
240
+ raise ConfigError.new(source_file),
241
+ "in #{source_file}: invalid source name '#{@name}': source names can only contain alphanumeric characters, and .-_"
242
+ elsif @name == "local"
243
+ raise ConfigError.new(source_file),
244
+ "in #{source_file}: the name 'local' is a reserved name"
245
+ end
246
+
247
+ @provides = (definition['provides'] || Set.new).to_set
248
+ @imports = (definition['imports'] || Array.new).map do |set_def|
249
+ pkg_set = Autoproj.in_file(source_file) do
250
+ PackageSet.from_spec(manifest, set_def, false)
251
+ end
252
+
253
+ pkg_set.imported_from = self
254
+ pkg_set
255
+ end
256
+
257
+ rescue InternalError
258
+ # This ignores raw_description_file error if the package set is not
259
+ # checked out yet
260
+ end
261
+
262
+ # Yields the imports this package set declares, as PackageSet instances
263
+ def each_imported_set(&block)
264
+ @imports.each(&block)
265
+ end
266
+
267
+ # Path to the source.yml file
268
+ def source_file
269
+ File.join(local_dir, 'source.yml')
270
+ end
271
+
272
+ # Load the source.yml file and resolves all information it contains.
273
+ #
274
+ # This for instance requires configuration options to be defined. Use
275
+ # PackageSet#load_minimal to load only self-contained information
276
+ def load_description_file
277
+ if @source_definition
278
+ return
279
+ end
280
+
281
+ @source_definition = raw_description_file
282
+ load_minimal
283
+
284
+ # Compute the definition of constants
285
+ Autoproj.in_file(source_file) do
286
+ constants = source_definition['constants'] || Hash.new
287
+ @constants_definitions = Autoproj.resolve_constant_definitions(constants)
288
+ end
289
+ end
290
+
291
+ def single_expansion(data, additional_expansions = Hash.new)
292
+ if !source_definition
293
+ load_description_file
294
+ end
295
+ Autoproj.single_expansion(data, additional_expansions.merge(constants_definitions))
296
+ end
297
+
298
+ # Expands the given string as much as possible using the expansions
299
+ # listed in the source.yml file, and returns it. Raises if not all
300
+ # variables can be expanded.
301
+ def expand(data, additional_expansions = Hash.new)
302
+ if !source_definition
303
+ load_description_file
304
+ end
305
+ Autoproj.expand(data, additional_expansions.merge(constants_definitions))
306
+ end
307
+
308
+ # Returns the default importer definition for this package set, as a
309
+ # VCSDefinition instance
310
+ def default_importer
311
+ importer_definition_for('default')
312
+ end
313
+
314
+ # Returns an importer definition for the given package, if one is
315
+ # available. Otherwise returns nil.
316
+ #
317
+ # The returned value is a VCSDefinition object.
318
+ def version_control_field(package_name, section_name, validate = true)
319
+ urls = source_definition['urls'] || Hash.new
320
+ urls['HOME'] = ENV['HOME']
321
+
322
+ all_vcs = source_definition[section_name]
323
+ if all_vcs
324
+ if all_vcs.kind_of?(Hash)
325
+ raise ConfigError.new, "wrong format for the #{section_name} section, you forgot the '-' in front of the package names"
326
+ elsif !all_vcs.kind_of?(Array)
327
+ raise ConfigError.new, "wrong format for the #{section_name} section"
328
+ end
329
+ end
330
+
331
+ raw = []
332
+ vcs_spec = Hash.new
333
+
334
+ if all_vcs
335
+ all_vcs.each do |spec|
336
+ spec = spec.dup
337
+ if spec.values.size != 1
338
+ # Maybe the user wrote the spec like
339
+ # - package_name:
340
+ # type: git
341
+ # url: blah
342
+ #
343
+ # or as
344
+ # - package_name
345
+ # type: git
346
+ # url: blah
347
+ #
348
+ # In that case, we should have the package name as
349
+ # "name => nil". Check that.
350
+ name, _ = spec.find { |n, v| v.nil? }
351
+ if name
352
+ spec.delete(name)
353
+ else
354
+ name, _ = spec.find { |n, v| n =~ / \w+$/ }
355
+ name =~ / (\w+)$/
356
+ spec[$1] = spec.delete(name)
357
+ name = name.gsub(/ \w+$/, '')
358
+ end
359
+ else
360
+ name, spec = spec.to_a.first
361
+ if name =~ / (\w+)/
362
+ spec = { $1 => spec }
363
+ name = name.gsub(/ \w+$/, '')
364
+ end
365
+
366
+ if spec.respond_to?(:to_str)
367
+ if spec == "none"
368
+ spec = { :type => "none" }
369
+ else
370
+ raise ConfigError.new, "invalid VCS specification in the #{section_name} section '#{name}: #{spec}'"
371
+ end
372
+ end
373
+ end
374
+
375
+ name_match = name
376
+ if name_match =~ /[^\w\/_-]/
377
+ name_match = Regexp.new("^" + name_match)
378
+ end
379
+ if name_match === package_name
380
+ raw << [self.name, spec]
381
+ vcs_spec =
382
+ begin
383
+ VCSDefinition.update_raw_vcs_spec(vcs_spec, spec)
384
+ rescue ConfigError => e
385
+ raise ConfigError.new, "invalid VCS definition in the #{section_name} section for '#{name}': #{e.message}", e.backtrace
386
+ end
387
+ end
388
+ end
389
+ end
390
+
391
+ if !vcs_spec.empty?
392
+ expansions = Hash["PACKAGE" => package_name,
393
+ "PACKAGE_BASENAME" => File.basename(package_name),
394
+ "AUTOPROJ_ROOT" => Autoproj.root_dir,
395
+ "AUTOPROJ_CONFIG" => Autoproj.config_dir,
396
+ "AUTOPROJ_SOURCE_DIR" => local_dir]
397
+
398
+ vcs_spec = expand(vcs_spec, expansions)
399
+ vcs_spec.dup.each do |name, value|
400
+ vcs_spec[name] = expand(value, expansions)
401
+ end
402
+
403
+ # If required, verify that the configuration is a valid VCS
404
+ # configuration
405
+ if validate
406
+ begin
407
+ VCSDefinition.from_raw(vcs_spec)
408
+ rescue ConfigError => e
409
+ raise ConfigError.new, "invalid resulting VCS definition for package #{package_name}: #{e.message}", e.backtrace
410
+ end
411
+ end
412
+ return vcs_spec, raw
413
+ else
414
+ return nil, []
415
+ end
416
+ end
417
+
418
+ # Returns the VCS definition for +package_name+ as defined in this
419
+ # source, or nil if the source does not have any.
420
+ #
421
+ # The definition is an instance of VCSDefinition
422
+ def importer_definition_for(package_name)
423
+ Autoproj.in_file source_file do
424
+ vcs_spec, raw = version_control_field(package_name, 'version_control')
425
+ if vcs_spec
426
+ VCSDefinition.from_raw(vcs_spec, raw)
427
+ end
428
+ end
429
+ end
430
+
431
+ # Enumerates the Autobuild::Package instances that are defined in this
432
+ # source
433
+ def each_package
434
+ if !block_given?
435
+ return enum_for(:each_package)
436
+ end
437
+
438
+ Autoproj.manifest.packages.each_value do |pkg|
439
+ if pkg.package_set.name == name
440
+ yield(pkg.autobuild)
441
+ end
442
+ end
443
+ end
444
+
445
+ # True if this package set provides the given package set name. I.e. if
446
+ # it has this name or the name is listed in the "replaces" field of
447
+ # source.yml
448
+ def provides?(name)
449
+ name == self.name ||
450
+ provides.include?(name)
451
+ end
452
+ end
453
+
454
+ # Specialization of the PackageSet class for the overrides listed in autoproj/
455
+ class LocalPackageSet < PackageSet
456
+ def initialize(manifest)
457
+ super(manifest, VCSDefinition.from_raw(:type => 'local', :url => Autoproj.config_dir))
458
+ end
459
+
460
+ def name
461
+ 'local'
462
+ end
463
+ def load_minimal
464
+ end
465
+ def repository_id
466
+ 'local'
467
+ end
468
+
469
+ def source_file
470
+ File.join(Autoproj.config_dir, "overrides.yml")
471
+ end
472
+
473
+ # Returns the default importer for this package set
474
+ def default_importer
475
+ importer_definition_for('default') ||
476
+ VCSDefinition.from_raw(:type => 'none')
477
+ end
478
+
479
+ def raw_description_file
480
+ path = source_file
481
+ if File.file?(path)
482
+ data = Autoproj.in_file(path, Autoproj::YAML_LOAD_ERROR) do
483
+ YAML.load(File.read(path)) || Hash.new
484
+ end
485
+ data['name'] = 'local'
486
+ data
487
+ else
488
+ { 'name' => 'local' }
489
+ end
490
+ end
491
+ end
492
+
493
+ # DEPRECATED. For backward-compatibility only.
494
+ Source = PackageSet
495
+ # DEPRECATED. For backward-compatibility only.
496
+ LocalSource = LocalPackageSet
497
+ end