aur.rb 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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.yardopts +6 -0
- data/ChangeLog.md +4 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +31 -0
- data/Rakefile +29 -0
- data/aur.rb.gemspec +69 -0
- data/gemspec.yml +17 -0
- data/lib/aur.rb +1861 -0
- data/lib/aur/version.rb +4 -0
- data/test/helper.rb +13 -0
- data/test/test_aur.rb +12 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c21f4da799946a33333f0361e3ad2b92a191fa2519f8ffdfbdf61ac9c7322002
|
4
|
+
data.tar.gz: e59e5616ccaa0d8678eb17da201c85f79406b1d1b8fc981344b61d571b0240d0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fda385190d3693a71127bf4866b7ceae8ccade180cbb9101c1499d7c08b9a5dff7302fe0a2ec222a0e5e3d3af5609f7754b557cac6b2e75ba5ccd6798b6bbb84
|
7
|
+
data.tar.gz: 2532269251f3a31201c0ba6418a53b52d5d3600acc84359ab07022c1aa370ff699d4c7156a832b2f3f003c809d2bf4824a136680a34886a38e4f8635b5371e9c
|
data/.gitignore
ADDED
data/.yardopts
ADDED
data/ChangeLog.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2018 Damien Robert
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# aur.rb
|
2
|
+
|
3
|
+
* [Homepage](https://github.com/DamienRobert/aur.rb#readme)
|
4
|
+
* [Issues](https://github.com/DamienRobert/aur.rb/issues)
|
5
|
+
* [Documentation](http://rubydoc.info/gems/aur.rb)
|
6
|
+
* [Email](mailto:Damien.Olivier.Robert+gems at gmail.com)
|
7
|
+
|
8
|
+
[](https://rubygems.org/gems/aur.rb)
|
9
|
+
|
10
|
+
## Description
|
11
|
+
|
12
|
+
A set of utilities to handle archlinux packages databases and aur
|
13
|
+
installation.
|
14
|
+
|
15
|
+
## Features
|
16
|
+
|
17
|
+
## Examples
|
18
|
+
|
19
|
+
require 'aur.rb'
|
20
|
+
|
21
|
+
## Requirements
|
22
|
+
|
23
|
+
## Install
|
24
|
+
|
25
|
+
$ gem install aur.rb
|
26
|
+
|
27
|
+
## Copyright
|
28
|
+
|
29
|
+
Copyright © 2018 Damien Robert
|
30
|
+
|
31
|
+
MIT License. See [LICENSE.txt](./LICENSE.txt) for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rubygems/tasks'
|
5
|
+
Gem::Tasks.new(sign: {checksum: true, pgp: true},
|
6
|
+
scm: {status: true}) do |tasks|
|
7
|
+
tasks.console.command = 'pry'
|
8
|
+
end
|
9
|
+
rescue LoadError => e
|
10
|
+
warn e.message
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'rake/testtask'
|
14
|
+
Rake::TestTask.new do |test|
|
15
|
+
test.libs << 'test'
|
16
|
+
test.pattern = 'test/**/test_*.rb'
|
17
|
+
test.verbose = true
|
18
|
+
end
|
19
|
+
|
20
|
+
begin
|
21
|
+
require 'yard'
|
22
|
+
YARD::Rake::YardocTask.new
|
23
|
+
rescue LoadError => e
|
24
|
+
task :yard do
|
25
|
+
warn e.message
|
26
|
+
end
|
27
|
+
end
|
28
|
+
task :doc => :yard
|
29
|
+
|
data/aur.rb.gemspec
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gemspec = YAML.load_file('gemspec.yml')
|
5
|
+
|
6
|
+
gem.name = gemspec.fetch('name')
|
7
|
+
gem.version = gemspec.fetch('version') do
|
8
|
+
lib_dir = File.join(File.dirname(__FILE__),'lib')
|
9
|
+
$LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
|
10
|
+
|
11
|
+
require 'aur/version'
|
12
|
+
Archlinux::VERSION
|
13
|
+
end
|
14
|
+
|
15
|
+
gem.summary = gemspec['summary']
|
16
|
+
gem.description = gemspec['description']
|
17
|
+
gem.licenses = Array(gemspec['license'])
|
18
|
+
gem.authors = Array(gemspec['authors'])
|
19
|
+
gem.email = gemspec['email']
|
20
|
+
gem.homepage = gemspec['homepage']
|
21
|
+
|
22
|
+
glob = lambda { |patterns| gem.files & Dir[*patterns] }
|
23
|
+
|
24
|
+
gem.files = `git ls-files`.split($/)
|
25
|
+
|
26
|
+
`git submodule --quiet foreach --recursive pwd`.split($/).each do |submodule|
|
27
|
+
submodule.sub!("#{Dir.pwd}/",'')
|
28
|
+
|
29
|
+
Dir.chdir(submodule) do
|
30
|
+
`git ls-files`.split($/).map do |subpath|
|
31
|
+
gem.files << File.join(submodule,subpath)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
gem.files = glob[gemspec['files']] if gemspec['files']
|
36
|
+
|
37
|
+
gem.executables = gemspec.fetch('executables') do
|
38
|
+
glob['bin/*'].map { |path| File.basename(path) }
|
39
|
+
end
|
40
|
+
gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
|
41
|
+
|
42
|
+
gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
|
43
|
+
gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
|
44
|
+
|
45
|
+
gem.require_paths = Array(gemspec.fetch('require_paths') {
|
46
|
+
%w[ext lib].select { |dir| File.directory?(dir) }
|
47
|
+
})
|
48
|
+
|
49
|
+
gem.requirements = Array(gemspec['requirements'])
|
50
|
+
gem.required_ruby_version = gemspec['required_ruby_version']
|
51
|
+
gem.required_rubygems_version = gemspec['required_rubygems_version']
|
52
|
+
gem.post_install_message = gemspec['post_install_message']
|
53
|
+
|
54
|
+
split = lambda { |string| string.split(/,\s*/) }
|
55
|
+
|
56
|
+
if gemspec['dependencies']
|
57
|
+
gemspec['dependencies'].each do |name,versions|
|
58
|
+
gem.add_dependency(name,split[versions])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if gemspec['development_dependencies']
|
63
|
+
gemspec['development_dependencies'].each do |name,versions|
|
64
|
+
gem.add_development_dependency(name,split[versions])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
gem.metadata['yard.run']='yri'
|
69
|
+
end
|
data/gemspec.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
name: aur.rb
|
2
|
+
summary: "Set of archlinux and aur utilities"
|
3
|
+
description: "A set of utilities to handle archlinux packages databases and aur
|
4
|
+
installation."
|
5
|
+
license: MIT
|
6
|
+
authors: Damien Robert
|
7
|
+
email: Damien.Olivier.Robert+gems@gmail.com
|
8
|
+
homepage: https://github.com/DamienRobert/aur.rb#readme
|
9
|
+
|
10
|
+
dependencies:
|
11
|
+
drain: ~> 0.3
|
12
|
+
shell_helpers: ~> 0.3
|
13
|
+
development_dependencies:
|
14
|
+
minitest: "~> 5.0"
|
15
|
+
rake: "~> 10"
|
16
|
+
rubygems-tasks: "~> 0.2"
|
17
|
+
yard: "~> 0.9"
|
data/lib/aur.rb
ADDED
@@ -0,0 +1,1861 @@
|
|
1
|
+
require 'aur/version'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
require 'time'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'tsort'
|
7
|
+
require 'dr/base/utils'
|
8
|
+
require 'shell_helpers'
|
9
|
+
|
10
|
+
module Archlinux
|
11
|
+
ArchlinuxError=Class.new(StandardError)
|
12
|
+
Utils=::DR::Utils
|
13
|
+
Pathname=::SH::Pathname
|
14
|
+
|
15
|
+
def self.delegate_h(klass, var)
|
16
|
+
klass.extend(Forwardable)
|
17
|
+
methods=[:[], :[]=, :any?, :assoc, :clear, :compact, :compact!, :delete, :delete_if, :dig, :each, :each_key, :each_pair, :each_value, :empty?, :fetch, :fetch_values, :has_key?, :has_value?, :include?, :index, :invert, :keep_if, :key, :key?, :keys, :length, :member?, :merge, :merge!, :rassoc, :reject, :reject!, :select, :select!, :shift, :size, :slice, :store, :to_a, :to_h, :to_s, :transform_keys, :transform_keys!, :transform_values, :transform_values!, :update, :value?, :values, :values_at]
|
18
|
+
klass.include(Enumerable)
|
19
|
+
klass.send(:def_delegators, var, *methods)
|
20
|
+
end
|
21
|
+
|
22
|
+
class Config
|
23
|
+
def self.create(v)
|
24
|
+
v.is_a?(self) ? v : self.new(v)
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_accessor :opts
|
28
|
+
Archlinux.delegate_h(self, :@opts)
|
29
|
+
include SH::ShConfig
|
30
|
+
|
31
|
+
def initialize(file, **opts)
|
32
|
+
file=Pathname.new(file)
|
33
|
+
file=Pathname.new(ENV["XDG_CONFIG_HOME"] || "#{ENV['HOME']}/.config") + file if file.relative?
|
34
|
+
file_config= file.readable? ? file.read : '{}'
|
35
|
+
wrap=eval("Proc.new { |context| #{file_config} }")
|
36
|
+
@opts=default_config.merge(opts)
|
37
|
+
@opts.merge!(wrap.call(self))
|
38
|
+
end
|
39
|
+
|
40
|
+
def sh_config
|
41
|
+
@opts[:sh_config]
|
42
|
+
end
|
43
|
+
|
44
|
+
def default_config
|
45
|
+
{
|
46
|
+
cache: "arch_aur", #where we dl PKGBUILDs
|
47
|
+
db: 'aur', #if relative the db will be in cachedir
|
48
|
+
aur_url: "https://aur.archlinux.org/", #base aur url
|
49
|
+
chroot: "/var/lib/aurbuild/x86_64", #chroot root
|
50
|
+
build_chroot: false, #do we use the chroot?
|
51
|
+
chroot_update: 'pacman -Syu --noconfirm', #how to update an existing chroot
|
52
|
+
sign: true, #can be made more atomic, cf the sign method
|
53
|
+
devtools_pacman: "/usr/share/devtools/pacman-extra.conf", #pacman.conf for chroot build
|
54
|
+
pacman_conf: "/etc/pacman.conf", #default pacman-conf (for makepkg build)
|
55
|
+
sh_config: { #default programs options
|
56
|
+
makepkg: {default_opts: ["-crs", "--needed"]},
|
57
|
+
makechrootpkg: {default_opts: ["-cu"]},
|
58
|
+
},
|
59
|
+
view: "vifm -c view! -c tree -c 0",
|
60
|
+
git_update: "git pull",
|
61
|
+
git_clone: "git clone",
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
#:makepkg, :makechrootpkg, :repo (=repo-add, repo-remove)
|
66
|
+
def sign(mode)
|
67
|
+
opt_sign=@opts[:sign]
|
68
|
+
if opt_sign.is_a?(Hash)
|
69
|
+
opt_sign[mode]
|
70
|
+
else
|
71
|
+
opt_sign
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def view(dir)
|
76
|
+
view=@opts[:view]
|
77
|
+
case view
|
78
|
+
when Proc
|
79
|
+
view.call(dir)
|
80
|
+
else
|
81
|
+
success, _rest=SH.sh("#{view} #{dir.shellescape}")
|
82
|
+
return success
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def git_update
|
87
|
+
method=@opts[:git_update]
|
88
|
+
case method
|
89
|
+
when Proc
|
90
|
+
method.call(dir)
|
91
|
+
else
|
92
|
+
success, _rest=SH.sh(method)
|
93
|
+
return success
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def git_clone(url, dir)
|
98
|
+
method=@opts[:git_clone]
|
99
|
+
case method
|
100
|
+
when Proc
|
101
|
+
method.call(url, dir)
|
102
|
+
else
|
103
|
+
success, _rest=SH.sh("#{method} #{url.shellescape} #{dir.shellescape}")
|
104
|
+
return success
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def cachedir
|
109
|
+
global_cache=Pathname.new(ENV["XDG_CACHE_HOME"] || "#{ENV['HOME']}/.cache")
|
110
|
+
cache=global_cache+@opts[:cache]
|
111
|
+
cache.mkpath
|
112
|
+
cache
|
113
|
+
end
|
114
|
+
|
115
|
+
def setup_pacman_conf(conf)
|
116
|
+
pacman=PacmanConf.create(conf)
|
117
|
+
aur=self.db(false)
|
118
|
+
if aur and !pacman[:repos].include?(aur.repo_name)
|
119
|
+
pacman[:repos][aur.repo_name]={Server: ["file://#{URI.escape(aur.dir.to_s)}"]}
|
120
|
+
end
|
121
|
+
pacman
|
122
|
+
end
|
123
|
+
|
124
|
+
def default_pacman_conf
|
125
|
+
@default_pacman_conf ||= if (conf=@opts[:pacman_conf])
|
126
|
+
PacmanConf.new(conf)
|
127
|
+
else
|
128
|
+
PacmanConf
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def devtools
|
133
|
+
unless @devtools_config
|
134
|
+
require 'uri'
|
135
|
+
# here we need the raw value, since this will be used by pacstrap
|
136
|
+
# which calls pacman --root; so the inferred path for DBPath and so
|
137
|
+
# on would not be correct since it is specified
|
138
|
+
devtools_pacman=PacmanConf.new(@opts[:devtools_pacman], raw: true)
|
139
|
+
# here we need to expand the config, so that Server =
|
140
|
+
# file://...$repo...$arch get their real values
|
141
|
+
my_pacman=default_pacman_conf
|
142
|
+
devtools_pacman[:repos].merge!(my_pacman.non_official_repos)
|
143
|
+
setup_pacman_conf(devtools_pacman)
|
144
|
+
@devtools_config = Devtools.new(pacman_conf: devtools_pacman)
|
145
|
+
end
|
146
|
+
@devtools_config
|
147
|
+
end
|
148
|
+
|
149
|
+
def makepkg_config
|
150
|
+
unless @makepkg_config
|
151
|
+
makepkg_pacman=default_pacman_conf
|
152
|
+
setup_pacman_conf(makepkg_pacman)
|
153
|
+
@makepkg_config = Devtools.new(pacman_conf: makepkg_pacman)
|
154
|
+
end
|
155
|
+
@makepkg_config
|
156
|
+
end
|
157
|
+
|
158
|
+
def db=(name)
|
159
|
+
case name
|
160
|
+
when DB
|
161
|
+
@db=name
|
162
|
+
when Pathname
|
163
|
+
@db=DB.new(name)
|
164
|
+
when String
|
165
|
+
if DB.db_file?(name)
|
166
|
+
@db=DB.new(name)
|
167
|
+
else
|
168
|
+
@db=DB.new(cachedir+".db"+"#{name}.db.tar.gz")
|
169
|
+
end
|
170
|
+
when true
|
171
|
+
@db=DB.new(cachedir+".db"+"aur.db.tar.gz")
|
172
|
+
when false, nil
|
173
|
+
@db=name #false for false, nil to reset
|
174
|
+
else
|
175
|
+
SH.logger.warn("Database name #{name} not suitable")
|
176
|
+
@db=nil
|
177
|
+
end
|
178
|
+
# reset these so the pacman_conf gets the correct db name
|
179
|
+
@makepkg_config=nil
|
180
|
+
@devtools_config=nil
|
181
|
+
end
|
182
|
+
|
183
|
+
def db(create=true)
|
184
|
+
@db.nil? and self.db=@opts[:db]
|
185
|
+
if create and @db
|
186
|
+
@db.create
|
187
|
+
end
|
188
|
+
@db
|
189
|
+
end
|
190
|
+
|
191
|
+
# if changing a setting, we may need to reset the rest
|
192
|
+
def reset
|
193
|
+
self.db=nil
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
self.singleton_class.attr_accessor :config
|
198
|
+
@config=Config.new("aur.rb")
|
199
|
+
|
200
|
+
def self.add_to_hash(h, key, value)
|
201
|
+
case h[key]
|
202
|
+
when nil
|
203
|
+
h[key] = value
|
204
|
+
when Array
|
205
|
+
h[key] << value
|
206
|
+
else
|
207
|
+
h[key]=[h[key], value]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
class Version
|
212
|
+
def self.create(v)
|
213
|
+
v.is_a?(self) ? v : self.new(v)
|
214
|
+
end
|
215
|
+
|
216
|
+
include Comparable
|
217
|
+
attr_reader :epoch, :version, :pkgrel
|
218
|
+
def initialize(v)
|
219
|
+
@v=v
|
220
|
+
parse(v)
|
221
|
+
end
|
222
|
+
|
223
|
+
private def parse(v)
|
224
|
+
if v.nil? or v.empty?
|
225
|
+
@epoch=-1 #any version is better than empty version
|
226
|
+
@version=Gem::Version.new(0)
|
227
|
+
@pkgrel=nil
|
228
|
+
return
|
229
|
+
end
|
230
|
+
epoch, rest = v.split(':', 2)
|
231
|
+
if rest.nil?
|
232
|
+
rest=epoch; epoch=0
|
233
|
+
@real_epoch=false
|
234
|
+
else
|
235
|
+
@real_epoch=true
|
236
|
+
end
|
237
|
+
@epoch=epoch
|
238
|
+
version, pkgrel=Utils.rsplit(rest, '-', 2)
|
239
|
+
version.tr!('+_','.')
|
240
|
+
@version=Gem::Version.new(version) rescue Gem::Version.new("0.#{version}")
|
241
|
+
@pkgrel=pkgrel
|
242
|
+
end
|
243
|
+
|
244
|
+
def <=>(w)
|
245
|
+
w=self.class.new(w.to_s)
|
246
|
+
r= @epoch <=> w.epoch
|
247
|
+
if r==0
|
248
|
+
r= @version <=> w.version
|
249
|
+
if r==0 and @pkgrel and w.pkgrel
|
250
|
+
r= @pkgrel <=> w.pkgrel
|
251
|
+
end
|
252
|
+
end
|
253
|
+
r
|
254
|
+
end
|
255
|
+
|
256
|
+
def to_s
|
257
|
+
@v
|
258
|
+
# r=""
|
259
|
+
# r << "#{@epoch}:" if @real_epoch
|
260
|
+
# r << "#{@version}"
|
261
|
+
# r << "-#{@pkgrel}" if @pkgrel
|
262
|
+
# r
|
263
|
+
end
|
264
|
+
|
265
|
+
# strip version information from a package name
|
266
|
+
def self.strip(v)
|
267
|
+
v.sub(/[><=]+[\w.\-+:]*$/,'')
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# ex: pacman>=2.0.0
|
272
|
+
QueryError=Class.new(ArchlinuxError)
|
273
|
+
class Query
|
274
|
+
def self.create(v)
|
275
|
+
v.is_a?(self) ? v : self.new(v)
|
276
|
+
end
|
277
|
+
|
278
|
+
def self.strip(query)
|
279
|
+
self.create(query).name
|
280
|
+
end
|
281
|
+
|
282
|
+
include Comparable
|
283
|
+
attr_accessor :name, :op, :version, :op2, :version2
|
284
|
+
def initialize(query)
|
285
|
+
@query=query
|
286
|
+
@name, @op, version, @op2, version2=parse(query)
|
287
|
+
@version=Version.new(version)
|
288
|
+
@version2=Version.new(version2)
|
289
|
+
end
|
290
|
+
|
291
|
+
def to_s
|
292
|
+
@query
|
293
|
+
end
|
294
|
+
|
295
|
+
def max
|
296
|
+
strict=false
|
297
|
+
if @op=='<=' or @op=='='
|
298
|
+
max=@version
|
299
|
+
elsif @op=="<"
|
300
|
+
max=@version; strict=true
|
301
|
+
elsif @op2=="<="
|
302
|
+
max=@version2
|
303
|
+
elsif @op2=="<"
|
304
|
+
max=@version2; strict=true
|
305
|
+
end
|
306
|
+
return max, strict #nil means Float::INFINITY, ie no restriction
|
307
|
+
end
|
308
|
+
|
309
|
+
def min
|
310
|
+
strict=false
|
311
|
+
if @op=='>=' or @op=='='
|
312
|
+
min=@version
|
313
|
+
elsif @op==">"
|
314
|
+
min=@version; strict=true
|
315
|
+
elsif @op2==">="
|
316
|
+
min=@version2
|
317
|
+
elsif @op2==">"
|
318
|
+
min=@version2; strict=true
|
319
|
+
end
|
320
|
+
return min, strict
|
321
|
+
end
|
322
|
+
|
323
|
+
def <=>(other)
|
324
|
+
other=self.class.create(other)
|
325
|
+
min, strict=self.min
|
326
|
+
omin, ostrict=other.min
|
327
|
+
return 1 if min==omin and strict
|
328
|
+
return -1 if min==omin and ostrict
|
329
|
+
min <=> omin
|
330
|
+
end
|
331
|
+
|
332
|
+
# here we check if a package can satisfy a query
|
333
|
+
# note that other can itself be a query, think of a package that
|
334
|
+
# requires foo>=2.0 and bar which provides foo>=3
|
335
|
+
# satisfy? is symmetric, it means that the intersection of the
|
336
|
+
# available version ranges is non empty
|
337
|
+
def satisfy?(other)
|
338
|
+
case other
|
339
|
+
when Version
|
340
|
+
omin=other; omax=other; ominstrict=false; omaxstrict=false
|
341
|
+
oname=@name #we assume the name comparison was already done
|
342
|
+
else
|
343
|
+
other=self.class.create(other)
|
344
|
+
omax, omaxstrict=other.max
|
345
|
+
omin, ominstrict=other.min
|
346
|
+
oname=other.name
|
347
|
+
end
|
348
|
+
return false unless @name==oname
|
349
|
+
min, strict=self.min
|
350
|
+
return false if omax and min and (omax < min or omax == min && (strict or omaxstrict))
|
351
|
+
max, strict=self.max
|
352
|
+
return false if max and omin and (omin > max or omin == max && (strict or ominstrict))
|
353
|
+
true
|
354
|
+
end
|
355
|
+
|
356
|
+
private def parse(query)
|
357
|
+
if (m=query.match(/^([^><=]*)([><=]*)([\w.\-+:]*)([><=]*)([\w.\-+:]*)$/))
|
358
|
+
name=m[1]
|
359
|
+
op=m[2]
|
360
|
+
version=m[3]
|
361
|
+
op2=m[4]
|
362
|
+
version2=m[5]
|
363
|
+
if op.nil?
|
364
|
+
name, version=Utils.rsplit(name, '-', 2)
|
365
|
+
op="="; op2=nil; version2=nil
|
366
|
+
end
|
367
|
+
return name, op, version, op2, version2
|
368
|
+
else
|
369
|
+
raise QueryError.new("Bad query #{query}")
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
module AurQuery
|
375
|
+
extend self
|
376
|
+
attr_accessor :config
|
377
|
+
@config=Archlinux.config
|
378
|
+
|
379
|
+
AurQueryError=Class.new(ArchlinuxError)
|
380
|
+
|
381
|
+
# AurQuery.query(type: "info", arg: pacaur)
|
382
|
+
# AurQuery.query(type: "info", :"arg[]" => %w(cower pacaur))
|
383
|
+
# AurQuery.query(type: "search", by: "name", arg: "aur")
|
384
|
+
# by name (search by package name only)
|
385
|
+
# *name-desc* (search by package name and description)
|
386
|
+
# maintainer (search by package maintainer)
|
387
|
+
# depends (search for packages that depend on keywords)
|
388
|
+
# makedepends (search for packages that makedepend on keywords)
|
389
|
+
# optdepends (search for packages that optdepend on keywords)
|
390
|
+
# checkdepends (search for packages that checkdepend on keywords)
|
391
|
+
# => search result:
|
392
|
+
# {"ID"=>514909,
|
393
|
+
# "Name"=>"pacaur",
|
394
|
+
# "PackageBaseID"=>49145,
|
395
|
+
# "PackageBase"=>"pacaur",
|
396
|
+
# "Version"=>"4.7.90-1",
|
397
|
+
# "Description"=>"An AUR helper that minimizes user interaction",
|
398
|
+
# "URL"=>"https://github.com/rmarquis/pacaur",
|
399
|
+
# "NumVotes"=>1107,
|
400
|
+
# "Popularity"=>7.382043,
|
401
|
+
# "OutOfDate"=>nil,
|
402
|
+
# "Maintainer"=>"Spyhawk",
|
403
|
+
# "FirstSubmitted"=>1305666963,
|
404
|
+
# "LastModified"=>1527690065,
|
405
|
+
# "URLPath"=>"/cgit/aur.git/snapshot/pacaur.tar.gz"},
|
406
|
+
# => info result adds:
|
407
|
+
# "Depends"=>["cower", "expac", "sudo", "git"],
|
408
|
+
# "MakeDepends"=>["perl"],
|
409
|
+
# "License"=>["ISC"],
|
410
|
+
# "Keywords"=>["AUR", "helper", "wrapper"]}]
|
411
|
+
|
412
|
+
def query(h)
|
413
|
+
uri=URI("#{@config[:aur_url]}/rpc/")
|
414
|
+
params = {v:5}.merge(h)
|
415
|
+
uri.query = URI.encode_www_form(params)
|
416
|
+
res = Net::HTTP.get_response(uri)
|
417
|
+
if res.is_a?(Net::HTTPSuccess)
|
418
|
+
r= res.body
|
419
|
+
else
|
420
|
+
raise AurQueryError.new("AUR: Got error response for query #{h}")
|
421
|
+
end
|
422
|
+
data = JSON.parse(r)
|
423
|
+
case data['type']
|
424
|
+
when 'error'
|
425
|
+
raise AurQueryError.new("Error: #{data['results']}")
|
426
|
+
when 'search','info','multiinfo'
|
427
|
+
return data['results']
|
428
|
+
else
|
429
|
+
raise AurQueryError.new("Error in response data #{data}")
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
# Outdated packages: Aur.search(nil, by: "maintainer")
|
434
|
+
def search(arg, by: nil)
|
435
|
+
r={type: "search", arg: arg}
|
436
|
+
r[:by]=by if by
|
437
|
+
# if :by is not specified, aur defaults to name-desc
|
438
|
+
self.query(r)
|
439
|
+
end
|
440
|
+
|
441
|
+
def infos(*pkgs, slice: 150)
|
442
|
+
search=[]
|
443
|
+
pkgs.each_slice(slice) do |pkgs_slice|
|
444
|
+
r={type: "info", :"arg[]" => pkgs_slice}
|
445
|
+
search+=self.query(r)
|
446
|
+
end
|
447
|
+
search.each { |pkg| pkg[:repo]=:aur }
|
448
|
+
search
|
449
|
+
end
|
450
|
+
|
451
|
+
# try to use infos if possible
|
452
|
+
def info(pkg)
|
453
|
+
r={type: "info", arg: pkg}
|
454
|
+
self.query(r).first
|
455
|
+
end
|
456
|
+
|
457
|
+
def packages(*pkgs, klass: AurPackageList)
|
458
|
+
klass.new(infos(*pkgs))
|
459
|
+
end
|
460
|
+
|
461
|
+
def pkglist(type="packages", delay: 3600, query: :auto) #type=pkgbase
|
462
|
+
require 'zlib'
|
463
|
+
cache=self.cachedir
|
464
|
+
file=cache+"#{type}.gz"
|
465
|
+
in_epoch=nil
|
466
|
+
if file.exist?
|
467
|
+
# intime=file.read.each_line.first
|
468
|
+
file.open do |io|
|
469
|
+
Zlib::GzipReader.wrap(io) do |gz|
|
470
|
+
intime=gz.each_line.first
|
471
|
+
intime.match(/^# AUR package list, generated on (.*)/) do |m|
|
472
|
+
in_epoch=Time.parse(m[1]).to_i
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
if query
|
478
|
+
Net::HTTP.get_response(URI("#{@config[:aur_url]}/#{type}.gz")) do |res|
|
479
|
+
date=res["date"]
|
480
|
+
update=true
|
481
|
+
if date
|
482
|
+
epoch=Time.parse(date).to_i
|
483
|
+
update=false if epoch and in_epoch and (epoch-in_epoch < delay) and !query==true
|
484
|
+
end
|
485
|
+
if update
|
486
|
+
file.open('w') do |io|
|
487
|
+
Zlib::GzipWriter.wrap(io) do |gz|
|
488
|
+
res.read_body(gz)
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
file.open do |io|
|
495
|
+
Zlib::GzipReader.wrap(io) do |gz|
|
496
|
+
return gz.each_line.map(&:chomp).drop(1)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
class DB
|
503
|
+
def self.create(v)
|
504
|
+
v.is_a?(self) ? v : self.new(v)
|
505
|
+
end
|
506
|
+
|
507
|
+
def self.db_file?(name)
|
508
|
+
case name
|
509
|
+
when Pathname
|
510
|
+
true
|
511
|
+
when String
|
512
|
+
if name.include?('/') or name.match(/\.db(\..*)?$/)
|
513
|
+
true
|
514
|
+
else
|
515
|
+
false #Usually we assume this is a repo name
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
attr_accessor :file, :config
|
521
|
+
def initialize(file, config: Archlinux.config)
|
522
|
+
@file=Pathname.new(file)
|
523
|
+
@config=config
|
524
|
+
end
|
525
|
+
|
526
|
+
def mkpath
|
527
|
+
@file.dirname.mkpath
|
528
|
+
end
|
529
|
+
|
530
|
+
def path
|
531
|
+
if file.exist?
|
532
|
+
file.realpath
|
533
|
+
else
|
534
|
+
file
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
def repo_name
|
539
|
+
@file.basename.to_s.sub(/\.db(\..*)?$/,'')
|
540
|
+
end
|
541
|
+
|
542
|
+
def create
|
543
|
+
mkpath
|
544
|
+
unless @file.exist?
|
545
|
+
call(:'repo-add', path.shellescape)
|
546
|
+
end
|
547
|
+
self
|
548
|
+
end
|
549
|
+
|
550
|
+
def to_s
|
551
|
+
@file.to_s
|
552
|
+
end
|
553
|
+
|
554
|
+
# a db is a tar.gz archive of packages/desc, like yay-8.998-1/descr
|
555
|
+
def list
|
556
|
+
require 'dr/sh'
|
557
|
+
res= SH.run_simple("bsdcat #{@file.shellescape}", chomp: :lines) {return nil}
|
558
|
+
list=[]; pkg={}; mode=nil
|
559
|
+
flush = lambda do
|
560
|
+
# catch old deps files which don't specify the full infos
|
561
|
+
unless pkg[:name].nil? and pkg[:base].nil?
|
562
|
+
pkg[:repo]||=path
|
563
|
+
list << pkg
|
564
|
+
end
|
565
|
+
end
|
566
|
+
res.each do |l|
|
567
|
+
next if l.empty? or l.match(/^\u0000*$/)
|
568
|
+
if (m=l.match(/(\u0000+.*\u0000+)?%([A-Z0-9]*)%$/))
|
569
|
+
mode=m[2].downcase.to_sym
|
570
|
+
if m[1] #new db entry
|
571
|
+
flush.call #store old db entry
|
572
|
+
pkg={}
|
573
|
+
end
|
574
|
+
else
|
575
|
+
l=l.to_i if mode==:csize or mode==:isize
|
576
|
+
l=Time.at(l.to_i) if mode==:builddate
|
577
|
+
Archlinux.add_to_hash(pkg, mode, l)
|
578
|
+
end
|
579
|
+
end
|
580
|
+
flush.call #don't forget the last one
|
581
|
+
list
|
582
|
+
end
|
583
|
+
|
584
|
+
def files(absolute=true)
|
585
|
+
list.map do |pkg|
|
586
|
+
file=Pathname.new(pkg[:filename])
|
587
|
+
absolute ? dir + file : file
|
588
|
+
end
|
589
|
+
end
|
590
|
+
# def files
|
591
|
+
# packages.l.map {|pkg| dir+Pathname.new(pkg[:filename])}
|
592
|
+
# end
|
593
|
+
|
594
|
+
def dir
|
595
|
+
@file.dirname.realpath
|
596
|
+
end
|
597
|
+
|
598
|
+
def call(*args)
|
599
|
+
@config.launch(*args) do |*args|
|
600
|
+
dir.chdir do
|
601
|
+
SH.sh(*args)
|
602
|
+
end
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
def move_to_db(*files, op: :mv)
|
607
|
+
files=files.map {|f| Pathname.new(f).realpath}
|
608
|
+
dir=self.dir
|
609
|
+
files.map do |f|
|
610
|
+
if f.dirname == dir
|
611
|
+
f
|
612
|
+
else
|
613
|
+
new=dir+f.basename
|
614
|
+
f.send(op, new)
|
615
|
+
new
|
616
|
+
end
|
617
|
+
end
|
618
|
+
files
|
619
|
+
end
|
620
|
+
|
621
|
+
def add(*files, cmd: :'repo-add', default_opts:[], sign: @config.sign(:repo), **opts)
|
622
|
+
default_opts+=['-s', '-v'] if sign
|
623
|
+
default_opts+=['--key', sign] if sign.is_a?(String)
|
624
|
+
unless files.empty?
|
625
|
+
call(cmd, path.shellescape, *files, default_opts: default_opts, **opts)
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
def remove(*args, **opts)
|
630
|
+
add(*args, cmd: :'repo-remove', **opts)
|
631
|
+
end
|
632
|
+
|
633
|
+
def packages(refresh=false)
|
634
|
+
@packages=nil if refresh
|
635
|
+
@packages||=PackageList.new(list)
|
636
|
+
end
|
637
|
+
|
638
|
+
def dir_packages
|
639
|
+
PackageFiles.from_dir(dir).packages
|
640
|
+
end
|
641
|
+
|
642
|
+
def package_files
|
643
|
+
PackageFiles.new(*files).packages
|
644
|
+
end
|
645
|
+
|
646
|
+
def check
|
647
|
+
packages.same?(package_files)
|
648
|
+
end
|
649
|
+
|
650
|
+
def check_update(other=dir_packages)
|
651
|
+
up=self.packages.check_updates(other)
|
652
|
+
refresh=up.select {|_k, u| u[:op]==:upgrade or u[:op]==:downgrade}
|
653
|
+
add=up.select {|_k, u| u[:op]==:install}
|
654
|
+
remove=up.select {|_k, u| u[:op]==:obsolete}
|
655
|
+
return {refresh: refresh, add: add, remove: remove}
|
656
|
+
end
|
657
|
+
|
658
|
+
def update(other=dir_packages)
|
659
|
+
r=check_update(other)
|
660
|
+
add(*(r[:refresh].merge(r[:add])).map {|_k,v| other[v[:out_pkg]].file.shellescape})
|
661
|
+
# remove(*(r[:remove].map {|_k,v| packages[v[:in_pkg]].file.shellescape}))
|
662
|
+
remove(*(r[:remove].map {|_k,v| Query.strip(v[:in_pkg])}))
|
663
|
+
r
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
class Repo
|
668
|
+
def self.create(v)
|
669
|
+
v.is_a?(self) ? v : self.new(v)
|
670
|
+
end
|
671
|
+
|
672
|
+
def initialize(name)
|
673
|
+
@repo=name
|
674
|
+
end
|
675
|
+
|
676
|
+
def list(mode: :pacsift)
|
677
|
+
command= case mode
|
678
|
+
when :pacman
|
679
|
+
"pacman -Slq #{@repo.shellescape}" #returns pkg
|
680
|
+
when :pacsift
|
681
|
+
"pacsift --exact --repo=#{@repo.shellescape} <&-" #returns repo/pkg
|
682
|
+
when :paclist
|
683
|
+
"paclist #{@repo.shellescape}" #returns pkg version
|
684
|
+
end
|
685
|
+
SH.run_simple(command, chomp: :lines) {return nil}
|
686
|
+
end
|
687
|
+
|
688
|
+
def packages(refresh=false)
|
689
|
+
@packages=nil if refresh
|
690
|
+
@packages ||= PackageList.new(self.class.info(*list))
|
691
|
+
end
|
692
|
+
|
693
|
+
def self.pacman_info(*pkgs, local: false) #local=true refers to the local db info
|
694
|
+
list=[]
|
695
|
+
to_list=lambda do |s|
|
696
|
+
return [] if s=="None"
|
697
|
+
s.split
|
698
|
+
end
|
699
|
+
# Note: core/pacman only works for -Sddp or -Si, not for -Qi
|
700
|
+
# Indeed local/pacman works for pacinfo, but not for pacman (needs
|
701
|
+
# the -Q options)
|
702
|
+
res=SH.run_simple({'COLUMNS' => '1000'}, "pacman -#{local ? 'Q': 'S'}i #{pkgs.shelljoin}", chomp: :lines)
|
703
|
+
key=nil; info={}
|
704
|
+
res.each do |l|
|
705
|
+
if key==:optional_deps and (m=l.match(/^\s+(\w*):\s+(.*)$/))
|
706
|
+
#here we cannot split(':') because we need to check for the leading space
|
707
|
+
info[key][m[1]]=m[2]
|
708
|
+
else
|
709
|
+
key, value=l.split(/\s*:\s*/,2)
|
710
|
+
if key.nil? #new package
|
711
|
+
list << info; key=nil; info={}
|
712
|
+
next
|
713
|
+
end
|
714
|
+
key=key.strip.downcase.gsub(' ', '_').to_sym
|
715
|
+
case key
|
716
|
+
when :optional_deps
|
717
|
+
dep, reason=value.split(/\s*:\s*/,2)
|
718
|
+
value={dep => reason}
|
719
|
+
when :groups, :provides, :depends_on, :required_by, :optional_for, :conflicts_with, :replaces
|
720
|
+
value=to_list.call(value)
|
721
|
+
when :install_script
|
722
|
+
value=false if value=="No"
|
723
|
+
value=true if value=="Yes"
|
724
|
+
when :install_date, :build_date
|
725
|
+
value=Time.parse(value)
|
726
|
+
end
|
727
|
+
info[key]=value
|
728
|
+
end
|
729
|
+
end
|
730
|
+
# no need to add the last info, pacman -Q/Si always end with a new line
|
731
|
+
list
|
732
|
+
end
|
733
|
+
|
734
|
+
def self.pacinfo(*pkgs) #this refers to the local db info
|
735
|
+
list=[]; info={}
|
736
|
+
res=SH.run_simple("pacinfo #{pkgs.shelljoin}", chomp: :lines)
|
737
|
+
res.each do |l|
|
738
|
+
key, value=l.split(/\s*:\s*/,2)
|
739
|
+
if key.nil? #next package
|
740
|
+
list << info; info={}
|
741
|
+
next
|
742
|
+
end
|
743
|
+
key=key.downcase.gsub(' ', '_').to_sym
|
744
|
+
case key
|
745
|
+
when :install_script
|
746
|
+
value=false if value=="No"
|
747
|
+
value=true if value=="Yes"
|
748
|
+
when :install_date, :build_date
|
749
|
+
value=Time.parse(value)
|
750
|
+
end
|
751
|
+
Archlinux.add_to_hash(info, key, value)
|
752
|
+
end
|
753
|
+
# no need to add at the end, pacinfo always end with a new line
|
754
|
+
list
|
755
|
+
end
|
756
|
+
|
757
|
+
def self.info(*packages)
|
758
|
+
if SH.find_executable("pacinfo")
|
759
|
+
pacinfo(*packages)
|
760
|
+
else
|
761
|
+
pacman_info(*packages)
|
762
|
+
end
|
763
|
+
end
|
764
|
+
|
765
|
+
def self.packages(*packages)
|
766
|
+
PackageList.new(info(*packages))
|
767
|
+
end
|
768
|
+
end
|
769
|
+
|
770
|
+
class PackageFiles
|
771
|
+
def self.create(v)
|
772
|
+
v.is_a?(self) ? v : self.new(v)
|
773
|
+
end
|
774
|
+
|
775
|
+
attr_accessor :files
|
776
|
+
def initialize(*files)
|
777
|
+
@files=files.map {|file| DR::Pathname.new(file)}
|
778
|
+
end
|
779
|
+
|
780
|
+
def infos(slice=200) #the command line should not be too long
|
781
|
+
format={
|
782
|
+
filename: "%f",
|
783
|
+
pkgname: "%n",
|
784
|
+
pkgbase: "%e",
|
785
|
+
version: "%v",
|
786
|
+
url: "%u",
|
787
|
+
description: "%d",
|
788
|
+
packager: "%p",
|
789
|
+
architecture: "%a",
|
790
|
+
build_date: "%b",
|
791
|
+
download_size: "%k",
|
792
|
+
install_size: "%m",
|
793
|
+
depends: "%D",
|
794
|
+
conflicts: "%H",
|
795
|
+
opt_depends: "%O",
|
796
|
+
provides: "%P",
|
797
|
+
replaces: "%T",
|
798
|
+
# format << "%r\n" #repo
|
799
|
+
}
|
800
|
+
total=format.keys.count
|
801
|
+
r=[]; delim=" , "
|
802
|
+
split=lambda do |l| l.split(delim) end
|
803
|
+
@files.each_slice(slice) do |files|
|
804
|
+
SH.run_simple("expac --timefmt=%s #{format.values.join("\n").shellescape} -l #{delim.shellescape} -p #{files.shelljoin}", chomp: :lines).each_slice(total).with_index do |l,i|
|
805
|
+
info={}
|
806
|
+
format.keys.each_with_index do |k, kk|
|
807
|
+
value=l[kk]
|
808
|
+
value=split[value] if %i(depends conflicts opt_depends provides replaces).include?(k)
|
809
|
+
value=nil if k==:pkgbase and value=="(null)"
|
810
|
+
value=Time.at(value.to_i) if k==:build_date
|
811
|
+
value=value.to_i if k==:download_size or k==:install_size
|
812
|
+
info[k]=value if value
|
813
|
+
end
|
814
|
+
info[:repo]=files[i]
|
815
|
+
r<<info
|
816
|
+
end
|
817
|
+
end
|
818
|
+
r
|
819
|
+
end
|
820
|
+
|
821
|
+
def packages(refresh=false)
|
822
|
+
@packages=nil if refresh
|
823
|
+
@packages ||= PackageList.new(self.infos)
|
824
|
+
end
|
825
|
+
|
826
|
+
def self.from_dir(dir)
|
827
|
+
dir=Pathname.new(dir)
|
828
|
+
self.new(*dir.glob('*.pkg.*').map {|g| next if g.to_s.end_with?('~') or g.to_s.end_with?('.sig'); f=dir+g; next unless f.readable?; f}.compact)
|
829
|
+
end
|
830
|
+
end
|
831
|
+
|
832
|
+
class Makepkg
|
833
|
+
def self.create(v)
|
834
|
+
v.is_a?(self) ? v : self.new(v)
|
835
|
+
end
|
836
|
+
|
837
|
+
attr_accessor :dir, :base, :env, :config, :asdeps
|
838
|
+
|
839
|
+
def initialize(dir, config: Archlinux.config, env: {}, asdeps: false)
|
840
|
+
@dir=DR::Pathname.new(dir)
|
841
|
+
@base=@dir.basename
|
842
|
+
@config=config
|
843
|
+
@env=env
|
844
|
+
@asdeps=asdeps
|
845
|
+
db=@config.db
|
846
|
+
@env['PKGDEST']=db.dir.to_s if db
|
847
|
+
end
|
848
|
+
|
849
|
+
def name
|
850
|
+
@base.to_s
|
851
|
+
end
|
852
|
+
|
853
|
+
def call(*args, run: :run_simple, **opts)
|
854
|
+
@config.launch(:makepkg, *args, **opts) do |*args, **opts|
|
855
|
+
@dir.chdir do
|
856
|
+
SH.public_send(run, @env, *args, **opts)
|
857
|
+
end
|
858
|
+
end
|
859
|
+
end
|
860
|
+
|
861
|
+
def info
|
862
|
+
stdin=call("--printsrcinfo", chomp: :lines)
|
863
|
+
mode=nil; r={}; current={}; pkgbase=nil; pkgname=nil
|
864
|
+
stdin.each do |l|
|
865
|
+
key, value=l.split(/\s*=\s*/,2)
|
866
|
+
next if key.nil?
|
867
|
+
if key=="pkgbase"
|
868
|
+
mode=:pkgbase; current[:pkgbase]=value
|
869
|
+
elsif key=="pkgname"
|
870
|
+
if mode==:pkgbase
|
871
|
+
r=current
|
872
|
+
r[:pkgs]={repo: @dir}
|
873
|
+
else
|
874
|
+
r[:pkgs][pkgname]=current
|
875
|
+
end
|
876
|
+
current={}; mode=:pkgname; pkgname=value
|
877
|
+
else
|
878
|
+
key=key.strip.to_sym
|
879
|
+
Archlinux.add_to_hash(current, key, value)
|
880
|
+
end
|
881
|
+
end
|
882
|
+
r[:pkgs][pkgname]=current #don't forget to update the last one
|
883
|
+
r
|
884
|
+
end
|
885
|
+
|
886
|
+
def packages(refresh=false)
|
887
|
+
@packages=nil if refresh
|
888
|
+
unless @packages
|
889
|
+
r=info
|
890
|
+
pkgs=r.delete(:pkgs)
|
891
|
+
r[:pkgbase]
|
892
|
+
base=Package.new(r)
|
893
|
+
list=pkgs.map do |name, pkg|
|
894
|
+
pkg[:name]=name
|
895
|
+
Package.new(pkg).merge(base)
|
896
|
+
end
|
897
|
+
@packages=PackageList.new(list)
|
898
|
+
end
|
899
|
+
@packages
|
900
|
+
end
|
901
|
+
|
902
|
+
def url
|
903
|
+
@config[:aur_url]+@base.to_s+".git"
|
904
|
+
end
|
905
|
+
|
906
|
+
def get(logdir: nil, view: false)
|
907
|
+
#SH.sh("vcs clone_or_update --diff #{url.shellescape} #{@dir.shellescape}")
|
908
|
+
if logdir
|
909
|
+
logdir=DR::Pathname.new(logdir)
|
910
|
+
logdir.mkpath
|
911
|
+
end
|
912
|
+
if @dir.exist?
|
913
|
+
# TODO: what if @dir exist but is not a git directory?
|
914
|
+
@dir.chdir do
|
915
|
+
unless @config.git_update
|
916
|
+
SH.logger.error("Error in updating #{@dir}")
|
917
|
+
end
|
918
|
+
if logdir
|
919
|
+
SH::VirtualFile.new("orderfile", "PKGBUILD").create(true) do |tmp|
|
920
|
+
patch=SH.run_simple("git diff -O#{tmp} HEAD@{1}")
|
921
|
+
(logdir+"#{@dir.basename}.patch").write(patch) unless patch.empty?
|
922
|
+
(logdir+"#{@dir.basename}").on_ln_s(@dir.realpath, rm: :symlink)
|
923
|
+
end
|
924
|
+
end
|
925
|
+
end
|
926
|
+
else
|
927
|
+
unless @config.git_clone(url, @dir)
|
928
|
+
SH.logger.error("Error in cloning #{url} to #{@dir}")
|
929
|
+
end
|
930
|
+
SH.sh("git clone #{url.shellescape} #{@dir.shellescape}")
|
931
|
+
if logdir
|
932
|
+
(logdir+"!#{@dir.basename}").on_ln_s(@dir.realpath)
|
933
|
+
end
|
934
|
+
end
|
935
|
+
if view
|
936
|
+
return @config.view(@dir)
|
937
|
+
else
|
938
|
+
return true
|
939
|
+
end
|
940
|
+
end
|
941
|
+
|
942
|
+
# raw call to makepkg
|
943
|
+
def makepkg(*args, **opts)
|
944
|
+
call(*args, run: :sh, **opts)
|
945
|
+
end
|
946
|
+
|
947
|
+
def make(*args, sign: config.sign(:makepkg), default_opts: [], force: false, asdeps: @asdeps, **opts)
|
948
|
+
default_opts << "--sign" if sign
|
949
|
+
default_opts << "--key=#{sign}" if sign.is_a?(String)
|
950
|
+
default_opts << "--force" if force
|
951
|
+
default_opts << "--asdeps" if asdeps
|
952
|
+
|
953
|
+
tools=@config.makepkg_config #this set up pacman and makepkg config files
|
954
|
+
success=false
|
955
|
+
@dir.chdir do
|
956
|
+
success=tools.makepkg(*args, default_opts: default_opts, env: @env, **opts)
|
957
|
+
end
|
958
|
+
success
|
959
|
+
end
|
960
|
+
|
961
|
+
def mkarchroot
|
962
|
+
@config.devtools.mkarchroot("base-devel")
|
963
|
+
end
|
964
|
+
|
965
|
+
def makechroot(*args, sign: @config.sign(:makechrootpkg), force: false, **opts)
|
966
|
+
unless force
|
967
|
+
if list.all? {|f| f.exist?}
|
968
|
+
SH.logger.info "Skipping #{@dir} since it is already built (use force=trye to override)"
|
969
|
+
return false
|
970
|
+
end
|
971
|
+
end
|
972
|
+
devtools=@config.devtools
|
973
|
+
success=false
|
974
|
+
@dir.chdir do
|
975
|
+
success=devtools.makechrootpkg(*args, env: @env, **opts)
|
976
|
+
end
|
977
|
+
self.sign(sign) if sign and success
|
978
|
+
success
|
979
|
+
end
|
980
|
+
|
981
|
+
def add_to_db(db=@config.db)
|
982
|
+
SH.logger.warn "Bad database #{db}" unless db.is_a?(DB)
|
983
|
+
db.add(*list.select {|l| l.exist?})
|
984
|
+
end
|
985
|
+
|
986
|
+
def build(*makepkg_args, mkarchroot: false, chroot: @config[:build_chroot], **opts)
|
987
|
+
SH.logger.info "=> Building #{@dir}"
|
988
|
+
if chroot
|
989
|
+
self.mkarchroot if mkarchroot
|
990
|
+
success, _r=makechroot(*makepkg_args, **opts)
|
991
|
+
else
|
992
|
+
success, _r=make(*makepkg_args, **opts)
|
993
|
+
end
|
994
|
+
if success and (db=@config.db)
|
995
|
+
add_to_db(db)
|
996
|
+
if !chroot #sync db
|
997
|
+
tools=@config.makepkg_config
|
998
|
+
tools.sync_db(db.repo_name)
|
999
|
+
end
|
1000
|
+
end
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
def install(*args, view: true, **opts)
|
1004
|
+
r=get(view: view)
|
1005
|
+
build(*args, **opts) if r
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
def list(**opts)
|
1009
|
+
call("--packagelist", chomp: :lines, err: "/dev/null", **opts).map {|f| Pathname.new(f)}
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
def sign(sign, **opts)
|
1013
|
+
list(**opts).each do |pkg|
|
1014
|
+
if pkg.file?
|
1015
|
+
if (sig=Pathname.new("#{pkg}.sig")).file?
|
1016
|
+
SH.logger.warn "Signature #{sig} already exits, skipping"
|
1017
|
+
else
|
1018
|
+
SH.sh("gpg #{sign.is_a?(String) ? "-u #{sign}" : ""} --detach-sign --no-armor #{pkg.shellescape}")
|
1019
|
+
end
|
1020
|
+
end
|
1021
|
+
end
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
class MakepkgList
|
1027
|
+
def self.create(v)
|
1028
|
+
v.is_a?(self) ? v : self.new(v)
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
Archlinux.delegate_h(self, :@l)
|
1032
|
+
attr_accessor :config, :cache, :l
|
1033
|
+
|
1034
|
+
def initialize(l, config: Archlinux.config, cache: config.cachedir)
|
1035
|
+
@config=config
|
1036
|
+
@cache=Pathname.new(cache)
|
1037
|
+
@l={}
|
1038
|
+
l.each do |m|
|
1039
|
+
unless m.is_a?(Makepkg)
|
1040
|
+
m=Pathname.new(m)
|
1041
|
+
m = @cache+m if m.relative?
|
1042
|
+
v=Makepkg.new(m, config: @config)
|
1043
|
+
@l[v.name]=v
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
def packages(refresh=false)
|
1049
|
+
@packages = nil if refresh
|
1050
|
+
@packages ||= @l.values.reduce do |list, makepkg|
|
1051
|
+
list.merge(makepkg.packages)
|
1052
|
+
end
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
def get(*args, view: true)
|
1056
|
+
Dir.mktmpdir("aur_view") do |d|
|
1057
|
+
@l.values.each do |l|
|
1058
|
+
l.get(*args, logdir: d)
|
1059
|
+
end
|
1060
|
+
if view
|
1061
|
+
return @config.view(d)
|
1062
|
+
else
|
1063
|
+
return true
|
1064
|
+
end
|
1065
|
+
end
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
def make(*args)
|
1069
|
+
@l.values.each do |l|
|
1070
|
+
l.make(*args)
|
1071
|
+
end
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
def makechroot(*args)
|
1075
|
+
@l.values.each do |l|
|
1076
|
+
l.makechroot(*args)
|
1077
|
+
end
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
def mkarchroot
|
1081
|
+
@config.devtools.mkarchroot("base-devel")
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
def list
|
1085
|
+
@l.values.flat_map { |l| l.list }
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
def add_to_db(db=@config.db)
|
1089
|
+
SH.logger.warn "Bad database #{db}" unless db.is_a?(DB)
|
1090
|
+
db.add(*list.select {|l| l.exist?})
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
def build(*args, chroot: @config[:build_chroot], **opts)
|
1094
|
+
mkarchroot if chroot
|
1095
|
+
@l.values.each do |l|
|
1096
|
+
l.build(*args, chroot: chroot, **opts)
|
1097
|
+
end
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
def install(*args, view: true, **opts)
|
1101
|
+
r=get(view: view)
|
1102
|
+
build(*args, **opts) if r
|
1103
|
+
end
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
PackageError=Class.new(ArchlinuxError)
|
1107
|
+
class Package
|
1108
|
+
def self.create(v)
|
1109
|
+
v.is_a?(self) ? v : self.new(v)
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
Archlinux.delegate_h(self, :@props)
|
1113
|
+
attr_reader :name, :props
|
1114
|
+
|
1115
|
+
def initialize(*args)
|
1116
|
+
case args.length
|
1117
|
+
when 2
|
1118
|
+
name, props=args
|
1119
|
+
when 1
|
1120
|
+
props=args.first
|
1121
|
+
name=nil
|
1122
|
+
else
|
1123
|
+
raise PackageError.new("Error the number of arguments should be 1 or 2")
|
1124
|
+
end
|
1125
|
+
@name=name
|
1126
|
+
@props={}
|
1127
|
+
self.props=(props)
|
1128
|
+
@name=@props[:name] || @props[:pkgname] || @props[:pkgbase] unless @name
|
1129
|
+
end
|
1130
|
+
|
1131
|
+
def props=(props)
|
1132
|
+
[:groups, :depends, :make_depends, :check_depends, :conflicts, :replaces, :provides, :depends_for, :opt_depends_for].each do |k|
|
1133
|
+
props.key?(k) or @props[k]=[]
|
1134
|
+
end
|
1135
|
+
@props[:opt_depends]||={}
|
1136
|
+
props.each do |k,v|
|
1137
|
+
k=Utils.to_snake_case(k.to_s).to_sym
|
1138
|
+
k=:opt_depends if k==:optdepends || k==:optional_deps
|
1139
|
+
k=:make_depends if k==:makedepends
|
1140
|
+
k=:check_depends if k==:checkdepends
|
1141
|
+
k=:build_date if k==:checkdepends
|
1142
|
+
k=:depends if k==:depends_on or k==:requires
|
1143
|
+
k=:conflicts if k==:conflicts_with
|
1144
|
+
k=:pkgbase if k==:base
|
1145
|
+
k=:depends_for if k==:required_by
|
1146
|
+
k=:opt_depends_for if k==:optional_for
|
1147
|
+
k=:description if k==:desc
|
1148
|
+
case k
|
1149
|
+
when :first_submitted, :last_modified, :out_of_date, :build_date, :install_date
|
1150
|
+
if v and !v.is_a?(Time)
|
1151
|
+
v= v.is_a?(Integer) ? Time.at(v) : Time.parse(v)
|
1152
|
+
end
|
1153
|
+
when :repository, :groups, :depends, :make_depends, :check_depends, :conflicts, :replaces, :provides, :depends_for, :opt_depends_for
|
1154
|
+
v=Array(v)
|
1155
|
+
when :opt_depends
|
1156
|
+
unless v.is_a?(Hash)
|
1157
|
+
w={}
|
1158
|
+
Array(v).each do |l|
|
1159
|
+
l.match(/(\w*)\s+:\s+(.*)/) do |m|
|
1160
|
+
w[m[1]]=m[2]
|
1161
|
+
end
|
1162
|
+
end
|
1163
|
+
v=w
|
1164
|
+
end
|
1165
|
+
end
|
1166
|
+
@props[k]=v
|
1167
|
+
end
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
def merge(h)
|
1171
|
+
h.each do |k,v|
|
1172
|
+
if @props[k].nil? or ((k==:package_size or k==:download_size or k==:installed_size) and (@props[k]=="0.00 B" or @props[k]==0))
|
1173
|
+
@props[k]=v
|
1174
|
+
elsif k==:repository
|
1175
|
+
@props[k]=(Array(@props[k])+v).uniq
|
1176
|
+
end
|
1177
|
+
end
|
1178
|
+
self
|
1179
|
+
end
|
1180
|
+
|
1181
|
+
def dependencies(l=%i(depends))
|
1182
|
+
l.map {|i| a=@props[i]; a.is_a?(Hash) ? a.keys : Array(a)}.flatten.uniq
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
def version
|
1186
|
+
Version.new(@props[:version])
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
def name_version
|
1190
|
+
r=self.name
|
1191
|
+
version=self.version
|
1192
|
+
r+="="+version.to_s if version
|
1193
|
+
r
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
def file
|
1197
|
+
Pathname.new(@props[:filename])
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
def same?(other)
|
1201
|
+
# @props.slice(*(@props.keys - [:repo])) == other.props.slice(*(other.props.keys - [:repo]))
|
1202
|
+
slice=%i(version description depends provides opt_depends replaces conflicts)
|
1203
|
+
# p name, other.name, @props.slice(*slice), other.props.slice(*slice)
|
1204
|
+
name == other.name && @props.slice(*slice) == other.props.slice(*slice)
|
1205
|
+
end
|
1206
|
+
end
|
1207
|
+
|
1208
|
+
class PackageList
|
1209
|
+
def self.create(v)
|
1210
|
+
v.is_a?(self) ? v : self.new(v)
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
Archlinux.delegate_h(self, :@l)
|
1214
|
+
attr_accessor :children_mode, :ext_query
|
1215
|
+
attr_reader :l, :versions, :provides_for
|
1216
|
+
|
1217
|
+
def initialize(list)
|
1218
|
+
@l={}
|
1219
|
+
@versions={} #hash of versions for quick look ups
|
1220
|
+
@provides_for={} #hash of provides for quick look ups
|
1221
|
+
@ext_query=nil #how to get missing packages, default
|
1222
|
+
@children_mode=%i(depends) #children, default
|
1223
|
+
@ignore=[] #ignore packages update
|
1224
|
+
@query_ignore=[] #query ignore packages (ie these won't be returned by a query)
|
1225
|
+
merge(list)
|
1226
|
+
end
|
1227
|
+
|
1228
|
+
def packages
|
1229
|
+
@versions.keys
|
1230
|
+
end
|
1231
|
+
|
1232
|
+
def name_of(pkg)
|
1233
|
+
pkg.name_version
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
def same?(other)
|
1237
|
+
unless @l.keys == other.keys
|
1238
|
+
SH.logger.warn("Inconsistency in the package names")
|
1239
|
+
return false
|
1240
|
+
end
|
1241
|
+
r=true
|
1242
|
+
@l.each do |name, pkg|
|
1243
|
+
unless pkg.same?(other[name])
|
1244
|
+
SH.logger.warn("Inconsistensy for the package #{name}")
|
1245
|
+
r=false
|
1246
|
+
end
|
1247
|
+
end
|
1248
|
+
return r
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
def merge(l)
|
1252
|
+
# l=l.values if l.is_a?(PackageList) # this is handled below
|
1253
|
+
l= case l
|
1254
|
+
when Hash
|
1255
|
+
l.values.compact
|
1256
|
+
when PackageList
|
1257
|
+
l.values
|
1258
|
+
else
|
1259
|
+
l.to_a
|
1260
|
+
end
|
1261
|
+
l.each do |pkg|
|
1262
|
+
pkg=Package.new(pkg) unless pkg.is_a?(Package)
|
1263
|
+
name=name_of(pkg)
|
1264
|
+
if @l.key?(name)
|
1265
|
+
@l[name].merge(pkg)
|
1266
|
+
else
|
1267
|
+
@l[name]=pkg
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
@versions[pkg.name]||={}
|
1271
|
+
@versions[pkg.name][pkg.version.to_s]=name
|
1272
|
+
|
1273
|
+
pkg[:provides].each do |p|
|
1274
|
+
pkg=Query.strip(p)
|
1275
|
+
@provides_for[pkg]||={}
|
1276
|
+
@provides_for[pkg][p]=name #todo: do we pass the name or the full pkg?
|
1277
|
+
end
|
1278
|
+
end
|
1279
|
+
self
|
1280
|
+
end
|
1281
|
+
|
1282
|
+
def latest
|
1283
|
+
r={}
|
1284
|
+
@versions.each do |pkg, versions|
|
1285
|
+
v=versions.keys.max do |v1,v2|
|
1286
|
+
Version.create(v1) <=> Version.create(v2)
|
1287
|
+
end
|
1288
|
+
r[pkg]=@l[versions[v]]
|
1289
|
+
end
|
1290
|
+
r
|
1291
|
+
end
|
1292
|
+
|
1293
|
+
def find(q, **opts)
|
1294
|
+
return q if @l.key?(q)
|
1295
|
+
q=Query.create(q); pkg=q.name
|
1296
|
+
query(q, **opts) do |found, type|
|
1297
|
+
if type==:version
|
1298
|
+
unless found.empty? #we select the most up to date
|
1299
|
+
max = found.max { |v,w| Version.create(v) <=> Version.create(w) }
|
1300
|
+
return @versions[pkg][max]
|
1301
|
+
end
|
1302
|
+
elsif type==:provides
|
1303
|
+
max = found.max { |v,w| Query.create(v) <=> Query.create(w) }
|
1304
|
+
return @provides_for[pkg][max]
|
1305
|
+
end
|
1306
|
+
end
|
1307
|
+
return nil
|
1308
|
+
end
|
1309
|
+
|
1310
|
+
def query(q, provides: false, &b) #provides: do we check Provides?
|
1311
|
+
q=Query.new(q) unless q.is_a?(Query)
|
1312
|
+
matches=[]; pkg=q.name
|
1313
|
+
if @versions.key?(pkg)
|
1314
|
+
matches+=(found=@versions[pkg].keys.select {|v| q.satisfy?(Version.create(v))}).map {|k| @versions[pkg][k]}
|
1315
|
+
yield(found, :version) if block_given?
|
1316
|
+
end
|
1317
|
+
if provides and @provides_for.key?(pkg)
|
1318
|
+
matches+=(found=@provides_for[pkg].keys.select {|v| q.satisfy?(Query.create(v))}).map {|k| @provides_for[pkg][k]}
|
1319
|
+
yield(found, :provides) if block_given?
|
1320
|
+
end
|
1321
|
+
matches
|
1322
|
+
end
|
1323
|
+
|
1324
|
+
def to_a
|
1325
|
+
@l.values
|
1326
|
+
end
|
1327
|
+
|
1328
|
+
# here the arguments are Strings
|
1329
|
+
# return the arguments replaced by eventual provides + missing packages
|
1330
|
+
# are added to @l
|
1331
|
+
def resolve(*queries, provides: true, ext_query: @ext_query, fallback: true, **opts)
|
1332
|
+
got={}; missed=[]
|
1333
|
+
pkgs=queries.map {|p| Query.strip(p)}
|
1334
|
+
ignored = pkgs & @query_ignore
|
1335
|
+
queries.each do |query|
|
1336
|
+
if ignored.include?(Query.strip(query))
|
1337
|
+
got[query]=nil #=> means the query was ignored
|
1338
|
+
else
|
1339
|
+
pkg=self.find(query, provides: provides, **opts)
|
1340
|
+
if pkg
|
1341
|
+
got[query]=pkg
|
1342
|
+
else
|
1343
|
+
missed << query
|
1344
|
+
end
|
1345
|
+
end
|
1346
|
+
end
|
1347
|
+
# we do it this way to call ext_query in batch
|
1348
|
+
if ext_query and !missed.empty?
|
1349
|
+
found, new_pkgs=ext_query.call(*missed, provides: provides)
|
1350
|
+
self.merge(new_pkgs)
|
1351
|
+
got.merge!(found)
|
1352
|
+
missed-=found.keys
|
1353
|
+
end
|
1354
|
+
if fallback and !missed.empty?
|
1355
|
+
new_queries={}
|
1356
|
+
missed.each do |query|
|
1357
|
+
if (query_pkg=Query.strip(query)) != query
|
1358
|
+
new_queries[query]=query_pkg
|
1359
|
+
# missed.delete(query)
|
1360
|
+
end
|
1361
|
+
end
|
1362
|
+
unless new_queries.empty?
|
1363
|
+
SH.logger.warn "Trying fallback for packages: #{new_queries.keys.join(', ')}"
|
1364
|
+
fallback_got=self.resolve(*new_queries.values, provides: provides, ext_query: ext_query, fallback: false, **opts)
|
1365
|
+
got.merge!(fallback_got)
|
1366
|
+
SH.logger.warn "Missing packages: #{missed.map {|m| r=m; r<<" [fallback: #{fallback}]" if (fallback=fallback_got[new_queries[m]]); r}.join(', ')}"
|
1367
|
+
end
|
1368
|
+
else
|
1369
|
+
SH.logger.warn "Missing packages: #{missed.join(', ')}" if !missed.empty? and ext_query != false #ext_query=false is a hack to silence this message
|
1370
|
+
end
|
1371
|
+
got
|
1372
|
+
end
|
1373
|
+
|
1374
|
+
def get(*args)
|
1375
|
+
#compact because the resolution can be nil for an ignored package
|
1376
|
+
resolve(*args).values.compact
|
1377
|
+
end
|
1378
|
+
|
1379
|
+
def children(node, mode=@children_mode, verbose: false, **opts, &b)
|
1380
|
+
deps=@l.fetch(node).dependencies(mode)
|
1381
|
+
SH.logger.info "- #{node} => #{deps}" if verbose
|
1382
|
+
deps=get(*deps, **opts)
|
1383
|
+
SH.logger.info " => #{deps}" if verbose
|
1384
|
+
if b
|
1385
|
+
deps.each(&b)
|
1386
|
+
else
|
1387
|
+
deps
|
1388
|
+
end
|
1389
|
+
end
|
1390
|
+
|
1391
|
+
private def call_tsort(l, method: :tsort, **opts, &b)
|
1392
|
+
each_node=l.method(:each)
|
1393
|
+
s=self
|
1394
|
+
each_child = lambda do |node, &b|
|
1395
|
+
s.children(node, **opts, &b)
|
1396
|
+
end
|
1397
|
+
TSort.public_send(method, each_node, each_child, &b)
|
1398
|
+
end
|
1399
|
+
|
1400
|
+
def tsort(l, **opts, &b)
|
1401
|
+
if b
|
1402
|
+
call_tsort(l, method: :each_strongly_connected_component, **opts, &b)
|
1403
|
+
else
|
1404
|
+
r=call_tsort(l, method: :strongly_connected_components, **opts)
|
1405
|
+
cycles=r.select {|c| c.length > 1}
|
1406
|
+
SH.logger.warn "Cycles detected: #{cycles}" unless cycles.empty?
|
1407
|
+
r.flatten
|
1408
|
+
end
|
1409
|
+
end
|
1410
|
+
|
1411
|
+
# recursive get
|
1412
|
+
def rget(*pkgs)
|
1413
|
+
l=get(*pkgs)
|
1414
|
+
tsort(l)
|
1415
|
+
end
|
1416
|
+
|
1417
|
+
# check updates compared to another list
|
1418
|
+
def check_updates(l)
|
1419
|
+
l=self.class.create(l)
|
1420
|
+
a=self.latest; b=l.latest
|
1421
|
+
r={}
|
1422
|
+
b.each do |k, v|
|
1423
|
+
if a.key?(k)
|
1424
|
+
v1=a[k].version
|
1425
|
+
v2=v.version
|
1426
|
+
h={in: v1.to_s, out: v2.to_s, in_pkg: name_of(a[k]), out_pkg: name_of(v)}
|
1427
|
+
case v1 <=> v2
|
1428
|
+
when -1
|
1429
|
+
h[:op]=:upgrade
|
1430
|
+
when 0
|
1431
|
+
h[:op]=:equal
|
1432
|
+
when 1
|
1433
|
+
h[:op]=:downgrade
|
1434
|
+
end
|
1435
|
+
r[k]=h
|
1436
|
+
else
|
1437
|
+
#new installation
|
1438
|
+
r[k]={op: :install,
|
1439
|
+
in: nil,
|
1440
|
+
out: v.version.to_s, out_pkg: name_of(v)}
|
1441
|
+
end
|
1442
|
+
end
|
1443
|
+
(a.keys-b.keys).each do |k|
|
1444
|
+
r[k]={op: :obsolete,
|
1445
|
+
in: a[k].version.to_s,
|
1446
|
+
out: nil, in_pkg: name_of(a[k])}
|
1447
|
+
end
|
1448
|
+
r
|
1449
|
+
end
|
1450
|
+
|
1451
|
+
def select_updates(r)
|
1452
|
+
r.select {|_k,v| v[:op]==:upgrade or v[:op]==:install}.map {|_k, v| v[:out_pkg]}
|
1453
|
+
end
|
1454
|
+
|
1455
|
+
def get_updates(l, verbose: true, obsolete: true)
|
1456
|
+
c=check_updates(l)
|
1457
|
+
show_updates(c, obsolete: obsolete) if verbose
|
1458
|
+
select_updates(c)
|
1459
|
+
end
|
1460
|
+
|
1461
|
+
#take the result of check_updates and pretty print them
|
1462
|
+
def show_updates(r, obsolete: true)
|
1463
|
+
require 'simplecolor'
|
1464
|
+
r.each do |k,v|
|
1465
|
+
next if v[:op]==:equal
|
1466
|
+
next if obsolete and v[:op]==:obsolete
|
1467
|
+
vin= v[:in] ? v[:in] : "(none)"
|
1468
|
+
vout= v[:out] ? v[:out] : "(none)"
|
1469
|
+
op = "->"; op="<-" if v[:op]==:downgrade
|
1470
|
+
extra=""
|
1471
|
+
extra=" [#{v[:op]}]" if v[:op]!=:upgrade
|
1472
|
+
SH.logger.info SimpleColor.color(" -> #{k}: #{vin} #{op} #{vout}#{extra}", :black)
|
1473
|
+
end
|
1474
|
+
end
|
1475
|
+
|
1476
|
+
def check_update(ext_query=@ext_query)
|
1477
|
+
if ext_query
|
1478
|
+
_found, new_pkgs=ext_query.call(*packages)
|
1479
|
+
check_updates(new_pkgs)
|
1480
|
+
end
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
def update(**opts)
|
1484
|
+
install(update: true, **opts)
|
1485
|
+
end
|
1486
|
+
|
1487
|
+
# take a list of packages to install
|
1488
|
+
def install(*packages, update: false, ext_query: @ext_query, verbose: true, obsolete: true)
|
1489
|
+
packages+=self.packages if update
|
1490
|
+
if ext_query
|
1491
|
+
_found, new_pkgs=ext_query.call(*packages)
|
1492
|
+
SH.logger.info "# Checking packages" if verbose
|
1493
|
+
u=get_updates(new_pkgs, verbose: verbose, obsolete: obsolete)
|
1494
|
+
new=self.class.new(l.values).merge(new_pkgs)
|
1495
|
+
# The updates or new packages may need new deps
|
1496
|
+
SH.logger.info "# Checking dependencies" if verbose
|
1497
|
+
full=new.rget(*u)
|
1498
|
+
full_updates=get_updates(new.values_at(*full), verbose: verbose, obsolete: obsolete)
|
1499
|
+
yield u, full_updates if block_given?
|
1500
|
+
full_updates
|
1501
|
+
else
|
1502
|
+
SH.logger.warn "External query not defined"
|
1503
|
+
end
|
1504
|
+
end
|
1505
|
+
end
|
1506
|
+
|
1507
|
+
class AurCache < PackageList
|
1508
|
+
def self.create(v)
|
1509
|
+
v.is_a?(self) ? v : self.new(v)
|
1510
|
+
end
|
1511
|
+
|
1512
|
+
def initialize(l)
|
1513
|
+
super
|
1514
|
+
@ext_query=method(:ext_query)
|
1515
|
+
@query_ignore=AurPackageList.official
|
1516
|
+
end
|
1517
|
+
|
1518
|
+
def ext_query(*queries, provides: false)
|
1519
|
+
pkgs=queries.map {|p| Query.strip(p)}
|
1520
|
+
# in a query like foo>1000, even if foo exist and was queried,
|
1521
|
+
# the query fails so it gets called in ext_query
|
1522
|
+
# remove these packages
|
1523
|
+
# TODO: do the same for a provides query
|
1524
|
+
pkgs-=self.packages
|
1525
|
+
if pkgs.empty?
|
1526
|
+
l=self.class.new([])
|
1527
|
+
else
|
1528
|
+
SH.logger.warn "! Calling aur for infos on: #{pkgs.join(', ')}"
|
1529
|
+
l=AurQuery.packages(*pkgs)
|
1530
|
+
@query_ignore += pkgs - l.packages #these don't exist in aur
|
1531
|
+
end
|
1532
|
+
r=l.resolve(*queries, ext_query: false, fallback: false)
|
1533
|
+
return r, l
|
1534
|
+
end
|
1535
|
+
end
|
1536
|
+
|
1537
|
+
class AurPackageList < PackageList
|
1538
|
+
def self.create(v)
|
1539
|
+
v.is_a?(self) ? v : self.new(v)
|
1540
|
+
end
|
1541
|
+
|
1542
|
+
def self.official
|
1543
|
+
@official||=%w(core extra community).map {|repo| Repo.new(repo).list(mode: :pacman)}.flatten.compact
|
1544
|
+
end
|
1545
|
+
|
1546
|
+
def self.cache
|
1547
|
+
@cache ||= AurCache.new([])
|
1548
|
+
end
|
1549
|
+
|
1550
|
+
def initialize(l)
|
1551
|
+
super
|
1552
|
+
@missed=[]
|
1553
|
+
@ext_query=method(:ext_query)
|
1554
|
+
@children_mode=%i(depends make_depends check_depends)
|
1555
|
+
end
|
1556
|
+
|
1557
|
+
def official
|
1558
|
+
self.class.official
|
1559
|
+
end
|
1560
|
+
|
1561
|
+
def ext_query(*queries, provides: false)
|
1562
|
+
cache=self.class.cache
|
1563
|
+
got=cache.resolve(*queries, fallback: false, provides: provides)
|
1564
|
+
return got, self.class.new(cache.l.slice(*got.values.compact))
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
def do_update(**opts, &b)
|
1568
|
+
do_install(update: true, **opts)
|
1569
|
+
end
|
1570
|
+
|
1571
|
+
def do_install(*args, **opts)
|
1572
|
+
install_opts={}
|
1573
|
+
%i(update ext_query verbose obsolete).each do |key|
|
1574
|
+
opts.key?(key) && install_opts[key]=opts.delete(key)
|
1575
|
+
end
|
1576
|
+
deps=[]
|
1577
|
+
l=install(*args, **install_opts) do |orig, with_deps|
|
1578
|
+
deps=with_deps-orig
|
1579
|
+
end
|
1580
|
+
unless l.empty?
|
1581
|
+
m=MakepkgList.new(l.map {|p| Query.strip(p)})
|
1582
|
+
deps.each { |dep| m[Query.strip(dep)]&.asdeps=true }
|
1583
|
+
if block_given?
|
1584
|
+
yield m
|
1585
|
+
else
|
1586
|
+
m.install(**opts)
|
1587
|
+
end
|
1588
|
+
m
|
1589
|
+
end
|
1590
|
+
end
|
1591
|
+
end
|
1592
|
+
|
1593
|
+
class PacmanConf
|
1594
|
+
def self.create(v)
|
1595
|
+
v.is_a?(self) ? v : self.new(v, {}) #pass empty keywords so that a Hash is seen as an argument and not a list of keywords
|
1596
|
+
end
|
1597
|
+
|
1598
|
+
Archlinux.delegate_h(self, :@pacman_conf)
|
1599
|
+
attr_accessor :pacman_conf, :config
|
1600
|
+
|
1601
|
+
def initialize(conf="/etc/pacman.conf", config: Archlinux.config, **keys)
|
1602
|
+
@config=config
|
1603
|
+
if conf.is_a?(String) or conf.is_a?(Pathname)
|
1604
|
+
conf=parse(conf, **keys)
|
1605
|
+
end
|
1606
|
+
@pacman_conf=conf
|
1607
|
+
end
|
1608
|
+
|
1609
|
+
def self.parse(content)
|
1610
|
+
list=%i(HoldPkg IgnorePkg IgnoreGroup NoUpgrade NoExtract SigLevel LocalFileSigLevel RemoteFileSigLevel Usage Server)
|
1611
|
+
mode=:options
|
1612
|
+
config={options: {}, repos: {}}
|
1613
|
+
content=content.each_line if content.is_a?(String)
|
1614
|
+
content.each do |l|
|
1615
|
+
if (m=l.match(/^\[([\w-]+)\]$/))
|
1616
|
+
mode=m[1]
|
1617
|
+
if mode == "options"
|
1618
|
+
mode=:options
|
1619
|
+
else
|
1620
|
+
config[:repos][mode]||={}
|
1621
|
+
end
|
1622
|
+
else
|
1623
|
+
key, value=l.split(' = ', 2)
|
1624
|
+
key=key.to_sym
|
1625
|
+
h = mode==:options ? config[:options] : config[:repos][mode]
|
1626
|
+
if list.include?(key)
|
1627
|
+
h[key]||=[]
|
1628
|
+
h[key]<<value
|
1629
|
+
else
|
1630
|
+
h[key]=value
|
1631
|
+
end
|
1632
|
+
end
|
1633
|
+
end
|
1634
|
+
config
|
1635
|
+
end
|
1636
|
+
|
1637
|
+
def parse(file, raw: false, args: nil)
|
1638
|
+
unless args
|
1639
|
+
if raw
|
1640
|
+
args=[:'pacconf', "--raw", "--config=#{file}"]
|
1641
|
+
else
|
1642
|
+
args=[:'pacman-conf', "--config=#{file}"]
|
1643
|
+
end
|
1644
|
+
end
|
1645
|
+
output=@config.launch(*args) do |*args|
|
1646
|
+
SH.run_simple(*args, chomp: :lines)
|
1647
|
+
end
|
1648
|
+
self.class.parse(output)
|
1649
|
+
end
|
1650
|
+
|
1651
|
+
def non_official_repos
|
1652
|
+
repos=@pacman_conf[:repos]
|
1653
|
+
repos.slice(*(repos.keys - %i(core extra community multilib testing community-testing multilib-testing)))
|
1654
|
+
end
|
1655
|
+
|
1656
|
+
def to_s
|
1657
|
+
r=[]
|
1658
|
+
print_values=lambda do |h, section|
|
1659
|
+
r<<"[#{section}]" if section
|
1660
|
+
h.each do |k,v|
|
1661
|
+
case v
|
1662
|
+
when nil
|
1663
|
+
r << k
|
1664
|
+
when Array
|
1665
|
+
v.each { |vv| r << "#{k} = #{vv}" }
|
1666
|
+
else
|
1667
|
+
r << "#{k} = #{v}"
|
1668
|
+
end
|
1669
|
+
end
|
1670
|
+
end
|
1671
|
+
print_values.call(@pacman_conf[:options], "options")
|
1672
|
+
@pacman_conf[:repos].each do |section, props|
|
1673
|
+
print_values.call(props, section)
|
1674
|
+
end
|
1675
|
+
r.join("\n")+"\n"
|
1676
|
+
end
|
1677
|
+
|
1678
|
+
def tempfile
|
1679
|
+
SH::VirtualFile.new("pacman.conf", to_s)
|
1680
|
+
end
|
1681
|
+
end
|
1682
|
+
|
1683
|
+
class Devtools
|
1684
|
+
def self.create(v)
|
1685
|
+
v.is_a?(self) ? v : self.new(v)
|
1686
|
+
end
|
1687
|
+
Archlinux.delegate_h(self, :@opts)
|
1688
|
+
|
1689
|
+
attr_accessor :config, :opts
|
1690
|
+
def initialize(config: Archlinux.config, **opts)
|
1691
|
+
@config=config
|
1692
|
+
@opts=@config.opts.merge(opts)
|
1693
|
+
# %i(pacman_conf makepkg_conf).each do |key|
|
1694
|
+
# @opts[key]=@opts[key].tempfile.file if @opts[key].respond_to?(:tempfile)
|
1695
|
+
# end
|
1696
|
+
@opts[:chroot]=Pathname.new(@opts[:chroot]) if @opts[:chroot]
|
1697
|
+
add_binds
|
1698
|
+
end
|
1699
|
+
|
1700
|
+
def add_binds
|
1701
|
+
require 'uri'
|
1702
|
+
if (conf=@opts[:pacman_conf]).is_a?(PacmanConf)
|
1703
|
+
conf[:repos].each do |_name, opts|
|
1704
|
+
opts[:Server].each do |server|
|
1705
|
+
server.match(%r!file://(.*)!) do |m|
|
1706
|
+
@opts[:bind_ro]||=[]
|
1707
|
+
@opts[:bind_ro] << URI.unescape(m[1])
|
1708
|
+
end
|
1709
|
+
end
|
1710
|
+
end
|
1711
|
+
end
|
1712
|
+
end
|
1713
|
+
|
1714
|
+
def files
|
1715
|
+
%i(pacman_conf makepkg_conf).each do |key|
|
1716
|
+
if @opts[key]
|
1717
|
+
file=@opts[key]
|
1718
|
+
file=file.tempfile.file if file.respond_to?(:tempfile)
|
1719
|
+
yield key, file
|
1720
|
+
end
|
1721
|
+
end
|
1722
|
+
end
|
1723
|
+
|
1724
|
+
def pacman_config
|
1725
|
+
Pacman.create(@opts[:pacman_conf])
|
1726
|
+
end
|
1727
|
+
|
1728
|
+
def pacman(*args, default_opts: [], **opts)
|
1729
|
+
files do |key, file|
|
1730
|
+
default_opts += ["--config", file] if key==:pacman_conf
|
1731
|
+
end
|
1732
|
+
@config.launch(:pacman, *args, default_opts: default_opts, **opts) do |*args|
|
1733
|
+
SH.sh(*args)
|
1734
|
+
end
|
1735
|
+
end
|
1736
|
+
|
1737
|
+
def makepkg(*args, default_opts: [], **opts)
|
1738
|
+
files do |key, file|
|
1739
|
+
# trick to pass options to pacman
|
1740
|
+
args << "PACMAN_OPTS+=--config=#{file.shellescape}"
|
1741
|
+
default_opts += ["--config", file] if key==:makepkg_conf
|
1742
|
+
end
|
1743
|
+
@config.launch(:makepkg, *args, default_opts: default_opts, **opts) do |*args|
|
1744
|
+
SH.sh(*args)
|
1745
|
+
end
|
1746
|
+
end
|
1747
|
+
|
1748
|
+
|
1749
|
+
def nspawn(*args, root: @opts[:chroot]+'root', default_opts: [], **opts)
|
1750
|
+
files do |key, file|
|
1751
|
+
default_opts += ["-C", file] if key==:pacman_conf
|
1752
|
+
default_opts += ["-M", file] if key==:makepkg_conf
|
1753
|
+
end
|
1754
|
+
if (binds_ro=@opts[:bind_ro])
|
1755
|
+
binds_ro.each do |b|
|
1756
|
+
args.unshift("--bind-ro=#{b}")
|
1757
|
+
end
|
1758
|
+
end
|
1759
|
+
if (binds_rw=@opts[:bind_rw])
|
1760
|
+
binds_rw.each do |b|
|
1761
|
+
args.unshift("--bind=#{b}")
|
1762
|
+
end
|
1763
|
+
end
|
1764
|
+
args.unshift root
|
1765
|
+
|
1766
|
+
@config.launch(:'arch-nspawn', *args, default_opts: default_opts, **opts) do |*args|
|
1767
|
+
SH.sh(*args)
|
1768
|
+
end
|
1769
|
+
end
|
1770
|
+
|
1771
|
+
# this takes the same options as nspawn
|
1772
|
+
def mkarchroot(*args, nspawn: @config[:chroot_update], default_opts: [], **opts)
|
1773
|
+
files do |key, file|
|
1774
|
+
default_opts += ["-C", file] if key==:pacman_conf
|
1775
|
+
default_opts += ["-M", file] if key==:makepkg_conf
|
1776
|
+
end
|
1777
|
+
chroot=@opts[:chroot]
|
1778
|
+
chroot.sudo_mkpath unless chroot.directory?
|
1779
|
+
args.unshift(chroot+'root')
|
1780
|
+
if (chroot+'root'+'.arch-chroot').file?
|
1781
|
+
# Note that if nspawn is not called (and the chroot does not
|
1782
|
+
# exist), then the passed pacman.conf will not be replace the one
|
1783
|
+
# in the chroot. And when makechrootpkg calls nspawn, it does not
|
1784
|
+
# transmit the -C/-M options. So even if we don't want to update,
|
1785
|
+
# we should call a dummy bin like 'true'
|
1786
|
+
if nspawn
|
1787
|
+
nspawn=nspawn.shellsplit if nspawn.is_a?(String)
|
1788
|
+
self.nspawn(*nspawn, **opts)
|
1789
|
+
end
|
1790
|
+
else
|
1791
|
+
@config.launch(:mkarchroot, *args, default_opts: default_opts, escape: true, **opts) do |*args|
|
1792
|
+
SH.sh(*args)
|
1793
|
+
end
|
1794
|
+
end
|
1795
|
+
end
|
1796
|
+
|
1797
|
+
def makechrootpkg(*args, default_opts: [], **opts)
|
1798
|
+
default_opts+=['-r', @opts[:chroot]]
|
1799
|
+
if (binds_ro=@opts[:bind_ro])
|
1800
|
+
binds_ro.each do |b|
|
1801
|
+
default_opts += ["-D", b]
|
1802
|
+
end
|
1803
|
+
end
|
1804
|
+
if (binds_rw=@opts[:bind_rw])
|
1805
|
+
binds_rw.each do |b|
|
1806
|
+
default_opts += ["-d", b]
|
1807
|
+
end
|
1808
|
+
end
|
1809
|
+
|
1810
|
+
#makechrootpkg calls itself with sudo --preserve-env=SOURCE_DATE_EPOCH,GNUPGHOME so it does not keep PKGDEST..., work around this by providing our own sudo
|
1811
|
+
@config.launch(:makechrootpkg, *args, default_opts: default_opts, sudo: 'sudo --preserve-env=GNUPGHOME,PKGDEST,SOURCE_DATE_EPOCH', **opts) do |*args|
|
1812
|
+
SH.sh(*args)
|
1813
|
+
end
|
1814
|
+
end
|
1815
|
+
|
1816
|
+
def tmp_pacman(conf, **opts)
|
1817
|
+
PacmanConf.create(conf).tempfile.create(true) do |file|
|
1818
|
+
pacman=lambda do |*args, **pac_opts|
|
1819
|
+
@config.launch(:pacman, *args, default_opts: ["--config", file], **opts.merge(pac_opts)) do |*args|
|
1820
|
+
SH.sh(*args)
|
1821
|
+
end
|
1822
|
+
end
|
1823
|
+
yield pacman, file
|
1824
|
+
end
|
1825
|
+
end
|
1826
|
+
|
1827
|
+
def sync_db(*names)
|
1828
|
+
conf=PacmanConf.create(@opts[:pacman_conf])
|
1829
|
+
new_conf={options: conf[:options], repos: {}}
|
1830
|
+
repos=conf[:repos]
|
1831
|
+
names.each do |name|
|
1832
|
+
if repos[name]
|
1833
|
+
new_conf[:repos][name]=repos[name]
|
1834
|
+
else
|
1835
|
+
SH.logger.warn "sync_db: unknown repo #{name}"
|
1836
|
+
end
|
1837
|
+
end
|
1838
|
+
tmp_pacman(new_conf) do |pacman|
|
1839
|
+
if block_given?
|
1840
|
+
yield(pacman)
|
1841
|
+
else
|
1842
|
+
pacman['-Syu', sudo: true]
|
1843
|
+
end
|
1844
|
+
end
|
1845
|
+
end
|
1846
|
+
|
1847
|
+
end
|
1848
|
+
end
|
1849
|
+
|
1850
|
+
=begin
|
1851
|
+
aur=Archlinux::AurPackageList.new([])
|
1852
|
+
l=aur.install("pacaur")
|
1853
|
+
|
1854
|
+
aur=Archlinux::AurPackageList.new(Archlinux.config.db.packages)
|
1855
|
+
aur.do_update
|
1856
|
+
=end
|
1857
|
+
|
1858
|
+
# TODO:
|
1859
|
+
# --devel
|
1860
|
+
# aur provides
|
1861
|
+
# @ignore
|