Skip to content
Closed
2 changes: 2 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ Suggests:
rcmdcheck,
knitr,
lintr,
mockery (>= 0.4.5),
patrick (>= 0.3.0),
rmarkdown,
testthat (>= 3.1.7),
usethis
Expand Down
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
* Unknown macro options in dev and release now throw errors instead of warnings
* `vendor_pkgs()` now has a `clean` argument to remove the `src/rust/vendor` directory after creating the `vendor.tar.xz` file. (#479)
* `Makevars`(.win) now uses the `vendor/`, if it exists, before unzipping the tarball. (#479)
* Enhanced runtime compilation with `rust_source()` family of functions (#481)
* Dropped support for 32-bit Windows target
* Added support for ARM64 Windows target


# rextendr 0.4.2

Expand Down
64 changes: 5 additions & 59 deletions R/source.R
Original file line number Diff line number Diff line change
Expand Up @@ -278,65 +278,7 @@ rust_source <- function(

# append rtools path to the end of PATH on Windows
if (opts[["use_rtools"]] && .Platform$OS.type == "windows") {
if (!suppressMessages(pkgbuild::has_rtools())) {
cli::cli_abort(
c(
"Unable to find Rtools that are needed for compilation.",
"i" = "Required version is {.emph {pkgbuild::rtools_needed()}}."
),
class = "rextendr_error"
)
}

if (identical(R.version$crt, "ucrt")) {
# TODO: update this when R 5.0 is released.
if (!identical(R.version$major, "4")) {
cli::cli_abort(
"rextendr currently supports R 4.2",
call = rlang::caller_call(),
class = "rextendr_error"
)
}

minor_patch <- package_version(R.version$minor)

if (minor_patch >= "5.0") {
rtools_version <- "45" # nolint: object_usage_linter
} else if (minor_patch >= "4.0") {
rtools_version <- "44" # nolint: object_usage_linter
} else if (minor_patch >= "3.0") {
rtools_version <- "43" # nolint: object_usage_linter
} else {
rtools_version <- "42" # nolint: object_usage_linter
}

rtools_home <- normalizePath(
Sys.getenv(
glue("RTOOLS{rtools_version}_HOME"),
glue("C:\\rtools{rtools_version}")
),
mustWork = TRUE
)

# c.f. https://github.com/wch/r-source/blob/f09d3d7fa4af446ad59a375d914a0daf3ffc4372/src/library/profile/Rprofile.windows#L70-L71 # nolint: line_length_linter
subdir <- c("x86_64-w64-mingw32.static.posix", "usr")
} else {
# rtools_path() returns path to the RTOOLS40_HOME\usr\bin,
# but we need RTOOLS40_HOME\mingw{arch}\bin, hence the "../.."
rtools_home <- normalizePath(
# `pkgbuild` may return two paths for R < 4.2 with Rtools40v2
file.path(pkgbuild::rtools_path()[1], "..", ".."),
winslash = "/",
mustWork = TRUE
)

subdir <- paste0("mingw", ifelse(R.version$arch == "i386", "32", "64"))
# if RTOOLS40_HOME is properly set, this will have no real effect
withr::local_envvar(RTOOLS40_HOME = rtools_home)
}

rtools_bin_path <- normalizePath(file.path(rtools_home, subdir, "bin"))
withr::local_path(rtools_bin_path, action = "suffix")
use_rtools()
}

# get target name, not null for Windows
Expand Down Expand Up @@ -804,6 +746,10 @@ get_specific_target_name <- function() {
return("i686-pc-windows-gnu")
}

if (R.version$arch == "aarch64") {
return(NULL)
}

cli::cli_abort(
"Unknown Windows architecture",
class = "rextendr_error"
Expand Down
124 changes: 124 additions & 0 deletions R/use_rtools.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
get_r_version <- function() {
R.version
}
Comment on lines +1 to +3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this just calls a constant value, can we remove this in favor of R.version so that we don't have to tease out the indirection?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a trade off for testability. Depending on static state (R.version) is suboptimal

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no practical different between get_r_version() and R.version in this use case. The function's sole purpose is to grab R.version but with one level of ambiguity.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and so that you can unit test it


is_windows_arm <- function() {
proc_arch <- Sys.getenv("PROCESSOR_ARCHITECTURE")
r_arch <- get_r_version()[["arch"]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
r_arch <- get_r_version()[["arch"]]
r_arch <- R.version[["arch"]]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reason as above


if (identical(proc_arch, "ARM64") && !identical(r_arch, "aarch64")) {
cli::cli_abort(
c(
"Architecture mismatch detected.",
"i" = "You are running the {.code {proc_arch}} version of Windows, but the {.code {r_arch}} version of R.",
"i" = "You can find ARM64 build of R at {.url https://www.r-project.org/nosvn/winutf8/aarch64}"
),
class = "rextendr_error"
)
}

identical(proc_arch, "ARM64") && identical(r_arch, "aarch64")
}

throw_if_no_rtools <- function() {
if (!suppressMessages(pkgbuild::has_rtools())) {
cli::cli_abort(
c(
"Unable to find Rtools that are needed for compilation.",
"i" = "Required version is {.emph {pkgbuild::rtools_needed()}}."
),
class = "rextendr_error"
)
}
}

throw_if_not_ucrt <- function() {
if (!identical(get_r_version()[["crt"]], "ucrt")) {
cli::cli_abort(
c(
"R must be built with UCRT to use rextendr.",
"i" = "Please install the UCRT version of R from {.url https://cran.r-project.org/}."
),
class = "rextendr_error"
)
}
}

get_rtools_version <- function() {
minor_patch <- package_version(get_r_version()[["minor"]])

if (minor_patch >= "5.0") {
"45"
} else if (minor_patch >= "4.0") {
"44"
} else if (minor_patch >= "3.0") {
"43"
} else {
"42"
}
}

get_path_to_cargo_folder_arm <- function(rtools_root) {
path_to_cargo_folder <- file.path(rtools_root, "clangarm64", "bin")
path_to_cargo <- file.path(path_to_cargo_folder, "cargo.exe")
if (!file.exists(path_to_cargo)) {
cli::cli_abort(
c(
"{.code rextendr} on ARM Windows requires an ARM-compatible Rust toolchain.",
"i" = "Check this instructions to set up {.code cargo} using ARM version of RTools: {.url https://github.com/r-rust/faq?tab=readme-ov-file#does-rust-support-windows-on-arm64-aarch64}." # nolint: line_length_linter
),
class = "rextendr_error"
)
}

normalizePath(path_to_cargo_folder, mustWork = TRUE)
}
Comment on lines +61 to +75
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add internal doc comments here? I don't necessarily follow what this function does 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this differ from rtools bin path funciton?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path to rtools cargo installation, different subfolder

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay! So rtools now distributes cargo??!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but you can install cargo msys2 style on Arm machine because the target we need is not yet supported (Tier 2).


get_rtools_home <- function(rtools_version, is_arm) {
env_var <- if (is_arm) {
sprintf("RTOOLS%s_AARCH64_HOME", rtools_version)
} else {
sprintf("RTOOLS%s_HOME", rtools_version)
}

default_path <- if (is_arm) {
sprintf("C:\\rtools%s-aarch64", rtools_version)
} else {
sprintf("C:\\rtools%s", rtools_version)
}

normalizePath(
Sys.getenv(env_var, default_path),
mustWork = TRUE
)
}
Comment on lines +77 to +94
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between this and pkgbuild::rtools_path()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It clones old behavior. Pkgbuild is not 100% reliable


get_rtools_bin_path <- function(rtools_home, is_arm) {
# c.f. https://github.com/wch/r-source/blob/f09d3d7fa4af446ad59a375d914a0daf3ffc4372/src/library/profile/Rprofile.windows#L70-L71 # nolint: line_length_linter
subdir <- if (is_arm) {
c("aarch64-w64-mingw32.static.posix", "usr")
} else {
c("x86_64-w64-mingw32.static.posix", "usr")
}

normalizePath(file.path(rtools_home, subdir, "bin"), mustWork = TRUE)
}
Comment on lines +96 to +105
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between this and pkgbuild::rtools_path()? Should we prefer pkgbuild here?


use_rtools <- function(.local_envir = parent.frame()) {
throw_if_no_rtools()
throw_if_not_ucrt()
Comment on lines +108 to +109
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since these functions are only used once, would you mind either defining subroutines in the function or removing the function calls for the body calls instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is the sole purpose of this: stop writing notebook style code with functions spanning hundreds of rows, with deep nesting.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would make sense if the function was utilized in more than one place. Since it isn't it has the effect of obfuscating the function definition and code making it more challenging for others to contribute or understand the logic of the function.


is_arm <- is_windows_arm()
rtools_version <- get_rtools_version()
rtools_home <- get_rtools_home(rtools_version, is_arm)
rtools_bin_path <- get_rtools_bin_path(rtools_home, is_arm)

withr::local_path(rtools_bin_path, action = "suffix", .local_envir = .local_envir)

if (is_arm) {
cargo_path <- get_path_to_cargo_folder_arm(rtools_home)
withr::local_path(cargo_path, .local_envir = .local_envir)
}

invisible()
}
Loading
Loading