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
Expand Up @@ -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
Expand All @@ -18,6 +19,7 @@ struct BuildOptions: Hashable, Codable, Sendable {
self.extraFlags = extraFlags
self.extraBuildParameters = extraBuildParameters
self.enableLibraryEvolution = enableLibraryEvolution
self.customFrameworkModuleMapContents = customFrameworkModuleMapContents
}

let buildConfiguration: BuildConfiguration
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
45 changes: 32 additions & 13 deletions Sources/ScipioKit/Producer/PIF/FrameworkComponentsCollector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion Sources/ScipioKit/Producer/PIF/ModuleMapGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 28 additions & 12 deletions Sources/ScipioKit/Runner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -149,6 +152,7 @@ extension Runner {
self.extraFlags = extraFlags
self.extraBuildParameters = extraBuildParameters
self.enableLibraryEvolution = enableLibraryEvolution
self.frameworkModuleMapGenerationPolicy = frameworkModuleMapGenerationPolicy
}
}
public struct TargetBuildOptions {
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -179,6 +185,7 @@ extension Runner {
self.extraBuildParameters = extraBuildParameters
self.extraFlags = extraFlags
self.enableLibraryEvolution = enableLibraryEvolution
self.frameworkModuleMapGenerationPolicy = frameworkModuleMapGenerationPolicy
}
}

Expand Down Expand Up @@ -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
)
}

Expand Down Expand Up @@ -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)
}
}
Expand Down
13 changes: 13 additions & 0 deletions Sources/scipio/scipio.docc/build-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 10 additions & 1 deletion Tests/ScipioKitTests/CacheSystemTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
)
Expand All @@ -41,6 +49,7 @@ InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault
{
"buildOptions" : {
"buildConfiguration" : "release",
"customFrameworkModuleMapContents" : "ZnJhbWV3b3JrIG1vZHVsZSBNeVRhcmdldCB7CiAgICB1bWJyZWxsYSBoZWFkZXIgInVtYnJlbGxhLmgiCiAgICBleHBvcnQgKgp9",
"enableLibraryEvolution" : true,
"extraBuildParameters" : {
"SWIFT_OPTIMIZATION_LEVEL" : "-Osize"
Expand Down
3 changes: 2 additions & 1 deletion Tests/ScipioKitTests/RunnerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,7 @@ extension BuildOptions {
sdks: [.iOS],
extraFlags: nil,
extraBuildParameters: nil,
enableLibraryEvolution: true
enableLibraryEvolution: true,
customFrameworkModuleMapContents: nil
)
}
Loading