Skip to content

Commit

Permalink
Merge pull request #127 from giginet/mergeable-library-support
Browse files Browse the repository at this point in the history
Introduce Mergeable Library Support
  • Loading branch information
giginet authored Jun 4, 2024
2 parents d029413 + cf373db commit 2d76897
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 17 deletions.
1 change: 1 addition & 0 deletions Sources/ScipioKit/BuildOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 6 additions & 1 deletion Sources/ScipioKit/Producer/PIF/PIFGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions Sources/scipio/CommandType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 5 additions & 5 deletions Sources/scipio/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -43,8 +47,4 @@ struct BuildOptionGroup: ParsableArguments {
var overwrite: Bool = false
}

extension BuildOptionGroup {
var frameworkType: FrameworkType {
shouldBuildStaticFramework ? .static : .dynamic
}
}
extension FrameworkType: ExpressibleByArgument { }
28 changes: 28 additions & 0 deletions Sources/scipio/scipio.docc/mergeable-library.md
Original file line number Diff line number Diff line change
@@ -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.
20 changes: 10 additions & 10 deletions Sources/scipio/scipio.docc/prepare-cache-for-applications.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Sources/scipio/scipio.docc/scipio.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ A new build tool to generate XCFramework from Swift Package
- <doc:cache-system>
- <doc:build-pipeline>
- <doc:using-s3-storage>

- <doc:mergeable-library>
39 changes: 39 additions & 0 deletions Tests/ScipioKitTests/RunnerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 2d76897

Please sign in to comment.