fpm-cookery 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +3 -0
- data/README.md +159 -0
- data/Rakefile +10 -0
- data/bin/fpm-cook +7 -0
- data/fpm-cookery.gemspec +24 -0
- data/lib/fpm/cookery/book.rb +16 -0
- data/lib/fpm/cookery/book_hook.rb +17 -0
- data/lib/fpm/cookery/cli.rb +35 -0
- data/lib/fpm/cookery/packager.rb +155 -0
- data/lib/fpm/cookery/path.rb +75 -0
- data/lib/fpm/cookery/path_helper.rb +49 -0
- data/lib/fpm/cookery/recipe.rb +88 -0
- data/lib/fpm/cookery/source_handler.rb +28 -0
- data/lib/fpm/cookery/source_handler/curl.rb +53 -0
- data/lib/fpm/cookery/source_handler/template.rb +32 -0
- data/lib/fpm/cookery/utils.rb +62 -0
- data/lib/fpm/cookery/version.rb +5 -0
- data/recipes/nodejs/recipe.rb +35 -0
- data/recipes/redis/recipe.rb +38 -0
- data/recipes/redis/redis-server.init.d +80 -0
- data/spec/path_spec.rb +123 -0
- data/spec/recipe_spec.rb +248 -0
- data/spec/spec_helper.rb +2 -0
- metadata +104 -0
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# fpm-cookery - For building software
|
2
|
+
|
3
|
+
A tool for building software packages with
|
4
|
+
[fpm](https://github.com/jordansissel/fpm).
|
5
|
+
|
6
|
+
The [fpm](https://github.com/jordansissel/fpm) project is really nice for
|
7
|
+
building operating system packages like `.deb` and `.rpm`. But it only helps
|
8
|
+
you to create the packages and doesn't help you with actually building the
|
9
|
+
software.
|
10
|
+
|
11
|
+
__fpm-cookery__ provides an infrastructure to automatically build software
|
12
|
+
based on recipes. It's heavily inspired and borrows code from the great
|
13
|
+
[homebrew](https://github.com/mxcl/homebrew) and
|
14
|
+
[brew2deb](https://github.com/tmm1/brew2deb) projects.
|
15
|
+
The [OpenBSD Ports System](http://www.openbsd.org/faq/ports/index.html) is
|
16
|
+
probably another source of inspiration since I've been working with that for
|
17
|
+
quite some time
|
18
|
+
|
19
|
+
It is using __fpm__ to create the actual packages.
|
20
|
+
|
21
|
+
## Why?
|
22
|
+
|
23
|
+
Building operating system packages for Debian/Ubuntu and RedHat using the
|
24
|
+
official process and tools is pretty annoying if you just want some custom
|
25
|
+
packages. Jordan's [fpm](https://github.com/jordansissel/fpm) removes the
|
26
|
+
biggest hurdle by providing a simple command line tool to build packages
|
27
|
+
for different operating systems.
|
28
|
+
|
29
|
+
Before you can use __fpm__ to create the package, you have to build the software,
|
30
|
+
though. In the past I've been using some shell scripts and Makefiles to
|
31
|
+
automate this task.
|
32
|
+
|
33
|
+
Then I discovered Aman's [brew2deb](https://github.com/tmm1/brew2deb) which is
|
34
|
+
actually [homebrew](https://github.com/mxcl/homebrew) with some modifications
|
35
|
+
to make it work on Linux. (only Debian/Ubuntu for now) Since __homebrew__ was
|
36
|
+
designed for Mac OS X, I thought it would be nice to have a "native" Linux
|
37
|
+
tool for the job.
|
38
|
+
|
39
|
+
__fpm-cookery__ is my attempt to build such a tool.
|
40
|
+
|
41
|
+
## Features
|
42
|
+
|
43
|
+
* Download of the source archives. (via __curl(1)__)
|
44
|
+
* Recipes to describe and execute the software build.
|
45
|
+
(e.g. configure, make, make install)
|
46
|
+
* Sandboxed builds.
|
47
|
+
* Package creation via __fpm__.
|
48
|
+
* Standalone recipe trees/books/you name it. No need to put the recipes into
|
49
|
+
the __fpm-cookery__ source tree.
|
50
|
+
|
51
|
+
## Upcoming Features
|
52
|
+
|
53
|
+
* Apply custom patches.
|
54
|
+
* Dependency checking.
|
55
|
+
* Recipe validation.
|
56
|
+
* Integrity checks for downloaded archives.
|
57
|
+
* More source types. (git, svn, ...)
|
58
|
+
* Progress output and logging.
|
59
|
+
* Make package output type configurable. (deb, rpm)
|
60
|
+
* Extend recipe features and build/install helpers.
|
61
|
+
* Configuration file. (for stuff like vendor and maintainer)
|
62
|
+
* Options for the `fpm-cook` command.
|
63
|
+
* Manpage for the `fpm-cook` command.
|
64
|
+
|
65
|
+
## Getting Started
|
66
|
+
|
67
|
+
Since there is no gem available yet, you have to clone the repository to
|
68
|
+
your local machine and run the following to build a recipe.
|
69
|
+
|
70
|
+
$ ruby bin/fpm-cook recipes/redis/recipe.rb clean
|
71
|
+
$ ruby bin/fpm-cook recipes/redis/recipe.rb
|
72
|
+
|
73
|
+
Or change into the recipe directory.
|
74
|
+
|
75
|
+
$ export PATH="$PWD/bin:$PATH"
|
76
|
+
$ cd recipes/redis
|
77
|
+
$ fpm-cook clean
|
78
|
+
$ fpm-cook
|
79
|
+
|
80
|
+
You can run the included test suite with `rake test`. This needs the __rake__
|
81
|
+
and __minitest__ gems.
|
82
|
+
|
83
|
+
## Status
|
84
|
+
|
85
|
+
It can build the included `recipes/redis/recipe.rb` and
|
86
|
+
`recipes/nodejs/recipe.rb` recipes. (both imported from __brew2deb__)
|
87
|
+
See __CAVEATS__ for an incomplete list of missing stuff.
|
88
|
+
|
89
|
+
## Example Recipe
|
90
|
+
|
91
|
+
The following is an example recipe. I have some more in my recipe collection
|
92
|
+
[over here](https://github.com/bernd/fpm-recipes).
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
class Redis < FPM::Cookery::Recipe
|
96
|
+
homepage 'http://redis.io'
|
97
|
+
source 'http://redis.googlecode.com/files/redis-2.2.5.tar.gz'
|
98
|
+
md5 'fe6395bbd2cadc45f4f20f6bbe05ed09'
|
99
|
+
|
100
|
+
name 'redis-server'
|
101
|
+
version '2.2.5'
|
102
|
+
revision '1'
|
103
|
+
|
104
|
+
description 'An advanced key-value store.'
|
105
|
+
|
106
|
+
conflicts 'redis-server'
|
107
|
+
|
108
|
+
config_files '/etc/redis/redis.conf'
|
109
|
+
|
110
|
+
def build
|
111
|
+
make
|
112
|
+
|
113
|
+
inline_replace 'redis.conf' do |s|
|
114
|
+
s.gsub! 'daemonize no', 'daemonize yes'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def install
|
119
|
+
# make :install, 'DESTDIR' => destdir
|
120
|
+
|
121
|
+
var('lib/redis').mkdir
|
122
|
+
|
123
|
+
%w(run log/redis).each {|p| var(p).mkdir }
|
124
|
+
|
125
|
+
bin.install ['src/redis-server', 'src/redis-cli']
|
126
|
+
|
127
|
+
etc('redis').install 'redis.conf'
|
128
|
+
etc('init.d').install 'redis-server.init.d' => 'redis-server'
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
## CAVEATS
|
134
|
+
|
135
|
+
* At the moment, there's only a small subset of the __homebrew__ DSL implemented.
|
136
|
+
* No recipe documentation and API documentation yet.
|
137
|
+
* No recipe validation yet.
|
138
|
+
* No dependency validation yet.
|
139
|
+
* No integrity check of the downloaded archives yet.
|
140
|
+
* No support for patches yet.
|
141
|
+
* Only simple source/url types (via curl) for now.
|
142
|
+
* No real logging output yet.
|
143
|
+
* No gem on rubygems.org yet.
|
144
|
+
* Pretty new and not well tested.
|
145
|
+
|
146
|
+
## Credits
|
147
|
+
|
148
|
+
__fpm-cookery__ borrows lots of __ideas__ and also __code__ from the
|
149
|
+
[homebrew](https://github.com/mxcl/homebrew) and
|
150
|
+
[brew2deb](https://github.com/tmm1/brew2deb) projects. Both projects don't
|
151
|
+
have any licensing information included in their repositories. So licensing
|
152
|
+
is still an open question for now.
|
153
|
+
|
154
|
+
## How To Contribute
|
155
|
+
|
156
|
+
* I'd love to hear if you like it, hate it, use it and if you have suggestions
|
157
|
+
and/or problems.
|
158
|
+
* Send pull requests. (hugs for topic branches and tests)
|
159
|
+
* Have fun!
|
data/Rakefile
ADDED
data/bin/fpm-cook
ADDED
data/fpm-cookery.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "fpm/cookery/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "fpm-cookery"
|
7
|
+
s.version = FPM::Cookery::VERSION
|
8
|
+
s.authors = ["Bernd Ahlers"]
|
9
|
+
s.email = ["bernd@tuneafish.de"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{A tool for building software packages with fpm.}
|
12
|
+
s.description = %q{A tool for building software packages with fpm.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "fpm-cookery"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "fpm"
|
22
|
+
s.add_development_dependency "minitest"
|
23
|
+
s.add_runtime_dependency "fpm"
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module FPM
|
2
|
+
module Cookery
|
3
|
+
class Book
|
4
|
+
# Load the given file and instantiate an object. Wrap the class in an
|
5
|
+
# anonymous module to avoid namespace cluttering. (see Kernel.load)
|
6
|
+
def self.load_recipe(filename, &callback)
|
7
|
+
Kernel.load(filename, true)
|
8
|
+
callback.call(@recipe.new(filename))
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.loaded_recipe(klass)
|
12
|
+
@recipe = klass
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'fpm/cookery/book'
|
2
|
+
|
3
|
+
module FPM
|
4
|
+
module Cookery
|
5
|
+
module BookHook
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def inherited(klass)
|
12
|
+
FPM::Cookery::Book.loaded_recipe(klass)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'fpm/cookery/book_hook'
|
2
|
+
require 'fpm/cookery/recipe'
|
3
|
+
require 'fpm/cookery/packager'
|
4
|
+
|
5
|
+
module FPM
|
6
|
+
module Cookery
|
7
|
+
class CLI
|
8
|
+
def initialize(argv)
|
9
|
+
@argv = argv
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
filename = @argv.find {|arg| arg =~ /\.rb$/ and File.exists?(arg) }
|
14
|
+
filename ||= File.expand_path('recipe.rb')
|
15
|
+
|
16
|
+
unless File.exists?(filename)
|
17
|
+
STDERR.puts 'No recipe.rb found in the current directory, abort.'
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
FPM::Cookery::Recipe.send(:include, FPM::Cookery::BookHook)
|
22
|
+
|
23
|
+
FPM::Cookery::Book.load_recipe(filename) do |recipe|
|
24
|
+
packager = FPM::Cookery::Packager.new(recipe)
|
25
|
+
|
26
|
+
if @argv.include?('clean')
|
27
|
+
packager.cleanup
|
28
|
+
else
|
29
|
+
packager.dispense
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
#require 'digest/md5'
|
2
|
+
#require 'fpm/cookery/recipe_inspector'
|
3
|
+
#require 'fpm/cookery/dependency_inspector'
|
4
|
+
require 'fpm/cookery/utils'
|
5
|
+
|
6
|
+
module FPM
|
7
|
+
module Cookery
|
8
|
+
class Packager
|
9
|
+
include FPM::Cookery::Utils
|
10
|
+
|
11
|
+
attr_reader :recipe, :config
|
12
|
+
|
13
|
+
def initialize(recipe, config = {})
|
14
|
+
@recipe = recipe
|
15
|
+
@config = config
|
16
|
+
end
|
17
|
+
|
18
|
+
def cleanup
|
19
|
+
FileUtils.rm_rf(recipe.builddir)
|
20
|
+
FileUtils.rm_rf(recipe.destdir)
|
21
|
+
end
|
22
|
+
|
23
|
+
def dispense
|
24
|
+
env = ENV.to_hash
|
25
|
+
|
26
|
+
# RecipeInspector.verify!(recipe)
|
27
|
+
# DependencyInspector.verify!(recipe.depends, recipe.build_depends)
|
28
|
+
|
29
|
+
recipe.installing = false
|
30
|
+
|
31
|
+
source = recipe.source_handler
|
32
|
+
|
33
|
+
recipe.cachedir.mkdir
|
34
|
+
Dir.chdir(recipe.cachedir) do
|
35
|
+
source.fetch
|
36
|
+
|
37
|
+
#check = Source::IntegrityCheck.new(source.filename, recipe.md5, Digest::MD5)
|
38
|
+
|
39
|
+
#if check.error?
|
40
|
+
# check.actual, check.expected, check.filename, check.digest
|
41
|
+
#end
|
42
|
+
end
|
43
|
+
|
44
|
+
recipe.builddir.mkdir
|
45
|
+
Dir.chdir(recipe.builddir) do
|
46
|
+
extracted_source = source.extract
|
47
|
+
|
48
|
+
Dir.chdir(extracted_source) do
|
49
|
+
#Source::Patches.new(recipe.patches).apply!
|
50
|
+
|
51
|
+
build_cookie = build_cookie_name("#{recipe.name}-#{recipe.version}")
|
52
|
+
|
53
|
+
if File.exists?(build_cookie)
|
54
|
+
STDERR.puts 'Skipping build (`cook clean` to rebuild)'
|
55
|
+
else
|
56
|
+
recipe.build and FileUtils.touch(build_cookie)
|
57
|
+
end
|
58
|
+
|
59
|
+
FileUtils.rm_rf(recipe.destdir)
|
60
|
+
recipe.destdir.mkdir
|
61
|
+
|
62
|
+
begin
|
63
|
+
recipe.installing = true
|
64
|
+
recipe.install
|
65
|
+
ensure
|
66
|
+
recipe.installing = false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
build_package(recipe, config)
|
72
|
+
ensure
|
73
|
+
# Make sure we reset the environment.
|
74
|
+
ENV.replace(env)
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_cookie_name(name)
|
78
|
+
(recipe.builddir/".build-cookie-#{name.gsub(/[^\w]/,'_')}").to_s
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_package(recipe, config)
|
82
|
+
recipe.pkgdir.mkdir
|
83
|
+
Dir.chdir(recipe.pkgdir) do
|
84
|
+
epoch, ver = recipe.version.split(':', 2)
|
85
|
+
if ver.nil?
|
86
|
+
ver, epoch = epoch, nil
|
87
|
+
end
|
88
|
+
|
89
|
+
# Build a version including vendor and revision.
|
90
|
+
vendor = config[:vendor] || recipe.vendor
|
91
|
+
vendor_rev = "#{vendor}#{recipe.revision}"
|
92
|
+
version = [ver, vendor_rev].join('+')
|
93
|
+
|
94
|
+
maintainer = recipe.maintainer || begin
|
95
|
+
username = `git config --get user.name`.strip
|
96
|
+
useremail = `git config --get user.email`.strip
|
97
|
+
raise 'Set maintainer name/email via `git config --global user.name <name>`' if username.empty?
|
98
|
+
"#{username} <#{useremail}>"
|
99
|
+
end
|
100
|
+
|
101
|
+
opts = [
|
102
|
+
'-n', recipe.name,
|
103
|
+
'-v', version,
|
104
|
+
'-t', 'deb',
|
105
|
+
'-s', 'dir',
|
106
|
+
'--url', recipe.homepage || recipe.url,
|
107
|
+
'-C', recipe.destdir,
|
108
|
+
'--maintainer', maintainer,
|
109
|
+
'--category', recipe.section || 'optional',
|
110
|
+
]
|
111
|
+
|
112
|
+
opts += [
|
113
|
+
'--epoch', epoch
|
114
|
+
] if epoch
|
115
|
+
|
116
|
+
opts += [
|
117
|
+
'--description', recipe.description.strip
|
118
|
+
] if recipe.description
|
119
|
+
|
120
|
+
opts += [
|
121
|
+
'--architecture', recipe.arch.to_s
|
122
|
+
] if recipe.arch
|
123
|
+
|
124
|
+
# if self.postinst
|
125
|
+
# postinst_file = Tempfile.open('postinst')
|
126
|
+
# postinst_file.puts(postinst)
|
127
|
+
# chmod 0755, postinst_file.path
|
128
|
+
# postinst_file.close
|
129
|
+
# opts += ['--post-install', postinst_file.path]
|
130
|
+
# end
|
131
|
+
# if self.postrm
|
132
|
+
# postrm_file = Tempfile.open('postrm')
|
133
|
+
# postrm_file.puts(postrm)
|
134
|
+
# chmod 0755, postrm_file.path
|
135
|
+
# postrm_file.close
|
136
|
+
# opts += ['--post-uninstall', postrm_file.path]
|
137
|
+
# end
|
138
|
+
|
139
|
+
%w[ depends exclude provides replaces conflicts config_files ].each do |type|
|
140
|
+
if recipe.send(type).any?
|
141
|
+
recipe.send(type).each do |dep|
|
142
|
+
opts += ["--#{type.gsub('_','-')}", dep]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
opts << '.'
|
148
|
+
|
149
|
+
STDERR.puts ['fpm', opts].flatten.inspect
|
150
|
+
safesystem(*['fpm', opts].flatten)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|