Skip to content

Commit

Permalink
Merge pull request #138 from giginet/custom-module-map
Browse files Browse the repository at this point in the history
Add feature to pass the custom modulemap
  • Loading branch information
giginet authored Aug 1, 2024
2 parents 379ec10 + cca2a67 commit c044248
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 29 deletions.
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
)
}

0 comments on commit c044248

Please sign in to comment.