| /** |
| * 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 |
| } |