aur.rb 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|