From 0af8bdeb0f63018f00d859b86a8d4cbfe5dc004f Mon Sep 17 00:00:00 2001 From: giginet Date: Sun, 26 May 2024 22:22:21 +0900 Subject: [PATCH 1/9] Add mergeable framework type --- Sources/ScipioKit/BuildOptions.swift | 1 + Sources/ScipioKit/Producer/PIF/PIFGenerator.swift | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) 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..c36f5e1e 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" @@ -243,6 +243,10 @@ private struct PIFLibraryTargetModifier { settings[.SWIFT_EMIT_MODULE_INTERFACE] = "YES" } settings[.SWIFT_INSTALL_OBJC_HEADER] = "YES" + + if frameworkType == .mergeable { + settings[.OTHER_LDFLAGS] = ["-Wl,-make_mergeable"] + } appendExtraFlagsByBuildOptionsMatrix(to: &settings) From 687339f1c6b9c8928fbc334e19c792968060ce2a Mon Sep 17 00:00:00 2001 From: giginet Date: Sun, 26 May 2024 22:47:33 +0900 Subject: [PATCH 2/9] Implement CLI interface to pass framework type --- Sources/scipio/CommandType.swift | 5 +++++ Sources/scipio/Options.swift | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Sources/scipio/CommandType.swift b/Sources/scipio/CommandType.swift index b7ba9bc7..dd682db6 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..d40d483c 100644 --- a/Sources/scipio/Options.swift +++ b/Sources/scipio/Options.swift @@ -28,6 +28,10 @@ struct BuildOptionGroup: ParsableArguments { @Flag(name: [.customLong("static")], 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, @@ -43,8 +47,4 @@ struct BuildOptionGroup: ParsableArguments { var overwrite: Bool = false } -extension BuildOptionGroup { - var frameworkType: FrameworkType { - shouldBuildStaticFramework ? .static : .dynamic - } -} +extension FrameworkType: ExpressibleByArgument { } From 3df7f7d6837e3e2ddbc65ba1eb161c023a80c8a4 Mon Sep 17 00:00:00 2001 From: giginet Date: Sun, 26 May 2024 23:03:49 +0900 Subject: [PATCH 3/9] Add article about Mergeable Library --- .../scipio/scipio.docc/mergeable-library.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Sources/scipio/scipio.docc/mergeable-library.md diff --git a/Sources/scipio/scipio.docc/mergeable-library.md b/Sources/scipio/scipio.docc/mergeable-library.md new file mode 100644 index 00000000..51b118f4 --- /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 path/to/binary) | 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. From bdfe916f1f5d576136e913caa039b9e509ddb98c Mon Sep 17 00:00:00 2001 From: giginet Date: Fri, 31 May 2024 20:52:24 +0900 Subject: [PATCH 4/9] Update documents --- .../prepare-cache-for-applications.md | 20 +++++++++---------- Sources/scipio/scipio.docc/scipio.md | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) 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 - - - - +- From 9606352805ccac00e120b93e7fcb49ccbba68afb Mon Sep 17 00:00:00 2001 From: giginet Date: Fri, 31 May 2024 20:56:46 +0900 Subject: [PATCH 5/9] Resolve SwiftLint warnings --- Sources/ScipioKit/Producer/PIF/PIFGenerator.swift | 2 +- Sources/scipio/CommandType.swift | 2 +- Sources/scipio/Options.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/ScipioKit/Producer/PIF/PIFGenerator.swift b/Sources/ScipioKit/Producer/PIF/PIFGenerator.swift index c36f5e1e..3a56fbb2 100644 --- a/Sources/ScipioKit/Producer/PIF/PIFGenerator.swift +++ b/Sources/ScipioKit/Producer/PIF/PIFGenerator.swift @@ -243,7 +243,7 @@ private struct PIFLibraryTargetModifier { settings[.SWIFT_EMIT_MODULE_INTERFACE] = "YES" } settings[.SWIFT_INSTALL_OBJC_HEADER] = "YES" - + if frameworkType == .mergeable { settings[.OTHER_LDFLAGS] = ["-Wl,-make_mergeable"] } diff --git a/Sources/scipio/CommandType.swift b/Sources/scipio/CommandType.swift index dd682db6..3c31d9c2 100644 --- a/Sources/scipio/CommandType.swift +++ b/Sources/scipio/CommandType.swift @@ -39,7 +39,7 @@ extension Runner { 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 d40d483c..e58ff33a 100644 --- a/Sources/scipio/Options.swift +++ b/Sources/scipio/Options.swift @@ -28,7 +28,7 @@ struct BuildOptionGroup: ParsableArguments { @Flag(name: [.customLong("static")], 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 From f9646496628da351d2bcca8e6e4d237ab5e93d2f Mon Sep 17 00:00:00 2001 From: giginet Date: Fri, 31 May 2024 20:58:14 +0900 Subject: [PATCH 6/9] Use append instead --- Sources/ScipioKit/Producer/PIF/PIFGenerator.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/ScipioKit/Producer/PIF/PIFGenerator.swift b/Sources/ScipioKit/Producer/PIF/PIFGenerator.swift index 3a56fbb2..56674d6d 100644 --- a/Sources/ScipioKit/Producer/PIF/PIFGenerator.swift +++ b/Sources/ScipioKit/Producer/PIF/PIFGenerator.swift @@ -245,7 +245,8 @@ private struct PIFLibraryTargetModifier { settings[.SWIFT_INSTALL_OBJC_HEADER] = "YES" if frameworkType == .mergeable { - settings[.OTHER_LDFLAGS] = ["-Wl,-make_mergeable"] + settings[.OTHER_LDFLAGS, default: ["$(inherited)"]] + .append("-Wl,-make_mergeable") } appendExtraFlagsByBuildOptionsMatrix(to: &settings) From 1b59af9020bff105a802df5359ce5ef6e48c0e46 Mon Sep 17 00:00:00 2001 From: giginet Date: Fri, 31 May 2024 21:55:07 +0900 Subject: [PATCH 7/9] Add test case to generate Mergeable Library --- Tests/ScipioKitTests/RunnerTests.swift | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Tests/ScipioKitTests/RunnerTests.swift b/Tests/ScipioKitTests/RunnerTests.swift index 4454e548..f2f1b38f 100644 --- a/Tests/ScipioKitTests/RunnerTests.swift +++ b/Tests/ScipioKitTests/RunnerTests.swift @@ -519,6 +519,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( From 0565b50b94b30f0cefdded1db3a9fb84f6764358 Mon Sep 17 00:00:00 2001 From: giginet Date: Fri, 31 May 2024 21:56:25 +0900 Subject: [PATCH 8/9] Update to binary path --- Sources/scipio/scipio.docc/mergeable-library.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/scipio/scipio.docc/mergeable-library.md b/Sources/scipio/scipio.docc/mergeable-library.md index 51b118f4..271fdf3a 100644 --- a/Sources/scipio/scipio.docc/mergeable-library.md +++ b/Sources/scipio/scipio.docc/mergeable-library.md @@ -20,7 +20,7 @@ In general, mergeable frameworks will be about 2x bigger binary size than the no Mergeable frameworks have `LC_ATOM_INFO` load command in the binary. You can check it by `otool` command. ```shell -echo $(otool -l path/to/binary) | grep "LC_ATOM_INFO" +echo $(otool -l MyFramework.framework/MyFramework) | grep "LC_ATOM_INFO" ``` ## Limitation From cf373db4f4f0c9d4c0f6152e09deddff35b57386 Mon Sep 17 00:00:00 2001 From: giginet Date: Fri, 31 May 2024 21:57:54 +0900 Subject: [PATCH 9/9] Fix SwiftLint violation --- Tests/ScipioKitTests/RunnerTests.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/ScipioKitTests/RunnerTests.swift b/Tests/ScipioKitTests/RunnerTests.swift index f2f1b38f..db9263cf 100644 --- a/Tests/ScipioKitTests/RunnerTests.swift +++ b/Tests/ScipioKitTests/RunnerTests.swift @@ -519,7 +519,7 @@ final class RunnerTests: XCTestCase { ) } } - + func testMergeableLibrary() async throws { let runner = Runner( mode: .createPackage, @@ -537,10 +537,10 @@ final class RunnerTests: XCTestCase { frameworkOutputDir: .custom(frameworkOutputDir)) let xcFramework = frameworkOutputDir.appendingPathComponent("TestingPackage.xcframework") - + let executor = ProcessExecutor() - - for arch in ["ios-arm64",] { + + for arch in ["ios-arm64"] { let binaryPath = xcFramework .appendingPathComponent(arch) .appendingPathComponent("TestingPackage.framework") @@ -549,7 +549,7 @@ final class RunnerTests: XCTestCase { 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(