Skip to content

Commit

Permalink
Merge pull request #49 from giginet/emit-swiftinterface
Browse files Browse the repository at this point in the history
Add option to specify enableLibraryEvolution
  • Loading branch information
giginet authored Feb 20, 2023
2 parents a36f6ab + ecee2d4 commit ea23471
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 26 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-log.git",
.upToNextMinor(from: "1.4.2")),
.package(url: "https://github.com/apple/swift-argument-parser.git",
.upToNextMinor(from: "1.1.0")),
from: "1.1.0"),
.package(url: "https://github.com/onevcat/Rainbow",
.upToNextMinor(from: "4.0.1")),
],
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,21 @@ All XCFrameworks are generated into `MyAppDependencies/XCFramework` in default.
|-\-static|Whether generated frameworks are Static Frameworks or not|-|
|-\-support-simulators|Whether also building for simulators of each SDKs or not|-|
|-\-cache-policy|How to reuse built frameworks|project|
|-\-disable-library-evolution|Whether to enable Library Evolution feature or not|-|


See `--help` for details.

#### Library Evolution support

Scipio automatically enables [Library Evolution](https://www.swift.org/blog/library-evolution/) feature in default.

It means built frameworks always keep compatibility even if linked from products built in other Swift versions. (ABI stability)

However, as known, some packages doesn't support Library Evolution or there are issues to generate swiftinterface. (https://developer.apple.com/forums/thread/123253)

You can disable Library Evolution with `--disable-library-evolution` flag if you need.

#### Build cache

In default, Scipio checks whether re-building is required or not for existing XCFrameworks.
Expand Down
5 changes: 4 additions & 1 deletion Sources/ScipioKit/BuildOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ struct BuildOptions: Hashable, Codable {
frameworkType: FrameworkType,
sdks: OrderedSet<SDK>,
extraFlags: ExtraFlags?,
extraBuildParameters: ExtraBuildParameters?
extraBuildParameters: ExtraBuildParameters?,
enableLibraryEvolution: Bool
) {
self.buildConfiguration = buildConfiguration
self.isDebugSymbolsEmbedded = isDebugSymbolsEmbedded
self.frameworkType = frameworkType
self.sdks = sdks
self.extraFlags = extraFlags
self.extraBuildParameters = extraBuildParameters
self.enableLibraryEvolution = enableLibraryEvolution
}

var buildConfiguration: BuildConfiguration
Expand All @@ -24,6 +26,7 @@ struct BuildOptions: Hashable, Codable {
var sdks: OrderedSet<SDK>
var extraFlags: ExtraFlags?
var extraBuildParameters: ExtraBuildParameters?
var enableLibraryEvolution: Bool
}

public struct ExtraFlags: Hashable, Codable {
Expand Down
3 changes: 2 additions & 1 deletion Sources/ScipioKit/Producer/PIF/PIFCompiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ struct PIFCompiler: Compiler {
let xcBuildClient: XCBuildClient = .init(
package: descriptionPackage,
buildProduct: buildProduct,
buildOptions: buildOptions,
configuration: buildOptions.buildConfiguration
)

Expand Down Expand Up @@ -115,7 +116,7 @@ struct PIFCompiler: Compiler {
toolchain: toolchain,
destinationTriple: toolchain.triple,
flags: .init(),
enableParseableModuleInterfaces: true,
enableParseableModuleInterfaces: buildOptions.enableLibraryEvolution,
isXcodeBuildSystemEnabled: true
)
}
Expand Down
15 changes: 6 additions & 9 deletions Sources/ScipioKit/Producer/PIF/PIFGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,15 @@ struct PIFGenerator {

settings[.GENERATE_INFOPLIST_FILE] = "YES"

// Set the project and marketing version for the framework because the app store requires these to be
// present. The AppStore requires bumping the project version when ingesting new builds but that's for
// top-level apps and not frameworks embedded inside it.
settings[.MARKETING_VERSION] = "1.0" // Version
settings[.CURRENT_PROJECT_VERSION] = "1" // Build

// Enable `-enable-library-evolution` to emit swiftinterface
settings[.OTHER_SWIFT_FLAGS, default: ["$(inherited)"]]
.append("-enable-library-evolution")

settings[.SWIFT_EMIT_MODULE_INTERFACE] = "YES"
// Add Bridging Headers to frameworks
// Enable to emit swiftinterface
if buildOptions.enableLibraryEvolution {
settings[.OTHER_SWIFT_FLAGS, default: ["$(inherited)"]]
.append("-enable-library-evolution")
settings[.SWIFT_EMIT_MODULE_INTERFACE] = "YES"
}
settings[.SWIFT_INSTALL_OBJC_HEADER] = "YES"

pifTarget.impartedBuildProperties.buildSettings[.OTHER_CFLAGS] = ["$(inherited)"]
Expand Down
7 changes: 6 additions & 1 deletion Sources/ScipioKit/Producer/PIF/XCBuildClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PackageGraph

struct XCBuildClient {
private let descriptionPackage: DescriptionPackage
private let buildOptions: BuildOptions
private let buildProduct: BuildProduct
private let configuration: BuildConfiguration
private let executor: any Executor
Expand All @@ -12,12 +13,14 @@ struct XCBuildClient {
init(
package: DescriptionPackage,
buildProduct: BuildProduct,
buildOptions: BuildOptions,
configuration: BuildConfiguration,
executor: any Executor = ProcessExecutor(decoder: StandardOutputDecoder()),
xcBuildExecutor: any Executor = ProcessExecutor(decoder: XCBuildOutputDecoder())
) {
self.descriptionPackage = package
self.buildProduct = buildProduct
self.buildOptions = buildOptions
self.configuration = configuration
self.executor = executor
self.buildExecutor = xcBuildExecutor
Expand Down Expand Up @@ -106,7 +109,9 @@ struct XCBuildClient {

let outputPathArguments: [String] = ["-output", outputPath.pathString]

return frameworksArguments + debugSymbolsArguments + outputPathArguments
// Default behavior, this command requires swiftinterface. If they don't exist, `-allow-internal-distribution` must be required.
let additionalFlags = buildOptions.enableLibraryEvolution ? [] : ["-allow-internal-distribution"]
return frameworksArguments + debugSymbolsArguments + outputPathArguments + additionalFlags
}
}

Expand Down
16 changes: 12 additions & 4 deletions Sources/ScipioKit/Runner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ extension Runner {
public var frameworkType: FrameworkType
public var extraFlags: ExtraFlags?
public var extraBuildParameters: [String: String]?
public var enableLibraryEvolution: Bool

public init(
buildConfiguration: BuildConfiguration = .release,
Expand All @@ -135,7 +136,8 @@ extension Runner {
isDebugSymbolsEmbedded: Bool = false,
frameworkType: FrameworkType = .dynamic,
extraFlags: ExtraFlags? = nil,
extraBuildParameters: [String: String]? = nil
extraBuildParameters: [String: String]? = nil,
enableLibraryEvolution: Bool = true
) {
self.buildConfiguration = buildConfiguration
self.platforms = platforms
Expand All @@ -144,6 +146,7 @@ extension Runner {
self.frameworkType = frameworkType
self.extraFlags = extraFlags
self.extraBuildParameters = extraBuildParameters
self.enableLibraryEvolution = enableLibraryEvolution
}
}
public struct TargetBuildOptions {
Expand All @@ -154,6 +157,7 @@ extension Runner {
public var frameworkType: FrameworkType?
public var extraFlags: ExtraFlags?
public var extraBuildParameters: [String: String]?
public var enableLibraryEvolution: Bool?

public init(
buildConfiguration: BuildConfiguration? = nil,
Expand All @@ -162,14 +166,16 @@ extension Runner {
isDebugSymbolsEmbedded: Bool? = nil,
frameworkType: FrameworkType? = nil,
extraFlags: ExtraFlags? = nil,
extraBuildParameters: [String: String]? = nil
extraBuildParameters: [String: String]? = nil,
enableLibraryEvolution: Bool? = nil
) {
self.buildConfiguration = buildConfiguration
self.platforms = platforms
self.isSimulatorSupported = isSimulatorSupported
self.isDebugSymbolsEmbedded = isDebugSymbolsEmbedded
self.frameworkType = frameworkType
self.extraFlags = extraFlags
self.enableLibraryEvolution = enableLibraryEvolution
}
}

Expand Down Expand Up @@ -257,7 +263,8 @@ extension Runner.Options.BuildOptions {
frameworkType: frameworkType,
sdks: OrderedSet(sdks),
extraFlags: extraFlags,
extraBuildParameters: extraBuildParameters
extraBuildParameters: extraBuildParameters,
enableLibraryEvolution: enableLibraryEvolution
)
}

Expand Down Expand Up @@ -299,7 +306,8 @@ extension Runner.Options.BuildOptions {
isDebugSymbolsEmbedded: fetch(\.isDebugSymbolsEmbedded, by: \.isDebugSymbolsEmbedded),
frameworkType: fetch(\.frameworkType, by: \.frameworkType),
extraFlags: mergedExtraFlags,
extraBuildParameters: mergedExtraBuildParameters
extraBuildParameters: mergedExtraBuildParameters,
enableLibraryEvolution: fetch(\.enableLibraryEvolution, by: \.enableLibraryEvolution)
)
}
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/scipio/CommandType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ extension Runner {
platforms: commandType.platformSpecifier,
isSimulatorSupported: buildOptions.supportSimulators,
isDebugSymbolsEmbedded: buildOptions.embedDebugSymbols,
frameworkType: buildOptions.frameworkType
frameworkType: buildOptions.frameworkType,
enableLibraryEvolution: buildOptions.shouldEnableLibraryEvolution
)
let runnerOptions = Runner.Options(
baseBuildOptions: baseBuildOptions,
Expand Down
5 changes: 5 additions & 0 deletions Sources/scipio/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ struct BuildOptionGroup: ParsableArguments {
help: "Whether generated frameworks are Static Frameworks or not")
var shouldBuildStaticFramework = false

@Flag(name: [.customLong("library-evolution")],
inversion: .prefixedEnableDisable,
help: "Whether to enable Library Evolution feature or not")
var shouldEnableLibraryEvolution = true

@Flag(name: [.customShort("f", allowingJoined: false), .long],
help: "Whether overwrite generated frameworks or not")
var overwrite: Bool = false
Expand Down
12 changes: 7 additions & 5 deletions Tests/ScipioKitTests/CacheSystemTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault
frameworkType: .dynamic,
sdks: [.iOS],
extraFlags: .init(swiftFlags: ["-D", "SOME_FLAG"]),
extraBuildParameters: ["SWIFT_OPTIMIZATION_LEVEL": "-Osize"]),
extraBuildParameters: ["SWIFT_OPTIMIZATION_LEVEL": "-Osize"],
enableLibraryEvolution: true),
clangVersion: "clang-1400.0.29.102")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
Expand All @@ -43,15 +44,16 @@ InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault
"SOME_FLAG"
]
},
"buildConfiguration" : "release",
"isDebugSymbolsEmbedded" : false,
"extraBuildParameters" : {
"SWIFT_OPTIMIZATION_LEVEL" : "-Osize"
},
"enableLibraryEvolution" : true,
"frameworkType" : "dynamic",
"sdks" : [
"iOS"
],
"extraBuildParameters" : {
"SWIFT_OPTIMIZATION_LEVEL" : "-Osize"
}
"buildConfiguration" : "release"
},
"targetName" : "MyTarget",
"clangVersion" : "clang-1400.0.29.102",
Expand Down
74 changes: 71 additions & 3 deletions Tests/ScipioKitTests/RunnerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,25 @@ final class RunnerTests: XCTestCase {
for library in ["ScipioTesting"] {
let xcFramework = frameworkOutputDir.appendingPathComponent("\(library).xcframework")
let versionFile = frameworkOutputDir.appendingPathComponent(".\(library).version")
let simulatorFramework = xcFramework.appendingPathComponent("ios-arm64_x86_64-simulator")
let simulatorFramework = xcFramework.appendingPathComponent("ios-arm64_x86_64-simulator/\(library).framework")
let deviceFramework = xcFramework.appendingPathComponent("ios-arm64/\(library).framework")

XCTAssertTrue(
fileManager.fileExists(atPath: deviceFramework.appendingPathComponent("Headers/\(library)-Swift.h").path),
"Should exist a bridging header"
)

XCTAssertTrue(
fileManager.fileExists(atPath: deviceFramework.appendingPathComponent("Modules/module.modulemap").path),
"Should exist a modulemap"
)

let expectedSwiftInterface = deviceFramework.appendingPathComponent("Modules/\(library).swiftmodule/arm64-apple-ios.swiftinterface")
XCTAssertTrue(
fileManager.fileExists(atPath: expectedSwiftInterface.path),
"Should exist a swiftinterface"
)

XCTAssertTrue(fileManager.fileExists(atPath: xcFramework.path),
"Should create \(library).xcramework")
XCTAssertTrue(fileManager.fileExists(atPath: versionFile.path),
Expand All @@ -64,7 +82,8 @@ final class RunnerTests: XCTestCase {
frameworkType: .dynamic,
sdks: [.iOS],
extraFlags: nil,
extraBuildParameters: nil),
extraBuildParameters: nil,
enableLibraryEvolution: true),
outputDirectory: frameworkOutputDir,
storage: nil)
let packages = descriptionPackage.graph.packages
Expand Down Expand Up @@ -222,7 +241,8 @@ final class RunnerTests: XCTestCase {
frameworkType: .dynamic,
sdks: [.iOS],
extraFlags: nil,
extraBuildParameters: nil),
extraBuildParameters: nil,
enableLibraryEvolution: true),
outputDirectory: frameworkOutputDir,
storage: nil)
let packages = descriptionPackage.graph.packages
Expand Down Expand Up @@ -397,6 +417,54 @@ final class RunnerTests: XCTestCase {
}
}

func testBuildXCFrameworkWithNoLibraryEvolution() async throws {
let runner = Runner(
mode: .prepareDependencies,
options: .init(
baseBuildOptions: .init(
isSimulatorSupported: false,
enableLibraryEvolution: false
)
)
)
do {
try await runner.run(packageDirectory: testPackagePath,
frameworkOutputDir: .custom(frameworkOutputDir))
} catch {
XCTFail("Build should be succeeded. \(error.localizedDescription)")
}

for library in ["ScipioTesting"] {
let xcFramework = frameworkOutputDir.appendingPathComponent("\(library).xcframework")
let versionFile = frameworkOutputDir.appendingPathComponent(".\(library).version")
let simulatorFramework = xcFramework.appendingPathComponent("ios-arm64_x86_64-simulator/\(library).framework")
let deviceFramework = xcFramework.appendingPathComponent("ios-arm64/\(library).framework")

XCTAssertTrue(
fileManager.fileExists(atPath: deviceFramework.appendingPathComponent("Headers/\(library)-Swift.h").path),
"Should exist a bridging header"
)

XCTAssertTrue(
fileManager.fileExists(atPath: deviceFramework.appendingPathComponent("Modules/module.modulemap").path),
"Should exist a modulemap"
)

let expectedSwiftInterface = deviceFramework.appendingPathComponent("Modules/\(library).swiftmodule/arm64-apple-ios.swiftinterface")
XCTAssertFalse(
fileManager.fileExists(atPath: expectedSwiftInterface.path),
"Should not exist a swiftinterface because emission is disabled"
)

XCTAssertTrue(fileManager.fileExists(atPath: xcFramework.path),
"Should create \(library).xcramework")
XCTAssertTrue(fileManager.fileExists(atPath: versionFile.path),
"Should create .\(library).version")
XCTAssertFalse(fileManager.fileExists(atPath: simulatorFramework.path),
"Should not create Simulator framework")
}
}

override func tearDownWithError() throws {
try removeIfExist(at: testPackagePath.appendingPathComponent(".build"))
try removeIfExist(at: frameworkOutputDir)
Expand Down

0 comments on commit ea23471

Please sign in to comment.