akabei 0.1.0

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.
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