blob: 23dd4b9aee7ca61825288f77c85910f8667edc5c [file] [log] [blame]
package cli
import (
"os"
"path/filepath"
"strings"
"time"
)
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
}
// Parses a dependency (.d) file generated by gcc. On success, the returned
// string array is populated with the dependency filenames. This function
// expects the first line of a dependency file to have the following format:
//
// <file>.d: <file>.c a.h b.h c.h \
// d.h e.h f.h
//
// This function ignores all lines except for the first.
func ParseDepsFile(filename string) ([]string, error) {
lines, err := ReadLines(filename)
if err != nil {
return nil, err
}
if len(lines) == 0 {
return []string{}, nil
}
// Assume only the first line is important.
tokens := strings.Fields(lines[0])
if len(tokens) == 0 {
return nil, NewNewtError("Invalid Makefile dependency file; first " +
"line is blank")
}
dFileTok := tokens[0]
if dFileTok[len(dFileTok)-1:] != ":" {
return nil, NewNewtError("Invalid Makefile dependency file; first " +
"line missing ':'")
}
return tokens[1:], 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 := FileModificationTime(file)
if err != nil {
return err
}
if modTime.After(tracker.MostRecent) {
tracker.MostRecent = modTime
}
return nil
}
// 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) {
wd, _ := os.Getwd()
objDir := wd + "/obj/" + tracker.compiler.TargetName + "/"
objFile := objDir + strings.TrimSuffix(srcFile, filepath.Ext(srcFile)) +
".o"
depFile := objDir + strings.TrimSuffix(srcFile, filepath.Ext(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(objFile, cmd) {
return true, nil
}
srcModTime, err := FileModificationTime(srcFile)
if err != nil {
return false, err
}
objModTime, err := FileModificationTime(objFile)
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) {
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 := FileModificationTime(depFile)
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(depFile)
if err != nil {
return false, err
}
// Check if any dependencies are newer than the destination object file.
for _, dep := range deps {
if NodeNotExist(dep) {
depModTime = time.Now()
} else {
depModTime, err = FileModificationTime(dep)
if err != nil {
return false, err
}
}
if depModTime.After(objModTime) {
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 := 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
}
// 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) (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)
if CommandHasChanged(dstFile, cmd) {
return true, nil
}
// If the elf file doesn't exist or is older than any input file, a rebuild
// is required.
dstModTime, err := FileModificationTime(dstFile)
if err != nil {
return false, err
}
// Check timestamp of each .o file in the project.
if tracker.MostRecent.After(dstModTime) {
return true, nil
}
// Check timestamp of the linker script and all input libraries.
if tracker.compiler.LinkerScript != "" {
objFiles = append(objFiles, tracker.compiler.LinkerScript)
}
for _, obj := range objFiles {
objModTime, err := FileModificationTime(obj)
if err != nil {
return false, err
}
if objModTime.After(dstModTime) {
return true, nil
}
}
return false, nil
}