| 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 |
| } |