Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Release Build

on:
push:
tags:
- 'v*'

jobs:
build-release:
strategy:
fail-fast: false
matrix:
java: [11, 21]
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Extract version from tag
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT

- name: Set up GraalVM (Java 21)
if: matrix.java == 21
uses: graalvm/setup-graalvm@v1
with:
java-version: ${{ matrix.java }}
distribution: 'graalvm'
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up JDK (Java 11)
if: matrix.java == 11
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
distribution: 'temurin'

- name: Set Java type variable
id: java_type
run: |
if [ ${{ matrix.java }} -eq 21 ]; then
echo "type=graalvm" >> $GITHUB_OUTPUT
else
echo "type=jdk" >> $GITHUB_OUTPUT
fi

- name: Build regular JAR with Maven
run: mvn -B package -DskipTests=true --file pom.xml

- name: Rename regular JAR with version and Java type
run: |
cp target/rsession.jar target/Rsession-${{ steps.get_version.outputs.VERSION }}-${{ steps.java_type.outputs.type }}.jar

- name: Build lite JAR (without Rserve binaries)
run: |
# Create lite version by excluding Rserve binaries (tgz, zip, tar.gz)
mkdir -p target/lite-classes
cd target/classes

# Copy all classes and resources EXCEPT Rserve binaries and test classes
find . -type f \( -name "*.class" -o -name "*.js" -o -name "*.form" \) \
! -name "*Test.class" \
! -name "*Test$*.class" \
-exec cp --parents {} ../lite-classes/ \;

cd ../lite-classes
jar cf ../Rsession-lite-${{ steps.get_version.outputs.VERSION }}-${{ steps.java_type.outputs.type }}.jar .
cd ../..

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: rsession-jars-java${{ matrix.java }}
path: |
target/Rsession-*.jar

- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: |
target/Rsession-*.jar
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Binary file removed dist/rsession-lite.jar
Binary file not shown.
Binary file removed dist/rsession.jar
Binary file not shown.
35 changes: 33 additions & 2 deletions src/main/java/org/math/R/RserveSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,16 @@ public RserveSession(final RLog console, Properties properties, RserverConf serv
return;
}

silentlyVoidEval("if (!any(file.access(.libPaths(),2)>=0)) .libPaths(new=tempdir())"); // ensure a writable directory for libPath
// Ensure a writable directory for libPath, especially for remote Rserve
// For remote connections, prioritize tempdir() to avoid client username path issues
if (RserveConf != null && !RserveConf.isLocal()) {
// Remote Rserve: always put tempdir first to avoid /home/<client_user> issues
silentlyVoidEval(".libPaths(c(tempdir(), .libPaths()[file.access(.libPaths(), 2) >= 0]))");
log("Remote Rserve: prioritized tempdir() for package installation", Level.INFO);
} else {
// Local Rserve: only add tempdir if no writable path exists
silentlyVoidEval("if (!any(file.access(.libPaths(),2)>=0)) .libPaths(new=tempdir())");
}

setenv(properties);
}
Expand Down Expand Up @@ -218,7 +227,7 @@ void startup() throws Exception {
status = STATUS_NOT_CONNECTED;

if (RserveConf == null) {// no RserveConf given, so create one, and need to be started
RserveConf = new RserverConf(RserverConf.DEFAULT_RSERVE_HOST, -1, null, null, RserverConf.DEFAULT_RSERVE_WORKDIR);
RserveConf = new RserverConf(RserverConf.DEFAULT_RSERVE_HOST, -1, null, null, System.getProperty("user.home") + File.separator + RserverConf.DEFAULT_RSERVE_WORKDIR);
log("No Rserve conf given. Trying to use " + RserveConf.toString(), Level.INFO);

try {
Expand Down Expand Up @@ -253,6 +262,20 @@ void startup() throws Exception {
status = STATUS_ERROR;
throw new RException("Rserve " + RserveConf + " version is too old.");
} else {
// Ensure workdir exists on remote Rserve
if (RserveConf.workdir != null && !RserveConf.workdir.isEmpty()) {
try {
String checkCmd = "dir.exists('" + RserveConf.workdir.replace("\\", "/") + "')";
REXP dirExists = R.eval(checkCmd);
if (dirExists != null && dirExists.asInteger() == 0) {
log("Creating workdir on remote Rserve: " + RserveConf.workdir, Level.INFO);
String createCmd = "dir.create('" + RserveConf.workdir.replace("\\", "/") + "', recursive=TRUE, showWarnings=FALSE)";
R.eval(createCmd);
}
} catch (Exception ex) {
log(HEAD_ERROR + "Failed to check/create workdir on remote Rserve: " + ex.getMessage(), Level.WARNING);
}
}
status = STATUS_READY;
}
}
Expand Down Expand Up @@ -1252,6 +1275,14 @@ public void getFile(File localfile) {
getFile(localfile, localfile.getName());
}

@Override
public boolean isLocal() {
if (RserveConf == null) {
return true;
} else {
return RserveConf.isLocal();
}
}
/**
* Get file from R environment to user filesystem
*
Expand Down
41 changes: 25 additions & 16 deletions src/main/java/org/math/R/RserverConf.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

public class RserverConf {

public static String DEFAULT_RSERVE_HOST = "localhost"; // InetAddress.getLocalHost().getHostName(); should not be used, as it seems an incoming connection, not authorized
public static String DEFAULT_RSERVE_HOST = "localhost"; // InetAddress.getLocalHost().getHostName(); should not be
// used, as it seems an incoming connection, not authorized
public static int DEFAULT_RSERVE_PORT = 6311;
public static String DEFAULT_RSERVE_WORKDIR = System.getProperty("user.home") + "/" + ".Rserve";
public static String DEFAULT_RSERVE_WORKDIR = "tmp" + "/" + ".Rserve";

RConnection connection;
public String host;
Expand All @@ -24,6 +25,7 @@ public RserverConf(String RserverHostName, int RserverPort, String login, String
this.password = password;
this.workdir = workdir != null ? workdir : DEFAULT_RSERVE_WORKDIR;
}

public static long CONNECT_TIMEOUT = 5000;

public static abstract class TimeOut {
Expand Down Expand Up @@ -55,6 +57,7 @@ public void run() {
}
}
}

private boolean timedOut = false;
private Object result = null;

Expand Down Expand Up @@ -91,7 +94,7 @@ public synchronized void execute(long timeout) throws TimeOutException {
}

public synchronized RConnection connect() {
//Logger.err.print("Connecting " + toString()+" ... ");
// Logger.err.print("Connecting " + toString()+" ... ");

TimeOut t = new TimeOut() {

Expand Down Expand Up @@ -124,7 +127,8 @@ protected Object command() {
}
return 0;
} catch (RserveException ex) {
Log.Err.println("Failed to connect on host:" + host + " port:" + port + " login:" + login + "\n " + ex.getMessage());
Log.Err.println("Failed to connect on host:" + host + " port:" + port + " login:" + login
+ "\n " + ex.getMessage());
}
}
return -1;
Expand All @@ -151,7 +155,9 @@ public boolean isLocal() {

@Override
public String toString() {
return RURL_START + (login != null ? (login + ":" + password + "@") : "") + (host == null ? DEFAULT_RSERVE_HOST : host) + (port > 0 ? ":" + port : "") + (workdir != null ? "/" + workdir : DEFAULT_RSERVE_WORKDIR);
return RURL_START + (login != null ? (login + ":" + password + "@") : "")
+ (host == null ? DEFAULT_RSERVE_HOST : host) + (port > 0 ? ":" + port : "")
+ (workdir != null ? "/" + workdir : DEFAULT_RSERVE_WORKDIR);
}

public final static String RURL_START = "R://";
Expand All @@ -162,18 +168,18 @@ public static RserverConf parse(String RURL) {
String host = null;
int port = -1;
String workdir = null;
//Properties props = null;
// Properties props = null;
try {
String loginhostportpath = null;
if (RURL.contains("?")) {
loginhostportpath = beforeFirst(RURL, "?").substring((RURL_START).length());
// String[] allprops = afterFirst(RURL, "?").split("&");
// props = new Properties();
// for (String prop : allprops) {
// if (prop.contains("=")) {
// props.put(beforeFirst(prop, "="), afterFirst(prop, "="));
// } // else ignore
// }
// String[] allprops = afterFirst(RURL, "?").split("&");
// props = new Properties();
// for (String prop : allprops) {
// if (prop.contains("=")) {
// props.put(beforeFirst(prop, "="), afterFirst(prop, "="));
// } // else ignore
// }
} else {
loginhostportpath = RURL.substring((RURL_START).length());
}
Expand Down Expand Up @@ -203,9 +209,12 @@ public static RserverConf parse(String RURL) {
hostportpath = beforeFirst(hostportpath, "/");
}

return new RserverConf(host, port, login, passwd, workdir != null ? workdir : DEFAULT_RSERVE_WORKDIR);
RserverConf rc = new RserverConf(host, port, login, passwd,
workdir != null ? workdir : DEFAULT_RSERVE_WORKDIR);
return rc;
} catch (Exception e) {
throw new IllegalArgumentException("Impossible to parse " + RURL + ":\n host=" + host + "\n port=" + port + "\n login=" + login + "\n password=" + passwd + "\n workdir=" + workdir);
throw new IllegalArgumentException("Impossible to parse " + RURL + ":\n host=" + host + "\n port=" + port
+ "\n login=" + login + "\n password=" + passwd + "\n workdir=" + workdir);
}
}

Expand Down Expand Up @@ -235,5 +244,5 @@ static String afterFirst(String txt, String sep) {
} else {
return "";
}
}
}
}
10 changes: 8 additions & 2 deletions src/main/java/org/math/R/Rsession.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ public RException(String cause, Rsession r, boolean details) {
List<RLog> loggers;
public boolean debug;

public boolean isLocal() {
return true;
}

//** GLG HACK: Logging fix **//
// No sink file (Passed to false) a lot faster not to sink the output
boolean SINK_OUTPUT = true, SINK_MESSAGE = true;
Expand Down Expand Up @@ -707,7 +711,8 @@ public String installPackages(String[] pack, boolean load) {
public String installPackage(File pack, boolean load) {
pack = putFileInWorkspace(pack);
try {
rawEval("install.packages('" + pack.getPath().replace("\\", "/") + "',repos=NULL,quiet=T" + install_packages_moreargs + ")");
// Explicitly specify lib to use first writable path (avoids client username path issues)
rawEval("install.packages('" + pack.getPath().replace("\\", "/") + "',repos=NULL,lib=.libPaths()[1],quiet=T" + install_packages_moreargs + ")");
} catch (Exception ex) {
log(ex.getMessage(), Level.ERROR);
}
Expand Down Expand Up @@ -819,7 +824,8 @@ public String installPackage(String pack, boolean load) {
log(" package " + pack + " not accessible on " + repos + ": CRAN unreachable.");
return "Impossible to get package " + pack + " from " + repos;
}*/
rawEval("install.packages('" + pack + "',repos='" + repos + "',quiet=T" + install_packages_moreargs + ")", TRY_MODE);
// Explicitly specify lib to use first writable path (avoids client username path issues)
rawEval("install.packages('" + pack + "',repos='" + repos + "',lib=.libPaths()[1],quiet=T" + install_packages_moreargs + ")", TRY_MODE);
log(" request if package " + pack + " is installed...", Level.INFO);

if (isPackageInstalled(pack, null)) {
Expand Down
Loading