Skip to content

Commit

Permalink
Enable cross-PR testing
Browse files Browse the repository at this point in the history
  • Loading branch information
ahoppen committed Oct 26, 2024
1 parent d82d736 commit 8bb6b22
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 10 deletions.
15 changes: 10 additions & 5 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ jobs:
tests:
name: Test
uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main
soundness:
name: Soundness
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
with:
license_header_check_enabled: false
license_header_check_project_name: "Swift.org"
enable_windows_checks: false
linux_pre_build_command: |
swiftc cross-pr-checkout.swift -o /tmp/cross-pr-checkout
/tmp/cross-pr-checkout "${{ github.repository }}" "${{ github.event.number }}"
# soundness:
# name: Soundness
# uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
# with:
# license_header_check_enabled: false
# license_header_check_project_name: "Swift.org"
10 changes: 5 additions & 5 deletions Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
switch node.name.text {
case "Array":
guard let argument = genericArgumentList.firstAndOnly,
case .type(let typeArgument) = argument else {
case .type(let typeArgument) = argument.argument else {
newNode = nil
break
}
Expand All @@ -62,7 +62,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
case "Dictionary":
guard let arguments = exactlyTwoChildren(of: genericArgumentList),
case .type(let type0Argument) = arguments.0.argument,
caes .type(let type1Argument) = arguments.1.argument else {
case .type(let type1Argument) = arguments.1.argument else {
newNode = nil
break
}
Expand All @@ -79,7 +79,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
break
}
guard let argument = genericArgumentList.firstAndOnly,
case .type(let typeArgument) = argument else {
case .type(let typeArgument) = argument.argument else {
newNode = nil
break
}
Expand Down Expand Up @@ -143,7 +143,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
switch expression.baseName.text {
case "Array":
guard let argument = genericArgumentList.firstAndOnly,
case .type(let typeArgument) = argument else {
case .type(let typeArgument) = argument.argument else {
newNode = nil
break
}
Expand Down Expand Up @@ -172,7 +172,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {

case "Optional":
guard let argument = genericArgumentList.firstAndOnly,
case .type(let typeArgument) = argument else {
case .type(let typeArgument) = argument.argument else {
newNode = nil
break
}
Expand Down
33 changes: 33 additions & 0 deletions cross-pr-checkout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import subprocess
import pathlib
import requests

class CrossRepoPR:
org: str
repo: str
pr_num: str

def __init__(self, org: str, repo: str, pr_num: str) -> None:
self.org = org
self.repo = repo
self.pr_num = pr_num

def cross_repo_prs() -> list[CrossRepoPR]:
return [
CrossRepoPR("swiftlang", "swift-syntax", "2859")
]

def run(cmd: list[str], cwd: str|None = None):
print(" ".join(cmd))
subprocess.check_call(cmd, cwd=cwd)

def main():
for cross_repo_pr in cross_repo_prs():
run(["git", "clone", f"https://github.com/{cross_repo_pr.org}/{cross_repo_pr.repo}.git", f"{cross_repo_pr.repo}"], cwd="..")
run(["git", "fetch", "origin", f"pull/{cross_repo_pr.pr_num}/merge:pr_merge"], cwd="../swift-syntax")
run(["git", "checkout", "main"], cwd="../swift-syntax")
run(["git", "reset", "--hard", "pr_merge"], cwd="../swift-syntax")
run(["swift", "package", "config", "set-mirror", "--package-url", "https://github.com/swiftlang/swift-syntax.git", "--mirror-url", str(pathlib.Path("../swift-syntax").resolve())])

if __name__ == "__main__":
main()
171 changes: 171 additions & 0 deletions cross-pr-checkout.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import Foundation
import RegexBuilder

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

struct GenericError: Error, CustomStringConvertible {
var description: String

init(_ description: String) {
self.description = description
}
}

/// Launch a subprocess with the given command and wait for it to finish
func run(_ executable: URL, _ arguments: String..., workingDirectory: URL? = nil) throws {
print("Running " + ([executable.path] + arguments).joined(separator: " "))
let process = Process()
process.executableURL = executable
process.arguments = arguments
if let workingDirectory {
process.currentDirectoryURL = workingDirectory
}

try process.run()
process.waitUntilExit()
}

/// Find the executable with the given name
public func lookup(executable: String) throws -> URL {
// Compute search paths from PATH variable.
#if os(Windows)
let pathSeparator: Character = ";"
let pathVariable = "Path"
#else
let pathSeparator: Character = ":"
let pathVariable = "PATH"
#endif
guard let pathString = ProcessInfo.processInfo.environment[pathVariable] else {
throw GenericError("Failed to read path environment variable")
}
for searchPath in pathString.split(separator: pathSeparator) {
let candidateUrl = URL(fileURLWithPath: String(searchPath)).appendingPathComponent(executable)
if FileManager.default.isExecutableFile(atPath: candidateUrl.path) {
return candidateUrl
}
}
throw GenericError("Did not find \(executable)")
}

struct CrossRepoPR {
let repositoryOwner: String
let repositoryName: String
let prNumber: String
}

/// The JSON fields of the `https://api.github.com/repos/\(repository)/pulls/\(prNumber)` endpoint that we care about.
struct PRInfo: Codable {
struct Base: Codable {
let ref: String
}
let base: Base
let body: String
}

/// - Parameters:
/// - repository: The repository's name, eg. `swiftlang/swift-syntax`
func getPRInfo(repository: String, prNumber: String) throws -> PRInfo {
guard let prInfoUrl = URL(string: "https://api.github.com/repos/\(repository)/pulls/\(prNumber)") else {
throw GenericError("Failed to form URL for GitHub API")
}

do {
let data = try Data(contentsOf: prInfoUrl)
return try JSONDecoder().decode(PRInfo.self, from: data)
} catch {
throw GenericError("Failed to load PR info from \(prInfoUrl): \(error)")
}
}

func getCrossRepoPrs(repository: String, prNumber: String) throws -> [CrossRepoPR] {
var result: [CrossRepoPR] = []
let prInfo = try getPRInfo(repository: repository, prNumber: prNumber)
for line in prInfo.body.split(separator: "\n") {
guard line.lowercased().starts(with: "linked pr:") else {
continue
}
let repoRegex = Regex {
Capture {
#/swiftlang|apple/#
}
"/"
Capture {
#/[-a-zA-Z0-9_]+/#
}
ChoiceOf {
"/pull/"
"#"
}
Capture {
OneOrMore(.digit)
}
}
for match in line.matches(of: repoRegex) {
result.append(
CrossRepoPR(repositoryOwner: String(match.1), repositoryName: String(match.2), prNumber: String(match.3))
)
}
}
return result
}

func main() throws {
print("Start")
print(ProcessInfo.processInfo.arguments)

guard ProcessInfo.processInfo.arguments.count >= 3 else {
throw GenericError(
"""
Expected two arguments:
- Repository name, eg. `swiftlang/swift-syntax
- PR number
"""
)
}
let repository = ProcessInfo.processInfo.arguments[1]
let prNumber = ProcessInfo.processInfo.arguments[2]

let crossRepoPrs = try getCrossRepoPrs(repository: repository, prNumber: prNumber)
print("Detected cross-repo PRs: \(crossRepoPrs)")

for crossRepoPr in crossRepoPrs {
let git = try lookup(executable: "git")
let swift = try lookup(executable: "swift")
let baseBranch = try getPRInfo(
repository: "\(crossRepoPr.repositoryOwner)/\(crossRepoPr.repositoryOwner)",
prNumber: crossRepoPr.prNumber
).base.ref

let workspaceDir = URL(fileURLWithPath: "..")
let repoDir = workspaceDir.appendingPathComponent(crossRepoPr.repositoryName)
try run(
git,
"clone",
"https://github.com/\(crossRepoPr.repositoryOwner)/\(crossRepoPr.repositoryName).git",
"\(crossRepoPr.repositoryName)",
workingDirectory: workspaceDir
)
try run(git, "fetch", "origin", "pull/\(crossRepoPr.prNumber)/merge:pr_merge", workingDirectory: repoDir)
try run(git, "checkout", baseBranch, workingDirectory: repoDir)
try run(git, "reset", "--hard", "pr_merge", workingDirectory: repoDir)
try run(
swift,
"package",
"config",
"set-mirror",
"--package-url",
"https://github.com/\(crossRepoPr.repositoryOwner)/\(crossRepoPr.repositoryName).git",
"--mirror-url",
repoDir.resolvingSymlinksInPath().path
)
}
}

do {
try main()
} catch {
print(error)
exit(1)
}

0 comments on commit 8bb6b22

Please sign in to comment.