Skip to content

Commit 277609f

Browse files
committed
cmd/link/internal/ld: copy Mach-O platform version commands to go.o
To build for watchOS and tvOS the Apple toolchain requires a Mach-O load command that matches the platform for all object files in a build. The go.o object file produced by the Go linker contains no such command. The loader commands are mutually exclusive so we need to pick the right one. Fortunately, cgo must be enabled for watchOS and tvOS to be useful, so we can copy the first loader command we find in the object files produced by the host compiler. Add a test that builds a small program for tvOS to test both this CL and the previous CL that added bitcode support. Updates #22395 Change-Id: I7a47d19be9d80f0459dc358c600cddd9f236c444 Reviewed-on: https://go-review.googlesource.com/c/go/+/168321 TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Cherry Zhang <[email protected]>
1 parent 0fe1986 commit 277609f

File tree

4 files changed

+101
-2
lines changed

4 files changed

+101
-2
lines changed

src/cmd/link/dwarf_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,54 @@ func TestDWARFiOS(t *testing.T) {
171171
testDWARF(t, "c-archive", true, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm", "GOARM=7")
172172
testDWARF(t, "c-archive", true, cc, "CGO_ENABLED=1", "GOOS=darwin", "GOARCH=arm64")
173173
}
174+
175+
func TestBuildFortvOS(t *testing.T) {
176+
testenv.MustHaveCGO(t)
177+
testenv.MustHaveGoBuild(t)
178+
179+
// Only run this on darwin/amd64, where we can cross build for tvOS.
180+
if runtime.GOARCH != "amd64" || runtime.GOOS != "darwin" {
181+
t.Skip("skipping on non-darwin/amd64 platform")
182+
}
183+
if err := exec.Command("xcrun", "--help").Run(); err != nil {
184+
t.Skipf("error running xcrun, required for iOS cross build: %v", err)
185+
}
186+
187+
sdkPath, err := exec.Command("xcrun", "--sdk", "appletvos", "--show-sdk-path").Output()
188+
if err != nil {
189+
t.Fatalf("xcrun --sdk appletvos --show-sdk-path failed: %v", err)
190+
}
191+
CC := []string{
192+
"clang",
193+
"-arch",
194+
"arm64",
195+
"-isysroot", strings.TrimSpace(string(sdkPath)),
196+
"-mtvos-version-min=12.0",
197+
"-fembed-bitcode",
198+
"-framework", "CoreFoundation",
199+
}
200+
lib := filepath.Join("testdata", "lib.go")
201+
tmpDir, err := ioutil.TempDir("", "go-link-TestBuildFortvOS")
202+
if err != nil {
203+
t.Fatal(err)
204+
}
205+
defer os.RemoveAll(tmpDir)
206+
207+
ar := filepath.Join(tmpDir, "lib.a")
208+
cmd := exec.Command(testenv.GoToolPath(t), "build", "-buildmode=c-archive", "-o", ar, lib)
209+
cmd.Env = append(os.Environ(),
210+
"CGO_ENABLED=1",
211+
"GOOS=darwin",
212+
"GOARCH=arm64",
213+
"CC="+strings.Join(CC, " "),
214+
)
215+
if out, err := cmd.CombinedOutput(); err != nil {
216+
t.Fatalf("%v: %v:\n%s", cmd.Args, err, out)
217+
}
218+
219+
link := exec.Command(CC[0], CC[1:]...)
220+
link.Args = append(link.Args, ar, filepath.Join("testdata", "main.m"))
221+
if out, err := link.CombinedOutput(); err != nil {
222+
t.Fatalf("%v: %v:\n%s", link.Args, err, out)
223+
}
224+
}

src/cmd/link/internal/ld/macho.go

+37-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"cmd/link/internal/sym"
1212
"debug/macho"
1313
"encoding/binary"
14+
"fmt"
15+
"io"
16+
"os"
1417
"sort"
1518
"strings"
1619
)
@@ -691,8 +694,14 @@ func Asmbmacho(ctxt *Link) {
691694
}
692695
}
693696
}
694-
695-
if ctxt.LinkMode == LinkInternal {
697+
load, err := hostobjMachoPlatform(hostobj)
698+
if err != nil {
699+
Exitf("%v", err)
700+
}
701+
if load != nil {
702+
ml := newMachoLoad(ctxt.Arch, load.cmd.type_, uint32(len(load.cmd.data)))
703+
copy(ml.data, load.cmd.data)
704+
} else if ctxt.LinkMode == LinkInternal {
696705
// For lldb, must say LC_VERSION_MIN_MACOSX or else
697706
// it won't know that this Mach-O binary is from OS X
698707
// (could be iOS or WatchOS instead).
@@ -1017,6 +1026,32 @@ func Machoemitreloc(ctxt *Link) {
10171026
}
10181027
}
10191028

1029+
// hostobjMachoPlatform returns the first platform load command found
1030+
// in the host objects, if any.
1031+
func hostobjMachoPlatform(hostobj []Hostobj) (*MachoPlatformLoad, error) {
1032+
for _, h := range hostobj {
1033+
f, err := os.Open(h.file)
1034+
if err != nil {
1035+
return nil, fmt.Errorf("%s: failed to open host object: %v\n", h.file, err)
1036+
}
1037+
defer f.Close()
1038+
sr := io.NewSectionReader(f, h.off, h.length)
1039+
m, err := macho.NewFile(sr)
1040+
if err != nil {
1041+
// Not a valid Mach-O file.
1042+
return nil, nil
1043+
}
1044+
load, err := peekMachoPlatform(m)
1045+
if err != nil {
1046+
return nil, err
1047+
}
1048+
if load != nil {
1049+
return load, nil
1050+
}
1051+
}
1052+
return nil, nil
1053+
}
1054+
10201055
// peekMachoPlatform returns the first LC_VERSION_MIN_* or LC_BUILD_VERSION
10211056
// load command found in the Mach-O file, if any.
10221057
func peekMachoPlatform(m *macho.File) (*MachoPlatformLoad, error) {

src/cmd/link/testdata/lib.go

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package main
2+
3+
import "C"
4+
5+
//export GoFunc
6+
func GoFunc() {}
7+
8+
func main() {}

src/cmd/link/testdata/main.m

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
extern void GoFunc();
2+
3+
int main(int argc, char **argv) {
4+
GoFunc();
5+
}

0 commit comments

Comments
 (0)