Last active
July 5, 2016 11:38
-
-
Save jon-cotton/21bd64017efe063e3253d51c69eb7dd8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/swift | |
import Foundation | |
// MARK:- Functions | |
func runShellCommand(cmd: String, env: [String : String]? = nil) -> (output: [String], error: [String], exitCode: Int32) { | |
var output : [String] = [] | |
var error : [String] = [] | |
let task = NSTask() | |
task.launchPath = "/bin/bash" | |
task.arguments = ["-c", "\(cmd)"] | |
if let env = env { | |
task.environment = env | |
} | |
let outpipe = NSPipe() | |
task.standardOutput = outpipe | |
let errpipe = NSPipe() | |
task.standardError = errpipe | |
task.launch() | |
let outdata = outpipe.fileHandleForReading.readDataToEndOfFile() | |
if var string = String(data: outdata, encoding: NSUTF8StringEncoding) { | |
string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet()) | |
output = string.componentsSeparatedByString("\n") | |
} | |
let errdata = errpipe.fileHandleForReading.readDataToEndOfFile() | |
if var string = String(data: errdata, encoding: NSUTF8StringEncoding) { | |
string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet()) | |
error = string.componentsSeparatedByString("\n") | |
} | |
task.waitUntilExit() | |
let status = task.terminationStatus | |
return (output, error, status) | |
} | |
typealias CommandRunner = (cmd: String) -> () | |
func pathExists(path: String) -> Bool { | |
let fileManager = NSFileManager.defaultManager() | |
return fileManager.fileExistsAtPath(path) | |
} | |
struct BuildError: ErrorType { | |
let description: String | |
init(description: String = "Build Failed") { | |
self.description = description | |
} | |
} | |
enum Project: String { | |
case ios | |
case android | |
case service | |
func installDependencies(run: CommandRunner) { | |
switch self { | |
case .ios: | |
print("iOS dependencies") | |
case .android: | |
print("Android dependencies") | |
run(cmd: "brew tap caskroom/cask") | |
run(cmd: "brew install brew-cask") | |
run(cmd: "brew tap caskroom/versions") | |
run(cmd: "brew cask install java7") | |
// only update the sdks & tools if android-sdk isn't already installed | |
// if these change, you will need to rebuild without cache on circle | |
if !pathExists("/usr/local/Cellar/android-sdk") { | |
run(cmd: "brew install android-sdk") | |
run(cmd: "echo y | android --silent update sdk --no-ui --all --filter tools,platform-tools") | |
run(cmd: "echo y | android --silent update sdk --no-ui --all --filter build-tools-23.0.3,android-23") | |
run(cmd: "echo y | android --silent update sdk --no-ui --all --filter extra-android-m2repository,extra-google-m2repository") | |
} else { | |
print("Skipping Android SDK and tools updates") | |
} | |
case .service: | |
print("Ruby dependencies") | |
run(cmd: "cd ./service && bundle") | |
} | |
} | |
func build(run: CommandRunner) { | |
switch self { | |
case .ios: | |
print("iOS build") | |
run(cmd: "xcodebuild test -workspace ios/Sky/Sky.xcworkspace -scheme Sky -destination 'platform=iOS Simulator,name=iPhone 6,OS=9.3'") | |
case .android: | |
print("Android build") | |
run(cmd: "export ANDROID_HOME=/usr/local/Cellar/android-sdk && ./android/gradlew -p ./android test") | |
case .service: | |
print("Ruby build") | |
run(cmd: "cd ./service && bundle exec rspec") | |
} | |
} | |
} | |
func changedProjects(firstCommit firstCommit: String?, lastCommit: String?) -> Set<Project> { | |
guard let firstCommit = firstCommit | |
else { | |
print("No commits found to build") | |
return [] | |
} | |
var command = "git --no-pager show --pretty=\"format:\" --name-only \(firstCommit)" | |
if let lastCommit = lastCommit { | |
command = "git diff --name-only \(firstCommit)..\(lastCommit)" | |
} | |
let changes = runShellCommand(command) | |
guard changes.exitCode == 0 | |
else { | |
print(changes.error.reduce("", combine: { "\($0)\n\($1)" })) | |
return [] | |
} | |
let allChangedFolders = changes.output.map({ String($0).componentsSeparatedByString("/")[0] }) | |
let uniqueChangedFolders = Set(allChangedFolders) | |
var projects = Set<Project>() | |
for folder in uniqueChangedFolders { | |
guard let project = Project(rawValue: folder) | |
else { continue } | |
projects.insert(project) | |
} | |
return projects | |
} | |
// MARK:- Run | |
let env = NSProcessInfo.processInfo().environment as [String : String] | |
let args = Process.arguments | |
var shouldInstallDependencies = false | |
var shouldBuild = false | |
var shouldPrintVerboseOutput = false | |
// not configurable | |
let shouldStopOnErrors = true | |
if args.contains("setup") { | |
shouldInstallDependencies = true | |
} | |
if args.contains("build") { | |
shouldBuild = true | |
} | |
if args.contains("--verbose") { | |
shouldPrintVerboseOutput = true | |
} | |
if !shouldInstallDependencies && !shouldBuild { | |
print("Usage: circle-build-helper.swift setup | build [--verbose]") | |
exit(1) | |
} | |
let runner: CommandRunner = { (cmd: String) -> () in | |
print(">>> Running '\(cmd)'") | |
let cmdReturn = runShellCommand(cmd) | |
if cmdReturn.error.count > 1 { | |
print(">>> Errors from '\(cmd)'") | |
print(cmdReturn.error.reduce("", combine: { "\($0)\n\($1)" })) | |
} | |
if shouldPrintVerboseOutput { | |
print(">>> Output from \(cmd)") | |
print(cmdReturn.output.reduce("", combine: { "\($0)\n\($1)" })) | |
} | |
if shouldStopOnErrors { | |
guard cmdReturn.exitCode == 0 | |
else { | |
print("!!! '\(cmd)' returned non-zero exit code") | |
exit(1) | |
} | |
} | |
} | |
// work out which projects need working on | |
var firstCommit: String? | |
var lastCommit: String? | |
let rangeSeparator = "..." | |
if let | |
compareURLString = env["CIRCLE_COMPARE_URL"], | |
range = compareURLString.componentsSeparatedByString("/").last | |
{ | |
if range.rangeOfString(rangeSeparator) != nil { | |
firstCommit = range.componentsSeparatedByString(rangeSeparator)[0] | |
lastCommit = range.componentsSeparatedByString(rangeSeparator)[1] | |
} | |
} else { | |
if let commitSHA = env["CIRCLE_SHA1"] { | |
firstCommit = commitSHA | |
} else { | |
print("Neither of CIRCLE_SHA1 or CIRCLE_COMPARE_URL env vars defined") | |
} | |
} | |
var projectsToBuild = changedProjects(firstCommit: firstCommit, lastCommit: lastCommit) | |
if env["SKY_FORCE_ANDROID_BUILD"] != nil { | |
print("Forcing an android build") | |
projectsToBuild.insert(Project.android) | |
} | |
if env["SKY_FORCE_IOS_BUILD"] != nil { | |
print("Forcing an ios build") | |
projectsToBuild.insert(Project.ios) | |
} | |
if env["SKY_FORCE_SERVICE_BUILD"] != nil { | |
print("Forcing a service build") | |
projectsToBuild.insert(Project.service) | |
} | |
if projectsToBuild.isEmpty { | |
print("No projects to build") | |
exit(0) | |
} | |
// setup/build | |
for project in projectsToBuild { | |
if shouldInstallDependencies { | |
project.installDependencies(runner) | |
} | |
if shouldBuild { | |
project.build(runner) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment