
Add a 'result' member to the compileJob struct which is used by the link job to get all the paths that should be linked together. This is not yet necessary (the paths are fixed), but soon the paths are only known after a linker dependency has run.
149 строки
4,8 КиБ
Go
149 строки
4,8 КиБ
Go
package builder
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/tinygo-org/tinygo/goenv"
|
|
)
|
|
|
|
// Library is a container for information about a single C library, such as a
|
|
// compiler runtime or libc.
|
|
type Library struct {
|
|
// The library name, such as compiler-rt or picolibc.
|
|
name string
|
|
|
|
cflags func() []string
|
|
|
|
// The source directory, relative to TINYGOROOT.
|
|
sourceDir string
|
|
|
|
// The source files, relative to sourceDir.
|
|
sources func(target string) []string
|
|
}
|
|
|
|
// fullPath returns the full path to the source directory.
|
|
func (l *Library) fullPath() string {
|
|
return filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir)
|
|
}
|
|
|
|
// sourcePaths returns a slice with the full paths to the source files.
|
|
func (l *Library) sourcePaths(target string) []string {
|
|
sources := l.sources(target)
|
|
paths := make([]string, len(sources))
|
|
for i, name := range sources {
|
|
paths[i] = filepath.Join(l.fullPath(), name)
|
|
}
|
|
return paths
|
|
}
|
|
|
|
// Load the library archive, possibly generating and caching it if needed.
|
|
// The resulting file is stored in the provided tmpdir, which is expected to be
|
|
// removed after the Load call.
|
|
func (l *Library) Load(target, tmpdir string) (path string, err error) {
|
|
job, err := l.load(target, "", tmpdir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
jobs := append([]*compileJob{job}, job.dependencies...)
|
|
err = runJobs(jobs)
|
|
return job.result, err
|
|
}
|
|
|
|
// load returns a compile job to build this library file for the given target
|
|
// and CPU. It may return a dummy compileJob if the library build is already
|
|
// cached. The path is stored as job.result but is only valid if the job and
|
|
// job.dependencies have been run.
|
|
// The provided tmpdir will be used to store intermediary files and possibly the
|
|
// output archive file, it is expected to be removed after use.
|
|
func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error) {
|
|
// Try to load a precompiled library.
|
|
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
|
|
if _, err := os.Stat(precompiledPath); err == nil {
|
|
// Found a precompiled library for this OS/architecture. Return the path
|
|
// directly.
|
|
return dummyCompileJob(precompiledPath), nil
|
|
}
|
|
|
|
var outfile string
|
|
if cpu != "" {
|
|
outfile = l.name + "-" + target + "-" + cpu + ".a"
|
|
} else {
|
|
outfile = l.name + "-" + target + ".a"
|
|
}
|
|
|
|
// Try to fetch this library from the cache.
|
|
if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil {
|
|
// Cache hit.
|
|
return dummyCompileJob(path), nil
|
|
}
|
|
// Cache miss, build it now.
|
|
|
|
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name)
|
|
dir := filepath.Join(tmpdir, "build-lib-"+l.name)
|
|
err = os.Mkdir(dir, 0777)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Precalculate the flags to the compiler invocation.
|
|
// Note: -fdebug-prefix-map is necessary to make the output archive
|
|
// reproducible. Otherwise the temporary directory is stored in the archive
|
|
// itself, which varies each run.
|
|
args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
|
|
if cpu != "" {
|
|
args = append(args, "-mcpu="+cpu)
|
|
}
|
|
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
|
|
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft")
|
|
}
|
|
if strings.HasPrefix(target, "riscv32-") {
|
|
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
|
|
}
|
|
if strings.HasPrefix(target, "riscv64-") {
|
|
args = append(args, "-march=rv64gc", "-mabi=lp64")
|
|
}
|
|
|
|
// Create job to put all the object files in a single archive. This archive
|
|
// file is the (static) library file.
|
|
var objs []string
|
|
arpath := filepath.Join(dir, l.name+".a")
|
|
job = &compileJob{
|
|
description: "ar " + l.name + ".a",
|
|
result: arpath,
|
|
run: func(*compileJob) error {
|
|
// Create an archive of all object files.
|
|
err := makeArchive(arpath, objs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Store this archive in the cache.
|
|
_, err = cacheStore(arpath, outfile, l.sourcePaths(target))
|
|
return err
|
|
},
|
|
}
|
|
|
|
// Create jobs to compile all sources. These jobs are depended upon by the
|
|
// archive job above, so must be run first.
|
|
for _, srcpath := range l.sourcePaths(target) {
|
|
srcpath := srcpath // avoid concurrency issues by redefining inside the loop
|
|
objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
|
|
objs = append(objs, objpath)
|
|
job.dependencies = append(job.dependencies, &compileJob{
|
|
description: "compile " + srcpath,
|
|
run: func(*compileJob) error {
|
|
var compileArgs []string
|
|
compileArgs = append(compileArgs, args...)
|
|
compileArgs = append(compileArgs, "-o", objpath, srcpath)
|
|
err := runCCompiler("clang", compileArgs...)
|
|
if err != nil {
|
|
return &commandError{"failed to build", srcpath, err}
|
|
}
|
|
return nil
|
|
},
|
|
})
|
|
}
|
|
|
|
return job, nil
|
|
}
|