autobuild 1.17.0 → 1.18.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +107 -0
  3. data/Gemfile +2 -1
  4. data/Rakefile +1 -4
  5. data/autobuild.gemspec +14 -11
  6. data/bin/autobuild +4 -3
  7. data/lib/autobuild.rb +4 -5
  8. data/lib/autobuild/build_logfile.rb +6 -4
  9. data/lib/autobuild/config.rb +90 -40
  10. data/lib/autobuild/configurable.rb +30 -18
  11. data/lib/autobuild/environment.rb +126 -120
  12. data/lib/autobuild/exceptions.rb +48 -31
  13. data/lib/autobuild/import/archive.rb +134 -82
  14. data/lib/autobuild/import/cvs.rb +28 -24
  15. data/lib/autobuild/import/darcs.rb +13 -16
  16. data/lib/autobuild/import/git-lfs.rb +37 -30
  17. data/lib/autobuild/import/git.rb +231 -179
  18. data/lib/autobuild/import/hg.rb +23 -18
  19. data/lib/autobuild/import/svn.rb +48 -29
  20. data/lib/autobuild/importer.rb +530 -499
  21. data/lib/autobuild/mail_reporter.rb +77 -77
  22. data/lib/autobuild/package.rb +171 -101
  23. data/lib/autobuild/packages/autotools.rb +47 -42
  24. data/lib/autobuild/packages/cmake.rb +71 -65
  25. data/lib/autobuild/packages/dummy.rb +9 -8
  26. data/lib/autobuild/packages/genom.rb +1 -1
  27. data/lib/autobuild/packages/gnumake.rb +19 -13
  28. data/lib/autobuild/packages/import.rb +2 -6
  29. data/lib/autobuild/packages/orogen.rb +32 -31
  30. data/lib/autobuild/packages/pkgconfig.rb +2 -2
  31. data/lib/autobuild/packages/python.rb +7 -2
  32. data/lib/autobuild/packages/ruby.rb +22 -17
  33. data/lib/autobuild/parallel.rb +35 -39
  34. data/lib/autobuild/pkgconfig.rb +25 -13
  35. data/lib/autobuild/progress_display.rb +23 -23
  36. data/lib/autobuild/rake_task_extension.rb +6 -6
  37. data/lib/autobuild/reporting.rb +38 -26
  38. data/lib/autobuild/subcommand.rb +72 -65
  39. data/lib/autobuild/test.rb +8 -7
  40. data/lib/autobuild/test_utility.rb +10 -9
  41. data/lib/autobuild/timestamps.rb +28 -23
  42. data/lib/autobuild/tools.rb +17 -16
  43. data/lib/autobuild/utility.rb +16 -18
  44. data/lib/autobuild/version.rb +1 -1
  45. metadata +39 -38
@@ -10,13 +10,13 @@ class Hg < Importer
10
10
  #
11
11
  # This importer uses the 'hg' tool to perform the
12
12
  # import. It defaults to 'hg' and can be configured by
13
- # doing
13
+ # doing
14
14
  # Autobuild.programs['hg'] = 'my_git_tool'
15
15
  #
16
16
  # @param [String] repository the repository URL
17
17
  # @option options [String] :branch (default) the branch to track
18
18
  def initialize(repository, options = {})
19
- hgopts, common = Kernel.filter_options options,
19
+ hgopts, _common = Kernel.filter_options options,
20
20
  branch: 'default'
21
21
  sourceopts, common = Kernel.filter_options options,
22
22
  :repository_id, :source_id
@@ -29,10 +29,12 @@ def initialize(repository, options = {})
29
29
  # Changes the repository this importer is pointing to
30
30
  def relocate(repository, options = Hash.new)
31
31
  @repository = repository
32
- @repository_id = options[:repository_id] ||
32
+ @repository_id =
33
+ options[:repository_id] ||
33
34
  "hg:#{@repository}"
34
- @source_id = options[:source_id] ||
35
- "#{self.repository_id} branch=#{self.branch}"
35
+ @source_id =
36
+ options[:source_id] ||
37
+ "#{repository_id} branch=#{branch}"
36
38
  end
37
39
 
38
40
  # The remote repository URL.
@@ -44,38 +46,41 @@ def relocate(repository, options = Hash.new)
44
46
  # Raises ConfigException if the current directory is not a hg
45
47
  # repository
46
48
  def validate_importdir(package)
47
- if !File.directory?(File.join(package.importdir, '.hg'))
48
- raise ConfigException.new(package, 'import'), "while importing #{package.name}, #{package.importdir} is not a hg repository"
49
+ unless File.directory?(File.join(package.importdir, '.hg'))
50
+ raise ConfigException.new(package, 'import'),
51
+ "while importing #{package.name}, "\
52
+ "#{package.importdir} is not a hg repository"
49
53
  end
50
54
  end
51
55
 
52
56
  def update(package, options = Hash.new)
53
57
  if options[:only_local]
54
- package.warn "%s: the Mercurial importer does not support local updates, skipping"
58
+ package.warn "%s: the Mercurial importer does not support "\
59
+ "local updates, skipping"
55
60
  return false
56
61
  end
57
62
  validate_importdir(package)
58
- package.run(:import, Autobuild.tool('hg'), 'pull', repository, retry: true, working_directory: package.importdir)
59
- package.run(:import, Autobuild.tool('hg'), 'update', branch, working_directory: package.importdir)
63
+ package.run(:import, Autobuild.tool('hg'), 'pull',
64
+ repository, retry: true, working_directory: package.importdir)
65
+ package.run(:import, Autobuild.tool('hg'), 'update',
66
+ branch, working_directory: package.importdir)
60
67
  true # no easy to know if package was updated, keep previous behavior
61
68
  end
62
69
 
63
- def checkout(package, options = Hash.new)
70
+ def checkout(package, _options = Hash.new)
64
71
  base_dir = File.expand_path('..', package.importdir)
65
- if !File.directory?(base_dir)
66
- FileUtils.mkdir_p base_dir
67
- end
72
+ FileUtils.mkdir_p(base_dir) unless File.directory?(base_dir)
68
73
 
69
- package.run(:import, Autobuild.tool('hg'), 'clone', '-u', branch, repository, package.importdir, retry: true)
74
+ package.run(:import, Autobuild.tool('hg'), 'clone',
75
+ '-u', branch, repository, package.importdir, retry: true)
70
76
  end
71
77
  end
72
78
 
73
- # Creates a hg importer which gets the source for the given repository and branch
74
- # URL +source+.
79
+ # Creates a hg importer which gets the source for the given repository and
80
+ # branch URL +source+.
75
81
  #
76
82
  # @param (see Hg#initialize)
77
83
  def self.hg(repository, options = {})
78
84
  Hg.new(repository, options)
79
85
  end
80
86
  end
81
-
@@ -10,7 +10,7 @@ class SVN < Importer
10
10
  # [:svnco] options to give to 'svn co'
11
11
  #
12
12
  # This importer uses the 'svn' tool to perform the import. It defaults
13
- # to 'svn' and can be configured by doing
13
+ # to 'svn' and can be configured by doing
14
14
  # Autobuild.programs['svn'] = 'my_svn_tool'
15
15
  def initialize(svnroot, options = {})
16
16
  svnroot = [*svnroot].join("/")
@@ -25,7 +25,9 @@ def initialize(svnroot, options = {})
25
25
  # @deprecated use {svnroot} instead
26
26
  #
27
27
  # @return [String]
28
- def source; svnroot end
28
+ def source
29
+ svnroot
30
+ end
29
31
 
30
32
  # Returns the SVN root
31
33
  #
@@ -63,13 +65,23 @@ def relocate(root, options = Hash.new)
63
65
  def svn_revision(package)
64
66
  svninfo = svn_info(package)
65
67
  revision = svninfo.grep(/^Revision: /).first
66
- if !revision
67
- raise ConfigException.new(package, 'import'), "cannot get SVN information for #{package.importdir}"
68
+ unless revision
69
+ raise ConfigException.new(package, 'import'),
70
+ "cannot get SVN information for #{package.importdir}"
68
71
  end
69
72
  revision =~ /Revision: (\d+)/
70
73
  Integer($1)
71
74
  end
72
75
 
76
+ # fingerprint method returns an unique hash to identify this package,
77
+ # for SVN the revision and URL will be used
78
+ # @param [Package] package
79
+ # @return [String]
80
+ # @raises (see svn_info)
81
+ def vcs_fingerprint(package)
82
+ Digest::SHA1.hexdigest(svn_info(package).grep(/^(URL|Revision):/).sort.join("\n"))
83
+ end
84
+
73
85
  # Returns the URL of the remote SVN repository
74
86
  #
75
87
  # @param [Package] package
@@ -79,8 +91,9 @@ def svn_revision(package)
79
91
  def svn_url(package)
80
92
  svninfo = svn_info(package)
81
93
  url = svninfo.grep(/^URL: /).first
82
- if !url
83
- raise ConfigException.new(package, 'import'), "cannot get SVN information for #{package.importdir}"
94
+ unless url
95
+ raise ConfigException.new(package, 'import'),
96
+ "cannot get SVN information for #{package.importdir}"
84
97
  end
85
98
  url.chomp =~ /URL: (.+)/
86
99
  $1
@@ -97,10 +110,8 @@ def svn_url(package)
97
110
  def has_local_modifications?(package, with_untracked_files = false)
98
111
  status = run_svn(package, 'status', '--xml')
99
112
 
100
- not_modified = %w{external ignored none normal}
101
- if !with_untracked_files
102
- not_modified << "unversioned"
103
- end
113
+ not_modified = %w[external ignored none normal]
114
+ not_modified << "unversioned" unless with_untracked_files
104
115
 
105
116
  REXML::Document.new(status.join("")).
106
117
  elements.enum_for(:each, '//wc-status').
@@ -126,13 +137,14 @@ def status(package, only_local = false)
126
137
  else
127
138
  log = run_svn(package, 'log', '-r', 'BASE:HEAD', '--xml', '.')
128
139
  log = REXML::Document.new(log.join("\n"))
129
- missing_revisions = log.elements.enum_for(:each, 'log/logentry').map do |l|
130
- rev = l.attributes['revision']
131
- date = l.elements['date'].first.to_s
132
- author = l.elements['author'].first.to_s
133
- msg = l.elements['msg'].first.to_s.split("\n").first
134
- "#{rev} #{DateTime.parse(date)} #{author} #{msg}"
135
- end
140
+ missing_revisions = log.elements.enum_for(:each, 'log/logentry').
141
+ map do |l|
142
+ rev = l.attributes['revision']
143
+ date = l.elements['date'].first.to_s
144
+ author = l.elements['author'].first.to_s
145
+ msg = l.elements['msg'].first.to_s.split("\n").first
146
+ "#{rev} #{DateTime.parse(date)} #{author} #{msg}"
147
+ end
136
148
  status.remote_commits = missing_revisions[1..-1]
137
149
  status.status =
138
150
  if missing_revisions.empty?
@@ -146,10 +158,13 @@ def status(package, only_local = false)
146
158
 
147
159
  # Helper method to run a SVN command on a package's working copy
148
160
  def run_svn(package, *args, &block)
149
- options = Hash.new
150
- if args.last.kind_of?(Hash)
151
- options = args.pop
152
- end
161
+ options =
162
+ if args.last.kind_of?(Hash)
163
+ args.pop
164
+ else
165
+ Hash.new
166
+ end
167
+
153
168
  options, other_options = Kernel.filter_options options,
154
169
  working_directory: package.importdir, retry: true
155
170
  options = options.merge(other_options)
@@ -173,7 +188,8 @@ def validate_importdir(package)
173
188
  # @raises [ConfigException] if the working copy is not a subversion
174
189
  # working copy
175
190
  def svn_info(package)
176
- old_lang, ENV['LC_ALL'] = ENV['LC_ALL'], 'C'
191
+ old_lang = ENV['LC_ALL']
192
+ ENV['LC_ALL'] = 'C'
177
193
  begin
178
194
  svninfo = run_svn(package, 'info')
179
195
  rescue SubcommandFailed => e
@@ -185,7 +201,7 @@ def svn_info(package)
185
201
  end
186
202
  end
187
203
 
188
- if !svninfo.grep(/is not a working copy/).empty?
204
+ unless svninfo.grep(/is not a working copy/).empty?
189
205
  raise ConfigException.new(package, 'import'),
190
206
  "#{package.importdir} does not appear to be a Subversion working copy"
191
207
  end
@@ -196,13 +212,16 @@ def svn_info(package)
196
212
 
197
213
  def update(package, options = Hash.new) # :nodoc:
198
214
  if options[:only_local]
199
- package.warn "%s: the svn importer does not support local updates, skipping"
215
+ package.warn "%s: the svn importer does not support local updates, "\
216
+ "skipping"
200
217
  return false
201
218
  end
202
219
 
203
220
  url = svn_url(package)
204
221
  if url != svnroot
205
- raise ConfigException.new(package, 'import'), "current checkout found at #{package.importdir} is from #{url}, was expecting #{svnroot}"
222
+ raise ConfigException.new(package, 'import'), "current checkout "\
223
+ "found at #{package.importdir} is from #{url}, "\
224
+ "was expecting #{svnroot}"
206
225
  end
207
226
 
208
227
  options_up = @options_up.dup
@@ -220,9 +239,10 @@ def update(package, options = Hash.new) # :nodoc:
220
239
  true
221
240
  end
222
241
 
223
- def checkout(package, options = Hash.new) # :nodoc:
224
- run_svn(package, 'co', "--non-interactive", *@options_co, svnroot, package.importdir,
225
- working_directory: nil)
242
+ def checkout(package, _options = Hash.new) # :nodoc:
243
+ run_svn(package, 'co', "--non-interactive", *@options_co,
244
+ svnroot, package.importdir,
245
+ working_directory: nil)
226
246
  end
227
247
  end
228
248
 
@@ -232,4 +252,3 @@ def self.svn(source, options = {})
232
252
  SVN.new(source, options)
233
253
  end
234
254
  end
235
-
@@ -5,595 +5,626 @@
5
5
  # various RCS into the package source directory. A list of patches to apply
6
6
  # after the import can be given in the +:patches+ option.
7
7
  module Autobuild
8
- class Importer
9
- # call-seq:
10
- # Autobuild::Importer.fallback { |package, importer| ... }
11
- #
12
- # If called, registers the given block as a fallback mechanism for failing
13
- # imports.
14
- #
15
- # Fallbacks are tried in reverse order with the failing importer object as
16
- # argument. The first valid importer object that has been returned will be
17
- # used instead.
18
- #
19
- # It is the responsibility of the fallback handler to make sure that it does
20
- # not do infinite recursions and stuff like that.
21
- def self.fallback(&block)
22
- @fallback_handlers.unshift(block)
23
- end
8
+ class Importer
9
+ # call-seq:
10
+ # Autobuild::Importer.fallback { |package, importer| ... }
11
+ #
12
+ # If called, registers the given block as a fallback mechanism for failing
13
+ # imports.
14
+ #
15
+ # Fallbacks are tried in reverse order with the failing importer object as
16
+ # argument. The first valid importer object that has been returned will be
17
+ # used instead.
18
+ #
19
+ # It is the responsibility of the fallback handler to make sure that it does
20
+ # not do infinite recursions and stuff like that.
21
+ def self.fallback(&block)
22
+ @fallback_handlers.unshift(block)
23
+ end
24
24
 
25
- class << self
26
- # The set of handlers registered by Importer.fallback
27
- attr_reader :fallback_handlers
28
- end
25
+ class << self
26
+ # The set of handlers registered by Importer.fallback
27
+ attr_reader :fallback_handlers
28
+ end
29
+
30
+ @fallback_handlers = Array.new
31
+
32
+ # Instances of the Importer::Status class represent the status of a current
33
+ # checkout w.r.t. the remote repository.
34
+ class Status
35
+ # Remote and local are at the same point
36
+ UP_TO_DATE = 0
37
+ # Local contains all data that remote has, but has new commits
38
+ ADVANCED = 1
39
+ # Next update will require a merge
40
+ NEEDS_MERGE = 2
41
+ # Next update will be simple (no merge)
42
+ SIMPLE_UPDATE = 3
43
+
44
+ # The update status
45
+ attr_accessor :status
46
+ # True if there is code in the working copy that is not committed
47
+ attr_accessor :uncommitted_code
48
+ # A list of messages describing differences between the local working
49
+ # copy and its expected state
50
+ #
51
+ # On git, it would for instance mention that currently checked out
52
+ # branch is not the one autoproj expects
53
+ #
54
+ # @return [Array<String>]
55
+ attr_reader :unexpected_working_copy_state
56
+
57
+ # An array of strings that represent commits that are in the remote
58
+ # repository and not in this one (would be merged by an update)
59
+ attr_accessor :remote_commits
60
+ # An array of strings that represent commits that are in the local
61
+ # repository and not in the remote one (would be pushed by an update)
62
+ attr_accessor :local_commits
63
+
64
+ def initialize(status = -1)
65
+ @status = status
66
+ @unexpected_working_copy_state = Array.new
67
+ @uncommitted_code = false
68
+ @remote_commits = Array.new
69
+ @local_commits = Array.new
70
+ end
71
+ end
29
72
 
30
- @fallback_handlers = Array.new
31
-
32
- # Instances of the Importer::Status class represent the status of a current
33
- # checkout w.r.t. the remote repository.
34
- class Status
35
- # Remote and local are at the same point
36
- UP_TO_DATE = 0
37
- # Local contains all data that remote has, but has new commits
38
- ADVANCED = 1
39
- # Next update will require a merge
40
- NEEDS_MERGE = 2
41
- # Next update will be simple (no merge)
42
- SIMPLE_UPDATE = 3
43
-
44
- # The update status
45
- attr_accessor :status
46
- # True if there is code in the working copy that is not committed
47
- attr_accessor :uncommitted_code
48
- # A list of messages describing differences between the local working
49
- # copy and its expected state
73
+ # The cache directories for the given importer type.
74
+ #
75
+ # This is used by some importers to save disk space and/or avoid downloading
76
+ # the same things over and over again
77
+ #
78
+ # The default global cache directory is initialized from the
79
+ # AUTOBUILD_CACHE_DIR environment variable. Per-importer cache directories
80
+ # can be overriden by setting AUTOBUILD_{TYPE}_CACHE_DIR (e.g.
81
+ # AUTOBUILD_GIT_CACHE_DIR)
82
+ #
83
+ # The following importers use caches:
84
+ # - the archive importer saves downloaded files in the cache. They are
85
+ # saved under an archives/ subdirectory of the default cache if set, or to
86
+ # the value of AUTOBUILD_ARCHIVES_CACHE_DIR
87
+ # - the git importer uses the cache directories as alternates for the git
88
+ # checkouts
50
89
  #
51
- # On git, it would for instance mention that currently checked out
52
- # branch is not the one autoproj expects
90
+ # @param [String] type the importer type. If set, it Given a root cache
91
+ # directory X, and importer specific cache is setup as a subdirectory of X
92
+ # with e.g. X/git or X/archives. The subdirectory name is defined by this
93
+ # argument
94
+ # @return [nil,Array<String>]
53
95
  #
54
- # @return [Array<String>]
55
- attr_reader :unexpected_working_copy_state
56
-
57
- # An array of strings that represent commits that are in the remote
58
- # repository and not in this one (would be merged by an update)
59
- attr_accessor :remote_commits
60
- # An array of strings that represent commits that are in the local
61
- # repository and not in the remote one (would be pushed by an update)
62
- attr_accessor :local_commits
63
-
64
- def initialize(status = -1)
65
- @status = status
66
- @unexpected_working_copy_state = Array.new
67
- @uncommitted_code = false
68
- @remote_commits = Array.new
69
- @local_commits = Array.new
96
+ # @see .set_cache_dirs .default_cache_dirs .default_cache_dirs=
97
+ def self.cache_dirs(type)
98
+ if @cache_dirs[type] || (env = ENV["AUTOBUILD_#{type.upcase}_CACHE_DIR"])
99
+ @cache_dirs[type] ||= env.split(":")
100
+ elsif (dirs = default_cache_dirs)
101
+ dirs.map { |d| File.join(d, type) }
102
+ end
70
103
  end
71
- end
72
104
 
73
- # The cache directories for the given importer type.
74
- #
75
- # This is used by some importers to save disk space and/or avoid downloading
76
- # the same things over and over again
77
- #
78
- # The default global cache directory is initialized from the
79
- # AUTOBUILD_CACHE_DIR environment variable. Per-importer cache directories
80
- # can be overriden by setting AUTOBUILD_{TYPE}_CACHE_DIR (e.g.
81
- # AUTOBUILD_GIT_CACHE_DIR)
82
- #
83
- # The following importers use caches:
84
- # - the archive importer saves downloaded files in the cache. They are
85
- # saved under an archives/ subdirectory of the default cache if set, or to
86
- # the value of AUTOBUILD_ARCHIVES_CACHE_DIR
87
- # - the git importer uses the cache directories as alternates for the git
88
- # checkouts
89
- #
90
- # @param [String] type the importer type. If set, it Given a root cache
91
- # directory X, and importer specific cache is setup as a subdirectory of X
92
- # with e.g. X/git or X/archives. The subdirectory name is defined by this
93
- # argument
94
- # @return [nil,Array<String>]
95
- #
96
- # @see .set_cache_dirs .default_cache_dirs .default_cache_dirs=
97
- def self.cache_dirs(type)
98
- if @cache_dirs[type] || (env = ENV["AUTOBUILD_#{type.upcase}_CACHE_DIR"])
99
- @cache_dirs[type] ||= env.split(":")
100
- elsif dirs = default_cache_dirs
101
- dirs.map { |d| File.join(d, type) }
105
+ # Returns the default cache directory if there is one
106
+ #
107
+ # @return [Array<String>,nil]
108
+ # @see .cache_dirs
109
+ def self.default_cache_dirs
110
+ [@default_cache_dirs] if @default_cache_dirs ||= ENV['AUTOBUILD_CACHE_DIR']
111
+ end
112
+
113
+ # Sets the cache directory for a given importer type
114
+ #
115
+ # @param [String] type the importer type
116
+ # @param [String] dir the cache directory
117
+ # @see .cache_dirs
118
+ def self.set_cache_dirs(type, *dirs)
119
+ @cache_dirs[type] = dirs
102
120
  end
103
- end
104
121
 
105
- # Returns the default cache directory if there is one
106
- #
107
- # @return [Array<String>,nil]
108
- # @see .cache_dirs
109
- def self.default_cache_dirs
110
- if @default_cache_dirs ||= ENV['AUTOBUILD_CACHE_DIR']
111
- [@default_cache_dirs]
122
+ # Sets the default cache directory
123
+ #
124
+ # @param [Array<String>,String] the directories
125
+ # @see .cache_dirs
126
+ def self.default_cache_dirs=(dirs)
127
+ @default_cache_dirs = Array(dirs)
112
128
  end
113
- end
114
129
 
115
- # Sets the cache directory for a given importer type
116
- #
117
- # @param [String] type the importer type
118
- # @param [String] dir the cache directory
119
- # @see .cache_dirs
120
- def self.set_cache_dirs(type, *dirs)
121
- @cache_dirs[type] = dirs
122
- end
130
+ # Unset all cache directories
131
+ def self.unset_cache_dirs
132
+ @cache_dirs = Hash.new
133
+ @default_cache_dirs = nil
134
+ end
123
135
 
124
- # Sets the default cache directory
125
- #
126
- # @param [Array<String>,String] the directories
127
- # @see .cache_dirs
128
- def self.default_cache_dirs=(dirs)
129
- @default_cache_dirs = Array(dirs)
130
- end
136
+ unset_cache_dirs
131
137
 
132
- # Unset all cache directories
133
- def self.unset_cache_dirs
134
- @cache_dirs = Hash.new
135
- @default_cache_dirs = nil
136
- end
138
+ # @return [Hash] the original option hash as given to #initialize
139
+ attr_reader :options
137
140
 
138
- unset_cache_dirs
139
-
140
- # @return [Hash] the original option hash as given to #initialize
141
- attr_reader :options
142
-
143
- # Creates a new Importer object. The options known to Importer are:
144
- # [:patches] a list of patch to apply after import
145
- #
146
- # More options are specific to each importer type.
147
- def initialize(options)
148
- @options = options.dup
149
- @options[:retry_count] = Integer(@options[:retry_count] || 0)
150
- @repository_id = options[:repository_id] || "#{self.class.name}:#{object_id}"
151
- @interactive = options[:interactive]
152
- @source_id = options[:source_id] || @repository_id
153
- @post_hooks = Array.new
154
- end
141
+ # Creates a new Importer object. The options known to Importer are:
142
+ # [:patches] a list of patch to apply after import
143
+ #
144
+ # More options are specific to each importer type.
145
+ def initialize(options)
146
+ @options = options.dup
147
+ @options[:retry_count] = Integer(@options[:retry_count] || 0)
148
+ @repository_id = options[:repository_id] || "#{self.class.name}:#{object_id}"
149
+ @interactive = options[:interactive]
150
+ @source_id = options[:source_id] || @repository_id
151
+ @post_hooks = Array.new
152
+ end
155
153
 
156
- # Returns a string that identifies the remote repository uniquely
157
- #
158
- # This can be used to check whether two importers are pointing to the same
159
- # repository, regardless of e.g. the access protocol used. For instance,
160
- # two git importers that point to the same repository but different branches
161
- # would have the same repository_id but different source_id
162
- #
163
- # @return [String]
164
- # @see source_id
165
- attr_reader :repository_id
166
-
167
- # Returns a string that identifies the remote source uniquely
168
- #
169
- # This can be used to check whether two importers are pointing to the same
170
- # code base inside the same repository. For instance, two git importers that
171
- # point to the same repository but different branches would have the same
172
- # repository_id but different source_id
173
- #
174
- # @return [String]
175
- # @see repository_id
176
- attr_reader :source_id
177
-
178
- # Whether this importer will need interaction with the user, for instance to
179
- # give credentials
180
- def interactive?; !!@interactive end
181
-
182
- # Changes whether this importer is interactive or not
183
- def interactive=(value)
184
- @interactive = !!value
185
- end
154
+ # Returns a string that identifies the remote repository uniquely
155
+ #
156
+ # This can be used to check whether two importers are pointing to the same
157
+ # repository, regardless of e.g. the access protocol used. For instance,
158
+ # two git importers that point to the same repository but different branches
159
+ # would have the same repository_id but different source_id
160
+ #
161
+ # @return [String]
162
+ # @see source_id
163
+ attr_reader :repository_id
186
164
 
187
- # The number of times update / checkout should be retried before giving up.
188
- # The default is 0 (do not retry)
189
- #
190
- # Set either with #retry_count= or by setting the :retry_count option when
191
- # constructing this importer
192
- def retry_count
193
- @options[:retry_count] || 0
194
- end
165
+ # Returns a string that identifies the remote source uniquely
166
+ #
167
+ # This can be used to check whether two importers are pointing to the same
168
+ # code base inside the same repository. For instance, two git importers that
169
+ # point to the same repository but different branches would have the same
170
+ # repository_id but different source_id
171
+ #
172
+ # @return [String]
173
+ # @see repository_id
174
+ attr_reader :source_id
175
+
176
+ # Whether this importer will need interaction with the user, for instance to
177
+ # give credentials
178
+ def interactive?
179
+ @interactive
180
+ end
195
181
 
196
- # Sets the number of times update / checkout should be retried before giving
197
- # up. 0 (the default) disables retrying.
198
- #
199
- # See also #retry_count
200
- def retry_count=(count)
201
- @options[:retry_count] = Integer(count)
202
- end
182
+ # Changes whether this importer is interactive or not
183
+ attr_writer :interactive
203
184
 
204
- def patches
205
- patches =
206
- if @options[:patches].respond_to?(:to_ary)
207
- @options[:patches]
208
- elsif !@options[:patches]
209
- []
210
- else
211
- [[@options[:patches], 0]]
185
+ # The number of times update / checkout should be retried before giving up.
186
+ # The default is 0 (do not retry)
187
+ #
188
+ # Set either with #retry_count= or by setting the :retry_count option when
189
+ # constructing this importer
190
+ def retry_count
191
+ @options[:retry_count] || 0
192
+ end
193
+
194
+ # Returns a unique hash representing the state of the imported package
195
+ # as a whole unit, including its dependencies and patches
196
+ def fingerprint(package)
197
+ vcs_fingerprint_string = vcs_fingerprint(package)
198
+ return unless vcs_fingerprint_string
199
+
200
+ patches_fingerprint_string = patches_fingerprint(package)
201
+ if patches_fingerprint_string
202
+ Digest::SHA1.hexdigest(vcs_fingerprint_string + patches_fingerprint_string)
203
+ elsif patches.empty?
204
+ vcs_fingerprint_string
212
205
  end
206
+ end
207
+
208
+ # basic fingerprint of the package and its dependencies
209
+ def vcs_fingerprint(package)
210
+ #each importer type should implement its own
211
+ Autoproj.warn "Fingerprint in #{package.name} has not been implemented for this type of packages, results should be discarded"
212
+ return nil
213
+ end
214
+
215
+ # fingerprint for patches associated to this package
216
+ def patches_fingerprint(package)
217
+ cur_patches = currently_applied_patches(package)
218
+ cur_patches.map(&:shift) #leave only level and source information
219
+ Digest::SHA1.hexdigest(cur_patches.sort.flatten.join("")) if !patches.empty? && cur_patches
220
+ end
221
+
222
+ # Sets the number of times update / checkout should be retried before giving
223
+ # up. 0 (the default) disables retrying.
224
+ #
225
+ # See also #retry_count
226
+ def retry_count=(count)
227
+ @options[:retry_count] = Integer(count)
228
+ end
229
+
230
+ def patches
231
+ patches =
232
+ if @options[:patches].respond_to?(:to_ary)
233
+ @options[:patches]
234
+ elsif !@options[:patches]
235
+ []
236
+ else
237
+ [[@options[:patches], 0]]
238
+ end
213
239
 
214
- if patches.size == 2 && patches[0].respond_to?(:to_str) && patches[1].respond_to?(:to_int)
215
- patches = [patches]
216
- else
217
- patches = patches.map do |obj|
240
+ single_patch = (patches.size == 2 &&
241
+ patches[0].respond_to?(:to_str) &&
242
+ patches[1].respond_to?(:to_int))
243
+
244
+ patches = [patches] if single_patch
245
+ patches.map do |obj|
218
246
  if obj.respond_to?(:to_str)
219
- [obj, 0]
247
+ path = obj
248
+ level = 0
220
249
  elsif obj.respond_to?(:to_ary)
221
- obj
250
+ path, level = obj
222
251
  else
223
252
  raise Arguments, "wrong patch specification #{obj.inspect}"
224
- obj
225
253
  end
254
+ [path, level, File.read(path)]
226
255
  end
227
256
  end
228
- patches.map do |path, level|
229
- [path, level, File.read(path)]
230
- end
231
- end
232
257
 
233
- def update_retry_count(original_error, retry_count)
234
- if !original_error.respond_to?(:retry?) ||
235
- !original_error.retry?
236
- return
258
+ def update_retry_count(original_error, retry_count)
259
+ return if !original_error.respond_to?(:retry?) || !original_error.retry?
260
+
261
+ retry_count += 1
262
+ retry_count if retry_count <= self.retry_count
237
263
  end
238
264
 
239
- retry_count += 1
240
- if retry_count <= self.retry_count
241
- retry_count
265
+ # A list of hooks that are called after a successful checkout or update
266
+ #
267
+ # They are added either at the instance level with {#add_post_hook} or
268
+ # globally for all importers of a given type with {Importer.add_post_hook}
269
+ attr_reader :post_hooks
270
+
271
+ Hook = Struct.new :always, :callback
272
+
273
+ # Define a post-import hook for all instances of this class
274
+ #
275
+ # @yieldparam [Importer] importer the importer that finished
276
+ # @yieldparam [Package] package the package we're acting on
277
+ # @see Importer#add_post_hook
278
+ def self.add_post_hook(always: false, &hook)
279
+ @post_hooks ||= Array.new
280
+ @post_hooks << Hook.new(always, hook)
281
+ nil
242
282
  end
243
- end
244
283
 
245
- # A list of hooks that are called after a successful checkout or update
246
- #
247
- # They are added either at the instance level with {#add_post_hook} or
248
- # globally for all importers of a given type with {Importer.add_post_hook}
249
- attr_reader :post_hooks
250
-
251
- Hook = Struct.new :always, :callback
252
-
253
- # Define a post-import hook for all instances of this class
254
- #
255
- # @yieldparam [Importer] importer the importer that finished
256
- # @yieldparam [Package] package the package we're acting on
257
- # @see Importer#add_post_hook
258
- def self.add_post_hook(always: false, &hook)
259
- @post_hooks ||= Array.new
260
- @post_hooks << Hook.new(always, hook)
261
- nil
262
- end
284
+ # Enumerate the post-import hooks defined for all instances of this class
285
+ def self.each_post_hook(error: false)
286
+ return enum_for(__method__) unless block_given?
263
287
 
264
- # Enumerate the post-import hooks defined for all instances of this class
265
- def self.each_post_hook(error: false)
266
- return enum_for(__method__) if !block_given?
288
+ (@post_hooks ||= Array.new).each do |hook|
289
+ yield(hook.callback) if hook.always || !error
290
+ end
291
+ end
267
292
 
268
- (@post_hooks ||= Array.new).each do |hook|
269
- if hook.always || !error
270
- yield(hook.callback)
293
+ # @api private
294
+ #
295
+ # Call the post-import hooks added with {#add_post_hook}
296
+ def execute_post_hooks(package, error: false)
297
+ each_post_hook(error: error) do |block|
298
+ block.call(self, package)
271
299
  end
272
300
  end
273
- end
274
301
 
275
- # @api private
276
- #
277
- # Call the post-import hooks added with {#add_post_hook}
278
- def execute_post_hooks(package, error: false)
279
- each_post_hook(error: error) do |block|
280
- block.call(self, package)
302
+ # Add a block that should be called when the import has successfully
303
+ # finished
304
+ #
305
+ # @yieldparam [Importer] importer the importer that finished
306
+ # @yieldparam [Package] package the package we're acting on
307
+ # @see Importer.add_post_hook
308
+ def add_post_hook(always: false, &hook)
309
+ post_hooks << Hook.new(always, hook)
281
310
  end
282
- end
283
311
 
284
- # Add a block that should be called when the import has successfully
285
- # finished
286
- #
287
- # @yieldparam [Importer] importer the importer that finished
288
- # @yieldparam [Package] package the package we're acting on
289
- # @see Importer.add_post_hook
290
- def add_post_hook(always: false, &hook)
291
- post_hooks << Hook.new(always, hook)
292
- end
312
+ # Enumerate the post-import hooks for this importer
313
+ def each_post_hook(error: false)
314
+ return enum_for(__method__, error: false) unless block_given?
293
315
 
294
- # Enumerate the post-import hooks for this importer
295
- def each_post_hook(error: false, &hook)
296
- return enum_for(__method__) if !block_given?
316
+ self.class.each_post_hook(error: error) do |callback|
317
+ yield(callback)
318
+ end
297
319
 
298
- self.class.each_post_hook(error: error, &hook)
299
- post_hooks.each do |hook|
300
- if hook.always || !error
301
- yield(hook.callback)
320
+ post_hooks.each do |hook|
321
+ yield(hook.callback) if hook.always || !error
302
322
  end
303
323
  end
304
- end
305
324
 
306
- def perform_update(package,only_local=false)
307
- cur_patches = currently_applied_patches(package)
308
- needed_patches = self.patches
309
- if cur_patches.map(&:last) != needed_patches.map(&:last)
310
- patch(package, [])
311
- end
325
+ def perform_update(package, only_local = false)
326
+ cur_patches = currently_applied_patches(package)
327
+ needed_patches = patches
328
+ patch_changed = cur_patches.map(&:last) != needed_patches.map(&:last)
329
+ patch(package, []) if patch_changed
312
330
 
313
- last_error = nil
314
- retry_count = 0
315
- package.progress_start "updating %s"
316
- begin
331
+ last_error = nil
332
+ retry_count = 0
333
+ package.progress_start "updating %s"
317
334
  begin
318
- did_update = update(package,only_local)
319
- execute_post_hooks(package, error: false)
320
- rescue ::Exception
321
- execute_post_hooks(package, error: true)
322
- raise
323
- end
324
-
325
- message = if did_update == false
326
- Autobuild.color('already up-to-date', :green)
327
- else
328
- Autobuild.color('updated', :yellow)
329
- end
330
-
331
- did_update
332
- rescue Interrupt
333
- message = Autobuild.color('interrupted', :red)
334
- if last_error
335
- raise last_error
336
- else raise
337
- end
338
- rescue ::Exception => original_error
339
- message = Autobuild.color('update failed', :red)
340
- last_error = original_error
341
- # If the package is patched, it might be that the update
342
- # failed because we needed to unpatch first. Try it out
343
- #
344
- # This assumes that importing data with conflict will
345
- # make the import fail, but not make the patch
346
- # un-appliable. Importers that do not follow this rule
347
- # will have to unpatch by themselves.
348
- cur_patches = currently_applied_patches(package)
349
- if !cur_patches.empty?
350
- package.progress_done
351
- package.message "update failed and some patches are applied, removing all patches and retrying"
352
335
  begin
353
- patch(package, [])
354
- return perform_update(package,only_local)
355
- rescue Interrupt
356
- raise
336
+ did_update = update(package, only_local)
337
+ execute_post_hooks(package, error: false)
357
338
  rescue ::Exception
358
- raise original_error
339
+ execute_post_hooks(package, error: true)
340
+ raise
359
341
  end
360
- end
361
-
362
- retry_count = update_retry_count(original_error, retry_count)
363
- raise if !retry_count
364
- package.message "update failed in #{package.importdir}, retrying (#{retry_count}/#{self.retry_count})"
365
- retry
366
- ensure
367
- package.progress_done "#{message} %s"
368
- end
369
342
 
370
- patch(package)
371
- package.updated = true
372
- did_update
373
- rescue Autobuild::Exception => e
374
- fallback(e, package, :import, package)
375
- end
343
+ message = if did_update == false
344
+ Autobuild.color('already up-to-date', :green)
345
+ else
346
+ Autobuild.color('updated', :yellow)
347
+ end
376
348
 
377
- def perform_checkout(package, options = Hash.new)
378
- last_error = nil
379
- package.progress_start "checking out %s", :done_message => 'checked out %s' do
380
- retry_count = 0
381
- begin
382
- checkout(package, options)
383
- execute_post_hooks(package)
349
+ did_update
384
350
  rescue Interrupt
385
- if last_error then raise last_error
351
+ message = Autobuild.color('interrupted', :red)
352
+ if last_error
353
+ raise last_error
386
354
  else raise
387
355
  end
388
356
  rescue ::Exception => original_error
357
+ message = Autobuild.color('update failed', :red)
389
358
  last_error = original_error
390
- retry_count = update_retry_count(original_error, retry_count)
391
- if !retry_count
392
- raise
359
+ # If the package is patched, it might be that the update
360
+ # failed because we needed to unpatch first. Try it out
361
+ #
362
+ # This assumes that importing data with conflict will
363
+ # make the import fail, but not make the patch
364
+ # un-appliable. Importers that do not follow this rule
365
+ # will have to unpatch by themselves.
366
+ cur_patches = currently_applied_patches(package)
367
+ unless cur_patches.empty?
368
+ package.progress_done
369
+ package.message "update failed and some patches are applied, "\
370
+ "removing all patches and retrying"
371
+ begin
372
+ patch(package, [])
373
+ return perform_update(package, only_local)
374
+ rescue Interrupt
375
+ raise
376
+ rescue ::Exception
377
+ raise original_error
378
+ end
393
379
  end
394
- package.message "checkout of %s failed, deleting the source directory #{package.importdir} and retrying (#{retry_count}/#{self.retry_count})"
395
- FileUtils.rm_rf package.importdir
380
+
381
+ retry_count = update_retry_count(original_error, retry_count)
382
+ raise unless retry_count
383
+
384
+ package.message "update failed in #{package.importdir}, "\
385
+ "retrying (#{retry_count}/#{self.retry_count})"
396
386
  retry
387
+ ensure
388
+ package.progress_done "#{message} %s"
397
389
  end
390
+
391
+ patch(package)
392
+ package.updated = true
393
+ did_update
394
+ rescue Autobuild::Exception => e
395
+ fallback(e, package, :import, package)
398
396
  end
399
397
 
400
- patch(package)
401
- package.updated = true
402
- rescue Interrupt
403
- raise
404
- rescue ::Exception
405
- package.message "checkout of %s failed, deleting the source directory #{package.importdir}"
406
- FileUtils.rm_rf package.importdir
407
- raise
408
- rescue Autobuild::Exception => e
409
- FileUtils.rm_rf package.importdir
410
- fallback(e, package, :import, package)
411
- end
398
+ def perform_checkout(package, options = Hash.new)
399
+ last_error = nil
400
+ package.progress_start "checking out %s", :done_message => 'checked out %s' do
401
+ retry_count = 0
402
+ begin
403
+ checkout(package, options)
404
+ execute_post_hooks(package)
405
+ rescue Interrupt
406
+ if last_error then raise last_error
407
+ else raise
408
+ end
409
+ rescue ::Exception => original_error
410
+ last_error = original_error
411
+ retry_count = update_retry_count(original_error, retry_count)
412
+ raise unless retry_count
413
+
414
+ package.message "checkout of %s failed, "\
415
+ "deleting the source directory #{package.importdir} "\
416
+ "and retrying (#{retry_count}/#{self.retry_count})"
417
+ FileUtils.rm_rf package.importdir
418
+ retry
419
+ end
420
+ end
412
421
 
413
- # Imports the given package
414
- #
415
- # The importer will checkout or update code in package.importdir. No update
416
- # will be done if {update?} returns false.
417
- #
418
- # @raises ConfigException if package.importdir exists and is not a directory
419
- #
420
- # @option options [Boolean] :checkout_only (false) if true, the importer
421
- # will not update an already checked-out package.
422
- # @option options [Boolean] :only_local (false) if true, will only perform
423
- # actions that do not require network access. Importers that do not
424
- # support this mode will simply do nothing
425
- # @option options [Boolean] :reset (false) if true, the importer's
426
- # configuration is interpreted as a hard state in which it should put the
427
- # working copy. Otherwise, it tries to update the local repository with
428
- # the remote information. For instance, a git importer for which a commit
429
- # ID is given will, in this mode, reset the repository to the requested ID
430
- # (if that does not involve losing commits). Otherwise, it will only
431
- # ensure that the requested commit ID is present in the current HEAD.
432
- def import(package, options = Hash.new)
433
- # Backward compatibility
434
- if !options.kind_of?(Hash)
435
- options = !!options
436
- Autoproj.warn "calling #import with a boolean as second argument is deprecated, switch to the named argument interface instead"
437
- Autoproj.warn " e.g. call import(package, only_local: #{options})"
438
- Autoproj.warn " #{caller(1).first}"
439
- options = Hash[only_local: !!options]
440
- end
441
-
442
- options = Kernel.validate_options options,
443
- only_local: false,
444
- reset: false,
445
- checkout_only: false,
446
- ignore_errors: false,
447
- allow_interactive: true
448
- ignore_errors = options.delete(:ignore_errors)
449
-
450
- importdir = package.importdir
451
- if File.directory?(importdir)
452
- package.isolate_errors(mark_as_failed: false, ignore_errors: ignore_errors) do
453
- if !options[:checkout_only] && package.update?
454
- perform_update(package, options)
455
- else
456
- if Autobuild.verbose
422
+ patch(package)
423
+ package.updated = true
424
+ rescue Interrupt
425
+ raise
426
+ rescue ::Exception # rubocop:disable Lint/ShadowedException
427
+ package.message "checkout of %s failed, "\
428
+ "deleting the source directory #{package.importdir}"
429
+ FileUtils.rm_rf package.importdir
430
+ raise
431
+ rescue Autobuild::Exception => e
432
+ FileUtils.rm_rf package.importdir
433
+ fallback(e, package, :import, package)
434
+ end
435
+
436
+ # Imports the given package
437
+ #
438
+ # The importer will checkout or update code in package.importdir. No update
439
+ # will be done if {update?} returns false.
440
+ #
441
+ # @raises ConfigException if package.importdir exists and is not a directory
442
+ #
443
+ # @option options [Boolean] :checkout_only (false) if true, the importer
444
+ # will not update an already checked-out package.
445
+ # @option options [Boolean] :only_local (false) if true, will only perform
446
+ # actions that do not require network access. Importers that do not
447
+ # support this mode will simply do nothing
448
+ # @option options [Boolean] :reset (false) if true, the importer's
449
+ # configuration is interpreted as a hard state in which it should put the
450
+ # working copy. Otherwise, it tries to update the local repository with
451
+ # the remote information. For instance, a git importer for which a commit
452
+ # ID is given will, in this mode, reset the repository to the requested ID
453
+ # (if that does not involve losing commits). Otherwise, it will only
454
+ # ensure that the requested commit ID is present in the current HEAD.
455
+ def import(package, options = Hash.new)
456
+ # Backward compatibility
457
+ unless options.kind_of?(Hash)
458
+ options = options
459
+ Autoproj.warn "calling #import with a boolean as second argument "\
460
+ "is deprecated, switch to the named argument interface instead"
461
+ Autoproj.warn " e.g. call import(package, only_local: #{options})"
462
+ Autoproj.warn " #{caller(1..1).first}"
463
+ options = Hash[only_local: options]
464
+ end
465
+
466
+ options = Kernel.validate_options options,
467
+ only_local: false,
468
+ reset: false,
469
+ checkout_only: false,
470
+ ignore_errors: false,
471
+ allow_interactive: true
472
+ ignore_errors = options.delete(:ignore_errors)
473
+
474
+ importdir = package.importdir
475
+ if File.directory?(importdir)
476
+ package.isolate_errors(mark_as_failed: false,
477
+ ignore_errors: ignore_errors) do
478
+ if !options[:checkout_only] && package.update?
479
+ perform_update(package, options)
480
+ elsif Autobuild.verbose
457
481
  package.message "%s: not updating"
458
482
  end
459
483
  end
460
- end
461
484
 
462
- elsif File.exist?(importdir)
463
- raise ConfigException.new(package, 'import'), "#{importdir} exists but is not a directory"
464
- else
465
- package.isolate_errors(mark_as_failed: true, ignore_errors: ignore_errors) do
466
- perform_checkout(package, allow_interactive: options[:allow_interactive])
467
- true
485
+ elsif File.exist?(importdir)
486
+ raise ConfigException.new(package, 'import'),
487
+ "#{importdir} exists but is not a directory"
488
+ else
489
+ package.isolate_errors(mark_as_failed: true,
490
+ ignore_errors: ignore_errors) do
491
+ perform_checkout(package,
492
+ allow_interactive: options[:allow_interactive])
493
+ true
494
+ end
468
495
  end
469
496
  end
470
- end
471
497
 
472
- # Tries to find a fallback importer because of the given error.
473
- def fallback(error, package, *args, &block)
474
- Importer.fallback_handlers.each do |handler|
475
- fallback_importer = handler.call(package, self)
476
- if fallback_importer.kind_of?(Importer)
477
- begin
478
- return fallback_importer.send(*args, &block)
479
- rescue Exception
480
- raise error
498
+ # Tries to find a fallback importer because of the given error.
499
+ def fallback(error, package, *args, &block)
500
+ Importer.fallback_handlers.each do |handler|
501
+ fallback_importer = handler.call(package, self)
502
+ if fallback_importer.kind_of?(Importer)
503
+ begin
504
+ return fallback_importer.send(*args, &block)
505
+ rescue Exception
506
+ raise error
507
+ end
481
508
  end
482
509
  end
510
+ raise error
483
511
  end
484
- raise error
485
- end
486
512
 
487
- def patchdir(package)
488
- File.join(package.importdir, ".autobuild-patches")
489
- end
490
-
491
- # We assume that package.importdir already exists (checkout is supposed to
492
- # have been called)
493
- def patchlist(package)
494
- File.join(patchdir(package), "list")
495
- end
513
+ def patchdir(package)
514
+ File.join(package.importdir, ".autobuild-patches")
515
+ end
496
516
 
497
- def call_patch(package, reverse, file, patch_level)
498
- package.run(:patch, Autobuild.tool('patch'),
499
- "-p#{patch_level}", (reverse ? '-R' : nil), '--forward', input: file,
500
- working_directory: package.importdir)
501
- end
517
+ # We assume that package.importdir already exists (checkout is supposed to
518
+ # have been called)
519
+ def patchlist(package)
520
+ File.join(patchdir(package), "list")
521
+ end
502
522
 
503
- def apply(package, path, patch_level = 0); call_patch(package, false, path, patch_level) end
504
- def unapply(package, path, patch_level = 0); call_patch(package, true, path, patch_level) end
523
+ def call_patch(package, reverse, file, patch_level)
524
+ package.run(:patch, Autobuild.tool('patch'),
525
+ "-p#{patch_level}", (reverse ? '-R' : nil), '--forward',
526
+ input: file, working_directory: package.importdir)
527
+ end
505
528
 
506
- def parse_patch_list(package, patches_file)
507
- File.readlines(patches_file).map do |line|
508
- line = line.rstrip
509
- if line =~ /^(.*)\s+(\d+)$/
510
- path = File.expand_path($1, package.srcdir)
511
- level = Integer($2)
512
- else
513
- path = File.expand_path(line, package.srcdir)
514
- level = 0
515
- end
516
- [path, level, File.read(path)]
529
+ def apply(package, path, patch_level = 0)
530
+ call_patch(package, false, path, patch_level)
517
531
  end
518
- end
519
532
 
520
- def currently_applied_patches(package)
521
- patches_file = patchlist(package)
522
- if File.exist?(patches_file)
523
- return parse_patch_list(package, patches_file)
533
+ def unapply(package, path, patch_level = 0)
534
+ call_patch(package, true, path, patch_level)
524
535
  end
525
536
 
526
- patches_file = File.join(package.importdir, "patches-autobuild-stamp")
527
- if File.exist?(patches_file)
528
- cur_patches = parse_patch_list(package, patches_file)
529
- save_patch_state(package, cur_patches)
530
- FileUtils.rm_f patches_file
531
- return currently_applied_patches(package)
537
+ def parse_patch_list(package, patches_file)
538
+ File.readlines(patches_file).map do |line|
539
+ line = line.rstrip
540
+ if line =~ /^(.*)\s+(\d+)$/
541
+ path = File.expand_path($1, package.srcdir)
542
+ level = Integer($2)
543
+ else
544
+ path = File.expand_path(line, package.srcdir)
545
+ level = 0
546
+ end
547
+ [path, level, File.read(path)]
548
+ end
532
549
  end
533
550
 
534
- return Array.new
535
- end
551
+ def currently_applied_patches(package)
552
+ patches_file = patchlist(package)
553
+ return parse_patch_list(package, patches_file) if File.exist?(patches_file)
536
554
 
537
- def patch(package, patches = self.patches)
538
- # Get the list of already applied patches
539
- cur_patches = currently_applied_patches(package)
540
-
541
- cur_patches_state = cur_patches.map { |_, level, content| [level, content] }
542
- patches_state = patches.map { |_, level, content| [level, content] }
543
- if cur_patches_state == patches_state
544
- return false
545
- end
546
-
547
- # Do not be smart, remove all already applied patches
548
- # and then apply the new ones
549
- begin
550
- apply_count = (patches - cur_patches).size
551
- unapply_count = (cur_patches - patches).size
552
- if apply_count > 0 && unapply_count > 0
553
- package.message "patching %s: applying #{apply_count} and unapplying #{unapply_count} patch(es)"
554
- elsif apply_count > 0
555
- package.message "patching %s: applying #{apply_count} patch(es)"
556
- elsif unapply_count > 0
557
- package.message "patching %s: unapplying #{unapply_count} patch(es)"
555
+ patches_file = File.join(package.importdir, "patches-autobuild-stamp")
556
+ if File.exist?(patches_file)
557
+ cur_patches = parse_patch_list(package, patches_file)
558
+ save_patch_state(package, cur_patches)
559
+ FileUtils.rm_f patches_file
560
+ return currently_applied_patches(package)
558
561
  end
559
562
 
560
- while p = cur_patches.last
561
- p, level, _ = *p
562
- unapply(package, p, level)
563
- cur_patches.pop
564
- end
563
+ []
564
+ end
565
565
 
566
- patches.to_a.each do |new_patch, new_patch_level, content|
567
- apply(package, new_patch, new_patch_level)
568
- cur_patches << [new_patch, new_patch_level, content]
566
+ def patch(package, patches = self.patches)
567
+ # Get the list of already applied patches
568
+ cur_patches = currently_applied_patches(package)
569
+
570
+ cur_patches_state = cur_patches.map { |_, level, content| [level, content] }
571
+ patches_state = patches.map { |_, level, content| [level, content] }
572
+ return false if cur_patches_state == patches_state
573
+
574
+ # Do not be smart, remove all already applied patches
575
+ # and then apply the new ones
576
+ begin
577
+ apply_count = (patches - cur_patches).size
578
+ unapply_count = (cur_patches - patches).size
579
+ if apply_count > 0 && unapply_count > 0
580
+ package.message "patching %s: applying #{apply_count} and "\
581
+ "unapplying #{unapply_count} patch(es)"
582
+ elsif apply_count > 0
583
+ package.message "patching %s: applying #{apply_count} patch(es)"
584
+ elsif unapply_count > 0
585
+ package.message "patching %s: unapplying #{unapply_count} patch(es)"
586
+ end
587
+
588
+ while (p = cur_patches.last)
589
+ p, level, = *p
590
+ unapply(package, p, level)
591
+ cur_patches.pop
592
+ end
593
+
594
+ patches.to_a.each do |new_patch, new_patch_level, content|
595
+ apply(package, new_patch, new_patch_level)
596
+ cur_patches << [new_patch, new_patch_level, content]
597
+ end
598
+ ensure
599
+ save_patch_state(package, cur_patches)
569
600
  end
570
- ensure
571
- save_patch_state(package, cur_patches)
601
+
602
+ true
572
603
  end
573
604
 
574
- return true
575
- end
576
-
577
- def save_patch_state(package, cur_patches)
578
- patch_dir = patchdir(package)
579
- FileUtils.mkdir_p patch_dir
580
- cur_patches = cur_patches.each_with_index.map do |(path, level, content), idx|
581
- path = File.join(patch_dir, idx.to_s)
582
- File.open(path, 'w') do |patch_io|
583
- patch_io.write content
605
+ def save_patch_state(package, cur_patches)
606
+ patch_dir = patchdir(package)
607
+ FileUtils.mkdir_p patch_dir
608
+ cur_patches = cur_patches.each_with_index.
609
+ map do |(_path, level, content), idx|
610
+ path = File.join(patch_dir, idx.to_s)
611
+ File.open(path, 'w') do |patch_io|
612
+ patch_io.write content
613
+ end
614
+ [path, level]
615
+ end
616
+ File.open(patchlist(package), 'w') do |f|
617
+ patch_state = cur_patches.map do |path, level|
618
+ path = Pathname.new(path).
619
+ relative_path_from(Pathname.new(package.srcdir)).to_s
620
+ "#{path} #{level}"
621
+ end
622
+ f.write(patch_state.join("\n"))
584
623
  end
585
- [path, level]
586
624
  end
587
- File.open(patchlist(package), 'w') do |f|
588
- patch_state = cur_patches.map do |path, level|
589
- path = Pathname.new(path).relative_path_from( Pathname.new(package.srcdir) ).to_s
590
- "#{path} #{level}"
591
- end
592
- f.write(patch_state.join("\n"))
625
+
626
+ def supports_relocation?
627
+ false
593
628
  end
594
629
  end
595
-
596
- def supports_relocation?; false end
597
630
  end
598
- end
599
-