jiri-swift: Copy related static libs into target directory

In the case of SyncbaseCore, this means we automatically copy each
architecture's copy of Snappy and LevelDB automatically.

MultiPart: 1/2
Change-Id: I301c473271844ad20319059459db743ac2b52178
diff --git a/jiri-swift/build_cgo.go b/jiri-swift/build_cgo.go
index 9cd83e6..4c14b9b 100644
--- a/jiri-swift/build_cgo.go
+++ b/jiri-swift/build_cgo.go
@@ -14,6 +14,7 @@
 	"text/template"
 
 	"v.io/jiri"
+	"v.io/jiri/profiles"
 )
 
 const singleHeaderTmpl = `/* Created by jiri-swift - DO NOT EDIT. */
@@ -46,6 +47,7 @@
 `
 
 func runBuildCgo(jirix *jiri.X) error {
+	// Copy over dependent libraries.
 	if flagBuildDirCgo == "" {
 		flagBuildDirCgo = sh.MakeTempDir()
 	}
@@ -55,6 +57,9 @@
 		cleanOldCompiledFiles(jirix, targetArch)
 		compileCgo(jirix, targetArch)
 		installCgoBinary(jirix, targetArch)
+		if err := copyLinkedLibraries(jirix, targetArch); err != nil {
+			return err
+		}
 	}
 
 	copyCommonHeaders(jirix)
@@ -127,6 +132,114 @@
 	verifyCgoBinaryArchOrPanic(destLibPath, targetArch)
 }
 
+// copyLinkedLibraries will look at the project-specific profile requirements (like v23:syncbase) to find
+// any static libraries in the profile that Go might have linked to via CGO_LDFLAGS, and then copy these
+// static archives to the target directory. This allows Xcode to be able to directly link to a local copy
+// of these files as CGO doesn't statically link the libraries. While it might seem like a bug, it's
+// actually a feature: it allows us to distribute a version of a framework without potentially-conflicting
+// dependencies like LevelDB should the end-user wish to provide their own copy (or already has another
+// library that has statically-linked it).
+func copyLinkedLibraries(jirix *jiri.X, targetArch string) error {
+	if len(selectedProject.jiriProfiles) == 0 {
+		// No files to copy over
+		verbose(jirix, "No jiri profiles associated with project; not copying any linked static libs\n")
+		return nil
+	}
+	// Load jiri profiles database
+	db := profiles.NewDB()
+	if err := db.Read(jirix, jirix.ProfilesDBDir()); err != nil {
+		return fmt.Errorf("failed to read profiles db at path %v: %v", jirix.ProfilesDBDir(), err)
+	}
+	// Copy any profile's static libraries over
+	for _, pn := range selectedProject.jiriProfiles {
+		// Get profile
+		splitPn := strings.Split(pn, ":")
+		if len(splitPn) != 2 {
+			return fmt.Errorf("did not understand jiri profile %v -- expected format is <installer>:<name>", pn)
+		}
+		p := db.LookupProfile(splitPn[0], splitPn[1])
+		if p == nil {
+			return fmt.Errorf("unable to find profile %v", pn)
+		}
+		// Find target for this architecture & os
+		var target *profiles.Target
+		for _, t := range p.Targets() {
+			if t.Arch() != targetArch {
+				continue
+			}
+			if t.OS() != "ios" {
+				continue
+			}
+			target = t
+		}
+		if target == nil {
+			return fmt.Errorf("couldn't find target arch %v in targets %v for profile %v", targetArch, p.Targets(), pn)
+		}
+		copyLinkedLibrariesForTarget(jirix, target)
+	}
+	return nil
+}
+
+// copyLinkedLibrariesForTarget copies any static libraries included on the
+// CGO_LDFLAGS to our target directory to make it easy to link to (or
+// distribute as its own library) in Xcode.
+func copyLinkedLibrariesForTarget(jirix *jiri.X, target *profiles.Target) {
+	libs := findStaticLibsInDirs(findLibDirsInTargetEnv(jirix, target))
+	for _, l := range libs {
+		// Convert path to dst/libname_arch.a
+		bn := filepath.Base(l)
+		bn = strings.Trim(bn, filepath.Ext(bn))
+		dst := filepath.Join(getSwiftTargetDir(jirix), fmt.Sprintf("%v_%v.a", bn, target.Arch()))
+		verbose(jirix, "Copying %v to %v\n", l, dst)
+		sh.Cmd("cp", l, dst).Run()
+	}
+}
+
+// findLibDirsInTargetEnv parses a profile target's CGO_LDFLAGS for any included
+// library dirs, intersects them with the target's installation dir (to make sure
+// we only get our own locally-built and not system-wide libraries), and returns
+// these absolute paths. For example it will return the directories associated
+// with target architecture's compiled static archives of LevelDB and Snappy
+// when searching the v23:syncbase profile target.
+func findLibDirsInTargetEnv(jirix *jiri.X, t *profiles.Target) []string {
+	var dirs []string
+	for _, v := range t.Env.Vars {
+		if !strings.HasPrefix(v, "CGO_LDFLAGS") {
+			continue
+		}
+		dirs = strings.Split(v, "-L")
+		break
+	}
+	if len(dirs) == 0 {
+		return dirs
+	}
+	var paths []string
+	for _, d := range dirs {
+		i := strings.Index(d, t.InstallationDir)
+		if i == -1 {
+			continue
+		}
+		d = filepath.Join(jirix.Root, strings.TrimSpace(d[i:]))
+		paths = append(paths, d)
+	}
+	return paths
+}
+
+// findStaticLibsInDirs walks a slice of directories searching for static archives
+// (files that end with .a), and returns those as a string slice.
+func findStaticLibsInDirs(dirs []string) []string {
+	var libs []string
+	for _, d := range dirs {
+		filepath.Walk(d, func(path string, f os.FileInfo, err error) error {
+			if strings.HasSuffix(path, ".a") {
+				libs = append(libs, path)
+			}
+			return nil
+		})
+	}
+	return libs
+}
+
 func copyCommonHeaders(jirix *jiri.X) {
 	verbose(jirix, "Copying common shared headers between Swift and Go\n")
 	// Take types.h and make it into go_types.h
diff --git a/jiri-swift/swift.go b/jiri-swift/swift.go
index 4f79b59..2a6c916 100644
--- a/jiri-swift/swift.go
+++ b/jiri-swift/swift.go
@@ -56,6 +56,7 @@
 			exportedHeadersPackageRoot: "v.io/x",
 			frameworkName:              "SyncbaseCore.framework",
 			frameworkBinaryName:        "SyncbaseCore",
+			jiriProfiles:               []string{"v23:syncbase"},
 			libraryBinaryName:          "sbcore",
 			mainPackage:                "v.io/x/ref/services/syncbase/bridge/cgo",
 			testCheckExportedSymbols:   []string{"v23_syncbase_Init", "v23_syncbase_DbLeaveSyncgroup", "v23_syncbase_RowDelete"},
@@ -72,6 +73,7 @@
 	exportedHeadersPackageRoot string
 	frameworkName              string
 	frameworkBinaryName        string
+	jiriProfiles               []string
 	libraryBinaryName          string
 	mainPackage                string
 	testCheckExportedSymbols   []string