newt/builder/extcmd.go (190 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 builder import ( "io/ioutil" "os" "os/exec" "github.com/kballard/go-shellquote" log "github.com/sirupsen/logrus" "mynewt.apache.org/newt/newt/stage" "mynewt.apache.org/newt/util" ) // replaceArtifactsIfChanged compares the artifacts just produced (temp // directory) to those from the previous build (user bin directory). If they // are different, it replaces the old with the new so that they get relinked // during this build. func replaceArtifactsIfChanged(oldDir string, newDir string) error { eq, err := util.DirsAreEqual(oldDir, newDir) if err != nil { return err } if eq { // No changes detected. return nil } log.Debugf("changes detected; replacing %s with %s", oldDir, newDir) os.RemoveAll(oldDir) if err := util.MoveDir(newDir, oldDir); err != nil { return err } return nil } // createTempUserDirs creates a set of temporary directories for holding build // inputs. It returns: // * base-dir // * src-dir // * include-dir func createTempUserDirs(label string) (string, string, string, error) { tmpDir, err := ioutil.TempDir("", "mynewt-user-"+label) if err != nil { return "", "", "", util.ChildNewtError(err) } log.Debugf("created user %s dir: %s", label, tmpDir) tmpSrcDir := UserTempSrcDir(tmpDir) log.Debugf("creating user %s src dir: %s", label, tmpSrcDir) if err := os.MkdirAll(tmpSrcDir, 0755); err != nil { os.RemoveAll(tmpDir) return "", "", "", util.ChildNewtError(err) } tmpIncDir := UserTempIncludeDir(tmpDir) log.Debugf("creating user %s include dir: %s", label, tmpIncDir) if err := os.MkdirAll(tmpIncDir, 0755); err != nil { os.RemoveAll(tmpDir) return "", "", "", util.ChildNewtError(err) } return tmpDir, tmpSrcDir, tmpIncDir, nil } // envVarsForCmd calculates the set of environment variables to export for the // specified external command. func (t *TargetBuilder) envVarsForCmd(sf stage.StageFunc, userSrcDir string, userIncDir string, workDir string) (map[string]string, error) { // Determine whether the owning package is part of the loader or the app. slot := 0 buildName := "app" if t.LoaderBuilder != nil { rpkg := t.res.LpkgRpkgMap[sf.Pkg] if rpkg == nil { return nil, util.FmtNewtError( "resolution missing expected package: %s", sf.Pkg.FullName()) } if t.LoaderBuilder.PkgMap[rpkg] != nil { buildName = "loader" } else { slot = 1 } } env, err := t.AppBuilder.EnvVars(slot) if err != nil { return nil, err } p := UserEnvParams{ Lpkg: sf.Pkg, TargetName: t.target.FullName(), BuildProfile: t.target.BuildProfile, AppName: t.appPkg.FullName(), BuildName: buildName, UserSrcDir: userSrcDir, UserIncDir: userIncDir, WorkDir: workDir, } uenv := UserEnvVars(p) for k, v := range uenv { env[k] = v } c, err := t.NewCompiler("", "") if err != nil { return nil, err } tenv := ToolchainEnvVars(c) for k, v := range tenv { env[k] = v } return env, nil } // execExtCmds executes a set of user scripts. func (t *TargetBuilder) execExtCmds(sf stage.StageFunc, userSrcDir string, userIncDir string, workDir string) error { env, err := t.envVarsForCmd(sf, userSrcDir, userIncDir, workDir) if err != nil { return err } toks, err := shellquote.Split(sf.Name) if err != nil { return util.FmtNewtError( "invalid command string: \"%s\": %s", sf.Name, err.Error()) } // Replace environment variables in command string. for i, tok := range toks { toks[i] = os.ExpandEnv(tok) } // If the command is in the user's PATH, expand it to its real location. cmd, err := exec.LookPath(toks[0]) if err == nil { toks[0] = cmd } // Execute the commands from the package's directory. pwd, err := os.Getwd() if err != nil { return util.ChildNewtError(err) } if err := os.Chdir(sf.Pkg.BasePath()); err != nil { return util.ChildNewtError(err) } defer os.Chdir(pwd) util.StatusMessage(util.VERBOSITY_DEFAULT, "Executing %s\n", sf.Name) if err := util.ShellInteractiveCommand(toks, env, true); err != nil { return err } return nil } // execPreBuildCmds runs the target's set of pre-build user commands. It is an // error if any command fails (exits with a nonzero status). func (t *TargetBuilder) execPreBuildCmds(workDir string) error { // Create temporary directories where scripts can put build inputs. tmpDir, tmpSrcDir, tmpIncDir, err := createTempUserDirs("pre-build") if err != nil { return err } defer func() { log.Debugf("removing user pre-build dir: %s", tmpDir) os.RemoveAll(tmpDir) }() for _, sf := range t.res.PreBuildCmdCfg.StageFuncs { if err := t.execExtCmds(sf, tmpSrcDir, tmpIncDir, workDir); err != nil { return err } } srcDir := UserPreBuildSrcDir(t.target.FullName()) if err := replaceArtifactsIfChanged(srcDir, tmpSrcDir); err != nil { return err } incDir := UserPreBuildIncludeDir(t.target.FullName()) if err := replaceArtifactsIfChanged(incDir, tmpIncDir); err != nil { return err } return nil } // execPreLinkCmds runs the target's set of post-build user commands. It is // an error if any command fails (exits with a nonzero status). func (t *TargetBuilder) execPreLinkCmds(workDir string) error { // Create temporary directories where scripts can put build inputs. tmpDir, tmpSrcDir, _, err := createTempUserDirs("pre-link") if err != nil { return err } defer func() { log.Debugf("removing user pre-link dir: %s", tmpDir) os.RemoveAll(tmpDir) }() for _, sf := range t.res.PreLinkCmdCfg.StageFuncs { if err := t.execExtCmds(sf, tmpSrcDir, "", workDir); err != nil { return err } } srcDir := UserPreLinkSrcDir(t.target.FullName()) err = replaceArtifactsIfChanged(srcDir, tmpSrcDir) if err != nil { return err } return nil } // execPostLinkCmds runs the target's set of post-build user commands. It is // an error if any command fails (exits with a nonzero status). func (t *TargetBuilder) execPostLinkCmds(workDir string) error { for _, sf := range t.res.PostLinkCmdCfg.StageFuncs { if err := t.execExtCmds(sf, "", "", workDir); err != nil { return err } } return nil } // makeUserDir creates a temporary directory where scripts can put build // inputs. func makeUserDir() (string, error) { tmpDir, err := ioutil.TempDir("", "mynewt-user") if err != nil { return "", util.ChildNewtError(err) } log.Debugf("created user dir: %s", tmpDir) return tmpDir, nil } func makeUserWorkDir() (string, error) { tmpDir, err := ioutil.TempDir("", "mynewt-user-work") if err != nil { return "", util.ChildNewtError(err) } log.Debugf("created user work dir: %s", tmpDir) return tmpDir, nil }