aur.rb 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ChangeLog.md +136 -2
- data/LICENSE.txt +1 -1
- data/README.md +21 -1
- data/Rakefile +7 -10
- data/TODO.md +69 -0
- data/aur.rb.gemspec +1 -0
- data/bin/aur.rb +6 -0
- data/lib/aur.rb +43 -1853
- data/lib/aur/aur_rpc.rb +178 -0
- data/lib/aur/cli.rb +334 -0
- data/lib/aur/config.rb +330 -0
- data/lib/aur/db.rb +328 -0
- data/lib/aur/devtools.rb +258 -0
- data/lib/aur/helpers.rb +56 -0
- data/lib/aur/install_packages.rb +158 -0
- data/lib/aur/load_config.rb +5 -0
- data/lib/aur/makepkg.rb +445 -0
- data/lib/aur/no_load_config.rb +5 -0
- data/lib/aur/packages.rb +599 -0
- data/lib/aur/repos.rb +343 -0
- data/lib/aur/version.rb +1 -1
- data/lib/aur/versions.rb +186 -0
- metadata +22 -5
data/lib/aur/repos.rb
ADDED
@@ -0,0 +1,343 @@
|
|
1
|
+
require 'aur/helpers'
|
2
|
+
require 'aur/packages'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Archlinux
|
6
|
+
# this hold a repo name
|
7
|
+
class Repo
|
8
|
+
extend CreateHelper
|
9
|
+
|
10
|
+
attr_accessor :repo, :config
|
11
|
+
def initialize(name, config: Archlinux.config)
|
12
|
+
@repo=name
|
13
|
+
@config=config
|
14
|
+
end
|
15
|
+
|
16
|
+
def list(mode: :pacsift)
|
17
|
+
command= case mode
|
18
|
+
when :pacman, :name
|
19
|
+
if @repo=="local"
|
20
|
+
"pacman -Qq"
|
21
|
+
else
|
22
|
+
"pacman -Slq #{@repo.shellescape}" #returns pkg
|
23
|
+
end
|
24
|
+
when :repo_name
|
25
|
+
#like pacsift, but using pacman
|
26
|
+
list(mode: :name).map {|i| @repo+"/"+i}
|
27
|
+
when :pacsift
|
28
|
+
#this mode is prefered, so that if the same pkg is in different
|
29
|
+
#repo, than pacman_info returns the correct info
|
30
|
+
#pacsift understand the 'local' repo
|
31
|
+
"pacsift --exact --repo=#{@repo.shellescape} <&-" #returns repo/pkg
|
32
|
+
when :paclist, :name_version
|
33
|
+
#cannot show the local repo; we could use `expac -Q '%n %v' but we
|
34
|
+
#don't use this mode anyway
|
35
|
+
"paclist #{@repo.shellescape}" #returns 'pkg version'
|
36
|
+
end
|
37
|
+
SH.run_simple(command, chomp: :lines) {return nil}
|
38
|
+
end
|
39
|
+
|
40
|
+
def packages(refresh=false)
|
41
|
+
@packages=nil if refresh
|
42
|
+
@packages ||= @config.to_packages(self.class.info(*list))
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.pacman_info(*pkgs, local: false) #local=true refers to the local db info. Note that pacman does not understand local/pkg but pacinfo does
|
46
|
+
list=[]
|
47
|
+
to_list=lambda do |s|
|
48
|
+
return [] if s=="None"
|
49
|
+
s.split
|
50
|
+
end
|
51
|
+
# Note: core/pacman only works for -Sddp or -Si, not for -Qi
|
52
|
+
# Indeed local/pacman works for pacinfo, but not for pacman (needs
|
53
|
+
# the -Q options)
|
54
|
+
res=SH.run_simple({'COLUMNS' => '1000'}, "pacman -#{local ? 'Q': 'S'}i #{pkgs.shelljoin}", chomp: :lines)
|
55
|
+
key=nil; info={}
|
56
|
+
res.each do |l|
|
57
|
+
if key==:optional_deps and (m=l.match(/^\s+(\w*):\s+(.*)$/))
|
58
|
+
#here we cannot split(':') because we need to check for the leading space
|
59
|
+
info[key][m[1]]=m[2]
|
60
|
+
else
|
61
|
+
key, value=l.split(/\s*:\s*/,2)
|
62
|
+
if key.nil? #new package
|
63
|
+
list << info; key=nil; info={}
|
64
|
+
next
|
65
|
+
end
|
66
|
+
key=key.strip.downcase.gsub(' ', '_').to_sym
|
67
|
+
case key
|
68
|
+
when :optional_deps
|
69
|
+
dep, reason=value.split(/\s*:\s*/,2)
|
70
|
+
value={dep => reason}
|
71
|
+
when :groups, :provides, :depends_on, :required_by, :optional_for, :conflicts_with, :replaces
|
72
|
+
value=to_list.call(value)
|
73
|
+
when :install_script
|
74
|
+
value=false if value=="No"
|
75
|
+
value=true if value=="Yes"
|
76
|
+
when :install_date, :build_date
|
77
|
+
value=Time.parse(value)
|
78
|
+
end
|
79
|
+
info[key]=value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
# no need to add the last info, pacman -Q/Si always end with a new line
|
83
|
+
list
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.pacinfo(*pkgs) #this refers to the local db info
|
87
|
+
list=[]; info={}
|
88
|
+
res=SH.run_simple("pacinfo #{pkgs.shelljoin}", chomp: :lines)
|
89
|
+
res.each do |l|
|
90
|
+
key, value=l.split(/\s*:\s*/,2)
|
91
|
+
if key.nil? #next package
|
92
|
+
list << info; info={}
|
93
|
+
next
|
94
|
+
end
|
95
|
+
key=key.downcase.gsub(' ', '_').to_sym
|
96
|
+
case key
|
97
|
+
when :install_script
|
98
|
+
value=false if value=="No"
|
99
|
+
value=true if value=="Yes"
|
100
|
+
when :install_date, :build_date
|
101
|
+
value=Time.parse(value)
|
102
|
+
end
|
103
|
+
Archlinux.add_to_hash(info, key, value)
|
104
|
+
end
|
105
|
+
# no need to add at the end, pacinfo always end with a new line
|
106
|
+
list
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.info(*packages)
|
110
|
+
if SH.find_executable("pacinfo")
|
111
|
+
pacinfo(*packages)
|
112
|
+
else
|
113
|
+
pacman_info(*packages)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Exemple: Archlinux::Repo.packages(* %x/pacman -Qqm/.split)
|
118
|
+
# Warning: this does not use config, so this is just for a convenience
|
119
|
+
# helper. Use RepoPkgs for install/config stuff
|
120
|
+
def self.packages(*packages)
|
121
|
+
PackageList.new(info(*packages))
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.foreign_list
|
125
|
+
%x(pacman -Qqm).split
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.foreign_packages
|
129
|
+
packages(*foreign_list)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# can combined several repos
|
134
|
+
class RepoPkgs
|
135
|
+
extend CreateHelper
|
136
|
+
attr_accessor :list, :config
|
137
|
+
|
138
|
+
def initialize(list, config: Archlinux.config)
|
139
|
+
@list=list
|
140
|
+
@config=config
|
141
|
+
end
|
142
|
+
|
143
|
+
def infos
|
144
|
+
Repo.info(*@list)
|
145
|
+
end
|
146
|
+
|
147
|
+
def packages(refresh=false)
|
148
|
+
@packages=nil if refresh
|
149
|
+
@packages ||= @config.to_packages(infos)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class LocalRepo
|
154
|
+
extend CreateHelper
|
155
|
+
attr_accessor :dir, :config
|
156
|
+
|
157
|
+
def initialize(dir="/var/lib/pacman/local", config: Archlinux.config)
|
158
|
+
@dir=Pathname.new(dir)
|
159
|
+
@config=config
|
160
|
+
end
|
161
|
+
|
162
|
+
def infos
|
163
|
+
#todo: this is essentially the same code as for db repo; factorize this?
|
164
|
+
list=[]
|
165
|
+
@dir.glob("*/desc").each do |desc|
|
166
|
+
pkg={repo: :local}; mode=nil
|
167
|
+
desc.read.each_line do |l|
|
168
|
+
l.chomp!
|
169
|
+
next if l.empty?
|
170
|
+
if (m=l.match(/^%([A-Z0-9]*)%$/))
|
171
|
+
mode=m[1].downcase.to_sym
|
172
|
+
else
|
173
|
+
l=l.to_i if mode==:csize or mode==:isize
|
174
|
+
l=Time.at(l.to_i) if mode==:builddate
|
175
|
+
Archlinux.add_to_hash(pkg, mode, l)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
list << pkg
|
179
|
+
end
|
180
|
+
list
|
181
|
+
end
|
182
|
+
|
183
|
+
def packages(refresh=false)
|
184
|
+
@packages=nil if refresh
|
185
|
+
@packages ||= @config.to_packages(infos)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# a list of packages archives
|
190
|
+
class PackageFiles
|
191
|
+
extend CreateHelper
|
192
|
+
|
193
|
+
attr_accessor :files
|
194
|
+
def initialize(*files, config: Archlinux.config)
|
195
|
+
files=files.flatten #in case we are used with create
|
196
|
+
@files=files.map {|file| Pathname.new(file)}
|
197
|
+
@config=config
|
198
|
+
end
|
199
|
+
|
200
|
+
def infos
|
201
|
+
# expac_infos
|
202
|
+
bsdtar_infos
|
203
|
+
end
|
204
|
+
|
205
|
+
def bsdtar_infos
|
206
|
+
list=[]
|
207
|
+
@files.each do |file|
|
208
|
+
info={repo: file}
|
209
|
+
SH.run_simple("bsdtar -xOqf #{file.shellescape} .PKGINFO", chomp: :lines) do |error|
|
210
|
+
SH.logger.info "Skipping #{file}: #{error}"
|
211
|
+
next
|
212
|
+
end.each do |l|
|
213
|
+
next if l=~/^#/
|
214
|
+
key, value=l.split(/\s*=\s*/,2)
|
215
|
+
key=key.to_sym
|
216
|
+
case key
|
217
|
+
when :builddate
|
218
|
+
value=Time.at(value.to_i)
|
219
|
+
when :size
|
220
|
+
key=:install_size; value=value.to_i
|
221
|
+
when :pkgver
|
222
|
+
key=:version
|
223
|
+
end
|
224
|
+
Archlinux.add_to_hash(info, key, value)
|
225
|
+
end
|
226
|
+
# no need to add at the end, pacinfo always end with a new line
|
227
|
+
list << info
|
228
|
+
end
|
229
|
+
list
|
230
|
+
end
|
231
|
+
|
232
|
+
def expac_infos(slice=200) #the command line should not be too long
|
233
|
+
format={
|
234
|
+
filename: "%f",
|
235
|
+
pkgname: "%n",
|
236
|
+
pkgbase: "%e",
|
237
|
+
version: "%v",
|
238
|
+
url: "%u",
|
239
|
+
description: "%d",
|
240
|
+
packager: "%p",
|
241
|
+
architecture: "%a",
|
242
|
+
build_date: "%b",
|
243
|
+
download_size: "%k",
|
244
|
+
install_size: "%m",
|
245
|
+
depends: "%D",
|
246
|
+
conflicts: "%H",
|
247
|
+
opt_depends: "%O",
|
248
|
+
provides: "%P",
|
249
|
+
replaces: "%T",
|
250
|
+
# format << "%r\n" #repo
|
251
|
+
}
|
252
|
+
total=format.keys.count
|
253
|
+
r=[]; delim=" , "
|
254
|
+
split=lambda do |l| l.split(delim) end
|
255
|
+
@files.each_slice(slice) do |files|
|
256
|
+
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|
|
257
|
+
info={}
|
258
|
+
format.keys.each_with_index do |k, kk|
|
259
|
+
value=l[kk]
|
260
|
+
value=split[value] if %i(depends conflicts opt_depends provides replaces).include?(k)
|
261
|
+
value=nil if k==:pkgbase and value=="(null)"
|
262
|
+
value=Time.at(value.to_i) if k==:build_date
|
263
|
+
value=value.to_i if k==:download_size or k==:install_size
|
264
|
+
info[k]=value if value
|
265
|
+
end
|
266
|
+
info[:repo]=files[i]
|
267
|
+
r<<info
|
268
|
+
end
|
269
|
+
end
|
270
|
+
r
|
271
|
+
end
|
272
|
+
|
273
|
+
def packages(refresh=false)
|
274
|
+
@packages=nil if refresh
|
275
|
+
@packages ||= @config.to_packages(self.infos)
|
276
|
+
end
|
277
|
+
|
278
|
+
def sign(sign_name: :package, **opts)
|
279
|
+
@config&.sign(*@files, sign_name: sign_name, **opts)
|
280
|
+
end
|
281
|
+
|
282
|
+
def self.rm_files(*files, dir: nil)
|
283
|
+
deleted=[]
|
284
|
+
files.each do |file|
|
285
|
+
path=Pathname.new(file)
|
286
|
+
path=dir+path if path.relative? and dir
|
287
|
+
if path.exist?
|
288
|
+
path.rm
|
289
|
+
deleted << path
|
290
|
+
end
|
291
|
+
sig=Pathname.new("#{path}.sig")
|
292
|
+
if sig.exist?
|
293
|
+
sig.rm
|
294
|
+
deleted << sig
|
295
|
+
end
|
296
|
+
end
|
297
|
+
SH.logger.verbose2 "Deleted: #{deleted}"
|
298
|
+
deleted
|
299
|
+
end
|
300
|
+
|
301
|
+
# pass packages names to remove
|
302
|
+
def rm_pkgs(*pkgs)
|
303
|
+
files=[]
|
304
|
+
pkgs.each do |pkg_name|
|
305
|
+
pkg=packages.fetch(pkg_name, nil)
|
306
|
+
files << pkg.path if pkg
|
307
|
+
end
|
308
|
+
@packages=nil #we need to refresh the list.
|
309
|
+
self.class.rm_files(*files)
|
310
|
+
end
|
311
|
+
|
312
|
+
def clean(dry_run: true)
|
313
|
+
to_clean=[]
|
314
|
+
latest=packages.latest.values
|
315
|
+
packages.each do |_name, pkg|
|
316
|
+
to_clean << pkg unless latest.include?(pkg)
|
317
|
+
end
|
318
|
+
if block_given?
|
319
|
+
to_clean = yield to_clean
|
320
|
+
end
|
321
|
+
unless dry_run
|
322
|
+
to_clean.each do |f|
|
323
|
+
p=f.path
|
324
|
+
p.rm if p.exist?
|
325
|
+
sig=Pathname.new(p.to_s+".sig")
|
326
|
+
sig.rm if sig.exist?
|
327
|
+
end
|
328
|
+
end
|
329
|
+
return to_clean.map {|pkg| pkg.path}, to_clean
|
330
|
+
end
|
331
|
+
|
332
|
+
def self.from_dir(dir, config: Archlinux.config)
|
333
|
+
dir=Pathname.new(dir)
|
334
|
+
list=dir.glob('*.pkg.*').map do |g|
|
335
|
+
next if g.to_s.end_with?('~') or g.to_s.end_with?('.sig')
|
336
|
+
f=dir+g
|
337
|
+
next unless f.readable?
|
338
|
+
f
|
339
|
+
end.compact
|
340
|
+
self.new(*list, config: config)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
data/lib/aur/version.rb
CHANGED
data/lib/aur/versions.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'aur/helpers'
|
2
|
+
|
3
|
+
module Archlinux
|
4
|
+
class Version
|
5
|
+
def self.create(v)
|
6
|
+
v.is_a?(self) ? v : self.new(v)
|
7
|
+
end
|
8
|
+
|
9
|
+
include Comparable
|
10
|
+
attr_reader :epoch, :version, :pkgrel
|
11
|
+
def initialize(*v)
|
12
|
+
if v.length==1
|
13
|
+
@v=v.first
|
14
|
+
parse(@v)
|
15
|
+
elsif v.empty?
|
16
|
+
@epoch=-1 #any version is better than empty version
|
17
|
+
@version=Gem::Version.new(0)
|
18
|
+
@pkgrel=nil
|
19
|
+
else
|
20
|
+
@v=v
|
21
|
+
epoch, version, pkgrel=v
|
22
|
+
@epoch=epoch || 0
|
23
|
+
@real_epoch= epoch ? true : false
|
24
|
+
@real_version=version
|
25
|
+
@version=set_version(version)
|
26
|
+
@pkgrel=pkgrel
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Gem::Version is super pickly :-(
|
31
|
+
def set_version(version)
|
32
|
+
version=version.tr('+_','.')
|
33
|
+
@version=Gem::Version.new(version) rescue Gem::Version.new("0.#{version}")
|
34
|
+
end
|
35
|
+
|
36
|
+
private def parse(v)
|
37
|
+
if v.nil? or v.empty?
|
38
|
+
@epoch=-1 #any version is better than empty version
|
39
|
+
@version=Gem::Version.new(0)
|
40
|
+
@pkgrel=nil
|
41
|
+
return
|
42
|
+
end
|
43
|
+
epoch, rest = v.split(':', 2)
|
44
|
+
if rest.nil?
|
45
|
+
rest=epoch; epoch=0
|
46
|
+
@real_epoch=false
|
47
|
+
else
|
48
|
+
@real_epoch=true
|
49
|
+
end
|
50
|
+
@epoch=epoch
|
51
|
+
version, pkgrel=Utils.rsplit(rest, '-', 2)
|
52
|
+
set_version(version)
|
53
|
+
@real_version=version
|
54
|
+
@pkgrel=pkgrel.to_i
|
55
|
+
end
|
56
|
+
|
57
|
+
def <=>(w)
|
58
|
+
w=self.class.new(w.to_s)
|
59
|
+
r= @epoch <=> w.epoch
|
60
|
+
if r==0
|
61
|
+
r= @version <=> w.version
|
62
|
+
if r==0 and @pkgrel and w.pkgrel
|
63
|
+
r= @pkgrel <=> w.pkgrel
|
64
|
+
end
|
65
|
+
end
|
66
|
+
r
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
# @v.to_s
|
71
|
+
r=""
|
72
|
+
r << "#{@epoch}:" if @real_epoch
|
73
|
+
r << "#{@real_version}"
|
74
|
+
r << "-#{@pkgrel}" if @pkgrel
|
75
|
+
r
|
76
|
+
end
|
77
|
+
|
78
|
+
# strip version information from a package name
|
79
|
+
def self.strip(v)
|
80
|
+
v.sub(/[><=]+[\w.\-+:]*$/,'')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# ex: pacman>=2.0.0
|
85
|
+
QueryError=Class.new(ArchlinuxError)
|
86
|
+
class Query
|
87
|
+
def self.create(v)
|
88
|
+
v.is_a?(self) ? v : self.new(v)
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.strip(query)
|
92
|
+
self.create(query).name
|
93
|
+
end
|
94
|
+
|
95
|
+
include Comparable
|
96
|
+
attr_accessor :name, :op, :version, :op2, :version2
|
97
|
+
def initialize(query)
|
98
|
+
@query=query
|
99
|
+
@name, @op, version, @op2, version2=parse(query)
|
100
|
+
@version=Version.new(version)
|
101
|
+
@version2=Version.new(version2)
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_s
|
105
|
+
@query
|
106
|
+
end
|
107
|
+
|
108
|
+
def max
|
109
|
+
strict=false
|
110
|
+
if @op=='<=' or @op=='='
|
111
|
+
max=@version
|
112
|
+
elsif @op=="<"
|
113
|
+
max=@version; strict=true
|
114
|
+
elsif @op2=="<="
|
115
|
+
max=@version2
|
116
|
+
elsif @op2=="<"
|
117
|
+
max=@version2; strict=true
|
118
|
+
end
|
119
|
+
return max, strict #nil means Float::INFINITY, ie no restriction
|
120
|
+
end
|
121
|
+
|
122
|
+
def min
|
123
|
+
strict=false
|
124
|
+
if @op=='>=' or @op=='='
|
125
|
+
min=@version
|
126
|
+
elsif @op==">"
|
127
|
+
min=@version; strict=true
|
128
|
+
elsif @op2==">="
|
129
|
+
min=@version2
|
130
|
+
elsif @op2==">"
|
131
|
+
min=@version2; strict=true
|
132
|
+
end
|
133
|
+
return min, strict
|
134
|
+
end
|
135
|
+
|
136
|
+
def <=>(other)
|
137
|
+
other=self.class.create(other)
|
138
|
+
min, strict=self.min
|
139
|
+
omin, ostrict=other.min
|
140
|
+
return 1 if min==omin and strict
|
141
|
+
return -1 if min==omin and ostrict
|
142
|
+
min <=> omin
|
143
|
+
end
|
144
|
+
|
145
|
+
# here we check if a package can satisfy a query
|
146
|
+
# note that other can itself be a query, think of a package that
|
147
|
+
# requires foo>=2.0 and bar which provides foo>=3
|
148
|
+
# satisfy? is symmetric, it means that the intersection of the
|
149
|
+
# available version ranges is non empty
|
150
|
+
def satisfy?(other)
|
151
|
+
case other
|
152
|
+
when Version
|
153
|
+
omin=other; omax=other; ominstrict=false; omaxstrict=false
|
154
|
+
oname=@name #we assume the name comparison was already done
|
155
|
+
else
|
156
|
+
other=self.class.create(other)
|
157
|
+
omax, omaxstrict=other.max
|
158
|
+
omin, ominstrict=other.min
|
159
|
+
oname=other.name
|
160
|
+
end
|
161
|
+
return false unless @name==oname
|
162
|
+
min, strict=self.min
|
163
|
+
return false if omax and min and (omax < min or omax == min && (strict or omaxstrict))
|
164
|
+
max, strict=self.max
|
165
|
+
return false if max and omin and (omin > max or omin == max && (strict or ominstrict))
|
166
|
+
true
|
167
|
+
end
|
168
|
+
|
169
|
+
private def parse(query)
|
170
|
+
if (m=query.match(/^([^><=]*)([><=]*)([\w.\-+:]*)([><=]*)([\w.\-+:]*)$/))
|
171
|
+
name=m[1]
|
172
|
+
op=m[2]
|
173
|
+
version=m[3]
|
174
|
+
op2=m[4]
|
175
|
+
version2=m[5]
|
176
|
+
if op.nil?
|
177
|
+
name, version=Utils.rsplit(name, '-', 2)
|
178
|
+
op="="; op2=nil; version2=nil
|
179
|
+
end
|
180
|
+
return name, op, version, op2, version2
|
181
|
+
else
|
182
|
+
raise QueryError.new("Bad query #{query}")
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|