autobuild 1.17.0 → 1.21.0

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