| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- // Copyright 2015 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package main
- import (
- "bytes"
- "crypto/x509"
- "encoding/pem"
- "fmt"
- "os"
- "os/exec"
- "path"
- "path/filepath"
- "strings"
- "text/template"
- "golang.org/x/tools/go/packages"
- )
- func goAppleBuild(pkg *packages.Package, bundleID string, targets []targetInfo) (map[string]bool, error) {
- src := pkg.PkgPath
- if buildO != "" && !strings.HasSuffix(buildO, ".app") {
- return nil, fmt.Errorf("-o must have an .app for -target=ios")
- }
- productName := rfc1034Label(path.Base(pkg.PkgPath))
- if productName == "" {
- productName = "ProductName" // like xcode.
- }
- infoplist := new(bytes.Buffer)
- if err := infoplistTmpl.Execute(infoplist, infoplistTmplData{
- BundleID: bundleID + "." + productName,
- Name: strings.Title(path.Base(pkg.PkgPath)),
- }); err != nil {
- return nil, err
- }
- // Detect the team ID
- teamID, err := detectTeamID()
- if err != nil {
- return nil, err
- }
- projPbxproj := new(bytes.Buffer)
- if err := projPbxprojTmpl.Execute(projPbxproj, projPbxprojTmplData{
- TeamID: teamID,
- }); err != nil {
- return nil, err
- }
- files := []struct {
- name string
- contents []byte
- }{
- {tmpdir + "/main.xcodeproj/project.pbxproj", projPbxproj.Bytes()},
- {tmpdir + "/main/Info.plist", infoplist.Bytes()},
- {tmpdir + "/main/Images.xcassets/AppIcon.appiconset/Contents.json", []byte(contentsJSON)},
- }
- for _, file := range files {
- if err := mkdir(filepath.Dir(file.name)); err != nil {
- return nil, err
- }
- if buildX {
- printcmd("echo \"%s\" > %s", file.contents, file.name)
- }
- if !buildN {
- if err := os.WriteFile(file.name, file.contents, 0644); err != nil {
- return nil, err
- }
- }
- }
- // We are using lipo tool to build multiarchitecture binaries.
- cmd := exec.Command(
- "xcrun", "lipo",
- "-o", filepath.Join(tmpdir, "main/main"),
- "-create",
- )
- var nmpkgs map[string]bool
- builtArch := map[string]bool{}
- for _, t := range targets {
- // Only one binary per arch allowed
- // e.g. ios/arm64 + iossimulator/amd64
- if builtArch[t.arch] {
- continue
- }
- builtArch[t.arch] = true
- path := filepath.Join(tmpdir, t.platform, t.arch)
- // Disable DWARF; see golang.org/issues/25148.
- if err := goBuild(src, appleEnv[t.String()], "-ldflags=-w", "-o="+path); err != nil {
- return nil, err
- }
- if nmpkgs == nil {
- var err error
- nmpkgs, err = extractPkgs(appleNM, path)
- if err != nil {
- return nil, err
- }
- }
- cmd.Args = append(cmd.Args, path)
- }
- if err := runCmd(cmd); err != nil {
- return nil, err
- }
- // TODO(jbd): Set the launcher icon.
- if err := appleCopyAssets(pkg, tmpdir); err != nil {
- return nil, err
- }
- // Build and move the release build to the output directory.
- cmdStrings := []string{
- "xcodebuild",
- "-configuration", "Release",
- "-project", tmpdir + "/main.xcodeproj",
- "-allowProvisioningUpdates",
- "DEVELOPMENT_TEAM=" + teamID,
- }
- cmd = exec.Command("xcrun", cmdStrings...)
- if err := runCmd(cmd); err != nil {
- return nil, err
- }
- // TODO(jbd): Fallback to copying if renaming fails.
- if buildO == "" {
- n := pkg.PkgPath
- if n == "." {
- // use cwd name
- cwd, err := os.Getwd()
- if err != nil {
- return nil, fmt.Errorf("cannot create .app; cannot get the current working dir: %v", err)
- }
- n = cwd
- }
- n = path.Base(n)
- buildO = n + ".app"
- }
- if buildX {
- printcmd("mv %s %s", tmpdir+"/build/Release-iphoneos/main.app", buildO)
- }
- if !buildN {
- // if output already exists, remove.
- if err := os.RemoveAll(buildO); err != nil {
- return nil, err
- }
- if err := os.Rename(tmpdir+"/build/Release-iphoneos/main.app", buildO); err != nil {
- return nil, err
- }
- }
- return nmpkgs, nil
- }
- func detectTeamID() (string, error) {
- // Grabs the first certificate for "Apple Development"; will not work if there
- // are multiple certificates and the first is not desired.
- cmd := exec.Command(
- "security", "find-certificate",
- "-c", "Apple Development", "-p",
- )
- pemString, err := cmd.Output()
- if err != nil {
- err = fmt.Errorf("failed to pull the signing certificate to determine your team ID: %v", err)
- return "", err
- }
- block, _ := pem.Decode(pemString)
- if block == nil {
- err = fmt.Errorf("failed to decode the PEM to determine your team ID: %s", pemString)
- return "", err
- }
- cert, err := x509.ParseCertificate(block.Bytes)
- if err != nil {
- err = fmt.Errorf("failed to parse your signing certificate to determine your team ID: %v", err)
- return "", err
- }
- if len(cert.Subject.OrganizationalUnit) == 0 {
- err = fmt.Errorf("the signing certificate has no organizational unit (team ID)")
- return "", err
- }
- return cert.Subject.OrganizationalUnit[0], nil
- }
- func appleCopyAssets(pkg *packages.Package, xcodeProjDir string) error {
- dstAssets := xcodeProjDir + "/main/assets"
- if err := mkdir(dstAssets); err != nil {
- return err
- }
- // TODO(hajimehoshi): This works only with Go tools that assume all source files are in one directory.
- // Fix this to work with other Go tools.
- srcAssets := filepath.Join(filepath.Dir(pkg.GoFiles[0]), "assets")
- fi, err := os.Stat(srcAssets)
- if err != nil {
- if os.IsNotExist(err) {
- // skip walking through the directory to deep copy.
- return nil
- }
- return err
- }
- if !fi.IsDir() {
- // skip walking through to deep copy.
- return nil
- }
- // if assets is a symlink, follow the symlink.
- srcAssets, err = filepath.EvalSymlinks(srcAssets)
- if err != nil {
- return err
- }
- return filepath.Walk(srcAssets, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- if name := filepath.Base(path); strings.HasPrefix(name, ".") {
- // Do not include the hidden files.
- return nil
- }
- if info.IsDir() {
- return nil
- }
- dst := dstAssets + "/" + path[len(srcAssets)+1:]
- return copyFile(dst, path)
- })
- }
- type infoplistTmplData struct {
- BundleID string
- Name string
- }
- var infoplistTmpl = template.Must(template.New("infoplist").Parse(`<?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>main</string>
- <key>CFBundleIdentifier</key>
- <string>{{.BundleID}}</string>
- <key>CFBundleInfoDictionaryVersion</key>
- <string>6.0</string>
- <key>CFBundleName</key>
- <string>{{.Name}}</string>
- <key>CFBundlePackageType</key>
- <string>APPL</string>
- <key>CFBundleShortVersionString</key>
- <string>1.0</string>
- <key>CFBundleSignature</key>
- <string>????</string>
- <key>CFBundleVersion</key>
- <string>1</string>
- <key>LSRequiresIPhoneOS</key>
- <true/>
- <key>UILaunchStoryboardName</key>
- <string>LaunchScreen</string>
- <key>UIRequiredDeviceCapabilities</key>
- <array>
- <string>armv7</string>
- </array>
- <key>UISupportedInterfaceOrientations</key>
- <array>
- <string>UIInterfaceOrientationPortrait</string>
- <string>UIInterfaceOrientationLandscapeLeft</string>
- <string>UIInterfaceOrientationLandscapeRight</string>
- </array>
- <key>UISupportedInterfaceOrientations~ipad</key>
- <array>
- <string>UIInterfaceOrientationPortrait</string>
- <string>UIInterfaceOrientationPortraitUpsideDown</string>
- <string>UIInterfaceOrientationLandscapeLeft</string>
- <string>UIInterfaceOrientationLandscapeRight</string>
- </array>
- </dict>
- </plist>
- `))
- type projPbxprojTmplData struct {
- TeamID string
- }
- var projPbxprojTmpl = template.Must(template.New("projPbxproj").Parse(`// !$*UTF8*$!
- {
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 46;
- objects = {
- /* Begin PBXBuildFile section */
- 254BB84F1B1FD08900C56DE9 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 254BB84E1B1FD08900C56DE9 /* Images.xcassets */; };
- 254BB8681B1FD16500C56DE9 /* main in Resources */ = {isa = PBXBuildFile; fileRef = 254BB8671B1FD16500C56DE9 /* main */; };
- 25FB30331B30FDEE0005924C /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 25FB30321B30FDEE0005924C /* assets */; };
- /* End PBXBuildFile section */
- /* Begin PBXFileReference section */
- 254BB83E1B1FD08900C56DE9 /* main.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = main.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 254BB8421B1FD08900C56DE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
- 254BB84E1B1FD08900C56DE9 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
- 254BB8671B1FD16500C56DE9 /* main */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = main; sourceTree = "<group>"; };
- 25FB30321B30FDEE0005924C /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = main/assets; sourceTree = "<group>"; };
- /* End PBXFileReference section */
- /* Begin PBXGroup section */
- 254BB8351B1FD08900C56DE9 = {
- isa = PBXGroup;
- children = (
- 25FB30321B30FDEE0005924C /* assets */,
- 254BB8401B1FD08900C56DE9 /* main */,
- 254BB83F1B1FD08900C56DE9 /* Products */,
- );
- sourceTree = "<group>";
- usesTabs = 0;
- };
- 254BB83F1B1FD08900C56DE9 /* Products */ = {
- isa = PBXGroup;
- children = (
- 254BB83E1B1FD08900C56DE9 /* main.app */,
- );
- name = Products;
- sourceTree = "<group>";
- };
- 254BB8401B1FD08900C56DE9 /* main */ = {
- isa = PBXGroup;
- children = (
- 254BB8671B1FD16500C56DE9 /* main */,
- 254BB84E1B1FD08900C56DE9 /* Images.xcassets */,
- 254BB8411B1FD08900C56DE9 /* Supporting Files */,
- );
- path = main;
- sourceTree = "<group>";
- };
- 254BB8411B1FD08900C56DE9 /* Supporting Files */ = {
- isa = PBXGroup;
- children = (
- 254BB8421B1FD08900C56DE9 /* Info.plist */,
- );
- name = "Supporting Files";
- sourceTree = "<group>";
- };
- /* End PBXGroup section */
- /* Begin PBXNativeTarget section */
- 254BB83D1B1FD08900C56DE9 /* main */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 254BB8611B1FD08900C56DE9 /* Build configuration list for PBXNativeTarget "main" */;
- buildPhases = (
- 254BB83C1B1FD08900C56DE9 /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- );
- name = main;
- productName = main;
- productReference = 254BB83E1B1FD08900C56DE9 /* main.app */;
- productType = "com.apple.product-type.application";
- };
- /* End PBXNativeTarget section */
- /* Begin PBXProject section */
- 254BB8361B1FD08900C56DE9 /* Project object */ = {
- isa = PBXProject;
- attributes = {
- LastUpgradeCheck = 0630;
- ORGANIZATIONNAME = Developer;
- TargetAttributes = {
- 254BB83D1B1FD08900C56DE9 = {
- CreatedOnToolsVersion = 6.3.1;
- DevelopmentTeam = {{.TeamID}};
- };
- };
- };
- buildConfigurationList = 254BB8391B1FD08900C56DE9 /* Build configuration list for PBXProject "main" */;
- compatibilityVersion = "Xcode 3.2";
- developmentRegion = English;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- Base,
- );
- mainGroup = 254BB8351B1FD08900C56DE9;
- productRefGroup = 254BB83F1B1FD08900C56DE9 /* Products */;
- projectDirPath = "";
- projectRoot = "";
- targets = (
- 254BB83D1B1FD08900C56DE9 /* main */,
- );
- };
- /* End PBXProject section */
- /* Begin PBXResourcesBuildPhase section */
- 254BB83C1B1FD08900C56DE9 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 25FB30331B30FDEE0005924C /* assets in Resources */,
- 254BB8681B1FD16500C56DE9 /* main in Resources */,
- 254BB84F1B1FD08900C56DE9 /* Images.xcassets in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- /* End PBXResourcesBuildPhase section */
- /* Begin XCBuildConfiguration section */
- 254BB8601B1FD08900C56DE9 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- ENABLE_BITCODE = YES;
- };
- name = Release;
- };
- 254BB8631B1FD08900C56DE9 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- INFOPLIST_FILE = main/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- PRODUCT_NAME = "$(TARGET_NAME)";
- };
- name = Release;
- };
- /* End XCBuildConfiguration section */
- /* Begin XCConfigurationList section */
- 254BB8391B1FD08900C56DE9 /* Build configuration list for PBXProject "main" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 254BB8601B1FD08900C56DE9 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 254BB8611B1FD08900C56DE9 /* Build configuration list for PBXNativeTarget "main" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 254BB8631B1FD08900C56DE9 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- /* End XCConfigurationList section */
- };
- rootObject = 254BB8361B1FD08900C56DE9 /* Project object */;
- }
- `))
- const contentsJSON = `{
- "images" : [
- {
- "idiom" : "iphone",
- "size" : "29x29",
- "scale" : "2x"
- },
- {
- "idiom" : "iphone",
- "size" : "29x29",
- "scale" : "3x"
- },
- {
- "idiom" : "iphone",
- "size" : "40x40",
- "scale" : "2x"
- },
- {
- "idiom" : "iphone",
- "size" : "40x40",
- "scale" : "3x"
- },
- {
- "idiom" : "iphone",
- "size" : "60x60",
- "scale" : "2x"
- },
- {
- "idiom" : "iphone",
- "size" : "60x60",
- "scale" : "3x"
- },
- {
- "idiom" : "ipad",
- "size" : "29x29",
- "scale" : "1x"
- },
- {
- "idiom" : "ipad",
- "size" : "29x29",
- "scale" : "2x"
- },
- {
- "idiom" : "ipad",
- "size" : "40x40",
- "scale" : "1x"
- },
- {
- "idiom" : "ipad",
- "size" : "40x40",
- "scale" : "2x"
- },
- {
- "idiom" : "ipad",
- "size" : "76x76",
- "scale" : "1x"
- },
- {
- "idiom" : "ipad",
- "size" : "76x76",
- "scale" : "2x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
- }
- `
- // rfc1034Label sanitizes the name to be usable in a uniform type identifier.
- // The sanitization is similar to xcode's rfc1034identifier macro that
- // replaces illegal characters (not conforming the rfc1034 label rule) with '-'.
- func rfc1034Label(name string) string {
- // * Uniform type identifier:
- //
- // According to
- // https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/understanding_utis/understand_utis_conc/understand_utis_conc.html
- //
- // A uniform type identifier is a Unicode string that usually contains characters
- // in the ASCII character set. However, only a subset of the ASCII characters are
- // permitted. You may use the Roman alphabet in upper and lower case (A–Z, a–z),
- // the digits 0 through 9, the dot (“.”), and the hyphen (“-”). This restriction
- // is based on DNS name restrictions, set forth in RFC 1035.
- //
- // Uniform type identifiers may also contain any of the Unicode characters greater
- // than U+007F.
- //
- // Note: the actual implementation of xcode does not allow some unicode characters
- // greater than U+007f. In this implementation, we just replace everything non
- // alphanumeric with "-" like the rfc1034identifier macro.
- //
- // * RFC1034 Label
- //
- // <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
- // <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
- // <let-dig-hyp> ::= <let-dig> | "-"
- // <let-dig> ::= <letter> | <digit>
- const surrSelf = 0x10000
- begin := false
- var res []rune
- for i, r := range name {
- if r == '.' && !begin {
- continue
- }
- begin = true
- switch {
- case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z':
- res = append(res, r)
- case '0' <= r && r <= '9':
- if i == 0 {
- res = append(res, '-')
- } else {
- res = append(res, r)
- }
- default:
- if r < surrSelf {
- res = append(res, '-')
- } else {
- res = append(res, '-', '-')
- }
- }
- }
- return string(res)
- }
|