Skip to content

Commit

Permalink
rstudio: build as electron app, add darwin support
Browse files Browse the repository at this point in the history
  • Loading branch information
TomaSajt committed Dec 9, 2024
1 parent 62dc1f5 commit 90fc1f7
Show file tree
Hide file tree
Showing 4 changed files with 274 additions and 31 deletions.
134 changes: 111 additions & 23 deletions pkgs/applications/editors/rstudio/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
zlib,
openssl,
R,
libsForQt5,
fontconfig,
quarto,
libuuid,
hunspellDicts,
Expand All @@ -21,16 +21,28 @@
yaml-cpp,
soci,
sqlite,
apple-sdk_11,
xcbuild,
nodejs,
npmHooks,
fetchNpmDeps,
yarn,
yarnConfigHook,
fetchYarnDeps,
zip,
git,
makeWrapper,
electron_31,
electron_31-bin,
server ? false, # build server version
pam,
nixosTests,
}:

let
electron = electron_31;
electron-bin = electron_31-bin;

mathJaxSrc = fetchzip {
url = "https://s3.amazonaws.com/rstudio-buildtools/mathjax-27.zip";
hash = "sha256-J7SZK/9q3HcXTD7WFHxvh++ttuCd89Vc4SEBrUEU0AI=";
Expand Down Expand Up @@ -76,12 +88,15 @@ stdenv.mkDerivation rec {
ant
jdk
nodejs
(nodejs.python.withPackages (ps: [ ps.setuptools ]))
npmHooks.npmConfigHook
yarn
yarnConfigHook
zip
git
]
++ lib.optionals (!server) [
libsForQt5.wrapQtAppsHook
];
++ lib.optionals (stdenv.hostPlatform.isDarwin) [ xcbuild ]
++ lib.optionals (!server) [ makeWrapper ];

buildInputs =
[
Expand All @@ -94,30 +109,32 @@ stdenv.mkDerivation rec {
soci
sqlite.dev
]
++ lib.optionals (stdenv.hostPlatform.isDarwin) [ apple-sdk_11 ]
++ lib.optionals server [ pam ]
++ lib.optionals (!server) [
libsForQt5.qtbase
libsForQt5.qtxmlpatterns
libsForQt5.qtsensors
libsForQt5.qtwebengine
libsForQt5.qtwebchannel
];
++ lib.optionals (!server) [ fontconfig ];

cmakeFlags =
[
(lib.cmakeFeature "RSTUDIO_TARGET" (if server then "Server" else "Desktop"))
(lib.cmakeFeature "RSTUDIO_TARGET" (if server then "Server" else "Electron"))
(lib.cmakeBool "RSTUDIO_USE_SYSTEM_SOCI" true)
(lib.cmakeBool "RSTUDIO_USE_SYSTEM_BOOST" true)
(lib.cmakeBool "RSTUDIO_USE_SYSTEM_YAML_CPP" true)
(lib.cmakeBool "RSTUDIO_DISABLE_CHECK_FOR_UPDATES" true)
(lib.cmakeBool "QUARTO_ENABLED" true)
(lib.cmakeFeature "CMAKE_INSTALL_PREFIX" "${placeholder "out"}/lib/rstudio")
(lib.cmakeBool "RSTUDIO_CRASHPAD_ENABLED" false) # need to explicitly disable for x86_64-darwin
(lib.cmakeFeature "CMAKE_INSTALL_PREFIX" (
(placeholder "out") + (if stdenv.isDarwin then "/Applications" else "/lib/rstudio")
))
]
++ lib.optionals (!server) [
(lib.cmakeFeature "QT_QMAKE_EXECUTABLE" "${libsForQt5.qmake}/bin/qmake")
(lib.cmakeBool "RSTUDIO_INSTALL_FREEDESKTOP" true)
(lib.cmakeFeature "NODEJS" (lib.getExe' nodejs "node"))
(lib.cmakeFeature "NPM" (lib.getExe' nodejs "npm"))
(lib.cmakeBool "RSTUDIO_INSTALL_FREEDESKTOP" stdenv.hostPlatform.isLinux)
];

# on Darwin, cmake uses find_library to locate R instead of using the PATH
env.NIX_LDFLAGS = "-L${R}/lib/R/lib";

patches = [
# Hack RStudio to only use the input R and provided libclang.
(replaceVars ./r-location.patch {
Expand All @@ -131,6 +148,10 @@ stdenv.mkDerivation rec {
./ignore-etc-os-release.patch
./dont-yarn-install.patch
./dont-assume-pandoc-in-quarto.patch
./fix-darwin-cmake.patch

# needed for rebuilding for later electron versions
./update-nan-and-node-abi.patch
];

postPatch = ''
Expand All @@ -150,6 +171,21 @@ stdenv.mkDerivation rec {

dontYarnInstallDeps = true; # will call manually in preConfigure

npmRoot = "src/node/desktop";

# don't build native modules with node headers
npmFlags = [ "--ignore-scripts" ];

npmDeps = fetchNpmDeps {
name = "rstudio-${version}-npm-deps";
inherit src;
patches = [ ./update-nan-and-node-abi.patch ];
postPatch = "cd ${npmRoot}";
hash = "sha256-CtHCN4sWeHNDd59TV/TgTC4d6h7X1Cl4E/aJkAfRk7g=";
};

env.ELECTRON_SKIP_BINARY_DOWNLOAD = "1";

preConfigure = ''
# set up node_modules directory inside quarto so that panmirror can be built
mkdir src/gwt/lib/quarto
Expand Down Expand Up @@ -181,23 +217,75 @@ stdenv.mkDerivation rec {
# version in CMakeGlobals.txt (RSTUDIO_INSTALLED_NODE_VERSION)
mkdir -p dependencies/common/node
ln -s ${nodejs} dependencies/common/node/18.20.3
pushd $npmRoot
substituteInPlace package.json \
--replace-fail "npm ci && " ""
### use our electron's headers as our nodedir so that electron-rebuild can rebuild for our electron's ABI
# note: because we're not specifying the `--version` flag to pass to electron-rebuild in forge.config.js,
# it thinks it's compiling for the ABI version chosen by upstream, but this doesn't seem to cause any issues
# note: currently we need to use electron-bin's headers, because electron-source's headers are wrong
mkdir electron-headers
tar xf ${electron-bin.headers} -C electron-headers --strip-components=1
export npm_config_nodedir="$(pwd)/electron-headers"
### override the detected electron version
substituteInPlace node_modules/@electron-forge/core-utils/dist/electron-version.js \
--replace-fail "return version" "return '${electron.version}'"
### create the electron archive to be used by electron-packager
cp -r ${electron.dist} electron-dist
chmod -R u+w electron-dist
pushd electron-dist
zip -0Xqr ../electron.zip .
popd
rm -r electron-dist
# force @electron/packager to use our electron instead of downloading it
substituteInPlace node_modules/@electron/packager/src/index.js \
--replace-fail "await this.getElectronZipPath(downloadOpts)" "'$(pwd)/electron.zip'"
# now that we patched everything, we still have to run the scripts we ignored with --ignore-scripts
npm rebuild
popd
'';

postInstall = ''
mkdir -p $out/bin
${lib.optionalString server ''
${lib.optionalString (server && stdenv.hostPlatform.isLinux) ''
ln -s $out/lib/rstudio/bin/{crash-handler-proxy,postback,r-ldpath,rpostback,rserver,rserver-pam,rsession,rstudio-server} $out/bin
''}
${lib.optionalString (!server) ''
ln -s $out/lib/rstudio/bin/{diagnostics,rpostback,rstudio} $out/bin
${lib.optionalString (!server && stdenv.hostPlatform.isLinux) ''
# remove unneeded electron files, since we'll wrap the app with out own electron
shopt -s extglob
rm -r $out/lib/rstudio/!(locales|resources|resources.pak)
makeWrapper ${lib.getExe electron} "$out/bin/rstudio" \
--add-flags "$out/lib/rstudio/resources/app/" \
--set-default ELECTRON_FORCE_IS_PACKAGED 1 \
--suffix PATH : ${lib.makeBinPath [ gnumake ]}
ln -s $out/lib/rstudio/resources/app/bin/{diagnostics,rpostback} $out/bin
''}
${lib.optionalString (server && stdenv.hostPlatform.isDarwin) ''
ln -s $out/Applications/RStudio.app/Contents/MacOS/{crash-handler-proxy,postback,r-ldpath,rpostback,rserver,rserver-pam,rsession,rstudio-server} $out/bin
''}
'';
qtWrapperArgs = lib.optionals (!server) [
"--suffix PATH : ${lib.makeBinPath [ gnumake ]}"
];
${lib.optionalString (!server && stdenv.hostPlatform.isDarwin) ''
# maybe this should use makeWrapper? I remember something about makeWrapper being needed for darwin
ln -s $out/Applications/RStudio.app/Contents/MacOS/RStudio $out/bin/rstudio
ln -s $out/Applications/RStudio.app/Contents/Resources/app/bin/{diagnostics,rpostback} $out/bin
''}
'';

passthru = {
inherit server;
Expand All @@ -216,6 +304,6 @@ stdenv.mkDerivation rec {
cfhammill
];
mainProgram = "rstudio" + lib.optionalString server "-server";
platforms = lib.platforms.linux;
platforms = lib.platforms.linux ++ lib.platforms.darwin;
};
}
72 changes: 72 additions & 0 deletions pkgs/applications/editors/rstudio/fix-darwin-cmake.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
diff --git a/CMakeGlobals.txt b/CMakeGlobals.txt
index 8868c4426e..19b2fa0892 100644
--- a/CMakeGlobals.txt
+++ b/CMakeGlobals.txt
@@ -345,6 +345,7 @@ if (APPLE)
set(RSTUDIO_INSTALL_SUPPORTING RStudio.app/Contents/Resources/app)
# handles Quarto share when not stored alongside bin
set(RSTUDIO_INSTALL_RESOURCES RStudio.app/Contents/Resources)
+ set(RSTUDIO_INSTALL_ELECTRON .)
else()
set(RSTUDIO_INSTALL_BIN RStudio.app/Contents/MacOS)
set(RSTUDIO_INSTALL_SUPPORTING RStudio.app/Contents/Resources)
diff --git a/src/cpp/CMakeLists.txt b/src/cpp/CMakeLists.txt
index b47f04f84d..928165d612 100644
--- a/src/cpp/CMakeLists.txt
+++ b/src/cpp/CMakeLists.txt
@@ -240,7 +240,7 @@ endif()
# determine whether we should statically link boost. we always do this
# unless we are building a non-packaged build on linux (in which case
# boost dynamic libraries are presumed to be installed on the system ldpath)
-if(APPLE OR WIN32 OR RSTUDIO_PACKAGE_BUILD)
+if(WIN32 OR RSTUDIO_PACKAGE_BUILD)
set(Boost_USE_STATIC_LIBS ON)
endif()

@@ -475,7 +475,7 @@ endif()

# find SOCI libraries
if(UNIX)
- if(NOT APPLE AND RSTUDIO_USE_SYSTEM_SOCI)
+ if(RSTUDIO_USE_SYSTEM_SOCI)
find_library(SOCI_CORE_LIB NAMES "libsoci_core.a" "soci_core" REQUIRED)
find_library(SOCI_SQLITE_LIB NAMES "libsoci_sqlite3.a" "soci_sqlite3" REQUIRED)
if(RSTUDIO_PRO_BUILD)
diff --git a/src/node/desktop/CMakeLists.txt b/src/node/desktop/CMakeLists.txt
index 2f4d9290e6..7ef0c0ea07 100644
--- a/src/node/desktop/CMakeLists.txt
+++ b/src/node/desktop/CMakeLists.txt
@@ -119,7 +119,9 @@ if (APPLE)
# configure Info.plist
configure_file (${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
+endif()

+if (false)
# copy sources to build directory. note that the build directory cannot
# be the "true" CMake directory as some files are resolved relative to
# the desktop project's relative path in the application structure
@@ -230,16 +232,21 @@ if(WIN32)
PROGRAMS ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}
DESTINATION "${RSTUDIO_INSTALL_BIN}")

-elseif(LINUX)
+elseif(LINUX OR APPLE)

if(UNAME_M STREQUAL aarch64)
set(ELECTRON_ARCH arm64)
else()
set(ELECTRON_ARCH x64)
endif()
+ if(APPLE)
+ set(ELECTRON_PLATFORM darwin)
+ else()
+ set(ELECTRON_PLATFORM linux)
+ endif()

install(
- DIRECTORY "${ELECTRON_BUILD_DIR}/out/RStudio-linux-${ELECTRON_ARCH}/"
+ DIRECTORY "${ELECTRON_BUILD_DIR}/out/RStudio-${ELECTRON_PLATFORM}-${ELECTRON_ARCH}/"
DIRECTORY_PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
27 changes: 19 additions & 8 deletions pkgs/applications/editors/rstudio/r-location.patch
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
diff --git a/src/cpp/core/r_util/REnvironmentPosix.cpp b/src/cpp/core/r_util/REnvironmentPosix.cpp
index dbc9a9a1..9a526a86 100644
index a4e964d49d..512707801b 100644
--- a/src/cpp/core/r_util/REnvironmentPosix.cpp
+++ b/src/cpp/core/r_util/REnvironmentPosix.cpp
@@ -107,12 +107,9 @@ FilePath systemDefaultRScript(std::string* pErrMsg)
@@ -108,13 +108,7 @@ FilePath systemDefaultRScript(std::string* pErrMsg)
{
// check fallback paths
std::vector<std::string> rScriptPaths = {
- "/usr/bin/R",
- "/usr/local/bin/R",
- "/opt/local/bin/R",
+ "@R@/bin/R"
#ifdef __APPLE__
- #ifdef __APPLE__
- "/opt/homebrew/bin/R",
- "/Library/Frameworks/R.framework/Resources/bin/R",
+ "@R@/bin/R",
#endif
- #endif
+ "@R@/bin/R"
};

@@ -225,8 +222,7 @@ FilePath systemDefaultRScript(std::string* pErrMsg)
return scanForRScript(rScriptPaths, pErrMsg);
@@ -226,8 +220,7 @@ FilePath systemDefaultRScript(std::string* pErrMsg)
// scan in standard locations as a fallback
std::string scanErrMsg;
std::vector<std::string> rScriptPaths;
Expand All @@ -27,4 +27,15 @@ index dbc9a9a1..9a526a86 100644
FilePath scriptPath = scanForRScript(rScriptPaths, &scanErrMsg);
if (scriptPath.isEmpty())
{

diff --git a/src/node/desktop/src/main/detect-r.ts b/src/node/desktop/src/main/detect-r.ts
index 5b768b7cbf..c0efeac0fe 100644
--- a/src/node/desktop/src/main/detect-r.ts
+++ b/src/node/desktop/src/main/detect-r.ts
@@ -305,6 +305,7 @@ writeLines(sep = "\x1F", c(
}

export function scanForR(): Expected<string> {
+ return ok('@R@/bin/R');
// if the RSTUDIO_WHICH_R environment variable is set, use that
// note that this does not pick up variables set in a user's bash profile, for example
const rstudioWhichR = getenv('RSTUDIO_WHICH_R');
Loading

0 comments on commit 90fc1f7

Please sign in to comment.