fpm-fry 0.1.3
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.
- checksums.yaml +7 -0
- data/bin/fpm-fry +10 -0
- data/lib/cabin/nice_output.rb +70 -0
- data/lib/fpm/fry/block_enumerator.rb +25 -0
- data/lib/fpm/fry/build_output_parser.rb +22 -0
- data/lib/fpm/fry/client.rb +162 -0
- data/lib/fpm/fry/command/cook.rb +370 -0
- data/lib/fpm/fry/command.rb +90 -0
- data/lib/fpm/fry/detector.rb +109 -0
- data/lib/fpm/fry/docker_file.rb +149 -0
- data/lib/fpm/fry/joined_io.rb +63 -0
- data/lib/fpm/fry/os_db.rb +35 -0
- data/lib/fpm/fry/plugin/alternatives.rb +90 -0
- data/lib/fpm/fry/plugin/edit_staging.rb +66 -0
- data/lib/fpm/fry/plugin/exclude.rb +18 -0
- data/lib/fpm/fry/plugin/init.rb +53 -0
- data/lib/fpm/fry/plugin/platforms.rb +10 -0
- data/lib/fpm/fry/plugin/script_helper.rb +176 -0
- data/lib/fpm/fry/plugin/service.rb +100 -0
- data/lib/fpm/fry/plugin.rb +3 -0
- data/lib/fpm/fry/recipe/builder.rb +267 -0
- data/lib/fpm/fry/recipe.rb +141 -0
- data/lib/fpm/fry/source/dir.rb +56 -0
- data/lib/fpm/fry/source/git.rb +90 -0
- data/lib/fpm/fry/source/package.rb +202 -0
- data/lib/fpm/fry/source/patched.rb +118 -0
- data/lib/fpm/fry/source.rb +47 -0
- data/lib/fpm/fry/stream_parser.rb +98 -0
- data/lib/fpm/fry/tar.rb +71 -0
- data/lib/fpm/fry/templates/debian/after_install.erb +9 -0
- data/lib/fpm/fry/templates/debian/before_install.erb +13 -0
- data/lib/fpm/fry/templates/debian/before_remove.erb +13 -0
- data/lib/fpm/fry/templates/redhat/after_install.erb +2 -0
- data/lib/fpm/fry/templates/redhat/before_install.erb +6 -0
- data/lib/fpm/fry/templates/redhat/before_remove.erb +6 -0
- data/lib/fpm/fry/templates/sysv.erb +125 -0
- data/lib/fpm/fry/templates/upstart.erb +15 -0
- data/lib/fpm/fry/ui.rb +12 -0
- data/lib/fpm/package/docker.rb +186 -0
- metadata +111 -0
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'fpm/fry/plugin'
|
2
|
+
module FPM::Fry::Plugin::ScriptHelper
|
3
|
+
|
4
|
+
class Script
|
5
|
+
|
6
|
+
attr :renderer
|
7
|
+
|
8
|
+
def initialize(renderer)
|
9
|
+
@renderer = renderer
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
renderer.call(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
self.class.name.split('::').last.gsub(/([^A-Z])([A-Z])/,'\1_\2').downcase
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module RenderErb
|
22
|
+
def render_path(script, path)
|
23
|
+
_erbout = ""
|
24
|
+
erb = ERB.new(
|
25
|
+
IO.read(File.join(File.dirname(__FILE__),'..','templates',path, "#{script.name}.erb"))
|
26
|
+
)
|
27
|
+
script.instance_eval(erb.src)
|
28
|
+
return _erbout
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module DebianRenderer
|
33
|
+
extend RenderErb
|
34
|
+
def self.call(script)
|
35
|
+
render_path(script,'debian')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module RedhatRenderer
|
40
|
+
extend RenderErb
|
41
|
+
def self.call(script)
|
42
|
+
render_path(script,'redhat')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class BeforeInstall < Script
|
47
|
+
|
48
|
+
# deb: $1 == install
|
49
|
+
# rpm: $1 == 1
|
50
|
+
attr :install
|
51
|
+
|
52
|
+
# deb: $1 == upgrade
|
53
|
+
# rpm: $1 >= 2
|
54
|
+
attr :upgrade
|
55
|
+
|
56
|
+
def initialize(*_)
|
57
|
+
super
|
58
|
+
@install = []
|
59
|
+
@upgrade = []
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
class AfterInstall < Script
|
65
|
+
|
66
|
+
def initialize(*_)
|
67
|
+
super
|
68
|
+
@configure = []
|
69
|
+
end
|
70
|
+
|
71
|
+
# deb: $1 == configure
|
72
|
+
# rpm: -always-
|
73
|
+
attr :configure
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
class BeforeRemove < Script
|
78
|
+
|
79
|
+
def initialize(*_)
|
80
|
+
super
|
81
|
+
@remove = []
|
82
|
+
@upgrade = []
|
83
|
+
end
|
84
|
+
|
85
|
+
# deb: $1 == remove
|
86
|
+
# rpm: $1 == 0
|
87
|
+
attr :remove
|
88
|
+
|
89
|
+
# deb: $1 == upgrade
|
90
|
+
# rpm: $1 >= 1
|
91
|
+
attr :upgrade
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
class AfterRemove < Script
|
96
|
+
|
97
|
+
def initialize
|
98
|
+
@remove = []
|
99
|
+
@upgrade = []
|
100
|
+
end
|
101
|
+
|
102
|
+
# deb: $1 == upgrade
|
103
|
+
# rpm: $1 == 1
|
104
|
+
attr :upgrade
|
105
|
+
|
106
|
+
# deb: $1 == remove
|
107
|
+
# rpm: $1 == 0
|
108
|
+
attr :remove
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
NAME_TO_SCRIPT = {
|
113
|
+
before_install: BeforeInstall,
|
114
|
+
after_install: AfterInstall,
|
115
|
+
before_remove: BeforeRemove,
|
116
|
+
after_remove: AfterRemove
|
117
|
+
}
|
118
|
+
|
119
|
+
SCRIPT_TO_NAME = NAME_TO_SCRIPT.invert
|
120
|
+
|
121
|
+
class DSL < Struct.new(:builder)
|
122
|
+
|
123
|
+
# before(install) => before_install:install
|
124
|
+
# before(upgrade) => before_install:upgrade
|
125
|
+
# after(install_or_upgrade) => after_install:configure
|
126
|
+
# before(remove_for_upgrade) => before_remove:upgrade
|
127
|
+
# before(remove) => before_remove:remove
|
128
|
+
# after(remove) => after_remove:remove
|
129
|
+
# after(remove_for_upgrade) => after_remove:upgrade
|
130
|
+
|
131
|
+
def after_install_or_upgrade(*scripts)
|
132
|
+
find(:after_install).configure.push(*scripts)
|
133
|
+
end
|
134
|
+
|
135
|
+
def before_remove_entirely(*scripts)
|
136
|
+
find(:before_remove).remove.push(*scripts)
|
137
|
+
end
|
138
|
+
|
139
|
+
def after_remove_entirely(*scripts)
|
140
|
+
find(:after_remove).remove.push(*scripts)
|
141
|
+
end
|
142
|
+
private
|
143
|
+
|
144
|
+
def find(type)
|
145
|
+
klass = NAME_TO_SCRIPT[type]
|
146
|
+
script = builder.script(type).find{|s| s.kind_of? klass }
|
147
|
+
if script.nil?
|
148
|
+
script = klass.new( renderer )
|
149
|
+
builder.script(type,script)
|
150
|
+
end
|
151
|
+
return script
|
152
|
+
end
|
153
|
+
|
154
|
+
def renderer
|
155
|
+
@renderer ||= case(builder.flavour)
|
156
|
+
when 'debian' then DebianRenderer
|
157
|
+
when 'redhat' then RedhatRenderer
|
158
|
+
else
|
159
|
+
raise "Unknown flavour: #{builder.flavour.inspect}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.apply(builder, options = {}, &block)
|
166
|
+
dsl = DSL.new(builder)
|
167
|
+
if block
|
168
|
+
if block.arity == 1
|
169
|
+
yield dsl
|
170
|
+
else
|
171
|
+
dsl.instance_eval(&block)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'fpm/fry/plugin'
|
2
|
+
require 'fpm/fry/plugin/init'
|
3
|
+
require 'fpm/fry/plugin/edit_staging'
|
4
|
+
require 'erb'
|
5
|
+
require 'shellwords'
|
6
|
+
module FPM::Fry::Plugin ; module Service
|
7
|
+
|
8
|
+
class Environment < Struct.new(:name,:command, :description)
|
9
|
+
|
10
|
+
def render(file)
|
11
|
+
_erbout = ""
|
12
|
+
erb = ERB.new(
|
13
|
+
IO.read(File.join(File.dirname(__FILE__),'..','templates',file))
|
14
|
+
)
|
15
|
+
eval(erb.src)
|
16
|
+
return _erbout
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class DSL
|
22
|
+
|
23
|
+
def initialize(*_)
|
24
|
+
super
|
25
|
+
@name = nil
|
26
|
+
@command = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def name( n = nil )
|
30
|
+
if n
|
31
|
+
@name = n
|
32
|
+
end
|
33
|
+
return @name
|
34
|
+
end
|
35
|
+
|
36
|
+
def command( *args )
|
37
|
+
if args.any?
|
38
|
+
@command = args
|
39
|
+
end
|
40
|
+
return @command
|
41
|
+
end
|
42
|
+
|
43
|
+
# @api private
|
44
|
+
def add!(builder)
|
45
|
+
name = self.name || builder.name || raise
|
46
|
+
init = Init.detect_init(builder.variables)
|
47
|
+
edit = builder.plugin('edit_staging')
|
48
|
+
env = Environment.new(name, command, "")
|
49
|
+
case(init)
|
50
|
+
when 'upstart' then
|
51
|
+
edit.add_file "/etc/init/#{name}.conf",StringIO.new( env.render('upstart.erb') )
|
52
|
+
edit.ln_s '/lib/init/upstart-job', "/etc/init.d/#{name}"
|
53
|
+
builder.plugin('script_helper') do |sh|
|
54
|
+
sh.after_install_or_upgrade(<<BASH)
|
55
|
+
if status #{Shellwords.shellescape name} 2>/dev/null | grep -q ' start/'; then
|
56
|
+
# It has to be stop+start because upstart doesn't pickup changes with restart.
|
57
|
+
stop #{Shellwords.shellescape name}
|
58
|
+
fi
|
59
|
+
start #{Shellwords.shellescape name}
|
60
|
+
BASH
|
61
|
+
sh.before_remove_entirely(<<BASH)
|
62
|
+
if status #{Shellwords.shellescape name} 2>/dev/null | grep -q ' start/'; then
|
63
|
+
stop #{Shellwords.shellescape name}
|
64
|
+
fi
|
65
|
+
BASH
|
66
|
+
end
|
67
|
+
when 'sysv' then
|
68
|
+
edit.add_file "/etc/init.d/#{name}",StringIO.new( env.render('sysv.erb') ), chmod: '750'
|
69
|
+
builder.plugin('script_helper') do |sh|
|
70
|
+
sh.after_install_or_upgrade(<<BASH)
|
71
|
+
update-rc.d #{Shellwords.shellescape name} defaults
|
72
|
+
/etc/init.d/#{Shellwords.shellescape name} restart
|
73
|
+
BASH
|
74
|
+
sh.before_remove_entirely(<<BASH)
|
75
|
+
/etc/init.d/#{Shellwords.shellescape name} stop
|
76
|
+
update-rc.d -f #{Shellwords.shellescape name} remove
|
77
|
+
BASH
|
78
|
+
end
|
79
|
+
when 'systemd' then
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.apply(builder, &block)
|
87
|
+
d = DSL.new
|
88
|
+
if !block
|
89
|
+
raise ArgumentError, "service plugin requires a block"
|
90
|
+
elsif block.arity == 1
|
91
|
+
block.call(d)
|
92
|
+
else
|
93
|
+
d.instance_eval(&block)
|
94
|
+
end
|
95
|
+
d.add!(builder)
|
96
|
+
return nil
|
97
|
+
end
|
98
|
+
|
99
|
+
end end
|
100
|
+
|
@@ -0,0 +1,267 @@
|
|
1
|
+
require 'fpm/fry/recipe'
|
2
|
+
require 'forwardable'
|
3
|
+
module FPM::Fry
|
4
|
+
class Recipe
|
5
|
+
|
6
|
+
class NotFound < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class PackageBuilder < Struct.new(:variables, :package_recipe)
|
10
|
+
|
11
|
+
attr :logger
|
12
|
+
|
13
|
+
def initialize( variables, recipe = PackageRecipe.new, options = {})
|
14
|
+
super(variables, recipe)
|
15
|
+
@logger = options.fetch(:logger){ Cabin::Channel.get }
|
16
|
+
end
|
17
|
+
|
18
|
+
def flavour
|
19
|
+
variables[:flavour]
|
20
|
+
end
|
21
|
+
|
22
|
+
def distribution
|
23
|
+
variables[:distribution]
|
24
|
+
end
|
25
|
+
alias platform distribution
|
26
|
+
|
27
|
+
def distribution_version
|
28
|
+
variables[:distribution_version]
|
29
|
+
end
|
30
|
+
alias platform_version distribution_version
|
31
|
+
|
32
|
+
def codename
|
33
|
+
variables[:codename]
|
34
|
+
end
|
35
|
+
|
36
|
+
def iteration(value = Not)
|
37
|
+
get_or_set('@iteration',value)
|
38
|
+
end
|
39
|
+
alias revision iteration
|
40
|
+
|
41
|
+
def version(value = Not)
|
42
|
+
get_or_set('@version',value)
|
43
|
+
end
|
44
|
+
|
45
|
+
def name(value = Not)
|
46
|
+
get_or_set('@name',value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def vendor(value = Not)
|
50
|
+
get_or_set('@vendor',value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def depends( name , options = {} )
|
54
|
+
name, options = parse_package(name, options)
|
55
|
+
package_recipe.depends[name] = options
|
56
|
+
end
|
57
|
+
|
58
|
+
def conflicts( name , options = {} )
|
59
|
+
name, options = parse_package(name, options)
|
60
|
+
package_recipe.conflicts[name] = options
|
61
|
+
end
|
62
|
+
|
63
|
+
def provides( name , options = {} )
|
64
|
+
name, options = parse_package(name, options)
|
65
|
+
package_recipe.provides[name] = options
|
66
|
+
end
|
67
|
+
|
68
|
+
def replaces( name , options = {} )
|
69
|
+
name, options = parse_package(name, options)
|
70
|
+
package_recipe.replaces[name] = options
|
71
|
+
end
|
72
|
+
|
73
|
+
def files( pattern )
|
74
|
+
package_recipe.files << pattern
|
75
|
+
end
|
76
|
+
|
77
|
+
def plugin(name, *args, &block)
|
78
|
+
logger.debug('Loading Plugin', name: name, args: args, block: block, load_path: $LOAD_PATH)
|
79
|
+
if name =~ /\A\./
|
80
|
+
require name
|
81
|
+
else
|
82
|
+
require File.join('fpm/fry/plugin',name)
|
83
|
+
end
|
84
|
+
module_name = File.basename(name,'.rb').gsub(/(?:\A|_)([a-z])/){ $1.upcase }
|
85
|
+
mod = FPM::Fry::Plugin.const_get(module_name)
|
86
|
+
if mod.respond_to? :apply
|
87
|
+
mod.apply(self, *args, &block)
|
88
|
+
else
|
89
|
+
extend(mod)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def script(type, value = Not)
|
94
|
+
if value != Not
|
95
|
+
package_recipe.scripts[type] << value
|
96
|
+
end
|
97
|
+
return package_recipe.scripts[type]
|
98
|
+
end
|
99
|
+
|
100
|
+
def before_install(*args)
|
101
|
+
script(:before_install, *args)
|
102
|
+
end
|
103
|
+
alias pre_install before_install
|
104
|
+
alias preinstall before_install
|
105
|
+
|
106
|
+
def after_install(*args)
|
107
|
+
script(:after_install, *args)
|
108
|
+
end
|
109
|
+
alias post_install after_install
|
110
|
+
alias postinstall after_install
|
111
|
+
|
112
|
+
def before_remove(*args)
|
113
|
+
script(:before_remove, *args)
|
114
|
+
end
|
115
|
+
alias before_uninstall before_remove
|
116
|
+
alias pre_uninstall before_remove
|
117
|
+
alias preuninstall before_remove
|
118
|
+
|
119
|
+
def after_remove(*args)
|
120
|
+
script(:after_remove, *args)
|
121
|
+
end
|
122
|
+
alias after_uninstall after_remove
|
123
|
+
alias post_uninstall after_remove
|
124
|
+
alias postuninstall after_remove
|
125
|
+
|
126
|
+
def output_hooks
|
127
|
+
package_recipe.output_hooks
|
128
|
+
end
|
129
|
+
|
130
|
+
protected
|
131
|
+
|
132
|
+
def parse_package( name, options = {} )
|
133
|
+
if options.kind_of? String
|
134
|
+
options = {version: options}
|
135
|
+
end
|
136
|
+
case(v = options[:version])
|
137
|
+
when String
|
138
|
+
if v =~ /\A(<=|<<|>=|>>|<>|=|>|<)(\s*)/
|
139
|
+
options[:version] = ' ' + $1 + ' ' + $'
|
140
|
+
else
|
141
|
+
options[:version] = ' = ' + v
|
142
|
+
end
|
143
|
+
end
|
144
|
+
return name, options
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
Not = Module.new
|
149
|
+
def get_or_set(name, value = Not)
|
150
|
+
if value == Not
|
151
|
+
return package_recipe.instance_variable_get(name)
|
152
|
+
else
|
153
|
+
return package_recipe.instance_variable_set(name, value)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
class Builder < PackageBuilder
|
160
|
+
|
161
|
+
attr :recipe
|
162
|
+
|
163
|
+
def initialize( variables, recipe = Recipe.new, options = {})
|
164
|
+
variables = variables.dup
|
165
|
+
if variables[:distribution] && !variables[:flavour] && OsDb[variables[:distribution]]
|
166
|
+
variables[:flavour] = OsDb[variables[:distribution]][:flavour]
|
167
|
+
end
|
168
|
+
if !variables[:codename] && OsDb[variables[:distribution]] && variables[:distribution_version]
|
169
|
+
codename = OsDb[variables[:distribution]][:codenames].find{|name,version| variables[:distribution_version].start_with? version }
|
170
|
+
variables[:codename] = codename[0] if codename
|
171
|
+
end
|
172
|
+
variables.freeze
|
173
|
+
@recipe = recipe
|
174
|
+
super(variables, recipe.packages[0], options = {})
|
175
|
+
end
|
176
|
+
|
177
|
+
def load_file( file )
|
178
|
+
file = File.expand_path(file)
|
179
|
+
begin
|
180
|
+
content = IO.read(file)
|
181
|
+
rescue Errno::ENOENT => e
|
182
|
+
raise NotFound, e
|
183
|
+
end
|
184
|
+
basedir = File.dirname(file)
|
185
|
+
Dir.chdir(basedir) do
|
186
|
+
instance_eval(content,file,0)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def source( url , options = {} )
|
191
|
+
options = options.merge(logger: logger)
|
192
|
+
source = Source::Patched.decorate(options) do |options|
|
193
|
+
guess_source(url,options).new(url, options)
|
194
|
+
end
|
195
|
+
recipe.source = source
|
196
|
+
end
|
197
|
+
|
198
|
+
def run(*args)
|
199
|
+
if args.first.kind_of? Hash
|
200
|
+
options = args.shift
|
201
|
+
else
|
202
|
+
options = {}
|
203
|
+
end
|
204
|
+
command = args.shift
|
205
|
+
name = options.fetch(:name){ [command,*args].select{|c| c[0] != '-' }.join('-') }
|
206
|
+
recipe.steps[name] = Shellwords.join([command, *args])
|
207
|
+
end
|
208
|
+
|
209
|
+
def build_depends( name , options = {} )
|
210
|
+
name, options = parse_package(name, options)
|
211
|
+
recipe.build_depends[name] = options
|
212
|
+
end
|
213
|
+
|
214
|
+
def input_hooks
|
215
|
+
recipe.input_hooks
|
216
|
+
end
|
217
|
+
|
218
|
+
def package(name, &block)
|
219
|
+
pr = PackageRecipe.new
|
220
|
+
pr.name = name
|
221
|
+
pr.version = package_recipe.version
|
222
|
+
pr.iteration = package_recipe.iteration
|
223
|
+
recipe.packages << pr
|
224
|
+
PackageBuilder.new(variables, pr).instance_eval(&block)
|
225
|
+
end
|
226
|
+
|
227
|
+
protected
|
228
|
+
|
229
|
+
def source_types
|
230
|
+
@source_types ||= {
|
231
|
+
git: Source::Git,
|
232
|
+
http: Source::Package,
|
233
|
+
tar: Source::Package,
|
234
|
+
dir: Source::Dir
|
235
|
+
}
|
236
|
+
end
|
237
|
+
|
238
|
+
def register_source_type( name, klass )
|
239
|
+
if !klass.respond_to? :new
|
240
|
+
raise ArgumentError.new("Expected something that responds to :new, got #{klass.inspect}")
|
241
|
+
end
|
242
|
+
source_types[name] = klass
|
243
|
+
end
|
244
|
+
|
245
|
+
NEG_INF = (-1.0/0.0)
|
246
|
+
|
247
|
+
def guess_source( url, options = {} )
|
248
|
+
if w = options[:with]
|
249
|
+
return source_types.fetch(w){ raise ArgumentError.new("Unknown source type: #{w}") }
|
250
|
+
end
|
251
|
+
scores = source_types.values.uniq\
|
252
|
+
.select{|klass| klass.respond_to? :guess }\
|
253
|
+
.group_by{|klass| klass.guess(url) }\
|
254
|
+
.sort_by{|score,_| score.nil? ? NEG_INF : score }
|
255
|
+
score, klasses = scores.last
|
256
|
+
if score == nil
|
257
|
+
raise ArgumentError.new("No source provide found for #{url}.\nMaybe try explicitly setting the type using :with parameter. Valid options are: #{source_types.keys.join(', ')}")
|
258
|
+
end
|
259
|
+
if klasses.size != 1
|
260
|
+
raise ArgumentError.new("Multiple possible source providers found for #{url}: #{klasses.join(', ')}.\nMaybe try explicitly setting the type using :with parameter. Valid options are: #{source_types.keys.join(', ')}")
|
261
|
+
end
|
262
|
+
return klasses.first
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'fpm/fry/source'
|
2
|
+
require 'fpm/fry/source/package'
|
3
|
+
require 'fpm/fry/source/dir'
|
4
|
+
require 'fpm/fry/source/patched'
|
5
|
+
require 'fpm/fry/source/git'
|
6
|
+
require 'fpm/fry/plugin'
|
7
|
+
require 'fpm/fry/os_db'
|
8
|
+
require 'shellwords'
|
9
|
+
require 'cabin'
|
10
|
+
require 'open3'
|
11
|
+
module FPM; module Fry
|
12
|
+
|
13
|
+
class Recipe
|
14
|
+
|
15
|
+
class PackageRecipe
|
16
|
+
attr_accessor :name,
|
17
|
+
:iteration,
|
18
|
+
:version,
|
19
|
+
:maintainer,
|
20
|
+
:vendor,
|
21
|
+
:depends,
|
22
|
+
:provides,
|
23
|
+
:conflicts,
|
24
|
+
:replaces,
|
25
|
+
:suggests,
|
26
|
+
:recommends,
|
27
|
+
:scripts,
|
28
|
+
:output_hooks,
|
29
|
+
:files
|
30
|
+
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@name = nil
|
34
|
+
@iteration = nil
|
35
|
+
@version = '0.0.0'
|
36
|
+
@maintainer = nil
|
37
|
+
@vendor = nil
|
38
|
+
@depends = {}
|
39
|
+
@provides = {}
|
40
|
+
@conflicts = {}
|
41
|
+
@replaces = {}
|
42
|
+
@scripts = {
|
43
|
+
before_install: [],
|
44
|
+
after_install: [],
|
45
|
+
before_remove: [],
|
46
|
+
after_remove: []
|
47
|
+
}
|
48
|
+
@output_hooks = []
|
49
|
+
@files = []
|
50
|
+
end
|
51
|
+
|
52
|
+
alias dependencies depends
|
53
|
+
|
54
|
+
def apply_output( package )
|
55
|
+
package.name = name
|
56
|
+
package.version = version
|
57
|
+
package.iteration = iteration
|
58
|
+
package.maintainer = maintainer if maintainer
|
59
|
+
package.vendor = vendor if vendor
|
60
|
+
scripts.each do |type, scripts|
|
61
|
+
package.scripts[type] = scripts.join("\n") if scripts.any?
|
62
|
+
end
|
63
|
+
[:dependencies, :conflicts, :replaces, :provides].each do |sym|
|
64
|
+
send(sym).each do |name, options|
|
65
|
+
package.send(sym) << "#{name}#{options[:version]}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
output_hooks.each{|h| h.call(self, package) }
|
69
|
+
return package
|
70
|
+
end
|
71
|
+
|
72
|
+
alias apply apply_output
|
73
|
+
|
74
|
+
SYNTAX_CHECK_SHELLS = ['/bin/sh','/bin/bash', '/bin/dash']
|
75
|
+
|
76
|
+
def lint
|
77
|
+
problems = []
|
78
|
+
problems << "Name is empty." if name.to_s == ''
|
79
|
+
scripts.each do |type,scripts|
|
80
|
+
next if scripts.none?
|
81
|
+
s = scripts.join("\n")
|
82
|
+
if s == ''
|
83
|
+
problems << "#{type} script is empty. This will produce broken packages."
|
84
|
+
next
|
85
|
+
end
|
86
|
+
m = /\A#!([^\n]+)\n/.match(s)
|
87
|
+
if !m
|
88
|
+
problems << "#{type} script doesn't have a valid shebang"
|
89
|
+
next
|
90
|
+
end
|
91
|
+
begin
|
92
|
+
args = m[1].shellsplit
|
93
|
+
rescue ArgumentError => e
|
94
|
+
problems << "#{type} script doesn't have a valid command in shebang"
|
95
|
+
end
|
96
|
+
if SYNTAX_CHECK_SHELLS.include? args[0]
|
97
|
+
sin, sout, serr, th = Open3.popen3(args[0],'-n')
|
98
|
+
sin.write(s)
|
99
|
+
sin.close
|
100
|
+
if th.value.exitstatus != 0
|
101
|
+
problems << "#{type} script is not valid #{args[0]} code: #{serr.read.chomp}"
|
102
|
+
end
|
103
|
+
serr.close
|
104
|
+
sout.close
|
105
|
+
end
|
106
|
+
end
|
107
|
+
return problems
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
attr_accessor :source, :steps, :packages, :build_depends, :input_hooks
|
112
|
+
|
113
|
+
def initialize
|
114
|
+
@source = Source::Null
|
115
|
+
@steps = {}
|
116
|
+
@packages = [PackageRecipe.new]
|
117
|
+
@packages[0].files << '**'
|
118
|
+
@build_depends = {}
|
119
|
+
@input_hooks = []
|
120
|
+
end
|
121
|
+
|
122
|
+
def depends
|
123
|
+
depends = @packages.map(&:depends).inject(:merge)
|
124
|
+
@packages.map(&:name).each do | n |
|
125
|
+
depends.delete(n)
|
126
|
+
end
|
127
|
+
return depends
|
128
|
+
end
|
129
|
+
|
130
|
+
def lint
|
131
|
+
packages.flat_map(&:lint)
|
132
|
+
end
|
133
|
+
|
134
|
+
def apply_input( package )
|
135
|
+
input_hooks.each{|h| h.call(self, package) }
|
136
|
+
return package
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end ; end
|