newt/toolchain/deps.go (270 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 toolchain import ( "bytes" "fmt" "io/ioutil" "os" "path/filepath" "strings" "time" "mynewt.apache.org/newt/util" ) type DepTracker struct { // Most recent .o modification time. MostRecentName string MostRecentTime time.Time compiler *Compiler } func NewDepTracker(c *Compiler) DepTracker { tracker := DepTracker{ MostRecentName: "???", MostRecentTime: time.Unix(0, 0), compiler: c, } return tracker } func (d *DepTracker) SetMostRecent(name string, t time.Time) { d.MostRecentName = name // Truncate sub-second part of timestamp. Timestamps of generated files // seems to differ among tools. See // https://github.com/apache/mynewt-newt/pull/276 for details. d.MostRecentTime = time.Unix(t.Unix(), 0) } // @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.MostRecentTime) { tracker.SetMostRecent(file, 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 } func logRebuildReqd(dest string, reason string) { util.StatusMessage(util.VERBOSITY_VERBOSE, "%s - rebuild required; %s\n", dest, reason) } func logRebuildReqdCmdChanged(dest string) { logRebuildReqd(dest, "different command") } func logRebuildReqdModTime(dest string, src string) { logRebuildReqd(dest, fmt.Sprintf( "source (%s) newer than destination", src)) } func logRebuildReqdNoDep(dest string, dep string) { logRebuildReqd(dest, fmt.Sprintf( "dependency \"%s\" has been deleted", dep)) } func logRebuildReqdNewDep(dest string, dep string) { logRebuildReqd(dest, fmt.Sprintf( "destination older than dependency (%s)", dep)) } // 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) { logRebuildReqdCmdChanged(srcFile) err := tracker.compiler.GenDepsForFile(srcFile, compilerType) if err != nil { return false, err } return true, nil } if util.NodeNotExist(depPath) { err := tracker.compiler.GenDepsForFile(srcFile, compilerType) 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) { logRebuildReqdModTime(objPath, 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, compilerType) 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. logRebuildReqdNoDep(srcFile, dep) os.Remove(depPath) return true, nil } else { depModTime, err = util.FileModificationTime(dep) if err != nil { return false, err } } if depModTime.After(objModTime) { logRebuildReqdNewDep(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) { logRebuildReqdCmdChanged(archiveFile) 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.MostRecentTime.After(aModTime) { logRebuildReqdModTime(archiveFile, tracker.MostRecentName) return true, nil } // The library is up to date. 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) { logRebuildReqdCmdChanged(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) { logRebuildReqdModTime(dstFile, elfLib) return true, nil } } // Check timestamp of each .o file in the project. if tracker.MostRecentTime.After(dstModTime) { logRebuildReqdModTime(dstFile, tracker.MostRecentName) 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) { logRebuildReqdNewDep(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) { logRebuildReqdModTime(dstFile, elfFile) return true, nil } for _, arch := range archFiles { objModTime, err := util.FileModificationTime(arch) if err != nil { return false, err } if objModTime.After(dstModTime) { logRebuildReqdModTime(dstFile, arch) 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 }