akabei 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +10 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +55 -0
  8. data/Rakefile +14 -0
  9. data/akabei.gemspec +29 -0
  10. data/bin/akabei +9 -0
  11. data/lib/akabei.rb +5 -0
  12. data/lib/akabei/abs.rb +60 -0
  13. data/lib/akabei/archive_utils.rb +75 -0
  14. data/lib/akabei/attr_path.rb +20 -0
  15. data/lib/akabei/builder.rb +108 -0
  16. data/lib/akabei/chroot_tree.rb +81 -0
  17. data/lib/akabei/cli.rb +172 -0
  18. data/lib/akabei/error.rb +4 -0
  19. data/lib/akabei/package.rb +174 -0
  20. data/lib/akabei/package_entry.rb +110 -0
  21. data/lib/akabei/package_info.rb +68 -0
  22. data/lib/akabei/repository.rb +156 -0
  23. data/lib/akabei/signer.rb +75 -0
  24. data/lib/akabei/thor_handler.rb +14 -0
  25. data/lib/akabei/version.rb +3 -0
  26. data/spec/akabei/abs_spec.rb +95 -0
  27. data/spec/akabei/archive_utils_spec.rb +44 -0
  28. data/spec/akabei/builder_spec.rb +129 -0
  29. data/spec/akabei/chroot_tree_spec.rb +108 -0
  30. data/spec/akabei/cli_spec.rb +5 -0
  31. data/spec/akabei/package_entry_spec.rb +70 -0
  32. data/spec/akabei/package_info_spec.rb +17 -0
  33. data/spec/akabei/package_spec.rb +54 -0
  34. data/spec/akabei/repository_spec.rb +157 -0
  35. data/spec/akabei/signer_spec.rb +5 -0
  36. data/spec/data/input/abs.tar.gz +0 -0
  37. data/spec/data/input/htop-vi.tar.gz +0 -0
  38. data/spec/data/input/makepkg.x86_64.conf +140 -0
  39. data/spec/data/input/nkf-2.1.3-1-x86_64.pkg.tar.xz +0 -0
  40. data/spec/data/input/nkf.PKGINFO +22 -0
  41. data/spec/data/input/nkf.tar.gz +0 -0
  42. data/spec/data/input/pacman.x86_64.conf +99 -0
  43. data/spec/data/input/ruby.PKGINFO +38 -0
  44. data/spec/data/input/test.db +0 -0
  45. data/spec/data/input/test.files +0 -0
  46. data/spec/integration/build_spec.rb +105 -0
  47. data/spec/spec_helper.rb +110 -0
  48. metadata +225 -0
@@ -0,0 +1,81 @@
1
+ require 'akabei/attr_path'
2
+ require 'akabei/error'
3
+ require 'pathname'
4
+ require 'tmpdir'
5
+
6
+ module Akabei
7
+ class ChrootTree
8
+ class CommandFailed < Error
9
+ attr_reader :args
10
+ def initialize(args)
11
+ super("command failed: #{args.join(' ')}")
12
+ @args = args
13
+ end
14
+ end
15
+
16
+ extend AttrPath
17
+ attr_path_accessor :makepkg_config, :pacman_config
18
+
19
+ def initialize(root, arch)
20
+ @root = root && Pathname.new(root)
21
+ @arch = arch
22
+ end
23
+
24
+ BASE_PACKAGES = %w[base base-devel sudo]
25
+
26
+ def with_chroot(&block)
27
+ if @root
28
+ mkarchroot(*BASE_PACKAGES)
29
+ block.call
30
+ else
31
+ @root = Pathname.new(Dir.mktmpdir)
32
+ begin
33
+ mkarchroot(*BASE_PACKAGES)
34
+ block.call
35
+ ensure
36
+ execute('rm', '-rf', @root.to_s)
37
+ @root = nil
38
+ end
39
+ end
40
+ end
41
+
42
+ def makechrootpkg(dir, env)
43
+ execute('makechrootpkg', '-cur', @root.to_s, chdir: dir, env: env)
44
+ end
45
+
46
+ def mkarchroot(*args)
47
+ cmd = ['mkarchroot']
48
+ [['-M', makepkg_config], ['-C', pacman_config]].each do |flag, path|
49
+ if path
50
+ cmd << flag << path.to_s
51
+ end
52
+ end
53
+ cmd << @root.join('root').to_s
54
+ execute(*(cmd + args))
55
+ end
56
+
57
+ def arch_nspawn(*args)
58
+ execute('arch-nspawn', @root.join('root').to_s, *args)
59
+ end
60
+
61
+ def execute(*args)
62
+ if args.last.is_a?(Hash)
63
+ opts = args.last
64
+ if opts.has_key?(:env)
65
+ opts = opts.dup
66
+ env = opts.delete(:env)
67
+ env.each do |k, v|
68
+ args.unshift("#{k}=#{v}")
69
+ end
70
+ args.unshift('env')
71
+ args[-1] = opts
72
+ end
73
+ end
74
+
75
+ puts "Execute: #{args.join(' ')}"
76
+ unless system('sudo', 'setarch', @arch, *args)
77
+ raise CommandFailed.new(args)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,172 @@
1
+ require 'akabei/abs'
2
+ require 'akabei/builder'
3
+ require 'akabei/chroot_tree'
4
+ require 'akabei/package'
5
+ require 'akabei/repository'
6
+ require 'akabei/signer'
7
+ require 'pathname'
8
+ require 'thor'
9
+ require 'tmpdir'
10
+
11
+ module Akabei
12
+ class CLI < Thor
13
+ module CommonOptions
14
+ COMMON_OPTIONS = {
15
+ repo_key: {
16
+ desc: 'GPG key to sign repository database',
17
+ banner: 'GPGKEY',
18
+ type: :string
19
+ },
20
+ repo_name: {
21
+ desc: 'Name of the repository',
22
+ banner: 'NAME',
23
+ type: :string,
24
+ required: true,
25
+ },
26
+ srcdest: {
27
+ desc: 'Path to the directory to store sources',
28
+ banner: 'FILE',
29
+ type: :string,
30
+ },
31
+ }.freeze
32
+
33
+ def common_options(*opts)
34
+ opts.each do |opt|
35
+ unless COMMON_OPTIONS.has_key?(opt)
36
+ raise "No such common option: #{opt}"
37
+ end
38
+ option(opt, COMMON_OPTIONS[opt])
39
+ end
40
+ end
41
+ end
42
+ extend CommonOptions
43
+
44
+ desc 'build DIR', 'Build package in chroot environment'
45
+ option :chroot_dir,
46
+ desc: 'Path to chroot top',
47
+ banner: 'DIR',
48
+ type: :string
49
+ option :makepkg_config,
50
+ desc: 'Path to makepkg.conf used in chroot',
51
+ banner: 'FILE',
52
+ type: :string
53
+ option :pacman_config,
54
+ desc: 'Path to pacman.conf used in chroot',
55
+ banner: 'FILE',
56
+ type: :string
57
+ option :package_key,
58
+ desc: 'GPG key to sign packages',
59
+ banner: 'GPGKEY',
60
+ type: :string
61
+ option :logdest,
62
+ desc: 'Path to the directory to store logs',
63
+ banner: 'FILE',
64
+ type: :string
65
+ option :repo_dir,
66
+ desc: 'Path to the repository',
67
+ banner: 'DIR',
68
+ type: :string,
69
+ required: true
70
+ option :arch,
71
+ desc: 'Archtecture',
72
+ banner: 'ARCH',
73
+ enum: %w[i686 x86_64],
74
+ required: true
75
+ common_options :repo_name, :repo_key, :srcdest
76
+ def build(package_dir)
77
+ chroot = ChrootTree.new(options[:chroot_dir], options[:arch])
78
+ if options[:makepkg_config]
79
+ chroot.makepkg_config = options[:makepkg_config]
80
+ end
81
+ if options[:pacman_config]
82
+ chroot.pacman_config = options[:pacman_config]
83
+ end
84
+
85
+ repo_db = Repository.new
86
+ repo_db.signer = options[:repo_key] && Signer.new(options[:repo_key])
87
+ repo_files = Repository.new
88
+ repo_files.include_files = true
89
+
90
+ builder = Builder.new
91
+ builder.signer = options[:package_key] && Signer.new(options[:package_key])
92
+ builder.srcdest = options[:srcdest]
93
+ builder.logdest = options[:logdest]
94
+
95
+ repo_path = Pathname.new(options[:repo_dir])
96
+ repo_name = options[:repo_name]
97
+ builder.pkgdest = repo_path
98
+
99
+ db_path = repo_path.join("#{repo_name}.db")
100
+ files_path = repo_path.join("#{repo_name}.files")
101
+ repo_db.load(db_path)
102
+ repo_files.load(files_path)
103
+
104
+ abs = Abs.new(repo_path.join("#{repo_name}.abs.tar.gz"), repo_name)
105
+
106
+ chroot.with_chroot do
107
+ packages = builder.build_package(package_dir, chroot)
108
+ packages.each do |package|
109
+ repo_db.add(package)
110
+ repo_files.add(package)
111
+ end
112
+ abs.add(package_dir, builder)
113
+ repo_db.save(db_path)
114
+ repo_files.save(files_path)
115
+ end
116
+ end
117
+
118
+ desc 'abs-add DIR ABS_TARBALL', 'Add the package inside DIR to ABS_TARBALL'
119
+ common_options :repo_name, :srcdest
120
+ def abs_add(package_dir, abs_path)
121
+ builder = Builder.new
122
+ builder.srcdest = options[:srcdest]
123
+ abs = Abs.new(abs_path, options[:repo_name])
124
+ abs.add(package_dir, builder)
125
+ end
126
+
127
+ desc 'abs-remove PKG_NAME ABS_TARBALL', 'Remove PKG_NAME from ABS_TARBALL'
128
+ common_options :repo_name
129
+ def abs_remove(package_name, abs_path)
130
+ abs = Abs.new(abs_path, options[:repo_name])
131
+ abs.remove(package_name)
132
+ end
133
+
134
+ desc 'repo-add PACKAGE_PATH REPOSITORY_DB', 'Add PACKAGE_PATH to REPOSITORY_DB'
135
+ common_options :repo_key
136
+ def repo_add(package_path, db_path)
137
+ repo = Repository.new
138
+ repo.signer = options[:repo_key] && Signer.new(options[:repo_key])
139
+ repo.load(db_path)
140
+ repo.add(Package.new(package_path))
141
+ repo.save(db_path)
142
+ end
143
+
144
+ desc 'repo-remove PACKAGE_NAME REPOSITORY_DB', 'Remove PACKAGE_NAME from REPOSITORY_DB'
145
+ common_options :repo_key
146
+ def repo_remove(package_name, db_path)
147
+ repo = Repository.new
148
+ repo.signer = options[:repo_key] && Signer.new(options[:repo_key])
149
+ repo.load(db_path)
150
+ repo.remove(package_name)
151
+ repo.save(db_path)
152
+ end
153
+
154
+ desc 'files-add PACKAGE_PATH FILES_DB', 'Add PACKAGE_PATH to FILES_DB'
155
+ def files_add(package_path, db_path)
156
+ repo = Repository.new
157
+ repo.include_files = true
158
+ repo.load(db_path)
159
+ repo.add(Package.new(package_path))
160
+ repo.save(db_path)
161
+ end
162
+
163
+ desc 'files-remove PACKAGE_NAME FILES_DB', 'Remove PACKAGE_NAME from FILES_DB'
164
+ def files_remove(package_name, db_path)
165
+ repo = Repository.new
166
+ repo.include_files = true
167
+ repo.load(db_path)
168
+ repo.remove(package_name)
169
+ repo.save(db_path)
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,4 @@
1
+ module Akabei
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,174 @@
1
+ require 'akabei/archive_utils'
2
+ require 'akabei/error'
3
+ require 'akabei/package_entry'
4
+ require 'akabei/package_info'
5
+ require 'base64'
6
+ require 'digest'
7
+
8
+ module Akabei
9
+ class Package
10
+ class NotFound < Akabei::Error
11
+ attr_reader :path, :archive
12
+
13
+ def initialize(archive, path)
14
+ super("#{path} is not found in #{archive}")
15
+ @path = path
16
+ @archive = archive
17
+ end
18
+ end
19
+
20
+ attr_reader :path
21
+
22
+ def initialize(path)
23
+ @path = Pathname.new(path)
24
+ end
25
+
26
+ def pkginfo
27
+ @pkginfo ||= extract_pkginfo
28
+ end
29
+
30
+ def extract_pkginfo
31
+ ArchiveUtils.each_entry(@path.to_s) do |entry, archive|
32
+ if entry.pathname == '.PKGINFO'
33
+ return PackageInfo.parse(archive.read_data)
34
+ end
35
+ end
36
+ raise NotFound.new(@path, '.PKGINFO')
37
+ end
38
+
39
+ def db_name
40
+ "#{pkgname}-#{pkgver}"
41
+ end
42
+
43
+ def csize
44
+ @path.size
45
+ end
46
+
47
+ def isize
48
+ pkginfo.size
49
+ end
50
+
51
+ def md5sum
52
+ @md5sum ||= compute_checksum('MD5')
53
+ end
54
+
55
+ def sha256sum
56
+ @sha256sum ||= compute_checksum('SHA256')
57
+ end
58
+
59
+ def compute_checksum(algo)
60
+ Digest(algo).file(@path).hexdigest
61
+ end
62
+
63
+ %w[
64
+ pkgname
65
+ pkgver
66
+ pkgbase
67
+ pkgdesc
68
+ url
69
+ license
70
+ arch
71
+ builddate
72
+ packager
73
+ replaces
74
+
75
+ provides
76
+ ].each do |attr|
77
+ define_method(attr) do
78
+ pkginfo.public_send(attr)
79
+ end
80
+ end
81
+
82
+ %w[
83
+ group
84
+ depend
85
+ conflict
86
+ optdepend
87
+ makedepend
88
+ checkdepend
89
+ ].each do |attr|
90
+ define_method("#{attr}s") do
91
+ pkginfo.public_send(attr)
92
+ end
93
+ end
94
+
95
+ def filename
96
+ @path.basename.to_s
97
+ end
98
+
99
+ def name
100
+ pkginfo.pkgname
101
+ end
102
+
103
+ def base
104
+ pkginfo.pkgbase
105
+ end
106
+
107
+ def version
108
+ pkginfo.pkgver
109
+ end
110
+
111
+ def desc
112
+ pkginfo.pkgdesc
113
+ end
114
+
115
+ def pgpsig
116
+ sig_file = @path.parent.join("#{filename}.sig")
117
+ if sig_file.readable?
118
+ Base64.strict_encode64(sig_file.binread)
119
+ end
120
+ end
121
+
122
+ def files
123
+ xs = []
124
+ ArchiveUtils.each_entry(@path) do |entry|
125
+ unless entry.pathname.start_with?('.')
126
+ xs << entry.pathname
127
+ end
128
+ end
129
+ xs.sort
130
+ end
131
+
132
+ def to_entry
133
+ entry = PackageEntry.new
134
+ %w[
135
+ filename
136
+ name
137
+ base
138
+ version
139
+ desc
140
+ groups
141
+ csize
142
+ isize
143
+
144
+ md5sum
145
+ sha256sum
146
+
147
+ pgpsig
148
+
149
+ url
150
+ license
151
+ builddate
152
+ packager
153
+ replaces
154
+
155
+ provides
156
+ optdepends
157
+ makedepends
158
+ checkdepends
159
+
160
+ files
161
+ ].each do |attr|
162
+ val = send(attr)
163
+ if val.is_a?(Array)
164
+ val.each do |v|
165
+ entry.add(attr, v)
166
+ end
167
+ else
168
+ entry.add(attr, val)
169
+ end
170
+ end
171
+ entry
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,110 @@
1
+ require 'akabei/error'
2
+
3
+ module Akabei
4
+ class PackageEntry
5
+ ARRAY_DESC_ATTRIBUTES = %w[
6
+ groups
7
+ license
8
+ replaces
9
+ ].freeze
10
+ DESC_ATTRIBUTES = %w[
11
+ filename
12
+ name
13
+ base
14
+ version
15
+ desc
16
+ csize
17
+ isize
18
+ md5sum
19
+ sha256sum
20
+ pgpsig
21
+ url
22
+ arch
23
+ builddate
24
+ packager
25
+ ].freeze
26
+ ARRAY_DEPENDS_ATTRIBUTES = %w[
27
+ depends
28
+ conflicts
29
+ provides
30
+ optdepends
31
+ makedepends
32
+ checkdepends
33
+ ].freeze
34
+ ARRAY_FILES_ATTRIBUTES = %w[files].freeze
35
+
36
+ ARRAY_ATTRIBUTES = (ARRAY_DESC_ATTRIBUTES + ARRAY_DEPENDS_ATTRIBUTES + ARRAY_FILES_ATTRIBUTES).freeze
37
+
38
+ attr_reader *ARRAY_ATTRIBUTES
39
+ attr_reader *DESC_ATTRIBUTES
40
+
41
+ def initialize
42
+ ARRAY_ATTRIBUTES.each do |key|
43
+ instance_variable_set("@#{key}", [])
44
+ end
45
+ end
46
+
47
+ def add(key, val)
48
+ ivar = "@#{key}".intern
49
+ if ARRAY_ATTRIBUTES.include?(key)
50
+ instance_variable_get(ivar) << val
51
+ elsif DESC_ATTRIBUTES.include?(key)
52
+ if v = instance_variable_get(ivar)
53
+ raise Error.new("Multiple entry found: #{v} and #{val}")
54
+ else
55
+ instance_variable_set(ivar, val)
56
+ end
57
+ else
58
+ raise Error.new("Unknown entry key: #{key}")
59
+ end
60
+ end
61
+
62
+ def ==(other)
63
+ (ARRAY_ATTRIBUTES + DESC_ATTRIBUTES).all? do |attr|
64
+ public_send(attr) == other.public_send(attr)
65
+ end
66
+ end
67
+
68
+ def write_desc(io)
69
+ ARRAY_DESC_ATTRIBUTES.each do |attr|
70
+ write_array(io, attr)
71
+ end
72
+
73
+ DESC_ATTRIBUTES.each do |attr|
74
+ write_string(io, attr)
75
+ end
76
+ end
77
+
78
+ def write_depends(io)
79
+ ARRAY_DEPENDS_ATTRIBUTES.each do |attr|
80
+ write_array(io, attr)
81
+ end
82
+ end
83
+
84
+ def write_files(io)
85
+ unless write_array(io, 'files')
86
+ warn 'WARNING: files attribute is empty.'
87
+ end
88
+ end
89
+
90
+ def write_array(io, attr)
91
+ arr = instance_variable_get("@#{attr}")
92
+ unless arr.empty?
93
+ write_entry(io, attr, arr)
94
+ end
95
+ end
96
+
97
+ def write_string(io, attr)
98
+ if v = instance_variable_get("@#{attr}")
99
+ write_entry(io, attr, v)
100
+ end
101
+ end
102
+
103
+ def write_entry(io, attr, val)
104
+ io.puts "%#{attr.upcase}%"
105
+ io.puts val
106
+ io.puts
107
+ true
108
+ end
109
+ end
110
+ end