Skip to content

Commit

Permalink
Merge pull request #152 from ikesyo/keep-public-headers-structure-option
Browse files Browse the repository at this point in the history
Add an option for keeping public headers structure (subdirectories) when copying public headers and generating modulemap
  • Loading branch information
ikesyo authored Oct 24, 2024
2 parents 4c2cc39 + ead3d74 commit 578ce98
Show file tree
Hide file tree
Showing 14 changed files with 289 additions and 25 deletions.
3 changes: 3 additions & 0 deletions Sources/ScipioKit/BuildOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ struct BuildOptions: Hashable, Codable, Sendable {
extraFlags: ExtraFlags?,
extraBuildParameters: ExtraBuildParameters?,
enableLibraryEvolution: Bool,
keepPublicHeadersStructure: Bool,
customFrameworkModuleMapContents: Data?
) {
self.buildConfiguration = buildConfiguration
Expand All @@ -21,6 +22,7 @@ struct BuildOptions: Hashable, Codable, Sendable {
self.extraFlags = extraFlags
self.extraBuildParameters = extraBuildParameters
self.enableLibraryEvolution = enableLibraryEvolution
self.keepPublicHeadersStructure = keepPublicHeadersStructure
self.customFrameworkModuleMapContents = customFrameworkModuleMapContents
}

Expand All @@ -31,6 +33,7 @@ struct BuildOptions: Hashable, Codable, Sendable {
let extraFlags: ExtraFlags?
let extraBuildParameters: ExtraBuildParameters?
let enableLibraryEvolution: Bool
let keepPublicHeadersStructure: 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.
Expand Down
50 changes: 46 additions & 4 deletions Sources/ScipioKit/Producer/PIF/FrameworkBundleAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ import TSCBasic
/// This assembler just relocates framework components into the framework structure
struct FrameworkBundleAssembler {
private let frameworkComponents: FrameworkComponents
private let keepPublicHeadersStructure: Bool
private let outputDirectory: AbsolutePath
private let fileSystem: any FileSystem

private var frameworkBundlePath: AbsolutePath {
outputDirectory.appending(component: "\(frameworkComponents.frameworkName).framework")
}

init(frameworkComponents: FrameworkComponents, outputDirectory: AbsolutePath, fileSystem: some FileSystem) {
init(
frameworkComponents: FrameworkComponents,
keepPublicHeadersStructure: Bool,
outputDirectory: AbsolutePath,
fileSystem: some FileSystem
) {
self.frameworkComponents = frameworkComponents
self.keepPublicHeadersStructure = keepPublicHeadersStructure
self.outputDirectory = outputDirectory
self.fileSystem = fileSystem
}
Expand Down Expand Up @@ -73,11 +80,46 @@ struct FrameworkBundleAssembler {
try fileSystem.createDirectory(headerDir)

for header in headers {
try fileSystem.copy(
from: header,
to: headerDir.appending(component: header.basename)
if keepPublicHeadersStructure, let includeDir = frameworkComponents.includeDir {
try copyHeaderKeepingStructure(
header: header,
includeDir: includeDir,
into: headerDir
)
} else {
try fileSystem.copy(
from: header,
to: headerDir.appending(component: header.basename)
)
}
}
}

private func copyHeaderKeepingStructure(
header: AbsolutePath,
includeDir: AbsolutePath,
into headerDir: AbsolutePath
) throws {
let subdirectoryComponents: [String] = if header.dirname.hasPrefix(includeDir.pathString) {
header.dirname.dropFirst(includeDir.pathString.count)
.split(separator: "/")
.map(String.init)
} else {
[]
}

if !subdirectoryComponents.isEmpty {
try fileSystem.createDirectory(
headerDir.appending(components: subdirectoryComponents),
recursive: true
)
}
try fileSystem.copy(
from: header,
to: headerDir
.appending(components: subdirectoryComponents)
.appending(component: header.basename)
)
}

private func copyModules() throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ struct FrameworkComponents {
var binaryPath: AbsolutePath
var infoPlistPath: AbsolutePath
var swiftModulesPath: AbsolutePath?
var includeDir: AbsolutePath?
var publicHeaderPaths: Set<AbsolutePath>?
var bridgingHeaderPath: AbsolutePath?
var modulemapPath: AbsolutePath?
Expand Down Expand Up @@ -85,6 +86,7 @@ struct FrameworkComponentsCollector {
binaryPath: binaryPath,
infoPlistPath: infoPlistPath,
swiftModulesPath: swiftModulesPath,
includeDir: (buildProduct.target.underlying as? ScipioClangModule)?.includeDir.scipioAbsolutePath,
publicHeaderPaths: publicHeaders,
bridgingHeaderPath: bridgingHeaderPath,
modulemapPath: frameworkModuleMapPath,
Expand Down Expand Up @@ -113,7 +115,7 @@ struct FrameworkComponentsCollector {
let frameworkModuleMapPath = try modulemapGenerator.generate(
resolvedTarget: buildProduct.target,
sdk: sdk,
buildConfiguration: buildOptions.buildConfiguration
keepPublicHeadersStructure: buildOptions.keepPublicHeadersStructure
)
return frameworkModuleMapPath
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ struct FrameworkModuleMapGenerator {
private struct Context {
var resolvedTarget: ScipioResolvedModule
var sdk: SDK
var configuration: BuildConfiguration
var keepPublicHeadersStructure: Bool
}

private var packageLocator: any PackageLocator
Expand All @@ -30,8 +30,16 @@ struct FrameworkModuleMapGenerator {
self.fileSystem = fileSystem
}

func generate(resolvedTarget: ScipioResolvedModule, sdk: SDK, buildConfiguration: BuildConfiguration) throws -> AbsolutePath? {
let context = Context(resolvedTarget: resolvedTarget, sdk: sdk, configuration: buildConfiguration)
func generate(
resolvedTarget: ScipioResolvedModule,
sdk: SDK,
keepPublicHeadersStructure: Bool
) throws -> AbsolutePath? {
let context = Context(
resolvedTarget: resolvedTarget,
sdk: sdk,
keepPublicHeadersStructure: keepPublicHeadersStructure
)

if let clangTarget = resolvedTarget.underlying as? ScipioClangModule {
switch clangTarget.moduleMapType {
Expand Down Expand Up @@ -66,7 +74,13 @@ struct FrameworkModuleMapGenerator {
.joined()
case .umbrellaDirectory(let directoryPath):
let headers = try walkDirectoryContents(of: directoryPath.scipioAbsolutePath)
let declarations = headers.map { " header \"\($0)\"" }
let declarations = headers.map { header in
generateHeaderEntry(
for: header,
of: directoryPath.scipioAbsolutePath,
keepPublicHeadersStructure: context.keepPublicHeadersStructure
)
}

return ([
"framework module \(context.resolvedTarget.c99name) {",
Expand All @@ -92,17 +106,38 @@ struct FrameworkModuleMapGenerator {
}
}

private func walkDirectoryContents(of directoryPath: AbsolutePath) throws -> Set<String> {
private func walkDirectoryContents(of directoryPath: AbsolutePath) throws -> Set<AbsolutePath> {
try fileSystem.getDirectoryContents(directoryPath).reduce(into: Set()) { headers, file in
let path = directoryPath.appending(component: file)
if fileSystem.isDirectory(path) {
headers.formUnion(try walkDirectoryContents(of: path))
} else if file.hasSuffix(".h") {
headers.insert(file)
headers.insert(path)
}
}
}

private func generateHeaderEntry(
for header: AbsolutePath,
of directoryPath: AbsolutePath,
keepPublicHeadersStructure: Bool
) -> String {
if keepPublicHeadersStructure {
let subdirectoryComponents: [String] = if header.dirname.hasPrefix(directoryPath.pathString) {
header.dirname.dropFirst(directoryPath.pathString.count)
.split(separator: "/")
.map(String.init)
} else {
[]
}

let path = (subdirectoryComponents + [header.basename]).joined(separator: "/")
return " header \"\(path)\""
} else {
return " header \"\(header.basename)\""
}
}

private func generateLinkSection(context: Context) -> [String] {
context.resolvedTarget.dependencies
.compactMap(\.module?.c99name)
Expand Down
1 change: 1 addition & 0 deletions Sources/ScipioKit/Producer/PIF/XCBuildClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ struct XCBuildClient {

let assembler = FrameworkBundleAssembler(
frameworkComponents: components,
keepPublicHeadersStructure: buildOptions.keepPublicHeadersStructure,
outputDirectory: frameworkOutputDir,
fileSystem: fileSystem
)
Expand Down
20 changes: 19 additions & 1 deletion Sources/ScipioKit/Runner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ extension Runner {
public var extraFlags: ExtraFlags?
public var extraBuildParameters: [String: String]?
public var enableLibraryEvolution: Bool
/// For clang target, whether to keep subdirectories in publicHeadersPath or not when copying public headers to Headers directory.
/// If this is false, public headers are copied to Headers directory flattened (default).
public var keepPublicHeadersStructure: Bool
/// An option indicates use custom modulemaps for distributionb
public var frameworkModuleMapGenerationPolicy: FrameworkModuleMapGenerationPolicy

Expand All @@ -138,6 +141,7 @@ extension Runner {
extraFlags: ExtraFlags? = nil,
extraBuildParameters: [String: String]? = nil,
enableLibraryEvolution: Bool = false,
keepPublicHeadersStructure: Bool = false,
frameworkModuleMapGenerationPolicy: FrameworkModuleMapGenerationPolicy = .autoGenerated
) {
self.buildConfiguration = buildConfiguration
Expand All @@ -148,6 +152,7 @@ extension Runner {
self.extraFlags = extraFlags
self.extraBuildParameters = extraBuildParameters
self.enableLibraryEvolution = enableLibraryEvolution
self.keepPublicHeadersStructure = keepPublicHeadersStructure
self.frameworkModuleMapGenerationPolicy = frameworkModuleMapGenerationPolicy
}
}
Expand All @@ -160,6 +165,9 @@ extension Runner {
public var extraFlags: ExtraFlags?
public var extraBuildParameters: [String: String]?
public var enableLibraryEvolution: Bool?
/// For clang target, whether to keep subdirectories in publicHeadersPath or not when copying public headers to Headers directory.
/// If this is false or nil, public headers are copied to Headers directory flattened (default).
public var keepPublicHeadersStructure: Bool?
public var frameworkModuleMapGenerationPolicy: FrameworkModuleMapGenerationPolicy?

public init(
Expand All @@ -171,6 +179,7 @@ extension Runner {
extraFlags: ExtraFlags? = nil,
extraBuildParameters: [String: String]? = nil,
enableLibraryEvolution: Bool? = nil,
keepPublicHeadersStructure: Bool? = nil,
frameworkModuleMapGenerationPolicy: FrameworkModuleMapGenerationPolicy? = nil
) {
self.buildConfiguration = buildConfiguration
Expand All @@ -181,6 +190,7 @@ extension Runner {
self.extraBuildParameters = extraBuildParameters
self.extraFlags = extraFlags
self.enableLibraryEvolution = enableLibraryEvolution
self.keepPublicHeadersStructure = keepPublicHeadersStructure
self.frameworkModuleMapGenerationPolicy = frameworkModuleMapGenerationPolicy
}
}
Expand Down Expand Up @@ -295,6 +305,7 @@ extension Runner.Options.BuildOptions {
extraFlags: extraFlags,
extraBuildParameters: extraBuildParameters,
enableLibraryEvolution: enableLibraryEvolution,
keepPublicHeadersStructure: keepPublicHeadersStructure,
customFrameworkModuleMapContents: customFrameworkModuleMapContents
)
}
Expand Down Expand Up @@ -339,7 +350,14 @@ extension Runner.Options.BuildOptions {
extraFlags: mergedExtraFlags,
extraBuildParameters: mergedExtraBuildParameters,
enableLibraryEvolution: fetch(\.enableLibraryEvolution, by: \.enableLibraryEvolution),
frameworkModuleMapGenerationPolicy: fetch(\.frameworkModuleMapGenerationPolicy, by: \.frameworkModuleMapGenerationPolicy)
keepPublicHeadersStructure: fetch(
\.keepPublicHeadersStructure,
by: \.keepPublicHeadersStructure
),
frameworkModuleMapGenerationPolicy: fetch(
\.frameworkModuleMapGenerationPolicy,
by: \.frameworkModuleMapGenerationPolicy
)
)
}
}
Expand Down
31 changes: 18 additions & 13 deletions Tests/ScipioKitTests/CacheSystemTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,22 @@ final class CacheSystemTests: XCTestCase {
"""

func testEncodeCacheKey() throws {
let cacheKey = SwiftPMCacheKey(targetName: "MyTarget",
pin: .revision("111111111"),
buildOptions: .init(buildConfiguration: .release,
isDebugSymbolsEmbedded: false,
frameworkType: .dynamic,
sdks: [.iOS],
extraFlags: .init(swiftFlags: ["-D", "SOME_FLAG"]),
extraBuildParameters: ["SWIFT_OPTIMIZATION_LEVEL": "-Osize"],
enableLibraryEvolution: true,
customFrameworkModuleMapContents: Data(customModuleMap.utf8)
),
clangVersion: "clang-1400.0.29.102",
xcodeVersion: .init(xcodeVersion: "15.4", xcodeBuildVersion: "15F31d")
let cacheKey = SwiftPMCacheKey(
targetName: "MyTarget",
pin: .revision("111111111"),
buildOptions: .init(
buildConfiguration: .release,
isDebugSymbolsEmbedded: false,
frameworkType: .dynamic,
sdks: [.iOS],
extraFlags: .init(swiftFlags: ["-D", "SOME_FLAG"]),
extraBuildParameters: ["SWIFT_OPTIMIZATION_LEVEL": "-Osize"],
enableLibraryEvolution: true,
keepPublicHeadersStructure: false,
customFrameworkModuleMapContents: Data(customModuleMap.utf8)
),
clangVersion: "clang-1400.0.29.102",
xcodeVersion: .init(xcodeVersion: "15.4", xcodeBuildVersion: "15F31d")
)
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys, .prettyPrinted]
Expand All @@ -55,6 +58,7 @@ final class CacheSystemTests: XCTestCase {
},
"frameworkType" : "dynamic",
"isDebugSymbolsEmbedded" : false,
"keepPublicHeadersStructure" : false,
"sdks" : [
"iOS"
]
Expand Down Expand Up @@ -113,6 +117,7 @@ final class CacheSystemTests: XCTestCase {
extraFlags: nil,
extraBuildParameters: nil,
enableLibraryEvolution: false,
keepPublicHeadersStructure: false,
customFrameworkModuleMapContents: nil
)
)
Expand Down
Loading

0 comments on commit 578ce98

Please sign in to comment.