From 428b72be66c6f6802a7019b9ecf3d5355ca688a9 Mon Sep 17 00:00:00 2001 From: Burcu Dogan Date: Sun, 7 Jun 2015 22:20:23 -0700 Subject: [PATCH] mobile/cmd/gomobile: create boilerplate Xcode project This CL adds the generation of a boilerplate Xcode project to make it possible to shell out to xcodebuild to make a release build. In order to support multiple architectures, we are using lipo tool to create fat binaries that target both darwin/arm and darwin/arm64. This build strategy will require a darwin host with Xcode CLI tools installed. Updates #11043. Change-Id: I741b05f5e34bf2a90103b1efdfa2db97a743e2a6 Reviewed-on: https://go-review.googlesource.com/10813 Reviewed-by: David Crawshaw --- cmd/gomobile/build.go | 51 ----- cmd/gomobile/ios.go | 482 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 482 insertions(+), 51 deletions(-) create mode 100644 cmd/gomobile/ios.go diff --git a/cmd/gomobile/build.go b/cmd/gomobile/build.go index 4ea5fc4..c666caf 100644 --- a/cmd/gomobile/build.go +++ b/cmd/gomobile/build.go @@ -17,7 +17,6 @@ import ( "os/exec" "path" "path/filepath" - "runtime" "strconv" "strings" ) @@ -327,56 +326,6 @@ func addBuildFlagsNVX(cmd *command) { cmd.flag.BoolVar(&buildX, "x", false, "") } -// TODO(jbd): Build darwin/arm cross compiler during gomobile init. - -func goIOSBuild(src string) error { - // iOS builds are achievable only if the host machine is darwin. - if runtime.GOOS != "darwin" { - return nil - } - - goroot := goEnv("GOROOT") - gopath := goEnv("GOPATH") - gocmd := exec.Command( - `go`, - `build`, - `-tags=`+strconv.Quote(strings.Join(ctx.BuildTags, ","))) - if buildV { - gocmd.Args = append(gocmd.Args, "-v") - } - if buildI { - gocmd.Args = append(gocmd.Args, "-i") - } - if buildX { - gocmd.Args = append(gocmd.Args, "-x") - } - gocmd.Args = append(gocmd.Args, src) - // TODO(jbd): Return a user-friendly error if xcode command line - // tools are not available. - gocmd.Stdout = os.Stdout - gocmd.Stderr = os.Stderr - gocmd.Env = []string{ - `GOOS=darwin`, - `GOARCH=arm`, // TODO(jbd): Build for arm64 - `GOARM=7`, - `CGO_ENABLED=1`, - `CC=` + filepath.Join(goroot, "misc/ios/clangwrap.sh"), // TODO(jbd): reimplement clangwrap here. - `CXX=` + filepath.Join(goroot, "misc/ios/clangwrap.sh"), - `GOROOT=` + goroot, - `GOPATH=` + gopath, - } - if buildX { - printcmd("%s", strings.Join(gocmd.Env, " ")+" "+strings.Join(gocmd.Args, " ")) - } - if !buildN { - gocmd.Env = environ(gocmd.Env) - if err := gocmd.Run(); err != nil { - return err - } - } - return nil -} - // goAndroidBuild builds a package. // If libPath is specified then it builds as a shared library. func goAndroidBuild(src, libPath string) error { diff --git a/cmd/gomobile/ios.go b/cmd/gomobile/ios.go new file mode 100644 index 0000000..aeb041b --- /dev/null +++ b/cmd/gomobile/ios.go @@ -0,0 +1,482 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" +) + +// TODO(jbd): iOS builds are achievable only if the host machine is darwin. +// TODO(jbd): Build darwin/arm cross compiler during gomobile init. + +func goIOSBuild(src string) error { + dir := "$XCODEPROJ" + if !buildN { + tmp, err := ioutil.TempDir("", "xcodeproject") + if err != nil { + return err + } + dir = tmp + } + if buildX { + printcmd("mkdir %s", dir) + } + + layout := map[string][]byte{ + dir + "/main.xcodeproj/project.pbxproj": []byte(projPbxproj), + dir + "/main/Info.plist": []byte(infoPlist), + dir + "/main/Images.xcassets/AppIcon.appiconset/Contents.json": []byte(contentsJSON), + } + + for dst, v := range layout { + if buildX { + printcmd("echo \"%s\" > %s", v, dst) + } + if !buildN { + if err := os.MkdirAll(filepath.Dir(dst), 0775|os.ModeDir); err != nil { + return err + } + if err := ioutil.WriteFile(dst, v, 0644); err != nil { + return err + } + } + } + + armPath := filepath.Join(dir, "arm") + if err := goBuild(src, armPath, []string{ + `GOOS=darwin`, + `GOARCH=arm`, + `GOARM=7`, + }); err != nil { + return err + } + + arm64Path := filepath.Join(dir, "arm64") + if err := goBuild(src, arm64Path, []string{ + `GOOS=darwin`, + `GOARCH=arm64`, + }); err != nil { + return err + } + + // Apple requires builds to target both darwin/arm and darwin/arm64. + // We are using lipo tool to build multiarchitecture binaries. + // TODO(jbd): Investigate the new announcements about iO9's fat binary + // size limitations are breaking this feature. + if buildX { + printcmd("xcrun lipo -create %s %s -o %s", armPath, arm64Path, filepath.Join(dir, "main/main")) + } + if !buildN { + err := exec.Command( + "xcrun", "lipo", + "-create", armPath, arm64Path, + "-o", filepath.Join(dir, "main/main"), + ).Run() + if err != nil { + return err + } + } + + // TODO(jbd): Copy assets. + // TODO(jbd): Run xcodebuild and move the build to the desired output directory. + // TODO(jbd): Remove the temp dir. + return nil +} + +func goBuild(src, o string, env []string) error { + goroot := goEnv("GOROOT") + gopath := goEnv("GOPATH") + cmd := exec.Command( + `go`, + `build`, + `-tags=`+strconv.Quote(strings.Join(ctx.BuildTags, ",")), + ) + if buildV { + cmd.Args = append(cmd.Args, "-v") + } + if buildI { + cmd.Args = append(cmd.Args, "-i") + } + if buildX { + cmd.Args = append(cmd.Args, "-x") + } + cmd.Args = append(cmd.Args, "-o="+o) + cmd.Args = append(cmd.Args, src) + cmd.Env = append(env, []string{ + `CGO_ENABLED=1`, + `GOROOT=` + goroot, + `GOPATH=` + gopath, + }...) + if buildX { + printcmd("%s", strings.Join(cmd.Env, " ")+" "+strings.Join(cmd.Args, " ")) + } + if !buildN { + cmd.Env = environ(cmd.Env) + return cmd.Run() + } + return nil +} + +const infoPlist = ` + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + main + CFBundleIdentifier + org.golang.todo.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + +` + +const projPbxproj = `// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { +/* Begin PBXBuildFile section */ + 254BB84F1B1FD08900C56DE9 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 254BB84E1B1FD08900C56DE9 /* Images.xcassets */; }; + 254BB8681B1FD16500C56DE9 /* main in Resources */ = {isa = PBXBuildFile; fileRef = 254BB8671B1FD16500C56DE9 /* main */; }; +/* End PBXBuildFile section */ +/* Begin PBXFileReference section */ + 254BB83E1B1FD08900C56DE9 /* main.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = main.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 254BB8421B1FD08900C56DE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 254BB84E1B1FD08900C56DE9 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 254BB8671B1FD16500C56DE9 /* main */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = main; sourceTree = ""; }; +/* End PBXFileReference section */ +/* Begin PBXGroup section */ + 254BB8351B1FD08900C56DE9 = { + isa = PBXGroup; + children = ( + 254BB8401B1FD08900C56DE9 /* main */, + 254BB83F1B1FD08900C56DE9 /* Products */, + ); + sourceTree = ""; + usesTabs = 0; + }; + 254BB83F1B1FD08900C56DE9 /* Products */ = { + isa = PBXGroup; + children = ( + 254BB83E1B1FD08900C56DE9 /* main.app */, + ); + name = Products; + sourceTree = ""; + }; + 254BB8401B1FD08900C56DE9 /* main */ = { + isa = PBXGroup; + children = ( + 254BB8671B1FD16500C56DE9 /* main */, + 254BB84E1B1FD08900C56DE9 /* Images.xcassets */, + 254BB8411B1FD08900C56DE9 /* Supporting Files */, + ); + path = main; + sourceTree = ""; + }; + 254BB8411B1FD08900C56DE9 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 254BB8421B1FD08900C56DE9 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ +/* Begin PBXNativeTarget section */ + 254BB83D1B1FD08900C56DE9 /* main */ = { + isa = PBXNativeTarget; + buildConfigurationList = 254BB8611B1FD08900C56DE9 /* Build configuration list for PBXNativeTarget "main" */; + buildPhases = ( + 254BB83C1B1FD08900C56DE9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = main; + productName = main; + productReference = 254BB83E1B1FD08900C56DE9 /* main.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ +/* Begin PBXProject section */ + 254BB8361B1FD08900C56DE9 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0630; + ORGANIZATIONNAME = Developer; + TargetAttributes = { + 254BB83D1B1FD08900C56DE9 = { + CreatedOnToolsVersion = 6.3.1; + }; + }; + }; + buildConfigurationList = 254BB8391B1FD08900C56DE9 /* Build configuration list for PBXProject "main" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 254BB8351B1FD08900C56DE9; + productRefGroup = 254BB83F1B1FD08900C56DE9 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 254BB83D1B1FD08900C56DE9 /* main */, + ); + }; +/* End PBXProject section */ +/* Begin PBXResourcesBuildPhase section */ + 254BB83C1B1FD08900C56DE9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 254BB8681B1FD16500C56DE9 /* main in Resources */, + 254BB84F1B1FD08900C56DE9 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ +/* Begin XCBuildConfiguration section */ + 254BB85F1B1FD08900C56DE9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 254BB8601B1FD08900C56DE9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 254BB8621B1FD08900C56DE9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = main/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 254BB8631B1FD08900C56DE9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = main/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ +/* Begin XCConfigurationList section */ + 254BB8391B1FD08900C56DE9 /* Build configuration list for PBXProject "main" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 254BB85F1B1FD08900C56DE9 /* Debug */, + 254BB8601B1FD08900C56DE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 254BB8611B1FD08900C56DE9 /* Build configuration list for PBXNativeTarget "main" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 254BB8621B1FD08900C56DE9 /* Debug */, + 254BB8631B1FD08900C56DE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 254BB8361B1FD08900C56DE9 /* Project object */; +} +` + +const contentsJSON = `{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} +`