fpm-cookery 0.0.1
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.
- 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
|