autobuild 1.17.0 → 1.18.0

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