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