Skip to content

Commit 57c869a

Browse files
committed
Merge pull request #183 from aciidb0mb3r/c_support
Add basic support for building C via swiftpm
2 parents 1fe7638 + 114cf3e commit 57c869a

File tree

48 files changed

+357
-15
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+357
-15
lines changed
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
int foo() {
2+
int a = 5;
3+
int b = a;
4+
a = b;
5+
return a;
6+
}

Fixtures/ClangModules/CLibraryFlat/Package.swift

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int foo();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module CLibraryFlat {
2+
header "abc.h"
3+
link "CLibraryFlat"
4+
export *
5+
}

Fixtures/ClangModules/CLibrarySources/Package.swift

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
int foo() {
2+
int a = 5;
3+
int b = a;
4+
a = b;
5+
return a;
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int foo();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module CLibrarySources {
2+
header "abc.h"
3+
link "CLibrarySources"
4+
export *
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import PackageDescription
2+
3+
let package = Package(
4+
name: "CLibraryiquote",
5+
targets: [
6+
Target(name: "Bar", dependencies: ["Foo"]),
7+
Target(name: "Baz", dependencies: ["Foo", "Bar"])]
8+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include "include/Bar/Bar.h"
2+
#include "Foo/Foo.h"
3+
4+
int bar() {
5+
int a = foo();
6+
int b = a;
7+
a = b;
8+
return a;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int bar();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module Bar {
2+
header "Bar/Bar.h"
3+
link "Bar"
4+
export *
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Foo
2+
import Bar
3+
4+
let _ = foo()
5+
let _ = bar()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "include/Foo/Foo.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();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module Foo {
2+
header "Foo/Foo.h"
3+
link "Foo"
4+
export *
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import PackageDescription
2+
3+
let package = Package(
4+
name: "SwiftCMixed",
5+
targets: [Target(name: "SeaExec", dependencies: ["SeaLib"])]
6+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import SeaLib
2+
3+
let a = foo(5)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
int foo(int a) {
2+
return a;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int foo(int a);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module SeaLib {
2+
header "Foo.h"
3+
link "SeaLib"
4+
export *
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import PackageDescription
2+
3+
let package = Package(
4+
name: "Bar",
5+
dependencies: [
6+
.Package(url: "../Foo", majorVersion: 1)
7+
]
8+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include <Foo/Foo.h>
2+
3+
void cool() {
4+
foo();
5+
}

Fixtures/DependencyResolution/External/CUsingCDep/Bar/Sources/SeaLover/include/Sea.h

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module SeaLover {
2+
header "Sea/Sea.h"
3+
link "SeaLover"
4+
export *
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Foo
2+
3+
foo()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
void foo() {
2+
3+
}

Fixtures/DependencyResolution/External/CUsingCDep/Foo/Package.swift

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
void foo();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module Foo {
2+
header "Foo/Foo.h"
3+
link "Foo"
4+
export *
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import PackageDescription
2+
3+
let package = Package(
4+
name: "Bar",
5+
dependencies: [
6+
.Package(url: "../Foo", majorVersion: 1)])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Foo
2+
3+
foo()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
void foo() {
2+
3+
}

Fixtures/DependencyResolution/External/SimpleCDep/Foo/Package.swift

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
void foo();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module Foo {
2+
header "Foo.h"
3+
link "Foo"
4+
export *
5+
}

Sources/Build/describe().swift

+67
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,72 @@ public func describe(prefix: String, _ conf: Configuration, _ modules: [Module],
103103
}
104104
}
105105

106+
//For C language Modules
107+
//FIXME: Probably needs more compiler options for debug and release modes
108+
//FIXME: Incremental builds
109+
//FIXME: Add support for executables
110+
for case let module as ClangModule in modules {
111+
112+
//FIXME: Generate modulemaps if possible
113+
//Since we're not generating modulemaps currently we'll just emit empty module map file
114+
//if it not present
115+
if !module.moduleMapPath.isFile {
116+
try mkdir(module.moduleMapPath.parentDirectory)
117+
try fopen(module.moduleMapPath, mode: .Write) { fp in
118+
try fputs("\n", fp)
119+
}
120+
}
121+
122+
let inputs = module.dependencies.map{ $0.targetName } + module.sources.paths
123+
let productPath = Path.join(prefix, "lib\(module.c99name).so")
124+
let wd = Path.join(prefix, "\(module.c99name).build")
125+
mkdirs.insert(wd)
126+
127+
var args: [String] = []
128+
#if os(Linux)
129+
args += ["-fPIC"]
130+
#endif
131+
args += ["-fmodules", "-fmodule-name=\(module.name)"]
132+
args += ["-L\(prefix)"]
133+
134+
for case let dep as ClangModule in module.dependencies {
135+
let includeFlag: String
136+
//add `-iquote` argument to the include directory of every target in the package in the
137+
//transitive closure of the target being built allowing the use of `#include "..."`
138+
//add `-I` argument to the include directory of every target outside the package in the
139+
//transitive closure of the target being built allowing the use of `#include <...>`
140+
//FIXME: To detect external deps we're checking if their path's parent.parent directory
141+
//is `Packages` as external deps will get copied to `Packages` dir. There should be a
142+
//better way to do this.
143+
if dep.path.parentDirectory.parentDirectory.basename == "Packages" {
144+
includeFlag = "-I"
145+
} else {
146+
includeFlag = "-iquote"
147+
}
148+
args += [includeFlag, dep.path]
149+
args += ["-l\(dep.c99name)"] //FIXME: giving path to other module's -fmodule-map-file is not linking that module
150+
}
151+
152+
switch conf {
153+
case .Debug:
154+
args += ["-g", "-O0"]
155+
case .Release:
156+
args += ["-O2"]
157+
}
158+
159+
args += module.sources.paths
160+
args += ["-shared", "-o", productPath]
161+
162+
let clang = ShellTool(
163+
description: "Compiling \(module.name)",
164+
inputs: inputs,
165+
outputs: [productPath, module.targetName],
166+
args: [Toolchain.clang] + args)
167+
168+
let command = Command(name: module.targetName, tool: clang)
169+
append(command, buildable: module)
170+
}
171+
106172
// make eg .build/debug/foo.build/subdir for eg. Sources/foo/subdir/bar.swift
107173
// TODO swift-build-tool should do this
108174
for dir in mkdirs {
@@ -171,6 +237,7 @@ public func describe(prefix: String, _ conf: Configuration, _ modules: [Module],
171237
}
172238
args += platformArgs() //TODO don't need all these here or above: split outname
173239
args += Xld
240+
args += ["-L\(prefix)"]
174241
args += ["-o", outpath]
175242
args += objects
176243

Sources/Build/misc.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,17 @@ func platformArgs() -> [String] {
2626
return args
2727
}
2828

29+
extension CModule {
30+
var moduleMapPath: String {
31+
return Path.join(path, "module.modulemap")
32+
}
33+
}
34+
2935
extension Module {
3036
var Xcc: [String] {
3137
return recursiveDependencies.flatMap { module -> [String] in
3238
if let module = module as? CModule {
33-
let moduleMapPath = Path.join(module.path, "module.modulemap")
34-
return ["-Xcc", "-fmodule-map-file=\(moduleMapPath)"]
39+
return ["-Xcc", "-fmodule-map-file=\(module.moduleMapPath)"]
3540
} else {
3641
return []
3742
}

Sources/PackageType/Module.swift

+10
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ public class CModule: Module {
7474
}
7575
}
7676

77+
public class ClangModule: CModule {
78+
public let sources: Sources
79+
80+
public init(name: String, sources: Sources) {
81+
self.sources = sources
82+
//TODO: generate module map using swiftpm if layout can support
83+
super.init(name: name, path: sources.root + "/include")
84+
}
85+
}
86+
7787
public class TestModule: SwiftModule {
7888

7989
public init(basename: String, sources: Sources) {

Sources/PackageType/Sources.swift

+12
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,16 @@ public struct Sources {
2626
relativePaths = paths.map { Path($0).relative(to: root) }
2727
self.root = root
2828
}
29+
30+
static public var validSwiftExtensions: Set<String> {
31+
return ["swift"]
32+
}
33+
34+
static public var validCExtensions: Set<String> {
35+
return ["c"]
36+
}
37+
38+
static public var validExtensions: Set<String> {
39+
return validSwiftExtensions.union(validCExtensions)
40+
}
2941
}

Sources/Transmute/Error.swift

+2
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,7 @@ extension Package {
2626
extension Module {
2727
public enum Error: ErrorProtocol {
2828
case NoSources(String)
29+
case MixedSources(String)
30+
case CExecutableNotSupportedYet(String) //TODO: Remove this when add Support for C Exectuable
2931
}
3032
}

Sources/Transmute/Package+modules.swift

+28-11
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,19 @@ extension Package {
3838
let modules: [Module]
3939
if maybeModules.isEmpty {
4040
do {
41-
modules = [SwiftModule(name: self.name, sources: try sourcify(srcroot))]
41+
modules = [try modulify(srcroot, name: self.name)]
4242
} catch Module.Error.NoSources {
4343
throw ModuleError.NoModules(self)
4444
}
4545
} else {
46-
modules = try maybeModules.map(sourcify).map { sources in
46+
modules = try maybeModules.map { path in
4747
let name: String
48-
if sources.root == srcroot {
48+
if path == srcroot {
4949
name = self.name
5050
} else {
51-
name = sources.root.basename
51+
name = path.basename
5252
}
53-
return SwiftModule(name: name, sources: sources)
53+
return try modulify(path, name: name)
5454
}
5555
}
5656

@@ -74,19 +74,36 @@ extension Package {
7474

7575
return modules
7676
}
77-
78-
func sourcify(path: String) throws -> Sources {
79-
let sources = walk(path, recursing: shouldConsiderDirectory).filter(isValidSource)
80-
guard sources.count > 0 else { throw Module.Error.NoSources(path) }
81-
return Sources(paths: sources, root: path)
77+
78+
func modulify(path: String, name: String) throws -> Module {
79+
let walked = walk(path, recursing: shouldConsiderDirectory).map{ $0 }
80+
81+
let cSources = walked.filter{ isValidSource($0, validExtensions: Sources.validCExtensions) }
82+
let swiftSources = walked.filter{ isValidSource($0, validExtensions: Sources.validSwiftExtensions) }
83+
84+
if !cSources.isEmpty {
85+
guard swiftSources.isEmpty else { throw Module.Error.MixedSources(path) }
86+
//FIXME: Support executables for C languages
87+
guard !cSources.contains({ $0.hasSuffix("main.c") }) else { throw Module.Error.CExecutableNotSupportedYet(path) }
88+
return ClangModule(name: name, sources: Sources(paths: cSources, root: path))
89+
}
90+
91+
guard !swiftSources.isEmpty else { throw Module.Error.NoSources(path) }
92+
return SwiftModule(name: name, sources: Sources(paths: swiftSources, root: path))
8293
}
8394

8495
func isValidSource(path: String) -> Bool {
96+
return isValidSource(path, validExtensions: Sources.validExtensions)
97+
}
98+
99+
func isValidSource(path: String, validExtensions: Set<String>) -> Bool {
85100
if path.basename.hasPrefix(".") { return false }
86101
let path = path.normpath
87102
if path == manifest.path.normpath { return false }
88103
if excludes.contains(path) { return false }
89-
return path.lowercased().hasSuffix(".swift") && path.isFile
104+
if !path.isFile { return false }
105+
guard let ext = path.fileExt else { return false }
106+
return validExtensions.contains(ext)
90107
}
91108

92109
private func targetForName(name: String) -> Target? {

Sources/Transmute/Package+shouldConsiderDirectory.swift

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extension Package {
1515
func shouldConsiderDirectory(path: String) -> Bool {
1616
let base = path.basename.lowercased()
1717
if base == "tests" { return false }
18+
if base == "include" { return false }
1819
if base.hasSuffix(".xcodeproj") { return false }
1920
if base.hasSuffix(".playground") { return false }
2021
if base.hasPrefix(".") { return false } // eg .git

Sources/Transmute/Package+testModules.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ extension Package {
1717
//Don't try to walk Tests if it is in excludes
1818
if testsPath.isDirectory && excludes.contains(testsPath) { return [] }
1919
return walk(testsPath, recursively: false).filter(shouldConsiderDirectory).flatMap { dir in
20-
if let sources = try? self.sourcify(dir) {
21-
return TestModule(basename: dir.basename, sources: sources)
20+
let sources = walk(dir, recursing: shouldConsiderDirectory).filter{ isValidSource($0, validExtensions: Sources.validSwiftExtensions) }
21+
if sources.count > 0 {
22+
return TestModule(basename: dir.basename, sources: Sources(paths: sources, root: dir))
2223
} else {
2324
print("warning: no sources in test module: \(path)")
2425
return nil

0 commit comments

Comments
 (0)