From a5f8152e25be1ab09acda5b4e20476c7731c2ffd Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Mon, 10 Aug 2026 16:03:06 +0200 Subject: [PATCH 01/27] first draft --- ...p-With-Conan-Managed-Dependencies.markdown | 458 ++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 _posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown new file mode 100644 index 00000000..9260389d --- /dev/null +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -0,0 +1,458 @@ +--- +layout: post +comments: false +title: "Calling Conan-Managed C and C++ Libraries Directly from Swift" +description: "A Swift app calls two unmodified ConanCenter packages, LunaSVG and SDL, directly through Swift's C++ interoperability mode, with no hand-written C wrapper." +meta_title: "Swift C++ Interop with Conan-Managed Dependencies - Conan Blog" +categories: [cpp, conan, swift, macos, cmake] +--- + +Swift's C++ interoperability makes an appealing promise: import a C++ module and +call its APIs directly from Swift, without first building a C wrapper. But a +real C++ library is more than a header. It also brings compiled binaries, +transitive dependencies, build options, a C++ standard library, and ABI +constraints. + +That is where Conan fits. + +In this post, we will build a small Swift application with two unmodified +packages from ConanCenter: + +- [LunaSVG](https://conan.io/center/recipes/lunasvg), a C++ SVG renderer. +- [SDL](https://conan.io/center/recipes/sdl), whose SDL3 C API creates the + window and displays the rendered pixels. + +The application generates an animated SVG scene in Swift, asks LunaSVG to +rasterize it into a Swift-owned pixel buffer, and uploads that buffer to an SDL +texture. Neither dependency knows anything about Swift, neither ships a Swift +module map, and there is no wrapper library between Swift and C++. + +The complete example is available in the [Conan examples +repository](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop). + +## Why This Example Is Interesting + +A trivial interop sample can stop at a header-only function. This one crosses +most of the boundaries that matter in a real project: + +- Conan resolves and, when necessary, builds two ordinary ConanCenter packages. +- CMake receives package configuration and imported targets from Conan. +- Small generated Clang module maps make the libraries importable by Swift. +- Swift calls C++ classes, static methods, constructors, and member functions + directly. +- A C++ object writes into memory owned by a Swift `Array`. +- The resulting executable links the native libraries and their platform + requirements. + +The result is deliberately visual: a window with clouds drifting over a horizon. + +## Build and Run It + +The example currently targets macOS and requires the Xcode command-line tools, +CMake 3.28 or newer, and Ninja. We select Ninja explicitly because CMake's Swift +support does not work with the Unix Makefiles generator that some CMake versions +choose by default on macOS. C++ interoperability was introduced in Swift 5.9 and +has continued to evolve; use a recent Swift toolchain for the full set of +features exercised here, including importing the `std::unique_ptr` returned by +LunaSVG. + +```bash +git clone https://github.com/conan-io/examples2.git +cd examples2/examples/languages/swift/cxx_interop + +conan install . --build=missing \ + -c tools.cmake.cmaketoolchain:generator=Ninja +cmake --preset conan-release +cmake --build --preset conan-release +./build/Release/demo +``` + +`conan install` resolves the dependency graph and obtains binaries matching the +active Conan profile. If a suitable binary is unavailable, `--build=missing` +builds it from source. The generated CMake preset then carries that +configuration into the native build. + +## What Swift's C++ Interoperability Does — and What It Does Not Do + +Swift does not translate a C++ library's implementation into Swift. It imports +the declarations in the public C++ headers and emits calls that follow the +target C++ ABI. At link time, those calls are resolved against the already +compiled libraries supplied by Conan. + +The Swift compiler embeds Clang, so it can parse the headers as a Clang module +and expose their declarations to Swift. Public C++ classes normally appear as +Swift value types; constructors become initializers, member functions become +methods, and namespaces remain available. The compiler then lowers those +operations to calls that follow the target C++ ABI, and the linker resolves +their symbols in the Conan package binaries. Compiler-generated adapters may +still be needed for particular language features, but there is no separately +maintained or compiled C wrapper library in this example. + +This division of responsibilities is useful: + +- Swift interoperability handles the language boundary. +- Conan handles the dependency and binary boundary. + +A [Clang module map](https://clang.llvm.org/docs/Modules.html) only tells the +importer which headers form a module. It does not contain bindings, compile the +library, or make an arbitrary binary ABI-compatible. Conan still has to provide +the matching headers, libraries, transitive requirements, and build +configuration. + +## Describing the Native Dependencies with Conan + +The recipe starts like a conventional CMake-based Conan consumer: + +```python +from conan import ConanFile +from conan.tools.cmake import CMakeDeps, CMakeToolchain, cmake_layout + + +class SwiftCppDemo(ConanFile): + settings = "os", "arch", "compiler", "build_type" + + def layout(self): + cmake_layout(self) + + def requirements(self): + self.requires("lunasvg/3.5.0") + self.requires("sdl/3.2.14") + + def generate(self): + CMakeDeps(self).generate() + CMakeToolchain(self).generate() +``` + +[`CMakeDeps`](https://docs.conan.io/2/reference/tools/cmake/cmakedeps.html) +generates the package configuration consumed by `find_package()`. +[`CMakeToolchain`](https://docs.conan.io/2/reference/tools/cmake/cmaketoolchain.html) +translates the Conan configuration into CMake toolchain data and presets. That +part is independent of Swift: from Conan's perspective, this is a native +executable consuming two C and C++ dependencies. + +## Generating the Missing Module Maps + +Swift imports C and C++ headers as Clang modules. For that, it must be able to +find a `module.modulemap`. LunaSVG and SDL are regular C/C++ packages and do not +ship one for this use case, so the recipe generates two tiny shim module maps: + +```python +import os + +from conan.tools.files import save + + +def _write_modulemap(self, filename, module_name, header_path): + content = ( + f'module {module_name} {{\n' + f' header "{header_path}"\n' + ' export *\n' + '}\n' + ) + save( + self, + os.path.join(self.generators_folder, "shim", filename), + content, + ) + + +def generate(self): + CMakeDeps(self).generate() + CMakeToolchain(self).generate() + + lunasvg_include = self.dependencies["lunasvg"].cpp_info.includedirs[0] + self._write_modulemap( + "lunasvg.modulemap", + "LunaSVGMod", + f"{lunasvg_include}/lunasvg/lunasvg.h", + ) + + sdl_include = ( + self.dependencies["sdl"] + .cpp_info.components["sdl3"] + .includedirs[0] + ) + self._write_modulemap( + "sdl3.modulemap", + "SDL3Mod", + f"{sdl_include}/SDL3/SDL.h", + ) +``` + +The generated files are intentionally simple. Conceptually, the LunaSVG one +contains only this: + +``` +module LunaSVGMod { + header "/path/to/conan/package/include/lunasvg/lunasvg.h" + export * +} +``` + +There are two details worth calling out. + +First, the recipe takes header locations from each dependency's `cpp_info` +instead of guessing paths in the Conan cache. This works with the package layout +selected by Conan and also respects component metadata — in SDL's case, the +`sdl3` component. + +Second, these are **Clang modules**, not the named modules introduced by C++20. +Swift's C++ importer currently consumes headers through Clang module maps; it +does not import C++20 modules directly. + +In a library designed specifically for Swift consumption, a maintained module +map can live beside the public headers. Generating one in the consumer is a +pragmatic bridge for an existing, Swift-unaware package. + +## Connecting Conan, CMake, and `swiftc` + +The CMake project enables both Swift and C++ and consumes the targets generated +by Conan: + +```cmake +cmake_minimum_required(VERSION 3.28) +project(swift_cpp_demo LANGUAGES CXX Swift) + +find_package(lunasvg REQUIRED) +find_package(SDL3 REQUIRED) + +add_executable(demo main.swift) + +target_link_libraries(demo PRIVATE + lunasvg::lunasvg + sdl::sdl +) +``` + +Those imported targets are important. They carry much more than a library +filename: include paths, transitive link requirements, and other usage +information modeled by the packages. + +The Swift-specific part enables C++ interoperability and forwards the module +maps to the Clang instance embedded in the Swift compiler: + +```cmake +get_filename_component( + _conan_generators_dir + "${CMAKE_TOOLCHAIN_FILE}" + DIRECTORY +) + +target_compile_options(demo PRIVATE + "$<$:-cxx-interoperability-mode=default>" + "$<$:SHELL:-Xcc -std=c++17>" + "$<$:SHELL:-Xcc -fmodule-map-file=${_conan_generators_dir}/shim/lunasvg.modulemap>" + "$<$:SHELL:-Xcc -fmodule-map-file=${_conan_generators_dir}/shim/sdl3.modulemap>" +) +``` + +`-cxx-interoperability-mode=default` switches on C++ importing. Each `-Xcc` +forwards the following argument to embedded Clang, in this case selecting C++17 +and loading a module map. CMake generator expressions keep these flags attached +only to Swift compilation. + +With those pieces in place, the module names from the generated maps become +ordinary Swift imports: + +```swift +import LunaSVGMod +import SDL3Mod +``` + +### A Small macOS Linker Wrinkle + +Real dependency graphs occasionally expose assumptions made by one compiler +driver that another driver does not share. SDL3 exports a raw +`-Wl,-weak_framework,CoreHaptics` option on macOS, but the `swiftc` driver +cannot parse that form. The example removes this single option from SDL's +imported target and adds the framework using arguments that `swiftc` +understands: + +```cmake +get_target_property(_sdl3_link_opts SDL3::SDL3 INTERFACE_LINK_OPTIONS) +if(_sdl3_link_opts) + list(FILTER _sdl3_link_opts EXCLUDE REGEX "weak_framework") + set_target_properties( + SDL3::SDL3 + PROPERTIES INTERFACE_LINK_OPTIONS "${_sdl3_link_opts}" + ) +endif() + +target_link_options( + demo PRIVATE + "SHELL:-Xlinker -framework -Xlinker CoreHaptics" +) +``` + +This is not a Swift binding layer. It is a narrow adaptation between +linker-driver syntaxes, and a useful reminder that direct language +interoperability still sits inside a complete native toolchain. + +## Calling an Unmodified C++ API from Swift + +Once the module is visible, the LunaSVG calls are almost a transcription of the +C++ API: + +```swift +let svg = sceneSVG(cloud1X: cloud1X, cloud2X: cloud2X) +let doc = lunasvg.Document.loadFromData(svg) + +pixels.withUnsafeMutableBytes { storage in + let address = storage.bindMemory(to: UInt8.self).baseAddress! + var bitmap = lunasvg.Bitmap( + address, + winW, + winH, + Int32(stride) + ) + bitmap.clear(0x00000000) + doc.pointee.render(&bitmap, lunasvg.Matrix()) +} +``` + +Several interoperability features appear in these few lines: + +- The C++ `lunasvg` namespace is available directly in Swift. +- `Document::loadFromData` is imported as a static method. LunaSVG returns a + `std::unique_ptr`, which Swift can dereference through `pointee` + while the smart pointer retains ownership. +- The `Bitmap` C++ constructor is called directly. +- `Matrix()` constructs another C++ value in Swift. +- `render` invokes a C++ member function on the document. + +Direct does not mean that every operation is zero-copy. Passing a Swift `String` +to an API that expects `std::string`, for example, can require a conversion and +an allocation. The useful property here is that the integration does not require +a hand-written C facade; normal costs implied by the two type systems still +apply. + +The most interesting boundary is the pixel buffer. Swift owns `[UInt8]`, while +LunaSVG receives a pointer to its storage and renders into it. +`withUnsafeMutableBytes` scopes that pointer access: the pointer must not escape +the closure, and the array must not be resized while C++ is using its storage. + +After rendering, the same bytes go to SDL: + +```swift +pixels.withUnsafeBytes { storage in + _ = SDL_UpdateTexture( + texture, + nil, + storage.baseAddress, + Int32(stride) + ) +} + +_ = SDL_RenderClear(renderer) +_ = SDL_RenderTexture(renderer, texture, nil, nil) +_ = SDL_RenderPresent(renderer) +``` + +SDL exposes a C API, which Swift has long been able to import through Clang. The +C++ interoperability mode is needed for LunaSVG, while the module-map and +dependency-management pattern applies to both libraries. Using explicit types +such as `CFloat`, `Int32`, and `UInt8` also keeps the native widths visible at +the boundary. + +LunaSVG is a static SVG renderer, so the sample regenerates the SVG text with +new cloud positions for each frame. That keeps the animation intentionally +simple and keeps the focus on the native interoperability path. + +## Direct Calls Make ABI Compatibility More Important, Not Less + +Avoiding a C wrapper removes boilerplate and an extra API surface, but it does +not create an ABI firewall. The generated Swift code calls the C++ ABI expected +by the imported declarations. The headers used during Swift compilation +therefore need to agree with the linked binaries on the details that affect that +ABI. + +That includes: + +- Target operating system, architecture, and deployment target. +- Compiler ABI and C++ standard-library choice. +- Debug/Release and other relevant build settings. +- Dependency versions and transitive dependencies. +- Preprocessor definitions or package options that change public declarations or + layouts. + +The Swift and C++ sides must also use the same C++ standard library. On Apple +platforms that normally means libc++. Conan profiles, package IDs, and +dependency metadata help keep these decisions explicit. `CMakeToolchain` carries +the selected build configuration into CMake, and `CMakeDeps` gives CMake targets +that describe how each package is meant to be consumed. + +This is the deeper value of putting Conan underneath Swift/C++ interoperability: +the language feature lets Swift express the call, while the package manager +makes the native artifact behind that call reproducible. + +Conan cannot infer every compatibility rule automatically. If a library has an +ABI-changing macro or option that its recipe does not model, the recipe still +needs to expose it correctly. Direct C++ interoperability rewards accurate +package metadata. + +## Ownership and Safety Still Cross the Boundary + +The syntax can look very Swift-like, but the semantics still come from both +languages. A few rules are especially important when moving beyond a demo: + +- **C++ classes are generally imported as Swift value types.** Copies can run + C++ copy constructors, and destruction runs C++ destructors. For large + containers, an innocent-looking Swift copy or iteration can therefore have a + real cost. +- **Pointers and views need explicit lifetime reasoning.** `withUnsafeBytes` and + `withUnsafeMutableBytes` make the valid scope clear in this example, but Swift + cannot prove that an arbitrary third-party function will not retain the + pointer. The C++ API contract still matters. +- **Noncopyable ownership should remain visible.** A `std::unique_ptr` is not a + shared reference. Keeping the owner alive while using `pointee` is part of the + program's correctness. +- **C++ exceptions are not Swift errors.** Swift cannot catch a C++ exception. A + production boundary should prevent exceptions from escaping C++ into Swift. +- **Interop supports a growing subset of C++.** Rvalue-reference APIs, some + template patterns, and C++20 named modules still have limitations. Check the + current [Swift C++ interoperability + status](https://www.swift.org/documentation/cxx-interop/status/) when + evaluating a library. + +Swift 6.2 also added stricter memory-safety checking and new safe-interop +facilities for annotated C++ APIs. Those features can improve bounds and +lifetime checking when you control or can adapt the C++ interface, but they do +not retroactively make every raw-pointer API safe. + +The sample keeps error handling short to make the interop mechanics visible. +Production code should additionally check the nullable results from window, +renderer, texture, and document creation and report the corresponding SDL or +parse errors. + +## The Reusable Pattern + +Although this demo renders moving clouds, the integration pattern is not +graphics-specific: + +1. Declare the native libraries and relevant settings in Conan. +2. Let Conan select or build a compatible dependency graph. +3. Generate a Clang module map when a package does not provide one. +4. Link the Conan-generated CMake targets normally. +5. Enable Swift C++ interoperability and pass the module map to embedded Clang. +6. Treat ownership, lifetimes, error models, and ABI options as part of the API + boundary. + +That opens a large body of existing C and C++ libraries to Swift without +requiring each upstream project to publish a separate Swift wrapper. A +purpose-built wrapper can still be valuable when an API is unsafe, +exception-heavy, or awkward to import, but it is now an architectural choice +rather than an automatic prerequisite. + +In short: Swift can speak to the C++ API, and Conan can make sure the right +native implementation is there to answer. + +Try the [complete +example](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop), +then take a look at the official [Swift C++ interoperability +guide](https://www.swift.org/documentation/cxx-interop/) for the complete +mapping and safety rules. If you have questions or feedback, please open an +issue on [GitHub](https://github.com/conan-io/conan/issues). + +Happy coding! + +*This post was written with AI assistance and reviewed by humans.* From ef310446850305ca900058fbc48c314d9d642b23 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 07:32:35 +0200 Subject: [PATCH 02/27] wip --- ...p-With-Conan-Managed-Dependencies.markdown | 106 +++++++++++++----- 1 file changed, 80 insertions(+), 26 deletions(-) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 9260389d..b3021c39 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -2,19 +2,51 @@ layout: post comments: false title: "Calling Conan-Managed C and C++ Libraries Directly from Swift" -description: "A Swift app calls two unmodified ConanCenter packages, LunaSVG and SDL, directly through Swift's C++ interoperability mode, with no hand-written C wrapper." +description: "A Swift app calls unmodified C and C++ ConanCenter packages, SDL and LunaSVG, directly through Swift's native interoperability, with no hand-written wrapper." meta_title: "Swift C++ Interop with Conan-Managed Dependencies - Conan Blog" categories: [cpp, conan, swift, macos, cmake] --- -Swift's C++ interoperability makes an appealing promise: import a C++ module and -call its APIs directly from Swift, without first building a C wrapper. But a -real C++ library is more than a header. It also brings compiled binaries, -transitive dependencies, build options, a C++ standard library, and ABI -constraints. +Swift's C++ interoperability makes an appealing promise: import a C++ library +through a Clang module and call its APIs directly from Swift, without first +building a C wrapper. But a real C++ library is more than a header. It also +brings compiled binaries, transitive dependencies, build options, a C++ standard +library, and ABI constraints. That is where Conan fits. +## From C Interoperability to C++ Interoperability + +Swift's native interoperability story started with C and Objective-C. Those APIs +are imported through Clang by default: functions, structures, enumerations, and +pointers declared in headers become declarations that Swift can call, while the +implementation remains in the original native library. That made C the usual +common denominator for exposing an existing native library to Swift. + +C++ was a harder boundary. Namespaces, overloaded functions, templates, +constructors and destructors, value semantics, the C++ standard library, and +platform-specific ABI rules all have to be represented correctly. Before +supported C++ interoperability arrived in [Swift +5.9](https://www.swift.org/blog/swift-5.9-released/), the usual approach was to +put a C facade or an Objective-C++ wrapper in front of a C++ library. That +works, but it also creates another API surface to design, build, and maintain. + +Swift 5.9 introduced bidirectional C++ interoperability for a useful subset of +the language. [Swift 6](https://www.swift.org/blog/announcing-swift-6/) expanded +it with move-only C++ types, virtual methods, default arguments, and more +standard-library types. [Swift +6.2](https://www.swift.org/blog/swift-6.2-released/) then added opt-in safety +facilities for pointers and view types. The feature continues to evolve, but a +large class of libraries can now be consumed without first reducing their API to +C. + +This example puts both paths in the same executable. SDL3 exposes a plain C API +and follows Swift's long-established C interoperability path. LunaSVG exposes +C++ classes and returns a `std::unique_ptr`, so it exercises the newer C++ path. +Both start with Clang-readable headers and end in calls to native libraries, but +only the C++ side needs C++ interoperability mode and the additional C++ ABI and +standard-library guarantees. + In this post, we will build a small Swift application with two unmodified packages from ConanCenter: @@ -25,7 +57,8 @@ packages from ConanCenter: The application generates an animated SVG scene in Swift, asks LunaSVG to rasterize it into a Swift-owned pixel buffer, and uploads that buffer to an SDL texture. Neither dependency knows anything about Swift, neither ships a Swift -module map, and there is no wrapper library between Swift and C++. +module map, and there is no wrapper library between Swift and either native +dependency. The complete example is available in the [Conan examples repository](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop). @@ -38,6 +71,7 @@ most of the boundaries that matter in a real project: - Conan resolves and, when necessary, builds two ordinary ConanCenter packages. - CMake receives package configuration and imported targets from Conan. - Small generated Clang module maps make the libraries importable by Swift. +- The same Swift target imports a plain C API and a C++ API side by side. - Swift calls C++ classes, static methods, constructors, and member functions directly. - A C++ object writes into memory owned by a Swift `Array`. @@ -51,10 +85,9 @@ The result is deliberately visual: a window with clouds drifting over a horizon. The example currently targets macOS and requires the Xcode command-line tools, CMake 3.28 or newer, and Ninja. We select Ninja explicitly because CMake's Swift support does not work with the Unix Makefiles generator that some CMake versions -choose by default on macOS. C++ interoperability was introduced in Swift 5.9 and -has continued to evolve; use a recent Swift toolchain for the full set of -features exercised here, including importing the `std::unique_ptr` returned by -LunaSVG. +choose by default on macOS. Use a recent Swift toolchain: the example exercises +support for C++ move-only types through the `std::unique_ptr` returned by +LunaSVG, an area expanded in Swift 6. ```bash git clone https://github.com/conan-io/examples2.git @@ -79,9 +112,10 @@ the declarations in the public C++ headers and emits calls that follow the target C++ ABI. At link time, those calls are resolved against the already compiled libraries supplied by Conan. -The Swift compiler embeds Clang, so it can parse the headers as a Clang module -and expose their declarations to Swift. Public C++ classes normally appear as -Swift value types; constructors become initializers, member functions become +As the [Swift documentation](https://www.swift.org/documentation/cxx-interop/) +puts it: "The Swift compiler embeds the Clang compiler. This allows Swift to +import C++ header files using Clang modules." Public C++ classes normally appear +as Swift value types; constructors become initializers, member functions become methods, and namespaces remain available. The compiler then lowers those operations to calls that follow the target C++ ABI, and the linker resolves their symbols in the Conan package binaries. Compiler-generated adapters may @@ -197,8 +231,8 @@ selected by Conan and also respects component metadata — in SDL's case, the `sdl3` component. Second, these are **Clang modules**, not the named modules introduced by C++20. -Swift's C++ importer currently consumes headers through Clang module maps; it -does not import C++20 modules directly. +As the same documentation states plainly: "Swift currently cannot import C++ +modules introduced in the C++20 language standard." In a library designed specifically for Swift consumption, a maintained module map can live beside the public headers. Generating one in the consumer is a @@ -331,7 +365,22 @@ LunaSVG receives a pointer to its storage and renders into it. `withUnsafeMutableBytes` scopes that pointer access: the pointer must not escape the closure, and the array must not be resized while C++ is using its storage. -After rendering, the same bytes go to SDL: +LunaSVG is a static SVG renderer, so the sample regenerates the SVG text with +new cloud positions for each frame. That keeps the animation intentionally +simple and keeps the focus on the native interoperability path. + +## Calling a Plain C API from Swift + +The other half of the example uses Swift's older [C interoperability +path](https://www.swift.org/blog/improving-usability-of-c-libraries-in-swift/). +SDL3 exposes a C API, so importing it does not itself require +`-cxx-interoperability-mode=default`; the target enables that mode because it +also imports LunaSVG. The generated SDL module map is still required, because +Swift imports both C and C++ headers as Clang modules. + +Importing `SDL3Mod` makes SDL's C functions, structures, constants, and pointer +types available directly. After LunaSVG has rendered the image, the same bytes +go to SDL: ```swift pixels.withUnsafeBytes { storage in @@ -348,15 +397,20 @@ _ = SDL_RenderTexture(renderer, texture, nil, nil) _ = SDL_RenderPresent(renderer) ``` -SDL exposes a C API, which Swift has long been able to import through Clang. The -C++ interoperability mode is needed for LunaSVG, while the module-map and -dependency-management pattern applies to both libraries. Using explicit types -such as `CFloat`, `Int32`, and `UInt8` also keeps the native widths visible at -the boundary. - -LunaSVG is a static SVG renderer, so the sample regenerates the SVG text with -new cloud positions for each frame. That keeps the animation intentionally -simple and keeps the focus on the native interoperability path. +These calls intentionally remain close to SDL's C documentation. There are no +C++ namespaces, constructors, templates, exceptions, name mangling, or C++ +standard-library types at this boundary. That makes C binary compatibility +simpler, but not automatic: the headers and library must still match the target +platform, architecture, and calling convention. + +Nor does direct C interoperability make a C API automatically safe or +Swift-shaped. SDL handles are pointers, errors need explicit checks, and +resources are released with functions such as `SDL_DestroyTexture` and +`SDL_DestroyWindow`. Using explicit types such as `CFloat`, `Int32`, and `UInt8` +keeps the native widths visible, while `withUnsafeBytes` limits how long SDL can +access the Swift-owned buffer. Conan and CMake provide the binary and link +requirements for SDL in exactly the same way as for LunaSVG; only the language +mapping at the call boundary is different. ## Direct Calls Make ABI Compatibility More Important, Not Less From 410989147891d371b39fd85475d5666078dc7f99 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 07:55:58 +0200 Subject: [PATCH 03/27] wip --- ...p-With-Conan-Managed-Dependencies.markdown | 314 +++++++----------- 1 file changed, 119 insertions(+), 195 deletions(-) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index b3021c39..272b090c 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -1,9 +1,9 @@ --- layout: post comments: false -title: "Calling Conan-Managed C and C++ Libraries Directly from Swift" -description: "A Swift app calls unmodified C and C++ ConanCenter packages, SDL and LunaSVG, directly through Swift's native interoperability, with no hand-written wrapper." -meta_title: "Swift C++ Interop with Conan-Managed Dependencies - Conan Blog" +title: "Calling a Conan-Managed C++ Library Directly from Swift" +description: "A Swift app calls an unmodified C++ ConanCenter package, LunaSVG, directly through Swift's native C++ interoperability, with no hand-written wrapper." +meta_title: "Swift C++ Interop with a Conan-Managed Dependency - Conan Blog" categories: [cpp, conan, swift, macos, cmake] --- @@ -40,25 +40,19 @@ facilities for pointers and view types. The feature continues to evolve, but a large class of libraries can now be consumed without first reducing their API to C. -This example puts both paths in the same executable. SDL3 exposes a plain C API -and follows Swift's long-established C interoperability path. LunaSVG exposes -C++ classes and returns a `std::unique_ptr`, so it exercises the newer C++ path. -Both start with Clang-readable headers and end in calls to native libraries, but -only the C++ side needs C++ interoperability mode and the additional C++ ABI and -standard-library guarantees. +This example exercises that C++ path end to end: LunaSVG exposes C++ classes, +returns a `std::unique_ptr`, and is called from Swift without ever reducing its +API to C. -In this post, we will build a small Swift application with two unmodified -packages from ConanCenter: +In this post, we will build a small Swift application around one unmodified +package from ConanCenter: - [LunaSVG](https://conan.io/center/recipes/lunasvg), a C++ SVG renderer. -- [SDL](https://conan.io/center/recipes/sdl), whose SDL3 C API creates the - window and displays the rendered pixels. -The application generates an animated SVG scene in Swift, asks LunaSVG to -rasterize it into a Swift-owned pixel buffer, and uploads that buffer to an SDL -texture. Neither dependency knows anything about Swift, neither ships a Swift -module map, and there is no wrapper library between Swift and either native -dependency. +The application builds an SVG scene as a Swift string, hands it to LunaSVG to +parse and rasterize under two different CSS stylesheets, and writes each result +to a PNG file. LunaSVG knows nothing about Swift, ships no Swift module map, and +there is no wrapper library between Swift and the library. The complete example is available in the [Conan examples repository](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop). @@ -68,26 +62,27 @@ repository](https://github.com/conan-io/examples2/tree/main/examples/languages/s A trivial interop sample can stop at a header-only function. This one crosses most of the boundaries that matter in a real project: -- Conan resolves and, when necessary, builds two ordinary ConanCenter packages. -- CMake receives package configuration and imported targets from Conan. -- Small generated Clang module maps make the libraries importable by Swift. -- The same Swift target imports a plain C API and a C++ API side by side. -- Swift calls C++ classes, static methods, constructors, and member functions - directly. -- A C++ object writes into memory owned by a Swift `Array`. -- The resulting executable links the native libraries and their platform - requirements. +- Conan resolves and, when necessary, builds an ordinary ConanCenter package. +- CMake receives package configuration and an imported target from Conan. +- A small generated Clang module map makes the library importable by Swift. +- Swift calls a C++ namespace, a static method, and instance methods directly. +- A C++ static method returns a `std::unique_ptr`, and a C++ method returns a + value type by value. +- The resulting executable links the native library and writes real output + files. -The result is deliberately visual: a window with clouds drifting over a horizon. +The result is deliberately visual: the same illustrated landscape, rendered +twice under two different color palettes. ## Build and Run It The example currently targets macOS and requires the Xcode command-line tools, CMake 3.28 or newer, and Ninja. We select Ninja explicitly because CMake's Swift support does not work with the Unix Makefiles generator that some CMake versions -choose by default on macOS. Use a recent Swift toolchain: the example exercises -support for C++ move-only types through the `std::unique_ptr` returned by -LunaSVG, an area expanded in Swift 6. +choose by default on macOS. C++ interoperability was introduced in Swift 5.9 and +has continued to evolve; use a recent Swift toolchain for the full set of +features exercised here, including importing the `std::unique_ptr` returned by +LunaSVG. ```bash git clone https://github.com/conan-io/examples2.git @@ -103,14 +98,16 @@ cmake --build --preset conan-release `conan install` resolves the dependency graph and obtains binaries matching the active Conan profile. If a suitable binary is unavailable, `--build=missing` builds it from source. The generated CMake preset then carries that -configuration into the native build. +configuration into the native build. Running the binary writes `summer.png` and +`winter.png` to the working directory and exits — there is no window or event +loop, so the same command also runs unattended in CI. ## What Swift's C++ Interoperability Does — and What It Does Not Do Swift does not translate a C++ library's implementation into Swift. It imports the declarations in the public C++ headers and emits calls that follow the target C++ ABI. At link time, those calls are resolved against the already -compiled libraries supplied by Conan. +compiled library supplied by Conan. As the [Swift documentation](https://www.swift.org/documentation/cxx-interop/) puts it: "The Swift compiler embeds the Clang compiler. This allows Swift to @@ -118,8 +115,8 @@ import C++ header files using Clang modules." Public C++ classes normally appear as Swift value types; constructors become initializers, member functions become methods, and namespaces remain available. The compiler then lowers those operations to calls that follow the target C++ ABI, and the linker resolves -their symbols in the Conan package binaries. Compiler-generated adapters may -still be needed for particular language features, but there is no separately +their symbols in the Conan package binary. Compiler-generated adapters may still +be needed for particular language features, but there is no separately maintained or compiled C wrapper library in this example. This division of responsibilities is useful: @@ -130,10 +127,9 @@ This division of responsibilities is useful: A [Clang module map](https://clang.llvm.org/docs/Modules.html) only tells the importer which headers form a module. It does not contain bindings, compile the library, or make an arbitrary binary ABI-compatible. Conan still has to provide -the matching headers, libraries, transitive requirements, and build -configuration. +the matching headers, library, transitive requirements, and build configuration. -## Describing the Native Dependencies with Conan +## Describing the Native Dependency with Conan The recipe starts like a conventional CMake-based Conan consumer: @@ -150,7 +146,6 @@ class SwiftCppDemo(ConanFile): def requirements(self): self.requires("lunasvg/3.5.0") - self.requires("sdl/3.2.14") def generate(self): CMakeDeps(self).generate() @@ -162,13 +157,13 @@ generates the package configuration consumed by `find_package()`. [`CMakeToolchain`](https://docs.conan.io/2/reference/tools/cmake/cmaketoolchain.html) translates the Conan configuration into CMake toolchain data and presets. That part is independent of Swift: from Conan's perspective, this is a native -executable consuming two C and C++ dependencies. +executable consuming a single C++ dependency. -## Generating the Missing Module Maps +## Generating the Missing Module Map Swift imports C and C++ headers as Clang modules. For that, it must be able to -find a `module.modulemap`. LunaSVG and SDL are regular C/C++ packages and do not -ship one for this use case, so the recipe generates two tiny shim module maps: +find a `module.modulemap`. LunaSVG is a regular C++ package and does not ship +one for this use case, so the recipe generates a tiny shim module map: ```python import os @@ -200,21 +195,9 @@ def generate(self): "LunaSVGMod", f"{lunasvg_include}/lunasvg/lunasvg.h", ) - - sdl_include = ( - self.dependencies["sdl"] - .cpp_info.components["sdl3"] - .includedirs[0] - ) - self._write_modulemap( - "sdl3.modulemap", - "SDL3Mod", - f"{sdl_include}/SDL3/SDL.h", - ) ``` -The generated files are intentionally simple. Conceptually, the LunaSVG one -contains only this: +The generated file is intentionally simple. Conceptually, it contains only this: ``` module LunaSVGMod { @@ -225,12 +208,12 @@ module LunaSVGMod { There are two details worth calling out. -First, the recipe takes header locations from each dependency's `cpp_info` -instead of guessing paths in the Conan cache. This works with the package layout -selected by Conan and also respects component metadata — in SDL's case, the -`sdl3` component. +First, the recipe takes the header location from LunaSVG's `cpp_info` instead of +guessing a path in the Conan cache. This works with whatever package layout +Conan selects, and the same call would work for a package that exposes its +headers through Conan components instead of a single `includedirs` entry. -Second, these are **Clang modules**, not the named modules introduced by C++20. +Second, this is a **Clang module**, not the named modules introduced by C++20. As the same documentation states plainly: "Swift currently cannot import C++ modules introduced in the C++20 language standard." @@ -240,7 +223,7 @@ pragmatic bridge for an existing, Swift-unaware package. ## Connecting Conan, CMake, and `swiftc` -The CMake project enables both Swift and C++ and consumes the targets generated +The CMake project enables both Swift and C++ and consumes the target generated by Conan: ```cmake @@ -248,22 +231,20 @@ cmake_minimum_required(VERSION 3.28) project(swift_cpp_demo LANGUAGES CXX Swift) find_package(lunasvg REQUIRED) -find_package(SDL3 REQUIRED) add_executable(demo main.swift) target_link_libraries(demo PRIVATE lunasvg::lunasvg - sdl::sdl ) ``` -Those imported targets are important. They carry much more than a library -filename: include paths, transitive link requirements, and other usage -information modeled by the packages. +That imported target is important. It carries much more than a library filename: +include paths, transitive link requirements, and other usage information modeled +by the package. -The Swift-specific part enables C++ interoperability and forwards the module -maps to the Clang instance embedded in the Swift compiler: +The Swift-specific part enables C++ interoperability and forwards the module map +to the Clang instance embedded in the Swift compiler: ```cmake get_filename_component( @@ -276,148 +257,83 @@ target_compile_options(demo PRIVATE "$<$:-cxx-interoperability-mode=default>" "$<$:SHELL:-Xcc -std=c++17>" "$<$:SHELL:-Xcc -fmodule-map-file=${_conan_generators_dir}/shim/lunasvg.modulemap>" - "$<$:SHELL:-Xcc -fmodule-map-file=${_conan_generators_dir}/shim/sdl3.modulemap>" ) ``` -`-cxx-interoperability-mode=default` switches on C++ importing. Each `-Xcc` -forwards the following argument to embedded Clang, in this case selecting C++17 -and loading a module map. CMake generator expressions keep these flags attached +`-cxx-interoperability-mode=default` switches on C++ importing. `-Xcc` forwards +the following argument to embedded Clang, in this case selecting C++17 and +loading the module map. CMake generator expressions keep these flags attached only to Swift compilation. -With those pieces in place, the module names from the generated maps become -ordinary Swift imports: +With those pieces in place, the module name from the generated map becomes an +ordinary Swift import, alongside `CxxStdlib`, the overlay module Swift provides +for bridging C++ standard-library types such as `std::string`: ```swift +import CxxStdlib import LunaSVGMod -import SDL3Mod ``` -### A Small macOS Linker Wrinkle - -Real dependency graphs occasionally expose assumptions made by one compiler -driver that another driver does not share. SDL3 exports a raw -`-Wl,-weak_framework,CoreHaptics` option on macOS, but the `swiftc` driver -cannot parse that form. The example removes this single option from SDL's -imported target and adds the framework using arguments that `swiftc` -understands: +## Calling an Unmodified C++ API from Swift -```cmake -get_target_property(_sdl3_link_opts SDL3::SDL3 INTERFACE_LINK_OPTIONS) -if(_sdl3_link_opts) - list(FILTER _sdl3_link_opts EXCLUDE REGEX "weak_framework") - set_target_properties( - SDL3::SDL3 - PROPERTIES INTERFACE_LINK_OPTIONS "${_sdl3_link_opts}" - ) -endif() - -target_link_options( - demo PRIVATE - "SHELL:-Xlinker -framework -Xlinker CoreHaptics" -) -``` +Once the module is visible, the LunaSVG calls are close to a transcription of +the C++ API. The example renders the same SVG scene twice, once per season, by +loading a fresh `Document`, applying a different CSS stylesheet, and rendering +the result to a bitmap: -This is not a Swift binding layer. It is a narrow adaptation between -linker-driver syntaxes, and a useful reminder that direct language -interoperability still sits inside a complete native toolchain. +```swift +let seasons = [ + (name: "summer", css: ".sky{fill:#8ECBEB} .hills{fill:#8FA89B} .ground{fill:#8FC77E} .cloud{fill:#FFFFFF}"), + (name: "winter", css: ".sky{fill:#C9D6E3} .hills{fill:#9FAFAF} .ground{fill:#F2F5F7} .cloud{fill:#E7EEF3}"), +] -## Calling an Unmodified C++ API from Swift +for season in seasons { + let document = lunasvg.Document.loadFromData(std.string(svg)) + document.pointee.applyStyleSheet(std.string(season.css)) -Once the module is visible, the LunaSVG calls are almost a transcription of the -C++ API: + let bitmap = document.pointee.renderToBitmap() + _ = bitmap.writeToPng(std.string("\(season.name).png")) -```swift -let svg = sceneSVG(cloud1X: cloud1X, cloud2X: cloud2X) -let doc = lunasvg.Document.loadFromData(svg) - -pixels.withUnsafeMutableBytes { storage in - let address = storage.bindMemory(to: UInt8.self).baseAddress! - var bitmap = lunasvg.Bitmap( - address, - winW, - winH, - Int32(stride) - ) - bitmap.clear(0x00000000) - doc.pointee.render(&bitmap, lunasvg.Matrix()) + print("Generated \(season.name).png") } ``` +A fresh `Document` is loaded for each season rather than reused, because +`applyStyleSheet` mutates the document it is called on; there is no way to undo +a stylesheet once applied. + Several interoperability features appear in these few lines: - The C++ `lunasvg` namespace is available directly in Swift. - `Document::loadFromData` is imported as a static method. LunaSVG returns a - `std::unique_ptr`, which Swift can dereference through `pointee` - while the smart pointer retains ownership. -- The `Bitmap` C++ constructor is called directly. -- `Matrix()` constructs another C++ value in Swift. -- `render` invokes a C++ member function on the document. - -Direct does not mean that every operation is zero-copy. Passing a Swift `String` -to an API that expects `std::string`, for example, can require a conversion and -an allocation. The useful property here is that the integration does not require -a hand-written C facade; normal costs implied by the two type systems still + `std::unique_ptr`, which Swift dereferences through `pointee` while + the smart pointer keeps ownership of the object. +- `applyStyleSheet` and `renderToBitmap` are called as ordinary instance methods + on that dereferenced value. +- `renderToBitmap` returns a `Bitmap` by value — a C++ object constructed on the + C++ side and handed back into Swift. +- `writeToPng` is a `const` C++ member function that writes to disk. + +The `std.string(...)` calls are not decoration. As the [Swift +documentation](https://www.swift.org/documentation/cxx-interop/) states: "Swift +does not convert C++ `std::string` type to Swift's `String` type automatically." +Every Swift `String` crossing into an API that expects `std::string` — the SVG +markup, the CSS, the output filename — goes through an explicit +`std.string(...)` initializer from the `CxxStdlib` overlay module. That +conversion allocates and copies; it is not free, and it is not automatic, only +explicit and predictable. + +Direct does not mean that every operation is zero-copy in general, either. The +useful property here is that the integration does not require a hand-written C +facade; the normal costs implied by two type systems meeting at a boundary still apply. -The most interesting boundary is the pixel buffer. Swift owns `[UInt8]`, while -LunaSVG receives a pointer to its storage and renders into it. -`withUnsafeMutableBytes` scopes that pointer access: the pointer must not escape -the closure, and the array must not be resized while C++ is using its storage. - -LunaSVG is a static SVG renderer, so the sample regenerates the SVG text with -new cloud positions for each frame. That keeps the animation intentionally -simple and keeps the focus on the native interoperability path. - -## Calling a Plain C API from Swift - -The other half of the example uses Swift's older [C interoperability -path](https://www.swift.org/blog/improving-usability-of-c-libraries-in-swift/). -SDL3 exposes a C API, so importing it does not itself require -`-cxx-interoperability-mode=default`; the target enables that mode because it -also imports LunaSVG. The generated SDL module map is still required, because -Swift imports both C and C++ headers as Clang modules. - -Importing `SDL3Mod` makes SDL's C functions, structures, constants, and pointer -types available directly. After LunaSVG has rendered the image, the same bytes -go to SDL: - -```swift -pixels.withUnsafeBytes { storage in - _ = SDL_UpdateTexture( - texture, - nil, - storage.baseAddress, - Int32(stride) - ) -} - -_ = SDL_RenderClear(renderer) -_ = SDL_RenderTexture(renderer, texture, nil, nil) -_ = SDL_RenderPresent(renderer) -``` - -These calls intentionally remain close to SDL's C documentation. There are no -C++ namespaces, constructors, templates, exceptions, name mangling, or C++ -standard-library types at this boundary. That makes C binary compatibility -simpler, but not automatic: the headers and library must still match the target -platform, architecture, and calling convention. - -Nor does direct C interoperability make a C API automatically safe or -Swift-shaped. SDL handles are pointers, errors need explicit checks, and -resources are released with functions such as `SDL_DestroyTexture` and -`SDL_DestroyWindow`. Using explicit types such as `CFloat`, `Int32`, and `UInt8` -keeps the native widths visible, while `withUnsafeBytes` limits how long SDL can -access the Swift-owned buffer. Conan and CMake provide the binary and link -requirements for SDL in exactly the same way as for LunaSVG; only the language -mapping at the call boundary is different. - ## Direct Calls Make ABI Compatibility More Important, Not Less Avoiding a C wrapper removes boilerplate and an extra API surface, but it does not create an ABI firewall. The generated Swift code calls the C++ ABI expected by the imported declarations. The headers used during Swift compilation -therefore need to agree with the linked binaries on the details that affect that +therefore need to agree with the linked binary on the details that affect that ABI. That includes: @@ -433,7 +349,7 @@ The Swift and C++ sides must also use the same C++ standard library. On Apple platforms that normally means libc++. Conan profiles, package IDs, and dependency metadata help keep these decisions explicit. `CMakeToolchain` carries the selected build configuration into CMake, and `CMakeDeps` gives CMake targets -that describe how each package is meant to be consumed. +that describe how the package is meant to be consumed. This is the deeper value of putting Conan underneath Swift/C++ interoperability: the language feature lets Swift express the call, while the package manager @@ -453,10 +369,12 @@ languages. A few rules are especially important when moving beyond a demo: C++ copy constructors, and destruction runs C++ destructors. For large containers, an innocent-looking Swift copy or iteration can therefore have a real cost. -- **Pointers and views need explicit lifetime reasoning.** `withUnsafeBytes` and - `withUnsafeMutableBytes` make the valid scope clear in this example, but Swift - cannot prove that an arbitrary third-party function will not retain the - pointer. The C++ API contract still matters. +- **Pointers and views still need explicit lifetime reasoning.** Even though + this example only crosses a `std::unique_ptr`, many C++ APIs hand back raw + pointers or views instead; Swift's `withUnsafeBytes` and + `withUnsafeMutableBytes` scope such access explicitly, but Swift cannot prove + that an arbitrary third-party function will not retain a pointer past that + scope. The C++ API contract still matters. - **Noncopyable ownership should remain visible.** A `std::unique_ptr` is not a shared reference. Keeping the owner alive while using `pointee` is part of the program's correctness. @@ -474,23 +392,29 @@ lifetime checking when you control or can adapt the C++ interface, but they do not retroactively make every raw-pointer API safe. The sample keeps error handling short to make the interop mechanics visible. -Production code should additionally check the nullable results from window, -renderer, texture, and document creation and report the corresponding SDL or -parse errors. +Production code should additionally check `loadFromData`'s result for a null +pointer before dereferencing it through `pointee`, and check the boolean +returned by `writeToPng`. ## The Reusable Pattern -Although this demo renders moving clouds, the integration pattern is not -graphics-specific: +Although this demo renders a small illustrated landscape, the integration +pattern is not graphics-specific: -1. Declare the native libraries and relevant settings in Conan. -2. Let Conan select or build a compatible dependency graph. +1. Declare the native library and relevant settings in Conan. +2. Let Conan select or build a compatible binary. 3. Generate a Clang module map when a package does not provide one. -4. Link the Conan-generated CMake targets normally. +4. Link the Conan-generated CMake target normally. 5. Enable Swift C++ interoperability and pass the module map to embedded Clang. 6. Treat ownership, lifetimes, error models, and ABI options as part of the API boundary. +Pure C libraries follow the same general dependency pattern: Conan provides the +headers and the binary, and a Clang module map makes the headers importable. +They do not, however, require C++ interoperability mode or the C++ ABI +guarantees described above — Swift has been able to import a plain C API through +Clang since long before C++ interoperability existed. + That opens a large body of existing C and C++ libraries to Swift without requiring each upstream project to publish a separate Swift wrapper. A purpose-built wrapper can still be valuable when an API is unsafe, From 3f5a534953cdc33983958f168411763a424ca468 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 11:31:40 +0200 Subject: [PATCH 04/27] wip --- ...p-With-Conan-Managed-Dependencies.markdown | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 272b090c..57fb6c1f 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -71,9 +71,6 @@ most of the boundaries that matter in a real project: - The resulting executable links the native library and writes real output files. -The result is deliberately visual: the same illustrated landscape, rendered -twice under two different color palettes. - ## Build and Run It The example currently targets macOS and requires the Xcode command-line tools, @@ -99,8 +96,7 @@ cmake --build --preset conan-release active Conan profile. If a suitable binary is unavailable, `--build=missing` builds it from source. The generated CMake preset then carries that configuration into the native build. Running the binary writes `summer.png` and -`winter.png` to the working directory and exits — there is no window or event -loop, so the same command also runs unattended in CI. +`winter.png` to the working directory. ## What Swift's C++ Interoperability Does — and What It Does Not Do @@ -255,15 +251,27 @@ get_filename_component( target_compile_options(demo PRIVATE "$<$:-cxx-interoperability-mode=default>" - "$<$:SHELL:-Xcc -std=c++17>" + "$<$:SHELL:-Xcc -std=c++${CMAKE_CXX_STANDARD}>" "$<$:SHELL:-Xcc -fmodule-map-file=${_conan_generators_dir}/shim/lunasvg.modulemap>" ) ``` `-cxx-interoperability-mode=default` switches on C++ importing. `-Xcc` forwards -the following argument to embedded Clang, in this case selecting C++17 and -loading the module map. CMake generator expressions keep these flags attached -only to Swift compilation. +the following argument to embedded Clang. `CMAKE_CXX_STANDARD` is not +hardcoded here — `CMakeToolchain` already sets it from the active profile's +`compiler.cppstd` when it generates `conan_toolchain.cmake`, the same setting +that determined which C++ dialect lunasvg itself was built with. Reading it +back is a one-line habit, not a fix for an active bug in this particular +library: LunaSVG's header has no code conditioned on the active C++ standard, +so a hardcoded `c++17` would behave identically here. But some libraries do +gate declarations behind `#if __cplusplus` — GCC's libstdc++ famously did this +for years with its ["dual +ABI"](https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html) for +`std::string`, and modern libraries such as Abseil still gate parts of their +public API on the active standard. For those, letting this value silently +drift from what Conan resolved is how you get a linker error, or worse, a +mismatched layout that never surfaces as one. CMake generator expressions keep +the resulting flags attached only to Swift compilation. With those pieces in place, the module name from the generated map becomes an ordinary Swift import, alongside `CxxStdlib`, the overlay module Swift provides @@ -323,11 +331,6 @@ markup, the CSS, the output filename — goes through an explicit conversion allocates and copies; it is not free, and it is not automatic, only explicit and predictable. -Direct does not mean that every operation is zero-copy in general, either. The -useful property here is that the integration does not require a hand-written C -facade; the normal costs implied by two type systems meeting at a boundary still -apply. - ## Direct Calls Make ABI Compatibility More Important, Not Less Avoiding a C wrapper removes boilerplate and an extra API surface, but it does @@ -421,9 +424,6 @@ purpose-built wrapper can still be valuable when an API is unsafe, exception-heavy, or awkward to import, but it is now an architectural choice rather than an automatic prerequisite. -In short: Swift can speak to the C++ API, and Conan can make sure the right -native implementation is there to answer. - Try the [complete example](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop), then take a look at the official [Swift C++ interoperability From 21949ca463de0ba3f83b7e412aa305f65adad93d Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 15:02:57 +0200 Subject: [PATCH 05/27] wip --- ...p-With-Conan-Managed-Dependencies.markdown | 597 ++++++++---------- assets/post_images/2026-08-12/summer.png | Bin 0 -> 12458 bytes assets/post_images/2026-08-12/winter.png | Bin 0 -> 12385 bytes 3 files changed, 254 insertions(+), 343 deletions(-) create mode 100644 assets/post_images/2026-08-12/summer.png create mode 100644 assets/post_images/2026-08-12/winter.png diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 57fb6c1f..0888a52b 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -1,137 +1,133 @@ --- layout: post comments: false -title: "Calling a Conan-Managed C++ Library Directly from Swift" -description: "A Swift app calls an unmodified C++ ConanCenter package, LunaSVG, directly through Swift's native C++ interoperability, with no hand-written wrapper." -meta_title: "Swift C++ Interop with a Conan-Managed Dependency - Conan Blog" +title: "Calling a C++ Library Directly from Swift" +description: "Swift can call supported C++ APIs without a hand-written wrapper. We use Conan to supply an ordinary LunaSVG package and connect its headers, binary, and build settings to Swift." +meta_title: "Calling a C++ Library Directly from Swift with Conan - Conan Blog" categories: [cpp, conan, swift, macos, cmake] --- -Swift's C++ interoperability makes an appealing promise: import a C++ library -through a Clang module and call its APIs directly from Swift, without first -building a C wrapper. But a real C++ library is more than a header. It also -brings compiled binaries, transitive dependencies, build options, a C++ standard -library, and ABI constraints. - -That is where Conan fits. - -## From C Interoperability to C++ Interoperability - -Swift's native interoperability story started with C and Objective-C. Those APIs -are imported through Clang by default: functions, structures, enumerations, and -pointers declared in headers become declarations that Swift can call, while the -implementation remains in the original native library. That made C the usual -common denominator for exposing an existing native library to Swift. - -C++ was a harder boundary. Namespaces, overloaded functions, templates, -constructors and destructors, value semantics, the C++ standard library, and -platform-specific ABI rules all have to be represented correctly. Before -supported C++ interoperability arrived in [Swift -5.9](https://www.swift.org/blog/swift-5.9-released/), the usual approach was to -put a C facade or an Objective-C++ wrapper in front of a C++ library. That -works, but it also creates another API surface to design, build, and maintain. - -Swift 5.9 introduced bidirectional C++ interoperability for a useful subset of -the language. [Swift 6](https://www.swift.org/blog/announcing-swift-6/) expanded -it with move-only C++ types, virtual methods, default arguments, and more -standard-library types. [Swift -6.2](https://www.swift.org/blog/swift-6.2-released/) then added opt-in safety -facilities for pointers and view types. The feature continues to evolve, but a -large class of libraries can now be consumed without first reducing their API to -C. - -This example exercises that C++ path end to end: LunaSVG exposes C++ classes, -returns a `std::unique_ptr`, and is called from Swift without ever reducing its -API to C. - -In this post, we will build a small Swift application around one unmodified -package from ConanCenter: - -- [LunaSVG](https://conan.io/center/recipes/lunasvg), a C++ SVG renderer. - -The application builds an SVG scene as a Swift string, hands it to LunaSVG to -parse and rasterize under two different CSS stylesheets, and writes each result -to a PNG file. LunaSVG knows nothing about Swift, ships no Swift module map, and -there is no wrapper library between Swift and the library. - -The complete example is available in the [Conan examples -repository](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop). +Swift has been able to import C and Objective-C APIs since its early releases. +C++ was a harder boundary: namespaces, overloaded functions, templates, +constructors, destructors, and the C++ standard library all have to retain their +meaning across languages. Calling a C++ library from Swift therefore usually +meant putting a C facade or an Objective-C++ wrapper in front of it. -## Why This Example Is Interesting +[Swift 5.9](https://www.swift.org/blog/swift-5.9-released/) changed that by +introducing direct C++ interoperability. Support has continued to expand: Swift +6 added move-only C++ types and more standard-library types, `std::unique_ptr` +support arrived in 2025, and Swift 6.2 introduced safer ways to work with +annotated pointer and view APIs. -A trivial interop sample can stop at a header-only function. This one crosses -most of the boundaries that matter in a real project: +Most interop samples control both sides of the boundary. We wanted to try the +less tidy case: could Swift call an ordinary, pre-existing C++ package without +modifying its source or writing a wrapper library? -- Conan resolves and, when necessary, builds an ordinary ConanCenter package. -- CMake receives package configuration and an imported target from Conan. -- A small generated Clang module map makes the library importable by Swift. -- Swift calls a C++ namespace, a static method, and instance methods directly. -- A C++ static method returns a `std::unique_ptr`, and a C++ method returns a - value type by value. -- The resulting executable links the native library and writes real output - files. +For this example, we use [LunaSVG](https://conan.io/center/recipes/lunasvg), a +C++ SVG renderer from ConanCenter. The Swift application creates an SVG scene, +asks LunaSVG to render it with two stylesheets, and writes `summer.png` and +`winter.png`. LunaSVG has no Swift-specific code and does not ship a Swift +module map. -## Build and Run It +The result is a useful division of responsibilities: -The example currently targets macOS and requires the Xcode command-line tools, -CMake 3.28 or newer, and Ninja. We select Ninja explicitly because CMake's Swift -support does not work with the Unix Makefiles generator that some CMake versions -choose by default on macOS. C++ interoperability was introduced in Swift 5.9 and -has continued to evolve; use a recent Swift toolchain for the full set of -features exercised here, including importing the `std::unique_ptr` returned by -LunaSVG. +- Swift interoperability teaches the compiler how to call the C++ API. +- Conan supplies the compatible headers, binary, dependencies, and build + configuration behind that API. -```bash -git clone https://github.com/conan-io/examples2.git -cd examples2/examples/languages/swift/cxx_interop +The complete project is available in the [Conan examples +repository](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop). -conan install . --build=missing \ - -c tools.cmake.cmaketoolchain:generator=Ninja -cmake --preset conan-release -cmake --build --preset conan-release -./build/Release/demo +## The C++ API as Seen from Swift + +This is the core of the example: + +```swift +import CxxStdlib +import LunaSVGMod + +let seasons = [ + ( + name: "summer", + css: ".sky{fill:#8ECBEB} .hills{fill:#8FA89B} " + + ".ground{fill:#8FC77E} .cloud{fill:#FFFFFF} .title{fill:#3B4A40}" + ), + ( + name: "winter", + css: ".sky{fill:#C9D6E3} .hills{fill:#9FAFAF} " + + ".ground{fill:#F2F5F7} .cloud{fill:#E7EEF3} .title{fill:#4A5A66}" + ), +] + +for season in seasons { + let document = lunasvg.Document.loadFromData(std.string(svg)) + document.pointee.applyStyleSheet(std.string(season.css)) + + let bitmap = document.pointee.renderToBitmap() + _ = bitmap.writeToPng(std.string("\(season.name).png")) +} ``` -`conan install` resolves the dependency graph and obtains binaries matching the -active Conan profile. If a suitable binary is unavailable, `--build=missing` -builds it from source. The generated CMake preset then carries that -configuration into the native build. Running the binary writes `summer.png` and -`winter.png` to the working directory. +These calls go directly to LunaSVG's C++ API: + +- The C++ namespace `lunasvg` remains visible in Swift. +- `Document::loadFromData` becomes a static method. +- Its `std::unique_ptr` result keeps ownership of the C++ object; + Swift accesses that object through `pointee`. +- `renderToBitmap` returns a C++ `Bitmap` by value. +- `writeToPng` calls a `const` C++ member function. -## What Swift's C++ Interoperability Does — and What It Does Not Do +The underlying `svg` string also includes a `` element +naming the stack behind the demo. LunaSVG renders it with its own built-in +fallback font — no font file needs to be registered for this — and each season's +stylesheet colors it along with everything else. It is real output from the C++ +renderer, not something added afterward. -Swift does not translate a C++ library's implementation into Swift. It imports -the declarations in the public C++ headers and emits calls that follow the -target C++ ABI. At link time, those calls are resolved against the already -compiled library supplied by Conan. +The `std.string(...)` conversions are explicit for a reason. Swift does not +automatically bridge a dynamic Swift `String` to C++ `std::string`. Importing +`CxxStdlib` exposes the supported standard-library types and their conversions. +Creating the C++ strings still allocates and copies data; direct interop does +not mean that every value crosses the boundary at zero cost. -As the [Swift documentation](https://www.swift.org/documentation/cxx-interop/) -puts it: "The Swift compiler embeds the Clang compiler. This allows Swift to -import C++ header files using Clang modules." Public C++ classes normally appear -as Swift value types; constructors become initializers, member functions become -methods, and namespaces remain available. The compiler then lowers those -operations to calls that follow the target C++ ABI, and the linker resolves -their symbols in the Conan package binary. Compiler-generated adapters may still -be needed for particular language features, but there is no separately -maintained or compiled C wrapper library in this example. +## What "Direct" Interoperability Means + +Swift does not translate LunaSVG into Swift, and this project does not compile a +hand-written C wrapper. Instead, the path looks like this: + +```text +C++ headers -> Clang module -> declarations visible to Swift +Swift calls -> platform C++ ABI -> compiled LunaSVG library +``` -This division of responsibilities is useful: +The Swift compiler embeds Clang. With C++ interoperability enabled, Clang parses +the public headers and Swift represents supported declarations in its own type +system. The compiler then emits native calls that follow the target's C++ ABI, +and the linker resolves those calls against the library provided by Conan. -- Swift interoperability handles the language boundary. -- Conan handles the dependency and binary boundary. +This is why the header is only half of the dependency. Swift also needs a binary +built for a compatible target, C++ standard library, ABI, and set of options. A +module map makes headers importable; it does not make an arbitrary binary +compatible. -A [Clang module map](https://clang.llvm.org/docs/Modules.html) only tells the -importer which headers form a module. It does not contain bindings, compile the -library, or make an arbitrary binary ABI-compatible. Conan still has to provide -the matching headers, library, transitive requirements, and build configuration. +Pure C libraries follow the same broad dependency pattern: Conan can provide +their headers and binaries, and a Clang module map can expose the headers to +Swift. The difference is that Swift imports C by default, so a C library does +not need C++ interoperability mode or the additional C++ ABI constraints +discussed here. That is also why C facades were historically the common route +from Swift to C++. -## Describing the Native Dependency with Conan +## Giving Swift a Clang Module -The recipe starts like a conventional CMake-based Conan consumer: +Swift imports C and C++ headers through Clang modules. To describe one, it needs +a `module.modulemap` file. LunaSVG is an ordinary C++ package and does not ship +a module map for Swift, so the consumer recipe generates a small one: ```python +import os + from conan import ConanFile from conan.tools.cmake import CMakeDeps, CMakeToolchain, cmake_layout +from conan.tools.files import save class SwiftCppDemo(ConanFile): @@ -146,81 +142,47 @@ class SwiftCppDemo(ConanFile): def generate(self): CMakeDeps(self).generate() CMakeToolchain(self).generate() -``` - -[`CMakeDeps`](https://docs.conan.io/2/reference/tools/cmake/cmakedeps.html) -generates the package configuration consumed by `find_package()`. -[`CMakeToolchain`](https://docs.conan.io/2/reference/tools/cmake/cmaketoolchain.html) -translates the Conan configuration into CMake toolchain data and presets. That -part is independent of Swift: from Conan's perspective, this is a native -executable consuming a single C++ dependency. - -## Generating the Missing Module Map -Swift imports C and C++ headers as Clang modules. For that, it must be able to -find a `module.modulemap`. LunaSVG is a regular C++ package and does not ship -one for this use case, so the recipe generates a tiny shim module map: - -```python -import os - -from conan.tools.files import save - - -def _write_modulemap(self, filename, module_name, header_path): - content = ( - f'module {module_name} {{\n' - f' header "{header_path}"\n' - ' export *\n' - '}\n' - ) - save( - self, - os.path.join(self.generators_folder, "shim", filename), - content, - ) - - -def generate(self): - CMakeDeps(self).generate() - CMakeToolchain(self).generate() - - lunasvg_include = self.dependencies["lunasvg"].cpp_info.includedirs[0] - self._write_modulemap( - "lunasvg.modulemap", - "LunaSVGMod", - f"{lunasvg_include}/lunasvg/lunasvg.h", - ) + include_dir = self.dependencies["lunasvg"].cpp_info.includedirs[0] + header = f"{include_dir}/lunasvg/lunasvg.h" + module_map = ( + "module LunaSVGMod {\n" + f' header "{header}"\n' + " export *\n" + "}\n" + ) + save( + self, + os.path.join( + self.generators_folder, + "shim", + "lunasvg.modulemap", + ), + module_map, + ) ``` -The generated file is intentionally simple. Conceptually, it contains only this: +The generated file is only a description of the module: -``` +```text module LunaSVGMod { header "/path/to/conan/package/include/lunasvg/lunasvg.h" export * } ``` -There are two details worth calling out. - -First, the recipe takes the header location from LunaSVG's `cpp_info` instead of -guessing a path in the Conan cache. This works with whatever package layout -Conan selects, and the same call would work for a package that exposes its -headers through Conan components instead of a single `includedirs` entry. +The recipe gets the include directory from LunaSVG's `cpp_info` instead of +guessing a path inside the Conan cache. This keeps the shim tied to the package +Conan actually selected. -Second, this is a **Clang module**, not the named modules introduced by C++20. -As the same documentation states plainly: "Swift currently cannot import C++ -modules introduced in the C++20 language standard." - -In a library designed specifically for Swift consumption, a maintained module -map can live beside the public headers. Generating one in the consumer is a -pragmatic bridge for an existing, Swift-unaware package. +Despite the similar terminology, this is a [Clang +module](https://clang.llvm.org/docs/Modules.html), not a named C++20 module. +Swift does not currently import C++20 modules. ## Connecting Conan, CMake, and `swiftc` -The CMake project enables both Swift and C++ and consumes the target generated -by Conan: +The CMake project links the Conan target as it would for a C++ executable, then +adds three Swift-specific compiler options: ```cmake cmake_minimum_required(VERSION 3.28) @@ -229,20 +191,8 @@ project(swift_cpp_demo LANGUAGES CXX Swift) find_package(lunasvg REQUIRED) add_executable(demo main.swift) +target_link_libraries(demo PRIVATE lunasvg::lunasvg) -target_link_libraries(demo PRIVATE - lunasvg::lunasvg -) -``` - -That imported target is important. It carries much more than a library filename: -include paths, transitive link requirements, and other usage information modeled -by the package. - -The Swift-specific part enables C++ interoperability and forwards the module map -to the Clang instance embedded in the Swift compiler: - -```cmake get_filename_component( _conan_generators_dir "${CMAKE_TOOLCHAIN_FILE}" @@ -256,180 +206,141 @@ target_compile_options(demo PRIVATE ) ``` -`-cxx-interoperability-mode=default` switches on C++ importing. `-Xcc` forwards -the following argument to embedded Clang. `CMAKE_CXX_STANDARD` is not -hardcoded here — `CMakeToolchain` already sets it from the active profile's -`compiler.cppstd` when it generates `conan_toolchain.cmake`, the same setting -that determined which C++ dialect lunasvg itself was built with. Reading it -back is a one-line habit, not a fix for an active bug in this particular -library: LunaSVG's header has no code conditioned on the active C++ standard, -so a hardcoded `c++17` would behave identically here. But some libraries do -gate declarations behind `#if __cplusplus` — GCC's libstdc++ famously did this -for years with its ["dual -ABI"](https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html) for -`std::string`, and modern libraries such as Abseil still gate parts of their -public API on the active standard. For those, letting this value silently -drift from what Conan resolved is how you get a linker error, or worse, a -mismatched layout that never surfaces as one. CMake generator expressions keep -the resulting flags attached only to Swift compilation. - -With those pieces in place, the module name from the generated map becomes an -ordinary Swift import, alongside `CxxStdlib`, the overlay module Swift provides -for bridging C++ standard-library types such as `std::string`: +Each piece has one job: -```swift -import CxxStdlib -import LunaSVGMod -``` +- `lunasvg::lunasvg`, generated by `CMakeDeps`, carries the native link and + usage requirements modeled by the Conan package. +- `-cxx-interoperability-mode=default` enables C++ imports in Swift. +- `-Xcc` forwards the C++ language mode and module-map path to the embedded + Clang compiler. -## Calling an Unmodified C++ API from Swift +`CMakeToolchain` derives `CMAKE_CXX_STANDARD` from the consumer profile's +`compiler.cppstd`. Forwarding it to Clang keeps the imported headers in the same +requested language mode. That matters for libraries whose public declarations +change according to `__cplusplus`. -Once the module is visible, the LunaSVG calls are close to a transcription of -the C++ API. The example renders the same SVG scene twice, once per season, by -loading a fresh `Document`, applying a different CSS stylesheet, and rendering -the result to a bitmap: +## Build and Run -```swift -let seasons = [ - (name: "summer", css: ".sky{fill:#8ECBEB} .hills{fill:#8FA89B} .ground{fill:#8FC77E} .cloud{fill:#FFFFFF}"), - (name: "winter", css: ".sky{fill:#C9D6E3} .hills{fill:#9FAFAF} .ground{fill:#F2F5F7} .cloud{fill:#E7EEF3}"), -] +The example currently targets macOS. It requires the Xcode command-line tools, +CMake 3.28 or newer, and Ninja. CMake supports Swift with its Ninja and Xcode +generators; this example selects Ninja explicitly through the Conan toolchain. -for season in seasons { - let document = lunasvg.Document.loadFromData(std.string(svg)) - document.pointee.applyStyleSheet(std.string(season.css)) +Use a recent Swift toolchain. C++ interoperability began in Swift 5.9, but that +initial release is not sufficient for this exact example: support for the +`std::unique_ptr` returned by LunaSVG was added later. - let bitmap = document.pointee.renderToBitmap() - _ = bitmap.writeToPng(std.string("\(season.name).png")) +```bash +git clone https://github.com/conan-io/examples2.git +cd examples2/examples/languages/swift/cxx_interop - print("Generated \(season.name).png") -} +conan install . --build=missing \ + -c tools.cmake.cmaketoolchain:generator=Ninja +cmake --preset conan-release +cmake --build --preset conan-release +./build/Release/demo ``` -A fresh `Document` is loaded for each season rather than reused, because -`applyStyleSheet` mutates the document it is called on; there is no way to undo -a stylesheet once applied. - -Several interoperability features appear in these few lines: - -- The C++ `lunasvg` namespace is available directly in Swift. -- `Document::loadFromData` is imported as a static method. LunaSVG returns a - `std::unique_ptr`, which Swift dereferences through `pointee` while - the smart pointer keeps ownership of the object. -- `applyStyleSheet` and `renderToBitmap` are called as ordinary instance methods - on that dereferenced value. -- `renderToBitmap` returns a `Bitmap` by value — a C++ object constructed on the - C++ side and handed back into Swift. -- `writeToPng` is a `const` C++ member function that writes to disk. - -The `std.string(...)` calls are not decoration. As the [Swift -documentation](https://www.swift.org/documentation/cxx-interop/) states: "Swift -does not convert C++ `std::string` type to Swift's `String` type automatically." -Every Swift `String` crossing into an API that expects `std::string` — the SVG -markup, the CSS, the output filename — goes through an explicit -`std.string(...)` initializer from the `CxxStdlib` overlay module. That -conversion allocates and copies; it is not free, and it is not automatic, only -explicit and predictable. - -## Direct Calls Make ABI Compatibility More Important, Not Less - -Avoiding a C wrapper removes boilerplate and an extra API surface, but it does -not create an ABI firewall. The generated Swift code calls the C++ ABI expected -by the imported declarations. The headers used during Swift compilation -therefore need to agree with the linked binary on the details that affect that -ABI. - -That includes: - -- Target operating system, architecture, and deployment target. -- Compiler ABI and C++ standard-library choice. -- Debug/Release and other relevant build settings. -- Dependency versions and transitive dependencies. -- Preprocessor definitions or package options that change public declarations or - layouts. - -The Swift and C++ sides must also use the same C++ standard library. On Apple -platforms that normally means libc++. Conan profiles, package IDs, and -dependency metadata help keep these decisions explicit. `CMakeToolchain` carries -the selected build configuration into CMake, and `CMakeDeps` gives CMake targets -that describe how the package is meant to be consumed. - -This is the deeper value of putting Conan underneath Swift/C++ interoperability: -the language feature lets Swift express the call, while the package manager -makes the native artifact behind that call reproducible. - -Conan cannot infer every compatibility rule automatically. If a library has an -ABI-changing macro or option that its recipe does not model, the recipe still -needs to expose it correctly. Direct C++ interoperability rewards accurate -package metadata. - -## Ownership and Safety Still Cross the Boundary - -The syntax can look very Swift-like, but the semantics still come from both -languages. A few rules are especially important when moving beyond a demo: - -- **C++ classes are generally imported as Swift value types.** Copies can run - C++ copy constructors, and destruction runs C++ destructors. For large - containers, an innocent-looking Swift copy or iteration can therefore have a - real cost. -- **Pointers and views still need explicit lifetime reasoning.** Even though - this example only crosses a `std::unique_ptr`, many C++ APIs hand back raw - pointers or views instead; Swift's `withUnsafeBytes` and - `withUnsafeMutableBytes` scope such access explicitly, but Swift cannot prove - that an arbitrary third-party function will not retain a pointer past that - scope. The C++ API contract still matters. -- **Noncopyable ownership should remain visible.** A `std::unique_ptr` is not a - shared reference. Keeping the owner alive while using `pointee` is part of the - program's correctness. -- **C++ exceptions are not Swift errors.** Swift cannot catch a C++ exception. A - production boundary should prevent exceptions from escaping C++ into Swift. -- **Interop supports a growing subset of C++.** Rvalue-reference APIs, some - template patterns, and C++20 named modules still have limitations. Check the - current [Swift C++ interoperability - status](https://www.swift.org/documentation/cxx-interop/status/) when - evaluating a library. - -Swift 6.2 also added stricter memory-safety checking and new safe-interop -facilities for annotated C++ APIs. Those features can improve bounds and -lifetime checking when you control or can adapt the C++ interface, but they do -not retroactively make every raw-pointer API safe. - -The sample keeps error handling short to make the interop mechanics visible. -Production code should additionally check `loadFromData`'s result for a null -pointer before dereferencing it through `pointee`, and check the boolean -returned by `writeToPng`. - -## The Reusable Pattern - -Although this demo renders a small illustrated landscape, the integration -pattern is not graphics-specific: - -1. Declare the native library and relevant settings in Conan. -2. Let Conan select or build a compatible binary. -3. Generate a Clang module map when a package does not provide one. -4. Link the Conan-generated CMake target normally. -5. Enable Swift C++ interoperability and pass the module map to embedded Clang. -6. Treat ownership, lifetimes, error models, and ABI options as part of the API - boundary. - -Pure C libraries follow the same general dependency pattern: Conan provides the -headers and the binary, and a Clang module map makes the headers importable. -They do not, however, require C++ interoperability mode or the C++ ABI -guarantees described above — Swift has been able to import a plain C API through -Clang since long before C++ interoperability existed. - -That opens a large body of existing C and C++ libraries to Swift without -requiring each upstream project to publish a separate Swift wrapper. A -purpose-built wrapper can still be valuable when an API is unsafe, -exception-heavy, or awkward to import, but it is now an architectural choice -rather than an automatic prerequisite. +`conan install` resolves LunaSVG and selects a package matching the active +profile. If no suitable binary is available, `--build=missing` builds one from +source. Running the executable writes `summer.png` and `winter.png` to the +working directory: + +
+ summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied + winter.png: the same SVG document rendered again with the winter stylesheet applied +
+ +Both files come from the same SVG document loaded twice, styled by a different +`applyStyleSheet` call each time, and rasterized by LunaSVG's C++ +`renderToBitmap`. Nothing here is a mockup: this is the literal output of +`./build/Release/demo`. + +The same integration model can be used on other Swift platforms, but the exact +supported C++ surface still varies. For example, the current Swift status page +lists `std::shared_ptr` and `std::unique_ptr` as unsupported on Windows. + +## The Sharp Edges Are Still C++ Sharp Edges + +The Swift syntax is pleasantly ordinary, but direct interop is neither a stable +C ABI nor an automatic safety boundary. + +### Headers and binary must agree + +The headers parsed by Swift and the linked library must agree on every choice +that affects the C++ ABI, including: + +- Target platform, architecture, and deployment target. +- Compiler ABI and C++ standard-library implementation. +- Dependency versions and transitive libraries. +- Defines or package options that change public declarations or object layout. + +Conan profiles, package IDs, and dependency metadata make these choices explicit +and let Conan select or build a compatible artifact. They cannot fix an +ABI-changing option that a package recipe fails to model, so accurate package +metadata still matters. + +### Ownership does not disappear + +C++ classes are generally imported as Swift value types. Copying one can invoke +its C++ copy constructor, and destroying it invokes its C++ destructor. A +`std::unique_ptr` remains a unique owner, so it must stay alive while Swift uses +`pointee`. + +Raw pointers, references, and view types require the same lifetime reasoning as +they do in C++. Swift 6.2's safe-interoperability features can improve this for +annotated APIs, but they do not make every third-party pointer API safe +retroactively. + +C++ exceptions are another important boundary: Swift cannot catch them. An +exception that escapes C++ into Swift terminates the program, so a production +API should catch and translate errors on the C++ side. + +Finally, interoperability still covers a growing subset of C++, not every +possible header. Some template patterns and standard-library types remain +unsupported. Check the current [Swift C++ interoperability status +page](https://www.swift.org/documentation/cxx-interop/status/) when evaluating a +library. + +The sample also keeps error handling short to make the interop visible. +Production code should verify that `loadFromData` did not return a null pointer +before dereferencing it and should check the result of `writeToPng`. + +## When a Wrapper Is Still the Better Boundary + +Direct interoperability removes boilerplate, but it does not make wrappers +obsolete. A small C or C++ adapter can still be the better design when the +upstream API: + +- exposes unsupported C++ constructs; +- relies heavily on exceptions, raw pointers, or ambiguous lifetimes; +- has a large template-heavy surface that should not leak into the Swift code; + or +- needs to be isolated behind a narrower, more stable ABI. + +The difference is that a wrapper is now an architectural choice for shaping the +boundary, rather than an automatic prerequisite for calling any C++ code. + +## A Reusable Pattern + +The LunaSVG example reduces to five steps: + +1. Let Conan select or build the native dependency for the active profile. +2. Describe the public headers with a Clang module map when upstream does not + provide one. +3. Link the Conan-generated CMake target normally. +4. Enable Swift C++ interoperability and give embedded Clang the same header + configuration. +5. Treat ABI, ownership, lifetime, and errors as part of the API boundary. + +Swift now makes the direct C++ call possible. Conan handles the less visible but +equally important part: making sure there is a suitable native artifact behind +that call. Try the [complete -example](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop), -then take a look at the official [Swift C++ interoperability +example](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop) +and consult the official [Swift C++ interoperability guide](https://www.swift.org/documentation/cxx-interop/) for the complete -mapping and safety rules. If you have questions or feedback, please open an -issue on [GitHub](https://github.com/conan-io/conan/issues). +mapping and safety rules. Happy coding! diff --git a/assets/post_images/2026-08-12/summer.png b/assets/post_images/2026-08-12/summer.png new file mode 100644 index 0000000000000000000000000000000000000000..73273aaff95e06ff0765ec43b0099c7051529148 GIT binary patch literal 12458 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 Date: Tue, 11 Aug 2026 15:46:24 +0200 Subject: [PATCH 06/27] wip --- ...ift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown | 6 ++++-- css/style.css | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 0888a52b..7b6c4c0c 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -50,12 +50,14 @@ let seasons = [ ( name: "summer", css: ".sky{fill:#8ECBEB} .hills{fill:#8FA89B} " + - ".ground{fill:#8FC77E} .cloud{fill:#FFFFFF} .title{fill:#3B4A40}" + ".ground{fill:#8FC77E} .cloud{fill:#FFFFFF} " + + ".title{fill:#3B4A40}" ), ( name: "winter", css: ".sky{fill:#C9D6E3} .hills{fill:#9FAFAF} " + - ".ground{fill:#F2F5F7} .cloud{fill:#E7EEF3} .title{fill:#4A5A66}" + ".ground{fill:#F2F5F7} .cloud{fill:#E7EEF3} " + + ".title{fill:#4A5A66}" ), ] diff --git a/css/style.css b/css/style.css index 0c5373ad..6f3cfa0a 100644 --- a/css/style.css +++ b/css/style.css @@ -420,6 +420,7 @@ body .month-content h2 { background: #eef7ff; padding: 22px; margin-bottom: 2em; + overflow-x: auto; } .container.single-post-page-container .spli-footer { padding-top: 20px; From ebd406c03fe6a66b25c10efe710eed093eb94930 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 16:24:17 +0200 Subject: [PATCH 07/27] wip --- ...p-With-Conan-Managed-Dependencies.markdown | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 7b6c4c0c..8acbd91f 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -8,32 +8,28 @@ categories: [cpp, conan, swift, macos, cmake] --- Swift has been able to import C and Objective-C APIs since its early releases. -C++ was a harder boundary: namespaces, overloaded functions, templates, -constructors, destructors, and the C++ standard library all have to retain their -meaning across languages. Calling a C++ library from Swift therefore usually -meant putting a C facade or an Objective-C++ wrapper in front of it. +C++ was more difficult to support: namespaces, overloaded functions, templates, +constructors, destructors, and the C++ standard library all need to be +represented correctly in Swift. Calling a C++ library from Swift therefore +usually meant putting a C facade or an Objective-C++ wrapper in front of it. [Swift 5.9](https://www.swift.org/blog/swift-5.9-released/) changed that by -introducing direct C++ interoperability. Support has continued to expand: Swift -6 added move-only C++ types and more standard-library types, `std::unique_ptr` -support arrived in 2025, and Swift 6.2 introduced safer ways to work with -annotated pointer and view APIs. - -Most interop samples control both sides of the boundary. We wanted to try the -less tidy case: could Swift call an ordinary, pre-existing C++ package without -modifying its source or writing a wrapper library? - -For this example, we use [LunaSVG](https://conan.io/center/recipes/lunasvg), a -C++ SVG renderer from ConanCenter. The Swift application creates an SVG scene, -asks LunaSVG to render it with two stylesheets, and writes `summer.png` and -`winter.png`. LunaSVG has no Swift-specific code and does not ship a Swift -module map. - -The result is a useful division of responsibilities: - -- Swift interoperability teaches the compiler how to call the C++ API. -- Conan supplies the compatible headers, binary, dependencies, and build - configuration behind that API. +introducing direct C++ interoperability, allowing Swift to import C++ headers +and call supported C++ APIs without first exposing them through a C facade or an +Objective-C++ wrapper. + +In this post, we use Swift’s direct C++ interoperability to call +[LunaSVG](https://conan.io/center/recipes/lunasvg), an existing C++ SVG renderer +from ConanCenter, without modifying the library or writing a wrapper. Conan +provides LunaSVG, its transitive dependencies, and the information required to +compile and link the application. A Clang module map then makes the library’s +headers importable from Swift. + +The application creates an SVG scene in Swift and asks LunaSVG to render it with +two different styles, producing `summer.png` and `winter.png`. The example shows +how the two pieces fit together: Swift understands how to call the C++ API, +while Conan makes the library and everything it depends on available to the +build. The complete project is available in the [Conan examples repository](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop). From 0ae598d06a3dfc7602f21cc34def5b8777d0808d Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 16:54:19 +0200 Subject: [PATCH 08/27] wip --- ...p-With-Conan-Managed-Dependencies.markdown | 153 +++++++++--------- 1 file changed, 81 insertions(+), 72 deletions(-) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 8acbd91f..5f8c5fd3 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -8,55 +8,68 @@ categories: [cpp, conan, swift, macos, cmake] --- Swift has been able to import C and Objective-C APIs since its early releases. -C++ was more difficult to support: namespaces, overloaded functions, templates, -constructors, destructors, and the C++ standard library all need to be -represented correctly in Swift. Calling a C++ library from Swift therefore -usually meant putting a C facade or an Objective-C++ wrapper in front of it. +C++ was more difficult to support because Swift's C importer could not represent +features such as namespaces, overloaded functions, templates, constructors, +destructors, and standard-library types. Calling a C++ library from Swift +therefore usually meant putting a C facade or an Objective-C++ wrapper in front +of it. [Swift 5.9](https://www.swift.org/blog/swift-5.9-released/) changed that by introducing direct C++ interoperability, allowing Swift to import C++ headers and call supported C++ APIs without first exposing them through a C facade or an Objective-C++ wrapper. -In this post, we use Swift’s direct C++ interoperability to call -[LunaSVG](https://conan.io/center/recipes/lunasvg), an existing C++ SVG renderer -from ConanCenter, without modifying the library or writing a wrapper. Conan -provides LunaSVG, its transitive dependencies, and the information required to -compile and link the application. A Clang module map then makes the library’s -headers importable from Swift. +To try this with an existing package, we use +[LunaSVG](https://conan.io/center/recipes/lunasvg), a C++ SVG renderer from +ConanCenter. The application creates an SVG scene in Swift and asks LunaSVG to +render it with two different styles, producing `summer.png` and `winter.png`. +LunaSVG is used without modifying its source or writing a wrapper. -The application creates an SVG scene in Swift and asks LunaSVG to render it with -two different styles, producing `summer.png` and `winter.png`. The example shows -how the two pieces fit together: Swift understands how to call the C++ API, -while Conan makes the library and everything it depends on available to the -build. +Swift knows how to call supported C++ APIs, while Conan provides LunaSVG, its +transitive dependencies, and the information needed to compile and link the +application. A small Clang module map makes LunaSVG's headers importable from +Swift. The complete project is available in the [Conan examples -repository](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop). +repository](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop): -## The C++ API as Seen from Swift +```text +cxx_interop/ +├── conanfile.py # requires lunasvg and generates the Clang module map +├── CMakeLists.txt # links lunasvg::lunasvg and sets the Swift compiler options +├── main.swift # the Swift application +├── ci_test_example.py # runs the example in CI +└── README.md +``` + +## Starting with the C++ API + +Before looking at Swift, it helps to see how the LunaSVG API would normally be +used from C++. Given `svg` and `css` as `std::string` values holding an SVG +document and a stylesheet, and `output` as the destination PNG path, rendering +and writing the file looks like this: + +```cpp +auto document = lunasvg::Document::loadFromData(svg); +document->applyStyleSheet(css); + +auto bitmap = document->renderToBitmap(); +bitmap.writeToPng(output); +``` + +Although short, this fragment already touches several C++ features: a +namespace, a static method, a `std::unique_ptr`, member functions, +and a `Bitmap` returned by value. + +## Calling the Same API from Swift -This is the core of the example: +After importing the C++ standard library and the LunaSVG module, `main.swift` +calls the same API to render the demo scene once per season: ```swift import CxxStdlib import LunaSVGMod -let seasons = [ - ( - name: "summer", - css: ".sky{fill:#8ECBEB} .hills{fill:#8FA89B} " + - ".ground{fill:#8FC77E} .cloud{fill:#FFFFFF} " + - ".title{fill:#3B4A40}" - ), - ( - name: "winter", - css: ".sky{fill:#C9D6E3} .hills{fill:#9FAFAF} " + - ".ground{fill:#F2F5F7} .cloud{fill:#E7EEF3} " + - ".title{fill:#4A5A66}" - ), -] - for season in seasons { let document = lunasvg.Document.loadFromData(std.string(svg)) document.pointee.applyStyleSheet(std.string(season.css)) @@ -66,28 +79,34 @@ for season in seasons { } ``` -These calls go directly to LunaSVG's C++ API: +`svg` holds the SVG markup and `seasons` pairs each season name with its +stylesheet; both are ordinary Swift values defined earlier in `main.swift`. The +loop body closely follows the C++ version above. + +The mapping is visible in the code: - The C++ namespace `lunasvg` remains visible in Swift. - `Document::loadFromData` becomes a static method. -- Its `std::unique_ptr` result keeps ownership of the C++ object; - Swift accesses that object through `pointee`. +- The returned `std::unique_ptr` owns the C++ object; Swift accesses + that object through `pointee`. - `renderToBitmap` returns a C++ `Bitmap` by value. -- `writeToPng` calls a `const` C++ member function. - -The underlying `svg` string also includes a `` element -naming the stack behind the demo. LunaSVG renders it with its own built-in -fallback font — no font file needs to be registered for this — and each season's -stylesheet colors it along with everything else. It is real output from the C++ -renderer, not something added afterward. +- `writeToPng` remains an ordinary member-function call. The `std.string(...)` conversions are explicit for a reason. Swift does not -automatically bridge a dynamic Swift `String` to C++ `std::string`. Importing -`CxxStdlib` exposes the supported standard-library types and their conversions. -Creating the C++ strings still allocates and copies data; direct interop does -not mean that every value crosses the boundary at zero cost. +automatically bridge a dynamic Swift `String` to C++ `std::string`. `CxxStdlib` +exposes supported standard-library types, but creating these C++ strings still +allocates and copies data. Direct interop removes the wrapper; it does not make +every conversion free. -## What "Direct" Interoperability Means +Calling `render` with the summer and winter styles produces the actual LunaSVG +output: + +
+ summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied + winter.png: the same SVG document rendered again with the winter stylesheet applied +
+ +## What "Direct" Means Swift does not translate LunaSVG into Swift, and this project does not compile a hand-written C wrapper. Instead, the path looks like this: @@ -97,10 +116,10 @@ C++ headers -> Clang module -> declarations visible to Swift Swift calls -> platform C++ ABI -> compiled LunaSVG library ``` -The Swift compiler embeds Clang. With C++ interoperability enabled, Clang parses -the public headers and Swift represents supported declarations in its own type -system. The compiler then emits native calls that follow the target's C++ ABI, -and the linker resolves those calls against the library provided by Conan. +The Swift compiler uses Clang to parse LunaSVG's public headers and imports the +supported declarations into Swift. It then emits native calls that follow the +target's C++ ABI, and the linker resolves those calls against the library +provided by Conan. This is why the header is only half of the dependency. Swift also needs a binary built for a compatible target, C++ standard library, ABI, and set of options. A @@ -114,11 +133,11 @@ not need C++ interoperability mode or the additional C++ ABI constraints discussed here. That is also why C facades were historically the common route from Swift to C++. -## Giving Swift a Clang Module +## Making the Headers Importable -Swift imports C and C++ headers through Clang modules. To describe one, it needs -a `module.modulemap` file. LunaSVG is an ordinary C++ package and does not ship -a module map for Swift, so the consumer recipe generates a small one: +Swift imports C and C++ headers through Clang modules. A `module.modulemap` file +tells Clang which headers belong to a module. LunaSVG does not ship one, so the +consumer recipe generates it: ```python import os @@ -160,7 +179,7 @@ class SwiftCppDemo(ConanFile): ) ``` -The generated file is only a description of the module: +The generated file is deliberately small: ```text module LunaSVGMod { @@ -241,23 +260,13 @@ cmake --build --preset conan-release `conan install` resolves LunaSVG and selects a package matching the active profile. If no suitable binary is available, `--build=missing` builds one from source. Running the executable writes `summer.png` and `winter.png` to the -working directory: - -
- summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied - winter.png: the same SVG document rendered again with the winter stylesheet applied -
- -Both files come from the same SVG document loaded twice, styled by a different -`applyStyleSheet` call each time, and rasterized by LunaSVG's C++ -`renderToBitmap`. Nothing here is a mockup: this is the literal output of -`./build/Release/demo`. +working directory. The same integration model can be used on other Swift platforms, but the exact supported C++ surface still varies. For example, the current Swift status page lists `std::shared_ptr` and `std::unique_ptr` as unsupported on Windows. -## The Sharp Edges Are Still C++ Sharp Edges +## What Direct Interoperability Does Not Solve The Swift syntax is pleasantly ordinary, but direct interop is neither a stable C ABI nor an automatic safety boundary. @@ -285,9 +294,9 @@ its C++ copy constructor, and destroying it invokes its C++ destructor. A `pointee`. Raw pointers, references, and view types require the same lifetime reasoning as -they do in C++. Swift 6.2's safe-interoperability features can improve this for -annotated APIs, but they do not make every third-party pointer API safe -retroactively. +they do in C++. Swift 6.2 introduced experimental safe-interoperability features +that can improve this for annotated APIs, but they do not make every existing +pointer API safe automatically. C++ exceptions are another important boundary: Swift cannot catch them. An exception that escapes C++ into Swift terminates the program, so a production From 4d1d45b5c8286bc436bf9be358683c196fbb0dc5 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 17:03:24 +0200 Subject: [PATCH 09/27] wip --- ...p-With-Conan-Managed-Dependencies.markdown | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 5f8c5fd3..7b969d6c 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -35,10 +35,10 @@ repository](https://github.com/conan-io/examples2/tree/main/examples/languages/s ```text cxx_interop/ -├── conanfile.py # requires lunasvg and generates the Clang module map -├── CMakeLists.txt # links lunasvg::lunasvg and sets the Swift compiler options -├── main.swift # the Swift application -├── ci_test_example.py # runs the example in CI +├── conanfile.py +├── CMakeLists.txt +├── main.swift +├── ci_test_example.py └── README.md ``` @@ -61,10 +61,34 @@ Although short, this fragment already touches several C++ features: a namespace, a static method, a `std::unique_ptr`, member functions, and a `Bitmap` returned by value. +## Making the C++ API Visible to Swift + +Before Swift can call this API, it needs to know which C++ header to import and +the module name it should use. Swift gets this information through a Clang +module map. + +LunaSVG does not provide one, so the project supplies a small module map file: + +```text +module LunaSVGMod { + header "/path/to/conan/package/include/lunasvg/lunasvg.h" + export * +} +``` + +This gives the LunaSVG header a module name, `LunaSVGMod`. The build must also +enable C++ interoperability and pass the module map to the Clang importer used +by the Swift compiler. Once that is configured, Swift can import the module and +use the supported declarations from the header. + +Despite the similar terminology, this is a [Clang +module](https://clang.llvm.org/docs/Modules.html), not a named C++20 module. +Swift does not currently import C++20 modules. + ## Calling the Same API from Swift -After importing the C++ standard library and the LunaSVG module, `main.swift` -calls the same API to render the demo scene once per season: +With the C++ standard library and `LunaSVGMod` imported, `main.swift` calls the +same API to render the demo scene once per season: ```swift import CxxStdlib @@ -98,8 +122,8 @@ exposes supported standard-library types, but creating these C++ strings still allocates and copies data. Direct interop removes the wrapper; it does not make every conversion free. -Calling `render` with the summer and winter styles produces the actual LunaSVG -output: +Running the loop with the summer and winter styles produces the following +LunaSVG output:
summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied @@ -133,11 +157,11 @@ not need C++ interoperability mode or the additional C++ ABI constraints discussed here. That is also why C facades were historically the common route from Swift to C++. -## Making the Headers Importable +## Generating the Module Map with Conan -Swift imports C and C++ headers through Clang modules. A `module.modulemap` file -tells Clang which headers belong to a module. LunaSVG does not ship one, so the -consumer recipe generates it: +The module map shown above contains a package-specific include path. Rather +than hard-coding that path, the Conan consumer recipe obtains it from the +selected LunaSVG package and generates the file during `conan install`: ```python import os @@ -179,23 +203,10 @@ class SwiftCppDemo(ConanFile): ) ``` -The generated file is deliberately small: - -```text -module LunaSVGMod { - header "/path/to/conan/package/include/lunasvg/lunasvg.h" - export * -} -``` - The recipe gets the include directory from LunaSVG's `cpp_info` instead of guessing a path inside the Conan cache. This keeps the shim tied to the package Conan actually selected. -Despite the similar terminology, this is a [Clang -module](https://clang.llvm.org/docs/Modules.html), not a named C++20 module. -Swift does not currently import C++20 modules. - ## Connecting Conan, CMake, and `swiftc` The CMake project links the Conan target as it would for a C++ executable, then From fa0c83faf4cdf009a6c0b826caa742f8dc6fb9f1 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 17:08:52 +0200 Subject: [PATCH 10/27] wip --- ...-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 7b969d6c..3ed60713 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -22,8 +22,7 @@ Objective-C++ wrapper. To try this with an existing package, we use [LunaSVG](https://conan.io/center/recipes/lunasvg), a C++ SVG renderer from ConanCenter. The application creates an SVG scene in Swift and asks LunaSVG to -render it with two different styles, producing `summer.png` and `winter.png`. -LunaSVG is used without modifying its source or writing a wrapper. +render it with two different styles. Swift knows how to call supported C++ APIs, while Conan provides LunaSVG, its transitive dependencies, and the information needed to compile and link the From 3cae2f180df3ada25475b8a67480cf0cc1ba4338 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 17:18:01 +0200 Subject: [PATCH 11/27] wip --- ...t-Cpp-Interop-With-Conan-Managed-Dependencies.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 3ed60713..839d44e1 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -66,11 +66,9 @@ Before Swift can call this API, it needs to know which C++ header to import and the module name it should use. Swift gets this information through a Clang module map. -LunaSVG does not provide one, so the project supplies a small module map file: - ```text module LunaSVGMod { - header "/path/to/conan/package/include/lunasvg/lunasvg.h" + header "/path/to/include/lunasvg/lunasvg.h" export * } ``` @@ -80,9 +78,11 @@ enable C++ interoperability and pass the module map to the Clang importer used by the Swift compiler. Once that is configured, Swift can import the module and use the supported declarations from the header. -Despite the similar terminology, this is a [Clang +
+Note: Despite the similar terminology, this is a [Clang module](https://clang.llvm.org/docs/Modules.html), not a named C++20 module. Swift does not currently import C++20 modules. +
## Calling the Same API from Swift From 904935a10b91c30d1fbaabd613be8fbc2f54b995 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 17:32:54 +0200 Subject: [PATCH 12/27] wip --- ...p-With-Conan-Managed-Dependencies.markdown | 51 +++++++++--------- assets/post_images/2026-08-12/winter.png | Bin 12385 -> 0 bytes 2 files changed, 27 insertions(+), 24 deletions(-) delete mode 100644 assets/post_images/2026-08-12/winter.png diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 839d44e1..0cfc26dd 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -22,7 +22,7 @@ Objective-C++ wrapper. To try this with an existing package, we use [LunaSVG](https://conan.io/center/recipes/lunasvg), a C++ SVG renderer from ConanCenter. The application creates an SVG scene in Swift and asks LunaSVG to -render it with two different styles. +render it to a PNG. Swift knows how to call supported C++ APIs, while Conan provides LunaSVG, its transitive dependencies, and the information needed to compile and link the @@ -73,12 +73,21 @@ module LunaSVGMod { } ``` -This gives the LunaSVG header a module name, `LunaSVGMod`. The build must also -enable C++ interoperability and pass the module map to the Clang importer used -by the Swift compiler. Once that is configured, Swift can import the module and -use the supported declarations from the header. +This gives the LunaSVG header a module name, `LunaSVGMod`. Swift must then be +compiled with C++ interoperability enabled and the module-map option is +forwarded to the Clang importer used by the Swift compiler: -
+```text +-cxx-interoperability-mode=default +-Xcc -fmodule-map-file=/path/to/lunasvg.modulemap +``` + +`-Xcc` passes the following option to the Clang instance embedded in the Swift +compiler. With these options, Swift can import `LunaSVGMod` and use the +supported declarations from the header. The CMake configuration shown later +adds the same options using the module map generated by Conan. + +
Note: Despite the similar terminology, this is a [Clang module](https://clang.llvm.org/docs/Modules.html), not a named C++20 module. Swift does not currently import C++20 modules. @@ -87,24 +96,21 @@ Swift does not currently import C++20 modules. ## Calling the Same API from Swift With the C++ standard library and `LunaSVGMod` imported, `main.swift` calls the -same API to render the demo scene once per season: +same API to render the demo scene: ```swift import CxxStdlib import LunaSVGMod -for season in seasons { - let document = lunasvg.Document.loadFromData(std.string(svg)) - document.pointee.applyStyleSheet(std.string(season.css)) +let document = lunasvg.Document.loadFromData(std.string(svg)) +document.pointee.applyStyleSheet(std.string(css)) - let bitmap = document.pointee.renderToBitmap() - _ = bitmap.writeToPng(std.string("\(season.name).png")) -} +let bitmap = document.pointee.renderToBitmap() +_ = bitmap.writeToPng(std.string("summer.png")) ``` -`svg` holds the SVG markup and `seasons` pairs each season name with its -stylesheet; both are ordinary Swift values defined earlier in `main.swift`. The -loop body closely follows the C++ version above. +`svg` and `css` are ordinary Swift string values defined earlier in +`main.swift`. This closely follows the C++ version above. The mapping is visible in the code: @@ -121,12 +127,10 @@ exposes supported standard-library types, but creating these C++ strings still allocates and copies data. Direct interop removes the wrapper; it does not make every conversion free. -Running the loop with the summer and winter styles produces the following -LunaSVG output: +Running this produces the actual LunaSVG output, `summer.png`: -
- summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied - winter.png: the same SVG document rendered again with the winter stylesheet applied +
+ summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied
## What "Direct" Means @@ -209,7 +213,7 @@ Conan actually selected. ## Connecting Conan, CMake, and `swiftc` The CMake project links the Conan target as it would for a C++ executable, then -adds three Swift-specific compiler options: +adds the compiler options introduced above together with the C++ language mode: ```cmake cmake_minimum_required(VERSION 3.28) @@ -269,8 +273,7 @@ cmake --build --preset conan-release `conan install` resolves LunaSVG and selects a package matching the active profile. If no suitable binary is available, `--build=missing` builds one from -source. Running the executable writes `summer.png` and `winter.png` to the -working directory. +source. Running the executable writes `summer.png` to the working directory. The same integration model can be used on other Swift platforms, but the exact supported C++ surface still varies. For example, the current Swift status page diff --git a/assets/post_images/2026-08-12/winter.png b/assets/post_images/2026-08-12/winter.png deleted file mode 100644 index eb381d210cd7a1ede308cb024c7d8113b6ab58ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12385 zcmeHuc{J4h+wg2=7-K16#=c~^Q3hk*W{hZ+$XYShrfx|ZWs6xfRJMwe$Y@%0w{VwA z62sWCj;N%BqEuwE@80i>`t>`{bI$Xg_dd@#&mYhGhwmRg%e7yhYnxN9F3u821tbUr zlGw7@(G3IwX99l)qHy3pqmPGbKp?E{7DvMFXulwvn9(1J%w1Ro2wVNvXFU_tNlr;) za>$~os~?c1I@Xt#dgF43G+Kft_<)%m$Y9)>pX;1rp#ZT1LdsUjOHjx5KruTG{l-l|4DJ^f+3NVJRoou^=GuJ#4nU8^?=!(C zLCxf{4tv=0@=`N@_=hI*_*kFG;^N#H+L`m`3673A1Yft)w6mvy?d5eFfjVo9A#i&fVz4$5EXg5%o+K2Z5gKu)pzKfn~>j#9qFygAnbI$sE$fW6cbmlp#pkdy~^ADTjCIyuL^3EBPCLuh~f)0v=60*&Mu zfe*bY&mLH;6DKLwIc~zNU%wt>L3@^A=wF4ki5Xwl?Y77%IkaHLd|m8k932~Nj5RiX zQ5SV_qd(kBrKP0>#hizu-f>QE_w`lSi*L_05r5|n@lb>2zgDqNXel|^>kD^nR#<1Y zb?a%=RgFMYSlAts`TO#iGXvAT)~FfP_xn>)Q-WRHT$PU0ac_0r#2g!aU&oj4_6`UN z+V8r{GgKNfZLH zLp)SibVE*i0l2Hi`JNgcv)tIhMj<3sNERoi4Hy@0$T8@2h{wL0w{LUR^fjRdIM-zC z`^poDM-3qFnCQ5e>e0pVA^kGajjlB;KkisxB08FSQdV3)X3O`FH+Q0%J@1fae@q}4 zsii1JRe>rar38Dr6HuzAlGssSX0nv3@twU-POIB)kg&L>mC`wU=xAgA`U)iViedJZzDb{BoI zNw7^ zry1O?fBk|9OR0q;bJN8LYhq-K9#(|t47?f69RP&h95MF(L`nX}bQct>5VKd3w10n4 z3D!Ia)M~o4yrgJ@z!w*CXa*EW6-o1GQccM&~-Tis{#AV|=+v9NEwrW=K&369y&^G3_ zi-txOCuSt`n0VO>_Z~@FECq@ecoq9I3;i#wd#!XWjNg&heJ!_8O^VG{!T$!){KR-YCrWg^vqv`g@nJc}d8|At1Uj0mcPR*KN zOLB$EIyeeOeR2!KcyehjMIrY|jTqvp!>LM)=az+nQeuyVWr{GACZLPwtZ(2VP?YclF)0{{3Z?bGi!nv$DNvUq^r? ze4g5YOmYEB-uS`b`ndjQ@G7$E#h;US$bp=+$F*n1} zpUy6G5&RZpu7%SyvdE1lv9sv*t)QvvVmt#=SySc`V#b8p0++3bEssUD7;Hy{-5EiZ za|$0_Z@x&I($>*7#u^!$@cDc!Pq89)3K^zF_AxSgCgybGthw>RxRyccV9IHgumD)@ zgl`4K2$~{?*fx$Ga~lQ5}X`h|?D6Dx8R z^pRJidr(i0jfV2zZ6b8JT(v#NF{i8_8QN)GPTL z&@HlxP`B1%5C{ZuI|aqek;)xBu{1Bc7a6QlbQ7E2vbI zr<@$jeO*z6<0B~_?hl!UU6bNac_fv2l#iJgFOz~N5f08$k2K-6K@a;0*$DCxhpldF zVn!~OiVfi}v>%QLNy?KCrk)|%CCrQN zEsswIXSF%$OT)_rdP_?HWf^szJC6Z;8)W<7YfL!kvl-s@Tsud?e}8*H5GcSkG)hF$ zlE%D7+UV}?9@70N$eMeif9-bH*Q&eubmIZ#{zdp^Tk;477ZATv4TTf?`RL7kqi-Kq z_q=@h5;H7@C#pWB62rSkV53BW+CnnlE>@Q;Ei9??m+0oo$7V zW>Y=XSf@hw&TsT9Px*jyy?rGIIaqUj8wv=p8or89p5jbRF`^!7_K8c(%XKwPTRka^ z7o9J^Wd>mP?AeoQDeijx>tKIl#G10f1&I>KiIoA{`Wrh;OiWNu3C-06I_u?8HEWV> zqiBETtA*oZnB5*8Mw8T7jT(oV{_8^}WzIXzoj9_L#z3{BA;H1bvZ6&F0?;B9F(Beh zY7>*l;t_~OY9rsjBEP?9Xs3+5uWIJ=tBDb}|LpwKimIZWq+h!AM|Tv&S$9FW65A0GeRgqFLQy99F!}Cbb#7?c%5g|j`u*k&a1zTnfmI%k0c#B zbSNw2p~3bRM@xxE18L{z1c$w;VD3K76IoY7$35E=E4(0|(1TYl|;1+*N zA>#tp)Vc*~Ys`$2Z~Yl~N5eXPP_#RiVtyJt^F$iCelUNCb&3-CVd2~B!~KQ&nSPd- z5glo2Y!igz_jO@L6^+jpoxhhzNNCaOtk`U;!Mh4KGc${57s+zLqk~n+8q1?k%{f=* z#F?KB{pfMS?X+i%dVFk*~Wb&L)dN_mH7bgg4jW(e{#o%_9jNC+UP*^9P#>p z;V*f)xzX?0!A3#OeIm<{GGhcK>3h%}aX{T3G zxqHAKYO$OpqcY#QMiJ%hhDGC`0G`rWIL6jSS@{raSC6hA4bU)8yITWLUx=hH3FOyv(2@#sl)8I$Qb-OME~s>B)Ky~o-h zT+%hiau6OVtN~-*S^I7q#y4F#@4B4@qmNN+5E{Z>zz=7l@E?2kJU$fET@fYj_ZB}P z8^AN2ucC6z+OOCp-+m4It_nUvij%^hf3H?~^{Pg+xpmQ1P?|pjX_l#^OQa(o50oT% ztSK)X|G{oRl_6yZsUlM?u{x6sb6Gm_>DkOqSy}bVE(C5)?W{a*uT|7Mj?dH8y&G{v z9l6q1l{kR=@L8%clEQO4ynR!xd@oDl!8fxz2=fa`w4)T3`W_y#IIqQkd9FsIUks|@ z`f(-5+eGQbWW<*9z!Yc5d__3QAAAE8+-Q@>i5MNn~Fb*;RKW!a&}x}K`^D6kVjQ;uj}Gl z8^&njrua}s+ab-@1*|&dWESxl%N=u{mJKJ1S&`?;v6B?qW1fg);>vOmT7+Q^100-H ztwhmDEnQI`<%fvq9QAqd@kSSrgl%>Gd_bHtew?-a9(d#=by5Yf5wZN^qfSKK1xUOW zsz?XohOqiL+&22OU&4QUJWKgxO)74YKi0Pb-d4v#zN{6!A0IPT`K(_eqD%>@0Ea1y zLC+yJY*d>W&MNdIAAl%|voNuFR;y_=wyLSOaMk{(X;9OI+t@j}WvcVuDu;Du>w95d-Suzfe|+l<_X|G; z2W$gvM%GwU8hLQEe2NssdTHLMSd6D1PusS009el4qq#DQ_eX8LRe2$`?Hy3?AAXdC?@obZw*kcw{813j%}yIeRDYKL)=@ zT2f_7w?}^&A3~KZnP5!_+2Z6|8_7`B&8Xec`Wng>2Le%+U;10#nDi6>INJ-!<@P*O zynksoq6`fw{g}V^G5ww)=*!abs5SRyd%><$@F6GIq4bkt^a~@#sdR!OgnfQ^ouh;z zpB%PP(i85Y78+7rb+xglCUw9v;=$|b-UZCPy-&sJ7vn2X4jlEO!yGo+?^y!0Z=I-> zSxx&}1UCf9Q4t}_HX9D#_v|h+->eQe6xpN_Tl80jEWz2#WU}WS-{+tnu7)wA0wDZBia31(TRUUQp1Ke2_ObNg z8)>mbP!pfp_hTV`{u~h-@&tF`IO-x*=3655D3+z^ir-d5;67weHS`137-Ujv8Q4Q1 zp7S26cuJ?4IlF)zCLhFxy?uCbpgup2L+6{Ixp`_ZrBC-C;`T=zD-D^Q{%FXZDM)nP zi|8p}p=_=wmZq*N-6FoE8?M!#WCg@-x7>HRUETT@^w4ro$q|{7>$74NlnJc}FN-3& zV#<1nzf#=X-OzaFQtB~HMtal=y^Ag~+GCDTIT{cWzGdd4Cw!Y6Vna53!v;IBjoska zhx#a1T4r&fqADRAw&7V+$>aT?DGitl+v6h`HwRwvyY#kMj5F zvzDy0X63Kh8{5zW#3LB$!-IoR&cZ(YCtB>TT+ZUa_Yacqq4y)F^x(YE9r8M#JJ?&` z2b61~$9q1muuW}_sB_xtYt(9+#caYdFMUvh9?Fb`+Bhvud^YTF0sHzJYU|O@?Mhf$ zZsspf9PdwyON85;#uJ@WIr*>Nz1z#sFkytd!VLF_sF;cMh)4is1?30E$Z-|I?f4|wnQNrSN zJEys>n*XDDremGW&wWq@2(cUz5E>dREjt)M3JOr!^;{HJXqS8VS80(&BsF@fv6_e| z@0gPGk|t;tsoRNF;C$w^uuNDj4_}xC^LYTCIUE7;@=6O=P;7PkRW^)J z#}?^J^XhF?`Yqj+l)_LWcUstAU<#0`G%)AD z;Ok<0w$Hx`toqV}(xAIVYx|-1iXh=2r5%dhL=a^|!_C9&<8~Q0G6Z)CioGy7hymy5 zjoPGWw0DlPlsosLo*lHkR-qSa>`M_ANl{E`^g5z9Yb#O7I|iHKsS@2;`&hLKV|QZC zDEH{EqHsVdgvO+%fXun%A)3AA;_TGrf!2n@>^Gr5OTO?OqNr%8Bt?r|ZD!>)$iwtm z&`4Cg%4kfgoT89^h5-z|2`D7%l_YbD`x2@0TbI!5D6l>tX5hd?ff9?)Oa6TQN;QDkZ^~zC=FbUPgq$J z&%>+wOreJzid5|rO{mNw{IfLwsuiQfrJ2Jx?yu5tCXxm=d{ULLVrpQRSd)#!+opMq zRi?iGY~<^@%9%N7G=Ew1g6YvZ&nO(>^M zVB?pj72_K$<@qYo_3D%>)?}@hGpdPbAiHQ*e)b@!l(TAu-_^0YuiNR!zQ>%S1$Obv z)tuJnO`!4VS(V>Z=5r zCAXy0kChf>AQO0ZlUUX~g1pP$pyG!>LKH{}g?J))e#qf_sa5z7D=Ud8pn@C(lKsxw z5o4dYvdQ4G$SajQ9QQ;Y+B_kYhOI^QA6lQ9iDZgBuSg_KESoGsU&*OGAbcY5?fJDU zD;+Uu9piyQ{YqkTGT&4q^co4yajx+dh!Re+2Ly$xa+X>CsT`~2zSy`<@{8a70yTY@ zQ+thquy9`s*oV3zIb9l8+KjAF0M{b+Nl82{kiab=d|43RFQQ*hFfLqJf!$lH)D~Cl zBxAaotn4@dXfKQi!s#?}p9Aizx`tDK+Fp?;SrH2Jmv5z&0vRBr)|~fO^_abL#w$## z$gg$+Q5}XqX&(kgC`K3|#7mM8r)Wl+WFthaJZlQWs#9dm*-OKfZIEA0sj&)q9Ifx$ z*KIt4@UT&9Y2dbjdLF=PR-EKdriN;)1+pff%dr>e%}A<)%A;gTm6VfCJ)_B#zRNoW z_TGjsZW$o4b{q?V_|~>_t3l<;lM;OI!n2WPz2uskHbA_a*|8g^0Kp-_t|B0X&<{g_ z`#~G5RY!ojl=WHZ5AeQcQsoyzaRC4i3x&;}&PKARt0fFC)6EJ{cde_gt+gnvk@x;~ zihtCoVsR#FQg5gukAQ_aUHp-*eXc{A6(~wId%E@XxG|NP#N0as7Du>hX>jU5e~CKF z0JhK%bbo-Z%n1NMwgWZeGJ~);N4}!xKoLUEL`H^hWA&(`)JOpmO(ao7|HRRRvIOY` zzj@<>?)JGveSGk~G1b#k;wA^SW0sM=Mq!5HS0>qICl9E@94u^J4pe0I${t+(hhRFipXwo1Lbj@NON@?Vd zrrsTV! z5kWgeQ;_K`O;Cz_`^Dlar_OqSZb7UREF>sh8niR(fpJC!AABWQ#Oei%LLVc404|PS z?a=`qn*%tuEfZ_xEm%#^=zQF8*-S?gKwXjpi6R+{O9RkMmqs6G_9o~oRnGCV!4&^S z@>Sr5Kw(zBLkCdQ8bHaDtjXN|i^cZT<0~23$sN*ac0wL>87n|69Qt1uRG}K>u@aA| z^K%P^T)@+#D*+c6Ti8Y3C@>d*rAdHa0(O3T?9aUlq3zo`^|{@i>7TfaU0hzu82CJ9 zk79fEq-&qpN)&d*h=43^L%@&PViF#hlt>L^d#lRjHmT>|2*_2&vaD5w5eIHu_gCwD5ycMw;MHX1EtsQSG$shT)f` z>@AmHsbj5@Ompi2E5M5`E-e%cyj&npzgz~delmKfG`^6(3zPJdyY|$3pm@;bib9^xma`)=|j|q9Mz+>X|>zyurL#1KZ1mZ65$Ez{qi?O#H!*?o&Aj2EPK~z1iL`@+d>Y{6 zAm*Q!p!X8yoB1Eji;_8@%6KFNuStHOv02rG|DEE*vf~Y;mHgSYva}y$SS&K7y9}{p z#iaG&A0#lm+?PUTrQEOzaeyGHt9+>f#AgD-6ZiP9K%|rw3FTlG+aV$Siibt+G&5Q> z)#Yl2_Q0>WK?3}r`;-FpRruNb1^TQkf6>%wyp#Le-(J&a6;j^6ZV{@i#IM%|Xb~F< zTg111w}^%A=)Y_T3tO!J*8ctF69LME5m)1?rJqXwSGW3~G;I?L?tes!0XWCx z5~uUS|DPQK!Tb-hBOn{@S9PMEKi%8hL9zWAm2>rA-cBFSKT-$%W#d;B1Amm1eKMPt zv6GBzbY2}+AbBTohLuDm*lV7QgM20?>rOBa_t|BH=Ks=W!N36MyEMMo2CoNnrQ7+l zZ)sym$eipx!GthbSINOK(GeBo<7npuys@_3Q6|7Eb zI8JGUr7*H%x(RR!u9e%MCzWa9WUi1dw~7kjJFqU$(!Xp-1hevvh*HpMV5KMMs;aBs zAn%LeJD59x=2{uM1Jb(!Y*oqR);+aBGR0GvhSNY^{}8>(OzSAe*m1c1Z~UP^-abhU2`HP2FjdKUF0$RIyqc9uD?%vMBIC1&`! zn&nyzs|w(_)tgeSj3B?G2J(#_w_FAX;;VVWq zkoT0inX!Tn2MP`bw7oPdA(+$I2m=p+lq|Jie|%Yo%ZeHsh+?YRSEk7o0)RWE(H*jl zuJ>$6w*o2b0luMnEz+cHe2>l#o zUdlp#T&b+(IOWzDpfZ4B8^M_FK+tvpAe392$?~*P-uKlSm>dEsi%8mlcMOLfDgg3_ zZc)CA^TqjePmNMh+8Wa&(>#4C*AD`^ZIEKV*E>6|SF*E39}-EmyVbbQ#;IDVz>UPG znJnkk8_&7|5r(6PF`)h4R|Us#;-axPejYP*Aa#4GY2F2nofaIsO+}|rtjfENm#!_u z!I)9NG5uf1kSVn7H`&`xIP0(33LNuP(-c4x9AiACq7A-K1f*?p0CJXQWN_&?eg_gz z3B>*ZZ1;;V!)|>YTa&t^(Z_(dafoIsCgAeklU&k{pEtTWki0$A*x88XB2@rK3uJO( z<|^6iYk}g#ExePyR85C1Ai{p}lZ9D7IRb=f-n#{-eu$j_n8lGr5{>fHH%=+vbu2jv zDpHD>UsT~{77Gr)keMBJBF@VriIzWiWD@(|K5j?NsQ3lV&VK3T-4kpZmzfr9lSOj1 zgxlW8K2Kz>^y{BjwR=(r`g)1(5Yz}f=K4}iG84eY9(hsj2I0m_*^R~iEb*prW2&6j zqI3Dt#Ld7ER5oI2?GM2|Sy8U3aGy_hWAe_!)kIIAAyCb?Xm%Ap-O&-n*5RJZ+gLVRs5~(@LLxbgku7Da{iX;bX@|=ir zB(0jdI%|!IC9>6Zmr}-s`Q_OkX#`K-leFyYL#2|10ismxrh>|7kiG529#4sA84}K2 zGJnzJ9N@=qxPKKIS83}^SDtRe=NDk$IW!sK{TZKD3%Il|S!M!5F+5b0-6J!^xG$iE z`1rHkIWxi0o|^)@>=F<1-JT^?4rDRzl1l-7XG=OFaQQC`1>(JKaOaQST`u=VDXKLJ ztyw589Q9dv?ecTMwJ`xMX#hR-X8V8)AapA;0D)JOAT-C-)deS~^*MEZ;cOZjcJu$h zRl)6Ao*zBRe2hb`gB`hL`l z5Fk@@H%7%_l>qbIag(8c2|%e%MRoFuZo@@AvL7I~t;{u0=C}YQN`$t6nv`fv(W!Cp zZGa0}+!P2giakKKQeJ#cxn?+}+z6}ywk=qJ5f2Ghte#WW19ZMlkBoE&?_ib)7vaPZ z;bJVXxMAx0=k#%cyAaqe5Q?lN} zDC(6EspxC&Jh#4ujBwD-+$Ga6X5>o7XEDQCIUm z2^gbv*M@(RLwX_v)K0PLx;QU6eJV2k7V|^?FrYZ4<7(P4_`DOhgJQMV`3L#dSjLz? z=9-o)Cg+k5GbtI-43GmobJfFpb+Z->eo#n| Date: Tue, 11 Aug 2026 17:52:03 +0200 Subject: [PATCH 13/27] wip --- ...p-With-Conan-Managed-Dependencies.markdown | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 0cfc26dd..221fd11a 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -102,6 +102,9 @@ same API to render the demo scene: import CxxStdlib import LunaSVGMod +let svg = "..." +let css = ".sky{fill:#8ECBEB} ..." + let document = lunasvg.Document.loadFromData(std.string(svg)) document.pointee.applyStyleSheet(std.string(css)) @@ -109,23 +112,24 @@ let bitmap = document.pointee.renderToBitmap() _ = bitmap.writeToPng(std.string("summer.png")) ``` -`svg` and `css` are ordinary Swift string values defined earlier in -`main.swift`. This closely follows the C++ version above. +`svg` and `css` are ordinary Swift strings. The mapping is visible in the code: - The C++ namespace `lunasvg` remains visible in Swift. - `Document::loadFromData` becomes a static method. -- The returned `std::unique_ptr` owns the C++ object; Swift accesses - that object through `pointee`. +- `document` is the `std::unique_ptr` that `loadFromData` returns, the + smart pointer that owns the C++ object. Swift reaches the object it owns + through `pointee`, the same role `->` plays in the C++ version above. - `renderToBitmap` returns a C++ `Bitmap` by value. - `writeToPng` remains an ordinary member-function call. The `std.string(...)` conversions are explicit for a reason. Swift does not automatically bridge a dynamic Swift `String` to C++ `std::string`. `CxxStdlib` exposes supported standard-library types, but creating these C++ strings still -allocates and copies data. Direct interop removes the wrapper; it does not make -every conversion free. +allocates and copies data. Direct interop removes the hand-written wrapper +layer, but it does not remove that cost. The allocation and copy still happen. +They just happen at the call site instead of inside a wrapper function. Running this produces the actual LunaSVG output, `summer.png`: @@ -185,7 +189,8 @@ class SwiftCppDemo(ConanFile): def generate(self): CMakeDeps(self).generate() - CMakeToolchain(self).generate() + + tc = CMakeToolchain(self) include_dir = self.dependencies["lunasvg"].cpp_info.includedirs[0] header = f"{include_dir}/lunasvg/lunasvg.h" @@ -195,20 +200,21 @@ class SwiftCppDemo(ConanFile): " export *\n" "}\n" ) - save( - self, - os.path.join( - self.generators_folder, - "shim", - "lunasvg.modulemap", - ), - module_map, + modulemap_path = os.path.join( + self.generators_folder, + "shim", + "lunasvg.modulemap", ) + save(self, modulemap_path, module_map) + tc.variables["LUNASVG_MODULEMAP"] = modulemap_path + + tc.generate() ``` The recipe gets the include directory from LunaSVG's `cpp_info` instead of guessing a path inside the Conan cache. This keeps the shim tied to the package -Conan actually selected. +Conan actually selected. It also hands the shim's path to CMake as a +`CMakeToolchain` variable, so `CMakeLists.txt` does not need to re-derive it. ## Connecting Conan, CMake, and `swiftc` @@ -224,16 +230,10 @@ find_package(lunasvg REQUIRED) add_executable(demo main.swift) target_link_libraries(demo PRIVATE lunasvg::lunasvg) -get_filename_component( - _conan_generators_dir - "${CMAKE_TOOLCHAIN_FILE}" - DIRECTORY -) - target_compile_options(demo PRIVATE "$<$:-cxx-interoperability-mode=default>" "$<$:SHELL:-Xcc -std=c++${CMAKE_CXX_STANDARD}>" - "$<$:SHELL:-Xcc -fmodule-map-file=${_conan_generators_dir}/shim/lunasvg.modulemap>" + "$<$:SHELL:-Xcc -fmodule-map-file=${LUNASVG_MODULEMAP}>" ) ``` @@ -241,6 +241,8 @@ Each piece has one job: - `lunasvg::lunasvg`, generated by `CMakeDeps`, carries the native link and usage requirements modeled by the Conan package. +- `LUNASVG_MODULEMAP` is set by the recipe's `generate()` shown above, through + a `CMakeToolchain` variable. - `-cxx-interoperability-mode=default` enables C++ imports in Swift. - `-Xcc` forwards the C++ language mode and module-map path to the embedded Clang compiler. From 7b9b0a52b172b667b10d2d6a0ef909bbcec9438c Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 19:16:44 +0200 Subject: [PATCH 14/27] wip --- ...p-With-Conan-Managed-Dependencies.markdown | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 221fd11a..c2b8a80d 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -137,33 +137,6 @@ Running this produces the actual LunaSVG output, `summer.png`: summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied
-## What "Direct" Means - -Swift does not translate LunaSVG into Swift, and this project does not compile a -hand-written C wrapper. Instead, the path looks like this: - -```text -C++ headers -> Clang module -> declarations visible to Swift -Swift calls -> platform C++ ABI -> compiled LunaSVG library -``` - -The Swift compiler uses Clang to parse LunaSVG's public headers and imports the -supported declarations into Swift. It then emits native calls that follow the -target's C++ ABI, and the linker resolves those calls against the library -provided by Conan. - -This is why the header is only half of the dependency. Swift also needs a binary -built for a compatible target, C++ standard library, ABI, and set of options. A -module map makes headers importable; it does not make an arbitrary binary -compatible. - -Pure C libraries follow the same broad dependency pattern: Conan can provide -their headers and binaries, and a Clang module map can expose the headers to -Swift. The difference is that Swift imports C by default, so a C library does -not need C++ interoperability mode or the additional C++ ABI constraints -discussed here. That is also why C facades were historically the common route -from Swift to C++. - ## Generating the Module Map with Conan The module map shown above contains a package-specific include path. Rather @@ -281,6 +254,33 @@ The same integration model can be used on other Swift platforms, but the exact supported C++ surface still varies. For example, the current Swift status page lists `std::shared_ptr` and `std::unique_ptr` as unsupported on Windows. +## What "Direct" Means + +Swift does not translate LunaSVG into Swift, and this project does not compile a +hand-written C wrapper. Instead, the path looks like this: + +```text +C++ headers -> Clang module -> declarations visible to Swift +Swift calls -> platform C++ ABI -> compiled LunaSVG library +``` + +The Swift compiler uses Clang to parse LunaSVG's public headers and imports the +supported declarations into Swift. It then emits native calls that follow the +target's C++ ABI, and the linker resolves those calls against the library +provided by Conan. + +This is why the header is only half of the dependency. Swift also needs a binary +built for a compatible target, C++ standard library, ABI, and set of options. A +module map makes headers importable; it does not make an arbitrary binary +compatible. + +Pure C libraries follow the same broad dependency pattern: Conan can provide +their headers and binaries, and a Clang module map can expose the headers to +Swift. The difference is that Swift imports C by default, so a C library does +not need C++ interoperability mode or the additional C++ ABI constraints +discussed here. That is also why C facades were historically the common route +from Swift to C++. + ## What Direct Interoperability Does Not Solve The Swift syntax is pleasantly ordinary, but direct interop is neither a stable From 7eacc8f627e25ddd7e82f1a4f50f392859c780ae Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 19:43:45 +0200 Subject: [PATCH 15/27] wip --- ...p-With-Conan-Managed-Dependencies.markdown | 247 +++++------------- 1 file changed, 59 insertions(+), 188 deletions(-) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index c2b8a80d..2a18346a 100644 --- a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -137,62 +137,34 @@ Running this produces the actual LunaSVG output, `summer.png`: summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied
-## Generating the Module Map with Conan +## Putting It Together with Conan and CMake -The module map shown above contains a package-specific include path. Rather -than hard-coding that path, the Conan consumer recipe obtains it from the -selected LunaSVG package and generates the file during `conan install`: +The module map shown above needs the real location of the LunaSVG header. The +consumer recipe obtains it from the package selected by Conan and generates the +file during `conan install`. The important part of `generate()` is: +{% raw %} ```python -import os - -from conan import ConanFile -from conan.tools.cmake import CMakeDeps, CMakeToolchain, cmake_layout -from conan.tools.files import save - - -class SwiftCppDemo(ConanFile): - settings = "os", "arch", "compiler", "build_type" - - def layout(self): - cmake_layout(self) - - def requirements(self): - self.requires("lunasvg/3.5.0") - - def generate(self): - CMakeDeps(self).generate() - - tc = CMakeToolchain(self) - - include_dir = self.dependencies["lunasvg"].cpp_info.includedirs[0] - header = f"{include_dir}/lunasvg/lunasvg.h" - module_map = ( - "module LunaSVGMod {\n" - f' header "{header}"\n' - " export *\n" - "}\n" - ) - modulemap_path = os.path.join( - self.generators_folder, - "shim", - "lunasvg.modulemap", - ) - save(self, modulemap_path, module_map) - tc.variables["LUNASVG_MODULEMAP"] = modulemap_path - - tc.generate() +def generate(self): + include_dir = self.dependencies["lunasvg"].cpp_info.includedir + header = f"{include_dir}/lunasvg/lunasvg.h" + + modulemap_path = os.path.join(self.generators_folder, "lunasvg.modulemap") + modulemap = textwrap.dedent(f'''\ + module LunaSVGMod {{ + header "{header}" + export * + }} + ''') + save(self, modulemap_path, modulemap) + + tc = CMakeToolchain(self) + tc.variables["LUNASVG_MODULEMAP"] = modulemap_path + tc.generate() ``` +{% endraw %} -The recipe gets the include directory from LunaSVG's `cpp_info` instead of -guessing a path inside the Conan cache. This keeps the shim tied to the package -Conan actually selected. It also hands the shim's path to CMake as a -`CMakeToolchain` variable, so `CMakeLists.txt` does not need to re-derive it. - -## Connecting Conan, CMake, and `swiftc` - -The CMake project links the Conan target as it would for a C++ executable, then -adds the compiler options introduced above together with the C++ language mode: +Then `CMakeLists.txt` links lunasvg the normal way: ```cmake cmake_minimum_required(VERSION 3.28) @@ -210,35 +182,13 @@ target_compile_options(demo PRIVATE ) ``` -Each piece has one job: - -- `lunasvg::lunasvg`, generated by `CMakeDeps`, carries the native link and - usage requirements modeled by the Conan package. -- `LUNASVG_MODULEMAP` is set by the recipe's `generate()` shown above, through - a `CMakeToolchain` variable. -- `-cxx-interoperability-mode=default` enables C++ imports in Swift. -- `-Xcc` forwards the C++ language mode and module-map path to the embedded - Clang compiler. +The three Swift options enable C++ interoperability and give Clang the C++ +language mode and module map it needs to parse the header. -`CMakeToolchain` derives `CMAKE_CXX_STANDARD` from the consumer profile's -`compiler.cppstd`. Forwarding it to Clang keeps the imported headers in the same -requested language mode. That matters for libraries whose public declarations -change according to `__cplusplus`. - -## Build and Run - -The example currently targets macOS. It requires the Xcode command-line tools, -CMake 3.28 or newer, and Ninja. CMake supports Swift with its Ninja and Xcode -generators; this example selects Ninja explicitly through the Conan toolchain. - -Use a recent Swift toolchain. C++ interoperability began in Swift 5.9, but that -initial release is not sufficient for this exact example: support for the -`std::unique_ptr` returned by LunaSVG was added later. +On macOS, with a recent Swift toolchain, CMake 3.28 or newer, and Ninja, the +complete example can be built and run with: ```bash -git clone https://github.com/conan-io/examples2.git -cd examples2/examples/languages/swift/cxx_interop - conan install . --build=missing \ -c tools.cmake.cmaketoolchain:generator=Ninja cmake --preset conan-release @@ -246,117 +196,38 @@ cmake --build --preset conan-release ./build/Release/demo ``` -`conan install` resolves LunaSVG and selects a package matching the active -profile. If no suitable binary is available, `--build=missing` builds one from -source. Running the executable writes `summer.png` to the working directory. - -The same integration model can be used on other Swift platforms, but the exact -supported C++ surface still varies. For example, the current Swift status page -lists `std::shared_ptr` and `std::unique_ptr` as unsupported on Windows. - -## What "Direct" Means - -Swift does not translate LunaSVG into Swift, and this project does not compile a -hand-written C wrapper. Instead, the path looks like this: - -```text -C++ headers -> Clang module -> declarations visible to Swift -Swift calls -> platform C++ ABI -> compiled LunaSVG library -``` - -The Swift compiler uses Clang to parse LunaSVG's public headers and imports the -supported declarations into Swift. It then emits native calls that follow the -target's C++ ABI, and the linker resolves those calls against the library -provided by Conan. - -This is why the header is only half of the dependency. Swift also needs a binary -built for a compatible target, C++ standard library, ABI, and set of options. A -module map makes headers importable; it does not make an arbitrary binary -compatible. - -Pure C libraries follow the same broad dependency pattern: Conan can provide -their headers and binaries, and a Clang module map can expose the headers to -Swift. The difference is that Swift imports C by default, so a C library does -not need C++ interoperability mode or the additional C++ ABI constraints -discussed here. That is also why C facades were historically the common route -from Swift to C++. - -## What Direct Interoperability Does Not Solve - -The Swift syntax is pleasantly ordinary, but direct interop is neither a stable -C ABI nor an automatic safety boundary. - -### Headers and binary must agree - -The headers parsed by Swift and the linked library must agree on every choice -that affects the C++ ABI, including: - -- Target platform, architecture, and deployment target. -- Compiler ABI and C++ standard-library implementation. -- Dependency versions and transitive libraries. -- Defines or package options that change public declarations or object layout. - -Conan profiles, package IDs, and dependency metadata make these choices explicit -and let Conan select or build a compatible artifact. They cannot fix an -ABI-changing option that a package recipe fails to model, so accurate package -metadata still matters. - -### Ownership does not disappear - -C++ classes are generally imported as Swift value types. Copying one can invoke -its C++ copy constructor, and destroying it invokes its C++ destructor. A -`std::unique_ptr` remains a unique owner, so it must stay alive while Swift uses -`pointee`. - -Raw pointers, references, and view types require the same lifetime reasoning as -they do in C++. Swift 6.2 introduced experimental safe-interoperability features -that can improve this for annotated APIs, but they do not make every existing -pointer API safe automatically. - -C++ exceptions are another important boundary: Swift cannot catch them. An -exception that escapes C++ into Swift terminates the program, so a production -API should catch and translate errors on the C++ side. - -Finally, interoperability still covers a growing subset of C++, not every -possible header. Some template patterns and standard-library types remain -unsupported. Check the current [Swift C++ interoperability status -page](https://www.swift.org/documentation/cxx-interop/status/) when evaluating a -library. - -The sample also keeps error handling short to make the interop visible. -Production code should verify that `loadFromData` did not return a null pointer -before dereferencing it and should check the result of `writeToPng`. - -## When a Wrapper Is Still the Better Boundary - -Direct interoperability removes boilerplate, but it does not make wrappers -obsolete. A small C or C++ adapter can still be the better design when the -upstream API: - -- exposes unsupported C++ constructs; -- relies heavily on exceptions, raw pointers, or ambiguous lifetimes; -- has a large template-heavy surface that should not leak into the Swift code; - or -- needs to be isolated behind a narrower, more stable ABI. - -The difference is that a wrapper is now an architectural choice for shaping the -boundary, rather than an automatic prerequisite for calling any C++ code. - -## A Reusable Pattern - -The LunaSVG example reduces to five steps: - -1. Let Conan select or build the native dependency for the active profile. -2. Describe the public headers with a Clang module map when upstream does not - provide one. -3. Link the Conan-generated CMake target normally. -4. Enable Swift C++ interoperability and give embedded Clang the same header - configuration. -5. Treat ABI, ownership, lifetime, and errors as part of the API boundary. - -Swift now makes the direct C++ call possible. Conan handles the less visible but -equally important part: making sure there is a suitable native artifact behind -that call. +`conan install` selects a package for the active profile or builds one when +needed. Running the executable writes `summer.png` to the working directory. + +Together, Conan, CMake, and Swift’s C++ interoperability make an unmodified +ConanCenter package directly callable from Swift, without a wrapper library. +Direct access, however, does not remove the usual binary compatibility +requirements of calling C++ code. + +## What Direct Interoperability Does—and Does Not—Provide + +The module map exposes LunaSVG's declarations, but Swift still calls a compiled +C++ library through the platform C++ ABI. The headers and binary therefore need +to agree on the target, compiler ABI, standard library, dependencies, and any +option that changes public declarations. Conan's package model is useful here: +it selects or builds the native artifact that sits behind the imported API. + +Direct interop also does not turn a C++ API into a Swift-safe API. The most +important points to keep in mind are: + +- C++ ownership and lifetime rules still apply. The `std::unique_ptr` in this + example must remain alive while Swift accesses `pointee`, and raw pointers or + views require the same care they would in C++. +- Swift cannot catch C++ exceptions. A C++ exception that crosses the boundary + terminates the program. +- Swift supports a growing subset of C++, not every template or standard-library + type. The [status page](https://www.swift.org/documentation/cxx-interop/status/) + documents the current limitations. + +For libraries with unsupported constructs, exception-heavy APIs, or difficult +lifetime rules, a small wrapper can still provide a cleaner boundary. The +difference is that it is now a design choice rather than a requirement for every +C++ library. Try the [complete example](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop) From 0f74917617c0d1532860e26b421763ababb1d4ca Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 11 Aug 2026 19:44:20 +0200 Subject: [PATCH 16/27] change date --- ...19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename _posts/{2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown => 2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown} (100%) diff --git a/_posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown similarity index 100% rename from _posts/2026-08-12-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown rename to _posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown From 2826ad8e5e926d1210e776d27e9366afb22d591a Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Wed, 12 Aug 2026 07:33:36 +0200 Subject: [PATCH 17/27] wip --- ...p-With-Conan-Managed-Dependencies.markdown | 38 +++++++++--------- .../{2026-08-12 => 2026-08-19}/summer.png | Bin 2 files changed, 19 insertions(+), 19 deletions(-) rename assets/post_images/{2026-08-12 => 2026-08-19}/summer.png (100%) diff --git a/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 2a18346a..60f00005 100644 --- a/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -7,17 +7,16 @@ meta_title: "Calling a C++ Library Directly from Swift with Conan - Conan Blog" categories: [cpp, conan, swift, macos, cmake] --- -Swift has been able to import C and Objective-C APIs since its early releases. -C++ was more difficult to support because Swift's C importer could not represent -features such as namespaces, overloaded functions, templates, constructors, -destructors, and standard-library types. Calling a C++ library from Swift -therefore usually meant putting a C facade or an Objective-C++ wrapper in front -of it. +If you've ever wanted to call a C++ library directly from Swift, you've +probably ended up writing a C facade or an Objective-C++ wrapper first. Swift +has been able to import C and Objective-C APIs since its early releases, but +its C importer couldn't represent C++ features such as namespaces, overloaded +functions, templates, constructors, destructors, and standard-library types, +so a wrapper was the only way in. [Swift 5.9](https://www.swift.org/blog/swift-5.9-released/) changed that by -introducing direct C++ interoperability, allowing Swift to import C++ headers -and call supported C++ APIs without first exposing them through a C facade or an -Objective-C++ wrapper. +introducing direct C++ interoperability: Swift can now import C++ headers and +call supported C++ APIs with no wrapper layer in between. To try this with an existing package, we use [LunaSVG](https://conan.io/center/recipes/lunasvg), a C++ SVG renderer from @@ -87,11 +86,9 @@ compiler. With these options, Swift can import `LunaSVGMod` and use the supported declarations from the header. The CMake configuration shown later adds the same options using the module map generated by Conan. -
-Note: Despite the similar terminology, this is a [Clang -module](https://clang.llvm.org/docs/Modules.html), not a named C++20 module. -Swift does not currently import C++20 modules. -
+> **Note**: Despite the similar terminology, this is a [Clang +> module](https://clang.llvm.org/docs/Modules.html), not a named C++20 module. +> Swift does not currently import C++20 modules. ## Calling the Same API from Swift @@ -134,7 +131,7 @@ They just happen at the call site instead of inside a wrapper function. Running this produces the actual LunaSVG output, `summer.png`:
- summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied + summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied
## Putting It Together with Conan and CMake @@ -182,8 +179,11 @@ target_compile_options(demo PRIVATE ) ``` -The three Swift options enable C++ interoperability and give Clang the C++ -language mode and module map it needs to parse the header. +The `-cxx-interoperability-mode` option enables C++ interoperability and the +module-map option gives Clang the module map it needs to parse the header. +`CMakeToolchain` derives `CMAKE_CXX_STANDARD` from the consumer profile's +`compiler.cppstd`. Passing that value to Swift's embedded Clang via `-Xcc` +ensures that LunaSVG's headers are parsed using the requested C++ standard. On macOS, with a recent Swift toolchain, CMake 3.28 or newer, and Ninja, the complete example can be built and run with: @@ -199,12 +199,12 @@ cmake --build --preset conan-release `conan install` selects a package for the active profile or builds one when needed. Running the executable writes `summer.png` to the working directory. -Together, Conan, CMake, and Swift’s C++ interoperability make an unmodified +Together, Conan, CMake, and Swift's C++ interoperability make an unmodified ConanCenter package directly callable from Swift, without a wrapper library. Direct access, however, does not remove the usual binary compatibility requirements of calling C++ code. -## What Direct Interoperability Does—and Does Not—Provide +## The Limits of Direct Interoperability The module map exposes LunaSVG's declarations, but Swift still calls a compiled C++ library through the platform C++ ABI. The headers and binary therefore need diff --git a/assets/post_images/2026-08-12/summer.png b/assets/post_images/2026-08-19/summer.png similarity index 100% rename from assets/post_images/2026-08-12/summer.png rename to assets/post_images/2026-08-19/summer.png From d10c6ae08343b94d2e675bb7c4128c6a15eadda3 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Wed, 12 Aug 2026 07:46:10 +0200 Subject: [PATCH 18/27] wip --- ...-Cpp-Interop-With-Conan-Managed-Dependencies.markdown | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 60f00005..ef9a950e 100644 --- a/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -216,10 +216,11 @@ Direct interop also does not turn a C++ API into a Swift-safe API. The most important points to keep in mind are: - C++ ownership and lifetime rules still apply. The `std::unique_ptr` in this - example must remain alive while Swift accesses `pointee`, and raw pointers or - views require the same care they would in C++. -- Swift cannot catch C++ exceptions. A C++ exception that crosses the boundary - terminates the program. + example must remain alive while Swift accesses `pointee`, and [raw pointers + or views](https://www.swift.org/documentation/cxx-interop/#working-with-c-references-and-view-types-in-swift) + require the same care they would in C++. +- Swift cannot catch [C++ exceptions](https://www.swift.org/documentation/cxx-interop/status/#c-exceptions). + A C++ exception that crosses the boundary terminates the program. - Swift supports a growing subset of C++, not every template or standard-library type. The [status page](https://www.swift.org/documentation/cxx-interop/status/) documents the current limitations. From 43b252549bd5afece8c3e9d1f6de278881a0cede Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Wed, 12 Aug 2026 08:06:50 +0200 Subject: [PATCH 19/27] fix --- ...Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index ef9a950e..e7455c54 100644 --- a/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -164,7 +164,7 @@ def generate(self): Then `CMakeLists.txt` links lunasvg the normal way: ```cmake -cmake_minimum_required(VERSION 3.28) +cmake_minimum_required(VERSION 3.23) project(swift_cpp_demo LANGUAGES CXX Swift) find_package(lunasvg REQUIRED) @@ -185,7 +185,7 @@ module-map option gives Clang the module map it needs to parse the header. `compiler.cppstd`. Passing that value to Swift's embedded Clang via `-Xcc` ensures that LunaSVG's headers are parsed using the requested C++ standard. -On macOS, with a recent Swift toolchain, CMake 3.28 or newer, and Ninja, the +On macOS, with a recent Swift toolchain, CMake 3.23 or newer, and Ninja, the complete example can be built and run with: ```bash From d7ce98dc683dfb802873c9878d977be0fd1ae0bb Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Wed, 12 Aug 2026 10:02:08 +0200 Subject: [PATCH 20/27] new title --- ...Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index e7455c54..3c93b7ee 100644 --- a/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -1,9 +1,9 @@ --- layout: post comments: false -title: "Calling a C++ Library Directly from Swift" +title: "Using an Unmodified C++ Package Directly from Swift" description: "Swift can call supported C++ APIs without a hand-written wrapper. We use Conan to supply an ordinary LunaSVG package and connect its headers, binary, and build settings to Swift." -meta_title: "Calling a C++ Library Directly from Swift with Conan - Conan Blog" +meta_title: "Using an Unmodified C++ Package Directly from Swift with Conan - Conan Blog" categories: [cpp, conan, swift, macos, cmake] --- From bef2ee11e31862c57e52db50eadeb66e15ee2616 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Wed, 12 Aug 2026 10:29:29 +0200 Subject: [PATCH 21/27] add comment --- ...wift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 3c93b7ee..8af989c0 100644 --- a/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -137,8 +137,9 @@ Running this produces the actual LunaSVG output, `summer.png`: ## Putting It Together with Conan and CMake The module map shown above needs the real location of the LunaSVG header. The -consumer recipe obtains it from the package selected by Conan and generates the -file during `conan install`. The important part of `generate()` is: +consumer recipe obtains it from the package selected by Conan, generates the +file during `conan install`, and passes its location to CMake as the +`LUNASVG_MODULEMAP` variable. The important part of `generate()` is: {% raw %} ```python From ea9161077b20c9e91ff9b471df956ad99ac6cdd8 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Thu, 13 Aug 2026 16:35:45 +0200 Subject: [PATCH 22/27] use xcode --- ...-With-Conan-Managed-Dependencies.markdown} | 106 ++++++++++-------- .../{2026-08-19 => 2026-09-02}/summer.png | Bin 2 files changed, 57 insertions(+), 49 deletions(-) rename _posts/{2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown => 2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown} (71%) rename assets/post_images/{2026-08-19 => 2026-09-02}/summer.png (100%) diff --git a/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown similarity index 71% rename from _posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown rename to _posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 8af989c0..944b09a4 100644 --- a/_posts/2026-08-19-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -4,7 +4,7 @@ comments: false title: "Using an Unmodified C++ Package Directly from Swift" description: "Swift can call supported C++ APIs without a hand-written wrapper. We use Conan to supply an ordinary LunaSVG package and connect its headers, binary, and build settings to Swift." meta_title: "Using an Unmodified C++ Package Directly from Swift with Conan - Conan Blog" -categories: [cpp, conan, swift, macos, cmake] +categories: [cpp, conan, swift, macos, xcode] --- If you've ever wanted to call a C++ library directly from Swift, you've @@ -19,9 +19,9 @@ introducing direct C++ interoperability: Swift can now import C++ headers and call supported C++ APIs with no wrapper layer in between. To try this with an existing package, we use -[LunaSVG](https://conan.io/center/recipes/lunasvg), a C++ SVG renderer from -ConanCenter. The application creates an SVG scene in Swift and asks LunaSVG to -render it to a PNG. +[LunaSVG](https://conan.io/center/recipes/lunasvg), a C++ SVG rendering +library with a package available in ConanCenter. The application creates an +SVG scene in Swift and asks LunaSVG to render it to a PNG. Swift knows how to call supported C++ APIs, while Conan provides LunaSVG, its transitive dependencies, and the information needed to compile and link the @@ -34,7 +34,7 @@ repository](https://github.com/conan-io/examples2/tree/main/examples/languages/s ```text cxx_interop/ ├── conanfile.py -├── CMakeLists.txt +├── demo.xcodeproj/ ├── main.swift ├── ci_test_example.py └── README.md @@ -83,7 +83,7 @@ forwarded to the Clang importer used by the Swift compiler: `-Xcc` passes the following option to the Clang instance embedded in the Swift compiler. With these options, Swift can import `LunaSVGMod` and use the -supported declarations from the header. The CMake configuration shown later +supported declarations from the header. The Xcode configuration shown later adds the same options using the module map generated by Conan. > **Note**: Despite the similar terminology, this is a [Clang @@ -121,29 +121,42 @@ The mapping is visible in the code: - `renderToBitmap` returns a C++ `Bitmap` by value. - `writeToPng` remains an ordinary member-function call. -The `std.string(...)` conversions are explicit for a reason. Swift does not -automatically bridge a dynamic Swift `String` to C++ `std::string`. `CxxStdlib` -exposes supported standard-library types, but creating these C++ strings still -allocates and copies data. Direct interop removes the hand-written wrapper -layer, but it does not remove that cost. The allocation and copy still happen. -They just happen at the call site instead of inside a wrapper function. +The `std.string(...)` conversions are explicit because Swift does not +automatically bridge a dynamic Swift `String` to `std::string`. Constructing +the C++ string allocates and copies the string data. Running this produces the actual LunaSVG output, `summer.png`:
- summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied + summer.png: LunaSVG rendering of the demo scene with the summer stylesheet applied
-## Putting It Together with Conan and CMake +## Putting It All Together -The module map shown above needs the real location of the LunaSVG header. The -consumer recipe obtains it from the package selected by Conan, generates the -file during `conan install`, and passes its location to CMake as the -`LUNASVG_MODULEMAP` variable. The important part of `generate()` is: +Conan feeds Xcode through two generators, +[`XcodeDeps`](https://docs.conan.io/2/reference/tools/apple/xcodedeps.html) +and +[`XcodeToolchain`](https://docs.conan.io/2/reference/tools/apple/xcodetoolchain.html), +which turn the dependency graph into a set of `.xcconfig` files an Xcode +project can use as its build configuration. + +Neither generator knows anything about Swift, though: they only write settings +for the C/C++/Objective-C compilers and the linker. The +`-cxx-interoperability-mode` flag and the module map still have to reach +`swiftc`, through `OTHER_SWIFT_FLAGS`, the build setting Xcode passes straight +to the Swift compiler. `XcodeToolchain` exposes `extra_xcconfig`, a plain dict +of build settings to add to the `.xcconfig` file it generates. The consumer +recipe's `generate()` sets it alongside the module map from earlier, while +`layout()` collects everything the generators write into a `generators` folder: {% raw %} ```python +def layout(self): + self.folders.generators = "generators" + def generate(self): + XcodeDeps(self).generate() + include_dir = self.dependencies["lunasvg"].cpp_info.includedir header = f"{include_dir}/lunasvg/lunasvg.h" @@ -156,51 +169,46 @@ def generate(self): ''') save(self, modulemap_path, modulemap) - tc = CMakeToolchain(self) - tc.variables["LUNASVG_MODULEMAP"] = modulemap_path + cppstd = cppstd_flag(self) + + tc = XcodeToolchain(self) + tc.extra_xcconfig["OTHER_SWIFT_FLAGS"] = ( + f'$(inherited) -cxx-interoperability-mode=default ' + f'-Xcc {cppstd} -Xcc -fmodule-map-file="{modulemap_path}"' + ) tc.generate() ``` {% endraw %} -Then `CMakeLists.txt` links lunasvg the normal way: +`cppstd_flag` turns the profile's `compiler.cppstd` into the matching `-std=` +flag, so the headers are parsed with the same standard as the rest of the +build. `$(inherited)` keeps any Swift flags the project already defines. +`extra_xcconfig` needs Conan 2.32 or newer. -```cmake -cmake_minimum_required(VERSION 3.23) -project(swift_cpp_demo LANGUAGES CXX Swift) +The Xcode project uses `generators/conan_config.xcconfig` as the Base +Configuration for its Release configuration, which makes everything Conan +generates — include paths, linker options, and the Swift flags above — +available to the target. -find_package(lunasvg REQUIRED) +Install the dependency, then open the project: -add_executable(demo main.swift) -target_link_libraries(demo PRIVATE lunasvg::lunasvg) - -target_compile_options(demo PRIVATE - "$<$:-cxx-interoperability-mode=default>" - "$<$:SHELL:-Xcc -std=c++${CMAKE_CXX_STANDARD}>" - "$<$:SHELL:-Xcc -fmodule-map-file=${LUNASVG_MODULEMAP}>" -) +```bash +conan install . --build=missing +open demo.xcodeproj ``` -The `-cxx-interoperability-mode` option enables C++ interoperability and the -module-map option gives Clang the module map it needs to parse the header. -`CMakeToolchain` derives `CMAKE_CXX_STANDARD` from the consumer profile's -`compiler.cppstd`. Passing that value to Swift's embedded Clang via `-Xcc` -ensures that LunaSVG's headers are parsed using the requested C++ standard. - -On macOS, with a recent Swift toolchain, CMake 3.23 or newer, and Ninja, the -complete example can be built and run with: +From there it is a normal Xcode project: press Run, and Swift calls into +LunaSVG. The same build also works from the command line: ```bash -conan install . --build=missing \ - -c tools.cmake.cmaketoolchain:generator=Ninja -cmake --preset conan-release -cmake --build --preset conan-release -./build/Release/demo +xcodebuild -project demo.xcodeproj -scheme demo -configuration Release \ + -derivedDataPath build build +./build/Build/Products/Release/demo ``` -`conan install` selects a package for the active profile or builds one when -needed. Running the executable writes `summer.png` to the working directory. +Running the executable writes `summer.png` to the working directory. -Together, Conan, CMake, and Swift's C++ interoperability make an unmodified +Together, Conan, Xcode, and Swift's C++ interoperability make an unmodified ConanCenter package directly callable from Swift, without a wrapper library. Direct access, however, does not remove the usual binary compatibility requirements of calling C++ code. diff --git a/assets/post_images/2026-08-19/summer.png b/assets/post_images/2026-09-02/summer.png similarity index 100% rename from assets/post_images/2026-08-19/summer.png rename to assets/post_images/2026-09-02/summer.png From d0602b89c082127198aaeae81034fc9c28968cc7 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Thu, 13 Aug 2026 16:41:39 +0200 Subject: [PATCH 23/27] wip --- ...-Interop-With-Conan-Managed-Dependencies.markdown | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 944b09a4..84cd912d 100644 --- a/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -20,8 +20,8 @@ call supported C++ APIs with no wrapper layer in between. To try this with an existing package, we use [LunaSVG](https://conan.io/center/recipes/lunasvg), a C++ SVG rendering -library with a package available in ConanCenter. The application creates an -SVG scene in Swift and asks LunaSVG to render it to a PNG. +library packaged in ConanCenter. The application creates an SVG scene in +Swift and asks LunaSVG to render it to a PNG. Swift knows how to call supported C++ APIs, while Conan provides LunaSVG, its transitive dependencies, and the information needed to compile and link the @@ -140,9 +140,9 @@ and which turn the dependency graph into a set of `.xcconfig` files an Xcode project can use as its build configuration. -Neither generator knows anything about Swift, though: they only write settings -for the C/C++/Objective-C compilers and the linker. The -`-cxx-interoperability-mode` flag and the module map still have to reach +The generators provide the dependency and C++ toolchain settings, but they do +not add the Swift-specific interoperability options required by this target. +The `-cxx-interoperability-mode` flag and the module map still have to reach `swiftc`, through `OTHER_SWIFT_FLAGS`, the build setting Xcode passes straight to the Swift compiler. `XcodeToolchain` exposes `extra_xcconfig`, a plain dict of build settings to add to the `.xcconfig` file it generates. The consumer @@ -193,7 +193,7 @@ available to the target. Install the dependency, then open the project: ```bash -conan install . --build=missing +conan install . -s build_type=Release --build=missing open demo.xcodeproj ``` From 4156a95b4972ffec0618504046d853979d157c37 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Thu, 13 Aug 2026 17:34:05 +0200 Subject: [PATCH 24/27] wip --- ...wift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 84cd912d..32af46c8 100644 --- a/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -239,6 +239,11 @@ lifetime rules, a small wrapper can still provide a cleaner boundary. The difference is that it is now a design choice rather than a requirement for every C++ library. +> **Note:** SwiftPM is a natural choice when the C++ dependency is packaged for +> SwiftPM. In this example, however, LunaSVG comes from ConanCenter, so Conan +> obtains the library and its dependencies and provides the build information +> Xcode needs to use them from Swift. + Try the [complete example](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop) and consult the official [Swift C++ interoperability From d18143498c4ebadc5f4ab08b7e83f3721982b34d Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Fri, 14 Aug 2026 07:53:40 +0200 Subject: [PATCH 25/27] better note --- ...p-Interop-With-Conan-Managed-Dependencies.markdown | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 32af46c8..7fd94608 100644 --- a/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -239,10 +239,13 @@ lifetime rules, a small wrapper can still provide a cleaner boundary. The difference is that it is now a design choice rather than a requirement for every C++ library. -> **Note:** SwiftPM is a natural choice when the C++ dependency is packaged for -> SwiftPM. In this example, however, LunaSVG comes from ConanCenter, so Conan -> obtains the library and its dependencies and provides the build information -> Xcode needs to use them from Swift. +> **Note**: SwiftPM can also distribute C++ libraries for use through Swift's +> C++ interoperability and is a convenient choice when a suitable package +> already exists. Here, Conan consumes LunaSVG and its transitive dependencies +> from ConanCenter without requiring the C++ dependency graph to be repackaged +> for SwiftPM. The Conan recipes reuse the libraries' upstream build systems +> rather than rewriting their configuration and platform logic in +> `Package.swift`. Try the [complete example](https://github.com/conan-io/examples2/tree/main/examples/languages/swift/cxx_interop) From b89a37e5f8ccf94f42d547d1ec448b3556be2473 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Mon, 17 Aug 2026 10:27:26 +0200 Subject: [PATCH 26/27] wip --- ...ift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index 7fd94608..ae88abfa 100644 --- a/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -144,7 +144,7 @@ The generators provide the dependency and C++ toolchain settings, but they do not add the Swift-specific interoperability options required by this target. The `-cxx-interoperability-mode` flag and the module map still have to reach `swiftc`, through `OTHER_SWIFT_FLAGS`, the build setting Xcode passes straight -to the Swift compiler. `XcodeToolchain` exposes `extra_xcconfig`, a plain dict +to the Swift compiler. `XcodeToolchain` exposes `build_settings`, a plain dict of build settings to add to the `.xcconfig` file it generates. The consumer recipe's `generate()` sets it alongside the module map from earlier, while `layout()` collects everything the generators write into a `generators` folder: @@ -172,7 +172,7 @@ def generate(self): cppstd = cppstd_flag(self) tc = XcodeToolchain(self) - tc.extra_xcconfig["OTHER_SWIFT_FLAGS"] = ( + tc.build_settings["OTHER_SWIFT_FLAGS"] = ( f'$(inherited) -cxx-interoperability-mode=default ' f'-Xcc {cppstd} -Xcc -fmodule-map-file="{modulemap_path}"' ) @@ -183,7 +183,7 @@ def generate(self): `cppstd_flag` turns the profile's `compiler.cppstd` into the matching `-std=` flag, so the headers are parsed with the same standard as the rest of the build. `$(inherited)` keeps any Swift flags the project already defines. -`extra_xcconfig` needs Conan 2.32 or newer. +`build_settings` needs Conan 2.32 or newer. The Xcode project uses `generators/conan_config.xcconfig` as the Base Configuration for its Release configuration, which makes everything Conan From 481c8b395dc71be511d60cd1ea7afbcbdfc454ac Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Wed, 19 Aug 2026 12:37:56 +0200 Subject: [PATCH 27/27] update title --- ...Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown index ae88abfa..32aeb65a 100644 --- a/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown +++ b/_posts/2026-09-02-Swift-Cpp-Interop-With-Conan-Managed-Dependencies.markdown @@ -1,9 +1,9 @@ --- layout: post comments: false -title: "Using an Unmodified C++ Package Directly from Swift" +title: "Calling Unmodified ConanCenter Packages from Swift via C++ Interoperability" description: "Swift can call supported C++ APIs without a hand-written wrapper. We use Conan to supply an ordinary LunaSVG package and connect its headers, binary, and build settings to Swift." -meta_title: "Using an Unmodified C++ Package Directly from Swift with Conan - Conan Blog" +meta_title: "Calling Unmodified ConanCenter Packages from Swift via C++ Interoperability - Conan Blog" categories: [cpp, conan, swift, macos, xcode] ---