From 322502b441c6137b2945131f8e3dda1bb3f8d6b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= <peterke@gmail.com>
Date: Wed, 9 Nov 2016 11:13:37 +0200
Subject: [PATCH] build: iOS XCode framework build and upload

---
 .travis.yml       |   6 ++-
 build/ci.go       | 135 ++++++++++++++++++++++++++++++++++++++--------
 build/mvn.pom     |   2 +-
 build/pod.podspec |  22 ++++++++
 4 files changed, 140 insertions(+), 25 deletions(-)
 create mode 100644 build/pod.podspec

diff --git a/.travis.yml b/.travis.yml
index 3841a78c0..c9b8b5025 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -63,7 +63,11 @@ matrix:
         - go run build/ci.go install
         - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds
 
-        # Build the Android archives and upload them to Maven Central
+        # Build the iOS framework and upload it to CocoaPods and Azure
+        - gem install cocoapods --pre
+        - go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds
+
+        # Build the Android archive and upload it to Maven Central and Azure
         - brew update
         - brew install android-sdk maven
         - export ANDROID_HOME=/usr/local/opt/android-sdk
diff --git a/build/ci.go b/build/ci.go
index cc99e3521..b4dbd7dbd 100644
--- a/build/ci.go
+++ b/build/ci.go
@@ -29,7 +29,8 @@ Available commands are:
    importkeys                                                                                -- imports signing keys from env
    debsrc     [ -signer key-id ] [ -upload dest ]                                            -- creates a debian source package
    nsis                                                                                      -- creates a Windows NSIS installer
-   aar        [ -sign key-id ] [ -upload dest ]                                              -- creates an android archive
+   aar        [ -sign key-id ] [-deploy repo] [ -upload dest ]                               -- creates an Android archive
+   xcode      [ -sign key-id ] [-deploy repo] [ -upload dest ]                               -- creates an iOS XCode framework
    xgo        [ options ]                                                                    -- cross builds according to options
 
 For all commands, -n prevents execution of external programs (dry run mode).
@@ -130,6 +131,8 @@ func main() {
 		doWindowsInstaller(os.Args[2:])
 	case "aar":
 		doAndroidArchive(os.Args[2:])
+	case "xcode":
+		doXCodeFramework(os.Args[2:])
 	case "xgo":
 		doXgo(os.Args[2:])
 	default:
@@ -339,14 +342,21 @@ func archiveBasename(arch string, env build.Environment) string {
 	if arch == "android" {
 		platform = "android-all"
 	}
-	archive := platform + "-" + build.VERSION()
+	if arch == "ios" {
+		platform = "ios-all"
+	}
+	return platform + "-" + archiveVersion(env)
+}
+
+func archiveVersion(env build.Environment) string {
+	version := build.VERSION()
 	if isUnstableBuild(env) {
-		archive += "-unstable"
+		version += "-unstable"
 	}
 	if env.Commit != "" {
-		archive += "-" + env.Commit[:8]
+		version += "-" + env.Commit[:8]
 	}
-	return archive
+	return version
 }
 
 func archiveUpload(archive string, blobstore string, signer string) error {
@@ -638,14 +648,15 @@ func doWindowsInstaller(cmdline []string) {
 	if err := archiveUpload(installer, *upload, *signer); err != nil {
 		log.Fatal(err)
 	}
+}
 
 // Android archives
 
 func doAndroidArchive(cmdline []string) {
 	var (
 		signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`)
-		deploy = flag.String("deploy", "", `Where to upload the deploy the archive (usually "https://oss.sonatype.org")`)
-		upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
+		deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`)
+		upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`)
 	)
 	flag.CommandLine.Parse(cmdline)
 	env := build.Env()
@@ -656,13 +667,13 @@ func doAndroidArchive(cmdline []string) {
 	build.MustRun(gomobileTool("bind", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile"))
 
 	meta := newMavenMetadata(env)
-	build.Render("build/mvn.pom", meta.PackageString()+".pom", 0755, meta)
+	build.Render("build/mvn.pom", meta.Package+".pom", 0755, meta)
 
 	// Skip Maven deploy and Azure upload for PR builds
 	maybeSkipArchive(env)
 
 	// Sign and upload all the artifacts to Maven Central
-	os.Rename("geth.aar", meta.PackageString()+".aar")
+	os.Rename("geth.aar", meta.Package+".aar")
 	if *signer != "" && *deploy != "" {
 		// Import the signing key into the local GPG instance
 		if b64key := os.Getenv(*signer); b64key != "" {
@@ -676,16 +687,16 @@ func doAndroidArchive(cmdline []string) {
 		}
 		// Upload the artifacts to Sonatype and/or Maven Central
 		repo := *deploy + "/service/local/staging/deploy/maven2"
-		if meta.Unstable {
+		if meta.Develop {
 			repo = *deploy + "/content/repositories/snapshots"
 		}
 		build.MustRunCommand("mvn", "gpg:sign-and-deploy-file",
 			"-settings=build/mvn.settings", "-Durl="+repo, "-DrepositoryId=ossrh",
-			"-DpomFile="+meta.PackageString()+".pom", "-Dfile="+meta.PackageString()+".aar")
+			"-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar")
 	}
 	// Sign and upload the archive to Azure
 	archive := "geth-" + archiveBasename("android", env) + ".aar"
-	os.Rename(meta.PackageString()+".aar", archive)
+	os.Rename(meta.Package+".aar", archive)
 
 	if err := archiveUpload(archive, *upload, *signer); err != nil {
 		log.Fatal(err)
@@ -708,9 +719,9 @@ func gomobileTool(subcmd string, args ...string) *exec.Cmd {
 }
 
 type mavenMetadata struct {
-	Env          build.Environment
 	Version      string
-	Unstable     bool
+	Package      string
+	Develop      bool
 	Contributors []mavenContributor
 }
 
@@ -740,23 +751,101 @@ func newMavenMetadata(env build.Environment) mavenMetadata {
 			}
 		}
 	}
+	// Render the version and package strings
+	version := build.VERSION()
+	if isUnstableBuild(env) {
+		version += "-SNAPSHOT"
+	}
 	return mavenMetadata{
-		Env:          env,
-		Version:      build.VERSION(),
-		Unstable:     isUnstableBuild(env),
+		Version:      version,
+		Package:      "geth-" + version,
+		Develop:      isUnstableBuild(env),
 		Contributors: contribs,
 	}
 }
 
-func (meta mavenMetadata) VersionString() string {
-	if meta.Unstable {
-		return meta.Version + "-SNAPSHOT"
+// XCode frameworks
+
+func doXCodeFramework(cmdline []string) {
+	var (
+		signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`)
+		deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`)
+		upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
+	)
+	flag.CommandLine.Parse(cmdline)
+	env := build.Env()
+
+	// Build the iOS XCode framework
+	build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile"))
+	build.MustRun(gomobileTool("init"))
+
+	archive := "geth-" + archiveBasename("ios", env)
+	if err := os.Mkdir(archive, os.ModePerm); err != nil {
+		log.Fatal(err)
 	}
-	return meta.Version
+	bind := gomobileTool("bind", "--target", "ios", "--tags", "ios", "--prefix", "GE", "-v", "github.com/ethereum/go-ethereum/mobile")
+	bind.Dir, _ = filepath.Abs(archive)
+	build.MustRun(bind)
+	build.MustRunCommand("tar", "-zcvf", archive+".tar.gz", archive)
+
+	// Skip CocoaPods deploy and Azure upload for PR builds
+	maybeSkipArchive(env)
+
+	// Sign and upload the framework to Azure
+	if err := archiveUpload(archive+".tar.gz", *upload, *signer); err != nil {
+		log.Fatal(err)
+	}
+	// Prepare and upload a PodSpec to CocoaPods
+	if *deploy != "" {
+		meta := newPodMetadata(env)
+		build.Render("build/pod.podspec", meta.Name+".podspec", 0755, meta)
+		build.MustRunCommand("pod", *deploy, "push", meta.Name+".podspec")
+	}
+}
+
+type podMetadata struct {
+	Name         string
+	Version      string
+	Commit       string
+	Contributors []podContributor
 }
 
-func (meta mavenMetadata) PackageString() string {
-	return "geth-" + meta.VersionString()
+type podContributor struct {
+	Name  string
+	Email string
+}
+
+func newPodMetadata(env build.Environment) podMetadata {
+	// Collect the list of authors from the repo root
+	contribs := []podContributor{}
+	if authors, err := os.Open("AUTHORS"); err == nil {
+		defer authors.Close()
+
+		scanner := bufio.NewScanner(authors)
+		for scanner.Scan() {
+			// Skip any whitespace from the authors list
+			line := strings.TrimSpace(scanner.Text())
+			if line == "" || line[0] == '#' {
+				continue
+			}
+			// Split the author and insert as a contributor
+			re := regexp.MustCompile("([^<]+) <(.+>)")
+			parts := re.FindStringSubmatch(line)
+			if len(parts) == 3 {
+				contribs = append(contribs, podContributor{Name: parts[1], Email: parts[2]})
+			}
+		}
+	}
+	name := "Geth"
+	if isUnstableBuild(env) {
+		name += "Develop"
+	}
+	return podMetadata{
+		Name:         name,
+		Version:      archiveVersion(env),
+		Commit:       env.Commit,
+		Contributors: contribs,
+	}
 }
 
 // Cross compilation
diff --git a/build/mvn.pom b/build/mvn.pom
index 009226876..7670246ba 100644
--- a/build/mvn.pom
+++ b/build/mvn.pom
@@ -6,7 +6,7 @@
 
   <groupId>org.ethereum</groupId>
   <artifactId>geth</artifactId>
-  <version>{{.VersionString}}</version>
+  <version>{{.Version}}</version>
   <packaging>aar</packaging>
 
   <name>Android Ethereum Client</name>
diff --git a/build/pod.podspec b/build/pod.podspec
new file mode 100644
index 000000000..2718522db
--- /dev/null
+++ b/build/pod.podspec
@@ -0,0 +1,22 @@
+Pod::Spec.new do |spec|
+  spec.name         = '{{.Name}}'
+  spec.version      = '{{.Version}}'
+  spec.license      = { :type => 'GNU Lesser General Public License, Version 3.0' }
+  spec.homepage     = 'https://github.com/ethereum/go-ethereum'
+  spec.authors      = { {{range .Contributors}}
+		'{{.Name}}' => '{{.Email}}',{{end}}
+	}
+  spec.summary      = 'iOS Ethereum Client'
+  spec.source       = { :git => 'https://github.com/ethereum/go-ethereum.git', :commit => '{{.Commit}}' }
+
+	spec.platform = :ios
+  spec.ios.deployment_target  = '9.0'
+	spec.ios.vendored_frameworks = 'Frameworks/Geth.framework'
+
+	spec.prepare_command = <<-CMD
+    curl https://gethstore.blob.core.windows.net/builds/geth-ios-all-{{.Version}}.tar.gz | tar -xvz
+    mkdir Frameworks
+    mv geth-ios-all-{{.Version}}/Geth.framework Frameworks
+    rm geth-ios-all-{{.Version}}
+  CMD
+end
-- 
GitLab