Skip to content

Commit db6c4cf

Browse files
committed
Enable cross-PR testing
1 parent d82d736 commit db6c4cf

File tree

4 files changed

+231
-10
lines changed

4 files changed

+231
-10
lines changed

.github/workflows/pull_request.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ jobs:
88
tests:
99
name: Test
1010
uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main
11-
soundness:
12-
name: Soundness
13-
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
1411
with:
15-
license_header_check_enabled: false
16-
license_header_check_project_name: "Swift.org"
12+
enable_windows_checks: false
13+
linux_pre_build_command: |
14+
swift cross-pr-checkout.swift
15+
# soundness:
16+
# name: Soundness
17+
# uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
18+
# with:
19+
# license_header_check_enabled: false
20+
# license_header_check_project_name: "Swift.org"

Sources/SwiftFormat/Rules/UseShorthandTypeNames.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
4848
switch node.name.text {
4949
case "Array":
5050
guard let argument = genericArgumentList.firstAndOnly,
51-
case .type(let typeArgument) = argument else {
51+
case .type(let typeArgument) = argument.argument else {
5252
newNode = nil
5353
break
5454
}
@@ -62,7 +62,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
6262
case "Dictionary":
6363
guard let arguments = exactlyTwoChildren(of: genericArgumentList),
6464
case .type(let type0Argument) = arguments.0.argument,
65-
caes .type(let type1Argument) = arguments.1.argument else {
65+
case .type(let type1Argument) = arguments.1.argument else {
6666
newNode = nil
6767
break
6868
}
@@ -79,7 +79,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
7979
break
8080
}
8181
guard let argument = genericArgumentList.firstAndOnly,
82-
case .type(let typeArgument) = argument else {
82+
case .type(let typeArgument) = argument.argument else {
8383
newNode = nil
8484
break
8585
}
@@ -143,7 +143,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
143143
switch expression.baseName.text {
144144
case "Array":
145145
guard let argument = genericArgumentList.firstAndOnly,
146-
case .type(let typeArgument) = argument else {
146+
case .type(let typeArgument) = argument.argument else {
147147
newNode = nil
148148
break
149149
}
@@ -172,7 +172,7 @@ public final class UseShorthandTypeNames: SyntaxFormatRule {
172172

173173
case "Optional":
174174
guard let argument = genericArgumentList.firstAndOnly,
175-
case .type(let typeArgument) = argument else {
175+
case .type(let typeArgument) = argument.argument else {
176176
newNode = nil
177177
break
178178
}

cross-pr-checkout.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import subprocess
2+
import pathlib
3+
import requests
4+
5+
class CrossRepoPR:
6+
org: str
7+
repo: str
8+
pr_num: str
9+
10+
def __init__(self, org: str, repo: str, pr_num: str) -> None:
11+
self.org = org
12+
self.repo = repo
13+
self.pr_num = pr_num
14+
15+
def cross_repo_prs() -> list[CrossRepoPR]:
16+
return [
17+
CrossRepoPR("swiftlang", "swift-syntax", "2859")
18+
]
19+
20+
def run(cmd: list[str], cwd: str|None = None):
21+
print(" ".join(cmd))
22+
subprocess.check_call(cmd, cwd=cwd)
23+
24+
def main():
25+
for cross_repo_pr in cross_repo_prs():
26+
run(["git", "clone", f"https://github.com/{cross_repo_pr.org}/{cross_repo_pr.repo}.git", f"{cross_repo_pr.repo}"], cwd="..")
27+
run(["git", "fetch", "origin", f"pull/{cross_repo_pr.pr_num}/merge:pr_merge"], cwd="../swift-syntax")
28+
run(["git", "checkout", "main"], cwd="../swift-syntax")
29+
run(["git", "reset", "--hard", "pr_merge"], cwd="../swift-syntax")
30+
run(["swift", "package", "config", "set-mirror", "--package-url", "https://github.com/swiftlang/swift-syntax.git", "--mirror-url", str(pathlib.Path("../swift-syntax").resolve())])
31+
32+
if __name__ == "__main__":
33+
main()

cross-pr-checkout.swift

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import Foundation
2+
import FoundationNetworking
3+
4+
/// Provides convenience APIs for launching and gathering output from a subprocess
5+
public class ProcessRunner {
6+
private static let serialQueue = DispatchQueue(label: "\(ProcessRunner.self)")
7+
8+
let process: Process
9+
var launched = false
10+
11+
public init(
12+
launchPath: String,
13+
arguments: [String],
14+
workingDirectory: String?,
15+
environment: [String: String] = [:]
16+
) {
17+
process = Process()
18+
process.launchPath = launchPath
19+
process.arguments = arguments
20+
if let workingDirectory {
21+
process.currentDirectoryURL = URL(fileURLWithPath: workingDirectory)
22+
}
23+
process.environment = environment.merging(ProcessInfo.processInfo.environment) { (current, _) in current }
24+
}
25+
26+
public func run(
27+
input: String? = nil,
28+
captureStdout: Bool = true,
29+
captureStderr: Bool = true
30+
) -> ProcessResult {
31+
let group = DispatchGroup()
32+
33+
let inPipe = Pipe()
34+
if input != nil {
35+
process.standardInput = inPipe
36+
}
37+
38+
var outData = Data()
39+
if captureStdout {
40+
let outPipe = Pipe()
41+
process.standardOutput = outPipe
42+
addHandler(pipe: outPipe, group: group) { outData.append($0) }
43+
}
44+
45+
var errData = Data()
46+
if captureStderr {
47+
let errPipe = Pipe()
48+
process.standardError = errPipe
49+
addHandler(pipe: errPipe, group: group) { errData.append($0) }
50+
}
51+
52+
ProcessRunner.serialQueue.sync {
53+
process.launch()
54+
launched = true
55+
}
56+
57+
if let input = input {
58+
guard let data = input.data(using: .utf8) else {
59+
return ProcessResult(
60+
status: 1,
61+
stdout: Data(),
62+
stderr: "Invalid input".data(using: .utf8)!
63+
)
64+
}
65+
inPipe.fileHandleForWriting.write(data)
66+
inPipe.fileHandleForWriting.closeFile()
67+
}
68+
69+
process.waitUntilExit()
70+
if captureStdout || captureStderr {
71+
// Make sure we've received all stdout/stderr
72+
group.wait()
73+
}
74+
75+
return ProcessResult(
76+
status: process.terminationStatus,
77+
stdout: outData,
78+
stderr: errData
79+
)
80+
}
81+
82+
public func terminate() {
83+
ProcessRunner.serialQueue.sync {
84+
if launched {
85+
process.terminate()
86+
}
87+
}
88+
}
89+
90+
private func addHandler(
91+
pipe: Pipe,
92+
group: DispatchGroup,
93+
addData: @escaping (Data) -> Void
94+
) {
95+
group.enter()
96+
pipe.fileHandleForReading.readabilityHandler = { fileHandle in
97+
// Apparently using availableData can cause various issues
98+
let newData = fileHandle.readData(ofLength: Int.max)
99+
if newData.count == 0 {
100+
pipe.fileHandleForReading.readabilityHandler = nil;
101+
group.leave()
102+
} else {
103+
addData(newData)
104+
}
105+
}
106+
}
107+
}
108+
109+
/// The exit code and output (if redirected) from a subprocess that has
110+
/// terminated
111+
public struct ProcessResult {
112+
public let status: Int32
113+
public let stdout: Data
114+
public let stderr: Data
115+
116+
public var stdoutStr: String? {
117+
return String(data: stdout, encoding: .utf8)
118+
}
119+
public var stderrStr: String? {
120+
return String(data: stderr, encoding: .utf8)
121+
}
122+
}
123+
124+
func run(_ executable: String, _ arguments: String..., workingDirectory: String? = nil) {
125+
let runner = ProcessRunner(
126+
launchPath: executable,
127+
arguments: arguments,
128+
workingDirectory: workingDirectory,
129+
environment: [:]
130+
)
131+
_ = runner.run()
132+
}
133+
134+
struct CrossRepoPR {
135+
let org: String
136+
let repo: String
137+
let prNum: String
138+
}
139+
140+
let crossRepoPrs = [
141+
CrossRepoPR(org: "swiftlang", repo: "swift-syntax", prNum: "2859")
142+
]
143+
144+
func getBaseBranch(_ crossRepoPr: CrossRepoPR) throws -> String {
145+
struct PR: Codable {
146+
struct Base: Codable {
147+
let ref: String
148+
}
149+
let base: Base
150+
}
151+
152+
let data = try Data(
153+
contentsOf: URL(
154+
string: "https://api.github.com/repos/\(crossRepoPr.org)/\(crossRepoPr.repo)/pulls/\(crossRepoPr.prNum)"
155+
)!
156+
)
157+
let decoded = try JSONDecoder().decode(PR.self, from: data)
158+
return decoded.base.ref
159+
}
160+
161+
for crossRepoPr in crossRepoPrs {
162+
let git = "/usr/bin/git"
163+
run(
164+
git,
165+
"clone",
166+
"https://github.com/\(crossRepoPr.org)/\(crossRepoPr.repo).git",
167+
"\(crossRepoPr.repo)",
168+
workingDirectory: ".."
169+
)
170+
run(git, "fetch", "origin", "pull/\(crossRepoPr.prNum)/merge:pr_merge", workingDirectory: "../swift-syntax")
171+
let baseBranch = try getBaseBranch(crossRepoPr)
172+
run(git, "checkout", baseBranch, workingDirectory: "../swift-syntax")
173+
run(git, "reset", "--hard", "pr_merge", workingDirectory: "../swift-syntax")
174+
run(
175+
"swift",
176+
"package",
177+
"config",
178+
"set-mirror",
179+
"--package-url",
180+
"https://github.com/swiftlang/swift-syntax.git",
181+
"--mirror-url",
182+
URL(fileURLWithPath: "../swift-syntax").resolvingSymlinksInPath().path
183+
)
184+
}

0 commit comments

Comments
 (0)