Skip to content

Commit b5fc1e3

Browse files
nmcc24pepicrft
andauthored
feat: Compatibility with the iOS platform (#1016)
* Fix compilation issues on iOS platform Currently, XcodeProj is not compilable on iOS platform. This is an issue when attempting to compile products that have XcodeProj as a (transitive) dependency * Skip tests on iOS platform that utilise unavailable APIs * Update xcodeproj.yml Add iOS simulator build and test stages to github workflow * Fix iOS workflow checks --------- Co-authored-by: Pedro Piñera Buendía <pedro@pepicrft.me>
1 parent 5120d2e commit b5fc1e3

6 files changed

Lines changed: 100 additions & 66 deletions

File tree

.github/workflows/xcodeproj.yml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ jobs:
3333
- run: mise use swift@6.0.2
3434
- name: Build
3535
run: swift build --configuration release
36+
build-ios:
37+
name: Build (iOS)
38+
runs-on: macos-latest
39+
steps:
40+
- uses: actions/checkout@v6
41+
- name: Build for iOS Simulator
42+
run: xcodebuild build -scheme XcodeProj -destination 'generic/platform=iOS Simulator'
3643
test:
3744
name: Test (macOS / Xcode)
3845
runs-on: macos-latest
@@ -41,7 +48,6 @@ jobs:
4148
- uses: jdx/mise-action@v3
4249
- name: Run tests
4350
run: mise run test
44-
4551
test-linux:
4652
name: Test (Linux)
4753
runs-on: ubuntu-latest
@@ -55,6 +61,22 @@ jobs:
5561
git config --global init.defaultBranch main
5662
- name: Test
5763
run: swift test
64+
test-ios:
65+
name: Test (iOS / Xcode)
66+
runs-on: macos-latest
67+
steps:
68+
- uses: actions/checkout@v3
69+
- name: Select iOS Simulator
70+
run: |
71+
simulator_id="$(xcrun simctl list devices available | awk -F '[()]' '/iPhone/ { print $2; exit }')"
72+
if [ -z "$simulator_id" ]; then
73+
echo "No available iPhone simulator found"
74+
xcrun simctl list devices available
75+
exit 1
76+
fi
77+
echo "IOS_SIMULATOR_ID=$simulator_id" >> "$GITHUB_ENV"
78+
- name: Run tests on iOS Simulator
79+
run: xcodebuild test -scheme XcodeProj -destination "platform=iOS Simulator,id=$IOS_SIMULATOR_ID"
5880

5981
lint:
6082
name: Lint

Sources/XcodeProj/Extensions/Path+Extras.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import PathKit
66
// MARK: - Path extras.
77

88
func systemGlob(_ pattern: UnsafePointer<CChar>!, _ flags: Int32, _ errfunc: (@convention(c) (UnsafePointer<CChar>?, Int32) -> Int32)!, _ vector_ptr: UnsafeMutablePointer<glob_t>!) -> Int32 {
9-
#if os(macOS)
9+
#if os(macOS) || os(iOS)
1010
return Darwin.glob(pattern, flags, errfunc, vector_ptr)
1111
#else
1212
return Glibc.glob(pattern, flags, errfunc, vector_ptr)

Sources/XcodeProj/Extensions/String+md5.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ extension String {
3131
return self
3232
}
3333
#if canImport(CryptoKit)
34-
if #available(OSX 10.15, *) {
34+
if #available(OSX 10.15, iOS 13.0, *) {
3535
return Insecure.MD5.hash(data: data)
3636
.withUnsafeBytes { Array($0) }.hexString
3737
} else {

Tests/XcodeProjTests/Extensions/XCTestCase+Shell.swift

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,27 @@ import Foundation
33
/// Returns the output of running `executable` with `args`. Throws an error if the process exits indicating failure.
44
@discardableResult
55
func checkedOutput(_ executable: String, _ args: [String]) throws -> String? {
6-
let process = Process()
7-
let output = Pipe()
6+
#if os(iOS)
7+
return nil
8+
#else
9+
let process = Process()
10+
let output = Pipe()
811

9-
if executable.contains("/") {
10-
process.launchPath = executable
11-
} else {
12-
process.launchPath = try checkedOutput("/usr/bin/which", [executable])?.trimmingCharacters(in: .newlines)
13-
}
12+
if executable.contains("/") {
13+
process.launchPath = executable
14+
} else {
15+
process.launchPath = try checkedOutput("/usr/bin/which", [executable])?.trimmingCharacters(in: .newlines)
16+
}
1417

15-
process.arguments = args
16-
process.standardOutput = output
17-
process.launch()
18-
process.waitUntilExit()
18+
process.arguments = args
19+
process.standardOutput = output
20+
process.launch()
21+
process.waitUntilExit()
1922

20-
guard process.terminationStatus == 0 else {
21-
throw NSError(domain: NSPOSIXErrorDomain, code: Int(process.terminationStatus))
22-
}
23+
guard process.terminationStatus == 0 else {
24+
throw NSError(domain: NSPOSIXErrorDomain, code: Int(process.terminationStatus))
25+
}
2326

24-
return String(data: output.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)
27+
return String(data: output.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)
28+
#endif
2529
}

Tests/XcodeProjTests/Scheme/XCSchemeManagementTests.swift

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -45,38 +45,42 @@ final class XCSchemeManagementTests: XCTestCase {
4545
}
4646

4747
func test_write_produces_no_diff() throws {
48-
let tmpDir = try Path.uniqueTemporary()
49-
defer {
50-
try? tmpDir.delete()
51-
}
48+
#if os(iOS)
49+
throw XCTSkip("'Process' API is unavailable on iOS platform")
50+
#else
51+
let tmpDir = try Path.uniqueTemporary()
52+
defer {
53+
try? tmpDir.delete()
54+
}
5255

53-
try tmpDir.chdir {
54-
// Write
55-
let plistPath = tmpDir + "xcschememanagement.plist"
56-
let subject = XCSchemeManagement(
57-
schemeUserState: [
58-
.init(name: "Test 0.xcscheme", shared: true, orderHint: 0, isShown: true),
59-
.init(name: "Test 1.xcscheme", shared: true, orderHint: 1, isShown: true),
60-
.init(name: "Test 2.xcscheme", shared: true, orderHint: 2, isShown: false),
61-
.init(name: "Test 3.xcscheme", shared: true, orderHint: 3, isShown: true),
62-
],
63-
suppressBuildableAutocreation: [
64-
"E525238B16245A900012E2BA": .init(primary: true),
65-
]
66-
)
67-
try subject.write(path: plistPath, override: true)
56+
try tmpDir.chdir {
57+
// Write
58+
let plistPath = tmpDir + "xcschememanagement.plist"
59+
let subject = XCSchemeManagement(
60+
schemeUserState: [
61+
.init(name: "Test 0.xcscheme", shared: true, orderHint: 0, isShown: true),
62+
.init(name: "Test 1.xcscheme", shared: true, orderHint: 1, isShown: true),
63+
.init(name: "Test 2.xcscheme", shared: true, orderHint: 2, isShown: false),
64+
.init(name: "Test 3.xcscheme", shared: true, orderHint: 3, isShown: true),
65+
],
66+
suppressBuildableAutocreation: [
67+
"E525238B16245A900012E2BA": .init(primary: true),
68+
]
69+
)
70+
try subject.write(path: plistPath, override: true)
6871

69-
// Create a commit
70-
try checkedOutput("git", ["init"])
71-
try checkedOutput("git", ["add", "."])
72-
try checkedOutput("git", ["commit", "-m", "test"])
72+
// Create a commit
73+
try checkedOutput("git", ["init"])
74+
try checkedOutput("git", ["add", "."])
75+
try checkedOutput("git", ["commit", "-m", "test"])
7376

74-
// Write again
75-
try subject.write(path: plistPath, override: true)
77+
// Write again
78+
try subject.write(path: plistPath, override: true)
7679

77-
let got = try checkedOutput("git", ["status"])
78-
XCTAssertTrue(got?.contains("nothing to commit") ?? false)
79-
}
80+
let got = try checkedOutput("git", ["status"])
81+
XCTAssertTrue(got?.contains("nothing to commit") ?? false)
82+
}
83+
#endif
8084
}
8185

8286
private var xcschememanagementPath: Path {

Tests/XcodeProjTests/Tests/testWrite.swift

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,32 @@ func testReadWriteProducesNoDiff(file _: StaticString = #file,
4545
from path: Path,
4646
initModel: (Path) throws -> some Writable) throws
4747
{
48-
let tmpDir = try Path.uniqueTemporary()
49-
defer {
50-
try? tmpDir.delete()
51-
}
48+
#if os(iOS)
49+
throw XCTSkip("'Process' API is unavailable on iOS platform")
50+
#else
51+
let tmpDir = try Path.uniqueTemporary()
52+
defer {
53+
try? tmpDir.delete()
54+
}
5255

53-
let fileName = path.lastComponent
54-
let tmpPath = tmpDir + fileName
55-
try path.copy(tmpPath)
56+
let fileName = path.lastComponent
57+
let tmpPath = tmpDir + fileName
58+
try path.copy(tmpPath)
5659

57-
try tmpDir.chdir {
58-
// Create a commit
59-
try checkedOutput("git", ["init"])
60-
try checkedOutput("git", ["add", "."])
61-
try checkedOutput("git", [
62-
"-c", "user.email=test@example.com", "-c", "user.name=Test User",
63-
"commit", "-m", "test",
64-
])
60+
try tmpDir.chdir {
61+
// Create a commit
62+
try checkedOutput("git", ["init"])
63+
try checkedOutput("git", ["add", "."])
64+
try checkedOutput("git", [
65+
"-c", "user.email=test@example.com", "-c", "user.name=Test User",
66+
"commit", "-m", "test",
67+
])
6568

66-
let object = try initModel(tmpPath)
67-
try object.write(path: tmpPath, override: true)
69+
let object = try initModel(tmpPath)
70+
try object.write(path: tmpPath, override: true)
6871

69-
let diff = try XCTUnwrap(checkedOutput("git", ["diff"]))
70-
XCTAssertEqual(diff, "")
71-
}
72+
let diff = try XCTUnwrap(checkedOutput("git", ["diff"]))
73+
XCTAssertEqual(diff, "")
74+
}
75+
#endif
7276
}

0 commit comments

Comments
 (0)