diff --git a/Sources/ScipioKit/BuildOptions.swift b/Sources/ScipioKit/BuildOptions.swift index a96a9708..89b582a9 100644 --- a/Sources/ScipioKit/BuildOptions.swift +++ b/Sources/ScipioKit/BuildOptions.swift @@ -65,6 +65,7 @@ public enum BuildConfiguration: String, Codable, Sendable { public enum FrameworkType: String, Codable, Sendable { case dynamic case `static` + case mergeable } public enum SDK: String, Codable, Hashable, Sendable { diff --git a/Sources/ScipioKit/Producer/PIF/PIFGenerator.swift b/Sources/ScipioKit/Producer/PIF/PIFGenerator.swift index af05c7c2..56674d6d 100644 --- a/Sources/ScipioKit/Producer/PIF/PIFGenerator.swift +++ b/Sources/ScipioKit/Producer/PIF/PIFGenerator.swift @@ -227,7 +227,7 @@ private struct PIFLibraryTargetModifier { // Set framework type switch frameworkType { - case .dynamic: + case .dynamic, .mergeable: settings[.MACH_O_TYPE] = "mh_dylib" case .static: settings[.MACH_O_TYPE] = "staticlib" @@ -244,6 +244,11 @@ private struct PIFLibraryTargetModifier { } settings[.SWIFT_INSTALL_OBJC_HEADER] = "YES" + if frameworkType == .mergeable { + settings[.OTHER_LDFLAGS, default: ["$(inherited)"]] + .append("-Wl,-make_mergeable") + } + appendExtraFlagsByBuildOptionsMatrix(to: &settings) // Original PIFBuilder implementation of SwiftPM generates modulemap for Swift target diff --git a/Sources/scipio/CommandType.swift b/Sources/scipio/CommandType.swift index b7ba9bc7..3c31d9c2 100644 --- a/Sources/scipio/CommandType.swift +++ b/Sources/scipio/CommandType.swift @@ -35,6 +35,11 @@ enum CommandType { extension Runner { init(commandType: CommandType, buildOptions: BuildOptionGroup, globalOptions: GlobalOptionGroup) { + // FIXME it's strange to raise the error here, but it will be removed in a future release + if buildOptions.shouldBuildStaticFramework { + fatalError("--static is deprecated. Use `-framework-type static` instead.") + } + let baseBuildOptions = Runner.Options.BuildOptions( buildConfiguration: buildOptions.buildConfiguration, platforms: commandType.platformSpecifier, diff --git a/Sources/scipio/Options.swift b/Sources/scipio/Options.swift index 52912129..e58ff33a 100644 --- a/Sources/scipio/Options.swift +++ b/Sources/scipio/Options.swift @@ -29,6 +29,10 @@ struct BuildOptionGroup: ParsableArguments { help: "Whether generated frameworks are Static Frameworks or not") var shouldBuildStaticFramework = false + @Option(name: [.customLong("framework-type")], + help: "Specify the frameworkType. Availables: dynamic, static or mergeable") + var frameworkType: FrameworkType = .dynamic + @Flag(name: [.customLong("library-evolution")], inversion: .prefixedEnableDisable, help: "Whether to enable Library Evolution feature or not") @@ -43,8 +47,4 @@ struct BuildOptionGroup: ParsableArguments { var overwrite: Bool = false } -extension BuildOptionGroup { - var frameworkType: FrameworkType { - shouldBuildStaticFramework ? .static : .dynamic - } -} +extension FrameworkType: ExpressibleByArgument { } diff --git a/Sources/scipio/scipio.docc/mergeable-library.md b/Sources/scipio/scipio.docc/mergeable-library.md new file mode 100644 index 00000000..271fdf3a --- /dev/null +++ b/Sources/scipio/scipio.docc/mergeable-library.md @@ -0,0 +1,28 @@ +# Support Mergeable Library + +Apple announced [Mergeable Library](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries) in WWDC23. Mergeable Library is the new framework type which can switch the linking style by the build configuration. It has the metadata to change the linking style on the link time. + +Scipio supports `mergeable` framework type to distribute packages as mergeable libraries. + +```shell +$ scipio create path/to/MyPackage --framework-type mergeable --enable-library-evolution +``` + +See details the official documentation and following WWDC session. + +- [Configuring your project to use mergeable libraries | Apple Developer Documentation](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries) +- [Meet mergeable libraries - WWDC23 - Videos - Apple Developer](https://developer.apple.com/videos/play/wwdc2023/10268/) + +In general, mergeable frameworks will be about 2x bigger binary size than the normal dynamic frameworks. + +## How to check whether the built framework is mergeable or not + +Mergeable frameworks have `LC_ATOM_INFO` load command in the binary. You can check it by `otool` command. + +```shell +echo $(otool -l MyFramework.framework/MyFramework) | grep "LC_ATOM_INFO" +``` + +## Limitation + +Some frameworks can't build as a dynamic framework by some reasons. They can't be distributed as mergeable libraries. Try `--framework-type static` instead. diff --git a/Sources/scipio/scipio.docc/prepare-cache-for-applications.md b/Sources/scipio/scipio.docc/prepare-cache-for-applications.md index cdc24ec5..a3aaea99 100644 --- a/Sources/scipio/scipio.docc/prepare-cache-for-applications.md +++ b/Sources/scipio/scipio.docc/prepare-cache-for-applications.md @@ -86,16 +86,16 @@ All XCFrameworks are generated into `MyAppDependencies/XCFramework` by default. `prepare` command has some options. These are available options. -|Flag|Description|Default| -|---------|------------|-----------| -|-\-configuration, -c|Build configuration for generated frameworks (debug / release)|release| -|-\-output, -o|Path indicates a XCFrameworks output directory|$PACKAGE_ROOT/XCFrameworks| -|-\-embed-debug-symbols|Whether embed debug symbols to frameworks or not|-| -|-\-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| -|-\-enable-library-evolution|Whether to enable Library Evolution feature or not|-| -|-\-only-use-versions-from-resolved-file|Whether to disable updating Package.resolved automatically|false| +| Flag | Description | Default | +|-----------------------------------------|---------------------------------------------------------------------|----------------------------| +| -\-configuration, -c | Build configuration for generated frameworks (debug / release) | release | +| -\-output, -o | Path indicates a XCFrameworks output directory | $PACKAGE_ROOT/XCFrameworks | +| -\-embed-debug-symbols | Whether embed debug symbols to frameworks or not | - | +| -\-framework-type | Framework type to generate Available: dynamic, static or mergeable) | dynamic | +| -\-support-simulators | Whether also building for simulators of each SDKs or not | - | +| -\-cache-policy | How to reuse built frameworks | project | +| -\-enable-library-evolution | Whether to enable Library Evolution feature or not | - | +| -\-only-use-versions-from-resolved-file | Whether to disable updating Package.resolved automatically | false | See `--help` for details. diff --git a/Sources/scipio/scipio.docc/scipio.md b/Sources/scipio/scipio.docc/scipio.md index 859aa27c..d188de31 100644 --- a/Sources/scipio/scipio.docc/scipio.md +++ b/Sources/scipio/scipio.docc/scipio.md @@ -23,4 +23,4 @@ A new build tool to generate XCFramework from Swift Package - - - - +- diff --git a/Tests/ScipioKitTests/RunnerTests.swift b/Tests/ScipioKitTests/RunnerTests.swift index 4454e548..db9263cf 100644 --- a/Tests/ScipioKitTests/RunnerTests.swift +++ b/Tests/ScipioKitTests/RunnerTests.swift @@ -520,6 +520,45 @@ final class RunnerTests: XCTestCase { } } + func testMergeableLibrary() async throws { + let runner = Runner( + mode: .createPackage, + options: .init( + baseBuildOptions: .init( + platforms: .specific([.iOS]), + frameworkType: .mergeable + ), + shouldOnlyUseVersionsFromResolvedFile: true, + cacheMode: .disabled + ) + ) + + try await runner.run(packageDirectory: testPackagePath, + frameworkOutputDir: .custom(frameworkOutputDir)) + + let xcFramework = frameworkOutputDir.appendingPathComponent("TestingPackage.xcframework") + + let executor = ProcessExecutor() + + for arch in ["ios-arm64"] { + let binaryPath = xcFramework + .appendingPathComponent(arch) + .appendingPathComponent("TestingPackage.framework") + .appendingPathComponent("TestingPackage") + XCTAssertTrue( + fileManager.fileExists(atPath: binaryPath.path), + "A framework for \(arch) should contain binary" + ) + + let executionResult = try await executor.execute("/usr/bin/otool", "-l", binaryPath.path()) + let loadCommands = try XCTUnwrap(executionResult.unwrapOutput()) + XCTAssertTrue( + loadCommands.contains("LC_ATOM_INFO"), + "A Mergeable Library should contain LC_ATOM_INFO segment" + ) + } + } + func testWithExtraBuildParameters() async throws { let runner = Runner( mode: .prepareDependencies,