diff --git a/r/.Rbuildignore b/r/.Rbuildignore new file mode 100644 index 0000000..7d10222 --- /dev/null +++ b/r/.Rbuildignore @@ -0,0 +1,6 @@ +^LICENSE\.md$ +^src/\.cargo$ +^src/rust/vendor$ +^src/rust/target$ +^src/Makevars$ +^src/Makevars\.win$ diff --git a/r/.gitignore b/r/.gitignore new file mode 100644 index 0000000..20cff42 --- /dev/null +++ b/r/.gitignore @@ -0,0 +1,3 @@ +src/rust/vendor +src/Makevars +src/Makevars.win diff --git a/r/DESCRIPTION b/r/DESCRIPTION new file mode 100644 index 0000000..0f6aa70 --- /dev/null +++ b/r/DESCRIPTION @@ -0,0 +1,14 @@ +Package: qbin +Title: Quadbin Spatial Indexing +Version: 0.0.1 +Authors@R: + person("First", "Last", , "first.last@example.com", role = c("aut", "cre")) +Description: Encoding and decoding geographical coordinates to and from Quadbin, a hierarchical geospatial indexing system for square cells in Web Mercator projection developed by Carto. An improved version of Microsoft's Bing Maps Tile System, aka Quadkey. +License: MIT + file LICENSE +Encoding: UTF-8 +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.3.2 +Config/rextendr/version: 0.3.1.9001 +SystemRequirements: Cargo (Rust's package manager), rustc +Depends: + R (>= 4.2) diff --git a/r/LICENSE b/r/LICENSE new file mode 100644 index 0000000..63277ab --- /dev/null +++ b/r/LICENSE @@ -0,0 +1,2 @@ +YEAR: 2025 +COPYRIGHT HOLDER: qbin authors diff --git a/r/LICENSE.md b/r/LICENSE.md new file mode 100644 index 0000000..7f9a770 --- /dev/null +++ b/r/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2025 qbin authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/r/NAMESPACE b/r/NAMESPACE new file mode 100644 index 0000000..0275bc5 --- /dev/null +++ b/r/NAMESPACE @@ -0,0 +1,3 @@ +# Generated by roxygen2: do not edit by hand + +useDynLib(qbin, .registration = TRUE) diff --git a/r/R/extendr-wrappers.R b/r/R/extendr-wrappers.R new file mode 100644 index 0000000..ce05081 --- /dev/null +++ b/r/R/extendr-wrappers.R @@ -0,0 +1,18 @@ +# Generated by extendr: Do not edit by hand + +# nolint start + +# +# This file was created with the following call: +# .Call("wrap__make_qbin_wrappers", use_symbols = TRUE, package_name = "qbin") + +#' @usage NULL +#' @useDynLib qbin, .registration = TRUE +NULL + +quadbin_to_ints <- function(x) .Call(wrap__quadbin_to_ints, x) + +vctrs_class <- function() .Call(wrap__vctrs_class) + + +# nolint end diff --git a/r/README.md b/r/README.md new file mode 100644 index 0000000..1ea83cc --- /dev/null +++ b/r/README.md @@ -0,0 +1,23 @@ +# qbin (R package) + + + + +This is the R version of the `qbin` library, a Quadbin spatial indexing tool. + +## Installation + +You can install the development version of `qbin` from [GitHub](https://github.com/) with: + +``` r +# install.packages("remotes") +remotes::install_github("atsyplenkov/qbin", subdir = "r") +``` + +## Example + +``` r +library(qbin) +## basic example code +``` + diff --git a/r/configure b/r/configure new file mode 100755 index 0000000..c608b11 --- /dev/null +++ b/r/configure @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +: "${R_HOME=`R RHOME`}" +"${R_HOME}/bin/Rscript" tools/config.R diff --git a/r/configure.win b/r/configure.win new file mode 100644 index 0000000..57eb255 --- /dev/null +++ b/r/configure.win @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +"${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" tools/config.R diff --git a/r/inst/dev.R b/r/inst/dev.R new file mode 100644 index 0000000..d4c0499 --- /dev/null +++ b/r/inst/dev.R @@ -0,0 +1,20 @@ +rextendr::document() + + +# h3o -------------------------------------------------------------------- +pnts <- + tibble::tibble( + x = runif(100, -5, 10), + y = runif(100, 40, 50) + ) |> + sf::st_as_sf( + coords = c("x", "y"), + crs = 4326 + ) + +library(h3o) + +h3s <- pnts |> + dplyr::mutate(h3 = h3_from_points(geometry, 5)) |> + sf::st_drop_geometry() +class(h3s$h3) diff --git a/r/src/.gitignore b/r/src/.gitignore new file mode 100644 index 0000000..c23c7b3 --- /dev/null +++ b/r/src/.gitignore @@ -0,0 +1,5 @@ +*.o +*.so +*.dll +target +.cargo diff --git a/r/src/Makevars.in b/r/src/Makevars.in new file mode 100644 index 0000000..d315541 --- /dev/null +++ b/r/src/Makevars.in @@ -0,0 +1,43 @@ +TARGET_DIR = ./rust/target +LIBDIR = $(TARGET_DIR)/@LIBDIR@ +STATLIB = $(LIBDIR)/libqbin.a +PKG_LIBS = -L$(LIBDIR) -lqbin + +all: $(SHLIB) rust_clean + rm -f $(CURDIR)/Makevars + +.PHONY: $(STATLIB) + +$(SHLIB): $(STATLIB) + +CARGOTMP = $(CURDIR)/.cargo +VENDOR_DIR = $(CURDIR)/vendor + + +# RUSTFLAGS appends --print=native-static-libs to ensure that +# the correct linkers are used. Use this for debugging if need. +# +# CRAN note: Cargo and Rustc versions are reported during +# configure via tools/msrv.R. +# +# vendor.tar.xz, if present, is unzipped and used for offline compilation. +$(STATLIB): + + if [ -f ./rust/vendor.tar.xz ]; then \ + tar xf rust/vendor.tar.xz && \ + mkdir -p $(CARGOTMP) && \ + cp rust/vendor-config.toml $(CARGOTMP)/config.toml; \ + fi + + export CARGO_HOME=$(CARGOTMP) && \ + export PATH="$(PATH):$(HOME)/.cargo/bin" && \ + RUSTFLAGS="$(RUSTFLAGS) --print=native-static-libs" cargo build @CRAN_FLAGS@ --lib @PROFILE@ --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR) @TARGET@ + + # Always clean up CARGOTMP + rm -Rf $(CARGOTMP); + +rust_clean: $(SHLIB) + rm -Rf $(CARGOTMP) $(VENDOR_DIR) @CLEAN_TARGET@ + +clean: + rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(TARGET_DIR) $(VENDOR_DIR) diff --git a/r/src/Makevars.win.in b/r/src/Makevars.win.in new file mode 100644 index 0000000..fb09d00 --- /dev/null +++ b/r/src/Makevars.win.in @@ -0,0 +1,40 @@ +TARGET = $(subst 64,x86_64,$(subst 32,i686,$(WIN)))-pc-windows-gnu + +TARGET_DIR = ./rust/target +LIBDIR = $(TARGET_DIR)/$(TARGET)/@LIBDIR@ +STATLIB = $(LIBDIR)/libqbin.a +PKG_LIBS = -L$(LIBDIR) -lqbin -lws2_32 -ladvapi32 -luserenv -lbcrypt -lntdll + +all: $(SHLIB) rust_clean + rm -f $(CURDIR)/Makevars.win + +.PHONY: $(STATLIB) + +$(SHLIB): $(STATLIB) + +CARGOTMP = $(CURDIR)/.cargo +VENDOR_DIR = vendor + +$(STATLIB): + mkdir -p $(TARGET_DIR)/libgcc_mock + touch $(TARGET_DIR)/libgcc_mock/libgcc_eh.a + + if [ -f ./rust/vendor.tar.xz ]; then \ + tar xf rust/vendor.tar.xz && \ + mkdir -p $(CARGOTMP) && \ + cp rust/vendor-config.toml $(CARGOTMP)/config.toml; \ + fi + + # Build the project using Cargo with additional flags + export CARGO_HOME=$(CARGOTMP) && \ + export LIBRARY_PATH="$(LIBRARY_PATH);$(CURDIR)/$(TARGET_DIR)/libgcc_mock" && \ + RUSTFLAGS="$(RUSTFLAGS) --print=native-static-libs" cargo build @CRAN_FLAGS@ --target=$(TARGET) --lib @PROFILE@ --manifest-path=rust/Cargo.toml --target-dir=$(TARGET_DIR) + + # Always clean up CARGOTMP + rm -Rf $(CARGOTMP); + +rust_clean: $(SHLIB) + rm -Rf $(CARGOTMP) $(VENDOR_DIR) @CLEAN_TARGET@ + +clean: + rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(TARGET_DIR) $(VENDOR_DIR) diff --git a/r/src/entrypoint.c b/r/src/entrypoint.c new file mode 100644 index 0000000..038a879 --- /dev/null +++ b/r/src/entrypoint.c @@ -0,0 +1,8 @@ +// We need to forward routine registration from C to Rust +// to avoid the linker removing the static library. + +void R_init_qbin_extendr(void *dll); + +void R_init_qbin(void *dll) { + R_init_qbin_extendr(dll); +} diff --git a/r/src/qbin-win.def b/r/src/qbin-win.def new file mode 100644 index 0000000..826fe1e --- /dev/null +++ b/r/src/qbin-win.def @@ -0,0 +1,2 @@ +EXPORTS +R_init_qbin diff --git a/r/src/rust/Cargo.lock b/r/src/rust/Cargo.lock new file mode 100644 index 0000000..988d48f --- /dev/null +++ b/r/src/rust/Cargo.lock @@ -0,0 +1,417 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "build-print" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a2128d00b7061b82b72844a351e80acd29e05afc60e9261e2ac90dca9ecc2ac" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "earcutr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01" +dependencies = [ + "itertools", + "num-traits", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "extendr-api" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cae12193370e4f00f4a54b64f40dabc753ad755f15229c367e4b5851ed206954" +dependencies = [ + "extendr-ffi", + "extendr-macros", + "once_cell", + "paste", +] + +[[package]] +name = "extendr-ffi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b48f96b7a4a2ff009ad9087f22a6de2312731a4096b520e3eb1c483df476ae95" +dependencies = [ + "build-print", +] + +[[package]] +name = "extendr-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdbbac9afddafddb4dabd10aefa8082e3f057ec5bfa519c7b44af114e7ebf1a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "geo" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4416397671d8997e9a3e7ad99714f4f00a22e9eaa9b966a5985d2194fc9e02e1" +dependencies = [ + "earcutr", + "float_next_after", + "geo-types", + "geographiclib-rs", + "i_overlay", + "log", + "num-traits", + "robust", + "rstar", + "spade", +] + +[[package]] +name = "geo-types" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ddb1950450d67efee2bbc5e429c68d052a822de3aad010d28b351fbb705224" +dependencies = [ + "approx", + "num-traits", + "rayon", + "rstar", + "serde", +] + +[[package]] +name = "geographiclib-rs" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e5ed84f8089c70234b0a8e0aedb6dc733671612ddc0d37c6066052f9781960" +dependencies = [ + "libm", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "i_float" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85df3a416829bb955fdc2416c7b73680c8dcea8d731f2c7aa23e1042fe1b8343" +dependencies = [ + "serde", +] + +[[package]] +name = "i_key_sort" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "347c253b4748a1a28baf94c9ce133b6b166f08573157e05afe718812bc599fcd" + +[[package]] +name = "i_overlay" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0542dfef184afdd42174a03dcc0625b6147fb73e1b974b1a08a2a42ac35cee49" +dependencies = [ + "i_float", + "i_key_sort", + "i_shape", + "i_tree", + "rayon", +] + +[[package]] +name = "i_shape" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a38f5a42678726718ff924f6d4a0e79b129776aeed298f71de4ceedbd091bce" +dependencies = [ + "i_float", + "serde", +] + +[[package]] +name = "i_tree" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155181bc97d770181cf9477da51218a19ee92a8e5be642e796661aee2b601139" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "libm" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qbin" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af66196ef798e5a4e53dd543a8722149e348c9f138d4e76eebbb609cbcabb974" +dependencies = [ + "geo", +] + +[[package]] +name = "qbin_r" +version = "0.1.0" +dependencies = [ + "extendr-api", + "qbin", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "robust" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf4a6aa5f6d6888f39e980649f3ad6b666acdce1d78e95b8a2cb076e687ae30" + +[[package]] +name = "rstar" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" +dependencies = [ + "heapless", + "num-traits", + "smallvec", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "spade" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ece03ff43cd2a9b57ebf776ea5e78bd30b3b4185a619f041079f4109f385034" +dependencies = [ + "hashbrown", + "num-traits", + "robust", + "smallvec", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" diff --git a/r/src/rust/Cargo.toml b/r/src/rust/Cargo.toml new file mode 100644 index 0000000..258004a --- /dev/null +++ b/r/src/rust/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = 'qbin_r' +publish = false +version = '0.1.0' +edition = '2021' +rust-version = '1.65' + +[lib] +crate-type = ['staticlib'] +name = 'qbin' + +[dependencies] +extendr-api = '*' +qbin = "0.2.0" diff --git a/r/src/rust/src/lib.rs b/r/src/rust/src/lib.rs new file mode 100644 index 0000000..780c34c --- /dev/null +++ b/r/src/rust/src/lib.rs @@ -0,0 +1,8 @@ +use extendr_api::prelude::*; + +mod qb; + +extendr_module! { + mod qbin; + use qb; +} diff --git a/r/src/rust/src/qb.rs b/r/src/rust/src/qb.rs new file mode 100644 index 0000000..186ec2a --- /dev/null +++ b/r/src/rust/src/qb.rs @@ -0,0 +1,60 @@ +use extendr_api::prelude::*; + +use qbin::Cell; + +#[derive(Debug, Clone, Copy)] +pub struct Quadbin { + pub index: Cell, +} + +#[extendr] +impl Quadbin {} + +impl From for Quadbin { + fn from(index: Cell) -> Self { + Quadbin { index: index } + } +} + +impl TryFrom<&Robj> for Quadbin { + type Error = Error; + + fn try_from(robj: &Robj) -> extendr_api::Result { + // Convert Option to Result with an error message + let value = robj + .as_real() + .ok_or_else(|| Error::Other("Expected a numeric value".into()))?; + let cell = Cell::try_from(value as u64) + .map_err(|_| Error::Other("Invalid Quadbin cell value".into()))?; + Ok(Quadbin { index: cell }) + } +} + +// returns an array of strings with the appropriate vctrs class +#[extendr] +pub fn vctrs_class() -> [String; 3] { + [ + String::from("Quadbin"), + String::from("vctrs_vctr"), + String::from("list"), + ] +} + +#[extendr] +fn quadbin_to_ints(x: List) -> Doubles { + let res = x + .into_iter() + .map(|(_, robj)| match Quadbin::try_from(&robj) { + Ok(quadbin) => Rfloat::from(quadbin.index.get() as f64), + Err(_) => Rfloat::na(), + }) + .collect::>(); + + Doubles::from_values(res) +} + +extendr_module! { + mod qb; + fn quadbin_to_ints; + fn vctrs_class; +} diff --git a/r/tools/config.R b/r/tools/config.R new file mode 100644 index 0000000..f317f06 --- /dev/null +++ b/r/tools/config.R @@ -0,0 +1,104 @@ +# Note: Any variables prefixed with `.` are used for text +# replacement in the Makevars.in and Makevars.win.in + +# check the packages MSRV first +source("tools/msrv.R") + +# check DEBUG and NOT_CRAN environment variables +env_debug <- Sys.getenv("DEBUG") +env_not_cran <- Sys.getenv("NOT_CRAN") + +# check if the vendored zip file exists +vendor_exists <- file.exists("src/rust/vendor.tar.xz") + +is_not_cran <- env_not_cran != "" +is_debug <- env_debug != "" + +if (is_debug) { + # if we have DEBUG then we set not cran to true + # CRAN is always release build + is_not_cran <- TRUE + message("Creating DEBUG build.") +} + +if (!is_not_cran) { + message("Building for CRAN.") +} + +# we set cran flags only if NOT_CRAN is empty and if +# the vendored crates are present. +.cran_flags <- ifelse( + !is_not_cran && vendor_exists, + "-j 2 --offline", + "" +) + +# when DEBUG env var is present we use `--debug` build +.profile <- ifelse(is_debug, "", "--release") +.clean_targets <- ifelse(is_debug, "", "$(TARGET_DIR)") + +# We specify this target when building for webR +webr_target <- "wasm32-unknown-emscripten" + +# here we check if the platform we are building for is webr +is_wasm <- identical(R.version$platform, webr_target) + +# print to terminal to inform we are building for webr +if (is_wasm) { + message("Building for WebR") +} + +# we check if we are making a debug build or not +# if so, the LIBDIR environment variable becomes: +# LIBDIR = $(TARGET_DIR)/{wasm32-unknown-emscripten}/debug +# this will be used to fill out the LIBDIR env var for Makevars.in +target_libpath <- if (is_wasm) "wasm32-unknown-emscripten" else NULL +cfg <- if (is_debug) "debug" else "release" + +# used to replace @LIBDIR@ +.libdir <- paste(c(target_libpath, cfg), collapse = "/") + +# use this to replace @TARGET@ +# we specify the target _only_ on webR +# there may be use cases later where this can be adapted or expanded +.target <- ifelse(is_wasm, paste0("--target=", webr_target), "") + +# read in the Makevars.in file checking +is_windows <- .Platform[["OS.type"]] == "windows" + +# if windows we replace in the Makevars.win.in +mv_fp <- ifelse( + is_windows, + "src/Makevars.win.in", + "src/Makevars.in" +) + +# set the output file +mv_ofp <- ifelse( + is_windows, + "src/Makevars.win", + "src/Makevars" +) + +# delete the existing Makevars{.win} +if (file.exists(mv_ofp)) { + message("Cleaning previous `", mv_ofp, "`.") + invisible(file.remove(mv_ofp)) +} + +# read as a single string +mv_txt <- readLines(mv_fp) + +# replace placeholder values +new_txt <- gsub("@CRAN_FLAGS@", .cran_flags, mv_txt) |> + gsub("@PROFILE@", .profile, x = _) |> + gsub("@CLEAN_TARGET@", .clean_targets, x = _) |> + gsub("@LIBDIR@", .libdir, x = _) |> + gsub("@TARGET@", .target, x = _) + +message("Writing `", mv_ofp, "`.") +con <- file(mv_ofp, open = "wb") +writeLines(new_txt, con, sep = "\n") +close(con) + +message("`tools/config.R` has finished.") diff --git a/r/tools/msrv.R b/r/tools/msrv.R new file mode 100644 index 0000000..59a61ab --- /dev/null +++ b/r/tools/msrv.R @@ -0,0 +1,116 @@ +# read the DESCRIPTION file +desc <- read.dcf("DESCRIPTION") + +if (!"SystemRequirements" %in% colnames(desc)) { + fmt <- c( + "`SystemRequirements` not found in `DESCRIPTION`.", + "Please specify `SystemRequirements: Cargo (Rust's package manager), rustc`" + ) + stop(paste(fmt, collapse = "\n")) +} + +# extract system requirements +sysreqs <- desc[, "SystemRequirements"] + +# check that cargo and rustc is found +if (!grepl("cargo", sysreqs, ignore.case = TRUE)) { + stop("You must specify `Cargo (Rust's package manager)` in your `SystemRequirements`") +} + +if (!grepl("rustc", sysreqs, ignore.case = TRUE)) { + stop("You must specify `Cargo (Rust's package manager), rustc` in your `SystemRequirements`") +} + +# split into parts +parts <- strsplit(sysreqs, ", ")[[1]] + +# identify which is the rustc +rustc_ver <- parts[grepl("rustc", parts)] + +# perform checks for the presence of rustc and cargo on the OS +no_cargo_msg <- c( + "----------------------- [CARGO NOT FOUND]--------------------------", + "The 'cargo' command was not found on the PATH. Please install Cargo", + "from: https://www.rust-lang.org/tools/install", + "", + "Alternatively, you may install Cargo from your OS package manager:", + " - Debian/Ubuntu: apt-get install cargo", + " - Fedora/CentOS: dnf install cargo", + " - macOS: brew install rust", + "-------------------------------------------------------------------" +) + +no_rustc_msg <- c( + "----------------------- [RUST NOT FOUND]---------------------------", + "The 'rustc' compiler was not found on the PATH. Please install", + paste(rustc_ver, "or higher from:"), + "https://www.rust-lang.org/tools/install", + "", + "Alternatively, you may install Rust from your OS package manager:", + " - Debian/Ubuntu: apt-get install rustc", + " - Fedora/CentOS: dnf install rustc", + " - macOS: brew install rust", + "-------------------------------------------------------------------" +) + +# Add {user}/.cargo/bin to path before checking +new_path <- paste0( + Sys.getenv("PATH"), + ":", + paste0(Sys.getenv("HOME"), "/.cargo/bin") +) + +# set the path with the new path +Sys.setenv("PATH" = new_path) + +# check for rustc installation +rustc_version <- tryCatch( + system("rustc --version", intern = TRUE), + error = function(e) { + stop(paste(no_rustc_msg, collapse = "\n")) + } +) + +# check for cargo installation +cargo_version <- tryCatch( + system("cargo --version", intern = TRUE), + error = function(e) { + stop(paste(no_cargo_msg, collapse = "\n")) + } +) + +# helper function to extract versions +extract_semver <- function(ver) { + if (grepl("\\d+\\.\\d+(\\.\\d+)?", ver)) { + sub(".*?(\\d+\\.\\d+(\\.\\d+)?).*", "\\1", ver) + } else { + NA + } +} + +# get the MSRV +msrv <- extract_semver(rustc_ver) + +# extract current version +current_rust_version <- extract_semver(rustc_version) + +# perform check +if (!is.na(msrv)) { + # -1 when current version is later + # 0 when they are the same + # 1 when MSRV is newer than current + is_msrv <- utils::compareVersion(msrv, current_rust_version) + if (is_msrv == 1) { + fmt <- paste0( + "\n------------------ [UNSUPPORTED RUST VERSION]------------------\n", + "- Minimum supported Rust version is %s.\n", + "- Installed Rust version is %s.\n", + "---------------------------------------------------------------" + ) + stop(sprintf(fmt, msrv, current_rust_version)) + } +} + +# print the versions +versions_fmt <- "Using %s\nUsing %s" +message(sprintf(versions_fmt, cargo_version, rustc_version))