“Scriptify” deployment

This commit is contained in:
Max Howell
2019-02-12 15:44:15 -05:00
parent 9a770ca576
commit b65d167937
3 changed files with 197 additions and 64 deletions

172
.github/deploy vendored Executable file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/swift sh
import func Darwin.fputs
import var Darwin.stderr
import PMKFoundation // PromiseKit/Foundation ~> 3.3
import LegibleError // @mxcl ~> 1.0
import Foundation
import PromiseKit // @mxcl ~> 6.8
import Path // mxcl/Path.swift == master
let env = ProcessInfo.processInfo.environment
let token = env["GITHUB_TOKEN"] ?? env["GITHUB_ACCESS_TOKEN"]!
let slug = env["TRAVIS_REPO_SLUG"]!
let tag = env["TRAVIS_TAG"]!
func fatal(message: String) -> Never {
fputs("error: \(message)\n", stderr)
exit(1)
}
func fatal(error: Error) -> Never {
fatal(message: error.legibleLocalizedDescription)
}
guard let licenseFile = try Path.cwd.ls().files.first(where: {
$0.basename().hasPrefix("LICENSE")
})?.basename() else {
fatal(message: "no LICENSE file found")
}
struct Repo: Decodable {
let description: String
let license: License
struct License: Decodable {
let spdx_id: String
}
}
struct Package: Decodable {
let swiftLanguageVersions: [String]
let targets: [Target]
struct Target: Decodable {
let path: String?
let type: Kind
enum Kind: String, Decodable {
case regular
case test
}
}
}
extension URLRequest {
init(github path: String) {
let url = URL(string: "https://api.github.com\(path)")!
self.init(url: url)
setValue("token \(token)", forHTTPHeaderField: "Authorization")
setValue("application/json", forHTTPHeaderField: "Content-Type")
setValue("application/json", forHTTPHeaderField: "Accept")
}
}
func description() -> Promise<Repo> {
let rq = URLRequest(github: "/repos/\(slug)")
return firstly {
URLSession.shared.dataTask(.promise, with: rq).validate()
}.map { data, _ in
try JSONDecoder().decode(Repo.self, from: data)
}
}
struct User: Decodable {
let name: String
let email: String
}
func email() -> Promise<User> {
let rq = URLRequest(github: "/user")
return firstly {
URLSession.shared.dataTask(.promise, with: rq).validate()
}.map { data, _ in
try JSONDecoder().decode(User.self, from: data)
}
}
func dumpPackage() -> Promise<Package> {
let task = Process()
task.launchPath = "/usr/bin/swift"
task.arguments = ["package", "dump-package"]
return firstly {
task.launch(.promise)
}.map { out, _ in
out.fileHandleForReading.readDataToEndOfFile()
}.map { data in
try JSONDecoder().decode(Package.self, from: data)
}
}
var defaultSwiftVersion: String {
let task = Process()
task.launchPath = "/usr/bin/swift"
task.arguments = ["--version"]
func extract(input: String) -> String {
let range = input.range(of: #"Apple Swift version \d+\.\d+"#, options: .regularExpression)!
return String(input[range].split(separator: " ").last!)
}
return try! firstly {
task.launch(.promise)
}.compactMap { out, _ in
String(data: out.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)
}.map { out in
extract(input: out)
}.wait()
}
func podspec(repo: Repo, user: User, pkg: Package) -> (Substring, String) {
let (owner, name) = { ($0[0], $0[1]) }(slug.split(separator: "/"))
let swiftVersion = pkg.swiftLanguageVersions.max() ?? defaultSwiftVersion
let sources = pkg.targets.filter{ $0.type == .regular }.compactMap{ $0.path }.first!
return (name, """
Pod::Spec.new do |s|
s.name = '\(name)'
s.version = '\(tag)'
s.summary = '\(repo.description)'
s.homepage = "https://github.com/\(slug)"
s.license = { type: '\(repo.license.spdx_id)', file: '\(licenseFile)' }
s.author = { '\(user.name)': '\(user.email)' }
s.source = { git: "https://github.com/\(slug).git", tag: '\(tag)' }
s.social_media_url = 'https://twitter.com/\(owner)'
s.osx.deployment_target = '10.10'
s.ios.deployment_target = '8.0'
s.tvos.deployment_target = '9.0'
s.watchos.deployment_target = '2.0'
s.source_files = '\(sources)/*.swift'
s.swift_version = '\(swiftVersion)'
end
""")
}
func publishRelease() throws -> Promise<Void> {
struct Input: Encodable {
var tag_name: String { return tag }
var name: String { return tag }
let body = ""
}
var rq = URLRequest(github: "/repos/\(slug)/releases")
rq.httpMethod = "POST"
rq.httpBody = try JSONEncoder().encode(Input())
return URLSession.shared.dataTask(.promise, with: rq).validate().asVoid()
}
switch CommandLine.arguments[1] {
case "generate-podspec":
firstly {
when(fulfilled: description(), email(), dumpPackage())
}.map(podspec).done { name, podspec in
try podspec.write(toFile: "\(name).podspec", atomically: false, encoding: .utf8)
exit(0)
}.catch {
fatal(error: $0)
}
case "publish-release":
try publishRelease().done {
exit(0)
}.catch {
fatal(error: $0)
}
default:
fatal(message: "invalid usage")
}
RunLoop.main.run()

13
.github/jazzy.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
module: Path
custom_categories:
- name: Path
children:
- Path
- /(_:_:)
xcodebuild_arguments:
- UseModernBuildSystem=NO
output:
../output
# output directory is relative to config file… ugh
exclude:
- Sources/Path+StringConvertibles.swift

View File

@@ -52,7 +52,7 @@ jobs:
- <<: *linux
env: SWIFT_VERSION='5.0-DEVELOPMENT-SNAPSHOT-2019-01-22-a'
name: Linux / Swift 5.0.0-dev (2019-01-22)
name: Linux / Swift 5.0.0-dev+2019-01-22
- stage: pretest
name: Check Linux tests are syncd
@@ -61,29 +61,12 @@ jobs:
- stage: deploy
name: Jazzy
before_install: |
cat <<\ \ EOF> .jazzy.yaml
module: Path
module_version: TRAVIS_TAG
custom_categories:
- name: Path
children:
- Path
- /(_:_:)
xcodebuild_arguments:
- UseModernBuildSystem=NO
output: output
github_url: https://github.com/mxcl/Path.swift
exclude:
- Sources/Path+StringConvertibles.swift
EOF
sed -i '' "s/TRAVIS_TAG/$TRAVIS_TAG/" .jazzy.yaml
# ^^ this weirdness because Travis multiline YAML is broken and inserts
# two spaces in front of the output which means we need a prefixed
# delimiter which also weirdly stops bash from doing variable substitution
install: gem install jazzy
before_script: swift package generate-xcodeproj
script: jazzy
script: |
jazzy --config .github/jazzy.yml \
--module-version $TRAVIS_TAG \
--github_url "https://github.com/$TRAVIS_REPO_SLUG"
deploy:
provider: pages
skip-cleanup: true
@@ -93,43 +76,8 @@ jobs:
tags: true
- name: CocoaPods
before_install: export TRAVIS_REPO_NAME=${TRAVIS_REPO_SLUG#*/}
install: gem install cocoapods
before_script: |
export DESCRIPTION=$(swift - <<\ \ EOF
import Foundation
struct Response: Decodable { let description: String }
let token = ProcessInfo.processInfo.environment["GITHUB_TOKEN"]!
let slug = ProcessInfo.processInfo.environment["TRAVIS_REPO_SLUG"]!
let url = URL(string: "https://api.github.com/repos/\(slug)")!
var rq = URLRequest(url: url)
rq.setValue("token \(token)", forHTTPHeaderField: "Authorization")
let semaphore = DispatchSemaphore(value: 0)
var data: Data!
URLSession.shared.dataTask(with: rq) { d, _, _ in
data = d
semaphore.signal()
}.resume()
semaphore.wait()
let rsp = try JSONDecoder().decode(Response.self, from: data)
print(rsp.description, terminator: "")
EOF)
cat <<\ \ EOF> $TRAVIS_REPO_NAME.podspec
Pod::Spec.new do |s|
s.name = ENV['TRAVIS_REPO_NAME']
s.version = ENV['TRAVIS_TAG']
s.summary = ENV['DESCRIPTION']
s.homepage = "https://github.com/#{ENV['TRAVIS_REPO_SLUG']}"
s.license = { type: 'Unlicense', file: 'LICENSE.md' }
s.author = { mxcl: 'mxcl@me.com' }
s.source = { git: "https://github.com/#{ENV['TRAVIS_REPO_SLUG']}.git", tag: s.version }
s.social_media_url = 'https://twitter.com/mxcl'
s.osx.deployment_target = '10.10'
s.ios.deployment_target = '8.0'
s.tvos.deployment_target = '10.0'
s.watchos.deployment_target = '3.0'
s.source_files = 'Sources/*'
s.swift_version = '4.2'
end
EOF
before_install: gem install cocoapods
install: brew install mxcl/made/swift-sh
before_script: .github/deploy generate-podspec
script: pod trunk push
after_success: .github/deploy post-release