Skip to content

Commit 0567f82

Browse files
committed
Merge pull request #454 from ThomasJClark/java-args
Add JVM args and more options to the deploy dialog
2 parents 34fd6be + 9d8d79f commit 0567f82

6 files changed

Lines changed: 174 additions & 75 deletions

File tree

core/src/main/java/edu/wpi/grip/core/settings/ProjectSettings.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public class ProjectSettings implements Cloneable {
3131
@Setting(label = "Deploy Java home", description = "Where Java is installed on the robot.")
3232
private String deployJavaHome = "/usr/local/frc/JRE/";
3333

34+
@Setting(label = "Deploy JVM options", description = "Command line options passed to the roboRIO JVM")
35+
private String deployJvmOptions = "-Xmx50m -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError";
36+
3437
/**
3538
* Set the FRC team number. If the deploy address and NetworkTables server address haven't been manually
3639
* overridden, this also changes them to the mDNS hostname of the team's roboRIO.
@@ -98,6 +101,14 @@ public void setDeployJavaHome(String deployJavaHome) {
98101
if (deployJavaHome != null) this.deployJavaHome = deployJavaHome;
99102
}
100103

104+
public String getDeployJvmOptions() {
105+
return deployJvmOptions;
106+
}
107+
108+
public void setDeployJvmOptions(String deployJvmOptions) {
109+
if (deployJvmOptions != null) this.deployJvmOptions = deployJvmOptions;
110+
}
111+
101112
private String computeFRCAddress(int teamNumber) {
102113
return "roborio-" + teamNumber + "-frc.local";
103114
}
@@ -109,6 +120,7 @@ public String toString() {
109120
.add("deployDir", deployDir)
110121
.add("deployUser", deployUser)
111122
.add("deployJavaHome", deployJavaHome)
123+
.add("deployJvmOptions", deployJvmOptions)
112124
.add("publishAddress", publishAddress)
113125
.add("teamNumber", teamNumber)
114126
.toString();

ui/src/main/java/edu/wpi/grip/ui/DeployController.java

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,24 @@
22

33
import com.google.common.eventbus.EventBus;
44
import com.google.common.eventbus.Subscribe;
5+
import com.google.common.hash.Hashing;
56
import com.google.common.io.LineReader;
7+
import com.google.common.io.Resources;
68
import edu.wpi.grip.core.Pipeline;
79
import edu.wpi.grip.core.events.ProjectSettingsChangedEvent;
810
import edu.wpi.grip.core.events.StopPipelineEvent;
911
import edu.wpi.grip.core.serialization.Project;
1012
import edu.wpi.grip.core.settings.ProjectSettings;
1113
import edu.wpi.grip.ui.util.StringInMemoryFile;
1214
import javafx.application.Platform;
15+
import javafx.beans.binding.Bindings;
1316
import javafx.beans.property.BooleanProperty;
14-
import javafx.beans.property.SimpleBooleanProperty;
17+
import javafx.beans.property.StringProperty;
1518
import javafx.fxml.FXML;
16-
import javafx.scene.control.*;
19+
import javafx.scene.control.Label;
20+
import javafx.scene.control.ProgressIndicator;
21+
import javafx.scene.control.TextArea;
22+
import javafx.scene.control.TextField;
1723
import net.schmizz.sshj.SSHClient;
1824
import net.schmizz.sshj.common.StreamCopier;
1925
import net.schmizz.sshj.connection.channel.direct.Session;
@@ -23,10 +29,8 @@
2329
import net.schmizz.sshj.xfer.scp.SCPFileTransfer;
2430

2531
import javax.inject.Inject;
26-
import java.io.IOException;
27-
import java.io.InputStreamReader;
28-
import java.io.InterruptedIOException;
29-
import java.io.StringWriter;
32+
import java.io.*;
33+
import java.net.URL;
3034
import java.net.URLDecoder;
3135
import java.net.UnknownHostException;
3236
import java.util.Optional;
@@ -42,35 +46,37 @@
4246
*/
4347
public class DeployController {
4448
private final static String GRIP_JAR = "grip.jar";
45-
private final static String PROJECT_FILE = "project.grip";
46-
private final static String LOCAL_GRIP_JAR = URLDecoder.decode(
47-
Project.class.getProtectionDomain().getCodeSource().getLocation().getPath());
49+
private final static String GRIP_WRAPPER = "grip";
50+
private final static URL LOCAL_GRIP_URL = Project.class.getProtectionDomain().getCodeSource().getLocation();
51+
private final static String LOCAL_GRIP_PATH = URLDecoder.decode(LOCAL_GRIP_URL.getPath());
4852

4953
@FXML private TextField address;
5054
@FXML private TextField user;
5155
@FXML private TextField password;
5256
@FXML private TextField javaHome;
57+
@FXML private TextField jvmArgs;
5358
@FXML private TextField deployDir;
59+
@FXML private TextField projectFile;
5460
@FXML private ProgressIndicator progress;
55-
@FXML private Button deployButton;
5661
@FXML private Label status;
5762
@FXML private TextArea console;
63+
@FXML private BooleanProperty deploying;
64+
@FXML private StringProperty command;
5865

5966
@Inject private EventBus eventBus;
6067
@Inject private Project project;
6168
@Inject private Pipeline pipeline;
6269
@Inject private Logger logger;
6370

64-
private final BooleanProperty deploying = new SimpleBooleanProperty(this, "deploying", false);
6571
private Optional<Thread> deployThread = Optional.empty();
6672

6773
@FXML
6874
public void initialize() {
69-
loadSettings(pipeline.getProjectSettings());
70-
71-
deployButton.disableProperty().bind(deploying);
72-
progress.disableProperty().bind(deploying.not());
7375
deploying.addListener((o, b, d) -> progress.setProgress(d ? ProgressIndicator.INDETERMINATE_PROGRESS : 0));
76+
command.bind(Bindings.concat(javaHome.textProperty(), "/bin/java ", jvmArgs.textProperty(), " -jar '",
77+
deployDir.textProperty(), "/", GRIP_JAR, "' '", deployDir.textProperty(), "/", projectFile.textProperty(), "'"));
78+
79+
loadSettings(pipeline.getProjectSettings());
7480
}
7581

7682
@Subscribe
@@ -85,6 +91,7 @@ private void loadSettings(ProjectSettings settings) {
8591
address.setText(settings.getDeployAddress());
8692
user.setText(settings.getDeployUser());
8793
javaHome.setText(settings.getDeployJavaHome());
94+
jvmArgs.setText(settings.getDeployJvmOptions());
8895
deployDir.setText(settings.getDeployDir());
8996
}
9097

@@ -95,6 +102,7 @@ private void saveSettings() {
95102
settings.setDeployAddress(address.getText());
96103
settings.setDeployUser(user.getText());
97104
settings.setDeployJavaHome(javaHome.getText());
105+
settings.setDeployJvmOptions(jvmArgs.getText());
98106
settings.setDeployDir(deployDir.getText());
99107
eventBus.post(new ProjectSettingsChangedEvent(settings));
100108
}
@@ -107,8 +115,7 @@ public void onDeploy() {
107115
console.clear();
108116

109117
// Start the deploy in a new thread, so the GUI doesn't freeze
110-
deployThread = Optional.of(new Thread(() ->
111-
deploy(address.getText(), user.getText(), password.getText(), javaHome.getText(), deployDir.getText())));
118+
deployThread = Optional.of(new Thread(this::deploy, "Deploy"));
112119
deployThread.get().setDaemon(true);
113120
deployThread.get().start();
114121
}
@@ -124,13 +131,13 @@ public void onStop() {
124131
* Upload and run the GRIP project using the current deploy settings. This is run in a separate thread, and it
125132
* periodically updates the GUI to inform the user of the current status of the deployment.
126133
*/
127-
private void deploy(String address, String user, String password, String javaHome, String deployDir) {
128-
setStatusAsync("Connecting to " + address, false);
134+
private void deploy() {
135+
setStatusAsync("Connecting to " + address.getText(), false);
129136

130137
try (SSHClient ssh = new SSHClient()) {
131138
ssh.addHostKeyVerifier((hostname, port, key) -> true);
132-
ssh.connect(address);
133-
ssh.authPassword(user, password);
139+
ssh.connect(address.getText());
140+
ssh.authPassword(user.getText(), password.getText());
134141

135142
// Update the progress bar and status text while uploading files
136143
SCPFileTransfer scp = ssh.newSCPFileTransfer();
@@ -150,20 +157,43 @@ public StreamCopier.Listener file(String name, long size) {
150157
StringWriter projectWriter = new StringWriter();
151158
project.save(projectWriter);
152159

153-
// Upload the GRIP core JAR and the serialized project to the robot
154-
scp.upload(new StringInMemoryFile(PROJECT_FILE, projectWriter.toString()), deployDir + "/");
155-
scp.upload(new FileSystemFile(LOCAL_GRIP_JAR), deployDir + "/" + GRIP_JAR);
160+
final String commandStr = command.get();
161+
final String pathStr = deployDir.getText() + "/";
162+
163+
164+
// Upload the core GRIP JAR only if there isn't already one with the same hash on the robot. This prevents
165+
// us from redundantly deploying the same JAR over and over again (so deploy times after the first are
166+
// much faster), while still ensuring that the JAR is deployed if it has to be.
167+
try (Session session = ssh.startSession()) {
168+
Session.Command md5Cmd = session.exec("md5sum " + pathStr + GRIP_JAR);
169+
String remoteMd5Sum = new DataInputStream(md5Cmd.getInputStream()).readLine();
170+
String localMd5Sum = Resources.asByteSource(LOCAL_GRIP_URL).hash(Hashing.md5()).toString();
171+
172+
// If the MD5 sum doesn't match up or there's any kind of error (there isn't a JAR there, etc...),
173+
// just upload the JAR.
174+
if (remoteMd5Sum == null || remoteMd5Sum.length() < 32 || !remoteMd5Sum.substring(0, 32).equals(localMd5Sum)) {
175+
scp.upload(new FileSystemFile(LOCAL_GRIP_PATH), pathStr + GRIP_JAR);
176+
} else {
177+
logger.info("md5sum " + GRIP_JAR + " matches. Skipping upload.");
178+
}
179+
}
180+
181+
// Upload the project file and a wrapper script with the JVM arguments. These are very small and change
182+
// often, so we might as well upload them every time.
183+
scp.upload(new StringInMemoryFile(GRIP_WRAPPER, "echo \"" + commandStr + "\"\n" + commandStr, 0755), pathStr);
184+
scp.upload(new StringInMemoryFile(projectFile.getText(), projectWriter.toString()), pathStr);
156185

157186
// Stop the pipeline before running it remotely, so the two instances of GRIP don't try to publish to the
158187
// same NetworkTables keys.
159188
eventBus.post(new StopPipelineEvent());
160189

161190
// Run the project!
162191
setStatusAsync("Running GRIP", false);
192+
logger.info("Running " + commandStr);
193+
163194
Session session = ssh.startSession();
164195
session.allocateDefaultPTY();
165-
Session.Command cmd = session.exec(javaHome + "/bin/java -jar " + deployDir + "/" + GRIP_JAR + "' '"
166-
+ deployDir + "/" + PROJECT_FILE + "'");
196+
Session.Command cmd = session.exec(String.format("'%s/%s'", pathStr, GRIP_WRAPPER));
167197

168198
LineReader inputReader = new LineReader(new InputStreamReader(cmd.getInputStream()));
169199
while (isNotCanceled()) {
@@ -175,7 +205,7 @@ public StreamCopier.Listener file(String name, long size) {
175205
Platform.runLater(() -> console.setText(console.getText() + line + "\n"));
176206
}
177207
} catch (UnknownHostException e) {
178-
setStatusAsync("Unknown host: " + address, true);
208+
setStatusAsync("Unknown host: " + address.getText(), true);
179209
} catch (UserAuthException e) {
180210
setStatusAsync("Invalid username or password (should be \"lvuser\" and blank for roboRIO)", true);
181211
} catch (InterruptedIOException e) {

ui/src/main/java/edu/wpi/grip/ui/MainWindowController.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ public void deploy() {
196196
graphic.setFitWidth(DPIUtility.SMALL_ICON_SIZE);
197197
graphic.setFitHeight(DPIUtility.SMALL_ICON_SIZE);
198198

199+
deployPane.requestFocus();
200+
199201
Dialog<ButtonType> dialog = new Dialog<>();
200202
dialog.setTitle("Deploy");
201203
dialog.setHeaderText("Deploy");

ui/src/main/java/edu/wpi/grip/ui/util/StringInMemoryFile.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,17 @@
1212
*/
1313
public class StringInMemoryFile extends InMemorySourceFile {
1414
private final String name, contents;
15+
private final int permissions;
1516

16-
public StringInMemoryFile(String name, String contents) {
17+
public StringInMemoryFile(String name, String contents, int permissions) {
1718
super();
1819
this.name = name;
1920
this.contents = contents;
21+
this.permissions = permissions;
22+
}
23+
24+
public StringInMemoryFile(String name, String contents) {
25+
this(name, contents, 0644);
2026
}
2127

2228
@Override
@@ -29,6 +35,11 @@ public long getLength() {
2935
return contents.getBytes().length;
3036
}
3137

38+
@Override
39+
public int getPermissions() {
40+
return permissions;
41+
}
42+
3243
@Override
3344
public InputStream getInputStream() throws IOException {
3445
return new StringBufferInputStream(contents);

0 commit comments

Comments
 (0)