From 9bbb233cf02be8f0521bfbc6f96f70c8a0c5c6b8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 25 Jan 2019 21:36:34 +0100 Subject: [PATCH] main: include prebuilt compiler-rt libraries in release tarball This avoids depending on clang-7 to build compiler-rt for the most common ARM microcontrollers. --- Makefile | 6 ++++++ buildcache.go | 60 ++++++++++++++++++++++++++++++++------------------- builtins.go | 58 ++++++++++++++++++++++++++++++++++++++++++------- main.go | 21 ++++++++++++++---- 4 files changed, 111 insertions(+), 34 deletions(-) diff --git a/Makefile b/Makefile index cc78a296..c913c92d 100644 --- a/Makefile +++ b/Makefile @@ -117,6 +117,9 @@ release: static gen-device @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/compiler-rt/lib @mkdir -p build/release/tinygo/lib/nrfx + @mkdir -p build/release/tinygo/pkg/armv6m-none-eabi + @mkdir -p build/release/tinygo/pkg/armv7m-none-eabi + @mkdir -p build/release/tinygo/pkg/armv7em-none-eabi @cp -p build/tinygo build/release/tinygo/bin @cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS @cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS @@ -126,6 +129,9 @@ release: static gen-device @cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx @cp -rp src build/release/tinygo/src @cp -rp targets build/release/tinygo/targets + ./build/tinygo build-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a + ./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a + ./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a tar -czf build/release.tar.gz -C build/release tinygo # Binary that can run on the host. diff --git a/buildcache.go b/buildcache.go index 3c2ea4b3..eb8fd2ea 100644 --- a/buildcache.go +++ b/buildcache.go @@ -80,29 +80,45 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string, return "", err } cachepath := filepath.Join(dir, name) - err = os.Rename(tmppath, cachepath) + err = moveFile(tmppath, cachepath) if err != nil { - inf, err := os.Open(tmppath) - if err != nil { - return "", err - } - defer inf.Close() - outf, err := os.Create(cachepath + ".tmp") - if err != nil { - return "", err - } - - _, err = io.Copy(outf, inf) - if err != nil { - return "", err - } - - err = os.Rename(cachepath+".tmp", cachepath) - if err != nil { - return "", err - } - - return cachepath, outf.Close() + return "", err } return cachepath, nil } + +// moveFile renames the file from src to dst. If renaming doesn't work (for +// example, the rename crosses a filesystem boundary), the file is copied and +// the old file is removed. +func moveFile(src, dst string) error { + err := os.Rename(src, dst) + if err == nil { + // Success! + return nil + } + // Failed to move, probably a different filesystem. + // Do a copy + remove. + 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 = os.Rename(dst+".tmp", dst) + if err != nil { + return err + } + + return outf.Close() +} diff --git a/builtins.go b/builtins.go index 1be0c6e7..2ec15588 100644 --- a/builtins.go +++ b/builtins.go @@ -157,16 +157,29 @@ var aeabiBuiltins = []string{ func builtinFiles(target string) []string { builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins - if target[:3] == "arm" { + if strings.HasPrefix(target, "arm") { builtins = append(builtins, aeabiBuiltins...) } return builtins } +// builtinsDir returns the directory where the sources for compiler-rt are kept. +func builtinsDir() string { + return filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins") +} + // Get the builtins archive, possibly generating it as needed. func loadBuiltins(target string) (path string, err error) { + // Try to load a precompiled compiler-rt library. + precompiledPath := filepath.Join(sourceDir(), "pkg", target, "compiler-rt.a") + if _, err := os.Stat(precompiledPath); err == nil { + // Found a precompiled compiler-rt for this OS/architecture. Return the + // path directly. + return precompiledPath, nil + } + outfile := "librt-" + target + ".a" - builtinsDir := filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins") + builtinsDir := builtinsDir() builtins := builtinFiles(target) srcs := make([]string, len(builtins)) @@ -178,9 +191,33 @@ func loadBuiltins(target string) (path string, err error) { return path, err } - dir, err := ioutil.TempDir("", "tinygo-builtins") + var cachepath string + err = compileBuiltins(target, func(path string) error { + path, err := cacheStore(path, outfile, commands["clang"], srcs) + cachepath = path + return err + }) + return cachepath, err +} + +// compileBuiltins compiles builtins from compiler-rt into a static library. +// When it succeeds, it will call the callback with the resulting path. The path +// will be removed after callback returns. If callback returns an error, this is +// passed through to the return value of this function. +func compileBuiltins(target string, callback func(path string) error) error { + builtinsDir := builtinsDir() + + builtins := builtinFiles(target) + srcs := make([]string, len(builtins)) + for i, name := range builtins { + srcs[i] = filepath.Join(builtinsDir, name) + } + + dirPrefix := "tinygo-builtins" + remapDir := filepath.Join(os.TempDir(), dirPrefix) + dir, err := ioutil.TempDir(os.TempDir(), dirPrefix) if err != nil { - return "", err + return err } defer os.RemoveAll(dir) @@ -195,13 +232,16 @@ func loadBuiltins(target string) (path string, err error) { objpath := filepath.Join(dir, objname+".o") objs = append(objs, objpath) srcpath := filepath.Join(builtinsDir, name) - cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-o", objpath, srcpath) + // 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. + cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Dir = dir err = cmd.Run() if err != nil { - return "", &commandError{"failed to build", srcpath, err} + return &commandError{"failed to build", srcpath, err} } } @@ -213,8 +253,10 @@ func loadBuiltins(target string) (path string, err error) { cmd.Dir = dir err = cmd.Run() if err != nil { - return "", &commandError{"failed to make static library", arpath, err} + return &commandError{"failed to make static library", arpath, err} } - return cacheStore(arpath, outfile, commands["clang"], srcs) + // Give the caller the resulting file. The callback must copy the file, + // because after it returns the temporary directory will be removed. + return callback(arpath) } diff --git a/main.go b/main.go index 0454c699..3f61b83c 100644 --- a/main.go +++ b/main.go @@ -186,13 +186,12 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act // Load builtins library from the cache, possibly compiling it on the // fly. - var cachePath string + var librt string if spec.RTLib == "compiler-rt" { - librt, err := loadBuiltins(spec.Triple) + librt, err = loadBuiltins(spec.Triple) if err != nil { return err } - cachePath, _ = filepath.Split(librt) } // Prepare link command. @@ -200,7 +199,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act tmppath := executable // final file ldflags := append(spec.LDFlags, "-o", executable, objfile) if spec.RTLib == "compiler-rt" { - ldflags = append(ldflags, "-L", cachePath, "-lrt-"+spec.Triple) + ldflags = append(ldflags, librt) } // Compile extra files. @@ -554,6 +553,20 @@ func main() { } err := Build(flag.Arg(0), *outpath, target, config) handleCompilerError(err) + case "build-builtins": + // Note: this command is only meant to be used while making a release! + if *outpath == "" { + fmt.Fprintln(os.Stderr, "No output filename supplied (-o).") + usage() + os.Exit(1) + } + if *target == "" { + fmt.Fprintln(os.Stderr, "No target (-target).") + } + err := compileBuiltins(*target, func(path string) error { + return moveFile(path, *outpath) + }) + handleCompilerError(err) case "flash", "gdb": if *outpath != "" { fmt.Fprintln(os.Stderr, "Output cannot be specified with the flash command.")