Skip to content

Commit 4f2d908

Browse files
committed
Allow environment editing in python options
Closes #9
1 parent efdb4bd commit 4f2d908

File tree

1 file changed

+120
-2
lines changed

1 file changed

+120
-2
lines changed

src/main/java/org/scijava/plugins/scripting/python/OptionsPython.java

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.nio.file.Paths;
3636
import java.util.LinkedHashMap;
3737
import java.util.Map;
38+
import java.util.StringJoiner;
3839

3940
import org.scijava.app.AppService;
4041
import org.scijava.command.CommandService;
@@ -48,6 +49,7 @@
4849
import org.scijava.ui.DialogPrompt;
4950
import org.scijava.ui.UIService;
5051
import org.scijava.widget.Button;
52+
import org.scijava.widget.TextWidget;
5153

5254
/**
5355
* Options for configuring the Python environment.
@@ -72,6 +74,14 @@ public class OptionsPython extends OptionsPlugin {
7274
@Parameter(label = "Python environment directory", persist = false)
7375
private File pythonDir;
7476

77+
@Parameter(label = "Conda dependencies", style = TextWidget.AREA_STYLE,
78+
persist = false)
79+
private String condaDependencies;
80+
81+
@Parameter(label = "Pip dependencies", style = TextWidget.AREA_STYLE,
82+
persist = false)
83+
private String pipDependencies;
84+
7585
@Parameter(label = "Build Python environment", callback = "rebuildEnv")
7686
private Button rebuildEnvironment;
7787

@@ -83,6 +93,8 @@ public class OptionsPython extends OptionsPlugin {
8393
private UIService uiService;
8494

8595
private boolean initialPythonMode = false;
96+
private String initialCondaDependencies;
97+
private String initialPipDependencies;
8698

8799
// -- OptionsPython methods --
88100

@@ -142,10 +154,70 @@ public void load() {
142154

143155
// Store the initial value of pythonMode for later comparison
144156
initialPythonMode = pythonMode;
157+
158+
// Populate condaDependencies and pipDependencies from environment.yml
159+
condaDependencies = "";
160+
pipDependencies = "";
161+
java.util.Set<String> pipBlacklist = new java.util.HashSet<>();
162+
pipBlacklist.add("appose-python");
163+
pipBlacklist.add("pyimagej");
164+
File envFile = getEnvironmentYamlFile();
165+
if (envFile.exists()) {
166+
try {
167+
java.util.List<String> lines = java.nio.file.Files.readAllLines(envFile
168+
.toPath());
169+
boolean inDeps = false, inPip = false;
170+
StringJoiner condaDeps = new StringJoiner("\n");
171+
StringJoiner pipDeps = new StringJoiner("\n");
172+
for (String line : lines) {
173+
String trimmed = line.trim();
174+
if (trimmed.startsWith("#") || trimmed.isEmpty()) {
175+
// Ignore empty and comment lines
176+
continue;
177+
}
178+
if (trimmed.startsWith("dependencies:")) {
179+
inDeps = true;
180+
continue;
181+
}
182+
if (inDeps && trimmed.startsWith("- pip")) {
183+
inPip = true;
184+
continue;
185+
}
186+
if (inDeps && trimmed.startsWith("- ") && !inPip) {
187+
String dep = trimmed.substring(2).trim();
188+
if (!dep.equals("pip")) condaDeps.add(dep);
189+
continue;
190+
}
191+
if (inPip && trimmed.startsWith("- ")) {
192+
String pipDep = trimmed.substring(2).trim();
193+
boolean blacklisted = false;
194+
for (String bad : pipBlacklist) {
195+
if (pipDep.contains(bad)) {
196+
blacklisted = true;
197+
break;
198+
}
199+
}
200+
if (!blacklisted) pipDeps.add(pipDep);
201+
continue;
202+
}
203+
if (inDeps && !trimmed.startsWith("- ") && !trimmed.isEmpty())
204+
inDeps = false;
205+
if (inPip && (!trimmed.startsWith("- ") || trimmed.isEmpty())) inPip =
206+
false;
207+
}
208+
condaDependencies = condaDeps.toString().trim();
209+
pipDependencies = pipDeps.toString().trim();
210+
initialCondaDependencies = condaDependencies;
211+
initialPipDependencies = pipDependencies;
212+
}
213+
catch (Exception e) {
214+
log.debug("Could not read environment.yml: " + e.getMessage());
215+
}
216+
}
145217
}
146218

147219
public void rebuildEnv() {
148-
File environmentYaml = getEnvironmentYamlFile();
220+
File environmentYaml = writeEnvironmentYaml();
149221
commandService.run(RebuildEnvironment.class, true, "environmentYaml",
150222
environmentYaml, "targetDir", pythonDir);
151223
}
@@ -163,7 +235,6 @@ private File getEnvironmentYamlFile() {
163235
environmentYaml = stringToFile(appPath, pythonEnvFileProp);
164236
}
165237
return environmentYaml;
166-
167238
}
168239

169240
@Override
@@ -197,6 +268,9 @@ public void save() {
197268
if (pythonMode && (pythonDir == null || !pythonDir.exists())) {
198269
rebuildEnv();
199270
}
271+
else {
272+
writeEnvironmentYaml();
273+
}
200274
// Warn the user if pythonMode was just enabled and wasn't before
201275
if (!initialPythonMode && pythonMode && uiService != null) {
202276
String msg =
@@ -208,6 +282,50 @@ public void save() {
208282
}
209283
}
210284

285+
private File writeEnvironmentYaml() {
286+
File envFile = getEnvironmentYamlFile();
287+
288+
// skip writing if nothing has changed
289+
if (initialCondaDependencies.equals(condaDependencies) &&
290+
initialPipDependencies.equals(pipDependencies)) return envFile;
291+
292+
// Update initial dependencies to detect future changes
293+
initialCondaDependencies = condaDependencies;
294+
initialPipDependencies = pipDependencies;
295+
296+
// Write environment.yml from condaDependencies and pipDependencies
297+
try {
298+
String name = "fiji";
299+
String[] channels = { "conda-forge" };
300+
String pyimagej = "pyimagej>=1.7.0";
301+
String apposePython =
302+
"git+https://github.com/apposed/appose-python.git@efe6dadb2242ca45820fcbb7aeea2096f99f9cb2";
303+
StringBuilder yml = new StringBuilder();
304+
yml.append("name: ").append(name).append("\nchannels:\n");
305+
for (String ch : channels)
306+
yml.append(" - ").append(ch).append("\n");
307+
yml.append("dependencies:\n");
308+
for (String dep : condaDependencies.split("\n")) {
309+
String trimmed = dep.trim();
310+
if (!trimmed.isEmpty()) yml.append(" - ").append(trimmed).append("\n");
311+
}
312+
yml.append(" - pip\n");
313+
yml.append(" - pip:\n");
314+
for (String dep : pipDependencies.split("\n")) {
315+
String trimmed = dep.trim();
316+
if (!trimmed.isEmpty()) yml.append(" - ").append(trimmed).append(
317+
"\n");
318+
}
319+
yml.append(" - ").append(pyimagej).append("\n");
320+
yml.append(" - ").append(apposePython).append("\n");
321+
java.nio.file.Files.write(envFile.toPath(), yml.toString().getBytes());
322+
}
323+
catch (Exception e) {
324+
log.debug("Could not write environment.yml: " + e.getMessage());
325+
}
326+
return envFile;
327+
}
328+
211329
// -- Utility methods --
212330

213331
/**

0 commit comments

Comments
 (0)