blob: adb99ce45386884f4a051b7d2a72589c9898a4b6 [file] [log] [blame]
/*
Copyright 2015 Runtime Inc.
Licensed 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 cli
import (
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"sort"
"strings"
"time"
)
const (
COMPILER_TYPE_C = 0
COMPILER_TYPE_ASM = 1
)
type Compiler struct {
ConfigPath string
TargetName string
BaseIncludes map[string]bool
ObjPathList map[string]bool
LinkerScript string
Cflags string
Aflags string
Lflags string
depTracker DepTracker
ccPath string
asPath string
arPath string
odPath string
osPath string
ocPath string
ldFlags string
ldResolveCircularDeps bool
ldMapFile bool
}
func NewCompiler(ccPath string, cDef string, tName string, includes []string) (
*Compiler, error) {
c := &Compiler{
ConfigPath: ccPath,
TargetName: tName,
BaseIncludes: map[string]bool{},
ObjPathList: map[string]bool{},
}
c.depTracker = NewDepTracker(c)
for _, incl := range includes {
c.BaseIncludes[incl] = true
}
StatusMessage(VERBOSITY_VERBOSE,
"Loading compiler %s, target %s, def %s\n", ccPath, tName, cDef)
err := c.ReadSettings(cDef)
if err != nil {
return nil, err
}
return c, nil
}
func (c *Compiler) ReadSettings(cDef string) error {
v, err := ReadConfig(c.ConfigPath, "compiler")
if err != nil {
return err
}
c.ccPath = v.GetString("compiler.path.cc")
c.asPath = v.GetString("compiler.path.as")
c.arPath = v.GetString("compiler.path.archive")
c.odPath = v.GetString("compiler.path.objdump")
c.osPath = v.GetString("compiler.path.objsize")
c.ocPath = v.GetString("compiler.path.objcopy")
cflags := v.GetStringSlice("compiler.flags." + cDef)
for _, flag := range cflags {
if strings.HasPrefix(flag, "compiler.flags") {
c.Cflags += " " + strings.Trim(v.GetString(flag), "\n")
} else {
c.Cflags += " " + strings.Trim(flag, "\n")
}
}
c.ldFlags = v.GetString("compiler.ld.flags")
c.ldResolveCircularDeps = v.GetBool("compiler.ld.resolve_circular_deps")
c.ldMapFile = v.GetBool("compiler.ld.mapfile")
log.Printf("[INFO] ccPath = %s, arPath = %s, flags = %s", c.ccPath,
c.arPath, c.Cflags)
return nil
}
// Skips compilation of the specified C or assembly file, but adds the name of
// the object file that would have been generated to the compiler's list of
// object files. This function is used when the object file is already up to
// date, so no compilation is necessary. The name of the object file should
// still be remembered so that it gets linked in to the final library or
// executable.
func (c *Compiler) SkipSourceFile(srcFile string) error {
wd, _ := os.Getwd()
objDir := wd + "/obj/" + c.TargetName + "/"
objFile := objDir + strings.TrimSuffix(srcFile, filepath.Ext(srcFile)) +
".o"
c.ObjPathList[filepath.ToSlash(objFile)] = true
// Update the dependency tracker with the object file's modification time.
// This is necessary later for determining if the library / executable
// needs to be rebuilt.
err := c.depTracker.ProcessFileTime(objFile)
if err != nil {
return err
}
return nil
}
// Generates a string consisting of all the necessary include path (-I)
// options. The result is sorted and contains no duplicate paths.
func (c *Compiler) IncludesString() string {
includes := make([]string, 0, len(c.BaseIncludes))
for k, _ := range c.BaseIncludes {
includes = append(includes, filepath.ToSlash(k))
}
sort.Strings(includes)
return "-I" + strings.Join(includes, " -I")
}
// Calculates the command-line invocation necessary to compile the specified C
// or assembly file.
//
// @param file The filename of the source file to compile.
// @param compilerType One of the COMPILER_TYPE_[...] constants.
//
// @return (success) The command string.
func (c *Compiler) CompileFileCmd(file string,
compilerType int) (string, error) {
wd, _ := os.Getwd()
objDir := wd + "/obj/" + c.TargetName + "/"
objFile := strings.TrimSuffix(file, filepath.Ext(file)) + ".o"
objPath := filepath.ToSlash(objDir + objFile)
var cmd string
switch compilerType {
case COMPILER_TYPE_C:
cmd = c.ccPath
case COMPILER_TYPE_ASM:
cmd = c.asPath
default:
return "", NewNewtError("Unknown compiler type")
}
cmd += " -c " + "-o " + objPath + " " + file +
" " + c.Cflags + " " + c.IncludesString()
return cmd, nil
}
// Generates a dependency Makefile (.d) for the specified source C file.
//
// @param file The name of the source file.
func (c *Compiler) GenDepsForFile(file string) error {
wd, _ := os.Getwd()
objDir := wd + "/obj/" + c.TargetName + "/"
if NodeNotExist(objDir) {
os.MkdirAll(objDir, 0755)
}
depFile := objDir + strings.TrimSuffix(file, filepath.Ext(file)) + ".d"
depFile = filepath.ToSlash(depFile)
cFlags := c.Cflags + " " + c.IncludesString()
var cmd string
var err error
cmd = c.ccPath + " " + cFlags + " -MM -MG " + file + " > " + depFile
_, err = ShellCommand(cmd)
if err != nil {
return err
}
return nil
}
// Compile the specified C or assembly file.
//
// @param file The filename of the source file to compile.
// @param compilerType One of the COMPILER_TYPE_[...] constants.
func (c *Compiler) CompileFile(file string, compilerType int) error {
wd, _ := os.Getwd()
objDir := wd + "/obj/" + c.TargetName + "/"
if NodeNotExist(objDir) {
os.MkdirAll(objDir, 0755)
}
objFile := strings.TrimSuffix(file, filepath.Ext(file)) + ".o"
objPath := objDir + objFile
c.ObjPathList[filepath.ToSlash(objPath)] = true
cmd, err := c.CompileFileCmd(file, compilerType)
if err != nil {
return err
}
switch compilerType {
case COMPILER_TYPE_C:
StatusMessage(VERBOSITY_DEFAULT, "Compiling %s\n", file)
case COMPILER_TYPE_ASM:
StatusMessage(VERBOSITY_DEFAULT, "Assembling %s\n", file)
default:
return NewNewtError("Unknown compiler type")
}
rsp, err := ShellCommand(cmd)
if err != nil {
StatusMessage(VERBOSITY_QUIET, string(rsp))
return err
}
err = WriteCommandFile(objPath, cmd)
if err != nil {
return err
}
// Tell the dependency tracker that an object file was just rebuilt.
c.depTracker.MostRecent = time.Now()
return nil
}
// Compiles all C files matching the specified file glob.
//
// @param match The file glob specifying which C files to
// compile.
func (c *Compiler) Compile(match string) error {
files, _ := filepath.Glob(match)
wd, err := os.Getwd()
if err != nil {
return err
}
log.Printf("[INFO] Compiling C if outdated (%s/%s) %s", wd, match,
strings.Join(files, " "))
for _, file := range files {
file = filepath.ToSlash(file)
compileRequired, err := c.depTracker.CompileRequired(file, 0)
if err != nil {
return err
}
if compileRequired {
err = c.CompileFile(file, COMPILER_TYPE_C)
} else {
err = c.SkipSourceFile(file)
}
if err != nil {
return err
}
}
return nil
}
// Compiles all assembly files matching the specified file glob.
//
// @param match The file glob specifying which assembly files
// to compile.
func (c *Compiler) CompileAs(match string) error {
files, _ := filepath.Glob(match)
wd, err := os.Getwd()
if err != nil {
return err
}
log.Printf("[INFO] Compiling assembly if outdated (%s/%s) %s", wd, match,
strings.Join(files, " "))
for _, file := range files {
compileRequired, err := c.depTracker.CompileRequired(file, 1)
if err != nil {
return err
}
if compileRequired {
err = c.CompileFile(file, COMPILER_TYPE_ASM)
} else {
err = c.SkipSourceFile(file)
}
if err != nil {
return err
}
}
return nil
}
func (c *Compiler) RecursiveClean(path string, tName string) error {
// Find all the subdirectories of path that contain an "obj/" directory,
// and remove that directory either altogether, or just the arch specific
// directory.
dirList, err := ioutil.ReadDir(path)
if err != nil {
return NewNewtError(err.Error())
}
for _, node := range dirList {
if node.IsDir() {
if node.Name() == "obj" || node.Name() == "bin" {
if tName == "" {
os.RemoveAll(path + "/" + node.Name() + "/")
} else {
os.RemoveAll(path + "/" + node.Name() + "/" + tName + "/")
}
} else {
// recurse into the directory.
err = c.RecursiveClean(path+"/"+node.Name(), tName)
if err != nil {
return err
}
}
}
}
return nil
}
func (c *Compiler) processEntry(wd string, node os.FileInfo, match string, cType int,
ignDirs []string) error {
// check to see if we ignore this element
for _, entry := range ignDirs {
if entry == node.Name() {
return nil
}
}
// if not, recurse into the directory
os.Chdir(wd + "/" + node.Name())
return c.RecursiveCompile(match, cType, ignDirs)
}
func (c *Compiler) RecursiveCompile(match string, cType int, ignDirs []string) error {
// Get a list of files in the current directory, and if they are a directory,
// and that directory is not in the ignDirs variable, then recurse into that
// directory and compile the files in there
wd, err := os.Getwd()
if err != nil {
return NewNewtError(err.Error())
}
wd = filepath.ToSlash(wd)
dirList, err := ioutil.ReadDir(wd)
if err != nil {
return NewNewtError(err.Error())
}
for _, node := range dirList {
if node.IsDir() {
err = c.processEntry(wd, node, match, cType, ignDirs)
if err != nil {
return err
}
}
}
os.Chdir(wd)
switch cType {
case 0:
return c.Compile(match)
case 1:
return c.CompileAs(match)
default:
return NewNewtError("Wrong compiler type specified to RecursiveCompile")
}
}
func (c *Compiler) getObjFiles(baseObjFiles []string) string {
for objName, _ := range c.ObjPathList {
baseObjFiles = append(baseObjFiles, objName)
}
sort.Strings(baseObjFiles)
objList := strings.Join(baseObjFiles, " ")
return objList
}
// Calculates the command-line invocation necessary to link the specified elf
// file.
//
// @param dstFile The filename of the destination elf file to
// link.
// @param options Some build options specifying how the elf file
// gets generated.
// @param objFiles An array of the source .o and .a filenames.
//
// @return (success) The command string.
func (c *Compiler) CompileBinaryCmd(dstFile string, options map[string]bool,
objFiles []string) string {
objList := c.getObjFiles(UniqueStrings(objFiles))
cmd := c.ccPath + " -o " + dstFile + " " + c.ldFlags + " " + c.Cflags
if c.ldResolveCircularDeps {
cmd += " -Wl,--start-group -lc " + objList + " -Wl,--end-group "
} else {
cmd += " " + objList
}
if c.LinkerScript != "" {
cmd += " -T " + c.LinkerScript
}
if checkBoolMap(options, "mapFile") {
cmd += " -Wl,-Map=" + dstFile + ".map"
}
return cmd
}
// Links the specified elf file.
//
// @param dstFile The filename of the destination elf file to
// link.
// @param options Some build options specifying how the elf file
// gets generated.
// @param objFiles An array of the source .o and .a filenames.
func (c *Compiler) CompileBinary(dstFile string, options map[string]bool,
objFiles []string) error {
objList := c.getObjFiles(UniqueStrings(objFiles))
StatusMessage(VERBOSITY_DEFAULT, "Linking %s\n", path.Base(dstFile))
StatusMessage(VERBOSITY_VERBOSE, "Linking %s with input files %s\n",
dstFile, objList)
cmd := c.CompileBinaryCmd(dstFile, options, objFiles)
rsp, err := ShellCommand(cmd)
if err != nil {
StatusMessage(VERBOSITY_QUIET, string(rsp))
return err
}
err = WriteCommandFile(dstFile, cmd)
if err != nil {
return err
}
return nil
}
// Generates the following build artifacts:
// * lst file
// * map file
// * bin file
//
// @param elfFilename The filename of the elf file corresponding to
// the artifacts to be generated.
// @param options Some build options specifying which artifacts
// get generated.
func (c *Compiler) generateExtras(elfFilename string,
options map[string]bool) error {
var cmd string
if checkBoolMap(options, "listFile") {
listFile := elfFilename + ".lst"
// if list file exists, remove it
if NodeExist(listFile) {
if err := os.RemoveAll(listFile); err != nil {
return err
}
}
cmd = c.odPath + " -wxdS " + elfFilename + " >> " + listFile
_, err := ShellCommand(cmd)
if err != nil {
// XXX: gobjdump appears to always crash. Until we get that sorted
// out, don't fail the link process if lst generation fails.
return nil
}
sects := []string{".text", ".rodata", ".data"}
for _, sect := range sects {
cmd = c.odPath + " -s -j " + sect + " " + elfFilename + " >> " +
listFile
ShellCommand(cmd)
}
cmd = c.osPath + " " + elfFilename + " >> " + listFile
_, err = ShellCommand(cmd)
if err != nil {
return err
}
}
if checkBoolMap(options, "binFile") {
binFile := elfFilename + ".bin"
cmd = c.ocPath + " -R .bss -R .bss.core -R .bss.core.nz -O binary " +
elfFilename + " " + binFile
_, err := ShellCommand(cmd)
if err != nil {
return err
}
}
return nil
}
// Links the specified elf file and generates some associated artifacts (lst,
// bin, and map files).
//
// @param binFile The filename of the destination elf file to
// link.
// @param options Some build options specifying how the elf file
// gets generated.
// @param objFiles An array of the source .o and .a filenames.
func (c *Compiler) CompileElf(binFile string, options map[string]bool,
objFiles []string) error {
binFile += ".elf"
linkRequired, err := c.depTracker.LinkRequired(binFile, options, objFiles)
if err != nil {
return err
}
if linkRequired {
err := c.CompileBinary(binFile, options, objFiles)
if err != nil {
return err
}
}
err = c.generateExtras(binFile, options)
if err != nil {
return err
}
return nil
}
// Calculates the command-line invocation necessary to archive the specified
// static library.
//
// @param archiveFile The filename of the library to archive.
// @param objFiles An array of the source .o filenames.
//
// @return The command string.
func (c *Compiler) CompileArchiveCmd(archiveFile string,
objFiles []string) string {
objList := c.getObjFiles(objFiles)
return c.arPath + " rcs " + archiveFile + " " + objList
}
// Archives the specified static library.
//
// @param archiveFile The filename of the library to archive.
// @param objFiles An array of the source .o filenames.
func (c *Compiler) CompileArchive(archiveFile string, objFiles []string) error {
arRequired, err := c.depTracker.ArchiveRequired(archiveFile, objFiles)
if err != nil {
return err
}
if !arRequired {
return nil
}
objList := c.getObjFiles(objFiles)
StatusMessage(VERBOSITY_DEFAULT, "Archiving %s\n", path.Base(archiveFile))
StatusMessage(VERBOSITY_VERBOSE, "Archiving %s with object files %s",
archiveFile, objList)
// Delete the old archive, if it exists.
err = os.Remove(archiveFile)
if err != nil && !os.IsNotExist(err) {
return NewNewtError(err.Error())
}
cmd := c.CompileArchiveCmd(archiveFile, objFiles)
rsp, err := ShellCommand(cmd)
if err != nil {
StatusMessage(VERBOSITY_QUIET, string(rsp))
return err
}
err = WriteCommandFile(archiveFile, cmd)
if err != nil {
return err
}
return nil
}