You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2016/12/01 19:52:22 UTC

incubator-mynewt-newt git commit: MYNEWT-365 newt can't handle files with spaces

Repository: incubator-mynewt-newt
Updated Branches:
  refs/heads/develop 037457dd6 -> 0241d2f38


MYNEWT-365 newt can't handle files with spaces

Newt no longer invokes /bin/sh to execute external commands.  Instead,
it now uses the Go exec library to fork+exec child processes.

NOTE: This commit breaks backwards-compatibility with the Mynewt core
repo.  Specifically, the compiler.yml definitions indicated command line
options as a space-delimited sequence of strings in the path settings.
E.g., the Cortex M4 assembler specified the following path:

    compiler.path.as: arm-none-eabi-gcc -x assembler-with-cpp

This compiler definition has now been changed to the following:

    compiler.path.as: arm-none-eabi-gcc
    compiler.as.flags: [-x, assembler-with-cpp]

We could have maintained backwards-compatibility by splitting paths on
spaces and interpreting secondary tokens as command-line options.
However, this approach prevents us from specifying a compiler path
that actually contains spaces.  I think paths with spaces is not
uncommon (e.g., "/Applications/GNU ARM Eclipse/...")

In addition, some packages' cflags contained shell-escaped strings
(e.g., backslash-quote).  These escapes had to be removed now that the
flags aren't passing through the shell.


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/commit/0241d2f3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/tree/0241d2f3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/diff/0241d2f3

Branch: refs/heads/develop
Commit: 0241d2f38e8236f03d6f20d5c7198d4a0a4cb169
Parents: 037457d
Author: Christopher Collins <cc...@apache.org>
Authored: Thu Dec 1 11:00:40 2016 -0800
Committer: Christopher Collins <cc...@apache.org>
Committed: Thu Dec 1 11:50:03 2016 -0800

----------------------------------------------------------------------
 newt/builder/build.go                           |   3 +-
 newt/builder/load.go                            |  25 +-
 newt/downloader/downloader.go                   |  11 +-
 newt/image/image.go                             |  37 ++-
 newt/toolchain/compiler.go                      | 265 +++++++++++++------
 newt/toolchain/deps.go                          |  13 +-
 newt/vendor/mynewt.apache.org/newt/util/util.go |  62 ++++-
 util/util.go                                    |  62 ++++-
 8 files changed, 358 insertions(+), 120 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0241d2f3/newt/builder/build.go
----------------------------------------------------------------------
diff --git a/newt/builder/build.go b/newt/builder/build.go
index 82798fb..230d988 100644
--- a/newt/builder/build.go
+++ b/newt/builder/build.go
@@ -547,7 +547,8 @@ func (b *Builder) Test(p *pkg.LocalPackage) error {
 
 	util.StatusMessage(util.VERBOSITY_DEFAULT, "Executing test: %s\n",
 		testFilename)
-	if _, err := util.ShellCommand(testFilename); err != nil {
+	cmd := []string{testFilename}
+	if _, err := util.ShellCommand(cmd, nil); err != nil {
 		newtError := err.(*util.NewtError)
 		newtError.Text = fmt.Sprintf("Test failure (%s):\n%s", p.Name(),
 			newtError.Text)

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0241d2f3/newt/builder/load.go
----------------------------------------------------------------------
diff --git a/newt/builder/load.go b/newt/builder/load.go
index ec07f1f..b7ae34e 100644
--- a/newt/builder/load.go
+++ b/newt/builder/load.go
@@ -24,6 +24,7 @@ import (
 	"os"
 	"sort"
 	"strconv"
+	"strings"
 
 	"mynewt.apache.org/newt/newt/pkg"
 	"mynewt.apache.org/newt/newt/project"
@@ -65,25 +66,27 @@ func Load(binBaseName string, bspPkg *pkg.BspPackage,
 	}
 	sort.Strings(sortedKeys)
 
-	envSettings := ""
+	env := []string{}
 	for _, key := range sortedKeys {
-		envSettings += fmt.Sprintf("%s=\"%s\" ", key, extraEnvSettings[key])
+		env = append(env, fmt.Sprintf("%s=%s", key, extraEnvSettings[key]))
 	}
 
 	coreRepo := project.GetProject().FindRepo("apache-mynewt-core")
-	envSettings += fmt.Sprintf("CORE_PATH=\"%s\" ", coreRepo.Path())
-	envSettings += fmt.Sprintf("BSP_PATH=\"%s\" ", bspPath)
-	envSettings += fmt.Sprintf("BIN_BASENAME=\"%s\" ", binBaseName)
+	env = append(env, fmt.Sprintf("CORE_PATH=%s", coreRepo.Path()))
+	env = append(env, fmt.Sprintf("BSP_PATH=%s", bspPath))
+	env = append(env, fmt.Sprintf("BIN_BASENAME=%s", binBaseName))
 
 	// bspPath, binBaseName are passed in command line for backwards
 	// compatibility
-	downloadCmd := fmt.Sprintf("%s %s %s %s", envSettings,
-		bspPkg.DownloadScript, bspPath, binBaseName)
+	cmd := []string{
+		bspPkg.DownloadScript,
+		bspPath,
+		binBaseName,
+	}
 
 	util.StatusMessage(util.VERBOSITY_VERBOSE, "Load command: %s\n",
-		downloadCmd)
-	_, err := util.ShellCommand(downloadCmd)
-	if err != nil {
+		strings.Join(cmd, " "))
+	if _, err := util.ShellCommand(cmd, env); err != nil {
 		return err
 	}
 	util.StatusMessage(util.VERBOSITY_VERBOSE, "Successfully loaded image.\n")
@@ -183,7 +186,7 @@ func (b *Builder) Debug(extraJtagCmd string, reset bool, noGDB bool) error {
 		fmt.Sprintf("CORE_PATH=%s", coreRepo.Path()),
 		fmt.Sprintf("BSP_PATH=%s", bspPath),
 		fmt.Sprintf("BIN_BASENAME=%s", binBaseName),
-		fmt.Sprintf("FEATURES=\"%s\"", featureString),
+		fmt.Sprintf("FEATURES=%s", featureString),
 	}
 	if extraJtagCmd != "" {
 		envSettings = append(envSettings,

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0241d2f3/newt/downloader/downloader.go
----------------------------------------------------------------------
diff --git a/newt/downloader/downloader.go b/newt/downloader/downloader.go
index 7849feb..7b073bb 100644
--- a/newt/downloader/downloader.go
+++ b/newt/downloader/downloader.go
@@ -26,7 +26,6 @@ import (
 	"net/http"
 	"os"
 	"os/exec"
-	"strings"
 
 	log "github.com/Sirupsen/logrus"
 
@@ -84,13 +83,13 @@ func checkout(repoDir string, commit string) error {
 	}
 
 	// Checkout the specified commit.
-	cmds := []string{
+	cmd := []string{
 		gitPath,
 		"checkout",
 		commit,
 	}
 
-	if o, err := util.ShellCommand(strings.Join(cmds, " ")); err != nil {
+	if o, err := util.ShellCommand(cmd, nil); err != nil {
 		return util.NewNewtError(string(o))
 	}
 
@@ -187,7 +186,7 @@ func (gd *GithubDownloader) DownloadRepo(commit string) (string, error) {
 	}
 
 	// Clone the repository.
-	cmds := []string{
+	cmd := []string{
 		gitPath,
 		"clone",
 		"-b",
@@ -197,12 +196,12 @@ func (gd *GithubDownloader) DownloadRepo(commit string) (string, error) {
 	}
 
 	if util.Verbosity >= util.VERBOSITY_VERBOSE {
-		if err := util.ShellInteractiveCommand(cmds, nil); err != nil {
+		if err := util.ShellInteractiveCommand(cmd, nil); err != nil {
 			os.RemoveAll(tmpdir)
 			return "", err
 		}
 	} else {
-		if _, err := util.ShellCommand(strings.Join(cmds, " ")); err != nil {
+		if _, err := util.ShellCommand(cmd, nil); err != nil {
 			return "", err
 		}
 	}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0241d2f3/newt/image/image.go
----------------------------------------------------------------------
diff --git a/newt/image/image.go b/newt/image/image.go
index 1e5e100..24e37c8 100644
--- a/newt/image/image.go
+++ b/newt/image/image.go
@@ -481,15 +481,36 @@ func (r *RepoManager) GetImageManifestPkg(
 		Name: ip.Repo,
 	}
 
-	res, err := util.ShellCommand(fmt.Sprintf("cd %s && git rev-parse HEAD",
-		path))
+	// Make sure we restore the current working dir to whatever it was when
+	// this function was called
+	cwd, err := os.Getwd()
+	if err != nil {
+		log.Debugf("Unable to determine current working directory: %v", err)
+		return ip
+	}
+	defer os.Chdir(cwd)
+
+	if err := os.Chdir(path); err != nil {
+		return ip
+	}
+
+	var res []byte
+
+	res, err = util.ShellCommand([]string{
+		"git",
+		"rev-parse",
+		"HEAD",
+	}, nil)
 	if err != nil {
 		log.Debugf("Unable to determine commit hash for %s: %v", path, err)
 		repo.Commit = "UNKNOWN"
 	} else {
 		repo.Commit = strings.TrimSpace(string(res))
-		res, err := util.ShellCommand(fmt.Sprintf(
-			"cd %s && git status --porcelain", path))
+		res, err = util.ShellCommand([]string{
+			"git",
+			"status",
+			"--porcelain",
+		}, nil)
 		if err != nil {
 			log.Debugf("Unable to determine dirty state for %s: %v", path, err)
 		} else {
@@ -497,8 +518,12 @@ func (r *RepoManager) GetImageManifestPkg(
 				repo.Dirty = true
 			}
 		}
-		res, err = util.ShellCommand(fmt.Sprintf(
-			"cd %s && git config --get remote.origin.url", path))
+		res, err = util.ShellCommand([]string{
+			"git",
+			"config",
+			"--get",
+			"remote.origin.url",
+		}, nil)
 		if err != nil {
 			log.Debugf("Unable to determine URL for %s: %v", path, err)
 		} else {

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0241d2f3/newt/toolchain/compiler.go
----------------------------------------------------------------------
diff --git a/newt/toolchain/compiler.go b/newt/toolchain/compiler.go
index 42af123..ed34254 100644
--- a/newt/toolchain/compiler.go
+++ b/newt/toolchain/compiler.go
@@ -306,16 +306,48 @@ func (c *Compiler) includesString() string {
 	return "-I" + strings.Join(includes, " -I")
 }
 
+// Generates a string consisting of all the necessary include path (-I)
+// options.  The result is sorted and contains no duplicate paths.
+func (c *Compiler) includesStrings() []string {
+	if len(c.info.Includes) == 0 {
+		return nil
+	}
+
+	includes := util.SortFields(c.info.Includes...)
+
+	tokens := make([]string, len(includes))
+	for i, s := range includes {
+		tokens[i] = "-I" + s
+	}
+
+	return tokens
+}
+
 func (c *Compiler) cflagsString() string {
 	cflags := util.SortFields(c.info.Cflags...)
 	return strings.Join(cflags, " ")
 }
 
+func (c *Compiler) cflagsStrings() []string {
+	cflags := util.SortFields(c.info.Cflags...)
+	return cflags
+}
+
+func (c *Compiler) aflagsStrings() []string {
+	aflags := util.SortFields(c.info.Aflags...)
+	return aflags
+}
+
 func (c *Compiler) lflagsString() string {
 	lflags := util.SortFields(c.info.Lflags...)
 	return strings.Join(lflags, " ")
 }
 
+func (c *Compiler) lflagsStrings() []string {
+	lflags := util.SortFields(c.info.Lflags...)
+	return lflags
+}
+
 func (c *Compiler) depsString() string {
 	extraDeps := util.SortFields(c.extraDeps...)
 	return strings.Join(extraDeps, " ") + "\n"
@@ -327,28 +359,42 @@ func (c *Compiler) depsString() string {
 // @param file                  The filename of the source file to compile.
 // @param compilerType          One of the COMPILER_TYPE_[...] constants.
 //
-// @return                      (success) The command string.
+// @return                      (success) The command arguments.
 func (c *Compiler) CompileFileCmd(file string,
-	compilerType int) (string, error) {
+	compilerType int) ([]string, error) {
 
 	objFile := strings.TrimSuffix(file, filepath.Ext(file)) + ".o"
 	objPath := filepath.ToSlash(c.dstDir + "/" + objFile)
 
-	var cmd string
-
+	var cmdName string
+	var flags []string
 	switch compilerType {
 	case COMPILER_TYPE_C:
-		cmd = c.ccPath
+		cmdName = c.ccPath
+		flags = c.cflagsStrings()
 	case COMPILER_TYPE_ASM:
-		cmd = c.asPath
+		cmdName = c.asPath
+
+		// Include both the compiler flags and the assembler flags.
+		// XXX: This is not great.  We don't have a way of specifying compiler
+		// flags without also passing them to the assembler.
+		flags = append(c.cflagsStrings(), c.aflagsStrings()...)
 	case COMPILER_TYPE_CPP:
-		cmd = c.cppPath
+		cmdName = c.cppPath
+		flags = c.cflagsStrings()
 	default:
-		return "", util.NewNewtError("Unknown compiler type")
+		return nil, util.NewNewtError("Unknown compiler type")
 	}
 
-	cmd += " -c " + "-o " + objPath + " " + file +
-		" " + c.cflagsString() + " " + c.includesString()
+	cmd := []string{cmdName}
+	cmd = append(cmd, flags...)
+	cmd = append(cmd, c.includesStrings()...)
+	cmd = append(cmd, []string{
+		"-c",
+		"-o",
+		objPath,
+		file,
+	}...)
 
 	return cmd, nil
 }
@@ -365,23 +411,24 @@ func (c *Compiler) GenDepsForFile(file string) error {
 		strings.TrimSuffix(file, filepath.Ext(file)) + ".d"
 	depFile = filepath.ToSlash(depFile)
 
-	var cmd string
-	var err error
+	cmd := []string{c.ccPath}
+	cmd = append(cmd, c.cflagsStrings()...)
+	cmd = append(cmd, c.includesStrings()...)
+	cmd = append(cmd, []string{"-MM", "-MG", file}...)
 
-	cmd = c.ccPath + " " + c.cflagsString() + " " + c.includesString() +
-		" -MM -MG " + file + " > " + depFile
-	o, err := util.ShellCommand(cmd)
+	o, err := util.ShellCommandLimitDbgOutput(cmd, nil, 0)
 	if err != nil {
 		return util.NewNewtError(string(o))
 	}
 
-	// Append the extra dependencies (.yml files) to the .d file.
-	f, err := os.OpenFile(depFile, os.O_APPEND|os.O_WRONLY, 0666)
+	// Write the compiler output to a dependency file.
+	f, err := os.OpenFile(depFile, os.O_CREATE|os.O_WRONLY, 0666)
 	if err != nil {
 		return util.NewNewtError(err.Error())
 	}
 	defer f.Close()
 
+	// Append the extra dependencies (.yml files) to the .d file.
 	objFile := strings.TrimSuffix(file, filepath.Ext(file)) + ".o"
 	if _, err := f.WriteString(objFile + ": " + c.depsString()); err != nil {
 		return util.NewNewtError(err.Error())
@@ -390,16 +437,23 @@ func (c *Compiler) GenDepsForFile(file string) error {
 	return nil
 }
 
+func serializeCommand(cmd []string) []byte {
+	// Use a newline as the separator rather than a space to disambiguate cases
+	// where arguments contain spaces.
+	return []byte(strings.Join(cmd, "\n"))
+}
+
 // Writes a file containing the command-line invocation used to generate the
 // specified file.  The file that this function writes can be used later to
 // determine if the set of compiler options has changed.
 //
 // @param dstFile               The output file whose build invocation is being
 //                                  recorded.
-// @param cmd                   The command to write.
-func writeCommandFile(dstFile string, cmd string) error {
+// @param cmd                   The command strings to write.
+func writeCommandFile(dstFile string, cmd []string) error {
 	cmdPath := dstFile + ".cmd"
-	err := ioutil.WriteFile(cmdPath, []byte(cmd), 0644)
+	content := serializeCommand(cmd)
+	err := ioutil.WriteFile(cmdPath, content, 0644)
 	if err != nil {
 		return err
 	}
@@ -448,7 +502,7 @@ func (c *Compiler) CompileFile(file string, compilerType int) error {
 		return util.NewNewtError("Unknown compiler type")
 	}
 
-	_, err = util.ShellCommand(cmd)
+	_, err = util.ShellCommand(cmd, nil)
 	if err != nil {
 		return err
 	}
@@ -706,14 +760,13 @@ func (c *Compiler) RecursiveCompile(cType int, ignDirs []string) error {
 	}
 }
 
-func (c *Compiler) getObjFiles(baseObjFiles []string) string {
+func (c *Compiler) getObjFiles(baseObjFiles []string) []string {
 	for objName, _ := range c.ObjPathList {
 		baseObjFiles = append(baseObjFiles, objName)
 	}
 
 	sort.Strings(baseObjFiles)
-	objList := strings.Join(baseObjFiles, " ")
-	return objList
+	return baseObjFiles
 }
 
 // Calculates the command-line invocation necessary to link the specified elf
@@ -725,40 +778,48 @@ func (c *Compiler) getObjFiles(baseObjFiles []string) string {
 //                                  gets generated.
 // @param objFiles              An array of the source .o and .a filenames.
 //
-// @return                      (success) The command string.
+// @return                      (success) The command tokens.
 func (c *Compiler) CompileBinaryCmd(dstFile string, options map[string]bool,
-	objFiles []string, keepSymbols []string, elfLib string) string {
+	objFiles []string, keepSymbols []string, elfLib string) []string {
 
 	objList := c.getObjFiles(util.UniqueStrings(objFiles))
 
-	cmd := c.ccPath + " -o " + dstFile + " " + " " + c.cflagsString()
+	cmd := []string{
+		c.ccPath,
+		"-o",
+		dstFile,
+	}
+	cmd = append(cmd, c.cflagsStrings()...)
 
 	if elfLib != "" {
-		cmd += " -Wl,--just-symbols=" + elfLib
+		cmd = append(cmd, "-Wl,--just-symbols="+elfLib)
 	}
 
 	if c.ldResolveCircularDeps {
-		cmd += " -Wl,--start-group " + objList + " -Wl,--end-group "
+		cmd = append(cmd, "-Wl,--start-group")
+		cmd = append(cmd, objList...)
+		cmd = append(cmd, "-Wl,--end-group")
 	} else {
-		cmd += " " + objList
+		cmd = append(cmd, objList...)
 	}
 
 	if keepSymbols != nil {
 		for _, name := range keepSymbols {
-			cmd += " -Wl,--undefined=" + name
+			cmd = append(cmd, "-Wl,--undefined="+name)
 		}
 	}
 
-	cmd += " " + c.lflagsString()
+	cmd = append(cmd, c.lflagsStrings()...)
 
 	/* so we don't get multiple global definitions of the same vartiable */
 	//cmd += " -Wl,--warn-common "
 
 	for _, ls := range c.LinkerScripts {
-		cmd += " -T " + ls
+		cmd = append(cmd, "-T")
+		cmd = append(cmd, ls)
 	}
 	if options["mapFile"] {
-		cmd += " -Wl,-Map=" + dstFile + ".map"
+		cmd = append(cmd, "-Wl,-Map="+dstFile+".map")
 	}
 
 	return cmd
@@ -789,7 +850,7 @@ func (c *Compiler) CompileBinary(dstFile string, options map[string]bool,
 	}
 
 	cmd := c.CompileBinaryCmd(dstFile, options, objFiles, keepSymbols, elfLib)
-	_, err := util.ShellCommand(cmd)
+	_, err := util.ShellCommand(cmd, nil)
 	if err != nil {
 		return err
 	}
@@ -814,13 +875,22 @@ func (c *Compiler) CompileBinary(dstFile string, options map[string]bool,
 func (c *Compiler) generateExtras(elfFilename string,
 	options map[string]bool) error {
 
-	var cmd string
-
 	if options["binFile"] {
 		binFile := elfFilename + ".bin"
-		cmd = c.ocPath + " -R .bss -R .bss.core -R .bss.core.nz -O binary " +
-			elfFilename + " " + binFile
-		_, err := util.ShellCommand(cmd)
+		cmd := []string{
+			c.ocPath,
+			"-R",
+			".bss",
+			"-R",
+			".bss.core",
+			"-R",
+			".bss.core.nz",
+			"-O",
+			"binary",
+			elfFilename,
+			binFile,
+		}
+		_, err := util.ShellCommand(cmd, nil)
 		if err != nil {
 			return err
 		}
@@ -828,45 +898,71 @@ func (c *Compiler) generateExtras(elfFilename string,
 
 	if options["listFile"] {
 		listFile := elfFilename + ".lst"
-		// if list file exists, remove it
-		if util.NodeExist(listFile) {
-			if err := os.RemoveAll(listFile); err != nil {
-				return err
-			}
+		f, err := os.OpenFile(listFile, os.O_CREATE|os.O_WRONLY, 0666)
+		if err != nil {
+			return util.NewNewtError(err.Error())
 		}
+		defer f.Close()
 
-		cmd = c.odPath + " -wxdS " + elfFilename + " >> " + listFile
-		_, err := util.ShellCommand(cmd)
+		cmd := []string{
+			c.odPath,
+			"-wxdS",
+			elfFilename,
+		}
+		o, err := util.ShellCommandLimitDbgOutput(cmd, nil, 0)
 		if err != nil {
 			// XXX: gobjdump appears to always crash.  Until we get that sorted
 			// out, don't fail the link process if lst generation fails.
 			return nil
 		}
 
+		if _, err := f.Write(o); err != nil {
+			return util.ChildNewtError(err)
+		}
+
 		sects := []string{".text", ".rodata", ".data"}
 		for _, sect := range sects {
-			cmd = c.odPath + " -s -j " + sect + " " + elfFilename + " >> " +
-				listFile
-			util.ShellCommand(cmd)
+			cmd := []string{
+				c.odPath,
+				"-s",
+				"-j",
+				sect,
+				elfFilename,
+			}
+			o, err := util.ShellCommandLimitDbgOutput(cmd, nil, 0)
+			if err != nil {
+				if _, err := f.Write(o); err != nil {
+					return util.NewNewtError(err.Error())
+				}
+			}
 		}
 
-		cmd = c.osPath + " " + elfFilename + " >> " + listFile
-		_, err = util.ShellCommand(cmd)
+		cmd = []string{
+			c.osPath,
+			elfFilename,
+		}
+		o, err = util.ShellCommandLimitDbgOutput(cmd, nil, 0)
 		if err != nil {
 			return err
 		}
+		if _, err := f.Write(o); err != nil {
+			return util.NewNewtError(err.Error())
+		}
 	}
 
 	return nil
 }
 
 func (c *Compiler) PrintSize(elfFilename string) (string, error) {
-	cmd := c.osPath + " " + elfFilename
-	rsp, err := util.ShellCommand(cmd)
+	cmd := []string{
+		c.osPath,
+		elfFilename,
+	}
+	o, err := util.ShellCommand(cmd, nil)
 	if err != nil {
 		return "", err
 	}
-	return string(rsp), nil
+	return string(o), nil
 }
 
 // Links the specified elf file and generates some associated artifacts (lst,
@@ -908,33 +1004,39 @@ func (c *Compiler) CompileElf(binFile string, objFiles []string,
 	return nil
 }
 
-func (c *Compiler) RenameSymbolsCmd(sm *symbol.SymbolMap, libraryFile string, ext string) string {
-	val := c.ocPath
+func (c *Compiler) RenameSymbolsCmd(
+	sm *symbol.SymbolMap, libraryFile string, ext string) []string {
 
+	cmd := []string{c.ocPath}
 	for s, _ := range *sm {
-		val += " --redefine-sym " + s + "=" + s + ext
+		cmd = append(cmd, "--redefine-sym")
+		cmd = append(cmd, s+"="+s+ext)
 	}
 
-	val += " " + libraryFile
-	return val
+	cmd = append(cmd, libraryFile)
+	return cmd
 }
 
-func (c *Compiler) ParseLibraryCmd(libraryFile string) string {
-	val := c.odPath + " -t " + libraryFile
-	return val
+func (c *Compiler) ParseLibraryCmd(libraryFile string) []string {
+	return []string{
+		c.odPath,
+		"-t",
+		libraryFile,
+	}
 }
 
-func (c *Compiler) CopySymbolsCmd(infile string, outfile string, sm *symbol.SymbolMap) string {
-
-	val := c.ocPath + " -S "
+func (c *Compiler) CopySymbolsCmd(infile string, outfile string, sm *symbol.SymbolMap) []string {
 
+	cmd := []string{c.ocPath, "-S"}
 	for symbol, _ := range *sm {
-		val += " -K " + symbol
+		cmd = append(cmd, "-K")
+		cmd = append(cmd, symbol)
 	}
 
-	val += " " + infile
-	val += " " + outfile
-	return val
+	cmd = append(cmd, infile)
+	cmd = append(cmd, outfile)
+
+	return cmd
 }
 
 // Calculates the command-line invocation necessary to archive the specified
@@ -945,10 +1047,15 @@ func (c *Compiler) CopySymbolsCmd(infile string, outfile string, sm *symbol.Symb
 //
 // @return                      The command string.
 func (c *Compiler) CompileArchiveCmd(archiveFile string,
-	objFiles []string) string {
+	objFiles []string) []string {
 
-	objList := c.getObjFiles(objFiles)
-	return c.arPath + " rcs " + archiveFile + " " + objList
+	cmd := []string{
+		c.arPath,
+		"rcs",
+		archiveFile,
+	}
+	cmd = append(cmd, c.getObjFiles(objFiles)...)
+	return cmd
 }
 
 func linkerScriptFileName(archiveFile string) string {
@@ -1027,18 +1134,18 @@ func (c *Compiler) CompileArchive(archiveFile string) error {
 	util.StatusMessage(util.VERBOSITY_DEFAULT, "Archiving %s\n",
 		path.Base(archiveFile))
 	objList := c.getObjFiles([]string{})
-	if objList == "" {
+	if objList == nil {
 		return nil
 	}
 	util.StatusMessage(util.VERBOSITY_VERBOSE, "Archiving %s with object "+
-		"files %s\n", archiveFile, objList)
+		"files %s\n", archiveFile, strings.Join(objList, " "))
 
 	if err != nil && !os.IsNotExist(err) {
 		return util.NewNewtError(err.Error())
 	}
 
 	cmd := c.CompileArchiveCmd(archiveFile, objFiles)
-	_, err = util.ShellCommand(cmd)
+	_, err = util.ShellCommand(cmd, nil)
 	if err != nil {
 		return err
 	}
@@ -1125,7 +1232,7 @@ func (c *Compiler) RenameSymbols(sm *symbol.SymbolMap, libraryFile string, ext s
 
 	cmd := c.RenameSymbolsCmd(sm, libraryFile, ext)
 
-	_, err := util.ShellCommand(cmd)
+	_, err := util.ShellCommand(cmd, nil)
 
 	return err
 }
@@ -1133,7 +1240,7 @@ func (c *Compiler) RenameSymbols(sm *symbol.SymbolMap, libraryFile string, ext s
 func (c *Compiler) ParseLibrary(libraryFile string) (error, []byte) {
 	cmd := c.ParseLibraryCmd(libraryFile)
 
-	out, err := util.ShellCommand(cmd)
+	out, err := util.ShellCommand(cmd, nil)
 	if err != nil {
 		return err, nil
 	}
@@ -1143,7 +1250,7 @@ func (c *Compiler) ParseLibrary(libraryFile string) (error, []byte) {
 func (c *Compiler) CopySymbols(infile string, outfile string, sm *symbol.SymbolMap) error {
 	cmd := c.CopySymbolsCmd(infile, outfile, sm)
 
-	_, err := util.ShellCommand(cmd)
+	_, err := util.ShellCommand(cmd, nil)
 	if err != nil {
 		return err
 	}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0241d2f3/newt/toolchain/deps.go
----------------------------------------------------------------------
diff --git a/newt/toolchain/deps.go b/newt/toolchain/deps.go
index 86ec2cc..a8c7f97 100644
--- a/newt/toolchain/deps.go
+++ b/newt/toolchain/deps.go
@@ -57,8 +57,7 @@ func parseDepsLine(line string) (string, []string, error) {
 
 	dFileTok := tokens[0]
 	if dFileTok[len(dFileTok)-1:] != ":" {
-		return "", nil, util.NewNewtError("Invalid Makefile dependency file; " +
-			"line missing ':'")
+		return "", nil, util.NewNewtError("line missing ':'")
 	}
 
 	dFileName := dFileTok[:len(dFileTok)-1]
@@ -91,7 +90,9 @@ func ParseDepsFile(filename string) ([]string, error) {
 	for _, line := range lines {
 		src, deps, err := parseDepsLine(line)
 		if err != nil {
-			return nil, err
+			return nil, util.FmtNewtError(
+				"Invalid Makefile dependency file \"%s\"; %s",
+				filename, err.Error())
 		}
 
 		if dFile == "" {
@@ -133,14 +134,16 @@ func (tracker *DepTracker) ProcessFileTime(file string) error {
 // @return                      true if the command has changed or if the
 //                                  destination file was never built;
 //                              false otherwise.
-func commandHasChanged(dstFile string, cmd string) bool {
+func commandHasChanged(dstFile string, cmd []string) bool {
 	cmdFile := dstFile + ".cmd"
 	prevCmd, err := ioutil.ReadFile(cmdFile)
 	if err != nil {
 		return true
 	}
 
-	return bytes.Compare(prevCmd, []byte(cmd)) != 0
+	curCmd := serializeCommand(cmd)
+
+	return bytes.Compare(prevCmd, curCmd) != 0
 }
 
 // Determines if the specified C or assembly file needs to be built.  A compile

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0241d2f3/newt/vendor/mynewt.apache.org/newt/util/util.go
----------------------------------------------------------------------
diff --git a/newt/vendor/mynewt.apache.org/newt/util/util.go b/newt/vendor/mynewt.apache.org/newt/util/util.go
index a1b85f2..37902b5 100644
--- a/newt/vendor/mynewt.apache.org/newt/util/util.go
+++ b/newt/vendor/mynewt.apache.org/newt/util/util.go
@@ -268,13 +268,47 @@ func ReadConfig(path string, name string) (*viper.Viper, error) {
 	}
 }
 
-// Execute the command specified by cmdStr on the shell and return results
-func ShellCommand(cmdStr string) ([]byte, error) {
-	log.Debug(cmdStr)
-	cmd := exec.Command("sh", "-c", cmdStr)
+// Execute the specified process and block until it completes.  Additionally,
+// the amount of combined stdout+stderr output to be logged to the debug log
+// can be restricted to a maximum number of characters.
+//
+// @param cmdStrs               The "argv" strings of the command to execute.
+// @param env                   Additional key=value pairs to inject into the
+//                                  child process's environment.  Specify null
+//                                  to just inherit the parent environment.
+// @param maxDbgOutputChrs      The maximum number of combined stdout+stderr
+//                                  characters to write to the debug log.
+//                                  Specify -1 for no limit; 0 for no output.
+//
+// @return []byte               Combined stdout and stderr output of process.
+// @return error                NewtError on failure.
+func ShellCommandLimitDbgOutput(
+	cmdStrs []string, env []string, maxDbgOutputChrs int) ([]byte, error) {
+
+	envLogStr := ""
+	if env != nil {
+		envLogStr = strings.Join(env, " ") + " "
+	}
+	log.Debugf("%s%s", envLogStr, strings.Join(cmdStrs, " "))
+
+	name := cmdStrs[0]
+	args := cmdStrs[1:]
+	cmd := exec.Command(name, args...)
+
+	if env != nil {
+		cmd.Env = append(env, os.Environ()...)
+	}
 
 	o, err := cmd.CombinedOutput()
-	log.Debugf("o=%s", string(o))
+
+	if maxDbgOutputChrs < 0 || len(o) <= maxDbgOutputChrs {
+		dbgStr := string(o)
+		log.Debugf("o=%s", dbgStr)
+	} else if maxDbgOutputChrs != 0 {
+		dbgStr := string(o[:maxDbgOutputChrs]) + "[...]"
+		log.Debugf("o=%s", dbgStr)
+	}
+
 	if err != nil {
 		return o, NewNewtError(string(o))
 	} else {
@@ -282,6 +316,19 @@ func ShellCommand(cmdStr string) ([]byte, error) {
 	}
 }
 
+// Execute the specified process and block until it completes.
+//
+// @param cmdStrs               The "argv" strings of the command to execute.
+// @param env                   Additional key=value pairs to inject into the
+//                                  child process's environment.  Specify null
+//                                  to just inherit the parent environment.
+//
+// @return []byte               Combined stdout and stderr output of process.
+// @return error                NewtError on failure.
+func ShellCommand(cmdStrs []string, env []string) ([]byte, error) {
+	return ShellCommandLimitDbgOutput(cmdStrs, env, -1)
+}
+
 // Run interactive shell command
 func ShellInteractiveCommand(cmdStr []string, env []string) error {
 	log.Print("[VERBOSE] " + cmdStr[0])
@@ -297,7 +344,10 @@ func ShellInteractiveCommand(cmdStr []string, env []string) error {
 		<-c
 	}()
 
-	env = append(env, os.Environ()...)
+	if env != nil {
+		env = append(env, os.Environ()...)
+	}
+
 	// Transfer stdin, stdout, and stderr to the new process
 	// and also set target directory for the shell to start in.
 	// and set the additional environment variables

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/0241d2f3/util/util.go
----------------------------------------------------------------------
diff --git a/util/util.go b/util/util.go
index a1b85f2..37902b5 100644
--- a/util/util.go
+++ b/util/util.go
@@ -268,13 +268,47 @@ func ReadConfig(path string, name string) (*viper.Viper, error) {
 	}
 }
 
-// Execute the command specified by cmdStr on the shell and return results
-func ShellCommand(cmdStr string) ([]byte, error) {
-	log.Debug(cmdStr)
-	cmd := exec.Command("sh", "-c", cmdStr)
+// Execute the specified process and block until it completes.  Additionally,
+// the amount of combined stdout+stderr output to be logged to the debug log
+// can be restricted to a maximum number of characters.
+//
+// @param cmdStrs               The "argv" strings of the command to execute.
+// @param env                   Additional key=value pairs to inject into the
+//                                  child process's environment.  Specify null
+//                                  to just inherit the parent environment.
+// @param maxDbgOutputChrs      The maximum number of combined stdout+stderr
+//                                  characters to write to the debug log.
+//                                  Specify -1 for no limit; 0 for no output.
+//
+// @return []byte               Combined stdout and stderr output of process.
+// @return error                NewtError on failure.
+func ShellCommandLimitDbgOutput(
+	cmdStrs []string, env []string, maxDbgOutputChrs int) ([]byte, error) {
+
+	envLogStr := ""
+	if env != nil {
+		envLogStr = strings.Join(env, " ") + " "
+	}
+	log.Debugf("%s%s", envLogStr, strings.Join(cmdStrs, " "))
+
+	name := cmdStrs[0]
+	args := cmdStrs[1:]
+	cmd := exec.Command(name, args...)
+
+	if env != nil {
+		cmd.Env = append(env, os.Environ()...)
+	}
 
 	o, err := cmd.CombinedOutput()
-	log.Debugf("o=%s", string(o))
+
+	if maxDbgOutputChrs < 0 || len(o) <= maxDbgOutputChrs {
+		dbgStr := string(o)
+		log.Debugf("o=%s", dbgStr)
+	} else if maxDbgOutputChrs != 0 {
+		dbgStr := string(o[:maxDbgOutputChrs]) + "[...]"
+		log.Debugf("o=%s", dbgStr)
+	}
+
 	if err != nil {
 		return o, NewNewtError(string(o))
 	} else {
@@ -282,6 +316,19 @@ func ShellCommand(cmdStr string) ([]byte, error) {
 	}
 }
 
+// Execute the specified process and block until it completes.
+//
+// @param cmdStrs               The "argv" strings of the command to execute.
+// @param env                   Additional key=value pairs to inject into the
+//                                  child process's environment.  Specify null
+//                                  to just inherit the parent environment.
+//
+// @return []byte               Combined stdout and stderr output of process.
+// @return error                NewtError on failure.
+func ShellCommand(cmdStrs []string, env []string) ([]byte, error) {
+	return ShellCommandLimitDbgOutput(cmdStrs, env, -1)
+}
+
 // Run interactive shell command
 func ShellInteractiveCommand(cmdStr []string, env []string) error {
 	log.Print("[VERBOSE] " + cmdStr[0])
@@ -297,7 +344,10 @@ func ShellInteractiveCommand(cmdStr []string, env []string) error {
 		<-c
 	}()
 
-	env = append(env, os.Environ()...)
+	if env != nil {
+		env = append(env, os.Environ()...)
+	}
+
 	// Transfer stdin, stdout, and stderr to the new process
 	// and also set target directory for the shell to start in.
 	// and set the additional environment variables