github.com/adwpc/xmobile@v0.0.0-20231212131043-3f9720cf0e99/cmd/gomobile/build_apple.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "bytes" 9 "crypto/x509" 10 "encoding/pem" 11 "fmt" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path" 16 "path/filepath" 17 "strings" 18 "text/template" 19 20 "golang.org/x/tools/go/packages" 21 ) 22 23 func goAppleBuild(pkg *packages.Package, bundleID string, targets []targetInfo) (map[string]bool, error) { 24 src := pkg.PkgPath 25 if buildO != "" && !strings.HasSuffix(buildO, ".app") { 26 return nil, fmt.Errorf("-o must have an .app for -target=ios") 27 } 28 29 productName := rfc1034Label(path.Base(pkg.PkgPath)) 30 if productName == "" { 31 productName = "ProductName" // like xcode. 32 } 33 34 infoplist := new(bytes.Buffer) 35 if err := infoplistTmpl.Execute(infoplist, infoplistTmplData{ 36 BundleID: bundleID + "." + productName, 37 Name: strings.Title(path.Base(pkg.PkgPath)), 38 }); err != nil { 39 return nil, err 40 } 41 42 // Detect the team ID 43 teamID, err := detectTeamID() 44 if err != nil { 45 return nil, err 46 } 47 48 projPbxproj := new(bytes.Buffer) 49 if err := projPbxprojTmpl.Execute(projPbxproj, projPbxprojTmplData{ 50 TeamID: teamID, 51 }); err != nil { 52 return nil, err 53 } 54 55 files := []struct { 56 name string 57 contents []byte 58 }{ 59 {tmpdir + "/main.xcodeproj/project.pbxproj", projPbxproj.Bytes()}, 60 {tmpdir + "/main/Info.plist", infoplist.Bytes()}, 61 {tmpdir + "/main/Images.xcassets/AppIcon.appiconset/Contents.json", []byte(contentsJSON)}, 62 } 63 64 for _, file := range files { 65 if err := mkdir(filepath.Dir(file.name)); err != nil { 66 return nil, err 67 } 68 if buildX { 69 printcmd("echo \"%s\" > %s", file.contents, file.name) 70 } 71 if !buildN { 72 if err := ioutil.WriteFile(file.name, file.contents, 0644); err != nil { 73 return nil, err 74 } 75 } 76 } 77 78 // We are using lipo tool to build multiarchitecture binaries. 79 cmd := exec.Command( 80 "xcrun", "lipo", 81 "-o", filepath.Join(tmpdir, "main/main"), 82 "-create", 83 ) 84 85 var nmpkgs map[string]bool 86 builtArch := map[string]bool{} 87 for _, t := range targets { 88 // Only one binary per arch allowed 89 // e.g. ios/arm64 + iossimulator/amd64 90 if builtArch[t.arch] { 91 continue 92 } 93 builtArch[t.arch] = true 94 95 path := filepath.Join(tmpdir, t.platform, t.arch) 96 97 // Disable DWARF; see golang.org/issues/25148. 98 if err := goBuild(src, appleEnv[t.String()], "-ldflags=-w", "-o="+path); err != nil { 99 return nil, err 100 } 101 if nmpkgs == nil { 102 var err error 103 nmpkgs, err = extractPkgs(appleNM, path) 104 if err != nil { 105 return nil, err 106 } 107 } 108 cmd.Args = append(cmd.Args, path) 109 110 } 111 112 if err := runCmd(cmd); err != nil { 113 return nil, err 114 } 115 116 // TODO(jbd): Set the launcher icon. 117 if err := appleCopyAssets(pkg, tmpdir); err != nil { 118 return nil, err 119 } 120 121 // Build and move the release build to the output directory. 122 cmdStrings := []string{ 123 "xcodebuild", 124 "-configuration", "Release", 125 "-project", tmpdir + "/main.xcodeproj", 126 "-allowProvisioningUpdates", 127 "DEVELOPMENT_TEAM=" + teamID, 128 } 129 130 cmd = exec.Command("xcrun", cmdStrings...) 131 if err := runCmd(cmd); err != nil { 132 return nil, err 133 } 134 135 // TODO(jbd): Fallback to copying if renaming fails. 136 if buildO == "" { 137 n := pkg.PkgPath 138 if n == "." { 139 // use cwd name 140 cwd, err := os.Getwd() 141 if err != nil { 142 return nil, fmt.Errorf("cannot create .app; cannot get the current working dir: %v", err) 143 } 144 n = cwd 145 } 146 n = path.Base(n) 147 buildO = n + ".app" 148 } 149 if buildX { 150 printcmd("mv %s %s", tmpdir+"/build/Release-iphoneos/main.app", buildO) 151 } 152 if !buildN { 153 // if output already exists, remove. 154 if err := os.RemoveAll(buildO); err != nil { 155 return nil, err 156 } 157 if err := os.Rename(tmpdir+"/build/Release-iphoneos/main.app", buildO); err != nil { 158 return nil, err 159 } 160 } 161 return nmpkgs, nil 162 } 163 164 func detectTeamID() (string, error) { 165 // Grabs the first certificate for "Apple Development"; will not work if there 166 // are multiple certificates and the first is not desired. 167 cmd := exec.Command( 168 "security", "find-certificate", 169 "-c", "Apple Development", "-p", 170 ) 171 pemString, err := cmd.Output() 172 if err != nil { 173 err = fmt.Errorf("failed to pull the signing certificate to determine your team ID: %v", err) 174 return "", err 175 } 176 177 block, _ := pem.Decode(pemString) 178 if block == nil { 179 err = fmt.Errorf("failed to decode the PEM to determine your team ID: %s", pemString) 180 return "", err 181 } 182 183 cert, err := x509.ParseCertificate(block.Bytes) 184 if err != nil { 185 err = fmt.Errorf("failed to parse your signing certificate to determine your team ID: %v", err) 186 return "", err 187 } 188 189 if len(cert.Subject.OrganizationalUnit) == 0 { 190 err = fmt.Errorf("the signing certificate has no organizational unit (team ID)") 191 return "", err 192 } 193 194 return cert.Subject.OrganizationalUnit[0], nil 195 } 196 197 func appleCopyAssets(pkg *packages.Package, xcodeProjDir string) error { 198 dstAssets := xcodeProjDir + "/main/assets" 199 if err := mkdir(dstAssets); err != nil { 200 return err 201 } 202 203 // TODO(hajimehoshi): This works only with Go tools that assume all source files are in one directory. 204 // Fix this to work with other Go tools. 205 srcAssets := filepath.Join(filepath.Dir(pkg.GoFiles[0]), "assets") 206 fi, err := os.Stat(srcAssets) 207 if err != nil { 208 if os.IsNotExist(err) { 209 // skip walking through the directory to deep copy. 210 return nil 211 } 212 return err 213 } 214 if !fi.IsDir() { 215 // skip walking through to deep copy. 216 return nil 217 } 218 // if assets is a symlink, follow the symlink. 219 srcAssets, err = filepath.EvalSymlinks(srcAssets) 220 if err != nil { 221 return err 222 } 223 return filepath.Walk(srcAssets, func(path string, info os.FileInfo, err error) error { 224 if err != nil { 225 return err 226 } 227 if name := filepath.Base(path); strings.HasPrefix(name, ".") { 228 // Do not include the hidden files. 229 return nil 230 } 231 if info.IsDir() { 232 return nil 233 } 234 dst := dstAssets + "/" + path[len(srcAssets)+1:] 235 return copyFile(dst, path) 236 }) 237 } 238 239 type infoplistTmplData struct { 240 BundleID string 241 Name string 242 } 243 244 var infoplistTmpl = template.Must(template.New("infoplist").Parse(`<?xml version="1.0" encoding="UTF-8"?> 245 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 246 <plist version="1.0"> 247 <dict> 248 <key>CFBundleDevelopmentRegion</key> 249 <string>en</string> 250 <key>CFBundleExecutable</key> 251 <string>main</string> 252 <key>CFBundleIdentifier</key> 253 <string>{{.BundleID}}</string> 254 <key>CFBundleInfoDictionaryVersion</key> 255 <string>6.0</string> 256 <key>CFBundleName</key> 257 <string>{{.Name}}</string> 258 <key>CFBundlePackageType</key> 259 <string>APPL</string> 260 <key>CFBundleShortVersionString</key> 261 <string>1.0</string> 262 <key>CFBundleSignature</key> 263 <string>????</string> 264 <key>CFBundleVersion</key> 265 <string>1</string> 266 <key>LSRequiresIPhoneOS</key> 267 <true/> 268 <key>UILaunchStoryboardName</key> 269 <string>LaunchScreen</string> 270 <key>UIRequiredDeviceCapabilities</key> 271 <array> 272 <string>armv7</string> 273 </array> 274 <key>UISupportedInterfaceOrientations</key> 275 <array> 276 <string>UIInterfaceOrientationPortrait</string> 277 <string>UIInterfaceOrientationLandscapeLeft</string> 278 <string>UIInterfaceOrientationLandscapeRight</string> 279 </array> 280 <key>UISupportedInterfaceOrientations~ipad</key> 281 <array> 282 <string>UIInterfaceOrientationPortrait</string> 283 <string>UIInterfaceOrientationPortraitUpsideDown</string> 284 <string>UIInterfaceOrientationLandscapeLeft</string> 285 <string>UIInterfaceOrientationLandscapeRight</string> 286 </array> 287 </dict> 288 </plist> 289 `)) 290 291 type projPbxprojTmplData struct { 292 TeamID string 293 } 294 295 var projPbxprojTmpl = template.Must(template.New("projPbxproj").Parse(`// !$*UTF8*$! 296 { 297 archiveVersion = 1; 298 classes = { 299 }; 300 objectVersion = 46; 301 objects = { 302 303 /* Begin PBXBuildFile section */ 304 254BB84F1B1FD08900C56DE9 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 254BB84E1B1FD08900C56DE9 /* Images.xcassets */; }; 305 254BB8681B1FD16500C56DE9 /* main in Resources */ = {isa = PBXBuildFile; fileRef = 254BB8671B1FD16500C56DE9 /* main */; }; 306 25FB30331B30FDEE0005924C /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 25FB30321B30FDEE0005924C /* assets */; }; 307 /* End PBXBuildFile section */ 308 309 /* Begin PBXFileReference section */ 310 254BB83E1B1FD08900C56DE9 /* main.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = main.app; sourceTree = BUILT_PRODUCTS_DIR; }; 311 254BB8421B1FD08900C56DE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 312 254BB84E1B1FD08900C56DE9 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; }; 313 254BB8671B1FD16500C56DE9 /* main */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = main; sourceTree = "<group>"; }; 314 25FB30321B30FDEE0005924C /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = main/assets; sourceTree = "<group>"; }; 315 /* End PBXFileReference section */ 316 317 /* Begin PBXGroup section */ 318 254BB8351B1FD08900C56DE9 = { 319 isa = PBXGroup; 320 children = ( 321 25FB30321B30FDEE0005924C /* assets */, 322 254BB8401B1FD08900C56DE9 /* main */, 323 254BB83F1B1FD08900C56DE9 /* Products */, 324 ); 325 sourceTree = "<group>"; 326 usesTabs = 0; 327 }; 328 254BB83F1B1FD08900C56DE9 /* Products */ = { 329 isa = PBXGroup; 330 children = ( 331 254BB83E1B1FD08900C56DE9 /* main.app */, 332 ); 333 name = Products; 334 sourceTree = "<group>"; 335 }; 336 254BB8401B1FD08900C56DE9 /* main */ = { 337 isa = PBXGroup; 338 children = ( 339 254BB8671B1FD16500C56DE9 /* main */, 340 254BB84E1B1FD08900C56DE9 /* Images.xcassets */, 341 254BB8411B1FD08900C56DE9 /* Supporting Files */, 342 ); 343 path = main; 344 sourceTree = "<group>"; 345 }; 346 254BB8411B1FD08900C56DE9 /* Supporting Files */ = { 347 isa = PBXGroup; 348 children = ( 349 254BB8421B1FD08900C56DE9 /* Info.plist */, 350 ); 351 name = "Supporting Files"; 352 sourceTree = "<group>"; 353 }; 354 /* End PBXGroup section */ 355 356 /* Begin PBXNativeTarget section */ 357 254BB83D1B1FD08900C56DE9 /* main */ = { 358 isa = PBXNativeTarget; 359 buildConfigurationList = 254BB8611B1FD08900C56DE9 /* Build configuration list for PBXNativeTarget "main" */; 360 buildPhases = ( 361 254BB83C1B1FD08900C56DE9 /* Resources */, 362 ); 363 buildRules = ( 364 ); 365 dependencies = ( 366 ); 367 name = main; 368 productName = main; 369 productReference = 254BB83E1B1FD08900C56DE9 /* main.app */; 370 productType = "com.apple.product-type.application"; 371 }; 372 /* End PBXNativeTarget section */ 373 374 /* Begin PBXProject section */ 375 254BB8361B1FD08900C56DE9 /* Project object */ = { 376 isa = PBXProject; 377 attributes = { 378 LastUpgradeCheck = 0630; 379 ORGANIZATIONNAME = Developer; 380 TargetAttributes = { 381 254BB83D1B1FD08900C56DE9 = { 382 CreatedOnToolsVersion = 6.3.1; 383 DevelopmentTeam = {{.TeamID}}; 384 }; 385 }; 386 }; 387 buildConfigurationList = 254BB8391B1FD08900C56DE9 /* Build configuration list for PBXProject "main" */; 388 compatibilityVersion = "Xcode 3.2"; 389 developmentRegion = English; 390 hasScannedForEncodings = 0; 391 knownRegions = ( 392 en, 393 Base, 394 ); 395 mainGroup = 254BB8351B1FD08900C56DE9; 396 productRefGroup = 254BB83F1B1FD08900C56DE9 /* Products */; 397 projectDirPath = ""; 398 projectRoot = ""; 399 targets = ( 400 254BB83D1B1FD08900C56DE9 /* main */, 401 ); 402 }; 403 /* End PBXProject section */ 404 405 /* Begin PBXResourcesBuildPhase section */ 406 254BB83C1B1FD08900C56DE9 /* Resources */ = { 407 isa = PBXResourcesBuildPhase; 408 buildActionMask = 2147483647; 409 files = ( 410 25FB30331B30FDEE0005924C /* assets in Resources */, 411 254BB8681B1FD16500C56DE9 /* main in Resources */, 412 254BB84F1B1FD08900C56DE9 /* Images.xcassets in Resources */, 413 ); 414 runOnlyForDeploymentPostprocessing = 0; 415 }; 416 /* End PBXResourcesBuildPhase section */ 417 418 /* Begin XCBuildConfiguration section */ 419 254BB8601B1FD08900C56DE9 /* Release */ = { 420 isa = XCBuildConfiguration; 421 buildSettings = { 422 ALWAYS_SEARCH_USER_PATHS = NO; 423 CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 424 CLANG_CXX_LIBRARY = "libc++"; 425 CLANG_ENABLE_MODULES = YES; 426 CLANG_ENABLE_OBJC_ARC = YES; 427 CLANG_WARN_BOOL_CONVERSION = YES; 428 CLANG_WARN_CONSTANT_CONVERSION = YES; 429 CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 430 CLANG_WARN_EMPTY_BODY = YES; 431 CLANG_WARN_ENUM_CONVERSION = YES; 432 CLANG_WARN_INT_CONVERSION = YES; 433 CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 434 CLANG_WARN_UNREACHABLE_CODE = YES; 435 CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 436 "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; 437 COPY_PHASE_STRIP = NO; 438 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 439 ENABLE_NS_ASSERTIONS = NO; 440 ENABLE_STRICT_OBJC_MSGSEND = YES; 441 GCC_C_LANGUAGE_STANDARD = gnu99; 442 GCC_NO_COMMON_BLOCKS = YES; 443 GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 444 GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 445 GCC_WARN_UNDECLARED_SELECTOR = YES; 446 GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 447 GCC_WARN_UNUSED_FUNCTION = YES; 448 GCC_WARN_UNUSED_VARIABLE = YES; 449 MTL_ENABLE_DEBUG_INFO = NO; 450 SDKROOT = iphoneos; 451 TARGETED_DEVICE_FAMILY = "1,2"; 452 VALIDATE_PRODUCT = YES; 453 ENABLE_BITCODE = YES; 454 }; 455 name = Release; 456 }; 457 254BB8631B1FD08900C56DE9 /* Release */ = { 458 isa = XCBuildConfiguration; 459 buildSettings = { 460 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 461 INFOPLIST_FILE = main/Info.plist; 462 LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 463 PRODUCT_NAME = "$(TARGET_NAME)"; 464 }; 465 name = Release; 466 }; 467 /* End XCBuildConfiguration section */ 468 469 /* Begin XCConfigurationList section */ 470 254BB8391B1FD08900C56DE9 /* Build configuration list for PBXProject "main" */ = { 471 isa = XCConfigurationList; 472 buildConfigurations = ( 473 254BB8601B1FD08900C56DE9 /* Release */, 474 ); 475 defaultConfigurationIsVisible = 0; 476 defaultConfigurationName = Release; 477 }; 478 254BB8611B1FD08900C56DE9 /* Build configuration list for PBXNativeTarget "main" */ = { 479 isa = XCConfigurationList; 480 buildConfigurations = ( 481 254BB8631B1FD08900C56DE9 /* Release */, 482 ); 483 defaultConfigurationIsVisible = 0; 484 defaultConfigurationName = Release; 485 }; 486 /* End XCConfigurationList section */ 487 }; 488 rootObject = 254BB8361B1FD08900C56DE9 /* Project object */; 489 } 490 `)) 491 492 const contentsJSON = `{ 493 "images" : [ 494 { 495 "idiom" : "iphone", 496 "size" : "29x29", 497 "scale" : "2x" 498 }, 499 { 500 "idiom" : "iphone", 501 "size" : "29x29", 502 "scale" : "3x" 503 }, 504 { 505 "idiom" : "iphone", 506 "size" : "40x40", 507 "scale" : "2x" 508 }, 509 { 510 "idiom" : "iphone", 511 "size" : "40x40", 512 "scale" : "3x" 513 }, 514 { 515 "idiom" : "iphone", 516 "size" : "60x60", 517 "scale" : "2x" 518 }, 519 { 520 "idiom" : "iphone", 521 "size" : "60x60", 522 "scale" : "3x" 523 }, 524 { 525 "idiom" : "ipad", 526 "size" : "29x29", 527 "scale" : "1x" 528 }, 529 { 530 "idiom" : "ipad", 531 "size" : "29x29", 532 "scale" : "2x" 533 }, 534 { 535 "idiom" : "ipad", 536 "size" : "40x40", 537 "scale" : "1x" 538 }, 539 { 540 "idiom" : "ipad", 541 "size" : "40x40", 542 "scale" : "2x" 543 }, 544 { 545 "idiom" : "ipad", 546 "size" : "76x76", 547 "scale" : "1x" 548 }, 549 { 550 "idiom" : "ipad", 551 "size" : "76x76", 552 "scale" : "2x" 553 } 554 ], 555 "info" : { 556 "version" : 1, 557 "author" : "xcode" 558 } 559 } 560 ` 561 562 // rfc1034Label sanitizes the name to be usable in a uniform type identifier. 563 // The sanitization is similar to xcode's rfc1034identifier macro that 564 // replaces illegal characters (not conforming the rfc1034 label rule) with '-'. 565 func rfc1034Label(name string) string { 566 // * Uniform type identifier: 567 // 568 // According to 569 // https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/understanding_utis/understand_utis_conc/understand_utis_conc.html 570 // 571 // A uniform type identifier is a Unicode string that usually contains characters 572 // in the ASCII character set. However, only a subset of the ASCII characters are 573 // permitted. You may use the Roman alphabet in upper and lower case (A–Z, a–z), 574 // the digits 0 through 9, the dot (“.”), and the hyphen (“-”). This restriction 575 // is based on DNS name restrictions, set forth in RFC 1035. 576 // 577 // Uniform type identifiers may also contain any of the Unicode characters greater 578 // than U+007F. 579 // 580 // Note: the actual implementation of xcode does not allow some unicode characters 581 // greater than U+007f. In this implementation, we just replace everything non 582 // alphanumeric with "-" like the rfc1034identifier macro. 583 // 584 // * RFC1034 Label 585 // 586 // <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ] 587 // <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str> 588 // <let-dig-hyp> ::= <let-dig> | "-" 589 // <let-dig> ::= <letter> | <digit> 590 const surrSelf = 0x10000 591 begin := false 592 593 var res []rune 594 for i, r := range name { 595 if r == '.' && !begin { 596 continue 597 } 598 begin = true 599 600 switch { 601 case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z': 602 res = append(res, r) 603 case '0' <= r && r <= '9': 604 if i == 0 { 605 res = append(res, '-') 606 } else { 607 res = append(res, r) 608 } 609 default: 610 if r < surrSelf { 611 res = append(res, '-') 612 } else { 613 res = append(res, '-', '-') 614 } 615 } 616 } 617 return string(res) 618 }