diff --git a/.github/deploy b/.github/deploy new file mode 100755 index 0000000..63010b2 --- /dev/null +++ b/.github/deploy @@ -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 { + 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 { + 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 { + 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 { + 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() diff --git a/.github/jazzy.yml b/.github/jazzy.yml new file mode 100644 index 0000000..88d484d --- /dev/null +++ b/.github/jazzy.yml @@ -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 diff --git a/.travis.yml b/.travis.yml index 56ab4c1..328efcf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ language: swift osx_image: xcode10.1 xcode_project: Path.swift.xcodeproj xcode_scheme: Path.swift-Package - + jobs: include: - name: macOS / Swift 4.2.1 @@ -49,11 +49,11 @@ jobs: sudo: false install: eval "$(curl -sL https://swiftenv.fuller.li/install.sh)" script: swift test --parallel - + - <<: *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 sync’d install: swift test --generate-linuxmain @@ -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