// helper.hpp
#pragma once
int add_numbers(int a, int b);
// helper.cpp
#include "helper.hpp"
int add_numbers(int a, int b) {
return a + b;
}
let a = add_numbers(1, 2)
print("Hello, World!: \(a)")
I am struggling to complete the CMakeLists.txt file to compile this properly. I am looking at https://github.com/swiftlang/swift-cmake-examples , but it is calling the cpp in the swift file from a separate target, and I do not want that.
Yet calling the compiler like this directly works perfectly:
No matter what I think of, add_numbers is not found during the compilation.
I can make it compile with cmake by referencing the helper header through target_compile_options, but then the editor still thinks that add_numbers is not found.
I feel so stupid. Can anyone help with this please?
I am looking for the smallest working configuration first:
cmake_minimum_required(VERSION 4.0)
project(CppInterop LANGUAGES CXX Swift)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_Swift_LANGUAGE_VERSION 6)
add_executable(CppInterop main.swift helper.cpp)
target_include_directories(CppInterop PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_options(CppInterop PUBLIC "$<$<COMPILE_LANGUAGE:Swift>:-cxx-interoperability-mode=default>")
# target_compile_options(CppInterop PUBLIC "$<$<COMPILE_LANGUAGE:Swift>:-import-objc-header>")
# target_compile_options(CppInterop PUBLIC "$<$<COMPILE_LANGUAGE:Swift>:${CMAKE_CURRENT_SOURCE_DIR}/helper.hpp>")
If I uncomment the last two lines, the project compiles but the editors still do not recognise add_numbers function in the main file with the same message: Cannot find 'add_numbers' in scope.
I’m not convinced that just adding 2 source files to a single executable target with cmake will actually compile them together like what is being done with swiftc. I would expect/think that the .cpp file would be compiled with something like gcc, while the .swift file would be compiled with swiftc. Can someone else confirm this?
Unless there’d be a way to set the compiler for .cpp to swiftc manually…
Then the target_include_directories(CppInterop PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) will add the -I ${CMAKE_CURRENT_SOURCE_DIR} to your Swift invocation, which will see that modulemap.
Then you should be able to import CppInterop from your Swift file to import the declarations in the C++ header. There are examples of this in the standard library:
The main difference is that you want it in C++ mode, which is where the requires cplusplus comes into the picture.
It's all about translation units. C/C++ translation units are a single C/C++ file that turns into a single object file. #include is effectively like taking the header file and copy-pasting the contents into the C/C++ file, so all of the header context is available in that one translation unit. Some C/C++ compiler drivers allow you to pass multiple C/C++ files to the compiler invocation, but the driver breaks the invocation apart into separate frontend invocations that independently compile everything and emit object files separately. CMake always invokes the C/C++ compiler separately for each object file. Swift translation units are all of the source files in a given module, so it needs more context. CMake 3.29 and after also separate the object file emission into a separate invocation. Finally once all of the objects are emitted, some compiler is invoked as a linker driver, taking all of the objects and either archiving them into a static archive, or linking them into a dynamic library/executable. Which compiler that is decided by the LINKER_LANGUAGE target property.
It didn't work for me. Does it work for you? I even added import CppInterop, but it does not really matter because of this warning:
warning: file 'main.swift' is part of module 'CppInterop'; ignoring import
1 | import CppInterop
| `- warning: file 'main.swift' is part of module 'CppInterop'; ignoring import
2 |
3 | let a = add_numbers(1, 2)
Try adding @_exported to the import statement (@_exported import CppInterop). It's an executable so you aren't really re-exporting the interface, but that should hint to the compiler that it should be looking for a clang module, not a Swift module while compiling the file.
Just to confirm a few things, you're using the Ninja generator and the editor is using sourcekit-lsp to provide language info? If so, you should be able to pass -DCMAKE_EXPORT_COMPILE_COMMANDS=YES to your CMake invocation to generate the compile-commands (can just run cmake . -DCMAKE_EXPORT_COMPILE_COMMANDS=YES to avoid needing to reconfigure and recompile everything), and then go to the root of your project and create a symlink to the compile_commands.json file that was generated into your build directory.
Then I recommend restarting your editor to make sure that sourcekit-lsp is seeing the compile commands.