newt/cli/util.go (253 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 cli import ( "bufio" "bytes" "fmt" "os" "regexp" "strings" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/apache/mynewt-artifact/errors" "mynewt.apache.org/newt/newt/builder" "mynewt.apache.org/newt/newt/newtutil" "mynewt.apache.org/newt/newt/pkg" "mynewt.apache.org/newt/newt/project" "mynewt.apache.org/newt/newt/resolve" "mynewt.apache.org/newt/newt/target" "mynewt.apache.org/newt/util" ) const TARGET_KEYWORD_ALL string = "all" const TARGET_DEFAULT_DIR string = "targets" const MFG_DEFAULT_DIR string = "mfgs" func NewtUsage(cmd *cobra.Command, err error) { if err != nil { if errors.HasStackTrace(err) { log.Debugf("%+v", err) } else if ne, ok := err.(*util.NewtError); ok { log.Debugf("%s", ne.StackTrace) } else { panic(fmt.Sprintf("unexpected error type: %T", err)) } fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) } if cmd != nil { fmt.Printf("%s - ", cmd.Name()) cmd.Help() } os.Exit(1) } // Display help text with a max line width of 79 characters func FormatHelp(text string) string { // first compress all new lines and extra spaces words := regexp.MustCompile("\\s+").Split(text, -1) linelen := 0 fmtText := "" for _, word := range words { word = strings.Trim(word, "\n ") + " " tmplen := linelen + len(word) if tmplen >= 80 { fmtText += "\n" linelen = 0 } fmtText += word linelen += len(word) } return fmtText } func ResolveTarget(name string) *target.Target { // Trim trailing slash from name. This is necessary when tab // completion is used to specify the name. name = strings.TrimSuffix(name, "/") targetMap := target.GetTargets() // Check for fully-qualified name. if t := targetMap[name]; t != nil { return t } // Check the local "targets" directory. if t := targetMap[TARGET_DEFAULT_DIR+"/"+name]; t != nil { return t } return nil } // Resolves a list of target names and checks for the optional "all" keyword // among them. Regardless of whether "all" is specified, all target names must // be valid, or an error is reported. // // @return targets, all (t/f), err func ResolveTargetsOrAll(names ...string) ([]*target.Target, bool, error) { targets := []*target.Target{} all := false for _, name := range names { if name == "all" { all = true } else { t := ResolveTarget(name) if t == nil { return nil, false, util.NewNewtError("Could not resolve target name: " + name) } targets = append(targets, t) } } return targets, all, nil } func ResolveTargets(names ...string) ([]*target.Target, error) { targets, all, err := ResolveTargetsOrAll(names...) if err != nil { return nil, err } if all { return nil, util.NewNewtError("Keyword \"all\" not allowed in thie context") } return targets, nil } func ResolveNewTargetName(name string) (string, error) { repoName, pkgName, err := newtutil.ParsePackageString(name) if err != nil { return "", err } if repoName != "" { return "", util.NewNewtError("Target name cannot contain repo; " + "must be local") } if pkgName == TARGET_KEYWORD_ALL { return "", util.NewNewtError("Target name " + TARGET_KEYWORD_ALL + " is reserved") } // "Naked" target names translate to "targets/<name>". if !strings.Contains(pkgName, "/") { pkgName = TARGET_DEFAULT_DIR + "/" + pkgName } if target.GetTargets()[pkgName] != nil { return "", util.NewNewtError("Target already exists: " + pkgName) } return pkgName, nil } func PackageNameList(pkgs []*pkg.LocalPackage) string { var buffer bytes.Buffer for i, pack := range pkgs { if i != 0 { buffer.WriteString(" ") } buffer.WriteString(pack.Name()) } return buffer.String() } func ResetGlobalState() error { // Make sure the current working directory is at the project base. if err := os.Chdir(project.GetProject().Path()); err != nil { return util.NewNewtError("Failed to reset global state: " + err.Error()) } target.ResetTargets() project.ResetProject() return nil } func TryGetProject() *project.Project { var p *project.Project var err error if p, err = project.TryGetProject(); err != nil { NewtUsage(nil, err) } for _, w := range p.Warnings() { util.ErrorMessage(util.VERBOSITY_QUIET, "* Warning: %s\n", w) } return p } func TryGetOrDownloadProject() *project.Project { var p *project.Project var err error if p, err = project.TryGetOrDownloadProject(); err != nil { NewtUsage(nil, err) } for _, w := range p.Warnings() { util.ErrorMessage(util.VERBOSITY_QUIET, "* Warning: %s\n", w) } return p } func ResolveUnittest(pkgName string) (*target.Target, error) { // Each unit test package gets its own target. This target is a copy // of the base unit test package, just with an appropriate name. The // reason each test needs a unique target is: syscfg and sysinit are // target-specific. If each test package shares a target, they will // overwrite these generated headers each time they are run. Worse, if // two tests are run back-to-back, the timestamps may indicate that the // headers have not changed between tests, causing build failures. baseTarget := ResolveTarget(TARGET_TEST_NAME) if baseTarget == nil { return nil, util.FmtNewtError("Can't find unit test target: %s", TARGET_TEST_NAME) } targetName := fmt.Sprintf("%s/%s/%s", TARGET_DEFAULT_DIR, TARGET_TEST_NAME, builder.TestTargetName(pkgName)) t := ResolveTarget(targetName) if t == nil { targetName, err := ResolveNewTargetName(targetName) if err != nil { return nil, err } t = baseTarget.Clone(TryGetProject().LocalRepo(), targetName) } return t, nil } // @return Target // @return LocalPackage The package under test, if any. // @return error func ResolveTargetOrUnittest(pkgName string) ( *target.Target, *pkg.LocalPackage, error) { // Argument can specify either a target or a unittest package. Determine // which type the package is and construct a target builder appropriately. if t, err := resolveExistingTargetArg(pkgName); err == nil { return t, nil, nil } // Package wasn't a target. Try for a unittest. proj := TryGetProject() pack, err := proj.ResolvePackage(proj.LocalRepo(), pkgName) if err != nil { return nil, nil, util.FmtNewtError( "Could not resolve target or unittest \"%s\"", pkgName) } if pack.Type() != pkg.PACKAGE_TYPE_UNITTEST { return nil, nil, util.FmtNewtError( "Package \"%s\" is of type %s; "+ "must be target or unittest", pkgName, pkg.PackageTypeNames[pack.Type()]) } t, err := ResolveUnittest(pack.Name()) if err != nil { return nil, nil, err } return t, pack, nil } func ResolvePackages(pkgNames []string) ([]*pkg.LocalPackage, error) { proj := TryGetProject() lpkgs := []*pkg.LocalPackage{} for _, pkgName := range pkgNames { pack, err := proj.ResolvePackage(proj.LocalRepo(), pkgName) if err != nil { return nil, err } lpkgs = append(lpkgs, pack) } return lpkgs, nil } func ResolveRpkgs(res *resolve.Resolution, pkgNames []string) ( []*resolve.ResolvePackage, error) { lpkgs, err := ResolvePackages(pkgNames) if err != nil { return nil, err } rpkgs := []*resolve.ResolvePackage{} for _, lpkg := range lpkgs { rpkg := res.LpkgRpkgMap[lpkg] if rpkg == nil { return nil, util.FmtNewtError("Unexpected error; local package "+ "%s lacks a corresponding resolve package", lpkg.FullName()) } rpkgs = append(rpkgs, rpkg) } return rpkgs, nil } func TargetBuilderForTargetOrUnittest(pkgName string) ( *builder.TargetBuilder, error) { t, testPkg, err := ResolveTargetOrUnittest(pkgName) if err != nil { return nil, err } if testPkg == nil { return builder.NewTargetBuilder(t) } else { return builder.NewTargetTester(t, testPkg) } } func PromptYesNo(dflt bool) bool { scanner := bufio.NewScanner(os.Stdin) rc := scanner.Scan() if !rc { return dflt } if strings.ToLower(scanner.Text()) == "y" { return true } if strings.ToLower(scanner.Text()) == "n" { return false } return dflt }