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.
@@ -0,0 +1,258 @@
1
+ require 'aur/config'
2
+
3
+ module Archlinux
4
+ class PacmanConf
5
+ def self.create(v, config: Archlinux.config)
6
+ v.is_a?(self) ? v : self.new(v, config: config) #pass empty keywords so that a Hash is seen as an argument and not a list of keywords
7
+ end
8
+
9
+ Archlinux.delegate_h(self, :@pacman_conf)
10
+ attr_accessor :pacman_conf, :config
11
+
12
+ def initialize(conf="/etc/pacman.conf", config: Archlinux.config, **keys)
13
+ @config=config
14
+ if conf.is_a?(String) or conf.is_a?(Pathname)
15
+ conf=parse(conf, **keys)
16
+ end
17
+ @pacman_conf=conf
18
+ end
19
+
20
+ def self.parse(content)
21
+ list=%i(HoldPkg IgnorePkg IgnoreGroup NoUpgrade NoExtract SigLevel LocalFileSigLevel RemoteFileSigLevel Usage Server)
22
+ mode=:options
23
+ config={options: {}, repos: {}}
24
+ content=content.each_line if content.is_a?(String)
25
+ content.each do |l|
26
+ if (m=l.match(/^\[([\w-]+)\]$/))
27
+ mode=m[1]
28
+ if mode == "options"
29
+ mode=:options
30
+ else
31
+ config[:repos][mode]||={}
32
+ end
33
+ else
34
+ key, value=l.split(' = ', 2)
35
+ key=key.to_sym
36
+ h = mode==:options ? config[:options] : config[:repos][mode]
37
+ if list.include?(key)
38
+ h[key]||=[]
39
+ h[key] << value
40
+ else
41
+ h[key]=value
42
+ end
43
+ end
44
+ end
45
+ config
46
+ end
47
+
48
+ def parse(file, raw: false, args: nil)
49
+ unless args
50
+ if raw
51
+ args=[:'pacconf', "--raw", "--config=#{file}"]
52
+ else
53
+ args=[:'pacman-conf', "--config=#{file}"]
54
+ end
55
+ end
56
+ output=@config.launch(*args) do |*args|
57
+ SH.run_simple(*args, chomp: :lines)
58
+ end
59
+ self.class.parse(output)
60
+ end
61
+
62
+ def non_official_repos
63
+ repos=@pacman_conf[:repos]
64
+ repos.slice(*(repos.keys - %w(core extra community multilib testing community-testing multilib-testing)))
65
+ end
66
+
67
+ def to_s
68
+ r=[]
69
+ print_values=lambda do |h, section|
70
+ r<<"[#{section}]" if section
71
+ h.each do |k,v|
72
+ case v
73
+ when nil
74
+ r << k
75
+ when Array
76
+ v.each { |vv| r << "#{k} = #{vv}" }
77
+ else
78
+ r << "#{k} = #{v}"
79
+ end
80
+ end
81
+ end
82
+ print_values.call(@pacman_conf[:options], "options")
83
+ @pacman_conf[:repos].each do |section, props|
84
+ print_values.call(props, section)
85
+ end
86
+ r.join("\n")+"\n"
87
+ end
88
+
89
+ def tempfile
90
+ SH::VirtualFile.new("pacman.conf", to_s)
91
+ end
92
+ end
93
+
94
+ class Devtools
95
+ def self.create(v)
96
+ v.is_a?(self) ? v : self.new(v)
97
+ end
98
+ Archlinux.delegate_h(self, :@opts)
99
+
100
+ attr_accessor :config, :opts
101
+ def initialize(config: Archlinux.config, **opts)
102
+ @config=config
103
+ @opts=@config.opts.merge(opts)
104
+ # %i(pacman_conf makepkg_conf).each do |key|
105
+ # @opts[key]=@opts[key].tempfile.file if @opts[key].respond_to?(:tempfile)
106
+ # end
107
+ root=@opts.dig(:chroot, :root) and @opts[:chroot][:root]=Pathname.new(root)
108
+ add_binds
109
+ end
110
+
111
+ def add_binds
112
+ require 'uri'
113
+ if (conf=@opts[:pacman_conf]).is_a?(PacmanConf)
114
+ conf[:repos].each do |_name, opts|
115
+ opts[:Server].each do |server|
116
+ server.match(%r!file://(.*)!) do |m|
117
+ @opts[:bind_ro]||=[]
118
+ @opts[:bind_ro] << URI.decode_www_form_component(m[1])
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ def files
126
+ %i(pacman_conf makepkg_conf).each do |key|
127
+ if @opts[key]
128
+ file=@opts[key]
129
+ file=file.tempfile.file if file.respond_to?(:tempfile)
130
+ yield key, file
131
+ end
132
+ end
133
+ end
134
+
135
+ def pacman_config
136
+ Pacman.create(@opts[:pacman_conf])
137
+ end
138
+
139
+ def pacman(*args, default_opts: [], **opts, &b)
140
+ files do |key, file|
141
+ default_opts += ["--config", file] if key==:pacman_conf
142
+ end
143
+ opts[:method]||=:sh
144
+ @config.launch(:pacman, *args, default_opts: default_opts, **opts, &b)
145
+ end
146
+
147
+ def makepkg(*args, run: :sh, default_opts: [], **opts, &b)
148
+ files do |key, file|
149
+ # trick to pass options to pacman
150
+ args << "PACMAN_OPTS+=--config=#{file.shellescape}"
151
+ default_opts += ["--config", file] if key==:makepkg_conf
152
+ end
153
+ opts[:method]||=run
154
+ @config.launch(:makepkg, *args, default_opts: default_opts, **opts, &b)
155
+ end
156
+
157
+
158
+ def nspawn(*args, root: @opts.dig(:chroot,:root)+'root', default_opts: [], **opts, &b)
159
+ files do |key, file|
160
+ default_opts += ["-C", file] if key==:pacman_conf
161
+ default_opts += ["-M", file] if key==:makepkg_conf
162
+ end
163
+ if (binds_ro=@opts[:bind_ro])
164
+ binds_ro.each do |b|
165
+ args.unshift("--bind-ro=#{b}")
166
+ end
167
+ end
168
+ if (binds_rw=@opts[:bind_rw])
169
+ binds_rw.each do |b|
170
+ args.unshift("--bind=#{b}")
171
+ end
172
+ end
173
+ args.unshift root
174
+
175
+ opts[:method]||=:sh
176
+ @config.launch(:'arch-nspawn', *args, default_opts: default_opts, **opts, &b)
177
+ end
178
+
179
+ # this takes the same options as nspawn
180
+ # => this creates or update a chroot
181
+ def mkarchroot(*args, nspawn: @opts.dig(:chroot, :update), default_opts: [], root: @opts.dig(:chroot, :root), **opts, &b)
182
+ files do |key, file|
183
+ default_opts += ["-C", file] if key==:pacman_conf
184
+ default_opts += ["-M", file] if key==:makepkg_conf
185
+ end
186
+ root.sudo_mkpath unless root.directory?
187
+ root=root+'root'
188
+ opts[:method]||=:sh
189
+ if (root+'.arch-chroot').file?
190
+ # Note that if nspawn is not called (and the chroot does not
191
+ # exist), then the passed pacman.conf will not be replace the one
192
+ # in the chroot. And when makechrootpkg calls nspawn, it does not
193
+ # transmit the -C/-M options. So even if we don't want to update,
194
+ # we should call a dummy bin like 'true'
195
+ if nspawn
196
+ return nspawn.call(root) if nspawn.is_a?(Proc)
197
+ nspawn=nspawn.shellsplit if nspawn.is_a?(String)
198
+ self.nspawn(*nspawn, root: root, **opts, &b)
199
+ end
200
+ else
201
+ @config.launch(:mkarchroot, root, *args, default_opts: default_opts, sudo: @config.sudo, **opts,&b)
202
+ end
203
+ end
204
+
205
+ def makechrootpkg(*args, default_opts: [], **opts, &b)
206
+ default_opts+=['-r', @opts.dig(:chroot, :root)]
207
+ if (binds_ro=@opts[:bind_ro])
208
+ binds_ro.each do |b|
209
+ default_opts += ["-D", b]
210
+ end
211
+ end
212
+ if (binds_rw=@opts[:bind_rw])
213
+ binds_rw.each do |b|
214
+ default_opts += ["-d", b]
215
+ end
216
+ end
217
+ opts[:method]||=:sh
218
+
219
+ #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
220
+ #@config.launch(:makechrootpkg, *args, default_opts: default_opts, sudo: @config.sudo('sudo --preserve-env=GNUPGHOME,PKGDEST,SOURCE_DATE_EPOCH'), **opts, &b)
221
+ ## Update: this has been fixed in devtools-20190329
222
+ @config.launch(:makechrootpkg, *args, default_opts: default_opts, **opts, &b)
223
+ end
224
+
225
+ def tmp_pacman(conf, **opts)
226
+ PacmanConf.create(conf).tempfile.create(true) do |file|
227
+ pacman=lambda do |*args, **pac_opts, &b|
228
+ pac_opts[:method]||=:sh
229
+ @config.launch(:pacman, *args, default_opts: ["--config", file], **opts.merge(pac_opts), &b)
230
+ end
231
+ yield pacman, file
232
+ end
233
+ end
234
+
235
+ def sync_db(*names, install: [], **pacman_opts)
236
+ conf=PacmanConf.create(@opts[:pacman_conf])
237
+ new_conf={options: conf[:options], repos: {}}
238
+ repos=conf[:repos]
239
+ names.each do |name|
240
+ if repos[name]
241
+ new_conf[:repos][name]=repos[name]
242
+ else
243
+ SH.logger.warn "sync_db: unknown repo #{name}"
244
+ end
245
+ end
246
+ tmp_pacman(new_conf) do |pacman, file|
247
+ if block_given?
248
+ return yield(pacman, file)
249
+ else
250
+ args=['-Syu']
251
+ args+=install
252
+ return pacman[*args, sudo: @config.sudo, **pacman_opts]
253
+ end
254
+ end
255
+ end
256
+
257
+ end
258
+ end
@@ -0,0 +1,56 @@
1
+ require 'forwardable'
2
+ require 'dr/base/utils'
3
+ require 'dr/base/uri' # for DR::URIEscape.escape
4
+ require 'shell_helpers'
5
+ require 'dr/ruby_ext/core_ext'
6
+
7
+ module Archlinux
8
+ ArchlinuxError=Class.new(StandardError)
9
+ Utils=::DR::Utils
10
+ Pathname=::SH::Pathname
11
+
12
+ def self.delegate_h(klass, var)
13
+ # put in a Module so that they are easier to distinguish from the
14
+ # 'real' functions
15
+ m=Module.new do
16
+ 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
+ include(Enumerable)
19
+ def_delegators var, *methods
20
+ end
21
+ klass.include(m)
22
+ end
23
+
24
+ def self.add_to_hash(h, key, value)
25
+ case h[key]
26
+ when nil
27
+ h[key] = value
28
+ when Array
29
+ h[key] << value
30
+ else
31
+ h[key]=[h[key], value]
32
+ end
33
+ end
34
+
35
+ def self.create_class(klass, *parameters, **kw, &b)
36
+ klass=Archlinux.const_get(klass) if klass.is_a?(Symbol)
37
+ if klass.is_a?(Proc)
38
+ klass.call(*parameters, **kw, &b)
39
+ else
40
+ klass.new(*parameters, **kw, &b)
41
+ end
42
+ end
43
+
44
+ module CreateHelper
45
+ def create(v, config: Archlinux.config)
46
+ v.is_a?(self) ? v : self.new(v, config: config)
47
+ end
48
+ end
49
+
50
+ ## Not used: we modify Config#pretty_print directly
51
+ # module PPHelper
52
+ # def pretty_print_instance_variables
53
+ # instance_variables.reject {|n| n==:@config}.sort
54
+ # end
55
+ # end
56
+ end
@@ -0,0 +1,158 @@
1
+ require 'aur/packages'
2
+ require 'aur/makepkg'
3
+
4
+ module Archlinux
5
+ # class that support installation (ie define install_method)
6
+ class InstallPackageList < PackageList
7
+ def initialize(*args, **opts)
8
+ super
9
+ @install_method=method(:install_method)
10
+ end
11
+
12
+ def get_makepkg_list(l)
13
+ MakepkgList.new(l.map {|p| Query.strip(p)}, config: @config)
14
+ end
15
+
16
+ def install_method(l, **opts, &b)
17
+ # if we are used as a source, fall back to the upstream method
18
+ if @install_list&.respond_to?(:install_method)
19
+ @install_list.install_method(l, **opts, &b)
20
+ else
21
+ m=get_makepkg_list(l)
22
+ info=opts.delete(:pkgs_info)
23
+ if info
24
+ tops=info[:top_pkgs].keys
25
+ deps=info[:all_pkgs].keys-tops
26
+ #if we cache the makepkg, we need to update both deps and tops
27
+ #in case we did a previous install
28
+ # require 'pry'; binding.pry
29
+ deps.each { |dep| m[Query.strip(dep)]&.asdeps=true }
30
+ tops.each { |dep| m[Query.strip(dep)]&.asdeps=false }
31
+ end
32
+ m=b.call(m) if b #return false to prevent install
33
+ m.install(**opts) if m
34
+ end
35
+ end
36
+ end
37
+
38
+ # cache aur queries
39
+ class AurCache < InstallPackageList
40
+
41
+ def initialize(*args, **kw)
42
+ super
43
+ @ext_query=method(:ext_query)
44
+ #@query_ignore=AurPackageList.official
45
+ if @config[:aur_url]==GlobalAurCache.config[:aur_url]
46
+ @klass=GlobalAurCache
47
+ else
48
+ @klass=AurQueryCustom.new(config: @config)
49
+ end
50
+ end
51
+
52
+ def ext_query(*queries, **_opts)
53
+ pkgs=queries.map {|p| Query.strip(p)}
54
+ # in a query like foo>1000, even if foo exist and was queried,
55
+ # the query fails so it gets called in ext_query
56
+ # remove these packages
57
+ # TODO: do the same for a provides query
58
+ pkgs-=self.names
59
+ if pkgs.empty?
60
+ l=self.class.new([])
61
+ else
62
+ SH.logger.debug "! #{self.class}: Calling aur for infos on: #{pkgs.join(', ')}"
63
+ l=@klass.packages(*pkgs)
64
+ @query_ignore += pkgs - l.names #these don't exist in aur
65
+ end
66
+ r=l.resolve(*queries, ext_query: false, fallback: false)
67
+ return r, l
68
+ end
69
+ end
70
+
71
+ # cache MakepkgList and download PKGBUILD dynamically
72
+ class MakepkgCache < InstallPackageList
73
+ attr_accessor :select_existing, :get_mode, :makepkg_list
74
+ def initialize(*args, get_mode: {}, **opts)
75
+ super(*args, **opts)
76
+ # puts "MakepkgCache called with options: #{[args, get_mode]}"
77
+ @select_existing=get_mode.delete(:existing)
78
+ @get_mode=get_mode
79
+ @ext_query=method(:ext_query)
80
+ @makepkg_list=MakepkgList.new([], config: @config)
81
+ #@query_ignore=AurPackageList.official
82
+ end
83
+
84
+ def get_makepkg_list(l)
85
+ pkgs=l.map {|p| Query.strip(p)}
86
+ # use the cache
87
+ m=MakepkgList.new(pkgs.map {|pkg| @makepkg_list.key?(pkg) ? @makepkg_list[pkg] : pkg}, config: @config)
88
+ @makepkg_list.merge(m.l.values)
89
+ m
90
+ end
91
+
92
+ def ext_query(*queries, **opts)
93
+ m=get_makepkg_list(queries)
94
+ m.keep_if {|_k,v| v.exist?} if @select_existing
95
+ m.packages(get: @get_mode).as_ext_query(*queries, full_pkgs: true, **opts)
96
+ end
97
+ end
98
+
99
+ # combine Aur and Makepkg caches
100
+ class AurMakepkgCache < InstallPackageList
101
+ attr_accessor :aur_cache, :makepkg_cache
102
+ def initialize(*args, **opts)
103
+ super
104
+ @aur_cache = AurCache.new(**opts)
105
+ @makepkg_cache = MakepkgCache.new(get_mode: {update: true, clone: true, pkgver: true, view: true}, **opts)
106
+ @ext_query=method(:ext_query)
107
+ #@query_ignore=AurPackageList.official
108
+ end
109
+
110
+ def ext_query(*queries, **opts)
111
+ devel=queries.select do |query|
112
+ Query.strip(query)=~/(-git|-hg|-svn)$/
113
+ end
114
+ if @install_list_of
115
+ # we only want to check the pkgver of packages we already have; for
116
+ # the others the aur version is enough
117
+ devel=devel & @install_list_of.names
118
+ end
119
+ aur=queries-devel
120
+ r1, l1=@makepkg_cache.as_ext_query(*devel, **opts)
121
+ missing=devel-r1.keys
122
+ r2, l2=@aur_cache.as_ext_query(*(missing+aur), **opts)
123
+ return r1.merge(r2), l1.merge(l2)
124
+ end
125
+
126
+ def get_makepkg_list(l)
127
+ got=l.select {|pkg| @makepkg_cache.key?(pkg)}
128
+ got_m=@makepkg_cache.get_makepkg_list(got)
129
+ rest=@aur_cache.get_makepkg_list(l-got)
130
+ MakepkgList.new(l.map do |name|
131
+ strip=Query.strip(name)
132
+ got_m.key?(strip) ? got_m[strip] : rest[strip]
133
+ end, config: @config)
134
+ end
135
+ end
136
+
137
+ class AurPackageList < InstallPackageList
138
+ def self.official
139
+ @official||=%w(core extra community).map {|repo| Repo.new(repo).list(mode: :pacman)}.flatten.compact
140
+ end
141
+
142
+ def initialize(*args, **opts)
143
+ super
144
+ # @install_list=self.class.cache
145
+ @install_list=@config.install_list #AurMakepkgCache.new(**opts)
146
+ # TODO this won't work if we use several PackageList with the same
147
+ # cache at the same time
148
+ @install_list.install_list_of=self
149
+ @children_mode=%i(depends make_depends check_depends)
150
+ @install_method=method(:install_method)
151
+ @query_ignore=official
152
+ end
153
+
154
+ def official
155
+ self.class.official
156
+ end
157
+ end
158
+ end