Swift binding integration #23

Merged
merged 6 commits into from Apr 25, 2016
View
@@ -16,6 +16,12 @@ endif()
include(CMakeParseArguments)
include(CheckCXXCompilerFlag)
+# Get the SDK path for OSX.
+execute_process(
+ COMMAND xcrun --sdk macosx --show-sdk-path
+ OUTPUT_VARIABLE CMAKE_OSX_SYSROOT
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+
project(llbuild)
# Add path for custom modules
@@ -62,6 +68,29 @@ set(LLBUILD_LIBRARY_OUTPUT_INTDIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR
find_package(Lit REQUIRED)
find_package(FileCheck REQUIRED)
+# Fine swiftc on OSX using `xcrun --find swiftc` and `find_package` on Linux.
+if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+ execute_process(
+ COMMAND xcrun --find swiftc
+ OUTPUT_VARIABLE SWIFTC_EXECUTABLE
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ set(SWIFTC_FOUND TRUE)
+else()
+ find_package(Swiftc QUIET)
+endif()
+
+# Check if we have correct swift version for bindings.
+if (SWIFTC_FOUND)
+ execute_process(
+ COMMAND ${SWIFTC_EXECUTABLE} --version
+ OUTPUT_VARIABLE SWIFTC_VERSION
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if (NOT SWIFTC_VERSION MATCHES "(.*)Swift version 3.0(.*)")
+ set(SWIFTC_EXECUTABLE)
+ set(SWIFTC_FOUND FALSE)
+ endif()
+endif()
+
###
# Setup compiler and project build settings
@@ -0,0 +1,16 @@
+# Usage: find_package(Swiftc)
+#
+# If successful the following variables will be defined
+# SWIFTC_FOUND
+# SWIFTC_EXECUTABLE
+
+find_program(SWIFTC_EXECUTABLE
+ NAMES swiftc
+ DOC "Path to 'swiftc' executable")

This comment has been minimized.

@ddunbar

ddunbar Apr 25, 2016

Member

We need to check and record the TOOLCHAINS and SDKROOT in use... if not, then what will happen when run as:

env TOOLCHAINS=swift cmake ...

is we will just find /usr/bin/swiftc as the swiftc, but if we run again with a different TOOLCHAINS variable it won't work. I'd prefer if the configure step would bake in the value for TOOLCHAINS, I think.

@ddunbar

ddunbar Apr 25, 2016

Member

We need to check and record the TOOLCHAINS and SDKROOT in use... if not, then what will happen when run as:

env TOOLCHAINS=swift cmake ...

is we will just find /usr/bin/swiftc as the swiftc, but if we run again with a different TOOLCHAINS variable it won't work. I'd prefer if the configure step would bake in the value for TOOLCHAINS, I think.

This comment has been minimized.

@aciidb0mb3r

aciidb0mb3r Apr 25, 2016

Member

fixed this by finding swiftc by xcrun --find swiftc on darwin

@aciidb0mb3r

aciidb0mb3r Apr 25, 2016

Member

fixed this by finding swiftc by xcrun --find swiftc on darwin

+
+# Handle REQUIRED and QUIET arguments, this will also set SWIFTC_FOUND to true
+# if SWIFTC_EXECUTABLE exists.
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Swiftc
+ "Failed to locate 'swiftc' executable"
+ SWIFTC_EXECUTABLE)
@@ -111,3 +111,98 @@ function(add_unittest test_suite test_name)
set_property(TARGET ${test_name} APPEND PROPERTY COMPILE_DEFINITIONS GTEST_HAS_RTTI=0)
endfunction()
+
+# Compile swift sources to a dynamic framework.
+# Usage:
+# target # Target name
+# name # Swift Module name
+# deps # Target dependencies
+# sources # List of sources
+# additional_args # List of additional args to pass
+function(add_swift_module target name deps sources additional_args)
+
+ set(BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY})
+
+ list(APPEND ARGS -module-name ${name})
+ list(APPEND ARGS -incremental -emit-dependencies -emit-module)
+ list(APPEND ARGS -emit-module-path ${name}.swiftmodule)
+
+ set(FILEMAP ${BUILD_DIR}/output-file-map.json)
+ set(OUTPUT_FILE_MAP ${FILEMAP})
+
+ # Remove old file and start writing new one.
+ file(REMOVE ${FILEMAP})
+ file(APPEND ${FILEMAP} "{\n")
+ foreach(source ${sources})
+ file(APPEND ${FILEMAP} "\"${CMAKE_CURRENT_SOURCE_DIR}/${source}\": {\n")
+ file(APPEND ${FILEMAP} "\"dependencies\": \"${BUILD_DIR}/${source}.d\",\n")
+ set(OBJECT ${BUILD_DIR}/${source}.o)
+ list(APPEND OUTPUTS ${OBJECT})
+ file(APPEND ${FILEMAP} "\"object\": \"${OBJECT}\",\n")
+ file(APPEND ${FILEMAP} "\"swiftmodule\": \"${BUILD_DIR}/${source}~partial.swiftmodule\",\n")
+ file(APPEND ${FILEMAP} "\"swift-dependencies\": \"${BUILD_DIR}/${source}.swiftdeps\"\n},\n")
+ endforeach()
+ file(APPEND ${FILEMAP} "\"\": {\n")
+ file(APPEND ${FILEMAP} "\"swift-dependencies\": \"${BUILD_DIR}/master.swiftdeps\"\n")
+ file(APPEND ${FILEMAP} "}\n")
+ file(APPEND ${FILEMAP} "}")
+
+ list(APPEND ARGS -output-file-map ${OUTPUT_FILE_MAP})
+ list(APPEND ARGS -parse-as-library)
+ list(APPEND ARGS -c)
+
+ foreach(source ${sources})
+ list(APPEND ARGS ${CMAKE_CURRENT_SOURCE_DIR}/${source})
+ endforeach()
+
+ # FIXME: Find a better way to handle build types.
+ if (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
+ list(APPEND ARGS -g)
+ endif()
+ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+ list(APPEND ARGS -Onone -g)
+ else()
+ list(APPEND ARGS -O -whole-module-optimization)
+ endif()
+
+ foreach(arg ${additional_args})
+ list(APPEND ARGS ${arg})
+ endforeach()
+
+ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+ list(APPEND ARGS -sdk ${CMAKE_OSX_SYSROOT})
+ endif()
+
+ # Compile swiftmodule.
+ add_custom_command(
+ OUTPUT ${OUTPUTS}
+ COMMAND swiftc
+ ARGS ${ARGS}
+ DEPENDS ${sources}
+ )
+
+ # Link and create dynamic framework.
+ set(DYLIB_OUTPUT ${LLBUILD_LIBRARY_OUTPUT_INTDIR}/${target}.dylib)
+
+ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+ list(APPEND DYLYB_ARGS -sdk ${CMAKE_OSX_SYSROOT})
+ endif()
+
+ list(APPEND DYLYB_ARGS -module-name ${name})
+ list(APPEND DYLYB_ARGS -o ${DYLIB_OUTPUT})
+ list(APPEND DYLYB_ARGS -emit-library ${OUTPUTS})
+ foreach(arg ${additional_args})
+ list(APPEND DYLYB_ARGS ${arg})
+ endforeach()
+ list(APPEND DYLYB_ARGS -L ${LLBUILD_LIBRARY_OUTPUT_INTDIR})
+
+ add_custom_command(
+ OUTPUT ${DYLIB_OUTPUT}
+ COMMAND swiftc
+ ARGS ${DYLYB_ARGS}
+ DEPENDS ${OUTPUTS}
+ )
+
+ # Add the target.
+ add_custom_target(${target} ALL DEPENDS ${deps} ${DYLIB_OUTPUT} ${sources})
+endfunction()
@@ -0,0 +1,93 @@
+import llbuild
+
+typealias Compute = [Int] -> Int
+
+class SimpleTask: Task {
+ let inputs: [Key]
+ var values: [Int]
+ let compute: Compute
+
+ init(_ inputs: [Key], compute: Compute) {
+ self.inputs = inputs
+ values = [Int](repeating: 0, count: inputs.count)
+ self.compute = compute
+ }
+
+ func start(_ engine: TaskBuildEngine) {
+ for (idx, input) in inputs.enumerated() {
+ engine.taskNeedsInput(input, inputID: idx)
+ }
+ }
+
+ func provideValue(_ engine: TaskBuildEngine, inputID: Int, value: Value) {
+ values[inputID] = Int(value.toString())!
+ }
+
+ func inputsAvailable(_ engine: TaskBuildEngine) {
+ let result = compute(values)
+ engine.taskIsComplete(Value("\(result)"), forceChange: false)
+ }
+}
+
+class SimpleBuildEngineDelegate: BuildEngineDelegate {
+ var builtKeys = [Key]()
+
+ func lookupRule(_ key: Key) -> Rule {
+ switch key.toString() {
+ case "A":
+ return SimpleRule([]) { arr in
+ precondition(self.builtKeys.isEmpty)
+ self.builtKeys.append(key)
+ return 2
+ }
+ case "B":
+ return SimpleRule([]) { arr in
+ precondition(self.builtKeys.count == 1)
+ self.builtKeys.append(key)
+ return 3
+ }
+ case "C":
+ return SimpleRule([Key("A"), Key("B")]) { arr in
+ precondition(self.builtKeys.count == 2)
+ precondition(self.builtKeys[0].toString() == "A")
+ precondition(self.builtKeys[1].toString() == "B")
+ self.builtKeys.append(key)
+ return arr[0] * arr[1]
+ }
+ default: fatalError("Unexpected key \(key) lookup")
+ }
+ }
+}
+
+class SimpleRule: Rule {
+ let inputs: [Key]
+ let compute: Compute
+ init(_ inputs: [Key], compute: Compute) {
+ self.inputs = inputs
+ self.compute = compute
+ }
+ func createTask() -> Task {
+ return SimpleTask(inputs, compute: compute)
+ }
+}
+
+let delegate = SimpleBuildEngineDelegate()
+var engine = BuildEngine(delegate: delegate)
+
+// C depends on A and B
+var result = engine.build(key: Key("C"))
+print("\(result.toString())")
+
+precondition(result.toString() == "6")
+
+// Make sure building already built keys do not re-compute.
+delegate.builtKeys.removeAll()
+precondition(delegate.builtKeys.isEmpty)
+
+result = engine.build(key: Key("A"))
+precondition(result.toString() == "2")
+precondition(delegate.builtKeys.isEmpty)
+
+result = engine.build(key: Key("B"))
+precondition(result.toString() == "3")
+precondition(delegate.builtKeys.isEmpty)
@@ -1,2 +1,11 @@
-add_custom_target(swift-bindings
- DEPENDS libllbuild)
+# Set sources.
+set(SOURCES llbuild.swift)
+
+# Link C API.
+list(APPEND additional_args -import-underlying-module -lllbuild)
+list(APPEND additional_args -I ${CMAKE_CURRENT_SOURCE_DIR}/../libllbuild/public-api)
+
+# Add swift bindings target if swift compiler is present.
+if (SWIFTC_FOUND)
+ add_swift_module(swift-bindings llbuild libllbuild "${SOURCES}" "${additional_args}")
+endif()
@@ -9,7 +9,6 @@
// This file contains Swift bindings for the llbuild C API.
import Foundation
-import llbuild
enum DatabaseError: ErrorProtocol {
case AttachFailure(message: String)
View
@@ -33,7 +33,7 @@ if(PYTHONINTERP_FOUND)
--param build_mode=${build_mode})
set(test_target_dependencies
- llbuild libllbuild swift-build-tool UnitTests)
+ llbuild libllbuild swift-bindings swift-build-tool UnitTests)
add_custom_target(test-llbuild
COMMAND ${lit_command} ${CMAKE_CURRENT_BINARY_DIR}
@@ -1 +1 @@
-config.suffixes = ['.llbuild']
+config.suffixes = ['.txt', '.llbuild']
@@ -0,0 +1,8 @@
+# Check basic 'core' functionality of the swift bindings
+#
+# REQUIRES: has-swift=TRUE
+# RUN: env LD_LIBRARY_PATH=%{llbuild-lib-dir} %{swiftc} %{swiftc-platform-flags} %{srcroot}/examples/swift-bindings/core/basic.swift -I %{srcroot}/build/products/swift-bindings -I %{srcroot}/products/libllbuild/public-api -Xlinker %{llbuild-lib-dir}/swift-bindings.dylib -o %t.exe
+# RUN: env LD_LIBRARY_PATH=%{llbuild-lib-dir} %t.exe %s > %t.out
+# RUN: cat %t.out
+# RUN: %{FileCheck} %s --input-file %t.out
+# CHECK: 6
View
@@ -64,6 +64,9 @@ config.target_triple = None
# Add a platform feature.
config.available_features.add("platform="+platform.system())
+# Add swiftc feature.
+config.available_features.add("has-swift="+config.swiftc_found)
+
###
# Define our supported substitutions.
@@ -72,8 +75,10 @@ config.substitutions.append( ('%{llbuild}', "%r" % (
config.substitutions.append( ('%{swift-build-tool}', "%r" % (
os.path.join(llbuild_tools_dir, 'swift-build-tool'),)) )
config.substitutions.append( ('%{FileCheck}', config.filecheck_path) )
+config.substitutions.append( ('%{swiftc}', config.swiftc_path) )
config.substitutions.append( ('%{llbuild-lib-dir}', llbuild_lib_dir) )
config.substitutions.append( ('%{srcroot}', llbuild_src_root) )
+config.substitutions.append( ('%{swiftc-platform-flags}', "" if not config.osx_sysroot else "-sdk " + config.osx_sysroot) )
###
View
@@ -12,6 +12,9 @@ config.llbuild_lib_dir = "@LLBUILD_LIBS_DIR@"
# Tools found by the build system.
config.filecheck_path = "@FILECHECK_EXECUTABLE@"
+config.swiftc_path = "@SWIFTC_EXECUTABLE@"
+config.swiftc_found = "@SWIFTC_FOUND@"
+config.osx_sysroot = "@CMAKE_OSX_SYSROOT@"
# Support substitution of the tools_dir with user parameters. This is
# used when we can't determine the tool dir at configuration time.