3535import java .nio .file .Paths ;
3636import java .util .LinkedHashMap ;
3737import java .util .Map ;
38+ import java .util .StringJoiner ;
3839
3940import org .scijava .app .AppService ;
4041import org .scijava .command .CommandService ;
4849import org .scijava .ui .DialogPrompt ;
4950import org .scijava .ui .UIService ;
5051import 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 ("\n channels:\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