|
| 1 | + |
| 2 | +# Check that a package linking against RcppParallel's TBB can be loaded on its |
| 3 | +# own, in a process that has not already loaded RcppParallel. |
| 4 | +# |
| 5 | +# The TBB libraries we ship on macOS record an '@rpath'-relative install name, |
| 6 | +# so LdFlags() must emit an '-rpath' alongside its '-L'. With only the '-L' the |
| 7 | +# link still succeeds -- so the package builds, and loads fine via its |
| 8 | +# NAMESPACE, whose importFrom(RcppParallel, ...) pulls TBB into the process |
| 9 | +# first -- but the shared object itself carries no runtime search path, and |
| 10 | +# dyld fails with "Library not loaded: @rpath/libtbb.dylib" for anything that |
| 11 | +# loads it directly. Hence the load below runs in a fresh R process which never |
| 12 | +# touches RcppParallel; loading it from here would prove nothing. |
| 13 | +# |
| 14 | +# See also tests/test-ld-flags.R, the cheap flag-level check that still runs |
| 15 | +# where no toolchain is available. |
| 16 | +# |
| 17 | +# https://github.com/RcppCore/RcppParallel/issues/209 |
| 18 | + |
| 19 | +RcppParallel:::test_init() |
| 20 | + |
| 21 | +# building the test package requires a toolchain, so keep this test |
| 22 | +# off of CRAN's machines |
| 23 | +ci <- nzchar(Sys.getenv("CI")) || identical(Sys.getenv("NOT_CRAN"), "true") |
| 24 | +if (!ci) { |
| 25 | + writeLines("Not running on CI; skipping downstream load test.") |
| 26 | + quit(save = "no") |
| 27 | +} |
| 28 | + |
| 29 | +if (!TBB_ENABLED) { |
| 30 | + writeLines("TBB is not enabled; skipping downstream load test.") |
| 31 | + quit(save = "no") |
| 32 | +} |
| 33 | + |
| 34 | +# Windows has no rpath: the loader resolves DLLs through the process search |
| 35 | +# path, and downstream packages link '-lRcppParallel' rather than TBB itself |
| 36 | +if (is_windows()) { |
| 37 | + writeLines("Windows resolves DLLs via the search path; skipping.") |
| 38 | + quit(save = "no") |
| 39 | +} |
| 40 | + |
| 41 | +# A standalone load can only work where TBB is linked into the client |
| 42 | +# explicitly: macOS (#206), and any platform configured against a system TBB. |
| 43 | +# Elsewhere the TBB symbols are deliberately left undefined at link time and |
| 44 | +# resolved from the libraries RcppParallel loads, so a standalone load is |
| 45 | +# expected to fail. Note this mirrors the branches in tbbLdFlags() rather than |
| 46 | +# inspecting its output: gating on the '-rpath' we are checking for would skip |
| 47 | +# precisely the configuration that regressed. |
| 48 | +tbbLib <- Sys.getenv("TBB_LINK_LIB", Sys.getenv("TBB_LIB", unset = TBB_LIB)) |
| 49 | +if (!is_mac() && !nzchar(tbbLib)) { |
| 50 | + writeLines("TBB is not linked into downstream packages here; skipping.") |
| 51 | + quit(save = "no") |
| 52 | +} |
| 53 | + |
| 54 | +# generate the test package |
| 55 | +pkgRoot <- file.path(tempdir(), "downstreamtest") |
| 56 | +dir.create(file.path(pkgRoot, "src"), recursive = TRUE, showWarnings = FALSE) |
| 57 | + |
| 58 | +writeLines(con = file.path(pkgRoot, "DESCRIPTION"), c( |
| 59 | + "Package: downstreamtest", |
| 60 | + "Type: Package", |
| 61 | + "Title: Test Loading a Package Linked Against RcppParallel's TBB", |
| 62 | + "Version: 0.1.0", |
| 63 | + "Author: RcppParallel Authors", |
| 64 | + "Maintainer: RcppParallel Authors <noreply@example.com>", |
| 65 | + "Description: Confirms that such packages can be loaded standalone.", |
| 66 | + "License: GPL-2", |
| 67 | + "Imports: RcppParallel" |
| 68 | +)) |
| 69 | + |
| 70 | +writeLines(con = file.path(pkgRoot, "NAMESPACE"), c( |
| 71 | + "useDynLib(downstreamtest)", |
| 72 | + "importFrom(RcppParallel, RcppParallelLibs)" |
| 73 | +)) |
| 74 | + |
| 75 | +writeLines(con = file.path(pkgRoot, "src", "Makevars"), c( |
| 76 | + 'PKG_CXXFLAGS = $(shell "${R_HOME}/bin/Rscript" -e "RcppParallel::CxxFlags()")', |
| 77 | + 'PKG_LIBS = $(shell "${R_HOME}/bin/Rscript" -e "RcppParallel::RcppParallelLibs()")' |
| 78 | +)) |
| 79 | + |
| 80 | +# call into TBB proper, so that the shared object records a real load-time |
| 81 | +# dependency on the TBB library rather than merely being linked against it |
| 82 | +writeLines(con = file.path(pkgRoot, "src", "concurrency.cpp"), c( |
| 83 | + "#include <tbb/task_arena.h>", |
| 84 | + "", |
| 85 | + "#include <R.h>", |
| 86 | + "#include <Rinternals.h>", |
| 87 | + "", |
| 88 | + 'extern "C" SEXP downstream_max_concurrency(void)', |
| 89 | + "{", |
| 90 | + " return Rf_ScalarInteger(tbb::this_task_arena::max_concurrency());", |
| 91 | + "}" |
| 92 | +)) |
| 93 | + |
| 94 | +# install it, making sure child processes resolve this RcppParallel |
| 95 | +libDir <- file.path(tempdir(), "library") |
| 96 | +dir.create(libDir, recursive = TRUE, showWarnings = FALSE) |
| 97 | +Sys.setenv(R_LIBS = paste(.libPaths(), collapse = .Platform$path.sep)) |
| 98 | + |
| 99 | +rExe <- file.path(R.home("bin"), "R") |
| 100 | +args <- c("CMD", "INSTALL", "--no-multiarch", paste0("--library=", shQuote(libDir)), shQuote(pkgRoot)) |
| 101 | + |
| 102 | +output <- suppressWarnings(system2(rExe, args, stdout = TRUE, stderr = TRUE)) |
| 103 | +writeLines(output) |
| 104 | + |
| 105 | +status <- attr(output, "status") |
| 106 | +if (is.numeric(status) && status != 0L) |
| 107 | + stop("error installing test package (status code ", status, ")") |
| 108 | + |
| 109 | +# locate the shared object that was just built |
| 110 | +pattern <- paste0("^downstreamtest\\", .Platform$dynlib.ext, "$") |
| 111 | +shlib <- list.files( |
| 112 | + file.path(libDir, "downstreamtest", "libs"), |
| 113 | + pattern = pattern, |
| 114 | + recursive = TRUE, |
| 115 | + full.names = TRUE |
| 116 | +) |
| 117 | + |
| 118 | +assert(length(shlib) == 1L) |
| 119 | + |
| 120 | +# load it from a process which has never loaded RcppParallel; deparse() is used |
| 121 | +# to embed the path as a properly-quoted R string literal |
| 122 | +script <- file.path(tempdir(), "downstream-load.R") |
| 123 | +writeLines(con = script, c( |
| 124 | + 'if ("RcppParallel" %in% loadedNamespaces())', |
| 125 | + ' stop("RcppParallel is loaded; this check would prove nothing")', |
| 126 | + "", |
| 127 | + sprintf("dll <- dyn.load(%s)", deparse(shlib)), |
| 128 | + 'value <- .Call(getNativeSymbolInfo("downstream_max_concurrency", dll))', |
| 129 | + 'cat("max concurrency:", value, "\\n")', |
| 130 | + "", |
| 131 | + 'if (!is.integer(value) || is.na(value) || value < 1L)', |
| 132 | + ' stop("unexpected concurrency: ", value)' |
| 133 | +)) |
| 134 | + |
| 135 | +rscript <- file.path(R.home("bin"), "Rscript") |
| 136 | +output <- suppressWarnings(system2(rscript, c("--vanilla", shQuote(script)), stdout = TRUE, stderr = TRUE)) |
| 137 | +writeLines(output) |
| 138 | + |
| 139 | +status <- attr(output, "status") |
| 140 | +assert(is.null(status) || status == 0L) |
0 commit comments