newt/project/pkgwriter.go (201 lines of code) (raw):

/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package project import ( "fmt" "io/ioutil" "os" "path" "path/filepath" "strings" "mynewt.apache.org/newt/newt/downloader" "mynewt.apache.org/newt/newt/newtutil" "mynewt.apache.org/newt/util" ) type templateRepo struct { owner string name string branch string } type PackageWriter struct { downloader *downloader.GithubDownloader repo templateRepo targetPath string template string fullName string project *Project } var TemplateRepoMap = map[string]templateRepo{ "APP": templateRepo{ owner: "runtimeco", name: "mynewt-pkg-app", branch: "master", }, "SDK": templateRepo{ owner: "apache", name: "mynewt-pkg-sdk", branch: "master", }, "BSP": templateRepo{ owner: "apache", name: "mynewt-pkg-bsp", branch: "master", }, "LIB": templateRepo{ owner: "apache", name: "mynewt-pkg-pkg", branch: "master", }, "UNITTEST": templateRepo{ owner: "runtimeco", name: "mynewt-pkg-unittest", branch: "master", }, // Type=pkg is identical to type=lib for backwards compatibility. "PKG": templateRepo{ owner: "apache", name: "mynewt-pkg-pkg", branch: "master", }, } func (pw *PackageWriter) ConfigurePackage(template string, loc string) error { tr, ok := TemplateRepoMap[template] if !ok { return util.NewNewtError(fmt.Sprintf("Cannot find matching "+ "repository for template %s", template)) } pw.repo = tr pw.fullName = path.Clean(loc) path := pw.project.Path() path = path + "/" + pw.fullName if util.NodeExist(path) { return util.NewNewtError(fmt.Sprintf("Cannot place a new package in "+ "%s, path already exists.", path)) } pw.template = template pw.targetPath = path return nil } // Creates a table of search-replace pairs. These pairs are simple // substitution rules (i.e., not regexes) that get applied to filenames, // directory names, and the contents of YAML files. func (pw *PackageWriter) replacementTable() [][]string { pkgBase := path.Base(pw.fullName) return [][]string{ {`$$pkgfullname`, pw.fullName}, {`$$pkgdir`, path.Dir(pw.fullName)}, {`$$pkgname`, path.Base(pw.fullName)}, // Legacy. {`your-pkg-name`, `"` + pw.fullName + `"`}, {`your-path`, pkgBase}, {`your-source`, pkgBase}, {`your-file`, pkgBase}, } } // Applies all the substitution rules in the supplied table to a string. func replaceText(s string, table [][]string) string { for _, r := range table { s = strings.Replace(s, r[0], r[1], -1) } return s } // Applies all the substitution rules in the supplied table to the contents of // a file. If the file contents change as a result, the file gets rewritten. func fixupFileText(path string, table [][]string) error { data, err := ioutil.ReadFile(path) if err != nil { return util.ChildNewtError(err) } s1 := string(data) s2 := replaceText(s1, table) if s2 != s1 { if err := ioutil.WriteFile(path, []byte(s2), 0666); err != nil { return util.ChildNewtError(err) } } return nil } // Retrieves the names of all child files and directories. // // @param path The root directory where the traversal starts. // // @return []string All descendent files. // []string All descendent directories. // error Error func collectPaths(path string) ([]string, []string, error) { files := []string{} dirs := []string{} collect := func(path string, f os.FileInfo, err error) error { if err != nil { return err } if f.IsDir() { dirs = append(dirs, path) } else { files = append(files, path) } return nil } if err := filepath.Walk(path, collect); err != nil { return nil, nil, util.ChildNewtError(err) } return files, dirs, nil } // Customizes a template package. Renames generic files and directories and // substitutes text in YAML files. func (pw *PackageWriter) fixupPkg() error { table := pw.replacementTable() pkgDir := pw.targetPath // Apply the replacement patterns to directory names. _, dirs, err := collectPaths(pkgDir) if err != nil { return err } for _, d1 := range dirs { d2 := replaceText(d1, table) if d1 != d2 { // Make parent directory to allow multiple replacements in path. if err := os.MkdirAll(filepath.Dir(d2), os.ModePerm); err != nil { return util.ChildNewtError(err) } if err := util.MoveDir(d1, d2); err != nil { return err } } } // Replace text inside YAML files. files, _, err := collectPaths(pkgDir) if err != nil { return err } for _, f := range files { if strings.HasSuffix(f, ".yml") { if err := fixupFileText(f, table); err != nil { return err } } } // Apply the replacement patterns to file names. for _, f1 := range files { f2 := replaceText(f1, table) if f2 != f1 { if err := util.MoveDir(f1, f2); err != nil { return err } } } return nil } func (pw *PackageWriter) WritePackage() error { dl := pw.downloader dl.User = pw.repo.owner dl.Repo = pw.repo.name util.StatusMessage(util.VERBOSITY_DEFAULT, "Download package template for package type %s.\n", strings.ToLower(pw.template)) tmpdir, err := newtutil.MakeTempRepoDir() if err != nil { return err } defer os.RemoveAll(tmpdir) if err := dl.Clone(pw.repo.branch, tmpdir); err != nil { return err } if err := os.RemoveAll(tmpdir + "/.git/"); err != nil { return util.NewNewtError(err.Error()) } if err := util.CopyDir(tmpdir, pw.targetPath); err != nil { return err } if err := pw.fixupPkg(); err != nil { return err } util.StatusMessage(util.VERBOSITY_DEFAULT, "Package successfuly installed into %s.\n", pw.targetPath) return nil } /** * Create new PackageWriter structure, and return it */ func NewPackageWriter() *PackageWriter { proj := GetProject() pw := &PackageWriter{ project: proj, downloader: downloader.NewGithubDownloader(), } return pw }