picolibc: add include directory to build artefact

This is really just a preparatory commit for musl support. The idea is
to store not just the archive file (.a) but also an include directory.
This is optional for picolibc but required for musl, so the main purpose
of this commit is the refactor needed for this change.
Этот коммит содержится в:
Ayke van Laethem 2021-09-13 01:16:02 +02:00 коммит произвёл Ron Evans
родитель 39ff13fd1a
коммит 79bdd3f79a
11 изменённых файлов: 176 добавлений и 189 удалений

Просмотреть файл

@ -15,8 +15,7 @@ RUN cd /tinygo/ && \
git submodule sync && \
git submodule update --init --recursive --force
COPY ./lib/picolibc-* /tinygo/lib/
COPY ./lib/picolibc-include/* /tinygo/lib/picolibc-include/
COPY ./lib/picolibc-stdio.c /tinygo/lib/picolibc-stdio.c
RUN cd /tinygo/ && \
go install /tinygo/

Просмотреть файл

@ -500,17 +500,16 @@ build/release: tinygo gen-device wasi-libc
@cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm
@cp -rp lib/picolibc-include build/release/tinygo/lib
@cp -rp lib/picolibc-stdio.c build/release/tinygo/lib
@cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
@cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets
./build/tinygo build-library -target=armv6m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv6m-unknown-unknown-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv7m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7m-unknown-unknown-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv7em-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7em-unknown-unknown-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv6m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv6m-unknown-unknown-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv7m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7m-unknown-unknown-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv7em-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7em-unknown-unknown-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv6m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv6m-unknown-unknown-eabi/compiler-rt compiler-rt
./build/tinygo build-library -target=armv7m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7m-unknown-unknown-eabi/compiler-rt compiler-rt
./build/tinygo build-library -target=armv7em-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7em-unknown-unknown-eabi/compiler-rt compiler-rt
./build/tinygo build-library -target=armv6m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv6m-unknown-unknown-eabi/picolibc picolibc
./build/tinygo build-library -target=armv7m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7m-unknown-unknown-eabi/picolibc picolibc
./build/tinygo build-library -target=armv7em-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7em-unknown-unknown-eabi/picolibc picolibc
release: build/release
tar -czf build/release.tar.gz -C build/release tinygo

Просмотреть файл

@ -18,17 +18,12 @@ import (
// given as a parameter. It is equivalent to the following command:
//
// ar -rcs <archivePath> <objs...>
func makeArchive(archivePath string, objs []string) error {
func makeArchive(arfile *os.File, objs []string) error {
// Open the archive file.
arfile, err := os.Create(archivePath)
if err != nil {
return err
}
defer arfile.Close()
arwriter := ar.NewWriter(arfile)
err = arwriter.WriteGlobalHeader()
err := arwriter.WriteGlobalHeader()
if err != nil {
return &os.PathError{Op: "write ar header", Path: archivePath, Err: err}
return &os.PathError{Op: "write ar header", Path: arfile.Name(), Err: err}
}
// Open all object files and read the symbols for the symbol table.
@ -133,7 +128,7 @@ func makeArchive(archivePath string, objs []string) error {
return err
}
if int64(int32(offset)) != offset {
return errors.New("large archives (4GB+) not supported: " + archivePath)
return errors.New("large archives (4GB+) not supported: " + arfile.Name())
}
objfiles[i].archiveOffset = int32(offset)
@ -160,7 +155,7 @@ func makeArchive(archivePath string, objs []string) error {
return err
}
if n != st.Size() {
return errors.New("file modified during ar creation: " + archivePath)
return errors.New("file modified during ar creation: " + arfile.Name())
}
// File is not needed anymore.

Просмотреть файл

@ -86,6 +86,30 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
}
defer os.RemoveAll(dir)
// Check for a libc dependency.
// As a side effect, this also creates the headers for the given libc, if
// the libc needs them.
root := goenv.Get("TINYGOROOT")
var libcDependencies []*compileJob
switch config.Target.Libc {
case "picolibc":
libcJob, err := Picolibc.load(config, dir)
if err != nil {
return err
}
libcDependencies = append(libcDependencies, libcJob)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) {
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
libcDependencies = append(libcDependencies, dummyCompileJob(path))
case "":
// no library specified, so nothing to do
default:
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
optLevel, sizeLevel, _ := config.OptLevels()
compilerConfig := &compiler.Config{
Triple: config.Triple(),
@ -489,7 +513,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
// Add compiler-rt dependency if needed. Usually this is a simple load from
// a cache.
if config.Target.RTLib == "compiler-rt" {
job, err := CompilerRT.load(config.Triple(), config.CPU(), dir)
job, err := CompilerRT.load(config, dir)
if err != nil {
return err
}
@ -499,7 +523,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
// Add jobs to compile extra files. These files are in C or assembly and
// contain things like the interrupt vector table and low level operations
// such as stack switching.
root := goenv.Get("TINYGOROOT")
for _, path := range config.ExtraFiles() {
abspath := filepath.Join(root, path)
job := &compileJob{
@ -538,26 +561,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
ldflags = append(ldflags, lprogram.LDFlags...)
}
// Add libc dependency if needed.
switch config.Target.Libc {
case "picolibc":
job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
linkerDependencies = append(linkerDependencies, job)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) {
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
job := dummyCompileJob(path)
linkerDependencies = append(linkerDependencies, job)
case "":
// no library specified, so nothing to do
default:
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
// Add libc dependencies, if they exist.
linkerDependencies = append(linkerDependencies, libcDependencies...)
// Strip debug information with -no-debug.
if !config.Debug() {

Просмотреть файл

@ -1,105 +0,0 @@
package builder
import (
"io"
"os"
"path/filepath"
"time"
"github.com/tinygo-org/tinygo/goenv"
)
// Return the newest timestamp of all the file paths passed in. Used to check
// for stale caches.
func cacheTimestamp(paths []string) (time.Time, error) {
var timestamp time.Time
for _, path := range paths {
st, err := os.Stat(path)
if err != nil {
return time.Time{}, err
}
if timestamp.IsZero() {
timestamp = st.ModTime()
} else if timestamp.Before(st.ModTime()) {
timestamp = st.ModTime()
}
}
return timestamp, nil
}
// Try to load a given file from the cache. Return "", nil if no cached file can
// be found (or the file is stale), return the absolute path if there is a cache
// and return an error on I/O errors.
func cacheLoad(name string, sourceFiles []string) (string, error) {
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
cacheStat, err := os.Stat(cachepath)
if os.IsNotExist(err) {
return "", nil // does not exist
} else if err != nil {
return "", err // cannot stat cache file
}
sourceTimestamp, err := cacheTimestamp(sourceFiles)
if err != nil {
return "", err // cannot stat source files
}
if cacheStat.ModTime().After(sourceTimestamp) {
return cachepath, nil
} else {
os.Remove(cachepath)
// stale cache
return "", nil
}
}
// Store the file located at tmppath in the cache with the given name. The
// tmppath may or may not be gone afterwards.
func cacheStore(tmppath, name string, sourceFiles []string) (string, error) {
// get the last modified time
if len(sourceFiles) == 0 {
panic("cache: no source files")
}
// TODO: check the config key
dir := goenv.Get("GOCACHE")
err := os.MkdirAll(dir, 0777)
if err != nil {
return "", err
}
cachepath := filepath.Join(dir, name)
err = copyFile(tmppath, cachepath)
if err != nil {
return "", err
}
return cachepath, nil
}
// copyFile copies the given file from src to dst. It can copy over
// a possibly already existing file at the destination.
func copyFile(src, dst string) error {
inf, err := os.Open(src)
if err != nil {
return err
}
defer inf.Close()
outpath := dst + ".tmp"
outf, err := os.Create(outpath)
if err != nil {
return err
}
_, err = io.Copy(outf, inf)
if err != nil {
os.Remove(outpath)
return err
}
err = outf.Close()
if err != nil {
return err
}
return os.Rename(dst+".tmp", dst)
}

Просмотреть файл

@ -157,8 +157,10 @@ var aeabiBuiltins = []string{
//
// For more information, see: https://compiler-rt.llvm.org/
var CompilerRT = Library{
name: "compiler-rt",
cflags: func() []string { return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} },
name: "compiler-rt",
cflags: func(headerPath string) []string {
return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"}
},
sourceDir: "lib/compiler-rt/lib/builtins",
sources: func(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins

Просмотреть файл

@ -1,10 +1,12 @@
package builder
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
@ -14,7 +16,11 @@ type Library struct {
// The library name, such as compiler-rt or picolibc.
name string
cflags func() []string
// makeHeaders creates a header include dir for the library
makeHeaders func(includeDir string) error
// cflags returns the C flags specific to this library
cflags func(headerPath string) []string
// The source directory, relative to TINYGOROOT.
sourceDir string
@ -39,15 +45,15 @@ func (l *Library) sourcePaths(target string) []string {
}
// 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)
// The resulting directory may be stored in the provided tmpdir, which is
// expected to be removed after the Load call.
func (l *Library) Load(config *compileopts.Config, tmpdir string) (dir string, err error) {
job, err := l.load(config, tmpdir)
if err != nil {
return "", err
}
err = runJobs(job)
return job.result, err
return filepath.Dir(job.result), err
}
// load returns a compile job to build this library file for the given target
@ -56,29 +62,52 @@ func (l *Library) Load(target, tmpdir string) (path string, err error) {
// 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 {
// As a side effect, this call creates the library header files if they didn't
// exist yet.
func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, err error) {
outdir, precompiled := config.LibcPath(l.name)
archiveFilePath := filepath.Join(outdir, "lib.a")
if precompiled {
// 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"
return dummyCompileJob(archiveFilePath), nil
}
// 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
if _, err := os.Stat(archiveFilePath); err == nil {
return dummyCompileJob(archiveFilePath), nil
}
// Cache miss, build it now.
// Create the destination directory where the components of this library
// (lib.a file, include directory) are placed.
outname := filepath.Base(outdir)
err = os.MkdirAll(filepath.Join(goenv.Get("GOCACHE"), outname), 0o777)
if err != nil {
// Could not create directory (and not because it already exists).
return nil, err
}
// Make headers if needed.
headerPath := filepath.Join(outdir, "include")
if l.makeHeaders != nil {
if _, err = os.Stat(headerPath); err != nil {
temporaryHeaderPath, err := ioutil.TempDir(outdir, "include.tmp*")
if err != nil {
return nil, err
}
defer os.RemoveAll(temporaryHeaderPath)
err = l.makeHeaders(temporaryHeaderPath)
if err != nil {
return nil, err
}
err = os.Rename(temporaryHeaderPath, headerPath)
if err != nil {
return nil, err
}
}
}
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name)
dir := filepath.Join(tmpdir, "build-lib-"+l.name)
err = os.Mkdir(dir, 0777)
@ -90,7 +119,9 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error)
// 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)
target := config.Triple()
args := append(l.cflags(headerPath), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
cpu := config.CPU()
if cpu != "" {
args = append(args, "-mcpu="+cpu)
}
@ -107,19 +138,25 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error)
// 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,
description: "ar " + l.name + "/lib.a",
result: filepath.Join(goenv.Get("GOCACHE"), outname, "lib.a"),
run: func(*compileJob) error {
// Create an archive of all object files.
err := makeArchive(arpath, objs)
f, err := ioutil.TempFile(outdir, "libc.a.tmp*")
if err != nil {
return err
}
err = makeArchive(f, objs)
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
// Store this archive in the cache.
_, err = cacheStore(arpath, outfile, l.sourcePaths(target))
return err
return os.Rename(f.Name(), archiveFilePath)
},
}

Просмотреть файл

@ -1,6 +1,7 @@
package builder
import (
"os"
"path/filepath"
"github.com/tinygo-org/tinygo/goenv"
@ -10,7 +11,14 @@ import (
// based on newlib.
var Picolibc = Library{
name: "picolibc",
cflags: func() []string {
makeHeaders: func(includeDir string) error {
f, err := os.Create(filepath.Join(includeDir, "picolibc.h"))
if err != nil {
return err
}
return f.Close()
},
cflags: func(headerPath string) []string {
picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc")
return []string{
"-Werror",
@ -22,7 +30,7 @@ var Picolibc = Library{
"-nostdlibinc",
"-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include",
"-I" + picolibcDir + "/tinystdio",
"-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include",
"-I" + headerPath,
}
},
sourceDir: "lib/picolibc/newlib/libc",

Просмотреть файл

@ -5,6 +5,7 @@ package compileopts
import (
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
@ -197,6 +198,29 @@ func (c *Config) RP2040BootPatch() bool {
return false
}
// LibcPath returns the path to the libc directory. The libc path will be either
// a precompiled libc shipped with a TinyGo build, or a libc path in the cache
// directory (which might not yet be built).
func (c *Config) LibcPath(name string) (path string, precompiled bool) {
// Try to load a precompiled library.
precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", c.Triple(), name)
if _, err := os.Stat(precompiledDir); err == nil {
// Found a precompiled library for this OS/architecture. Return the path
// directly.
return precompiledDir, true
}
// No precompiled library found. Determine the path name that will be used
// in the build cache.
var outname string
if c.CPU() != "" {
outname = name + "-" + c.Triple() + "-" + c.CPU()
} else {
outname = name + "-" + c.Triple()
}
return filepath.Join(goenv.Get("GOCACHE"), outname), false
}
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing.
func (c *Config) CFlags() []string {
@ -208,12 +232,12 @@ func (c *Config) CFlags() []string {
case "picolibc":
root := goenv.Get("TINYGOROOT")
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
path, _ := c.LibcPath("picolibc")
cflags = append(cflags,
"-nostdlibinc",
"--sysroot="+path,
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "include"),
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "tinystdio"),
)
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
case "wasi-libc":
root := goenv.Get("TINYGOROOT")
cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot")

Просмотреть файл

41
main.go
Просмотреть файл

@ -71,8 +71,8 @@ func moveFile(src, dst string) error {
return os.Remove(src)
}
// copyFile copies the given file from src to dst. It can copy over
// a possibly already existing file at the destination.
// copyFile copies the given file or directory from src to dst. It can copy over
// a possibly already existing file (but not directory) at the destination.
func copyFile(src, dst string) error {
source, err := os.Open(src)
if err != nil {
@ -85,14 +85,32 @@ func copyFile(src, dst string) error {
return err
}
destination, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, st.Mode())
if err != nil {
if st.IsDir() {
err := os.Mkdir(dst, st.Mode().Perm())
if err != nil {
return err
}
names, err := source.Readdirnames(0)
if err != nil {
return err
}
for _, name := range names {
err := copyFile(filepath.Join(src, name), filepath.Join(dst, name))
if err != nil {
return err
}
}
return nil
} else {
destination, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, st.Mode())
if err != nil {
return err
}
defer destination.Close()
_, err = io.Copy(destination, source)
return err
}
defer destination.Close()
_, err = io.Copy(destination, source)
return err
}
// executeCommand is a simple wrapper to exec.Cmd
@ -1259,7 +1277,12 @@ func main() {
handleCompilerError(err)
}
defer os.RemoveAll(tmpdir)
path, err := lib.Load(*target, tmpdir)
config := &compileopts.Config{
Target: &compileopts.TargetSpec{
Triple: *target,
},
}
path, err := lib.Load(config, tmpdir)
handleCompilerError(err)
err = copyFile(path, outpath)
if err != nil {