autoproj 1.6.2 → 1.7.0.b1

Sign up to get free protection for your applications and to get access to all the features.
@@ -86,7 +86,7 @@ module Autoproj
86
86
  def initialize(type, url, options)
87
87
  @type, @url, @options = type, url, options
88
88
  if type != "none" && type != "local" && !Autobuild.respond_to?(type)
89
- raise ConfigError, "version control #{type} is unknown to autoproj"
89
+ raise ConfigError.new, "version control #{type} is unknown to autoproj"
90
90
  end
91
91
  end
92
92
 
@@ -94,6 +94,17 @@ module Autoproj
94
94
  @type == 'local'
95
95
  end
96
96
 
97
+ def ==(other_vcs)
98
+ return false if !other_vcs.kind_of?(VCSDefinition)
99
+ if local?
100
+ other_vcs.local? && url == other.url
101
+ elsif !other_vcs.local?
102
+ this_importer = create_autobuild_importer
103
+ other_importer = other_vcs.create_autobuild_importer
104
+ this_importer.repository_id == other_importer.repository_id
105
+ end
106
+ end
107
+
97
108
  def self.to_absolute_url(url, root_dir = nil)
98
109
  # NOTE: we MUST use nil as default argument of root_dir as we don't
99
110
  # want to call Autoproj.root_dir unless completely necessary
@@ -119,7 +130,7 @@ module Autoproj
119
130
  else
120
131
  desc = "#{type}:#{url}"
121
132
  if !options.empty?
122
- desc = "#{desc} #{options.map { |key, value| "#{key}=#{value}" }.join(" ")}"
133
+ desc = "#{desc} #{options.to_a.sort_by { |key, _| key.to_s }.map { |key, value| "#{key}=#{value}" }.join(" ")}"
123
134
  end
124
135
  desc
125
136
  end
@@ -127,12 +138,21 @@ module Autoproj
127
138
  end
128
139
 
129
140
  def self.vcs_definition_to_hash(spec)
141
+ options = Hash.new
142
+ if spec.size == 1 && spec.keys.first =~ /auto_imports$/
143
+ # The user probably wrote
144
+ # - string
145
+ # auto_imports: false
146
+ options['auto_imports'] = spec.values.first
147
+ spec = spec.keys.first.split(" ").first
148
+ end
149
+
130
150
  if spec.respond_to?(:to_str)
131
151
  vcs, *url = spec.to_str.split ':'
132
152
  spec = if url.empty?
133
153
  source_dir = File.expand_path(File.join(Autoproj.config_dir, spec))
134
154
  if !File.directory?(source_dir)
135
- raise ConfigError, "'#{spec.inspect}' is neither a remote source specification, nor a local source definition"
155
+ raise ConfigError.new, "'#{spec.inspect}' is neither a remote source specification, nor a local source definition"
136
156
  end
137
157
 
138
158
  Hash[:type => 'local', :url => source_dir]
@@ -143,7 +163,7 @@ module Autoproj
143
163
 
144
164
  spec, vcs_options = Kernel.filter_options spec, :type => nil, :url => nil
145
165
 
146
- return spec.merge(vcs_options)
166
+ return spec.merge(vcs_options).merge(options)
147
167
  end
148
168
 
149
169
  # Autoproj configuration files accept VCS definitions in three forms:
@@ -155,7 +175,7 @@ module Autoproj
155
175
  def self.normalize_vcs_definition(spec)
156
176
  spec = vcs_definition_to_hash(spec)
157
177
  if !(spec[:type] && (spec[:type] == 'none' || spec[:url]))
158
- raise ConfigError, "the source specification #{spec.inspect} misses either the VCS type or an URL"
178
+ raise ConfigError.new, "the source specification #{spec.inspect} misses either the VCS type or an URL"
159
179
  end
160
180
 
161
181
  spec, vcs_options = Kernel.filter_options spec, :type => nil, :url => nil
@@ -191,7 +211,7 @@ module Autoproj
191
211
  else
192
212
  value = single_expansion(value, definitions)
193
213
  if contains_expansion?(value)
194
- raise ConfigError, "some expansions are not defined in #{value.inspect}"
214
+ raise ConfigError.new, "some expansions are not defined in #{value.inspect}"
195
215
  end
196
216
  value
197
217
  end
@@ -219,22 +239,43 @@ module Autoproj
219
239
  result
220
240
  end
221
241
 
222
- # A source is a version control repository which contains general source
242
+ # A package set is a version control repository which contains general
223
243
  # information with package version control information (source.yml file),
224
244
  # package definitions (.autobuild files), and finally definition of
225
245
  # dependencies that are provided by the operating system (.osdeps file).
226
- class Source
246
+ class PackageSet
247
+ attr_reader :manifest
227
248
  # The VCSDefinition object that defines the version control holding
228
249
  # information for this source. Local package sets (i.e. the ones that are not
229
250
  # under version control) use the 'local' version control name. For them,
230
251
  # local? returns true.
231
252
  attr_accessor :vcs
253
+
254
+ # If this package set has been imported from another package set, this
255
+ # is the other package set object
256
+ attr_accessor :imported_from
257
+
258
+ # If true, this package set has been loaded because another set imports
259
+ # it. If false, it is loaded explicitely by the user
260
+ def explicit?; !@imported_from end
261
+
232
262
  attr_reader :source_definition
233
263
  attr_reader :constants_definitions
234
264
 
265
+ # Sets the auto_imports flag. See #auto_imports?
266
+ attr_writer :auto_imports
267
+ # If true (the default), imports listed in this package set will be
268
+ # automatically loaded by autoproj
269
+ def auto_imports?; !!@auto_imports end
270
+
235
271
  # Create this source from a VCSDefinition object
236
- def initialize(vcs)
272
+ def initialize(manifest, vcs)
273
+ @manifest = manifest
237
274
  @vcs = vcs
275
+
276
+ @provides = Set.new
277
+ @imports = Array.new
278
+ @auto_imports = true
238
279
  end
239
280
 
240
281
  # True if this source has already been checked out on the local autoproj
@@ -250,6 +291,47 @@ module Autoproj
250
291
  !File.exists?(File.join(raw_local_dir, "init.rb"))
251
292
  end
252
293
 
294
+ # Create a PackageSet instance from its description as found in YAML
295
+ # configuration files
296
+ def self.from_spec(manifest, spec, load_description)
297
+ spec = Autoproj.vcs_definition_to_hash(spec)
298
+ options, vcs_spec = Kernel.filter_options spec, :auto_imports => true
299
+
300
+ # Look up for short notation (i.e. not an explicit hash). It is
301
+ # either vcs_type:url or just url. In the latter case, we expect
302
+ # 'url' to be a path to a local directory
303
+ vcs_spec = Autoproj.expand(vcs_spec, manifest.constant_definitions)
304
+ vcs_def = Autoproj.normalize_vcs_definition(vcs_spec)
305
+
306
+ source = PackageSet.new(manifest, vcs_def)
307
+ source.auto_imports = options[:auto_imports]
308
+ if load_description
309
+ if source.present?
310
+ source.load_description_file
311
+ else
312
+ raise InternalError, "cannot load description file as it has not been checked out yet"
313
+ end
314
+ else
315
+ # Try to load just the name from the source.yml file
316
+ source.load_minimal
317
+ end
318
+
319
+ source
320
+ end
321
+
322
+ def repository_id
323
+ if local?
324
+ local_dir
325
+ else
326
+ importer = vcs.create_autobuild_importer
327
+ if importer.respond_to?(:repository_id)
328
+ importer.repository_id
329
+ else
330
+ vcs.to_s
331
+ end
332
+ end
333
+ end
334
+
253
335
  # Remote sources can be accessed through a hidden directory in
254
336
  # $AUTOPROJ_ROOT/.remotes, or through a symbolic link in
255
337
  # autoproj/remotes/
@@ -259,9 +341,9 @@ module Autoproj
259
341
  # For local sources, is simply returns the path to the source directory.
260
342
  def raw_local_dir
261
343
  if local?
262
- return vcs.url
344
+ File.expand_path(vcs.url)
263
345
  else
264
- File.join(Autoproj.remotes_dir, automatic_name)
346
+ File.expand_path(File.join(Autoproj.remotes_dir, vcs.to_s.gsub(/[^\w]/, '_')))
265
347
  end
266
348
  end
267
349
 
@@ -291,19 +373,12 @@ module Autoproj
291
373
  end
292
374
  end
293
375
 
294
- # A name generated from the VCS url
295
- def automatic_name
296
- vcs.to_s.gsub(/[^\w]/, '_')
297
- end
298
-
299
376
  # Returns the source name
300
377
  def name
301
- if @source_definition then
302
- @source_definition['name'] || automatic_name
303
- elsif @name
378
+ if @name
304
379
  @name
305
380
  else
306
- automatic_name
381
+ vcs.to_s
307
382
  end
308
383
  end
309
384
 
@@ -318,34 +393,54 @@ module Autoproj
318
393
 
319
394
  source_file = File.join(raw_local_dir, "source.yml")
320
395
  if !File.exists?(source_file)
321
- raise ConfigError, "source #{vcs.type}:#{vcs.url} should have a source.yml file, but does not"
396
+ raise ConfigError.new, "source #{vcs.type}:#{vcs.url} should have a source.yml file, but does not"
322
397
  end
323
398
 
324
- begin
325
- source_definition = YAML.load(File.read(source_file))
326
- rescue ArgumentError => e
327
- raise ConfigError, "error in #{source_file}: #{e.message}"
399
+ source_definition = Autoproj.in_file(source_file, ArgumentError) do
400
+ YAML.load(File.read(source_file))
328
401
  end
329
402
 
330
403
  if !source_definition || !source_definition['name']
331
- raise ConfigError, "#{source_file} does not have a 'name' field"
404
+ raise ConfigError.new(source_file), "in #{source_file}: missing a 'name' field"
332
405
  end
333
406
 
334
407
  source_definition
335
408
  end
336
409
 
337
- # Load and validate the name from the YAML hash
338
- def load_name
410
+ # Load and validate the self-contained information from the YAML hash
411
+ def load_minimal
412
+ # If @source_definition is set, it means that load_description_file
413
+ # has been called and that therefore all information has already
414
+ # been parsed
339
415
  definition = @source_definition || raw_description_file
340
416
  @name = definition['name']
341
417
 
342
418
  if @name !~ /^[\w_\.-]+$/
343
- raise ConfigError, "invalid source name '#{@name}': source names can only contain alphanumeric characters, and .-_"
419
+ raise ConfigError.new(source_file),
420
+ "in #{source_file}: invalid source name '#{@name}': source names can only contain alphanumeric characters, and .-_"
344
421
  elsif @name == "local"
345
- raise ConfigError, "source #{self} is named 'local', but this is a reserved name"
422
+ raise ConfigError.new(source_file),
423
+ "in #{source_file}: the name 'local' is a reserved name"
424
+ end
425
+
426
+ @provides = (definition['provides'] || Set.new).to_set
427
+ @imports = (definition['imports'] || Array.new).map do |set_def|
428
+ pkg_set = Autoproj.in_file(source_file) do
429
+ PackageSet.from_spec(manifest, set_def, false)
430
+ end
431
+
432
+ pkg_set.imported_from = self
433
+ pkg_set
346
434
  end
347
435
 
348
436
  rescue InternalError
437
+ # This ignores raw_description_file error if the package set is not
438
+ # checked out yet
439
+ end
440
+
441
+ # Yields the imports this package set declares, as PackageSet instances
442
+ def each_imported_set(&block)
443
+ @imports.each(&block)
349
444
  end
350
445
 
351
446
  # Path to the source.yml file
@@ -353,21 +448,22 @@ module Autoproj
353
448
  File.join(local_dir, 'source.yml')
354
449
  end
355
450
 
356
- # Load the source.yml file that describes this source, and resolve the
357
- # $BLABLA values that are in there. Use #raw_description_file to avoid
358
- # resolving those values
451
+ # Load the source.yml file and resolves all information it contains.
452
+ #
453
+ # This for instance requires configuration options to be defined. Use
454
+ # PackageSet#load_minimal to load only self-contained information
359
455
  def load_description_file
456
+ if @source_definition
457
+ return
458
+ end
459
+
360
460
  @source_definition = raw_description_file
361
- load_name
461
+ load_minimal
362
462
 
363
-
364
463
  # Compute the definition of constants
365
- begin
464
+ Autoproj.in_file(source_file) do
366
465
  constants = source_definition['constants'] || Hash.new
367
466
  @constants_definitions = Autoproj.resolve_constant_definitions(constants)
368
-
369
- rescue ConfigError => e
370
- raise ConfigError, "#{File.join(local_dir, "source.yml")}: #{e.message}", e.backtrace
371
467
  end
372
468
  end
373
469
 
@@ -399,15 +495,19 @@ module Autoproj
399
495
  #
400
496
  # The returned value is a VCSDefinition object.
401
497
  def version_control_field(package_name, section_name, validate = true)
498
+ if !source_definition
499
+ raise InternalError, "source #{name} has not been loaded"
500
+ end
501
+
402
502
  urls = source_definition['urls'] || Hash.new
403
503
  urls['HOME'] = ENV['HOME']
404
504
 
405
505
  all_vcs = source_definition[section_name]
406
506
  if all_vcs
407
507
  if all_vcs.kind_of?(Hash)
408
- raise ConfigError, "wrong format for the #{section_name} section, you forgot the '-' in front of the package names"
508
+ raise ConfigError.new, "wrong format for the #{section_name} section, you forgot the '-' in front of the package names"
409
509
  elsif !all_vcs.kind_of?(Array)
410
- raise ConfigError, "wrong format for the #{section_name} section"
510
+ raise ConfigError.new, "wrong format for the #{section_name} section"
411
511
  end
412
512
  end
413
513
 
@@ -449,7 +549,7 @@ module Autoproj
449
549
  if spec == "none"
450
550
  spec = { :type => "none" }
451
551
  else
452
- raise ConfigError, "invalid VCS specification '#{name}: #{spec}'"
552
+ raise ConfigError.new, "invalid VCS specification '#{name}: #{spec}'"
453
553
  end
454
554
  end
455
555
  end
@@ -480,8 +580,6 @@ module Autoproj
480
580
  end
481
581
  vcs_spec
482
582
  end
483
- rescue ConfigError => e
484
- raise ConfigError, "#{e.message} in #{source_file}", e.backtrace
485
583
  end
486
584
 
487
585
  # Returns the VCS definition for +package_name+ as defined in this
@@ -508,18 +606,29 @@ module Autoproj
508
606
  end
509
607
  end
510
608
  end
609
+
610
+ # True if this package set provides the given package set name. I.e. if
611
+ # it has this name or the name is listed in the "replaces" field of
612
+ # source.yml
613
+ def provides?(name)
614
+ name == self.name ||
615
+ provides.include?(name)
616
+ end
511
617
  end
512
618
 
513
- # Specialization of the Source class for the overrides listed in autoproj/
514
- class LocalSource < Source
515
- def initialize
516
- super(Autoproj.normalize_vcs_definition(:type => 'local', :url => Autoproj.config_dir))
619
+ # Specialization of the PackageSet class for the overrides listed in autoproj/
620
+ class LocalPackageSet < PackageSet
621
+ def initialize(manifest)
622
+ super(manifest, Autoproj.normalize_vcs_definition(:type => 'local', :url => Autoproj.config_dir))
517
623
  end
518
624
 
519
625
  def name
520
626
  'local'
521
627
  end
522
- def load_name
628
+ def load_minimal
629
+ end
630
+ def repository_id
631
+ 'local'
523
632
  end
524
633
 
525
634
  def source_file
@@ -535,10 +644,8 @@ module Autoproj
535
644
  def raw_description_file
536
645
  path = source_file
537
646
  if File.file?(path)
538
- begin
539
- data = YAML.load(File.read(path)) || Hash.new
540
- rescue ArgumentError => e
541
- raise ConfigError, "error in #{source_file}: #{e.message}"
647
+ data = Autoproj.in_file(path, ArgumentError) do
648
+ YAML.load(File.read(path)) || Hash.new
542
649
  end
543
650
  data['name'] = 'local'
544
651
  data
@@ -548,12 +655,16 @@ module Autoproj
548
655
  end
549
656
  end
550
657
 
658
+ # DEPRECATED. For backward-compatibility only.
659
+ Source = PackageSet
660
+ # DEPRECATED. For backward-compatibility only.
661
+ LocalSource = LocalPackageSet
662
+
551
663
  PackageDefinition = Struct.new :autobuild, :user_block, :package_set, :file
552
664
 
553
665
  # The Manifest class represents the information included in the main
554
666
  # manifest file, and allows to manipulate it
555
667
  class Manifest
556
- FakePackage = Struct.new :text_name, :name, :srcdir, :importer, :updated
557
668
 
558
669
  # Data structure used to use autobuild importers without a package, to
559
670
  # import configuration data.
@@ -561,7 +672,21 @@ module Autoproj
561
672
  # It has to match the interface of Autobuild::Package that is relevant
562
673
  # for importers
563
674
  class FakePackage
564
- def autoproj_name; name end
675
+ attr_reader :text_name
676
+ attr_reader :name
677
+ attr_reader :srcdir
678
+ attr_reader :importer
679
+
680
+ # Used by the autobuild importers
681
+ attr_accessor :updated
682
+
683
+ def initialize(text_name, srcdir, importer = nil)
684
+ @text_name = text_name
685
+ @name = text_name.gsub /[^\w]/, '_'
686
+ @srcdir = srcdir
687
+ @importer = importer
688
+ end
689
+
565
690
  def import
566
691
  importer.import(self)
567
692
  end
@@ -593,21 +718,34 @@ module Autoproj
593
718
  # Loads the manifest file located at +file+ and returns the Manifest
594
719
  # instance that represents it
595
720
  def self.load(file)
596
- begin
597
- data = YAML.load(File.read(file))
598
- rescue Errno::ENOENT
599
- raise ConfigError, "expected an autoproj configuration in #{File.expand_path(File.dirname(file))}, but #{file} does not exist"
600
- rescue ArgumentError => e
601
- raise ConfigError, "error in #{file}: #{e.message}"
602
- end
603
- Manifest.new(file, data)
721
+ manifest = Manifest.new
722
+ manifest.load(file)
723
+ manifest
604
724
  end
605
725
 
726
+ # Load the manifest data contained in +file+
727
+ def load(file)
728
+ if !File.exists?(file)
729
+ raise ConfigError.new(dirname), "expected an autoproj configuration in #{dirname}, but #{file} does not exist"
730
+ end
731
+
732
+ data = Autoproj.in_file(file, ArgumentError) do
733
+ YAML.load(File.read(file))
734
+ end
735
+
736
+ @file = file
737
+ @data = data
738
+
739
+ if data['constants']
740
+ @constant_definitions = Autoproj.resolve_constant_definitions(data['constants'])
741
+ end
742
+ end
743
+
606
744
  # The manifest data as a Hash
607
745
  attr_reader :data
608
746
 
609
747
  # The set of packages defined so far as a mapping from package name to
610
- # [Autobuild::Package, source, file] tuple
748
+ # [Autobuild::Package, package_set, file] tuple
611
749
  attr_reader :packages
612
750
 
613
751
  # A mapping from package names into PackageManifest objects
@@ -633,22 +771,19 @@ module Autoproj
633
771
 
634
772
  attr_reader :constant_definitions
635
773
 
636
- def initialize(file, data)
637
- @file = file
638
- @data = data
774
+ def initialize
775
+ @file = nil
776
+ @data = nil
639
777
  @packages = Hash.new
640
778
  @package_manifests = Hash.new
641
779
  @automatic_exclusions = Hash.new
642
780
  @constants_definitions = Hash.new
781
+ @disabled_imports = Set.new
643
782
 
783
+ @constant_definitions = Hash.new
644
784
  if Autoproj.has_config_key?('manifest_source')
645
785
  @vcs = Autoproj.normalize_vcs_definition(Autoproj.user_config('manifest_source'))
646
786
  end
647
- if data['constants']
648
- @constant_definitions = Autoproj.resolve_constant_definitions(data['constants'])
649
- else
650
- @constant_definitions = Hash.new
651
- end
652
787
  end
653
788
 
654
789
  # True if the given package should not be built, with the packages that
@@ -730,7 +865,7 @@ module Autoproj
730
865
  end
731
866
 
732
867
  if source_name && !done_something
733
- raise ConfigError, "in #{file}: source '#{source_name}' does not exist"
868
+ raise ConfigError.new(file), "in #{file}: source '#{source_name}' does not exist"
734
869
  end
735
870
  end
736
871
 
@@ -767,11 +902,11 @@ module Autoproj
767
902
  end
768
903
 
769
904
  # Like #each_source, but filters out local package sets
770
- def each_remote_source(load_description = true)
905
+ def each_remote_package_set(load_description = true)
771
906
  if !block_given?
772
- enum_for(:each_remote_source, load_description)
907
+ enum_for(:each_remote_package_set, load_description)
773
908
  else
774
- each_source(load_description) do |source|
909
+ each_package_set(load_description) do |source|
775
910
  if !source.local?
776
911
  yield(source)
777
912
  end
@@ -779,73 +914,95 @@ module Autoproj
779
914
  end
780
915
  end
781
916
 
782
- def source_from_spec(spec, load_description) # :nodoc:
783
- # Look up for short notation (i.e. not an explicit hash). It is
784
- # either vcs_type:url or just url. In the latter case, we expect
785
- # 'url' to be a path to a local directory
786
- vcs_def = begin
787
- spec = Autoproj.expand(spec, constant_definitions)
788
- Autoproj.normalize_vcs_definition(spec)
789
- rescue ConfigError => e
790
- raise ConfigError, "in #{file}: #{e.message}"
791
- end
792
-
793
- source = Source.new(vcs_def)
794
- if load_description
795
- if source.present?
796
- source.load_description_file
797
- else
798
- raise InternalError, "cannot load description file as it has not been checked out yet"
799
- end
800
- else
801
- # Try to load just the name from the source.yml file
802
- source.load_name
917
+ def each_remote_source(*args, &block)
918
+ each_remote_package_set(*args, &block)
919
+ end
920
+
921
+ # Helper method for #each_package_set
922
+ def enumerate_package_set(pkg_set, explicit_sets, all_sets) # :nodoc:
923
+ if @disabled_imports.include?(pkg_set.name)
924
+ pkg_set.auto_imports = false
803
925
  end
804
926
 
805
- source
806
- end
927
+ result = []
928
+ if pkg_set.auto_imports?
929
+ pkg_set.each_imported_set do |imported_set|
930
+ if explicit_sets.any? { |src| src.vcs == imported_set.vcs } ||
931
+ all_sets.any? { |src| src.vcs == imported_set.vcs }
932
+ next
933
+ end
807
934
 
935
+ all_sets << imported_set
936
+ result.concat(enumerate_package_set(imported_set, explicit_sets, all_sets))
937
+ end
938
+ end
939
+ result << pkg_set
940
+ result
941
+ end
808
942
 
809
943
  # call-seq:
810
- # each_source { |source_description| ... }
944
+ # each_package_set { |pkg_set| ... }
811
945
  #
812
- # Lists all package sets defined in this manifest, by yielding a Source
813
- # object that describes it.
814
- def each_source(load_description = true, &block)
946
+ # Lists all package sets defined in this manifest, by yielding a
947
+ # PackageSet object that describes it.
948
+ def each_package_set(load_description = true, &block)
815
949
  if !block_given?
816
- return enum_for(:each_source, load_description)
950
+ return enum_for(:each_package_set, load_description)
817
951
  end
818
952
 
819
- if @sources
953
+ if @package_sets
820
954
  if load_description
821
- @sources.each do |src|
955
+ @package_sets.each do |src|
822
956
  if !src.source_definition
823
957
  src.load_description_file
824
958
  end
825
959
  end
826
960
  end
827
- return @sources.each(&block)
961
+ return @package_sets.each(&block)
828
962
  end
829
963
 
830
- all_sources = []
964
+ explicit_sets = (data['package_sets'] || []).map do |spec|
965
+ Autoproj.in_file(self.file) do
966
+ PackageSet.from_spec(self, spec, load_description)
967
+ end
968
+ end
831
969
 
832
- (data['package_sets'] || []).each do |spec|
833
- all_sources << source_from_spec(spec, load_description)
970
+ all_sets = Array.new
971
+ explicit_sets.each do |pkg_set|
972
+ all_sets.concat(enumerate_package_set(pkg_set, explicit_sets, all_sets + [pkg_set]))
834
973
  end
835
974
 
836
975
  # Now load the local source
837
- local = LocalSource.new
976
+ local = LocalPackageSet.new(self)
838
977
  if load_description
839
978
  local.load_description_file
840
979
  else
841
- local.load_name
980
+ local.load_minimal
842
981
  end
843
982
  if !load_description || !local.empty?
844
- all_sources << local
983
+ all_sets << local
984
+ end
985
+
986
+ if load_description
987
+ all_sets.each(&:load_description_file)
988
+ else
989
+ all_sets.each(&:load_minimal)
845
990
  end
991
+ all_sets.each(&block)
992
+ end
993
+
994
+ # DEPRECATED. For backward-compatibility only
995
+ #
996
+ # use #each_package_set instead
997
+ def each_source(*args, &block)
998
+ each_package_set(*args, &block)
999
+ end
846
1000
 
847
- all_sources.each(&block)
848
- @sources = all_sources
1001
+ # Save the currently known package sets. After this call,
1002
+ # #each_package_set will always return the same set regardless of
1003
+ # changes on the manifest's data structures
1004
+ def cache_package_sets
1005
+ @package_sets = each_package_set(false).to_a
849
1006
  end
850
1007
 
851
1008
  # Register a new package
@@ -860,6 +1017,10 @@ module Autoproj
860
1017
  @packages[package_name].file
861
1018
  end
862
1019
 
1020
+ def package(name)
1021
+ packages[name]
1022
+ end
1023
+
863
1024
  # Lists all defined packages and where they have been defined
864
1025
  def each_package
865
1026
  if !block_given?
@@ -877,11 +1038,11 @@ module Autoproj
877
1038
  end
878
1039
 
879
1040
  if vcs
880
- yield(vcs, "autoproj main configuration", "autoproj_config", Autoproj.config_dir)
1041
+ yield(vcs, "autoproj main configuration", Autoproj.config_dir)
881
1042
  end
882
1043
 
883
1044
  each_remote_source(false) do |source|
884
- yield(source.vcs, source.name || source.vcs.url, source.automatic_name, source.local_dir)
1045
+ yield(source.vcs, source.name || source.vcs.url, source.local_dir)
885
1046
  end
886
1047
  self
887
1048
  end
@@ -893,81 +1054,108 @@ module Autoproj
893
1054
  # the name used when displaying the import progress, +pkg_name+ the
894
1055
  # internal name used to represent the package and +into+ the directory
895
1056
  # in which the package should be checked out.
896
- def self.create_autobuild_package(vcs, text_name, pkg_name, into)
1057
+ def self.create_autobuild_package(vcs, text_name, into)
1058
+ if !into
1059
+ raise
1060
+ end
897
1061
  importer = vcs.create_autobuild_importer
898
1062
  return if !importer # updates have been disabled by using the 'none' type
899
1063
 
900
- fake_package = FakePackage.new(text_name, pkg_name, into)
901
- fake_package.importer = importer
902
- fake_package
1064
+ FakePackage.new(text_name, into, importer)
903
1065
 
904
1066
  rescue Autobuild::ConfigException => e
905
- raise ConfigError, "cannot import #{name}: #{e.message}", e.backtrace
1067
+ raise ConfigError.new, "cannot import #{name}: #{e.message}", e.backtrace
906
1068
  end
907
1069
 
908
1070
  # Imports or updates a source (remote or otherwise).
909
1071
  #
910
1072
  # See create_autobuild_package for informations about the arguments.
911
- def self.update_source(vcs, text_name, pkg_name, into)
912
- fake_package = create_autobuild_package(vcs, text_name, pkg_name, into)
1073
+ def self.update_package_set(vcs, text_name, into)
1074
+ if !into
1075
+ raise
1076
+ end
1077
+ fake_package = create_autobuild_package(vcs, text_name, into)
913
1078
  fake_package.import
914
1079
 
915
1080
  rescue Autobuild::ConfigException => e
916
- raise ConfigError, "cannot import #{name}: #{e.message}", e.backtrace
1081
+ raise ConfigError.new, "cannot import #{name}: #{e.message}", e.backtrace
917
1082
  end
918
1083
 
919
1084
  # Updates the main autoproj configuration
920
1085
  def update_yourself
921
- Manifest.update_source(vcs, "autoproj main configuration", "autoproj_conf", Autoproj.config_dir)
1086
+ Manifest.update_package_set(vcs, "autoproj main configuration", Autoproj.config_dir)
922
1087
  end
923
1088
 
924
- # Updates all the remote sources in ROOT_DIR/.remotes, as well as the
925
- # symbolic links in ROOT_DIR/autoproj/remotes
926
- def update_remote_sources
927
- # Iterate on the remote sources, without loading the source.yml
928
- # file (we're not ready for that yet)
929
- sources = []
930
- each_remote_source(false) do |source|
931
- name = if source.present? then source.name
932
- else source.vcs.url
933
- end
934
- Manifest.update_source(source.vcs, name, source.automatic_name, source.raw_local_dir)
935
- sources << source
936
- end
1089
+ def update_remote_set(pkg_set, remotes_symlinks_dir = nil)
1090
+ Manifest.update_package_set(
1091
+ pkg_set.vcs,
1092
+ pkg_set.name,
1093
+ pkg_set.raw_local_dir)
937
1094
 
938
- # Check for directories in ROOT_DIR/.remotes that do not map to a
939
- # source repository, and remove them
940
- Dir.glob(File.join(Autoproj.remotes_dir, '*')).each do |dir|
941
- dir = File.expand_path(dir)
942
- if File.directory?(dir) && !sources.any? { |s| s.raw_local_dir == dir }
943
- FileUtils.rm_rf dir
944
- end
945
- end
946
-
947
- remotes_symlinks_dir = File.join(Autoproj.config_dir, 'remotes')
948
- FileUtils.mkdir_p remotes_symlinks_dir
949
- known_remotes = []
950
-
951
- # Create symbolic links from .remotes/weird_url to
952
- # autoproj/remotes/name. Explicitely load the source name first
953
- each_remote_source(false) do |source|
954
- source.load_name
955
- symlink_dest = File.join(remotes_symlinks_dir, source.name)
1095
+ if remotes_symlinks_dir
1096
+ pkg_set.load_minimal
1097
+ symlink_dest = File.join(remotes_symlinks_dir, pkg_set.name)
956
1098
 
957
1099
  # Check if the current symlink is valid, and recreate it if it
958
1100
  # is not
959
1101
  if File.symlink?(symlink_dest)
960
1102
  dest = File.readlink(symlink_dest)
961
- if dest != source.raw_local_dir
1103
+ if dest != pkg_set.raw_local_dir
962
1104
  FileUtils.rm_f symlink_dest
963
- FileUtils.ln_sf source.raw_local_dir, symlink_dest
1105
+ FileUtils.ln_sf pkg_set.raw_local_dir, symlink_dest
964
1106
  end
965
1107
  else
966
1108
  FileUtils.rm_f symlink_dest
967
- FileUtils.ln_sf source.raw_local_dir, symlink_dest
1109
+ FileUtils.ln_sf pkg_set.raw_local_dir, symlink_dest
1110
+ end
1111
+
1112
+ symlink_dest
1113
+ end
1114
+ end
1115
+
1116
+ # Updates all the remote sources in ROOT_DIR/.remotes, as well as the
1117
+ # symbolic links in ROOT_DIR/autoproj/remotes
1118
+ def update_remote_package_sets
1119
+ remotes_symlinks_dir = File.join(Autoproj.config_dir, 'remotes')
1120
+ FileUtils.mkdir_p remotes_symlinks_dir
1121
+
1122
+ # Iterate on the remote sources, without loading the source.yml
1123
+ # file (we're not ready for that yet)
1124
+ #
1125
+ # Do it iteratively to properly take imports into account, but we
1126
+ # first unconditionally update all the existing sets to properly
1127
+ # handle imports that have been removed
1128
+ updated_sets = Hash.new
1129
+ known_remotes = []
1130
+ each_remote_package_set(false) do |pkg_set|
1131
+ next if !pkg_set.explicit?
1132
+ if pkg_set.present?
1133
+ known_remotes << update_remote_set(pkg_set, remotes_symlinks_dir)
1134
+ updated_sets[pkg_set.repository_id] = pkg_set
1135
+ end
1136
+ end
1137
+
1138
+ old_updated_sets = nil
1139
+ while old_updated_sets != updated_sets
1140
+ old_updated_sets = updated_sets.dup
1141
+ each_remote_package_set(false) do |pkg_set|
1142
+ next if updated_sets.has_key?(pkg_set.repository_id)
1143
+
1144
+ if !pkg_set.explicit?
1145
+ Autoproj.progress " #{pkg_set.imported_from.name}: auto-importing #{pkg_set.name}"
1146
+ end
1147
+ known_remotes << update_remote_set(pkg_set, remotes_symlinks_dir)
1148
+ updated_sets[pkg_set.repository_id] = pkg_set
968
1149
  end
1150
+ end
969
1151
 
970
- known_remotes << symlink_dest
1152
+ # Check for directories in ROOT_DIR/.remotes that do not map to a
1153
+ # source repository, and remove them
1154
+ Dir.glob(File.join(Autoproj.remotes_dir, '*')).each do |dir|
1155
+ dir = File.expand_path(dir)
1156
+ if File.directory?(dir) && !updated_sets.values.find { |pkg| pkg.raw_local_dir == dir }
1157
+ FileUtils.rm_rf dir
1158
+ end
971
1159
  end
972
1160
 
973
1161
  # Now remove obsolete symlinks
@@ -978,6 +1166,11 @@ module Autoproj
978
1166
  end
979
1167
  end
980
1168
 
1169
+ # DEPRECATED. For backward-compatibility only
1170
+ def update_remote_sources(*args, &block)
1171
+ update_remote_package_sets(*args, &block)
1172
+ end
1173
+
981
1174
  def importer_definition_for(package_name, package_source = nil)
982
1175
  if !package_source
983
1176
  package_source = packages.values.
@@ -985,13 +1178,16 @@ module Autoproj
985
1178
  package_set
986
1179
  end
987
1180
 
988
- sources = each_source.to_a
1181
+ sources = each_source.to_a.dup
989
1182
 
990
1183
  # Remove sources listed before the package source
991
1184
  while !sources.empty? && sources[0].name != package_source.name
992
1185
  sources.shift
993
1186
  end
994
1187
  package_source = sources.shift
1188
+ if !package_source
1189
+ raise InternalError, "cannot find the package set that defines #{package_name}"
1190
+ end
995
1191
 
996
1192
  # Get the version control information from the package source. There
997
1193
  # must be one
@@ -1032,7 +1228,7 @@ module Autoproj
1032
1228
  Autoproj.add_build_system_dependency vcs.type
1033
1229
  pkg.autobuild.importer = vcs.create_autobuild_importer
1034
1230
  else
1035
- raise ConfigError, "source #{pkg.package_set.name} defines #{pkg.autobuild.name}, but does not provide a version control definition for it"
1231
+ raise ConfigError.new, "source #{pkg.package_set.name} defines #{pkg.autobuild.name}, but does not provide a version control definition for it"
1036
1232
  end
1037
1233
  end
1038
1234
  end
@@ -1044,12 +1240,12 @@ module Autoproj
1044
1240
  if Autobuild::Package[name]
1045
1241
  [name]
1046
1242
  else
1047
- source = each_source.find { |source| source.name == name }
1048
- if !source
1049
- raise ConfigError, "in #{file}: #{name} is neither a package nor a source"
1243
+ pkg_set = each_package_set(false).find { |set| set.name == name }
1244
+ if !pkg_set
1245
+ raise ConfigError.new, "#{name} is neither a package nor a source"
1050
1246
  end
1051
1247
  packages.values.
1052
- find_all { |pkg| pkg.package_set.name == source.name }.
1248
+ find_all { |pkg| pkg.package_set.name == pkg_set.name }.
1053
1249
  map { |pkg| pkg.autobuild.name }.
1054
1250
  find_all { |pkg_name| !Autoproj.osdeps || !Autoproj.osdeps.has?(pkg_name) }
1055
1251
  end
@@ -1059,11 +1255,15 @@ module Autoproj
1059
1255
  #
1060
1256
  # If recursive is false, yields only the packages at this level.
1061
1257
  # Otherwise, return all packages.
1062
- def layout_packages(layout_def, recursive)
1258
+ def layout_packages(layout_def, recursive, validate = true)
1063
1259
  result = []
1064
1260
  layout_def.each do |value|
1065
1261
  if !value.kind_of?(Hash) # sublayout
1066
- result.concat(resolve_package_set(value))
1262
+ begin
1263
+ result.concat(resolve_package_set(value))
1264
+ rescue ConfigError
1265
+ raise if validate
1266
+ end
1067
1267
  end
1068
1268
  end
1069
1269
 
@@ -1088,7 +1288,7 @@ module Autoproj
1088
1288
 
1089
1289
  # Looks into the layout setup in the manifest, and yields each layout
1090
1290
  # and sublayout in order
1091
- def each_package_set(selection = nil, layout_name = '/', layout_def = data['layout'], &block)
1291
+ def each_layout_level(selection = nil, layout_name = '/', layout_def = data['layout'], &block)
1092
1292
  if !layout_def
1093
1293
  yield(layout_name, default_packages, default_packages)
1094
1294
  return nil
@@ -1112,14 +1312,14 @@ module Autoproj
1112
1312
 
1113
1313
  # Now, enumerate the sublayouts
1114
1314
  each_sublayout(layout_def) do |subname, sublayout|
1115
- each_package_set(selection, "#{layout_name}#{subname}/", sublayout, &block)
1315
+ each_layout_level(selection, "#{layout_name}#{subname}/", sublayout, &block)
1116
1316
  end
1117
1317
  end
1118
1318
 
1119
1319
  # Returns the set of package names that are explicitely listed in the
1120
1320
  # layout, minus the excluded and ignored ones
1121
- def all_layout_packages
1122
- default_packages
1321
+ def all_layout_packages(validate = true)
1322
+ default_packages(validate)
1123
1323
  end
1124
1324
 
1125
1325
  # Returns all defined package names, minus the excluded and ignored ones
@@ -1134,11 +1334,30 @@ module Autoproj
1134
1334
  find_all { |pkg_name| !Autoproj.osdeps || !Autoproj.osdeps.has?(pkg_name) }
1135
1335
  end
1136
1336
 
1337
+ # Returns true if +name+ is a valid package and is included in the build
1338
+ #
1339
+ # If +validate+ is true, the layout will have to be well-defined. It can
1340
+ # therefore be used only after all sources have been successfully
1341
+ # loaded.
1342
+ #
1343
+ # If it is false, non-defined packages/package sets in the layout will
1344
+ # simply be ignored
1345
+ def package_enabled?(name, validate = true)
1346
+ if !Autobuild::Package[name]
1347
+ if validate
1348
+ raise ArgumentError, "package #{name} does not exist"
1349
+ end
1350
+ return false
1351
+ end
1352
+
1353
+ !excluded?(name)
1354
+ end
1355
+
1137
1356
  # Returns the set of packages that should be built if the user does not
1138
1357
  # specify any on the command line
1139
- def default_packages
1358
+ def default_packages(validate = true)
1140
1359
  names = if layout = data['layout']
1141
- layout_packages(layout, true)
1360
+ layout_packages(layout, true, validate)
1142
1361
  else
1143
1362
  # No layout, all packages are selected
1144
1363
  all_packages
@@ -1150,12 +1369,20 @@ module Autoproj
1150
1369
 
1151
1370
  # Returns the package directory for the given package name
1152
1371
  def whereis(package_name)
1153
- each_package_set do |layout_name, packages, _|
1154
- if packages.include?(package_name)
1155
- return layout_name
1372
+ Autoproj.in_file(self.file) do
1373
+ each_layout_level do |layout_name, packages, _|
1374
+ if packages.include?(package_name)
1375
+ return layout_name
1376
+ end
1156
1377
  end
1378
+ raise ArgumentError, "cannot find #{package_name} in the layout section"
1379
+ end
1380
+ end
1381
+
1382
+ def resolve_optional_dependencies
1383
+ packages.each_value do |pkg|
1384
+ pkg.autobuild.resolve_optional_dependencies
1157
1385
  end
1158
- raise ArgumentError, "cannot find #{package_name} in the current layout"
1159
1386
  end
1160
1387
 
1161
1388
  # Loads the package's manifest.xml file for the current package
@@ -1180,13 +1407,19 @@ module Autoproj
1180
1407
  manifest = PackageManifest.load(package, manifest_path)
1181
1408
  package_manifests[package.name] = manifest
1182
1409
 
1183
- manifest.each_dependency do |name|
1410
+ manifest.each_dependency do |name, is_optional|
1184
1411
  begin
1185
- package.depends_on name
1412
+ if is_optional
1413
+ package.optional_dependency name
1414
+ else
1415
+ package.depends_on name
1416
+ end
1186
1417
  rescue Autobuild::ConfigException => e
1187
- raise ConfigError, "manifest #{manifest_path} of #{package.name} from #{source.name} lists '#{name}' as dependency, which is listed in the layout of #{file} but has no autobuild definition", e.backtrace
1418
+ raise ConfigError.new(manifest_path),
1419
+ "manifest #{manifest_path} of #{package.name} from #{source.name} lists '#{name}' as dependency, which is listed in the layout of #{file} but has no autobuild definition", e.backtrace
1188
1420
  rescue ConfigError => e
1189
- raise ConfigError, "manifest #{manifest_path} of #{package.name} from #{source.name} lists '#{name}' as dependency, but it is neither a normal package nor an osdeps package. osdeps reports: #{e.message}", e.backtrace
1421
+ raise ConfigError.new(manifest_path),
1422
+ "manifest #{manifest_path} of #{package.name} from #{source.name} lists '#{name}' as dependency, but it is neither a normal package nor an osdeps package. osdeps reports: #{e.message}", e.backtrace
1190
1423
  end
1191
1424
  end
1192
1425
  end
@@ -1198,6 +1431,11 @@ module Autoproj
1198
1431
  selected_packages.each(&:load_package_manifest)
1199
1432
  end
1200
1433
 
1434
+ # Disable all automatic imports from the given package set name
1435
+ def disable_imports_from(pkg_set_name)
1436
+ @disabled_imports << pkg_set_name
1437
+ end
1438
+
1201
1439
  # call-seq:
1202
1440
  # list_os_dependencies(packages) => required_packages, ospkg_to_pkg
1203
1441
  #
@@ -1264,7 +1502,7 @@ module Autoproj
1264
1502
  end
1265
1503
 
1266
1504
  # Now, search for layout names
1267
- each_package_set(nil) do |layout_name, packages, _|
1505
+ each_layout_level(nil) do |layout_name, packages, _|
1268
1506
  selected_packages.each do |sel|
1269
1507
  if layout_name[0..-1] =~ Regexp.new("#{sel}\/?$")
1270
1508
  expanded_packages |= packages.to_set
@@ -1325,6 +1563,15 @@ module Autoproj
1325
1563
  # The raw XML data as a Nokogiri document
1326
1564
  attr_reader :xml
1327
1565
 
1566
+ def short_documentation
1567
+ xml.xpath('//description').each do |node|
1568
+ if doc = node['brief']
1569
+ return doc.to_s
1570
+ end
1571
+ end
1572
+ nil
1573
+ end
1574
+
1328
1575
  def initialize(package, doc)
1329
1576
  @package = package
1330
1577
  @xml = doc
@@ -1342,10 +1589,10 @@ module Autoproj
1342
1589
  def each_os_dependency
1343
1590
  if block_given?
1344
1591
  xml.xpath('//rosdep').each do |node|
1345
- yield(node['name'])
1592
+ yield(node['name'], false)
1346
1593
  end
1347
1594
  package.os_packages.each do |name|
1348
- yield(name)
1595
+ yield(name, false)
1349
1596
  end
1350
1597
  else
1351
1598
  enum_for :each_os_dependency
@@ -1356,10 +1603,16 @@ module Autoproj
1356
1603
  if block_given?
1357
1604
  xml.xpath('//depend').each do |node|
1358
1605
  dependency = node['package']
1606
+ optional =
1607
+ if node['optional'].to_s == '1'
1608
+ true
1609
+ else false
1610
+ end
1611
+
1359
1612
  if dependency
1360
- yield(dependency)
1613
+ yield(dependency, optional)
1361
1614
  else
1362
- raise ConfigError, "manifest of #{package.name} has a <depend> tag without a 'package' attribute"
1615
+ raise ConfigError.new, "manifest of #{package.name} has a <depend> tag without a 'package' attribute"
1363
1616
  end
1364
1617
  end
1365
1618
  else