Allow compiling with Rust code (#3137)

Co-authored-by: Yuri Astrakhan <YuriAstrakhan@gmail.com>
Co-authored-by: Ian Wagner <ianwagner+github@switzerlandmail.ch>
This commit is contained in:
Bart Louwers
2025-01-16 16:01:11 +01:00
committed by GitHub
parent 79d625b03c
commit 527e70b6fa
23 changed files with 649 additions and 25 deletions

View File

@@ -1 +1 @@
7.3.1 8.0.0

View File

@@ -37,6 +37,7 @@ jobs:
- name: Get all Linux files that have changed - name: Get all Linux files that have changed
if: github.event_name != 'workflow_dispatch' if: github.event_name != 'workflow_dispatch'
id: changed-files id: changed-files
uses: tj-actions/changed-files@v45 uses: tj-actions/changed-files@v45
with: with:
files_yaml_from_source_file: .github/changed-files.yml files_yaml_from_source_file: .github/changed-files.yml
@@ -52,7 +53,7 @@ jobs:
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
renderer: [legacy, drawable, vulkan] renderer: [legacy, drawable, vulkan, drawable-rust]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -91,6 +92,11 @@ jobs:
- if: matrix.renderer == 'drawable' - if: matrix.renderer == 'drawable'
run: echo renderer_flag_cmake=-DMLN_DRAWABLE_RENDERER=ON >> "$GITHUB_ENV" 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' - if: matrix.renderer == 'legacy'
run: echo renderer_flag_cmake=-DMLN_LEGACY_RENDERER=ON >> "$GITHUB_ENV" run: echo renderer_flag_cmake=-DMLN_LEGACY_RENDERER=ON >> "$GITHUB_ENV"
@@ -158,7 +164,12 @@ jobs:
- name: Run render test - name: Run render test
id: 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 - name: Upload render test result
if: always() && steps.render_test.outcome == 'failure' if: always() && steps.render_test.outcome == 'failure'

View File

@@ -24,6 +24,13 @@ repos:
hooks: hooks:
- id: swiftformat - id: swiftformat
args: [--swiftversion, "5.8"] args: [--swiftversion, "5.8"]
- repo: local
hooks:
- id: rustfmt
name: rustfmt
entry: bash -c 'cd rustutils && cargo fmt' --
language: rust
types: [rust]
ci: ci:
# sometimes fails https://github.com/keith/pre-commit-buildifier/issues/13 # sometimes fails https://github.com/keith/pre-commit-buildifier/issues/13
skip: [buildifier] skip: [buildifier]

View File

@@ -1,5 +1,5 @@
load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_library", "js_run_binary") 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("@npm//:defs.bzl", "npm_link_all_packages")
load( load(
"//bazel:core.bzl", "//bazel:core.bzl",
@@ -124,7 +124,6 @@ cc_library(
":mbgl-core-generated-private-artifacts", ":mbgl-core-generated-private-artifacts",
":mbgl-core-generated-public-artifacts", ":mbgl-core-generated-public-artifacts",
"//vendor:boost", "//vendor:boost",
"//vendor:csscolorparser",
"//vendor:earcut.hpp", "//vendor:earcut.hpp",
"//vendor:eternal", "//vendor:eternal",
"//vendor:mapbox-base", "//vendor:mapbox-base",
@@ -149,6 +148,13 @@ cc_library(
"//vendor:metal-cpp", "//vendor:metal-cpp",
], ],
"//conditions:default": [], "//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( exports_files(
[ [
"LICENSE.md", "LICENSE.md",

View File

@@ -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_DRAWABLE_RENDERER "Include the drawable rendering pathway" OFF)
option(MLN_USE_UNORDERED_DENSE "Use ankerl dense containers for performance" ON) option(MLN_USE_UNORDERED_DENSE "Use ankerl dense containers for performance" ON)
option(MLN_USE_TRACY "Enable Tracy instrumentation" OFF) option(MLN_USE_TRACY "Enable Tracy instrumentation" OFF)
option(MLN_USE_RUST "Use components in Rust" OFF)
if (MLN_WITH_CLANG_TIDY) if (MLN_WITH_CLANG_TIDY)
find_program(CLANG_TIDY_COMMAND NAMES 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/bounding_volumes.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/util/chrono.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/chrono.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/util/client_options.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$<IF:$<BOOL:${MLN_USE_RUST}>,.rs.cpp,.cpp>
${PROJECT_SOURCE_DIR}/src/mbgl/util/constants.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/constants.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/util/convert.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/convert.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/util/event.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/wagyu.cmake)
include(${PROJECT_SOURCE_DIR}/vendor/metal-cpp.cmake) include(${PROJECT_SOURCE_DIR}/vendor/metal-cpp.cmake)
if(MLN_USE_RUST)
include(${PROJECT_SOURCE_DIR}/rustutils/rustutils.cmake)
endif()
target_link_libraries( target_link_libraries(
mbgl-core mbgl-core
PRIVATE PRIVATE
@@ -1468,7 +1473,6 @@ target_link_libraries(
Mapbox::Base::cheap-ruler-cpp Mapbox::Base::cheap-ruler-cpp
mbgl-compiler-options mbgl-compiler-options
mbgl-vendor-boost mbgl-vendor-boost
mbgl-vendor-csscolorparser
mbgl-vendor-earcut.hpp mbgl-vendor-earcut.hpp
mbgl-vendor-eternal mbgl-vendor-eternal
mbgl-vendor-parsedate mbgl-vendor-parsedate
@@ -1479,6 +1483,7 @@ target_link_libraries(
mbgl-vendor-vector-tile mbgl-vendor-vector-tile
mbgl-vendor-wagyu mbgl-vendor-wagyu
$<$<BOOL:${MLN_WITH_METAL}>:mbgl-vendor-metal-cpp> $<$<BOOL:${MLN_WITH_METAL}>:mbgl-vendor-metal-cpp>
$<IF:$<BOOL:${MLN_USE_RUST}>,mbgl-rustutils,mbgl-vendor-csscolorparser>
PUBLIC PUBLIC
Mapbox::Base Mapbox::Base
Mapbox::Base::Extras::expected-lite Mapbox::Base::Extras::expected-lite
@@ -1490,9 +1495,8 @@ target_link_libraries(
unordered_dense unordered_dense
) )
export(TARGETS set(EXPORT_TARGETS
mbgl-core mbgl-core
mapbox-base mapbox-base
mapbox-base-cheap-ruler-cpp mapbox-base-cheap-ruler-cpp
mapbox-base-extras-expected-lite mapbox-base-extras-expected-lite
@@ -1508,7 +1512,6 @@ export(TARGETS
mapbox-base-variant mapbox-base-variant
mbgl-compiler-options mbgl-compiler-options
mbgl-vendor-boost mbgl-vendor-boost
mbgl-vendor-csscolorparser
mbgl-vendor-earcut.hpp mbgl-vendor-earcut.hpp
mbgl-vendor-eternal mbgl-vendor-eternal
mbgl-vendor-parsedate mbgl-vendor-parsedate
@@ -1520,10 +1523,16 @@ export(TARGETS
mbgl-vendor-wagyu mbgl-vendor-wagyu
mbgl-vendor-metal-cpp mbgl-vendor-metal-cpp
unordered_dense 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) if(MLN_WITH_VULKAN)
include(${PROJECT_SOURCE_DIR}/vendor/vulkan.cmake) include(${PROJECT_SOURCE_DIR}/vendor/vulkan.cmake)

View File

@@ -1,6 +1,6 @@
module(name = "maplibre") 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 = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "platforms", version = "0.0.10") bazel_dep(name = "platforms", version = "0.0.10")
bazel_dep(name = "rules_apple", version = "3.16.1", repo_name = "build_bazel_rules_apple") bazel_dep(name = "rules_apple", version = "3.16.1", repo_name = "build_bazel_rules_apple")
@@ -60,3 +60,34 @@ darwin_config = use_repo_rule("//platform/darwin:bazel/darwin_config_repository_
darwin_config( darwin_config(
name = "darwin_config", name = "darwin_config",
) )
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")

View File

@@ -584,7 +584,6 @@ MLN_CORE_SOURCE = [
"src/mbgl/util/bounding_volumes.cpp", "src/mbgl/util/bounding_volumes.cpp",
"src/mbgl/util/chrono.cpp", "src/mbgl/util/chrono.cpp",
"src/mbgl/util/client_options.cpp", "src/mbgl/util/client_options.cpp",
"src/mbgl/util/color.cpp",
"src/mbgl/util/constants.cpp", "src/mbgl/util/constants.cpp",
"src/mbgl/util/convert.cpp", "src/mbgl/util/convert.cpp",
"src/mbgl/util/event.cpp", "src/mbgl/util/event.cpp",
@@ -652,7 +651,14 @@ MLN_CORE_SOURCE = [
"src/mbgl/util/version.cpp", "src/mbgl/util/version.cpp",
"src/mbgl/util/version.hpp", "src/mbgl/util/version.hpp",
"src/mbgl/util/work_request.cpp", "src/mbgl/util/work_request.cpp",
] ] + select({
"//:rust": [
"src/mbgl/util/color.rs.cpp",
],
"//conditions:default": [
"src/mbgl/util/color.cpp",
],
})
MLN_CORE_HEADERS = [ MLN_CORE_HEADERS = [
"include/mbgl/gfx/context.hpp", "include/mbgl/gfx/context.hpp",

View File

@@ -23,12 +23,16 @@ RUN apt-get update \
ccache \ ccache \
ninja-build \ ninja-build \
pkg-config \ pkg-config \
python3 \
python3-pip \
python-is-python3 \
clang-tidy \ 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 && : # 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 # 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 # 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 ;\ curl -fsSL https://github.com/bazelbuild/bazelisk/releases/download/v1.20.0/bazelisk-linux-arm64 -o /usr/local/bin/bazel ;\
else \ else \
curl -fsSL https://github.com/bazelbuild/bazelisk/releases/download/v1.20.0/bazelisk-linux-amd64 -o /usr/local/bin/bazel ;\ 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 \ && chmod +x /usr/local/bin/bazel \
&& : && :
WORKDIR /app
ARG USERNAME=user ARG USERNAME=user
ARG USER_UID=1000 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 \ && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME && chmod 0440 /etc/sudoers.d/$USERNAME
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 # This allows users to `docker run` without specifying -u and -g
USER $USERNAME 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
USER $USERNAME
WORKDIR /app
ENTRYPOINT ["/usr/local/bin/startup.sh"] ENTRYPOINT ["/usr/local/bin/startup.sh"]
CMD ["bash"] CMD ["bash"]

View File

@@ -1,13 +1,51 @@
#!/bin/sh #!/bin/sh
if [ ! -d /app/.github ] || [ ! -d /home/user/.cache ]; then if [ ! -d /app/.github ] || [ ! -d ~/.cache ]; then
echo " " echo " "
echo "ERROR: Docker container was not started properly." echo "ERROR: Docker container was not started properly."
echo " From the root of this repo, run the following command." 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 " You may add any command to perform in the container at the end of this command."
echo " " 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 exit 1
fi 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 "$@" exec "$@"

View File

@@ -22,3 +22,5 @@
- [Profiling applications that use MapLibre Native](./profiling/README.md) - [Profiling applications that use MapLibre Native](./profiling/README.md)
- [Tracy profiling](./profiling/tracy-profiling.md) - [Tracy profiling](./profiling/tracy-profiling.md)
- [Rust](./rust.md)

67
docs/mdbook/src/rust.md Normal file
View File

@@ -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 <rustutils/example.hpp>
```
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$<IF:$<BOOL:${MLN_USE_RUST}>,.rs.cpp,.cpp>
```
And here for Bazel:
```
select({
"//:rust": [
"src/mbgl/util/color.rs.cpp",
],
"//conditions:default": [
"src/mbgl/util/color.cpp",
],
})
```

1
rustutils/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

46
rustutils/BUILD.bazel Normal file
View File

@@ -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",
],
)

162
rustutils/Cargo.lock generated Normal file
View File

@@ -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"

19
rustutils/Cargo.toml Normal file
View File

@@ -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

35
rustutils/generate.sh Executable file
View File

@@ -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 <output_dir> <input_file1> [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

63
rustutils/rustutils.cmake Normal file
View File

@@ -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)

35
rustutils/src/color.rs Normal file
View File

@@ -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::<Color>()
.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,
})
}

1
rustutils/src/lib.rs Normal file
View File

@@ -0,0 +1 @@
mod color;

View File

@@ -2,7 +2,7 @@
function(mbgl_generate_license param) function(mbgl_generate_license param)
# Fake targets or non relevant. # Fake targets or non relevant.
set(BLACKLIST "mbgl-compiler-options") set(BLACKLIST "mbgl-compiler-options" "mbgl-rustutils")
get_target_property(LIBRARIES ${param} LINK_LIBRARIES) get_target_property(LIBRARIES ${param} LINK_LIBRARIES)
list(INSERT LIBRARIES 0 ${param}) list(INSERT LIBRARIES 0 ${param})

View File

@@ -6,7 +6,7 @@
namespace mbgl { namespace mbgl {
std::optional<Color> Color::parse(const std::string& s) { std::optional<Color> Color::parse(const std::string& s) {
auto css_color = CSSColorParser::parse(s); const auto css_color = CSSColorParser::parse(s);
// Premultiply the color. // Premultiply the color.
if (css_color) { if (css_color) {

View File

@@ -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 <cmath>
#include <mbgl/util/color.hpp>
#include <mbgl/util/string.hpp>
#include <rustutils/color.hpp>
namespace mbgl {
std::optional<Color> 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<double, 4> array = toArray();
return "rgba(" + util::toString(array[0]) + "," + util::toString(array[1]) + "," + util::toString(array[2]) + "," +
util::toString(array[3]) + ")";
}
std::array<double, 4> 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<double>(r)},
{"g", static_cast<double>(g)},
{"b", static_cast<double>(b)},
{"a", static_cast<double>(a)}};
}
mbgl::Value Color::serialize() const {
std::array<double, 4> array = toArray();
return std::vector<mbgl::Value>{
std::string("rgba"),
array[0],
array[1],
array[2],
array[3],
};
}
} // namespace mbgl

View File

@@ -25,6 +25,7 @@ const std::map<std::string, std::optional<Color>> testCases = {
{"#123", Color(0.067f, 0.133f, 0.2f, 1.0f)}, // Short hex format {"#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 {"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(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 // {"#GGGGGG", Color(0.0f, 0.0f, 0.0f, 1.0f)}, // Treated as fallback black
// not supported right now // not supported right now
// {"#0F0F", Color(0.0f, 1.0f, 0.0f, 1.0f)}, // {"#0F0F", Color(0.0f, 1.0f, 0.0f, 1.0f)},