diff --git a/.bazelversion b/.bazelversion index 643916c03f1..ae9a76b9249 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -7.3.1 +8.0.0 diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 97b90961f82..9293f241065 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -37,6 +37,7 @@ jobs: - name: Get all Linux files that have changed if: github.event_name != 'workflow_dispatch' id: changed-files + uses: tj-actions/changed-files@v45 with: files_yaml_from_source_file: .github/changed-files.yml @@ -52,7 +53,7 @@ jobs: strategy: fail-fast: true matrix: - renderer: [legacy, drawable, vulkan] + renderer: [legacy, drawable, vulkan, drawable-rust] runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -91,6 +92,11 @@ jobs: - if: matrix.renderer == 'drawable' run: echo renderer_flag_cmake=-DMLN_DRAWABLE_RENDERER=ON >> "$GITHUB_ENV" + - if: matrix.renderer == 'drawable-rust' + run: | + echo "renderer_flag_cmake=-DMLN_DRAWABLE_RENDERER=ON -DMLN_USE_RUST=ON" >> "$GITHUB_ENV" + cargo install cxxbridge-cmd + - if: matrix.renderer == 'legacy' run: echo renderer_flag_cmake=-DMLN_LEGACY_RENDERER=ON >> "$GITHUB_ENV" @@ -158,7 +164,12 @@ jobs: - name: Run render test id: render_test - run: xvfb-run -a build/mbgl-render-test-runner --manifestPath=metrics/linux-${{ matrix.renderer }}.json + run: | + renderer="${{ matrix.renderer }}" + if [[ "$renderer" == *-rust ]]; then + renderer=${renderer%-rust} + fi + xvfb-run -a build/mbgl-render-test-runner --manifestPath=metrics/linux-"$renderer".json - name: Upload render test result if: always() && steps.render_test.outcome == 'failure' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5482b7ab088..25392ab37d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,6 +24,13 @@ repos: hooks: - id: swiftformat args: [--swiftversion, "5.8"] +- repo: local + hooks: + - id: rustfmt + name: rustfmt + entry: bash -c 'cd rustutils && cargo fmt' -- + language: rust + types: [rust] ci: # sometimes fails https://github.com/keith/pre-commit-buildifier/issues/13 skip: [buildifier] diff --git a/BUILD.bazel b/BUILD.bazel index 80c7ccf3411..9b387f5a613 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,5 +1,5 @@ load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_library", "js_run_binary") -load("@bazel_skylib//rules:common_settings.bzl", "string_flag") +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_flag") load("@npm//:defs.bzl", "npm_link_all_packages") load( "//bazel:core.bzl", @@ -124,7 +124,6 @@ cc_library( ":mbgl-core-generated-private-artifacts", ":mbgl-core-generated-public-artifacts", "//vendor:boost", - "//vendor:csscolorparser", "//vendor:earcut.hpp", "//vendor:eternal", "//vendor:mapbox-base", @@ -149,6 +148,13 @@ cc_library( "//vendor:metal-cpp", ], "//conditions:default": [], + }) + select({ + ":rust": [ + "//rustutils:rustutilslib", + ], + "//conditions:default": [ + "//vendor:csscolorparser", + ], }), ) @@ -195,6 +201,19 @@ config_setting( }, ) +bool_flag( + name = "use_rust", + build_setting_default = False, + visibility = ["//visibility:public"], +) + +config_setting( + name = "rust", + flag_values = { + "//:use_rust": "true", + }, +) + exports_files( [ "LICENSE.md", diff --git a/CMakeLists.txt b/CMakeLists.txt index 835aa2b7eec..3c6b1da70de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ option(MLN_LEGACY_RENDERER "Include the legacy rendering pathway" ON) option(MLN_DRAWABLE_RENDERER "Include the drawable rendering pathway" OFF) option(MLN_USE_UNORDERED_DENSE "Use ankerl dense containers for performance" ON) option(MLN_USE_TRACY "Enable Tracy instrumentation" OFF) +option(MLN_USE_RUST "Use components in Rust" OFF) if (MLN_WITH_CLANG_TIDY) find_program(CLANG_TIDY_COMMAND NAMES clang-tidy) @@ -940,7 +941,7 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/util/bounding_volumes.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/chrono.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/client_options.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/util/color.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/util/color$,.rs.cpp,.cpp> ${PROJECT_SOURCE_DIR}/src/mbgl/util/constants.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/convert.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/event.cpp @@ -1458,6 +1459,10 @@ include(${PROJECT_SOURCE_DIR}/vendor/vector-tile.cmake) include(${PROJECT_SOURCE_DIR}/vendor/wagyu.cmake) include(${PROJECT_SOURCE_DIR}/vendor/metal-cpp.cmake) +if(MLN_USE_RUST) +include(${PROJECT_SOURCE_DIR}/rustutils/rustutils.cmake) +endif() + target_link_libraries( mbgl-core PRIVATE @@ -1468,7 +1473,6 @@ target_link_libraries( Mapbox::Base::cheap-ruler-cpp mbgl-compiler-options mbgl-vendor-boost - mbgl-vendor-csscolorparser mbgl-vendor-earcut.hpp mbgl-vendor-eternal mbgl-vendor-parsedate @@ -1479,6 +1483,7 @@ target_link_libraries( mbgl-vendor-vector-tile mbgl-vendor-wagyu $<$:mbgl-vendor-metal-cpp> + $,mbgl-rustutils,mbgl-vendor-csscolorparser> PUBLIC Mapbox::Base Mapbox::Base::Extras::expected-lite @@ -1490,9 +1495,8 @@ target_link_libraries( unordered_dense ) -export(TARGETS +set(EXPORT_TARGETS mbgl-core - mapbox-base mapbox-base-cheap-ruler-cpp mapbox-base-extras-expected-lite @@ -1508,7 +1512,6 @@ export(TARGETS mapbox-base-variant mbgl-compiler-options mbgl-vendor-boost - mbgl-vendor-csscolorparser mbgl-vendor-earcut.hpp mbgl-vendor-eternal mbgl-vendor-parsedate @@ -1520,10 +1523,16 @@ export(TARGETS mbgl-vendor-wagyu mbgl-vendor-metal-cpp unordered_dense - - FILE MapboxCoreTargets.cmake ) +if(MLN_USE_RUST) + list(APPEND EXPORT_TARGETS mbgl-rustutils rustutils) +else() + list(APPEND EXPORT_TARGETS mbgl-vendor-csscolorparser) +endif() + +export(TARGETS ${EXPORT_TARGETS} FILE MapboxCoreTargets.cmake) + if(MLN_WITH_VULKAN) include(${PROJECT_SOURCE_DIR}/vendor/vulkan.cmake) diff --git a/MODULE.bazel b/MODULE.bazel index 9684c906ac8..e71dc40ae56 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,6 +1,6 @@ module(name = "maplibre") -bazel_dep(name = "apple_support", version = "1.17.0", repo_name = "build_bazel_apple_support") +bazel_dep(name = "apple_support", version = "1.17.1", repo_name = "build_bazel_apple_support") bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "platforms", version = "0.0.10") bazel_dep(name = "rules_apple", version = "3.16.1", repo_name = "build_bazel_rules_apple") @@ -59,4 +59,35 @@ darwin_config = use_repo_rule("//platform/darwin:bazel/darwin_config_repository_ darwin_config( name = "darwin_config", -) \ No newline at end of file +) + +bazel_dep(name = "rules_rust", version = "0.56.0") +bazel_dep(name = "cxx.rs", version = "1.0.136") +git_override( + module_name = "cxx.rs", + commit = "d54e44698c3fa5833a861cb3ae502533b92f2f57", + remote = "https://github.com/dtolnay/cxx.git", +) + +rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") +rust.toolchain( + edition = "2021", + extra_target_triples = [ + "aarch64-apple-ios-sim", + "x86_64-apple-ios", + "aarch64-apple-ios", + "aarch64-apple-darwin", + "x86_64-apple-darwin", + ], +) +use_repo(rust, "rust_toolchains") + +register_toolchains("@rust_toolchains//:all") + +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") +crate.from_cargo( + name = "crates", + cargo_lockfile = "//rustutils:Cargo.lock", + manifests = ["//rustutils:Cargo.toml"], +) +use_repo(crate, "crates") diff --git a/bazel/core.bzl b/bazel/core.bzl index 6d7dbca391d..75a30b5b152 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -584,7 +584,6 @@ MLN_CORE_SOURCE = [ "src/mbgl/util/bounding_volumes.cpp", "src/mbgl/util/chrono.cpp", "src/mbgl/util/client_options.cpp", - "src/mbgl/util/color.cpp", "src/mbgl/util/constants.cpp", "src/mbgl/util/convert.cpp", "src/mbgl/util/event.cpp", @@ -652,7 +651,14 @@ MLN_CORE_SOURCE = [ "src/mbgl/util/version.cpp", "src/mbgl/util/version.hpp", "src/mbgl/util/work_request.cpp", -] +] + select({ + "//:rust": [ + "src/mbgl/util/color.rs.cpp", + ], + "//conditions:default": [ + "src/mbgl/util/color.cpp", + ], +}) MLN_CORE_HEADERS = [ "include/mbgl/gfx/context.hpp", diff --git a/docker/Dockerfile b/docker/Dockerfile index 0e55ad4718c..58a4e6ab55b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -23,12 +23,16 @@ RUN apt-get update \ ccache \ ninja-build \ pkg-config \ + python3 \ + python3-pip \ + python-is-python3 \ clang-tidy \ && : # end of the RUN cmd - easier to keep a colon at the end of the list, than to keep the backslashes in check # This could also be `.../releases/latest/download/bazelisk-linux-amd64` for the latest version, but for predictability better hardcode it # Detect if current CPU is x64 or ARM64 and download the appropriate binary -RUN if [ "$(uname -m)" = "aarch64" ]; then \ +RUN echo "Download and install Bazel" \ + && if [ "$(uname -m)" = "aarch64" ]; then \ curl -fsSL https://github.com/bazelbuild/bazelisk/releases/download/v1.20.0/bazelisk-linux-arm64 -o /usr/local/bin/bazel ;\ else \ curl -fsSL https://github.com/bazelbuild/bazelisk/releases/download/v1.20.0/bazelisk-linux-amd64 -o /usr/local/bin/bazel ;\ @@ -36,7 +40,6 @@ RUN if [ "$(uname -m)" = "aarch64" ]; then \ && chmod +x /usr/local/bin/bazel \ && : -WORKDIR /app ARG USERNAME=user ARG USER_UID=1000 @@ -50,11 +53,21 @@ RUN groupadd --force --gid $USER_GID $USERNAME \ && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ && chmod 0440 /etc/sudoers.d/$USERNAME +# This allows users to `docker run` without specifying -u and -g +USER $USERNAME + +RUN pip install pre-commit + +ENV RUSTUP_HOME=/home/$USERNAME/.cache/.rustup \ + CARGO_HOME=/home/$USERNAME/.cache/.cargo \ + PATH=/home/$USERNAME/.cache/.cargo/bin:$PATH + +# As the very last step, copy the startup script +USER root COPY startup.sh /usr/local/bin/startup.sh RUN chmod +x /usr/local/bin/startup.sh - -# This allows users to `docker run` without specifying -u and -g USER $USERNAME +WORKDIR /app ENTRYPOINT ["/usr/local/bin/startup.sh"] CMD ["bash"] diff --git a/docker/startup.sh b/docker/startup.sh index 6bb1f9a17e6..3a60b8e63e0 100644 --- a/docker/startup.sh +++ b/docker/startup.sh @@ -1,13 +1,51 @@ #!/bin/sh -if [ ! -d /app/.github ] || [ ! -d /home/user/.cache ]; then +if [ ! -d /app/.github ] || [ ! -d ~/.cache ]; then echo " " echo "ERROR: Docker container was not started properly." echo " From the root of this repo, run the following command." echo " You may add any command to perform in the container at the end of this command." echo " " - echo ' docker run --rm -it -v "$PWD:/app/" -v "$PWD/docker/.cache:/home/user/.cache" maplibre-native-image' + echo ' docker run --rm -it -v "$PWD:/app/" -v "$PWD/docker/.cache:/home/'"$USERNAME"'/.cache" maplibre-native-image' exit 1 fi +export PATH="$PATH:~/.local/bin/" + + +# Work in progress: install and configure Swift and pre-commit +# Detect if current CPU is x64 or ARM64 and download the appropriate binary +#RUN echo "Download and install SWIFT" \ +# && if [ "$(uname -m)" = "aarch64" ]; then \ +# curl -fsSL https://download.swift.org/swift-5.10.1-release/ubuntu2204-aarch64/swift-5.10.1-RELEASE/swift-5.10.1-RELEASE-ubuntu22.04-aarch64.tar.gz \ +# -o /tmp/swift.tar.gz ;\ +# else \ +# curl -fsSL https://download.swift.org/swift-5.10.1-release/ubuntu2204/swift-5.10.1-RELEASE/swift-5.10.1-RELEASE-ubuntu22.04.tar.gz \ +# -o /tmp/swift.tar.gz ;\ +# fi \ +# && tar -xzf /tmp/swift.tar.gz -C / --strip-components=1 \ +# && rm /tmp/swift.tar.gz \ +# && : +#if [ ! -f "/app/.git/hooks/pre-commit" ]; then +# echo "Configuring pre-commit git hooks by creating a .git/hooks/pre-commit file..." +# ~/.local/bin/pre-commit install +#fi + + + +if [ ! -f "$CARGO_HOME/env" ]; then + echo "Downloading and installing Rust..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal +fi +. "$CARGO_HOME/env" + + + +if ! command -v cxxbridge > /dev/null; then + echo "Installing cxxbridge..." + cargo install cxxbridge-cmd +fi + + + exec "$@" diff --git a/docs/mdbook/src/SUMMARY.md b/docs/mdbook/src/SUMMARY.md index d07b042abd6..951078c58f4 100644 --- a/docs/mdbook/src/SUMMARY.md +++ b/docs/mdbook/src/SUMMARY.md @@ -22,3 +22,5 @@ - [Profiling applications that use MapLibre Native](./profiling/README.md) - [Tracy profiling](./profiling/tracy-profiling.md) + +- [Rust](./rust.md) diff --git a/docs/mdbook/src/rust.md b/docs/mdbook/src/rust.md new file mode 100644 index 00000000000..ff2a05321db --- /dev/null +++ b/docs/mdbook/src/rust.md @@ -0,0 +1,67 @@ +# Rust + +We have added experimental support for intergrating Rust code into the source tree. + +## Rust Bridge + +The Rust bridge lives in the root `rustutils` directory. + +We use [CXX](https://cxx.rs/) to allow interop between Rust and C++. + +## Building + +### CMake + +When building with CMake, need to have the correct Rust toolchain(s) installed. See [Install Rust](https://www.rust-lang.org/tools/install) to install Rust. + +You can use `rustup` to manage toolchains. Which toolchain you needs depends on your host platform and for what platform you are trying to build. If your host and target platform are the same, you probably have the correct toolchain installed after installing Rust. For example when building for **Android** and building on a **x84 Linux** host you would use the following command: + +``` +rustup target add --toolchain stable-x86_64-unknown-linux-gnu aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android +``` + +See [Platform Support](https://doc.rust-lang.org/nightly/rustc/platform-support.html) in the Rust documentation for more details. You will get a descriptive error message when the correct toolchain is not available, so we don't list all possible combinations here. + +You also need to have cxxbridge installed: + +``` +cargo install cxxbridge-cmd +``` + +Set `-DMLN_USE_RUST=ON` when generating a configuration with CMake. + +### Bazel + +Pass the `--//:use_rust` flag to Bazel commands. + +Note that when [generating an Xcode project](./ios/README.md) you should not pass this option to Bazel directly, but as follows: + +``` +bazel run //platform/ios:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=metal --//:use_rust" +``` + +## Creating a new Module + +To create a new module: + +1. Add a new source file to `rustutils/src/example.rs`. +2. Implement it, see the [CXX documentation](https://cxx.rs/index.html) or see `rustutils/src/color.rs` for an example. +3. Create a C++ source file that will use the generated C++ header. See `src/mbgl/util/color.rs.cpp` for an example. Import the generated header with + ``` + #include + ``` +4. Conditionally include either the `*.rs.cpp` file or the `*.cpp` file it replaces in CMake and Bazel. Here is what it looks like for CMake: + ``` + ${PROJECT_SOURCE_DIR}/src/mbgl/util/color$,.rs.cpp,.cpp> + ``` + And here for Bazel: + ``` + select({ + "//:rust": [ + "src/mbgl/util/color.rs.cpp", + ], + "//conditions:default": [ + "src/mbgl/util/color.cpp", + ], + }) + ``` \ No newline at end of file diff --git a/rustutils/.gitignore b/rustutils/.gitignore new file mode 100644 index 00000000000..ea8c4bf7f35 --- /dev/null +++ b/rustutils/.gitignore @@ -0,0 +1 @@ +/target diff --git a/rustutils/BUILD.bazel b/rustutils/BUILD.bazel new file mode 100644 index 00000000000..9c8480dcdd5 --- /dev/null +++ b/rustutils/BUILD.bazel @@ -0,0 +1,46 @@ +load("@rules_rust//rust:defs.bzl", "rust_static_library") + +genrule( + name = "cpp_bindings", + srcs = [ + "src/color.rs", + ], + outs = [ + "cpp/include/rustutils/color.hpp", + "cpp/src/color.rs.cpp", + ], + cmd = "CXXBRIDGE_CMD=$(location @cxx.rs//:codegen) ./$(location :generate.sh) $(@D) $(SRCS)", + # Using the cxxbridge binary built by Bazel + tools = [ + ":generate.sh", + "@cxx.rs//:codegen", + ], +) + +rust_static_library( + name = "rustutils", + srcs = [ + "src/color.rs", + "src/lib.rs", + ], + crate_name = "rustutils", + visibility = ["//visibility:public"], + deps = [ + "@crates//:csscolorparser", + "@crates//:cxx", + ], +) + +cc_library( + name = "rustutilslib", + srcs = [ + ":cpp_bindings", + ], + includes = [ + "cpp/include", + ], + visibility = ["//visibility:public"], + deps = [ + ":rustutils", + ], +) diff --git a/rustutils/Cargo.lock b/rustutils/Cargo.lock new file mode 100644 index 00000000000..16003c0398f --- /dev/null +++ b/rustutils/Cargo.lock @@ -0,0 +1,162 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cc" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" + +[[package]] +name = "csscolorparser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +dependencies = [ + "phf", +] + +[[package]] +name = "cxx" +version = "1.0.124" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273dcfd3acd4e1e276af13ed2a43eea7001318823e7a726a6b3ed39b4acc0b82" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.124" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839fcd5e43464614ffaa989eaf1c139ef1f0c51672a1ed08023307fa1b909ccd" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.124" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rustutils" +version = "0.1.0" +dependencies = [ + "csscolorparser", + "cxx", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/rustutils/Cargo.toml b/rustutils/Cargo.toml new file mode 100644 index 00000000000..8d98a0d03c4 --- /dev/null +++ b/rustutils/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "rustutils" +version = "0.1.0" +authors = ["MapLibre contributors"] +description = "Core Rust utilities for MapLibre Native" +edition = "2021" +license = "BSD-2-Clause" +repository = "https://github.com/maplibre/maplibre-native" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +csscolorparser = "0.6.2" +cxx = "1" + +[profile.release] +lto = true +codegen-units = 1 diff --git a/rustutils/generate.sh b/rustutils/generate.sh new file mode 100755 index 00000000000..7b0b3c29e0a --- /dev/null +++ b/rustutils/generate.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Run this script from the repository root +# Install cxxbridge with: +# $ cargo install cxxbridge-cmd + +set -e + +if [ "$#" -lt 2 ]; then + echo "Error: output_dir and at least one input file are required." >&2 + echo "Usage: $0 [input_file2 ...]" >&2 + exit 1 +fi + +# Use CXXBRIDGE_CMD environment variable if set, otherwise fallback to "cxxbridge" +CXXBRIDGE_CMD="${CXXBRIDGE_CMD:-cxxbridge}" + +output_dir="$1" +shift + +for input_file in "$@"; do + if [[ "$input_file" != *.rs ]]; then + echo "Error: All input files must have a .rs extension. Invalid file: $input_file" >&2 + exit 1 + fi +done + +mkdir -p "$output_dir/cpp/include/rustutils" +mkdir -p "$output_dir/cpp/src" + +for input_file in "$@"; do + base_name=$(basename "$input_file" .rs) + "$CXXBRIDGE_CMD" "$input_file" --header > "$output_dir/cpp/include/rustutils/${base_name}.hpp" + "$CXXBRIDGE_CMD" "$input_file" > "$output_dir/cpp/src/${base_name}.rs.cpp" +done \ No newline at end of file diff --git a/rustutils/rustutils.cmake b/rustutils/rustutils.cmake new file mode 100644 index 00000000000..94decf5cd03 --- /dev/null +++ b/rustutils/rustutils.cmake @@ -0,0 +1,63 @@ +# Include guard +if(TARGET rustutils) + return() +endif() + +include(FetchContent) + +FetchContent_Declare( + Corrosion + GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git + GIT_TAG v0.5 # Optionally specify a commit hash, version tag or branch here +) +FetchContent_MakeAvailable(Corrosion) + +corrosion_import_crate(MANIFEST_PATH ${CMAKE_CURRENT_LIST_DIR}/Cargo.toml) + +# Define output directories for generated bindings +set(RUSTUTILS_OUTPUT_DIR "${CMAKE_BINARY_DIR}/rustutils_bindings") +set(RUSTUTILS_INCLUDE_DIR "${RUSTUTILS_OUTPUT_DIR}/cpp/include") +set(RUSTUTILS_SRC_DIR "${RUSTUTILS_OUTPUT_DIR}/cpp/src") + +# Ensure the output directories exist +file(MAKE_DIRECTORY ${RUSTUTILS_INCLUDE_DIR}) +file(MAKE_DIRECTORY ${RUSTUTILS_SRC_DIR}) + +set(RUSTUTILS_RS_FILES + ${CMAKE_CURRENT_LIST_DIR}/src/color.rs +) + +# Initialize variables for generated files +set(RUSTUTILS_GENERATED_SOURCES) +set(RUSTUTILS_GENERATED_HEADERS) + +# Transform .rs to .rs.cpp and .hpp +foreach(rs_file ${RUSTUTILS_RS_FILES}) + get_filename_component(base_name ${rs_file} NAME_WE) + list(APPEND RUSTUTILS_GENERATED_SOURCES "${RUSTUTILS_SRC_DIR}/${base_name}.rs.cpp") + list(APPEND RUSTUTILS_GENERATED_HEADERS "${RUSTUTILS_INCLUDE_DIR}/rustutils/${base_name}.hpp") +endforeach() + +add_custom_command( + OUTPUT ${RUSTUTILS_GENERATED_SOURCES} ${RUSTUTILS_GENERATED_HEADERS} + COMMAND ${CMAKE_COMMAND} -E env + ${CMAKE_CURRENT_LIST_DIR}/generate.sh ${RUSTUTILS_OUTPUT_DIR} ${RUSTUTILS_RS_FILES} + DEPENDS ${CMAKE_CURRENT_LIST_DIR}/generate.sh ${RUSTUTILS_RS_FILES} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + COMMENT "Generating C++ bindings for Rust sources" + VERBATIM +) + +add_custom_target(rustutils_bindings DEPENDS ${RUSTUTILS_GENERATED_SOURCES} ${RUSTUTILS_GENERATED_HEADERS}) + +add_library(mbgl-rustutils STATIC + ${RUSTUTILS_GENERATED_SOURCES} +) + +add_dependencies(mbgl-rustutils rustutils_bindings) + +target_include_directories(mbgl-rustutils PUBLIC + ${RUSTUTILS_INCLUDE_DIR} +) + +target_link_libraries(mbgl-rustutils PUBLIC rustutils) diff --git a/rustutils/src/color.rs b/rustutils/src/color.rs new file mode 100644 index 00000000000..090bb76a878 --- /dev/null +++ b/rustutils/src/color.rs @@ -0,0 +1,35 @@ +use csscolorparser::Color; + +#[cxx::bridge(namespace = "rustutils")] +mod ffi { + struct ParsedColor { + pub success: bool, + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, + } + + extern "Rust" { + fn parse_css_color(css_str: &str) -> ParsedColor; + } +} + +pub fn parse_css_color(css_str: &str) -> ffi::ParsedColor { + css_str + .parse::() + .map(|color| ffi::ParsedColor { + success: true, + r: color.r as f32, + g: color.g as f32, + b: color.b as f32, + a: color.a as f32, + }) + .unwrap_or_else(|_| ffi::ParsedColor { + success: false, + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }) +} diff --git a/rustutils/src/lib.rs b/rustutils/src/lib.rs new file mode 100644 index 00000000000..459f9e1c997 --- /dev/null +++ b/rustutils/src/lib.rs @@ -0,0 +1 @@ +mod color; diff --git a/scripts/license.cmake b/scripts/license.cmake index 7c0b0bd10af..86141540a31 100644 --- a/scripts/license.cmake +++ b/scripts/license.cmake @@ -2,7 +2,7 @@ function(mbgl_generate_license param) # Fake targets or non relevant. - set(BLACKLIST "mbgl-compiler-options") + set(BLACKLIST "mbgl-compiler-options" "mbgl-rustutils") get_target_property(LIBRARIES ${param} LINK_LIBRARIES) list(INSERT LIBRARIES 0 ${param}) diff --git a/src/mbgl/util/color.cpp b/src/mbgl/util/color.cpp index 44fb65f732a..cd4258aa44a 100644 --- a/src/mbgl/util/color.cpp +++ b/src/mbgl/util/color.cpp @@ -6,7 +6,7 @@ namespace mbgl { std::optional Color::parse(const std::string& s) { - auto css_color = CSSColorParser::parse(s); + const auto css_color = CSSColorParser::parse(s); // Premultiply the color. if (css_color) { diff --git a/src/mbgl/util/color.rs.cpp b/src/mbgl/util/color.rs.cpp new file mode 100644 index 00000000000..4dd537015b7 --- /dev/null +++ b/src/mbgl/util/color.rs.cpp @@ -0,0 +1,58 @@ +// This is an interface-compatible file analogous to color.cpp +// which is conditionally compiled when the optional Rust build flag is enabled. +#include + +#include +#include + +#include + +namespace mbgl { + +std::optional Color::parse(const std::string& s) { + const auto css_color = rustutils::parse_css_color(s); + if (css_color.success) { + return {{css_color.r * css_color.a, css_color.g * css_color.a, css_color.b * css_color.a, css_color.a}}; + } else { + return {}; + } +} + +std::string Color::stringify() const { + std::array array = toArray(); + return "rgba(" + util::toString(array[0]) + "," + util::toString(array[1]) + "," + util::toString(array[2]) + "," + + util::toString(array[3]) + ")"; +} + +std::array Color::toArray() const { + if (a == 0) { + return {{0, 0, 0, 0}}; + } else { + return {{ + r * 255 / a, + g * 255 / a, + b * 255 / a, + floor(a * 100 + .5) / 100 // round to 2 decimal places + }}; + } +} + +mbgl::Value Color::toObject() const { + return mapbox::base::ValueObject{{"r", static_cast(r)}, + {"g", static_cast(g)}, + {"b", static_cast(b)}, + {"a", static_cast(a)}}; +} + +mbgl::Value Color::serialize() const { + std::array array = toArray(); + return std::vector{ + std::string("rgba"), + array[0], + array[1], + array[2], + array[3], + }; +} + +} // namespace mbgl diff --git a/test/util/color.test.cpp b/test/util/color.test.cpp index dd3f67df8ba..ef217ec3389 100644 --- a/test/util/color.test.cpp +++ b/test/util/color.test.cpp @@ -25,6 +25,7 @@ const std::map> testCases = { {"#123", Color(0.067f, 0.133f, 0.2f, 1.0f)}, // Short hex format {"rgb(-10, 0, 0)", Color(0.0f, 0.0f, 0.0f, 1.0f)}, // Clamped to 0 {"rgba(300, 0, 0, 1.0)", Color(1.0f, 0.0f, 0.0f, 1.0f)}, // Clamped to 1 + {"rgba(100,100,100,0.2)", Color(20.0f / 255, 20.0f / 255, 20.0f / 255, 0.2f)}, // {"#GGGGGG", Color(0.0f, 0.0f, 0.0f, 1.0f)}, // Treated as fallback black // not supported right now // {"#0F0F", Color(0.0f, 1.0f, 0.0f, 1.0f)},