mirror of
https://github.com/mudler/luet.git
synced 2025-06-24 14:31:55 +00:00
When we are swapping packages, we do not run the solver to gather things to install, but we trust the given list when calling computeInstall. In this case, the assertion returned by computeInstall is empty, as we force l.Options.NoDeps. This change generates the assertion list while calling computeSwap so it's available later when we call ExecuteFinalizer.
852 lines
27 KiB
Go
852 lines
27 KiB
Go
// 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 (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
. "github.com/logrusorgru/aurora"
|
|
"github.com/mudler/luet/pkg/bus"
|
|
compiler "github.com/mudler/luet/pkg/compiler"
|
|
"github.com/mudler/luet/pkg/config"
|
|
"github.com/mudler/luet/pkg/helpers"
|
|
. "github.com/mudler/luet/pkg/logger"
|
|
pkg "github.com/mudler/luet/pkg/package"
|
|
"github.com/mudler/luet/pkg/solver"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type LuetInstallerOptions struct {
|
|
SolverOptions config.LuetSolverOptions
|
|
Concurrency int
|
|
NoDeps bool
|
|
OnlyDeps bool
|
|
Force bool
|
|
PreserveSystemEssentialData bool
|
|
FullUninstall, FullCleanUninstall bool
|
|
CheckConflicts bool
|
|
SolverUpgrade, RemoveUnavailableOnUpgrade, UpgradeNewRevisions bool
|
|
Ask bool
|
|
}
|
|
|
|
type LuetInstaller struct {
|
|
PackageRepositories Repositories
|
|
|
|
Options LuetInstallerOptions
|
|
}
|
|
|
|
type ArtifactMatch struct {
|
|
Package pkg.Package
|
|
Artifact compiler.Artifact
|
|
Repository Repository
|
|
}
|
|
|
|
func NewLuetInstaller(opts LuetInstallerOptions) Installer {
|
|
return &LuetInstaller{Options: opts}
|
|
}
|
|
|
|
// computeUpgrade returns the packages to be uninstalled and installed in a system to perform an upgrade
|
|
// based on the system repositories
|
|
func (l *LuetInstaller) computeUpgrade(syncedRepos Repositories, s *System) (pkg.Packages, pkg.Packages, error) {
|
|
toInstall := pkg.Packages{}
|
|
var uninstall pkg.Packages
|
|
var err error
|
|
// First match packages against repositories by priority
|
|
allRepos := pkg.NewInMemoryDatabase(false)
|
|
syncedRepos.SyncDatabase(allRepos)
|
|
// compute a "big" world
|
|
solv := solver.NewResolver(solver.Options{Type: l.Options.SolverOptions.Implementation, Concurrency: l.Options.Concurrency}, s.Database, allRepos, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver())
|
|
var solution solver.PackagesAssertions
|
|
|
|
if l.Options.SolverUpgrade {
|
|
uninstall, solution, err = solv.UpgradeUniverse(l.Options.RemoveUnavailableOnUpgrade)
|
|
if err != nil {
|
|
return uninstall, toInstall, errors.Wrap(err, "Failed solving solution for upgrade")
|
|
}
|
|
} else {
|
|
uninstall, solution, err = solv.Upgrade(l.Options.FullUninstall, true)
|
|
if err != nil {
|
|
return uninstall, toInstall, errors.Wrap(err, "Failed solving solution for upgrade")
|
|
}
|
|
}
|
|
|
|
for _, assertion := range solution {
|
|
// Be sure to filter from solutions packages already installed in the system
|
|
if _, err := s.Database.FindPackage(assertion.Package); err != nil && assertion.Value {
|
|
toInstall = append(toInstall, assertion.Package)
|
|
}
|
|
}
|
|
|
|
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() {
|
|
if artefact.GetCompileSpec().GetPackage() == nil {
|
|
return uninstall, toInstall, errors.New("Package in compilespec empty")
|
|
|
|
}
|
|
if artefact.GetCompileSpec().GetPackage().Matches(p) && artefact.GetCompileSpec().GetPackage().GetBuildTimestamp() != p.GetBuildTimestamp() {
|
|
toInstall = append(toInstall, matches[0].Package).Unique()
|
|
uninstall = append(uninstall, p).Unique()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return uninstall, toInstall, nil
|
|
}
|
|
|
|
func packsToList(p pkg.Packages) string {
|
|
var packs []string
|
|
|
|
for _, pp := range p {
|
|
packs = append(packs, pp.HumanReadableString())
|
|
}
|
|
return strings.Join(packs, " ")
|
|
}
|
|
|
|
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()))
|
|
}
|
|
return strings.Join(packs, " ")
|
|
}
|
|
|
|
// Upgrade upgrades a System based on the Installer options. Returns error in case of failure
|
|
func (l *LuetInstaller) Upgrade(s *System) error {
|
|
|
|
syncedRepos, err := l.SyncRepositories(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
Info(":thinking: Computing upgrade, please hang tight... :zzz:")
|
|
if l.Options.UpgradeNewRevisions {
|
|
Info(":memo: note: will consider new build revisions while upgrading")
|
|
}
|
|
|
|
Spinner(32)
|
|
uninstall, toInstall, err := l.computeUpgrade(syncedRepos, 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
|
|
}
|
|
|
|
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
|
|
return l.swap(syncedRepos, uninstall, toInstall, s, true)
|
|
} else {
|
|
return errors.New("Aborted by user")
|
|
}
|
|
}
|
|
|
|
Spinner(32)
|
|
defer SpinnerStop()
|
|
return l.swap(syncedRepos, uninstall, toInstall, s, true)
|
|
}
|
|
|
|
func (l *LuetInstaller) SyncRepositories(inMemory bool) (Repositories, error) {
|
|
Spinner(32)
|
|
defer SpinnerStop()
|
|
syncedRepos := Repositories{}
|
|
for _, r := range l.PackageRepositories {
|
|
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)
|
|
|
|
if !inMemory {
|
|
l.PackageRepositories = syncedRepos
|
|
}
|
|
|
|
return syncedRepos, nil
|
|
}
|
|
|
|
func (l *LuetInstaller) Swap(toRemove pkg.Packages, toInstall pkg.Packages, s *System) error {
|
|
syncedRepos, err := l.SyncRepositories(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
return l.swap(syncedRepos, toRemoveFinal, toInstall, s, false)
|
|
}
|
|
|
|
func (l *LuetInstaller) computeSwap(syncedRepos Repositories, toRemove pkg.Packages, toInstall pkg.Packages, s *System) (map[string]ArtifactMatch, pkg.Packages, solver.PackagesAssertions, pkg.PackageDatabase, error) {
|
|
|
|
allRepos := pkg.NewInMemoryDatabase(false)
|
|
syncedRepos.SyncDatabase(allRepos)
|
|
|
|
toInstall = syncedRepos.ResolveSelectors(toInstall)
|
|
|
|
// First check what would have been done
|
|
installedtmp, err := s.Database.Copy()
|
|
if err != nil {
|
|
return nil, nil, nil, nil, errors.Wrap(err, "Failed create temporary in-memory db")
|
|
}
|
|
|
|
systemAfterChanges := &System{Database: installedtmp}
|
|
|
|
packs, err := l.computeUninstall(systemAfterChanges, toRemove...)
|
|
if err != nil && !l.Options.Force {
|
|
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")
|
|
}
|
|
}
|
|
|
|
match, packages, assertions, allRepos, err := l.computeInstall(syncedRepos, toInstall, systemAfterChanges)
|
|
for _, p := range toInstall {
|
|
assertions = append(assertions, solver.PackageAssert{Package: p.(*pkg.DefaultPackage), Value: true})
|
|
}
|
|
return match, packages, assertions, allRepos, err
|
|
}
|
|
|
|
func (l *LuetInstaller) swap(syncedRepos Repositories, toRemove pkg.Packages, toInstall pkg.Packages, s *System, forceNodeps bool) error {
|
|
forced := l.Options.Force
|
|
nodeps := l.Options.NoDeps
|
|
|
|
// 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.
|
|
l.Options.Force = true
|
|
if forceNodeps {
|
|
l.Options.NoDeps = true
|
|
}
|
|
|
|
match, packages, assertions, allRepos, err := l.computeSwap(syncedRepos, toRemove, toInstall, s)
|
|
if err != nil {
|
|
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")
|
|
}
|
|
}
|
|
|
|
// First match packages against repositories by priority
|
|
if err := l.download(syncedRepos, match); err != nil {
|
|
return errors.Wrap(err, "Pre-downloading packages")
|
|
}
|
|
|
|
err = l.Uninstall(s, toRemove...)
|
|
if err != nil && !l.Options.Force {
|
|
Error("Failed uninstall for ", packsToList(toRemove))
|
|
return errors.Wrap(err, "uninstalling "+packsToList(toRemove))
|
|
}
|
|
|
|
l.Options.Force = forced
|
|
l.Options.NoDeps = nodeps
|
|
return l.install(syncedRepos, match, packages, assertions, allRepos, s)
|
|
}
|
|
|
|
func (l *LuetInstaller) Install(cp pkg.Packages, s *System) error {
|
|
syncedRepos, err := l.SyncRepositories(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
match, packages, assertions, allRepos, err := l.computeInstall(syncedRepos, cp, s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check if we have to process something, or return to the user an error
|
|
if len(match) == 0 {
|
|
Info("No packages to install")
|
|
return nil
|
|
}
|
|
// Resolvers might decide to remove some packages from being installed
|
|
if !l.Options.SolverOptions.ResolverIsSet() {
|
|
for _, p := range cp {
|
|
found := false
|
|
vers, _ := s.Database.FindPackageVersions(p) // If was installed, it is found, as it was filtered
|
|
if len(vers) >= 1 {
|
|
found = true
|
|
continue
|
|
}
|
|
|
|
for _, m := range match {
|
|
if m.Package.GetName() == p.GetName() {
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return fmt.Errorf("Package '%s' not found", p.HumanReadableString())
|
|
}
|
|
}
|
|
}
|
|
Info("Packages that are going to be installed in the system: \n ", Green(matchesToList(match)).BgBlack().String())
|
|
|
|
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
|
|
return l.install(syncedRepos, match, packages, assertions, allRepos, s)
|
|
} else {
|
|
return errors.New("Aborted by user")
|
|
}
|
|
}
|
|
return l.install(syncedRepos, match, packages, assertions, allRepos, s)
|
|
}
|
|
|
|
func (l *LuetInstaller) download(syncedRepos Repositories, toDownload map[string]ArtifactMatch) error {
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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 {
|
|
syncedRepos, err := l.SyncRepositories(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var toMerge []ArtifactMatch = []ArtifactMatch{}
|
|
|
|
for _, repo := range syncedRepos {
|
|
for _, artefact := range repo.GetIndex() {
|
|
Debug("Checking if",
|
|
artefact.GetCompileSpec().GetPackage().HumanReadableString(),
|
|
"from", repo.GetName(), "is installed")
|
|
FILES:
|
|
for _, f := range artefact.GetFiles() {
|
|
if helpers.Exists(filepath.Join(s.Target, f)) {
|
|
p, err := repo.GetTree().GetDatabase().FindPackage(artefact.GetCompileSpec().GetPackage())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
Info(":mag: Found package:", p.HumanReadableString())
|
|
toMerge = append(toMerge, ArtifactMatch{Artifact: artefact, Package: p})
|
|
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")
|
|
}
|
|
s.Database.SetPackageFiles(&pkg.PackageFile{PackageFingerprint: pack.GetFingerPrint(), Files: match.Artifact.GetFiles()})
|
|
Info(":zap:Reclaimed package:", pack.HumanReadableString())
|
|
}
|
|
Info("Done!")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *LuetInstaller) computeInstall(syncedRepos Repositories, cp pkg.Packages, s *System) (map[string]ArtifactMatch, pkg.Packages, solver.PackagesAssertions, pkg.PackageDatabase, error) {
|
|
var p pkg.Packages
|
|
toInstall := map[string]ArtifactMatch{}
|
|
allRepos := pkg.NewInMemoryDatabase(false)
|
|
var solution solver.PackagesAssertions
|
|
|
|
// Check if the package is installed first
|
|
for _, pi := range cp {
|
|
vers, _ := s.Database.FindPackageVersions(pi)
|
|
|
|
if len(vers) >= 1 {
|
|
// Warning("Filtering out package " + pi.HumanReadableString() + ", it has other versions already installed. Uninstall one of them first ")
|
|
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 {
|
|
return toInstall, p, solution, allRepos, nil
|
|
}
|
|
// First get metas from all repos (and decodes trees)
|
|
|
|
// First match packages against repositories by priority
|
|
// matches := syncedRepos.PackageMatches(p)
|
|
|
|
// compute a "big" world
|
|
syncedRepos.SyncDatabase(allRepos)
|
|
p = syncedRepos.ResolveSelectors(p)
|
|
var packagesToInstall pkg.Packages
|
|
var err error
|
|
|
|
if !l.Options.NoDeps {
|
|
solv := solver.NewResolver(solver.Options{Type: l.Options.SolverOptions.Implementation, Concurrency: l.Options.Concurrency}, s.Database, allRepos, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver())
|
|
solution, err = solv.Install(p)
|
|
/// TODO: PackageAssertions needs to be a map[fingerprint]pack so lookup is in O(1)
|
|
if err != nil && !l.Options.Force {
|
|
return toInstall, p, solution, allRepos, errors.Wrap(err, "Failed solving solution for package")
|
|
}
|
|
// Gathers things to install
|
|
for _, assertion := range solution {
|
|
if assertion.Value {
|
|
if _, err := s.Database.FindPackage(assertion.Package); err == nil {
|
|
// skip matching if it is installed already
|
|
continue
|
|
}
|
|
packagesToInstall = append(packagesToInstall, assertion.Package)
|
|
}
|
|
}
|
|
} else if !l.Options.OnlyDeps {
|
|
for _, currentPack := range p {
|
|
if _, err := s.Database.FindPackage(currentPack); err == nil {
|
|
// skip matching if it is installed already
|
|
continue
|
|
}
|
|
packagesToInstall = append(packagesToInstall, currentPack)
|
|
}
|
|
}
|
|
// Gathers things to install
|
|
for _, currentPack := range packagesToInstall {
|
|
// Check if package is already installed.
|
|
|
|
matches := syncedRepos.PackageMatches(pkg.Packages{currentPack})
|
|
if len(matches) == 0 {
|
|
return toInstall, p, solution, allRepos, errors.New("Failed matching solutions against repository for " + currentPack.HumanReadableString() + " where are definitions coming from?!")
|
|
}
|
|
A:
|
|
for _, artefact := range matches[0].Repo.GetIndex() {
|
|
if artefact.GetCompileSpec().GetPackage() == nil {
|
|
return toInstall, p, solution, allRepos, errors.New("Package in compilespec empty")
|
|
}
|
|
if matches[0].Package.Matches(artefact.GetCompileSpec().GetPackage()) {
|
|
currentPack.SetBuildTimestamp(artefact.GetCompileSpec().GetPackage().GetBuildTimestamp())
|
|
// Filter out already installed
|
|
if _, err := s.Database.FindPackage(currentPack); err != nil {
|
|
toInstall[currentPack.GetFingerPrint()] = ArtifactMatch{Package: currentPack, Artifact: artefact, Repository: matches[0].Repo}
|
|
}
|
|
break A
|
|
}
|
|
}
|
|
}
|
|
return toInstall, p, solution, allRepos, nil
|
|
}
|
|
|
|
func (l *LuetInstaller) install(syncedRepos Repositories, toInstall map[string]ArtifactMatch, p pkg.Packages, solution solver.PackagesAssertions, allRepos pkg.PackageDatabase, s *System) error {
|
|
// Install packages into rootfs in parallel.
|
|
if err := l.download(syncedRepos, toInstall); err != nil {
|
|
return errors.Wrap(err, "Downloading packages")
|
|
}
|
|
|
|
all := make(chan ArtifactMatch)
|
|
|
|
wg := new(sync.WaitGroup)
|
|
|
|
// Do the real install
|
|
for i := 0; i < l.Options.Concurrency; i++ {
|
|
wg.Add(1)
|
|
go l.installerWorker(i, wg, all, s)
|
|
}
|
|
|
|
for _, c := range toInstall {
|
|
all <- c
|
|
}
|
|
close(all)
|
|
wg.Wait()
|
|
|
|
for _, c := range toInstall {
|
|
// Annotate to the system that the package was installed
|
|
_, err := s.Database.CreatePackage(c.Package)
|
|
if err != nil && !l.Options.Force {
|
|
return errors.Wrap(err, "Failed creating package")
|
|
}
|
|
bus.Manager.Publish(bus.EventPackageInstall, c)
|
|
}
|
|
var toFinalize []pkg.Package
|
|
if !l.Options.NoDeps {
|
|
// TODO: Lower those errors as warning
|
|
for _, w := range p {
|
|
// Finalizers needs to run in order and in sequence.
|
|
ordered, err := solution.Order(allRepos, w.GetFingerPrint())
|
|
if err != nil {
|
|
return errors.Wrap(err, "While order a solution for "+w.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 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 errors.Wrap(err, "Error getting package "+c.Package.HumanReadableString())
|
|
}
|
|
toFinalize = append(toFinalize, treePackage)
|
|
}
|
|
}
|
|
|
|
return s.ExecuteFinalizers(toFinalize)
|
|
}
|
|
|
|
func (l *LuetInstaller) downloadPackage(a ArtifactMatch) (compiler.Artifact, error) {
|
|
|
|
artifact, err := a.Repository.Client().DownloadArtifact(a.Artifact)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Error on download artifact")
|
|
}
|
|
|
|
err = artifact.Verify()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Artifact integrity check failure")
|
|
}
|
|
return artifact, nil
|
|
}
|
|
|
|
func (l *LuetInstaller) installPackage(a ArtifactMatch, s *System) error {
|
|
|
|
artifact, err := l.downloadPackage(a)
|
|
if err != nil && !l.Options.Force {
|
|
return errors.Wrap(err, "Failed downloading package")
|
|
}
|
|
|
|
files, err := artifact.FileList()
|
|
if err != nil && !l.Options.Force {
|
|
return errors.Wrap(err, "Could not open package archive")
|
|
}
|
|
|
|
err = artifact.Unpack(s.Target, true)
|
|
if err != nil && !l.Options.Force {
|
|
return errors.Wrap(err, "Error met while unpacking rootfs")
|
|
}
|
|
|
|
// First create client and download
|
|
// Then unpack to system
|
|
return s.Database.SetPackageFiles(&pkg.PackageFile{PackageFingerprint: a.Package.GetFingerPrint(), Files: files})
|
|
}
|
|
|
|
func (l *LuetInstaller) downloadWorker(i int, wg *sync.WaitGroup, c <-chan ArtifactMatch) 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.downloadPackage(p)
|
|
if err != nil {
|
|
Fatal("Failed downloading package "+p.Package.GetName(), err.Error())
|
|
return errors.Wrap(err, "Failed downloading package "+p.Package.GetName())
|
|
} else {
|
|
Info(":package: Package ", p.Package.HumanReadableString(), "downloaded")
|
|
}
|
|
}
|
|
|
|
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 {
|
|
Info(":package: Package ", p.Package.HumanReadableString(), "installed")
|
|
} else if err != nil && l.Options.Force {
|
|
Info(":package: Package ", p.Package.HumanReadableString(), "installed with failures (forced install)")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *LuetInstaller) uninstall(p pkg.Package, s *System) error {
|
|
var cp *config.ConfigProtect
|
|
annotationDir := ""
|
|
|
|
files, err := s.Database.GetPackageFiles(p)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed getting installed files")
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
toRemove, notPresent := helpers.OrderFiles(s.Target, files)
|
|
|
|
// Remove from target
|
|
for _, f := range toRemove {
|
|
target := filepath.Join(s.Target, f)
|
|
|
|
if !config.LuetCfg.ConfigProtectSkip && cp.Protected(f) {
|
|
Debug("Preserving protected file:", f)
|
|
continue
|
|
}
|
|
|
|
Debug("Removing", target)
|
|
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
|
|
}
|
|
|
|
fi, err := os.Lstat(target)
|
|
if err != nil {
|
|
Warning("File not found (it was before?) ", err.Error())
|
|
continue
|
|
}
|
|
switch mode := fi.Mode(); {
|
|
case mode.IsDir():
|
|
files, err := ioutil.ReadDir(target)
|
|
if err != nil {
|
|
Warning("Failed reading folder", target, err.Error())
|
|
}
|
|
if len(files) != 0 {
|
|
Debug("Preserving not-empty folder", target)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if err = os.Remove(target); err != nil {
|
|
Warning("Failed removing file (maybe not present in the system target anymore ?)", target, err.Error())
|
|
}
|
|
}
|
|
|
|
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())
|
|
}
|
|
}
|
|
|
|
err = s.Database.RemovePackageFiles(p)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed removing package files from database")
|
|
}
|
|
err = s.Database.RemovePackage(p)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed removing package from database")
|
|
}
|
|
|
|
bus.Manager.Publish(bus.EventPackageUnInstall, p)
|
|
|
|
Info(":recycle: ", p.GetFingerPrint(), "Removed :heavy_check_mark:")
|
|
return nil
|
|
}
|
|
|
|
func (l *LuetInstaller) computeUninstall(s *System, packs ...pkg.Package) (pkg.Packages, error) {
|
|
|
|
var toUninstall pkg.Packages
|
|
// compute uninstall from all world - remove packages in parallel - run uninstall finalizer (in order) TODO - mark the uninstallation in db
|
|
// Get installed definition
|
|
checkConflicts := l.Options.CheckConflicts
|
|
full := l.Options.FullUninstall
|
|
if l.Options.Force == true { // IF forced, we want to remove the package and all its requires
|
|
checkConflicts = false
|
|
full = false
|
|
}
|
|
|
|
// Create a temporary DB with the installed packages
|
|
// so the solver is much faster finding the deptree
|
|
// 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")
|
|
}
|
|
|
|
if !l.Options.NoDeps {
|
|
solv := solver.NewResolver(solver.Options{Type: l.Options.SolverOptions.Implementation, Concurrency: l.Options.Concurrency}, installedtmp, installedtmp, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver())
|
|
var solution pkg.Packages
|
|
var err error
|
|
if l.Options.FullCleanUninstall {
|
|
solution, err = solv.UninstallUniverse(packs)
|
|
if err != nil {
|
|
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")
|
|
}
|
|
} else {
|
|
solution, err = solv.Uninstall(checkConflicts, full, packs...)
|
|
if err != nil && !l.Options.Force {
|
|
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")
|
|
}
|
|
}
|
|
|
|
for _, p := range solution {
|
|
toUninstall = append(toUninstall, p)
|
|
}
|
|
} else {
|
|
toUninstall = append(toUninstall, packs...)
|
|
}
|
|
|
|
return toUninstall, nil
|
|
}
|
|
func (l *LuetInstaller) Uninstall(s *System, packs ...pkg.Package) error {
|
|
|
|
for _, p := range packs {
|
|
if packs, _ := s.Database.FindPackages(p); len(packs) == 0 {
|
|
return errors.New("Package not found in the system")
|
|
}
|
|
|
|
}
|
|
|
|
Spinner(32)
|
|
toUninstall, err := l.computeUninstall(s, packs...)
|
|
if err != nil {
|
|
return errors.Wrap(err, "while computing uninstall")
|
|
}
|
|
SpinnerStop()
|
|
|
|
uninstall := func() error {
|
|
for _, p := range toUninstall {
|
|
err := l.uninstall(p, s)
|
|
if err != nil && !l.Options.Force {
|
|
return errors.Wrap(err, "Uninstall failed")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if len(toUninstall) == 0 {
|
|
Info("Nothing to do")
|
|
return nil
|
|
}
|
|
|
|
if l.Options.Ask {
|
|
Info(":recycle: Packages that are going to be removed from the system:\n ", Yellow(packsToList(toUninstall)).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
|
|
return uninstall()
|
|
} else {
|
|
return errors.New("Aborted by user")
|
|
}
|
|
}
|
|
return uninstall()
|
|
}
|
|
|
|
func (l *LuetInstaller) Repositories(r []Repository) { l.PackageRepositories = r }
|