aur.rb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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