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.
@@ -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
@@ -0,0 +1,6 @@
1
+ /.bundle
2
+ /.yardoc/
3
+ /Gemfile.lock
4
+ /doc/
5
+ /pkg/
6
+ /vendor/cache/*.gem
@@ -0,0 +1,6 @@
1
+ --markup markdown
2
+ --title "aur.rb Documentation"
3
+ --protected
4
+ -
5
+ ChangeLog.md
6
+ LICENSE.txt
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 2018-09-07
2
+
3
+ * Initial release:
4
+
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'kramdown'
7
+ end
@@ -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.
@@ -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
+ [![Gem Version](https://img.shields.io/gem/v/aur.rb.svg)](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.
@@ -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
+
@@ -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
@@ -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"
@@ -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