Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Rust wrapper #3181

Draft
wants to merge 64 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
619b05a
wip
nyurik Jan 17, 2025
cfd4f26
wip
nyurik Jan 24, 2025
bf1168e
wip
nyurik Jan 24, 2025
2ae0d90
wip
nyurik Jan 27, 2025
4a00747
Add ChatGPT generated build.rs
louwers Jan 28, 2025
e7f90dd
Add ccache, remove linking with STL
louwers Jan 28, 2025
dc5efbe
fmt
nyurik Jan 28, 2025
da5ff7b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 28, 2025
7baf25c
rm old rust stuff
nyurik Jan 28, 2025
90b7df6
wip
nyurik Jan 28, 2025
0cdc369
fix build on 1.82+
nyurik Jan 29, 2025
1fa4a96
ci
nyurik Jan 29, 2025
06c3292
ci wip
nyurik Jan 29, 2025
34ccd93
symlink
nyurik Jan 29, 2025
b85ac6f
add Rust-specific C++ wrapper, renderer args
nyurik Jan 29, 2025
27d1a34
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 29, 2025
ce96c62
implement TileServerOptions instantiation and access
nyurik Jan 30, 2025
07041ff
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 30, 2025
157f6aa
wip
nyurik Jan 30, 2025
94b0fb0
simplify all optional<str> getters
nyurik Feb 4, 2025
46ba4ca
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 4, 2025
04bdb41
Implement most setters as a Rust struct
nyurik Feb 5, 2025
0b5fb24
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 5, 2025
63774c6
cleanup
nyurik Feb 5, 2025
a45e009
minor cleanup
nyurik Feb 5, 2025
9ab5b0c
use my new cxx as_c_str
nyurik Feb 5, 2025
d7e88af
broken map-renderer class
nyurik Feb 7, 2025
8d1d573
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 7, 2025
11a47ef
Fix extern C++ rust section (still not working)
pre-commit-ci[bot] Feb 7, 2025
5273b1b
Fix compilation
louwers Feb 7, 2025
12c1f8e
it compiles!
nyurik Feb 7, 2025
34d13af
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 7, 2025
1e988cb
map-renderer class - not working
nyurik Feb 7, 2025
b2809e1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 7, 2025
7b73d2c
Push build.rs modifications that somehow work
louwers Feb 8, 2025
c94cb23
Add renderer backend as Cargo features
louwers Feb 8, 2025
79dba89
Create build_support crate
louwers Feb 8, 2025
9d4ce39
Merge branch 'main' into rust-wrapper
nyurik Feb 8, 2025
a897c07
fmt, fix warnings
nyurik Feb 8, 2025
3dd504f
Lots of refactoring, new api
nyurik Feb 9, 2025
6c416c5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 9, 2025
040c23f
Rework C++ API
nyurik Feb 9, 2025
294bdf6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 9, 2025
92d79e9
Merge branch 'main' into rust-wrapper
nyurik Feb 9, 2025
b367aa1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 9, 2025
1d82d0d
mode enum
nyurik Feb 9, 2025
5c7045b
use std::string instead of std::vector for images
nyurik Feb 9, 2025
da15a2e
move to mln namespace, debug enum
nyurik Feb 9, 2025
ca81f92
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 9, 2025
f3c79a0
Use enums as params
nyurik Feb 9, 2025
fbf1b4d
inline renderer
nyurik Feb 9, 2025
a254edb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 9, 2025
fcc032e
Massive rework, ability to reuse renderer
nyurik Feb 9, 2025
89ffd52
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 9, 2025
6147835
handle style url/paths, cleanup
nyurik Feb 9, 2025
313f101
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 9, 2025
de78b7d
inline render
nyurik Feb 9, 2025
9e91990
expose camera and debug mode api
nyurik Feb 9, 2025
412840e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 9, 2025
c948159
make map chainable
nyurik Feb 9, 2025
5bde308
first pass at tile impl
nyurik Feb 9, 2025
12685a5
Fix linking issue with dependency order
nyurik Feb 10, 2025
f490037
add sqlite3 dyn link workaround
nyurik Feb 10, 2025
6b54a8e
Merge branch 'main' into rust-wrapper
nyurik Feb 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/linux-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ jobs:
cmake -B build -GNinja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMLN_WITH_CLANG_TIDY=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DMLN_WITH_COVERAGE=ON ${{ env.renderer_flag_cmake }}
cmake --build build --target mbgl-core mbgl-test-runner mbgl-render-test-runner mbgl-expression-test mbgl-render mbgl-benchmark-runner

# test rust platform
- if: matrix.renderer == 'drawable-rust'
uses: taiki-e/install-action@v2
with: { tool: just }
- if: matrix.renderer == 'drawable-rust'
run: just -v ci-test
working-directory: platform/rust

# mbgl-render (used for size test) & mbgl-benchmark-runner

- name: Upload mbgl-render as artifact
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/rust-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@ on:

defaults:
run:
shell: bash
working-directory: rustutils/

jobs:
test:
name: Rust tests
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
include:
- os: macos-latest # M-series CPU
- os: macos-13 # x64 CPU
- os: windows-latest
- os: ubuntu-latest
steps:
- uses: taiki-e/install-action@v2
with: { tool: just }
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,8 @@ cache.sqlite-journal
out.png
/test/android/app/.cxx
/test/android/app/build

# Rust
**/target/
**/*.rs.bk
**/*.profraw
10 changes: 7 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,16 @@ repos:
args: [--swiftversion, "5.8"]
- repo: local
hooks:
- id: rustfmt
name: rustfmt
- id: rustfmt_utils
name: rustfmt rustutils
entry: bash -c 'cd rustutils && cargo fmt' --
language: rust
types: [rust]
- id: rustfmt_platform
name: rustfmt rust platform
entry: bash -c 'cd platform/rust && cargo fmt' --
language: rust
types: [rust]
ci:
# sometimes fails https://github.com/keith/pre-commit-buildifier/issues/13
skip: [buildifier]

2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1585,3 +1585,5 @@ endif()
add_subdirectory(${PROJECT_SOURCE_DIR}/test)
add_subdirectory(${PROJECT_SOURCE_DIR}/benchmark)
add_subdirectory(${PROJECT_SOURCE_DIR}/render-test)

include(cmake/mbgl-core-deps.cmake)
33 changes: 33 additions & 0 deletions cmake/mbgl-core-deps.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# this file defines a target that will generate a mbgl-core-deps.txt which includes the linker
# flags that would need to be passed to an executable or library that links with
# the static mbgl-core library

set(DUMMY_FILE "${CMAKE_BINARY_DIR}/dummy.cpp")
add_custom_command(
OUTPUT ${DUMMY_FILE}
COMMAND ${CMAKE_COMMAND} -E echo "int main() {}" > ${DUMMY_FILE}
COMMENT "Generating dummy.cpp"
VERBATIM
)

# https://stackoverflow.com/questions/34165365/retrieve-all-link-flags-in-cmake
set(CMAKE_ECHO_STANDARD_LIBRARIES ${CMAKE_CXX_STANDARD_LIBRARIES})
set(CMAKE_ECHO_FLAGS ${CMAKE_CXX_FLAGS})
set(CMAKE_ECHO_LINK_FLAGS ${CMAKE_CXX_LINK_FLAGS})
set(CMAKE_ECHO_IMPLICIT_LINK_DIRECTORIES ${CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES})
set(
CMAKE_ECHO_LINK_EXECUTABLE
"<CMAKE_COMMAND> -E echo \"<FLAGS> <LINK_FLAGS> <LINK_LIBRARIES>\" > <TARGET>"
)

add_executable(mbgl-core-deps EXCLUDE_FROM_ALL "${DUMMY_FILE}")
target_link_libraries(mbgl-core-deps mbgl-core)
add_custom_target(generate_dummy DEPENDS ${DUMMY_FILE})
add_dependencies(mbgl-core-deps generate_dummy)

set_target_properties(
mbgl-core-deps
PROPERTIES
LINKER_LANGUAGE ECHO
SUFFIX ".txt"
)
2 changes: 1 addition & 1 deletion include/mbgl/util/tile_server_options.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ class TileServerOptions final {
TileServerOptions& withDefaultStyles(std::vector<mbgl::util::DefaultStyle> styles);

/**
* @brief Sets the default style by name. The style name must exists in
* @brief Sets the default style by name. The style name must exist in
* defaultStyles collection
*
* @param defaultStyle The style name
Expand Down
16 changes: 16 additions & 0 deletions platform/rust/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated by Cargo
# will have compiled files and executables
**/target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# These are temp files generated by trybuild
wip

# coverage reports
**/*.profraw
29 changes: 29 additions & 0 deletions platform/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "maplibre-native"
version = "0.1.0"
edition = "2021"

[dependencies]
cxx = "1.0.138"
clap = { version = "4.5.27", features = ["derive", "env"] }

[dev-dependencies]
insta = { version = "1.42.1" }

[build-dependencies]
cmake = "0.1"
cxx-build = "1.0.138"
rustversion = "1.0.19"
walkdir = "2.5.0"
build_support = { path = "./build_support" }

[features]
metal = [] # default on Apple platforms, do not add to default features
opengl = []
vulkan = [] # default on other platforms

[workspace]
members = [
"build_support",
"."
]
122 changes: 122 additions & 0 deletions platform/rust/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use std::collections::HashSet;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;

use build_support::parse_deps;

/// Helper that returns a new cmake::Config with common settings.
/// It selects the renderer based on Cargo features: the user must enable exactly one of:
/// "metal", "opengl", or "vulkan". If none are explicitly enabled, on iOS/macOS the default is metal,
/// and on all other platforms the default is vulkan.
fn create_cmake_config(project_root: &Path) -> cmake::Config {
let mut cfg = cmake::Config::new(project_root);
cfg.generator("Ninja");
cfg.define("CMAKE_C_COMPILER_LAUNCHER", "ccache");
cfg.define("CMAKE_CXX_COMPILER_LAUNCHER", "ccache");
cfg.define("MLN_DRAWABLE_RENDERER", "ON");
cfg.define("MLN_WITH_OPENGL", "OFF");

let (metal_enabled, opengl_enabled, vulkan_enabled) = {
let metal = env::var("CARGO_FEATURE_METAL").is_ok();
let opengl = env::var("CARGO_FEATURE_OPENGL").is_ok();
let vulkan = env::var("CARGO_FEATURE_VULKAN").is_ok();
if !metal && !opengl && !vulkan {
if cfg!(target_os = "ios") || cfg!(target_os = "macos") {
(true, false, false)
} else {
(false, false, true)
}
} else {
(metal, opengl, vulkan)
}
};

let num_enabled = (metal_enabled as u8) + (opengl_enabled as u8) + (vulkan_enabled as u8);
if num_enabled > 1 {
panic!("Features 'metal', 'opengl', and 'vulkan' are mutually exclusive. Please enable only one.");
}

if opengl_enabled {
cfg.define("MLN_WITH_OPENGL", "ON");
cfg.define("MLN_WITH_METAL", "OFF");
cfg.define("MLN_WITH_VULKAN", "OFF");
} else if metal_enabled {
cfg.define("MLN_WITH_OPENGL", "OFF");
cfg.define("MLN_WITH_METAL", "ON");
cfg.define("MLN_WITH_VULKAN", "OFF");
} else if vulkan_enabled {
cfg.define("MLN_WITH_OPENGL", "OFF");
cfg.define("MLN_WITH_METAL", "OFF");
cfg.define("MLN_WITH_VULKAN", "ON");
}

cfg.define("MLN_WITH_WERROR", "OFF");
cfg
}

fn main() {
// Determine the project root (use the parent of CARGO_MANIFEST_DIR's parent to allow in‐source Windows builds).
let project_root = {
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
manifest.parent().unwrap().parent().unwrap().to_path_buf()
};

// ------------------------------------------------------------------------
// 1. Build the "mbgl-core-deps" target first so that mbgl-core-deps.txt is generated.
// Since CMake installs targets into a "build" subdirectory, we look for the file there.
// ------------------------------------------------------------------------
let deps_build_dir = create_cmake_config(&project_root)
.build_target("mbgl-core-deps")
.build();
let deps_file = deps_build_dir.join("build").join("mbgl-core-deps.txt");
let deps_contents = fs::read_to_string(&deps_file)
.unwrap_or_else(|_| panic!("Failed to read {}", deps_file.display()));

// Parse the deps file into a list of Cargo instructions.
let instructions = parse_deps(&deps_contents, &deps_build_dir.join("build"));
for instr in instructions {
println!("{}", instr);
}

// ------------------------------------------------------------------------
// 2. Build the actual "mbgl-core" target.
// ------------------------------------------------------------------------
let core_build_dir = create_cmake_config(&project_root)
.build_target("mbgl-core")
.build();
let static_lib_base = core_build_dir.join("build");
println!(
"cargo:rustc-link-search=native={}",
static_lib_base.display()
);

// ------------------------------------------------------------------------
// 3. Gather include directories and build the C++ bridge using cxx_build.
// ------------------------------------------------------------------------
let mut include_dirs = vec![
project_root.join("include"),
project_root.join("platform/default/include"),
];
for entry in WalkDir::new(project_root.join("vendor")) {
let entry = entry.expect("Failed reading vendor directory");
if entry.file_type().is_dir() && !entry.path_is_symlink() && entry.file_name() == "include"
{
include_dirs.push(entry.path().to_path_buf());
}
}

cxx_build::bridge("src/lib.rs")
.includes(include_dirs)
.file("src/wrapper.cpp")
.flag_if_supported("-std=c++20")
.compile("maplibre_rust_bindings");

// ------------------------------------------------------------------------
// 4. Instruct Cargo when to re-run the build script.
// ------------------------------------------------------------------------
println!("cargo:rerun-if-changed=src/lib.rs");
println!("cargo:rerun-if-changed=src/wrapper.cpp");
println!("cargo:rerun-if-changed=include/tile_server_options.h");
}
7 changes: 7 additions & 0 deletions platform/rust/build_support/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "build_support"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
76 changes: 76 additions & 0 deletions platform/rust/build_support/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use std::collections::HashSet;
use std::path::{Path, PathBuf};

/// Parses the contents of mbgl-core-deps.txt and returns Cargo linker instructions.
///
/// # Arguments
///
/// * `deps_contents` - The contents of the dependency file as a string.
/// * `static_lib_base` - The base directory where the static libraries reside.
pub fn parse_deps(deps_contents: &str, static_lib_base: &Path) -> Vec<String> {
let mut instructions = Vec::new();
let mut added_search_paths = HashSet::new();
let tokens: Vec<&str> = deps_contents.split_whitespace().collect();
let mut token_iter = tokens.iter().peekable();

while let Some(&token) = token_iter.next() {
if token == "-framework" {
if let Some(&framework) = token_iter.next() {
instructions.push(format!("cargo:rustc-link-lib=framework={}", framework));
} else {
panic!("Expected a framework name after '-framework'");
}
} else if token.starts_with("-l") {
let libname = &token[2..];
instructions.push(format!("cargo:rustc-link-lib={}", libname));
} else if token.ends_with(".a") {
let lib_path = Path::new(token);
let file_stem = lib_path.file_stem().expect("Library file has no stem");
let file_stem = file_stem
.to_str()
.expect("Library file stem is not valid UTF-8");
let lib_name = file_stem.strip_prefix("lib").unwrap_or(file_stem);

let search_dir = match lib_path.parent() {
Some(parent) if !parent.as_os_str().is_empty() => static_lib_base.join(parent),
_ => static_lib_base.to_path_buf(),
};
if added_search_paths.insert(search_dir.clone()) {
instructions.push(format!(
"cargo:rustc-link-search=native={}",
search_dir.display()
));
}
instructions.push(format!("cargo:rustc-link-lib=static={}", lib_name));
} else {
instructions.push(format!("cargo:rustc-link-arg={}", token));
}
}
instructions
}

#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;

#[test]
fn test_parse_deps() {
// Simulate a deps file with:
// - "-lsqlite3" (link sqlite3)
// - "libmbgl-core.a" (a static library with no parent directory)
// - "-framework AppKit"
// - "some_arg" (an extra linker argument)
let deps_content = "-lsqlite3 libmbgl-core.a -framework AppKit some_arg";
let base_dir = PathBuf::from("/build_dir/build");
let instructions = parse_deps(deps_content, &base_dir);
let expected = vec![
"cargo:rustc-link-lib=sqlite3".to_string(),
format!("cargo:rustc-link-search=native={}", base_dir.display()),
"cargo:rustc-link-lib=static=mbgl-core".to_string(),
"cargo:rustc-link-lib=framework=AppKit".to_string(),
"cargo:rustc-link-arg=some_arg".to_string(),
];
assert_eq!(instructions, expected);
}
}
Loading
Loading