mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 09:55:40 +00:00
cmd/gio: add iOS/tvOS support for -buildmode=exe
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+11
-125
@@ -10,10 +10,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -21,6 +18,7 @@ var (
|
|||||||
archNames = flag.String("arch", "", "specify architecture(s) to include")
|
archNames = flag.String("arch", "", "specify architecture(s) to include")
|
||||||
buildMode = flag.String("buildmode", "archive", "specify buildmode: archive or exe")
|
buildMode = flag.String("buildmode", "archive", "specify buildmode: archive or exe")
|
||||||
destPath = flag.String("o", "", "output file (Android .aar or .apk file) or directory (iOS/tvOS .framework)")
|
destPath = flag.String("o", "", "output file (Android .aar or .apk file) or directory (iOS/tvOS .framework)")
|
||||||
|
appID = flag.String("appid", "org.gioui.app", "app identifier (for -buildmode=exe)")
|
||||||
verbose = flag.Bool("v", false, "verbose output")
|
verbose = flag.Bool("v", false, "verbose output")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -92,7 +90,7 @@ func build(bi *buildInfo) error {
|
|||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
switch *target {
|
switch *target {
|
||||||
case "ios", "tvos":
|
case "ios", "tvos":
|
||||||
return archiveIOS(tmpDir, *target, bi)
|
return buildIOS(tmpDir, *target, bi)
|
||||||
case "android":
|
case "android":
|
||||||
return buildAndroid(tmpDir, bi)
|
return buildAndroid(tmpDir, bi)
|
||||||
default:
|
default:
|
||||||
@@ -100,140 +98,28 @@ func build(bi *buildInfo) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func archiveIOS(tmpDir, target string, bi *buildInfo) error {
|
|
||||||
frameworkRoot := *destPath
|
|
||||||
if frameworkRoot == "" {
|
|
||||||
appName := filepath.Base(bi.pkg)
|
|
||||||
frameworkRoot = fmt.Sprintf("%s.framework", strings.Title(appName))
|
|
||||||
}
|
|
||||||
framework := filepath.Base(frameworkRoot)
|
|
||||||
suf := ".framework"
|
|
||||||
if !strings.HasSuffix(framework, suf) {
|
|
||||||
return fmt.Errorf("the specified output %q does not end in '.framework'", frameworkRoot)
|
|
||||||
}
|
|
||||||
framework = framework[:len(framework)-len(suf)]
|
|
||||||
if err := os.RemoveAll(frameworkRoot); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
frameworkDir := filepath.Join(frameworkRoot, "Versions", "A")
|
|
||||||
for _, dir := range []string{"Headers", "Modules"} {
|
|
||||||
p := filepath.Join(frameworkDir, dir)
|
|
||||||
if err := os.MkdirAll(p, 0755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
symlinks := [][2]string{
|
|
||||||
{"Versions/Current/Headers", "Headers"},
|
|
||||||
{"Versions/Current/Modules", "Modules"},
|
|
||||||
{"Versions/Current/" + framework, framework},
|
|
||||||
{"A", filepath.Join("Versions", "Current")},
|
|
||||||
}
|
|
||||||
for _, l := range symlinks {
|
|
||||||
if err := os.Symlink(l[0], filepath.Join(frameworkRoot, l[1])); err != nil && !os.IsExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exe := filepath.Join(frameworkDir, framework)
|
|
||||||
lipo := exec.Command("xcrun", "lipo", "-o", exe, "-create")
|
|
||||||
var builds errgroup.Group
|
|
||||||
for _, a := range bi.archs {
|
|
||||||
arch := allArchs[a]
|
|
||||||
var platformSDK string
|
|
||||||
var platformOS string
|
|
||||||
switch target {
|
|
||||||
case "ios":
|
|
||||||
platformOS = "ios"
|
|
||||||
platformSDK = "iphone"
|
|
||||||
case "tvos":
|
|
||||||
platformOS = "tvos"
|
|
||||||
platformSDK = "appletv"
|
|
||||||
}
|
|
||||||
switch a {
|
|
||||||
case "arm", "arm64":
|
|
||||||
platformSDK += "os"
|
|
||||||
case "386", "amd64":
|
|
||||||
platformOS += "-simulator"
|
|
||||||
platformSDK += "simulator"
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported -arch: %s", a)
|
|
||||||
}
|
|
||||||
sdkPathOut, err := runCmd(exec.Command("xcrun", "--sdk", platformSDK, "--show-sdk-path"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sdkPath := string(bytes.TrimSpace(sdkPathOut))
|
|
||||||
clangOut, err := runCmd(exec.Command("xcrun", "--sdk", platformSDK, "--find", "clang"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
clang := string(bytes.TrimSpace(clangOut))
|
|
||||||
cflags := fmt.Sprintf("-fmodules -fobjc-arc -fembed-bitcode -Werror -arch %s -isysroot %s -m%s-version-min=9.0", arch.iosArch, sdkPath, platformOS)
|
|
||||||
lib := filepath.Join(tmpDir, "gio-"+a)
|
|
||||||
cmd := exec.Command(
|
|
||||||
"go",
|
|
||||||
"build",
|
|
||||||
"-ldflags=-s -w "+bi.ldflags,
|
|
||||||
"-buildmode=c-archive",
|
|
||||||
"-o", lib,
|
|
||||||
"-tags", "ios",
|
|
||||||
bi.pkg,
|
|
||||||
)
|
|
||||||
lipo.Args = append(lipo.Args, lib)
|
|
||||||
cmd.Env = append(
|
|
||||||
os.Environ(),
|
|
||||||
"GOOS=darwin",
|
|
||||||
"GOARCH="+a,
|
|
||||||
"CGO_ENABLED=1",
|
|
||||||
"CC="+clang,
|
|
||||||
"CGO_CFLAGS="+cflags,
|
|
||||||
"CGO_LDFLAGS="+cflags,
|
|
||||||
)
|
|
||||||
builds.Go(func() error {
|
|
||||||
_, err := runCmd(cmd)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err := builds.Wait(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := runCmd(lipo); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
appDir, err := appDir()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
headerDst := filepath.Join(frameworkDir, "Headers", framework+".h")
|
|
||||||
headerSrc := filepath.Join(appDir, "framework_ios.h")
|
|
||||||
if err := copyFile(headerDst, headerSrc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
module := fmt.Sprintf(`framework module "%s" {
|
|
||||||
header "%[1]s.h"
|
|
||||||
|
|
||||||
export *
|
|
||||||
}`, framework)
|
|
||||||
moduleFile := filepath.Join(frameworkDir, "Modules", "module.modulemap")
|
|
||||||
return ioutil.WriteFile(moduleFile, []byte(module), 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func errorf(format string, args ...interface{}) {
|
func errorf(format string, args ...interface{}) {
|
||||||
fmt.Fprintf(os.Stderr, format+"\n", args...)
|
fmt.Fprintf(os.Stderr, format+"\n", args...)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCmd(cmd *exec.Cmd) (string, error) {
|
func runCmdRaw(cmd *exec.Cmd) ([]byte, error) {
|
||||||
if *verbose {
|
if *verbose {
|
||||||
fmt.Printf("%s\n", strings.Join(cmd.Args, " "))
|
fmt.Printf("%s\n", strings.Join(cmd.Args, " "))
|
||||||
}
|
}
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return string(bytes.TrimSpace(out)), nil
|
return out, nil
|
||||||
}
|
}
|
||||||
if err, ok := err.(*exec.ExitError); ok {
|
if err, ok := err.(*exec.ExitError); ok {
|
||||||
return "", fmt.Errorf("%s failed: %s%s", strings.Join(cmd.Args, " "), out, err.Stderr)
|
return nil, fmt.Errorf("%s failed: %s%s", strings.Join(cmd.Args, " "), out, err.Stderr)
|
||||||
}
|
}
|
||||||
return "", err
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCmd(cmd *exec.Cmd) (string, error) {
|
||||||
|
out, err := runCmdRaw(cmd)
|
||||||
|
return string(bytes.TrimSpace(out)), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyFile(dst, src string) (err error) {
|
func copyFile(dst, src string) (err error) {
|
||||||
|
|||||||
@@ -0,0 +1,372 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildIOS(tmpDir, target string, bi *buildInfo) error {
|
||||||
|
appName := filepath.Base(bi.pkg)
|
||||||
|
switch *buildMode {
|
||||||
|
case "archive":
|
||||||
|
framework := *destPath
|
||||||
|
if framework == "" {
|
||||||
|
framework = fmt.Sprintf("%s.framework", strings.Title(appName))
|
||||||
|
}
|
||||||
|
return archiveIOS(tmpDir, target, framework, bi)
|
||||||
|
case "exe":
|
||||||
|
tmpFramework := filepath.Join(tmpDir, "Gio.framework")
|
||||||
|
if err := archiveIOS(tmpDir, target, tmpFramework, bi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out := *destPath
|
||||||
|
if out == "" {
|
||||||
|
out = appName + ".ipa"
|
||||||
|
}
|
||||||
|
isIPA := strings.HasSuffix(out, ".ipa")
|
||||||
|
if !isIPA && !strings.HasSuffix(out, ".app") {
|
||||||
|
return fmt.Errorf("the specified output directory %q does not end in .app or .ipa", out)
|
||||||
|
}
|
||||||
|
if !isIPA {
|
||||||
|
return exeIOS(tmpDir, target, out, bi)
|
||||||
|
}
|
||||||
|
payload := filepath.Join(tmpDir, "Payload")
|
||||||
|
appDir := filepath.Join(payload, "gio.app")
|
||||||
|
if err := os.MkdirAll(appDir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := exeIOS(tmpDir, target, appDir, bi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := signIOS(tmpDir, appDir, out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return zipDir(out, tmpDir, "Payload")
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func signIOS(tmpDir, app, ipa string) error {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
provPattern := filepath.Join(home, "Library", "MobileDevice", "Provisioning Profiles", "*.mobileprovision")
|
||||||
|
provisions, err := filepath.Glob(provPattern)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
provInfo := filepath.Join(tmpDir, "provision.plist")
|
||||||
|
for _, prov := range provisions {
|
||||||
|
// Decode the provision file to a plist.
|
||||||
|
_, err := runCmd(exec.Command("security", "cms", "-D", "-i", prov, "-o", provInfo))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
expUnix, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:ExpirationDate", provInfo))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
exp, err := time.Parse(time.UnixDate, expUnix)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sign: failed to parse expiration date from %q: %v", prov, err)
|
||||||
|
}
|
||||||
|
if exp.Before(time.Now()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
appIDPrefix, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:ApplicationIdentifierPrefix:0", provInfo))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
provAppID, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:Entitlements:application-identifier", provInfo))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
expAppID := fmt.Sprintf("%s.%s", appIDPrefix, *appID)
|
||||||
|
if expAppID != provAppID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
certDER, err := runCmdRaw(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:DeveloperCertificates:0", provInfo))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Omit trailing newline.
|
||||||
|
certDER = certDER[:len(certDER)-1]
|
||||||
|
cert, err := x509.ParseCertificate(certDER)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sign: failed to parse developer certificate from %q: %v", prov, err)
|
||||||
|
}
|
||||||
|
entitlements, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-x", "-c", "Print:Entitlements", provInfo))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
entFile := filepath.Join(tmpDir, "entitlements.plist")
|
||||||
|
if err := ioutil.WriteFile(entFile, []byte(entitlements), 0600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
signIdentity := cert.Subject.CommonName
|
||||||
|
_, err = runCmd(exec.Command("codesign", "-s", signIdentity, "--entitlements", entFile, app))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("sign: no valid provisioning profile found for bundle id %q", *appID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
|
||||||
|
if *appID == "" {
|
||||||
|
return errors.New("app id is empty; use -appid to set it")
|
||||||
|
}
|
||||||
|
if err := os.RemoveAll(app); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Mkdir(app, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mainm := filepath.Join(tmpDir, "main.m")
|
||||||
|
const mainmSrc = `@import UIKit;
|
||||||
|
@import Gio;
|
||||||
|
|
||||||
|
int main(int argc, char * argv[]) {
|
||||||
|
@autoreleasepool {
|
||||||
|
return UIApplicationMain(argc, argv, nil, NSStringFromClass([GioAppDelegate class]));
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
if err := ioutil.WriteFile(mainm, []byte(mainmSrc), 0600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
exe := filepath.Join(app, "app")
|
||||||
|
lipo := exec.Command("xcrun", "lipo", "-o", exe, "-create")
|
||||||
|
var builds errgroup.Group
|
||||||
|
for _, a := range bi.archs {
|
||||||
|
clang, cflags, err := iosCompilerFor(target, a)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
exeSlice := filepath.Join(tmpDir, "app-"+a)
|
||||||
|
lipo.Args = append(lipo.Args, exeSlice)
|
||||||
|
compile := exec.Command(clang, cflags...)
|
||||||
|
compile.Args = append(compile.Args,
|
||||||
|
"-F", tmpDir,
|
||||||
|
"-o", exeSlice,
|
||||||
|
mainm,
|
||||||
|
)
|
||||||
|
builds.Go(func() error {
|
||||||
|
_, err := runCmd(compile)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := builds.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := runCmd(lipo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
infoPlistSrc := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>app</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>%s</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>Gio</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
</dict>
|
||||||
|
</plist>`, *appID)
|
||||||
|
infoPlist := filepath.Join(app, "Info.plist")
|
||||||
|
if err := ioutil.WriteFile(infoPlist, []byte(infoPlistSrc), 0600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := runCmd(exec.Command("plutil", "-convert", "binary1", infoPlist)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error {
|
||||||
|
framework := filepath.Base(frameworkRoot)
|
||||||
|
const suf = ".framework"
|
||||||
|
if !strings.HasSuffix(framework, suf) {
|
||||||
|
return fmt.Errorf("the specified output %q does not end in '.framework'", frameworkRoot)
|
||||||
|
}
|
||||||
|
framework = framework[:len(framework)-len(suf)]
|
||||||
|
if err := os.RemoveAll(frameworkRoot); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
frameworkDir := filepath.Join(frameworkRoot, "Versions", "A")
|
||||||
|
for _, dir := range []string{"Headers", "Modules"} {
|
||||||
|
p := filepath.Join(frameworkDir, dir)
|
||||||
|
if err := os.MkdirAll(p, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
symlinks := [][2]string{
|
||||||
|
{"Versions/Current/Headers", "Headers"},
|
||||||
|
{"Versions/Current/Modules", "Modules"},
|
||||||
|
{"Versions/Current/" + framework, framework},
|
||||||
|
{"A", filepath.Join("Versions", "Current")},
|
||||||
|
}
|
||||||
|
for _, l := range symlinks {
|
||||||
|
if err := os.Symlink(l[0], filepath.Join(frameworkRoot, l[1])); err != nil && !os.IsExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exe := filepath.Join(frameworkDir, framework)
|
||||||
|
lipo := exec.Command("xcrun", "lipo", "-o", exe, "-create")
|
||||||
|
var builds errgroup.Group
|
||||||
|
for _, a := range bi.archs {
|
||||||
|
clang, cflags, err := iosCompilerFor(target, a)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lib := filepath.Join(tmpDir, "gio-"+a)
|
||||||
|
cmd := exec.Command(
|
||||||
|
"go",
|
||||||
|
"build",
|
||||||
|
"-ldflags=-s -w "+bi.ldflags,
|
||||||
|
"-buildmode=c-archive",
|
||||||
|
"-o", lib,
|
||||||
|
"-tags", "ios",
|
||||||
|
bi.pkg,
|
||||||
|
)
|
||||||
|
lipo.Args = append(lipo.Args, lib)
|
||||||
|
cflagsLine := strings.Join(cflags, " ")
|
||||||
|
cmd.Env = append(
|
||||||
|
os.Environ(),
|
||||||
|
"GOOS=darwin",
|
||||||
|
"GOARCH="+a,
|
||||||
|
"CGO_ENABLED=1",
|
||||||
|
"CC="+clang,
|
||||||
|
"CGO_CFLAGS="+cflagsLine,
|
||||||
|
"CGO_LDFLAGS="+cflagsLine,
|
||||||
|
)
|
||||||
|
builds.Go(func() error {
|
||||||
|
_, err := runCmd(cmd)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := builds.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := runCmd(lipo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
appDir, err := appDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
headerDst := filepath.Join(frameworkDir, "Headers", framework+".h")
|
||||||
|
headerSrc := filepath.Join(appDir, "framework_ios.h")
|
||||||
|
if err := copyFile(headerDst, headerSrc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
module := fmt.Sprintf(`framework module "%s" {
|
||||||
|
header "%[1]s.h"
|
||||||
|
|
||||||
|
export *
|
||||||
|
}`, framework)
|
||||||
|
moduleFile := filepath.Join(frameworkDir, "Modules", "module.modulemap")
|
||||||
|
return ioutil.WriteFile(moduleFile, []byte(module), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iosCompilerFor(target, arch string) (string, []string, error) {
|
||||||
|
var platformSDK string
|
||||||
|
var platformOS string
|
||||||
|
switch target {
|
||||||
|
case "ios":
|
||||||
|
platformOS = "ios"
|
||||||
|
platformSDK = "iphone"
|
||||||
|
case "tvos":
|
||||||
|
platformOS = "tvos"
|
||||||
|
platformSDK = "appletv"
|
||||||
|
}
|
||||||
|
switch arch {
|
||||||
|
case "arm", "arm64":
|
||||||
|
platformSDK += "os"
|
||||||
|
case "386", "amd64":
|
||||||
|
platformOS += "-simulator"
|
||||||
|
platformSDK += "simulator"
|
||||||
|
default:
|
||||||
|
return "", nil, fmt.Errorf("unsupported -arch: %s", arch)
|
||||||
|
}
|
||||||
|
sdkPath, err := runCmd(exec.Command("xcrun", "--sdk", platformSDK, "--show-sdk-path"))
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
clang, err := runCmd(exec.Command("xcrun", "--sdk", platformSDK, "--find", "clang"))
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
cflags := []string{
|
||||||
|
"-fmodules",
|
||||||
|
"-fobjc-arc",
|
||||||
|
"-fembed-bitcode",
|
||||||
|
"-Werror",
|
||||||
|
"-arch", allArchs[arch].iosArch,
|
||||||
|
"-isysroot", sdkPath,
|
||||||
|
"-m" + platformOS + "-version-min=9.0",
|
||||||
|
}
|
||||||
|
return clang, cflags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func zipDir(dst, base, dir string) (err error) {
|
||||||
|
f, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if cerr := f.Close(); err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
zipf := zip.NewWriter(f)
|
||||||
|
err = filepath.Walk(filepath.Join(base, dir), func(path string, f os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if f.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rel := filepath.ToSlash(path[len(base)+1:])
|
||||||
|
entry, err := zipf.Create(rel)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
src, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
_, err = io.Copy(entry, src)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return zipf.Close()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user