fpm-fry 0.2.2 → 0.3.0

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