Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature to pass the custom modulemap #138

Merged
merged 11 commits into from
Aug 1, 2024
16 changes: 15 additions & 1 deletion Sources/ScipioKit/BuildOptions.swift
Original file line number Diff line number Diff line change
@@ -9,7 +9,8 @@ struct BuildOptions: Hashable, Codable, Sendable {
sdks: Set<SDK>,
extraFlags: ExtraFlags?,
extraBuildParameters: ExtraBuildParameters?,
enableLibraryEvolution: Bool
enableLibraryEvolution: Bool,
customFrameworkModuleMapContents: Data?
) {
self.buildConfiguration = buildConfiguration
self.isDebugSymbolsEmbedded = isDebugSymbolsEmbedded
@@ -18,6 +19,7 @@ struct BuildOptions: Hashable, Codable, Sendable {
self.extraFlags = extraFlags
self.extraBuildParameters = extraBuildParameters
self.enableLibraryEvolution = enableLibraryEvolution
self.customFrameworkModuleMapContents = customFrameworkModuleMapContents
}

let buildConfiguration: BuildConfiguration
@@ -27,6 +29,10 @@ struct BuildOptions: Hashable, Codable, Sendable {
let extraFlags: ExtraFlags?
let extraBuildParameters: ExtraBuildParameters?
let enableLibraryEvolution: Bool
/// A custom framework modulemap contents
/// - Note: It have to store the actual file contents rather than its path,
/// because the cache key should change when the file contents change.
let customFrameworkModuleMapContents: Data?
}

public struct ExtraFlags: Hashable, Codable, Sendable {
@@ -48,6 +54,14 @@ public struct ExtraFlags: Hashable, Codable, Sendable {
}
}

/// A model indicates modulemap generation policy
public enum FrameworkModuleMapGenerationPolicy: Codable, Sendable, Hashable, Equatable {
// Generate modulemap automatically
case autoGenerated
// Pass the custom modulemap for each distributed framework
case custom(URL)
}

public typealias ExtraBuildParameters = [String: String]

public enum BuildConfiguration: String, Codable, Sendable {
45 changes: 32 additions & 13 deletions Sources/ScipioKit/Producer/PIF/FrameworkComponentsCollector.swift
Original file line number Diff line number Diff line change
@@ -48,19 +48,13 @@ struct FrameworkComponentsCollector {
}

func collectComponents(sdk: SDK) throws -> FrameworkComponents {
let modulemapGenerator = ModuleMapGenerator(
descriptionPackage: descriptionPackage,
fileSystem: fileSystem
)

// xcbuild automatically generates modulemaps. However, these are not for frameworks.
// Therefore, it's difficult to contain this generated modulemaps to final XCFrameworks.
// So generate modulemap for frameworks manually
let frameworkModuleMapPath = try modulemapGenerator.generate(
resolvedTarget: buildProduct.target,
sdk: sdk,
buildConfiguration: buildOptions.buildConfiguration
)
let frameworkModuleMapPath: AbsolutePath?
if let customFrameworkModuleMapContents = buildOptions.customFrameworkModuleMapContents {
logger.info("📝 Using custom modulemap for \(buildProduct.target.name)(\(sdk.displayName))")
frameworkModuleMapPath = try copyModuleMapContentsToBuildArtifacts(customFrameworkModuleMapContents)
} else {
frameworkModuleMapPath = try generateFrameworkModuleMap()
}

let targetName = buildProduct.target.c99name
let generatedFrameworkPath = generatedFrameworkPath()
@@ -99,6 +93,31 @@ struct FrameworkComponentsCollector {
return components
}

/// Copy content data to the build artifacts
private func copyModuleMapContentsToBuildArtifacts(_ data: Data) throws -> ScipioAbsolutePath {
let generatedModuleMapPath = try descriptionPackage.generatedModuleMapPath(of: buildProduct.target, sdk: sdk)

try fileSystem.writeFileContents(generatedModuleMapPath.spmAbsolutePath, data: data)
return generatedModuleMapPath
}

private func generateFrameworkModuleMap() throws -> AbsolutePath? {
let modulemapGenerator = FrameworkModuleMapGenerator(
descriptionPackage: descriptionPackage,
fileSystem: fileSystem
)

// xcbuild automatically generates modulemaps. However, these are not for frameworks.
// Therefore, it's difficult to contain this generated modulemaps to final XCFrameworks.
// So generate modulemap for frameworks manually
let frameworkModuleMapPath = try modulemapGenerator.generate(
resolvedTarget: buildProduct.target,
sdk: sdk,
buildConfiguration: buildOptions.buildConfiguration
)
return frameworkModuleMapPath
}

private func generatedFrameworkPath() -> AbsolutePath {
descriptionPackage.productsDirectory(
buildConfiguration: buildOptions.buildConfiguration,
3 changes: 2 additions & 1 deletion Sources/ScipioKit/Producer/PIF/ModuleMapGenerator.swift
Original file line number Diff line number Diff line change
@@ -3,7 +3,8 @@ import TSCBasic
import PackageGraph
import PackageModel

struct ModuleMapGenerator {
// A generator to generate modulemaps which are distributed in the XCFramework
struct FrameworkModuleMapGenerator {
private struct Context {
var resolvedTarget: ScipioResolvedModule
var sdk: SDK
40 changes: 28 additions & 12 deletions Sources/ScipioKit/Runner.swift
Original file line number Diff line number Diff line change
@@ -84,7 +84,7 @@ public struct Runner {
throw Error.invalidPackage(packageDirectory)
}

let buildOptions = options.buildOptionsContainer.makeBuildOptions(descriptionPackage: descriptionPackage)
let buildOptions = try options.buildOptionsContainer.makeBuildOptions(descriptionPackage: descriptionPackage)
guard !buildOptions.sdks.isEmpty else {
throw Error.platformNotSpecified
}
@@ -95,7 +95,7 @@ public struct Runner {

try fileSystem.createDirectory(outputDir.absolutePath, recursive: true)

let buildOptionsMatrix = options.buildOptionsContainer.makeBuildOptionsMatrix(descriptionPackage: descriptionPackage)
let buildOptionsMatrix = try options.buildOptionsContainer.makeBuildOptionsMatrix(descriptionPackage: descriptionPackage)

let producer = FrameworkProducer(
descriptionPackage: descriptionPackage,
@@ -130,6 +130,8 @@ extension Runner {
public var extraFlags: ExtraFlags?
public var extraBuildParameters: [String: String]?
public var enableLibraryEvolution: Bool
/// An option indicates use custom modulemaps for distributionb
public var frameworkModuleMapGenerationPolicy: FrameworkModuleMapGenerationPolicy

public init(
buildConfiguration: BuildConfiguration = .release,
@@ -139,7 +141,8 @@ extension Runner {
frameworkType: FrameworkType = .dynamic,
extraFlags: ExtraFlags? = nil,
extraBuildParameters: [String: String]? = nil,
enableLibraryEvolution: Bool = false
enableLibraryEvolution: Bool = false,
frameworkModuleMapGenerationPolicy: FrameworkModuleMapGenerationPolicy = .autoGenerated
) {
self.buildConfiguration = buildConfiguration
self.platforms = platforms
@@ -149,6 +152,7 @@ extension Runner {
self.extraFlags = extraFlags
self.extraBuildParameters = extraBuildParameters
self.enableLibraryEvolution = enableLibraryEvolution
self.frameworkModuleMapGenerationPolicy = frameworkModuleMapGenerationPolicy
}
}
public struct TargetBuildOptions {
@@ -160,6 +164,7 @@ extension Runner {
public var extraFlags: ExtraFlags?
public var extraBuildParameters: [String: String]?
public var enableLibraryEvolution: Bool?
public var frameworkModuleMapGenerationPolicy: FrameworkModuleMapGenerationPolicy?

public init(
buildConfiguration: BuildConfiguration? = nil,
@@ -169,7 +174,8 @@ extension Runner {
frameworkType: FrameworkType? = nil,
extraFlags: ExtraFlags? = nil,
extraBuildParameters: [String: String]? = nil,
enableLibraryEvolution: Bool? = nil
enableLibraryEvolution: Bool? = nil,
frameworkModuleMapGenerationPolicy: FrameworkModuleMapGenerationPolicy? = nil
) {
self.buildConfiguration = buildConfiguration
self.platforms = platforms
@@ -179,6 +185,7 @@ extension Runner {
self.extraBuildParameters = extraBuildParameters
self.extraFlags = extraFlags
self.enableLibraryEvolution = enableLibraryEvolution
self.frameworkModuleMapGenerationPolicy = frameworkModuleMapGenerationPolicy
}
}

@@ -268,20 +275,28 @@ extension Runner.Options.Platform {
}

extension Runner.Options.BuildOptions {
fileprivate func makeBuildOptions(descriptionPackage: DescriptionPackage) -> BuildOptions {
fileprivate func makeBuildOptions(descriptionPackage: DescriptionPackage, fileSystem: any FileSystem = localFileSystem) throws -> BuildOptions {
let sdks = detectSDKsToBuild(
platforms: platforms,
package: descriptionPackage,
isSimulatorSupported: isSimulatorSupported
)
let customFrameworkModuleMapContents: Data? = switch frameworkModuleMapGenerationPolicy {
case .autoGenerated:
nil
case .custom(let url):
try fileSystem.readFileContents(url.absolutePath.spmAbsolutePath)
}

return BuildOptions(
buildConfiguration: buildConfiguration,
isDebugSymbolsEmbedded: isDebugSymbolsEmbedded,
frameworkType: frameworkType,
sdks: Set(sdks),
extraFlags: extraFlags,
extraBuildParameters: extraBuildParameters,
enableLibraryEvolution: enableLibraryEvolution
enableLibraryEvolution: enableLibraryEvolution,
customFrameworkModuleMapContents: customFrameworkModuleMapContents
)
}

@@ -324,19 +339,20 @@ extension Runner.Options.BuildOptions {
frameworkType: fetch(\.frameworkType, by: \.frameworkType),
extraFlags: mergedExtraFlags,
extraBuildParameters: mergedExtraBuildParameters,
enableLibraryEvolution: fetch(\.enableLibraryEvolution, by: \.enableLibraryEvolution)
enableLibraryEvolution: fetch(\.enableLibraryEvolution, by: \.enableLibraryEvolution),
frameworkModuleMapGenerationPolicy: fetch(\.frameworkModuleMapGenerationPolicy, by: \.frameworkModuleMapGenerationPolicy)
)
}
}

extension Runner.Options.BuildOptionsContainer {
fileprivate func makeBuildOptions(descriptionPackage: DescriptionPackage) -> BuildOptions {
baseBuildOptions.makeBuildOptions(descriptionPackage: descriptionPackage)
fileprivate func makeBuildOptions(descriptionPackage: DescriptionPackage) throws -> BuildOptions {
try baseBuildOptions.makeBuildOptions(descriptionPackage: descriptionPackage)
}

fileprivate func makeBuildOptionsMatrix(descriptionPackage: DescriptionPackage) -> [String: BuildOptions] {
buildOptionsMatrix.mapValues { runnerOptions in
baseBuildOptions.overridden(by: runnerOptions)
fileprivate func makeBuildOptionsMatrix(descriptionPackage: DescriptionPackage) throws -> [String: BuildOptions] {
try buildOptionsMatrix.mapValues { runnerOptions in
try baseBuildOptions.overridden(by: runnerOptions)
.makeBuildOptions(descriptionPackage: descriptionPackage)
}
}
13 changes: 13 additions & 0 deletions Sources/scipio/scipio.docc/build-pipeline.md
Original file line number Diff line number Diff line change
@@ -200,6 +200,19 @@ This matrix can override build options of the specific targets to base build opt

Of-course, you are also pass `extraFlags` or `extraBuildParameters` per product.

#### Passing custom modulemap

Scipio attempt to generate modulemap automatically distributed in the XCFramework. However, if you want to custom modulemap, you can pass `frameworkModuleMapGenerationPolicy` to the build options. In general, this option should be used `buildOptionsMatrix`.

```swift
buildOptionsMatrix: [
"MyResourceFramework": .init(
frameworkModuleMapGenerationPolicy: .custom(URL(filePath: "path/to/module.modulemap"))
),
]

```

### Use Custom Cache Storage

In CLI version of Scipio, you can only use Project cache or Local disk cache as a cache storage backend.
11 changes: 10 additions & 1 deletion Tests/ScipioKitTests/CacheSystemTests.swift
Original file line number Diff line number Diff line change
@@ -9,6 +9,12 @@ Target: arm64-apple-darwin21.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
"""
private let customModuleMap = """
framework module MyTarget {
umbrella header "umbrella.h"
export *
}
"""

func testParseClangVersion() async throws {
let hook = { arguments in
@@ -29,7 +35,9 @@ InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault
sdks: [.iOS],
extraFlags: .init(swiftFlags: ["-D", "SOME_FLAG"]),
extraBuildParameters: ["SWIFT_OPTIMIZATION_LEVEL": "-Osize"],
enableLibraryEvolution: true),
enableLibraryEvolution: true,
customFrameworkModuleMapContents: Data(customModuleMap.utf8)
),
clangVersion: "clang-1400.0.29.102",
xcodeVersion: .init(xcodeVersion: "15.4", xcodeBuildVersion: "15F31d")
)
@@ -41,6 +49,7 @@ InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault
{
"buildOptions" : {
"buildConfiguration" : "release",
"customFrameworkModuleMapContents" : "ZnJhbWV3b3JrIG1vZHVsZSBNeVRhcmdldCB7CiAgICB1bWJyZWxsYSBoZWFkZXIgInVtYnJlbGxhLmgiCiAgICBleHBvcnQgKgp9",
"enableLibraryEvolution" : true,
"extraBuildParameters" : {
"SWIFT_OPTIMIZATION_LEVEL" : "-Osize"
3 changes: 2 additions & 1 deletion Tests/ScipioKitTests/RunnerTests.swift
Original file line number Diff line number Diff line change
@@ -715,6 +715,7 @@ extension BuildOptions {
sdks: [.iOS],
extraFlags: nil,
extraBuildParameters: nil,
enableLibraryEvolution: true
enableLibraryEvolution: true,
customFrameworkModuleMapContents: nil
)
}