blob: 3424cc02bd29bd0b00c19d817ccdbaa1ff9041cd [file] [log] [blame]
/**
* 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
}