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