From cdd267fa10370e294480fa203af1fc1cd91b47fa Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 10 Nov 2021 17:58:38 +0100 Subject: [PATCH] builder: add support for cross compiling to Darwin This means that it will be possible to generate a Darwin binary on any platform (Windows, Linux, and MacOS of course), including CGo. Of course, the resulting binaries can only run on MacOS itself. The binary links against libSystem.dylib, which is a shared library. The macos-minimal-sdk repository contains open source header files and generated symbol stubs so we can generate a stub libSystem.dylib without copying any closed source code. --- .github/workflows/build-macos.yml | 2 +- .github/workflows/linux.yml | 4 +-- .github/workflows/windows.yml | 2 +- .gitmodules | 3 ++ Makefile | 7 +++- builder/build.go | 3 ++ builder/darwin-libsystem.go | 58 +++++++++++++++++++++++++++++++ builder/lld.cpp | 5 +++ builder/tools-builtin.go | 12 +++++-- compileopts/config.go | 5 +++ compileopts/target.go | 12 +++++-- lib/macos-minimal-sdk | 1 + 12 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 builder/darwin-libsystem.go create mode 160000 lib/macos-minimal-sdk diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 9ca002a78..8f01f7d4a 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -48,7 +48,7 @@ jobs: uses: actions/cache@v2 id: cache-llvm-build with: - key: llvm-build-13-macos-v2 + key: llvm-build-13-macos-v3 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f9af94ce7..661bdc56d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -51,7 +51,7 @@ jobs: uses: actions/cache@v2 id: cache-llvm-build with: - key: llvm-build-13-linux-v1 + key: llvm-build-13-linux-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -192,7 +192,7 @@ jobs: uses: actions/cache@v2 id: cache-llvm-build with: - key: llvm-build-13-linux-asserts-v1 + key: llvm-build-13-linux-asserts-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index b2aaf1315..b5194408d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -48,7 +48,7 @@ jobs: uses: actions/cache@v2 id: cache-llvm-build with: - key: llvm-build-13-windows-v1 + key: llvm-build-13-windows-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/.gitmodules b/.gitmodules index 8143ce846..e82a938b7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,3 +32,6 @@ [submodule "lib/mingw-w64"] path = lib/mingw-w64 url = https://github.com/mingw-w64/mingw-w64.git +[submodule "lib/macos-minimal-sdk"] + path = lib/macos-minimal-sdk + url = https://github.com/aykevl/macos-minimal-sdk.git diff --git a/Makefile b/Makefile index bbce72fdd..86ce7f9d3 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,8 @@ ifeq ($(OS),Windows_NT) else ifeq ($(shell uname -s),Darwin) MD5SUM = md5 + CGO_LDFLAGS += -lxar + USE_SYSTEM_BINARYEN ?= 1 else ifeq ($(shell uname -s),FreeBSD) @@ -89,7 +91,7 @@ CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGe CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++ # Libraries that should be linked in for the statically linked LLD. -LLD_LIB_NAMES = lldCOFF lldCommon lldCore lldDriver lldELF lldMachO lldMinGW lldReaderWriter lldWasm lldYAML +LLD_LIB_NAMES = lldCOFF lldCommon lldCore lldDriver lldELF lldMachO2 lldMinGW lldReaderWriter lldWasm lldYAML LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP) # Other libraries that are needed to link TinyGo. @@ -566,6 +568,7 @@ endif @$(MD5SUM) test.hex GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo GOOS=windows GOARCH=amd64 $(TINYGO) build -size short -o test.exe ./testdata/cgo + GOOS=darwin GOARCH=amd64 $(TINYGO) build -o test ./testdata/cgo ifneq ($(OS),Windows_NT) # TODO: this does not yet work on Windows. Somehow, unused functions are # not garbage collected. @@ -581,6 +584,7 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN @mkdir -p build/release/tinygo/lib/clang/include @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/compiler-rt/lib + @mkdir -p build/release/tinygo/lib/macos-minimal-sdk @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @mkdir -p build/release/tinygo/lib/musl/arch @@ -604,6 +608,7 @@ endif @cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib @cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt @cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt + @cp -rp lib/macos-minimal-sdk/* build/release/tinygo/lib/macos-minimal-sdk @cp -rp lib/musl/arch/aarch64 build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch diff --git a/builder/build.go b/builder/build.go index 42a3dd333..c4831de02 100644 --- a/builder/build.go +++ b/builder/build.go @@ -107,6 +107,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil root := goenv.Get("TINYGOROOT") var libcDependencies []*compileJob switch config.Target.Libc { + case "darwin-libSystem": + job := makeDarwinLibSystemJob(config, dir) + libcDependencies = append(libcDependencies, job) case "musl": job, unlock, err := Musl.load(config, dir) if err != nil { diff --git a/builder/darwin-libsystem.go b/builder/darwin-libsystem.go new file mode 100644 index 000000000..328825810 --- /dev/null +++ b/builder/darwin-libsystem.go @@ -0,0 +1,58 @@ +package builder + +import ( + "path/filepath" + "strings" + + "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/goenv" +) + +// Create a job that builds a Darwin libSystem.dylib stub library. This library +// contains all the symbols needed so that we can link against it, but it +// doesn't contain any real symbol implementations. +func makeDarwinLibSystemJob(config *compileopts.Config, tmpdir string) *compileJob { + return &compileJob{ + description: "compile Darwin libSystem.dylib", + run: func(job *compileJob) (err error) { + arch := strings.Split(config.Triple(), "-")[0] + job.result = filepath.Join(tmpdir, "libSystem.dylib") + objpath := filepath.Join(tmpdir, "libSystem.o") + inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s") + + // Compile assembly file to object file. + flags := []string{ + "-nostdlib", + "--target=" + config.Triple(), + "-c", + "-o", objpath, + inpath, + } + if config.Options.PrintCommands != nil { + config.Options.PrintCommands("clang", flags...) + } + err = runCCompiler(flags...) + if err != nil { + return err + } + + // Link object file to dynamic library. + platformVersion := strings.TrimPrefix(strings.Split(config.Triple(), "-")[2], "macosx") + flags = []string{ + "-flavor", "darwinnew", + "-demangle", + "-dynamic", + "-dylib", + "-arch", arch, + "-platform_version", "macos", platformVersion, platformVersion, + "-install_name", "/usr/lib/libSystem.B.dylib", + "-o", job.result, + objpath, + } + if config.Options.PrintCommands != nil { + config.Options.PrintCommands("ld.lld", flags...) + } + return link("ld.lld", flags...) + }, + } +} diff --git a/builder/lld.cpp b/builder/lld.cpp index 5a9ad8a20..0543a3ea8 100644 --- a/builder/lld.cpp +++ b/builder/lld.cpp @@ -11,6 +11,11 @@ bool tinygo_link_elf(int argc, char **argv) { return lld::elf::link(args, false, llvm::outs(), llvm::errs()); } +bool tinygo_link_macho(int argc, char **argv) { + std::vector args(argv, argv + argc); + return lld::macho::link(args, false, llvm::outs(), llvm::errs()); +} + bool tinygo_link_mingw(int argc, char **argv) { std::vector args(argv, argv + argc); return lld::mingw::link(args, false, llvm::outs(), llvm::errs()); diff --git a/builder/tools-builtin.go b/builder/tools-builtin.go index e5ecd424c..5474e3e5e 100644 --- a/builder/tools-builtin.go +++ b/builder/tools-builtin.go @@ -14,6 +14,7 @@ import ( #include bool tinygo_clang_driver(int argc, char **argv); bool tinygo_link_elf(int argc, char **argv); +bool tinygo_link_macho(int argc, char **argv); bool tinygo_link_mingw(int argc, char **argv); bool tinygo_link_wasm(int argc, char **argv); */ @@ -27,8 +28,13 @@ const hasBuiltinTools = true // linking statically with LLVM (with the byollvm build tag). func RunTool(tool string, args ...string) error { linker := "elf" - if tool == "ld.lld" && len(args) >= 2 && args[0] == "-m" && args[1] == "i386pep" { - linker = "mingw" + if tool == "ld.lld" && len(args) >= 2 { + if args[0] == "-m" && args[1] == "i386pep" { + linker = "mingw" + } else if args[0] == "-flavor" { + linker = args[1] + args = args[2:] + } } args = append([]string{"tinygo:" + tool}, args...) @@ -48,6 +54,8 @@ func RunTool(tool string, args ...string) error { ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf)) case "ld.lld": switch linker { + case "darwinnew": + ok = C.tinygo_link_macho(C.int(len(args)), (**C.char)(buf)) case "elf": ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf)) case "mingw": diff --git a/compileopts/config.go b/compileopts/config.go index f33a4b1ac..392ef2233 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -225,6 +225,11 @@ func (c *Config) CFlags() []string { cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT"))) } switch c.Target.Libc { + case "darwin-libSystem": + root := goenv.Get("TINYGOROOT") + cflags = append(cflags, + "--sysroot="+filepath.Join(root, "lib/macos-minimal-sdk/src"), + ) case "picolibc": root := goenv.Get("TINYGOROOT") picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc") diff --git a/compileopts/target.go b/compileopts/target.go index 9e341537a..796310012 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -272,8 +272,16 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { spec.Features = "+neon" } if goos == "darwin" { - spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk") - spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip") + spec.Linker = "ld.lld" + spec.Libc = "darwin-libSystem" + arch := strings.Split(triple, "-")[0] + platformVersion := strings.TrimPrefix(strings.Split(triple, "-")[2], "macosx") + spec.LDFlags = append(spec.LDFlags, + "-flavor", "darwinnew", + "-dead_strip", + "-arch", arch, + "-platform_version", "macos", platformVersion, platformVersion, + ) } else if goos == "linux" { spec.Linker = "ld.lld" spec.RTLib = "compiler-rt" diff --git a/lib/macos-minimal-sdk b/lib/macos-minimal-sdk new file mode 160000 index 000000000..a9a5a723a --- /dev/null +++ b/lib/macos-minimal-sdk @@ -0,0 +1 @@ +Subproject commit a9a5a723a6d743a8562bbb62218c6f1361a05994