blob: db04e6d4ef8911ac3a9d8958a213bfed3fb0f82c [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 toolchain
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"mynewt.apache.org/newt/util"
)
type DepTracker struct {
// Most recent .o modification time.
MostRecent time.Time
compiler *Compiler
}
func NewDepTracker(c *Compiler) DepTracker {
tracker := DepTracker{
MostRecent: time.Unix(0, 0),
compiler: c,
}
return tracker
}
// @return string The name of the dependent file (i.e., the first
// .o file encountered).
// @return []string Populated with the dependencies' filenames.
func parseDepsLine(line string) (string, []string, error) {
tokens := strings.Fields(line)
if len(tokens) == 0 {
return "", nil, nil
}
dFileTok := tokens[0]
if dFileTok[len(dFileTok)-1:] != ":" {
return "", nil, util.NewNewtError("line missing ':'")
}
dFileName := dFileTok[:len(dFileTok)-1]
return dFileName, tokens[1:], nil
}
// Parses a dependency (.d) file generated by gcc. On success, the returned
// string array is populated with the dependency filenames. This function
// expects each line of a dependency file to have the following format:
//
// <file>.o: <file>.c a.h b.h c.h \
// d.h e.h f.h
//
// Only the first dependent object(<file>.o) is considered.
//
// @return []string Populated with the dependencies' filenames.
func ParseDepsFile(filename string) ([]string, error) {
lines, err := util.ReadLines(filename)
if err != nil {
return nil, err
}
if len(lines) == 0 {
return []string{}, nil
}
var dFile string
allDeps := []string{}
for _, line := range lines {
src, deps, err := parseDepsLine(line)
if err != nil {
return nil, util.FmtNewtError(
"Invalid Makefile dependency file \"%s\"; %s",
filename, err.Error())
}
if dFile == "" {
dFile = src
}
if src == dFile {
allDeps = append(allDeps, deps...)
}
}
return allDeps, nil
}
// Updates the dependency tracker's most recent timestamp according to the
// modification time of the specified file. If the specified file is older
// than the tracker's currently most-recent time, this function has no effect.
func (tracker *DepTracker) ProcessFileTime(file string) error {
modTime, err := util.FileModificationTime(file)
if err != nil {
return err
}
if modTime.After(tracker.MostRecent) {
tracker.MostRecent = modTime
}
return nil
}
// Determines if a file was previously built with a command line invocation
// different from the one specified.
//
// @param dstFile The output file whose build invocation is being
// tested.
// @param cmd The command that would be used to generate the
// specified destination file.
//
// @return true if the command has changed or if the
// destination file was never built;
// false otherwise.
func commandHasChanged(dstFile string, cmd []string) bool {
cmdFile := dstFile + ".cmd"
prevCmd, err := ioutil.ReadFile(cmdFile)
if err != nil {
return true
}
curCmd := serializeCommand(cmd)
changed := bytes.Compare(prevCmd, curCmd) != 0
return changed
}
// Determines if the specified C or assembly file needs to be built. A compile
// is required if any of the following is true:
// * The destination object file does not exist.
// * The existing object file was built with a different compiler
// invocation.
// * The source file has a newer modification time than the object file.
// * One or more included header files has a newer modification time than
// the object file.
func (tracker *DepTracker) CompileRequired(srcFile string,
compilerType int) (bool, error) {
objPath := tracker.compiler.dstFilePath(srcFile) + ".o"
depPath := tracker.compiler.dstFilePath(srcFile) + ".d"
// If the object was previously built with a different set of options, a
// rebuild is necessary.
cmd, err := tracker.compiler.CompileFileCmd(srcFile, compilerType)
if err != nil {
return false, err
}
if commandHasChanged(objPath, cmd) {
util.StatusMessage(util.VERBOSITY_VERBOSE, "%s - rebuild required; "+
"different command\n", srcFile)
err := tracker.compiler.GenDepsForFile(srcFile)
if err != nil {
return false, err
}
return true, nil
}
if util.NodeNotExist(depPath) {
err := tracker.compiler.GenDepsForFile(srcFile)
if err != nil {
return false, err
}
}
srcModTime, err := util.FileModificationTime(srcFile)
if err != nil {
return false, err
}
objModTime, err := util.FileModificationTime(objPath)
if err != nil {
return false, err
}
// If the object doesn't exist or is older than the source file, a build is
// required; no need to check dependencies.
if srcModTime.After(objModTime) {
util.StatusMessage(util.VERBOSITY_VERBOSE, "%s - rebuild required; "+
"source newer than obj\n", srcFile)
return true, nil
}
// Determine if the dependency (.d) file needs to be generated. If it
// doesn't exist or is older than the source file, it is out of date and
// needs to be created.
depModTime, err := util.FileModificationTime(depPath)
if err != nil {
return false, err
}
if srcModTime.After(depModTime) {
err := tracker.compiler.GenDepsForFile(srcFile)
if err != nil {
return false, err
}
}
// Extract the dependency filenames from the dependency file.
deps, err := ParseDepsFile(depPath)
if err != nil {
return false, err
}
// Check if any dependencies are newer than the destination object file.
for _, dep := range deps {
if util.NodeNotExist(dep) {
// The dependency has been deleted; a rebuild is required. Also,
// the dependency file is out of date, so it needs to be deleted.
// We cannot regenerate it now because the source file might be
// including a nonexistent header.
util.StatusMessage(util.VERBOSITY_VERBOSE,
"%s - rebuild required; dependency \"%s\" has been deleted\n",
srcFile, dep)
os.Remove(depPath)
return true, nil
} else {
depModTime, err = util.FileModificationTime(dep)
if err != nil {
return false, err
}
}
if depModTime.After(objModTime) {
util.StatusMessage(util.VERBOSITY_VERBOSE, "%s - rebuild required; obj older than dependency (%s)\n", srcFile, dep)
return true, nil
}
}
return false, nil
}
// Determines if the specified static library needs to be rearchived. The
// library needs to be archived if any of the following is true:
// * The destination library file does not exist.
// * The existing library file was built with a different compiler
// invocation.
// * One or more source object files has a newer modification time than the
// library file.
func (tracker *DepTracker) ArchiveRequired(archiveFile string,
objFiles []string) (bool, error) {
// If the archive was previously built with a different set of options, a
// rebuild is required.
cmd := tracker.compiler.CompileArchiveCmd(archiveFile, objFiles)
if commandHasChanged(archiveFile, cmd) {
return true, nil
}
// If the archive doesn't exist or is older than any object file, a rebuild
// is required.
aModTime, err := util.FileModificationTime(archiveFile)
if err != nil {
return false, err
}
if tracker.MostRecent.After(aModTime) {
return true, nil
}
// The library is up to date.
return false, nil
}
func (tracker *DepTracker) TrimmedArchiveRequired(dstFile string,
srcFile string, elfLib string) (bool, error) {
// If the .A file doesn't exist or is older than the input file, a rebuild
// is required.
dstModTime, err := util.FileModificationTime(dstFile)
if err != nil {
return false, err
}
// If the elf file doesn't exist or is older than any input file,
// a rebuild is required.
if elfLib != "" {
elfDstModTime, err := util.FileModificationTime(elfLib)
if err != nil {
return false, err
}
if elfDstModTime.After(dstModTime) {
return true, nil
}
}
objModTime, err := util.FileModificationTime(srcFile)
if err != nil {
return false, err
}
if objModTime.After(dstModTime) {
return true, nil
}
return false, nil
}
// Determines if the specified elf file needs to be linked. Linking is
// necessary if the elf file does not exist or has an older modification time
// than any source object or library file.
// Determines if the specified static library needs to be rearchived. The
// library needs to be archived if any of the following is true:
// * The destination library file does not exist.
// * The existing library file was built with a different compiler
// invocation.
// * One or more source object files has a newer modification time than the
// library file.
func (tracker *DepTracker) LinkRequired(dstFile string,
options map[string]bool, objFiles []string,
keepSymbols []string, elfLib string) (bool, error) {
// If the elf file was previously built with a different set of options, a
// rebuild is required.
cmd := tracker.compiler.CompileBinaryCmd(dstFile, options, objFiles, keepSymbols, elfLib)
if commandHasChanged(dstFile, cmd) {
util.StatusMessage(util.VERBOSITY_VERBOSE, "%s - link required; "+
"different command\n", dstFile)
return true, nil
}
// If the elf file doesn't exist or is older than any input file, a rebuild
// is required.
dstModTime, err := util.FileModificationTime(dstFile)
if err != nil {
return false, err
}
// If the elf file doesn't exist or is older than any input file, a rebuild
// is required.
if elfLib != "" {
elfDstModTime, err := util.FileModificationTime(elfLib)
if err != nil {
return false, err
}
if elfDstModTime.After(dstModTime) {
util.StatusMessage(util.VERBOSITY_VERBOSE, "%s - link required; "+
"old elf file\n", elfLib)
return true, nil
}
}
// Check timestamp of each .o file in the project.
if tracker.MostRecent.After(dstModTime) {
util.StatusMessage(util.VERBOSITY_VERBOSE, "%s - link required; "+
"source newer than elf\n", dstFile)
return true, nil
}
// Check timestamp of the linker script and all input libraries.
for _, ls := range tracker.compiler.LinkerScripts {
objFiles = append(objFiles, ls)
}
for _, obj := range objFiles {
objModTime, err := util.FileModificationTime(obj)
if err != nil {
return false, err
}
if objModTime.After(dstModTime) {
util.StatusMessage(util.VERBOSITY_VERBOSE, "%s - rebuild "+
"required; obj older than dependency (%s)\n", dstFile, obj)
return true, nil
}
}
return false, nil
}
/* Building a ROM elf is used for shared application linking.
* A ROM elf requires a rebuild if any of archives (.a files) are newer
* than the rom elf, or if the elf file is newer than the rom_elf */
func (tracker *DepTracker) RomElfBuildRequired(dstFile string, elfFile string,
archFiles []string) (bool, error) {
// If the rom_elf file doesn't exist or is older than any input file, a
// rebuild is required.
dstModTime, err := util.FileModificationTime(dstFile)
if err != nil {
return false, err
}
// If the elf file doesn't exist or is older than any input file, a rebuild
// is required.
elfDstModTime, err := util.FileModificationTime(elfFile)
if err != nil {
return false, err
}
if elfDstModTime.After(dstModTime) {
return true, nil
}
for _, arch := range archFiles {
objModTime, err := util.FileModificationTime(arch)
if err != nil {
return false, err
}
if objModTime.After(dstModTime) {
return true, nil
}
}
return false, nil
}
// Determines if the specified static library needs to be copied. The
// library needs to be archived if any of the following is true:
// * The destination library file does not exist.
// * Source object files has a newer modification time than the
// target file.
func (tracker *DepTracker) CopyRequired(srcFile string) (bool, error) {
tgtFile := tracker.compiler.DstDir() + "/" + filepath.Base(srcFile)
// If the target doesn't exist or is older than source file, a copy
// is required.
srcModTime, err := util.FileModificationTime(srcFile)
if err != nil {
return false, err
}
tgtModTime, err := util.FileModificationTime(tgtFile)
if err != nil {
return false, err
}
if srcModTime.After(tgtModTime) {
return true, nil
}
// The target is up to date.
return false, nil
}