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.
@@ -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