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 basic support for building C via swiftpm #183

Merged
merged 16 commits into from Mar 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions Fixtures/ClangModules/CLibraryFlat/Foo.c
@@ -0,0 +1,6 @@
int foo() {
int a = 5;
int b = a;
a = b;
return a;
}
Empty file.
1 change: 1 addition & 0 deletions Fixtures/ClangModules/CLibraryFlat/include/abc.h
@@ -0,0 +1 @@
int foo();
5 changes: 5 additions & 0 deletions Fixtures/ClangModules/CLibraryFlat/include/module.modulemap
@@ -0,0 +1,5 @@
module CLibraryFlat {
header "abc.h"
link "CLibraryFlat"
export *
}
Empty file.
6 changes: 6 additions & 0 deletions Fixtures/ClangModules/CLibrarySources/Sources/Foo.c
@@ -0,0 +1,6 @@
int foo() {
int a = 5;
int b = a;
a = b;
return a;
}
@@ -0,0 +1 @@
int foo();
@@ -0,0 +1,5 @@
module CLibrarySources {
header "abc.h"
link "CLibrarySources"
export *
}
8 changes: 8 additions & 0 deletions Fixtures/ClangModules/CLibraryiquote/Package.swift
@@ -0,0 +1,8 @@
import PackageDescription

let package = Package(
name: "CLibraryiquote",
targets: [
Target(name: "Bar", dependencies: ["Foo"]),
Target(name: "Baz", dependencies: ["Foo", "Bar"])]
)
9 changes: 9 additions & 0 deletions Fixtures/ClangModules/CLibraryiquote/Sources/Bar/Bar.c
@@ -0,0 +1,9 @@
#include "include/Bar/Bar.h"
#include "Foo/Foo.h"

int bar() {
int a = foo();
int b = a;
a = b;
return a;
}
@@ -0,0 +1 @@
int bar();
@@ -0,0 +1,5 @@
module Bar {
header "Bar/Bar.h"
link "Bar"
export *
}
5 changes: 5 additions & 0 deletions Fixtures/ClangModules/CLibraryiquote/Sources/Baz/main.swift
@@ -0,0 +1,5 @@
import Foo
import Bar

let _ = foo()
let _ = bar()
8 changes: 8 additions & 0 deletions Fixtures/ClangModules/CLibraryiquote/Sources/Foo/Foo.c
@@ -0,0 +1,8 @@
#include "include/Foo/Foo.h"

int foo() {
int a = 5;
int b = a;
a = b;
return a;
}
@@ -0,0 +1 @@
int foo();
@@ -0,0 +1,5 @@
module Foo {
header "Foo/Foo.h"
link "Foo"
export *
}
6 changes: 6 additions & 0 deletions Fixtures/ClangModules/SwiftCMixed/Package.swift
@@ -0,0 +1,6 @@
import PackageDescription

let package = Package(
name: "SwiftCMixed",
targets: [Target(name: "SeaExec", dependencies: ["SeaLib"])]
)
3 changes: 3 additions & 0 deletions Fixtures/ClangModules/SwiftCMixed/Sources/SeaExec/main.swift
@@ -0,0 +1,3 @@
import SeaLib

let a = foo(5)
3 changes: 3 additions & 0 deletions Fixtures/ClangModules/SwiftCMixed/Sources/SeaLib/Foo.c
@@ -0,0 +1,3 @@
int foo(int a) {
return a;
}
@@ -0,0 +1 @@
int foo(int a);
@@ -0,0 +1,5 @@
module SeaLib {
header "Foo.h"
link "SeaLib"
export *
}
@@ -0,0 +1,8 @@
import PackageDescription

let package = Package(
name: "Bar",
dependencies: [
.Package(url: "../Foo", majorVersion: 1)
]
)
@@ -0,0 +1,5 @@
#include <Foo/Foo.h>

void cool() {
foo();
}
@@ -0,0 +1,5 @@
module SeaLover {
header "Sea/Sea.h"
link "SeaLover"
export *
}
@@ -0,0 +1,3 @@
import Foo

foo()
3 changes: 3 additions & 0 deletions Fixtures/DependencyResolution/External/CUsingCDep/Foo/Foo.c
@@ -0,0 +1,3 @@
void foo() {

}
Empty file.
@@ -0,0 +1 @@
void foo();
@@ -0,0 +1,5 @@
module Foo {
header "Foo/Foo.h"
link "Foo"
export *
}
@@ -0,0 +1,6 @@
import PackageDescription

let package = Package(
name: "Bar",
dependencies: [
.Package(url: "../Foo", majorVersion: 1)])
@@ -0,0 +1,3 @@
import Foo

foo()
3 changes: 3 additions & 0 deletions Fixtures/DependencyResolution/External/SimpleCDep/Foo/Foo.c
@@ -0,0 +1,3 @@
void foo() {

}
Empty file.
@@ -0,0 +1 @@
void foo();
@@ -0,0 +1,5 @@
module Foo {
header "Foo.h"
link "Foo"
export *
}
67 changes: 67 additions & 0 deletions Sources/Build/describe().swift
Expand Up @@ -103,6 +103,72 @@ public func describe(prefix: String, _ conf: Configuration, _ modules: [Module],
}
}

//For C language Modules
//FIXME: Probably needs more compiler options for debug and release modes
//FIXME: Incremental builds
//FIXME: Add support for executables
for case let module as ClangModule in modules {

//FIXME: Generate modulemaps if possible
//Since we're not generating modulemaps currently we'll just emit empty module map file
//if it not present
if !module.moduleMapPath.isFile {
try mkdir(module.moduleMapPath.parentDirectory)
try fopen(module.moduleMapPath, mode: .Write) { fp in
try fputs("\n", fp)
}
}

let inputs = module.dependencies.map{ $0.targetName } + module.sources.paths
let productPath = Path.join(prefix, "lib\(module.c99name).so")
let wd = Path.join(prefix, "\(module.c99name).build")
mkdirs.insert(wd)

var args: [String] = []
#if os(Linux)
args += ["-fPIC"]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests were failing on linux and clang suggested to recompile using this. Not sure if this is correct? @mxcl @ddunbar

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is probably correct behavior, I would want to test it with Swift interop to know for sure.

On Mar 10, 2016, at 11:04 AM, Ankit Agarwal notifications@github.com wrote:

In Sources/Build/describe().swift #183 (comment):

  •    //if it not present
    
  •    if !module.moduleMapPath.isFile {
    
  •        try mkdir(module.moduleMapPath.parentDirectory)
    
  •        try fopen(module.moduleMapPath, mode: .Write) { fp in
    
  •            try fputs("\n", fp)
    
  •        }
    
  •    }
    
  •    let inputs = module.dependencies.map{ $0.targetName } + module.sources.paths
    
  •    let productPath = Path.join(prefix, "lib(module.c99name).so")
    
  •    let wd = Path.join(prefix, "(module.c99name).build")
    
  •    mkdirs.insert(wd)
    
  •    var args: [String] = []
    
  • #if os(Linux)
  •     args += ["-fPIC"]
    
    Tests were failing on linux and clang suggested to recompile using this. Not sure if this is correct? @mxcl https://github.com/mxcl @ddunbar https://github.com/ddunbar

    Reply to this email directly or view it on GitHub https://github.com/apple/swift-package-manager/pull/183/files#r55731599.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tests in this PR calling c methods from swift.

#endif
args += ["-fmodules", "-fmodule-name=\(module.name)"]
args += ["-L\(prefix)"]

for case let dep as ClangModule in module.dependencies {
let includeFlag: String
//add `-iquote` argument to the include directory of every target in the package in the
//transitive closure of the target being built allowing the use of `#include "..."`
//add `-I` argument to the include directory of every target outside the package in the
//transitive closure of the target being built allowing the use of `#include <...>`
//FIXME: To detect external deps we're checking if their path's parent.parent directory
//is `Packages` as external deps will get copied to `Packages` dir. There should be a
//better way to do this.
if dep.path.parentDirectory.parentDirectory.basename == "Packages" {
includeFlag = "-I"
} else {
includeFlag = "-iquote"
}
args += [includeFlag, dep.path]
args += ["-l\(dep.c99name)"] //FIXME: giving path to other module's -fmodule-map-file is not linking that module
}

switch conf {
case .Debug:
args += ["-g", "-O0"]
case .Release:
args += ["-O2"]
}

args += module.sources.paths
args += ["-shared", "-o", productPath]

let clang = ShellTool(
description: "Compiling \(module.name)",
inputs: inputs,
outputs: [productPath, module.targetName],
args: [Toolchain.clang] + args)

let command = Command(name: module.targetName, tool: clang)
append(command, buildable: module)
}

// make eg .build/debug/foo.build/subdir for eg. Sources/foo/subdir/bar.swift
// TODO swift-build-tool should do this
for dir in mkdirs {
Expand Down Expand Up @@ -171,6 +237,7 @@ public func describe(prefix: String, _ conf: Configuration, _ modules: [Module],
}
args += platformArgs() //TODO don't need all these here or above: split outname
args += Xld
args += ["-L\(prefix)"]
args += ["-o", outpath]
args += objects

Expand Down
9 changes: 7 additions & 2 deletions Sources/Build/misc.swift
Expand Up @@ -26,12 +26,17 @@ func platformArgs() -> [String] {
return args
}

extension CModule {
var moduleMapPath: String {
return Path.join(path, "module.modulemap")
}
}

extension Module {
var Xcc: [String] {
return recursiveDependencies.flatMap { module -> [String] in
if let module = module as? CModule {
let moduleMapPath = Path.join(module.path, "module.modulemap")
return ["-Xcc", "-fmodule-map-file=\(moduleMapPath)"]
return ["-Xcc", "-fmodule-map-file=\(module.moduleMapPath)"]
} else {
return []
}
Expand Down
10 changes: 10 additions & 0 deletions Sources/PackageType/Module.swift
Expand Up @@ -74,6 +74,16 @@ public class CModule: Module {
}
}

public class ClangModule: CModule {
public let sources: Sources

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

public class TestModule: SwiftModule {

public init(basename: String, sources: Sources) {
Expand Down
12 changes: 12 additions & 0 deletions Sources/PackageType/Sources.swift
Expand Up @@ -26,4 +26,16 @@ public struct Sources {
relativePaths = paths.map { Path($0).relative(to: root) }
self.root = root
}

static public var validSwiftExtensions: Set<String> {
return ["swift"]
}

static public var validCExtensions: Set<String> {
return ["c"]
}

static public var validExtensions: Set<String> {
return validSwiftExtensions.union(validCExtensions)
}
}
2 changes: 2 additions & 0 deletions Sources/Transmute/Error.swift
Expand Up @@ -26,5 +26,7 @@ extension Package {
extension Module {
public enum Error: ErrorProtocol {
case NoSources(String)
case MixedSources(String)
case CExecutableNotSupportedYet(String) //TODO: Remove this when add Support for C Exectuable
}
}
39 changes: 28 additions & 11 deletions Sources/Transmute/Package+modules.swift
Expand Up @@ -38,19 +38,19 @@ extension Package {
let modules: [Module]
if maybeModules.isEmpty {
do {
modules = [SwiftModule(name: self.name, sources: try sourcify(srcroot))]
modules = [try modulify(srcroot, name: self.name)]
} catch Module.Error.NoSources {
throw ModuleError.NoModules(self)
}
} else {
modules = try maybeModules.map(sourcify).map { sources in
modules = try maybeModules.map { path in
let name: String
if sources.root == srcroot {
if path == srcroot {
name = self.name
} else {
name = sources.root.basename
name = path.basename
}
return SwiftModule(name: name, sources: sources)
return try modulify(path, name: name)
}
}

Expand All @@ -74,19 +74,36 @@ extension Package {

return modules
}

func sourcify(path: String) throws -> Sources {
let sources = walk(path, recursing: shouldConsiderDirectory).filter(isValidSource)
guard sources.count > 0 else { throw Module.Error.NoSources(path) }
return Sources(paths: sources, root: path)

func modulify(path: String, name: String) throws -> Module {
let walked = walk(path, recursing: shouldConsiderDirectory).map{ $0 }

let cSources = walked.filter{ isValidSource($0, validExtensions: Sources.validCExtensions) }
let swiftSources = walked.filter{ isValidSource($0, validExtensions: Sources.validSwiftExtensions) }

if !cSources.isEmpty {
guard swiftSources.isEmpty else { throw Module.Error.MixedSources(path) }
//FIXME: Support executables for C languages
guard !cSources.contains({ $0.hasSuffix("main.c") }) else { throw Module.Error.CExecutableNotSupportedYet(path) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should check the basename.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raised #199 for this

return ClangModule(name: name, sources: Sources(paths: cSources, root: path))
}

guard !swiftSources.isEmpty else { throw Module.Error.NoSources(path) }
return SwiftModule(name: name, sources: Sources(paths: swiftSources, root: path))
}

func isValidSource(path: String) -> Bool {
return isValidSource(path, validExtensions: Sources.validExtensions)
}

func isValidSource(path: String, validExtensions: Set<String>) -> Bool {
if path.basename.hasPrefix(".") { return false }
let path = path.normpath
if path == manifest.path.normpath { return false }
if excludes.contains(path) { return false }
return path.lowercased().hasSuffix(".swift") && path.isFile
if !path.isFile { return false }
guard let ext = path.fileExt else { return false }
return validExtensions.contains(ext)
}

private func targetForName(name: String) -> Target? {
Expand Down
1 change: 1 addition & 0 deletions Sources/Transmute/Package+shouldConsiderDirectory.swift
Expand Up @@ -15,6 +15,7 @@ extension Package {
func shouldConsiderDirectory(path: String) -> Bool {
let base = path.basename.lowercased()
if base == "tests" { return false }
if base == "include" { return false }
if base.hasSuffix(".xcodeproj") { return false }
if base.hasSuffix(".playground") { return false }
if base.hasPrefix(".") { return false } // eg .git
Expand Down
5 changes: 3 additions & 2 deletions Sources/Transmute/Package+testModules.swift
Expand Up @@ -17,8 +17,9 @@ extension Package {
//Don't try to walk Tests if it is in excludes
if testsPath.isDirectory && excludes.contains(testsPath) { return [] }
return walk(testsPath, recursively: false).filter(shouldConsiderDirectory).flatMap { dir in
if let sources = try? self.sourcify(dir) {
return TestModule(basename: dir.basename, sources: sources)
let sources = walk(dir, recursing: shouldConsiderDirectory).filter{ isValidSource($0, validExtensions: Sources.validSwiftExtensions) }
if sources.count > 0 {
return TestModule(basename: dir.basename, sources: Sources(paths: sources, root: dir))
} else {
print("warning: no sources in test module: \(path)")
return nil
Expand Down