diff --git a/.gitignore b/.gitignore index e1b6d3fc..e84fda54 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ venv/ .pydevproject .settings/* .ropeproject/* +**/xcuserdata/ .tox CMakeUserPresets.json @@ -69,6 +70,7 @@ tutorial/versioning/revisions/intro/chat/* tutorial/versioning/revisions/intro/hello/* examples/libraries/tensorflow-lite/pose-estimation/pose-estimation examples/libraries/imgui/introduction/bindings +examples/languages/swift/cxx_interop/summer.png # Bazel files bazel-* diff --git a/README.md b/README.md index 4dd8739e..bd0c99f8 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ Sources for the [examples section](https://docs.conan.io/2/examples.html) of the ### [Libraries examples](examples/libraries) +### [Language interoperability examples](examples/languages) + ### [Graph examples](examples/graph) ### [Security examples](examples/security) \ No newline at end of file diff --git a/examples/languages/README.md b/examples/languages/README.md new file mode 100644 index 00000000..dc8f3713 --- /dev/null +++ b/examples/languages/README.md @@ -0,0 +1,5 @@ +## Language interoperability examples + +### [Swift / C++ interop](swift/cxx_interop) + +- Consume plain, Swift-unaware ConanCenter C++ packages directly from a Swift app, using Swift's C++ interoperability mode. diff --git a/examples/languages/swift/cxx_interop/README.md b/examples/languages/swift/cxx_interop/README.md new file mode 100644 index 00000000..a80c1a57 --- /dev/null +++ b/examples/languages/swift/cxx_interop/README.md @@ -0,0 +1,43 @@ +# Swift / C++ interop with Conan-managed dependencies + +A Swift app that consumes a plain, Swift-unaware ConanCenter package -- +[lunasvg](https://conan.io/center/recipes/lunasvg) -- directly, via +[Swift's C++ interoperability mode](https://www.swift.org/documentation/cxx-interop/). +Swift builds an SVG scene as a string, hands it to lunasvg's C++ `Document` +and `Bitmap` classes to parse and rasterize, and writes the result to a PNG +file -- no C wrapper library needed. + +Since lunasvg ships no Swift module map, `conanfile.py`'s `generate()` writes +a small [Clang module map](https://clang.llvm.org/docs/Modules.html) pointing +at its real installed header (read from `cpp_info`), and sets `OTHER_SWIFT_FLAGS` +through `XcodeToolchain.build_settings` to pass that to `swiftc` via +`-Xcc -fmodule-map-file=...` together with `-cxx-interoperability-mode=default`. +`demo.xcodeproj` is a plain Xcode project whose Release configuration is based +on the `.xcconfig` files that Conan's `XcodeDeps`/`XcodeToolchain` generators +write. + +## Requirements + +- macOS with Xcode (`swiftc`, `xcodebuild`), Swift 5.9+. +- Conan 2.32 or newer (`XcodeToolchain.build_settings`). + +## Build and run + +```bash +git clone https://github.com/conan-io/examples2.git +cd examples2/examples/languages/swift/cxx_interop + +conan install . -s build_type=Release --build=missing +open demo.xcodeproj +``` + +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 +xcodebuild -project demo.xcodeproj -scheme demo -configuration Release \ + -derivedDataPath build build +./build/Build/Products/Release/demo +``` + +The program renders the SVG and writes `summer.png` to the working directory. diff --git a/examples/languages/swift/cxx_interop/ci_test_example.py b/examples/languages/swift/cxx_interop/ci_test_example.py new file mode 100644 index 00000000..cdf3b02a --- /dev/null +++ b/examples/languages/swift/cxx_interop/ci_test_example.py @@ -0,0 +1,17 @@ +import platform + +from conan.tools.scm import Version + +from test.examples_tools import run + +print("Swift C++ interop: consuming a plain ConanCenter package from Swift") + +conan_version = run("conan --version").split()[-1] + +if platform.system() != "Darwin" or Version(conan_version).main < Version("2.32").main: + print("WARNING: Skipping Swift interop example, requires macOS with swiftc and Conan >= 2.32") +else: + run("conan install . -s build_type=Release --build=missing") + run("xcodebuild -project demo.xcodeproj -scheme demo -configuration Release " + "-derivedDataPath build build") + run("./build/Build/Products/Release/demo") diff --git a/examples/languages/swift/cxx_interop/conanfile.py b/examples/languages/swift/cxx_interop/conanfile.py new file mode 100644 index 00000000..4b3ba70f --- /dev/null +++ b/examples/languages/swift/cxx_interop/conanfile.py @@ -0,0 +1,40 @@ +import os +import textwrap +from conan import ConanFile +from conan.tools.apple import XcodeDeps, XcodeToolchain +from conan.tools.build import cppstd_flag +from conan.tools.files import save + + +class SwiftCppDemo(ConanFile): + settings = "os", "arch", "compiler", "build_type" + + def layout(self): + self.folders.generators = "generators" + + def requirements(self): + self.requires("lunasvg/3.5.0") + + def generate(self): + XcodeDeps(self).generate() + + 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) + + cppstd = cppstd_flag(self) + + tc = XcodeToolchain(self) + tc.build_settings["OTHER_SWIFT_FLAGS"] = ( + f'$(inherited) -cxx-interoperability-mode=default ' + f'-Xcc {cppstd} -Xcc -fmodule-map-file="{modulemap_path}"' + ) + tc.generate() diff --git a/examples/languages/swift/cxx_interop/demo.xcodeproj/project.pbxproj b/examples/languages/swift/cxx_interop/demo.xcodeproj/project.pbxproj new file mode 100644 index 00000000..a9884292 --- /dev/null +++ b/examples/languages/swift/cxx_interop/demo.xcodeproj/project.pbxproj @@ -0,0 +1,198 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 134B726B92DC37149DD8FB1A /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08895F9843B6FA00C49130BF /* main.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0507141398EC23BB262BA813 /* demo */ = {isa = PBXFileReference; includeInIndex = 0; path = demo; sourceTree = BUILT_PRODUCTS_DIR; }; + 08895F9843B6FA00C49130BF /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + A080ABC67B6FC4B3732B7606 /* conan_config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = conan_config.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + 253DD428CCB2178AB212D9C6 = { + isa = PBXGroup; + children = ( + 08895F9843B6FA00C49130BF /* main.swift */, + 5D676B908F57C1AF87D8AC72 /* generators */, + CBA6EB5BDDD3CD851F85C932 /* Products */, + ); + sourceTree = ""; + }; + 5D676B908F57C1AF87D8AC72 /* generators */ = { + isa = PBXGroup; + children = ( + A080ABC67B6FC4B3732B7606 /* conan_config.xcconfig */, + ); + path = generators; + sourceTree = ""; + }; + CBA6EB5BDDD3CD851F85C932 /* Products */ = { + isa = PBXGroup; + children = ( + 0507141398EC23BB262BA813 /* demo */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 25BF8E1F96134B86B979D8A6 /* demo */ = { + isa = PBXNativeTarget; + buildConfigurationList = EED75AB90447BE0300554485 /* Build configuration list for PBXNativeTarget "demo" */; + buildPhases = ( + 5227B068F7CA093BC3C8FF22 /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = demo; + packageProductDependencies = ( + ); + productName = demo; + productReference = 0507141398EC23BB262BA813 /* demo */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + EE8D0A5E600AE5890BCA0377 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + TargetAttributes = { + }; + }; + buildConfigurationList = 069786E56D7D9BA850EF6E04 /* Build configuration list for PBXProject "demo" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 253DD428CCB2178AB212D9C6; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = CBA6EB5BDDD3CD851F85C932 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 25BF8E1F96134B86B979D8A6 /* demo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 5227B068F7CA093BC3C8FF22 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 134B726B92DC37149DD8FB1A /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 13750F1439DABFE631C7C9E4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A080ABC67B6FC4B3732B7606 /* conan_config.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.conan.examples.demo; + SDKROOT = macosx; + }; + name = Release; + }; + CE5C8F26EC586CE7B2176121 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 069786E56D7D9BA850EF6E04 /* Build configuration list for PBXProject "demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CE5C8F26EC586CE7B2176121 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + EED75AB90447BE0300554485 /* Build configuration list for PBXNativeTarget "demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13750F1439DABFE631C7C9E4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = EE8D0A5E600AE5890BCA0377 /* Project object */; +} diff --git a/examples/languages/swift/cxx_interop/demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/languages/swift/cxx_interop/demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/examples/languages/swift/cxx_interop/demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/languages/swift/cxx_interop/main.swift b/examples/languages/swift/cxx_interop/main.swift new file mode 100644 index 00000000..3fc28558 --- /dev/null +++ b/examples/languages/swift/cxx_interop/main.swift @@ -0,0 +1,32 @@ +import CxxStdlib +import LunaSVGMod + +// lunasvg has no styling of its own; this stylesheet colors the markup below. +let svg = """ + + + + + + + + + + Swift + C++ + Conan + + + + + + +""" + +let css = ".sky{fill:#8ECBEB} .hills{fill:#8FA89B} .ground{fill:#8FC77E} .cloud{fill:#FFFFFF} .title{fill:#3B4A40}" + +let document = lunasvg.Document.loadFromData(std.string(svg)) +document.pointee.applyStyleSheet(std.string(css)) + +let bitmap = document.pointee.renderToBitmap() +_ = bitmap.writeToPng(std.string("summer.png")) + +print("Generated summer.png")