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