mirror of
https://github.com/mudler/luet.git
synced 2025-09-04 00:34:41 +00:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
3c5e144b8d | ||
|
44aa69928a | ||
|
c7652c8a70 | ||
|
fab2d21ac1 | ||
|
487239334f | ||
|
69477a0c36 | ||
|
67362a3bc1 | ||
|
302d18e749 | ||
|
9047e13d21 | ||
|
0249c0fa4a | ||
|
fc40c770ab | ||
|
4357ee45e9 | ||
|
6ce9a86245 | ||
|
853a1995b4 | ||
|
039b77fdfd | ||
|
050d9b3095 | ||
|
4985ff7b5a | ||
|
6f138811dd | ||
|
f02c54a274 | ||
|
d6182cba3b | ||
|
bc5c0fa0cf | ||
|
dc64dbff75 | ||
|
a94e430a3b | ||
|
93f8f0dd0f | ||
|
7bdcc72dd3 | ||
|
e47c07d438 | ||
|
6fc5d1a97b | ||
|
f4a5a97cff | ||
|
8c972403a3 | ||
|
82a17a1a22 | ||
|
17238d187a | ||
|
ad011e937f | ||
|
25c796c430 | ||
|
df5499393f | ||
|
026ddf6fc9 |
@@ -37,7 +37,7 @@ var cfgFile string
|
|||||||
var Verbose bool
|
var Verbose bool
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LuetCLIVersion = "0.6.4"
|
LuetCLIVersion = "0.7"
|
||||||
LuetEnvPrefix = "LUET"
|
LuetEnvPrefix = "LUET"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ func init() {
|
|||||||
cobra.OnInitialize(initConfig)
|
cobra.OnInitialize(initConfig)
|
||||||
pflags := RootCmd.PersistentFlags()
|
pflags := RootCmd.PersistentFlags()
|
||||||
pflags.StringVar(&cfgFile, "config", "", "config file (default is $HOME/.luet.yaml)")
|
pflags.StringVar(&cfgFile, "config", "", "config file (default is $HOME/.luet.yaml)")
|
||||||
pflags.BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
|
pflags.BoolP("debug", "d", false, "verbose output")
|
||||||
pflags.Bool("fatal", false, "Enables Warnings to exit")
|
pflags.Bool("fatal", false, "Enables Warnings to exit")
|
||||||
|
|
||||||
u, err := user.Current()
|
u, err := user.Current()
|
||||||
@@ -137,7 +137,7 @@ func init() {
|
|||||||
pflags.Int("concurrency", runtime.NumCPU(), "Concurrency")
|
pflags.Int("concurrency", runtime.NumCPU(), "Concurrency")
|
||||||
|
|
||||||
config.LuetCfg.Viper.BindPFlag("general.same_owner", pflags.Lookup("same-owner"))
|
config.LuetCfg.Viper.BindPFlag("general.same_owner", pflags.Lookup("same-owner"))
|
||||||
config.LuetCfg.Viper.BindPFlag("general.debug", pflags.Lookup("verbose"))
|
config.LuetCfg.Viper.BindPFlag("general.debug", pflags.Lookup("debug"))
|
||||||
config.LuetCfg.Viper.BindPFlag("general.concurrency", pflags.Lookup("concurrency"))
|
config.LuetCfg.Viper.BindPFlag("general.concurrency", pflags.Lookup("concurrency"))
|
||||||
config.LuetCfg.Viper.BindPFlag("general.fatal_warnings", pflags.Lookup("fatal"))
|
config.LuetCfg.Viper.BindPFlag("general.fatal_warnings", pflags.Lookup("fatal"))
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,6 @@ func initConfig() {
|
|||||||
viper.SetConfigType("yaml")
|
viper.SetConfigType("yaml")
|
||||||
viper.SetConfigName(".luet") // name of config file (without extension)
|
viper.SetConfigName(".luet") // name of config file (without extension)
|
||||||
if cfgFile != "" { // enable ability to specify config file via flag
|
if cfgFile != "" { // enable ability to specify config file via flag
|
||||||
Info(">>> cfgFile: ", cfgFile)
|
|
||||||
viper.SetConfigFile(cfgFile)
|
viper.SetConfigFile(cfgFile)
|
||||||
} else {
|
} else {
|
||||||
viper.AddConfigPath(dir)
|
viper.AddConfigPath(dir)
|
||||||
|
@@ -17,7 +17,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
|
|
||||||
. "github.com/mudler/luet/pkg/config"
|
. "github.com/mudler/luet/pkg/config"
|
||||||
installer "github.com/mudler/luet/pkg/installer"
|
installer "github.com/mudler/luet/pkg/installer"
|
||||||
@@ -51,6 +50,8 @@ var searchCmd = &cobra.Command{
|
|||||||
discount := LuetCfg.Viper.GetFloat64("solver.discount")
|
discount := LuetCfg.Viper.GetFloat64("solver.discount")
|
||||||
rate := LuetCfg.Viper.GetFloat64("solver.rate")
|
rate := LuetCfg.Viper.GetFloat64("solver.rate")
|
||||||
attempts := LuetCfg.Viper.GetInt("solver.max_attempts")
|
attempts := LuetCfg.Viper.GetInt("solver.max_attempts")
|
||||||
|
searchWithLabel, _ := cmd.Flags().GetBool("by-label")
|
||||||
|
searchWithLabelMatch, _ := cmd.Flags().GetBool("by-label-regex")
|
||||||
|
|
||||||
LuetCfg.GetSolverOptions().Type = stype
|
LuetCfg.GetSolverOptions().Type = stype
|
||||||
LuetCfg.GetSolverOptions().LearnRate = float32(rate)
|
LuetCfg.GetSolverOptions().LearnRate = float32(rate)
|
||||||
@@ -70,8 +71,12 @@ var searchCmd = &cobra.Command{
|
|||||||
repos = append(repos, r)
|
repos = append(repos, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{Concurrency: LuetCfg.GetGeneral().Concurrency, SolverOptions: *LuetCfg.GetSolverOptions()})
|
inst := installer.NewLuetInstaller(
|
||||||
|
installer.LuetInstallerOptions{
|
||||||
|
Concurrency: LuetCfg.GetGeneral().Concurrency,
|
||||||
|
SolverOptions: *LuetCfg.GetSolverOptions(),
|
||||||
|
},
|
||||||
|
)
|
||||||
inst.Repositories(repos)
|
inst.Repositories(repos)
|
||||||
synced, err := inst.SyncRepositories(false)
|
synced, err := inst.SyncRepositories(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -80,7 +85,14 @@ var searchCmd = &cobra.Command{
|
|||||||
|
|
||||||
Info("--- Search results: ---")
|
Info("--- Search results: ---")
|
||||||
|
|
||||||
matches := synced.Search(args[0])
|
matches := []installer.PackageMatch{}
|
||||||
|
if searchWithLabel {
|
||||||
|
matches = synced.SearchLabel(args[0])
|
||||||
|
} else if searchWithLabelMatch {
|
||||||
|
matches = synced.SearchLabelMatch(args[0])
|
||||||
|
} else {
|
||||||
|
matches = synced.Search(args[0])
|
||||||
|
}
|
||||||
for _, m := range matches {
|
for _, m := range matches {
|
||||||
Info(":package:", m.Package.GetCategory(), m.Package.GetName(),
|
Info(":package:", m.Package.GetCategory(), m.Package.GetName(),
|
||||||
m.Package.GetVersion(), "repository:", m.Repo.GetName())
|
m.Package.GetVersion(), "repository:", m.Repo.GetName())
|
||||||
@@ -94,14 +106,25 @@ var searchCmd = &cobra.Command{
|
|||||||
systemDB = pkg.NewInMemoryDatabase(true)
|
systemDB = pkg.NewInMemoryDatabase(true)
|
||||||
}
|
}
|
||||||
system := &installer.System{Database: systemDB, Target: LuetCfg.GetSystem().Rootfs}
|
system := &installer.System{Database: systemDB, Target: LuetCfg.GetSystem().Rootfs}
|
||||||
var term = regexp.MustCompile(args[0])
|
|
||||||
|
|
||||||
for _, k := range system.Database.GetPackages() {
|
var err error
|
||||||
pack, err := system.Database.GetPackage(k)
|
iMatches := []pkg.Package{}
|
||||||
if err == nil && term.MatchString(pack.GetName()) {
|
if searchWithLabel {
|
||||||
|
iMatches, err = system.Database.FindPackageLabel(args[0])
|
||||||
|
} else if searchWithLabelMatch {
|
||||||
|
iMatches, err = system.Database.FindPackageLabelMatch(args[0])
|
||||||
|
} else {
|
||||||
|
iMatches, err = system.Database.FindPackageMatch(args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
Fatal("Error: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pack := range iMatches {
|
||||||
Info(":package:", pack.GetCategory(), pack.GetName(), pack.GetVersion())
|
Info(":package:", pack.GetCategory(), pack.GetName(), pack.GetVersion())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -119,5 +142,7 @@ func init() {
|
|||||||
searchCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
|
searchCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
|
||||||
searchCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
|
searchCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
|
||||||
searchCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts")
|
searchCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts")
|
||||||
|
searchCmd.Flags().Bool("by-label", false, "Search packages through label")
|
||||||
|
searchCmd.Flags().Bool("by-label-regex", false, "Search packages through label regex")
|
||||||
RootCmd.AddCommand(searchCmd)
|
RootCmd.AddCommand(searchCmd)
|
||||||
}
|
}
|
||||||
|
38
cmd/tree.go
Normal file
38
cmd/tree.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
|
||||||
|
// Daniele Rondina <geaaru@sabayonlinux.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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/mudler/luet/cmd/tree"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var treeGroupCmd = &cobra.Command{
|
||||||
|
Use: "tree [command] [OPTIONS]",
|
||||||
|
Short: "Tree operations",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCmd.AddCommand(treeGroupCmd)
|
||||||
|
|
||||||
|
treeGroupCmd.AddCommand(
|
||||||
|
NewTreePkglistCommand(),
|
||||||
|
NewTreeValidateCommand(),
|
||||||
|
NewTreeBumpCommand(),
|
||||||
|
)
|
||||||
|
}
|
67
cmd/tree/bump.go
Normal file
67
cmd/tree/bump.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
|
||||||
|
// Daniele Rondina <geaaru@sabayonlinux.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 cmd_tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
//"os"
|
||||||
|
//"sort"
|
||||||
|
|
||||||
|
. "github.com/mudler/luet/pkg/logger"
|
||||||
|
tree "github.com/mudler/luet/pkg/tree"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTreeBumpCommand() *cobra.Command {
|
||||||
|
|
||||||
|
var ans = &cobra.Command{
|
||||||
|
Use: "bump [OPTIONS]",
|
||||||
|
Short: "Bump a new package build version.",
|
||||||
|
Args: cobra.OnlyValidArgs,
|
||||||
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
df, _ := cmd.Flags().GetString("definition-file")
|
||||||
|
if df == "" {
|
||||||
|
Fatal("Mandatory definition.yaml path missing.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
spec, _ := cmd.Flags().GetString("definition-file")
|
||||||
|
pack, err := tree.ReadDefinitionFile(spec)
|
||||||
|
if err != nil {
|
||||||
|
Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve version build section with Gentoo parser
|
||||||
|
err = pack.BumpBuildVersion()
|
||||||
|
if err != nil {
|
||||||
|
Fatal("Error on increment build version: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tree.WriteDefinitionFile(&pack, spec)
|
||||||
|
if err != nil {
|
||||||
|
Fatal("Error on write definition file: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Bumped package %s/%s-%s.\n", pack.Category, pack.Name, pack.Version)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ans.Flags().StringP("definition-file", "f", "", "Path of the definition to bump.")
|
||||||
|
|
||||||
|
return ans
|
||||||
|
}
|
140
cmd/tree/pkglist.go
Normal file
140
cmd/tree/pkglist.go
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
|
||||||
|
// Daniele Rondina <geaaru@sabayonlinux.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 cmd_tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
//. "github.com/mudler/luet/pkg/config"
|
||||||
|
helpers "github.com/mudler/luet/pkg/helpers"
|
||||||
|
. "github.com/mudler/luet/pkg/logger"
|
||||||
|
pkg "github.com/mudler/luet/pkg/package"
|
||||||
|
tree "github.com/mudler/luet/pkg/tree"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func pkgDetail(pkg pkg.Package) string {
|
||||||
|
ans := fmt.Sprintf(`
|
||||||
|
@@ Package: %s/%s-%s
|
||||||
|
Description: %s
|
||||||
|
License: %s`,
|
||||||
|
pkg.GetCategory(), pkg.GetName(), pkg.GetVersion(),
|
||||||
|
pkg.GetDescription(), pkg.GetLicense())
|
||||||
|
|
||||||
|
for idx, u := range pkg.GetURI() {
|
||||||
|
if idx == 0 {
|
||||||
|
ans += fmt.Sprintf(" URLs: %s", u)
|
||||||
|
} else {
|
||||||
|
ans += fmt.Sprintf(" %s", u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTreePkglistCommand() *cobra.Command {
|
||||||
|
var excludes []string
|
||||||
|
var matches []string
|
||||||
|
|
||||||
|
var ans = &cobra.Command{
|
||||||
|
Use: "pkglist [OPTIONS]",
|
||||||
|
Short: "List of the packages found in tree.",
|
||||||
|
Args: cobra.OnlyValidArgs,
|
||||||
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
t, _ := cmd.Flags().GetString("tree")
|
||||||
|
if t == "" {
|
||||||
|
Fatal("Mandatory tree param missing.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
|
treePath, _ := cmd.Flags().GetString("tree")
|
||||||
|
verbose, _ := cmd.Flags().GetBool("verbose")
|
||||||
|
full, _ := cmd.Flags().GetBool("full")
|
||||||
|
reciper := tree.NewInstallerRecipe(pkg.NewInMemoryDatabase(false))
|
||||||
|
err := reciper.Load(treePath)
|
||||||
|
if err != nil {
|
||||||
|
Fatal("Error on load tree ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
regExcludes, err := helpers.CreateRegexArray(excludes)
|
||||||
|
if err != nil {
|
||||||
|
Fatal(err.Error())
|
||||||
|
}
|
||||||
|
regMatches, err := helpers.CreateRegexArray(matches)
|
||||||
|
if err != nil {
|
||||||
|
Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
plist := make([]string, 0)
|
||||||
|
for _, p := range reciper.GetDatabase().World() {
|
||||||
|
pkgstr := ""
|
||||||
|
addPkg := true
|
||||||
|
if full {
|
||||||
|
pkgstr = pkgDetail(p)
|
||||||
|
} else if verbose {
|
||||||
|
pkgstr = fmt.Sprintf("%s/%s-%s", p.GetCategory(), p.GetName(), p.GetVersion())
|
||||||
|
} else {
|
||||||
|
pkgstr = fmt.Sprintf("%s/%s", p.GetCategory(), p.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matches) > 0 {
|
||||||
|
matched := false
|
||||||
|
for _, rgx := range regMatches {
|
||||||
|
if rgx.MatchString(pkgstr) {
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
addPkg = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(excludes) > 0 && addPkg {
|
||||||
|
for _, rgx := range regExcludes {
|
||||||
|
if rgx.MatchString(pkgstr) {
|
||||||
|
addPkg = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if addPkg {
|
||||||
|
plist = append(plist, pkgstr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(plist)
|
||||||
|
for _, p := range plist {
|
||||||
|
fmt.Println(p)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ans.Flags().BoolP("verbose", "v", false, "Add package version")
|
||||||
|
ans.Flags().BoolP("full", "f", false, "Show package detail")
|
||||||
|
ans.Flags().StringP("tree", "t", "", "Path of the tree to use.")
|
||||||
|
ans.Flags().StringSliceVarP(&matches, "matches", "m", []string{},
|
||||||
|
"Include only matched packages from list. (Use string as regex).")
|
||||||
|
ans.Flags().StringSliceVarP(&excludes, "exclude", "e", []string{},
|
||||||
|
"Exclude matched packages from list. (Use string as regex).")
|
||||||
|
|
||||||
|
return ans
|
||||||
|
}
|
212
cmd/tree/validate.go
Normal file
212
cmd/tree/validate.go
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
|
||||||
|
// Daniele Rondina <geaaru@sabayonlinux.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 cmd_tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
//. "github.com/mudler/luet/pkg/config"
|
||||||
|
helpers "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"
|
||||||
|
tree "github.com/mudler/luet/pkg/tree"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTreeValidateCommand() *cobra.Command {
|
||||||
|
var excludes []string
|
||||||
|
var matches []string
|
||||||
|
var treePaths []string
|
||||||
|
|
||||||
|
var ans = &cobra.Command{
|
||||||
|
Use: "validate [OPTIONS]",
|
||||||
|
Short: "Validate a tree or a list of packages",
|
||||||
|
Args: cobra.OnlyValidArgs,
|
||||||
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(treePaths) < 1 {
|
||||||
|
Fatal("Mandatory tree param missing.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
var depSolver solver.PackageSolver
|
||||||
|
var errstr string
|
||||||
|
|
||||||
|
errors := make([]string, 0)
|
||||||
|
|
||||||
|
brokenPkgs := 0
|
||||||
|
brokenDeps := 0
|
||||||
|
withSolver, _ := cmd.Flags().GetBool("with-solver")
|
||||||
|
|
||||||
|
reciper := tree.NewInstallerRecipe(pkg.NewInMemoryDatabase(false))
|
||||||
|
for _, treePath := range treePaths {
|
||||||
|
err := reciper.Load(treePath)
|
||||||
|
if err != nil {
|
||||||
|
Fatal("Error on load tree ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyInstallationDb := pkg.NewInMemoryDatabase(false)
|
||||||
|
if withSolver {
|
||||||
|
depSolver = solver.NewSolver(pkg.NewInMemoryDatabase(false),
|
||||||
|
reciper.GetDatabase(),
|
||||||
|
emptyInstallationDb)
|
||||||
|
}
|
||||||
|
|
||||||
|
regExcludes, err := helpers.CreateRegexArray(excludes)
|
||||||
|
if err != nil {
|
||||||
|
Fatal(err.Error())
|
||||||
|
}
|
||||||
|
regMatches, err := helpers.CreateRegexArray(matches)
|
||||||
|
if err != nil {
|
||||||
|
Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range reciper.GetDatabase().World() {
|
||||||
|
|
||||||
|
pkgstr := fmt.Sprintf("%s/%s-%s", p.GetCategory(), p.GetName(),
|
||||||
|
p.GetVersion())
|
||||||
|
|
||||||
|
validpkg := true
|
||||||
|
|
||||||
|
if len(matches) > 0 {
|
||||||
|
matched := false
|
||||||
|
for _, rgx := range regMatches {
|
||||||
|
if rgx.MatchString(pkgstr) {
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matched {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(excludes) > 0 {
|
||||||
|
excluded := false
|
||||||
|
for _, rgx := range regExcludes {
|
||||||
|
if rgx.MatchString(pkgstr) {
|
||||||
|
excluded = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if excluded {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Info("Checking package "+fmt.Sprintf("%s/%s-%s", p.GetCategory(), p.GetName(), p.GetVersion()), "with", len(p.GetRequires()), "dependencies.")
|
||||||
|
|
||||||
|
for _, r := range p.GetRequires() {
|
||||||
|
|
||||||
|
deps, err := reciper.GetDatabase().FindPackages(
|
||||||
|
&pkg.DefaultPackage{
|
||||||
|
Name: r.GetName(),
|
||||||
|
Category: r.GetCategory(),
|
||||||
|
Version: r.GetVersion(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil || len(deps) < 1 {
|
||||||
|
if err != nil {
|
||||||
|
errstr = err.Error()
|
||||||
|
} else {
|
||||||
|
errstr = "No packages"
|
||||||
|
}
|
||||||
|
Error(fmt.Sprintf("%s/%s-%s: Broken Dep %s/%s-%s - %s",
|
||||||
|
p.GetCategory(), p.GetName(), p.GetVersion(),
|
||||||
|
r.GetCategory(), r.GetName(), r.GetVersion(),
|
||||||
|
errstr,
|
||||||
|
))
|
||||||
|
|
||||||
|
errors = append(errors,
|
||||||
|
fmt.Sprintf("%s/%s-%s: Broken Dep %s/%s-%s - %s",
|
||||||
|
p.GetCategory(), p.GetName(), p.GetVersion(),
|
||||||
|
r.GetCategory(), r.GetName(), r.GetVersion(),
|
||||||
|
errstr))
|
||||||
|
|
||||||
|
brokenDeps++
|
||||||
|
|
||||||
|
validpkg = false
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Debug("Find packages for dep",
|
||||||
|
fmt.Sprintf("%s/%s-%s", r.GetCategory(), r.GetName(), r.GetVersion()))
|
||||||
|
|
||||||
|
if withSolver {
|
||||||
|
Spinner(32)
|
||||||
|
_, err := depSolver.Install([]pkg.Package{r})
|
||||||
|
SpinnerStop()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
Error(fmt.Sprintf("%s/%s-%s: solver broken for dep %s/%s-%s - %s",
|
||||||
|
p.GetCategory(), p.GetName(), p.GetVersion(),
|
||||||
|
r.GetCategory(), r.GetName(), r.GetVersion(),
|
||||||
|
err.Error(),
|
||||||
|
))
|
||||||
|
|
||||||
|
errors = append(errors,
|
||||||
|
fmt.Sprintf("%s/%s-%s: solver broken for Dep %s/%s-%s - %s",
|
||||||
|
p.GetCategory(), p.GetName(), p.GetVersion(),
|
||||||
|
r.GetCategory(), r.GetName(), r.GetVersion(),
|
||||||
|
err.Error()))
|
||||||
|
|
||||||
|
brokenDeps++
|
||||||
|
validpkg = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validpkg {
|
||||||
|
brokenPkgs++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(errors)
|
||||||
|
for _, e := range errors {
|
||||||
|
fmt.Println(e)
|
||||||
|
}
|
||||||
|
fmt.Println("Broken packages:", brokenPkgs, "(", brokenDeps, "deps ).")
|
||||||
|
|
||||||
|
if brokenPkgs > 0 {
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ans.Flags().BoolP("with-solver", "s", false,
|
||||||
|
"Enable check of requires also with solver.")
|
||||||
|
ans.Flags().StringSliceVarP(&treePaths, "tree", "t", []string{},
|
||||||
|
"Path of the tree to use.")
|
||||||
|
ans.Flags().StringSliceVarP(&excludes, "exclude", "e", []string{},
|
||||||
|
"Exclude matched packages from analysis. (Use string as regex).")
|
||||||
|
ans.Flags().StringSliceVarP(&matches, "matches", "m", []string{},
|
||||||
|
"Analyze only matched packages. (Use string as regex).")
|
||||||
|
|
||||||
|
return ans
|
||||||
|
}
|
3
go.mod
3
go.mod
@@ -5,7 +5,7 @@ go 1.12
|
|||||||
require (
|
require (
|
||||||
github.com/DataDog/zstd v1.4.4 // indirect
|
github.com/DataDog/zstd v1.4.4 // indirect
|
||||||
github.com/MottainaiCI/simplestreams-builder v0.0.0-20190710131531-efb382161f56 // indirect
|
github.com/MottainaiCI/simplestreams-builder v0.0.0-20190710131531-efb382161f56 // indirect
|
||||||
github.com/Sabayon/pkgs-checker v0.5.1-0.20200221202320-073693f2c657
|
github.com/Sabayon/pkgs-checker v0.6.2-0.20200315232328-b6efed54b4b1
|
||||||
github.com/asdine/storm v0.0.0-20190418133842-e0f77eada154
|
github.com/asdine/storm v0.0.0-20190418133842-e0f77eada154
|
||||||
github.com/briandowns/spinner v1.7.0
|
github.com/briandowns/spinner v1.7.0
|
||||||
github.com/cavaliercoder/grab v2.0.0+incompatible
|
github.com/cavaliercoder/grab v2.0.0+incompatible
|
||||||
@@ -22,6 +22,7 @@ require (
|
|||||||
github.com/logrusorgru/aurora v0.0.0-20190417123914-21d75270181e
|
github.com/logrusorgru/aurora v0.0.0-20190417123914-21d75270181e
|
||||||
github.com/marcsauter/single v0.0.0-20181104081128-f8bf46f26ec0
|
github.com/marcsauter/single v0.0.0-20181104081128-f8bf46f26ec0
|
||||||
github.com/mattn/go-isatty v0.0.10 // indirect
|
github.com/mattn/go-isatty v0.0.10 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
||||||
github.com/mudler/docker-companion v0.4.6-0.20191110154655-b8b364100616
|
github.com/mudler/docker-companion v0.4.6-0.20191110154655-b8b364100616
|
||||||
github.com/onsi/ginkgo v1.10.1
|
github.com/onsi/ginkgo v1.10.1
|
||||||
github.com/onsi/gomega v1.7.0
|
github.com/onsi/gomega v1.7.0
|
||||||
|
10
go.sum
10
go.sum
@@ -19,6 +19,14 @@ github.com/Sabayon/pkgs-checker v0.4.2-0.20200101193228-1d500105afb7/go.mod h1:G
|
|||||||
github.com/Sabayon/pkgs-checker v0.5.0 h1:VRyyAxo6ox41Dytyl+K+QC+Tps0IxvqYbidu+AH+HUQ=
|
github.com/Sabayon/pkgs-checker v0.5.0 h1:VRyyAxo6ox41Dytyl+K+QC+Tps0IxvqYbidu+AH+HUQ=
|
||||||
github.com/Sabayon/pkgs-checker v0.5.1-0.20200221202320-073693f2c657 h1:VK5S2Gh9kPUxX81zCFUgKVQn+hFy6VgyZMD3QLm76u8=
|
github.com/Sabayon/pkgs-checker v0.5.1-0.20200221202320-073693f2c657 h1:VK5S2Gh9kPUxX81zCFUgKVQn+hFy6VgyZMD3QLm76u8=
|
||||||
github.com/Sabayon/pkgs-checker v0.5.1-0.20200221202320-073693f2c657/go.mod h1:GFGM6ZzSE5owdGgjLnulj0+Vt9UTd5LFGmB2AOVPYrE=
|
github.com/Sabayon/pkgs-checker v0.5.1-0.20200221202320-073693f2c657/go.mod h1:GFGM6ZzSE5owdGgjLnulj0+Vt9UTd5LFGmB2AOVPYrE=
|
||||||
|
github.com/Sabayon/pkgs-checker v0.6.1 h1:7HIrrAQujfEQ0+vPjb1Px4AdUY7KWQPn8W0NjKMoGLI=
|
||||||
|
github.com/Sabayon/pkgs-checker v0.6.1/go.mod h1:GFGM6ZzSE5owdGgjLnulj0+Vt9UTd5LFGmB2AOVPYrE=
|
||||||
|
github.com/Sabayon/pkgs-checker v0.6.2-0.20200310081024-935615ba9d27 h1:+5UCxj8bQHrcjuZumsX+E4mvol2F2dW8lXb1K+A/VlM=
|
||||||
|
github.com/Sabayon/pkgs-checker v0.6.2-0.20200310081024-935615ba9d27/go.mod h1:GFGM6ZzSE5owdGgjLnulj0+Vt9UTd5LFGmB2AOVPYrE=
|
||||||
|
github.com/Sabayon/pkgs-checker v0.6.2-0.20200311072754-f4e0aec412f0 h1:C8pSo4uPvXcb5X2ai5oxuABdsOI1mlNNBaw2oxLbzy4=
|
||||||
|
github.com/Sabayon/pkgs-checker v0.6.2-0.20200311072754-f4e0aec412f0/go.mod h1:GFGM6ZzSE5owdGgjLnulj0+Vt9UTd5LFGmB2AOVPYrE=
|
||||||
|
github.com/Sabayon/pkgs-checker v0.6.2-0.20200315232328-b6efed54b4b1 h1:0hkt4Na3ZDZO6e9hCKATOki+zBKOdRa0LxUtYlm9oyE=
|
||||||
|
github.com/Sabayon/pkgs-checker v0.6.2-0.20200315232328-b6efed54b4b1/go.mod h1:GFGM6ZzSE5owdGgjLnulj0+Vt9UTd5LFGmB2AOVPYrE=
|
||||||
github.com/Sereal/Sereal v0.0.0-20181211220259-509a78ddbda3 h1:Xu7z47ZiE/J+sKXHZMGxEor/oY2q6dq51fkO0JqdSwY=
|
github.com/Sereal/Sereal v0.0.0-20181211220259-509a78ddbda3 h1:Xu7z47ZiE/J+sKXHZMGxEor/oY2q6dq51fkO0JqdSwY=
|
||||||
github.com/Sereal/Sereal v0.0.0-20181211220259-509a78ddbda3/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
github.com/Sereal/Sereal v0.0.0-20181211220259-509a78ddbda3/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
@@ -177,6 +185,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
|||||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
@@ -429,82 +429,6 @@ func (cs *LuetCompiler) compileWithImage(image, buildertaggedImage, packageImage
|
|||||||
return artifact, nil
|
return artifact, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *LuetCompiler) packageFromImage(p CompilationSpec, tag string, keepPermissions, keepImg bool, concurrency int) (Artifact, error) {
|
|
||||||
if !cs.Clean {
|
|
||||||
if art, err := LoadArtifactFromYaml(p); err == nil {
|
|
||||||
Debug("Artifact reloaded. Skipping build")
|
|
||||||
return art, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pkgTag := ":package: " + p.GetPackage().HumanReadableString()
|
|
||||||
|
|
||||||
Info(pkgTag, " 🍩 Build starts 🔨 🔨 🔨 ")
|
|
||||||
|
|
||||||
builderOpts := CompilerBackendOptions{
|
|
||||||
ImageName: p.GetImage(),
|
|
||||||
Destination: p.Rel(p.GetPackage().GetFingerPrint() + ".image.tar"),
|
|
||||||
}
|
|
||||||
err := cs.Backend.DownloadImage(builderOpts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Could not download image")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tag != "" {
|
|
||||||
err = cs.Backend.CopyImage(p.GetImage(), tag)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Could not download image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = cs.Backend.ExportImage(builderOpts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Could not export image")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cs.Options.KeepImageExport {
|
|
||||||
defer os.Remove(builderOpts.Destination)
|
|
||||||
}
|
|
||||||
|
|
||||||
rootfs, err := ioutil.TempDir(p.GetOutputPath(), "rootfs")
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Could not create tempdir")
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(rootfs) // clean up
|
|
||||||
|
|
||||||
// TODO: Compression and such
|
|
||||||
err = cs.Backend.ExtractRootfs(CompilerBackendOptions{
|
|
||||||
ImageName: p.GetImage(),
|
|
||||||
SourcePath: builderOpts.Destination, Destination: rootfs}, keepPermissions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Could not extract rootfs")
|
|
||||||
}
|
|
||||||
artifact := NewPackageArtifact(p.Rel(p.GetPackage().GetFingerPrint() + ".package.tar"))
|
|
||||||
artifact.SetCompileSpec(p)
|
|
||||||
artifact.SetCompressionType(cs.CompressionType)
|
|
||||||
|
|
||||||
err = artifact.Compress(rootfs, concurrency)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Error met while creating package archive")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !keepImg {
|
|
||||||
// We keep them around, so to not reload them from the tar (which should be the "correct way") and we automatically share the same layers
|
|
||||||
// TODO: Handle caching and optionally do not remove things
|
|
||||||
err = cs.Backend.RemoveImage(builderOpts)
|
|
||||||
if err != nil {
|
|
||||||
Warning("Could not remove image ", builderOpts.ImageName)
|
|
||||||
// return nil, errors.Wrap(err, "Could not remove image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Info(pkgTag, " :white_check_mark: Done")
|
|
||||||
|
|
||||||
err = artifact.WriteYaml(p.GetOutputPath())
|
|
||||||
if err != nil {
|
|
||||||
return artifact, err
|
|
||||||
}
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *LuetCompiler) ComputeDepTree(p CompilationSpec) (solver.PackagesAssertions, error) {
|
func (cs *LuetCompiler) ComputeDepTree(p CompilationSpec) (solver.PackagesAssertions, error) {
|
||||||
|
|
||||||
s := solver.NewResolver(pkg.NewInMemoryDatabase(false), cs.Database, pkg.NewInMemoryDatabase(false), cs.Options.SolverOptions.Resolver())
|
s := solver.NewResolver(pkg.NewInMemoryDatabase(false), cs.Database, pkg.NewInMemoryDatabase(false), cs.Options.SolverOptions.Resolver())
|
||||||
@@ -550,14 +474,13 @@ func (cs *LuetCompiler) compile(concurrency int, keepPermissions bool, p Compila
|
|||||||
return nil, errors.New("Package " + p.GetPackage().GetFingerPrint() + "with no deps and no seed image supplied, bailing out")
|
return nil, errors.New("Package " + p.GetPackage().GetFingerPrint() + "with no deps and no seed image supplied, bailing out")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
targetAssertion := p.GetSourceAssertion().Search(p.GetPackage().GetFingerPrint())
|
||||||
|
targetPackageHash := cs.ImageRepository + ":" + targetAssertion.Hash.PackageHash
|
||||||
|
|
||||||
// - If image is set we just generate a plain dockerfile
|
// - If image is set we just generate a plain dockerfile
|
||||||
// Treat last case (easier) first. The image is provided and we just compute a plain dockerfile with the images listed as above
|
// Treat last case (easier) first. The image is provided and we just compute a plain dockerfile with the images listed as above
|
||||||
if p.GetImage() != "" {
|
if p.GetImage() != "" {
|
||||||
if p.ImageUnpack() { // If it is just an entire image, create a package from it
|
return cs.compileWithImage(p.GetImage(), "", targetPackageHash, concurrency, keepPermissions, cs.KeepImg, p)
|
||||||
return cs.packageFromImage(p, "", keepPermissions, cs.KeepImg, concurrency)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cs.compileWithImage(p.GetImage(), "", "", concurrency, keepPermissions, cs.KeepImg, p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - If image is not set, we read a base_image. Then we will build one image from it to kick-off our build based
|
// - If image is not set, we read a base_image. Then we will build one image from it to kick-off our build based
|
||||||
@@ -595,20 +518,6 @@ func (cs *LuetCompiler) compile(concurrency int, keepPermissions bool, p Compila
|
|||||||
|
|
||||||
lastHash = currentPackageImageHash
|
lastHash = currentPackageImageHash
|
||||||
if compileSpec.GetImage() != "" {
|
if compileSpec.GetImage() != "" {
|
||||||
// TODO: Refactor this
|
|
||||||
if compileSpec.ImageUnpack() { // If it is just an entire image, create a package from it
|
|
||||||
if compileSpec.GetImage() == "" {
|
|
||||||
return nil, errors.New("No image defined for package: " + assertion.Package.HumanReadableString())
|
|
||||||
}
|
|
||||||
Info(pkgTag, ":whale: Sourcing package from image", compileSpec.GetImage())
|
|
||||||
artifact, err := cs.packageFromImage(compileSpec, currentPackageImageHash, keepPermissions, cs.KeepImg, concurrency)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Failed compiling "+compileSpec.GetPackage().HumanReadableString())
|
|
||||||
}
|
|
||||||
departifacts = append(departifacts, artifact)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug(pkgTag, " :wrench: Compiling "+compileSpec.GetPackage().HumanReadableString()+" from image")
|
Debug(pkgTag, " :wrench: Compiling "+compileSpec.GetPackage().HumanReadableString()+" from image")
|
||||||
artifact, err := cs.compileWithImage(compileSpec.GetImage(), buildImageHash, currentPackageImageHash, concurrency, keepPermissions, cs.KeepImg, compileSpec)
|
artifact, err := cs.compileWithImage(compileSpec.GetImage(), buildImageHash, currentPackageImageHash, concurrency, keepPermissions, cs.KeepImg, compileSpec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -636,7 +545,7 @@ func (cs *LuetCompiler) compile(concurrency int, keepPermissions bool, p Compila
|
|||||||
|
|
||||||
if !cs.Options.OnlyDeps {
|
if !cs.Options.OnlyDeps {
|
||||||
Info(":package:", p.GetPackage().HumanReadableString(), ":cyclone: Building package target from:", lastHash)
|
Info(":package:", p.GetPackage().HumanReadableString(), ":cyclone: Building package target from:", lastHash)
|
||||||
artifact, err := cs.compileWithImage(lastHash, "", "", concurrency, keepPermissions, cs.KeepImg, p)
|
artifact, err := cs.compileWithImage(lastHash, "", targetPackageHash, concurrency, keepPermissions, cs.KeepImg, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return artifact, err
|
return artifact, err
|
||||||
}
|
}
|
||||||
|
@@ -17,12 +17,29 @@
|
|||||||
package helpers
|
package helpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
_gentoo "github.com/Sabayon/pkgs-checker/pkg/gentoo"
|
_gentoo "github.com/Sabayon/pkgs-checker/pkg/gentoo"
|
||||||
pkg "github.com/mudler/luet/pkg/package"
|
pkg "github.com/mudler/luet/pkg/package"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func CreateRegexArray(rgx []string) ([]*regexp.Regexp, error) {
|
||||||
|
ans := make([]*regexp.Regexp, len(rgx))
|
||||||
|
if len(rgx) > 0 {
|
||||||
|
for idx, reg := range rgx {
|
||||||
|
re := regexp.MustCompile(reg)
|
||||||
|
if re == nil {
|
||||||
|
return nil, errors.New("Invalid regex " + reg + "!")
|
||||||
|
}
|
||||||
|
ans[idx] = re
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ans, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ParsePackageStr(p string) (*pkg.DefaultPackage, error) {
|
func ParsePackageStr(p string) (*pkg.DefaultPackage, error) {
|
||||||
gp, err := _gentoo.ParsePackageStr(p)
|
gp, err := _gentoo.ParsePackageStr(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -337,6 +337,7 @@ func (l *LuetInstaller) install(syncedRepos Repositories, cp []pkg.Package, s *S
|
|||||||
}
|
}
|
||||||
executedFinalizer := map[string]bool{}
|
executedFinalizer := map[string]bool{}
|
||||||
|
|
||||||
|
if !l.Options.NoDeps {
|
||||||
// TODO: Lower those errors as warning
|
// TODO: Lower those errors as warning
|
||||||
for _, w := range p {
|
for _, w := range p {
|
||||||
// Finalizers needs to run in order and in sequence.
|
// Finalizers needs to run in order and in sequence.
|
||||||
@@ -377,6 +378,32 @@ func (l *LuetInstaller) install(syncedRepos Repositories, cp []pkg.Package, s *S
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
} 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())
|
||||||
|
}
|
||||||
|
if helpers.Exists(treePackage.Rel(tree.FinalizerFile)) {
|
||||||
|
Info("Executing finalizer for " + c.Package.HumanReadableString())
|
||||||
|
finalizerRaw, err := ioutil.ReadFile(treePackage.Rel(tree.FinalizerFile))
|
||||||
|
if err != nil && !l.Options.Force {
|
||||||
|
return errors.Wrap(err, "Error reading file "+treePackage.Rel(tree.FinalizerFile))
|
||||||
|
}
|
||||||
|
if _, exists := executedFinalizer[c.Package.GetFingerPrint()]; !exists {
|
||||||
|
finalizer, err := NewLuetFinalizerFromYaml(finalizerRaw)
|
||||||
|
if err != nil && !l.Options.Force {
|
||||||
|
return errors.Wrap(err, "Error reading finalizer "+treePackage.Rel(tree.FinalizerFile))
|
||||||
|
}
|
||||||
|
err = finalizer.RunInstall()
|
||||||
|
if err != nil && !l.Options.Force {
|
||||||
|
return errors.Wrap(err, "Error executing install finalizer "+treePackage.Rel(tree.FinalizerFile))
|
||||||
|
}
|
||||||
|
executedFinalizer[c.Package.GetFingerPrint()] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -69,6 +68,19 @@ type LuetSystemRepositorySerialized struct {
|
|||||||
TreeChecksums compiler.Checksums `json:"treechecksums"`
|
TreeChecksums compiler.Checksums `json:"treechecksums"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LuetSearchModeType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SLabel LuetSearchModeType = "label"
|
||||||
|
SRegexPkg LuetSearchModeType = "regexPkg"
|
||||||
|
SRegexLabel LuetSearchModeType = "regexLabel"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LuetSearchOpts struct {
|
||||||
|
Pattern string
|
||||||
|
Mode LuetSearchModeType
|
||||||
|
}
|
||||||
|
|
||||||
func GenerateRepository(name, descr, t string, urls []string, priority int, src, treeDir string, db pkg.PackageDatabase) (Repository, error) {
|
func GenerateRepository(name, descr, t string, urls []string, priority int, src, treeDir string, db pkg.PackageDatabase) (Repository, error) {
|
||||||
|
|
||||||
art, err := buildPackageIndex(src)
|
art, err := buildPackageIndex(src)
|
||||||
@@ -554,14 +566,24 @@ PACKAGE:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (re Repositories) Search(s string) []PackageMatch {
|
func (re Repositories) SearchPackages(p string, o LuetSearchOpts) []PackageMatch {
|
||||||
sort.Sort(re)
|
sort.Sort(re)
|
||||||
var term = regexp.MustCompile(s)
|
|
||||||
var matches []PackageMatch
|
var matches []PackageMatch
|
||||||
|
var err error
|
||||||
|
|
||||||
for _, r := range re {
|
for _, r := range re {
|
||||||
for _, pack := range r.GetTree().GetDatabase().World() {
|
var repoMatches []pkg.Package
|
||||||
if term.MatchString(pack.GetName()) {
|
if o.Mode == SRegexPkg {
|
||||||
|
repoMatches, err = r.GetTree().GetDatabase().FindPackageMatch(p)
|
||||||
|
|
||||||
|
} else if o.Mode == SLabel {
|
||||||
|
repoMatches, err = r.GetTree().GetDatabase().FindPackageLabel(p)
|
||||||
|
} else if o.Mode == SRegexLabel {
|
||||||
|
repoMatches, err = r.GetTree().GetDatabase().FindPackageLabelMatch(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && len(repoMatches) > 0 {
|
||||||
|
for _, pack := range repoMatches {
|
||||||
matches = append(matches, PackageMatch{Package: pack, Repo: r})
|
matches = append(matches, PackageMatch{Package: pack, Repo: r})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -569,3 +591,15 @@ func (re Repositories) Search(s string) []PackageMatch {
|
|||||||
|
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (re Repositories) SearchLabelMatch(s string) []PackageMatch {
|
||||||
|
return re.SearchPackages(s, LuetSearchOpts{Pattern: s, Mode: SRegexLabel})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re Repositories) SearchLabel(s string) []PackageMatch {
|
||||||
|
return re.SearchPackages(s, LuetSearchOpts{Pattern: s, Mode: SLabel})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re Repositories) Search(s string) []PackageMatch {
|
||||||
|
return re.SearchPackages(s, LuetSearchOpts{Pattern: s, Mode: SRegexPkg})
|
||||||
|
}
|
||||||
|
@@ -45,6 +45,9 @@ type PackageSet interface {
|
|||||||
World() []Package
|
World() []Package
|
||||||
|
|
||||||
FindPackageCandidate(p Package) (Package, error)
|
FindPackageCandidate(p Package) (Package, error)
|
||||||
|
FindPackageLabel(labelKey string) ([]Package, error)
|
||||||
|
FindPackageLabelMatch(pattern string) ([]Package, error)
|
||||||
|
FindPackageMatch(pattern string) ([]Package, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PackageFile struct {
|
type PackageFile struct {
|
||||||
|
@@ -18,6 +18,7 @@ package pkg
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -384,3 +385,61 @@ func (db *BoltDatabase) FindPackageVersions(p Package) ([]Package, error) {
|
|||||||
}
|
}
|
||||||
return versionsInWorld, nil
|
return versionsInWorld, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *BoltDatabase) FindPackageLabel(labelKey string) ([]Package, error) {
|
||||||
|
var ans []Package
|
||||||
|
|
||||||
|
for _, k := range db.GetPackages() {
|
||||||
|
pack, err := db.GetPackage(k)
|
||||||
|
if err != nil {
|
||||||
|
return ans, err
|
||||||
|
}
|
||||||
|
if pack.HasLabel(labelKey) {
|
||||||
|
ans = append(ans, pack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *BoltDatabase) FindPackageLabelMatch(pattern string) ([]Package, error) {
|
||||||
|
var ans []Package
|
||||||
|
|
||||||
|
re := regexp.MustCompile(pattern)
|
||||||
|
if re == nil {
|
||||||
|
return nil, errors.New("Invalid regex " + pattern + "!")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range db.GetPackages() {
|
||||||
|
pack, err := db.GetPackage(k)
|
||||||
|
if err != nil {
|
||||||
|
return ans, err
|
||||||
|
}
|
||||||
|
if pack.MatchLabel(re) {
|
||||||
|
ans = append(ans, pack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ans, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *BoltDatabase) FindPackageMatch(pattern string) ([]Package, error) {
|
||||||
|
var ans []Package
|
||||||
|
|
||||||
|
re := regexp.MustCompile(pattern)
|
||||||
|
if re == nil {
|
||||||
|
return nil, errors.New("Invalid regex " + pattern + "!")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range db.GetPackages() {
|
||||||
|
pack, err := db.GetPackage(k)
|
||||||
|
if err != nil {
|
||||||
|
return ans, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if re.MatchString(pack.GetCategory() + pack.GetName()) {
|
||||||
|
ans = append(ans, pack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ans, nil
|
||||||
|
}
|
||||||
|
@@ -18,6 +18,7 @@ package pkg
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"regexp"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -358,3 +359,62 @@ func (db *InMemoryDatabase) FindPackageCandidate(p Package) (Package, error) {
|
|||||||
return required, err
|
return required, err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *InMemoryDatabase) FindPackageLabel(labelKey string) ([]Package, error) {
|
||||||
|
var ans []Package
|
||||||
|
|
||||||
|
for _, k := range db.GetPackages() {
|
||||||
|
pack, err := db.GetPackage(k)
|
||||||
|
if err != nil {
|
||||||
|
return ans, err
|
||||||
|
}
|
||||||
|
if pack.HasLabel(labelKey) {
|
||||||
|
ans = append(ans, pack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ans, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *InMemoryDatabase) FindPackageLabelMatch(pattern string) ([]Package, error) {
|
||||||
|
var ans []Package
|
||||||
|
|
||||||
|
re := regexp.MustCompile(pattern)
|
||||||
|
if re == nil {
|
||||||
|
return nil, errors.New("Invalid regex " + pattern + "!")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range db.GetPackages() {
|
||||||
|
pack, err := db.GetPackage(k)
|
||||||
|
if err != nil {
|
||||||
|
return ans, err
|
||||||
|
}
|
||||||
|
if pack.MatchLabel(re) {
|
||||||
|
ans = append(ans, pack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ans, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *InMemoryDatabase) FindPackageMatch(pattern string) ([]Package, error) {
|
||||||
|
var ans []Package
|
||||||
|
|
||||||
|
re := regexp.MustCompile(pattern)
|
||||||
|
if re == nil {
|
||||||
|
return nil, errors.New("Invalid regex " + pattern + "!")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range db.GetPackages() {
|
||||||
|
pack, err := db.GetPackage(k)
|
||||||
|
if err != nil {
|
||||||
|
return ans, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if re.MatchString(pack.GetCategory() + pack.GetName()) {
|
||||||
|
ans = append(ans, pack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ans, nil
|
||||||
|
}
|
||||||
|
@@ -22,16 +22,17 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
// . "github.com/mudler/luet/pkg/logger"
|
gentoo "github.com/Sabayon/pkgs-checker/pkg/gentoo"
|
||||||
|
|
||||||
"github.com/crillab/gophersat/bf"
|
"github.com/crillab/gophersat/bf"
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/ghodss/yaml"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Package is a package interface (TBD)
|
// Package is a package interface (TBD)
|
||||||
@@ -47,6 +48,7 @@ type Package interface {
|
|||||||
Requires([]*DefaultPackage) Package
|
Requires([]*DefaultPackage) Package
|
||||||
Conflicts([]*DefaultPackage) Package
|
Conflicts([]*DefaultPackage) Package
|
||||||
Revdeps(PackageDatabase) []Package
|
Revdeps(PackageDatabase) []Package
|
||||||
|
LabelDeps(PackageDatabase, string) []Package
|
||||||
|
|
||||||
GetProvides() []*DefaultPackage
|
GetProvides() []*DefaultPackage
|
||||||
SetProvides([]*DefaultPackage) Package
|
SetProvides([]*DefaultPackage) Package
|
||||||
@@ -62,7 +64,7 @@ type Package interface {
|
|||||||
GetVersion() string
|
GetVersion() string
|
||||||
RequiresContains(PackageDatabase, Package) (bool, error)
|
RequiresContains(PackageDatabase, Package) (bool, error)
|
||||||
Matches(m Package) bool
|
Matches(m Package) bool
|
||||||
Bigger(m Package) bool
|
BumpBuildVersion() error
|
||||||
|
|
||||||
AddUse(use string)
|
AddUse(use string)
|
||||||
RemoveUse(use string)
|
RemoveUse(use string)
|
||||||
@@ -84,6 +86,11 @@ type Package interface {
|
|||||||
SetLicense(string)
|
SetLicense(string)
|
||||||
GetLicense() string
|
GetLicense() string
|
||||||
|
|
||||||
|
AddLabel(string, string)
|
||||||
|
GetLabels() map[string]string
|
||||||
|
HasLabel(string) bool
|
||||||
|
MatchLabel(*regexp.Regexp) bool
|
||||||
|
|
||||||
IsSelector() bool
|
IsSelector() bool
|
||||||
VersionMatchSelector(string) (bool, error)
|
VersionMatchSelector(string) (bool, error)
|
||||||
SelectorMatchVersion(string) (bool, error)
|
SelectorMatchVersion(string) (bool, error)
|
||||||
@@ -150,9 +157,11 @@ type DefaultPackage struct {
|
|||||||
// Path is set only internally when tree is loaded from disk
|
// Path is set only internally when tree is loaded from disk
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
|
|
||||||
Description string `json:"description"`
|
Description string `json:"description,omitempty"`
|
||||||
Uri []string `json:"uri"`
|
Uri []string `json:"uri,omitempty"`
|
||||||
License string `json:"license"`
|
License string `json:"license,omitempty"`
|
||||||
|
|
||||||
|
Labels map[string]string `json:labels,omitempty`
|
||||||
}
|
}
|
||||||
|
|
||||||
// State represent the package state
|
// State represent the package state
|
||||||
@@ -160,7 +169,13 @@ type State string
|
|||||||
|
|
||||||
// NewPackage returns a new package
|
// NewPackage returns a new package
|
||||||
func NewPackage(name, version string, requires []*DefaultPackage, conflicts []*DefaultPackage) *DefaultPackage {
|
func NewPackage(name, version string, requires []*DefaultPackage, conflicts []*DefaultPackage) *DefaultPackage {
|
||||||
return &DefaultPackage{Name: name, Version: version, PackageRequires: requires, PackageConflicts: conflicts}
|
return &DefaultPackage{
|
||||||
|
Name: name,
|
||||||
|
Version: version,
|
||||||
|
PackageRequires: requires,
|
||||||
|
PackageConflicts: conflicts,
|
||||||
|
Labels: make(map[string]string, 0),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DefaultPackage) String() string {
|
func (p *DefaultPackage) String() string {
|
||||||
@@ -218,6 +233,28 @@ func (p *DefaultPackage) IsSelector() bool {
|
|||||||
return strings.ContainsAny(p.GetVersion(), "<>=")
|
return strings.ContainsAny(p.GetVersion(), "<>=")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPackage) HasLabel(label string) bool {
|
||||||
|
ans := false
|
||||||
|
for k, _ := range p.Labels {
|
||||||
|
if k == label {
|
||||||
|
ans = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPackage) MatchLabel(r *regexp.Regexp) bool {
|
||||||
|
ans := false
|
||||||
|
for k, v := range p.Labels {
|
||||||
|
if r.MatchString(k + "=" + v) {
|
||||||
|
ans = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
// AddUse adds a use to a package
|
// AddUse adds a use to a package
|
||||||
func (p *DefaultPackage) AddUse(use string) {
|
func (p *DefaultPackage) AddUse(use string) {
|
||||||
for _, v := range p.UseFlags {
|
for _, v := range p.UseFlags {
|
||||||
@@ -302,6 +339,12 @@ func (p *DefaultPackage) SetCategory(s string) {
|
|||||||
func (p *DefaultPackage) GetUses() []string {
|
func (p *DefaultPackage) GetUses() []string {
|
||||||
return p.UseFlags
|
return p.UseFlags
|
||||||
}
|
}
|
||||||
|
func (p *DefaultPackage) AddLabel(k, v string) {
|
||||||
|
p.Labels[k] = v
|
||||||
|
}
|
||||||
|
func (p *DefaultPackage) GetLabels() map[string]string {
|
||||||
|
return p.Labels
|
||||||
|
}
|
||||||
func (p *DefaultPackage) GetProvides() []*DefaultPackage {
|
func (p *DefaultPackage) GetProvides() []*DefaultPackage {
|
||||||
return p.Provides
|
return p.Provides
|
||||||
}
|
}
|
||||||
@@ -334,13 +377,6 @@ func (p *DefaultPackage) Matches(m Package) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func (p *DefaultPackage) Bigger(m Package) bool {
|
|
||||||
low := Lower([]Package{p, m})
|
|
||||||
if low.Matches(m) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DefaultPackage) Expand(definitiondb PackageDatabase) ([]Package, error) {
|
func (p *DefaultPackage) Expand(definitiondb PackageDatabase) ([]Package, error) {
|
||||||
var versionsInWorld []Package
|
var versionsInWorld []Package
|
||||||
@@ -379,6 +415,19 @@ func (p *DefaultPackage) Revdeps(definitiondb PackageDatabase) []Package {
|
|||||||
return versionsInWorld
|
return versionsInWorld
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPackage) LabelDeps(definitiondb PackageDatabase, labelKey string) []Package {
|
||||||
|
var pkgsWithLabelInWorld []Package
|
||||||
|
// TODO: check if integrate some index to improve
|
||||||
|
// research instead of iterate all list.
|
||||||
|
for _, w := range definitiondb.World() {
|
||||||
|
if w.HasLabel(labelKey) {
|
||||||
|
pkgsWithLabelInWorld = append(pkgsWithLabelInWorld, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkgsWithLabelInWorld
|
||||||
|
}
|
||||||
|
|
||||||
func DecodePackage(ID string, db PackageDatabase) (Package, error) {
|
func DecodePackage(ID string, db PackageDatabase) (Package, error) {
|
||||||
return db.GetPackage(ID)
|
return db.GetPackage(ID)
|
||||||
}
|
}
|
||||||
@@ -408,29 +457,7 @@ func (pack *DefaultPackage) RequiresContains(definitiondb PackageDatabase, s Pac
|
|||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
func Lower(set []Package) Package {
|
|
||||||
var versionsMap map[string]Package = make(map[string]Package)
|
|
||||||
if len(set) == 0 {
|
|
||||||
panic("Best needs a list with elements")
|
|
||||||
}
|
|
||||||
|
|
||||||
versionsRaw := []string{}
|
|
||||||
for _, p := range set {
|
|
||||||
versionsRaw = append(versionsRaw, p.GetVersion())
|
|
||||||
versionsMap[p.GetVersion()] = p
|
|
||||||
}
|
|
||||||
|
|
||||||
versions := make([]*version.Version, len(versionsRaw))
|
|
||||||
for i, raw := range versionsRaw {
|
|
||||||
v, _ := version.NewVersion(raw)
|
|
||||||
versions[i] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// After this, the versions are properly sorted
|
|
||||||
sort.Sort(version.Collection(versions))
|
|
||||||
|
|
||||||
return versionsMap[versions[0].Original()]
|
|
||||||
}
|
|
||||||
func Best(set []Package) Package {
|
func Best(set []Package) Package {
|
||||||
var versionsMap map[string]Package = make(map[string]Package)
|
var versionsMap map[string]Package = make(map[string]Package)
|
||||||
if len(set) == 0 {
|
if len(set) == 0 {
|
||||||
@@ -637,3 +664,89 @@ func (p *DefaultPackage) Explain() {
|
|||||||
fmt.Println("====================")
|
fmt.Println("====================")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPackage) BumpBuildVersion() error {
|
||||||
|
cat := p.Category
|
||||||
|
if cat == "" {
|
||||||
|
// Use fake category for parse package
|
||||||
|
cat = "app"
|
||||||
|
}
|
||||||
|
gp, err := gentoo.ParsePackageStr(
|
||||||
|
fmt.Sprintf("%s/%s-%s", cat,
|
||||||
|
p.Name, p.GetVersion()))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Error on parser version")
|
||||||
|
}
|
||||||
|
|
||||||
|
buildPrefix := ""
|
||||||
|
buildId := 0
|
||||||
|
|
||||||
|
if gp.VersionBuild != "" {
|
||||||
|
// Check if version build is a number
|
||||||
|
buildId, err = strconv.Atoi(gp.VersionBuild)
|
||||||
|
if err == nil {
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
// POST: is not only a number
|
||||||
|
|
||||||
|
// TODO: check if there is a better way to handle all use cases.
|
||||||
|
|
||||||
|
r1 := regexp.MustCompile(`^r[0-9]*$`)
|
||||||
|
if r1 == nil {
|
||||||
|
return errors.New("Error on create regex for -r[0-9]")
|
||||||
|
}
|
||||||
|
if r1.MatchString(gp.VersionBuild) {
|
||||||
|
buildId, err = strconv.Atoi(strings.ReplaceAll(gp.VersionBuild, "r", ""))
|
||||||
|
if err == nil {
|
||||||
|
buildPrefix = "r"
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p1 := regexp.MustCompile(`^p[0-9]*$`)
|
||||||
|
if p1 == nil {
|
||||||
|
return errors.New("Error on create regex for -p[0-9]")
|
||||||
|
}
|
||||||
|
if p1.MatchString(gp.VersionBuild) {
|
||||||
|
buildId, err = strconv.Atoi(strings.ReplaceAll(gp.VersionBuild, "p", ""))
|
||||||
|
if err == nil {
|
||||||
|
buildPrefix = "p"
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rc1 := regexp.MustCompile(`^rc[0-9]*$`)
|
||||||
|
if rc1 == nil {
|
||||||
|
return errors.New("Error on create regex for -rc[0-9]")
|
||||||
|
}
|
||||||
|
if rc1.MatchString(gp.VersionBuild) {
|
||||||
|
buildId, err = strconv.Atoi(strings.ReplaceAll(gp.VersionBuild, "rc", ""))
|
||||||
|
if err == nil {
|
||||||
|
buildPrefix = "rc"
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if version build contains a dot
|
||||||
|
dotIdx := strings.LastIndex(gp.VersionBuild, ".")
|
||||||
|
if dotIdx > 0 {
|
||||||
|
buildPrefix = gp.VersionBuild[0 : dotIdx+1]
|
||||||
|
bVersion := gp.VersionBuild[dotIdx+1:]
|
||||||
|
buildId, err = strconv.Atoi(bVersion)
|
||||||
|
if err == nil {
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildPrefix = gp.VersionBuild + "."
|
||||||
|
buildId = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
|
||||||
|
buildId++
|
||||||
|
p.Version = fmt.Sprintf("%s%s+%s%d",
|
||||||
|
gp.Version, gp.VersionSuffix, buildPrefix, buildId)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@@ -16,6 +16,8 @@
|
|||||||
package pkg_test
|
package pkg_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
. "github.com/mudler/luet/pkg/package"
|
. "github.com/mudler/luet/pkg/package"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
@@ -61,6 +63,34 @@ var _ = Describe("Package", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("Find label on packages", func() {
|
||||||
|
a := NewPackage("A", ">=1.0", []*DefaultPackage{}, []*DefaultPackage{})
|
||||||
|
a.AddLabel("project1", "test1")
|
||||||
|
a.AddLabel("label2", "value1")
|
||||||
|
b := NewPackage("B", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
|
||||||
|
b.AddLabel("project2", "test2")
|
||||||
|
b.AddLabel("label2", "value1")
|
||||||
|
It("Expands correctly", func() {
|
||||||
|
var err error
|
||||||
|
definitions := NewInMemoryDatabase(false)
|
||||||
|
for _, p := range []Package{a, b} {
|
||||||
|
_, err = definitions.CreatePackage(p)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
re := regexp.MustCompile("project[0-9][=].*")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(re).ToNot(BeNil())
|
||||||
|
Expect(a.HasLabel("label2")).To(Equal(true))
|
||||||
|
Expect(a.HasLabel("label3")).To(Equal(false))
|
||||||
|
Expect(a.HasLabel("project1")).To(Equal(true))
|
||||||
|
Expect(b.HasLabel("project2")).To(Equal(true))
|
||||||
|
Expect(b.HasLabel("label2")).To(Equal(true))
|
||||||
|
Expect(b.MatchLabel(re)).To(Equal(true))
|
||||||
|
Expect(a.MatchLabel(re)).To(Equal(true))
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Context("Check description", func() {
|
Context("Check description", func() {
|
||||||
a := NewPackage("A", ">=1.0", []*DefaultPackage{}, []*DefaultPackage{})
|
a := NewPackage("A", ">=1.0", []*DefaultPackage{}, []*DefaultPackage{})
|
||||||
a.SetDescription("Description A")
|
a.SetDescription("Description A")
|
||||||
@@ -252,4 +282,69 @@ var _ = Describe("Package", func() {
|
|||||||
Expect(len(a1.GetUses())).To(Equal(0))
|
Expect(len(a1.GetUses())).To(Equal(0))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("Check Bump build Version", func() {
|
||||||
|
It("Bump without build version", func() {
|
||||||
|
a1 := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
|
||||||
|
err := a1.BumpBuildVersion()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(a1.GetVersion()).To(Equal("1.0+1"))
|
||||||
|
})
|
||||||
|
It("Bump 2", func() {
|
||||||
|
p := NewPackage("A", "1.0+1", []*DefaultPackage{}, []*DefaultPackage{})
|
||||||
|
err := p.BumpBuildVersion()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(p.GetVersion()).To(Equal("1.0+2"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Bump 3", func() {
|
||||||
|
p := NewPackage("A", "1.0+100", []*DefaultPackage{}, []*DefaultPackage{})
|
||||||
|
err := p.BumpBuildVersion()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(p.GetVersion()).To(Equal("1.0+101"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Bump 4", func() {
|
||||||
|
p := NewPackage("A", "1.0+r1", []*DefaultPackage{}, []*DefaultPackage{})
|
||||||
|
err := p.BumpBuildVersion()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(p.GetVersion()).To(Equal("1.0+r2"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Bump 5", func() {
|
||||||
|
p := NewPackage("A", "1.0+p1", []*DefaultPackage{}, []*DefaultPackage{})
|
||||||
|
err := p.BumpBuildVersion()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(p.GetVersion()).To(Equal("1.0+p2"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Bump 6", func() {
|
||||||
|
p := NewPackage("A", "1.0+pre20200315", []*DefaultPackage{}, []*DefaultPackage{})
|
||||||
|
err := p.BumpBuildVersion()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(p.GetVersion()).To(Equal("1.0+pre20200315.1"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Bump 7", func() {
|
||||||
|
p := NewPackage("A", "1.0+pre20200315.1", []*DefaultPackage{}, []*DefaultPackage{})
|
||||||
|
err := p.BumpBuildVersion()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(p.GetVersion()).To(Equal("1.0+pre20200315.2"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Bump 8", func() {
|
||||||
|
p := NewPackage("A", "1.0+d-r1", []*DefaultPackage{}, []*DefaultPackage{})
|
||||||
|
err := p.BumpBuildVersion()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(p.GetVersion()).To(Equal("1.0+d-r1.1"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Bump 9", func() {
|
||||||
|
p := NewPackage("A", "1.0+p20200315.1", []*DefaultPackage{}, []*DefaultPackage{})
|
||||||
|
err := p.BumpBuildVersion()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(p.GetVersion()).To(Equal("1.0+p20200315.2"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@@ -256,15 +256,20 @@ func PackageAdmit(selector, i PkgVersionSelector) (bool, error) {
|
|||||||
var v2 *version.Version = nil
|
var v2 *version.Version = nil
|
||||||
var ans bool
|
var ans bool
|
||||||
var err error
|
var err error
|
||||||
|
var sanitizedSelectorVersion, sanitizedIVersion string
|
||||||
|
|
||||||
if selector.Version != "" {
|
if selector.Version != "" {
|
||||||
v1, err = version.NewVersion(selector.Version)
|
// TODO: This is temporary!. I promise it.
|
||||||
|
sanitizedSelectorVersion = strings.ReplaceAll(selector.Version, "_", "-")
|
||||||
|
|
||||||
|
v1, err = version.NewVersion(sanitizedSelectorVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i.Version != "" {
|
if i.Version != "" {
|
||||||
v2, err = version.NewVersion(i.Version)
|
sanitizedIVersion = strings.ReplaceAll(i.Version, "_", "-")
|
||||||
|
v2, err = version.NewVersion(sanitizedIVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -290,7 +295,7 @@ func PackageAdmit(selector, i PkgVersionSelector) (bool, error) {
|
|||||||
// TODO: case of 7.3* where 7.30 is accepted.
|
// TODO: case of 7.3* where 7.30 is accepted.
|
||||||
if v1 != nil && v2 != nil {
|
if v1 != nil && v2 != nil {
|
||||||
segments := v1.Segments()
|
segments := v1.Segments()
|
||||||
n := strings.Count(selector.Version, ".")
|
n := strings.Count(sanitizedIVersion, ".")
|
||||||
switch n {
|
switch n {
|
||||||
case 0:
|
case 0:
|
||||||
segments[0]++
|
segments[0]++
|
||||||
@@ -303,7 +308,7 @@ func PackageAdmit(selector, i PkgVersionSelector) (bool, error) {
|
|||||||
}
|
}
|
||||||
nextVersion := strings.Trim(strings.Replace(fmt.Sprint(segments), " ", ".", -1), "[]")
|
nextVersion := strings.Trim(strings.Replace(fmt.Sprint(segments), " ", ".", -1), "[]")
|
||||||
constraints, err := version.NewConstraint(
|
constraints, err := version.NewConstraint(
|
||||||
fmt.Sprintf(">= %s, < %s", selector.Version, nextVersion),
|
fmt.Sprintf(">= %s, < %s", sanitizedSelectorVersion, nextVersion),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@@ -191,6 +191,17 @@ var _ = Describe("Versions", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("Versions Parser16 - semver", func() {
|
||||||
|
v, err := ParseVersion("<=1.0.29+pre2_p20191024")
|
||||||
|
It("ParseVersion10", func() {
|
||||||
|
var c PkgSelectorCondition = PkgCondLessEqual
|
||||||
|
Expect(err).Should(BeNil())
|
||||||
|
Expect(v.Version).Should(Equal("1.0.29+pre2_p20191024"))
|
||||||
|
Expect(v.VersionSuffix).Should(Equal(""))
|
||||||
|
Expect(v.Condition).Should(Equal(c))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Context("Selector1", func() {
|
Context("Selector1", func() {
|
||||||
v1, err := ParseVersion(">=0.0.20190406.4.9.172-r1")
|
v1, err := ParseVersion(">=0.0.20190406.4.9.172-r1")
|
||||||
v2, err2 := ParseVersion("1.0.111")
|
v2, err2 := ParseVersion("1.0.111")
|
||||||
@@ -275,6 +286,18 @@ var _ = Describe("Versions", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("Selector8", func() {
|
||||||
|
v1, err := ParseVersion(">=0")
|
||||||
|
v2, err2 := ParseVersion("1.0.29+pre2_p20191024")
|
||||||
|
match, err3 := PackageAdmit(v1, v2)
|
||||||
|
It("Selector8", func() {
|
||||||
|
Expect(err).Should(BeNil())
|
||||||
|
Expect(err2).Should(BeNil())
|
||||||
|
Expect(err3).Should(BeNil())
|
||||||
|
Expect(match).Should(Equal(true))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Context("Condition Converter 1", func() {
|
Context("Condition Converter 1", func() {
|
||||||
gp, err := gentoo.ParsePackageStr("=layer/build-1.0")
|
gp, err := gentoo.ParsePackageStr("=layer/build-1.0")
|
||||||
var cond gentoo.PackageCond = gentoo.PkgCondEqual
|
var cond gentoo.PackageCond = gentoo.PkgCondEqual
|
||||||
|
@@ -35,7 +35,7 @@ func LoadRepositories(c *LuetConfig) error {
|
|||||||
|
|
||||||
files, err := ioutil.ReadDir(rdir)
|
files, err := ioutil.ReadDir(rdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Warning("Skip dir", rdir, ":", err.Error())
|
Debug("Skip dir", rdir, ":", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -363,12 +363,19 @@ var _ = Describe("Solver", func() {
|
|||||||
It("Selects best version", func() {
|
It("Selects best version", func() {
|
||||||
|
|
||||||
E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||||
|
E.SetCategory("test")
|
||||||
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||||
|
C.SetCategory("test")
|
||||||
D2 := pkg.NewPackage("D", "1.9", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
D2 := pkg.NewPackage("D", "1.9", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||||
|
D2.SetCategory("test")
|
||||||
D := pkg.NewPackage("D", "1.8", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
D := pkg.NewPackage("D", "1.8", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||||
|
D.SetCategory("test")
|
||||||
D1 := pkg.NewPackage("D", "1.4", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
D1 := pkg.NewPackage("D", "1.4", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||||
B := pkg.NewPackage("B", "1.1", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "D", Version: ">=1.0"}}, []*pkg.DefaultPackage{})
|
D1.SetCategory("test")
|
||||||
A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "D", Version: ">=1.0"}}, []*pkg.DefaultPackage{})
|
B := pkg.NewPackage("B", "1.1", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "D", Version: ">=1.0", Category: "test"}}, []*pkg.DefaultPackage{})
|
||||||
|
B.SetCategory("test")
|
||||||
|
A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "D", Version: ">=1.0", Category: "test"}}, []*pkg.DefaultPackage{})
|
||||||
|
A.SetCategory("test")
|
||||||
|
|
||||||
for _, p := range []pkg.Package{A, B, C, D, D1, D2, E} {
|
for _, p := range []pkg.Package{A, B, C, D, D1, D2, E} {
|
||||||
_, err := dbDefinitions.CreatePackage(p)
|
_, err := dbDefinitions.CreatePackage(p)
|
||||||
@@ -397,16 +404,23 @@ var _ = Describe("Solver", func() {
|
|||||||
It("Support provides", func() {
|
It("Support provides", func() {
|
||||||
|
|
||||||
E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||||
|
E.SetCategory("test")
|
||||||
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||||
|
C.SetCategory("test")
|
||||||
D2 := pkg.NewPackage("D", "1.9", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
D2 := pkg.NewPackage("D", "1.9", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||||
|
D2.SetCategory("test")
|
||||||
D := pkg.NewPackage("D", "1.8", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
D := pkg.NewPackage("D", "1.8", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||||
|
D.SetCategory("test")
|
||||||
D1 := pkg.NewPackage("D", "1.4", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
D1 := pkg.NewPackage("D", "1.4", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||||
B := pkg.NewPackage("B", "1.1", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "D", Version: ">=1.0"}}, []*pkg.DefaultPackage{})
|
D1.SetCategory("test")
|
||||||
A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "D", Version: ">=1.0"}}, []*pkg.DefaultPackage{})
|
B := pkg.NewPackage("B", "1.1", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "D", Version: ">=1.0", Category: "test"}}, []*pkg.DefaultPackage{})
|
||||||
|
B.SetCategory("test")
|
||||||
|
A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "D", Version: ">=1.0", Category: "test"}}, []*pkg.DefaultPackage{})
|
||||||
|
A.SetCategory("test")
|
||||||
|
|
||||||
D2.SetProvides([]*pkg.DefaultPackage{{Name: "E"}})
|
D2.SetProvides([]*pkg.DefaultPackage{{Name: "E", Category: "test"}})
|
||||||
A2 := pkg.NewPackage("A", "1.3", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "E", Version: ""}}, []*pkg.DefaultPackage{})
|
A2 := pkg.NewPackage("A", "1.3", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "E", Version: "", Category: "test"}}, []*pkg.DefaultPackage{})
|
||||||
|
A2.SetCategory("test")
|
||||||
|
|
||||||
for _, p := range []pkg.Package{A, B, C, D, D1, D2, A2, E} {
|
for _, p := range []pkg.Package{A, B, C, D, D1, D2, A2, E} {
|
||||||
_, err := dbDefinitions.CreatePackage(p)
|
_, err := dbDefinitions.CreatePackage(p)
|
||||||
|
@@ -26,5 +26,5 @@ type Builder interface {
|
|||||||
GetDatabase() pkg.PackageDatabase
|
GetDatabase() pkg.PackageDatabase
|
||||||
WithDatabase(d pkg.PackageDatabase)
|
WithDatabase(d pkg.PackageDatabase)
|
||||||
|
|
||||||
GetSourcePath() string
|
GetSourcePath() []string
|
||||||
}
|
}
|
||||||
|
@@ -38,6 +38,20 @@ func NewCompilerRecipe(d pkg.PackageDatabase) Builder {
|
|||||||
return &CompilerRecipe{Recipe: Recipe{Database: d}}
|
return &CompilerRecipe{Recipe: Recipe{Database: d}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReadDefinitionFile(path string) (pkg.DefaultPackage, error) {
|
||||||
|
empty := pkg.DefaultPackage{}
|
||||||
|
dat, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return empty, errors.Wrap(err, "Error reading file "+path)
|
||||||
|
}
|
||||||
|
pack, err := pkg.DefaultPackageFromYaml(dat)
|
||||||
|
if err != nil {
|
||||||
|
return empty, errors.Wrap(err, "Error reading yaml "+path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pack, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Recipe is the "general" reciper for Trees
|
// Recipe is the "general" reciper for Trees
|
||||||
type CompilerRecipe struct {
|
type CompilerRecipe struct {
|
||||||
Recipe
|
Recipe
|
||||||
@@ -45,7 +59,7 @@ type CompilerRecipe struct {
|
|||||||
|
|
||||||
func (r *CompilerRecipe) Load(path string) error {
|
func (r *CompilerRecipe) Load(path string) error {
|
||||||
|
|
||||||
r.SourcePath = path
|
r.SourcePath = append(r.SourcePath, path)
|
||||||
//tmpfile, err := ioutil.TempFile("", "luet")
|
//tmpfile, err := ioutil.TempFile("", "luet")
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
// return err
|
// return err
|
||||||
@@ -56,17 +70,17 @@ func (r *CompilerRecipe) Load(path string) error {
|
|||||||
// the function that handles each file or dir
|
// the function that handles each file or dir
|
||||||
var ff = func(currentpath string, info os.FileInfo, err error) error {
|
var ff = func(currentpath string, info os.FileInfo, err error) error {
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Error on walk path "+currentpath)
|
||||||
|
}
|
||||||
|
|
||||||
if info.Name() != DefinitionFile {
|
if info.Name() != DefinitionFile {
|
||||||
return nil // Skip with no errors
|
return nil // Skip with no errors
|
||||||
}
|
}
|
||||||
|
|
||||||
dat, err := ioutil.ReadFile(currentpath)
|
pack, err := ReadDefinitionFile(currentpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Error reading file "+currentpath)
|
return err
|
||||||
}
|
|
||||||
pack, err := pkg.DefaultPackageFromYaml(dat)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "Error reading yaml "+currentpath)
|
|
||||||
}
|
}
|
||||||
// Path is set only internally when tree is loaded from disk
|
// Path is set only internally when tree is loaded from disk
|
||||||
pack.SetPath(filepath.Dir(currentpath))
|
pack.SetPath(filepath.Dir(currentpath))
|
||||||
@@ -74,13 +88,17 @@ func (r *CompilerRecipe) Load(path string) error {
|
|||||||
// Instead of rdeps, have a different tree for build deps.
|
// Instead of rdeps, have a different tree for build deps.
|
||||||
compileDefPath := pack.Rel(CompilerDefinitionFile)
|
compileDefPath := pack.Rel(CompilerDefinitionFile)
|
||||||
if helpers.Exists(compileDefPath) {
|
if helpers.Exists(compileDefPath) {
|
||||||
dat, err = ioutil.ReadFile(compileDefPath)
|
dat, err := ioutil.ReadFile(compileDefPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Error reading file "+currentpath)
|
return errors.Wrap(err,
|
||||||
|
"Error reading file "+CompilerDefinitionFile+" from "+
|
||||||
|
filepath.Dir(currentpath))
|
||||||
}
|
}
|
||||||
packbuild, err := pkg.DefaultPackageFromYaml(dat)
|
packbuild, err := pkg.DefaultPackageFromYaml(dat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Error reading yaml "+currentpath)
|
return errors.Wrap(err,
|
||||||
|
"Error reading yaml "+CompilerDefinitionFile+" from "+
|
||||||
|
filepath.Dir(currentpath))
|
||||||
}
|
}
|
||||||
pack.Requires(packbuild.GetRequires())
|
pack.Requires(packbuild.GetRequires())
|
||||||
pack.Conflicts(packbuild.GetConflicts())
|
pack.Conflicts(packbuild.GetConflicts())
|
||||||
@@ -103,4 +121,4 @@ func (r *CompilerRecipe) Load(path string) error {
|
|||||||
|
|
||||||
func (r *CompilerRecipe) GetDatabase() pkg.PackageDatabase { return r.Database }
|
func (r *CompilerRecipe) GetDatabase() pkg.PackageDatabase { return r.Database }
|
||||||
func (r *CompilerRecipe) WithDatabase(d pkg.PackageDatabase) { r.Database = d }
|
func (r *CompilerRecipe) WithDatabase(d pkg.PackageDatabase) { r.Database = d }
|
||||||
func (r *CompilerRecipe) GetSourcePath() string { return r.SourcePath }
|
func (r *CompilerRecipe) GetSourcePath() []string { return r.SourcePath }
|
||||||
|
@@ -40,7 +40,7 @@ func NewInstallerRecipe(db pkg.PackageDatabase) Builder {
|
|||||||
|
|
||||||
// InstallerRecipe is the "general" reciper for Trees
|
// InstallerRecipe is the "general" reciper for Trees
|
||||||
type InstallerRecipe struct {
|
type InstallerRecipe struct {
|
||||||
SourcePath string
|
SourcePath []string
|
||||||
Database pkg.PackageDatabase
|
Database pkg.PackageDatabase
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ func (r *InstallerRecipe) Load(path string) error {
|
|||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return err
|
// return err
|
||||||
// }
|
// }
|
||||||
r.SourcePath = path
|
r.SourcePath = append(r.SourcePath, path)
|
||||||
|
|
||||||
//r.Tree().SetPackageSet(pkg.NewBoltDatabase(tmpfile.Name()))
|
//r.Tree().SetPackageSet(pkg.NewBoltDatabase(tmpfile.Name()))
|
||||||
// TODO: Handle cleaning after? Cleanup implemented in GetPackageSet().Clean()
|
// TODO: Handle cleaning after? Cleanup implemented in GetPackageSet().Clean()
|
||||||
@@ -114,4 +114,4 @@ func (r *InstallerRecipe) Load(path string) error {
|
|||||||
|
|
||||||
func (r *InstallerRecipe) GetDatabase() pkg.PackageDatabase { return r.Database }
|
func (r *InstallerRecipe) GetDatabase() pkg.PackageDatabase { return r.Database }
|
||||||
func (r *InstallerRecipe) WithDatabase(d pkg.PackageDatabase) { r.Database = d }
|
func (r *InstallerRecipe) WithDatabase(d pkg.PackageDatabase) { r.Database = d }
|
||||||
func (r *InstallerRecipe) GetSourcePath() string { return r.SourcePath }
|
func (r *InstallerRecipe) GetSourcePath() []string { return r.SourcePath }
|
||||||
|
@@ -37,24 +37,32 @@ func NewGeneralRecipe(db pkg.PackageDatabase) Builder { return &Recipe{Database:
|
|||||||
|
|
||||||
// Recipe is the "general" reciper for Trees
|
// Recipe is the "general" reciper for Trees
|
||||||
type Recipe struct {
|
type Recipe struct {
|
||||||
SourcePath string
|
SourcePath []string
|
||||||
Database pkg.PackageDatabase
|
Database pkg.PackageDatabase
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Recipe) Save(path string) error {
|
func WriteDefinitionFile(p pkg.Package, definitionFilePath string) error {
|
||||||
|
|
||||||
for _, p := range r.Database.World() {
|
|
||||||
dir := filepath.Join(path, p.GetCategory(), p.GetName(), p.GetVersion())
|
|
||||||
os.MkdirAll(dir, os.ModePerm)
|
|
||||||
data, err := p.Yaml()
|
data, err := p.Yaml()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile(filepath.Join(dir, DefinitionFile), data, 0644)
|
err = ioutil.WriteFile(definitionFilePath, data, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Recipe) Save(path string) error {
|
||||||
|
for _, p := range r.Database.World() {
|
||||||
|
dir := filepath.Join(path, p.GetCategory(), p.GetName(), p.GetVersion())
|
||||||
|
os.MkdirAll(dir, os.ModePerm)
|
||||||
|
|
||||||
|
err := WriteDefinitionFile(p, filepath.Join(dir, DefinitionFile))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -65,7 +73,7 @@ func (r *Recipe) Load(path string) error {
|
|||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return err
|
// return err
|
||||||
// }
|
// }
|
||||||
r.SourcePath = path
|
r.SourcePath = append(r.SourcePath, path)
|
||||||
|
|
||||||
if r.Database == nil {
|
if r.Database == nil {
|
||||||
r.Database = pkg.NewInMemoryDatabase(false)
|
r.Database = pkg.NewInMemoryDatabase(false)
|
||||||
@@ -109,4 +117,4 @@ func (r *Recipe) Load(path string) error {
|
|||||||
|
|
||||||
func (r *Recipe) GetDatabase() pkg.PackageDatabase { return r.Database }
|
func (r *Recipe) GetDatabase() pkg.PackageDatabase { return r.Database }
|
||||||
func (r *Recipe) WithDatabase(d pkg.PackageDatabase) { r.Database = d }
|
func (r *Recipe) WithDatabase(d pkg.PackageDatabase) { r.Database = d }
|
||||||
func (r *Recipe) GetSourcePath() string { return r.SourcePath }
|
func (r *Recipe) GetSourcePath() []string { return r.SourcePath }
|
||||||
|
@@ -96,4 +96,77 @@ var _ = Describe("Tree", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("Multiple trees", func() {
|
||||||
|
It("Merges", func() {
|
||||||
|
for index := 0; index < 300; index++ { // Just to make sure we don't have false positives
|
||||||
|
db := pkg.NewInMemoryDatabase(false)
|
||||||
|
generalRecipe := NewCompilerRecipe(db)
|
||||||
|
tmpdir, err := ioutil.TempDir("", "package")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
defer os.RemoveAll(tmpdir) // clean up
|
||||||
|
|
||||||
|
err = generalRecipe.Load("../../tests/fixtures/buildableseed")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(len(generalRecipe.GetDatabase().World())).To(Equal(4))
|
||||||
|
|
||||||
|
err = generalRecipe.Load("../../tests/fixtures/layers")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(len(generalRecipe.GetDatabase().World())).To(Equal(6))
|
||||||
|
|
||||||
|
extra, err := generalRecipe.GetDatabase().FindPackage(&pkg.DefaultPackage{Name: "extra", Category: "layer", Version: "1.0"})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(extra).ToNot(BeNil())
|
||||||
|
|
||||||
|
D, err := generalRecipe.GetDatabase().FindPackage(&pkg.DefaultPackage{Name: "d", Category: "test", Version: "1.0"})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(D.GetRequires()[0].GetName()).To(Equal("c"))
|
||||||
|
CfromD, err := generalRecipe.GetDatabase().FindPackage(D.GetRequires()[0])
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(len(CfromD.GetRequires()) != 0).To(BeTrue())
|
||||||
|
Expect(CfromD.GetRequires()[0].GetName()).To(Equal("b"))
|
||||||
|
|
||||||
|
s := solver.NewSolver(pkg.NewInMemoryDatabase(false), generalRecipe.GetDatabase(), db)
|
||||||
|
|
||||||
|
Dd, err := generalRecipe.GetDatabase().FindPackage(&pkg.DefaultPackage{Name: "d", Category: "test", Version: "1.0"})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
solution, err := s.Install([]pkg.Package{Dd})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
solution = solution.Order(generalRecipe.GetDatabase(), Dd.GetFingerPrint())
|
||||||
|
pack, err := generalRecipe.GetDatabase().FindPackage(&pkg.DefaultPackage{Name: "a", Category: "test", Version: "1.0"})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
base, err := generalRecipe.GetDatabase().FindPackage(&pkg.DefaultPackage{Name: "base", Category: "layer", Version: "0.2"})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(solution).To(ContainElement(solver.PackageAssert{Package: pack.(*pkg.DefaultPackage), Value: false}))
|
||||||
|
Expect(solution).To(ContainElement(solver.PackageAssert{Package: D.(*pkg.DefaultPackage), Value: true}))
|
||||||
|
Expect(solution).To(ContainElement(solver.PackageAssert{Package: extra.(*pkg.DefaultPackage), Value: false}))
|
||||||
|
Expect(solution).To(ContainElement(solver.PackageAssert{Package: base.(*pkg.DefaultPackage), Value: false}))
|
||||||
|
Expect(len(solution)).To(Equal(6))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Simple tree with labels", func() {
|
||||||
|
It("Read tree with labels", func() {
|
||||||
|
db := pkg.NewInMemoryDatabase(false)
|
||||||
|
generalRecipe := NewCompilerRecipe(db)
|
||||||
|
|
||||||
|
err := generalRecipe.Load("../../tests/fixtures/labels")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(len(generalRecipe.GetDatabase().World())).To(Equal(1))
|
||||||
|
pack, err := generalRecipe.GetDatabase().FindPackage(&pkg.DefaultPackage{Name: "pkgA", Category: "test", Version: "0.1"})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(pack.HasLabel("label1")).To(Equal(true))
|
||||||
|
Expect(pack.HasLabel("label3")).To(Equal(false))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
3
tests/fixtures/labels/pkgA/0.1/build.yaml
vendored
Normal file
3
tests/fixtures/labels/pkgA/0.1/build.yaml
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
image: "alpine"
|
||||||
|
steps:
|
||||||
|
- echo "test" > /file1
|
6
tests/fixtures/labels/pkgA/0.1/definition.yaml
vendored
Normal file
6
tests/fixtures/labels/pkgA/0.1/definition.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
category: "test"
|
||||||
|
name: "pkgA"
|
||||||
|
version: "0.1"
|
||||||
|
labels:
|
||||||
|
label1: "value1"
|
||||||
|
label2: "value2"
|
2
tests/fixtures/layered/layer/base/build.yaml
vendored
2
tests/fixtures/layered/layer/base/build.yaml
vendored
@@ -1,2 +1,2 @@
|
|||||||
image: "golang:alpine"
|
image: "golang"
|
||||||
unpack: true
|
unpack: true
|
||||||
|
@@ -3,8 +3,8 @@ requires:
|
|||||||
name: "base"
|
name: "base"
|
||||||
version: "0.1"
|
version: "0.1"
|
||||||
prelude:
|
prelude:
|
||||||
- apk update
|
- apt-get update
|
||||||
- apk add git
|
- apt-get install git
|
||||||
- rm -rf /go/pkg/mod
|
- rm -rf /go/pkg/mod
|
||||||
steps:
|
steps:
|
||||||
- echo test > /extra-layer
|
- echo test > /extra-layer
|
||||||
|
@@ -3,7 +3,7 @@ requires:
|
|||||||
name: "extra"
|
name: "extra"
|
||||||
version: "0.1"
|
version: "0.1"
|
||||||
prelude:
|
prelude:
|
||||||
- apk add git
|
- apt-get install git
|
||||||
- mkdir -p /go/src/github.com/Sabayon/
|
- mkdir -p /go/src/github.com/Sabayon/
|
||||||
- git clone https://github.com/Sabayon/pkgs-checker /go/src/github.com/Sabayon/pkgs-checker
|
- git clone https://github.com/Sabayon/pkgs-checker /go/src/github.com/Sabayon/pkgs-checker
|
||||||
steps:
|
steps:
|
||||||
|
5
vendor/github.com/Sabayon/pkgs-checker/pkg/gentoo/pkg.go
generated
vendored
5
vendor/github.com/Sabayon/pkgs-checker/pkg/gentoo/pkg.go
generated
vendored
@@ -55,8 +55,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RegexCatString = `(^[a-z]+[0-9]*[a-z]*[-][a-z]+[0-9]*[a-z]*|^virtual)`
|
RegexCatString = `(^[a-z]+[0-9]*[a-z]*[-]*[a-z]+[0-9]*[a-z]*|^virtual)`
|
||||||
RegexPkgNameString = `([a-z]+[0-9a-zA-Z\-[+]*]*[+]*)`
|
RegexPkgNameString = `([a-zA-Z]*[0-9a-zA-Z\.\-_]*[a-zA-Z0-9]+|[a-zA-Z]+[+]+)`
|
||||||
)
|
)
|
||||||
|
|
||||||
type GentooPackage struct {
|
type GentooPackage struct {
|
||||||
@@ -69,6 +69,7 @@ type GentooPackage struct {
|
|||||||
Condition PackageCond
|
Condition PackageCond
|
||||||
Repository string `json:"repository",omitempty"`
|
Repository string `json:"repository",omitempty"`
|
||||||
UseFlags []string `json:"use_flags",omitempty"`
|
UseFlags []string `json:"use_flags",omitempty"`
|
||||||
|
License string `json:"license",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *GentooPackage) String() string {
|
func (p *GentooPackage) String() string {
|
||||||
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@@ -23,7 +23,7 @@ github.com/Microsoft/hcsshim/internal/longpath
|
|||||||
github.com/Microsoft/hcsshim/internal/safefile
|
github.com/Microsoft/hcsshim/internal/safefile
|
||||||
# github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5
|
# github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5
|
||||||
github.com/Nvveen/Gotty
|
github.com/Nvveen/Gotty
|
||||||
# github.com/Sabayon/pkgs-checker v0.5.1-0.20200221202320-073693f2c657
|
# github.com/Sabayon/pkgs-checker v0.6.2-0.20200315232328-b6efed54b4b1
|
||||||
github.com/Sabayon/pkgs-checker/pkg/gentoo
|
github.com/Sabayon/pkgs-checker/pkg/gentoo
|
||||||
# github.com/apex/log v1.1.1
|
# github.com/apex/log v1.1.1
|
||||||
github.com/apex/log
|
github.com/apex/log
|
||||||
|
Reference in New Issue
Block a user