akabei 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile +14 -0
- data/akabei.gemspec +29 -0
- data/bin/akabei +9 -0
- data/lib/akabei.rb +5 -0
- data/lib/akabei/abs.rb +60 -0
- data/lib/akabei/archive_utils.rb +75 -0
- data/lib/akabei/attr_path.rb +20 -0
- data/lib/akabei/builder.rb +108 -0
- data/lib/akabei/chroot_tree.rb +81 -0
- data/lib/akabei/cli.rb +172 -0
- data/lib/akabei/error.rb +4 -0
- data/lib/akabei/package.rb +174 -0
- data/lib/akabei/package_entry.rb +110 -0
- data/lib/akabei/package_info.rb +68 -0
- data/lib/akabei/repository.rb +156 -0
- data/lib/akabei/signer.rb +75 -0
- data/lib/akabei/thor_handler.rb +14 -0
- data/lib/akabei/version.rb +3 -0
- data/spec/akabei/abs_spec.rb +95 -0
- data/spec/akabei/archive_utils_spec.rb +44 -0
- data/spec/akabei/builder_spec.rb +129 -0
- data/spec/akabei/chroot_tree_spec.rb +108 -0
- data/spec/akabei/cli_spec.rb +5 -0
- data/spec/akabei/package_entry_spec.rb +70 -0
- data/spec/akabei/package_info_spec.rb +17 -0
- data/spec/akabei/package_spec.rb +54 -0
- data/spec/akabei/repository_spec.rb +157 -0
- data/spec/akabei/signer_spec.rb +5 -0
- data/spec/data/input/abs.tar.gz +0 -0
- data/spec/data/input/htop-vi.tar.gz +0 -0
- data/spec/data/input/makepkg.x86_64.conf +140 -0
- data/spec/data/input/nkf-2.1.3-1-x86_64.pkg.tar.xz +0 -0
- data/spec/data/input/nkf.PKGINFO +22 -0
- data/spec/data/input/nkf.tar.gz +0 -0
- data/spec/data/input/pacman.x86_64.conf +99 -0
- data/spec/data/input/ruby.PKGINFO +38 -0
- data/spec/data/input/test.db +0 -0
- data/spec/data/input/test.files +0 -0
- data/spec/integration/build_spec.rb +105 -0
- data/spec/spec_helper.rb +110 -0
- metadata +225 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 573c7770c6f4880b4805deecf432dd1d893435fa
|
4
|
+
data.tar.gz: 5ba638d8e424de0d86b570ee3f4060d15100d08d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a89b4bdce9f46c869a90e255f07d124bd3f27bdea251de7593a5d93e938532b07aab9aea16f882148e5e684ef0576b65ac0db5e125d551c7350094cb4cb200a9
|
7
|
+
data.tar.gz: b93ee03dffba2e814da2f38e6077b4fa4865776f658ada381af69df06afd7d1b165ddec623e34aab4a3755a0ef537b7c4aeafaf00fffa6145290f72c606758c4
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Kohei Suzuki
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Akabei
|
2
|
+
[![Build Status](https://secure.travis-ci.org/eagletmt/akabei.png)](https://travis-ci.org/eagletmt/akabei)
|
3
|
+
[![Code Climate](https://codeclimate.com/github/eagletmt/akabei.png)](https://codeclimate.com/github/eagletmt/akabei)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/eagletmt/akabei/badge.png)](https://coveralls.io/r/eagletmt/akabei)
|
5
|
+
|
6
|
+
Custom repository manager for ArchLinux pacman.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'akabei'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install akabei
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
### Build a package and store it to a repository
|
24
|
+
Basic usage: build foo x86_64 package for bar repository.
|
25
|
+
|
26
|
+
```
|
27
|
+
% ls foo
|
28
|
+
PKGBUILD
|
29
|
+
% mkdir -p repo/x86_64
|
30
|
+
% akabei build foo --repo-dir repo/x86_64 --repo-name bar --arch x86_64
|
31
|
+
(snip)
|
32
|
+
% ls repo/x86_64
|
33
|
+
bar.abs.tar.gz bar.db bar.files foo-1.0.0-1-x86_64.pkg.tar.xz
|
34
|
+
```
|
35
|
+
|
36
|
+
With full options:
|
37
|
+
```
|
38
|
+
% [ -z $GPG_AGENT_INFO ] && eval `gpg-agent --daemon`
|
39
|
+
% akabei build foo --chroot-dir /var/cache/akabei/chroot-x86_64 --repo-dir repo/x86_64 --repo-name bar --arch x86_64 --package-key $GPGKEY --repo-key $GPGKEY --pacman-config pacman.x86_64.conf --makepkg-config makepkg.x86_64.conf --srcdest sources --logdest logs
|
40
|
+
(snip)
|
41
|
+
% ls repo/x86_64
|
42
|
+
bar.abs.tar.gz bar.db bar.db.sig bar.files bar.files.sig foo-1.0.0-1-x86_64.pkg.tar.xz foo-1.0.0-1-x86_64.pkg.tar.xz.sig
|
43
|
+
% ls sources
|
44
|
+
foo-1.0.0.tar.gz
|
45
|
+
% ls logs
|
46
|
+
foo-1.0.0-1-x86_64-build.log foo-1.0.0-1-x86_64-package.log
|
47
|
+
```
|
48
|
+
|
49
|
+
## Contributing
|
50
|
+
|
51
|
+
1. Fork it ( https://github.com/eagletmt/akabei/fork )
|
52
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
53
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
54
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
55
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
task :default => :spec
|
4
|
+
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
namespace :spec do
|
8
|
+
task :set_arch do
|
9
|
+
ENV['AKABEI_ARCH_SPEC'] = '1'
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'Run RSpec examples including :arch. It requires sudo and devtools package.'
|
13
|
+
task :arch => %w[spec:set_arch spec]
|
14
|
+
end
|
data/akabei.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'akabei/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "akabei"
|
8
|
+
spec.version = Akabei::VERSION
|
9
|
+
spec.authors = ["Kohei Suzuki"]
|
10
|
+
spec.email = ["eagletmt@gmail.com"]
|
11
|
+
spec.summary = %q{Custom repository manager for ArchLinux pacman}
|
12
|
+
spec.description = %q{Custom repository manager for ArchLinux pacman}
|
13
|
+
spec.homepage = "https://github.com/eagletmt/akabei"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "gpgme"
|
22
|
+
spec.add_dependency "libarchive"
|
23
|
+
spec.add_dependency "thor"
|
24
|
+
spec.add_development_dependency "bundler"
|
25
|
+
spec.add_development_dependency "coveralls"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.0.0.beta1"
|
28
|
+
spec.add_development_dependency "simplecov"
|
29
|
+
end
|
data/bin/akabei
ADDED
data/lib/akabei.rb
ADDED
data/lib/akabei/abs.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'akabei/archive_utils'
|
2
|
+
require 'akabei/error'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'pathname'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
module Akabei
|
8
|
+
class Abs
|
9
|
+
def initialize(path, repo_name)
|
10
|
+
@path = Pathname.new(path)
|
11
|
+
@repo_name = repo_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(dir, builder)
|
15
|
+
builder.with_source_package(dir) do |srcpkg|
|
16
|
+
Dir.mktmpdir do |tree|
|
17
|
+
tree = Pathname.new(tree)
|
18
|
+
root = tree.join(@repo_name)
|
19
|
+
root.mkpath
|
20
|
+
if @path.readable?
|
21
|
+
ArchiveUtils.extract_all(@path, tree)
|
22
|
+
end
|
23
|
+
pkgname = detect_pkgname(srcpkg)
|
24
|
+
FileUtils.rm_rf(root.join(pkgname).to_s)
|
25
|
+
ArchiveUtils.extract_all(srcpkg, root)
|
26
|
+
FileUtils.rm_f(@path.to_s)
|
27
|
+
ArchiveUtils.archive_all(tree, @path, Archive::COMPRESSION_GZIP, Archive::FORMAT_TAR)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def detect_pkgname(srcpkg)
|
33
|
+
ArchiveUtils.each_entry(srcpkg) do |entry|
|
34
|
+
if entry.directory?
|
35
|
+
if m = entry.pathname.match(%r{\A([^/]+)/\z})
|
36
|
+
return m[1]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
raise Error.new("Cannot detect pkgname from #{srcpkg}")
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove(package_name)
|
44
|
+
unless @path.readable?
|
45
|
+
raise Error.new("No such file: #{@path}")
|
46
|
+
end
|
47
|
+
|
48
|
+
Dir.mktmpdir do |tree|
|
49
|
+
tree = Pathname.new(tree)
|
50
|
+
ArchiveUtils.extract_all(@path, tree)
|
51
|
+
root = tree.join(@repo_name)
|
52
|
+
unless root.directory?
|
53
|
+
raise Error.new("No such repository: #{@repo_name}")
|
54
|
+
end
|
55
|
+
FileUtils.rm_rf(root.join(package_name))
|
56
|
+
ArchiveUtils.archive_all(tree, @path, Archive::COMPRESSION_GZIP, Archive::FORMAT_TAR)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'libarchive'
|
2
|
+
|
3
|
+
module Akabei
|
4
|
+
module ArchiveUtils
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def each_entry(path, &block)
|
8
|
+
Archive.read_open_filename(path.to_s) do |archive|
|
9
|
+
while entry = archive.next_header
|
10
|
+
block.call(entry, archive)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def list_paths(path)
|
16
|
+
paths = []
|
17
|
+
each_entry(path) do |entry|
|
18
|
+
paths << entry.pathname
|
19
|
+
end
|
20
|
+
paths
|
21
|
+
end
|
22
|
+
|
23
|
+
BUFSIZ = 8192
|
24
|
+
|
25
|
+
def extract_all(src, dest)
|
26
|
+
each_entry(src) do |entry, archive|
|
27
|
+
path = dest.join(entry.pathname)
|
28
|
+
path.parent.mkpath
|
29
|
+
if entry.regular?
|
30
|
+
path.open('wb') do |f|
|
31
|
+
archive.read_data do |buf|
|
32
|
+
f.write(buf)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def archive_all(src, dest, comp, format)
|
40
|
+
Archive::Writer.open_filename(dest.to_s, comp, format) do |archive|
|
41
|
+
list_tree_paths(src).sort.each do |path|
|
42
|
+
archive.new_entry do |entry|
|
43
|
+
entry.pathname = path.relative_path_from(src).to_s
|
44
|
+
is_dir = path.directory?
|
45
|
+
if is_dir
|
46
|
+
entry.pathname += '/'
|
47
|
+
end
|
48
|
+
entry.copy_stat(path.to_s)
|
49
|
+
archive.write_header(entry)
|
50
|
+
unless is_dir
|
51
|
+
path.open do |f|
|
52
|
+
archive.write_data do
|
53
|
+
f.read(BUFSIZ)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def list_tree_paths(dir)
|
63
|
+
paths = []
|
64
|
+
q = dir.each_child.to_a
|
65
|
+
until q.empty?
|
66
|
+
path = q.shift
|
67
|
+
paths << path
|
68
|
+
if path.directory?
|
69
|
+
q += path.each_child.to_a
|
70
|
+
end
|
71
|
+
end
|
72
|
+
paths
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Akabei
|
4
|
+
module AttrPath
|
5
|
+
def attr_path_writer(*attrs)
|
6
|
+
attrs.each do |attr|
|
7
|
+
define_method("#{attr}=") do |val|
|
8
|
+
unless val.nil?
|
9
|
+
instance_variable_set("@#{attr}", Pathname.new(val))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def attr_path_accessor(*attrs)
|
16
|
+
attr_reader *attrs
|
17
|
+
attr_path_writer *attrs
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'akabei/attr_path'
|
2
|
+
require 'akabei/error'
|
3
|
+
require 'akabei/package'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
module Akabei
|
8
|
+
class Builder
|
9
|
+
extend AttrPath
|
10
|
+
attr_path_accessor :srcdest, :pkgdest, :logdest
|
11
|
+
attr_accessor :signer
|
12
|
+
|
13
|
+
def build_package(dir, chroot_tree)
|
14
|
+
Dir.mktmpdir do |tmp_pkgdest|
|
15
|
+
wrap_dir(:srcdest) do
|
16
|
+
wrap_dir(:logdest) do
|
17
|
+
tmp_pkgdest = Pathname.new(tmp_pkgdest)
|
18
|
+
env = {
|
19
|
+
SRCDEST: srcdest.realpath,
|
20
|
+
PKGDEST: tmp_pkgdest.realpath,
|
21
|
+
LOGDEST: logdest.realpath,
|
22
|
+
}
|
23
|
+
chroot_tree.makechrootpkg(dir.to_s, env)
|
24
|
+
gather_packages(tmp_pkgdest)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def gather_packages(tmp_pkgdest)
|
31
|
+
tmp_pkgdest.each_child.map do |package_path|
|
32
|
+
dest = pkgdest.join(package_path.basename)
|
33
|
+
copy_and_sign_package(package_path, dest)
|
34
|
+
Package.new(dest)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def copy_and_sign_package(package_path, dest)
|
39
|
+
FileUtils.cp(package_path.to_s, dest.to_s)
|
40
|
+
if signer
|
41
|
+
signer.detach_sign(dest)
|
42
|
+
end
|
43
|
+
rescue => e
|
44
|
+
begin
|
45
|
+
dest.unlink
|
46
|
+
rescue Errno::ENOENT
|
47
|
+
end
|
48
|
+
raise e
|
49
|
+
end
|
50
|
+
|
51
|
+
def wrap_dir(attr, &block)
|
52
|
+
old_dir = instance_variable_get("@#{attr}")
|
53
|
+
if old_dir.nil?
|
54
|
+
Dir.mktmpdir do |tmp|
|
55
|
+
dir = Pathname.new(tmp)
|
56
|
+
instance_variable_set("@#{attr}", dir)
|
57
|
+
# Must be world-executable because it is referred from inside chroot.
|
58
|
+
dir.chmod(0755)
|
59
|
+
block.call
|
60
|
+
end
|
61
|
+
else
|
62
|
+
block.call
|
63
|
+
end
|
64
|
+
ensure
|
65
|
+
instance_variable_set("@#{attr}", old_dir)
|
66
|
+
end
|
67
|
+
|
68
|
+
def with_source_package(dir, &block)
|
69
|
+
dir = Pathname.new(dir)
|
70
|
+
Dir.mktmpdir do |tmp_srcpkgdest|
|
71
|
+
tmp_srcpkgdest = Pathname.new(tmp_srcpkgdest)
|
72
|
+
Dir.mktmpdir do |builddir|
|
73
|
+
builddir = Pathname.new(builddir)
|
74
|
+
wrap_dir(:srcdest) do
|
75
|
+
makepkg_source(dir, srcdest, tmp_srcpkgdest, builddir)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
srcpkg = find_source_package(tmp_srcpkgdest)
|
80
|
+
# Remove symlink created by makepkg
|
81
|
+
dir.join(srcpkg.basename).unlink
|
82
|
+
block.call(srcpkg)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def makepkg_source(dir, srcdest, srcpkgdest, builddir)
|
87
|
+
env = {
|
88
|
+
'SRCDEST' => srcdest.realpath.to_s,
|
89
|
+
'SRCPKGDEST' => srcpkgdest.realpath.to_s,
|
90
|
+
'BUILDDIR' => builddir.realpath.to_s,
|
91
|
+
}
|
92
|
+
unless system(env, 'makepkg', '--source', chdir: dir)
|
93
|
+
raise Error.new("makepkg --source failed: #{dir}")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def find_source_package(srcpkgdest)
|
98
|
+
children = srcpkgdest.each_child.to_a
|
99
|
+
if children.empty?
|
100
|
+
raise Error.new("makepkg --source generated nothing")
|
101
|
+
elsif children.size > 1
|
102
|
+
raise Error.new("makepkg --source generated multiple files???: #{children.map(&:to_s)}")
|
103
|
+
else
|
104
|
+
children.first
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|