diff options
author | Ayke van Laethem <[email protected]> | 2021-09-13 01:16:02 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2021-11-04 17:15:38 +0100 |
commit | 79bdd3f79a59a0edc724039f624e875ae718caf0 (patch) | |
tree | 73dd5510cd7eaa23e64b8f91c52e15e20266c29d /builder | |
parent | 39ff13fd1a31610f5fe733cb38908c8563643981 (diff) | |
download | tinygo-79bdd3f79a59a0edc724039f624e875ae718caf0.tar.gz tinygo-79bdd3f79a59a0edc724039f624e875ae718caf0.zip |
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.
Diffstat (limited to 'builder')
-rw-r--r-- | builder/ar.go | 15 | ||||
-rw-r--r-- | builder/build.go | 49 | ||||
-rw-r--r-- | builder/buildcache.go | 105 | ||||
-rw-r--r-- | builder/builtins.go | 6 | ||||
-rw-r--r-- | builder/library.go | 93 | ||||
-rw-r--r-- | builder/picolibc.go | 12 |
6 files changed, 111 insertions, 169 deletions
diff --git a/builder/ar.go b/builder/ar.go index ee8ba0706..aee6f00ce 100644 --- a/builder/ar.go +++ b/builder/ar.go @@ -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. diff --git a/builder/build.go b/builder/build.go index 436d0a107..18c9d8a33 100644 --- a/builder/build.go +++ b/builder/build.go @@ -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() { diff --git a/builder/buildcache.go b/builder/buildcache.go deleted file mode 100644 index b673de06e..000000000 --- a/builder/buildcache.go +++ /dev/null @@ -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) -} diff --git a/builder/builtins.go b/builder/builtins.go index ed159e7ed..33bcc99a3 100644 --- a/builder/builtins.go +++ b/builder/builtins.go @@ -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 diff --git a/builder/library.go b/builder/library.go index 7f7b77a53..0e09fa44c 100644 --- a/builder/library.go +++ b/builder/library.go @@ -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) }, } diff --git a/builder/picolibc.go b/builder/picolibc.go index 93a2639de..273c13609 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -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", |