mirror of
https://github.com/mudler/luet.git
synced 2025-09-02 07:45:02 +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
|
||||
|
||||
const (
|
||||
LuetCLIVersion = "0.6.4"
|
||||
LuetCLIVersion = "0.7"
|
||||
LuetEnvPrefix = "LUET"
|
||||
)
|
||||
|
||||
@@ -122,7 +122,7 @@ func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
pflags := RootCmd.PersistentFlags()
|
||||
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")
|
||||
|
||||
u, err := user.Current()
|
||||
@@ -137,7 +137,7 @@ func init() {
|
||||
pflags.Int("concurrency", runtime.NumCPU(), "Concurrency")
|
||||
|
||||
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.fatal_warnings", pflags.Lookup("fatal"))
|
||||
}
|
||||
@@ -154,7 +154,6 @@ func initConfig() {
|
||||
viper.SetConfigType("yaml")
|
||||
viper.SetConfigName(".luet") // name of config file (without extension)
|
||||
if cfgFile != "" { // enable ability to specify config file via flag
|
||||
Info(">>> cfgFile: ", cfgFile)
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
viper.AddConfigPath(dir)
|
||||
|
@@ -17,7 +17,6 @@ package cmd
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
. "github.com/mudler/luet/pkg/config"
|
||||
installer "github.com/mudler/luet/pkg/installer"
|
||||
@@ -51,6 +50,8 @@ var searchCmd = &cobra.Command{
|
||||
discount := LuetCfg.Viper.GetFloat64("solver.discount")
|
||||
rate := LuetCfg.Viper.GetFloat64("solver.rate")
|
||||
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().LearnRate = float32(rate)
|
||||
@@ -70,8 +71,12 @@ var searchCmd = &cobra.Command{
|
||||
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)
|
||||
synced, err := inst.SyncRepositories(false)
|
||||
if err != nil {
|
||||
@@ -80,7 +85,14 @@ var searchCmd = &cobra.Command{
|
||||
|
||||
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 {
|
||||
Info(":package:", m.Package.GetCategory(), m.Package.GetName(),
|
||||
m.Package.GetVersion(), "repository:", m.Repo.GetName())
|
||||
@@ -94,14 +106,25 @@ var searchCmd = &cobra.Command{
|
||||
systemDB = pkg.NewInMemoryDatabase(true)
|
||||
}
|
||||
system := &installer.System{Database: systemDB, Target: LuetCfg.GetSystem().Rootfs}
|
||||
var term = regexp.MustCompile(args[0])
|
||||
|
||||
for _, k := range system.Database.GetPackages() {
|
||||
pack, err := system.Database.GetPackage(k)
|
||||
if err == nil && term.MatchString(pack.GetName()) {
|
||||
Info(":package:", pack.GetCategory(), pack.GetName(), pack.GetVersion())
|
||||
}
|
||||
var err error
|
||||
iMatches := []pkg.Package{}
|
||||
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())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
@@ -119,5 +142,7 @@ func init() {
|
||||
searchCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
|
||||
searchCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
|
||||
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)
|
||||
}
|
||||
|
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 (
|
||||
github.com/DataDog/zstd v1.4.4 // 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/briandowns/spinner v1.7.0
|
||||
github.com/cavaliercoder/grab v2.0.0+incompatible
|
||||
@@ -22,6 +22,7 @@ require (
|
||||
github.com/logrusorgru/aurora v0.0.0-20190417123914-21d75270181e
|
||||
github.com/marcsauter/single v0.0.0-20181104081128-f8bf46f26ec0
|
||||
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/onsi/ginkgo v1.10.1
|
||||
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.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.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/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
||||
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.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||
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/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
targetAssertion := p.GetSourceAssertion().Search(p.GetPackage().GetFingerPrint())
|
||||
targetPackageHash := cs.ImageRepository + ":" + targetAssertion.Hash.PackageHash
|
||||
|
||||
// - 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
|
||||
if p.GetImage() != "" {
|
||||
if p.ImageUnpack() { // If it is just an entire image, create a package from it
|
||||
return cs.packageFromImage(p, "", keepPermissions, cs.KeepImg, concurrency)
|
||||
}
|
||||
|
||||
return cs.compileWithImage(p.GetImage(), "", "", concurrency, keepPermissions, cs.KeepImg, p)
|
||||
return cs.compileWithImage(p.GetImage(), "", targetPackageHash, 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
|
||||
@@ -595,20 +518,6 @@ func (cs *LuetCompiler) compile(concurrency int, keepPermissions bool, p Compila
|
||||
|
||||
lastHash = currentPackageImageHash
|
||||
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")
|
||||
artifact, err := cs.compileWithImage(compileSpec.GetImage(), buildImageHash, currentPackageImageHash, concurrency, keepPermissions, cs.KeepImg, compileSpec)
|
||||
if err != nil {
|
||||
@@ -636,7 +545,7 @@ func (cs *LuetCompiler) compile(concurrency int, keepPermissions bool, p Compila
|
||||
|
||||
if !cs.Options.OnlyDeps {
|
||||
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 {
|
||||
return artifact, err
|
||||
}
|
||||
|
@@ -17,12 +17,29 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
_gentoo "github.com/Sabayon/pkgs-checker/pkg/gentoo"
|
||||
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) {
|
||||
gp, err := _gentoo.ParsePackageStr(p)
|
||||
if err != nil {
|
||||
|
@@ -337,45 +337,72 @@ func (l *LuetInstaller) install(syncedRepos Repositories, cp []pkg.Package, s *S
|
||||
}
|
||||
executedFinalizer := map[string]bool{}
|
||||
|
||||
// TODO: Lower those errors as warning
|
||||
for _, w := range p {
|
||||
// Finalizers needs to run in order and in sequence.
|
||||
ordered := solution.Order(allRepos, w.GetFingerPrint())
|
||||
ORDER:
|
||||
for _, ass := range ordered {
|
||||
if ass.Value {
|
||||
if !l.Options.NoDeps {
|
||||
// TODO: Lower those errors as warning
|
||||
for _, w := range p {
|
||||
// Finalizers needs to run in order and in sequence.
|
||||
ordered := solution.Order(allRepos, w.GetFingerPrint())
|
||||
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())
|
||||
}
|
||||
if helpers.Exists(treePackage.Rel(tree.FinalizerFile)) {
|
||||
Info("Executing finalizer for " + ass.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))
|
||||
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
|
||||
}
|
||||
if _, exists := executedFinalizer[ass.Package.GetFingerPrint()]; !exists {
|
||||
finalizer, err := NewLuetFinalizerFromYaml(finalizerRaw)
|
||||
|
||||
treePackage, err := installed.Repository.GetTree().GetDatabase().FindPackage(ass.Package)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error getting package "+ass.Package.HumanReadableString())
|
||||
}
|
||||
if helpers.Exists(treePackage.Rel(tree.FinalizerFile)) {
|
||||
Info("Executing finalizer for " + ass.Package.HumanReadableString())
|
||||
finalizerRaw, err := ioutil.ReadFile(treePackage.Rel(tree.FinalizerFile))
|
||||
if err != nil && !l.Options.Force {
|
||||
return errors.Wrap(err, "Error reading finalizer "+treePackage.Rel(tree.FinalizerFile))
|
||||
return errors.Wrap(err, "Error reading file "+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))
|
||||
if _, exists := executedFinalizer[ass.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[ass.Package.GetFingerPrint()] = true
|
||||
}
|
||||
executedFinalizer[ass.Package.GetFingerPrint()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} 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
|
||||
|
||||
|
@@ -21,7 +21,6 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -69,6 +68,19 @@ type LuetSystemRepositorySerialized struct {
|
||||
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) {
|
||||
|
||||
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)
|
||||
var term = regexp.MustCompile(s)
|
||||
var matches []PackageMatch
|
||||
var err error
|
||||
|
||||
for _, r := range re {
|
||||
for _, pack := range r.GetTree().GetDatabase().World() {
|
||||
if term.MatchString(pack.GetName()) {
|
||||
var repoMatches []pkg.Package
|
||||
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})
|
||||
}
|
||||
}
|
||||
@@ -569,3 +591,15 @@ func (re Repositories) Search(s string) []PackageMatch {
|
||||
|
||||
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
|
||||
|
||||
FindPackageCandidate(p Package) (Package, error)
|
||||
FindPackageLabel(labelKey string) ([]Package, error)
|
||||
FindPackageLabelMatch(pattern string) ([]Package, error)
|
||||
FindPackageMatch(pattern string) ([]Package, error)
|
||||
}
|
||||
|
||||
type PackageFile struct {
|
||||
|
@@ -18,6 +18,7 @@ package pkg
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -384,3 +385,61 @@ func (db *BoltDatabase) FindPackageVersions(p Package) ([]Package, error) {
|
||||
}
|
||||
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 (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -358,3 +359,62 @@ func (db *InMemoryDatabase) FindPackageCandidate(p Package) (Package, error) {
|
||||
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"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
// . "github.com/mudler/luet/pkg/logger"
|
||||
|
||||
gentoo "github.com/Sabayon/pkgs-checker/pkg/gentoo"
|
||||
"github.com/crillab/gophersat/bf"
|
||||
"github.com/ghodss/yaml"
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/jinzhu/copier"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Package is a package interface (TBD)
|
||||
@@ -47,6 +48,7 @@ type Package interface {
|
||||
Requires([]*DefaultPackage) Package
|
||||
Conflicts([]*DefaultPackage) Package
|
||||
Revdeps(PackageDatabase) []Package
|
||||
LabelDeps(PackageDatabase, string) []Package
|
||||
|
||||
GetProvides() []*DefaultPackage
|
||||
SetProvides([]*DefaultPackage) Package
|
||||
@@ -62,7 +64,7 @@ type Package interface {
|
||||
GetVersion() string
|
||||
RequiresContains(PackageDatabase, Package) (bool, error)
|
||||
Matches(m Package) bool
|
||||
Bigger(m Package) bool
|
||||
BumpBuildVersion() error
|
||||
|
||||
AddUse(use string)
|
||||
RemoveUse(use string)
|
||||
@@ -84,6 +86,11 @@ type Package interface {
|
||||
SetLicense(string)
|
||||
GetLicense() string
|
||||
|
||||
AddLabel(string, string)
|
||||
GetLabels() map[string]string
|
||||
HasLabel(string) bool
|
||||
MatchLabel(*regexp.Regexp) bool
|
||||
|
||||
IsSelector() bool
|
||||
VersionMatchSelector(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 string `json:"path,omitempty"`
|
||||
|
||||
Description string `json:"description"`
|
||||
Uri []string `json:"uri"`
|
||||
License string `json:"license"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Uri []string `json:"uri,omitempty"`
|
||||
License string `json:"license,omitempty"`
|
||||
|
||||
Labels map[string]string `json:labels,omitempty`
|
||||
}
|
||||
|
||||
// State represent the package state
|
||||
@@ -160,7 +169,13 @@ type State string
|
||||
|
||||
// NewPackage returns a new package
|
||||
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 {
|
||||
@@ -218,6 +233,28 @@ func (p *DefaultPackage) IsSelector() bool {
|
||||
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
|
||||
func (p *DefaultPackage) AddUse(use string) {
|
||||
for _, v := range p.UseFlags {
|
||||
@@ -302,6 +339,12 @@ func (p *DefaultPackage) SetCategory(s string) {
|
||||
func (p *DefaultPackage) GetUses() []string {
|
||||
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 {
|
||||
return p.Provides
|
||||
}
|
||||
@@ -334,13 +377,6 @@ func (p *DefaultPackage) Matches(m Package) bool {
|
||||
}
|
||||
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) {
|
||||
var versionsInWorld []Package
|
||||
@@ -379,6 +415,19 @@ func (p *DefaultPackage) Revdeps(definitiondb PackageDatabase) []Package {
|
||||
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) {
|
||||
return db.GetPackage(ID)
|
||||
}
|
||||
@@ -408,29 +457,7 @@ func (pack *DefaultPackage) RequiresContains(definitiondb PackageDatabase, s Pac
|
||||
|
||||
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 {
|
||||
var versionsMap map[string]Package = make(map[string]Package)
|
||||
if len(set) == 0 {
|
||||
@@ -637,3 +664,89 @@ func (p *DefaultPackage) Explain() {
|
||||
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
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
. "github.com/mudler/luet/pkg/package"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "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() {
|
||||
a := NewPackage("A", ">=1.0", []*DefaultPackage{}, []*DefaultPackage{})
|
||||
a.SetDescription("Description A")
|
||||
@@ -252,4 +282,69 @@ var _ = Describe("Package", func() {
|
||||
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 ans bool
|
||||
var err error
|
||||
var sanitizedSelectorVersion, sanitizedIVersion string
|
||||
|
||||
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 {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if i.Version != "" {
|
||||
v2, err = version.NewVersion(i.Version)
|
||||
sanitizedIVersion = strings.ReplaceAll(i.Version, "_", "-")
|
||||
v2, err = version.NewVersion(sanitizedIVersion)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -290,7 +295,7 @@ func PackageAdmit(selector, i PkgVersionSelector) (bool, error) {
|
||||
// TODO: case of 7.3* where 7.30 is accepted.
|
||||
if v1 != nil && v2 != nil {
|
||||
segments := v1.Segments()
|
||||
n := strings.Count(selector.Version, ".")
|
||||
n := strings.Count(sanitizedIVersion, ".")
|
||||
switch n {
|
||||
case 0:
|
||||
segments[0]++
|
||||
@@ -303,7 +308,7 @@ func PackageAdmit(selector, i PkgVersionSelector) (bool, error) {
|
||||
}
|
||||
nextVersion := strings.Trim(strings.Replace(fmt.Sprint(segments), " ", ".", -1), "[]")
|
||||
constraints, err := version.NewConstraint(
|
||||
fmt.Sprintf(">= %s, < %s", selector.Version, nextVersion),
|
||||
fmt.Sprintf(">= %s, < %s", sanitizedSelectorVersion, nextVersion),
|
||||
)
|
||||
if err != nil {
|
||||
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() {
|
||||
v1, err := ParseVersion(">=0.0.20190406.4.9.172-r1")
|
||||
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() {
|
||||
gp, err := gentoo.ParsePackageStr("=layer/build-1.0")
|
||||
var cond gentoo.PackageCond = gentoo.PkgCondEqual
|
||||
|
@@ -35,7 +35,7 @@ func LoadRepositories(c *LuetConfig) error {
|
||||
|
||||
files, err := ioutil.ReadDir(rdir)
|
||||
if err != nil {
|
||||
Warning("Skip dir", rdir, ":", err.Error())
|
||||
Debug("Skip dir", rdir, ":", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
|
@@ -363,12 +363,19 @@ var _ = Describe("Solver", func() {
|
||||
It("Selects best version", func() {
|
||||
|
||||
E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
E.SetCategory("test")
|
||||
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
C.SetCategory("test")
|
||||
D2 := pkg.NewPackage("D", "1.9", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
D2.SetCategory("test")
|
||||
D := pkg.NewPackage("D", "1.8", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
D.SetCategory("test")
|
||||
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{})
|
||||
A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "D", Version: ">=1.0"}}, []*pkg.DefaultPackage{})
|
||||
D1.SetCategory("test")
|
||||
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} {
|
||||
_, err := dbDefinitions.CreatePackage(p)
|
||||
@@ -397,16 +404,23 @@ var _ = Describe("Solver", func() {
|
||||
It("Support provides", func() {
|
||||
|
||||
E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
|
||||
E.SetCategory("test")
|
||||
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
C.SetCategory("test")
|
||||
D2 := pkg.NewPackage("D", "1.9", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
D2.SetCategory("test")
|
||||
D := pkg.NewPackage("D", "1.8", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
|
||||
D.SetCategory("test")
|
||||
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{})
|
||||
A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "D", Version: ">=1.0"}}, []*pkg.DefaultPackage{})
|
||||
D1.SetCategory("test")
|
||||
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"}})
|
||||
A2 := pkg.NewPackage("A", "1.3", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "E", Version: ""}}, []*pkg.DefaultPackage{})
|
||||
D2.SetProvides([]*pkg.DefaultPackage{{Name: "E", Category: "test"}})
|
||||
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} {
|
||||
_, err := dbDefinitions.CreatePackage(p)
|
||||
|
@@ -26,5 +26,5 @@ type Builder interface {
|
||||
GetDatabase() 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}}
|
||||
}
|
||||
|
||||
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
|
||||
type CompilerRecipe struct {
|
||||
Recipe
|
||||
@@ -45,7 +59,7 @@ type CompilerRecipe struct {
|
||||
|
||||
func (r *CompilerRecipe) Load(path string) error {
|
||||
|
||||
r.SourcePath = path
|
||||
r.SourcePath = append(r.SourcePath, path)
|
||||
//tmpfile, err := ioutil.TempFile("", "luet")
|
||||
//if err != nil {
|
||||
// return err
|
||||
@@ -56,17 +70,17 @@ func (r *CompilerRecipe) Load(path string) error {
|
||||
// the function that handles each file or dir
|
||||
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 {
|
||||
return nil // Skip with no errors
|
||||
}
|
||||
|
||||
dat, err := ioutil.ReadFile(currentpath)
|
||||
pack, err := ReadDefinitionFile(currentpath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error reading file "+currentpath)
|
||||
}
|
||||
pack, err := pkg.DefaultPackageFromYaml(dat)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error reading yaml "+currentpath)
|
||||
return err
|
||||
}
|
||||
// Path is set only internally when tree is loaded from disk
|
||||
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.
|
||||
compileDefPath := pack.Rel(CompilerDefinitionFile)
|
||||
if helpers.Exists(compileDefPath) {
|
||||
dat, err = ioutil.ReadFile(compileDefPath)
|
||||
dat, err := ioutil.ReadFile(compileDefPath)
|
||||
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)
|
||||
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.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) 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
|
||||
type InstallerRecipe struct {
|
||||
SourcePath string
|
||||
SourcePath []string
|
||||
Database pkg.PackageDatabase
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func (r *InstallerRecipe) Load(path string) error {
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
r.SourcePath = path
|
||||
r.SourcePath = append(r.SourcePath, path)
|
||||
|
||||
//r.Tree().SetPackageSet(pkg.NewBoltDatabase(tmpfile.Name()))
|
||||
// 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) 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
|
||||
type Recipe struct {
|
||||
SourcePath string
|
||||
SourcePath []string
|
||||
Database pkg.PackageDatabase
|
||||
}
|
||||
|
||||
func (r *Recipe) Save(path string) error {
|
||||
func WriteDefinitionFile(p pkg.Package, definitionFilePath string) error {
|
||||
data, err := p.Yaml()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(definitionFilePath, data, 0644)
|
||||
if err != nil {
|
||||
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)
|
||||
data, err := p.Yaml()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(dir, DefinitionFile), data, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := WriteDefinitionFile(p, filepath.Join(dir, DefinitionFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -65,7 +73,7 @@ func (r *Recipe) Load(path string) error {
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
r.SourcePath = path
|
||||
r.SourcePath = append(r.SourcePath, path)
|
||||
|
||||
if r.Database == nil {
|
||||
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) 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
|
||||
|
@@ -3,8 +3,8 @@ requires:
|
||||
name: "base"
|
||||
version: "0.1"
|
||||
prelude:
|
||||
- apk update
|
||||
- apk add git
|
||||
- apt-get update
|
||||
- apt-get install git
|
||||
- rm -rf /go/pkg/mod
|
||||
steps:
|
||||
- echo test > /extra-layer
|
||||
|
@@ -3,7 +3,7 @@ requires:
|
||||
name: "extra"
|
||||
version: "0.1"
|
||||
prelude:
|
||||
- apk add git
|
||||
- apt-get install git
|
||||
- mkdir -p /go/src/github.com/Sabayon/
|
||||
- git clone https://github.com/Sabayon/pkgs-checker /go/src/github.com/Sabayon/pkgs-checker
|
||||
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 (
|
||||
RegexCatString = `(^[a-z]+[0-9]*[a-z]*[-][a-z]+[0-9]*[a-z]*|^virtual)`
|
||||
RegexPkgNameString = `([a-z]+[0-9a-zA-Z\-[+]*]*[+]*)`
|
||||
RegexCatString = `(^[a-z]+[0-9]*[a-z]*[-]*[a-z]+[0-9]*[a-z]*|^virtual)`
|
||||
RegexPkgNameString = `([a-zA-Z]*[0-9a-zA-Z\.\-_]*[a-zA-Z0-9]+|[a-zA-Z]+[+]+)`
|
||||
)
|
||||
|
||||
type GentooPackage struct {
|
||||
@@ -69,6 +69,7 @@ type GentooPackage struct {
|
||||
Condition PackageCond
|
||||
Repository string `json:"repository",omitempty"`
|
||||
UseFlags []string `json:"use_flags",omitempty"`
|
||||
License string `json:"license",omitempty"`
|
||||
}
|
||||
|
||||
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/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5
|
||||
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/apex/log v1.1.1
|
||||
github.com/apex/log
|
||||
|
Reference in New Issue
Block a user