fpm-fry 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,26 +1,28 @@
1
1
  require 'fpm/fry/source'
2
- require 'fpm/fry/source/package'
2
+ require 'fpm/fry/source/archive'
3
3
  require 'fpm/fry/source/dir'
4
4
  require 'fpm/fry/source/patched'
5
5
  require 'fpm/fry/source/git'
6
6
  require 'fpm/fry/plugin'
7
- require 'fpm/fry/os_db'
7
+ require 'fpm/fry/exec'
8
8
  require 'shellwords'
9
9
  require 'cabin'
10
- require 'open3'
11
10
  module FPM; module Fry
12
11
 
12
+ # A FPM::Fry::Recipe contains all information needed to build a package.
13
+ #
14
+ # It is usually created by {FPM::Fry::Recipe::Builder}.
13
15
  class Recipe
14
16
 
17
+ # A FPM::Fry::Recipe::Step is a named build step.
18
+ #
19
+ # @see FPM::Fry::Recipe#steps
15
20
  class Step < Struct.new(:name, :value)
16
21
  def to_s
17
22
  value.to_s
18
23
  end
19
24
  end
20
25
 
21
- class DuplicateDependency < ArgumentError
22
- end
23
-
24
26
  class PackageRecipe
25
27
  attr_accessor :name,
26
28
  :iteration,
@@ -60,6 +62,10 @@ module FPM; module Fry
60
62
 
61
63
  alias dependencies depends
62
64
 
65
+ # Applies settings to output package
66
+ # @param [FPM::Package] package
67
+ # @return [FPM::Package] package
68
+ # @api private
63
69
  def apply_output( package )
64
70
  package.name = name
65
71
  package.version = version
@@ -87,8 +93,11 @@ module FPM; module Fry
87
93
 
88
94
  alias apply apply_output
89
95
 
96
+ # @api private
90
97
  SYNTAX_CHECK_SHELLS = ['/bin/sh','/bin/bash', '/bin/dash']
91
98
 
99
+ # Lints the settings for some common problems
100
+ # @return [Array<String>] problems
92
101
  def lint
93
102
  problems = []
94
103
  problems << "Name is empty." if name.to_s == ''
@@ -110,28 +119,37 @@ module FPM; module Fry
110
119
  problems << "#{type} script doesn't have a valid command in shebang"
111
120
  end
112
121
  if SYNTAX_CHECK_SHELLS.include? args[0]
113
- sin, sout, serr, th = Open3.popen3(args[0],'-n')
114
- sin.write(s)
115
- sin.close
116
- if th.value.exitstatus != 0
117
- problems << "#{type} script is not valid #{args[0]} code: #{serr.read.chomp}"
122
+ begin
123
+ Exec::exec(args[0],'-n', stdin_data: s)
124
+ rescue Exec::Failed => e
125
+ problems << "#{type} script is not valid #{args[0]} code: #{e.stderr.chomp}"
118
126
  end
119
- serr.close
120
- sout.close
121
127
  end
122
128
  end
123
129
  return problems
124
130
  end
125
131
  end
126
132
 
127
- attr_accessor :source,
128
- :build_mounts,
129
- :apt_setup,
130
- :before_build_steps,
131
- :steps,
132
- :packages,
133
- :build_depends,
134
- :input_hooks
133
+ # @return [FPM::Fry::Source] the source used for building
134
+ attr_accessor :source
135
+
136
+ attr_accessor :build_mounts
137
+ attr_accessor :apt_setup
138
+
139
+ # @return [Array<#to_s>] steps that will be carried out before build
140
+ attr_accessor :before_build_steps
141
+
142
+ # @return [Array<#to_s>] steps that will be carried out during build
143
+ attr_accessor :steps
144
+
145
+ # @return [Array<FPM::Fry::PackageRecipe>] a list of packages that will be created
146
+ attr_accessor :packages
147
+
148
+ # @return [Hash<String,Hash>] build dependencies
149
+ attr_accessor :build_depends
150
+
151
+ # @return [Array<#call>] hooks that will be called on the input package
152
+ attr_accessor :input_hooks
135
153
 
136
154
  def initialize
137
155
  @source = Source::Null
@@ -145,6 +163,8 @@ module FPM; module Fry
145
163
  @apt_setup = []
146
164
  end
147
165
 
166
+ # Calculates all dependencies of this recipe
167
+ # @return [Hash<String,Hash>] the dependencies
148
168
  def depends
149
169
  depends = @packages.map(&:depends).inject(:merge)
150
170
  @packages.map(&:name).each do | n |
@@ -153,10 +173,15 @@ module FPM; module Fry
153
173
  return depends
154
174
  end
155
175
 
176
+ # Checks all packages for common errors
177
+ # @return [Array<String>] problems
156
178
  def lint
157
179
  packages.flat_map(&:lint)
158
180
  end
159
181
 
182
+ # Applies input settings to package
183
+ # @param [FPM::Package] package
184
+ # @return [FPM::Package]
160
185
  def apply_input( package )
161
186
  input_hooks.each{|h| h.call(self, package) }
162
187
  return package
@@ -7,13 +7,26 @@ module FPM::Fry
7
7
  class NotFound < StandardError
8
8
  end
9
9
 
10
- class PackageBuilder < Struct.new(:variables, :package_recipe)
10
+ class PackageBuilder
11
11
 
12
+ # @return [Hash<Symbol,Object>]
13
+ attr :variables
14
+
15
+ # @return [FPM::Fry::PackageRecipe]
16
+ attr :package_recipe
17
+
18
+ # @return [Cabin::Channel]
12
19
  attr :logger
13
20
 
14
- def initialize( variables, recipe = PackageRecipe.new, options = {})
15
- super(variables, recipe)
21
+ # @return [FPM::Fry::Inspector,nil]
22
+ attr :inspector
23
+
24
+ # @api private
25
+ def initialize( variables, package_recipe, options = {})
26
+ @variables = variables
27
+ @package_recipe = package_recipe
16
28
  @logger = options.fetch(:logger){ Cabin::Channel.get }
29
+ @inspector = options[:inspector]
17
30
  end
18
31
 
19
32
  def flavour
@@ -174,17 +187,17 @@ module FPM::Fry
174
187
 
175
188
  class Builder < PackageBuilder
176
189
 
190
+ # @return [FPM::Fry::Recipe]
177
191
  attr :recipe
178
192
 
179
- def initialize( variables, recipe = Recipe.new, options = {})
193
+ # @param [Hash<Symbol,Object>] variables
194
+ # @param [Hash] options
195
+ # @option options [FPM::Fry::Recipe] :recipe (Recipe.new)
196
+ # @option options [Cabin::Channel] :logger (default cabin channel)
197
+ # @option options [FPM::Fry::Inspector] :inspector
198
+ def initialize( variables, options = {} )
199
+ recipe = options.fetch(:recipe){ Recipe.new }
180
200
  variables = variables.dup
181
- if variables[:distribution] && !variables[:flavour] && OsDb[variables[:distribution]]
182
- variables[:flavour] = OsDb[variables[:distribution]][:flavour]
183
- end
184
- if !variables[:codename] && OsDb[variables[:distribution]] && variables[:distribution_version]
185
- codename = OsDb[variables[:distribution]][:codenames].find{|name,version| variables[:distribution_version].start_with? version }
186
- variables[:codename] = codename[0] if codename
187
- end
188
201
  variables.freeze
189
202
  @recipe = recipe
190
203
  @before_build = false
@@ -266,7 +279,7 @@ module FPM::Fry
266
279
  pr.version = package_recipe.version
267
280
  pr.iteration = package_recipe.iteration
268
281
  recipe.packages << pr
269
- PackageBuilder.new(variables, pr).instance_eval(&block)
282
+ PackageBuilder.new(variables, pr, logger: logger, inspector: inspector).instance_eval(&block)
270
283
  end
271
284
 
272
285
  protected
@@ -289,7 +302,7 @@ module FPM::Fry
289
302
 
290
303
  def register_default_source_types!
291
304
  register_source_type Source::Git
292
- register_source_type Source::Package
305
+ register_source_type Source::Archive
293
306
  register_source_type Source::Dir
294
307
  end
295
308
 
@@ -1,34 +1,35 @@
1
+ require 'fpm/fry/with_data'
1
2
  module FPM; module Fry ; module Source
2
3
 
4
+ # Raised when building a cache failed.
3
5
  class CacheFailed < StandardError
4
-
5
- attr :data
6
-
7
- def initialize(e, data = {})
8
- if e.kind_of? Exception
9
- @data = {reason: e}.merge data
10
- super(e.message)
11
- else
12
- @data = data.dup
13
- super(e.to_s)
14
- end
15
- end
6
+ include WithData
16
7
  end
17
8
 
9
+ # A special source that is empty.
18
10
  module Null
19
11
 
12
+ # A special cache that is empty.
20
13
  module Cache
14
+
15
+ # @return [IO] an empty tar
21
16
  def self.tar_io
22
17
  StringIO.new("\x00"*1024)
23
18
  end
19
+
20
+ # @return [Hash] an empty hash
24
21
  def self.file_map
25
22
  return {}
26
23
  end
24
+
25
+ # @return [String] 32 times zero
27
26
  def self.cachekey
28
27
  return '0' * 32
29
28
  end
30
29
  end
31
30
 
31
+ # @see FPM::Fry::Source::Null::Cache
32
+ # @return {FPM::Fry::Source::Null::Cache}
32
33
  def self.build_cache(*_)
33
34
  return Cache
34
35
  end
@@ -37,6 +38,7 @@ module FPM; module Fry ; module Source
37
38
 
38
39
  class << self
39
40
 
41
+ # @api private
40
42
  def guess_regex(rx, url)
41
43
  if m = rx.match(url.to_s)
42
44
  return m[0].size
@@ -4,32 +4,65 @@ require 'net/http'
4
4
  require 'forwardable'
5
5
  require 'zlib'
6
6
  require 'fpm/fry/source'
7
+ require 'fpm/fry/exec'
7
8
  require 'cabin'
8
9
  module FPM; module Fry ; module Source
9
- class Package
10
+ # Used to build from an archive.
11
+ #
12
+ # @example in a recipe
13
+ # source 'http://curl.haxx.se/download/curl-7.36.0.tar.gz',
14
+ # checksum: '33015795d5650a2bfdd9a4a28ce4317cef944722a5cfca0d1563db8479840e90'
15
+ #
16
+ # It is highly advised to supply a checksum ( althought it's not mandatory ).
17
+ # This checksum will be used to test for cache validity and data integrity. The
18
+ # checksum algorithm is automatically guessed based on the length of the checksum.
19
+ #
20
+ # - 40 characters = sha1
21
+ # - 64 characters = sha256
22
+ #
23
+ # Let's be honest: all other checksum algorithms aren't or shouldn't be in use anyway.
24
+ class Archive
10
25
 
11
26
  REGEX = %r!\Ahttps?:!
12
27
 
28
+ # @return [:archive]
13
29
  def self.name
14
30
  :package
15
31
  end
16
32
 
17
33
  def self.aliases
18
- [:http]
34
+ [:package,:http]
19
35
  end
20
36
 
37
+ # Guesses if the given url is an archive.
38
+ #
39
+ # @example not an archive
40
+ # FPM::Fry::Source::Archive.guess("bzr://something") # => nil
41
+ #
42
+ # @example an archive
43
+ # FPM::Fry::Source::Archive.guess("https://some/thing.tar.gz") #=> 6
44
+ #
45
+ # @return [nil] when it's not an archive
46
+ # @return [Numeric] number of characters used
21
47
  def self.guess( url )
22
48
  Source::guess_regex(REGEX, url)
23
49
  end
24
50
 
51
+ # Raised when too many redirects happened.
25
52
  class RedirectError < CacheFailed
26
53
  end
27
54
 
55
+ # Raised when the archive type is not known.
56
+ class UnknownArchiveType < StandardError
57
+ include WithData
58
+ end
59
+
28
60
  class Cache < Struct.new(:package,:tempdir)
29
61
  extend Forwardable
30
62
 
31
- def_delegators :package, :url, :checksum, :checksum_algorithm, :agent, :logger, :file_map
63
+ def_delegators :package, :url, :checksum, :checksum_algorithm, :logger, :file_map
32
64
 
65
+ # @return [String] cachekey which is equal to the checksum
33
66
  def cachekey
34
67
  @observed_checksum || checksum
35
68
  end
@@ -92,7 +125,7 @@ module FPM; module Fry ; module Source
92
125
  case(resp)
93
126
  when Net::HTTPRedirection
94
127
  if redirs == 0
95
- raise RedirectError, "Too many redirects"
128
+ raise RedirectError.new("Too many redirects", url: url.to_s, location: resp['location'])
96
129
  end
97
130
  logger.debug("Following redirect", url: url.to_s , location: resp['location'])
98
131
  return fetch_url( resp['location'], redirs - 1, &block)
@@ -119,9 +152,7 @@ module FPM; module Fry ; module Source
119
152
 
120
153
  def copy_to(dst)
121
154
  update!
122
- cmd = ['tar','-xf',tempfile,'-C',dst]
123
- logger.debug("Running tar",cmd: cmd)
124
- system(*cmd)
155
+ Exec['tar','-xf',tempfile,'-C',dst, logger: logger]
125
156
  end
126
157
 
127
158
  protected
@@ -142,9 +173,7 @@ module FPM; module Fry ; module Source
142
173
 
143
174
  def tar_io
144
175
  update!
145
- cmd = ['bzcat',tempfile]
146
- logger.debug("Running bzcat",cmd: cmd)
147
- return IO.popen(cmd)
176
+ return Exec::popen('bzcat', tempfile, logger: logger)
148
177
  end
149
178
 
150
179
  end
@@ -163,20 +192,14 @@ module FPM; module Fry ; module Source
163
192
  copy_to( workdir )
164
193
  File.rename(workdir, unpacked_tmpdir)
165
194
  end
166
- cmd = ['tar','-c','.']
167
- logger.debug("Running tar",cmd: cmd, dir: unpacked_tmpdir)
168
- ::Dir.chdir(unpacked_tmpdir) do
169
- return IO.popen(cmd)
170
- end
195
+ return Exec::popen('tar','-c','.', chdir: unpacked_tmpdir)
171
196
  end
172
197
 
173
198
  def copy_to(dst)
174
199
  update!
175
- cmd = ['unzip', tempfile, '-d', dst ]
176
- logger.debug("Running unzip",cmd: cmd)
177
- system(*cmd, out: '/dev/null')
200
+ Exec['unzip', tempfile, '-d', dst ]
178
201
  end
179
-
202
+ private
180
203
  def unpacked_tmpdir
181
204
  File.join(tempdir, cachekey)
182
205
  end
@@ -186,12 +209,8 @@ module FPM; module Fry ; module Source
186
209
 
187
210
  def tar_io
188
211
  update!
189
- cmd = ['tar','-c',::File.basename(tempfile)]
190
212
  dir = File.dirname(tempfile)
191
- logger.debug("Running tar",cmd: cmd, dir: dir)
192
- ::Dir.chdir(dir) do
193
- return IO.popen(cmd)
194
- end
213
+ Exec::popen('tar','-c',::File.basename(tempfile), logger: logger, chdir: dir)
195
214
  end
196
215
 
197
216
  def copy_to(dst)
@@ -211,26 +230,44 @@ module FPM; module Fry ; module Source
211
230
  '.bundle' => PlainCache
212
231
  }
213
232
 
214
- attr :file_map, :data, :url, :extension, :checksum, :checksum_algorithm, :agent, :logger
233
+ attr :file_map, :data, :url, :checksum, :checksum_algorithm, :logger
215
234
 
235
+ # @param [URI] url
236
+ # @param [Hash] options
237
+ # @option options [Cabin::Channel] :logger (default cabin channel)
238
+ # @option options [String] :checksum a checksum of the archive
239
+ # @raise [UnknownArchiveType] when the archive type is unknown
216
240
  def initialize( url, options = {} )
217
241
  @url = URI(url)
218
- @extension = options.fetch(:extension){
219
- CACHE_CLASSES.keys.find{|ext|
220
- @url.path.end_with?(ext)
221
- }
222
- }
242
+ @cache_class = guess_cache_class(@url)
223
243
  @logger = options.fetch(:logger){ Cabin::Channel.get }
224
244
  @checksum = options[:checksum]
225
245
  @checksum_algorithm = guess_checksum_algorithm(options[:checksum])
226
246
  @file_map = options.fetch(:file_map){ {'' => ''} }
227
247
  end
228
248
 
249
+ # Creates a cache.
250
+ #
251
+ # @param [String] tempdir
252
+ # @return [TarCache] for plain .tar files
253
+ # @return [TarGzCache] for .tar.gz files
254
+ # @return [TarBz2Cache] for .tar.bz2 files
255
+ # @return [ZipCache] for .zip files
256
+ # @return [PlainCache] for .bin files
229
257
  def build_cache(tempdir)
230
- CACHE_CLASSES.fetch(extension).new(self, tempdir)
258
+ @cache_class.new(self, tempdir)
231
259
  end
232
260
  private
233
261
 
262
+ def guess_cache_class( url )
263
+ CACHE_CLASSES.each do |ext,klass|
264
+ if url.path.end_with?(ext)
265
+ return klass
266
+ end
267
+ end
268
+ raise UnknownArchiveType.new("Unknown archive type", url: url.to_s, known_extensions: CACHE_CLASSES.keys)
269
+ end
270
+
234
271
  def guess_checksum_algorithm( checksum )
235
272
  case(checksum)
236
273
  when nil