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