2019-11-22 20:01:38 +00:00
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package installer
import (
2020-12-03 16:25:29 +00:00
"fmt"
2019-11-22 21:23:05 +00:00
"io/ioutil"
2019-11-22 22:12:03 +00:00
"os"
2019-11-23 23:16:12 +00:00
"path/filepath"
2019-11-22 20:01:38 +00:00
"sort"
2020-02-27 22:14:36 +00:00
"strings"
2019-11-22 20:01:38 +00:00
"sync"
2021-10-19 11:05:25 +00:00
"github.com/mudler/luet/pkg/api/core/types"
2020-11-13 17:25:44 +00:00
"github.com/mudler/luet/pkg/bus"
2021-06-01 14:43:31 +00:00
artifact "github.com/mudler/luet/pkg/compiler/types/artifact"
2020-02-12 09:20:07 +00:00
"github.com/mudler/luet/pkg/config"
2021-07-29 08:14:05 +00:00
"github.com/mudler/luet/pkg/helpers"
2021-06-01 14:43:31 +00:00
fileHelper "github.com/mudler/luet/pkg/helpers/file"
"github.com/mudler/luet/pkg/helpers/match"
2019-11-22 20:01:38 +00:00
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
"github.com/mudler/luet/pkg/solver"
2021-06-01 14:43:31 +00:00
. "github.com/logrusorgru/aurora"
2019-11-22 20:01:38 +00:00
"github.com/pkg/errors"
)
2020-02-12 10:21:30 +00:00
type LuetInstallerOptions struct {
2020-07-12 13:27:50 +00:00
SolverOptions config . LuetSolverOptions
Concurrency int
NoDeps bool
OnlyDeps bool
Force bool
PreserveSystemEssentialData bool
FullUninstall , FullCleanUninstall bool
CheckConflicts bool
SolverUpgrade , RemoveUnavailableOnUpgrade , UpgradeNewRevisions bool
2020-11-22 19:16:04 +00:00
Ask bool
2021-03-07 10:39:19 +00:00
DownloadOnly bool
2021-10-09 15:36:13 +00:00
Relaxed bool
2021-10-19 11:05:25 +00:00
PackageRepositories types . LuetRepositories
2020-02-12 10:21:30 +00:00
}
2019-11-22 20:01:38 +00:00
type LuetInstaller struct {
2020-02-12 10:21:30 +00:00
Options LuetInstallerOptions
2019-11-22 20:01:38 +00:00
}
type ArtifactMatch struct {
Package pkg . Package
2021-04-12 17:00:36 +00:00
Artifact * artifact . PackageArtifact
Repository * LuetSystemRepository
2019-11-22 20:01:38 +00:00
}
2021-04-12 17:00:36 +00:00
func NewLuetInstaller ( opts LuetInstallerOptions ) * LuetInstaller {
2020-02-12 10:21:30 +00:00
return & LuetInstaller { Options : opts }
2019-11-22 20:01:38 +00:00
}
2020-11-23 17:20:30 +00:00
// computeUpgrade returns the packages to be uninstalled and installed in a system to perform an upgrade
2020-11-22 19:16:04 +00:00
// based on the system repositories
2020-11-23 17:20:30 +00:00
func ( l * LuetInstaller ) computeUpgrade ( syncedRepos Repositories , s * System ) ( pkg . Packages , pkg . Packages , error ) {
2020-11-22 19:16:04 +00:00
toInstall := pkg . Packages { }
var uninstall pkg . Packages
2020-11-23 17:20:30 +00:00
var err error
2019-11-29 18:01:56 +00:00
// First match packages against repositories by priority
2019-12-31 11:48:12 +00:00
allRepos := pkg . NewInMemoryDatabase ( false )
2020-01-02 17:31:25 +00:00
syncedRepos . SyncDatabase ( allRepos )
2019-11-29 18:01:56 +00:00
// compute a "big" world
2020-10-25 17:43:35 +00:00
solv := solver . NewResolver ( solver . Options { Type : l . Options . SolverOptions . Implementation , Concurrency : l . Options . Concurrency } , s . Database , allRepos , pkg . NewInMemoryDatabase ( false ) , l . Options . SolverOptions . Resolver ( ) )
2020-05-22 18:45:28 +00:00
var solution solver . PackagesAssertions
if l . Options . SolverUpgrade {
uninstall , solution , err = solv . UpgradeUniverse ( l . Options . RemoveUnavailableOnUpgrade )
if err != nil {
2020-11-23 17:20:30 +00:00
return uninstall , toInstall , errors . Wrap ( err , "Failed solving solution for upgrade" )
2020-05-22 18:45:28 +00:00
}
} else {
2020-12-07 16:20:55 +00:00
uninstall , solution , err = solv . Upgrade ( l . Options . FullUninstall , true )
2020-05-22 18:45:28 +00:00
if err != nil {
2020-11-23 17:20:30 +00:00
return uninstall , toInstall , errors . Wrap ( err , "Failed solving solution for upgrade" )
2020-05-22 18:45:28 +00:00
}
2020-07-12 13:27:50 +00:00
}
2020-02-27 22:14:53 +00:00
for _ , assertion := range solution {
2020-03-24 19:30:32 +00:00
// Be sure to filter from solutions packages already installed in the system
if _ , err := s . Database . FindPackage ( assertion . Package ) ; err != nil && assertion . Value {
2020-02-27 22:14:53 +00:00
toInstall = append ( toInstall , assertion . Package )
}
}
2020-07-12 13:27:50 +00:00
if l . Options . UpgradeNewRevisions {
for _ , p := range s . Database . World ( ) {
matches := syncedRepos . PackageMatches ( pkg . Packages { p } )
if len ( matches ) == 0 {
// Package missing. the user should run luet upgrade --universe
continue
}
for _ , artefact := range matches [ 0 ] . Repo . GetIndex ( ) {
2021-04-12 17:00:36 +00:00
if artefact . CompileSpec . GetPackage ( ) == nil {
2020-11-23 17:20:30 +00:00
return uninstall , toInstall , errors . New ( "Package in compilespec empty" )
2020-07-12 13:27:50 +00:00
}
2021-04-12 17:00:36 +00:00
if artefact . CompileSpec . GetPackage ( ) . Matches ( p ) && artefact . CompileSpec . GetPackage ( ) . GetBuildTimestamp ( ) != p . GetBuildTimestamp ( ) {
2020-07-12 13:27:50 +00:00
toInstall = append ( toInstall , matches [ 0 ] . Package ) . Unique ( )
uninstall = append ( uninstall , p ) . Unique ( )
}
}
}
}
2020-11-23 17:20:30 +00:00
return uninstall , toInstall , nil
2020-11-22 19:16:04 +00:00
}
func packsToList ( p pkg . Packages ) string {
var packs [ ] string
for _ , pp := range p {
packs = append ( packs , pp . HumanReadableString ( ) )
}
2021-04-24 16:15:47 +00:00
sort . Strings ( packs )
2020-11-22 19:16:04 +00:00
return strings . Join ( packs , " " )
}
2020-12-03 16:25:29 +00:00
func matchesToList ( artefacts map [ string ] ArtifactMatch ) string {
var packs [ ] string
for fingerprint , match := range artefacts {
packs = append ( packs , fmt . Sprintf ( "%s (%s)" , fingerprint , match . Repository . GetName ( ) ) )
}
2021-04-24 16:15:47 +00:00
sort . Strings ( packs )
2020-12-03 16:25:29 +00:00
return strings . Join ( packs , " " )
}
2020-11-22 19:16:04 +00:00
// Upgrade upgrades a System based on the Installer options. Returns error in case of failure
func ( l * LuetInstaller ) Upgrade ( s * System ) error {
2020-11-23 17:20:30 +00:00
2021-10-19 11:05:25 +00:00
syncedRepos , err := l . SyncRepositories ( )
2020-11-23 17:20:30 +00:00
if err != nil {
return err
}
2020-11-22 19:16:04 +00:00
Info ( ":thinking: Computing upgrade, please hang tight... :zzz:" )
if l . Options . UpgradeNewRevisions {
Info ( ":memo: note: will consider new build revisions while upgrading" )
}
2021-04-16 21:38:10 +00:00
return l . checkAndUpgrade ( syncedRepos , s )
2020-02-27 22:48:14 +00:00
}
2021-10-19 11:05:25 +00:00
func ( l * LuetInstaller ) SyncRepositories ( ) ( Repositories , error ) {
2020-02-27 22:48:14 +00:00
Spinner ( 32 )
defer SpinnerStop ( )
syncedRepos := Repositories { }
2021-10-19 11:05:25 +00:00
for _ , r := range SystemRepositories ( l . Options . PackageRepositories ) {
2020-02-27 22:48:14 +00:00
repo , err := r . Sync ( false )
if err != nil {
return nil , errors . Wrap ( err , "Failed syncing repository: " + r . GetName ( ) )
}
syncedRepos = append ( syncedRepos , repo )
}
// compute what to install and from where
sort . Sort ( syncedRepos )
return syncedRepos , nil
}
2020-04-04 12:29:08 +00:00
func ( l * LuetInstaller ) Swap ( toRemove pkg . Packages , toInstall pkg . Packages , s * System ) error {
2021-10-19 11:05:25 +00:00
syncedRepos , err := l . SyncRepositories ( )
2020-02-27 22:48:14 +00:00
if err != nil {
return err
}
2020-12-02 17:24:32 +00:00
2020-12-17 23:49:51 +00:00
toRemoveFinal := pkg . Packages { }
for _ , p := range toRemove {
packs , _ := s . Database . FindPackages ( p )
if len ( packs ) == 0 {
return errors . New ( "Package " + p . HumanReadableString ( ) + " not found in the system" )
}
for _ , pp := range packs {
toRemoveFinal = append ( toRemoveFinal , pp )
}
}
2021-04-24 17:29:53 +00:00
o := Option {
FullUninstall : false ,
Force : true ,
CheckConflicts : false ,
FullCleanUninstall : false ,
NoDeps : l . Options . NoDeps ,
OnlyDeps : false ,
}
2020-12-17 23:49:51 +00:00
2021-04-24 17:29:53 +00:00
return l . swap ( o , syncedRepos , toRemoveFinal , toInstall , s )
2020-02-27 22:48:14 +00:00
}
2021-04-24 12:26:26 +00:00
func ( l * LuetInstaller ) computeSwap ( o Option , syncedRepos Repositories , toRemove pkg . Packages , toInstall pkg . Packages , s * System ) ( map [ string ] ArtifactMatch , pkg . Packages , solver . PackagesAssertions , pkg . PackageDatabase , error ) {
2020-12-19 13:55:59 +00:00
2020-02-27 22:48:14 +00:00
allRepos := pkg . NewInMemoryDatabase ( false )
syncedRepos . SyncDatabase ( allRepos )
2020-12-19 13:55:59 +00:00
2020-02-27 22:48:14 +00:00
toInstall = syncedRepos . ResolveSelectors ( toInstall )
2020-12-06 21:11:17 +00:00
// First check what would have been done
2020-12-19 16:45:50 +00:00
installedtmp , err := s . Database . Copy ( )
if err != nil {
return nil , nil , nil , nil , errors . Wrap ( err , "Failed create temporary in-memory db" )
2020-12-06 21:11:17 +00:00
}
2020-12-19 16:45:50 +00:00
2020-12-06 21:11:17 +00:00
systemAfterChanges := & System { Database : installedtmp }
2021-04-24 12:26:26 +00:00
packs , err := l . computeUninstall ( o , systemAfterChanges , toRemove ... )
2021-04-24 16:15:47 +00:00
if err != nil && ! o . Force {
2020-12-19 16:16:53 +00:00
Error ( "Failed computing uninstall for " , packsToList ( toRemove ) )
return nil , nil , nil , nil , errors . Wrap ( err , "computing uninstall " + packsToList ( toRemove ) )
}
for _ , p := range packs {
err = systemAfterChanges . Database . RemovePackage ( p )
if err != nil {
return nil , nil , nil , nil , errors . Wrap ( err , "Failed removing package from database" )
2019-11-29 18:01:56 +00:00
}
}
2021-04-24 16:15:47 +00:00
match , packages , assertions , allRepos , err := l . computeInstall ( o , syncedRepos , toInstall , systemAfterChanges )
2021-01-02 20:28:54 +00:00
for _ , p := range toInstall {
assertions = append ( assertions , solver . PackageAssert { Package : p . ( * pkg . DefaultPackage ) , Value : true } )
}
return match , packages , assertions , allRepos , err
2020-12-19 13:55:59 +00:00
}
2021-04-24 17:29:53 +00:00
func ( l * LuetInstaller ) swap ( o Option , syncedRepos Repositories , toRemove pkg . Packages , toInstall pkg . Packages , s * System ) error {
2020-12-19 14:26:18 +00:00
2021-04-24 12:26:26 +00:00
match , packages , assertions , allRepos , err := l . computeSwap ( o , syncedRepos , toRemove , toInstall , s )
2020-11-22 19:16:04 +00:00
if err != nil {
2020-12-19 13:55:59 +00:00
return errors . Wrap ( err , "failed computing package replacement" )
}
if l . Options . Ask {
if len ( toRemove ) > 0 {
Info ( ":recycle: Packages that are going to be removed from the system:\n " , Yellow ( packsToList ( toRemove ) ) . BgBlack ( ) . String ( ) )
}
if len ( match ) > 0 {
Info ( "Packages that are going to be installed in the system: \n " , Green ( matchesToList ( match ) ) . BgBlack ( ) . String ( ) )
}
Info ( "By going forward, you are also accepting the licenses of the packages that you are going to install in your system." )
if Ask ( ) {
l . Options . Ask = false // Don't prompt anymore
} else {
return errors . New ( "Aborted by user" )
}
2020-11-22 19:16:04 +00:00
}
2020-12-19 13:55:59 +00:00
// First match packages against repositories by priority
2020-12-06 21:11:17 +00:00
if err := l . download ( syncedRepos , match ) ; err != nil {
return errors . Wrap ( err , "Pre-downloading packages" )
}
2021-08-03 19:35:21 +00:00
if err := l . checkFileconflicts ( match , false , s ) ; err != nil {
if ! l . Options . Force {
return errors . Wrap ( err , "file conflict found" )
} else {
Warning ( "file conflict found" , err . Error ( ) )
}
}
2021-03-07 10:39:19 +00:00
if l . Options . DownloadOnly {
return nil
}
2020-12-06 21:11:17 +00:00
2021-04-24 16:15:47 +00:00
ops := l . getOpsWithOptions ( toRemove , match , Option {
2021-08-11 08:31:09 +00:00
Force : o . Force ,
NoDeps : false ,
OnlyDeps : o . OnlyDeps ,
RunFinalizers : false ,
CheckFileConflicts : false ,
2021-04-24 16:15:47 +00:00
} , o , syncedRepos , packages , assertions , allRepos )
err = l . runOps ( ops , s )
if err != nil {
return errors . Wrap ( err , "failed running installer options" )
2021-04-24 12:26:26 +00:00
}
2021-04-24 16:15:47 +00:00
toFinalize , err := l . getFinalizers ( allRepos , assertions , match , o . NoDeps )
if err != nil {
return errors . Wrap ( err , "failed getting package to finalize" )
2021-04-24 12:26:26 +00:00
}
2021-04-24 16:15:47 +00:00
return s . ExecuteFinalizers ( toFinalize )
2021-04-24 12:26:26 +00:00
}
type Option struct {
Force bool
NoDeps bool
CheckConflicts bool
FullUninstall bool
FullCleanUninstall bool
2021-04-24 16:15:47 +00:00
OnlyDeps bool
RunFinalizers bool
2021-08-11 08:31:09 +00:00
CheckFileConflicts bool
2021-04-24 12:26:26 +00:00
}
type operation struct {
Option Option
Package pkg . Package
}
2021-04-24 16:15:47 +00:00
type installOperation struct {
operation
Reposiories Repositories
Packages pkg . Packages
Assertions solver . PackagesAssertions
Database pkg . PackageDatabase
Matches map [ string ] ArtifactMatch
}
2021-04-24 12:26:26 +00:00
// installerOp is the operation that is sent to the
// upgradeWorker's channel (todo)
type installerOp struct {
Uninstall operation
2021-04-24 16:15:47 +00:00
Install installOperation
2021-04-24 12:26:26 +00:00
}
2021-04-24 16:15:47 +00:00
func ( l * LuetInstaller ) runOps ( ops [ ] installerOp , s * System ) error {
2021-04-24 12:26:26 +00:00
all := make ( chan installerOp )
wg := new ( sync . WaitGroup )
// Do the real install
for i := 0 ; i < l . Options . Concurrency ; i ++ {
wg . Add ( 1 )
go l . installerOpWorker ( i , wg , all , s )
2020-12-06 21:11:17 +00:00
}
2021-04-24 12:26:26 +00:00
for _ , c := range ops {
all <- c
}
close ( all )
wg . Wait ( )
return nil
}
2021-04-24 17:29:53 +00:00
// TODO: use installerOpWorker in place of all the other workers.
// This one is general enough to read a list of operations and execute them.
2021-04-24 12:26:26 +00:00
func ( l * LuetInstaller ) installerOpWorker ( i int , wg * sync . WaitGroup , c <- chan installerOp , s * System ) error {
defer wg . Done ( )
for p := range c {
if p . Uninstall . Package != nil {
2021-04-24 16:15:47 +00:00
Debug ( "Replacing package inplace" )
2021-04-24 12:26:26 +00:00
toUninstall , uninstall , err := l . generateUninstallFn ( p . Uninstall . Option , s , p . Uninstall . Package )
2021-04-24 16:15:47 +00:00
if err != nil {
Error ( "Failed to generate Uninstall function for" + err . Error ( ) )
continue
//return errors.Wrap(err, "while computing uninstall")
2021-04-24 12:26:26 +00:00
}
err = uninstall ( )
2021-04-24 16:15:47 +00:00
if err != nil {
2021-04-24 12:26:26 +00:00
Error ( "Failed uninstall for " , packsToList ( toUninstall ) )
2021-04-24 16:15:47 +00:00
continue
//return errors.Wrap(err, "uninstalling "+packsToList(toUninstall))
}
}
if p . Install . Package != nil {
artMatch := p . Install . Matches [ p . Install . Package . GetFingerPrint ( ) ]
ass := p . Install . Assertions . Search ( p . Install . Package . GetFingerPrint ( ) )
packageToInstall , _ := p . Install . Packages . Find ( p . Install . Package . GetPackageName ( ) )
err := l . install (
p . Install . Option ,
p . Install . Reposiories ,
map [ string ] ArtifactMatch { p . Install . Package . GetFingerPrint ( ) : artMatch } ,
pkg . Packages { packageToInstall } ,
solver . PackagesAssertions { * ass } ,
p . Install . Database ,
s ,
)
if err != nil {
Error ( err )
2021-04-24 12:26:26 +00:00
}
}
}
return nil
}
// checks wheter we can uninstall and install in place and compose installer worker ops
2021-04-24 16:15:47 +00:00
func ( l * LuetInstaller ) getOpsWithOptions (
toUninstall pkg . Packages , installMatch map [ string ] ArtifactMatch , installOpt , uninstallOpt Option ,
syncedRepos Repositories , toInstall pkg . Packages , solution solver . PackagesAssertions , allRepos pkg . PackageDatabase ) [ ] installerOp {
2021-04-24 12:26:26 +00:00
resOps := [ ] installerOp { }
for _ , match := range installMatch {
if pack , err := toUninstall . Find ( match . Package . GetPackageName ( ) ) ; err == nil {
resOps = append ( resOps , installerOp {
Uninstall : operation { Package : pack , Option : uninstallOpt } ,
2021-04-24 16:15:47 +00:00
Install : installOperation {
operation : operation {
Package : match . Package ,
Option : installOpt ,
} ,
Matches : installMatch ,
Packages : toInstall ,
Reposiories : syncedRepos ,
Assertions : solution ,
Database : allRepos ,
} ,
2021-04-24 12:26:26 +00:00
} )
} else {
resOps = append ( resOps , installerOp {
2021-04-24 16:15:47 +00:00
Install : installOperation {
operation : operation { Package : match . Package , Option : installOpt } ,
Matches : installMatch ,
Reposiories : syncedRepos ,
Packages : toInstall ,
Assertions : solution ,
Database : allRepos ,
} ,
2021-04-24 12:26:26 +00:00
} )
}
}
for _ , p := range toUninstall {
found := false
for _ , match := range installMatch {
if match . Package . GetPackageName ( ) == p . GetPackageName ( ) {
found = true
}
}
if ! found {
resOps = append ( resOps , installerOp {
Uninstall : operation { Package : p , Option : uninstallOpt } ,
} )
}
}
return resOps
2019-11-29 18:01:56 +00:00
}
2021-04-16 21:38:10 +00:00
func ( l * LuetInstaller ) checkAndUpgrade ( r Repositories , s * System ) error {
Spinner ( 32 )
uninstall , toInstall , err := l . computeUpgrade ( r , s )
if err != nil {
return errors . Wrap ( err , "failed computing upgrade" )
}
SpinnerStop ( )
if len ( uninstall ) > 0 {
Info ( ":recycle: Packages that are going to be removed from the system:\n " , Yellow ( packsToList ( uninstall ) ) . BgBlack ( ) . String ( ) )
}
if len ( toInstall ) > 0 {
Info ( ":zap:Packages that are going to be installed in the system:\n " , Green ( packsToList ( toInstall ) ) . BgBlack ( ) . String ( ) )
}
if len ( toInstall ) == 0 && len ( uninstall ) == 0 {
Info ( "Nothing to do" )
return nil
}
2021-04-24 17:29:53 +00:00
// We don't want any conflict with the installed to raise during the upgrade.
// In this way we both force uninstalls and we avoid to check with conflicts
// against the current system state which is pending to deletion
// E.g. you can't check for conflicts for an upgrade of a new version of A
// if the old A results installed in the system. This is due to the fact that
// now the solver enforces the constraints and explictly denies two packages
// of the same version installed.
o := Option {
FullUninstall : false ,
Force : true ,
CheckConflicts : false ,
FullCleanUninstall : false ,
NoDeps : true ,
OnlyDeps : false ,
}
2021-04-16 21:38:10 +00:00
if l . Options . Ask {
Info ( "By going forward, you are also accepting the licenses of the packages that you are going to install in your system." )
if Ask ( ) {
l . Options . Ask = false // Don't prompt anymore
2021-04-24 17:29:53 +00:00
return l . swap ( o , r , uninstall , toInstall , s )
2021-04-16 21:38:10 +00:00
} else {
return errors . New ( "Aborted by user" )
}
}
2021-04-24 17:29:53 +00:00
return l . swap ( o , r , uninstall , toInstall , s )
2021-04-16 21:38:10 +00:00
}
2020-04-13 16:49:05 +00:00
func ( l * LuetInstaller ) Install ( cp pkg . Packages , s * System ) error {
2021-10-19 11:05:25 +00:00
syncedRepos , err := l . SyncRepositories ( )
2020-02-27 22:25:29 +00:00
if err != nil {
return err
}
2020-11-22 19:16:04 +00:00
2021-10-09 15:36:13 +00:00
if len ( s . Database . World ( ) ) > 0 && ! l . Options . Relaxed {
2021-04-16 21:38:10 +00:00
Info ( ":thinking: Checking for available upgrades" )
if err := l . checkAndUpgrade ( syncedRepos , s ) ; err != nil {
return errors . Wrap ( err , "while checking upgrades before install" )
2021-04-16 20:28:30 +00:00
}
}
2021-04-24 16:15:47 +00:00
o := Option {
2021-08-11 08:31:09 +00:00
NoDeps : l . Options . NoDeps ,
Force : l . Options . Force ,
OnlyDeps : l . Options . OnlyDeps ,
CheckFileConflicts : true ,
RunFinalizers : true ,
2021-04-24 16:15:47 +00:00
}
match , packages , assertions , allRepos , err := l . computeInstall ( o , syncedRepos , cp , s )
2020-11-22 19:16:04 +00:00
if err != nil {
return err
}
2020-12-03 19:03:37 +00:00
// Check if we have to process something, or return to the user an error
2020-12-03 17:32:24 +00:00
if len ( match ) == 0 {
Info ( "No packages to install" )
return nil
}
2020-12-03 17:53:57 +00:00
// Resolvers might decide to remove some packages from being installed
if ! l . Options . SolverOptions . ResolverIsSet ( ) {
for _ , p := range cp {
found := false
2020-12-03 19:03:37 +00:00
vers , _ := s . Database . FindPackageVersions ( p ) // If was installed, it is found, as it was filtered
if len ( vers ) >= 1 {
found = true
continue
}
2020-12-03 17:53:57 +00:00
for _ , m := range match {
if m . Package . GetName ( ) == p . GetName ( ) {
found = true
}
2021-09-06 12:58:50 +00:00
for _ , pack := range m . Package . GetProvides ( ) {
if pack . GetName ( ) == p . GetName ( ) {
found = true
}
}
2020-12-03 17:53:57 +00:00
}
2020-12-03 19:03:37 +00:00
2020-12-03 17:53:57 +00:00
if ! found {
return fmt . Errorf ( "Package '%s' not found" , p . HumanReadableString ( ) )
2020-12-03 16:25:29 +00:00
}
}
}
2020-12-03 17:32:24 +00:00
Info ( "Packages that are going to be installed in the system: \n " , Green ( matchesToList ( match ) ) . BgBlack ( ) . String ( ) )
2021-04-24 16:15:47 +00:00
2020-11-22 19:16:04 +00:00
if l . Options . Ask {
Info ( "By going forward, you are also accepting the licenses of the packages that you are going to install in your system." )
if Ask ( ) {
2020-11-23 17:20:30 +00:00
l . Options . Ask = false // Don't prompt anymore
2021-04-24 12:26:26 +00:00
return l . install ( o , syncedRepos , match , packages , assertions , allRepos , s )
2020-11-22 19:16:04 +00:00
} else {
return errors . New ( "Aborted by user" )
}
}
2021-04-24 12:26:26 +00:00
return l . install ( o , syncedRepos , match , packages , assertions , allRepos , s )
2020-02-27 22:25:29 +00:00
}
2020-12-06 21:11:17 +00:00
func ( l * LuetInstaller ) download ( syncedRepos Repositories , toDownload map [ string ] ArtifactMatch ) error {
2020-03-24 17:13:10 +00:00
// Download packages into cache in parallel.
all := make ( chan ArtifactMatch )
var wg = new ( sync . WaitGroup )
// Download
for i := 0 ; i < l . Options . Concurrency ; i ++ {
wg . Add ( 1 )
go l . downloadWorker ( i , wg , all )
}
for _ , c := range toDownload {
all <- c
}
close ( all )
wg . Wait ( )
return nil
}
2020-04-13 17:30:40 +00:00
// Reclaim adds packages to the system database
// if files from artifacts in the repositories are found
// in the system target
func ( l * LuetInstaller ) Reclaim ( s * System ) error {
2021-10-19 11:05:25 +00:00
syncedRepos , err := l . SyncRepositories ( )
2020-04-13 17:30:40 +00:00
if err != nil {
return err
}
var toMerge [ ] ArtifactMatch = [ ] ArtifactMatch { }
for _ , repo := range syncedRepos {
for _ , artefact := range repo . GetIndex ( ) {
2020-05-02 10:17:54 +00:00
Debug ( "Checking if" ,
2021-04-12 17:00:36 +00:00
artefact . CompileSpec . GetPackage ( ) . HumanReadableString ( ) ,
2020-05-02 10:17:54 +00:00
"from" , repo . GetName ( ) , "is installed" )
2020-04-13 17:30:40 +00:00
FILES :
2021-04-12 17:00:36 +00:00
for _ , f := range artefact . Files {
2021-06-01 14:43:31 +00:00
if fileHelper . Exists ( filepath . Join ( s . Target , f ) ) {
2021-04-12 17:00:36 +00:00
p , err := repo . GetTree ( ) . GetDatabase ( ) . FindPackage ( artefact . CompileSpec . GetPackage ( ) )
2020-04-30 16:56:50 +00:00
if err != nil {
return err
}
2020-10-30 18:15:04 +00:00
Info ( ":mag: Found package:" , p . HumanReadableString ( ) )
2020-04-30 16:56:50 +00:00
toMerge = append ( toMerge , ArtifactMatch { Artifact : artefact , Package : p } )
2020-04-13 17:30:40 +00:00
break FILES
}
}
}
}
for _ , match := range toMerge {
pack := match . Package
vers , _ := s . Database . FindPackageVersions ( pack )
if len ( vers ) >= 1 {
Warning ( "Filtering out package " + pack . HumanReadableString ( ) + ", already reclaimed" )
continue
}
_ , err := s . Database . CreatePackage ( pack )
if err != nil && ! l . Options . Force {
return errors . Wrap ( err , "Failed creating package" )
}
2021-04-12 17:00:36 +00:00
s . Database . SetPackageFiles ( & pkg . PackageFile { PackageFingerprint : pack . GetFingerPrint ( ) , Files : match . Artifact . Files } )
2020-12-02 17:24:32 +00:00
Info ( ":zap:Reclaimed package:" , pack . HumanReadableString ( ) )
2020-04-13 17:30:40 +00:00
}
2020-05-02 10:17:54 +00:00
Info ( "Done!" )
2020-04-13 17:30:40 +00:00
return nil
}
2021-04-24 16:15:47 +00:00
func ( l * LuetInstaller ) computeInstall ( o Option , syncedRepos Repositories , cp pkg . Packages , s * System ) ( map [ string ] ArtifactMatch , pkg . Packages , solver . PackagesAssertions , pkg . PackageDatabase , error ) {
2020-04-04 12:29:08 +00:00
var p pkg . Packages
2020-11-22 19:16:04 +00:00
toInstall := map [ string ] ArtifactMatch { }
allRepos := pkg . NewInMemoryDatabase ( false )
var solution solver . PackagesAssertions
2020-01-01 10:53:50 +00:00
// Check if the package is installed first
for _ , pi := range cp {
vers , _ := s . Database . FindPackageVersions ( pi )
if len ( vers ) >= 1 {
2020-11-22 19:16:04 +00:00
// Warning("Filtering out package " + pi.HumanReadableString() + ", it has other versions already installed. Uninstall one of them first ")
2020-01-01 10:53:50 +00:00
continue
//return errors.New("Package " + pi.GetFingerPrint() + " has other versions already installed. Uninstall one of them first: " + strings.Join(vers, " "))
}
p = append ( p , pi )
}
if len ( p ) == 0 {
2020-11-22 19:16:04 +00:00
return toInstall , p , solution , allRepos , nil
2020-01-01 10:53:50 +00:00
}
2019-11-22 20:01:38 +00:00
// First get metas from all repos (and decodes trees)
// First match packages against repositories by priority
2019-11-22 21:23:05 +00:00
// matches := syncedRepos.PackageMatches(p)
2019-11-22 20:01:38 +00:00
// compute a "big" world
2019-12-31 15:50:44 +00:00
syncedRepos . SyncDatabase ( allRepos )
2020-02-04 19:15:59 +00:00
p = syncedRepos . ResolveSelectors ( p )
2020-04-04 12:29:08 +00:00
var packagesToInstall pkg . Packages
2020-02-27 22:25:29 +00:00
var err error
2020-02-18 17:37:56 +00:00
2021-04-24 16:15:47 +00:00
if ! o . NoDeps {
2020-10-25 17:43:35 +00:00
solv := solver . NewResolver ( solver . Options { Type : l . Options . SolverOptions . Implementation , Concurrency : l . Options . Concurrency } , s . Database , allRepos , pkg . NewInMemoryDatabase ( false ) , l . Options . SolverOptions . Resolver ( ) )
2021-10-09 15:36:13 +00:00
if l . Options . Relaxed {
solution , err = solv . RelaxedInstall ( p )
} else {
solution , err = solv . Install ( p )
}
2020-10-31 00:22:30 +00:00
/// TODO: PackageAssertions needs to be a map[fingerprint]pack so lookup is in O(1)
2021-04-24 16:15:47 +00:00
if err != nil && ! o . Force {
2020-11-22 19:16:04 +00:00
return toInstall , p , solution , allRepos , errors . Wrap ( err , "Failed solving solution for package" )
2020-02-18 17:37:56 +00:00
}
// Gathers things to install
for _ , assertion := range solution {
if assertion . Value {
2020-10-31 00:22:30 +00:00
if _ , err := s . Database . FindPackage ( assertion . Package ) ; err == nil {
// skip matching if it is installed already
continue
}
2020-02-18 17:37:56 +00:00
packagesToInstall = append ( packagesToInstall , assertion . Package )
}
}
2021-04-24 16:15:47 +00:00
} else if ! o . OnlyDeps {
2020-02-18 17:37:56 +00:00
for _ , currentPack := range p {
2020-10-31 00:22:30 +00:00
if _ , err := s . Database . FindPackage ( currentPack ) ; err == nil {
// skip matching if it is installed already
continue
}
2020-02-18 17:37:56 +00:00
packagesToInstall = append ( packagesToInstall , currentPack )
}
2019-11-22 20:01:38 +00:00
}
// Gathers things to install
2020-02-18 17:37:56 +00:00
for _ , currentPack := range packagesToInstall {
2020-05-02 11:24:46 +00:00
// Check if package is already installed.
2020-10-31 00:22:30 +00:00
2020-04-04 12:29:08 +00:00
matches := syncedRepos . PackageMatches ( pkg . Packages { currentPack } )
2020-02-18 17:37:56 +00:00
if len ( matches ) == 0 {
2020-11-22 19:16:04 +00:00
return toInstall , p , solution , allRepos , errors . New ( "Failed matching solutions against repository for " + currentPack . HumanReadableString ( ) + " where are definitions coming from?!" )
2020-02-18 17:37:56 +00:00
}
A :
for _ , artefact := range matches [ 0 ] . Repo . GetIndex ( ) {
2021-04-12 17:00:36 +00:00
if artefact . CompileSpec . GetPackage ( ) == nil {
2020-11-22 19:16:04 +00:00
return toInstall , p , solution , allRepos , errors . New ( "Package in compilespec empty" )
2020-02-18 17:37:56 +00:00
}
2021-04-12 17:00:36 +00:00
if matches [ 0 ] . Package . Matches ( artefact . CompileSpec . GetPackage ( ) ) {
currentPack . SetBuildTimestamp ( artefact . CompileSpec . GetPackage ( ) . GetBuildTimestamp ( ) )
2020-02-18 17:37:56 +00:00
// Filter out already installed
if _ , err := s . Database . FindPackage ( currentPack ) ; err != nil {
toInstall [ currentPack . GetFingerPrint ( ) ] = ArtifactMatch { Package : currentPack , Artifact : artefact , Repository : matches [ 0 ] . Repo }
2019-11-23 14:42:05 +00:00
}
2020-02-18 17:37:56 +00:00
break A
2019-11-22 20:01:38 +00:00
}
}
}
2020-11-22 19:16:04 +00:00
return toInstall , p , solution , allRepos , nil
}
2021-04-24 16:15:47 +00:00
func ( l * LuetInstaller ) getFinalizers ( allRepos pkg . PackageDatabase , solution solver . PackagesAssertions , toInstall map [ string ] ArtifactMatch , nodeps bool ) ( [ ] pkg . Package , error ) {
var toFinalize [ ] pkg . Package
if ! nodeps {
// TODO: Lower those errors as warning
for _ , w := range toInstall {
// Finalizers needs to run in order and in sequence.
ordered , err := solution . Order ( allRepos , w . Package . GetFingerPrint ( ) )
if err != nil {
return toFinalize , errors . Wrap ( err , "While order a solution for " + w . Package . HumanReadableString ( ) )
}
ORDER :
for _ , ass := range ordered {
if ass . Value {
installed , ok := toInstall [ ass . Package . GetFingerPrint ( ) ]
if ! ok {
// It was a dep already installed in the system, so we can skip it safely
continue ORDER
}
treePackage , err := installed . Repository . GetTree ( ) . GetDatabase ( ) . FindPackage ( ass . Package )
if err != nil {
return toFinalize , errors . Wrap ( err , "Error getting package " + ass . Package . HumanReadableString ( ) )
}
toFinalize = append ( toFinalize , treePackage )
}
}
}
} else {
for _ , c := range toInstall {
treePackage , err := c . Repository . GetTree ( ) . GetDatabase ( ) . FindPackage ( c . Package )
if err != nil {
return toFinalize , errors . Wrap ( err , "Error getting package " + c . Package . HumanReadableString ( ) )
}
toFinalize = append ( toFinalize , treePackage )
}
}
return toFinalize , nil
}
2021-08-03 19:35:21 +00:00
func ( l * LuetInstaller ) checkFileconflicts ( toInstall map [ string ] ArtifactMatch , checkSystem bool , s * System ) error {
2021-07-29 08:14:05 +00:00
Info ( "Checking for file conflicts.." )
2021-07-30 09:38:49 +00:00
defer s . Clean ( ) // Release memory
2021-07-29 08:14:05 +00:00
filesToInstall := [ ] string { }
for _ , m := range toInstall {
a , err := l . downloadPackage ( m )
if err != nil && ! l . Options . Force {
return errors . Wrap ( err , "Failed downloading package" )
}
files , err := a . FileList ( )
if err != nil && ! l . Options . Force {
return errors . Wrapf ( err , "Could not get filelist for %s" , a . CompileSpec . Package . HumanReadableString ( ) )
}
for _ , f := range files {
if helpers . Contains ( filesToInstall , f ) {
return fmt . Errorf (
"file conflict between packages to be installed" ,
)
}
2021-08-03 19:35:21 +00:00
if checkSystem {
exists , p , err := s . ExistsPackageFile ( f )
if err != nil {
return errors . Wrap ( err , "failed checking into system db" )
}
if exists {
return fmt . Errorf (
"file conflict between '%s' and '%s' ( file: %s )" ,
p . HumanReadableString ( ) ,
m . Package . HumanReadableString ( ) ,
f ,
)
}
2021-07-29 08:14:05 +00:00
}
}
filesToInstall = append ( filesToInstall , files ... )
}
return nil
}
2021-04-24 12:26:26 +00:00
func ( l * LuetInstaller ) install ( o Option , syncedRepos Repositories , toInstall map [ string ] ArtifactMatch , p pkg . Packages , solution solver . PackagesAssertions , allRepos pkg . PackageDatabase , s * System ) error {
2021-07-29 08:14:05 +00:00
// Download packages in parallel first
2020-12-06 21:11:17 +00:00
if err := l . download ( syncedRepos , toInstall ) ; err != nil {
return errors . Wrap ( err , "Downloading packages" )
2020-03-24 17:13:10 +00:00
}
2020-02-27 22:14:53 +00:00
2021-08-11 08:31:09 +00:00
if o . CheckFileConflicts {
// Check file conflicts
if err := l . checkFileconflicts ( toInstall , true , s ) ; err != nil {
if ! l . Options . Force {
return errors . Wrap ( err , "file conflict found" )
} else {
Warning ( "file conflict found" , err . Error ( ) )
}
2021-07-29 08:14:05 +00:00
}
}
2021-03-07 10:39:19 +00:00
if l . Options . DownloadOnly {
return nil
}
2020-12-06 21:11:17 +00:00
all := make ( chan ArtifactMatch )
2020-02-27 22:14:53 +00:00
2020-12-06 21:11:17 +00:00
wg := new ( sync . WaitGroup )
2020-02-27 22:14:53 +00:00
2020-03-24 17:13:10 +00:00
// Do the real install
for i := 0 ; i < l . Options . Concurrency ; i ++ {
wg . Add ( 1 )
go l . installerWorker ( i , wg , all , s )
2019-11-22 20:01:38 +00:00
}
2020-03-24 17:13:10 +00:00
for _ , c := range toInstall {
all <- c
2019-11-22 20:01:38 +00:00
}
2020-03-24 17:13:10 +00:00
close ( all )
wg . Wait ( )
2019-11-22 20:01:38 +00:00
2020-02-27 17:26:48 +00:00
for _ , c := range toInstall {
// Annotate to the system that the package was installed
_ , err := s . Database . CreatePackage ( c . Package )
2021-04-24 12:26:26 +00:00
if err != nil && ! o . Force {
2020-02-27 17:26:48 +00:00
return errors . Wrap ( err , "Failed creating package" )
}
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackageInstall , c )
2020-02-27 17:26:48 +00:00
}
2020-03-28 15:57:40 +00:00
2021-04-24 16:15:47 +00:00
if ! o . RunFinalizers {
return nil
}
2019-11-22 21:23:05 +00:00
2021-04-24 16:15:47 +00:00
toFinalize , err := l . getFinalizers ( allRepos , solution , toInstall , o . NoDeps )
if err != nil {
return errors . Wrap ( err , "failed getting package to finalize" )
2019-11-22 21:23:05 +00:00
}
2020-03-28 15:57:40 +00:00
2020-12-25 09:34:37 +00:00
return s . ExecuteFinalizers ( toFinalize )
2019-11-22 20:01:38 +00:00
}
2021-04-12 17:00:36 +00:00
func ( l * LuetInstaller ) downloadPackage ( a ArtifactMatch ) ( * artifact . PackageArtifact , error ) {
2019-11-22 20:01:38 +00:00
2019-11-22 22:12:03 +00:00
artifact , err := a . Repository . Client ( ) . DownloadArtifact ( a . Artifact )
2020-02-02 23:58:55 +00:00
if err != nil {
2020-03-24 17:13:10 +00:00
return nil , errors . Wrap ( err , "Error on download artifact" )
2020-02-02 23:58:55 +00:00
}
2019-11-23 21:41:51 +00:00
2019-12-29 12:58:49 +00:00
err = artifact . Verify ( )
2020-12-09 20:31:07 +00:00
if err != nil {
2020-03-24 17:13:10 +00:00
return nil , errors . Wrap ( err , "Artifact integrity check failure" )
2019-12-29 12:58:49 +00:00
}
2020-03-24 17:13:10 +00:00
return artifact , nil
}
2021-04-12 17:00:36 +00:00
func ( l * LuetInstaller ) installPackage ( m ArtifactMatch , s * System ) error {
2020-03-24 17:13:10 +00:00
2021-04-12 17:00:36 +00:00
a , err := l . downloadPackage ( m )
2020-03-24 17:13:10 +00:00
if err != nil && ! l . Options . Force {
return errors . Wrap ( err , "Failed downloading package" )
2020-02-27 22:14:53 +00:00
}
2019-12-29 12:58:49 +00:00
2021-04-12 17:00:36 +00:00
files , err := a . FileList ( )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
2019-12-31 11:48:12 +00:00
return errors . Wrap ( err , "Could not open package archive" )
2019-11-23 21:41:51 +00:00
}
2021-04-12 17:00:36 +00:00
err = a . Unpack ( s . Target , true )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
2019-11-22 22:12:03 +00:00
return errors . Wrap ( err , "Error met while unpacking rootfs" )
}
2019-11-23 21:41:51 +00:00
2019-11-22 22:12:03 +00:00
// First create client and download
// Then unpack to system
2021-04-12 17:00:36 +00:00
return s . Database . SetPackageFiles ( & pkg . PackageFile { PackageFingerprint : m . Package . GetFingerPrint ( ) , Files : files } )
2019-11-22 20:01:38 +00:00
}
2020-03-24 17:13:10 +00:00
func ( l * LuetInstaller ) downloadWorker ( i int , wg * sync . WaitGroup , c <- chan ArtifactMatch ) error {
2019-11-22 20:01:38 +00:00
defer wg . Done ( )
for p := range c {
2019-11-23 15:28:50 +00:00
// TODO: Keep trace of what was added from the tar, and save it into system
2020-03-24 17:13:10 +00:00
_ , err := l . downloadPackage ( p )
2020-12-09 21:56:55 +00:00
if err != nil {
Fatal ( "Failed downloading package " + p . Package . GetName ( ) , err . Error ( ) )
return errors . Wrap ( err , "Failed downloading package " + p . Package . GetName ( ) )
2020-12-09 21:58:33 +00:00
} else {
2020-12-09 21:56:55 +00:00
Info ( ":package: Package " , p . Package . HumanReadableString ( ) , "downloaded" )
2020-03-24 17:13:10 +00:00
}
}
return nil
}
func ( l * LuetInstaller ) installerWorker ( i int , wg * sync . WaitGroup , c <- chan ArtifactMatch , s * System ) error {
defer wg . Done ( )
for p := range c {
// TODO: Keep trace of what was added from the tar, and save it into system
err := l . installPackage ( p , s )
if err != nil && ! l . Options . Force {
//TODO: Uninstall, rollback.
Fatal ( "Failed installing package " + p . Package . GetName ( ) , err . Error ( ) )
return errors . Wrap ( err , "Failed installing package " + p . Package . GetName ( ) )
}
if err == nil {
2020-11-22 19:16:04 +00:00
Info ( ":package: Package " , p . Package . HumanReadableString ( ) , "installed" )
2020-02-18 17:37:56 +00:00
} else if err != nil && l . Options . Force {
2020-11-22 19:16:04 +00:00
Info ( ":package: Package " , p . Package . HumanReadableString ( ) , "installed with failures (forced install)" )
2020-02-18 17:37:56 +00:00
}
2019-11-22 20:01:38 +00:00
}
return nil
}
2019-11-23 23:16:12 +00:00
2021-04-24 17:21:15 +00:00
func checkAndPrunePath ( path string ) {
// check if now the target path is empty
targetPath := filepath . Dir ( path )
fi , err := os . Lstat ( targetPath )
if err != nil {
2021-04-24 17:29:53 +00:00
// Warning("Dir not found (it was before?) ", err.Error())
2021-04-24 17:21:15 +00:00
return
}
switch mode := fi . Mode ( ) ; {
case mode . IsDir ( ) :
files , err := ioutil . ReadDir ( targetPath )
if err != nil {
Warning ( "Failed reading folder" , targetPath , err . Error ( ) )
}
if len ( files ) != 0 {
Debug ( "Preserving not-empty folder" , targetPath )
return
}
}
if err = os . Remove ( targetPath ) ; err != nil {
Warning ( "Failed removing file (maybe not present in the system target anymore ?)" , targetPath , err . Error ( ) )
}
}
// We will try to cleanup every path from the file, if the folders left behind are empty
func pruneEmptyFilePath ( path string ) {
checkAndPrunePath ( path )
// A path is for e.g. /usr/bin/bar
// we want to create an array as "/usr", "/usr/bin", "/usr/bin/bar"
paths := strings . Split ( path , string ( os . PathSeparator ) )
currentPath := filepath . Join ( string ( os . PathSeparator ) , paths [ 0 ] )
allPaths := [ ] string { currentPath }
for _ , p := range paths [ 1 : ] {
currentPath = filepath . Join ( currentPath , p )
allPaths = append ( allPaths , currentPath )
}
2021-06-01 14:43:31 +00:00
match . ReverseAny ( allPaths )
2021-04-24 17:21:15 +00:00
for _ , p := range allPaths {
checkAndPrunePath ( p )
}
}
2019-11-23 23:16:12 +00:00
func ( l * LuetInstaller ) uninstall ( p pkg . Package , s * System ) error {
2020-11-06 19:14:25 +00:00
var cp * config . ConfigProtect
annotationDir := ""
2019-11-23 23:16:12 +00:00
files , err := s . Database . GetPackageFiles ( p )
if err != nil {
return errors . Wrap ( err , "Failed getting installed files" )
}
2020-11-06 19:14:25 +00:00
if ! config . LuetCfg . ConfigProtectSkip {
if p . HasAnnotation ( string ( pkg . ConfigProtectAnnnotation ) ) {
dir , ok := p . GetAnnotations ( ) [ string ( pkg . ConfigProtectAnnnotation ) ]
if ok {
annotationDir = dir
}
}
cp = config . NewConfigProtect ( annotationDir )
cp . Map ( files )
}
2021-06-01 14:43:31 +00:00
toRemove , notPresent := fileHelper . OrderFiles ( s . Target , files )
2020-11-08 11:36:41 +00:00
2019-11-23 23:16:12 +00:00
// Remove from target
2020-11-08 11:36:41 +00:00
for _ , f := range toRemove {
2019-11-23 23:16:12 +00:00
target := filepath . Join ( s . Target , f )
2020-02-27 22:14:36 +00:00
2020-11-06 19:14:25 +00:00
if ! config . LuetCfg . ConfigProtectSkip && cp . Protected ( f ) {
2020-11-07 11:27:18 +00:00
Debug ( "Preserving protected file:" , f )
2020-11-06 19:14:25 +00:00
continue
}
Debug ( "Removing" , target )
2020-02-27 22:14:36 +00:00
if l . Options . PreserveSystemEssentialData &&
strings . HasPrefix ( f , config . LuetCfg . GetSystem ( ) . GetSystemPkgsCacheDirPath ( ) ) ||
strings . HasPrefix ( f , config . LuetCfg . GetSystem ( ) . GetSystemRepoDatabaseDirPath ( ) ) {
Warning ( "Preserve " , f , " which is required by luet ( you have to delete it manually if you really need to)" )
continue
}
2020-11-06 20:34:56 +00:00
fi , err := os . Lstat ( target )
2019-11-23 23:16:12 +00:00
if err != nil {
2020-11-08 11:36:41 +00:00
Warning ( "File not found (it was before?) " , err . Error ( ) )
2020-10-31 10:56:03 +00:00
continue
}
switch mode := fi . Mode ( ) ; {
case mode . IsDir ( ) :
files , err := ioutil . ReadDir ( target )
if err != nil {
2020-11-06 20:34:56 +00:00
Warning ( "Failed reading folder" , target , err . Error ( ) )
2020-10-31 10:56:03 +00:00
}
if len ( files ) != 0 {
2020-11-08 11:36:41 +00:00
Debug ( "Preserving not-empty folder" , target )
2020-10-31 10:56:03 +00:00
continue
}
}
if err = os . Remove ( target ) ; err != nil {
2020-11-08 11:36:41 +00:00
Warning ( "Failed removing file (maybe not present in the system target anymore ?)" , target , err . Error ( ) )
}
2021-04-24 17:21:15 +00:00
pruneEmptyFilePath ( target )
2020-11-08 11:36:41 +00:00
}
for _ , f := range notPresent {
target := filepath . Join ( s . Target , f )
if ! config . LuetCfg . ConfigProtectSkip && cp . Protected ( f ) {
Debug ( "Preserving protected file:" , f )
continue
}
if err = os . Remove ( target ) ; err != nil {
Debug ( "Failed removing file (not present in the system target)" , target , err . Error ( ) )
2019-11-23 23:16:12 +00:00
}
2021-04-24 17:21:15 +00:00
pruneEmptyFilePath ( target )
2019-11-23 23:16:12 +00:00
}
2020-11-08 11:36:41 +00:00
2019-11-25 19:02:18 +00:00
err = s . Database . RemovePackageFiles ( p )
if err != nil {
return errors . Wrap ( err , "Failed removing package files from database" )
}
2019-11-23 23:16:12 +00:00
err = s . Database . RemovePackage ( p )
if err != nil {
return errors . Wrap ( err , "Failed removing package from database" )
}
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackageUnInstall , p )
2020-12-02 17:24:32 +00:00
Info ( ":recycle: " , p . GetFingerPrint ( ) , "Removed :heavy_check_mark:" )
2019-11-23 23:16:12 +00:00
return nil
}
2021-04-24 12:26:26 +00:00
func ( l * LuetInstaller ) computeUninstall ( o Option , s * System , packs ... pkg . Package ) ( pkg . Packages , error ) {
2020-05-03 11:16:52 +00:00
2020-11-22 19:16:04 +00:00
var toUninstall pkg . Packages
2020-02-27 17:26:48 +00:00
// compute uninstall from all world - remove packages in parallel - run uninstall finalizer (in order) TODO - mark the uninstallation in db
2019-11-23 23:16:12 +00:00
// Get installed definition
2021-04-24 12:26:26 +00:00
checkConflicts := o . CheckConflicts
full := o . FullUninstall
2021-04-24 16:15:47 +00:00
// if o.Force == true { // IF forced, we want to remove the package and all its requires
// checkConflicts = false
// full = false
// }
2020-05-03 10:16:45 +00:00
2020-05-02 13:43:57 +00:00
// Create a temporary DB with the installed packages
// so the solver is much faster finding the deptree
2020-12-19 16:45:50 +00:00
// First check what would have been done
installedtmp , err := s . Database . Copy ( )
if err != nil {
return toUninstall , errors . Wrap ( err , "Failed create temporary in-memory db" )
2020-05-02 13:43:57 +00:00
}
2020-02-27 17:26:48 +00:00
2021-04-24 16:15:47 +00:00
if ! o . NoDeps {
2020-10-25 17:43:35 +00:00
solv := solver . NewResolver ( solver . Options { Type : l . Options . SolverOptions . Implementation , Concurrency : l . Options . Concurrency } , installedtmp , installedtmp , pkg . NewInMemoryDatabase ( false ) , l . Options . SolverOptions . Resolver ( ) )
2020-05-22 18:45:28 +00:00
var solution pkg . Packages
var err error
2021-04-24 12:26:26 +00:00
if o . FullCleanUninstall {
2020-12-19 16:16:53 +00:00
solution , err = solv . UninstallUniverse ( packs )
2020-05-22 18:45:28 +00:00
if err != nil {
2020-11-22 19:16:04 +00:00
return toUninstall , errors . Wrap ( err , "Could not solve the uninstall constraints. Tip: try with --solver-type qlearning or with --force, or by removing packages excluding their dependencies with --nodeps" )
2020-05-22 18:45:28 +00:00
}
} else {
2020-12-19 16:16:53 +00:00
solution , err = solv . Uninstall ( checkConflicts , full , packs ... )
2020-05-22 18:45:28 +00:00
if err != nil && ! l . Options . Force {
2020-11-22 19:16:04 +00:00
return toUninstall , errors . Wrap ( err , "Could not solve the uninstall constraints. Tip: try with --solver-type qlearning or with --force, or by removing packages excluding their dependencies with --nodeps" )
2020-05-22 18:45:28 +00:00
}
2020-02-18 17:37:56 +00:00
}
2020-05-22 18:45:28 +00:00
2020-02-18 17:37:56 +00:00
for _ , p := range solution {
2020-11-22 19:16:04 +00:00
toUninstall = append ( toUninstall , p )
}
} else {
2020-12-19 16:16:53 +00:00
toUninstall = append ( toUninstall , packs ... )
2020-11-22 19:16:04 +00:00
}
return toUninstall , nil
}
2020-12-19 16:16:53 +00:00
2021-04-24 12:26:26 +00:00
func ( l * LuetInstaller ) generateUninstallFn ( o Option , s * System , packs ... pkg . Package ) ( pkg . Packages , func ( ) error , error ) {
2020-12-19 16:16:53 +00:00
for _ , p := range packs {
if packs , _ := s . Database . FindPackages ( p ) ; len ( packs ) == 0 {
2021-08-26 12:45:19 +00:00
return nil , nil , errors . New ( fmt . Sprintf ( "Package %s not found in the system" , p . HumanReadableString ( ) ) )
2020-12-19 16:16:53 +00:00
}
2020-12-03 16:25:29 +00:00
}
2021-04-24 12:26:26 +00:00
toUninstall , err := l . computeUninstall ( o , s , packs ... )
2020-11-22 19:16:04 +00:00
if err != nil {
2021-04-24 12:26:26 +00:00
return nil , nil , errors . Wrap ( err , "while computing uninstall" )
2020-11-22 19:16:04 +00:00
}
uninstall := func ( ) error {
for _ , p := range toUninstall {
2020-02-18 17:37:56 +00:00
err := l . uninstall ( p , s )
2021-04-24 12:26:26 +00:00
if err != nil && ! o . Force {
2020-02-18 17:37:56 +00:00
return errors . Wrap ( err , "Uninstall failed" )
}
}
2020-11-22 19:16:04 +00:00
return nil
2019-11-23 23:16:12 +00:00
}
2019-11-22 20:01:38 +00:00
2021-04-24 12:26:26 +00:00
return toUninstall , uninstall , nil
}
func ( l * LuetInstaller ) Uninstall ( s * System , packs ... pkg . Package ) error {
Spinner ( 32 )
o := Option {
FullUninstall : l . Options . FullUninstall ,
Force : l . Options . Force ,
CheckConflicts : l . Options . CheckConflicts ,
FullCleanUninstall : l . Options . FullCleanUninstall ,
}
toUninstall , uninstall , err := l . generateUninstallFn ( o , s , packs ... )
if err != nil {
return errors . Wrap ( err , "while computing uninstall" )
}
SpinnerStop ( )
2020-11-23 17:20:30 +00:00
if len ( toUninstall ) == 0 {
Info ( "Nothing to do" )
return nil
}
2020-11-22 19:16:04 +00:00
if l . Options . Ask {
2020-12-19 13:55:59 +00:00
Info ( ":recycle: Packages that are going to be removed from the system:\n " , Yellow ( packsToList ( toUninstall ) ) . BgBlack ( ) . String ( ) )
2020-11-22 19:16:04 +00:00
if Ask ( ) {
2020-11-23 17:20:30 +00:00
l . Options . Ask = false // Don't prompt anymore
2020-11-22 19:16:04 +00:00
return uninstall ( )
} else {
return errors . New ( "Aborted by user" )
}
}
return uninstall ( )
2019-11-22 20:01:38 +00:00
}