Skip to content

Commit 29691fa

Browse files
authored
Merge pull request #62 from JamitLabs/work/#61-swift-version-bugfix
Fix cache mixed up with different swift versions
2 parents 0bc9116 + 41255b0 commit 29691fa

11 files changed

+238
-46
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
1313
### Removed
1414
- None.
1515
### Fixed
16-
- None.
16+
- Fix mixed caching of frameworks with different Swift versions.
17+
Issue: [#61](https://github.com/JamitLabs/Accio/issues/61) | PR: [#62](https://github.com/JamitLabs/Accio/pull/62) | Author: [Frederick Pietschmann](https://github.com/fredpi)
1718
### Security
1819
- None.
1920

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/AccioKit/Commands/Protocols/DependencyInstaller.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ extension DependencyInstaller {
6565
try bash("mkdir -p '\(Constants.temporaryFrameworksUrl.path)'")
6666
try bash("mkdir -p '\(Constants.temporaryUncachingUrl.path)'")
6767

68+
let swiftVersion = try SwiftVersionDetectorService.shared.getCurrentSwiftVersion()
69+
6870
typealias ParsingResult = (target: AppTarget, platform: Platform, frameworkProducts: [FrameworkProduct])
6971

7072
let appTargets: [AppTarget] = try manifest.appTargets()
@@ -77,7 +79,13 @@ extension DependencyInstaller {
7779
let platform = try PlatformDetectorService.shared.detectPlatform(of: appTarget)
7880
print("Resolving dependencies for target '\(appTarget.targetName)' on platform '\(platform.rawValue)' ...", level: .info)
7981

80-
let frameworkProducts = try CachedBuilderService(sharedCachePath: sharedCachePath).frameworkProducts(manifest: manifest, appTarget: appTarget, dependencyGraph: dependencyGraph, platform: platform)
82+
let frameworkProducts = try CachedBuilderService(sharedCachePath: sharedCachePath).frameworkProducts(
83+
manifest: manifest,
84+
appTarget: appTarget,
85+
dependencyGraph: dependencyGraph,
86+
platform: platform,
87+
swiftVersion: swiftVersion
88+
)
8189
return ParsingResult(target: appTarget, platform: platform, frameworkProducts: frameworkProducts)
8290
}
8391

@@ -94,7 +102,12 @@ extension DependencyInstaller {
94102
at: URL(fileURLWithPath: workingDirectory).appendingPathComponent("Package.resolved"),
95103
with: parsingResults.flatMap {
96104
$0.frameworkProducts.map {
97-
CachedFrameworkProduct(swiftVersion: Constants.swiftVersion, libraryName: $0.libraryName, commitHash: $0.commitHash, platform: $0.platformName)
105+
CachedFrameworkProduct(
106+
swiftVersion: swiftVersion,
107+
libraryName: $0.libraryName,
108+
commitHash: $0.commitHash,
109+
platform: $0.platformName
110+
)
98111
}
99112
}
100113
)

Sources/AccioKit/Globals/Constants.swift

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,4 @@ enum Constants {
1919

2020
return FileManager.userCacheDirUrl.appendingPathComponent("Accio/Cache").path
2121
}
22-
23-
static var swiftVersion: String {
24-
#if swift(>=6.0)
25-
return "Swift-6.0"
26-
#elseif swift(>=5.2)
27-
return "Swift-5.2"
28-
#elseif swift(>=5.1)
29-
return "Swift-5.1"
30-
#elseif swift(>=5.0)
31-
return "Swift-5.0"
32-
#else
33-
return "Swift-4.2"
34-
#endif
35-
}
3622
}

Sources/AccioKit/Services/CachedBuilderService.swift

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,38 @@
11
import Foundation
22

3+
enum CachedBuilderServiceError: Error {
4+
case unableToRetrieveSwiftVersion
5+
case swiftVersionChanged
6+
}
7+
8+
extension CachedBuilderServiceError: LocalizedError {
9+
var errorDescription: String? {
10+
switch self {
11+
case .unableToRetrieveSwiftVersion:
12+
return "Unable to retrieve Swift version used for command line."
13+
14+
case .swiftVersionChanged:
15+
return "Swift version used for command line apparently changed during runtime."
16+
}
17+
}
18+
}
19+
320
final class CachedBuilderService {
421
private let frameworkCachingService: FrameworkCachingService
522
private let carthageBuilderService: CarthageBuilderService
623

724
init(sharedCachePath: String?) {
8-
self.frameworkCachingService = FrameworkCachingService(sharedCachePath: sharedCachePath)
9-
self.carthageBuilderService = CarthageBuilderService(frameworkCachingService: frameworkCachingService)
25+
frameworkCachingService = FrameworkCachingService(sharedCachePath: sharedCachePath)
26+
carthageBuilderService = CarthageBuilderService(frameworkCachingService: frameworkCachingService)
1027
}
1128

12-
func frameworkProducts(manifest: Manifest, appTarget: AppTarget, dependencyGraph: DependencyGraph, platform: Platform) throws -> [FrameworkProduct] {
29+
func frameworkProducts(
30+
manifest: Manifest,
31+
appTarget: AppTarget,
32+
dependencyGraph: DependencyGraph,
33+
platform: Platform,
34+
swiftVersion: String
35+
) throws -> [FrameworkProduct] {
1336
var frameworkProducts: [FrameworkProduct] = []
1437

1538
let frameworks = try appTarget.frameworkDependencies(manifest: manifest, dependencyGraph: dependencyGraph).flattenedDeepFirstOrder()
@@ -18,28 +41,53 @@ final class CachedBuilderService {
1841
}
1942

2043
for framework in frameworksWithoutDuplicates {
21-
if let cachedFrameworkProduct = try frameworkCachingService.cachedProduct(framework: framework, platform: platform) {
44+
if
45+
let cachedFrameworkProduct = try frameworkCachingService.cachedProduct(
46+
framework: framework,
47+
platform: platform,
48+
swiftVersion: swiftVersion
49+
)
50+
{
2251
frameworkProducts.append(cachedFrameworkProduct)
2352
} else {
53+
let frameworkProduct: FrameworkProduct
2454
switch try InstallationTypeDetectorService.shared.detectInstallationType(for: framework) {
2555
case .swiftPackageManager:
2656
try XcodeProjectGeneratorService.shared.generateXcodeProject(framework: framework)
27-
let frameworkProduct = try carthageBuilderService.build(
57+
frameworkProduct = try carthageBuilderService.build(
2858
framework: framework,
2959
platform: platform,
60+
swiftVersion: swiftVersion,
3061
alreadyBuiltFrameworkProducts: frameworkProducts
3162
)
32-
frameworkProducts.append(frameworkProduct)
3363

3464
case .carthage:
3565
try GitResetService.shared.resetGit(atPath: framework.projectDirectory, includeUntrackedFiles: false)
36-
let frameworkProduct = try carthageBuilderService.build(
66+
frameworkProduct = try carthageBuilderService.build(
3767
framework: framework,
3868
platform: platform,
69+
swiftVersion: swiftVersion,
3970
alreadyBuiltFrameworkProducts: frameworkProducts
4071
)
41-
frameworkProducts.append(frameworkProduct)
4272
}
73+
74+
if
75+
let frameworkSwiftVersion = (
76+
try? SwiftVersionDetectorService.shared.detectSwiftVersion(ofFrameworkProduct: frameworkProduct)
77+
) ?? (
78+
try? SwiftVersionDetectorService.shared.getCurrentSwiftVersion()
79+
)
80+
{
81+
// If detectSwiftVersion doesn't work (e. g. happening for RxAtomic because of missing Swift header file),
82+
// fallback to just retrieving current swift version via bash command.
83+
guard frameworkSwiftVersion == swiftVersion else {
84+
throw CachedBuilderServiceError.swiftVersionChanged
85+
}
86+
} else {
87+
throw CachedBuilderServiceError.unableToRetrieveSwiftVersion
88+
}
89+
90+
frameworkProducts.append(frameworkProduct)
4391
}
4492
}
4593

Sources/AccioKit/Services/CarthageBuilderService.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ final class CarthageBuilderService {
1212
self.frameworkCachingService = frameworkCachingService
1313
}
1414

15-
func build(framework: Framework, platform: Platform, alreadyBuiltFrameworkProducts: [FrameworkProduct]) throws -> FrameworkProduct {
15+
func build(
16+
framework: Framework,
17+
platform: Platform,
18+
swiftVersion: String,
19+
alreadyBuiltFrameworkProducts: [FrameworkProduct]
20+
) throws -> FrameworkProduct {
1621
print("Building library \(framework.libraryName) with Carthage ...", level: .info)
1722

1823
// link already built subdependencies from previous calls of this method
@@ -58,7 +63,7 @@ final class CarthageBuilderService {
5863
try frameworkProduct.cleanupRecursiveFrameworkIfNeeded()
5964

6065
print("Completed building scheme \(framework.libraryName) with Carthage.", level: .info)
61-
try frameworkCachingService.cache(product: frameworkProduct, framework: framework, platform: platform)
66+
try frameworkCachingService.cache(product: frameworkProduct, framework: framework, platform: platform, swiftVersion: swiftVersion)
6267

6368
return frameworkProduct
6469
}

Sources/AccioKit/Services/FrameworkCachingService.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ final class FrameworkCachingService {
88
self.sharedCachePath = sharedCachePath
99
}
1010

11-
func cachedProduct(framework: Framework, platform: Platform) throws -> FrameworkProduct? {
12-
let subpath: String = cacheFileSubPath(framework: framework, platform: platform)
11+
func cachedProduct(framework: Framework, platform: Platform, swiftVersion: String) throws -> FrameworkProduct? {
12+
let subpath: String = cacheFileSubPath(framework: framework, platform: platform, swiftVersion: swiftVersion)
1313
let localCachedFileUrl = URL(fileURLWithPath: Constants.localCachePath).appendingPathComponent(subpath)
1414

1515
if FileManager.default.fileExists(atPath: localCachedFileUrl.path) {
@@ -29,8 +29,8 @@ final class FrameworkCachingService {
2929
return nil
3030
}
3131

32-
func cache(product: FrameworkProduct, framework: Framework, platform: Platform) throws {
33-
let subpath: String = cacheFileSubPath(framework: framework, platform: platform)
32+
func cache(product: FrameworkProduct, framework: Framework, platform: Platform, swiftVersion: String) throws {
33+
let subpath: String = cacheFileSubPath(framework: framework, platform: platform, swiftVersion: swiftVersion)
3434

3535
if
3636
let sharedCachePath = sharedCachePath,
@@ -70,8 +70,8 @@ final class FrameworkCachingService {
7070
return frameworkProduct
7171
}
7272

73-
private func cacheFileSubPath(framework: Framework, platform: Platform) -> String {
74-
return "\(Constants.swiftVersion)/\(framework.libraryName)/\(framework.commitHash)/\(platform.rawValue).zip"
73+
private func cacheFileSubPath(framework: Framework, platform: Platform, swiftVersion: String) -> String {
74+
return "\(swiftVersion)/\(framework.libraryName)/\(framework.commitHash)/\(platform.rawValue).zip"
7575
}
7676

7777
private func cache(product: FrameworkProduct, to targetUrl: URL) throws {

Sources/AccioKit/Services/ResolvedManifestCachingService.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ final class ResolvedManifestCachingService {
4141

4242
if FileManager.default.fileExists(atPath: localCachedFileUrl.path) {
4343
print("Found cached resolved manifest results in local cache - trying to reuse cached build products.", level: .info)
44-
return try JSONDecoder().decode([CachedFrameworkProduct].self, from: Data(contentsOf: localCachedFileUrl))
44+
return try? JSONDecoder().decode([CachedFrameworkProduct].self, from: Data(contentsOf: localCachedFileUrl))
4545
}
4646

4747
if let sharedCachePath = sharedCachePath {
4848
let sharedCacheFileUrl = URL(fileURLWithPath: sharedCachePath).appendingPathComponent(subpath)
4949

5050
if FileManager.default.fileExists(atPath: sharedCacheFileUrl.path) {
5151
print("Found cached resolved manifest results in shared cache - trying to reuse cached build products.", level: .info)
52-
return try JSONDecoder().decode([CachedFrameworkProduct].self, from: Data(contentsOf: sharedCacheFileUrl))
52+
return try? JSONDecoder().decode([CachedFrameworkProduct].self, from: Data(contentsOf: sharedCacheFileUrl))
5353
}
5454
}
5555

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import Foundation
2+
import HandySwift
3+
import SwiftShell
4+
5+
enum SwiftVersionDetectorError: Error {
6+
case swiftVersionCommandError
7+
case parsingError
8+
}
9+
10+
class SwiftVersionDetectorService {
11+
static let shared = SwiftVersionDetectorService()
12+
13+
func getCurrentSwiftVersion() throws -> String {
14+
let result = run(bash: "swift --version")
15+
guard result.succeeded else {
16+
throw SwiftVersionDetectorError.swiftVersionCommandError
17+
}
18+
19+
return try convertToSwiftVersion(swiftVersionOutput: result.stdout)
20+
}
21+
22+
func convertToSwiftVersion(swiftVersionOutput: String) throws -> String {
23+
do {
24+
let regex = try Regex(#"Apple Swift version ([\d.]*) \(swiftlang"#)
25+
guard let versionNumber = regex.firstMatch(in: swiftVersionOutput)?.captures.first ?? nil else {
26+
throw SwiftVersionDetectorError.parsingError
27+
}
28+
29+
return "Swift-\(versionNumber)"
30+
}
31+
catch {
32+
throw SwiftVersionDetectorError.parsingError
33+
}
34+
}
35+
36+
func detectSwiftVersion(ofFrameworkProduct frameworkProduct: FrameworkProduct) throws -> String {
37+
let swiftHeaderFileUrl = frameworkProduct.frameworkDirUrl.appendingPathComponent(
38+
"Headers/\(frameworkProduct.libraryName)-Swift.h"
39+
)
40+
41+
if
42+
FileManager.default.fileExists(atPath: swiftHeaderFileUrl.path),
43+
let swiftHeaderFileContents = try? String(contentsOf: swiftHeaderFileUrl, encoding: .utf8)
44+
{
45+
do {
46+
let regex = try Regex(#"// Generated by Apple Swift version ([\d.]*) "#)
47+
guard let versionNumber = regex.firstMatch(in: swiftHeaderFileContents)?.captures.first ?? nil else {
48+
throw SwiftVersionDetectorError.parsingError
49+
}
50+
51+
return "Swift-\(versionNumber)"
52+
}
53+
catch {
54+
throw SwiftVersionDetectorError.parsingError
55+
}
56+
}
57+
58+
throw SwiftVersionDetectorError.parsingError
59+
}
60+
}

Tests/AccioKitTests/Services/FrameworkCachingServiceTests.swift

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,20 @@ class FrameworkCachingServiceTests: XCTestCase {
3434
TestHelper.shared.isStartedByUnitTests = true
3535
let frameworkCachingService = FrameworkCachingService(sharedCachePath: nil)
3636

37-
let testFrameworkLocalCacheFilePath: String = "\(Constants.localCachePath)/\(Constants.swiftVersion)/\(testFramework.libraryName)/\(testFramework.commitHash)/\(Platform.iOS.rawValue).zip"
38-
let testFrameworkSharedCacheFilePath: String = "\(sharedCachePath)/\(Constants.swiftVersion)/\(testFramework.libraryName)/\(testFramework.commitHash)/\(Platform.iOS.rawValue).zip"
37+
let swiftVersion = try! SwiftVersionDetectorService.shared.getCurrentSwiftVersion()
3938

40-
var cachedProduct: FrameworkProduct? = try! frameworkCachingService.cachedProduct(framework: testFramework, platform: .iOS)
39+
let testFrameworkLocalCacheFilePath: String = "\(Constants.localCachePath)/\(swiftVersion)/\(testFramework.libraryName)/\(testFramework.commitHash)/\(Platform.iOS.rawValue).zip"
40+
let testFrameworkSharedCacheFilePath: String = "\(sharedCachePath)/\(swiftVersion)/\(testFramework.libraryName)/\(testFramework.commitHash)/\(Platform.iOS.rawValue).zip"
41+
42+
var cachedProduct: FrameworkProduct? = try! frameworkCachingService.cachedProduct(framework: testFramework, platform: .iOS, swiftVersion: swiftVersion)
4143
XCTAssertNil(cachedProduct)
4244

4345
XCTAssert(!FileManager.default.fileExists(atPath: testFrameworkLocalCacheFilePath))
4446
XCTAssert(!FileManager.default.fileExists(atPath: testFrameworkSharedCacheFilePath))
4547

46-
try! frameworkCachingService.cache(product: testFrameworkProduct, framework: testFramework, platform: .iOS)
48+
try! frameworkCachingService.cache(product: testFrameworkProduct, framework: testFramework, platform: .iOS, swiftVersion: swiftVersion)
4749

48-
cachedProduct = try! frameworkCachingService.cachedProduct(framework: testFramework, platform: .iOS)
50+
cachedProduct = try! frameworkCachingService.cachedProduct(framework: testFramework, platform: .iOS, swiftVersion: swiftVersion)
4951
XCTAssertNotNil(cachedProduct)
5052

5153
XCTAssert(cachedProduct!.frameworkDirPath.hasPrefix(Constants.temporaryFrameworksUrl.path))
@@ -62,18 +64,20 @@ class FrameworkCachingServiceTests: XCTestCase {
6264
TestHelper.shared.isStartedByUnitTests = true
6365
let frameworkCachingService = FrameworkCachingService(sharedCachePath: sharedCachePath)
6466

65-
let testFrameworkLocalCacheFilePath: String = "\(Constants.localCachePath)/\(Constants.swiftVersion)/\(testFramework.libraryName)/\(testFramework.commitHash)/\(Platform.iOS.rawValue).zip"
66-
let testFrameworkSharedCacheFilePath: String = "\(sharedCachePath)/\(Constants.swiftVersion)/\(testFramework.libraryName)/\(testFramework.commitHash)/\(Platform.iOS.rawValue).zip"
67+
let swiftVersion = try! SwiftVersionDetectorService.shared.getCurrentSwiftVersion()
68+
69+
let testFrameworkLocalCacheFilePath: String = "\(Constants.localCachePath)/\(swiftVersion)/\(testFramework.libraryName)/\(testFramework.commitHash)/\(Platform.iOS.rawValue).zip"
70+
let testFrameworkSharedCacheFilePath: String = "\(sharedCachePath)/\(swiftVersion)/\(testFramework.libraryName)/\(testFramework.commitHash)/\(Platform.iOS.rawValue).zip"
6771

68-
var cachedProduct: FrameworkProduct? = try! frameworkCachingService.cachedProduct(framework: testFramework, platform: .iOS)
72+
var cachedProduct: FrameworkProduct? = try! frameworkCachingService.cachedProduct(framework: testFramework, platform: .iOS, swiftVersion: swiftVersion)
6973
XCTAssertNil(cachedProduct)
7074

7175
XCTAssert(!FileManager.default.fileExists(atPath: testFrameworkLocalCacheFilePath))
7276
XCTAssert(!FileManager.default.fileExists(atPath: testFrameworkSharedCacheFilePath))
7377

74-
try! frameworkCachingService.cache(product: testFrameworkProduct, framework: testFramework, platform: .iOS)
78+
try! frameworkCachingService.cache(product: testFrameworkProduct, framework: testFramework, platform: .iOS, swiftVersion: swiftVersion)
7579

76-
cachedProduct = try! frameworkCachingService.cachedProduct(framework: testFramework, platform: .iOS)
80+
cachedProduct = try! frameworkCachingService.cachedProduct(framework: testFramework, platform: .iOS, swiftVersion: swiftVersion)
7781
XCTAssertNotNil(cachedProduct)
7882

7983
XCTAssert(cachedProduct!.frameworkDirPath.hasPrefix(Constants.temporaryFrameworksUrl.path))

0 commit comments

Comments
 (0)