blob: 96476b3fad0a1c183f1cdf6f2d25a7a8d3afc244 [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 (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"time"
log "github.com/Sirupsen/logrus"
"mynewt.apache.org/newt/newt/newtutil"
"mynewt.apache.org/newt/newt/project"
"mynewt.apache.org/newt/newt/symbol"
"mynewt.apache.org/newt/newt/ycfg"
"mynewt.apache.org/newt/util"
)
const COMPILER_FILENAME string = "compiler.yml"
const (
COMPILER_TYPE_C = 0
COMPILER_TYPE_ASM = 1
COMPILER_TYPE_CPP = 2
COMPILER_TYPE_ARCHIVE = 3
)
type CompilerInfo struct {
Includes []string
Cflags []string
CXXflags []string
Lflags []string
Aflags []string
IgnoreFiles []*regexp.Regexp
IgnoreDirs []*regexp.Regexp
}
type CompileCommand struct {
Directory string `json:"directory"`
Command string `json:"command"`
File string `json:"file"`
}
type Compiler struct {
objPathList map[string]bool
LinkerScripts []string
// Needs to be locked whenever a mutable field in this struct is accessed
// during a build. Currently, objPathList is the only such member.
mutex *sync.Mutex
depTracker DepTracker
ccPath string
cppPath string
asPath string
arPath string
odPath string
osPath string
ocPath string
ldResolveCircularDeps bool
ldMapFile bool
ldBinFile bool
baseDir string
srcDir string
dstDir string
// The info to be applied during compilation.
info CompilerInfo
// Info read from the compiler package itself. This is kept separate from
// the rest of the info because it has the lowest priority; it can only be
// added immediately before compiling beings.
lclInfo CompilerInfo
// Indicates whether the local compiler info has been appended to the
// common info set. Ensures the local info only gets added once.
lclInfoAdded bool
compileCommands []CompileCommand
extraDeps []string
}
func (c *Compiler) GetCompileCommands() []CompileCommand {
return c.compileCommands
}
func (c *Compiler) GetCcPath() string {
return c.ccPath
}
func (c *Compiler) GetCppPath() string {
return c.cppPath
}
func (c *Compiler) GetAsPath() string {
return c.asPath
}
func (c *Compiler) GetArPath() string {
return c.arPath
}
func (c *Compiler) GetLdResolveCircularDeps() bool {
return c.ldResolveCircularDeps
}
func (c *Compiler) GetCompilerInfo() CompilerInfo {
return c.info
}
func (c *Compiler) GetLocalCompilerInfo() CompilerInfo {
return c.lclInfo
}
type CompilerJob struct {
Filename string
Compiler *Compiler
CompilerType int
}
func NewCompilerInfo() *CompilerInfo {
ci := &CompilerInfo{}
ci.Includes = []string{}
ci.Cflags = []string{}
ci.CXXflags = []string{}
ci.Lflags = []string{}
ci.Aflags = []string{}
ci.IgnoreFiles = []*regexp.Regexp{}
ci.IgnoreDirs = []*regexp.Regexp{}
return ci
}
// Extracts the base of a flag string. A flag base is used when detecting flag
// conflicts. If two flags have identicial bases, then they are in conflict.
func flagsBase(cflags string) string {
// "-O" (optimization level) is one possible flag base. By singling these
// out, newt can prevent the original optimization flag from being
// overwritten by subsequent ones.
if cflags == "-O" || len(cflags) == 3 && strings.HasPrefix(cflags, "-O") {
return "-O"
}
// Identify <key>=<value> pairs. Newt prevents subsequent assignments to
// the same key from overriding the original.
eqIdx := strings.IndexByte(cflags, '=')
if eqIdx == -1 {
return cflags
} else {
return cflags[:eqIdx]
}
}
// Creates a map of flag bases to flag values, i.e.,
// [flag-base] => flag
//
// This is used to make flag conflict detection more efficient.
func flagsMap(cflags []string) map[string]string {
hash := map[string]string{}
for _, cf := range cflags {
hash[flagsBase(cf)] = cf
}
return hash
}
// Appends a new set of flags to an original set. If a new flag conflicts with
// an original, the new flag is discarded. The assumption is that flags from
// higher priority packages get added first.
//
// This is not terribly efficient: it results in flag maps being generated
// repeatedly when they could be cached. Any inefficiencies here are probably
// negligible compared to the time spent compiling and linking. If this
// assumption turns out to be incorrect, we should cache the flag maps.
func addFlags(flagType string, orig []string, new []string) []string {
origMap := flagsMap(orig)
combined := orig
for _, c := range new {
newBase := flagsBase(c)
origVal := origMap[newBase]
if origVal == "" {
// New flag; add it.
combined = append(combined, c)
} else {
// Flag already present from a higher priority package; discard the
// new one.
if origVal != c {
log.Debugf("Discarding %s %s in favor of %s", flagType, c,
origVal)
}
}
}
return combined
}
func (ci *CompilerInfo) AddCflags(cflags []string) {
ci.Cflags = addFlags("cflag", ci.Cflags, cflags)
}
func (ci *CompilerInfo) AddCompilerInfo(newCi *CompilerInfo) {
ci.Includes = append(ci.Includes, newCi.Includes...)
ci.Cflags = addFlags("cflag", ci.Cflags, newCi.Cflags)
ci.CXXflags = addFlags("cxxflag", ci.CXXflags, newCi.CXXflags)
ci.Lflags = addFlags("lflag", ci.Lflags, newCi.Lflags)
ci.Aflags = addFlags("aflag", ci.Aflags, newCi.Aflags)
ci.IgnoreFiles = append(ci.IgnoreFiles, newCi.IgnoreFiles...)
ci.IgnoreDirs = append(ci.IgnoreDirs, newCi.IgnoreDirs...)
}
func NewCompiler(compilerDir string, dstDir string,
buildProfile string) (*Compiler, error) {
c := &Compiler{
mutex: &sync.Mutex{},
objPathList: map[string]bool{},
baseDir: project.GetProject().BasePath,
srcDir: "",
dstDir: dstDir,
extraDeps: []string{},
compileCommands: []CompileCommand{},
}
c.depTracker = NewDepTracker(c)
util.StatusMessage(util.VERBOSITY_VERBOSE,
"Loading compiler %s, buildProfile %s\n", compilerDir,
buildProfile)
err := c.load(compilerDir, buildProfile)
if err != nil {
return nil, err
}
return c, nil
}
func loadFlags(yc ycfg.YCfg, settings map[string]string, key string) []string {
flags := []string{}
rawFlags := yc.GetValStringSlice(key, settings)
for _, rawFlag := range rawFlags {
if strings.HasPrefix(rawFlag, key) {
expandedFlags := yc.GetValStringSlice(rawFlag, settings)
flags = append(flags, expandedFlags...)
} else {
flags = append(flags, strings.Trim(rawFlag, "\n"))
}
}
return flags
}
func (c *Compiler) load(compilerDir string, buildProfile string) error {
yc, err := newtutil.ReadConfig(compilerDir, "compiler")
if err != nil {
return err
}
settings := map[string]string{
buildProfile: "1",
strings.ToUpper(runtime.GOOS): "1",
}
c.ccPath = yc.GetValString("compiler.path.cc", settings)
c.cppPath = yc.GetValString("compiler.path.cpp", settings)
c.asPath = yc.GetValString("compiler.path.as", settings)
c.arPath = yc.GetValString("compiler.path.archive", settings)
c.odPath = yc.GetValString("compiler.path.objdump", settings)
c.osPath = yc.GetValString("compiler.path.objsize", settings)
c.ocPath = yc.GetValString("compiler.path.objcopy", settings)
c.lclInfo.Cflags = loadFlags(yc, settings, "compiler.flags")
c.lclInfo.CXXflags = loadFlags(yc, settings, "compiler.cxx.flags")
c.lclInfo.Lflags = loadFlags(yc, settings, "compiler.ld.flags")
c.lclInfo.Aflags = loadFlags(yc, settings, "compiler.as.flags")
c.ldResolveCircularDeps = yc.GetValBool(
"compiler.ld.resolve_circular_deps", settings)
c.ldMapFile = yc.GetValBool("compiler.ld.mapfile", settings)
c.ldBinFile = yc.GetValBoolDflt("compiler.ld.binfile", settings, true)
if len(c.lclInfo.Cflags) == 0 {
// Assume no Cflags implies an unsupported build profile.
return util.FmtNewtError("Compiler doesn't support build profile "+
"specified by target on this OS (build_profile=\"%s\" OS=\"%s\")",
buildProfile, runtime.GOOS)
}
return nil
}
func (c *Compiler) AddInfo(info *CompilerInfo) {
c.info.AddCompilerInfo(info)
}
func (c *Compiler) DstDir() string {
return c.dstDir
}
func (c *Compiler) SetSrcDir(srcDir string) {
c.srcDir = filepath.ToSlash(filepath.Clean(srcDir))
}
func (c *Compiler) AddDeps(depFilenames ...string) {
c.extraDeps = append(c.extraDeps, depFilenames...)
}
// 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 {
objPath := c.dstFilePath(srcFile) + ".o"
c.mutex.Lock()
c.objPathList[filepath.ToSlash(objPath)] = true
c.mutex.Unlock()
// 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(objPath)
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) includesStrings() []string {
if len(c.info.Includes) == 0 {
return nil
}
includes := util.SortFields(c.info.Includes...)
tokens := make([]string, len(includes))
for i, s := range includes {
s = strings.TrimPrefix(filepath.ToSlash(filepath.Clean(s)), c.baseDir+"/")
tokens[i] = "-I" + s
}
return tokens
}
func (c *Compiler) cflagsStrings() []string {
cflags := util.SortFields(c.info.Cflags...)
return cflags
}
func (c *Compiler) cxxflagsStrings() []string {
cxxflags := util.SortFields(c.info.CXXflags...)
return cxxflags
}
func (c *Compiler) aflagsStrings() []string {
aflags := util.SortFields(c.info.Aflags...)
return aflags
}
func (c *Compiler) lflagsStrings() []string {
lflags := util.SortFields(c.info.Lflags...)
return lflags
}
func (c *Compiler) depsString() string {
extraDeps := util.SortFields(c.extraDeps...)
return strings.Join(extraDeps, " ") + "\n"
}
func (c *Compiler) dstFilePath(srcPath string) string {
relSrcPath := strings.TrimPrefix(filepath.ToSlash(srcPath), c.baseDir+"/")
relDstPath := strings.TrimSuffix(relSrcPath, filepath.Ext(srcPath))
dstPath := fmt.Sprintf("%s/%s", c.dstDir, relDstPath)
return dstPath
}
// 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 arguments.
func (c *Compiler) CompileFileCmd(file string, compilerType int) (
[]string, error) {
objPath := c.dstFilePath(file) + ".o"
var cmdName string
var flags []string
switch compilerType {
case COMPILER_TYPE_C:
cmdName = c.ccPath
flags = c.cflagsStrings()
case COMPILER_TYPE_ASM:
cmdName = c.asPath
// Include both the compiler flags and the assembler flags.
// XXX: This is not great. We don't have a way of specifying compiler
// flags without also passing them to the assembler.
flags = append(c.cflagsStrings(), c.aflagsStrings()...)
case COMPILER_TYPE_CPP:
cmdName = c.cppPath
flags = append(c.cflagsStrings(), c.cxxflagsStrings()...)
default:
return nil, util.NewNewtError("Unknown compiler type")
}
srcPath := strings.TrimPrefix(file, c.baseDir+"/")
cmd := []string{cmdName}
cmd = append(cmd, flags...)
cmd = append(cmd, c.includesStrings()...)
cmd = append(cmd, []string{
"-c",
"-o",
objPath,
srcPath,
}...)
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 {
depPath := c.dstFilePath(file) + ".d"
depDir := filepath.Dir(depPath)
if util.NodeNotExist(depDir) {
os.MkdirAll(depDir, 0755)
}
srcPath := strings.TrimPrefix(file, c.baseDir+"/")
cmd := []string{c.ccPath}
cmd = append(cmd, c.cflagsStrings()...)
cmd = append(cmd, c.includesStrings()...)
cmd = append(cmd, []string{"-MM", "-MG", srcPath}...)
o, err := util.ShellCommandLimitDbgOutput(cmd, nil, true, 0)
if err != nil {
return err
}
// Write the compiler output to a dependency file.
f, err := os.OpenFile(depPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
return util.ChildNewtError(err)
}
defer f.Close()
if _, err := f.Write(o); err != nil {
return util.ChildNewtError(err)
}
// Append the extra dependencies (.yml files) to the .d file.
objFile := strings.TrimSuffix(file, filepath.Ext(file)) + ".o"
if _, err := f.WriteString(objFile + ": " + c.depsString()); err != nil {
return util.NewNewtError(err.Error())
}
return nil
}
func serializeCommand(cmd []string) []byte {
// Use a newline as the separator rather than a space to disambiguate cases
// where arguments contain spaces.
return []byte(strings.Join(cmd, "\n"))
}
// Writes a file containing the command-line invocation used to generate the
// specified file. The file that this function writes can be used later to
// determine if the set of compiler options has changed.
//
// @param dstFile The output file whose build invocation is being
// recorded.
// @param cmd The command strings to write.
func writeCommandFile(dstFile string, cmd []string) error {
cmdPath := dstFile + ".cmd"
content := serializeCommand(cmd)
err := ioutil.WriteFile(cmdPath, content, 0644)
if err != nil {
return err
}
return nil
}
// Adds the info from the compiler package to the common set if it hasn't
// already been added. The compiler package's info needs to be added last
// because the compiler is the lowest priority package.
func (c *Compiler) ensureLclInfoAdded() {
if !c.lclInfoAdded {
log.Debugf("Generating build flags for compiler")
c.AddInfo(&c.lclInfo)
c.lclInfoAdded = true
}
}
// 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 {
objPath := c.dstFilePath(file) + ".o"
objDir := filepath.Dir(objPath)
if util.NodeNotExist(objDir) {
os.MkdirAll(objDir, 0755)
}
c.mutex.Lock()
c.objPathList[filepath.ToSlash(objPath)] = true
c.mutex.Unlock()
cmd, err := c.CompileFileCmd(file, compilerType)
if err != nil {
return err
}
srcPath := strings.TrimPrefix(file, c.baseDir+"/")
switch compilerType {
case COMPILER_TYPE_C:
util.StatusMessage(util.VERBOSITY_DEFAULT, "Compiling %s\n", srcPath)
case COMPILER_TYPE_CPP:
util.StatusMessage(util.VERBOSITY_DEFAULT, "Compiling %s\n", srcPath)
case COMPILER_TYPE_ASM:
util.StatusMessage(util.VERBOSITY_DEFAULT, "Assembling %s\n", srcPath)
default:
return util.NewNewtError("Unknown compiler type")
}
_, err = util.ShellCommand(cmd, nil)
if err != nil {
return err
}
c.compileCommands = append(c.compileCommands,
CompileCommand{
Command: strings.Join(cmd, " "),
File: file,
})
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
}
func (c *Compiler) ShouldIgnoreFile(file string) bool {
file = strings.TrimPrefix(file, c.srcDir)
file = strings.TrimLeft(file, "/\\")
for _, re := range c.info.IgnoreFiles {
if match := re.MatchString(file); match {
return true
}
}
return false
}
func compilerTypeToExts(compilerType int) ([]string, error) {
switch compilerType {
case COMPILER_TYPE_C:
return []string{"c"}, nil
case COMPILER_TYPE_ASM:
return []string{"s", "S"}, nil
case COMPILER_TYPE_CPP:
return []string{"cc", "cpp", "cxx"}, nil
case COMPILER_TYPE_ARCHIVE:
return []string{"a"}, nil
default:
return nil, util.NewNewtError("Wrong compiler type specified to " +
"compilerTypeToExts")
}
}
// Compiles all C files matching the specified file glob.
func (c *Compiler) CompileC(filename string) error {
filename = filepath.ToSlash(filename)
if c.ShouldIgnoreFile(filename) {
log.Infof("Ignoring %s because package dictates it.", filename)
return nil
}
compileRequired, err := c.depTracker.CompileRequired(filename,
COMPILER_TYPE_C)
if err != nil {
return err
}
if compileRequired {
err = c.CompileFile(filename, COMPILER_TYPE_C)
} else {
err = c.SkipSourceFile(filename)
}
if err != nil {
return err
}
return nil
}
// Compiles all CPP files
func (c *Compiler) CompileCpp(filename string) error {
filename = filepath.ToSlash(filename)
if c.ShouldIgnoreFile(filename) {
log.Infof("Ignoring %s because package dictates it.", filename)
return nil
}
compileRequired, err := c.depTracker.CompileRequired(filename,
COMPILER_TYPE_CPP)
if err != nil {
return err
}
if compileRequired {
err = c.CompileFile(filename, COMPILER_TYPE_CPP)
} else {
err = c.SkipSourceFile(filename)
}
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(filename string) error {
filename = filepath.ToSlash(filename)
if c.ShouldIgnoreFile(filename) {
log.Infof("Ignoring %s because package dictates it.", filename)
return nil
}
compileRequired, err := c.depTracker.CompileRequired(filename,
COMPILER_TYPE_ASM)
if err != nil {
return err
}
if compileRequired {
err = c.CompileFile(filename, COMPILER_TYPE_ASM)
} else {
err = c.SkipSourceFile(filename)
}
if err != nil {
return err
}
return nil
}
// Copies all archive files matching the specified file glob.
//
// @param match The file glob specifying which assembly files
// to compile.
func (c *Compiler) CopyArchive(filename string) error {
filename = filepath.ToSlash(filename)
if c.ShouldIgnoreFile(filename) {
log.Infof("Ignoring %s because package dictates it.", filename)
return nil
}
tgtFile := c.dstDir + "/" + filepath.Base(filename)
copyRequired, err := c.depTracker.CopyRequired(filename)
if err != nil {
return err
}
if copyRequired {
err = util.CopyFile(filename, tgtFile)
util.StatusMessage(util.VERBOSITY_DEFAULT, "Copying %s\n",
filepath.ToSlash(tgtFile))
}
if err != nil {
return err
}
return nil
}
func (c *Compiler) processEntry(node os.FileInfo, cType int,
ignDirs []string) ([]CompilerJob, error) {
// check to see if we ignore this element
for _, dir := range ignDirs {
if dir == node.Name() {
return nil, nil
}
}
// Check in the user specified ignore directories
for _, dir := range c.info.IgnoreDirs {
if dir.MatchString(node.Name()) {
return nil, nil
}
}
// If not, recurse into the directory. Make the output directory
// structure mirror that of the source tree.
prevSrcDir := c.srcDir
prevDstDir := c.dstDir
c.srcDir += "/" + node.Name()
c.dstDir += "/" + node.Name()
entries, err := c.RecursiveCollectEntries(cType, ignDirs)
// Restore the compiler destination directory now that the child
// directory has been fully built.
c.srcDir = prevSrcDir
c.dstDir = prevDstDir
return entries, err
}
func (c *Compiler) RecursiveCollectEntries(cType int,
ignDirs []string) ([]CompilerJob, error) {
// Make sure the compiler package info is added to the global set.
c.ensureLclInfoAdded()
if err := os.Chdir(c.baseDir); err != nil {
return nil, util.ChildNewtError(err)
}
// 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
ls, err := ioutil.ReadDir(c.srcDir)
if err != nil {
return nil, util.NewNewtError(err.Error())
}
entries := []CompilerJob{}
for _, node := range ls {
if node.IsDir() {
subEntries, err := c.processEntry(node, cType, ignDirs)
if err != nil {
return nil, err
}
entries = append(entries, subEntries...)
}
}
exts, err := compilerTypeToExts(cType)
if err != nil {
return nil, err
}
for _, ext := range exts {
files, _ := filepath.Glob(c.srcDir + "/*." + ext)
for _, file := range files {
file = filepath.ToSlash(file)
entries = append(entries, CompilerJob{
Filename: file,
Compiler: c,
CompilerType: cType,
})
}
}
return entries, nil
}
func RunJob(record CompilerJob) error {
switch record.CompilerType {
case COMPILER_TYPE_C:
return record.Compiler.CompileC(record.Filename)
case COMPILER_TYPE_ASM:
return record.Compiler.CompileAs(record.Filename)
case COMPILER_TYPE_CPP:
return record.Compiler.CompileCpp(record.Filename)
case COMPILER_TYPE_ARCHIVE:
return record.Compiler.CopyArchive(record.Filename)
default:
return util.NewNewtError("Wrong compiler type specified to " +
"RunJob")
}
}
func (c *Compiler) getObjFiles(baseObjFiles []string) []string {
c.mutex.Lock()
for objName, _ := range c.objPathList {
baseObjFiles = append(baseObjFiles, objName)
}
c.mutex.Unlock()
sort.Strings(baseObjFiles)
return baseObjFiles
}
// 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 tokens.
func (c *Compiler) CompileBinaryCmd(dstFile string, options map[string]bool,
objFiles []string, keepSymbols []string, elfLib string) []string {
objList := c.getObjFiles(util.UniqueStrings(objFiles))
cmd := []string{
c.ccPath,
"-o",
dstFile,
}
cmd = append(cmd, c.cflagsStrings()...)
if elfLib != "" {
cmd = append(cmd, "-Wl,--just-symbols="+elfLib)
}
if c.ldResolveCircularDeps {
cmd = append(cmd, "-Wl,--start-group")
cmd = append(cmd, objList...)
cmd = append(cmd, "-Wl,--end-group")
} else {
cmd = append(cmd, objList...)
}
if keepSymbols != nil {
for _, name := range keepSymbols {
cmd = append(cmd, "-Wl,--undefined="+name)
}
}
cmd = append(cmd, c.lflagsStrings()...)
/* so we don't get multiple global definitions of the same vartiable */
//cmd += " -Wl,--warn-common "
for _, ls := range c.LinkerScripts {
cmd = append(cmd, "-T")
cmd = append(cmd, ls)
}
if options["mapFile"] {
cmd = append(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, keepSymbols []string, elfLib string) error {
// Make sure the compiler package info is added to the global set.
c.ensureLclInfoAdded()
objList := c.getObjFiles(util.UniqueStrings(objFiles))
util.StatusMessage(util.VERBOSITY_DEFAULT, "Linking %s\n", dstFile)
util.StatusMessage(util.VERBOSITY_VERBOSE, "Linking %s with input files %s\n",
dstFile, objList)
if elfLib != "" {
util.StatusMessage(util.VERBOSITY_VERBOSE, "Linking %s with rom image %s\n",
dstFile, elfLib)
}
cmd := c.CompileBinaryCmd(dstFile, options, objFiles, keepSymbols, elfLib)
_, err := util.ShellCommand(cmd, nil)
if err != nil {
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 {
if options["binFile"] {
binFile := elfFilename + ".bin"
cmd := []string{
c.ocPath,
"-R",
".bss",
"-R",
".bss.core",
"-R",
".bss.core.nz",
"-O",
"binary",
elfFilename,
binFile,
}
_, err := util.ShellCommand(cmd, nil)
if err != nil {
return err
}
}
if options["listFile"] {
listFile := elfFilename + ".lst"
f, err := os.OpenFile(listFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC,
0666)
if err != nil {
return util.NewNewtError(err.Error())
}
defer f.Close()
cmd := []string{
c.odPath,
"-wxdS",
elfFilename,
}
o, err := util.ShellCommandLimitDbgOutput(cmd, nil, true, 0)
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
}
if _, err := f.Write(o); err != nil {
return util.ChildNewtError(err)
}
sects := []string{".text", ".rodata", ".data"}
for _, sect := range sects {
cmd := []string{
c.odPath,
"-s",
"-j",
sect,
elfFilename,
}
o, err := util.ShellCommandLimitDbgOutput(cmd, nil, true, 0)
if err != nil {
if _, err := f.Write(o); err != nil {
return util.NewNewtError(err.Error())
}
}
}
cmd = []string{
c.osPath,
elfFilename,
}
o, err = util.ShellCommandLimitDbgOutput(cmd, nil, true, 0)
if err != nil {
return err
}
if _, err := f.Write(o); err != nil {
return util.NewNewtError(err.Error())
}
}
return nil
}
func (c *Compiler) PrintSize(elfFilename string) (string, error) {
cmd := []string{
c.osPath,
elfFilename,
}
o, err := util.ShellCommand(cmd, nil)
if err != nil {
return "", err
}
return string(o), 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, objFiles []string,
keepSymbols []string, elfLib string) error {
options := map[string]bool{"mapFile": c.ldMapFile,
"listFile": true, "binFile": c.ldBinFile}
// Make sure the compiler package info is added to the global set.
c.ensureLclInfoAdded()
linkRequired, err := c.depTracker.LinkRequired(binFile, options,
objFiles, keepSymbols, elfLib)
if err != nil {
return err
}
if linkRequired {
if err := os.MkdirAll(filepath.Dir(binFile), 0755); err != nil {
return util.NewNewtError(err.Error())
}
err := c.CompileBinary(binFile, options, objFiles, keepSymbols, elfLib)
if err != nil {
return err
}
}
err = c.generateExtras(binFile, options)
if err != nil {
return err
}
return nil
}
func (c *Compiler) RenameSymbolsCmd(
sm *symbol.SymbolMap, libraryFile string, ext string) []string {
cmd := []string{c.ocPath}
for s, _ := range *sm {
cmd = append(cmd, "--redefine-sym")
cmd = append(cmd, s+"="+s+ext)
}
cmd = append(cmd, libraryFile)
return cmd
}
func (c *Compiler) ParseLibraryCmd(libraryFile string) []string {
return []string{
c.odPath,
"-t",
libraryFile,
}
}
func (c *Compiler) CopySymbolsCmd(infile string, outfile string, sm *symbol.SymbolMap) []string {
cmd := []string{c.ocPath, "-S"}
for symbol, _ := range *sm {
cmd = append(cmd, "-K")
cmd = append(cmd, symbol)
}
cmd = append(cmd, infile)
cmd = append(cmd, outfile)
return cmd
}
// 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 {
cmd := []string{
c.arPath,
"rcs",
archiveFile,
}
cmd = append(cmd, c.getObjFiles(objFiles)...)
return cmd
}
func linkerScriptFileName(archiveFile string) string {
ar_script_name := strings.TrimSuffix(archiveFile, filepath.Ext(archiveFile)) + "_ar.mri"
return ar_script_name
}
/* this create a new library combining all of the other libraries */
func createSplitArchiveLinkerFile(archiveFile string,
archFiles []string) error {
/* create a name for this script */
ar_script_name := linkerScriptFileName(archiveFile)
// open the file and write out the script
f, err := os.OpenFile(ar_script_name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC,
0666)
if err != nil {
return util.NewNewtError(err.Error())
}
defer f.Close()
if _, err := f.WriteString("CREATE " + archiveFile + "\n"); err != nil {
return util.NewNewtError(err.Error())
}
for _, arch := range archFiles {
if _, err := f.WriteString("ADDLIB " + arch + "\n"); err != nil {
return util.NewNewtError(err.Error())
}
}
if _, err := f.WriteString("SAVE\n"); err != nil {
return util.NewNewtError(err.Error())
}
if _, err := f.WriteString("END\n"); err != nil {
return util.NewNewtError(err.Error())
}
return nil
}
// calculates the command-line invocation necessary to build a split all
// archive from the collection of archive files
func (c *Compiler) BuildSplitArchiveCmd(archiveFile string) string {
str := c.arPath + " -M < " + linkerScriptFileName(archiveFile)
return str
}
// 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) error {
objFiles := []string{}
// Make sure the compiler package info is added to the global set.
c.ensureLclInfoAdded()
arRequired, err := c.depTracker.ArchiveRequired(archiveFile, objFiles)
if err != nil {
return err
}
if !arRequired {
return nil
}
if err := os.MkdirAll(filepath.Dir(archiveFile), 0755); err != nil {
return util.NewNewtError(err.Error())
}
objList := c.getObjFiles([]string{})
if len(objList) == 0 {
return nil
}
if len(objList) == 0 {
util.StatusMessage(util.VERBOSITY_VERBOSE,
"Not archiving %s; no object files\n", archiveFile)
return nil
}
// Delete the old archive, if it exists.
os.Remove(archiveFile)
util.StatusMessage(util.VERBOSITY_DEFAULT, "Archiving %s",
path.Base(archiveFile))
util.StatusMessage(util.VERBOSITY_VERBOSE, " with object files %s",
strings.Join(objList, " "))
util.StatusMessage(util.VERBOSITY_DEFAULT, "\n")
if err != nil && !os.IsNotExist(err) {
return util.NewNewtError(err.Error())
}
cmd := c.CompileArchiveCmd(archiveFile, objFiles)
_, err = util.ShellCommand(cmd, nil)
if err != nil {
return err
}
err = writeCommandFile(archiveFile, cmd)
if err != nil {
return err
}
return nil
}
func getParseRexeg() (error, *regexp.Regexp) {
r, err := regexp.Compile("^([0-9A-Fa-f]+)[\t ]+([lgu! ][w ][C ][W ][Ii ][Dd ][FfO ])[\t ]+([^\t\n\f\r ]+)[\t ]+([0-9a-fA-F]+)[\t ]([^\t\n\f\r ]+)")
if err != nil {
return err, nil
}
return nil, r
}
/* This is a tricky thing to parse. Right now, I keep all the
* flags together and just store the offset, size, name and flags.
* 00012970 l .bss 00000000 _end
* 00011c60 l .init_array 00000000 __init_array_start
* 00011c60 l .init_array 00000000 __preinit_array_start
* 000084b0 g F .text 00000034 os_arch_start
* 00000000 g .debug_aranges 00000000 __HeapBase
* 00011c88 g O .data 00000008 g_os_task_list
* 000082cc g F .text 0000004c os_idle_task
* 000094e0 g F .text 0000002e .hidden __gnu_uldivmod_helper
* 00000000 g .svc_table 00000000 SVC_Count
* 000125e4 g O .bss 00000004 g_console_is_init
* 00009514 g F .text 0000029c .hidden __divdi3
* 000085a8 g F .text 00000054 os_eventq_put
*/
func ParseObjectLine(line string, r *regexp.Regexp) (error, *symbol.SymbolInfo) {
answer := r.FindAllStringSubmatch(line, 11)
if len(answer) == 0 {
return nil, nil
}
data := answer[0]
if len(data) != 6 {
util.StatusMessage(util.VERBOSITY_DEFAULT,
"Not enough content in object file line --- %s", line)
return nil, nil
}
si := symbol.NewSymbolInfo()
si.Name = data[5]
v, err := strconv.ParseUint(data[1], 16, 32)
if err != nil {
util.StatusMessage(util.VERBOSITY_DEFAULT,
"Could not convert location from object file line --- %s", line)
return nil, nil
}
si.Loc = int(v)
v, err = strconv.ParseUint(data[4], 16, 32)
if err != nil {
util.StatusMessage(util.VERBOSITY_DEFAULT,
"Could not convert size form object file line --- %s", line)
return nil, nil
}
si.Size = int(v)
si.Code = data[2]
si.Section = data[3]
return nil, si
}
func (c *Compiler) RenameSymbols(sm *symbol.SymbolMap, libraryFile string, ext string) error {
cmd := c.RenameSymbolsCmd(sm, libraryFile, ext)
_, err := util.ShellCommand(cmd, nil)
return err
}
func (c *Compiler) ParseLibrary(libraryFile string) (error, []byte) {
cmd := c.ParseLibraryCmd(libraryFile)
out, err := util.ShellCommand(cmd, nil)
if err != nil {
return err, nil
}
return err, out
}
func (c *Compiler) CopySymbols(infile string, outfile string, sm *symbol.SymbolMap) error {
cmd := c.CopySymbolsCmd(infile, outfile, sm)
_, err := util.ShellCommand(cmd, nil)
if err != nil {
return err
}
return err
}
func (c *Compiler) ConvertBinToHex(inFile string, outFile string, baseAddr int) error {
cmd := []string{
c.ocPath,
"-I",
"binary",
"-O",
"ihex",
"--adjust-vma",
"0x" + strconv.FormatInt(int64(baseAddr), 16),
inFile,
outFile,
}
_, err := util.ShellCommand(cmd, nil)
if err != nil {
return err
}
return nil
}