diff --git a/backends/backends.go b/backends/backends.go index 69a4771..1ebc8dd 100644 --- a/backends/backends.go +++ b/backends/backends.go @@ -1,10 +1,11 @@ package backends import ( + "errors" + "github.com/Fishwaldo/go-yocto/backends/kde" "github.com/Fishwaldo/go-yocto/source" "github.com/Fishwaldo/go-yocto/utils" - ) @@ -13,7 +14,8 @@ type Backend interface { Init() error LoadCache() error LoadSource() error - SearchSource(keyword string) (source []source.RecipeSource, err error) + SearchSource(keyword string) (source []source.RecipeSource, err error) + GetRecipe(identifier string) (*source.RecipeSource, error) Ready() bool } @@ -77,4 +79,19 @@ func SearchSource(be string, keyword string) (sources []source.RecipeSource, err } } return sources, nil +} + +func GetRecipe(be string, identifier string) (source *source.RecipeSource, err error) { + utils.Logger.Trace("Getting Recipe", utils.Logger.Args("backend", be, "identifier", identifier)) + if be, ok := Backends[be]; ok { + if source, err := be.GetRecipe(identifier); err != nil { + utils.Logger.Error("Failed to Get Recipe", utils.Logger.Args("backend", be.GetName(), "identifier", identifier, "error", err)) + return nil, errors.New("Failed to Get Recipe") + } else { + return source, nil + } + } else { + utils.Logger.Error("Backend not found", utils.Logger.Args("backend", be)) + } + return nil, errors.New("Backend not found") } \ No newline at end of file diff --git a/backends/kde/kde.go b/backends/kde/kde.go index e50ccdd..6800b45 100644 --- a/backends/kde/kde.go +++ b/backends/kde/kde.go @@ -1,15 +1,20 @@ package kde import ( + "bufio" "encoding/base64" "encoding/json" - + "errors" "io/ioutil" "net/url" "os" + "path" "path/filepath" + "regexp" "strings" + // "fmt" + "github.com/Fishwaldo/go-yocto/parsers" "github.com/Fishwaldo/go-yocto/repo" "github.com/Fishwaldo/go-yocto/source" @@ -21,24 +26,14 @@ import ( "github.com/spf13/viper" "github.com/xanzy/go-gitlab" "gopkg.in/yaml.v3" + // "github.com/davecgh/go-spew/spew" ) - -type Deps struct { - Dependencies []struct { - On []string - Require map[string]string - } `yaml:"Dependencies"` - Options interface{} `yaml:"Options"` - Environment interface{} `yaml:"Environment"` -} - type Project struct { source.RecipeSource `yaml:",inline"` ProjectPath string Repoactive bool Repopath string - Identifier string Hasrepo bool Source string Bugzilla struct { @@ -47,12 +42,14 @@ type Project struct { DNUlegacyproduct string `yaml:"__do_not_use-legacy-product"` } Topics []string + MetaData map[string]map[string]interface{} } type KDEBe struct { MetaDataRepo repo.Repo br map[string]map[string]string pr map[string]Project + dep map[string][]string ready bool } @@ -103,6 +100,7 @@ func (l *KDEBe) LoadSource() (err error) { } maps.Clear(l.br) maps.Clear(l.pr) + maps.Clear(l.dep) err = l.parseMetadata() if (err != nil) { utils.Logger.Error("Failed to parse metadata", utils.Logger.Args("error", err)) @@ -113,16 +111,18 @@ func (l *KDEBe) LoadSource() (err error) { func (l *KDEBe) parseMetadata() (err error) { - l.pr = make(map[string]Project) utils.Logger.Trace("Parsing metadata", utils.Logger.Args("layer", l.GetName())) + + l.pr = make(map[string]Project) + l.br = make(map[string]map[string]string) + l.dep = make(map[string][]string) + brfile, err := ioutil.ReadFile(l.getDir() + "/branch-rules.yml") if err != nil { utils.Logger.Error("Failed to read branch-rules.yaml", utils.Logger.Args("error", err)) os.Exit(-1) } - l.br = make(map[string]map[string]string) - err = yaml.Unmarshal(brfile, &l.br) if err != nil { utils.Logger.Error("Failed to unmarshal branch-rules.yaml", utils.Logger.Args("error", err)) @@ -140,6 +140,22 @@ func (l *KDEBe) parseMetadata() (err error) { os.Exit(-1) } + /* parse dependancy file dependencies/dependency-data-kf5-qt5 */ + depsfile, err := os.Open(l.getDir() + "/dependencies/dependency-data-kf5-qt5") + if err != nil { + utils.Logger.Error("Failed to open dependency file", utils.Logger.Args("error", err)) + } + defer depsfile.Close() + scanner := bufio.NewScanner(depsfile) + var entry = regexp.MustCompile(`^(.*):.(.*)$`) + for scanner.Scan() { + if entry.MatchString(scanner.Text()) { + match := entry.FindStringSubmatch(scanner.Text()) + l.dep[match[1]] = append(l.dep[match[1]], match[2]) + } + } + + /* now parse the directory */ files := findmetdata(l.getDir()) p, _ := pterm.DefaultProgressbar.WithTotal(len(files)).WithTitle("Parsing Metadata...").Start() @@ -161,7 +177,7 @@ func (l *KDEBe) parseMetadata() (err error) { data.MetaData = make(map[string]map[string]interface{}) data.MetaData["branch-rules"] = make(map[string]interface{}) data.MetaData["branch-rules"]["branch"] = utils.Config.KDEConfig.DefaultBranch - data.RecipeSource.Backend = l.GetName() + data.RecipeSource.BackendID = l.GetName() data.RecipeSource.Url, _ = url.JoinPath(utils.Config.KDEConfig.KDEGitLabURL, data.Repopath) /* find out which branch this is in... */ for project, branch := range l.br[utils.Config.KDEConfig.Release] { @@ -171,36 +187,11 @@ func (l *KDEBe) parseMetadata() (err error) { break } } - - /* get the .kde-ci.yml for dependencies */ gf := &gitlab.GetFileOptions{ Ref: gitlab.String(data.MetaData["branch-rules"]["branch"].(string)), } - f, res, err := gl.RepositoryFiles.GetFile(data.Repopath, ".kde-ci.yml", gf) - if err != nil { - if res.StatusCode != 404 { - utils.Logger.Error("Failed to get .kde-ci.yml", utils.Logger.Args("error", err)) - } - } else { - /* now parse the .kde-ci.yml */ - var deps Deps - content, err := base64.StdEncoding.DecodeString(f.Content) - if err != nil { - utils.Logger.Error("Failed to decode .kde-ci.yml", utils.Logger.Args("error", err)) - } else { - ymldeps := yaml.NewDecoder(strings.NewReader(string(content))) - ymldeps.KnownFields(false); - err = ymldeps.Decode(&deps) - if err != nil { - utils.Logger.Error("Failed to unmarshal .kde-ci.yml", utils.Logger.Args("error", err, "project", data.Repopath)) - } else { - data.MetaData["kde-ci"] = make(map[string]interface{}) - data.MetaData["kde-ci"]["dependencies"] = deps - } - } - } /* now get appstream if it exists */ - f, res, err = gl.RepositoryFiles.GetFile(data.Repopath, "org.kde." + data.Identifier + ".appdata.xml", gf) + f, res, err := gl.RepositoryFiles.GetFile(data.Repopath, "org.kde." + data.Identifier + ".appdata.xml", gf) if err != nil { if res.StatusCode != 404 { utils.Logger.Error("Failed to get appstream", utils.Logger.Args("error", err)) @@ -240,6 +231,17 @@ func (l *KDEBe) parseMetadata() (err error) { utils.Logger.Error("Failed to write metadata", utils.Logger.Args("error", err)) os.Exit(-1) } + depcache, err := json.Marshal(l.dep) + if err != nil { + utils.Logger.Error("Failed to marshal dependancy metadata", utils.Logger.Args("error", err)) + os.Exit(-1) + } + err = ioutil.WriteFile(utils.Config.BaseDir + "/" + l.GetName() + "-dep-cache.json", depcache, 0644) + if err != nil { + utils.Logger.Error("Failed to write dependancy metadata", utils.Logger.Args("error", err)) + os.Exit(-1) + } + brcache, err := json.Marshal(l.br) if err != nil { utils.Logger.Error("Failed to marshal branch metadata", utils.Logger.Args("error", err)) @@ -250,6 +252,10 @@ func (l *KDEBe) parseMetadata() (err error) { utils.Logger.Error("Failed to write branch metadata", utils.Logger.Args("error", err)) os.Exit(-1) } + if err := RefreshDownloadLocations(); err != nil { + utils.Logger.Error("Failed to refresh download locations", utils.Logger.Args("error", err)) + os.Exit(-1) + } utils.Logger.Trace("Parsed metadata", utils.Logger.Args("layers", len(l.pr), "branches", len(l.br))); @@ -268,6 +274,15 @@ func (l *KDEBe) LoadCache() (err error) { utils.Logger.Error("Failed to unmarshal cache", utils.Logger.Args("error", err)) } } + depcache, err := ioutil.ReadFile(utils.Config.BaseDir + "/" + l.GetName() + "-dep-cache.json") + if err != nil { + utils.Logger.Error("Failed to read dependancy cache", utils.Logger.Args("error", err)) + } else { + err = json.Unmarshal(depcache, &l.dep) + if err != nil { + utils.Logger.Error("Failed to unmarshal dependancy cache", utils.Logger.Args("error", err)) + } + } brcache, err := ioutil.ReadFile(utils.Config.BaseDir + "/" + l.GetName() + "-branch-cache.json") if err != nil { utils.Logger.Error("Failed to read branch cache", utils.Logger.Args("error", err)) @@ -277,7 +292,9 @@ func (l *KDEBe) LoadCache() (err error) { utils.Logger.Error("Failed to unmarshal branch cache", utils.Logger.Args("error", err)) } } - + if err := LoadDownloadLocationsCache(); err != nil { + utils.Logger.Error("Failed to load download locations cache", utils.Logger.Args("error", err)) + } utils.Logger.Trace("KDE Cache Loaded", utils.Logger.Args("layers", len(l.pr), "branches", len(l.br))) return nil } @@ -312,4 +329,66 @@ func findmetdata(path string) (files []string) { panic(err) } return files -} \ No newline at end of file +} + +func (l *KDEBe) GetRecipe(identifier string) (*source.RecipeSource, error) { + utils.Logger.Trace("Getting KDE Recipe", utils.Logger.Args("recipe", identifier)) + if recipe, ok := l.pr[identifier]; ok { + + /* get the summary if it exists from appstream */ + if as, ok := recipe.MetaData["appstream"]; ok { + if summary, ok := as["summary"]; ok { + recipe.Summary = summary.(string) + } + } else { + result, _ := pterm.DefaultInteractiveTextInput.WithMultiLine(false).Show("Summary") + recipe.Summary = strings.TrimSpace(result) + } + /* get version from Appstream */ + if as, ok := recipe.MetaData["appstream"]; ok { + if version, ok := as["version"]; ok { + recipe.Version = version.(string) + } + } else { + result, _ := pterm.DefaultInteractiveTextInput.WithMultiLine(false).Show("Version Number") + recipe.Version = strings.TrimSpace(result) + } + if dlpath, err := GetDownloadPath(recipe.Identifier, recipe.Version); err != nil { + utils.Logger.Error("Failed to get download path", utils.Logger.Args("error", err)) + return nil, err + } else { + recipe.SrcURI = dlpath + } + if (len(recipe.SrcURI) > 0) { + if sha, err := GetDownloadSHA(recipe.Identifier, recipe.Version); err != nil { + utils.Logger.Error("Failed to get download SHA", utils.Logger.Args("error", err)) + } else { + recipe.SrcSHA256 = sha + } + } + licenses, err := GetLicense(recipe) + if err != nil { + utils.Logger.Error("Failed to get License", utils.Logger.Args("error", err)) + } else { + recipe.Licenses = licenses + } + + inherits, err := GetInherits(recipe, l.dep) + if err != nil { + utils.Logger.Error("Failed to get Inherits", utils.Logger.Args("error", err)) + } else { + recipe.Inherits = inherits + } + + depends, err := GetDepends(recipe, l.dep) + if err != nil { + utils.Logger.Error("Failed to get Inherits", utils.Logger.Args("error", err)) + } else { + recipe.Depends = depends + } + recipe.Section = path.Dir(recipe.Repopath) + + return &recipe.RecipeSource, nil + } + return nil, errors.New("Recipe Not Found") +} diff --git a/backends/kde/kdedownload.go b/backends/kde/kdedownload.go new file mode 100644 index 0000000..dea6a88 --- /dev/null +++ b/backends/kde/kdedownload.go @@ -0,0 +1,117 @@ +package kde + +import ( + "bufio" + "regexp" + "net/http" + "encoding/json" + "io/ioutil" + "errors" + "strings" + "crypto/sha256" + "io" + "fmt" + + "github.com/Fishwaldo/go-yocto/utils" + "github.com/pterm/pterm" +) + +type dirListing struct { + Directory string +} + +var files map[string]map[string]dirListing = make(map[string]map[string]dirListing) + +func LoadDownloadLocationsCache() (error) { + utils.Logger.Trace("Loading KDE Download Cache") + cache, err := ioutil.ReadFile(utils.Config.BaseDir + "/kdedownload-cache.json") + if err != nil { + utils.Logger.Error("Failed to read download cache", utils.Logger.Args("error", err)) + return RefreshDownloadLocations() + } else { + err = json.Unmarshal(cache, &files) + if err != nil { + utils.Logger.Error("Failed to unmarshal cache", utils.Logger.Args("error", err)) + return err + } + } + return nil +} + +func RefreshDownloadLocations() (error) { + file, err := http.Get("https://download.kde.org/ls-lR") + if err != nil { + return err + } + defer file.Body.Close() + scanner := bufio.NewScanner(file.Body) + var directory = regexp.MustCompile(`^(\./)?(.+):$`) + var fn = regexp.MustCompile(`^[^dl].* ((.*)-(.*)\.tar\.(bz2|xz))$`) + var curdir string + for scanner.Scan() { + if directory.MatchString(scanner.Text()) { + curdir = strings.TrimPrefix( directory.FindStringSubmatch(scanner.Text())[2], "/srv/archives/ftp/") + + } + if fn.MatchString(scanner.Text()) { + f := fn.FindStringSubmatch(scanner.Text()) + if _, ok := files[f[2]]; !ok { + files[f[2]] = make(map[string]dirListing) + } + files[f[2]][f[3]] = dirListing{Directory: curdir + "/" + f[1]} + } + } + cache, err := json.Marshal(files) + if err != nil { + utils.Logger.Error("Failed to marshal metadata", utils.Logger.Args("error", err)) + return err + } + err = ioutil.WriteFile(utils.Config.BaseDir + "/kdedownload-cache.json", cache, 0644) + if err != nil { + utils.Logger.Error("Failed to write metadata", utils.Logger.Args("error", err)) + return err + } + return nil +} + +func GetDownloadPath(source string, version string) (string, error) { + if _, ok := files[source]; !ok { + return "", errors.New("Source not found") + } + if _, ok := files[source][version]; !ok { + return "", errors.New("Version not found") + } + return "https://download.kde.org/" + files[source][version].Directory, nil +} + +func GetDownloadSHA(source string, version string) (string, error) { + if _, ok := files[source]; !ok { + return "", errors.New("Source not found") + } + if _, ok := files[source][version]; !ok { + return "", errors.New("Version not found") + } + path, err := GetDownloadPath(source, version) + if err != nil { + return "", err + } + utils.Logger.Trace("Getting SHA", utils.Logger.Args("path", path)) + spinnerInfo, _ := pterm.DefaultSpinner.Start("Downloading Source for SHA Calculation") + + file, err := http.Get(path) + if err != nil { + utils.Logger.Warn("Failed to get SHA", utils.Logger.Args("path", path, "error", err)) + spinnerInfo.Fail("Failed to Download Source for SHA Calculation") + return "", err + } + defer file.Body.Close() + hash := sha256.New() + if _, err := io.Copy(hash, file.Body); err != nil { + utils.Logger.Warn("Failed to calculate SHA", utils.Logger.Args("path", path, "error", err)) + spinnerInfo.Fail("Failed to Download Source for SHA Calculation") + return "", err + } + spinnerInfo.Success("Downloaded Source for SHA Calculation") + return fmt.Sprintf("%x", hash.Sum(nil)), nil + +} \ No newline at end of file diff --git a/backends/kde/kdedpendancies.go b/backends/kde/kdedpendancies.go new file mode 100644 index 0000000..ad11b23 --- /dev/null +++ b/backends/kde/kdedpendancies.go @@ -0,0 +1,54 @@ +package kde + +import ( + "path" + + "github.com/Fishwaldo/go-yocto/utils" +) + +var inheritmap map[string]string = make(map[string]string) +var dependmap map[string]string = make(map[string]string) + +func init() { + inheritmap = map[string]string { + "frameworks/extra-cmake-modules": "cmake_plasma", + "frameworks/kauth": "kauth", + "frameworks/kcmutils": "kcmutils", + "frameworks/kconfig": "kconfig", + "frameworks/kcoreaddons": "kcoreaddons", + "frameworks/kdoctools": "kdoctools", + "frameworks/ki18n" : "ki18n", + } + dependmap = map[string]string { + } + +} + + + +func GetInherits(pr Project, depmap map[string][]string) (inherits []string, err error) { + utils.Logger.Trace("Getting Inherits", utils.Logger.Args("project", pr.Name)) + if _, ok := depmap[pr.ProjectPath]; ok { + for _, v := range depmap[pr.ProjectPath] { + if i, ok := inheritmap[v]; ok { + inherits = append(inherits, i) + } + } + } + /* add our default inherit */ + inherits = append(inherits, "reuse_license_checksums") + + return inherits, nil +} + +func GetDepends(pr Project, depmap map[string][]string) (depends []string, err error) { + utils.Logger.Trace("Getting Depends", utils.Logger.Args("project", pr.Name)) + if _, ok := depmap[pr.ProjectPath]; ok { + for _, v := range depmap[pr.ProjectPath] { + if _, ok := inheritmap[v]; !ok { + depends = append(depends, path.Base(v)) + } + } + } + return depends, nil +} \ No newline at end of file diff --git a/backends/kde/kdelicenses.go b/backends/kde/kdelicenses.go new file mode 100644 index 0000000..d70af76 --- /dev/null +++ b/backends/kde/kdelicenses.go @@ -0,0 +1,33 @@ +package kde + +import ( + "strings" + + "github.com/Fishwaldo/go-yocto/utils" + "github.com/xanzy/go-gitlab" +) + + + +func GetLicense(pr Project) (license []string, err error) { + utils.Logger.Trace("Getting License", utils.Logger.Args("project", pr.Name)) + gl, err := gitlab.NewClient(utils.Config.KDEConfig.AccessToken, gitlab.WithBaseURL(utils.Config.KDEConfig.KDEGitLabURL+"/api/v4")) + + gf := &gitlab.ListTreeOptions { + Path: gitlab.String("LICENSES"), + Ref: gitlab.String(pr.MetaData["branch-rules"]["branch"].(string)), + } + f, res, err := gl.Repositories.ListTree(pr.Repopath, gf) + if err != nil { + utils.Logger.Error("Failed to get License", utils.Logger.Args("error", err)) + return nil, err + } + if res.StatusCode != 200 { + utils.Logger.Error("Failed to get License", utils.Logger.Args("error", res.Status)) + return nil, err + } + for _, file := range f { + license = append(license, strings.TrimSuffix(file.Name, ".txt")) + } + return license, nil +} \ No newline at end of file diff --git a/cmd/recipe.go b/cmd/recipe.go new file mode 100644 index 0000000..6253cc9 --- /dev/null +++ b/cmd/recipe.go @@ -0,0 +1,39 @@ +/* +Copyright © 2023 NAME HERE + +*/ +package cmd + +import ( + "fmt" + + "github.com/Fishwaldo/go-yocto/utils" + "github.com/spf13/cobra" + "github.com/Fishwaldo/go-yocto/cmd/recipe" + "github.com/spf13/viper" +) + +// recipeCmd represents the recipe command +var recipeCmd = &cobra.Command{ + Use: "recipe", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("recipe called") + }, +} + +func init() { + rootCmd.AddCommand(recipeCmd) + recipeCmd.AddCommand(cmdRecipe.CreateCmd) + + cmdRecipe.CreateCmd.Flags().StringP("layer", "l", ".", "Layer Directory to create recipe in") + if err := viper.BindPFlag("yocto.layerdirectory", cmdRecipe.CreateCmd.Flags().Lookup("layer")); err != nil { + utils.Logger.Error("Failed to Bind Flag", utils.Logger.Args("flag", "layer", "error", err)) + } +} diff --git a/cmd/recipe/create.go b/cmd/recipe/create.go new file mode 100644 index 0000000..b679c68 --- /dev/null +++ b/cmd/recipe/create.go @@ -0,0 +1,25 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmdRecipe + +import ( + "github.com/Fishwaldo/go-yocto/recipe" + "github.com/Fishwaldo/go-yocto/utils" + "github.com/spf13/cobra" +) + +// createCmd represents the update command +var CreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a new recipe", + Long: `Create a new recipe`, + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + utils.Logger.Info("Creating Recipe", utils.Logger.Args("backend", args[0], "name", args[1])) + recipe.CreateRecipe(args[0], args[1]) + }, +} + +func init() { +} diff --git a/cmd/source.go b/cmd/source.go index 9267a9a..e759370 100644 --- a/cmd/source.go +++ b/cmd/source.go @@ -29,13 +29,7 @@ to quickly create a Cobra application.`, func init() { rootCmd.AddCommand(sourceCmd) sourceCmd.AddCommand(cmdSource.SearchCmd) - // Here you will define your flags and configuration settings. - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // sourceCmd.PersistentFlags().String("foo", "", "A help for foo") - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // sourceCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + } diff --git a/cmd/source/search.go b/cmd/source/search.go index 0c6a65a..0ae0cbf 100644 --- a/cmd/source/search.go +++ b/cmd/source/search.go @@ -20,7 +20,7 @@ var SearchCmd = &cobra.Command{ if err == nil { td := pterm.TableData{{"Name", "Description", "Backend", "Url"}} for _, source := range sources { - td = append(td, []string{source.Name, source.Description, source.Backend, source.Url}) + td = append(td, []string{source.Name, source.Description, source.BackendID, source.Url}) } pterm.DefaultTable.WithHasHeader().WithData( td, diff --git a/go.mod b/go.mod index 71eb7db..a00dd2e 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.18 require ( github.com/Masterminds/semver/v3 v3.2.1 + github.com/davecgh/go-spew v1.1.1 github.com/go-git/go-git/v5 v5.6.1 github.com/pterm/pterm v0.12.60 github.com/spf13/cobra v1.7.0 diff --git a/recipe/recipe.go b/recipe/recipe.go new file mode 100644 index 0000000..f3164c5 --- /dev/null +++ b/recipe/recipe.go @@ -0,0 +1,204 @@ +package recipe + +import ( + "errors" + "fmt" + "io/fs" + "io/ioutil" + "os" + "path" + "path/filepath" + "regexp" + "strings" + "text/template" + + "github.com/Fishwaldo/go-yocto/backends" + "github.com/Fishwaldo/go-yocto/source" + "github.com/Fishwaldo/go-yocto/utils" + "github.com/spf13/viper" + "github.com/pterm/pterm" +// "github.com/davecgh/go-spew/spew" +) + +var ( + funcs = template.FuncMap{"join": strings.Join} + parserecipename = regexp.MustCompile(`^(.*)_(.*)\.bb$`) + existingRecipes map[string]*source.RecipeSource = make(map[string]*source.RecipeSource) + +) + + +func init() { +} + + +func CreateRecipe(be string, name string) (error) { + utils.Logger.Trace("Creating Recipe", utils.Logger.Args("backend", be, "name", name)) + b, ok := backends.Backends[be] + if !ok { + utils.Logger.Error("Backend not found", utils.Logger.Args("backend", be)) + return errors.New("backend not found") + } + if !b.Ready() { + utils.Logger.Error("Backend not ready", utils.Logger.Args("backend", be)) + return errors.New("backend not ready") + } + + + layers := viper.GetStringSlice("yocto.layers") + for _, layer := range layers { + spinnerInfo, _ := pterm.DefaultSpinner.Start("Scanning Existing Recipe Files in " + layer) + scanRecipes(layer, 0); + spinnerInfo.Success() + } + s, err := b.GetRecipe(name) + if err != nil { + utils.Logger.Error("Failed to get Recipe", utils.Logger.Args("backend", be, "name", name, "error", err)) + return err + } + + /* if it already exists, bail out */ + if _, ok := existingRecipes[s.Identifier]; ok { + utils.Logger.Warn("Recipe already exists", utils.Logger.Args("recipe", s.Name)) + return errors.New("recipe already exists") + } + + /* check out dependancies */ + for _, dep := range s.Depends { + if _, ok := existingRecipes[dep]; !ok { + utils.Logger.Warn("Recipe Dependancy not found", utils.Logger.Args("recipe", s.Name, "dependancy", dep)) + return errors.New("recipe dependancy not found") + } + } + + if err := writeRecipeFiles(s); err != nil { + utils.Logger.Error("Failed to write Recipe Files", utils.Logger.Args("error", err)) + return err + } + return nil +} + +func writeRecipeFiles(s *source.RecipeSource) (error) { + dir := path.Join(viper.GetString("yocto.layerdirectory"), "recipes-" + s.Section) + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err := os.Mkdir(dir, 0755); err != nil { + utils.Logger.Error("Failed to create directory", utils.Logger.Args("error", err, "dir", dir)) + return err + } + } + + dir = path.Join(dir, s.Identifier) + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err := os.Mkdir(dir, 0755); err != nil { + utils.Logger.Error("Failed to create directory", utils.Logger.Args("error", err)) + return err + } + } + + utils.Logger.Info("Publishing to " + dir) + + maintmpl, err := ioutil.ReadFile("templates/recipe-main.tmpl") + if err != nil { + utils.Logger.Error("Failed to read main template", utils.Logger.Args("error", err)) + } else { + + /* create recipe_version.bb file */ + masterTmpl, err := template.New("master").Funcs(funcs).Parse(string(maintmpl)) + if err != nil { + utils.Logger.Error("Failed to parse template", utils.Logger.Args("error", err)) + return err + } + + fname := path.Join(dir, fmt.Sprintf("%s_%s.bb", s.Identifier, s.Version)) + + if f, err := os.Create(fname); err != nil { + utils.Logger.Error("Failed to create file", utils.Logger.Args("error", err)) + return err + } else { + if err := masterTmpl.Execute(f, s); err != nil { + utils.Logger.Error("Failed to execute template", utils.Logger.Args("error", err)) + return err + } else { + utils.Logger.Info("Created " + fname) + } + f.Close(); + } + + /* create recipe.inc file */ + maintmpl, err := ioutil.ReadFile("templates/recipe-include.tmpl") + if err != nil { + utils.Logger.Error("Failed to read main template", utils.Logger.Args("error", err)) + } else { + overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(string(maintmpl)) + if err != nil { + utils.Logger.Error("Failed to parse template", utils.Logger.Args("error", err)) + return err + } + fname = path.Join(dir, fmt.Sprintf("%s.inc", s.Identifier)) + + if f, err := os.Create(fname); err != nil { + utils.Logger.Error("Failed to create file", utils.Logger.Args("error", err)) + return err + } else { + if err := overlayTmpl.Execute(f, s); err != nil { + utils.Logger.Error("Failed to execute template", utils.Logger.Args("error", err)) + return err + } else { + utils.Logger.Info("Created " + fname) + } + f.Close(); + } + } + } + return nil +} + + +func scanRecipes(dir string, level int) (error) { + + fsrecipessections, err := os.ReadDir(dir) + if err != nil { + utils.Logger.Error("Failed to read directory", utils.Logger.Args("error", err)) + return err + } + + for _, file := range fsrecipessections { + if file.IsDir() { + if (level == 0) { + ok, _ := filepath.Match("recipes-*", file.Name()) + if !ok { + continue + } + } + if err := scanRecipes(path.Join(dir, file.Name()), level + 1); err != nil { + utils.Logger.Error("Failed to scan directory", utils.Logger.Args("error", err)) + continue + } + } else if file.Type().IsRegular() { + if strings.EqualFold(path.Ext(file.Name()), ".bb") { + if err := parseRecipeFile(file, path.Join(dir, file.Name())); err != nil { + utils.Logger.Error("Failed to parse recipe file", utils.Logger.Args("error", err, "file", path.Join(dir, file.Name()))) + continue + } + } + } + } + return nil +} + +func parseRecipeFile(file fs.DirEntry, dir string) (error) { + if parserecipename.Match([]byte(file.Name())) { + match := parserecipename.FindSubmatch([]byte(file.Name())) + s := source.RecipeSource{ + Identifier: string(match[1]), + Version: string(match[2]), + Section: strings.TrimPrefix(file.Name(), "-"), + BackendID: "existing", + Location: path.Join(dir, file.Name()), + } + if _, ok := existingRecipes[s.Identifier]; !ok { + existingRecipes[s.Identifier] = &s + } + } + return nil +} \ No newline at end of file diff --git a/source/source.go b/source/source.go index 667f51b..468b9cf 100644 --- a/source/source.go +++ b/source/source.go @@ -2,10 +2,18 @@ package source type RecipeSource struct { Name string + Identifier string Description string + Summary string + Version string Url string - Backend string + Section string BackendID string - MetaData map[string]map[string]interface{} + Inherits []string + Depends []string + SrcURI string + SrcSHA256 string + Licenses []string + Location string } diff --git a/templates/recipe-include.tmpl b/templates/recipe-include.tmpl new file mode 100644 index 0000000..d7e22fc --- /dev/null +++ b/templates/recipe-include.tmpl @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2023 Justin Hammond +# +# SPDX-License-Identifier: MIT + +DESCRIPTION = "{{.Description}}" +SUMMARY = "{{.Summary}}" +HOMEPAGE = "{{.Url}}" +LICENSE = "{{block "Licenses" .Licenses}} {{join . " & "}}{{end}}" + +{{block "Inherits" .Inherits}}{{"\n"}}{{range .}}{{println "inherit" .}}{{end}}{{end}} + +DEPENDS = " \ +{{block "Depends" .Depends }}{{range .}}{{print " " . }} \{{println}}{{end}}{{end}}" + + +KF5_REUSE_LICENSECHECK_ENABLED="1" + diff --git a/templates/recipe-main.tmpl b/templates/recipe-main.tmpl new file mode 100644 index 0000000..4fd8d0e --- /dev/null +++ b/templates/recipe-main.tmpl @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: none +# SPDX-License-Identifier: CC0-1.0 + +require ${PN}.inc +SRC_URI = "{{.SrcURI}}" +SRC_URI[sha256sum] = "{{.SrcSHA256}}" +