Skip to content

Commit e93a62d

Browse files
committed
Merge pull request #219 from aciidb0mb3r/modulemap_generation
Auto generate module map file for C libs
2 parents 389ce07 + c47c6b8 commit e93a62d

File tree

27 files changed

+208
-60
lines changed

27 files changed

+208
-60
lines changed

Fixtures/ClangModules/CLibraryFlat/include/module.modulemap

Lines changed: 0 additions & 5 deletions
This file was deleted.

Fixtures/ClangModules/CLibrarySources/Sources/include/module.modulemap

Lines changed: 0 additions & 5 deletions
This file was deleted.

Fixtures/ClangModules/CLibraryiquote/Sources/Bar/include/module.modulemap

Lines changed: 0 additions & 5 deletions
This file was deleted.

Fixtures/ClangModules/CLibraryiquote/Sources/Foo/include/module.modulemap

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import PackageDescription
2+
3+
let package = Package(
4+
name: "ModuleMapGenerationCases",
5+
targets: [
6+
Target(name: "Baz", dependencies: ["FlatInclude", "UmbrellaHeader", "UmbellaModuleNameInclude", "UmbrellaHeaderFlat"])]
7+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import UmbellaModuleNameInclude
2+
import FlatInclude
3+
import UmbrellaHeader
4+
import UmbrellaHeaderFlat
5+
6+
let _ = foo()
7+
let _ = bar()
8+
let _ = jaz()
9+
let _ = umbrellaHeaderFlat()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "include/FlatIncludeHeader.h"
2+
3+
int bar() {
4+
int a = 6;
5+
int b = a;
6+
a = b;
7+
return a;
8+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int bar();
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
int noDir() {
3+
int a = 6;
4+
int b = a;
5+
a = b;
6+
return a;
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "include/UmbellaModuleNameInclude/Paz.h"
2+
3+
int jaz() {
4+
int a = 6;
5+
int b = a;
6+
a = b;
7+
return a;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int jaz();
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "include/UmbrellaHeader/UmbrellaHeader.h"
2+
3+
int foo() {
4+
int a = 5;
5+
int b = a;
6+
a = b;
7+
return a;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int foo();
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "include/UmbrellaHeaderFlat.h"
2+
3+
int umbrellaHeaderFlat() {
4+
int a = 5;
5+
int b = a;
6+
a = b;
7+
return a;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int umbrellaHeaderFlat();

Fixtures/ClangModules/SwiftCMixed/Sources/SeaLib/include/module.modulemap

Lines changed: 0 additions & 5 deletions
This file was deleted.

Fixtures/DependencyResolution/External/CUsingCDep/Bar/Sources/SeaLover/include/module.modulemap

Lines changed: 0 additions & 5 deletions
This file was deleted.

Fixtures/DependencyResolution/External/CUsingCDep/Foo/include/module.modulemap

Lines changed: 0 additions & 5 deletions
This file was deleted.

Fixtures/DependencyResolution/External/SimpleCDep/Foo/include/module.modulemap

Lines changed: 0 additions & 5 deletions
This file was deleted.

Sources/Build/Buildable.swift

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,44 @@
99
*/
1010

1111
import PackageType
12+
import struct Utility.Path
1213

1314
protocol Buildable {
1415
var targetName: String { get }
1516
var isTest: Bool { get }
1617
}
1718

19+
extension CModule {
20+
///Returns the build directory path of a CModule
21+
func buildDirectory(prefix: String) -> String {
22+
return Path.join(prefix, "\(c99name).build")
23+
}
24+
}
25+
1826
extension Module: Buildable {
1927
var isTest: Bool {
2028
return self is TestModule
2129
}
2230

23-
var Xcc: [String] {
31+
func XccFlags(prefix: String) -> [String] {
2432
return recursiveDependencies.flatMap { module -> [String] in
25-
if let module = module as? CModule {
33+
if let module = module as? ClangModule {
34+
///For ClangModule we check if there is a user provided module map
35+
///otherwise we return with path of generated one.
36+
///We will fail before this is ever called if there is no module map.
37+
///FIXME: The user provided modulemap should be copied to build dir
38+
///but that requires copying the complete include dir because it'll
39+
///mostly likely contain relative paths.
40+
///FIXME: This is already computed when trying to generate modulemap
41+
///in ClangModule's `generateModuleMap(inDir wd: String)`
42+
///there shouldn't be need to redo this but is difficult in
43+
///current architecture
44+
if module.moduleMapPath.isFile {
45+
return ["-Xcc", "-fmodule-map-file=\(module.moduleMapPath)"]
46+
}
47+
let genModuleMap = Path.join(module.buildDirectory(prefix), module.moduleMap)
48+
return ["-Xcc", "-fmodule-map-file=\(genModuleMap)"]
49+
} else if let module = module as? CModule {
2650
return ["-Xcc", "-fmodule-map-file=\(module.moduleMapPath)"]
2751
} else {
2852
return []

Sources/Build/Command.compile(ClangModule).swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,14 @@ private extension Sources {
6767
}
6868

6969
extension Command {
70-
static func compile(clangModule module: ClangModule, externalModules: Set<Module>, configuration conf: Configuration, prefix: String, CC: String) -> ([Command], Command) {
70+
static func compile(clangModule module: ClangModule, externalModules: Set<Module>, configuration conf: Configuration, prefix: String, CC: String) throws -> ([Command], Command) {
7171

72-
let wd = Path.join(prefix, "\(module.c99name).build")
72+
let wd = module.buildDirectory(prefix)
73+
74+
if module.type == .Library {
75+
try module.generateModuleMap(inDir: wd)
76+
}
77+
7378
let mkdir = Command.createDirectory(wd)
7479

7580
///------------------------------ Compile -----------------------------------------

Sources/Build/Command.compile(SwiftModule).swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import Utility
1414
extension Command {
1515
static func compile(swiftModule module: SwiftModule, configuration conf: Configuration, prefix: String, otherArgs: [String], SWIFT_EXEC: String) throws -> (Command, [Command]) {
1616

17-
let otherArgs = otherArgs + module.Xcc
17+
let otherArgs = otherArgs + module.XccFlags(prefix)
1818

1919
func cmd(tool: ToolProtocol) -> Command {
2020
return Command(node: module.targetName, tool: tool)

Sources/Build/Command.link().swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ extension Command {
8181
let main = Path.join(testDirectory, "LinuxMain.swift")
8282
args.append(main)
8383
for module in product.modules {
84-
args += module.Xcc
84+
args += module.XccFlags(prefix)
8585
}
8686
args.append("-emit-executable")
8787
args += ["-I", prefix]

Sources/Build/describe().swift

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,7 @@ public func describe(prefix: String, _ conf: Configuration, _ modules: [Module],
4343
targets.append(compile, for: module)
4444

4545
case let module as ClangModule:
46-
//FIXME: Generate modulemaps if possible
47-
//Since we're not generating modulemaps currently we'll just emit empty module map file
48-
//if it not present
49-
if module.type == .Library && !module.moduleMapPath.isFile {
50-
try POSIX.mkdir(module.moduleMapPath.parentDirectory)
51-
try fopen(module.moduleMapPath, mode: .Write) { fp in
52-
try fputs("\n", fp)
53-
}
54-
}
55-
56-
let (compile, mkdir) = Command.compile(clangModule: module, externalModules: externalModules, configuration: conf, prefix: prefix, CC: CC)
46+
let (compile, mkdir) = try Command.compile(clangModule: module, externalModules: externalModules, configuration: conf, prefix: prefix, CC: CC)
5747
commands += compile
5848
commands.append(mkdir)
5949
targets.main.cmds += compile

Sources/Build/misc.swift

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
import func POSIX.getenv
1212
import func POSIX.popen
13+
import func POSIX.mkdir
14+
import func POSIX.fopen
15+
import func libc.fclose
1316
import PackageType
1417
import Utility
1518

@@ -29,8 +32,104 @@ func platformFrameworksPath() throws -> String {
2932
}
3033

3134
extension CModule {
35+
36+
var moduleMap: String {
37+
return "module.modulemap"
38+
}
39+
3240
var moduleMapPath: String {
33-
return Path.join(path, "module.modulemap")
41+
return Path.join(path, moduleMap)
42+
}
43+
}
44+
45+
extension ClangModule {
46+
47+
public enum ModuleMapError: ErrorProtocol {
48+
case UnsupportedIncludeLayoutForModule(String)
49+
}
50+
51+
///FIXME: we recompute the generated modulemap's path
52+
///when building swift modules in `XccFlags(prefix: String)`
53+
///there shouldn't be need to redo this there but is difficult
54+
///in current architecture
55+
public func generateModuleMap(inDir wd: String) throws {
56+
57+
///Return if module map is already present
58+
guard !moduleMapPath.isFile else {
59+
return
60+
}
61+
62+
let includeDir = path
63+
64+
///Warn and return if no include directory
65+
guard includeDir.isDirectory else {
66+
print("warning: No include directory found, a library can not be imported without any public headers.")
67+
return
68+
}
69+
70+
let walked = walk(includeDir, recursively: false).map{$0}
71+
72+
let files = walked.filter{$0.isFile && $0.hasSuffix(".h")}
73+
let dirs = walked.filter{$0.isDirectory}
74+
75+
///We generate modulemap for a C module `foo` if:
76+
///* `umbrella header "path/to/include/foo/foo.h"` exists and `foo` is the only
77+
/// directory under include directory
78+
///* `umbrella header "path/to/include/foo.h"` exists and include contains no other
79+
/// directory
80+
///* `umbrella "path/to/include"` in all other cases
81+
82+
let umbrellaHeaderFlat = Path.join(includeDir, "\(c99name).h")
83+
if umbrellaHeaderFlat.isFile {
84+
guard dirs.isEmpty else { throw ModuleMapError.UnsupportedIncludeLayoutForModule(name) }
85+
try createModuleMap(inDir: wd, type: .Header(umbrellaHeaderFlat))
86+
return
87+
}
88+
diagnoseInvalidUmbrellaHeader(includeDir)
89+
90+
let umbrellaHeader = Path.join(includeDir, c99name, "\(c99name).h")
91+
if umbrellaHeader.isFile {
92+
guard dirs.count == 1 && files.isEmpty else { throw ModuleMapError.UnsupportedIncludeLayoutForModule(name) }
93+
try createModuleMap(inDir: wd, type: .Header(umbrellaHeader))
94+
return
95+
}
96+
diagnoseInvalidUmbrellaHeader(Path.join(includeDir, c99name))
97+
98+
try createModuleMap(inDir: wd, type: .Directory(includeDir))
99+
}
100+
101+
///warn user if in case module name and c99name are different and there a `name.h` umbrella header
102+
private func diagnoseInvalidUmbrellaHeader(path: String) {
103+
let umbrellaHeader = Path.join(path, "\(c99name).h")
104+
let invalidUmbrellaHeader = Path.join(path, "\(name).h")
105+
if c99name != name && invalidUmbrellaHeader.isFile {
106+
print("warning: \(invalidUmbrellaHeader) should be renamed to \(umbrellaHeader) to be used as an umbrella header")
107+
}
108+
}
109+
110+
private enum UmbrellaType {
111+
case Header(String)
112+
case Directory(String)
113+
}
114+
115+
private func createModuleMap(inDir wd: String, type: UmbrellaType) throws {
116+
try POSIX.mkdir(wd)
117+
let moduleMapFile = Path.join(wd, self.moduleMap)
118+
let moduleMap = try fopen(moduleMapFile, mode: .Write)
119+
defer { fclose(moduleMap) }
120+
121+
var output = "module \(c99name) {\n"
122+
switch type {
123+
case .Header(let header):
124+
output += " umbrella header \"\(header)\"\n"
125+
case .Directory(let path):
126+
output += " umbrella \"\(path)\"\n"
127+
}
128+
output += " link \"\(c99name)\"\n"
129+
output += " export *\n"
130+
output += "}\n"
131+
132+
try fputs(output, moduleMap)
34133
}
35134
}
36135

Sources/PackageType/Module.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ public class ClangModule: CModule {
9393

9494
public init(name: String, sources: Sources) {
9595
self.sources = sources
96-
//TODO: generate module map using swiftpm if layout can support
9796
super.init(name: name, path: sources.root + "/include")
9897
}
9998
}

Tests/Functional/TestClangModules.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class TestClangModulesTestCase: XCTestCase {
7373
XCTAssertEqual(output, "hello 5")
7474
}
7575
}
76-
76+
7777
func testCUsingCDep2() {
7878
//The C dependency "Foo" has different layout
7979
fixture(name: "DependencyResolution/External/CUsingCDep2") { prefix in
@@ -82,6 +82,17 @@ class TestClangModulesTestCase: XCTestCase {
8282
XCTAssertDirectoryExists(prefix, "Bar/Packages/Foo-1.2.3")
8383
}
8484
}
85+
86+
func testModuleMapGenerationCases() {
87+
fixture(name: "ClangModules/ModuleMapGenerationCases") { prefix in
88+
XCTAssertBuilds(prefix)
89+
XCTAssertFileExists(prefix, ".build", "debug", "libUmbrellaHeader.so")
90+
XCTAssertFileExists(prefix, ".build", "debug", "libFlatInclude.so")
91+
XCTAssertFileExists(prefix, ".build", "debug", "libUmbellaModuleNameInclude.so")
92+
XCTAssertFileExists(prefix, ".build", "debug", "libNoIncludeDir.so")
93+
XCTAssertFileExists(prefix, ".build", "debug", "Baz")
94+
}
95+
}
8596
}
8697

8798

@@ -95,6 +106,7 @@ extension TestClangModulesTestCase {
95106
("testiquoteDep", testiquoteDep),
96107
("testCUsingCDep", testCUsingCDep),
97108
("testCExecutable", testCExecutable),
109+
("testModuleMapGenerationCases", testModuleMapGenerationCases),
98110
]
99111
}
100112
}

0 commit comments

Comments
 (0)