diff --git a/.cargo/config.toml b/.cargo/config.toml index 577c74660..2f21861d5 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,16 +1,3 @@ - -[target.x86_64-apple-darwin] -rustflags = [ - "-C", "link-arg=-undefined", - "-C", "link-arg=dynamic_lookup", -] - -[target.aarch64-apple-darwin] -rustflags = [ - "-C", "link-arg=-undefined", - "-C", "link-arg=dynamic_lookup", -] - # Increase stack space on Windows to 8m # See https://github.com/posit-dev/positron/issues/1975 diff --git a/.github/workflows/amalthea-ci.yml b/.github/workflows/amalthea-ci.yml index 14955f12e..77354d07b 100644 --- a/.github/workflows/amalthea-ci.yml +++ b/.github/workflows/amalthea-ci.yml @@ -27,6 +27,9 @@ jobs: run: | cargo build --verbose + # Ubuntu runners already have a version of R installed. + # Unit tests "automatically" find and set `R_HOME` if it isn't set by + # calling `R RHOME`. See the testing version of `start_r()` for this. - name: Run Unit Tests id: amalthea-test run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b56f01b5..98842a39c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -123,29 +123,12 @@ jobs: run: | echo "~/${{matrix.homebrew_folder}}/bin" >> $GITHUB_PATH - # Determine R_HOME (for building ark) - - name: Find Homebrew R installation - id: r_installation - run: | - # Path to the Homebrew build of R, e.g. /Users/user229818/homebrew/Cellar/r - R_FOLDER=~/${{matrix.homebrew_folder}}/Cellar/r - # Get the first (and generally) only installed version, e.g. 4.2.2 - R_VERSION=$(ls ${R_FOLDER} | head -1) - # Form the path to the R binary, e.g. /Users/user229818/homebrew/Cellar/r/4.2.2/bin/R - R_EXECUTABLE="${R_FOLDER}/${R_VERSION}/bin/R" - # Invoke the R binary to determine its RHOME directory (usually lib/R) - R_HOME=$(${R_EXECUTABLE} RHOME) - # Output the result for consumption in later steps - echo "Using R at ${R_HOME}" - echo "r_home=${R_HOME}" >> $GITHUB_OUTPUT - # Compile - name: Compile ARK (${{ matrix.arch }}) env: npm_config_arch: ${{ matrix.arch }} ARK_BUILD_TYPE: ${{ matrix.flavor }} RUST_TARGET: ${{ matrix.rust_target_prefix }}-apple-darwin - R_HOME: ${{ steps.r_installation.outputs.r_home }} CARGO_FLAGS: PKG_CONFIG_ALLOW_CROSS: 1 run: | @@ -158,22 +141,10 @@ jobs: # Enter the build directory pushd target/${{ matrix.rust_target_prefix }}-apple-darwin/${{ matrix.flavor }} - # On macOS, we use install_name_tool to fix up the link to libR.dylib. - # - # Note that we still try to link with '-undefined dynamic_lookup', just to ensure that - # linking succeeds when we compile against a version of R compiled for a different - # architecture. This is mostly relevant when producing x86_64 builds of ark on an arm64 - # machine. - # - # However, because using libR-sys still implies that the path to the R library ends up in - # the library load list, we have to modify that after the fact anyhow. - OLD_LIBR_PATH=$(otool -L ark | grep libR.dylib | cut -c2- | cut -d ' ' -f1) - echo "Fixing path to shared R library at ${OLD_LIBR_PATH}..." - install_name_tool -change "${OLD_LIBR_PATH}" "@rpath/libR.dylib" ark - # Compress the kernel to an archive ARCHIVE="$GITHUB_WORKSPACE/ark-${{ needs.get_version.outputs.ARK_VERSION }}-${{ matrix.flavor }}-darwin-${{ matrix.arch }}.zip" zip -Xry $ARCHIVE ark + popd # Create build artifact @@ -204,33 +175,10 @@ jobs: - name: Checkout sources uses: actions/checkout@v4 - - name: Setup R - uses: r-lib/actions/setup-r@v2 - with: - r-version: '4.3.2' - - - name: Find R installation - id: r_installation - shell: bash - run: | - R_HOME="C:\R" - R_SCRIPT="${R_HOME}\bin\x64\Rscript.exe" - echo "Using R at ${R_HOME}" - echo "Using Rscript at ${R_SCRIPT}" - # Output the result for consumption in later steps - echo "r_home=${R_HOME}" >> $GITHUB_OUTPUT - echo "r_script=${R_SCRIPT}" >> $GITHUB_OUTPUT - - - name: Generate LIB from DLL - shell: cmd - run: | - ${{ steps.r_installation.outputs.r_script }} "scripts\windows\dll2lib.R" - - name: Compile ARK env: ARK_BUILD_TYPE: ${{ matrix.flavor }} RUST_TARGET: ${{ matrix.rust_target_prefix }}-pc-windows-msvc - R_HOME: ${{ steps.r_installation.outputs.r_home }} shell: cmd run: | cargo clean diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c720f6e16..941ea1983 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,18 +3,7 @@ "tasks": [ { "type": "shell", - "osx": { - "command": "cargo build && scripts/post-install.sh" - }, - "windows": { - // Note that the environment variable, R_HOME, must be set prior to `cargo build` - // in order to build the ark dependency libR-sys. Once libR-sys is built, - // R_HOME is not required for subsequent runs of `cargo build`. - // Prior to running `cargo build`, execute something like this: - // PowerShell: $env:R_HOME = "C:\\Program Files\\R\\R-4.3.2" - // bash: export R_HOME="C:\\Program Files\\R\\R-4.3.2" - "command": "cargo build" - }, + "command": "cargo build", "problemMatcher": [ "$rustc" ], diff --git a/Cargo.lock b/Cargo.lock index ca3d51536..f10d4bea9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,8 +318,8 @@ dependencies = [ "hyper", "itertools", "lazy_static", - "libR-shim", "libc", + "libr", "log", "nix", "notify", @@ -394,28 +394,6 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" -[[package]] -name = "bindgen" -version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" -dependencies = [ - "bitflags 1.3.2", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 1.0.109", - "which", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -525,15 +503,6 @@ dependencies = [ "jobserver", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-expr" version = "0.15.1" @@ -566,27 +535,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "clang" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c044c781163c001b913cd018fc95a628c50d0d2dfea8bca77dad71edb16e37" -dependencies = [ - "clang-sys", - "libc", -] - -[[package]] -name = "clang-sys" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "convert_case" version = "0.4.0" @@ -947,23 +895,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -1188,12 +1125,6 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "h2" version = "0.3.19" @@ -1224,8 +1155,9 @@ dependencies = [ "harp-macros", "itertools", "lazy_static", - "libR-shim", "libc", + "libloading", + "libr", "log", "once_cell", "parking_lot", @@ -1578,42 +1510,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] -name = "lazycell" -version = "1.3.0" +name = "libc" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] -name = "libR-shim" -version = "0.1.0" -dependencies = [ - "libR-sys", -] - -[[package]] -name = "libR-sys" -version = "0.5.0" +name = "libloading" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470a5563a65d7ca413e2fda72d6e13d0b732c4b52ed561d774f6f349b6bdf1fb" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ - "bindgen", - "clang", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] -name = "libc" -version = "0.2.146" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +name = "libr" +version = "0.1.0" dependencies = [ "cfg-if", - "winapi", + "libc", + "libloading", + "paste", ] [[package]] @@ -1630,9 +1549,9 @@ checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" [[package]] name = "linux-raw-sys" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "local-channel" @@ -1745,12 +1664,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.6.2" @@ -1825,16 +1738,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "notify" version = "6.0.0" @@ -1981,12 +1884,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "percent-encoding" version = "2.3.0" @@ -2313,6 +2210,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -2441,12 +2347,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc_version" version = "0.4.0" @@ -2472,15 +2372,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.3" +version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.8", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.13", + "windows-sys 0.52.0", ] [[package]] @@ -2706,12 +2606,6 @@ dependencies = [ "digest", ] -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2909,15 +2803,15 @@ checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" [[package]] name = "tempfile" -version = "3.8.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", - "rustix 0.38.3", - "windows-sys 0.48.0", + "redox_syscall 0.4.1", + "rustix 0.38.30", + "windows-sys 0.52.0", ] [[package]] @@ -3455,17 +3349,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] - [[package]] name = "winapi" version = "0.3.9" @@ -3524,6 +3407,15 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -3554,6 +3446,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -3566,6 +3473,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -3578,6 +3491,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -3590,6 +3509,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -3602,6 +3527,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -3614,6 +3545,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -3626,6 +3563,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -3638,6 +3581,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.4.6" diff --git a/Cargo.toml b/Cargo.toml index c60e892c3..de423827b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ "crates/ark", "crates/echo", "crates/harp", - "crates/libR-shim", + "crates/libr", "crates/stdext" ] diff --git a/README.md b/README.md index 7e0cf2269..8e01c0cb1 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ a[Amalthea] <--Message Handlers--> ark(((Amalthea R Kernel - ark))) a <--ZeroMQ--> jf[Jupyter Frontend] ark <--> lsp[Language Protocol Server] ark <--> h[harp R wrapper] -ark <--> libr[libR-sys bindings] +ark <--> libr[Rust R bindings] h <--> libr libr <--> r[R Shared Library] lsp <--> h @@ -45,10 +45,10 @@ $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh $ source $HOME/.cargo/env ``` -Assuming you have a working Rust toolchain, you can just run `cargo build` followed by the post-install script: +Assuming you have a working Rust toolchain, you can just run `cargo build`: ```bash -$ cargo build && scripts/post-install.sh +$ cargo build ``` #### Standalone diff --git a/crates/ark/Cargo.toml b/crates/ark/Cargo.toml index ea3f2f977..21e87114f 100644 --- a/crates/ark/Cargo.toml +++ b/crates/ark/Cargo.toml @@ -29,8 +29,8 @@ home = "0.5.5" hyper = { version = "0.14.24", features = ["full"] } itertools = "0.10.5" lazy_static = "1.4.0" -libR-shim = { path = "../libR-shim" } libc = "0.2" +libr = { path = "../libr" } log = "0.4.17" nix = { version = "0.26.2", features = ["signal"] } notify = "6.0.0" diff --git a/crates/ark/src/browser.rs b/crates/ark/src/browser.rs index b31acd4f1..c0294b0dd 100644 --- a/crates/ark/src/browser.rs +++ b/crates/ark/src/browser.rs @@ -9,7 +9,8 @@ use std::process::Command; use anyhow::Result; use harp::object::RObject; -use libR_shim::*; +use libr::Rf_ScalarLogical; +use libr::SEXP; use crate::help::message::HelpReply; use crate::help::message::HelpRequest; diff --git a/crates/ark/src/connections/r_connection.rs b/crates/ark/src/connections/r_connection.rs index 76a5de776..a037cdfce 100644 --- a/crates/ark/src/connections/r_connection.rs +++ b/crates/ark/src/connections/r_connection.rs @@ -12,7 +12,8 @@ use crossbeam::channel::Sender; use harp::exec::RFunction; use harp::exec::RFunctionExt; use harp::object::RObject; -use libR_shim::*; +use libr::R_NilValue; +use libr::SEXP; use serde::Deserialize; use serde::Serialize; use stdext::result::ResultOrLog; diff --git a/crates/ark/src/data_viewer/r_data_viewer.rs b/crates/ark/src/data_viewer/r_data_viewer.rs index 10ea33753..85a0a49cd 100644 --- a/crates/ark/src/data_viewer/r_data_viewer.rs +++ b/crates/ark/src/data_viewer/r_data_viewer.rs @@ -21,17 +21,17 @@ use harp::utils::r_typeof; use harp::vector::formatted_vector::FormattedVector; use harp::vector::CharacterVector; use harp::vector::Vector; -use libR_shim::R_DimSymbol; -use libR_shim::R_MissingArg; -use libR_shim::R_NamesSymbol; -use libR_shim::R_NilValue; -use libR_shim::R_RowNamesSymbol; -use libR_shim::Rf_getAttrib; -use libR_shim::INTEGER_ELT; -use libR_shim::SEXP; -use libR_shim::STRSXP; -use libR_shim::VECTOR_ELT; -use libR_shim::XLENGTH; +use libr::R_DimSymbol; +use libr::R_MissingArg; +use libr::R_NamesSymbol; +use libr::R_NilValue; +use libr::R_RowNamesSymbol; +use libr::Rf_getAttrib; +use libr::Rf_xlength; +use libr::INTEGER_ELT; +use libr::SEXP; +use libr::STRSXP; +use libr::VECTOR_ELT; use serde::Deserialize; use serde::Serialize; use stdext::local; @@ -125,7 +125,7 @@ impl DataSet { unsafe { let names = ColumnNames::new(Rf_getAttrib(object, R_NamesSymbol)); - let n_columns = XLENGTH(object); + let n_columns = Rf_xlength(object); for i in 0..n_columns { let col_name = names.get_unchecked(i); @@ -205,7 +205,7 @@ impl DataSet { let row_count = { if r_is_data_frame(*object) { let row_names = Rf_getAttrib(*object, R_RowNamesSymbol); - XLENGTH(row_names) as usize + Rf_xlength(row_names) as usize } else if r_is_matrix(*object) { let dim = Rf_getAttrib(*object, R_DimSymbol); INTEGER_ELT(dim, 0) as usize diff --git a/crates/ark/src/errors.rs b/crates/ark/src/errors.rs index dd97ab8c6..5e1044b35 100644 --- a/crates/ark/src/errors.rs +++ b/crates/ark/src/errors.rs @@ -7,7 +7,11 @@ use harp::object::RObject; use harp::r_symbol; -use libR_shim::*; +use libr::R_GlobalEnv; +use libr::R_NilValue; +use libr::Rf_eval; +use libr::Rf_lcons; +use libr::SEXP; use log::info; use log::warn; use stdext::unwrap; diff --git a/crates/ark/src/html_widget.rs b/crates/ark/src/html_widget.rs index 7ea66626b..79124af12 100644 --- a/crates/ark/src/html_widget.rs +++ b/crates/ark/src/html_widget.rs @@ -10,8 +10,8 @@ use std::result::Result::Ok; use amalthea::socket::iopub::IOPubMessage; use amalthea::wire::display_data::DisplayData; use harp::object::RObject; -use libR_shim::R_NilValue; -use libR_shim::SEXP; +use libr::R_NilValue; +use libr::SEXP; use serde_json::Value; use crate::interface::RMain; diff --git a/crates/ark/src/interface.rs b/crates/ark/src/interface.rs index 5d6c94662..65cf127e0 100644 --- a/crates/ark/src/interface.rs +++ b/crates/ark/src/interface.rs @@ -13,6 +13,7 @@ use std::collections::VecDeque; use std::ffi::*; use std::os::raw::c_uchar; +use std::path::PathBuf; use std::result::Result::Ok; use std::sync::Arc; use std::sync::Mutex; @@ -62,6 +63,7 @@ use harp::exec::r_source; use harp::exec::RFunction; use harp::exec::RFunctionExt; use harp::exec::RSandboxScope; +use harp::library::RLibraries; use harp::line_ending::convert_line_endings; use harp::line_ending::LineEnding; use harp::object::RObject; @@ -72,14 +74,14 @@ use harp::utils::r_get_option; use harp::utils::r_is_data_frame; use harp::utils::r_poke_option_show_error_messages; use harp::R_MAIN_THREAD_ID; -use libR_shim::R_BaseNamespace; -use libR_shim::R_GlobalEnv; -use libR_shim::R_ProcessEvents; -use libR_shim::R_RunPendingFinalizers; -use libR_shim::Rf_error; -use libR_shim::Rf_findVarInFrame; -use libR_shim::Rf_onintr; -use libR_shim::SEXP; +use libr::R_BaseNamespace; +use libr::R_GlobalEnv; +use libr::R_ProcessEvents; +use libr::R_RunPendingFinalizers; +use libr::Rf_error; +use libr::Rf_findVarInFrame; +use libr::Rf_onintr; +use libr::SEXP; use log::*; use once_cell::sync::Lazy; use regex::Regex; @@ -168,8 +170,19 @@ pub fn start_r( args.push(CString::new(arg).unwrap().into_raw()); } + // Get `R_HOME`, set by Positron / CI / kernel specification + let r_home = match std::env::var("R_HOME") { + Ok(home) => PathBuf::from(home), + Err(err) => panic!("Can't find `R_HOME`: {err:?}"), + }; + + let libraries = RLibraries::from_r_home_path(&r_home); + libraries.initialize_pre_setup_r(); + crate::sys::interface::setup_r(args); + libraries.initialize_post_setup_r(); + unsafe { // Optionally run a user specified R startup script if let Some(file) = &startup_file { diff --git a/crates/ark/src/json.rs b/crates/ark/src/json.rs index 4423ce395..2c7bab911 100644 --- a/crates/ark/src/json.rs +++ b/crates/ark/src/json.rs @@ -6,7 +6,7 @@ // use harp::object::RObject; -use libR_shim::SEXP; +use libr::SEXP; /// Convenience method to convert a JSON object to a string #[harp::register] diff --git a/crates/ark/src/lsp/completions/completion_item.rs b/crates/ark/src/lsp/completions/completion_item.rs index 1cede8fb4..193c119cd 100644 --- a/crates/ark/src/lsp/completions/completion_item.rs +++ b/crates/ark/src/lsp/completions/completion_item.rs @@ -21,7 +21,13 @@ use harp::utils::r_symbol_quote; use harp::utils::r_symbol_quote_invalid; use harp::utils::r_symbol_valid; use harp::utils::r_typeof; -use libR_shim::*; +use libr::R_UnboundValue; +use libr::Rf_findVarInFrame; +use libr::Rf_isFunction; +use libr::ENCLOS; +use libr::PROMSXP; +use libr::PRVALUE; +use libr::SEXP; use stdext::*; use tower_lsp::lsp_types::Command; use tower_lsp::lsp_types::CompletionItem; diff --git a/crates/ark/src/lsp/completions/sources/composite/search_path.rs b/crates/ark/src/lsp/completions/sources/composite/search_path.rs index 6c9fee9d3..b0d8edfd4 100644 --- a/crates/ark/src/lsp/completions/sources/composite/search_path.rs +++ b/crates/ark/src/lsp/completions/sources/composite/search_path.rs @@ -12,10 +12,10 @@ use harp::utils::r_env_is_pkg_env; use harp::utils::r_envir_name; use harp::vector::CharacterVector; use harp::vector::Vector; -use libR_shim::R_EmptyEnv; -use libR_shim::R_GlobalEnv; -use libR_shim::R_lsInternal; -use libR_shim::ENCLOS; +use libr::R_EmptyEnv; +use libr::R_GlobalEnv; +use libr::R_lsInternal; +use libr::ENCLOS; use tower_lsp::lsp_types::CompletionItem; use crate::lsp::completions::completion_item::completion_item_from_package; diff --git a/crates/ark/src/lsp/completions/sources/unique/comment.rs b/crates/ark/src/lsp/completions/sources/unique/comment.rs index 949c86ea3..c72b18bc4 100644 --- a/crates/ark/src/lsp/completions/sources/unique/comment.rs +++ b/crates/ark/src/lsp/completions/sources/unique/comment.rs @@ -141,7 +141,7 @@ fn test_comment() { #[test] fn test_roxygen_comment() { - use libR_shim::LOGICAL_ELT; + use libr::LOGICAL_ELT; use tree_sitter::Point; use crate::lsp::documents::Document; diff --git a/crates/ark/src/lsp/completions/sources/unique/custom.rs b/crates/ark/src/lsp/completions/sources/unique/custom.rs index e3c355de9..de2aa3b39 100644 --- a/crates/ark/src/lsp/completions/sources/unique/custom.rs +++ b/crates/ark/src/lsp/completions/sources/unique/custom.rs @@ -11,9 +11,9 @@ use harp::exec::RFunctionExt; use harp::object::RObject; use harp::utils::r_symbol_quote_invalid; use harp::utils::r_typeof; -use libR_shim::R_NilValue; -use libR_shim::VECSXP; -use libR_shim::VECTOR_ELT; +use libr::R_NilValue; +use libr::VECSXP; +use libr::VECTOR_ELT; use stdext::unwrap; use stdext::IntoResult; use tower_lsp::lsp_types::CompletionItem; diff --git a/crates/ark/src/lsp/completions/sources/unique/extractor.rs b/crates/ark/src/lsp/completions/sources/unique/extractor.rs index 87a14e11d..d92c0d4eb 100644 --- a/crates/ark/src/lsp/completions/sources/unique/extractor.rs +++ b/crates/ark/src/lsp/completions/sources/unique/extractor.rs @@ -13,7 +13,7 @@ use harp::exec::RFunctionExt; use harp::r_symbol; use harp::utils::r_env_has; use harp::utils::r_typeof; -use libR_shim::STRSXP; +use libr::STRSXP; use tower_lsp::lsp_types::CompletionItem; use crate::lsp::completions::completion_item::completion_item_from_data_variable; diff --git a/crates/ark/src/lsp/completions/sources/unique/namespace.rs b/crates/ark/src/lsp/completions/sources/unique/namespace.rs index 7aee2c738..ef7a945d1 100644 --- a/crates/ark/src/lsp/completions/sources/unique/namespace.rs +++ b/crates/ark/src/lsp/completions/sources/unique/namespace.rs @@ -10,11 +10,11 @@ use harp::exec::RFunction; use harp::exec::RFunctionExt; use harp::object::RObject; use harp::r_symbol; -use libR_shim::R_UnboundValue; -use libR_shim::R_lsInternal; -use libR_shim::Rboolean_TRUE; -use libR_shim::Rf_findVarInFrame; -use libR_shim::SEXP; +use libr::R_UnboundValue; +use libr::R_lsInternal; +use libr::Rboolean_TRUE; +use libr::Rf_findVarInFrame; +use libr::SEXP; use tower_lsp::lsp_types::CompletionItem; use crate::lsp::completions::completion_item::completion_item_from_lazydata; diff --git a/crates/ark/src/lsp/diagnostics.rs b/crates/ark/src/lsp/diagnostics.rs index 77fcf5820..43bf7527f 100644 --- a/crates/ark/src/lsp/diagnostics.rs +++ b/crates/ark/src/lsp/diagnostics.rs @@ -23,7 +23,24 @@ use harp::utils::r_symbol_quote_invalid; use harp::utils::r_symbol_valid; use harp::vector::CharacterVector; use harp::vector::Vector; -use libR_shim::*; +use libr::R_EmptyEnv; +use libr::R_GlobalEnv; +use libr::R_NilValue; +use libr::R_lsInternal; +use libr::Rf_ScalarInteger; +use libr::Rf_allocVector; +use libr::Rf_cons; +use libr::Rf_lang1; +use libr::Rf_xlength; +use libr::CDR; +use libr::ENCLOS; +use libr::RAW; +use libr::RAWSXP; +use libr::SETCDR; +use libr::SET_TAG; +use libr::SET_VECTOR_ELT; +use libr::VECSXP; +use libr::VECTOR_ELT; use stdext::*; use tower_lsp::lsp_types::Diagnostic; use tower_lsp::lsp_types::DiagnosticSeverity; @@ -681,7 +698,7 @@ fn recurse_call_arguments_custom( .call()?; if !r_is_null(*custom_diagnostics) { - let n = XLENGTH(*custom_diagnostics); + let n = Rf_xlength(*custom_diagnostics); for i in 0..n { // diag is a list with: // - The kind of diagnostic: skip, default, simple diff --git a/crates/ark/src/lsp/editor.rs b/crates/ark/src/lsp/editor.rs index c98b52ddb..6fde88433 100644 --- a/crates/ark/src/lsp/editor.rs +++ b/crates/ark/src/lsp/editor.rs @@ -7,7 +7,8 @@ use harp::vector::CharacterVector; use harp::vector::Vector; -use libR_shim::*; +use libr::R_NilValue; +use libr::SEXP; use stdext::unwrap; use tower_lsp::lsp_types::ShowDocumentParams; use tower_lsp::lsp_types::Url; diff --git a/crates/ark/src/lsp/help.rs b/crates/ark/src/lsp/help.rs index 9c9c3ef48..a4cab441a 100644 --- a/crates/ark/src/lsp/help.rs +++ b/crates/ark/src/lsp/help.rs @@ -9,7 +9,7 @@ use anyhow::*; use harp::exec::RFunction; use harp::exec::RFunctionExt; use harp::utils::r_typeof; -use libR_shim::*; +use libr::NILSXP; use regex::Regex; use scraper::ElementRef; use scraper::Html; diff --git a/crates/ark/src/lsp/show_message.rs b/crates/ark/src/lsp/show_message.rs index 97e2b5d45..a5f70b5d6 100644 --- a/crates/ark/src/lsp/show_message.rs +++ b/crates/ark/src/lsp/show_message.rs @@ -8,7 +8,8 @@ use amalthea::comm::ui_comm::ShowMessageParams; use amalthea::comm::ui_comm::UiFrontendEvent; use harp::object::RObject; -use libR_shim::*; +use libr::R_NilValue; +use libr::SEXP; use stdext::unwrap; use crate::interface::RMain; diff --git a/crates/ark/src/lsp/treesitter.rs b/crates/ark/src/lsp/treesitter.rs index fc6d904f3..0597c2a53 100644 --- a/crates/ark/src/lsp/treesitter.rs +++ b/crates/ark/src/lsp/treesitter.rs @@ -7,8 +7,8 @@ use harp::external_ptr::ExternalPointer; use harp::object::RObject; -use libR_shim::RAW; -use libR_shim::SEXP; +use libr::RAW; +use libr::SEXP; use tree_sitter::Node; #[harp::register] diff --git a/crates/ark/src/lsp/util.rs b/crates/ark/src/lsp/util.rs index 54e4fda45..679305651 100644 --- a/crates/ark/src/lsp/util.rs +++ b/crates/ark/src/lsp/util.rs @@ -8,7 +8,9 @@ use std::os::raw::c_char; use harp::object::RObject; -use libR_shim::*; +use libr::R_NilValue; +use libr::Rf_mkString; +use libr::SEXP; /// Shows a message in the Positron frontend #[harp::register] diff --git a/crates/ark/src/main.rs b/crates/ark/src/main.rs index 33a3c8692..8279789eb 100644 --- a/crates/ark/src/main.rs +++ b/crates/ark/src/main.rs @@ -240,12 +240,6 @@ Available options: } fn main() { - #[cfg(target_os = "macos")] - { - // Unset DYLD_INSERT_LIBRARIES if it was passed down - std::env::remove_var("DYLD_INSERT_LIBRARIES"); - } - // Block signals in this thread (and any child threads). initialize_signal_block(); diff --git a/crates/ark/src/modules.rs b/crates/ark/src/modules.rs index 7385029df..f7687eeb5 100644 --- a/crates/ark/src/modules.rs +++ b/crates/ark/src/modules.rs @@ -17,7 +17,13 @@ use harp::protect::RProtect; use harp::r_string; use harp::r_symbol; use harp::utils::r_poke_option; -use libR_shim::*; +use libr::R_GlobalEnv; +use libr::R_NilValue; +use libr::R_PreserveObject; +use libr::Rf_ScalarLogical; +use libr::Rf_asInteger; +use libr::Rf_setAttrib; +use libr::SEXP; use stdext::local; use stdext::spawn; use stdext::unwrap; diff --git a/crates/ark/src/plots/dev_desc.rs b/crates/ark/src/plots/dev_desc.rs deleted file mode 100644 index fc8c32e20..000000000 --- a/crates/ark/src/plots/dev_desc.rs +++ /dev/null @@ -1,811 +0,0 @@ -// -// dev_desc.rs -// -// Copyright (C) 2022 by Posit Software, PBC -// -// - -#![allow(non_snake_case)] - -use libR_shim::*; - -// This file captures the device description for the different versions -// of R graphics engines that we support. - -// Graphics Engine version 13 (R 4.0.x) -#[repr(C)] -#[derive(Copy, Clone)] -pub struct DevDescVersion13 { - pub left: f64, - pub right: f64, - pub bottom: f64, - pub top: f64, - pub clipLeft: f64, - pub clipRight: f64, - pub clipBottom: f64, - pub clipTop: f64, - pub xCharOffset: f64, - pub yCharOffset: f64, - pub yLineBias: f64, - pub ipr: [f64; 2usize], - pub cra: [f64; 2usize], - pub gamma: f64, - pub canClip: Rboolean, - pub canChangeGamma: Rboolean, - pub canHAdj: ::std::os::raw::c_int, - pub startps: f64, - pub startcol: ::std::os::raw::c_int, - pub startfill: ::std::os::raw::c_int, - pub startlty: ::std::os::raw::c_int, - pub startfont: ::std::os::raw::c_int, - pub startgamma: f64, - pub deviceSpecific: *mut ::std::os::raw::c_void, - pub displayListOn: Rboolean, - pub canGenMouseDown: Rboolean, - pub canGenMouseMove: Rboolean, - pub canGenMouseUp: Rboolean, - pub canGenKeybd: Rboolean, - pub canGenIdle: Rboolean, - pub gettingEvent: Rboolean, - pub activate: ::std::option::Option, - pub circle: ::std::option::Option< - unsafe extern "C" fn(x: f64, y: f64, r: f64, gc: pGEcontext, dd: pDevDesc), - >, - pub clip: ::std::option::Option< - unsafe extern "C" fn(x0: f64, x1: f64, y0: f64, y1: f64, dd: pDevDesc), - >, - pub close: ::std::option::Option, - pub deactivate: ::std::option::Option, - pub locator: ::std::option::Option< - unsafe extern "C" fn(x: *mut f64, y: *mut f64, dd: pDevDesc) -> Rboolean, - >, - pub line: ::std::option::Option< - unsafe extern "C" fn(x1: f64, y1: f64, x2: f64, y2: f64, gc: pGEcontext, dd: pDevDesc), - >, - pub metricInfo: ::std::option::Option< - unsafe extern "C" fn( - c: ::std::os::raw::c_int, - gc: pGEcontext, - ascent: *mut f64, - descent: *mut f64, - width: *mut f64, - dd: pDevDesc, - ), - >, - pub mode: - ::std::option::Option, - pub newPage: ::std::option::Option, - pub polygon: ::std::option::Option< - unsafe extern "C" fn( - n: ::std::os::raw::c_int, - x: *mut f64, - y: *mut f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub polyline: ::std::option::Option< - unsafe extern "C" fn( - n: ::std::os::raw::c_int, - x: *mut f64, - y: *mut f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub rect: ::std::option::Option< - unsafe extern "C" fn(x0: f64, y0: f64, x1: f64, y1: f64, gc: pGEcontext, dd: pDevDesc), - >, - pub path: ::std::option::Option< - unsafe extern "C" fn( - x: *mut f64, - y: *mut f64, - npoly: ::std::os::raw::c_int, - nper: *mut ::std::os::raw::c_int, - winding: Rboolean, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub raster: ::std::option::Option< - unsafe extern "C" fn( - raster: *mut ::std::os::raw::c_uint, - w: ::std::os::raw::c_int, - h: ::std::os::raw::c_int, - x: f64, - y: f64, - width: f64, - height: f64, - rot: f64, - interpolate: Rboolean, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub cap: ::std::option::Option SEXP>, - pub size: ::std::option::Option< - unsafe extern "C" fn( - left: *mut f64, - right: *mut f64, - bottom: *mut f64, - top: *mut f64, - dd: pDevDesc, - ), - >, - pub strWidth: ::std::option::Option< - unsafe extern "C" fn( - str: *const ::std::os::raw::c_char, - gc: pGEcontext, - dd: pDevDesc, - ) -> f64, - >, - pub text: ::std::option::Option< - unsafe extern "C" fn( - x: f64, - y: f64, - str: *const ::std::os::raw::c_char, - rot: f64, - hadj: f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub onExit: ::std::option::Option, - pub getEvent: ::std::option::Option< - unsafe extern "C" fn(arg1: SEXP, arg2: *const ::std::os::raw::c_char) -> SEXP, - >, - pub newFrameConfirm: ::std::option::Option Rboolean>, - pub hasTextUTF8: Rboolean, - pub textUTF8: ::std::option::Option< - unsafe extern "C" fn( - x: f64, - y: f64, - str: *const ::std::os::raw::c_char, - rot: f64, - hadj: f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub strWidthUTF8: ::std::option::Option< - unsafe extern "C" fn( - str: *const ::std::os::raw::c_char, - gc: pGEcontext, - dd: pDevDesc, - ) -> f64, - >, - pub wantSymbolUTF8: Rboolean, - pub useRotatedTextInContour: Rboolean, - pub eventEnv: SEXP, - pub eventHelper: - ::std::option::Option, - pub holdflush: ::std::option::Option< - unsafe extern "C" fn(dd: pDevDesc, level: ::std::os::raw::c_int) -> ::std::os::raw::c_int, - >, - pub haveTransparency: ::std::os::raw::c_int, - pub haveTransparentBg: ::std::os::raw::c_int, - pub haveRaster: ::std::os::raw::c_int, - pub haveCapture: ::std::os::raw::c_int, - pub haveLocator: ::std::os::raw::c_int, - pub reserved: [::std::os::raw::c_char; 64usize], -} - -// Graphics Engine version 14 (R 4.1.x) -#[repr(C)] -#[derive(Copy, Clone)] -pub struct DevDescVersion14 { - pub left: f64, - pub right: f64, - pub bottom: f64, - pub top: f64, - pub clipLeft: f64, - pub clipRight: f64, - pub clipBottom: f64, - pub clipTop: f64, - pub xCharOffset: f64, - pub yCharOffset: f64, - pub yLineBias: f64, - pub ipr: [f64; 2usize], - pub cra: [f64; 2usize], - pub gamma: f64, - pub canClip: Rboolean, - pub canChangeGamma: Rboolean, - pub canHAdj: ::std::os::raw::c_int, - pub startps: f64, - pub startcol: ::std::os::raw::c_int, - pub startfill: ::std::os::raw::c_int, - pub startlty: ::std::os::raw::c_int, - pub startfont: ::std::os::raw::c_int, - pub startgamma: f64, - pub deviceSpecific: *mut ::std::os::raw::c_void, - pub displayListOn: Rboolean, - pub canGenMouseDown: Rboolean, - pub canGenMouseMove: Rboolean, - pub canGenMouseUp: Rboolean, - pub canGenKeybd: Rboolean, - pub canGenIdle: Rboolean, - pub gettingEvent: Rboolean, - pub activate: ::std::option::Option, - pub circle: ::std::option::Option< - unsafe extern "C" fn(x: f64, y: f64, r: f64, gc: pGEcontext, dd: pDevDesc), - >, - pub clip: ::std::option::Option< - unsafe extern "C" fn(x0: f64, x1: f64, y0: f64, y1: f64, dd: pDevDesc), - >, - pub close: ::std::option::Option, - pub deactivate: ::std::option::Option, - pub locator: ::std::option::Option< - unsafe extern "C" fn(x: *mut f64, y: *mut f64, dd: pDevDesc) -> Rboolean, - >, - pub line: ::std::option::Option< - unsafe extern "C" fn(x1: f64, y1: f64, x2: f64, y2: f64, gc: pGEcontext, dd: pDevDesc), - >, - pub metricInfo: ::std::option::Option< - unsafe extern "C" fn( - c: ::std::os::raw::c_int, - gc: pGEcontext, - ascent: *mut f64, - descent: *mut f64, - width: *mut f64, - dd: pDevDesc, - ), - >, - pub mode: - ::std::option::Option, - pub newPage: ::std::option::Option, - pub polygon: ::std::option::Option< - unsafe extern "C" fn( - n: ::std::os::raw::c_int, - x: *mut f64, - y: *mut f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub polyline: ::std::option::Option< - unsafe extern "C" fn( - n: ::std::os::raw::c_int, - x: *mut f64, - y: *mut f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub rect: ::std::option::Option< - unsafe extern "C" fn(x0: f64, y0: f64, x1: f64, y1: f64, gc: pGEcontext, dd: pDevDesc), - >, - pub path: ::std::option::Option< - unsafe extern "C" fn( - x: *mut f64, - y: *mut f64, - npoly: ::std::os::raw::c_int, - nper: *mut ::std::os::raw::c_int, - winding: Rboolean, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub raster: ::std::option::Option< - unsafe extern "C" fn( - raster: *mut ::std::os::raw::c_uint, - w: ::std::os::raw::c_int, - h: ::std::os::raw::c_int, - x: f64, - y: f64, - width: f64, - height: f64, - rot: f64, - interpolate: Rboolean, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub cap: ::std::option::Option SEXP>, - pub size: ::std::option::Option< - unsafe extern "C" fn( - left: *mut f64, - right: *mut f64, - bottom: *mut f64, - top: *mut f64, - dd: pDevDesc, - ), - >, - pub strWidth: ::std::option::Option< - unsafe extern "C" fn( - str: *const ::std::os::raw::c_char, - gc: pGEcontext, - dd: pDevDesc, - ) -> f64, - >, - pub text: ::std::option::Option< - unsafe extern "C" fn( - x: f64, - y: f64, - str: *const ::std::os::raw::c_char, - rot: f64, - hadj: f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub onExit: ::std::option::Option, - pub getEvent: ::std::option::Option< - unsafe extern "C" fn(arg1: SEXP, arg2: *const ::std::os::raw::c_char) -> SEXP, - >, - pub newFrameConfirm: ::std::option::Option Rboolean>, - pub hasTextUTF8: Rboolean, - pub textUTF8: ::std::option::Option< - unsafe extern "C" fn( - x: f64, - y: f64, - str: *const ::std::os::raw::c_char, - rot: f64, - hadj: f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub strWidthUTF8: ::std::option::Option< - unsafe extern "C" fn( - str: *const ::std::os::raw::c_char, - gc: pGEcontext, - dd: pDevDesc, - ) -> f64, - >, - pub wantSymbolUTF8: Rboolean, - pub useRotatedTextInContour: Rboolean, - pub eventEnv: SEXP, - pub eventHelper: - ::std::option::Option, - pub holdflush: ::std::option::Option< - unsafe extern "C" fn(dd: pDevDesc, level: ::std::os::raw::c_int) -> ::std::os::raw::c_int, - >, - pub haveTransparency: ::std::os::raw::c_int, - pub haveTransparentBg: ::std::os::raw::c_int, - pub haveRaster: ::std::os::raw::c_int, - pub haveCapture: ::std::os::raw::c_int, - pub haveLocator: ::std::os::raw::c_int, - pub setPattern: - ::std::option::Option SEXP>, - pub releasePattern: ::std::option::Option, - pub setClipPath: - ::std::option::Option SEXP>, - pub releaseClipPath: ::std::option::Option, - pub setMask: - ::std::option::Option SEXP>, - pub releaseMask: ::std::option::Option, - pub deviceVersion: ::std::os::raw::c_int, - pub deviceClip: Rboolean, - pub reserved: [::std::os::raw::c_char; 64usize], -} - -// Graphics Engine version 15 (R 4.2.x) -#[repr(C)] -#[derive(Copy, Clone)] -pub struct DevDescVersion15 { - pub left: f64, - pub right: f64, - pub bottom: f64, - pub top: f64, - pub clipLeft: f64, - pub clipRight: f64, - pub clipBottom: f64, - pub clipTop: f64, - pub xCharOffset: f64, - pub yCharOffset: f64, - pub yLineBias: f64, - pub ipr: [f64; 2usize], - pub cra: [f64; 2usize], - pub gamma: f64, - pub canClip: Rboolean, - pub canChangeGamma: Rboolean, - pub canHAdj: ::std::os::raw::c_int, - pub startps: f64, - pub startcol: ::std::os::raw::c_int, - pub startfill: ::std::os::raw::c_int, - pub startlty: ::std::os::raw::c_int, - pub startfont: ::std::os::raw::c_int, - pub startgamma: f64, - pub deviceSpecific: *mut ::std::os::raw::c_void, - pub displayListOn: Rboolean, - pub canGenMouseDown: Rboolean, - pub canGenMouseMove: Rboolean, - pub canGenMouseUp: Rboolean, - pub canGenKeybd: Rboolean, - pub canGenIdle: Rboolean, - pub gettingEvent: Rboolean, - pub activate: ::std::option::Option, - pub circle: ::std::option::Option< - unsafe extern "C" fn(x: f64, y: f64, r: f64, gc: pGEcontext, dd: pDevDesc), - >, - pub clip: ::std::option::Option< - unsafe extern "C" fn(x0: f64, x1: f64, y0: f64, y1: f64, dd: pDevDesc), - >, - pub close: ::std::option::Option, - pub deactivate: ::std::option::Option, - pub locator: ::std::option::Option< - unsafe extern "C" fn(x: *mut f64, y: *mut f64, dd: pDevDesc) -> Rboolean, - >, - pub line: ::std::option::Option< - unsafe extern "C" fn(x1: f64, y1: f64, x2: f64, y2: f64, gc: pGEcontext, dd: pDevDesc), - >, - pub metricInfo: ::std::option::Option< - unsafe extern "C" fn( - c: ::std::os::raw::c_int, - gc: pGEcontext, - ascent: *mut f64, - descent: *mut f64, - width: *mut f64, - dd: pDevDesc, - ), - >, - pub mode: - ::std::option::Option, - pub newPage: ::std::option::Option, - pub polygon: ::std::option::Option< - unsafe extern "C" fn( - n: ::std::os::raw::c_int, - x: *mut f64, - y: *mut f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub polyline: ::std::option::Option< - unsafe extern "C" fn( - n: ::std::os::raw::c_int, - x: *mut f64, - y: *mut f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub rect: ::std::option::Option< - unsafe extern "C" fn(x0: f64, y0: f64, x1: f64, y1: f64, gc: pGEcontext, dd: pDevDesc), - >, - pub path: ::std::option::Option< - unsafe extern "C" fn( - x: *mut f64, - y: *mut f64, - npoly: ::std::os::raw::c_int, - nper: *mut ::std::os::raw::c_int, - winding: Rboolean, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub raster: ::std::option::Option< - unsafe extern "C" fn( - raster: *mut ::std::os::raw::c_uint, - w: ::std::os::raw::c_int, - h: ::std::os::raw::c_int, - x: f64, - y: f64, - width: f64, - height: f64, - rot: f64, - interpolate: Rboolean, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub cap: ::std::option::Option SEXP>, - pub size: ::std::option::Option< - unsafe extern "C" fn( - left: *mut f64, - right: *mut f64, - bottom: *mut f64, - top: *mut f64, - dd: pDevDesc, - ), - >, - pub strWidth: ::std::option::Option< - unsafe extern "C" fn( - str: *const ::std::os::raw::c_char, - gc: pGEcontext, - dd: pDevDesc, - ) -> f64, - >, - pub text: ::std::option::Option< - unsafe extern "C" fn( - x: f64, - y: f64, - str: *const ::std::os::raw::c_char, - rot: f64, - hadj: f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub onExit: ::std::option::Option, - pub getEvent: ::std::option::Option< - unsafe extern "C" fn(arg1: SEXP, arg2: *const ::std::os::raw::c_char) -> SEXP, - >, - pub newFrameConfirm: ::std::option::Option Rboolean>, - pub hasTextUTF8: Rboolean, - pub textUTF8: ::std::option::Option< - unsafe extern "C" fn( - x: f64, - y: f64, - str: *const ::std::os::raw::c_char, - rot: f64, - hadj: f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub strWidthUTF8: ::std::option::Option< - unsafe extern "C" fn( - str: *const ::std::os::raw::c_char, - gc: pGEcontext, - dd: pDevDesc, - ) -> f64, - >, - pub wantSymbolUTF8: Rboolean, - pub useRotatedTextInContour: Rboolean, - pub eventEnv: SEXP, - pub eventHelper: - ::std::option::Option, - pub holdflush: ::std::option::Option< - unsafe extern "C" fn(dd: pDevDesc, level: ::std::os::raw::c_int) -> ::std::os::raw::c_int, - >, - pub haveTransparency: ::std::os::raw::c_int, - pub haveTransparentBg: ::std::os::raw::c_int, - pub haveRaster: ::std::os::raw::c_int, - pub haveCapture: ::std::os::raw::c_int, - pub haveLocator: ::std::os::raw::c_int, - pub setPattern: - ::std::option::Option SEXP>, - pub releasePattern: ::std::option::Option, - pub setClipPath: - ::std::option::Option SEXP>, - pub releaseClipPath: ::std::option::Option, - pub setMask: - ::std::option::Option SEXP>, - pub releaseMask: ::std::option::Option, - pub deviceVersion: ::std::os::raw::c_int, - pub deviceClip: Rboolean, - pub defineGroup: ::std::option::Option< - unsafe extern "C" fn( - source: SEXP, - op: ::std::os::raw::c_int, - destination: SEXP, - dd: pDevDesc, - ) -> SEXP, - >, - pub useGroup: - ::std::option::Option, - pub releaseGroup: ::std::option::Option, - pub stroke: - ::std::option::Option, - pub fill: ::std::option::Option< - unsafe extern "C" fn(path: SEXP, rule: ::std::os::raw::c_int, gc: pGEcontext, dd: pDevDesc), - >, - pub fillStroke: ::std::option::Option< - unsafe extern "C" fn(path: SEXP, rule: ::std::os::raw::c_int, gc: pGEcontext, dd: pDevDesc), - >, - pub capabilities: ::std::option::Option SEXP>, - pub reserved: [::std::os::raw::c_char; 64usize], -} - -// Graphics Engine version 16 (R 4.3.0) -#[repr(C)] -#[derive(Copy, Clone)] -pub struct DevDescVersion16 { - pub left: f64, - pub right: f64, - pub bottom: f64, - pub top: f64, - pub clipLeft: f64, - pub clipRight: f64, - pub clipBottom: f64, - pub clipTop: f64, - pub xCharOffset: f64, - pub yCharOffset: f64, - pub yLineBias: f64, - pub ipr: [f64; 2usize], - pub cra: [f64; 2usize], - pub gamma: f64, - pub canClip: Rboolean, - pub canChangeGamma: Rboolean, - pub canHAdj: ::std::os::raw::c_int, - pub startps: f64, - pub startcol: ::std::os::raw::c_int, - pub startfill: ::std::os::raw::c_int, - pub startlty: ::std::os::raw::c_int, - pub startfont: ::std::os::raw::c_int, - pub startgamma: f64, - pub deviceSpecific: *mut ::std::os::raw::c_void, - pub displayListOn: Rboolean, - pub canGenMouseDown: Rboolean, - pub canGenMouseMove: Rboolean, - pub canGenMouseUp: Rboolean, - pub canGenKeybd: Rboolean, - pub canGenIdle: Rboolean, - pub gettingEvent: Rboolean, - pub activate: ::std::option::Option, - pub circle: ::std::option::Option< - unsafe extern "C" fn(x: f64, y: f64, r: f64, gc: pGEcontext, dd: pDevDesc), - >, - pub clip: ::std::option::Option< - unsafe extern "C" fn(x0: f64, x1: f64, y0: f64, y1: f64, dd: pDevDesc), - >, - pub close: ::std::option::Option, - pub deactivate: ::std::option::Option, - pub locator: ::std::option::Option< - unsafe extern "C" fn(x: *mut f64, y: *mut f64, dd: pDevDesc) -> Rboolean, - >, - pub line: ::std::option::Option< - unsafe extern "C" fn(x1: f64, y1: f64, x2: f64, y2: f64, gc: pGEcontext, dd: pDevDesc), - >, - pub metricInfo: ::std::option::Option< - unsafe extern "C" fn( - c: ::std::os::raw::c_int, - gc: pGEcontext, - ascent: *mut f64, - descent: *mut f64, - width: *mut f64, - dd: pDevDesc, - ), - >, - pub mode: - ::std::option::Option, - pub newPage: ::std::option::Option, - pub polygon: ::std::option::Option< - unsafe extern "C" fn( - n: ::std::os::raw::c_int, - x: *mut f64, - y: *mut f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub polyline: ::std::option::Option< - unsafe extern "C" fn( - n: ::std::os::raw::c_int, - x: *mut f64, - y: *mut f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub rect: ::std::option::Option< - unsafe extern "C" fn(x0: f64, y0: f64, x1: f64, y1: f64, gc: pGEcontext, dd: pDevDesc), - >, - pub path: ::std::option::Option< - unsafe extern "C" fn( - x: *mut f64, - y: *mut f64, - npoly: ::std::os::raw::c_int, - nper: *mut ::std::os::raw::c_int, - winding: Rboolean, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub raster: ::std::option::Option< - unsafe extern "C" fn( - raster: *mut ::std::os::raw::c_uint, - w: ::std::os::raw::c_int, - h: ::std::os::raw::c_int, - x: f64, - y: f64, - width: f64, - height: f64, - rot: f64, - interpolate: Rboolean, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub cap: ::std::option::Option SEXP>, - pub size: ::std::option::Option< - unsafe extern "C" fn( - left: *mut f64, - right: *mut f64, - bottom: *mut f64, - top: *mut f64, - dd: pDevDesc, - ), - >, - pub strWidth: ::std::option::Option< - unsafe extern "C" fn( - str: *const ::std::os::raw::c_char, - gc: pGEcontext, - dd: pDevDesc, - ) -> f64, - >, - pub text: ::std::option::Option< - unsafe extern "C" fn( - x: f64, - y: f64, - str: *const ::std::os::raw::c_char, - rot: f64, - hadj: f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub onExit: ::std::option::Option, - pub getEvent: ::std::option::Option< - unsafe extern "C" fn(arg1: SEXP, arg2: *const ::std::os::raw::c_char) -> SEXP, - >, - pub newFrameConfirm: ::std::option::Option Rboolean>, - pub hasTextUTF8: Rboolean, - pub textUTF8: ::std::option::Option< - unsafe extern "C" fn( - x: f64, - y: f64, - str: *const ::std::os::raw::c_char, - rot: f64, - hadj: f64, - gc: pGEcontext, - dd: pDevDesc, - ), - >, - pub strWidthUTF8: ::std::option::Option< - unsafe extern "C" fn( - str: *const ::std::os::raw::c_char, - gc: pGEcontext, - dd: pDevDesc, - ) -> f64, - >, - pub wantSymbolUTF8: Rboolean, - pub useRotatedTextInContour: Rboolean, - pub eventEnv: SEXP, - pub eventHelper: - ::std::option::Option, - pub holdflush: ::std::option::Option< - unsafe extern "C" fn(dd: pDevDesc, level: ::std::os::raw::c_int) -> ::std::os::raw::c_int, - >, - pub haveTransparency: ::std::os::raw::c_int, - pub haveTransparentBg: ::std::os::raw::c_int, - pub haveRaster: ::std::os::raw::c_int, - pub haveCapture: ::std::os::raw::c_int, - pub haveLocator: ::std::os::raw::c_int, - pub setPattern: - ::std::option::Option SEXP>, - pub releasePattern: ::std::option::Option, - pub setClipPath: - ::std::option::Option SEXP>, - pub releaseClipPath: ::std::option::Option, - pub setMask: - ::std::option::Option SEXP>, - pub releaseMask: ::std::option::Option, - pub deviceVersion: ::std::os::raw::c_int, - pub deviceClip: Rboolean, - pub defineGroup: ::std::option::Option< - unsafe extern "C" fn( - source: SEXP, - op: ::std::os::raw::c_int, - destination: SEXP, - dd: pDevDesc, - ) -> SEXP, - >, - pub useGroup: - ::std::option::Option, - pub releaseGroup: ::std::option::Option, - pub stroke: - ::std::option::Option, - pub fill: ::std::option::Option< - unsafe extern "C" fn(path: SEXP, rule: ::std::os::raw::c_int, gc: pGEcontext, dd: pDevDesc), - >, - pub fillStroke: ::std::option::Option< - unsafe extern "C" fn(path: SEXP, rule: ::std::os::raw::c_int, gc: pGEcontext, dd: pDevDesc), - >, - pub capabilities: ::std::option::Option SEXP>, - pub glyph: ::std::option::Option< - unsafe extern "C" fn( - n: ::std::os::raw::c_int, - glyphs: *mut ::std::os::raw::c_int, - x: *mut f64, - y: *mut f64, - font: SEXP, - size: f64, - colour: ::std::os::raw::c_int, - rot: f64, - dd: pDevDesc, - ), - >, - pub reserved: [::std::os::raw::c_char; 64usize], -} diff --git a/crates/ark/src/plots/graphics_device.rs b/crates/ark/src/plots/graphics_device.rs index e2807d015..b802f6374 100644 --- a/crates/ark/src/plots/graphics_device.rs +++ b/crates/ark/src/plots/graphics_device.rs @@ -47,7 +47,11 @@ use crossbeam::channel::Sender; use harp::exec::RFunction; use harp::exec::RFunctionExt; use harp::object::RObject; -use libR_shim::*; +use libr::pDevDesc; +use libr::pGEcontext; +use libr::R_NilValue; +use libr::Rf_ScalarLogical; +use libr::SEXP; use once_cell::sync::Lazy; use serde_json::json; use stdext::result::ResultOrLog; @@ -397,19 +401,23 @@ static mut DEVICE_CONTEXT: Lazy = Lazy::new(|| DeviceContext::def // TODO: This macro needs to be updated every time we introduce support // for a new graphics device. Is there a better way? macro_rules! with_device { - ($value:expr, | $name:ident | $block:block) => {{ - let version = R_GE_getVersion(); + ($ge_value:expr, | $ge_name:ident, $name:ident | $block:block) => {{ + let version = libr::R_GE_getVersion(); if version == 13 { - let $name = $value as *mut $crate::plots::dev_desc::DevDescVersion13; + let $ge_name = $ge_value as *mut libr::GEDevDescVersion13; + let $name = (*$ge_name).dev; $block; } else if version == 14 { - let $name = $value as *mut $crate::plots::dev_desc::DevDescVersion14; + let $ge_name = $ge_value as *mut libr::GEDevDescVersion14; + let $name = (*$ge_name).dev; $block; } else if version == 15 { - let $name = $value as *mut $crate::plots::dev_desc::DevDescVersion15; + let $ge_name = $ge_value as *mut libr::GEDevDescVersion15; + let $name = (*$ge_name).dev; $block; } else if version == 16 { - let $name = $value as *mut $crate::plots::dev_desc::DevDescVersion16; + let $ge_name = $ge_value as *mut libr::GEDevDescVersion16; + let $name = (*$ge_name).dev; $block; } else { panic!( @@ -505,16 +513,19 @@ unsafe fn ps_graphics_device_impl() -> anyhow::Result { .param("res", res) .call()?; - // get reference to current device - let dd = GEcurrentDevice(); + // Get reference to current device (opaque pointer) + let ge_device = libr::GEcurrentDevice(); - // initialize our _callbacks - let device = (*dd).dev; - with_device!(device, |device| { - // initialize display list (needed for copying of plots) - GEinitDisplayList(dd); - (*dd).displayListOn = 1; - // (*dd).recordGraphics = 1; + // Initialize display list (needed for copying of plots) + // (Called on opaque pointer, because that matches the function signature. + // Pointer specialization is done below, at which point we can access and set + // `displayListOn` too) + libr::GEinitDisplayList(ge_device); + + // Get a specialized versioned pointer from our opaque one so we can initialize our _callbacks + with_device!(ge_device, |ge_device, device| { + (*ge_device).displayListOn = 1; + // (*ge_device).recordGraphics = 1; // device description struct let callbacks = &mut DEVICE_CONTEXT._callbacks; diff --git a/crates/ark/src/plots/mod.rs b/crates/ark/src/plots/mod.rs index ebf4c4140..a8aa9bc25 100644 --- a/crates/ark/src/plots/mod.rs +++ b/crates/ark/src/plots/mod.rs @@ -5,5 +5,4 @@ // // -pub mod dev_desc; pub mod graphics_device; diff --git a/crates/ark/src/shell.rs b/crates/ark/src/shell.rs index 72cc91fa5..fbcb519bf 100644 --- a/crates/ark/src/shell.rs +++ b/crates/ark/src/shell.rs @@ -42,7 +42,7 @@ use harp::exec::ParseResult; use harp::line_ending::convert_line_endings; use harp::line_ending::LineEnding; use harp::object::RObject; -use libR_shim::*; +use libr::R_GlobalEnv; use log::*; use serde_json::json; use stdext::spawn; diff --git a/crates/ark/src/sys/unix/interface.rs b/crates/ark/src/sys/unix/interface.rs index 9cf39e9d3..5ab6d8980 100644 --- a/crates/ark/src/sys/unix/interface.rs +++ b/crates/ark/src/sys/unix/interface.rs @@ -7,15 +7,26 @@ use std::ffi::CStr; use std::os::raw::c_char; -use std::os::raw::c_void; -use libR_shim::run_Rmainloop; -use libR_shim::setup_Rmainloop; -use libR_shim::R_HomeDir; -use libR_shim::R_SignalHandlers; -use libR_shim::Rboolean; -use libR_shim::Rf_initialize_R; -use libR_shim::FILE; +use libr::ptr_R_Busy; +use libr::ptr_R_ReadConsole; +use libr::ptr_R_ShowMessage; +use libr::ptr_R_WriteConsole; +use libr::ptr_R_WriteConsoleEx; +use libr::run_Rmainloop; +use libr::setup_Rmainloop; +use libr::R_Consolefile; +use libr::R_HomeDir; +use libr::R_InputHandlers; +use libr::R_Interactive; +use libr::R_Outputfile; +use libr::R_PolledEvents; +use libr::R_SignalHandlers; +use libr::R_checkActivity; +use libr::R_runHandlers; +use libr::R_running_as_main_program; +use libr::R_wait_usec; +use libr::Rf_initialize_R; use crate::interface::r_busy; use crate::interface::r_polled_events; @@ -27,9 +38,9 @@ use crate::signals::initialize_signal_handlers; pub fn setup_r(mut args: Vec<*mut c_char>) { unsafe { // Before `Rf_initialize_R()` - R_running_as_main_program = 1; + libr::set(R_running_as_main_program, 1); - R_SignalHandlers = 0; + libr::set(R_SignalHandlers, 0); Rf_initialize_R(args.len() as i32, args.as_mut_ptr() as *mut *mut c_char); @@ -38,21 +49,21 @@ pub fn setup_r(mut args: Vec<*mut c_char>) { // Mark R session as interactive // (Should have also been set by call to `Rf_initialize_R()`) - R_Interactive = 1; + libr::set(R_Interactive, 1); // Log the value of R_HOME, so we can know if something hairy is afoot let home = CStr::from_ptr(R_HomeDir()); log::trace!("R_HOME: {:?}", home); // Redirect console - R_Consolefile = std::ptr::null_mut(); - R_Outputfile = std::ptr::null_mut(); + libr::set(R_Consolefile, std::ptr::null_mut()); + libr::set(R_Outputfile, std::ptr::null_mut()); - ptr_R_WriteConsole = None; - ptr_R_WriteConsoleEx = Some(r_write_console); - ptr_R_ReadConsole = Some(r_read_console); - ptr_R_ShowMessage = Some(r_show_message); - ptr_R_Busy = Some(r_busy); + libr::set(ptr_R_WriteConsole, None); + libr::set(ptr_R_WriteConsoleEx, Some(r_write_console)); + libr::set(ptr_R_ReadConsole, Some(r_read_console)); + libr::set(ptr_R_ShowMessage, Some(r_show_message)); + libr::set(ptr_R_Busy, Some(r_busy)); // Set up main loop setup_Rmainloop(); @@ -62,8 +73,8 @@ pub fn setup_r(mut args: Vec<*mut c_char>) { pub fn run_r() { unsafe { // Listen for polled events - R_wait_usec = 10000; - R_PolledEvents = Some(r_polled_events); + libr::set(R_wait_usec, 10000); + libr::set(R_PolledEvents, Some(r_polled_events)); run_Rmainloop(); } @@ -83,51 +94,8 @@ pub fn run_activity_handlers() { let mut fdset = R_checkActivity(0, 1); while fdset != std::ptr::null_mut() { - R_runHandlers(R_InputHandlers, fdset); + R_runHandlers(libr::get(R_InputHandlers), fdset); fdset = R_checkActivity(0, 1); } } } - -extern "C" { - static mut R_running_as_main_program: ::std::os::raw::c_int; - static mut R_Interactive: Rboolean; - static mut R_InputHandlers: *const c_void; - static mut R_Consolefile: *mut FILE; - static mut R_Outputfile: *mut FILE; - - static mut R_wait_usec: i32; - static mut R_PolledEvents: Option; - - // NOTE: Some of these routines don't really return (or use) void pointers, - // but because we never introspect these values directly and they're always - // passed around in R as pointers, it suffices to just use void pointers. - fn R_checkActivity(usec: i32, ignore_stdin: i32) -> *const c_void; - fn R_runHandlers(handlers: *const c_void, fdset: *const c_void); - - static mut ptr_R_WriteConsole: ::std::option::Option< - unsafe extern "C" fn(arg1: *const ::std::os::raw::c_char, arg2: ::std::os::raw::c_int), - >; - - static mut ptr_R_WriteConsoleEx: ::std::option::Option< - unsafe extern "C" fn( - arg1: *const ::std::os::raw::c_char, - arg2: ::std::os::raw::c_int, - arg3: ::std::os::raw::c_int, - ), - >; - - static mut ptr_R_ReadConsole: ::std::option::Option< - unsafe extern "C" fn( - arg1: *const ::std::os::raw::c_char, - arg2: *mut ::std::os::raw::c_uchar, - arg3: ::std::os::raw::c_int, - arg4: ::std::os::raw::c_int, - ) -> ::std::os::raw::c_int, - >; - - static mut ptr_R_ShowMessage: - ::std::option::Option; - - static mut ptr_R_Busy: ::std::option::Option; -} diff --git a/crates/ark/src/sys/unix/signals.rs b/crates/ark/src/sys/unix/signals.rs index bde128e8a..7b8ac4599 100644 --- a/crates/ark/src/sys/unix/signals.rs +++ b/crates/ark/src/sys/unix/signals.rs @@ -5,7 +5,7 @@ * */ -use libR_shim::R_interrupts_pending; +use libr::R_interrupts_pending; use nix::sys::signal::*; /// Reset the signal block. @@ -53,14 +53,14 @@ pub fn initialize_signal_block() { } pub fn interrupts_pending() -> bool { - unsafe { R_interrupts_pending == 1 } + unsafe { libr::get(R_interrupts_pending) == 1 } } pub fn set_interrupts_pending(pending: bool) { if pending { - unsafe { R_interrupts_pending = 1 }; + unsafe { libr::set(R_interrupts_pending, 1) }; } else { - unsafe { R_interrupts_pending = 0 }; + unsafe { libr::set(R_interrupts_pending, 0) }; } } diff --git a/crates/ark/src/sys/windows.rs b/crates/ark/src/sys/windows.rs index 9c23ae30c..dc0f6216e 100644 --- a/crates/ark/src/sys/windows.rs +++ b/crates/ark/src/sys/windows.rs @@ -8,7 +8,6 @@ pub mod console; pub mod control; pub mod interface; -mod interface_types; pub mod signals; mod strings; pub mod traps; diff --git a/crates/ark/src/sys/windows/interface.rs b/crates/ark/src/sys/windows/interface.rs index d0ee044fb..94698b305 100644 --- a/crates/ark/src/sys/windows/interface.rs +++ b/crates/ark/src/sys/windows/interface.rs @@ -11,22 +11,26 @@ use std::ffi::CStr; use std::ffi::CString; use std::mem::MaybeUninit; -use libR_shim::run_Rmainloop; -use libR_shim::setup_Rmainloop; -use libR_shim::R_HomeDir; -use libR_shim::R_SignalHandlers; +use libr::cmdlineoptions; +use libr::get_R_HOME; +use libr::readconsolecfg; +use libr::run_Rmainloop; +use libr::setup_Rmainloop; +use libr::R_DefParamsEx; +use libr::R_HomeDir; +use libr::R_SetParams; +use libr::R_SignalHandlers; use stdext::cargs; use crate::interface::r_busy; use crate::interface::r_read_console; use crate::interface::r_show_message; use crate::interface::r_write_console; -use crate::sys::windows::interface_types; use crate::sys::windows::strings::system_to_utf8; pub fn setup_r(mut _args: Vec<*mut c_char>) { unsafe { - R_SignalHandlers = 0; + libr::set(R_SignalHandlers, 0); // - We have to collect these before `cmdlineoptions()` is called, because // it alters the env vars, which we then reset to our own paths in `R_SetParams()`. @@ -51,7 +55,7 @@ pub fn setup_r(mut _args: Vec<*mut c_char>) { cmdlineoptions(rargc, rargv.as_mut_ptr() as *mut *mut c_char); let mut params_struct = MaybeUninit::uninit(); - let params: interface_types::Rstart = params_struct.as_mut_ptr(); + let params: libr::Rstart = params_struct.as_mut_ptr(); // TODO: Windows // We eventually need to use `RSTART_VERSION` (i.e., 1). It might just @@ -62,7 +66,7 @@ pub fn setup_r(mut _args: Vec<*mut c_char>) { R_DefParamsEx(params, 0); (*params).R_Interactive = 1; - (*params).CharacterMode = interface_types::UImode_RGui; + (*params).CharacterMode = libr::UImode_RGui; (*params).WriteConsole = None; (*params).WriteConsoleEx = Some(r_write_console); @@ -83,7 +87,7 @@ pub fn setup_r(mut _args: Vec<*mut c_char>) { R_SetParams(params); // R global ui initialization - GA_initapp(0, std::ptr::null_mut()); + libr::graphapp::GA_initapp(0, std::ptr::null_mut()); readconsolecfg(); // Log the value of R_HOME, so we can know if something hairy is afoot @@ -135,7 +139,7 @@ fn get_r_home() -> String { } fn get_user_home() -> String { - let r_path = unsafe { getRUser() }; + let r_path = unsafe { libr::getRUser() }; if r_path.is_null() { panic!("`getRUser()` failed to report a user home directory."); @@ -176,59 +180,3 @@ extern "C" fn r_yes_no_cancel(question: *const c_char) -> c_int { log::warn!("Ignoring `YesNoCancel` question: '{question}'. Returning `NO`."); return -1; } - -extern "C" { - fn cmdlineoptions(ac: i32, av: *mut *mut ::std::os::raw::c_char); - - fn readconsolecfg(); - - fn R_DefParamsEx(Rp: interface_types::Rstart, RstartVersion: i32); - - fn R_SetParams(Rp: interface_types::Rstart); - - /// Get user home directory - /// - /// Checks: - /// - C `R_USER` env var - /// - C `USER` env var - /// - `Documents` folder, if one exists, through `ShellGetPersonalDirectory()` - /// - `HOMEDRIVE` + `HOMEPATH` - /// - Current directory through `GetCurrentDirectory()` - /// - /// Probably returns a system encoded result? - /// So needs to be converted to UTF-8. - /// - /// https://github.com/wch/r-source/blob/55cd975c538ad5a086c2085ccb6a3037d5a0cb9a/src/gnuwin32/shext.c#L55 - fn getRUser() -> *mut ::std::os::raw::c_char; - - /// Get R_HOME from the environment or the registry - /// - /// Checks: - /// - C `R_HOME` env var - /// - Windows API `R_HOME` environment space - /// - Current user registry - /// - Local machine registry - /// - /// Probably returns a system encoded result? - /// So needs to be converted to UTF-8. - /// - /// https://github.com/wch/r-source/blob/55cd975c538ad5a086c2085ccb6a3037d5a0cb9a/src/gnuwin32/rhome.c#L152 - fn get_R_HOME() -> *mut ::std::os::raw::c_char; - - // In theory we should call these, but they are very new, roughly R 4.3.0. - // It isn't super harmful if we don't free these. - // https://github.com/wch/r-source/commit/9210c59281e7ab93acff9f692c31b83d07a506a6 - // fn freeRUser(s: *mut ::std::os::raw::c_char); - // fn free_R_HOME(s: *mut ::std::os::raw::c_char); -} - -// It doesn't seem like we can use the binding provided by libR-sys, -// as that doesn't link to Rgraphapp so it becomes an unresolved -// external symbol. -#[link(name = "Rgraphapp", kind = "dylib")] -extern "C" { - pub fn GA_initapp( - arg1: ::std::os::raw::c_int, - arg2: *mut *mut ::std::os::raw::c_char, - ) -> ::std::os::raw::c_int; -} diff --git a/crates/ark/src/sys/windows/signals.rs b/crates/ark/src/sys/windows/signals.rs index 0facf7fb1..0a567848a 100644 --- a/crates/ark/src/sys/windows/signals.rs +++ b/crates/ark/src/sys/windows/signals.rs @@ -5,8 +5,9 @@ * */ -use libR_shim::Rboolean_FALSE; -use libR_shim::Rboolean_TRUE; +use libr::Rboolean_FALSE; +use libr::Rboolean_TRUE; +use libr::UserBreak; pub fn initialize_signal_handlers() { // Nothing to do on Windows. Signal blocking is POSIX only. @@ -17,18 +18,13 @@ pub fn initialize_signal_block() { } pub fn interrupts_pending() -> bool { - unsafe { UserBreak == Rboolean_TRUE } + unsafe { libr::get(UserBreak) == Rboolean_TRUE } } pub fn set_interrupts_pending(pending: bool) { if pending { - unsafe { UserBreak = Rboolean_TRUE }; + unsafe { libr::set(UserBreak, Rboolean_TRUE) }; } else { - unsafe { UserBreak = Rboolean_FALSE }; + unsafe { libr::set(UserBreak, Rboolean_FALSE) }; } } - -#[link(name = "R", kind = "dylib")] -extern "C" { - static mut UserBreak: libR_shim::Rboolean; -} diff --git a/crates/ark/src/sys/windows/strings.rs b/crates/ark/src/sys/windows/strings.rs index 28b19cfd8..f7ae37453 100644 --- a/crates/ark/src/sys/windows/strings.rs +++ b/crates/ark/src/sys/windows/strings.rs @@ -5,6 +5,7 @@ * */ +use libr::localeCP; use winsafe::co::CP; use winsafe::co::MBC; use winsafe::co::WC; @@ -40,14 +41,7 @@ pub fn code_page_to_utf8(x: &[u8], code_page: CP) -> anyhow::Result { pub fn get_system_code_page() -> CP { // Lookup code page that R is using - let code_page = unsafe { localeCP } as u16; + let code_page = unsafe { libr::get(localeCP) } as u16; let code_page = unsafe { CP::from_raw(code_page) }; code_page } - -#[link(name = "R", kind = "dylib")] -extern "C" { - /// The codepage that R thinks it should be using for Windows. - /// Should map to `winsafe::kernel::co::CP`. - static mut localeCP: ::std::os::raw::c_uint; -} diff --git a/crates/ark/src/ui/methods.rs b/crates/ark/src/ui/methods.rs index e7e8ac55d..353464130 100644 --- a/crates/ark/src/ui/methods.rs +++ b/crates/ark/src/ui/methods.rs @@ -8,7 +8,7 @@ use amalthea::comm::ui_comm::DebugSleepParams; use amalthea::comm::ui_comm::UiFrontendRequest; use harp::object::RObject; -use libR_shim::SEXP; +use libr::SEXP; use crate::interface::RMain; diff --git a/crates/ark/src/variables/r_variables.rs b/crates/ark/src/variables/r_variables.rs index 16fdd3303..1d4a6f797 100644 --- a/crates/ark/src/variables/r_variables.rs +++ b/crates/ark/src/variables/r_variables.rs @@ -29,7 +29,9 @@ use harp::object::RObject; use harp::utils::r_assert_type; use harp::vector::CharacterVector; use harp::vector::Vector; -use libR_shim::*; +use libr::R_GlobalEnv; +use libr::Rf_ScalarLogical; +use libr::ENVSXP; use log::debug; use log::error; use log::warn; diff --git a/crates/ark/src/variables/variable.rs b/crates/ark/src/variables/variable.rs index d0db27417..e610a4aa3 100644 --- a/crates/ark/src/variables/variable.rs +++ b/crates/ark/src/variables/variable.rs @@ -44,7 +44,7 @@ use harp::vector::CharacterVector; use harp::vector::IntegerVector; use harp::vector::Vector; use itertools::Itertools; -use libR_shim::*; +use libr::*; use stdext::local; use stdext::unwrap; @@ -355,7 +355,7 @@ impl WorkspaceVariableDisplayType { let rtype = r_typeof(value); match rtype { EXPRSXP => { - let default = format!("expression [{}]", unsafe { XLENGTH(value) }); + let default = format!("expression [{}]", unsafe { Rf_xlength(value) }); Self::from_class(value, default) }, LANGSXP => Self::from_class(value, String::from("language")), @@ -387,7 +387,7 @@ impl WorkspaceVariableDisplayType { let display_type = format!("{} [{}]", dfclass, shape); Self::simple(display_type) } else { - let default = format!("list [{}]", XLENGTH(value)); + let default = format!("list [{}]", Rf_xlength(value)); Self::from_class(value, default) } }, @@ -432,11 +432,13 @@ fn has_children(value: SEXP) -> bool { } } else { match r_typeof(value) { - VECSXP | EXPRSXP => unsafe { XLENGTH(value) != 0 }, + VECSXP | EXPRSXP => unsafe { Rf_xlength(value) != 0 }, LISTSXP => true, ENVSXP => !Environment::new(RObject::view(value)) .is_empty(EnvironmentFilter::ExcludeHiddenBindings), - LGLSXP | RAWSXP | STRSXP | INTSXP | REALSXP | CPLXSXP => unsafe { XLENGTH(value) > 1 }, + LGLSXP | RAWSXP | STRSXP | INTSXP | REALSXP | CPLXSXP => unsafe { + Rf_xlength(value) > 1 + }, _ => false, } } @@ -568,10 +570,12 @@ impl PositronVariable { fn variable_length(x: SEXP) -> usize { let rtype = r_typeof(x); match rtype { - LGLSXP | RAWSXP | INTSXP | REALSXP | CPLXSXP | STRSXP => unsafe { XLENGTH(x) as usize }, + LGLSXP | RAWSXP | INTSXP | REALSXP | CPLXSXP | STRSXP => unsafe { + Rf_xlength(x) as usize + }, VECSXP => unsafe { if r_inherits(x, "POSIXlt") { - XLENGTH(VECTOR_ELT(x, 0)) as usize + Rf_xlength(VECTOR_ELT(x, 0)) as usize } else if r_is_data_frame(x) { let dim = RFunction::new("base", "dim.data.frame") .add(x) @@ -580,7 +584,7 @@ impl PositronVariable { INTEGER_ELT(*dim, 0) as usize } else { - XLENGTH(x) as usize + Rf_xlength(x) as usize } }, LISTSXP => match pairlist_size(x) { @@ -622,7 +626,7 @@ impl PositronVariable { VECSXP => unsafe { let dim = Rf_getAttrib(x, R_DimSymbol); - if dim != R_NilValue && XLENGTH(dim) == 2 { + if dim != R_NilValue && Rf_xlength(dim) == 2 { VariableKind::Table } else { VariableKind::Map @@ -631,9 +635,9 @@ impl PositronVariable { LGLSXP => unsafe { let dim = Rf_getAttrib(x, R_DimSymbol); - if dim != R_NilValue && XLENGTH(dim) == 2 { + if dim != R_NilValue && Rf_xlength(dim) == 2 { VariableKind::Table - } else if XLENGTH(x) == 1 { + } else if Rf_xlength(x) == 1 { if LOGICAL_ELT(x, 0) == R_NaInt { VariableKind::Empty } else { @@ -646,9 +650,9 @@ impl PositronVariable { INTSXP => unsafe { let dim = Rf_getAttrib(x, R_DimSymbol); - if dim != R_NilValue && XLENGTH(dim) == 2 { + if dim != R_NilValue && Rf_xlength(dim) == 2 { VariableKind::Table - } else if XLENGTH(x) == 1 { + } else if Rf_xlength(x) == 1 { if INTEGER_ELT(x, 0) == R_NaInt { VariableKind::Empty } else { @@ -661,9 +665,9 @@ impl PositronVariable { REALSXP => unsafe { let dim = Rf_getAttrib(x, R_DimSymbol); - if dim != R_NilValue && XLENGTH(dim) == 2 { + if dim != R_NilValue && Rf_xlength(dim) == 2 { VariableKind::Table - } else if XLENGTH(x) == 1 { + } else if Rf_xlength(x) == 1 { if R_IsNA(REAL_ELT(x, 0)) == 1 { VariableKind::Empty } else { @@ -676,9 +680,9 @@ impl PositronVariable { CPLXSXP => unsafe { let dim = Rf_getAttrib(x, R_DimSymbol); - if dim != R_NilValue && XLENGTH(dim) == 2 { + if dim != R_NilValue && Rf_xlength(dim) == 2 { VariableKind::Table - } else if XLENGTH(x) == 1 { + } else if Rf_xlength(x) == 1 { let value = COMPLEX_ELT(x, 0); if R_IsNA(value.r) == 1 || R_IsNA(value.i) == 1 { VariableKind::Empty @@ -692,9 +696,9 @@ impl PositronVariable { STRSXP => unsafe { let dim = Rf_getAttrib(x, R_DimSymbol); - if dim != R_NilValue && XLENGTH(dim) == 2 { + if dim != R_NilValue && Rf_xlength(dim) == 2 { VariableKind::Table - } else if XLENGTH(x) == 1 { + } else if Rf_xlength(x) == 1 { if STRING_ELT(x, 0) == R_NaString { VariableKind::Empty } else { @@ -949,7 +953,7 @@ impl PositronVariable { fn inspect_list(value: SEXP) -> Result, harp::error::Error> { let mut out: Vec = vec![]; - let n = unsafe { XLENGTH(value) }; + let n = unsafe { Rf_xlength(value) }; let names = Names::new(value, |i| format!("[[{}]]", i + 1)); @@ -1036,7 +1040,7 @@ impl PositronVariable { fn inspect_vector(vector: SEXP) -> harp::error::Result> { unsafe { let vector = RObject::new(vector); - let n = XLENGTH(*vector); + let n = Rf_xlength(*vector); let mut out: Vec = vec![]; let r_type = r_typeof(*vector); diff --git a/crates/ark/src/version.rs b/crates/ark/src/version.rs index cad6471bc..f97e2342c 100644 --- a/crates/ark/src/version.rs +++ b/crates/ark/src/version.rs @@ -13,7 +13,7 @@ use std::process::Command; use anyhow::Context; use harp::object::RObject; use itertools::Itertools; -use libR_shim::*; +use libr::SEXP; pub struct RVersion { // Major version of the R installation diff --git a/crates/ark/src/viewer.rs b/crates/ark/src/viewer.rs index 7ac0074aa..5c5b70e1e 100644 --- a/crates/ark/src/viewer.rs +++ b/crates/ark/src/viewer.rs @@ -10,8 +10,8 @@ use amalthea::wire::display_data::DisplayData; use anyhow::Result; use crossbeam::channel::Sender; use harp::object::RObject; -use libR_shim::R_NilValue; -use libR_shim::SEXP; +use libr::R_NilValue; +use libr::SEXP; use crate::interface::RMain; diff --git a/crates/ark/tests/environment.rs b/crates/ark/tests/environment.rs index bba4afcab..eeebae3c2 100644 --- a/crates/ark/tests/environment.rs +++ b/crates/ark/tests/environment.rs @@ -26,7 +26,12 @@ use harp::r_symbol; use harp::test::start_r; use harp::utils::r_envir_remove; use harp::utils::r_envir_set; -use libR_shim::*; +use libr::R_EmptyEnv; +use libr::R_lsInternal; +use libr::Rboolean_TRUE; +use libr::Rf_ScalarInteger; +use libr::Rf_defineVar; +use libr::Rf_xlength; /** * Basic test for the R environment list. This test: @@ -216,7 +221,7 @@ fn test_environment_list() { r_task(|| unsafe { let test_env = test_env.get().clone(); let contents = RObject::new(R_lsInternal(*test_env, Rboolean_TRUE)); - assert_eq!(Rf_length(*contents), 0); + assert_eq!(Rf_xlength(*contents), 0); }); // Create some more variables diff --git a/crates/harp/Cargo.toml b/crates/harp/Cargo.toml index fe06d5027..9ead4d5b1 100644 --- a/crates/harp/Cargo.toml +++ b/crates/harp/Cargo.toml @@ -15,8 +15,9 @@ ctor = "0.1.26" harp-macros = { path = "./harp-macros" } itertools = "0.10.5" lazy_static = "1.4.0" -libR-shim = { path = "../libR-shim" } libc = "0.2.140" +libloading = "0.8.1" +libr = { path = "../libr" } log = "0.4.17" once_cell = "1.17.1" parking_lot = "0.12.1" diff --git a/crates/harp/harp-macros/src/lib.rs b/crates/harp/harp-macros/src/lib.rs index f0bfc413c..148d8d4aa 100644 --- a/crates/harp/harp-macros/src/lib.rs +++ b/crates/harp/harp-macros/src/lib.rs @@ -45,7 +45,7 @@ pub fn vector(_attr: TokenStream, item: TokenStream) -> TokenStream { #data impl std::ops::Deref for #ident { - type Target = SEXP; + type Target = libr::SEXP; fn deref(&self) -> &Self::Target { &self.object.sexp @@ -58,9 +58,9 @@ pub fn vector(_attr: TokenStream, item: TokenStream) -> TokenStream { } } - impl std::convert::TryFrom for #ident { + impl std::convert::TryFrom for #ident { type Error = crate::error::Error; - fn try_from(value: SEXP) -> Result { + fn try_from(value: libr::SEXP) -> Result { unsafe { Self::new(value) } } } @@ -200,7 +200,7 @@ pub fn register(_attr: TokenStream, item: TokenStream) -> TokenStream { fn #register() { unsafe { - harp::routines::add(libR_shim::R_CallMethodDef { + harp::routines::add(libr::R_CallMethodDef { name: (#name).as_ptr() as *const std::os::raw::c_char, fun: Some(::std::mem::transmute(#ident as *const ())), numArgs: #nargs @@ -247,7 +247,7 @@ pub fn register(_attr: TokenStream, item: TokenStream) -> TokenStream { // `r_unwrap()` which takes the function body as input, ensuring that // it's a `Result` and guaranteeing that the expanded function // body does return a `SEXP` type. - let sexp_type: syn::ReturnType = syn::parse(quote! { -> libR_shim::SEXP }.into()).unwrap(); + let sexp_type: syn::ReturnType = syn::parse(quote! { -> libr::SEXP }.into()).unwrap(); function.sig.output = sexp_type; // Put everything together diff --git a/crates/harp/src/call.rs b/crates/harp/src/call.rs index c86cc0b8d..4fffeb131 100644 --- a/crates/harp/src/call.rs +++ b/crates/harp/src/call.rs @@ -7,7 +7,8 @@ use std::ops::Deref; -use libR_shim::*; +use libr::LANGSXP; +use libr::SEXP; use crate::error::Result; use crate::object::RObject; diff --git a/crates/harp/src/environment.rs b/crates/harp/src/environment.rs index 3f5fce456..5851044c0 100644 --- a/crates/harp/src/environment.rs +++ b/crates/harp/src/environment.rs @@ -7,7 +7,7 @@ use std::ops::Deref; -use libR_shim::*; +use libr::*; use once_cell::sync::Lazy; use stdext::unwrap; @@ -72,7 +72,7 @@ fn has_reference(value: SEXP) -> bool { LISTSXP | LANGSXP => unsafe { has_reference(CAR(value)) || has_reference(CDR(value)) }, VECSXP | EXPRSXP => unsafe { - let n = XLENGTH(value); + let n = Rf_xlength(value); let mut has_ref = false; for i in 0..n { if has_reference(VECTOR_ELT(value, i)) { @@ -224,7 +224,7 @@ impl<'a> HashedEnvironmentIter<'a> { pub fn new(env: &'a Environment) -> Self { unsafe { let hashtab = HASHTAB(**env); - let hashtab_len = XLENGTH(hashtab); + let hashtab_len = Rf_xlength(hashtab); let mut hashtab_index = 0; let mut frame = R_NilValue; @@ -269,7 +269,7 @@ impl<'a> Iterator for HashedEnvironmentIter<'a> { if self.frame == R_NilValue { // end of frame: move to the next non empty frame - let hashtab_len = XLENGTH(self.hashtab); + let hashtab_len = Rf_xlength(self.hashtab); loop { // move to the next frame self.hashtab_index = self.hashtab_index + 1; @@ -471,7 +471,8 @@ impl From for RObject { #[cfg(test)] mod tests { - use libR_shim::*; + use libr::Rf_ScalarInteger; + use libr::Rf_defineVar; use super::*; use crate::exec::RFunction; diff --git a/crates/harp/src/eval.rs b/crates/harp/src/eval.rs index 46aec2983..9f0eae511 100644 --- a/crates/harp/src/eval.rs +++ b/crates/harp/src/eval.rs @@ -5,7 +5,10 @@ // // -use libR_shim::*; +use libr::R_NilValue; +use libr::R_tryEvalSilent; +use libr::Rf_xlength; +use libr::VECTOR_ELT; use crate::environment::R_ENVS; use crate::error::Error; @@ -48,8 +51,8 @@ pub fn r_parse_eval(code: &str, options: RParseEvalOptions) -> Result { // Evaluate the provided code. let mut value = R_NilValue; - for i in 0..Rf_length(*parsed_sexp) { - let expr = VECTOR_ELT(*parsed_sexp, i as isize); + for i in 0..Rf_xlength(*parsed_sexp) { + let expr = VECTOR_ELT(*parsed_sexp, i); let mut errc: i32 = 0; value = R_tryEvalSilent(expr, options.env.sexp, &mut errc); if errc != 0 { diff --git a/crates/harp/src/exec.rs b/crates/harp/src/exec.rs index c9d2d01e8..f7510a1e2 100644 --- a/crates/harp/src/exec.rs +++ b/crates/harp/src/exec.rs @@ -8,11 +8,9 @@ use std::ffi::CStr; use std::mem; use std::mem::take; -use std::os::raw::c_char; -use std::os::raw::c_int; use std::os::raw::c_void; -use libR_shim::*; +use libr::*; use crate::environment::R_ENVS; use crate::error::Error; @@ -30,13 +28,6 @@ use crate::utils::r_stringify; use crate::utils::r_typeof; use crate::vector::CharacterVector; use crate::vector::Vector; - -extern "C" { - pub static R_ParseError: c_int; - pub static R_ParseErrorMsg: [c_char; 256usize]; - pub static mut R_DirtyImage: ::std::os::raw::c_int; -} - pub struct RArgument { pub name: String, pub value: RObject, @@ -458,10 +449,10 @@ pub unsafe fn r_parse_vector(code: &str) -> Result { ParseStatus_PARSE_OK => Ok(ParseResult::Complete(*result)), ParseStatus_PARSE_INCOMPLETE => Ok(ParseResult::Incomplete()), ParseStatus_PARSE_ERROR => Err(Error::ParseSyntaxError { - message: CStr::from_ptr(R_ParseErrorMsg.as_ptr()) + message: CStr::from_ptr(libr::get(R_ParseErrorMsg).as_ptr()) .to_string_lossy() .to_string(), - line: R_ParseError as i32, + line: libr::get(R_ParseError) as i32, }), _ => { // should not get here @@ -504,7 +495,7 @@ pub fn r_parse(code: &str) -> Result { unsafe { let exprs = r_parse_exprs(code)?; - let n = Rf_length(*exprs); + let n = Rf_xlength(*exprs); if n != 1 { return Err(Error::ParseError { code: code.to_string(), @@ -789,7 +780,7 @@ mod tests { let call = VECTOR_ELT(out, 0); assert_eq!(r_typeof(call), LANGSXP as u32); - assert_eq!(Rf_length(call), 2); + assert_eq!(Rf_xlength(call), 2); assert_eq!(CAR(call), r_symbol!("force")); let arg = CADR(call); @@ -842,18 +833,18 @@ mod tests { #[test] fn test_dirty_image() { r_test! { - R_DirtyImage = 2; + libr::set(R_DirtyImage, 2); let sym = r_symbol!("aaa"); Rf_defineVar(sym, Rf_ScalarInteger(42), R_GlobalEnv); - assert_eq!(R_DirtyImage, 1); + assert_eq!(libr::get(R_DirtyImage), 1); - R_DirtyImage = 2; + libr::set(R_DirtyImage, 2); Rf_setVar(sym, Rf_ScalarInteger(43), R_GlobalEnv); - assert_eq!(R_DirtyImage, 1); + assert_eq!(libr::get(R_DirtyImage), 1); - R_DirtyImage = 2; + libr::set(R_DirtyImage, 2); r_envir_remove("aaa", R_GlobalEnv); - assert_eq!(R_DirtyImage, 1); + assert_eq!(libr::get(R_DirtyImage), 1); } } diff --git a/crates/harp/src/external_ptr.rs b/crates/harp/src/external_ptr.rs index 6d613f71d..9fb3febe2 100644 --- a/crates/harp/src/external_ptr.rs +++ b/crates/harp/src/external_ptr.rs @@ -1,7 +1,10 @@ use std::marker::PhantomData; use std::os::raw::c_void; -use libR_shim::*; +use libr::R_ExternalPtrAddr; +use libr::R_MakeExternalPtr; +use libr::R_NilValue; +use libr::SEXP; use crate::object::RObject; diff --git a/crates/harp/src/interrupts.rs b/crates/harp/src/interrupts.rs index c1fb093f6..c4c9f69f4 100644 --- a/crates/harp/src/interrupts.rs +++ b/crates/harp/src/interrupts.rs @@ -5,7 +5,9 @@ // // -use libR_shim::*; +use libr::R_interrupts_suspended; +use libr::Rboolean; +use libr::Rboolean_TRUE; static mut R_INTERRUPTS_SUSPENDED: i32 = 0; @@ -16,8 +18,8 @@ pub struct RInterruptsSuspendedScope { impl RInterruptsSuspendedScope { pub fn new() -> RInterruptsSuspendedScope { unsafe { - let suspended = R_interrupts_suspended; - R_interrupts_suspended = 1; + let suspended = libr::get(R_interrupts_suspended); + libr::set(R_interrupts_suspended, Rboolean_TRUE); R_INTERRUPTS_SUSPENDED += 1; RInterruptsSuspendedScope { suspended } @@ -30,7 +32,7 @@ impl Drop for RInterruptsSuspendedScope { unsafe { R_INTERRUPTS_SUSPENDED -= 1; if R_INTERRUPTS_SUSPENDED == 0 { - R_interrupts_suspended = self.suspended; + libr::set(R_interrupts_suspended, self.suspended); } } } diff --git a/crates/harp/src/json.rs b/crates/harp/src/json.rs index b954ad142..6fdb691a7 100644 --- a/crates/harp/src/json.rs +++ b/crates/harp/src/json.rs @@ -7,7 +7,17 @@ use std::cmp::min; -use libR_shim::*; +use libr::R_NamesSymbol; +use libr::Rf_allocVector; +use libr::Rf_setAttrib; +use libr::INTSXP; +use libr::LGLSXP; +use libr::NILSXP; +use libr::REALSXP; +use libr::SET_VECTOR_ELT; +use libr::STRSXP; +use libr::SYMSXP; +use libr::VECSXP; use log::warn; use serde_json::json; use serde_json::Map; diff --git a/crates/harp/src/lib.rs b/crates/harp/src/lib.rs index ec17aa347..db22793a6 100644 --- a/crates/harp/src/lib.rs +++ b/crates/harp/src/lib.rs @@ -12,6 +12,7 @@ pub mod exec; pub mod external_ptr; pub mod interrupts; pub mod json; +pub mod library; pub mod line_ending; pub mod object; pub mod polled_events; @@ -89,13 +90,13 @@ macro_rules! r_symbol { ($id:literal) => {{ use std::os::raw::c_char; let value = concat!($id, "\0"); - libR_shim::Rf_install(value.as_ptr() as *const c_char) + libr::Rf_install(value.as_ptr() as *const c_char) }}; ($id:expr) => {{ use std::os::raw::c_char; let cstr = [&*$id, "\0"].concat(); - libR_shim::Rf_install(cstr.as_ptr() as *const c_char) + libr::Rf_install(cstr.as_ptr() as *const c_char) }}; } @@ -104,13 +105,11 @@ macro_rules! r_char { ($id:expr) => {{ use std::os::raw::c_char; - use libR_shim::*; - let value = &*$id; - Rf_mkCharLenCE( + libr::Rf_mkCharLenCE( value.as_ptr() as *mut c_char, value.len() as i32, - cetype_t_CE_UTF8, + libr::cetype_t_CE_UTF8, ) }}; } @@ -118,10 +117,8 @@ macro_rules! r_char { #[macro_export] macro_rules! r_string { ($id:expr, $protect:expr) => {{ - use libR_shim::*; - - let string_sexp = $protect.add(Rf_allocVector(STRSXP, 1)); - SET_STRING_ELT(string_sexp, 0, $crate::r_char!($id)); + let string_sexp = $protect.add(libr::Rf_allocVector(libr::STRSXP, 1)); + libr::SET_STRING_ELT(string_sexp, 0, $crate::r_char!($id)); string_sexp }}; } @@ -129,7 +126,7 @@ macro_rules! r_string { #[macro_export] macro_rules! r_double { ($id:expr) => { - libR_shim::Rf_ScalarReal($id) + libr::Rf_ScalarReal($id) }; } @@ -138,7 +135,7 @@ macro_rules! r_pairlist_impl { ($head:expr, $tail:expr) => {{ let head = $crate::object::RObject::from($head); let tail = $crate::object::RObject::from($tail); - libR_shim::Rf_cons(*head, *tail) + libr::Rf_cons(*head, *tail) }}; } @@ -148,14 +145,14 @@ macro_rules! r_pairlist { // Dotted pairlist entry with injected name. (!!$name:ident = $value:expr $(, $($tts:tt)*)?) => {{ let value = $crate::r_pairlist_impl!($value, $crate::r_pairlist!($($($tts)*)?)); - libR_shim::SET_TAG(value, $name); + libr::SET_TAG(value, $name); value }}; // Dotted pairlist entry. ($name:pat = $value:expr $(, $($tts:tt)*)?) => {{ let value = $crate::r_pairlist_impl!($value, $crate::r_pairlist!($($($tts)*)?)); - libR_shim::SET_TAG(value, r_symbol!(stringify!($name))); + libr::SET_TAG(value, r_symbol!(stringify!($name))); value }}; @@ -166,7 +163,7 @@ macro_rules! r_pairlist { // Empty pairlist. () => { - R_NilValue + libr::R_NilValue }; } @@ -176,7 +173,7 @@ macro_rules! r_lang { ($($tts:tt)*) => {{ let value = $crate::r_pairlist!($($tts)*); - libR_shim::SET_TYPEOF(value, LANGSXP as i32); + libr::SET_TYPEOF(value, libr::LANGSXP as i32); value }} @@ -232,7 +229,7 @@ macro_rules! push_rds { #[cfg(test)] mod tests { - use libR_shim::*; + use libr::*; use super::*; use crate::object::RObject; @@ -268,7 +265,7 @@ mod tests { r_double!(42.0), }); - assert!(Rf_length(*value) == 3); + assert!(Rf_xlength(*value) == 3); let e1 = CAR(*value); assert!(r_typeof(e1) == SYMSXP); @@ -282,7 +279,7 @@ mod tests { assert!(RObject::view(e3).to::().unwrap() == 42.0); let value = RObject::new(r_pairlist! {}); - assert!(Rf_length(*value) == 0); + assert!(Rf_xlength(*value) == 0); let value = RObject::new(r_pairlist! { "a", 12, 42.0 }); diff --git a/crates/harp/src/library.rs b/crates/harp/src/library.rs new file mode 100644 index 000000000..8a291aa95 --- /dev/null +++ b/crates/harp/src/library.rs @@ -0,0 +1,67 @@ +// +// library.rs +// +// Copyright (C) 2024 by Posit Software, PBC +// +// + +use std::env::consts::DLL_PREFIX; +use std::env::consts::DLL_SUFFIX; +use std::path::PathBuf; + +use crate::sys; +pub use crate::sys::library::RLibraries; + +/// Open an R shared library located at the specified `path`. +/// Returned with `'static` lifetime because we `Box::leak()` the `Library`. +pub(crate) fn open_and_leak_r_shared_library(path: &PathBuf) -> &'static libloading::Library { + // Call system specific open helper + let library = sys::library::open_r_shared_library(path); + + let library = match library { + Ok(library) => library, + Err(err) => panic!( + "The R shared library at '{}' could not be opened: {:?}", + path.display(), + err, + ), + }; + + log::info!( + "Successfully opened R shared library at '{}'.", + path.display() + ); + + // Leak the `Library` to ensure that it lives for the lifetime of the program (ark). + // Otherwise, if the library closes then we can't safely access the functions inside it. + let library = Box::new(library); + let library = Box::leak(library); + + library +} + +/// Navigate to an R shared library from `R_HOME` +/// +/// i.e. like `R` or `Rgraphapp` +/// +/// This assumes that the shared library is in the "standard place" below `R_HOME`, which +/// may not always prove to be true. If this ever fails, we will need to revisit our +/// assumptions. +pub(crate) fn find_r_shared_library(home: &PathBuf, name: &str) -> PathBuf { + // Navigate to system specific library folder from `R_HOME` + let folder = crate::sys::library::find_r_shared_library_folder(home); + + // i.e. + // * On macOS: `libR.dylib` + // * On Windows: `R.dll` + // * On Linux: `libR.so` + let name = DLL_PREFIX.to_string() + name + DLL_SUFFIX; + + let path = folder.join(name.as_str()); + + match path.try_exists() { + Ok(true) => return path, + Ok(false) => panic!("Can't find R shared library '{}' at '{}'. If this is a custom build of R, ensure it is compiled with `--enable-R-shlib`.", name, path.display()), + Err(err) => panic!("Can't determine if R shared library path exists: {err:?}"), + } +} diff --git a/crates/harp/src/object.rs b/crates/harp/src/object.rs index 0587d0d23..6b7a99cf5 100644 --- a/crates/harp/src/object.rs +++ b/crates/harp/src/object.rs @@ -14,7 +14,7 @@ use std::os::raw::c_char; use std::os::raw::c_int; use std::sync::Once; -use libR_shim::*; +use libr::*; use crate::error::Error; use crate::exec::RFunction; @@ -669,7 +669,7 @@ impl TryFrom for Vec { r_assert_type(*value, &[STRSXP, NILSXP])?; let mut result: Vec = Vec::new(); - let n = Rf_length(*value) as isize; + let n = Rf_xlength(*value); for i in 0..n { let res = r_chr_get_owned_utf8(*value, i)?; result.push(res); @@ -686,7 +686,7 @@ impl TryFrom for Vec> { unsafe { r_assert_type(*value, &[STRSXP, NILSXP])?; - let n = Rf_length(*value); + let n = Rf_xlength(*value); let mut result: Vec> = Vec::with_capacity(n as usize); for i in 0..n { result.push(value.get_string(i as isize)?); @@ -708,13 +708,13 @@ impl TryFrom for HashMap { let mut protect = RProtect::new(); let value = protect.add(Rf_coerceVector(*value, STRSXP)); - let n = Rf_length(names); + let n = Rf_xlength(names); let mut map = HashMap::::with_capacity(n as usize); - for i in 0..Rf_length(names) { + for i in 0..Rf_xlength(names) { // Translate the name and value into Rust strings. - let lhs = r_chr_get_owned_utf8(names, i as isize)?; - let rhs = r_chr_get_owned_utf8(value, i as isize)?; + let lhs = r_chr_get_owned_utf8(names, i)?; + let rhs = r_chr_get_owned_utf8(value, i)?; map.insert(lhs, rhs); } @@ -726,7 +726,7 @@ impl TryFrom for HashMap { #[cfg(test)] mod tests { - use libR_shim::*; + use libr::SET_STRING_ELT; use super::*; use crate::assert_match; diff --git a/crates/harp/src/protect.rs b/crates/harp/src/protect.rs index 4f9c2ebb4..813dd63a9 100644 --- a/crates/harp/src/protect.rs +++ b/crates/harp/src/protect.rs @@ -5,7 +5,9 @@ // // -use libR_shim::*; +use libr::Rf_protect; +use libr::Rf_unprotect; +use libr::SEXP; // NOTE: The RProtect struct uses R's stack-based object protection, and so is // only appropriate for R objects with 'automatic' lifetime. In general, this diff --git a/crates/harp/src/r_version.rs b/crates/harp/src/r_version.rs index aa5f34430..acdc126d5 100644 --- a/crates/harp/src/r_version.rs +++ b/crates/harp/src/r_version.rs @@ -17,11 +17,9 @@ use crate::exec::RFunctionExt; /// Determine the running R version /// /// Determining the running R version from within ark -/// is complicated by the fact that there is no C _function_ -/// in the R API that returns the version dynamically. Instead, -/// there is an `R_VERSION` macro, but this reflects the R version -/// at libR-sys build time, not at runtime. To determine the running -/// R version, we call out to R once and store the result. +/// is complicated by the fact that there is no C function +/// in the R API that returns the version dynamically. To determine the +/// running R version, we call out to R once and store the result. pub unsafe fn r_version() -> &'static Version { static INIT_R_VERSION: Once = Once::new(); static mut R_VERSION: MaybeUninit = MaybeUninit::uninit(); diff --git a/crates/harp/src/routines.rs b/crates/harp/src/routines.rs index adaa55d22..fff49bf8a 100644 --- a/crates/harp/src/routines.rs +++ b/crates/harp/src/routines.rs @@ -5,7 +5,9 @@ // // -use libR_shim::*; +use libr::R_CallMethodDef; +use libr::R_getEmbeddingDllInfo; +use libr::R_registerRoutines; use log::error; static mut R_ROUTINES: Vec = vec![]; diff --git a/crates/harp/src/session.rs b/crates/harp/src/session.rs index 300c97fd9..54622dc16 100644 --- a/crates/harp/src/session.rs +++ b/crates/harp/src/session.rs @@ -7,8 +7,7 @@ use std::sync::Once; -use libR_shim::*; -use libc::c_int; +use libr::*; use stdext::unwrap; use crate::exec::r_parse; @@ -38,7 +37,7 @@ pub fn r_n_frame() -> crate::error::Result { } } -pub fn r_sys_frame(n: c_int) -> crate::error::Result { +pub fn r_sys_frame(n: std::ffi::c_int) -> crate::error::Result { unsafe { let mut protect = RProtect::new(); let n = protect.add(Rf_ScalarInteger(n)); @@ -122,7 +121,7 @@ pub fn r_stack_info() -> anyhow::Result> { let info = r_try_eval_silent(STACK_INFO_CALL.unwrap(), R_GlobalEnv)?; Rf_protect(info); - let n: isize = Rf_length(info).try_into()?; + let n: isize = Rf_xlength(info).try_into()?; for i in (0..n).rev() { let frame = VECTOR_ELT(info, i); @@ -158,7 +157,7 @@ fn stack_pointer_frame() -> anyhow::Result { srcref = VECTOR_ELT(srcref, 0); } - let n = Rf_length(srcref); + let n = Rf_xlength(srcref); if r_typeof(srcref) != INTSXP || n < 4 { anyhow::bail!("Expected integer vector for srcref"); } diff --git a/crates/harp/src/string.rs b/crates/harp/src/string.rs index e83c5b14b..5aff5d348 100644 --- a/crates/harp/src/string.rs +++ b/crates/harp/src/string.rs @@ -5,7 +5,15 @@ // // -use libR_shim::*; +use libr::ParseStatus; +use libr::R_NaString; +use libr::R_NilValue; +use libr::R_ParseVector; +use libr::Rf_xlength; +use libr::EXPRSXP; +use libr::SEXP; +use libr::STRSXP; +use libr::VECTOR_ELT; use crate::object::RObject; use crate::protect::RProtect; @@ -24,7 +32,7 @@ pub unsafe fn r_string_decode(code: &str) -> Option { // check for string in result if r_typeof(result) == EXPRSXP { - if Rf_length(result) != 0 { + if Rf_xlength(result) != 0 { let value = VECTOR_ELT(result, 0); if r_typeof(value) == STRSXP { return RObject::view(value).to::().ok(); @@ -36,5 +44,5 @@ pub unsafe fn r_string_decode(code: &str) -> Option { } pub fn r_is_string(x: SEXP) -> bool { - unsafe { r_typeof(x) == STRSXP && Rf_length(x) == 1 && x != R_NaString } + unsafe { r_typeof(x) == STRSXP && Rf_xlength(x) == 1 && x != R_NaString } } diff --git a/crates/harp/src/symbol.rs b/crates/harp/src/symbol.rs index 9cbe7ee1f..73ba00427 100644 --- a/crates/harp/src/symbol.rs +++ b/crates/harp/src/symbol.rs @@ -9,7 +9,13 @@ use std::cmp::Ordering; use std::ffi::CStr; use std::ops::Deref; -use libR_shim::*; +use libr::vmaxget; +use libr::vmaxset; +use libr::Rf_translateCharUTF8; +use libr::PRINTNAME; +use libr::R_CHAR; +use libr::SEXP; +use libr::SYMSXP; use crate::error::Result; use crate::object::r_length; diff --git a/crates/harp/src/sys/unix.rs b/crates/harp/src/sys/unix.rs index b05dc356d..15b1e4003 100644 --- a/crates/harp/src/sys/unix.rs +++ b/crates/harp/src/sys/unix.rs @@ -5,5 +5,6 @@ * */ +pub mod library; pub mod line_ending; pub mod polled_events; diff --git a/crates/harp/src/sys/unix/library.rs b/crates/harp/src/sys/unix/library.rs new file mode 100644 index 000000000..31a57a712 --- /dev/null +++ b/crates/harp/src/sys/unix/library.rs @@ -0,0 +1,166 @@ +/* + * library.rs + * + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + * + */ + +use std::path::PathBuf; +use std::process::Command; + +use anyhow::anyhow; +use libloading::os::unix::Library; +use libloading::os::unix::RTLD_GLOBAL; +use libloading::os::unix::RTLD_LAZY; + +use crate::library::find_r_shared_library; +use crate::library::open_and_leak_r_shared_library; + +pub struct RLibraries { + r: &'static libloading::Library, +} + +impl RLibraries { + pub fn from_r_home_path(path: &PathBuf) -> Self { + // Before we open the libraries, set `DYLD_FALLBACK_LIBRARY_PATH` or + // `LD_LIBRARY_PATH` as needed + set_library_path_env_var(path); + + let r_path = find_r_shared_library(&path, "R"); + let r = open_and_leak_r_shared_library(&r_path); + + Self { r } + } + + /// Initialize dynamic bindings to functions and mutable globals. These are required + /// to even start R (for things like `Rf_initialize_R()` and `R_running_as_main_program`). + pub fn initialize_pre_setup_r(&self) { + libr::initialize::functions(self.r); + libr::initialize::functions_variadic(self.r); + libr::initialize::mutable_globals(self.r); + } + + /// After `setup_Rmainloop()` has run, which initializes R's "constant" global variables, + /// we can initialize our own. + pub fn initialize_post_setup_r(&self) { + libr::initialize::constant_globals(self.r); + } +} + +pub fn open_r_shared_library(path: &PathBuf) -> Result { + // Default behavior of `Library` is `RTLD_LAZY | RTLD_LOCAL`. + // In general this makes sense, where you want to isolate modules as much as possible. + // However, for us `libR` is like our main application. + // + // Setting `RTLD_GLOBAL` means that the symbols of the opened library (and its + // dependencies) become available for subsequently loaded libraries WITHOUT them + // needing to use `dlsym()`. Subsequent libraries here can correspond to R packages, + // like `utils.so` or any R package with compiled code. + // + // The main reason we do this is that we believe this most closely reproduces what + // happens when you link your application to `libR.so` at load time rather than + // runtime (i.e. RStudio's `rsession` does load time linking). We believe load time + // linking makes the libR library (and therefore its symbols) available globally to + // downstream loaded libraries. + // + // More discussion in: + // https://github.com/posit-dev/amalthea/pull/205 + let flags = RTLD_LAZY | RTLD_GLOBAL; + + let library = unsafe { Library::open(Some(&path), flags) }; + + // Map from the OS specific `Library` into the cross platform `Library` + let library = library.map(|library| library.into()); + + library +} + +pub fn find_r_shared_library_folder(path: &PathBuf) -> PathBuf { + path.join("lib") +} + +#[cfg(target_os = "macos")] +const LIBRARY_PATH_ENVVAR: &'static str = "DYLD_FALLBACK_LIBRARY_PATH"; +#[cfg(target_os = "linux")] +const LIBRARY_PATH_ENVVAR: &'static str = "LD_LIBRARY_PATH"; + +fn set_library_path_env_var(path: &PathBuf) { + // In the future, we may add additional paths to the env var beyond just what R + // gives us, like RStudio does. + // https://github.com/rstudio/rstudio/blob/50d1a034a04188b42cf7560a86a268a95e62d129/src/cpp/core/r_util/REnvironmentPosix.cpp#L817 + + let mut paths = Vec::new(); + + // Expect that this includes the existing env var value, if there was one + match source_ldpaths_script(path) { + Ok(path) => paths.push(path), + Err(err) => log::error!("Failed to source `ldpaths` script: {err:?}."), + } + + // Only set if we have something + if paths.is_empty() { + return; + } + + let paths = paths.join(":"); + + log::info!("Setting '{LIBRARY_PATH_ENVVAR}' env var to '{paths}'."); + + std::env::set_var(LIBRARY_PATH_ENVVAR, paths); +} + +/// Source `{R_HOME}/etc/ldpaths` +/// +/// - On macOS, this is for `DYLD_FALLBACK_LIBRARY_PATH` +/// - On linux, this is for `LD_LIBRARY_PATH` +/// +/// This is a file that R provides which adds the `{R_HOME}/lib/` directory and a Java +/// related directory (relevant for rJava, apparently) to the relevant library path env +/// var. +/// +/// Adding R's `lib/` directory to the front of `LD_LIBRARY_PATH` is particularly +/// important. We open `libR` with `RTLD_GLOBAL`, but there are other libs shipped by R +/// in that `lib/` folder that other packages might link to, and having the `lib/` folder +/// included in `LD_LIBRARY_PATH` is how those packages will find those libs. +fn source_ldpaths_script(path: &PathBuf) -> anyhow::Result { + let ldpaths = path.join("etc").join("ldpaths"); + + let Some(ldpaths) = ldpaths.to_str() else { + let ldpaths = ldpaths.to_string_lossy(); + return Err(anyhow!( + "Failed to convert `ldpaths` path to UTF-8 string: '{ldpaths}'" + )); + }; + + // Source (i.e. `.`) the `ldpaths` file into the current bash session, and then + // print out the relevant env var that it set. `printf` is more portable than `echo -n`. + let command = format!(". {ldpaths} && printf '%s' \"${LIBRARY_PATH_ENVVAR}\""); + + // Need to ensure `R_HOME` is set, as `ldpaths` references it. + // Expect that `ldpaths` appends to an existing env var if there is one, + // rather than overwriting it, so we don't have to do that. + let output = Command::new("sh") + .env("R_HOME", &path) + .arg("-c") + .arg(command) + .output()?; + + if !output.status.success() { + let status = output.status; + return Err(anyhow!("Failed with status: {status}")); + } + if !output.stderr.is_empty() { + let stderr = String::from_utf8(output.stderr)?; + return Err(anyhow!("Unexpected output on stderr: '{stderr}'")); + } + + let value = String::from_utf8(output.stdout)?; + + if value.is_empty() { + return Err(anyhow!( + "Empty string returned for '{LIBRARY_PATH_ENVVAR}'. Expected at least one path." + )); + } + + Ok(value) +} diff --git a/crates/harp/src/sys/unix/polled_events.rs b/crates/harp/src/sys/unix/polled_events.rs index 3cbc74b7c..8e5b2dba1 100644 --- a/crates/harp/src/sys/unix/polled_events.rs +++ b/crates/harp/src/sys/unix/polled_events.rs @@ -5,6 +5,8 @@ // // +use libr::R_PolledEvents; + static mut R_POLLED_EVENTS_OLD: Option = None; static mut R_POLLED_EVENTS_SUSPENDED: i32 = 0; @@ -14,8 +16,8 @@ impl RPolledEventsSuspendedScope { pub fn new() -> Self { unsafe { if R_POLLED_EVENTS_SUSPENDED == 0 { - R_POLLED_EVENTS_OLD = R_PolledEvents; - R_PolledEvents = Some(r_polled_events_disabled); + R_POLLED_EVENTS_OLD = libr::get(R_PolledEvents); + libr::set(R_PolledEvents, Some(r_polled_events_disabled)); } R_POLLED_EVENTS_SUSPENDED += 1; } @@ -30,15 +32,11 @@ impl Drop for RPolledEventsSuspendedScope { R_POLLED_EVENTS_SUSPENDED -= 1; if R_POLLED_EVENTS_SUSPENDED == 0 { - R_PolledEvents = R_POLLED_EVENTS_OLD; + libr::set(R_PolledEvents, R_POLLED_EVENTS_OLD); } } } } -extern "C" { - static mut R_PolledEvents: Option; -} - #[no_mangle] extern "C" fn r_polled_events_disabled() {} diff --git a/crates/harp/src/sys/windows.rs b/crates/harp/src/sys/windows.rs index 20765c7d0..95932ce46 100644 --- a/crates/harp/src/sys/windows.rs +++ b/crates/harp/src/sys/windows.rs @@ -5,5 +5,6 @@ * */ +pub mod library; pub mod line_ending; pub mod polled_events; diff --git a/crates/harp/src/sys/windows/library.rs b/crates/harp/src/sys/windows/library.rs new file mode 100644 index 000000000..e9d96834b --- /dev/null +++ b/crates/harp/src/sys/windows/library.rs @@ -0,0 +1,94 @@ +/* + * library.rs + * + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + * + */ + +use std::path::PathBuf; + +use libloading::os::windows::Library; +use libloading::os::windows::LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR; +use libloading::os::windows::LOAD_LIBRARY_SEARCH_SYSTEM32; + +use crate::library::find_r_shared_library; +use crate::library::open_and_leak_r_shared_library; + +pub struct RLibraries { + r: &'static libloading::Library, + r_graphapp: &'static libloading::Library, + // We don't currently initialize bindings from these three, + // but keep them around for consistency + _r_lapack: &'static libloading::Library, + _r_iconv: &'static libloading::Library, + _r_blas: &'static libloading::Library, +} + +impl RLibraries { + pub fn from_r_home_path(path: &PathBuf) -> Self { + let r_path = find_r_shared_library(&path, "R"); + let r = open_and_leak_r_shared_library(&r_path); + + // On Windows, we preemptively open the supporting R DLLs that live in + // `bin/x64/` before starting R. R packages are allowed to link to these + // DLLs, like stats, and they must be able to find them when the packages + // are loaded. Because we don't add the `bin/x64` folder to the `PATH`, + // we instead open these 4 DLLs preemptively and rely on the fact that the + // "Loaded-module list" is part of the standard search path for dynamic link + // library searching. + // https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order + let r_graphapp_path = find_r_shared_library(&path, "Rgraphapp"); + let r_graphapp = open_and_leak_r_shared_library(&r_graphapp_path); + + let r_lapack_path = find_r_shared_library(&path, "Rlapack"); + let r_lapack = open_and_leak_r_shared_library(&r_lapack_path); + + let r_iconv_path = find_r_shared_library(&path, "Riconv"); + let r_iconv = open_and_leak_r_shared_library(&r_iconv_path); + + let r_blas_path = find_r_shared_library(&path, "Rblas"); + let r_blas = open_and_leak_r_shared_library(&r_blas_path); + + Self { + r, + r_graphapp, + _r_lapack: r_lapack, + _r_iconv: r_iconv, + _r_blas: r_blas, + } + } + + pub fn initialize_pre_setup_r(&self) { + // R + libr::initialize::functions(self.r); + libr::initialize::functions_variadic(self.r); + libr::initialize::mutable_globals(self.r); + + // Rgraphapp + libr::graphapp::initialize::functions(self.r_graphapp); + } + + pub fn initialize_post_setup_r(&self) { + libr::initialize::constant_globals(self.r); + } +} + +pub fn open_r_shared_library(path: &PathBuf) -> Result { + // Each R shared library may have its own set of DLL dependencies. For example, + // `R.dll` depends on `Rblas.dll` and some DLLs in system32. For each of the R DLLs we load, + // the combination of R's DLL folder (i.e. `bin/x64`) and the system32 folder are enough to + // load it, so we instruct libloading to tell the Windows function `LoadLibraryExW()` to + // search those two places when looking for dependencies. + let flags = LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32; + + let library = unsafe { Library::load_with_flags(path, flags) }; + + // Map from the OS specific `Library` into the cross platform `Library` + let library = library.map(|library| library.into()); + + library +} + +pub fn find_r_shared_library_folder(path: &PathBuf) -> PathBuf { + path.join("bin").join("x64") +} diff --git a/crates/harp/src/test.rs b/crates/harp/src/test.rs index 1c1b8bf68..562d3dd4b 100644 --- a/crates/harp/src/test.rs +++ b/crates/harp/src/test.rs @@ -14,14 +14,18 @@ #![allow(dead_code)] use std::os::raw::c_char; +use std::path::PathBuf; use std::process::Command; use std::sync::Mutex; use std::sync::Once; -use libR_shim::*; +use libr::setup_Rmainloop; +use libr::R_CStackLimit; +use libr::Rf_initialize_R; use stdext::cargs; use crate::exec::r_sandbox; +use crate::library::RLibraries; use crate::R_MAIN_THREAD_ID; // Escape hatch for unit tests. We need this because the default @@ -41,31 +45,24 @@ pub fn start_r() { R_MAIN_THREAD_ID = Some(std::thread::current().id()); } - // TODO: Right now, tests can fail if the version of R discovered - // on the PATH, and the version of R that 'ark' linked to at compile - // time, do not match. We could relax this requirement by allowing - // 'ark' to have undefined symbols, and use the DYLD_INSERT_LIBRARIES - // trick to insert the right version of R when 'ark' is launched, - // but for now we just have this comment as a reminder. - // Set up R_HOME if necessary. - if let Err(_) = std::env::var("R_HOME") { - let result = Command::new("R").arg("RHOME").output().unwrap(); - let home = String::from_utf8(result.stdout).unwrap(); - std::env::set_var("R_HOME", home.trim()); - } + let r_home = match std::env::var("R_HOME") { + Ok(r_home) => PathBuf::from(r_home), + Err(_) => { + let result = Command::new("R").arg("RHOME").output().unwrap(); + let r_home = String::from_utf8(result.stdout).unwrap(); + let r_home = r_home.trim(); + std::env::set_var("R_HOME", r_home); + PathBuf::from(r_home) + }, + }; - // Build the argument list for Rf_initialize_R - let mut arguments = cargs!["R", "--slave", "--no-save", "--no-restore"]; + let libraries = RLibraries::from_r_home_path(&r_home); + libraries.initialize_pre_setup_r(); - unsafe { - Rf_initialize_R( - arguments.len() as i32, - arguments.as_mut_ptr() as *mut *mut c_char, - ); - R_CStackLimit = usize::MAX; - setup_Rmainloop(); - } + setup_r(); + + libraries.initialize_post_setup_r(); // Initialize harp globals unsafe { @@ -75,6 +72,20 @@ pub fn start_r() { }); } +fn setup_r() { + // Build the argument list for Rf_initialize_R + let mut arguments = cargs!["R", "--slave", "--no-save", "--no-restore"]; + + unsafe { + Rf_initialize_R( + arguments.len() as i32, + arguments.as_mut_ptr() as *mut *mut c_char, + ); + libr::set(R_CStackLimit, usize::MAX); + setup_Rmainloop(); + } +} + pub fn r_test(f: F) { start_r(); let guard = unsafe { R_RUNTIME_LOCK.lock() }; diff --git a/crates/harp/src/utils.rs b/crates/harp/src/utils.rs index 1fd48d0d0..dd577524b 100644 --- a/crates/harp/src/utils.rs +++ b/crates/harp/src/utils.rs @@ -7,16 +7,14 @@ use std::ffi::CStr; use std::ffi::CString; -use std::os::raw::c_void; use std::path::Path; use c2rust_bitfields::BitfieldStruct; use harp_macros::register; use itertools::Itertools; -use libR_shim::*; +use libr::*; use once_cell::sync::Lazy; use regex::Regex; -use semver::Version; use stdext::unwrap; use crate::environment::Environment; @@ -33,7 +31,6 @@ use crate::protect::RProtect; use crate::r_char; use crate::r_lang; use crate::r_symbol; -use crate::r_version::r_version; use crate::string::r_is_string; use crate::symbol::RSymbol; use crate::vector::CharacterVector; @@ -47,10 +44,6 @@ pub static mut HARP_ENV: SEXP = std::ptr::null_mut(); static RE_SYNTACTIC_IDENTIFIER: Lazy = Lazy::new(|| Regex::new(r"^[\p{L}\p{Nl}.][\p{L}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}.]*$").unwrap()); -extern "C" { - fn R_removeVarFromFrame(symbol: SEXP, envir: SEXP) -> c_void; -} - #[derive(Copy, Clone, BitfieldStruct)] #[repr(C)] pub struct Sxpinfo { @@ -110,7 +103,7 @@ pub extern "C" fn harp_log_warning(msg: SEXP) -> crate::error::Result { let msg = String::try_from(RObject::view(msg))?; log::warn!("{msg}"); - unsafe { Ok(R_NilValue) } + unsafe { Ok(libr::R_NilValue) } } #[register] @@ -118,7 +111,7 @@ pub extern "C" fn harp_log_error(msg: SEXP) -> crate::error::Result { let msg = String::try_from(RObject::view(msg))?; log::error!("{msg}"); - unsafe { Ok(R_NilValue) } + unsafe { Ok(libr::R_NilValue) } } pub fn r_assert_type(object: SEXP, expected: &[u32]) -> Result { @@ -132,7 +125,7 @@ pub fn r_assert_type(object: SEXP, expected: &[u32]) -> Result { } pub unsafe fn r_assert_capacity(object: SEXP, required: usize) -> Result { - let actual = Rf_length(object) as usize; + let actual = Rf_xlength(object) as usize; if actual < required { return Err(Error::UnexpectedLength(actual, required)); } @@ -154,7 +147,7 @@ pub fn r_is_data_frame(object: SEXP) -> bool { } pub fn r_is_null(object: SEXP) -> bool { - unsafe { object == R_NilValue } + unsafe { object == libr::R_NilValue } } pub fn r_is_altrep(object: SEXP) -> bool { @@ -194,7 +187,7 @@ pub fn r_classes(value: SEXP) -> Option { unsafe { let classes = RObject::from(Rf_getAttrib(value, R_ClassSymbol)); - if *classes == R_NilValue { + if *classes == libr::R_NilValue { None } else { Some(CharacterVector::new_unchecked(classes)) @@ -206,7 +199,7 @@ pub fn r_classes(value: SEXP) -> Option { /// /// - `x` is the R vector to translate from. /// - `i` is the index in the vector of the string to translate. -pub fn r_chr_get_owned_utf8(x: *mut SEXPREC, i: isize) -> Result { +pub fn r_chr_get_owned_utf8(x: SEXP, i: isize) -> Result { unsafe { r_str_to_owned_utf8(STRING_ELT(x, i)) } } @@ -252,7 +245,7 @@ pub fn r_str_to_owned_utf8_unchecked(x: SEXP) -> String { pub fn pairlist_size(mut pairlist: SEXP) -> Result { let mut n = 0; unsafe { - while pairlist != R_NilValue { + while pairlist != libr::R_NilValue { r_assert_type(pairlist, &[LISTSXP])?; pairlist = CDR(pairlist); @@ -263,7 +256,7 @@ pub fn pairlist_size(mut pairlist: SEXP) -> Result { } pub fn r_vec_is_single_dimension_with_single_value(value: SEXP) -> bool { - unsafe { Rf_getAttrib(value, R_DimSymbol) == R_NilValue && Rf_xlength(value) == 1 } + unsafe { Rf_getAttrib(value, R_DimSymbol) == libr::R_NilValue && Rf_xlength(value) == 1 } } pub fn r_vec_type(value: SEXP) -> String { @@ -271,7 +264,7 @@ pub fn r_vec_type(value: SEXP) -> String { INTSXP => unsafe { if r_inherits(value, "factor") { let levels = Rf_getAttrib(value, R_LevelsSymbol); - format!("fct({})", XLENGTH(levels)) + format!("fct({})", Rf_xlength(levels)) } else { String::from("int") } @@ -355,7 +348,7 @@ pub unsafe fn r_formals(object: SEXP) -> Result> { // iterate through the entries let mut arguments = Vec::new(); - while formals != R_NilValue { + while formals != libr::R_NilValue { let name = RObject::from(TAG(formals)).to::()?; let value = CAR(formals); arguments.push(RArgument::new(name.as_str(), RObject::new(value))); @@ -491,10 +484,8 @@ pub unsafe fn r_promise_is_lazy_load_binding(x: SEXP) -> bool { } pub unsafe fn r_env_has(env: SEXP, sym: SEXP) -> bool { - const R_4_2_0: Version = Version::new(4, 2, 0); - - if r_version() >= &R_4_2_0 { - R_existsVarInFrame(env, sym) == Rboolean_TRUE + if libr::has::R_existsVarInFrame() { + libr::R_existsVarInFrame(env, sym) == libr::Rboolean_TRUE } else { // Not particularly fast, but seems to be good enough for checking symbol // existance during completion generation @@ -530,7 +521,7 @@ pub unsafe fn r_pkg_env_name(env: SEXP) -> SEXP { let name = R_PackageEnvName(env); - if name == R_NilValue { + if name == libr::R_NilValue { // Should be very unlikely, but `NULL` can be returned return r_char!(""); } @@ -551,7 +542,7 @@ pub unsafe fn r_ns_env_name(env: SEXP) -> SEXP { let spec = protect.add(R_NamespaceEnvSpec(env)); - if spec == R_NilValue { + if spec == libr::R_NilValue { // Should be very unlikely, but `NULL` can be returned return r_char!(""); } diff --git a/crates/harp/src/vector/character_vector.rs b/crates/harp/src/vector/character_vector.rs index 1621d7947..bf490e858 100644 --- a/crates/harp/src/vector/character_vector.rs +++ b/crates/harp/src/vector/character_vector.rs @@ -7,7 +7,14 @@ use std::os::raw::c_char; -use libR_shim::*; +use libr::cetype_t_CE_UTF8; +use libr::R_NaString; +use libr::R_xlen_t; +use libr::Rf_mkCharLenCE; +use libr::SET_STRING_ELT; +use libr::SEXP; +use libr::STRING_ELT; +use libr::STRSXP; use crate::object::RObject; use crate::utils::r_str_to_owned_utf8_unchecked; @@ -80,6 +87,8 @@ impl Vector for CharacterVector { #[cfg(test)] mod test { + use libr::STRSXP; + use crate::r_test; use crate::utils::r_typeof; use crate::vector::*; diff --git a/crates/harp/src/vector/complex_vector.rs b/crates/harp/src/vector/complex_vector.rs index 5789d245f..a649fb374 100644 --- a/crates/harp/src/vector/complex_vector.rs +++ b/crates/harp/src/vector/complex_vector.rs @@ -5,7 +5,14 @@ // // -use libR_shim::*; +use libr::R_IsNA; +use libr::R_xlen_t; +use libr::Rcomplex; +use libr::Rf_allocVector; +use libr::COMPLEX_ELT; +use libr::CPLXSXP; +use libr::DATAPTR; +use libr::SEXP; use crate::object::RObject; use crate::vector::Vector; diff --git a/crates/harp/src/vector/factor.rs b/crates/harp/src/vector/factor.rs index 9ae6551eb..5c7d23f5f 100644 --- a/crates/harp/src/vector/factor.rs +++ b/crates/harp/src/vector/factor.rs @@ -5,7 +5,14 @@ // // -use libR_shim::*; +use libr::R_NaInt; +use libr::R_xlen_t; +use libr::Rf_allocVector; +use libr::Rf_getAttrib; +use libr::DATAPTR; +use libr::INTEGER_ELT; +use libr::INTSXP; +use libr::SEXP; use crate::object::RObject; use crate::r_symbol; diff --git a/crates/harp/src/vector/formatted_vector.rs b/crates/harp/src/vector/formatted_vector.rs index c65b7f0a7..aa07dc213 100644 --- a/crates/harp/src/vector/formatted_vector.rs +++ b/crates/harp/src/vector/formatted_vector.rs @@ -4,7 +4,17 @@ // Copyright (C) 2023 Posit Software, PBC. All rights reserved. // // -use libR_shim::*; +use libr::R_ClassSymbol; +use libr::R_DimSymbol; +use libr::Rf_getAttrib; +use libr::Rf_xlength; +use libr::CPLXSXP; +use libr::INTSXP; +use libr::LGLSXP; +use libr::RAWSXP; +use libr::REALSXP; +use libr::SEXP; +use libr::STRSXP; use crate::error::Error; use crate::error::Result; diff --git a/crates/harp/src/vector/integer_vector.rs b/crates/harp/src/vector/integer_vector.rs index 9c822e19a..7e010de36 100644 --- a/crates/harp/src/vector/integer_vector.rs +++ b/crates/harp/src/vector/integer_vector.rs @@ -5,7 +5,13 @@ // // -use libR_shim::*; +use libr::R_NaInt; +use libr::R_xlen_t; +use libr::Rf_allocVector; +use libr::DATAPTR; +use libr::INTEGER_ELT; +use libr::INTSXP; +use libr::SEXP; use crate::object::RObject; use crate::vector::Vector; diff --git a/crates/harp/src/vector/logical_vector.rs b/crates/harp/src/vector/logical_vector.rs index 270d47c34..f819e64df 100644 --- a/crates/harp/src/vector/logical_vector.rs +++ b/crates/harp/src/vector/logical_vector.rs @@ -5,7 +5,13 @@ // // -use libR_shim::*; +use libr::R_NaInt; +use libr::R_xlen_t; +use libr::Rf_allocVector; +use libr::DATAPTR; +use libr::LGLSXP; +use libr::LOGICAL_ELT; +use libr::SEXP; use crate::object::RObject; use crate::vector::Vector; diff --git a/crates/harp/src/vector/mod.rs b/crates/harp/src/vector/mod.rs index a7b5e31b0..a829c8369 100644 --- a/crates/harp/src/vector/mod.rs +++ b/crates/harp/src/vector/mod.rs @@ -5,7 +5,9 @@ // // -use libR_shim::*; +use libr::Rf_allocVector; +use libr::Rf_xlength; +use libr::SEXP; use crate::error::Result; use crate::utils::r_assert_capacity; diff --git a/crates/harp/src/vector/names.rs b/crates/harp/src/vector/names.rs index 3af4063d0..0c23787bc 100644 --- a/crates/harp/src/vector/names.rs +++ b/crates/harp/src/vector/names.rs @@ -4,7 +4,9 @@ // Copyright (C) 2022 Posit Software, PBC. All rights reserved. // // -use libR_shim::*; +use libr::R_NamesSymbol; +use libr::Rf_getAttrib; +use libr::SEXP; use crate::object::RObject; use crate::utils::r_is_null; diff --git a/crates/harp/src/vector/numeric_vector.rs b/crates/harp/src/vector/numeric_vector.rs index 1a45b9631..90c5adef1 100644 --- a/crates/harp/src/vector/numeric_vector.rs +++ b/crates/harp/src/vector/numeric_vector.rs @@ -5,7 +5,13 @@ // // -use libR_shim::*; +use libr::R_IsNA; +use libr::R_xlen_t; +use libr::Rf_allocVector; +use libr::DATAPTR; +use libr::REALSXP; +use libr::REAL_ELT; +use libr::SEXP; use crate::object::RObject; use crate::vector::Vector; diff --git a/crates/harp/src/vector/raw_vector.rs b/crates/harp/src/vector/raw_vector.rs index dc3956420..faf4efa23 100644 --- a/crates/harp/src/vector/raw_vector.rs +++ b/crates/harp/src/vector/raw_vector.rs @@ -5,7 +5,12 @@ // // -use libR_shim::*; +use libr::R_xlen_t; +use libr::Rf_allocVector; +use libr::DATAPTR; +use libr::RAWSXP; +use libr::RAW_ELT; +use libr::SEXP; use crate::object::RObject; use crate::vector::Vector; diff --git a/crates/libR-shim/Cargo.toml b/crates/libR-shim/Cargo.toml deleted file mode 100644 index 520ae546b..000000000 --- a/crates/libR-shim/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "libR-shim" -version = "0.1.0" -edition = "2021" -rust-version = "1.70.0" -description = """ -Shim over libR-sys bindings, fixing up global variables for Windows and exposing -additional IDE specific tools. -""" - -[dependencies] -libR-sys = "0.5.0" - -# The libR-sys package doesn't currently provide bindings for R on aarch64 -# Linux platforms, so on those systems we need to add the use-bingen feature, -# which causes the package to build its own bindings from scratch. -# -# Note that this typically requires the `llvm` and `clang` packages to be -# installed on the build host. -[target.'cfg(all(target_os="linux", target_arch="aarch64"))'.dependencies] -libR-sys = { version = "0.5.0", features = ["use-bindgen"] } diff --git a/crates/libR-shim/src/lib.rs b/crates/libR-shim/src/lib.rs deleted file mode 100644 index d42d7807a..000000000 --- a/crates/libR-shim/src/lib.rs +++ /dev/null @@ -1,319 +0,0 @@ -// -// lib.rs -// -// Copyright (C) 2023 Posit Software, PBC. All rights reserved. -// -// - -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(improper_ctypes)] - -pub use libR_sys::cetype_t_CE_UTF8; -pub use libR_sys::pDevDesc; -pub use libR_sys::pGEcontext; -pub use libR_sys::setup_Rmainloop; -pub use libR_sys::vmaxget; -pub use libR_sys::vmaxset; -pub use libR_sys::GEcurrentDevice; -pub use libR_sys::GEinitDisplayList; -pub use libR_sys::ParseStatus; -pub use libR_sys::ParseStatus_PARSE_ERROR; -pub use libR_sys::ParseStatus_PARSE_INCOMPLETE; -pub use libR_sys::ParseStatus_PARSE_NULL; -pub use libR_sys::ParseStatus_PARSE_OK; -pub use libR_sys::R_BindingIsActive; -pub use libR_sys::R_CallMethodDef; -pub use libR_sys::R_CheckStack; -pub use libR_sys::R_CheckStack2; -pub use libR_sys::R_CheckUserInterrupt; -pub use libR_sys::R_ExternalPtrAddr; -pub use libR_sys::R_GE_getVersion; -pub use libR_sys::R_IsNA; -pub use libR_sys::R_IsNamespaceEnv; -pub use libR_sys::R_IsPackageEnv; -pub use libR_sys::R_MakeExternalPtr; -pub use libR_sys::R_NamespaceEnvSpec; -pub use libR_sys::R_PackageEnvName; -pub use libR_sys::R_ParseVector; -pub use libR_sys::R_PreserveObject; -pub use libR_sys::R_RunPendingFinalizers; -pub use libR_sys::R_ToplevelExec; -pub use libR_sys::R_altrep_data1; -pub use libR_sys::R_altrep_data2; -pub use libR_sys::R_curErrorBuf; -pub use libR_sys::R_do_slot; -pub use libR_sys::R_existsVarInFrame; -pub use libR_sys::R_getEmbeddingDllInfo; -pub use libR_sys::R_lsInternal; -pub use libR_sys::R_registerRoutines; -pub use libR_sys::R_tryCatch; -pub use libR_sys::R_tryEvalSilent; -pub use libR_sys::R_xlen_t; -pub use libR_sys::Rboolean; -pub use libR_sys::Rboolean_FALSE; -pub use libR_sys::Rboolean_TRUE; -pub use libR_sys::Rcomplex; -pub use libR_sys::Rf_GetOption1; -pub use libR_sys::Rf_ScalarInteger; -pub use libR_sys::Rf_ScalarLogical; -pub use libR_sys::Rf_ScalarReal; -pub use libR_sys::Rf_ScalarString; -pub use libR_sys::Rf_allocVector; -pub use libR_sys::Rf_asInteger; -pub use libR_sys::Rf_coerceVector; -pub use libR_sys::Rf_cons; -pub use libR_sys::Rf_defineVar; -pub use libR_sys::Rf_error; -pub use libR_sys::Rf_errorcall; -pub use libR_sys::Rf_eval; -pub use libR_sys::Rf_findVar; -pub use libR_sys::Rf_findVarInFrame; -pub use libR_sys::Rf_getAttrib; -pub use libR_sys::Rf_inherits; -pub use libR_sys::Rf_initialize_R; -pub use libR_sys::Rf_install; -pub use libR_sys::Rf_isFunction; -pub use libR_sys::Rf_isInteger; -pub use libR_sys::Rf_isMatrix; -pub use libR_sys::Rf_isNumeric; -pub use libR_sys::Rf_isString; -pub use libR_sys::Rf_lang1; -pub use libR_sys::Rf_lang2; -pub use libR_sys::Rf_lang3; -pub use libR_sys::Rf_lang4; -pub use libR_sys::Rf_lcons; -pub use libR_sys::Rf_length; -pub use libR_sys::Rf_mkCharLenCE; -pub use libR_sys::Rf_mkString; -pub use libR_sys::Rf_onintr; -pub use libR_sys::Rf_protect; -pub use libR_sys::Rf_setAttrib; -pub use libR_sys::Rf_setVar; -pub use libR_sys::Rf_translateCharUTF8; -pub use libR_sys::Rf_type2char; -pub use libR_sys::Rf_unprotect; -pub use libR_sys::Rf_xlength; -pub use libR_sys::ALTREP; -pub use libR_sys::ALTREP_CLASS; -pub use libR_sys::ATTRIB; -pub use libR_sys::BUILTINSXP; -pub use libR_sys::CADDDR; -pub use libR_sys::CADDR; -pub use libR_sys::CADR; -pub use libR_sys::CAR; -pub use libR_sys::CDDDR; -pub use libR_sys::CDDR; -pub use libR_sys::CDR; -pub use libR_sys::CHARSXP; -pub use libR_sys::CLOSXP; -pub use libR_sys::COMPLEX_ELT; -pub use libR_sys::CPLXSXP; -pub use libR_sys::DATAPTR; -pub use libR_sys::ENCLOS; -pub use libR_sys::ENVSXP; -pub use libR_sys::EXPRSXP; -pub use libR_sys::FILE; -pub use libR_sys::FORMALS; -pub use libR_sys::FRAME; -pub use libR_sys::HASHTAB; -pub use libR_sys::INTEGER_ELT; -pub use libR_sys::INTSXP; -pub use libR_sys::LANGSXP; -pub use libR_sys::LGLSXP; -pub use libR_sys::LISTSXP; -pub use libR_sys::LOGICAL; -pub use libR_sys::LOGICAL_ELT; -pub use libR_sys::NILSXP; -pub use libR_sys::PRCODE; -pub use libR_sys::PRENV; -pub use libR_sys::PRINTNAME; -pub use libR_sys::PROMSXP; -pub use libR_sys::PRVALUE; -pub use libR_sys::RAW; -pub use libR_sys::RAWSXP; -pub use libR_sys::RAW_ELT; -pub use libR_sys::RDEBUG; -pub use libR_sys::REAL; -pub use libR_sys::REALSXP; -pub use libR_sys::REAL_ELT; -pub use libR_sys::R_CHAR; -pub use libR_sys::SETCAR; -pub use libR_sys::SETCDR; -pub use libR_sys::SET_PRVALUE; -pub use libR_sys::SET_STRING_ELT; -pub use libR_sys::SET_TAG; -pub use libR_sys::SET_TYPEOF; -pub use libR_sys::SET_VECTOR_ELT; -pub use libR_sys::SEXP; -pub use libR_sys::SEXPREC; -pub use libR_sys::SPECIALSXP; -pub use libR_sys::STRING_ELT; -pub use libR_sys::STRSXP; -pub use libR_sys::SYMSXP; -pub use libR_sys::TAG; -pub use libR_sys::TYPEOF; -pub use libR_sys::VECSXP; -pub use libR_sys::VECTOR_ELT; -pub use libR_sys::XLENGTH; - -// Functions not exported by libR_sys, but we need them as an IDE -#[link(name = "R", kind = "dylib")] -extern "C" { - pub fn run_Rmainloop(); - pub fn R_HomeDir() -> *mut ::std::os::raw::c_char; - pub fn R_ProcessEvents(); -} - -// Global variables not exported by libR_sys, but we need them as an IDE -#[link(name = "R", kind = "dylib")] -extern "C" { - // Special declaration for this global variable - // - // I don't fully understand this! - // - // This is exposed in Rinterface.h, which is not available on Windows: - // https://github.com/wch/r-source/blob/459492bc14ad5a3ff735d90a70ad71f6d5fe9faa/src/include/Rinterface.h#L176 - // But is defined as a global variable in main.c, so presumably that is what RStudio is yanking out - // https://github.com/wch/r-source/blob/459492bc14ad5a3ff735d90a70ad71f6d5fe9faa/src/main/main.c#L729 - // It controls whether R level signal handlers are set up, which presumably we don't want - // https://github.com/wch/r-source/blob/459492bc14ad5a3ff735d90a70ad71f6d5fe9faa/src/main/main.c#L1047 - // RStudio sets this, and I think they access it by using this dllimport - // https://github.com/rstudio/rstudio/blob/07ef754fc9f27d41b100bb40d83ec3ddf485b47b/src/cpp/r/include/r/RInterface.hpp#L40 - // A normal declaration won't work here, as global variables on Windows seem to require an explicit dllimport to access them, - // according to this SO post, specifying the `kind` is a way to force that in the generated code - // https://stackoverflow.com/questions/66181735/rust-how-to-use-global-variable-from-dll-c-equivalent-requires-declspecdl - pub static mut R_SignalHandlers: ::std::os::raw::c_int; -} - -// Global variables exported by libR_sys, but without the `#[link]` attribute, -// so they don't work on Windows -#[link(name = "R", kind = "dylib")] -extern "C" { - pub static mut R_interrupts_suspended: Rboolean; - pub static mut R_interrupts_pending: ::std::os::raw::c_int; - - #[doc = "IEEE NaN"] - pub static mut R_NaN: f64; - #[doc = "IEEE Inf"] - pub static mut R_PosInf: f64; - #[doc = "IEEE -Inf"] - pub static mut R_NegInf: f64; - #[doc = "NA_REAL: IEEE"] - pub static mut R_NaReal: f64; - #[doc = "NA_INTEGER:= INT_MIN currently"] - pub static mut R_NaInt: ::std::os::raw::c_int; - - #[doc = "C stack limit"] - pub static mut R_CStackLimit: usize; - - #[doc = "The \"global\" environment"] - pub static mut R_GlobalEnv: SEXP; - #[doc = "An empty environment at the root of the\nenvironment tree"] - pub static mut R_EmptyEnv: SEXP; - #[doc = "The base environment; formerly R_NilValue"] - pub static mut R_BaseEnv: SEXP; - #[doc = "The (fake) namespace for base"] - pub static mut R_BaseNamespace: SEXP; - #[doc = "Registry for registered namespaces"] - pub static mut R_NamespaceRegistry: SEXP; - #[doc = "Current srcref, for debuggers"] - pub static mut R_Srcref: SEXP; - #[doc = "The nil object"] - pub static mut R_NilValue: SEXP; - #[doc = "Unbound marker"] - pub static mut R_UnboundValue: SEXP; - #[doc = "Missing argument marker"] - pub static mut R_MissingArg: SEXP; - #[doc = "To be found in BC interp. state\n(marker)"] - pub static mut R_InBCInterpreter: SEXP; - #[doc = "Use current expression (marker)"] - pub static mut R_CurrentExpression: SEXP; - #[doc = "Marker for restarted function calls"] - pub static mut R_RestartToken: SEXP; - #[doc = "\"as.character\""] - pub static mut R_AsCharacterSymbol: SEXP; - #[doc = "\"@\""] - pub static mut R_AtsignSymbol: SEXP; - #[doc = "<-- backcompatible version of:"] - pub static mut R_baseSymbol: SEXP; - #[doc = "\"base\""] - pub static mut R_BaseSymbol: SEXP; - #[doc = "\"{\""] - pub static mut R_BraceSymbol: SEXP; - #[doc = "\"\\[\\[\""] - pub static mut R_Bracket2Symbol: SEXP; - #[doc = "\"\\[\""] - pub static mut R_BracketSymbol: SEXP; - #[doc = "\"class\""] - pub static mut R_ClassSymbol: SEXP; - #[doc = "\".Device\""] - pub static mut R_DeviceSymbol: SEXP; - #[doc = "\"dimnames\""] - pub static mut R_DimNamesSymbol: SEXP; - #[doc = "\"dim\""] - pub static mut R_DimSymbol: SEXP; - #[doc = "\"$\""] - pub static mut R_DollarSymbol: SEXP; - #[doc = "\"...\""] - pub static mut R_DotsSymbol: SEXP; - #[doc = "\"::\""] - pub static mut R_DoubleColonSymbol: SEXP; - #[doc = "\"drop\""] - pub static mut R_DropSymbol: SEXP; - #[doc = "\"eval\""] - pub static mut R_EvalSymbol: SEXP; - #[doc = "\"function\""] - pub static mut R_FunctionSymbol: SEXP; - #[doc = "\".Last.value\""] - pub static mut R_LastvalueSymbol: SEXP; - #[doc = "\"levels\""] - pub static mut R_LevelsSymbol: SEXP; - #[doc = "\"mode\""] - pub static mut R_ModeSymbol: SEXP; - #[doc = "\"na.rm\""] - pub static mut R_NaRmSymbol: SEXP; - #[doc = "\"name\""] - pub static mut R_NameSymbol: SEXP; - #[doc = "\"names\""] - pub static mut R_NamesSymbol: SEXP; - #[doc = "\".__NAMESPACE__.\""] - pub static mut R_NamespaceEnvSymbol: SEXP; - #[doc = "\"package\""] - pub static mut R_PackageSymbol: SEXP; - #[doc = "\"previous\""] - pub static mut R_PreviousSymbol: SEXP; - #[doc = "\"quote\""] - pub static mut R_QuoteSymbol: SEXP; - #[doc = "\"row.names\""] - pub static mut R_RowNamesSymbol: SEXP; - #[doc = "\".Random.seed\""] - pub static mut R_SeedsSymbol: SEXP; - #[doc = "\"sort.list\""] - pub static mut R_SortListSymbol: SEXP; - #[doc = "\"source\""] - pub static mut R_SourceSymbol: SEXP; - #[doc = "\"spec\""] - pub static mut R_SpecSymbol: SEXP; - #[doc = "\":::\""] - pub static mut R_TripleColonSymbol: SEXP; - #[doc = "\"tsp\""] - pub static mut R_TspSymbol: SEXP; - #[doc = "\".defined\""] - pub static mut R_dot_defined: SEXP; - #[doc = "\".Method\""] - pub static mut R_dot_Method: SEXP; - #[doc = "\".packageName\""] - pub static mut R_dot_packageName: SEXP; - #[doc = "\".target\""] - pub static mut R_dot_target: SEXP; - #[doc = "\".Generic\""] - pub static mut R_dot_Generic: SEXP; - #[doc = "NA_STRING as a CHARSXP"] - pub static mut R_NaString: SEXP; - #[doc = "\"\" as a CHARSXP"] - pub static mut R_BlankString: SEXP; - #[doc = "\"\" as a STRSXP"] - pub static mut R_BlankScalarString: SEXP; -} diff --git a/crates/libr/Cargo.toml b/crates/libr/Cargo.toml new file mode 100644 index 000000000..77f96a4b8 --- /dev/null +++ b/crates/libr/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "libr" +version = "0.1.0" +edition = "2021" +rust-version = "1.70.0" +description = """ +Bindings to R's C API, resolved dynamically at runtime. +""" + +[dependencies] +cfg-if = "1.0.0" +libc = "0.2.152" +libloading = "0.8.1" +paste = "1.0.14" diff --git a/crates/libr/src/constant_globals.rs b/crates/libr/src/constant_globals.rs new file mode 100644 index 000000000..0ae410c63 --- /dev/null +++ b/crates/libr/src/constant_globals.rs @@ -0,0 +1,82 @@ +// +// constant_globals.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +macro_rules! generate { + ( + $( + $(#[doc=$doc:expr])* + $(#[cfg($cfg:meta)])* + #[default=$default:expr] + pub static $name:ident: $ty:ty; + )+ + ) => ( + // Define globals + // The macro signature doesn't use `mut` so that we convey that they are meant + // to be constant, but really they have to be mutable because we need to define + // them to their initial value + $( + $(#[doc=$doc])* + $(#[cfg($cfg)])* + pub static mut $name: $ty = $default; + )+ + + // Define `has::` booleans + $( + paste::paste! { + $(#[cfg($cfg)])* + static mut [<$name _has>]: bool = false; + } + )+ + + // Make `has::` helpers for each global. + // i.e. `libr::has::Rf_error()`. + pub(super) mod constant_globals_has { + use super::*; + + $( + paste::paste! { + $(#[doc=$doc])* + $(#[cfg($cfg)])* + pub unsafe fn $name() -> bool { + [<$name _has>] + } + } + )+ + } + + pub(super) mod constant_globals_initializer { + use super::*; + + /// Initialize library constant globals + pub fn constant_globals(library: &libloading::Library) { + $( + $(#[cfg($cfg)])* + paste::paste! { + let symbol = unsafe { library.get(stringify!($name).as_bytes()) }; + + // If the symbol doesn't exist in the library, assume it simply + // isn't available in this version of R. + if let Ok(symbol) = symbol { + // Pull into Rust as a constant pointer + let pointer: *const $ty = *symbol; + + // Assume global has been initialized on the R side, and is + // otherwise constant, so deref to copy it over and assign it + // once. + unsafe { $name = *pointer }; + + // Update our `has::` marker + unsafe { [<$name _has>] = true }; + } + } + )+ + } + } + ); +} + +pub(crate) use generate; diff --git a/crates/libr/src/functions.rs b/crates/libr/src/functions.rs new file mode 100644 index 000000000..d553e6f08 --- /dev/null +++ b/crates/libr/src/functions.rs @@ -0,0 +1,78 @@ +// +// functions.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +macro_rules! generate { + ( + $( + $(#[doc=$doc:expr])* + $(#[cfg($cfg:meta)])* + pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*; + )+ + ) => { + // Make `Option` wrappers around the function pointers, initialized to `None` + $( + paste::paste! { + $(#[cfg($cfg)])* + static mut [<$name _opt>]: Option $ret)*> = None; + } + )+ + + // Make public functions that pass through to the `Option` wrappers. + // Initialization routine MUST be called before using these. + $( + paste::paste! { + $(#[doc=$doc])* + $(#[cfg($cfg)])* + pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* { + [<$name _opt>].unwrap_unchecked()($($pname), *) + } + } + )+ + + // Make `has::` helpers for each function. + // i.e. `libr::has::Rf_error()`. + pub(super) mod functions_has { + use super::*; + + $( + paste::paste! { + $(#[doc=$doc])* + $(#[cfg($cfg)])* + pub unsafe fn $name() -> bool { + [<$name _opt>].is_some() + } + } + )+ + } + + pub(super) mod functions_initializer { + use super::*; + + /// Initialize library functions + /// + /// If we can't find it in the library, the `Option` wrapper remains `None`. + /// This indicates that this version of R doesn't have that function. + pub fn functions(library: &libloading::Library) { + $( + $(#[cfg($cfg)])* + paste::paste! { + let symbol = unsafe { library.get(stringify!($name).as_bytes()) }; + + let pointer = match symbol { + Ok(symbol) => Some(*symbol), + Err(_) => None + }; + + unsafe { [<$name _opt>] = pointer }; + } + )+ + } + } + } +} + +pub(crate) use generate; diff --git a/crates/libr/src/functions_variadic.rs b/crates/libr/src/functions_variadic.rs new file mode 100644 index 000000000..053c3bc39 --- /dev/null +++ b/crates/libr/src/functions_variadic.rs @@ -0,0 +1,80 @@ +// +// functions_variadic.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +// Exact same as `functions.rs`, but accounts for `...` that comes from variadic functions + +macro_rules! generate { + ( + $( + $(#[doc=$doc:expr])* + $(#[cfg($cfg:meta)])* + pub fn $name:ident($($pname:ident: $pty:ty), *, ...) $(-> $ret:ty)*; + )+ + ) => { + // Make `Option` wrappers around the function pointers, initialized to `None` + $( + paste::paste! { + $(#[cfg($cfg)])* + static mut [<$name _opt>]: Option $ret)*> = None; + } + )+ + + // Make public functions that pass through to the `Option` wrappers. + // Initialization routine MUST be called before using these. + $( + paste::paste! { + $(#[doc=$doc])* + $(#[cfg($cfg)])* + pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* { + [<$name _opt>].unwrap_unchecked()($($pname), *) + } + } + )+ + + // Make `has::` helpers for each function. + // i.e. `libr::has::Rf_error()`. + pub(super) mod functions_variadic_has { + use super::*; + + $( + paste::paste! { + $(#[doc=$doc])* + $(#[cfg($cfg)])* + pub unsafe fn $name() -> bool { + [<$name _opt>].is_some() + } + } + )+ + } + + pub(super) mod functions_variadic_initializer { + use super::*; + + /// Initialize library functions + /// + /// If we can't find it in the library, the `Option` wrapper remains `None`. + /// This indicates that this version of R doesn't have that function. + pub fn functions_variadic(library: &libloading::Library) { + $( + $(#[cfg($cfg)])* + paste::paste! { + let symbol = unsafe { library.get(stringify!($name).as_bytes()) }; + + let pointer = match symbol { + Ok(symbol) => Some(*symbol), + Err(_) => None + }; + + unsafe { [<$name _opt>] = pointer }; + } + )+ + } + } + } +} + +pub(crate) use generate; diff --git a/crates/libr/src/graphapp.rs b/crates/libr/src/graphapp.rs new file mode 100644 index 000000000..deef53efa --- /dev/null +++ b/crates/libr/src/graphapp.rs @@ -0,0 +1,21 @@ +// +// graphapp.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +// This file is Windows specific + +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +use crate::functions; + +// --------------------------------------------------------------------------------------- +// Functions and globals + +functions::generate! { + pub fn GA_initapp(arg1: std::ffi::c_int, arg2: *mut *mut std::ffi::c_char) -> std::ffi::c_int; +} diff --git a/crates/libr/src/graphics.rs b/crates/libr/src/graphics.rs new file mode 100644 index 000000000..e2eef9227 --- /dev/null +++ b/crates/libr/src/graphics.rs @@ -0,0 +1,844 @@ +// +// graphics.rs +// +// Copyright (C) 2024 by Posit Software, PBC +// +// + +// This file captures the device description for the different versions +// of R graphics engines that we support. + +// Opaque struct idea comes from the Nomicon +// https://doc.rust-lang.org/nomicon/ffi.html?highlight=Opaque#representing-opaque-structs + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +use crate::Rboolean; +use crate::SEXP; + +// --------------------------------------------------------------------------------------- + +// Opaque structs used with R API graphics functions +// +// Some example usage: +// - `GEcurrentDevice()` returns a `pGEDevDesc`. +// - `GEinitDisplayList()` takes a `pGEDevDesc`. +// - Our `DeviceCallbacks` are given `pDevDesc` and `pGEcontext` pointers. +// +// The key idea is that the opaque `pGEDevDesc` pointer returned by `GEcurrentDevice()` is +// cast to a more specific "versioned" pointer using one of the versioned structs further +// below (the version is supplied by `R_GE_getVersion()`). We can then extract the +// corresponding versioned `.dev` field, and modify that. This is implemented through +// the `with_device` macro. + +#[repr(C)] +pub struct DevDesc { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +pub type pDevDesc = *mut DevDesc; + +#[repr(C)] +pub struct GEDevDesc { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +pub type pGEDevDesc = *mut GEDevDesc; + +#[repr(C)] +pub struct R_GE_gcontext { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +pub type pGEcontext = *mut R_GE_gcontext; + +// --------------------------------------------------------------------------------------- + +// Supporting oqaque structs + +// Used as `*mut GESystemDesc` by the `GEDevDesc` variants +#[repr(C)] +pub struct GESystemDesc { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} + +// --------------------------------------------------------------------------------------- + +// Per-version variants of `GEDevDesc`. +// This has been mostly stable over the past 17 years, with the exception of adding +// `appending` around R 4.2.x. We need the actual struct layout, because we have to +// extract the `dev` field and set the `displayListOn` field. + +// Graphics Engine Wrapper version 13 (R 4.0.x) +#[repr(C)] +pub struct GEDevDescVersion13 { + pub dev: pDevDescVersion13, + pub displayListOn: Rboolean, + pub displayList: SEXP, + pub DLlastElt: SEXP, + pub savedSnapshot: SEXP, + pub dirty: Rboolean, + pub recordGraphics: Rboolean, + pub gesd: [*mut GESystemDesc; 24usize], + pub ask: Rboolean, +} + +// Graphics Engine Wrapper version 14 (R 4.1.x) +#[repr(C)] +pub struct GEDevDescVersion14 { + pub dev: pDevDescVersion14, + pub displayListOn: Rboolean, + pub displayList: SEXP, + pub DLlastElt: SEXP, + pub savedSnapshot: SEXP, + pub dirty: Rboolean, + pub recordGraphics: Rboolean, + pub gesd: [*mut GESystemDesc; 24usize], + pub ask: Rboolean, +} + +// Graphics Engine Wrapper version 15 (R 4.2.x) +#[repr(C)] +pub struct GEDevDescVersion15 { + pub dev: pDevDescVersion15, + pub displayListOn: Rboolean, + pub displayList: SEXP, + pub DLlastElt: SEXP, + pub savedSnapshot: SEXP, + pub dirty: Rboolean, + pub recordGraphics: Rboolean, + pub gesd: [*mut GESystemDesc; 24usize], + pub ask: Rboolean, + pub appending: Rboolean, +} + +// Graphics Engine Wrapper version 16 (R 4.3.0) +#[repr(C)] +pub struct GEDevDescVersion16 { + pub dev: pDevDescVersion16, + pub displayListOn: Rboolean, + pub displayList: SEXP, + pub DLlastElt: SEXP, + pub savedSnapshot: SEXP, + pub dirty: Rboolean, + pub recordGraphics: Rboolean, + pub gesd: [*mut GESystemDesc; 24usize], + pub ask: Rboolean, + pub appending: Rboolean, +} + +// --------------------------------------------------------------------------------------- + +// Per-version variants of `DevDesc`. +// This is subject to a large amount of change between R versions. + +// Graphics Engine version 13 (R 4.0.x) +#[repr(C)] +pub struct DevDescVersion13 { + pub left: f64, + pub right: f64, + pub bottom: f64, + pub top: f64, + pub clipLeft: f64, + pub clipRight: f64, + pub clipBottom: f64, + pub clipTop: f64, + pub xCharOffset: f64, + pub yCharOffset: f64, + pub yLineBias: f64, + pub ipr: [f64; 2usize], + pub cra: [f64; 2usize], + pub gamma: f64, + pub canClip: Rboolean, + pub canChangeGamma: Rboolean, + pub canHAdj: std::ffi::c_int, + pub startps: f64, + pub startcol: std::ffi::c_int, + pub startfill: std::ffi::c_int, + pub startlty: std::ffi::c_int, + pub startfont: std::ffi::c_int, + pub startgamma: f64, + pub deviceSpecific: *mut std::ffi::c_void, + pub displayListOn: Rboolean, + pub canGenMouseDown: Rboolean, + pub canGenMouseMove: Rboolean, + pub canGenMouseUp: Rboolean, + pub canGenKeybd: Rboolean, + pub canGenIdle: Rboolean, + pub gettingEvent: Rboolean, + pub activate: Option, + pub circle: Option, + pub clip: Option, + pub close: Option, + pub deactivate: Option, + pub locator: Option Rboolean>, + pub line: Option< + unsafe extern "C" fn(x1: f64, y1: f64, x2: f64, y2: f64, gc: pGEcontext, dd: pDevDesc), + >, + pub metricInfo: Option< + unsafe extern "C" fn( + c: std::ffi::c_int, + gc: pGEcontext, + ascent: *mut f64, + descent: *mut f64, + width: *mut f64, + dd: pDevDesc, + ), + >, + pub mode: Option, + pub newPage: Option, + pub polygon: Option< + unsafe extern "C" fn( + n: std::ffi::c_int, + x: *mut f64, + y: *mut f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub polyline: Option< + unsafe extern "C" fn( + n: std::ffi::c_int, + x: *mut f64, + y: *mut f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub rect: Option< + unsafe extern "C" fn(x0: f64, y0: f64, x1: f64, y1: f64, gc: pGEcontext, dd: pDevDesc), + >, + pub path: Option< + unsafe extern "C" fn( + x: *mut f64, + y: *mut f64, + npoly: std::ffi::c_int, + nper: *mut std::ffi::c_int, + winding: Rboolean, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub raster: Option< + unsafe extern "C" fn( + raster: *mut std::ffi::c_uint, + w: std::ffi::c_int, + h: std::ffi::c_int, + x: f64, + y: f64, + width: f64, + height: f64, + rot: f64, + interpolate: Rboolean, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub cap: Option SEXP>, + pub size: Option< + unsafe extern "C" fn( + left: *mut f64, + right: *mut f64, + bottom: *mut f64, + top: *mut f64, + dd: pDevDesc, + ), + >, + pub strWidth: Option< + unsafe extern "C" fn(str: *const std::ffi::c_char, gc: pGEcontext, dd: pDevDesc) -> f64, + >, + pub text: Option< + unsafe extern "C" fn( + x: f64, + y: f64, + str: *const std::ffi::c_char, + rot: f64, + hadj: f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub onExit: Option, + pub getEvent: Option SEXP>, + pub newFrameConfirm: Option Rboolean>, + pub hasTextUTF8: Rboolean, + pub textUTF8: Option< + unsafe extern "C" fn( + x: f64, + y: f64, + str: *const std::ffi::c_char, + rot: f64, + hadj: f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub strWidthUTF8: Option< + unsafe extern "C" fn(str: *const std::ffi::c_char, gc: pGEcontext, dd: pDevDesc) -> f64, + >, + pub wantSymbolUTF8: Rboolean, + pub useRotatedTextInContour: Rboolean, + pub eventEnv: SEXP, + pub eventHelper: Option, + pub holdflush: + Option std::ffi::c_int>, + pub haveTransparency: std::ffi::c_int, + pub haveTransparentBg: std::ffi::c_int, + pub haveRaster: std::ffi::c_int, + pub haveCapture: std::ffi::c_int, + pub haveLocator: std::ffi::c_int, + pub reserved: [std::ffi::c_char; 64usize], +} +pub type pDevDescVersion13 = *mut DevDescVersion13; + +// Graphics Engine version 14 (R 4.1.x) +#[repr(C)] +pub struct DevDescVersion14 { + pub left: f64, + pub right: f64, + pub bottom: f64, + pub top: f64, + pub clipLeft: f64, + pub clipRight: f64, + pub clipBottom: f64, + pub clipTop: f64, + pub xCharOffset: f64, + pub yCharOffset: f64, + pub yLineBias: f64, + pub ipr: [f64; 2usize], + pub cra: [f64; 2usize], + pub gamma: f64, + pub canClip: Rboolean, + pub canChangeGamma: Rboolean, + pub canHAdj: std::ffi::c_int, + pub startps: f64, + pub startcol: std::ffi::c_int, + pub startfill: std::ffi::c_int, + pub startlty: std::ffi::c_int, + pub startfont: std::ffi::c_int, + pub startgamma: f64, + pub deviceSpecific: *mut std::ffi::c_void, + pub displayListOn: Rboolean, + pub canGenMouseDown: Rboolean, + pub canGenMouseMove: Rboolean, + pub canGenMouseUp: Rboolean, + pub canGenKeybd: Rboolean, + pub canGenIdle: Rboolean, + pub gettingEvent: Rboolean, + pub activate: Option, + pub circle: Option, + pub clip: Option, + pub close: Option, + pub deactivate: Option, + pub locator: Option Rboolean>, + pub line: Option< + unsafe extern "C" fn(x1: f64, y1: f64, x2: f64, y2: f64, gc: pGEcontext, dd: pDevDesc), + >, + pub metricInfo: Option< + unsafe extern "C" fn( + c: std::ffi::c_int, + gc: pGEcontext, + ascent: *mut f64, + descent: *mut f64, + width: *mut f64, + dd: pDevDesc, + ), + >, + pub mode: Option, + pub newPage: Option, + pub polygon: Option< + unsafe extern "C" fn( + n: std::ffi::c_int, + x: *mut f64, + y: *mut f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub polyline: Option< + unsafe extern "C" fn( + n: std::ffi::c_int, + x: *mut f64, + y: *mut f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub rect: Option< + unsafe extern "C" fn(x0: f64, y0: f64, x1: f64, y1: f64, gc: pGEcontext, dd: pDevDesc), + >, + pub path: Option< + unsafe extern "C" fn( + x: *mut f64, + y: *mut f64, + npoly: std::ffi::c_int, + nper: *mut std::ffi::c_int, + winding: Rboolean, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub raster: Option< + unsafe extern "C" fn( + raster: *mut std::ffi::c_uint, + w: std::ffi::c_int, + h: std::ffi::c_int, + x: f64, + y: f64, + width: f64, + height: f64, + rot: f64, + interpolate: Rboolean, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub cap: Option SEXP>, + pub size: Option< + unsafe extern "C" fn( + left: *mut f64, + right: *mut f64, + bottom: *mut f64, + top: *mut f64, + dd: pDevDesc, + ), + >, + pub strWidth: Option< + unsafe extern "C" fn(str: *const std::ffi::c_char, gc: pGEcontext, dd: pDevDesc) -> f64, + >, + pub text: Option< + unsafe extern "C" fn( + x: f64, + y: f64, + str: *const std::ffi::c_char, + rot: f64, + hadj: f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub onExit: Option, + pub getEvent: Option SEXP>, + pub newFrameConfirm: Option Rboolean>, + pub hasTextUTF8: Rboolean, + pub textUTF8: Option< + unsafe extern "C" fn( + x: f64, + y: f64, + str: *const std::ffi::c_char, + rot: f64, + hadj: f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub strWidthUTF8: Option< + unsafe extern "C" fn(str: *const std::ffi::c_char, gc: pGEcontext, dd: pDevDesc) -> f64, + >, + pub wantSymbolUTF8: Rboolean, + pub useRotatedTextInContour: Rboolean, + pub eventEnv: SEXP, + pub eventHelper: Option, + pub holdflush: + Option std::ffi::c_int>, + pub haveTransparency: std::ffi::c_int, + pub haveTransparentBg: std::ffi::c_int, + pub haveRaster: std::ffi::c_int, + pub haveCapture: std::ffi::c_int, + pub haveLocator: std::ffi::c_int, + pub setPattern: Option SEXP>, + pub releasePattern: Option, + pub setClipPath: Option SEXP>, + pub releaseClipPath: Option, + pub setMask: Option SEXP>, + pub releaseMask: Option, + pub deviceVersion: std::ffi::c_int, + pub deviceClip: Rboolean, + pub reserved: [std::ffi::c_char; 64usize], +} +pub type pDevDescVersion14 = *mut DevDescVersion14; + +// Graphics Engine version 15 (R 4.2.x) +#[repr(C)] +pub struct DevDescVersion15 { + pub left: f64, + pub right: f64, + pub bottom: f64, + pub top: f64, + pub clipLeft: f64, + pub clipRight: f64, + pub clipBottom: f64, + pub clipTop: f64, + pub xCharOffset: f64, + pub yCharOffset: f64, + pub yLineBias: f64, + pub ipr: [f64; 2usize], + pub cra: [f64; 2usize], + pub gamma: f64, + pub canClip: Rboolean, + pub canChangeGamma: Rboolean, + pub canHAdj: std::ffi::c_int, + pub startps: f64, + pub startcol: std::ffi::c_int, + pub startfill: std::ffi::c_int, + pub startlty: std::ffi::c_int, + pub startfont: std::ffi::c_int, + pub startgamma: f64, + pub deviceSpecific: *mut std::ffi::c_void, + pub displayListOn: Rboolean, + pub canGenMouseDown: Rboolean, + pub canGenMouseMove: Rboolean, + pub canGenMouseUp: Rboolean, + pub canGenKeybd: Rboolean, + pub canGenIdle: Rboolean, + pub gettingEvent: Rboolean, + pub activate: Option, + pub circle: Option, + pub clip: Option, + pub close: Option, + pub deactivate: Option, + pub locator: Option Rboolean>, + pub line: Option< + unsafe extern "C" fn(x1: f64, y1: f64, x2: f64, y2: f64, gc: pGEcontext, dd: pDevDesc), + >, + pub metricInfo: Option< + unsafe extern "C" fn( + c: std::ffi::c_int, + gc: pGEcontext, + ascent: *mut f64, + descent: *mut f64, + width: *mut f64, + dd: pDevDesc, + ), + >, + pub mode: Option, + pub newPage: Option, + pub polygon: Option< + unsafe extern "C" fn( + n: std::ffi::c_int, + x: *mut f64, + y: *mut f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub polyline: Option< + unsafe extern "C" fn( + n: std::ffi::c_int, + x: *mut f64, + y: *mut f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub rect: Option< + unsafe extern "C" fn(x0: f64, y0: f64, x1: f64, y1: f64, gc: pGEcontext, dd: pDevDesc), + >, + pub path: Option< + unsafe extern "C" fn( + x: *mut f64, + y: *mut f64, + npoly: std::ffi::c_int, + nper: *mut std::ffi::c_int, + winding: Rboolean, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub raster: Option< + unsafe extern "C" fn( + raster: *mut std::ffi::c_uint, + w: std::ffi::c_int, + h: std::ffi::c_int, + x: f64, + y: f64, + width: f64, + height: f64, + rot: f64, + interpolate: Rboolean, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub cap: Option SEXP>, + pub size: Option< + unsafe extern "C" fn( + left: *mut f64, + right: *mut f64, + bottom: *mut f64, + top: *mut f64, + dd: pDevDesc, + ), + >, + pub strWidth: Option< + unsafe extern "C" fn(str: *const std::ffi::c_char, gc: pGEcontext, dd: pDevDesc) -> f64, + >, + pub text: Option< + unsafe extern "C" fn( + x: f64, + y: f64, + str: *const std::ffi::c_char, + rot: f64, + hadj: f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub onExit: Option, + pub getEvent: Option SEXP>, + pub newFrameConfirm: Option Rboolean>, + pub hasTextUTF8: Rboolean, + pub textUTF8: Option< + unsafe extern "C" fn( + x: f64, + y: f64, + str: *const std::ffi::c_char, + rot: f64, + hadj: f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub strWidthUTF8: Option< + unsafe extern "C" fn(str: *const std::ffi::c_char, gc: pGEcontext, dd: pDevDesc) -> f64, + >, + pub wantSymbolUTF8: Rboolean, + pub useRotatedTextInContour: Rboolean, + pub eventEnv: SEXP, + pub eventHelper: Option, + pub holdflush: + Option std::ffi::c_int>, + pub haveTransparency: std::ffi::c_int, + pub haveTransparentBg: std::ffi::c_int, + pub haveRaster: std::ffi::c_int, + pub haveCapture: std::ffi::c_int, + pub haveLocator: std::ffi::c_int, + pub setPattern: Option SEXP>, + pub releasePattern: Option, + pub setClipPath: Option SEXP>, + pub releaseClipPath: Option, + pub setMask: Option SEXP>, + pub releaseMask: Option, + pub deviceVersion: std::ffi::c_int, + pub deviceClip: Rboolean, + pub defineGroup: Option< + unsafe extern "C" fn( + source: SEXP, + op: std::ffi::c_int, + destination: SEXP, + dd: pDevDesc, + ) -> SEXP, + >, + pub useGroup: Option, + pub releaseGroup: Option, + pub stroke: Option, + pub fill: Option< + unsafe extern "C" fn(path: SEXP, rule: std::ffi::c_int, gc: pGEcontext, dd: pDevDesc), + >, + pub fillStroke: Option< + unsafe extern "C" fn(path: SEXP, rule: std::ffi::c_int, gc: pGEcontext, dd: pDevDesc), + >, + pub capabilities: Option SEXP>, + pub reserved: [std::ffi::c_char; 64usize], +} +pub type pDevDescVersion15 = *mut DevDescVersion15; + +// Graphics Engine version 16 (R 4.3.0) +#[repr(C)] +pub struct DevDescVersion16 { + pub left: f64, + pub right: f64, + pub bottom: f64, + pub top: f64, + pub clipLeft: f64, + pub clipRight: f64, + pub clipBottom: f64, + pub clipTop: f64, + pub xCharOffset: f64, + pub yCharOffset: f64, + pub yLineBias: f64, + pub ipr: [f64; 2usize], + pub cra: [f64; 2usize], + pub gamma: f64, + pub canClip: Rboolean, + pub canChangeGamma: Rboolean, + pub canHAdj: std::ffi::c_int, + pub startps: f64, + pub startcol: std::ffi::c_int, + pub startfill: std::ffi::c_int, + pub startlty: std::ffi::c_int, + pub startfont: std::ffi::c_int, + pub startgamma: f64, + pub deviceSpecific: *mut std::ffi::c_void, + pub displayListOn: Rboolean, + pub canGenMouseDown: Rboolean, + pub canGenMouseMove: Rboolean, + pub canGenMouseUp: Rboolean, + pub canGenKeybd: Rboolean, + pub canGenIdle: Rboolean, + pub gettingEvent: Rboolean, + pub activate: Option, + pub circle: Option, + pub clip: Option, + pub close: Option, + pub deactivate: Option, + pub locator: Option Rboolean>, + pub line: Option< + unsafe extern "C" fn(x1: f64, y1: f64, x2: f64, y2: f64, gc: pGEcontext, dd: pDevDesc), + >, + pub metricInfo: Option< + unsafe extern "C" fn( + c: std::ffi::c_int, + gc: pGEcontext, + ascent: *mut f64, + descent: *mut f64, + width: *mut f64, + dd: pDevDesc, + ), + >, + pub mode: Option, + pub newPage: Option, + pub polygon: Option< + unsafe extern "C" fn( + n: std::ffi::c_int, + x: *mut f64, + y: *mut f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub polyline: Option< + unsafe extern "C" fn( + n: std::ffi::c_int, + x: *mut f64, + y: *mut f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub rect: Option< + unsafe extern "C" fn(x0: f64, y0: f64, x1: f64, y1: f64, gc: pGEcontext, dd: pDevDesc), + >, + pub path: Option< + unsafe extern "C" fn( + x: *mut f64, + y: *mut f64, + npoly: std::ffi::c_int, + nper: *mut std::ffi::c_int, + winding: Rboolean, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub raster: Option< + unsafe extern "C" fn( + raster: *mut std::ffi::c_uint, + w: std::ffi::c_int, + h: std::ffi::c_int, + x: f64, + y: f64, + width: f64, + height: f64, + rot: f64, + interpolate: Rboolean, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub cap: Option SEXP>, + pub size: Option< + unsafe extern "C" fn( + left: *mut f64, + right: *mut f64, + bottom: *mut f64, + top: *mut f64, + dd: pDevDesc, + ), + >, + pub strWidth: Option< + unsafe extern "C" fn(str: *const std::ffi::c_char, gc: pGEcontext, dd: pDevDesc) -> f64, + >, + pub text: Option< + unsafe extern "C" fn( + x: f64, + y: f64, + str: *const std::ffi::c_char, + rot: f64, + hadj: f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub onExit: Option, + pub getEvent: Option SEXP>, + pub newFrameConfirm: Option Rboolean>, + pub hasTextUTF8: Rboolean, + pub textUTF8: Option< + unsafe extern "C" fn( + x: f64, + y: f64, + str: *const std::ffi::c_char, + rot: f64, + hadj: f64, + gc: pGEcontext, + dd: pDevDesc, + ), + >, + pub strWidthUTF8: Option< + unsafe extern "C" fn(str: *const std::ffi::c_char, gc: pGEcontext, dd: pDevDesc) -> f64, + >, + pub wantSymbolUTF8: Rboolean, + pub useRotatedTextInContour: Rboolean, + pub eventEnv: SEXP, + pub eventHelper: Option, + pub holdflush: + Option std::ffi::c_int>, + pub haveTransparency: std::ffi::c_int, + pub haveTransparentBg: std::ffi::c_int, + pub haveRaster: std::ffi::c_int, + pub haveCapture: std::ffi::c_int, + pub haveLocator: std::ffi::c_int, + pub setPattern: Option SEXP>, + pub releasePattern: Option, + pub setClipPath: Option SEXP>, + pub releaseClipPath: Option, + pub setMask: Option SEXP>, + pub releaseMask: Option, + pub deviceVersion: std::ffi::c_int, + pub deviceClip: Rboolean, + pub defineGroup: Option< + unsafe extern "C" fn( + source: SEXP, + op: std::ffi::c_int, + destination: SEXP, + dd: pDevDesc, + ) -> SEXP, + >, + pub useGroup: Option, + pub releaseGroup: Option, + pub stroke: Option, + pub fill: Option< + unsafe extern "C" fn(path: SEXP, rule: std::ffi::c_int, gc: pGEcontext, dd: pDevDesc), + >, + pub fillStroke: Option< + unsafe extern "C" fn(path: SEXP, rule: std::ffi::c_int, gc: pGEcontext, dd: pDevDesc), + >, + pub capabilities: Option SEXP>, + pub glyph: Option< + unsafe extern "C" fn( + n: std::ffi::c_int, + glyphs: *mut std::ffi::c_int, + x: *mut f64, + y: *mut f64, + font: SEXP, + size: f64, + colour: std::ffi::c_int, + rot: f64, + dd: pDevDesc, + ), + >, + pub reserved: [std::ffi::c_char; 64usize], +} +pub type pDevDescVersion16 = *mut DevDescVersion16; diff --git a/crates/libr/src/lib.rs b/crates/libr/src/lib.rs new file mode 100644 index 000000000..42dd11715 --- /dev/null +++ b/crates/libr/src/lib.rs @@ -0,0 +1,81 @@ +// +// lib.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +mod constant_globals; +mod functions; +mod functions_variadic; +mod graphics; +mod mutable_globals; +mod r; +mod sys; +mod types; +#[cfg(target_family = "windows")] +#[path = "graphapp.rs"] +mod windows_graphapp; + +// --------------------------------------------------------------------------------------- +// R + +/// Initialization functions that must be called before using any functions or globals +/// exported by the crate +pub mod initialize { + pub use crate::r::constant_globals_initializer::constant_globals; + pub use crate::r::functions_initializer::functions; + pub use crate::r::functions_variadic_initializer::functions_variadic; + pub use crate::r::mutable_globals_initializer::mutable_globals; +} + +pub mod has { + pub use crate::r::constant_globals_has::*; + pub use crate::r::functions_has::*; + pub use crate::r::functions_variadic_has::*; + pub use crate::r::mutable_globals_has::*; +} + +// Expose all R types, API functions, and API globals at the top level +pub use graphics::*; +pub use r::*; +pub use types::*; + +// --------------------------------------------------------------------------------------- +// graphapp + +#[cfg(target_family = "windows")] +pub mod graphapp { + /// Initialization functions that must be called before using any functions or globals + /// exported by the crate + pub mod initialize { + pub use crate::windows_graphapp::functions_initializer::functions; + } + + pub mod has { + pub use crate::windows_graphapp::functions_has::*; + } + + pub use crate::windows_graphapp::*; +} + +// --------------------------------------------------------------------------------------- +// Helpers + +/// Get the value of a mutable global using its pointer +/// +/// We prefer using this over dereferencing the pointers directly. +/// +/// NOTE: Must be `Copy`, as you can't move out of a raw pointer, so every access to +/// the pointer value generates a copy, but it should be cheap. +pub unsafe fn get(x: *mut T) -> T +where + T: Copy, +{ + *x +} + +/// Set the value of a mutable global using its pointer +pub unsafe fn set(x: *mut T, value: T) { + *x = value; +} diff --git a/crates/libr/src/mutable_globals.rs b/crates/libr/src/mutable_globals.rs new file mode 100644 index 000000000..ae1b6b9e6 --- /dev/null +++ b/crates/libr/src/mutable_globals.rs @@ -0,0 +1,60 @@ +// +// mutable_globals.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +macro_rules! generate { + ( + $( + $(#[doc=$doc:expr])* + $(#[cfg($cfg:meta)])* + pub static mut $name:ident: $ty:ty; + )+ + ) => ( + // Define global pointers, initialized to null pointer + $( + $(#[doc=$doc])* + $(#[cfg($cfg)])* + pub static mut $name: *mut $ty = std::ptr::null_mut(); + )+ + + // Make `has::` helpers for each global. + // i.e. `libr::has::Rf_error()`. + pub(super) mod mutable_globals_has { + $( + paste::paste! { + $(#[doc=$doc])* + $(#[cfg($cfg)])* + pub unsafe fn $name() -> bool { + super::$name.is_null() + } + } + )+ + } + + pub(super) mod mutable_globals_initializer { + use super::*; + + /// Initialize library mutable globals + pub fn mutable_globals(library: &libloading::Library) { + $( + $(#[cfg($cfg)])* + paste::paste! { + let symbol = unsafe { library.get(stringify!($name).as_bytes()) }; + + // If the symbol doesn't exist in the library, assume it simply + // isn't available in this version of R. + if let Ok(symbol) = symbol { + // Pull into Rust as a mutable pointer + unsafe { $name = *symbol }; + } + } + )+ + } + } + ); +} + +pub(crate) use generate; diff --git a/crates/libr/src/r.rs b/crates/libr/src/r.rs new file mode 100644 index 000000000..aa515d69a --- /dev/null +++ b/crates/libr/src/r.rs @@ -0,0 +1,674 @@ +// +// r.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +#![allow(non_upper_case_globals)] +#![allow(non_snake_case)] + +use crate::constant_globals; +use crate::functions; +use crate::functions_variadic; +use crate::graphics::pGEDevDesc; +use crate::mutable_globals; +use crate::types::*; + +// --------------------------------------------------------------------------------------- +// Functions and globals + +functions::generate! { + pub fn Rf_initialize_R(ac: std::ffi::c_int, av: *mut *mut std::ffi::c_char) -> std::ffi::c_int; + + pub fn run_Rmainloop(); + + pub fn setup_Rmainloop(); + + pub fn R_HomeDir() -> *mut std::ffi::c_char; + + pub fn R_ProcessEvents(); + + pub fn R_removeVarFromFrame(symbol: SEXP, envir: SEXP); + + pub fn R_getEmbeddingDllInfo() -> *mut DllInfo; + + pub fn R_registerRoutines( + info: *mut DllInfo, + croutines: *const R_CMethodDef, + callRoutines: *const R_CallMethodDef, + fortranRoutines: *const R_FortranMethodDef, + externalRoutines: *const R_ExternalMethodDef + ) -> std::ffi::c_int; + + pub fn vmaxget() -> *mut std::ffi::c_void; + + pub fn vmaxset(arg1: *const std::ffi::c_void); + + pub fn R_BindingIsActive(sym: SEXP, env: SEXP) -> Rboolean; + + pub fn R_CheckStack(); + + pub fn R_CheckStack2(arg1: usize); + + pub fn R_CheckUserInterrupt(); + + pub fn R_ExternalPtrAddr(s: SEXP) -> *mut std::ffi::c_void; + + pub fn R_MakeExternalPtr(p: *mut std::ffi::c_void, tag: SEXP, prot: SEXP) -> SEXP; + + pub fn R_IsNA(arg1: f64) -> std::ffi::c_int; + + pub fn R_IsNamespaceEnv(rho: SEXP) -> Rboolean; + + pub fn R_IsPackageEnv(rho: SEXP) -> Rboolean; + + pub fn R_NamespaceEnvSpec(rho: SEXP) -> SEXP; + + pub fn R_PackageEnvName(rho: SEXP) -> SEXP; + + pub fn R_ParseVector( + arg1: SEXP, + arg2: std::ffi::c_int, + arg3: *mut ParseStatus, + arg4: SEXP + ) -> SEXP; + + pub fn R_PreserveObject(arg1: SEXP); + + pub fn R_RunPendingFinalizers(); + + pub fn R_ToplevelExec( + fun: Option, + data: *mut std::ffi::c_void + ) -> Rboolean; + + pub fn R_altrep_data1(x: SEXP) -> SEXP; + + pub fn R_altrep_data2(x: SEXP) -> SEXP; + + pub fn R_curErrorBuf() -> *const std::ffi::c_char; + + pub fn R_do_slot(obj: SEXP, name: SEXP) -> SEXP; + + pub fn R_lsInternal(arg1: SEXP, arg2: Rboolean) -> SEXP; + + pub fn R_tryCatch( + arg1: Option SEXP>, + arg2: *mut std::ffi::c_void, + arg3: SEXP, + arg4: Option SEXP>, + arg5: *mut std::ffi::c_void, + arg6: Option, + arg7: *mut std::ffi::c_void + ) -> SEXP; + + pub fn R_tryEvalSilent(arg1: SEXP, arg2: SEXP, arg3: *mut std::ffi::c_int) -> SEXP; + + pub fn Rf_GetOption1(arg1: SEXP) -> SEXP; + + pub fn Rf_ScalarInteger(arg1: std::ffi::c_int) -> SEXP; + + pub fn Rf_ScalarLogical(arg1: std::ffi::c_int) -> SEXP; + + pub fn Rf_ScalarReal(arg1: f64) -> SEXP; + + pub fn Rf_ScalarString(arg1: SEXP) -> SEXP; + + pub fn Rf_allocVector(arg1: SEXPTYPE, arg2: R_xlen_t) -> SEXP; + + pub fn Rf_asInteger(x: SEXP) -> std::ffi::c_int; + + pub fn Rf_coerceVector(arg1: SEXP, arg2: SEXPTYPE) -> SEXP; + + pub fn Rf_cons(arg1: SEXP, arg2: SEXP) -> SEXP; + + pub fn Rf_defineVar(arg1: SEXP, arg2: SEXP, arg3: SEXP); + + pub fn Rf_eval(arg1: SEXP, arg2: SEXP) -> SEXP; + + pub fn Rf_findVar(arg1: SEXP, arg2: SEXP) -> SEXP; + + pub fn Rf_findVarInFrame(arg1: SEXP, arg2: SEXP) -> SEXP; + + pub fn Rf_getAttrib(arg1: SEXP, arg2: SEXP) -> SEXP; + + pub fn Rf_inherits(arg1: SEXP, arg2: *const std::ffi::c_char) -> Rboolean; + + pub fn Rf_install(arg1: *const std::ffi::c_char) -> SEXP; + + pub fn Rf_isFunction(arg1: SEXP) -> Rboolean; + + pub fn Rf_isInteger(arg1: SEXP) -> Rboolean; + + pub fn Rf_isMatrix(arg1: SEXP) -> Rboolean; + + pub fn Rf_isNumeric(arg1: SEXP) -> Rboolean; + + pub fn Rf_isString(s: SEXP) -> Rboolean; + + pub fn Rf_lang1(arg1: SEXP) -> SEXP; + + pub fn Rf_lang2(arg1: SEXP, arg2: SEXP) -> SEXP; + + pub fn Rf_lang3(arg1: SEXP, arg2: SEXP, arg3: SEXP) -> SEXP; + + pub fn Rf_lang4(arg1: SEXP, arg2: SEXP, arg3: SEXP, arg4: SEXP) -> SEXP; + + pub fn Rf_lcons(arg1: SEXP, arg2: SEXP) -> SEXP; + + pub fn Rf_mkCharLenCE( + arg1: *const std::ffi::c_char, + arg2: std::ffi::c_int, + arg3: cetype_t + ) -> SEXP; + + pub fn Rf_mkString(arg1: *const std::ffi::c_char) -> SEXP; + + pub fn Rf_onintr(); + + pub fn Rf_protect(arg1: SEXP) -> SEXP; + + pub fn Rf_setAttrib(arg1: SEXP, arg2: SEXP, arg3: SEXP) -> SEXP; + + pub fn Rf_setVar(arg1: SEXP, arg2: SEXP, arg3: SEXP); + + pub fn Rf_translateCharUTF8(arg1: SEXP) -> *const std::ffi::c_char; + + pub fn Rf_type2char(arg1: SEXPTYPE) -> *const std::ffi::c_char; + + pub fn Rf_unprotect(arg1: std::ffi::c_int); + + pub fn Rf_xlength(arg1: SEXP) -> R_xlen_t; + + pub fn ALTREP(x: SEXP) -> std::ffi::c_int; + + pub fn ALTREP_CLASS(x: SEXP) -> SEXP; + + pub fn ATTRIB(x: SEXP) -> SEXP; + + pub fn CADDDR(e: SEXP) -> SEXP; + + pub fn CADDR(e: SEXP) -> SEXP; + + pub fn CADR(e: SEXP) -> SEXP; + + pub fn CAR(e: SEXP) -> SEXP; + + pub fn CDDDR(e: SEXP) -> SEXP; + + pub fn CDDR(e: SEXP) -> SEXP; + + pub fn CDR(e: SEXP) -> SEXP; + + pub fn COMPLEX_ELT(x: SEXP, i: R_xlen_t) -> Rcomplex; + + pub fn DATAPTR(x: SEXP) -> *mut std::ffi::c_void; + + pub fn ENCLOS(x: SEXP) -> SEXP; + + pub fn FORMALS(x: SEXP) -> SEXP; + + pub fn FRAME(x: SEXP) -> SEXP; + + pub fn HASHTAB(x: SEXP) -> SEXP; + + pub fn INTEGER_ELT(x: SEXP, i: R_xlen_t) -> std::ffi::c_int; + + pub fn LOGICAL(x: SEXP) -> *mut std::ffi::c_int; + + pub fn LOGICAL_ELT(x: SEXP, i: R_xlen_t) -> std::ffi::c_int; + + pub fn PRCODE(x: SEXP) -> SEXP; + + pub fn PRENV(x: SEXP) -> SEXP; + + pub fn PRINTNAME(x: SEXP) -> SEXP; + + pub fn PRVALUE(x: SEXP) -> SEXP; + + pub fn RAW(x: SEXP) -> *mut Rbyte; + + pub fn RAW_ELT(x: SEXP, i: R_xlen_t) -> Rbyte; + + pub fn RDEBUG(x: SEXP) -> std::ffi::c_int; + + pub fn REAL(x: SEXP) -> *mut f64; + + pub fn REAL_ELT(x: SEXP, i: R_xlen_t) -> f64; + + pub fn R_CHAR(x: SEXP) -> *const std::ffi::c_char; + + pub fn SETCAR(x: SEXP, y: SEXP) -> SEXP; + + pub fn SETCDR(x: SEXP, y: SEXP) -> SEXP; + + pub fn SET_PRVALUE(x: SEXP, v: SEXP); + + pub fn SET_STRING_ELT(x: SEXP, i: R_xlen_t, v: SEXP); + + pub fn SET_TAG(x: SEXP, y: SEXP); + + pub fn SET_TYPEOF(x: SEXP, v: std::ffi::c_int); + + pub fn SET_VECTOR_ELT(x: SEXP, i: R_xlen_t, v: SEXP) -> SEXP; + + pub fn STRING_ELT(x: SEXP, i: R_xlen_t) -> SEXP; + + pub fn TAG(e: SEXP) -> SEXP; + + pub fn TYPEOF(x: SEXP) -> std::ffi::c_int; + + pub fn VECTOR_ELT(x: SEXP, i: R_xlen_t) -> SEXP; + + pub fn R_GE_getVersion() -> std::ffi::c_int; + + pub fn GEcurrentDevice() -> pGEDevDesc; + + pub fn GEinitDisplayList(dd: pGEDevDesc); + + /// R >= 4.2.0 + pub fn R_existsVarInFrame(rho: SEXP, symbol: SEXP) -> Rboolean; + + // ----------------------------------------------------------------------------------- + // Unix + + /// NOTE: `R_checkActivity()` doesn't really return a void pointer, it returns + /// a `*fd_set`. But because we never introspect these values directly and they're + /// always passed around in R as pointers, it suffices to just use void pointers. + #[cfg(target_family = "unix")] + pub fn R_checkActivity(usec: i32, ignore_stdin: i32) -> *const std::ffi::c_void; + + /// NOTE: `R_runHandlers()` doesn't really take void pointers. But because we never + /// introspect these values directly and they're always passed around in R as + /// pointers, it suffices to just use void pointers. + #[cfg(target_family = "unix")] + pub fn R_runHandlers(handlers: *const std::ffi::c_void, fdset: *const std::ffi::c_void); + + // ----------------------------------------------------------------------------------- + // Windows + + #[cfg(target_family = "windows")] + pub fn cmdlineoptions(ac: i32, av: *mut *mut std::ffi::c_char); + + #[cfg(target_family = "windows")] + pub fn readconsolecfg(); + + /// R >= 4.2.0 + #[cfg(target_family = "windows")] + pub fn R_DefParamsEx(Rp: Rstart, RstartVersion: i32); + + #[cfg(target_family = "windows")] + pub fn R_SetParams(Rp: Rstart); + + /// Get R_HOME from the environment or the registry + /// + /// Checks: + /// - C `R_HOME` env var + /// - Windows API `R_HOME` environment space + /// - Current user registry + /// - Local machine registry + /// + /// Probably returns a system encoded result? + /// So needs to be converted to UTF-8. + /// + /// https://github.com/wch/r-source/blob/55cd975c538ad5a086c2085ccb6a3037d5a0cb9a/src/gnuwin32/rhome.c#L152 + #[cfg(target_family = "windows")] + pub fn get_R_HOME() -> *mut std::ffi::c_char; + + /// Get user home directory + /// + /// Checks: + /// - C `R_USER` env var + /// - C `USER` env var + /// - `Documents` folder, if one exists, through `ShellGetPersonalDirectory()` + /// - `HOMEDRIVE` + `HOMEPATH` + /// - Current directory through `GetCurrentDirectory()` + /// + /// Probably returns a system encoded result? + /// So needs to be converted to UTF-8. + /// + /// https://github.com/wch/r-source/blob/55cd975c538ad5a086c2085ccb6a3037d5a0cb9a/src/gnuwin32/shext.c#L55 + #[cfg(target_family = "windows")] + pub fn getRUser() -> *mut std::ffi::c_char; + + // In theory we should call these, but they are very new, roughly R 4.3.0. + // It isn't super harmful if we don't free these. + // https://github.com/wch/r-source/commit/9210c59281e7ab93acff9f692c31b83d07a506a6 + // pub fn freeRUser(s: *mut std::ffi::c_char); + // pub fn free_R_HOME(s: *mut std::ffi::c_char); +} + +functions_variadic::generate! { + pub fn Rf_error(arg1: *const std::ffi::c_char, ...) -> !; + + pub fn Rf_errorcall(arg1: SEXP, arg2: *const std::ffi::c_char, ...) -> !; +} + +constant_globals::generate! { + #[doc = "IEEE NaN"] + #[default = 0.0] + pub static R_NaN: f64; + + #[doc = "IEEE Inf"] + #[default = 0.0] + pub static R_PosInf: f64; + + #[doc = "IEEE -Inf"] + #[default = 0.0] + pub static R_NegInf: f64; + + #[doc = "NA_REAL: IEEE"] + #[default = 0.0] + pub static R_NaReal: f64; + + #[doc = "NA_INTEGER:= INT_MIN currently"] + #[default = 0] + pub static R_NaInt: std::ffi::c_int; + + #[doc = "The \"global\" environment"] + #[default = std::ptr::null_mut()] + pub static R_GlobalEnv: SEXP; + + #[doc = "An empty environment at the root of the\nenvironment tree"] + #[default = std::ptr::null_mut()] + pub static R_EmptyEnv: SEXP; + + #[doc = "The base environment; formerly R_NilValue"] + #[default = std::ptr::null_mut()] + pub static R_BaseEnv: SEXP; + + #[doc = "The (fake) namespace for base"] + #[default = std::ptr::null_mut()] + pub static R_BaseNamespace: SEXP; + + #[doc = "Registry for registered namespaces"] + #[default = std::ptr::null_mut()] + pub static R_NamespaceRegistry: SEXP; + + #[doc = "Current srcref, for debuggers"] + #[default = std::ptr::null_mut()] + pub static R_Srcref: SEXP; + + #[doc = "The nil object"] + #[default = std::ptr::null_mut()] + pub static R_NilValue: SEXP; + + #[doc = "Unbound marker"] + #[default = std::ptr::null_mut()] + pub static R_UnboundValue: SEXP; + + #[doc = "Missing argument marker"] + #[default = std::ptr::null_mut()] + pub static R_MissingArg: SEXP; + + #[doc = "To be found in BC interp. state\n(marker)"] + #[default = std::ptr::null_mut()] + pub static R_InBCInterpreter: SEXP; + + #[doc = "Use current expression (marker)"] + #[default = std::ptr::null_mut()] + pub static R_CurrentExpression: SEXP; + + #[doc = "Marker for restarted function calls"] + #[default = std::ptr::null_mut()] + pub static R_RestartToken: SEXP; + + #[doc = "\"as.character\""] + #[default = std::ptr::null_mut()] + pub static R_AsCharacterSymbol: SEXP; + + #[doc = "\"@\""] + #[default = std::ptr::null_mut()] + pub static R_AtsignSymbol: SEXP; + + #[doc = "\"base\""] + #[default = std::ptr::null_mut()] + pub static R_BaseSymbol: SEXP; + + #[doc = "\"{\""] + #[default = std::ptr::null_mut()] + pub static R_BraceSymbol: SEXP; + + #[doc = "\"\\[\\[\""] + #[default = std::ptr::null_mut()] + pub static R_Bracket2Symbol: SEXP; + + #[doc = "\"\\[\""] + #[default = std::ptr::null_mut()] + pub static R_BracketSymbol: SEXP; + + #[doc = "\"class\""] + #[default = std::ptr::null_mut()] + pub static R_ClassSymbol: SEXP; + + #[doc = "\".Device\""] + #[default = std::ptr::null_mut()] + pub static R_DeviceSymbol: SEXP; + + #[doc = "\"dimnames\""] + #[default = std::ptr::null_mut()] + pub static R_DimNamesSymbol: SEXP; + + #[doc = "\"dim\""] + #[default = std::ptr::null_mut()] + pub static R_DimSymbol: SEXP; + + #[doc = "\"$\""] + #[default = std::ptr::null_mut()] + pub static R_DollarSymbol: SEXP; + + #[doc = "\"...\""] + #[default = std::ptr::null_mut()] + pub static R_DotsSymbol: SEXP; + + #[doc = "\"::\""] + #[default = std::ptr::null_mut()] + pub static R_DoubleColonSymbol: SEXP; + + #[doc = "\"drop\""] + #[default = std::ptr::null_mut()] + pub static R_DropSymbol: SEXP; + + #[doc = "\"eval\""] + #[default = std::ptr::null_mut()] + pub static R_EvalSymbol: SEXP; + + #[doc = "\"function\""] + #[default = std::ptr::null_mut()] + pub static R_FunctionSymbol: SEXP; + + #[doc = "\".Last.value\""] + #[default = std::ptr::null_mut()] + pub static R_LastvalueSymbol: SEXP; + + #[doc = "\"levels\""] + #[default = std::ptr::null_mut()] + pub static R_LevelsSymbol: SEXP; + + #[doc = "\"mode\""] + #[default = std::ptr::null_mut()] + pub static R_ModeSymbol: SEXP; + + #[doc = "\"na.rm\""] + #[default = std::ptr::null_mut()] + pub static R_NaRmSymbol: SEXP; + + #[doc = "\"name\""] + #[default = std::ptr::null_mut()] + pub static R_NameSymbol: SEXP; + + #[doc = "\"names\""] + #[default = std::ptr::null_mut()] + pub static R_NamesSymbol: SEXP; + + #[doc = "\".__NAMESPACE__.\""] + #[default = std::ptr::null_mut()] + pub static R_NamespaceEnvSymbol: SEXP; + + #[doc = "\"package\""] + #[default = std::ptr::null_mut()] + pub static R_PackageSymbol: SEXP; + + #[doc = "\"previous\""] + #[default = std::ptr::null_mut()] + pub static R_PreviousSymbol: SEXP; + + #[doc = "\"quote\""] + #[default = std::ptr::null_mut()] + pub static R_QuoteSymbol: SEXP; + + #[doc = "\"row.names\""] + #[default = std::ptr::null_mut()] + pub static R_RowNamesSymbol: SEXP; + + #[doc = "\".Random.seed\""] + #[default = std::ptr::null_mut()] + pub static R_SeedsSymbol: SEXP; + + #[doc = "\"sort.list\""] + #[default = std::ptr::null_mut()] + pub static R_SortListSymbol: SEXP; + + #[doc = "\"source\""] + #[default = std::ptr::null_mut()] + pub static R_SourceSymbol: SEXP; + + #[doc = "\"spec\""] + #[default = std::ptr::null_mut()] + pub static R_SpecSymbol: SEXP; + + #[doc = "\":::\""] + #[default = std::ptr::null_mut()] + pub static R_TripleColonSymbol: SEXP; + + #[doc = "\"tsp\""] + #[default = std::ptr::null_mut()] + pub static R_TspSymbol: SEXP; + + #[doc = "\".defined\""] + #[default = std::ptr::null_mut()] + pub static R_dot_defined: SEXP; + + #[doc = "\".Method\""] + #[default = std::ptr::null_mut()] + pub static R_dot_Method: SEXP; + + #[doc = "\".packageName\""] + #[default = std::ptr::null_mut()] + pub static R_dot_packageName: SEXP; + + #[doc = "\".target\""] + #[default = std::ptr::null_mut()] + pub static R_dot_target: SEXP; + + #[doc = "\".Generic\""] + #[default = std::ptr::null_mut()] + pub static R_dot_Generic: SEXP; + + #[doc = "NA_STRING as a CHARSXP"] + #[default = std::ptr::null_mut()] + pub static R_NaString: SEXP; + + #[doc = "\"\" as a CHARSXP"] + #[default = std::ptr::null_mut()] + pub static R_BlankString: SEXP; + + #[doc = "\"\" as a STRSXP"] + #[default = std::ptr::null_mut()] + pub static R_BlankScalarString: SEXP; +} + +mutable_globals::generate! { + pub static mut R_interrupts_pending: std::ffi::c_int; + + pub static mut R_interrupts_suspended: Rboolean; + + /// Special declaration for this global variable + /// + /// I don't fully understand this! + /// + /// This is exposed in Rinterface.h, which is not available on Windows: + /// https://github.com/wch/r-source/blob/459492bc14ad5a3ff735d90a70ad71f6d5fe9faa/src/include/Rinterface.h#L176 + /// But is defined as a global variable in main.c, so presumably that is what RStudio is yanking out + /// https://github.com/wch/r-source/blob/459492bc14ad5a3ff735d90a70ad71f6d5fe9faa/src/main/main.c#L729 + /// It controls whether R level signal handlers are set up, which presumably we don't want + /// https://github.com/wch/r-source/blob/459492bc14ad5a3ff735d90a70ad71f6d5fe9faa/src/main/main.c#L1047 + /// RStudio sets this, and I think they access it by using this dllimport + /// https://github.com/rstudio/rstudio/blob/07ef754fc9f27d41b100bb40d83ec3ddf485b47b/src/cpp/r/include/r/RInterface.hpp#L40 + pub static mut R_SignalHandlers: std::ffi::c_int; + + pub static mut R_ParseError: std::ffi::c_int; + + /// 256 comes from R's `PARSE_ERROR_SIZE` define + pub static mut R_ParseErrorMsg: [std::ffi::c_char; 256usize]; + + pub static mut R_DirtyImage: std::ffi::c_int; + + pub static mut R_CStackLimit: usize; + + // ----------------------------------------------------------------------------------- + // Unix + + #[cfg(target_family = "unix")] + pub static mut R_running_as_main_program: std::ffi::c_int; + + #[cfg(target_family = "unix")] + pub static mut R_Interactive: Rboolean; + + #[cfg(target_family = "unix")] + pub static mut R_InputHandlers: *const std::ffi::c_void; + + #[cfg(target_family = "unix")] + pub static mut R_Consolefile: *mut libc::FILE; + + #[cfg(target_family = "unix")] + pub static mut R_Outputfile: *mut libc::FILE; + + #[cfg(target_family = "unix")] + pub static mut R_wait_usec: i32; + + #[cfg(target_family = "unix")] + pub static mut R_PolledEvents: Option; + + #[cfg(target_family = "unix")] + pub static mut ptr_R_WriteConsole: Option< + unsafe extern "C" fn(arg1: *const std::ffi::c_char, arg2: std::ffi::c_int), + >; + + #[cfg(target_family = "unix")] + pub static mut ptr_R_WriteConsoleEx: Option< + unsafe extern "C" fn( + arg1: *const std::ffi::c_char, + arg2: std::ffi::c_int, + arg3: std::ffi::c_int, + ), + >; + + #[cfg(target_family = "unix")] + pub static mut ptr_R_ReadConsole: Option< + unsafe extern "C" fn( + arg1: *const std::ffi::c_char, + arg2: *mut std::ffi::c_uchar, + arg3: std::ffi::c_int, + arg4: std::ffi::c_int, + ) -> std::ffi::c_int, + >; + + #[cfg(target_family = "unix")] + pub static mut ptr_R_ShowMessage: Option; + + #[cfg(target_family = "unix")] + pub static mut ptr_R_Busy: Option; + + // ----------------------------------------------------------------------------------- + // Windows + + #[cfg(target_family = "windows")] + pub static mut UserBreak: Rboolean; + + /// The codepage that R thinks it should be using for Windows. + /// Should map to `winsafe::kernel::co::CP`. + #[cfg(target_family = "windows")] + pub static mut localeCP: std::ffi::c_uint; +} diff --git a/crates/libr/src/sys.rs b/crates/libr/src/sys.rs new file mode 100644 index 000000000..b9e34711e --- /dev/null +++ b/crates/libr/src/sys.rs @@ -0,0 +1,16 @@ +// +// sys.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +cfg_if::cfg_if! { + if #[cfg(unix)] { + mod unix; + pub use self::unix::*; + } else if #[cfg(windows)] { + mod windows; + pub use self::windows::*; + } +} diff --git a/crates/libr/src/sys/unix.rs b/crates/libr/src/sys/unix.rs new file mode 100644 index 000000000..83d6b126f --- /dev/null +++ b/crates/libr/src/sys/unix.rs @@ -0,0 +1,8 @@ +// +// unix.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +pub mod types; diff --git a/crates/libr/src/sys/unix/types.rs b/crates/libr/src/sys/unix/types.rs new file mode 100644 index 000000000..853d0f464 --- /dev/null +++ b/crates/libr/src/sys/unix/types.rs @@ -0,0 +1,10 @@ +// +// types.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +// Currently not used, but this keeps us aligned with `sys/windows/types.rs`. +// If we start using this, remove this line from `types.rs`: +// `#[cfg_attr(target_family = "unix", allow(unused_imports))]` diff --git a/crates/libr/src/sys/windows.rs b/crates/libr/src/sys/windows.rs new file mode 100644 index 000000000..983652d42 --- /dev/null +++ b/crates/libr/src/sys/windows.rs @@ -0,0 +1,8 @@ +// +// windows.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +pub mod types; diff --git a/crates/ark/src/sys/windows/interface_types.rs b/crates/libr/src/sys/windows/types.rs similarity index 97% rename from crates/ark/src/sys/windows/interface_types.rs rename to crates/libr/src/sys/windows/types.rs index 9e3217364..c9c7b1523 100644 --- a/crates/ark/src/sys/windows/interface_types.rs +++ b/crates/libr/src/sys/windows/types.rs @@ -1,3 +1,12 @@ +// +// types.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +// Windows specific types required by R functions we need +// // Generated by: // - Cloning libR-sys and doing the Windows specific setup to be able to generate bindings // - Adding `#define Win32` to `wrapper.h` (otherwise not everything Windows specific comes through) @@ -12,7 +21,7 @@ #![allow(non_snake_case)] #![allow(unused)] -use libR_shim::Rboolean; +use crate::Rboolean; // Start of copy from libR-sys `bindings.rs` diff --git a/crates/libr/src/types.rs b/crates/libr/src/types.rs new file mode 100644 index 000000000..7680c9107 --- /dev/null +++ b/crates/libr/src/types.rs @@ -0,0 +1,113 @@ +// +// types.rs +// +// Copyright (C) 2024 Posit Software, PBC. All rights reserved. +// +// + +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] + +// Reexport all system specific R types +#[cfg_attr(target_family = "unix", allow(unused_imports))] +pub use crate::sys::types::*; + +#[doc = "R_xlen_t is defined as int on 32-bit platforms, and that confuses Rust. Keeping it always as ptrdiff_t works fine even on 32-bit."] +pub type R_xlen_t = isize; + +pub type SEXPTYPE = std::ffi::c_uint; +pub const NILSXP: u32 = 0; +pub const SYMSXP: u32 = 1; +pub const LISTSXP: u32 = 2; +pub const CLOSXP: u32 = 3; +pub const ENVSXP: u32 = 4; +pub const PROMSXP: u32 = 5; +pub const LANGSXP: u32 = 6; +pub const SPECIALSXP: u32 = 7; +pub const BUILTINSXP: u32 = 8; +pub const CHARSXP: u32 = 9; +pub const LGLSXP: u32 = 10; +pub const INTSXP: u32 = 13; +pub const REALSXP: u32 = 14; +pub const CPLXSXP: u32 = 15; +pub const STRSXP: u32 = 16; +pub const DOTSXP: u32 = 17; +pub const ANYSXP: u32 = 18; +pub const VECSXP: u32 = 19; +pub const EXPRSXP: u32 = 20; +pub const BCODESXP: u32 = 21; +pub const EXTPTRSXP: u32 = 22; +pub const WEAKREFSXP: u32 = 23; +pub const RAWSXP: u32 = 24; +pub const S4SXP: u32 = 25; +pub const NEWSXP: u32 = 30; +pub const FREESXP: u32 = 31; +pub const FUNSXP: u32 = 99; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct SEXPREC { + _unused: [u8; 0], +} +pub type SEXP = *mut SEXPREC; + +#[doc = "R 4.3 redefined `Rcomplex` to a union for compatibility with Fortran.\n But the old definition is compatible both the union version\n and the struct version.\n See: https://github.com/extendr/extendr/issues/524"] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Rcomplex { + pub r: f64, + pub i: f64, +} + +pub type Rboolean = u32; +pub const Rboolean_FALSE: Rboolean = 0; +pub const Rboolean_TRUE: Rboolean = 1; + +pub type Rbyte = std::ffi::c_uchar; + +pub type cetype_t = u32; +pub const cetype_t_CE_NATIVE: cetype_t = 0; +pub const cetype_t_CE_UTF8: cetype_t = 1; +pub const cetype_t_CE_LATIN1: cetype_t = 2; +pub const cetype_t_CE_BYTES: cetype_t = 3; +pub const cetype_t_CE_SYMBOL: cetype_t = 5; +pub const cetype_t_CE_ANY: cetype_t = 99; + +pub type ParseStatus = u32; +pub const ParseStatus_PARSE_NULL: ParseStatus = 0; +pub const ParseStatus_PARSE_OK: ParseStatus = 1; +pub const ParseStatus_PARSE_INCOMPLETE: ParseStatus = 2; +pub const ParseStatus_PARSE_ERROR: ParseStatus = 3; +pub const ParseStatus_PARSE_EOF: ParseStatus = 4; + +pub type DL_FUNC = Option *mut std::ffi::c_void>; +pub type R_NativePrimitiveArgType = std::ffi::c_uint; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _DllInfo { + _unused: [u8; 0], +} +pub type DllInfo = _DllInfo; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct R_CMethodDef { + pub name: *const std::ffi::c_char, + pub fun: DL_FUNC, + pub numArgs: std::ffi::c_int, + pub types: *mut R_NativePrimitiveArgType, +} + +pub type R_FortranMethodDef = R_CMethodDef; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct R_CallMethodDef { + pub name: *const std::ffi::c_char, + pub fun: DL_FUNC, + pub numArgs: std::ffi::c_int, +} + +pub type R_ExternalMethodDef = R_CallMethodDef; diff --git a/scripts/post-install.sh b/scripts/post-install.sh deleted file mode 100755 index 23b074c48..000000000 --- a/scripts/post-install.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env sh - -set -eu - -if [ "$(uname)" != "Darwin" ]; then - exit 0 -fi - -# On macOS, we use install_name_tool to fix up the link to libR.dylib. -# -# Note that we still try to link with '-undefined dynamic_lookup', just to -# ensure that linking succeeds when we compile against a version of R compiled -# for a different architecture. This is mostly relevant when producing x86_64 -# builds of ark on an arm64 machine. -# -# However, because using libR-sys still implies that the path to the R library -# ends up in the library load list, we have to modify that after the fact anyhow. - -: ${AMALTHEA_BUILD_TYPE="debug"} - -# Should be called from Amalthea root by default so that we can find the Ark executable -AMALTHEA_ROOT="${AMALTHEA_PATH:-.}" - -# Normalize -AMALTHEA_ROOT=$(cd "$AMALTHEA_ROOT" && pwd) - -# Get the path to the Ark executable -ARK_PATH="${AMALTHEA_ROOT}/target/${AMALTHEA_BUILD_TYPE}/ark" - -if [ ! -f "$ARK_PATH" ]; then - echo "Can't find Ark executable in $AMALTHEA_ROOT" - echo "- Do you need to set 'AMALTHEA_ROOT'?" - echo "- Do you need to run 'cargo build'?" - exit 1 -fi - -# Figure out what version of R that we linked to -OLD_PATH=`otool -L target/debug/ark | grep libR.dylib | cut -c2- | cut -d' ' -f1` -NEW_PATH="@rpath/libR.dylib" - -# Change that to use @rpath instead. We don't actually set an @rpath in the compiled -# executable (we inject R via DYLD_INSERT_LIBRARIES) so this is mainly just hygiene. -install_name_tool -change "${OLD_PATH}" "${NEW_PATH}" "${ARK_PATH}" diff --git a/scripts/windows/dll2lib.R b/scripts/windows/dll2lib.R deleted file mode 100644 index f4da50897..000000000 --- a/scripts/windows/dll2lib.R +++ /dev/null @@ -1,125 +0,0 @@ -# --------------------------------------------------------------------------------------------- -# Copyright (C) 2023 Posit Software, PBC. All rights reserved. -# --------------------------------------------------------------------------------------------- - -# 2023-12-06 -# At the time of writing, this script is for "manual" execution on Windows. -# For the moment, it is a one-time setup task for a specific R installation. -# -# You must be running R as an administrator to execute this script. -# If, for example, you do this via RStudio, make sure to launch RStudio as an administrator. - -# Typically something like: -# "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.37.32822\\bin\\Hostx86\\x86" -# A number of the pieces are unstable (year, Community vs Enterprise, exact version) -# so we try and use `vswhere.exe` and `vsdevcmd.bat` to find the exact path - -get_visual_studio_tools_directory <- function() { - # First find `vswhere.exe`, which is supposedly always in the same spot - vswhere <- file.path("C:", "Program Files (x86)", "Microsoft Visual Studio", "Installer") - - if (!dir.exists(vswhere)) { - stop("Microsoft Visual Studio Installer folder does not exist.") - } - - vswhere <- file.path(vswhere, "vswhere.exe") - vswhere <- normalizePath(vswhere, mustWork = TRUE) - vswhere <- shQuote(vswhere) - vswhere <- paste(vswhere, "-prerelease -latest -property installationPath") - - # `vswhere` tells us where Microsoft Visual Studio lives - visualstudio <- system(vswhere, intern = TRUE) - - if (!is.character(visualstudio) && length(visualstudio) != 1L && !is.na(visualstudio) && !dir.exists(visualstudio)) { - stop("`vswhere` failed to find Microsoft Visual Studio") - } - - # Next we navigate to `vsdevcmd.bat`, which also has a stable path, according - # to https://github.com/microsoft/vswhere/wiki/Start-Developer-Command-Prompt - vscmdbat <- file.path(visualstudio, "Common7", "Tools", "VsDevCmd.bat") - vscmdbat <- normalizePath(vscmdbat, mustWork = TRUE) - vscmdbat <- shQuote(vscmdbat) - vscmdbat <- paste(vscmdbat, "-arch=amd64 -startdir=none -host_arch=amd64 -no_logo") - - where <- "where dumpbin.exe" - - # Running `VsDevCmd.bat` puts tools like `dumpbin.exe` and `link.exe` on the - # PATH in the current command prompt, so we run that and then ask `where` to - # find `dumpbin.exe` (finding `link.exe` also finds one from RTools). - command <- paste(vscmdbat, "&&", where) - dumpbin <- system(command, intern = TRUE) - - if (length(dumpbin) > 1L) { - warning("Found multiple `dumpbin.exe`. Looking for one tied to Visual Studio.") - dumpbin <- dumpbin[grepl("Microsoft Visual Studio", dumpbin)] - - if (length(dumpbin) > 1L) { - warning("Still have multiple `dumpbin.exe`. Taking the first.") - dumpbin <- dumpbin[[1L]] - } - } - if (!is.character(dumpbin) && length(dumpbin) != 1L && !is.na(dumpbin) && !file.exists(dumpbin)) { - stop("`where` failed to find `dumpbin.exe`.") - } - - # Now just look up one level - path <- normalizePath(file.path(dumpbin, "..")) - - path -} - -# Get the Visual Studio tools directory where `dumpbin.exe` and `lib.exe` live -path <- get_visual_studio_tools_directory() - -# Put the path containing the tools on the PATH. -Sys.setenv(PATH = paste(path, Sys.getenv("PATH"), sep = ";")) - -# Find R DLLs. -dlls <- list.files(R.home("bin"), pattern = "dll$", full.names = TRUE) - -message("Generating .lib files for DLLs in ", R.home("bin")) - -# Generate corresponding 'lib' file for each DLL. -for (dll in dlls) { - - # Check to see if we've already generated our exports - def <- sub("dll$", "def", dll) - if (file.exists(def)) - next - - # Call it on R.dll to generate exports. - command <- sprintf("dumpbin.exe /EXPORTS /NOLOGO %s", dll) - message("> ", command) - output <- system(paste(command), intern = TRUE) - - # Remove synonyms. - output <- sub("=.*$", "", output) - - # Find start, end markers - start <- grep("ordinal\\s+hint\\s+RVA\\s+name", output) - end <- grep("^\\s*Summary\\s*$", output) - contents <- output[start:(end - 1)] - contents <- contents[nzchar(contents)] - - # Remove forwarded fields (not certain that this does anything) - contents <- grep("forwarded to", contents, invert = TRUE, value = TRUE, fixed = TRUE) - - # Parse into a table - tbl <- read.table(text = contents, header = TRUE, stringsAsFactors = FALSE) - exports <- tbl$name - - # Sort and re-format exports - exports <- sort(exports) - exports <- c("EXPORTS", paste("\t", tbl$name, sep = "")) - - # Write the exports to a def file - def <- sub("dll$", "def", dll) - cat(exports, file = def, sep = "\n") - - # Call 'lib.exe' to generate the library file. - outfile <- sub("dll$", "lib", dll) - fmt <- "lib.exe /nologo /def:%s /out:%s /machine:%s" - cmd <- sprintf(fmt, def, outfile, .Platform$r_arch) - system(cmd) - -}