8d855be790e6175686f6155172fc8b5a190c995b
[platform/upstream/libsolv.git] / examples / tclsolv
1 #!/usr/bin/tclsh
2
3 package require solv
4 package require inifile
5 package require fileutil
6
7 set reposdir /etc/zypp/repos.d
8
9 ### some helpers
10
11 proc fileno {file} {
12   if [regexp -- {^file(\d+)$} $file match fd] {
13     return $fd
14   }
15   error "file not open"
16 }
17
18 set ::globalarray_cnt 0
19
20 proc globalarray {} {
21   set name "::globalarray_[incr ::globalarray_cnt]"
22   array set $name [list varName $name]
23   return $name
24 }
25
26 ### generic repo handling (cache reading/writing)
27
28 proc repo_calc_cookie_file {selfName filename} {
29   upvar $selfName self
30   set chksum [solv::new_Chksum $solv::REPOKEY_TYPE_SHA256]
31   $chksum add "1.1"
32   $chksum add_stat $filename
33   return [$chksum raw]
34 }
35
36 proc repo_calc_cookie_fp {selfName fp} {
37   upvar $selfName self
38   set chksum [solv::new_Chksum $solv::REPOKEY_TYPE_SHA256]
39   $chksum add "1.1"
40   $chksum add_fp $fp
41   return [$chksum raw]
42 }
43
44 proc repo_calc_cookie_ext {selfName f cookie} {
45   upvar $selfName self
46   set chksum [solv::new_Chksum $solv::REPOKEY_TYPE_SHA256]
47   $chksum add "1.1"
48   $chksum add $cookie
49   $chksum add_fstat [$f fileno]
50   return [$chksum raw]
51 }
52
53 proc repo_cachepath {selfName {ext "-"}} {
54   upvar $selfName self
55   regsub {^\.} $self(name) _ path
56   if {$ext ne "-"} {
57     set path "${path}_$ext.solvx"
58   } else {
59     set path "${path}.solv"
60   }
61   regsub -all / $path _ path
62   return "/var/cache/solv/$path"
63 }
64
65 proc repo_generic_load {selfName pool} {
66   upvar $selfName self
67   set handle [ $pool add_repo $self(name) ]
68   set self(handle) $handle
69   $handle configure -priority [expr 99 - $self(priority)] -appdata $self(varName)
70   set dorefresh $self(autorefresh)
71   set metadata_expire $self(metadata_expire)
72   catch {
73     if {$metadata_expire == -1 || [clock seconds] - [file mtime [repo_cachepath self]] < $metadata_expire} {
74       set dorefresh 0
75     }
76   }
77   set self(cookie) {}
78   set self(extcookie) {}
79   if { !$dorefresh && [repo_usecachedrepo self] } {
80     puts "repo $self(name): cached"
81     return 1 
82   }
83   return 0 
84 }
85
86 proc repo_free_handle {selfName} {
87   upvar $selfName self
88   set handle $self(handle)
89   unset self(handle)
90   $handle free 1
91 }
92
93 proc repo_usecachedrepo {selfName {ext "-"} {mark 0}} {
94   upvar $selfName self
95   set repopath [repo_cachepath self $ext]
96   set code [catch {
97     set f [open $repopath "rb"]
98     seek $f -32 end
99     set fcookie [read $f 32]
100     set cookie [expr {$ext eq "-" ? $self(cookie) : $self(extcookie)}]
101     if {$cookie ne {} && $cookie ne $fcookie} {
102       close $f
103       return 0
104     }
105     set fextcookie {}
106     if {$ext eq "-" && $self(type) ne "system"} {
107       seek $f -64 end
108       set fextcookie [read $f 32]
109     }
110     seek $f 0 start
111     set ff [solv::xfopen_fd {} [fileno $f]]
112     close $f
113     set flags 0
114     if {$ext ne "-"} {
115       set flags [expr $solv::Repo_REPO_USE_LOADING | $solv::Repo_REPO_EXTEND_SOLVABLES]
116       if {$ext ne "DL"} {
117         set flags [expr $flags | $solv::Repo_REPO_LOCALPOOL]
118       }
119     }
120     if {! [$self(handle) add_solv $ff $flags]} {
121       $ff close
122       return 0
123     }
124     $ff close
125     if {$self(type) ne "system" && $ext eq "-"} {
126       set self(cookie) $fcookie
127       set self(extcookie) $fextcookie
128     }
129     if {$mark} {
130       catch {
131         ::fileutil::touch -c -m -t [clock seconds] $repopath
132       }
133     }
134     return 1
135   } res]
136   return [expr {$code == 2 ? $res : 0}]
137 }
138
139 proc repo_writecachedrepo {selfName {ext "-"} {repodata "NULL"}} {
140   upvar $selfName self
141   if [info exists self(incomplete)] {
142     return
143   }
144   file mkdir "/var/cache/solv"
145   ::fileutil::tempdir "/var/cache/solv"
146   set tempfilename [::fileutil::tempfile ".newsolv-"]
147   ::fileutil::tempdirReset
148   set f [solv::xfopen $tempfilename "w+"]
149   file attributes $tempfilename -permissions 0444
150   if {$repodata eq {NULL}} {
151     $self(handle) write $f
152   } else {
153     $repodata write $f
154   }
155   $f flush
156   if {$self(type) ne "system" && $ext eq "-"} {
157     if {$self(extcookie) eq {}} {
158       set self(extcookie) [repo_calc_cookie_ext self $f $self(cookie)]
159     }
160     $f write $self(extcookie)
161   }
162   $f write [expr {$ext eq "-" ? $self(cookie) : $self(extcookie)}]
163   $f close
164   file rename -force -- $tempfilename [repo_cachepath self $ext]
165 }
166
167 proc repo_download {selfName file uncompress chksum {markincomplete 0}} {
168   upvar $selfName self
169   regsub {/$} $self(baseurl) {} url
170   set url "$url/$file"
171   set tempfilename [::fileutil::tempfile]
172   set f [open $tempfilename rb+]
173   file delete -- $tempfilename
174   if [catch {
175     exec -ignorestderr -- curl -f -s -L $url ">@$f"
176   }] {
177     seek $f 0 end
178     if {($chksum ne "" && $chksum ne "NULL") || [tell $f] != 0} {
179       puts "$file: download error"
180     }
181     close $f
182     return {NULL}
183   }
184   seek $f 0 start
185   if {$chksum ne "" && $chksum ne "NULL"} {
186     set fchksum [solv::new_Chksum [$chksum cget -type]]
187     if {$fchksum eq "" || $fchksum eq "NULL"} {
188       puts "$file: unknown checksum type"
189       if {$markincomplete} {
190         set self(incomplete) 1
191       }
192       close $f
193       return {NULL}
194     }
195     $fchksum add_fd [fileno $f]
196     if {[$fchksum != $chksum]} {
197       puts "$file: checksum mismatch"
198       if {$markincomplete} {
199         set self(incomplete) 1
200       }
201       close $f
202       return {NULL}
203     }
204   }
205   set ff [solv::xfopen_fd [expr {$uncompress ? $file : ""}] [fileno $f]]
206   close $f
207   return $ff
208 }
209
210 proc repo_generic_add_ext_keys {selfName ext repodata h} {
211   upvar $selfName self
212   if {$ext eq "DL"} {
213     $repodata add_idarray $h $solv::REPOSITORY_KEYS $solv::REPOSITORY_DELTAINFO
214     $repodata add_idarray $h $solv::REPOSITORY_KEYS $solv::REPOKEY_TYPE_FLEXARRAY
215   } elseif {$ext eq "DU"} {
216     $repodata add_idarray $h $solv::REPOSITORY_KEYS $solv::SOLVABLE_DISKUSAGE
217     $repodata add_idarray $h $solv::REPOSITORY_KEYS $solv::REPOKEY_TYPE_DIRNUMNUMARRAY
218   } elseif {$ext eq "FL"} {
219     $repodata add_idarray $h $solv::REPOSITORY_KEYS $solv::SOLVABLE_FILELIST
220     $repodata add_idarray $h $solv::REPOSITORY_KEYS $solv::REPOKEY_TYPE_DIRSTRARRAY
221   }
222 }
223
224 ### system
225
226 proc repo_system_load {selfName pool} {
227   upvar $selfName self
228   set handle [ $pool add_repo $self(name) ]
229   set self(handle) $handle
230   $handle configure -appdata $self(varName)
231   $pool configure -installed $handle
232   puts -nonewline "rpm database: "
233   set self(cookie) [repo_calc_cookie_file self "/var/lib/rpm/Packages"]
234   if [repo_usecachedrepo self] {
235     puts "cached"
236     return 1
237   }
238   puts "reading"
239   set f [solv::xfopen [repo_cachepath self]]
240   $handle add_rpmdb_reffp $f $solv::Repo_REPO_REUSE_REPODATA
241   repo_writecachedrepo self
242 }
243
244 ### repomd
245
246 proc repo_repomd_add_ext {selfName repodata what ext} {
247   upvar $selfName self
248   set where [repo_repomd_find self $what]
249   if {$where eq {}} {
250     return
251   }
252   set h [$repodata new_handle]
253   $repodata set_poolstr $h $solv::REPOSITORY_REPOMD_TYPE $what
254   $repodata set_str $h $solv::REPOSITORY_REPOMD_LOCATION [lindex $where 0]
255   $repodata set_checksum $h $solv::REPOSITORY_REPOMD_CHECKSUM [lindex $where 1]
256   repo_generic_add_ext_keys self $ext $repodata $h
257   $repodata add_flexarray $solv::SOLVID_META $solv::REPOSITORY_EXTERNAL $h
258 }
259
260 proc repo_repomd_add_exts {selfName} {
261   upvar $selfName self
262   set repodata [$self(handle) add_repodata 0]
263   $repodata extend_to_repo
264   repo_repomd_add_ext self $repodata "filelists" "FL"
265   $repodata internalize
266 }
267
268 proc repo_repomd_find {selfName what} {
269   upvar $selfName self
270   set di [$self(handle) Dataiterator_meta $solv::REPOSITORY_REPOMD_TYPE $what $solv::Dataiterator_SEARCH_STRING]
271   $di prepend_keyname $solv::REPOSITORY_REPOMD
272   solv::iter d $di {
273     set dp [$d parentpos]
274     set filename [$dp lookup_str $solv::REPOSITORY_REPOMD_LOCATION]
275     set checksum [$dp lookup_checksum $solv::REPOSITORY_REPOMD_CHECKSUM]
276     if {$filename ne "" && $checksum eq "NULL"} {
277       puts "no $filename file checksum"
278     } elseif {$filename ne ""} {
279       return [list $filename $checksum]
280     }
281   }
282   return {}
283 }
284
285 proc repo_repomd_load {selfName pool} {
286   upvar $selfName self
287   if [repo_generic_load self $pool] {
288     return 1
289   }
290   puts -nonewline "rpmmd repo '$self(name)': "
291   set f [repo_download self {repodata/repomd.xml} 0 {}]
292   if {$f eq {NULL}} {
293     puts "no repomd.xml file, skipped"
294     repo_free_handle self
295     return 0
296   }
297   set self(cookie) [repo_calc_cookie_fp self $f]
298   if [repo_usecachedrepo self "-" 1] {
299     puts "cached"
300     return 1
301   }
302   set handle $self(handle)
303   $handle add_repomdxml $f
304   puts "fetching"
305   set primary [repo_repomd_find self primary]
306   if {$primary ne {}} {
307     set f [repo_download self [lindex $primary 0] 1 [lindex $primary 1] 1]
308     if {$f ne {NULL}} {
309       $handle add_rpmmd $f {}
310       $f close
311     }
312     if [info exists self(incomplete)] {
313       return 0
314     }
315   }
316   set updateinfo [repo_repomd_find self primary]
317   if {$updateinfo ne {}} {
318     set f [repo_download self [lindex $updateinfo  0] 1 [lindex $updateinfo 1] 1]
319     if {$f ne {NULL}} {
320       $handle add_updateinfoxml $f
321       $f close
322     }
323   }
324   repo_repomd_add_exts self
325   repo_writecachedrepo self
326   $self(handle) create_stubs
327   return 1
328 }
329
330 proc repo_repomd_packagespath {selfName} {
331   return ""
332 }
333
334 proc repo_repomd_load_ext {selfName repodata} {
335   upvar $selfName self
336   switch -- [$repodata lookup_str $solv::SOLVID_META $solv::REPOSITORY_REPOMD_TYPE] {
337     "deltainfo" {
338       set ext DL
339     }
340     "filelists" {
341       set ext FL
342     }
343     default {
344       return 0
345     }
346   }
347   puts -nonewline "\[$self(name):$ext: "
348   flush stdout
349   if [repo_usecachedrepo self $ext] {
350     puts "cached]"
351     return 1
352   }
353   puts "fetching]"
354   set handle $self(handle)
355   set filename [$repodata lookup_str $solv::SOLVID_META $solv::REPOSITORY_REPOMD_LOCATION]
356   set filechecksum [$repodata lookup_checksum $solv::SOLVID_META $solv::REPOSITORY_REPOMD_CHECKSUM]
357   set f [repo_download self $filename 1 $filechecksum]
358   if {$f eq {NULL}} {
359     return 0
360   }
361   if {$ext eq "FL"} {
362     $handle add_rpmmd $f "FL" [ expr $solv::Repo_REPO_USE_LOADING | $solv::Repo_REPO_EXTEND_SOLVABLES | $solv::Repo_REPO_LOCALPOOL]
363   }
364   $f close
365   repo_writecachedrepo self $ext $repodata
366   return 1
367 }
368
369 ### susetags
370
371 proc repo_susetags_add_ext {selfName repodata what ext} {
372   upvar $selfName self
373   set where [repo_susetags_find self $what]
374   if {$where eq {}} {
375     return
376   }
377   set h [$repodata new_handle]
378   $repodata set_str $h $solv::SUSETAGS_FILE_NAME [lindex $where 0]
379   $repodata set_checksum $h $solv::SUSETAGS_FILE_CHECKSUM [lindex $where 1]
380   repo_generic_add_ext_keys self $ext $repodata $h
381   $repodata add_flexarray $solv::SOLVID_META $solv::REPOSITORY_EXTERNAL $h
382 }
383
384 proc repo_susetags_add_exts {selfName} {
385   upvar $selfName self
386   set repodata [$self(handle) add_repodata 0]
387   $repodata extend_to_repo
388   repo_susetags_add_ext self $repodata "packages.FL" "FL"
389   repo_susetags_add_ext self $repodata "packages.FL.gz" "FL"
390   $repodata internalize
391 }
392
393 proc repo_susetags_find {selfName what} {
394   upvar $selfName self
395   set di [$self(handle) Dataiterator_meta $solv::SUSETAGS_FILE_NAME $what $solv::Dataiterator_SEARCH_STRING]
396   $di prepend_keyname $solv::SUSETAGS_FILE
397   solv::iter d $di {
398     set dp [$d parentpos]
399     set checksum [$dp lookup_checksum $solv::SUSETAGS_FILE_CHECKSUM]
400     return [list $what $checksum]
401   }
402   return {}
403 }
404
405 proc repo_susetags_load {selfName pool} {
406   upvar $selfName self
407   if [repo_generic_load self $pool] {
408     return 1
409   }
410   puts -nonewline "susetags repo '$self(name)': "
411   set f [repo_download self {content} 0 {}]
412   if {$f eq {NULL}} {
413     puts "no content file, skipped"
414     repo_free_handle self
415     return 0
416   }
417   set self(cookie) [repo_calc_cookie_fp self $f]
418   if [repo_usecachedrepo self "-" 1] {
419     puts "cached"
420     return 1
421   }
422   set handle $self(handle)
423   $handle add_content $f
424   puts "fetching"
425   set defvendorid [[$handle cget -meta] lookup_id $solv::SUSETAGS_DEFAULTVENDOR]
426   set descrdir [[$handle cget -meta] lookup_str $solv::SUSETAGS_DESCRDIR]
427   if {$descrdir eq {NULL}} {
428     set descrdir "suse/setup/descr"
429   }
430   set packages [repo_susetags_find self "packages.gz"]
431   if {$packages eq {}} {
432     set packages [repo_susetags_find self "packages"]
433   }
434   if {$packages ne {}} {
435     set f [repo_download self "$descrdir/[lindex $packages 0]" 1 [lindex $packages 1] 1]
436     if {$f ne {NULL}} {
437       $handle add_susetags $f $defvendorid {} [expr $solv::Repo_REPO_NO_INTERNALIZE | $solv::Repo_SUSETAGS_RECORD_SHARES]
438       $f close
439       set packages [repo_susetags_find self "packages.en.gz"]
440       if {$packages eq {}} {
441         set packages [repo_susetags_find self "packages.en"]
442       }
443       if {$packages ne {}} {
444         set f [repo_download self "$descrdir/[lindex $packages 0]" 1 [lindex $packages 1] 1]
445         if {$f ne {NULL}} {
446           $handle add_susetags $f $defvendorid {} [expr $solv::Repo_REPO_NO_INTERNALIZE | $solv::Repo_REPO_REUSE_REPODATA | $solv::Repo_REPO_EXTEND_SOLVABLES ]
447           $f close
448         }
449       }
450       $handle internalize
451     }
452   }
453   repo_susetags_add_exts self
454   repo_writecachedrepo self
455   $self(handle) create_stubs
456   return 1
457 }
458
459 proc repo_susetags_packagespath {selfName} {
460   upvar $selfName self
461   set datadir [[$self(handle) cget -meta] lookup_str $solv::SUSETAGS_DATADIR]
462   return [expr {$datadir ne {} ? "$datadir/" : "suse/"}]
463 }
464
465 proc repo_susetags_load_ext {selfName repodata} {
466   upvar $selfName self
467   set filename [$repodata lookup_str $solv::SOLVID_META $solv::SUSETAGS_FILE_NAME]
468   set ext [string range $filename 9 10]
469   puts -nonewline "\[$self(name):$ext: "
470   flush stdout
471   if [repo_usecachedrepo self $ext] {
472     puts "cached]"
473     return 1
474   }
475   puts "fetching]"
476   set handle $self(handle)
477   set defvendorid [[$handle cget -meta] lookup_id $solv::SUSETAGS_DEFAULTVENDOR]
478   set descrdir [[$handle cget -meta] lookup_str $solv::SUSETAGS_DESCRDIR]
479   if {$descrdir eq {NULL}} {
480     set descrdir "suse/setup/descr"
481   }
482   set filechecksum [$repodata lookup_checksum $solv::SOLVID_META $solv::SUSETAGS_FILE_CHECKSUM]
483   set f [repo_download self "$descrdir/$filename" 1 $filechecksum]
484   if {$f eq {NULL}} {
485     return 0
486   }
487   set flags [expr $solv::Repo_REPO_USE_LOADING | $solv::Repo_REPO_EXTEND_SOLVABLES]
488   if {$ext ne "DL"} {
489     set flags [expr $flags | $solv::Repo_REPO_LOCALPOOL]
490   }
491   $handle add_susetags $f $defvendorid $ext $flags
492   $f close
493   repo_writecachedrepo self $ext $repodata
494   return 1
495 }
496
497 ### unknown
498
499 proc repo_unknown_load {selfName pool} {
500   upvar $selfName self
501   puts "unsupported repo '$self(name)': skipped"
502   return 0
503 }
504
505 ### poor man's OO
506
507 proc repo_load {selfName pool} {
508   upvar $selfName self
509   "repo_$self(type)_load" self $pool
510 }
511
512 proc repo_packagespath {selfName} {
513   upvar $selfName self
514   "repo_$self(type)_packagespath" self
515 }
516
517 proc repo_load_ext {selfName repodata} {
518   upvar $selfName self
519   "repo_$self(type)_load_ext" self $repodata
520 }
521
522 ###
523
524 proc load_stub {repodata} {
525   set code [catch {
526     upvar #0 [[$repodata cget -repo] cget -appdata] repo
527     if [info exists repo(handle)] {
528       return [repo_load_ext repo $repodata]
529     }
530     return 0
531   } res]
532   if {$code == 2} {
533     return $res
534   }
535   puts stderr $res
536   return 0
537 }
538
539 ###
540
541 set repoNames {}
542 foreach reponame [lsort [glob -nocomplain -directory $reposdir *.repo]] {
543   set ini [::ini::open $reponame r]
544   foreach alias [::ini::sections $ini] {
545     upvar #0 [globalarray] repo
546     array set repo {enabled 0 priority 99 autorefresh 1 type rpm-md metadata_expire 900}
547     array set repo [::ini::get $ini $alias]
548     set repo(name) $alias
549     switch -exact -- $repo(type) {
550       rpm-md  { set repo(type) repomd }
551       yast2   { set repo(type) susetags }
552       default { set repo(type) unknown }
553     }
554     lappend repoNames $repo(varName)
555   }
556   ::ini::close $ini
557 }
558
559 set pool [solv::new_Pool]
560 $pool setarch
561 $pool set_loadcallback load_stub
562
563 upvar #0 [globalarray] sysrepo
564 array set sysrepo [list name {@System} type system]
565 repo_load sysrepo $pool
566
567 foreach repoName $repoNames {
568   upvar 0 $repoName repo
569   if {$repo(enabled)} {
570     repo_load repo $pool
571   }
572 }
573
574
575 set cmd [lindex $::argv 0]
576 set ::argv [lreplace $::argv 0 0]
577
578 array set cmdabbrev [ list \
579   in install \
580   rm erase \
581   ls list \
582   ve verify \
583   se search \
584 ]
585 if [info exists cmdabbrev($cmd)] {
586   set cmd $cmdabbrev($cmd)
587 }
588
589 if {$cmd eq "search"} {
590   set arg [lindex $::argv 0]
591   $pool createwhatprovides
592   set sel [$pool Selection]
593   set di [$pool Dataiterator $solv::SOLVABLE_NAME $arg [ expr $solv::Dataiterator_SEARCH_SUBSTRING | $solv::Dataiterator_SEARCH_NOCASE ]]
594   solv::iter d $di {
595     $sel add_raw $solv::Job_SOLVER_SOLVABLE [$d cget -solvid]
596   }
597   foreach s [$sel solvables] {
598     puts [format { - %s [%s]: %s} [$s str] [[$s cget -repo] cget -name] [$s lookup_str $solv::SOLVABLE_SUMMARY]]
599   }
600   exit
601 }
602
603 $pool addfileprovides
604 $pool createwhatprovides
605
606 array set cmdactionmap [ list \
607   install $solv::Job_SOLVER_INSTALL \
608   erase $solv::Job_SOLVER_ERASE \
609   up $solv::Job_SOLVER_UPDATE \
610   dup $solv::Job_SOLVER_DISTUPGRADE \
611   verify $solv::Job_SOLVER_VERIFY \
612   list 0 \
613   info 0 \
614 ]
615
616 set jobs {}
617 foreach arg $::argv {
618   set flags [expr $solv::Selection_SELECTION_NAME | $solv::Selection_SELECTION_PROVIDES | $solv::Selection_SELECTION_GLOB | \
619                   $solv::Selection_SELECTION_CANON | $solv::Selection_SELECTION_DOTARCH | $solv::Selection_SELECTION_REL ]
620   switch -glob -- $arg {
621     "/*" {
622       set flags [expr $flags | $solv::Selection_SELECTION_FILELIST ]
623       if {$cmd eq "erase"} {
624         set flags [expr $flags | $solv::Selection_SELECTION_INSTALLED_ONLY ]
625       }
626     }
627   }
628   set sel [$pool select $arg $flags]
629   if [$sel isempty] {
630     set sel [$pool select $arg [expr $flags | $solv::Selection_SELECTION_NOCASE]]
631     if {![$sel isempty]} {
632       puts "\[ignoring case for '$arg']"
633     }
634   }
635   if [$sel isempty] {
636     puts "nothing matches '$arg'"
637     exit 1
638   }
639   if {[$sel flags] & $solv::Selection_SELECTION_FILELIST} {
640     puts "\[using file list match for '$arg']"
641   }
642   if {[$sel flags] & $solv::Selection_SELECTION_PROVIDES} {
643     puts "\[using capability match for '$arg']"
644   }
645   lappend jobs {*}[$sel jobs $cmdactionmap($cmd)]
646 }
647
648 if {$jobs eq {} && ($cmd eq "up" || $cmd eq "dup" || $cmd eq "verify") } {
649   set sel [$pool Selection_all]
650   lappend jobs {*}[$sel jobs $cmdactionmap($cmd)]
651 }
652
653 if {$jobs eq {}} {
654   puts "no package matched."
655   exit 1
656 }
657
658 if {$cmd eq "list" || $cmd eq "info"} {
659   foreach job $jobs {
660     foreach s [$job solvables] {
661       if {$cmd eq "info"} {
662         puts [format {Name:        %s} [$s str]]
663         puts [format {Repo:        %s} [[$s cget -repo] cget -name]]
664         puts [format {Summary:     %s} [$s lookup_str $solv::SOLVABLE_SUMMARY]]
665         set str [$s lookup_str $solv::SOLVABLE_URL]
666         if {$str ne {}} {
667           puts [format {Url:         %s} $str]
668         }
669         set str [$s lookup_str $solv::SOLVABLE_LICENSE]
670         if {$str ne {}} {
671           puts [format {License      %s} $str]
672         }
673         puts [format {Description: %s} [$s lookup_str $solv::SOLVABLE_DESCRIPTION]]
674         puts {}
675       } else {
676         puts [format {  - %s [%s]} [$s str] [[$s cget -repo] cget -name]]
677         puts [format {    %s} [$s lookup_str $solv::SOLVABLE_SUMMARY]]
678       }
679     }
680   }
681   exit
682 }
683
684 #$pool set_debuglevel 1
685 set solver [$pool Solver]
686 $solver set_flag $solv::Solver_SOLVER_FLAG_SPLITPROVIDES 1
687 if {$cmd eq "erase"} {
688   $solver set_flag $solv::Solver_SOLVER_FLAG_ALLOW_UNINSTALL 1
689 }
690
691 set problems [$solver solve $jobs]
692 if {$problems ne {}} {
693   set pcnt 1
694   foreach problem $problems {
695     puts [format {Problem %d/%d:} $pcnt [llength $problems]]
696     puts [$problem str]
697     incr pcnt
698   }
699   exit 1
700 }
701
702 set trans [$solver transaction]
703
704 if [$trans isempty] {
705   puts "Nothing to do."
706   exit
707 }
708
709 puts {}
710 puts "Transaction summary:"
711 puts {}
712 foreach cl [$trans classify [expr $solv::Transaction_SOLVER_TRANSACTION_SHOW_OBSOLETES | $solv::Transaction_SOLVER_TRANSACTION_OBSOLETE_IS_UPGRADE]] {
713   switch -- [$cl cget -type] \
714     $solv::Transaction_SOLVER_TRANSACTION_ERASE {
715       puts [format {%d erased packages:} [$cl cget -count]]
716     } \
717     $solv::Transaction_SOLVER_TRANSACTION_INSTALL {
718       puts [format {%d installed packages:} [$cl cget -count]]
719     } \
720     $solv::Transaction_SOLVER_TRANSACTION_REINSTALLED {
721       puts [format {%d reinstalled packages:} [$cl cget -count]]
722     } \
723     $solv::Transaction_SOLVER_TRANSACTION_DOWNGRADED {
724       puts [format {%d downgraded packages:} [$cl cget -count]]
725     } \
726     $solv::Transaction_SOLVER_TRANSACTION_CHANGED {
727       puts [format {%d changed packages:} [$cl cget -count]]
728     } \
729     $solv::Transaction_SOLVER_TRANSACTION_UPGRADED {
730       puts [format {%d upgraded packages:} [$cl cget -count]]
731     } \
732     $solv::Transaction_SOLVER_TRANSACTION_VENDORCHANGE {
733       puts [format {%d vendor changes from '%s' to '%s':} [$cl cget -count] [$cl cget -fromstr] [$cl cget -tostr]]
734     } \
735     $solv::Transaction_SOLVER_TRANSACTION_ARCHCHANGE {
736       puts [format {%d archchanges from '%s' to '%s':} [$cl cget -count] [$cl cget -fromstr] [$cl cget -tostr]]
737     } \
738     default continue
739   foreach p [$cl solvables] {
740     set cltype [$cl cget -type]
741     if {$cltype == $solv::Transaction_SOLVER_TRANSACTION_UPGRADED || $cltype ==$solv::Transaction_SOLVER_TRANSACTION_DOWNGRADED} {
742       set op [$trans othersolvable $p]
743       puts [format {  - %s -> %s} [$p str] [$op str]]
744     } else {
745       puts [format {  - %s} [$p str]]
746     }
747   }
748   puts {}
749 }
750 puts [format {install size change: %d K} [$trans calc_installsizechange]]
751 puts {}
752
753 while 1 {
754   puts -nonewline "OK to continue (y/n)? "
755   flush stdout
756   set yn [gets stdin]
757   if {$yn eq "y"} {
758     break
759   }
760   if {$yn eq "n" || $yn eq "q"} {
761     exit
762   }
763 }
764
765 set newpkgs [$trans newsolvables]
766 array set newpkgs_f {}
767 if {$newpkgs ne {}} {
768   set downloadsize 0
769   foreach p $newpkgs {
770     set downloadsize [expr $downloadsize + [$p lookup_num $solv::SOLVABLE_DOWNLOADSIZE]]
771   }
772   puts [format {Downloading %d packages, %d K} [llength $newpkgs] [expr $downloadsize / 1024]]
773   foreach p $newpkgs {
774     upvar #0 [[$p cget -repo] cget -appdata] repo
775     set location [$p lookup_location]
776     if {$location eq {}} {
777       continue
778     }
779     set location "[repo_packagespath repo][lindex $location 0]"
780     set checksum [$p lookup_checksum $solv::SOLVABLE_CHECKSUM]
781     set f [repo_download repo $location 0 $checksum]
782     set newpkgs_f([$p cget -id]) $f
783     puts -nonewline "."
784     flush stdout
785   }
786   puts {}
787 }
788
789 puts "Committing transaction:"
790 $trans order
791 foreach p [$trans steps] {
792   set steptype [$trans steptype $p $solv::Transaction_SOLVER_TRANSACTION_RPM_ONLY]
793   if {$steptype == $solv::Transaction_SOLVER_TRANSACTION_ERASE} {
794     puts "erase [$p str]"
795     regsub {^[0-9]+:} [$p cget -evr] {} nvr
796     set nvr "[$p cget -name]-$nvr.[$p cget -arch]"
797     exec -ignorestderr -- rpm -e --nodeps --nodigest --nosignature $nvr
798   } elseif {$steptype == $solv::Transaction_SOLVER_TRANSACTION_INSTALL || $steptype == $solv::Transaction_SOLVER_TRANSACTION_MULTIINSTALL} {
799     puts "install [$p str]"
800     set f $newpkgs_f([$p cget -id])
801     set mode [expr {$steptype == $solv::Transaction_SOLVER_TRANSACTION_INSTALL ? "-U" : "-i"}]
802     $f cloexec 0
803     exec -ignorestderr -- rpm $mode --force --nodeps --nodigest --nosignature "/dev/fd/[$f fileno]"
804   }
805 }