From 194871c04b96c434077ab5afffb0f310dd6e4342 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
+ * Note that the detail message associated with {@code cause} is not
+ * automatically incorporated in this exception's detail message.
+ *
+ * @param message
+ * the detail message (which is saved for later retrieval by the
+ * {@link #getMessage()} method).
+ * @param cause
+ * the cause (which is saved for later retrieval by the
+ * {@link #getCause()} method). (A null value is permitted,
+ * and indicates that the cause is nonexistent or unknown.)
+ * @since 1.4
+ */
+ public EnvironmentExistsException( String message, Throwable cause )
+ {
+ super( message, cause );
+ }
+
+ /**
+ * Constructs a new exception with the specified cause and a detail message of
+ * (cause==null ? null : cause.toString()) (which typically contains
+ * the class and detail message of cause). This constructor is useful
+ * for exceptions that are little more than wrappers for other throwables (for
+ * example, {@link java.security.PrivilegedActionException}).
+ *
+ * @param cause
+ * the cause (which is saved for later retrieval by the
+ * {@link #getCause()} method). (A null value is permitted,
+ * and indicates that the cause is nonexistent or unknown.)
+ * @since 1.4
+ */
+ public EnvironmentExistsException( Throwable cause )
+ {
+ super( cause );
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message, cause,
+ * suppression enabled or disabled, and writable stack trace enabled or
+ * disabled.
+ *
+ * @param message
+ * the detail message.
+ * @param cause
+ * the cause. (A {@code null} value is permitted, and indicates that
+ * the cause is nonexistent or unknown.)
+ * @param enableSuppression
+ * whether or not suppression is enabled or disabled
+ * @param writableStackTrace
+ * whether or not the stack trace should be writable
+ * @since 1.7
+ */
+ protected EnvironmentExistsException( String message, Throwable cause,
+ boolean enableSuppression,
+ boolean writableStackTrace )
+ {
+ super( message, cause, enableSuppression, writableStackTrace );
+ }
+ }
+
+}
From aef15793a25ec48b568501dbc19ac41795c822a5 Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Thu, 2 Nov 2023 21:58:23 +0100
Subject: [PATCH 004/120] add utils to decompress mamba
---
.../java/org/apposed/appose/Bzip2Utils.java | 285 ++++++++++++++++++
1 file changed, 285 insertions(+)
create mode 100644 src/main/java/org/apposed/appose/Bzip2Utils.java
diff --git a/src/main/java/org/apposed/appose/Bzip2Utils.java b/src/main/java/org/apposed/appose/Bzip2Utils.java
new file mode 100644
index 0000000..6fbc29b
--- /dev/null
+++ b/src/main/java/org/apposed/appose/Bzip2Utils.java
@@ -0,0 +1,285 @@
+/*-
+ * #%L
+ * Appose: multi-language interprocess cooperation with shared memory.
+ * %%
+ * Copyright (C) 2023 Appose developers.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+package org.apposed.appose;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.compress.archivers.ArchiveStreamFactory;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
+import org.apache.commons.compress.utils.IOUtils;
+import org.itadaki.bzip2.BZip2InputStream;
+
+/**
+ * Utility methods unzip bzip2 files
+ */
+public final class Bzip2Utils {
+
+ final private static int BUFFER_SIZE = 1024 * 20;
+
+ private Bzip2Utils() {
+ // Prevent instantiation of utility class.
+ }
+
+ /**
+ * DEcompress a bzip2 file into a new file.
+ * The method is needed because Micromamba is distributed as a .tr.bz2 file and
+ * many distributions do not have tools readily available to extract the required files
+ * @param source
+ * .bzip2 file
+ * @param destination
+ * destination folder where the contents of the file are going to be decompressed
+ * @throws FileNotFoundException if the .bzip2 file is not found or does not exist
+ * @throws IOException if the source file already exists or there is any error with the decompression
+ */
+ public static void decompress(File source, File destination) throws FileNotFoundException, IOException {
+ try (
+ BZip2CompressorInputStream input = new BZip2CompressorInputStream(new BufferedInputStream(new FileInputStream(source)));
+ FileOutputStream output = new FileOutputStream(destination);
+ ) {
+ IOUtils.copy(input, output);
+ }
+ }
+
+ /** Untar an input file into an output file.
+
+ * The output file is created in the output folder, having the same name
+ * as the input file, minus the '.tar' extension.
+ *
+ * @param inputFile the input .tar file
+ * @param outputDir the output directory file.
+ * @throws IOException
+ * @throws FileNotFoundException
+ *
+ * @throws ArchiveException
+ */
+ private static void unTar(final File inputFile, final File outputDir) throws FileNotFoundException, IOException, ArchiveException {
+
+ final InputStream is = new FileInputStream(inputFile);
+ final TarArchiveInputStream debInputStream = (TarArchiveInputStream) new ArchiveStreamFactory().createArchiveInputStream("tar", is);
+ TarArchiveEntry entry = null;
+ while ((entry = (TarArchiveEntry)debInputStream.getNextEntry()) != null) {
+ final File outputFile = new File(outputDir, entry.getName());
+ if (entry.isDirectory()) {
+ if (!outputFile.exists()) {
+ if (!outputFile.mkdirs()) {
+ throw new IllegalStateException(String.format("Couldn't create directory %s.", outputFile.getAbsolutePath()));
+ }
+ }
+ } else {
+ final OutputStream outputFileStream = new FileOutputStream(outputFile);
+ IOUtils.copy(debInputStream, outputFileStream);
+ outputFileStream.close();
+ }
+ }
+ debInputStream.close();
+
+ }
+
+ public static void main(String[] args) throws FileNotFoundException, IOException, ArchiveException {
+ String tarPath = "C:\\Users\\angel\\OneDrive\\Documentos\\pasteur\\git\\micromamba-1.5.1-1.tar";
+ String mambaPath = "C:\\Users\\angel\\OneDrive\\Documentos\\pasteur\\git\\mamba";
+ decompress(new File("C:\\Users\\angel\\OneDrive\\Documentos\\pasteur\\git\\micromamba-1.5.1-1.tar.bz2"),
+ new File(tarPath));
+ unTar(new File(tarPath), new File(mambaPath));
+ }
+
+ // Size of the block in a standard tar file.
+ private static final int BLOCK_SIZE = 512;
+
+ public static void tarDecompress(String tarFilePath, String outputDirPath) {
+
+ File tarFile = new File(tarFilePath);
+ File outputDir = new File(outputDirPath);
+
+ // Make sure the output directory exists
+ if (!outputDir.isDirectory())
+ outputDir.mkdirs();
+
+ try (FileInputStream fis = new FileInputStream(tarFile);
+ BufferedInputStream bis = new BufferedInputStream(fis)) {
+
+ boolean endOfArchive = false;
+ byte[] block = new byte[BLOCK_SIZE];
+ while (!endOfArchive) {
+ // Read a block from the tar archive.
+ int bytesRead = bis.read(block);
+ if (bytesRead < BLOCK_SIZE) {
+ throw new IOException("Incomplete block read.");
+ }
+
+ // Check for the end of the archive. An empty block signals end.
+ endOfArchive = isEndOfArchive(block);
+ if (endOfArchive) {
+ break;
+ }
+
+ // Read the header from the block.
+ TarHeader header = new TarHeader(block);
+
+ // If the file size is nonzero, create an output file.
+ if (header.fileSize > 0) {
+ File outputFile = new File(outputDir, header.fileName);
+ if (header.fileType == TarHeader.FileType.DIRECTORY) {
+ outputFile.mkdirs();
+ } else {
+ try (FileOutputStream fos = new FileOutputStream(outputFile);
+ BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+ long fileSizeRemaining = header.fileSize;
+ while (fileSizeRemaining > 0) {
+ int toRead = (int) Math.min(fileSizeRemaining, BLOCK_SIZE);
+ bytesRead = bis.read(block, 0, toRead);
+ if (bytesRead != toRead) {
+ throw new IOException("Unexpected end of file");
+ }
+ bos.write(block, 0, bytesRead);
+ fileSizeRemaining -= bytesRead;
+ }
+ }
+ }
+ }
+
+ // Skip to the next file entry in the tar archive by advancing to the next block boundary.
+ long fileEntrySize = (header.fileSize + BLOCK_SIZE - 1) / BLOCK_SIZE * BLOCK_SIZE;
+ long bytesToSkip = fileEntrySize - header.fileSize;
+ long skipped = bis.skip(bytesToSkip);
+ if (skipped != bytesToSkip) {
+ throw new IOException("Failed to skip bytes for the next entry");
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static boolean isEndOfArchive(byte[] block) {
+ // An empty block signals the end of the archive in a tar file.
+ for (int i = 0; i < BLOCK_SIZE; i++) {
+ if (block[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static class TarHeader {
+ String fileName;
+ int fileMode;
+ int ownerId;
+ int groupId;
+ long fileSize;
+ long modificationTime;
+ int checksum;
+ FileType fileType;
+ String linkName;
+ String magic; // UStar indicator
+ String version;
+ String ownerUserName;
+ String ownerGroupName;
+ String devMajor;
+ String devMinor;
+ String prefix; // Used if the file name is longer than 100 characters
+
+ enum FileType {
+ FILE, DIRECTORY, SYMLINK, CHARACTER_DEVICE, BLOCK_DEVICE, FIFO, CONTIGUOUS_FILE, GLOBAL_EXTENDED_HEADER, EXTENDED_HEADER, OTHER
+ }
+
+ TarHeader(byte[] headerBlock) {
+ fileName = extractString(headerBlock, 0, 100);
+ fileMode = (int) extractOctal(headerBlock, 100, 8);
+ ownerId = (int) extractOctal(headerBlock, 108, 8);
+ groupId = (int) extractOctal(headerBlock, 116, 8);
+ fileSize = extractOctal(headerBlock, 124, 12);
+ modificationTime = extractOctal(headerBlock, 136, 12);
+ checksum = (int) extractOctal(headerBlock, 148, 8);
+ fileType = determineFileType(headerBlock[156]);
+ linkName = extractString(headerBlock, 157, 100);
+ magic = extractString(headerBlock, 257, 6);
+ version = extractString(headerBlock, 263, 2);
+ ownerUserName = extractString(headerBlock, 265, 32);
+ ownerGroupName = extractString(headerBlock, 297, 32);
+ devMajor = extractString(headerBlock, 329, 8);
+ devMinor = extractString(headerBlock, 337, 8);
+ prefix = extractString(headerBlock, 345, 155);
+ // Note: The prefix is used in conjunction with the filename to allow for longer file names.
+ }
+
+ private long extractOctal(byte[] buffer, int offset, int length) {
+ String octalString = extractString(buffer, offset, length);
+ return Long.parseLong(octalString, 8);
+ }
+
+ private String extractString(byte[] buffer, int offset, int length) {
+ StringBuilder stringBuilder = new StringBuilder(length);
+ for (int i = offset; i < offset + length; i++) {
+ if (buffer[i] == 0) break; // Stop at the first null character.
+ stringBuilder.append((char) buffer[i]);
+ }
+ return stringBuilder.toString();
+ }
+
+ private FileType determineFileType(byte typeFlag) {
+ switch (typeFlag) {
+ case '0':
+ case '\0':
+ return FileType.FILE;
+ case '2':
+ return FileType.SYMLINK;
+ case '3':
+ return FileType.CHARACTER_DEVICE;
+ case '4':
+ return FileType.BLOCK_DEVICE;
+ case '5':
+ return FileType.DIRECTORY;
+ case '6':
+ return FileType.FIFO;
+ case '7':
+ return FileType.CONTIGUOUS_FILE;
+ case 'g':
+ return FileType.GLOBAL_EXTENDED_HEADER;
+ case 'x':
+ return FileType.EXTENDED_HEADER;
+ default:
+ return FileType.OTHER;
+ }
+ }
+ }
+
+}
From 782f16728a61c3937176b0f276b4e5f7b5d5975e Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Fri, 3 Nov 2023 01:22:36 +0100
Subject: [PATCH 005/120] finalize the methods to unbzip2 and untar
---
.../java/org/apposed/appose/Bzip2Utils.java | 209 +++---------------
1 file changed, 26 insertions(+), 183 deletions(-)
diff --git a/src/main/java/org/apposed/appose/Bzip2Utils.java b/src/main/java/org/apposed/appose/Bzip2Utils.java
index 6fbc29b..669a77c 100644
--- a/src/main/java/org/apposed/appose/Bzip2Utils.java
+++ b/src/main/java/org/apposed/appose/Bzip2Utils.java
@@ -69,7 +69,7 @@ private Bzip2Utils() {
* @throws FileNotFoundException if the .bzip2 file is not found or does not exist
* @throws IOException if the source file already exists or there is any error with the decompression
*/
- public static void decompress(File source, File destination) throws FileNotFoundException, IOException {
+ public static void unBZip2(File source, File destination) throws FileNotFoundException, IOException {
try (
BZip2CompressorInputStream input = new BZip2CompressorInputStream(new BufferedInputStream(new FileInputStream(source)));
FileOutputStream output = new FileOutputStream(destination);
@@ -87,199 +87,42 @@ public static void decompress(File source, File destination) throws FileNotFound
* @param outputDir the output directory file.
* @throws IOException
* @throws FileNotFoundException
- *
* @throws ArchiveException
*/
private static void unTar(final File inputFile, final File outputDir) throws FileNotFoundException, IOException, ArchiveException {
- final InputStream is = new FileInputStream(inputFile);
- final TarArchiveInputStream debInputStream = (TarArchiveInputStream) new ArchiveStreamFactory().createArchiveInputStream("tar", is);
- TarArchiveEntry entry = null;
- while ((entry = (TarArchiveEntry)debInputStream.getNextEntry()) != null) {
- final File outputFile = new File(outputDir, entry.getName());
- if (entry.isDirectory()) {
- if (!outputFile.exists()) {
- if (!outputFile.mkdirs()) {
- throw new IllegalStateException(String.format("Couldn't create directory %s.", outputFile.getAbsolutePath()));
- }
- }
- } else {
- final OutputStream outputFileStream = new FileOutputStream(outputFile);
- IOUtils.copy(debInputStream, outputFileStream);
- outputFileStream.close();
- }
- }
- debInputStream.close();
+ try (
+ InputStream is = new FileInputStream(inputFile);
+ TarArchiveInputStream debInputStream = (TarArchiveInputStream) new ArchiveStreamFactory().createArchiveInputStream("tar", is);
+ ) {
+ TarArchiveEntry entry = null;
+ while ((entry = (TarArchiveEntry)debInputStream.getNextEntry()) != null) {
+ final File outputFile = new File(outputDir, entry.getName());
+ if (entry.isDirectory()) {
+ if (!outputFile.exists()) {
+ if (!outputFile.mkdirs()) {
+ throw new IllegalStateException(String.format("Couldn't create directory %s.", outputFile.getAbsolutePath()));
+ }
+ }
+ } else {
+ if (!outputFile.getParentFile().exists()) {
+ if (!outputFile.getParentFile().mkdirs())
+ throw new IOException("Failed to create directory " + outputFile.getParentFile().getAbsolutePath());
+ }
+ try (OutputStream outputFileStream = new FileOutputStream(outputFile)) {
+ IOUtils.copy(debInputStream, outputFileStream);
+ }
+ }
+ }
+ }
}
public static void main(String[] args) throws FileNotFoundException, IOException, ArchiveException {
String tarPath = "C:\\Users\\angel\\OneDrive\\Documentos\\pasteur\\git\\micromamba-1.5.1-1.tar";
String mambaPath = "C:\\Users\\angel\\OneDrive\\Documentos\\pasteur\\git\\mamba";
- decompress(new File("C:\\Users\\angel\\OneDrive\\Documentos\\pasteur\\git\\micromamba-1.5.1-1.tar.bz2"),
+ unBZip2(new File("C:\\Users\\angel\\OneDrive\\Documentos\\pasteur\\git\\micromamba-1.5.1-1.tar.bz2"),
new File(tarPath));
unTar(new File(tarPath), new File(mambaPath));
}
-
- // Size of the block in a standard tar file.
- private static final int BLOCK_SIZE = 512;
-
- public static void tarDecompress(String tarFilePath, String outputDirPath) {
-
- File tarFile = new File(tarFilePath);
- File outputDir = new File(outputDirPath);
-
- // Make sure the output directory exists
- if (!outputDir.isDirectory())
- outputDir.mkdirs();
-
- try (FileInputStream fis = new FileInputStream(tarFile);
- BufferedInputStream bis = new BufferedInputStream(fis)) {
-
- boolean endOfArchive = false;
- byte[] block = new byte[BLOCK_SIZE];
- while (!endOfArchive) {
- // Read a block from the tar archive.
- int bytesRead = bis.read(block);
- if (bytesRead < BLOCK_SIZE) {
- throw new IOException("Incomplete block read.");
- }
-
- // Check for the end of the archive. An empty block signals end.
- endOfArchive = isEndOfArchive(block);
- if (endOfArchive) {
- break;
- }
-
- // Read the header from the block.
- TarHeader header = new TarHeader(block);
-
- // If the file size is nonzero, create an output file.
- if (header.fileSize > 0) {
- File outputFile = new File(outputDir, header.fileName);
- if (header.fileType == TarHeader.FileType.DIRECTORY) {
- outputFile.mkdirs();
- } else {
- try (FileOutputStream fos = new FileOutputStream(outputFile);
- BufferedOutputStream bos = new BufferedOutputStream(fos)) {
- long fileSizeRemaining = header.fileSize;
- while (fileSizeRemaining > 0) {
- int toRead = (int) Math.min(fileSizeRemaining, BLOCK_SIZE);
- bytesRead = bis.read(block, 0, toRead);
- if (bytesRead != toRead) {
- throw new IOException("Unexpected end of file");
- }
- bos.write(block, 0, bytesRead);
- fileSizeRemaining -= bytesRead;
- }
- }
- }
- }
-
- // Skip to the next file entry in the tar archive by advancing to the next block boundary.
- long fileEntrySize = (header.fileSize + BLOCK_SIZE - 1) / BLOCK_SIZE * BLOCK_SIZE;
- long bytesToSkip = fileEntrySize - header.fileSize;
- long skipped = bis.skip(bytesToSkip);
- if (skipped != bytesToSkip) {
- throw new IOException("Failed to skip bytes for the next entry");
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- private static boolean isEndOfArchive(byte[] block) {
- // An empty block signals the end of the archive in a tar file.
- for (int i = 0; i < BLOCK_SIZE; i++) {
- if (block[i] != 0) {
- return false;
- }
- }
- return true;
- }
-
- private static class TarHeader {
- String fileName;
- int fileMode;
- int ownerId;
- int groupId;
- long fileSize;
- long modificationTime;
- int checksum;
- FileType fileType;
- String linkName;
- String magic; // UStar indicator
- String version;
- String ownerUserName;
- String ownerGroupName;
- String devMajor;
- String devMinor;
- String prefix; // Used if the file name is longer than 100 characters
-
- enum FileType {
- FILE, DIRECTORY, SYMLINK, CHARACTER_DEVICE, BLOCK_DEVICE, FIFO, CONTIGUOUS_FILE, GLOBAL_EXTENDED_HEADER, EXTENDED_HEADER, OTHER
- }
-
- TarHeader(byte[] headerBlock) {
- fileName = extractString(headerBlock, 0, 100);
- fileMode = (int) extractOctal(headerBlock, 100, 8);
- ownerId = (int) extractOctal(headerBlock, 108, 8);
- groupId = (int) extractOctal(headerBlock, 116, 8);
- fileSize = extractOctal(headerBlock, 124, 12);
- modificationTime = extractOctal(headerBlock, 136, 12);
- checksum = (int) extractOctal(headerBlock, 148, 8);
- fileType = determineFileType(headerBlock[156]);
- linkName = extractString(headerBlock, 157, 100);
- magic = extractString(headerBlock, 257, 6);
- version = extractString(headerBlock, 263, 2);
- ownerUserName = extractString(headerBlock, 265, 32);
- ownerGroupName = extractString(headerBlock, 297, 32);
- devMajor = extractString(headerBlock, 329, 8);
- devMinor = extractString(headerBlock, 337, 8);
- prefix = extractString(headerBlock, 345, 155);
- // Note: The prefix is used in conjunction with the filename to allow for longer file names.
- }
-
- private long extractOctal(byte[] buffer, int offset, int length) {
- String octalString = extractString(buffer, offset, length);
- return Long.parseLong(octalString, 8);
- }
-
- private String extractString(byte[] buffer, int offset, int length) {
- StringBuilder stringBuilder = new StringBuilder(length);
- for (int i = offset; i < offset + length; i++) {
- if (buffer[i] == 0) break; // Stop at the first null character.
- stringBuilder.append((char) buffer[i]);
- }
- return stringBuilder.toString();
- }
-
- private FileType determineFileType(byte typeFlag) {
- switch (typeFlag) {
- case '0':
- case '\0':
- return FileType.FILE;
- case '2':
- return FileType.SYMLINK;
- case '3':
- return FileType.CHARACTER_DEVICE;
- case '4':
- return FileType.BLOCK_DEVICE;
- case '5':
- return FileType.DIRECTORY;
- case '6':
- return FileType.FIFO;
- case '7':
- return FileType.CONTIGUOUS_FILE;
- case 'g':
- return FileType.GLOBAL_EXTENDED_HEADER;
- case 'x':
- return FileType.EXTENDED_HEADER;
- default:
- return FileType.OTHER;
- }
- }
- }
-
}
From b5b947d1c48cbd55b29ba5ed9d862dace1a8e24f Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Fri, 3 Nov 2023 01:34:52 +0100
Subject: [PATCH 006/120] imporve mamba installation
---
src/main/java/org/apposed/appose/Builder.java | 4 ++--
src/main/java/org/apposed/appose/Conda.java | 19 ++++++++++++-------
2 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/src/main/java/org/apposed/appose/Builder.java b/src/main/java/org/apposed/appose/Builder.java
index c6e6e03..87e48d2 100644
--- a/src/main/java/org/apposed/appose/Builder.java
+++ b/src/main/java/org/apposed/appose/Builder.java
@@ -34,6 +34,7 @@
import java.nio.file.Paths;
public class Builder {
+
public Environment build() {
String base = baseDir.getPath();
@@ -46,8 +47,7 @@ public Environment build() {
// - Populate ${baseDirectory}/jars with Maven artifacts?
try {
- String minicondaBase = Paths.get(System.getProperty("user.home"), ".local", "share", "appose", "miniconda").toString();
- Conda conda = new Conda(minicondaBase);
+ Conda conda = new Conda(Conda.basePath);
String envName = "appose";
if (conda.getEnvironmentNames().contains( envName )) {
// TODO: Should we update it? For now, we just use it.
diff --git a/src/main/java/org/apposed/appose/Conda.java b/src/main/java/org/apposed/appose/Conda.java
index 62d8d73..16bbd12 100644
--- a/src/main/java/org/apposed/appose/Conda.java
+++ b/src/main/java/org/apposed/appose/Conda.java
@@ -66,6 +66,8 @@ public class Conda {
private final static int TIMEOUT_MILLIS = 10 * 1000;
+ final public static String basePath = Paths.get(System.getProperty("user.home"), ".local", "share", "appose", "micromamba").toString();
+
private final static String MICROMAMBA_URL =
"https://micro.mamba.pm/api/micromamba/" + microMambaPlatform() + "/latest";
@@ -132,19 +134,21 @@ public Conda( final String rootdir ) throws IOException, InterruptedException
if ( Files.notExists( Paths.get( rootdir ) ) )
{
- final File tempFile = File.createTempFile( "miniconda", SystemUtils.IS_OS_WINDOWS ? ".tar.bz2" : ".sh" );
+ final File tempFile = File.createTempFile( "miniconda", ".tar.bz2" );
tempFile.deleteOnExit();
FileUtils.copyURLToFile(
new URL( MICROMAMBA_URL ),
tempFile,
TIMEOUT_MILLIS,
TIMEOUT_MILLIS );
+
+ final File tempTarFile = File.createTempFile( "miniconda", ".tar" );
+ tempTarFile.deleteOnExit();
+ Bzip2Utils.unBZip2(tempFile, tempTarFile);
+ File mambaBaseDir = new File(basePath);
+ if (!mambaBaseDir.isDirectory() && !mambaBaseDir.mkdirs())
+ throw new IOException("Failed to create Micromamba default directory " + mambaBaseDir.getParentFile().getAbsolutePath());
- String command = "tar xf " + tempFile.getAbsolutePath();
- // Setting up the ProcessBuilder to use PowerShell
- ProcessBuilder processBuilder = new ProcessBuilder("powershell.exe", "-Command", command);
- if ( processBuilder.inheritIO().start().waitFor() != 0 )
- throw new RuntimeException();
final List< String > cmd = getBaseCommand();
if ( SystemUtils.IS_OS_WINDOWS )
@@ -176,7 +180,8 @@ private List< String > getBaseCommand()
{
final List< String > cmd = new ArrayList<>();
if ( SystemUtils.IS_OS_WINDOWS )
- cmd.addAll( Arrays.asList( "cmd.exe", "/c" ) );
+ cmd.addAll( Arrays.asList( basePath + File.separator + "Library"
+ + File.separator + "bin" + File.separator + "micromamba.exe", "/c" ) );
return cmd;
}
From 73afaf75084c334497a16ed3c10f9e6a0a84c22b Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Fri, 3 Nov 2023 02:02:15 +0100
Subject: [PATCH 007/120] adapt to micromamba
---
src/main/java/org/apposed/appose/Builder.java | 2 +-
src/main/java/org/apposed/appose/Conda.java | 31 ++++++++++++-------
2 files changed, 20 insertions(+), 13 deletions(-)
diff --git a/src/main/java/org/apposed/appose/Builder.java b/src/main/java/org/apposed/appose/Builder.java
index 87e48d2..2eaaecf 100644
--- a/src/main/java/org/apposed/appose/Builder.java
+++ b/src/main/java/org/apposed/appose/Builder.java
@@ -47,7 +47,7 @@ public Environment build() {
// - Populate ${baseDirectory}/jars with Maven artifacts?
try {
- Conda conda = new Conda(Conda.basePath);
+ Conda conda = new Conda(Conda.BASE_PATH);
String envName = "appose";
if (conda.getEnvironmentNames().contains( envName )) {
// TODO: Should we update it? For now, we just use it.
diff --git a/src/main/java/org/apposed/appose/Conda.java b/src/main/java/org/apposed/appose/Conda.java
index 16bbd12..a0c3419 100644
--- a/src/main/java/org/apposed/appose/Conda.java
+++ b/src/main/java/org/apposed/appose/Conda.java
@@ -56,7 +56,9 @@ public class Conda {
final String pythonCommand = SystemUtils.IS_OS_WINDOWS ? "python.exe" : "bin/python";
- final String condaCommand = SystemUtils.IS_OS_WINDOWS ? "condabin\\conda.bat" : "condabin/conda";
+ final String condaCommand = SystemUtils.IS_OS_WINDOWS ?
+ BASE_PATH + File.separator + "Library" + File.separator + "bin" + File.separator + "micromamba.exe"
+ : BASE_PATH + File.separator + "bin" + File.separator + "micromamba";
private String envName = DEFAULT_ENVIRONMENT_NAME;
@@ -66,7 +68,9 @@ public class Conda {
private final static int TIMEOUT_MILLIS = 10 * 1000;
- final public static String basePath = Paths.get(System.getProperty("user.home"), ".local", "share", "appose", "micromamba").toString();
+ final public static String BASE_PATH = Paths.get(System.getProperty("user.home"), ".local", "share", "appose", "micromamba").toString();
+
+ final public static String ENVS_PATH = Paths.get(BASE_PATH, "envs").toString();
private final static String MICROMAMBA_URL =
"https://micro.mamba.pm/api/micromamba/" + microMambaPlatform() + "/latest";
@@ -145,7 +149,7 @@ public Conda( final String rootdir ) throws IOException, InterruptedException
final File tempTarFile = File.createTempFile( "miniconda", ".tar" );
tempTarFile.deleteOnExit();
Bzip2Utils.unBZip2(tempFile, tempTarFile);
- File mambaBaseDir = new File(basePath);
+ File mambaBaseDir = new File(BASE_PATH);
if (!mambaBaseDir.isDirectory() && !mambaBaseDir.mkdirs())
throw new IOException("Failed to create Micromamba default directory " + mambaBaseDir.getParentFile().getAbsolutePath());
@@ -180,8 +184,7 @@ private List< String > getBaseCommand()
{
final List< String > cmd = new ArrayList<>();
if ( SystemUtils.IS_OS_WINDOWS )
- cmd.addAll( Arrays.asList( basePath + File.separator + "Library"
- + File.separator + "bin" + File.separator + "micromamba.exe", "/c" ) );
+ cmd.addAll( Arrays.asList( "cmd.exe", "/c" ) );
return cmd;
}
@@ -271,8 +274,7 @@ public void createWithYaml( final String envName, final String envYaml, final bo
if ( !isForceCreation && getEnvironmentNames().contains( envName ) )
throw new EnvironmentExistsException();
runConda( "env", "create", "--prefix",
- rootdir + File.separator + "envs", "--force",
- "-n", envName, "--file", envYaml, "-y" );
+ ENVS_PATH + File.separator + envName, "--force", "--file", envYaml, "-y" );
}
/**
@@ -312,7 +314,7 @@ public void create( final String envName, final boolean isForceCreation ) throws
{
if ( !isForceCreation && getEnvironmentNames().contains( envName ) )
throw new EnvironmentExistsException();
- runConda( "create", "-y", "-n", envName );
+ runConda( "create", "-y", "-p", ENVS_PATH + File.separator + envName );
}
/**
@@ -360,7 +362,7 @@ public void create( final String envName, final boolean isForceCreation, final S
{
if ( !isForceCreation && getEnvironmentNames().contains( envName ) )
throw new EnvironmentExistsException();
- final List< String > cmd = new ArrayList<>( Arrays.asList( "env", "create", "--force", "-n", envName ) );
+ final List< String > cmd = new ArrayList<>( Arrays.asList( "env", "create", "--force", "-p", ENVS_PATH + File.separator + envName ) );
cmd.addAll( Arrays.asList( args ) );
runConda( cmd.stream().toArray( String[]::new ) );
}
@@ -554,7 +556,7 @@ public void runPythonIn( final String envName, final String... args ) throws IOE
envs.put( "Path", Paths.get( envDir, "Library" ).toString() + ";" + envs.get( "Path" ) );
envs.put( "Path", Paths.get( envDir, "Library", "Bin" ).toString() + ";" + envs.get( "Path" ) );
}
- builder.environment().putAll( getEnvironmentVariables( envName ) );
+ // TODO find way to get env vars in micromamba builder.environment().putAll( getEnvironmentVariables( envName ) );
if ( builder.command( cmd ).start().waitFor() != 0 )
throw new RuntimeException();
}
@@ -573,7 +575,7 @@ public void runPythonIn( final String envName, final String... args ) throws IOE
public String getVersion() throws IOException, InterruptedException
{
final List< String > cmd = getBaseCommand();
- cmd.addAll( Arrays.asList( condaCommand, "-V" ) );
+ cmd.addAll( Arrays.asList( condaCommand, "--version" ) );
final Process process = getBuilder( false ).command( cmd ).start();
if ( process.waitFor() != 0 )
throw new RuntimeException();
@@ -613,10 +615,12 @@ public void runConda( final String... args ) throws RuntimeException, IOExceptio
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
*/
+ /* TODO find equivalent in mamba
public Map< String, String > getEnvironmentVariables() throws IOException, InterruptedException
{
return getEnvironmentVariables( envName );
}
+ */
/**
* Returns environment variables associated with the specified environment as
@@ -632,6 +636,8 @@ public Map< String, String > getEnvironmentVariables() throws IOException, Inter
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
*/
+ /**
+ * TODO find equivalent in mamba
public Map< String, String > getEnvironmentVariables( final String envName ) throws IOException, InterruptedException
{
final List< String > cmd = getBaseCommand();
@@ -652,6 +658,7 @@ public Map< String, String > getEnvironmentVariables( final String envName ) thr
}
return map;
}
+ */
/**
* Returns a list of the Conda environment names as {@code List< String >}.
@@ -667,7 +674,7 @@ public Map< String, String > getEnvironmentVariables( final String envName ) thr
public List< String > getEnvironmentNames() throws IOException
{
final List< String > envs = new ArrayList<>( Arrays.asList( DEFAULT_ENVIRONMENT_NAME ) );
- envs.addAll( Files.list( Paths.get( rootdir, "envs" ) )
+ envs.addAll( Files.list( Paths.get( ENVS_PATH ) )
.map( p -> p.getFileName().toString() )
.filter( p -> !p.startsWith( "." ) )
.collect( Collectors.toList() ) );
From d33b7858681e3595477393cf85380b09f9be7e02 Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Fri, 3 Nov 2023 02:02:57 +0100
Subject: [PATCH 008/120] remove not used anymore dep
---
pom.xml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index cea0ebe..5d89697 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
+ * CONDA_ROOT
+ * ├── condabin
+ * │ ├── conda(.bat)
+ * │ ...
+ * ├── envs
+ * │ ├── your_env
+ * │ │ ├── python(.exe)
+ *
+ *
+ * @param rootdir
+ * The root dir for Conda installation.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public Conda( final String rootdir ) throws IOException, InterruptedException
+ {
+ if ( Files.notExists( Paths.get( rootdir ) ) )
+ {
+ String downloadUrl = null;
+ if ( SystemUtils.IS_OS_WINDOWS )
+ downloadUrl = DOWNLOAD_URL_WIN;
+ else if ( SystemUtils.IS_OS_LINUX )
+ downloadUrl = DOWNLOAD_URL_LINUX;
+ else if ( SystemUtils.IS_OS_MAC && System.getProperty( "os.arch" ).equals( "aarch64" ) )
+ downloadUrl = DOWNLOAD_URL_MAC_M1;
+ else if ( SystemUtils.IS_OS_MAC )
+ downloadUrl = DOWNLOAD_URL_MAC;
+ else
+ throw new UnsupportedOperationException();
+
+ final File tempFile = File.createTempFile( "miniconda", SystemUtils.IS_OS_WINDOWS ? ".exe" : ".sh" );
+ tempFile.deleteOnExit();
+ FileUtils.copyURLToFile(
+ new URL( downloadUrl ),
+ tempFile,
+ TIMEOUT_MILLIS,
+ TIMEOUT_MILLIS );
+ final List< String > cmd = getBaseCommand();
+
+ if ( SystemUtils.IS_OS_WINDOWS )
+ cmd.addAll( Arrays.asList( "start", "/wait", "\"\"", tempFile.getAbsolutePath(), "/InstallationType=JustMe", "/AddToPath=0", "/RegisterPython=0", "/S", "/D=" + rootdir ) );
+ else
+ cmd.addAll( Arrays.asList( "bash", tempFile.getAbsolutePath(), "-b", "-p", rootdir ) );
+ if ( new ProcessBuilder().inheritIO().command( cmd ).start().waitFor() != 0 )
+ throw new RuntimeException();
+ }
+ this.rootdir = rootdir;
+
+ // The following command will throw an exception if Conda does not work as
+ // expected.
+ final List< String > cmd = getBaseCommand();
+ cmd.addAll( Arrays.asList( condaCommand, "-V" ) );
+ if ( getBuilder( false ).command( cmd ).start().waitFor() != 0 )
+ throw new RuntimeException();
+ }
+
+ /**
+ * Run {@code conda update} in the activated environment. A list of packages to
+ * be updated and extra parameters can be specified as {@code args}.
+ *
+ * @param args
+ * The list of packages to be updated and extra parameters as
+ * {@code String...}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void update( final String... args ) throws IOException, InterruptedException
+ {
+ updateIn( envName, args );
+ }
+
+ /**
+ * Run {@code conda update} in the specified environment. A list of packages to
+ * update and extra parameters can be specified as {@code args}.
+ *
+ * @param envName
+ * The environment name to be used for the update command.
+ * @param args
+ * The list of packages to be updated and extra parameters as
+ * {@code String...}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void updateIn( final String envName, final String... args ) throws IOException, InterruptedException
+ {
+ final List< String > cmd = new ArrayList<>( Arrays.asList( "update", "-y", "-n", envName ) );
+ cmd.addAll( Arrays.asList( args ) );
+ runConda( cmd.stream().toArray( String[]::new ) );
+ }
+
+ /**
+ * Run {@code conda create} to create an empty conda environment.
+ *
+ * @param envName
+ * The environment name to be created.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void create( final String envName ) throws IOException, InterruptedException
+ {
+ create( envName, false );
+ }
+
+ /**
+ * Run {@code conda create} to create an empty conda environment.
+ *
+ * @param envName
+ * The environment name to be created.
+ * @param isForceCreation
+ * Force creation of the environment if {@code true}. If this value
+ * is {@code false} and an environment with the specified name
+ * already exists, throw an {@link EnvironmentExistsException}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void create( final String envName, final boolean isForceCreation ) throws IOException, InterruptedException
+ {
+ if ( !isForceCreation && getEnvironmentNames().contains( envName ) )
+ throw new EnvironmentExistsException();
+ runConda( "create", "-y", "-n", envName );
+ }
+
+ /**
+ * Run {@code conda create} to create a new conda environment with a list of
+ * specified packages.
+ *
+ * @param envName
+ * The environment name to be created.
+ * @param args
+ * The list of packages to be installed on environment creation and
+ * extra parameters as {@code String...}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void create( final String envName, final String... args ) throws IOException, InterruptedException
+ {
+ create( envName, false, args );
+ }
+
+ /**
+ * Run {@code conda create} to create a new conda environment with a list of
+ * specified packages.
+ *
+ * @param envName
+ * The environment name to be created.
+ * @param isForceCreation
+ * Force creation of the environment if {@code true}. If this value
+ * is {@code false} and an environment with the specified name
+ * already exists, throw an {@link EnvironmentExistsException}.
+ * @param args
+ * The list of packages to be installed on environment creation and
+ * extra parameters as {@code String...}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void create( final String envName, final boolean isForceCreation, final String... args ) throws IOException, InterruptedException
+ {
+ if ( !isForceCreation && getEnvironmentNames().contains( envName ) )
+ throw new EnvironmentExistsException();
+ final List< String > cmd = new ArrayList<>( Arrays.asList( "env", "create", "--force", "-n", envName ) );
+ cmd.addAll( Arrays.asList( args ) );
+ runConda( cmd.stream().toArray( String[]::new ) );
+ }
+
+ /**
+ * This method works as if the user runs {@code conda activate envName}. This
+ * method internally calls {@link Conda#setEnvName(String)}.
+ *
+ * @param envName
+ * The environment name to be activated.
+ * @throws IOException
+ * If an I/O error occurs.
+ */
+ public void activate( final String envName ) throws IOException
+ {
+ if ( getEnvironmentNames().contains( envName ) )
+ setEnvName( envName );
+ else
+ throw new IllegalArgumentException( "environment: " + envName + " not found." );
+ }
+
+ /**
+ * This method works as if the user runs {@code conda deactivate}. This method
+ * internally sets the {@code envName} to {@code base}.
+ */
+ public void deactivate()
+ {
+ setEnvName( DEFAULT_ENVIRONMENT_NAME );
+ }
+
+ /**
+ * This method is used by {@code Conda#activate(String)} and
+ * {@code Conda#deactivate()}. This method is kept private since it is not
+ * expected to call this method directory.
+ *
+ * @param envName
+ * The environment name to be set.
+ */
+ private void setEnvName( final String envName )
+ {
+ this.envName = envName;
+ }
+
+ /**
+ * Returns the active environment name.
+ *
+ * @return The active environment name.
+ *
+ */
+ public String getEnvName()
+ {
+ return envName;
+ }
+
+ /**
+ * Run {@code conda install} in the activated environment. A list of packages to
+ * install and extra parameters can be specified as {@code args}.
+ *
+ * @param args
+ * The list of packages to be installed and extra parameters as
+ * {@code String...}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void install( final String... args ) throws IOException, InterruptedException
+ {
+ installIn( envName, args );
+ }
+
+ /**
+ * Run {@code conda install} in the specified environment. A list of packages to
+ * install and extra parameters can be specified as {@code args}.
+ *
+ * @param envName
+ * The environment name to be used for the install command.
+ * @param args
+ * The list of packages to be installed and extra parameters as
+ * {@code String...}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void installIn( final String envName, final String... args ) throws IOException, InterruptedException
+ {
+ final List< String > cmd = new ArrayList<>( Arrays.asList( "install", "-y", "-n", envName ) );
+ cmd.addAll( Arrays.asList( args ) );
+ runConda( cmd.stream().toArray( String[]::new ) );
+ }
+
+ /**
+ * Run {@code pip install} in the activated environment. A list of packages to
+ * install and extra parameters can be specified as {@code args}.
+ *
+ * @param args
+ * The list of packages to be installed and extra parameters as
+ * {@code String...}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void pipInstall( final String... args ) throws IOException, InterruptedException
+ {
+ pipInstallIn( envName, args );
+ }
+
+ /**
+ * Run {@code pip install} in the specified environment. A list of packages to
+ * install and extra parameters can be specified as {@code args}.
+ *
+ * @param envName
+ * The environment name to be used for the install command.
+ * @param args
+ * The list of packages to be installed and extra parameters as
+ * {@code String...}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void pipInstallIn( final String envName, final String... args ) throws IOException, InterruptedException
+ {
+ final List< String > cmd = new ArrayList<>( Arrays.asList( "-m", "pip", "install" ) );
+ cmd.addAll( Arrays.asList( args ) );
+ runPythonIn( envName, cmd.stream().toArray( String[]::new ) );
+ }
+
+ /**
+ * Run a Python command in the activated environment. This method automatically
+ * sets environment variables associated with the activated environment. In
+ * Windows, this method also sets the {@code PATH} environment variable so that
+ * the specified environment runs as expected.
+ *
+ * @param args
+ * One or more arguments for the Python command.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void runPython( final String... args ) throws IOException, InterruptedException
+ {
+ runPythonIn( envName, args );
+ }
+
+ /**
+ * Run a Python command in the specified environment. This method automatically
+ * sets environment variables associated with the specified environment. In
+ * Windows, this method also sets the {@code PATH} environment variable so that
+ * the specified environment runs as expected.
+ *
+ * @param envName
+ * The environment name used to run the Python command.
+ * @param args
+ * One or more arguments for the Python command.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void runPythonIn( final String envName, final String... args ) throws IOException, InterruptedException
+ {
+ final List< String > cmd = getBaseCommand();
+ if ( envName.equals( DEFAULT_ENVIRONMENT_NAME ) )
+ cmd.add( pythonCommand );
+ else
+ cmd.add( Paths.get( "envs", envName, pythonCommand ).toString() );
+ cmd.addAll( Arrays.asList( args ) );
+ final ProcessBuilder builder = getBuilder( true );
+ if ( SystemUtils.IS_OS_WINDOWS )
+ {
+ final Map< String, String > envs = builder.environment();
+ final String envDir = Paths.get( rootdir, "envs", envName ).toString();
+ envs.put( "Path", envDir + ";" + envs.get( "Path" ) );
+ envs.put( "Path", Paths.get( envDir, "Scripts" ).toString() + ";" + envs.get( "Path" ) );
+ envs.put( "Path", Paths.get( envDir, "Library" ).toString() + ";" + envs.get( "Path" ) );
+ envs.put( "Path", Paths.get( envDir, "Library", "Bin" ).toString() + ";" + envs.get( "Path" ) );
+ }
+ builder.environment().putAll( getEnvironmentVariables( envName ) );
+ if ( builder.command( cmd ).start().waitFor() != 0 )
+ throw new RuntimeException();
+ }
+
+ /**
+ * Returns Conda version as a {@code String}.
+ *
+ * @return The Conda version as a {@code String}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public String getVersion() throws IOException, InterruptedException
+ {
+ final List< String > cmd = getBaseCommand();
+ cmd.addAll( Arrays.asList( condaCommand, "-V" ) );
+ final Process process = getBuilder( false ).command( cmd ).start();
+ if ( process.waitFor() != 0 )
+ throw new RuntimeException();
+ return new BufferedReader( new InputStreamReader( process.getInputStream() ) ).readLine();
+ }
+
+ /**
+ * Run a Conda command with one or more arguments.
+ *
+ * @param args
+ * One or more arguments for the Conda command.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void runConda( final String... args ) throws RuntimeException, IOException, InterruptedException
+ {
+ final List< String > cmd = getBaseCommand();
+ cmd.add( condaCommand );
+ cmd.addAll( Arrays.asList( args ) );
+ if ( getBuilder( true ).command( cmd ).start().waitFor() != 0 )
+ throw new RuntimeException();
+ }
+
+ /**
+ * Returns environment variables associated with the activated environment as
+ * {@code Map< String, String >}.
+ *
+ * @return The environment variables as {@code Map< String, String >}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public Map< String, String > getEnvironmentVariables() throws IOException, InterruptedException
+ {
+ return getEnvironmentVariables( envName );
+ }
+
+ /**
+ * Returns environment variables associated with the specified environment as
+ * {@code Map< String, String >}.
+ *
+ * @param envName
+ * The environment name used to run the Python command.
+ * @return The environment variables as {@code Map< String, String >}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public Map< String, String > getEnvironmentVariables( final String envName ) throws IOException, InterruptedException
+ {
+ final List< String > cmd = getBaseCommand();
+ cmd.addAll( Arrays.asList( condaCommand, "env", "config", "vars", "list", "-n", envName ) );
+ final Process process = getBuilder( false ).command( cmd ).start();
+ if ( process.waitFor() != 0 )
+ throw new RuntimeException();
+ final Map< String, String > map = new HashMap<>();
+ try (final BufferedReader reader = new BufferedReader( new InputStreamReader( process.getInputStream() ) ))
+ {
+ String line;
+
+ while ( ( line = reader.readLine() ) != null )
+ {
+ final String[] keyVal = line.split( " = " );
+ map.put( keyVal[ 0 ], keyVal[ 1 ] );
+ }
+ }
+ return map;
+ }
+
+ /**
+ * Returns a list of the Conda environment names as {@code List< String >}.
+ *
+ * @return The list of the Conda environment names as {@code List< String >}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public List< String > getEnvironmentNames() throws IOException
+ {
+ final List< String > envs = new ArrayList<>( Arrays.asList( DEFAULT_ENVIRONMENT_NAME ) );
+ envs.addAll( Files.list( Paths.get( rootdir, "envs" ) )
+ .map( p -> p.getFileName().toString() )
+ .filter( p -> !p.startsWith( "." ) )
+ .collect( Collectors.toList() ) );
+ return envs;
+ }
+
+}
diff --git a/src/test/java/org/apposed/appose/ApposeTest.java b/src/test/java/org/apposed/appose/ApposeTest.java
index 5770cc4..26a8159 100644
--- a/src/test/java/org/apposed/appose/ApposeTest.java
+++ b/src/test/java/org/apposed/appose/ApposeTest.java
@@ -86,6 +86,14 @@ public void testPython() throws IOException, InterruptedException {
}
}
+ public void testConda() {
+ Environment env = Appose.conda("appose-environment.yml").build();
+ try (Service service = env.python()) {
+ service.debug(System.err::println);
+ executeAndAssert(service, "import cowsay; ");
+ }
+ }
+
@Test
public void testServiceStartupFailure() throws IOException {
Environment env = Appose.base("no-pythons-to-be-found-here").build();
From 673ffe4c1ac6f34e5eee9f07ba04116f8528351e Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Thu, 2 Nov 2023 20:56:34 +0100
Subject: [PATCH 002/120] test commit
---
src/test/java/org/apposed/appose/ApposeTest.java | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/test/java/org/apposed/appose/ApposeTest.java b/src/test/java/org/apposed/appose/ApposeTest.java
index 26a8159..6b5522b 100644
--- a/src/test/java/org/apposed/appose/ApposeTest.java
+++ b/src/test/java/org/apposed/appose/ApposeTest.java
@@ -34,6 +34,7 @@
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.fail;
+import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -87,10 +88,13 @@ public void testPython() throws IOException, InterruptedException {
}
public void testConda() {
- Environment env = Appose.conda("appose-environment.yml").build();
+ Environment env = Appose.conda(new File("appose-environment.yml")).build();
try (Service service = env.python()) {
service.debug(System.err::println);
executeAndAssert(service, "import cowsay; ");
+ } catch (IOException | InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
}
}
From 9dda13dc0774814cfa38eeb9250395252019f4a7 Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Thu, 2 Nov 2023 21:00:27 +0100
Subject: [PATCH 003/120] add code to be able to download micromamba
---
src/main/java/org/apposed/appose/Conda.java | 116 ++++++++++++++++--
.../org/apposed/appose/CondaException.java | 96 +++++++++++++++
2 files changed, 199 insertions(+), 13 deletions(-)
create mode 100644 src/main/java/org/apposed/appose/CondaException.java
diff --git a/src/main/java/org/apposed/appose/Conda.java b/src/main/java/org/apposed/appose/Conda.java
index 29e7c73..62d8d73 100644
--- a/src/main/java/org/apposed/appose/Conda.java
+++ b/src/main/java/org/apposed/appose/Conda.java
@@ -26,7 +26,11 @@
******************************************************************************/
package org.apposed.appose;
+import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.SystemUtils;
+import org.apposed.appose.CondaException.EnvironmentExistsException;
+
+import groovy.json.JsonSlurper;
import java.io.BufferedReader;
import java.io.File;
@@ -49,6 +53,16 @@
* @author Curtis Rueden
*/
public class Conda {
+
+ final String pythonCommand = SystemUtils.IS_OS_WINDOWS ? "python.exe" : "bin/python";
+
+ final String condaCommand = SystemUtils.IS_OS_WINDOWS ? "condabin\\conda.bat" : "condabin/conda";
+
+ private String envName = DEFAULT_ENVIRONMENT_NAME;
+
+ private final String rootdir;
+
+ public final static String DEFAULT_ENVIRONMENT_NAME = "base";
private final static int TIMEOUT_MILLIS = 10 * 1000;
@@ -117,25 +131,20 @@ public Conda( final String rootdir ) throws IOException, InterruptedException
{
if ( Files.notExists( Paths.get( rootdir ) ) )
{
- String downloadUrl = null;
- if ( SystemUtils.IS_OS_WINDOWS )
- downloadUrl = DOWNLOAD_URL_WIN;
- else if ( SystemUtils.IS_OS_LINUX )
- downloadUrl = DOWNLOAD_URL_LINUX;
- else if ( SystemUtils.IS_OS_MAC && System.getProperty( "os.arch" ).equals( "aarch64" ) )
- downloadUrl = DOWNLOAD_URL_MAC_M1;
- else if ( SystemUtils.IS_OS_MAC )
- downloadUrl = DOWNLOAD_URL_MAC;
- else
- throw new UnsupportedOperationException();
- final File tempFile = File.createTempFile( "miniconda", SystemUtils.IS_OS_WINDOWS ? ".exe" : ".sh" );
+ final File tempFile = File.createTempFile( "miniconda", SystemUtils.IS_OS_WINDOWS ? ".tar.bz2" : ".sh" );
tempFile.deleteOnExit();
FileUtils.copyURLToFile(
- new URL( downloadUrl ),
+ new URL( MICROMAMBA_URL ),
tempFile,
TIMEOUT_MILLIS,
TIMEOUT_MILLIS );
+
+ String command = "tar xf " + tempFile.getAbsolutePath();
+ // Setting up the ProcessBuilder to use PowerShell
+ ProcessBuilder processBuilder = new ProcessBuilder("powershell.exe", "-Command", command);
+ if ( processBuilder.inheritIO().start().waitFor() != 0 )
+ throw new RuntimeException();
final List< String > cmd = getBaseCommand();
if ( SystemUtils.IS_OS_WINDOWS )
@@ -155,6 +164,22 @@ else if ( SystemUtils.IS_OS_MAC )
throw new RuntimeException();
}
+ /**
+ * Returns {@code \{"cmd.exe", "/c"\}} for Windows and an empty list for
+ * Mac/Linux.
+ *
+ * @return {@code \{"cmd.exe", "/c"\}} for Windows and an empty list for
+ * Mac/Linux.
+ * @throws IOException
+ */
+ private List< String > getBaseCommand()
+ {
+ final List< String > cmd = new ArrayList<>();
+ if ( SystemUtils.IS_OS_WINDOWS )
+ cmd.addAll( Arrays.asList( "cmd.exe", "/c" ) );
+ return cmd;
+ }
+
/**
* Run {@code conda update} in the activated environment. A list of packages to
* be updated and extra parameters can be specified as {@code args}.
@@ -197,6 +222,54 @@ public void updateIn( final String envName, final String... args ) throws IOExce
runConda( cmd.stream().toArray( String[]::new ) );
}
+ /**
+ * Run {@code conda create} to create a conda environment defined by the input environment yaml file.
+ *
+ * @param envName
+ * The environment name to be created.
+ * @param envYaml
+ * The environment yaml file containing the information required to build it
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void createWithYaml( final String envName, final String envYaml ) throws IOException, InterruptedException
+ {
+ createWithYaml(envName, envYaml);
+ }
+
+ /**
+ * Run {@code conda create} to create a conda environment defined by the input environment yaml file.
+ *
+ * @param envName
+ * The environment name to be created.
+ * @param envYaml
+ * The environment yaml file containing the information required to build it
+ * @param envName
+ * The environment name to be created.
+ * @param isForceCreation
+ * Force creation of the environment if {@code true}. If this value
+ * is {@code false} and an environment with the specified name
+ * already exists, throw an {@link EnvironmentExistsException}.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ */
+ public void createWithYaml( final String envName, final String envYaml, final boolean isForceCreation ) throws IOException, InterruptedException
+ {
+ if ( !isForceCreation && getEnvironmentNames().contains( envName ) )
+ throw new EnvironmentExistsException();
+ runConda( "env", "create", "--prefix",
+ rootdir + File.separator + "envs", "--force",
+ "-n", envName, "--file", envYaml, "-y" );
+ }
+
/**
* Run {@code conda create} to create an empty conda environment.
*
@@ -595,5 +668,22 @@ public List< String > getEnvironmentNames() throws IOException
.collect( Collectors.toList() ) );
return envs;
}
+
+ public static boolean checkDependenciesInEnv(String envDir, List
+ * CONDA_ROOT
+ * ├── condabin
+ * │ ├── conda(.bat)
+ * │ ...
+ * ├── envs
+ * │ ├── your_env
+ * │ │ ├── python(.exe)
+ *
+ *
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ * @throws ArchiveException
+ * @throws URISyntaxException
+ */
+ public Conda() throws IOException, InterruptedException, ArchiveException, URISyntaxException
+ {
+ this(BASE_PATH);
+ }
+
/**
* Create a new Conda object. The root dir for Conda installation can be
* specified as {@code String}. If there is no directory found at the specified
@@ -171,6 +202,13 @@ public Conda( final String rootdir ) throws IOException, InterruptedException, A
}
+ // The following command will throw an exception if Conda does not work as
+ // expected.
+ boolean executableSet = new File(condaCommand).setExecutable(true);
+ if (!executableSet)
+ throw new IOException("Cannot set file as executable due to missing permissions, "
+ + "please do it manually: " + condaCommand);
+
// The following command will throw an exception if Conda does not work as
// expected.
getVersion();
@@ -662,7 +700,8 @@ public void runConda(Consumer
+ * CONDA_ROOT
+ * ├── bin
+ * │ ├── micromamba(.exe)
+ * │ ...
+ * ├── envs
+ * │ ├── your_env
+ * │ │ ├── python(.exe)
+ *
+ *
+ * @param rootdir
+ * The root dir for Mamba installation.
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ * @throws ArchiveException
+ * @throws URISyntaxException
+ */
+ public Mamba( final String rootdir, boolean installIfMissing) throws IOException, InterruptedException, ArchiveException, URISyntaxException
+ {
+ if (rootdir == null)
+ this.rootdir = BASE_PATH;
+ else
+ this.rootdir = rootdir;
+ this.mambaCommand = this.rootdir + MICROMAMBA_RELATIVE_PATH;
+ this.envsdir = this.rootdir + File.separator + "envs";
+ if ( Files.notExists( Paths.get( mambaCommand ) ) )
+ {
+
+ final File tempFile = File.createTempFile( "miniconda", ".tar.bz2" );
+ tempFile.deleteOnExit();
+ URL website = MambaInstallerUtils.redirectedURL(new URL(MICROMAMBA_URL));
+ ReadableByteChannel rbc = Channels.newChannel(website.openStream());
+ try (FileOutputStream fos = new FileOutputStream(tempFile)) {
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ }
+ final File tempTarFile = File.createTempFile( "miniconda", ".tar" );
+ tempTarFile.deleteOnExit();
+ MambaInstallerUtils.unBZip2(tempFile, tempTarFile);
+ File mambaBaseDir = new File(rootdir);
+ if (!mambaBaseDir.isDirectory() && !mambaBaseDir.mkdirs())
+ throw new IOException("Failed to create Micromamba default directory " + mambaBaseDir.getParentFile().getAbsolutePath());
+ MambaInstallerUtils.unTar(tempTarFile, mambaBaseDir);
+ if (!(new File(envsdir)).isDirectory() && !new File(envsdir).mkdirs())
+ throw new IOException("Failed to create Micromamba default envs directory " + envsdir);
+
+ }
+
+ // The following command will throw an exception if Conda does not work as
+ // expected.
+ boolean executableSet = new File(mambaCommand).setExecutable(true);
+ if (!executableSet)
+ throw new IOException("Cannot set file as executable due to missing permissions, "
+ + "please do it manually: " + mambaCommand);
+
+ // The following command will throw an exception if Conda does not work as
+ // expected.
+ getVersion();
+ }
+
/**
* Create a new Conda object. The root dir for Conda installation can be
* specified as {@code String}. If there is no directory found at the specified
@@ -177,15 +245,15 @@ public Conda() throws IOException, InterruptedException, ArchiveException, URISy
* @throws ArchiveException
* @throws URISyntaxException
*/
- public Conda( final String rootdir ) throws IOException, InterruptedException, ArchiveException, URISyntaxException
+ public Mamba( final String rootdir ) throws IOException, InterruptedException, ArchiveException, URISyntaxException
{
if (rootdir == null)
this.rootdir = BASE_PATH;
else
this.rootdir = rootdir;
- this.condaCommand = this.rootdir + CONDA_RELATIVE_PATH;
+ this.mambaCommand = this.rootdir + MICROMAMBA_RELATIVE_PATH;
this.envsdir = this.rootdir + File.separator + "envs";
- if ( Files.notExists( Paths.get( condaCommand ) ) )
+ if ( Files.notExists( Paths.get( mambaCommand ) ) )
{
final File tempFile = File.createTempFile( "miniconda", ".tar.bz2" );
@@ -209,10 +277,10 @@ public Conda( final String rootdir ) throws IOException, InterruptedException, A
// The following command will throw an exception if Conda does not work as
// expected.
- boolean executableSet = new File(condaCommand).setExecutable(true);
+ boolean executableSet = new File(mambaCommand).setExecutable(true);
if (!executableSet)
throw new IOException("Cannot set file as executable due to missing permissions, "
- + "please do it manually: " + condaCommand);
+ + "please do it manually: " + mambaCommand);
// The following command will throw an exception if Conda does not work as
// expected.
@@ -278,7 +346,7 @@ public void updateIn( final String envName, final String... args ) throws IOExce
{
final List< String > cmd = new ArrayList<>( Arrays.asList( "update", "-y", "-n", envName ) );
cmd.addAll( Arrays.asList( args ) );
- runConda( cmd.stream().toArray( String[]::new ) );
+ runMamba( cmd.stream().toArray( String[]::new ) );
}
/**
@@ -345,7 +413,7 @@ public void createWithYaml( final String envName, final String envYaml, final bo
{
if ( !isForceCreation && getEnvironmentNames().contains( envName ) )
throw new EnvironmentExistsException();
- runConda( "env", "create", "--prefix",
+ runMamba( "env", "create", "--prefix",
envsdir + File.separator + envName, "-f", envYaml, "-y" );
}
@@ -375,7 +443,7 @@ public void createWithYaml( final String envName, final String envYaml, final bo
{
if ( !isForceCreation && getEnvironmentNames().contains( envName ) )
throw new EnvironmentExistsException();
- runConda(consumer, "env", "create", "--prefix",
+ runMamba(consumer, "env", "create", "--prefix",
envsdir + File.separator + envName, "-f", envYaml, "-y", "-vv" );
}
@@ -416,7 +484,7 @@ public void create( final String envName, final boolean isForceCreation ) throws
{
if ( !isForceCreation && getEnvironmentNames().contains( envName ) )
throw new EnvironmentExistsException();
- runConda( "create", "-y", "-p", envsdir + File.separator + envName );
+ runMamba( "create", "-y", "-p", envsdir + File.separator + envName );
}
/**
@@ -466,12 +534,12 @@ public void create( final String envName, final boolean isForceCreation, final S
throw new EnvironmentExistsException();
final List< String > cmd = new ArrayList<>( Arrays.asList( "env", "create", "--force", "-p", envsdir + File.separator + envName ) );
cmd.addAll( Arrays.asList( args ) );
- runConda( cmd.stream().toArray( String[]::new ) );
+ runMamba( cmd.stream().toArray( String[]::new ) );
}
/**
* This method works as if the user runs {@code conda activate envName}. This
- * method internally calls {@link Conda#setEnvName(String)}.
+ * method internally calls {@link Mamba#setEnvName(String)}.
*
* @param envName
* The environment name to be activated.
@@ -558,7 +626,7 @@ public void installIn( final String envName, final String... args ) throws IOExc
{
final List< String > cmd = new ArrayList<>( Arrays.asList( "install", "-y", "-n", envName ) );
cmd.addAll( Arrays.asList( args ) );
- runConda( cmd.stream().toArray( String[]::new ) );
+ runMamba( cmd.stream().toArray( String[]::new ) );
}
/**
@@ -718,7 +786,7 @@ public static void runPythonIn( final File envFile, final String... args ) throw
public String getVersion() throws IOException, InterruptedException
{
final List< String > cmd = getBaseCommand();
- cmd.addAll( Arrays.asList( condaCommand, "--version" ) );
+ cmd.addAll( Arrays.asList( mambaCommand, "--version" ) );
final Process process = getBuilder( false ).command( cmd ).start();
if ( process.waitFor() != 0 )
throw new RuntimeException();
@@ -739,12 +807,12 @@ public String getVersion() throws IOException, InterruptedException
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
*/
- public void runConda(Consumer
+ * rootdir
+ * ├── bin
+ * │ ├── micromamba(.exe)
+ * │ ...
+ * ├── envs
+ * │ ├── your_env
+ * │ │ ├── python(.exe)
+ *
+ */
private final String rootdir;
-
+ /**
+ * Path to the folder that contains the directories
+ */
private final String envsdir;
-
+ /*
+ * Path to Python executable from the environment directory
+ */
+ final static String PYTHON_COMMAND = SystemUtils.IS_OS_WINDOWS ? "python.exe" : "bin/python";
+ /**
+ * Default name for a Python environment
+ */
public final static String DEFAULT_ENVIRONMENT_NAME = "base";
-
+ /**
+ * Relative path to the micromamba executable from the micromamba {@link #rootdir}
+ */
private final static String MICROMAMBA_RELATIVE_PATH = SystemUtils.IS_OS_WINDOWS ?
File.separator + "Library" + File.separator + "bin" + File.separator + "micromamba.exe"
: File.separator + "bin" + File.separator + "micromamba";
-
+ /**
+ * Path where Appose installs Micromamba by default
+ */
final public static String BASE_PATH = Paths.get(System.getProperty("user.home"), ".local", "share", "appose", "micromamba").toString();
-
+ /**
+ * Name of the folder inside the {@link #rootdir} that contains the different Python environments created by the Appose Micromamba
+ */
final public static String ENVS_NAME = "envs";
-
+ /**
+ * URL from where Micromamba is downloaded to be installed
+ */
public final static String MICROMAMBA_URL =
"https://micro.mamba.pm/api/micromamba/" + microMambaPlatform() + "/latest";
-
+ /**
+ * ID used to identify the text retrieved from the error stream when a consumer is used
+ */
public final static String ERR_STREAM_UUUID = UUID.randomUUID().toString();
+ /**
+ *
+ * @return a String that identifies the current OS to download the correct Micromamba version
+ */
private static String microMambaPlatform() {
String osName = System.getProperty("os.name");
if (osName.startsWith("Windows")) osName = "Windows";
@@ -119,16 +156,50 @@ private ProcessBuilder getBuilder( final boolean isInheritIO )
}
/**
- * Create a new Conda object. The root dir for the Micromamba installation
- * will be /user/.local/share/appose/micromamba.
- * If there is no directory found at the specified
- * path, Miniconda will be automatically installed in the path. It is expected
- * that the Conda installation has executable commands as shown below:
+ * Create a new {@link Mamba} object. The root dir for the Micromamba installation
+ * will be the default base path defined at {@link BASE_PATH}
+ * If there is no Micromamba found at the specified
+ * path, a {@link MambaInstallException} will be thrown
+ *
+ * It is expected that the Micromamba installation has executable commands as shown below:
*
*
- * CONDA_ROOT
- * ├── condabin
- * │ ├── conda(.bat)
+ * MAMBA_ROOT
+ * ├── bin
+ * │ ├── micromamba(.exe)
+ * │ ...
+ * ├── envs
+ * │ ├── your_env
+ * │ │ ├── python(.exe)
+ *
+ *
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ * @throws ArchiveException
+ * @throws URISyntaxException
+ * @throws MambaInstallException if Micromamba has not been already installed in the default path {@link #BASE_PATH}
+ */
+ public Mamba() throws IOException, InterruptedException, ArchiveException, URISyntaxException, MambaInstallException
+ {
+ this(BASE_PATH, false);
+ }
+
+ /**
+ * Create a new {@link Mamba} object. The root dir for the Micromamba installation
+ * will be the default base path defined at {@link BASE_PATH}
+ * If there is no Micromamba found at the specified
+ * path, it will be installed automatically.
+ *
+ * It is expected that the Micromamba installation has executable commands as shown below:
+ *
+ *
+ * MAMBA_ROOT
+ * ├── bin
+ * │ ├── micromamba(.exe)
* │ ...
* ├── envs
* │ ├── your_env
@@ -144,9 +215,9 @@ private ProcessBuilder getBuilder( final boolean isInheritIO )
* @throws ArchiveException
* @throws URISyntaxException
*/
- public Mamba() throws IOException, InterruptedException, ArchiveException, URISyntaxException
+ public Mamba(boolean installIfNeeded) throws IOException, InterruptedException, ArchiveException, URISyntaxException
{
- this(BASE_PATH);
+ this(BASE_PATH, installIfNeeded);
}
/**
@@ -176,46 +247,9 @@ public Mamba() throws IOException, InterruptedException, ArchiveException, URISy
* @throws ArchiveException
* @throws URISyntaxException
*/
- public Mamba( final String rootdir, boolean installIfMissing) throws IOException, InterruptedException, ArchiveException, URISyntaxException
+ public Mamba( final String rootdir) throws IOException, InterruptedException, ArchiveException, URISyntaxException
{
- if (rootdir == null)
- this.rootdir = BASE_PATH;
- else
- this.rootdir = rootdir;
- this.mambaCommand = this.rootdir + MICROMAMBA_RELATIVE_PATH;
- this.envsdir = this.rootdir + File.separator + "envs";
- if ( Files.notExists( Paths.get( mambaCommand ) ) )
- {
-
- final File tempFile = File.createTempFile( "miniconda", ".tar.bz2" );
- tempFile.deleteOnExit();
- URL website = MambaInstallerUtils.redirectedURL(new URL(MICROMAMBA_URL));
- ReadableByteChannel rbc = Channels.newChannel(website.openStream());
- try (FileOutputStream fos = new FileOutputStream(tempFile)) {
- fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
- }
- final File tempTarFile = File.createTempFile( "miniconda", ".tar" );
- tempTarFile.deleteOnExit();
- MambaInstallerUtils.unBZip2(tempFile, tempTarFile);
- File mambaBaseDir = new File(rootdir);
- if (!mambaBaseDir.isDirectory() && !mambaBaseDir.mkdirs())
- throw new IOException("Failed to create Micromamba default directory " + mambaBaseDir.getParentFile().getAbsolutePath());
- MambaInstallerUtils.unTar(tempTarFile, mambaBaseDir);
- if (!(new File(envsdir)).isDirectory() && !new File(envsdir).mkdirs())
- throw new IOException("Failed to create Micromamba default envs directory " + envsdir);
-
- }
-
- // The following command will throw an exception if Conda does not work as
- // expected.
- boolean executableSet = new File(mambaCommand).setExecutable(true);
- if (!executableSet)
- throw new IOException("Cannot set file as executable due to missing permissions, "
- + "please do it manually: " + mambaCommand);
-
- // The following command will throw an exception if Conda does not work as
- // expected.
- getVersion();
+ this(rootdir, false);
}
/**
@@ -226,8 +260,8 @@ public Mamba( final String rootdir, boolean installIfMissing) throws IOException
*
*
*
+ * @param installIfNeeded
+ * if Micormamba is not installed in the default dir {@link #BASE_PATH}, Appose installs it
+ * automatically
+ *
* @throws IOException
* If an I/O error occurs.
* @throws InterruptedException
@@ -214,20 +220,25 @@ public Mamba() throws IOException, InterruptedException, ArchiveException, URISy
* thrown.
* @throws ArchiveException
* @throws URISyntaxException
+ * @throws MambaInstallException if Micromamba has not been installed in the default path {@link #BASE_PATH}
+ * and 'installIfNeeded' is false
*/
- public Mamba(boolean installIfNeeded) throws IOException, InterruptedException, ArchiveException, URISyntaxException
+ public Mamba(boolean installIfNeeded) throws IOException,
+ InterruptedException, ArchiveException,
+ URISyntaxException, MambaInstallException
{
this(BASE_PATH, installIfNeeded);
}
/**
- * Create a new Conda object. The root dir for Conda installation can be
- * specified as {@code String}. If there is no directory found at the specified
- * path, Miniconda will be automatically installed in the path. It is expected
- * that the Conda installation has executable commands as shown below:
+ * Create a new Mamba object. The root dir for Mamba installation can be
+ * specified as {@code String}.
+ * If there is no Micromamba found at the specified path, a {@link MambaInstallException} will be thrown.
+ *
+ * It is expected that the Micromamba installation has executable commands as shown below:
*
*
* CONDA_ROOT
- * ├── condabin
- * │ ├── conda(.bat)
+ * ├── bin
+ * │ ├── micromamba(.exe)
* │ ...
* ├── envs
* │ ├── your_env
@@ -235,7 +269,7 @@ public Mamba( final String rootdir, boolean installIfMissing) throws IOException
*
*
* @param rootdir
- * The root dir for Conda installation.
+ * The root dir for Mamba installation.
* @throws IOException
* If an I/O error occurs.
* @throws InterruptedException
@@ -245,7 +279,7 @@ public Mamba( final String rootdir, boolean installIfMissing) throws IOException
* @throws ArchiveException
* @throws URISyntaxException
*/
- public Mamba( final String rootdir ) throws IOException, InterruptedException, ArchiveException, URISyntaxException
+ public Mamba( final String rootdir, boolean installIfMissing) throws IOException, InterruptedException, ArchiveException, URISyntaxException
{
if (rootdir == null)
this.rootdir = BASE_PATH;
From bcbb4cf6d77228994b38b292c2cd56fd329533dc Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Mon, 29 Jan 2024 19:22:12 +0100
Subject: [PATCH 036/120] reorganize method to make it more understandable
---
src/main/java/org/apposed/appose/Mamba.java | 122 +++++++++++++-------
1 file changed, 78 insertions(+), 44 deletions(-)
diff --git a/src/main/java/org/apposed/appose/Mamba.java b/src/main/java/org/apposed/appose/Mamba.java
index 62b47af..296018b 100644
--- a/src/main/java/org/apposed/appose/Mamba.java
+++ b/src/main/java/org/apposed/appose/Mamba.java
@@ -158,8 +158,7 @@ private ProcessBuilder getBuilder( final boolean isInheritIO )
/**
* Create a new {@link Mamba} object. The root dir for the Micromamba installation
* will be the default base path defined at {@link BASE_PATH}
- * If there is no Micromamba found at the specified
- * path, a {@link MambaInstallException} will be thrown
+ * If there is no Micromamba found at the base path {@link BASE_PATH}, a {@link MambaInstallException} will be thrown
*
* It is expected that the Micromamba installation has executable commands as shown below:
*
@@ -181,9 +180,11 @@ private ProcessBuilder getBuilder( final boolean isInheritIO )
* thrown.
* @throws ArchiveException
* @throws URISyntaxException
- * @throws MambaInstallException if Micromamba has not been already installed in the default path {@link #BASE_PATH}
+ * @throws MambaInstallException if Micromamba has not been installed in the default path {@link #BASE_PATH}
*/
- public Mamba() throws IOException, InterruptedException, ArchiveException, URISyntaxException, MambaInstallException
+ public Mamba() throws IOException,
+ InterruptedException, ArchiveException,
+ URISyntaxException, MambaInstallException
{
this(BASE_PATH, false);
}
@@ -192,7 +193,8 @@ public Mamba() throws IOException, InterruptedException, ArchiveException, URISy
* Create a new {@link Mamba} object. The root dir for the Micromamba installation
* will be the default base path defined at {@link BASE_PATH}
* If there is no Micromamba found at the specified
- * path, it will be installed automatically.
+ * path, it will be installed automatically if the parameter 'installIfNeeded'
+ * is true. If not a {@link MambaInstallException} will be thrown.
*
* It is expected that the Micromamba installation has executable commands as shown below:
*
@@ -206,6 +208,10 @@ public Mamba() throws IOException, InterruptedException, ArchiveException, URISy
* │ │ ├── python(.exe)
*
- * CONDA_ROOT
+ * MAMBA_ROOT
* ├── bin
* │ ├── micromamba(.exe)
* │ ...
@@ -246,20 +257,25 @@ public Mamba(boolean installIfNeeded) throws IOException, InterruptedException,
* thrown.
* @throws ArchiveException
* @throws URISyntaxException
+ * @throws MambaInstallException if Micromamba has not been installed in the provided 'rootdir' path
*/
- public Mamba( final String rootdir) throws IOException, InterruptedException, ArchiveException, URISyntaxException
+ public Mamba( final String rootdir) throws IOException,
+ InterruptedException, ArchiveException,
+ URISyntaxException, MambaInstallException
{
this(rootdir, false);
}
/**
* Create a new Conda object. The root dir for Conda installation can be
- * specified as {@code String}. If there is no directory found at the specified
- * path, Miniconda will be automatically installed in the path. It is expected
- * that the Conda installation has executable commands as shown below:
+ * specified as {@code String}.
+ * If there is no Micromamba found at the specified path, it will be installed automatically
+ * if the parameter 'installIfNeeded' is true. If not a {@link MambaInstallException} will be thrown.
+ *
+ * It is expected that the Conda installation has executable commands as shown below:
*
*
*
- * @throws IOException
- * If an I/O error occurs.
- * @throws InterruptedException
- * If the current thread is interrupted by another thread while it
- * is waiting, then the wait is ended and an InterruptedException is
- * thrown.
- * @throws ArchiveException
- * @throws URISyntaxException
- * @throws MambaInstallException if Micromamba has not been installed in the default path {@link #BASE_PATH}
*/
- public Mamba() throws IOException,
- InterruptedException, ArchiveException,
- URISyntaxException, MambaInstallException
- {
+ public Mamba() {
this(BASE_PATH);
}
@@ -272,21 +260,8 @@ public Mamba() throws IOException,
*
* @param rootdir
* The root dir for Mamba installation.
- * @throws IOException
- * If an I/O error occurs.
- * @throws InterruptedException
- * If the current thread is interrupted by another thread while it
- * is waiting, then the wait is ended and an InterruptedException is
- * thrown.
- * @throws ArchiveException
- * @throws URISyntaxException
- * @throws MambaInstallException if Micromamba has not been installed in the dir defined by 'rootdir'
- * and 'installIfNeeded' is false
*/
- public Mamba( final String rootdir) throws IOException,
- InterruptedException, ArchiveException,
- URISyntaxException, MambaInstallException
- {
+ public Mamba( final String rootdir) {
if (rootdir == null)
this.rootdir = BASE_PATH;
else
From b83da2ca41ba43613a43cf7e99161b21c5b09d49 Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Mon, 5 Feb 2024 13:58:56 +0100
Subject: [PATCH 058/120] add method to install if it is missing
---
src/main/java/org/apposed/appose/Builder.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/org/apposed/appose/Builder.java b/src/main/java/org/apposed/appose/Builder.java
index d661d93..e275ecb 100644
--- a/src/main/java/org/apposed/appose/Builder.java
+++ b/src/main/java/org/apposed/appose/Builder.java
@@ -51,6 +51,7 @@ public Environment build() {
try {
Mamba conda = new Mamba(Mamba.BASE_PATH);
+ conda.installMicromamba();
String envName = "appose";
if (conda.getEnvironmentNames().contains( envName )) {
// TODO: Should we update it? For now, we just use it.
From 28e61ed22c8bfb2c48670c341ced4b25edf7e612 Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Mon, 5 Feb 2024 14:02:33 +0100
Subject: [PATCH 059/120] incorrect logic
---
src/main/java/org/apposed/appose/Mamba.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/main/java/org/apposed/appose/Mamba.java b/src/main/java/org/apposed/appose/Mamba.java
index 71e43b6..71c5110 100644
--- a/src/main/java/org/apposed/appose/Mamba.java
+++ b/src/main/java/org/apposed/appose/Mamba.java
@@ -1006,7 +1006,6 @@ public static void runPythonIn( final File envFile, final String... args ) throw
*/
public String getVersion() throws IOException, InterruptedException, MambaInstallException
{
- checkMambaInstalled();
if (!installed) throw new MambaInstallException("Micromamba is not installed");
final List< String > cmd = getBaseCommand();
cmd.addAll( Arrays.asList( mambaCommand, "--version" ) );
From dd8cdb7fda05b2d06e5e306b47d482f2a02efa59 Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Mon, 5 Feb 2024 14:19:05 +0100
Subject: [PATCH 060/120] remove blocking code
---
src/main/java/org/apposed/appose/Mamba.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/main/java/org/apposed/appose/Mamba.java b/src/main/java/org/apposed/appose/Mamba.java
index 71c5110..c15256d 100644
--- a/src/main/java/org/apposed/appose/Mamba.java
+++ b/src/main/java/org/apposed/appose/Mamba.java
@@ -1006,7 +1006,6 @@ public static void runPythonIn( final File envFile, final String... args ) throw
*/
public String getVersion() throws IOException, InterruptedException, MambaInstallException
{
- if (!installed) throw new MambaInstallException("Micromamba is not installed");
final List< String > cmd = getBaseCommand();
cmd.addAll( Arrays.asList( mambaCommand, "--version" ) );
final Process process = getBuilder( false ).command( cmd ).start();
From ed58aff4546f339dece0c3beb816b201c507ba40 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
- * CONDA_ROOT
+ * MAMBA_ROOT
* ├── bin
* │ ├── micromamba(.exe)
* │ ...
@@ -269,7 +285,10 @@ public Mamba( final String rootdir) throws IOException, InterruptedException, Ar
*
*
* @param rootdir
- * The root dir for Mamba installation.
+ * The root dir for Mamba installation.
+ * @param installIfNeeded
+ * if Micormamba is not installed in the path specified by 'rootdir', Appose installs it
+ * automatically
* @throws IOException
* If an I/O error occurs.
* @throws InterruptedException
@@ -278,36 +297,55 @@ public Mamba( final String rootdir) throws IOException, InterruptedException, Ar
* thrown.
* @throws ArchiveException
* @throws URISyntaxException
+ * @throws MambaInstallException if Micromamba has not been installed in the dir defined by 'rootdir'
+ * and 'installIfNeeded' is false
*/
- public Mamba( final String rootdir, boolean installIfMissing) throws IOException, InterruptedException, ArchiveException, URISyntaxException
+ public Mamba( final String rootdir, boolean installIfMissing) throws IOException,
+ InterruptedException, ArchiveException,
+ URISyntaxException, MambaInstallException
{
if (rootdir == null)
this.rootdir = BASE_PATH;
else
this.rootdir = rootdir;
this.mambaCommand = this.rootdir + MICROMAMBA_RELATIVE_PATH;
- this.envsdir = this.rootdir + File.separator + "envs";
- if ( Files.notExists( Paths.get( mambaCommand ) ) )
- {
+ this.envsdir = this.rootdir + File.separator + ENVS_NAME;
+ boolean filesExist = Files.notExists( Paths.get( mambaCommand ) );
+ if (!filesExist && !installIfMissing)
+ throw new MambaInstallException();
+ boolean installed = true;
+ try {
+ getVersion();
+ } catch (RuntimeException ex) {
+ installed = false;
+ }
+ if (!installed && !installIfMissing)
+ throw new MambaInstallException("Even though Micromamba installation has been found, "
+ + "it did not work as expected. Re-installation is advised. Path of installation found: " + this.rootdir);
+ if (installed)
+ return;
+ installMicromamba();
+ }
+
+ private void installMicromamba() throws IOException, InterruptedException, ArchiveException, URISyntaxException {
- final File tempFile = File.createTempFile( "miniconda", ".tar.bz2" );
- tempFile.deleteOnExit();
- URL website = MambaInstallerUtils.redirectedURL(new URL(MICROMAMBA_URL));
- ReadableByteChannel rbc = Channels.newChannel(website.openStream());
- try (FileOutputStream fos = new FileOutputStream(tempFile)) {
- fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
- }
- final File tempTarFile = File.createTempFile( "miniconda", ".tar" );
- tempTarFile.deleteOnExit();
- MambaInstallerUtils.unBZip2(tempFile, tempTarFile);
- File mambaBaseDir = new File(rootdir);
- if (!mambaBaseDir.isDirectory() && !mambaBaseDir.mkdirs())
- throw new IOException("Failed to create Micromamba default directory " + mambaBaseDir.getParentFile().getAbsolutePath());
- MambaInstallerUtils.unTar(tempTarFile, mambaBaseDir);
- if (!(new File(envsdir)).isDirectory() && !new File(envsdir).mkdirs())
- throw new IOException("Failed to create Micromamba default envs directory " + envsdir);
-
+ final File tempFile = File.createTempFile( "micromamba", ".tar.bz2" );
+ tempFile.deleteOnExit();
+ URL website = MambaInstallerUtils.redirectedURL(new URL(MICROMAMBA_URL));
+ ReadableByteChannel rbc = Channels.newChannel(website.openStream());
+ try (FileOutputStream fos = new FileOutputStream(tempFile)) {
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
}
+ final File tempTarFile = File.createTempFile( "micromamba", ".tar" );
+ tempTarFile.deleteOnExit();
+ MambaInstallerUtils.unBZip2(tempFile, tempTarFile);
+ File mambaBaseDir = new File(rootdir);
+ if (!mambaBaseDir.isDirectory() && !mambaBaseDir.mkdirs())
+ throw new IOException("Failed to create Micromamba default directory " + mambaBaseDir.getParentFile().getAbsolutePath());
+ MambaInstallerUtils.unTar(tempTarFile, mambaBaseDir);
+ if (!(new File(envsdir)).isDirectory() && !new File(envsdir).mkdirs())
+ throw new IOException("Failed to create Micromamba default envs directory " + envsdir);
+
// The following command will throw an exception if Conda does not work as
// expected.
@@ -315,10 +353,6 @@ public Mamba( final String rootdir, boolean installIfMissing) throws IOException
if (!executableSet)
throw new IOException("Cannot set file as executable due to missing permissions, "
+ "please do it manually: " + mambaCommand);
-
- // The following command will throw an exception if Conda does not work as
- // expected.
- getVersion();
}
public String getEnvsDir() {
@@ -748,13 +782,13 @@ public void runPythonIn( final String envName, final String... args ) throws IOE
if ( envName.equals( DEFAULT_ENVIRONMENT_NAME ) )
cmd.add( PYTHON_COMMAND );
else
- cmd.add( Paths.get( "envs", envName, PYTHON_COMMAND ).toString() );
+ cmd.add( Paths.get( ENVS_NAME, envName, PYTHON_COMMAND ).toString() );
cmd.addAll( Arrays.asList( args ) );
final ProcessBuilder builder = getBuilder( true );
if ( SystemUtils.IS_OS_WINDOWS )
{
final Map< String, String > envs = builder.environment();
- final String envDir = Paths.get( rootdir, "envs", envName ).toString();
+ final String envDir = Paths.get( rootdir, ENVS_NAME, envName ).toString();
envs.put( "Path", envDir + ";" + envs.get( "Path" ) );
envs.put( "Path", Paths.get( envDir, "Scripts" ).toString() + ";" + envs.get( "Path" ) );
envs.put( "Path", Paths.get( envDir, "Library" ).toString() + ";" + envs.get( "Path" ) );
From 4b6b3f96c9dd88b9e854c2fd331280d7ec8602bb Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Mon, 29 Jan 2024 19:33:42 +0100
Subject: [PATCH 037/120] keep increaseing robustness
---
src/main/java/org/apposed/appose/Mamba.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/apposed/appose/Mamba.java b/src/main/java/org/apposed/appose/Mamba.java
index 296018b..ae91af2 100644
--- a/src/main/java/org/apposed/appose/Mamba.java
+++ b/src/main/java/org/apposed/appose/Mamba.java
@@ -412,7 +412,7 @@ public void update( final String... args ) throws IOException, InterruptedExcept
*/
public void updateIn( final String envName, final String... args ) throws IOException, InterruptedException
{
- final List< String > cmd = new ArrayList<>( Arrays.asList( "update", "-y", "-n", envName ) );
+ final List< String > cmd = new ArrayList<>( Arrays.asList( "update", "-y", "-p", this.envsdir + File.separator + envName ) );
cmd.addAll( Arrays.asList( args ) );
runMamba( cmd.stream().toArray( String[]::new ) );
}
From cbe14beed062b1b27035980a63b83fe05aca7652 Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Tue, 30 Jan 2024 00:13:40 +0100
Subject: [PATCH 038/120] delete unnecessary method
---
src/main/java/org/apposed/appose/Mamba.java | 21 ---------------------
1 file changed, 21 deletions(-)
diff --git a/src/main/java/org/apposed/appose/Mamba.java b/src/main/java/org/apposed/appose/Mamba.java
index ae91af2..63fa6bc 100644
--- a/src/main/java/org/apposed/appose/Mamba.java
+++ b/src/main/java/org/apposed/appose/Mamba.java
@@ -555,27 +555,6 @@ public void create( final String envName, final boolean isForceCreation ) throws
runMamba( "create", "-y", "-p", envsdir + File.separator + envName );
}
- /**
- * Run {@code conda create} to create a new conda environment with a list of
- * specified packages.
- *
- * @param envName
- * The environment name to be created.
- * @param args
- * The list of packages to be installed on environment creation and
- * extra parameters as {@code String...}.
- * @throws IOException
- * If an I/O error occurs.
- * @throws InterruptedException
- * If the current thread is interrupted by another thread while it
- * is waiting, then the wait is ended and an InterruptedException is
- * thrown.
- */
- public void create( final String envName, final String... args ) throws IOException, InterruptedException
- {
- create( envName, false, args );
- }
-
/**
* Run {@code conda create} to create a new conda environment with a list of
* specified packages.
From 923817a973c92df6f2f2d2d83cadd5339c68ff54 Mon Sep 17 00:00:00 2001
From: carlosuc3m <100329787@alumnos.uc3m.es>
Date: Tue, 30 Jan 2024 00:43:03 +0100
Subject: [PATCH 039/120] improve robustness of methods
---
src/main/java/org/apposed/appose/Mamba.java | 71 +++++++++++++++++++--
1 file changed, 64 insertions(+), 7 deletions(-)
diff --git a/src/main/java/org/apposed/appose/Mamba.java b/src/main/java/org/apposed/appose/Mamba.java
index 63fa6bc..e30bf05 100644
--- a/src/main/java/org/apposed/appose/Mamba.java
+++ b/src/main/java/org/apposed/appose/Mamba.java
@@ -48,6 +48,7 @@
import java.util.Calendar;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -560,14 +561,16 @@ public void create( final String envName, final boolean isForceCreation ) throws
* specified packages.
*
* @param envName
- * The environment name to be created.
+ * The environment name to be created. CAnnot be null.
* @param isForceCreation
* Force creation of the environment if {@code true}. If this value
* is {@code false} and an environment with the specified name
* already exists, throw an {@link EnvironmentExistsException}.
- * @param args
- * The list of packages to be installed on environment creation and
- * extra parameters as {@code String...}.
+ * @param channels
+ * the channels from where the packages can be installed. Can be null
+ * @param packages
+ * the packages that want to be installed during env creation. They can contain the version.
+ * For example, "python" or "python=3.10.1", "numpy" or "numpy=1.20.1". CAn be null if no packages want to be installed
* @throws IOException
* If an I/O error occurs.
* @throws InterruptedException
@@ -575,12 +578,16 @@ public void create( final String envName, final boolean isForceCreation ) throws
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
*/
- public void create( final String envName, final boolean isForceCreation, final String... args ) throws IOException, InterruptedException
+ public void create( final String envName, final boolean isForceCreation, List
- * MAMBA_ROOT
- * ├── bin
- * │ ├── micromamba(.exe)
- * │ ...
- * ├── envs
- * │ ├── your_env
- * │ │ ├── python(.exe)
- *
- *
- * @param installIfNeeded
- * if Micormamba is not installed in the default dir {@link #BASE_PATH}, Appose installs it
- * automatically
- *
- * @throws IOException
- * If an I/O error occurs.
- * @throws InterruptedException
- * If the current thread is interrupted by another thread while it
- * is waiting, then the wait is ended and an InterruptedException is
- * thrown.
- * @throws ArchiveException
- * @throws URISyntaxException
- * @throws MambaInstallException if Micromamba has not been installed in the default path {@link #BASE_PATH}
- * and 'installIfNeeded' is false
- */
- public Mamba(boolean installIfNeeded) throws IOException,
- InterruptedException, ArchiveException,
- URISyntaxException, MambaInstallException
- {
- this(BASE_PATH, installIfNeeded);
- }
-
- /**
- * Create a new Mamba object. The root dir for Mamba installation can be
- * specified as {@code String}.
- * If there is no Micromamba found at the specified path, a {@link MambaInstallException} will be thrown.
- *
- * It is expected that the Micromamba installation has executable commands as shown below:
- *
- *
- * MAMBA_ROOT
- * ├── bin
- * │ ├── micromamba(.exe)
- * │ ...
- * ├── envs
- * │ ├── your_env
- * │ │ ├── python(.exe)
- *
- *
- * @param rootdir
- * The root dir for Mamba installation.
- * @throws IOException
- * If an I/O error occurs.
- * @throws InterruptedException
- * If the current thread is interrupted by another thread while it
- * is waiting, then the wait is ended and an InterruptedException is
- * thrown.
- * @throws ArchiveException
- * @throws URISyntaxException
- * @throws MambaInstallException if Micromamba has not been installed in the provided 'rootdir' path
- */
- public Mamba( final String rootdir) throws IOException,
- InterruptedException, ArchiveException,
- URISyntaxException, MambaInstallException
- {
- this(rootdir, false);
+ this(BASE_PATH);
}
/**
@@ -345,9 +272,6 @@ public Mamba( final String rootdir) throws IOException,
*
* @param rootdir
* The root dir for Mamba installation.
- * @param installIfNeeded
- * if Micormamba is not installed in the path specified by 'rootdir', Appose installs it
- * automatically
* @throws IOException
* If an I/O error occurs.
* @throws InterruptedException
@@ -359,7 +283,7 @@ public Mamba( final String rootdir) throws IOException,
* @throws MambaInstallException if Micromamba has not been installed in the dir defined by 'rootdir'
* and 'installIfNeeded' is false
*/
- public Mamba( final String rootdir, boolean installIfMissing) throws IOException,
+ public Mamba( final String rootdir) throws IOException,
InterruptedException, ArchiveException,
URISyntaxException, MambaInstallException
{
@@ -370,20 +294,29 @@ public Mamba( final String rootdir, boolean installIfMissing) throws IOException
this.mambaCommand = this.rootdir + MICROMAMBA_RELATIVE_PATH;
this.envsdir = this.rootdir + File.separator + ENVS_NAME;
boolean filesExist = Files.notExists( Paths.get( mambaCommand ) );
- if (!filesExist && !installIfMissing)
- throw new MambaInstallException();
- boolean installed = true;
+ if (!filesExist)
+ return;
try {
getVersion();
} catch (Exception ex) {
- installed = false;
- }
- if (!installed && !installIfMissing)
- throw new MambaInstallException("Even though Micromamba installation has been found, "
- + "it did not work as expected. Re-installation is advised. Path of installation found: " + this.rootdir);
- if (installed)
return;
- installMicromamba();
+ }
+ installed = true;
+ }
+
+ /**
+ * Check whether micromamba is installed or not to be able to use the instance of {@link Mamba}
+ * @return whether micromamba is installed or not to be able to use the instance of {@link Mamba}
+ */
+ public boolean checkMambaInstalled() {
+ try {
+ getVersion();
+ this.installed = true;
+ } catch (Exception ex) {
+ this.installed = false;
+ return false;
+ }
+ return true;
}
/**
@@ -477,7 +410,22 @@ private void decompressMicromamba(final File tempFile) throws FileNotFoundExcept
+ "please do it manually: " + mambaCommand);
}
- private void installMicromamba() throws IOException, InterruptedException, ArchiveException, URISyntaxException {
+ /**
+ * Install Micromamba automatically
+ * @throws IOException
+ * If an I/O error occurs.
+ * @throws InterruptedException
+ * If the current thread is interrupted by another thread while it
+ * is waiting, then the wait is ended and an InterruptedException is
+ * thrown.
+ * @throws ArchiveException
+ * @throws URISyntaxException
+ * @throws MambaInstallException if Micromamba has not been installed in the dir defined by 'rootdir'
+ * and 'installIfNeeded' is false
+ */
+ public void installMicromamba() throws IOException, InterruptedException, ArchiveException, URISyntaxException {
+ checkMambaInstalled();
+ if (installed) return;
decompressMicromamba(downloadMicromamba());
}
@@ -514,9 +462,12 @@ private static List< String > getBaseCommand()
* If the current thread is interrupted by another thread while it
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
+ * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used
*/
- public void update( final String... args ) throws IOException, InterruptedException
+ public void update( final String... args ) throws IOException, InterruptedException, MambaInstallException
{
+ checkMambaInstalled();
+ if (!installed) throw new MambaInstallException("Micromamba is not installed");
updateIn( envName, args );
}
@@ -535,9 +486,12 @@ public void update( final String... args ) throws IOException, InterruptedExcept
* If the current thread is interrupted by another thread while it
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
+ * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used
*/
- public void updateIn( final String envName, final String... args ) throws IOException, InterruptedException
+ public void updateIn( final String envName, final String... args ) throws IOException, InterruptedException, MambaInstallException
{
+ checkMambaInstalled();
+ if (!installed) throw new MambaInstallException("Micromamba is not installed");
final List< String > cmd = new ArrayList<>( Arrays.asList( "update", "-p", this.envsdir + File.separator + envName ) );
cmd.addAll( Arrays.asList( args ) );
if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes");
@@ -557,9 +511,12 @@ public void updateIn( final String envName, final String... args ) throws IOExce
* If the current thread is interrupted by another thread while it
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
+ * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used
*/
- public void createWithYaml( final String envName, final String envYaml ) throws IOException, InterruptedException
+ public void createWithYaml( final String envName, final String envYaml ) throws IOException, InterruptedException, MambaInstallException
{
+ checkMambaInstalled();
+ if (!installed) throw new MambaInstallException("Micromamba is not installed");
createWithYaml(envName, envYaml, false);
}
@@ -582,9 +539,14 @@ public void createWithYaml( final String envName, final String envYaml ) throws
* If the current thread is interrupted by another thread while it
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
+ * @throws RuntimeException
+ * If there is any error running the commands
+ * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used
*/
- public void createWithYaml( final String envName, final String envYaml, final boolean isForceCreation) throws IOException, InterruptedException
+ public void createWithYaml( final String envName, final String envYaml, final boolean isForceCreation) throws IOException, InterruptedException, RuntimeException, MambaInstallException
{
+ checkMambaInstalled();
+ if (!installed) throw new MambaInstallException("Micromamba is not installed");
if ( !isForceCreation && getEnvironmentNames().contains( envName ) )
throw new EnvironmentExistsException();
runMamba("env", "create", "--prefix",
@@ -602,9 +564,12 @@ public void createWithYaml( final String envName, final String envYaml, final bo
* If the current thread is interrupted by another thread while it
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
+ * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used
*/
- public void create( final String envName ) throws IOException, InterruptedException
+ public void create( final String envName ) throws IOException, InterruptedException, MambaInstallException
{
+ checkMambaInstalled();
+ if (!installed) throw new MambaInstallException("Micromamba is not installed");
create( envName, false );
}
@@ -623,9 +588,14 @@ public void create( final String envName ) throws IOException, InterruptedExcept
* If the current thread is interrupted by another thread while it
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
+ * @throws RuntimeException
+ * If there is any error running the commands
+ * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used
*/
- public void create( final String envName, final boolean isForceCreation ) throws IOException, InterruptedException
+ public void create( final String envName, final boolean isForceCreation ) throws IOException, InterruptedException, RuntimeException, MambaInstallException
{
+ checkMambaInstalled();
+ if (!installed) throw new MambaInstallException("Micromamba is not installed");
if ( !isForceCreation && getEnvironmentNames().contains( envName ) )
throw new EnvironmentExistsException();
runMamba( "create", "-y", "-p", envsdir + File.separator + envName );
@@ -646,9 +616,12 @@ public void create( final String envName, final boolean isForceCreation ) throws
* If the current thread is interrupted by another thread while it
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
+ * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used
*/
- public void create( final String envName, final String... args ) throws IOException, InterruptedException
+ public void create( final String envName, final String... args ) throws IOException, InterruptedException, MambaInstallException
{
+ checkMambaInstalled();
+ if (!installed) throw new MambaInstallException("Micromamba is not installed");
create( envName, false, args );
}
@@ -671,9 +644,12 @@ public void create( final String envName, final String... args ) throws IOExcept
* If the current thread is interrupted by another thread while it
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
+ * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used
*/
- public void create( final String envName, final boolean isForceCreation, final String... args ) throws IOException, InterruptedException
+ public void create( final String envName, final boolean isForceCreation, final String... args ) throws IOException, InterruptedException, MambaInstallException
{
+ checkMambaInstalled();
+ if (!installed) throw new MambaInstallException("Micromamba is not installed");
if ( !isForceCreation && getEnvironmentNames().contains( envName ) )
throw new EnvironmentExistsException();
final List< String > cmd = new ArrayList<>( Arrays.asList( "create", "-p", envsdir + File.separator + envName ) );
@@ -703,9 +679,14 @@ public void create( final String envName, final boolean isForceCreation, final S
* If the current thread is interrupted by another thread while it
* is waiting, then the wait is ended and an InterruptedException is
* thrown.
+ * @throws RuntimeException
+ * If there is any error running the commands
+ * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used
*/
- public void create( final String envName, final boolean isForceCreation, List{"-v", "--enable-everything"}
.
+ * (e.g. {"-v", "--enable-everything"}
).
* @return The newly created service.
* @see #groovy To create a service for Groovy script execution.
* @see #python() To create a service for Python script execution.
@@ -183,7 +183,7 @@ default Service service(List
* MAMBA_ROOT * ├── bin @@ -252,7 +252,6 @@ private ProcessBuilder getBuilder( final boolean isInheritIO ) * │ ├── your_env * │ │ ├── python(.exe) *- * */ public Mamba() { this(BASE_PATH); @@ -263,9 +262,9 @@ public Mamba() { * specified as {@code String}. * If there is no Micromamba found at the specified path, it will be installed automatically * if the parameter 'installIfNeeded' is true. If not a {@link MambaInstallException} will be thrown. - * + *
* It is expected that the Conda installation has executable commands as shown below: - * + *
** MAMBA_ROOT * ├── bin @@ -279,7 +278,7 @@ public Mamba() { * @param rootdir * The root dir for Mamba installation. */ - public Mamba( final String rootdir) { + public Mamba(final String rootdir) { if (rootdir == null) this.rootdir = BASE_PATH; else @@ -372,8 +371,8 @@ private File downloadMicromamba() throws IOException, URISyntaxException { Thread dwnldThread = new Thread(() -> { try ( ReadableByteChannel rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(tempFile); - ) { + FileOutputStream fos = new FileOutputStream(tempFile) + ) { new FileDownloader(rbc, fos).call(currentThread); } catch (IOException | InterruptedException e) { e.printStackTrace(); @@ -388,7 +387,7 @@ private File downloadMicromamba() throws IOException, URISyntaxException { } private void decompressMicromamba(final File tempFile) - throws FileNotFoundException, IOException, ArchiveException, InterruptedException { + throws IOException, ArchiveException, InterruptedException { final File tempTarFile = File.createTempFile( "micromamba", ".tar" ); tempTarFile.deleteOnExit(); MambaInstallerUtils.unBZip2(tempFile, tempTarFile); @@ -433,7 +432,6 @@ public String getEnvsDir() { * * @return {@code \{"cmd.exe", "/c"\}} for Windows and an empty list for * Mac/Linux. - * @throws IOException */ private static List< String > getBaseCommand() { @@ -489,7 +487,7 @@ public void updateIn( final String envName, final String... args ) throws IOExce final List< String > cmd = new ArrayList<>( Arrays.asList( "update", "-p", this.envsdir + File.separator + envName ) ); cmd.addAll( Arrays.asList( args ) ); if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); - runMamba( cmd.stream().toArray( String[]::new ) ); + runMamba(cmd.toArray(new String[0])); } /** @@ -521,8 +519,6 @@ public void createWithYaml( final String envName, final String envYaml ) throws * The environment name to be created. It should not be a path, just the name. * @param envYaml * The environment yaml file containing the information required to build it - * @param envName - * The environment name to be created. * @param isForceCreation * Force creation of the environment if {@code true}. If this value * is {@code false} and an environment with the specified name @@ -655,7 +651,7 @@ public void create( final String envName, final boolean isForceCreation, final S final List< String > cmd = new ArrayList<>( Arrays.asList( "create", "-p", envsdir + File.separator + envName ) ); cmd.addAll( Arrays.asList( args ) ); if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); - runMamba( cmd.stream().toArray( String[]::new ) ); + runMamba(cmd.toArray(new String[0])); if (this.checkDependencyInEnv(envsdir + File.separator + envName, "python")) installApposeFromSource(envsdir + File.separator + envName); } @@ -693,12 +689,12 @@ public void create( final String envName, final boolean isForceCreation, List*/ private final String rootdir; - /** - * Path to the folder that contains the directories - */ - private final String envsdir; /** * Progress made on the download from the Internet of the micromamba software. VAlue between 0 and 1. * @@ -167,10 +163,6 @@ class Mamba { * Path where Appose installs Micromamba by default */ final public static String BASE_PATH = Paths.get(System.getProperty("user.home"), ".local", "share", "appose", "micromamba").toString(); - /** - * Name of the folder inside the {@link #rootdir} that contains the different Conda environments created by the Appose Micromamba - */ - final public static String ENVS_NAME = "envs"; /** * URL from where Micromamba is downloaded to be installed */ @@ -290,7 +282,6 @@ public Mamba(final String rootdir) { else this.rootdir = rootdir; this.mambaCommand = new File(this.rootdir + MICROMAMBA_RELATIVE_PATH).getAbsolutePath(); - this.envsdir = Paths.get(rootdir, ENVS_NAME).toAbsolutePath().toString(); boolean filesExist = Files.notExists( Paths.get( mambaCommand ) ); if (!filesExist) return; @@ -412,8 +403,6 @@ private void decompressMicromamba(final File tempFile) throws IOException, Inter throw new IOException("Failed to create Micromamba default directory " + mambaBaseDir.getParentFile().getAbsolutePath() + ". Please try installing it in another directory."); MambaInstallerUtils.unTar(tempTarFile, mambaBaseDir); - if (!(new File(envsdir)).isDirectory() && !new File(envsdir).mkdirs()) - throw new IOException("Failed to create Micromamba default envs directory " + envsdir); boolean executableSet = new File(mambaCommand).setExecutable(true); if (!executableSet) throw new IOException("Cannot set file as executable due to missing permissions, " @@ -434,14 +423,6 @@ public void installMicromamba() throws IOException, InterruptedException, URISyn if (isMambaInstalled()) return; decompressMicromamba(downloadMicromamba()); } - - public String getEnvsDir() { - return this.envsdir; - } - - public String getEnvDir(String envName) { - return Paths.get(getEnvsDir(), envName).toString(); - } /** * Returns {@code \{"cmd.exe", "/c"\}} for Windows and an empty list for @@ -461,9 +442,9 @@ private static List< String > getBaseCommand() /** * Run {@code conda update} in the specified environment. A list of packages to * update and extra parameters can be specified as {@code args}. - * - * @param envName - * The environment name to be used for the update command. + * + * @param envDir + * The directory within which the environment will be updated. * @param args * The list of packages to be updated and extra parameters as * {@code String...}. @@ -475,10 +456,10 @@ private static List< String > getBaseCommand() * thrown. * @throws IllegalStateException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used */ - public void updateIn( final String envName, final String... args ) throws IOException, InterruptedException + public void updateIn( final File envDir, final String... args ) throws IOException, InterruptedException { checkMambaInstalled(); - final List< String > cmd = new ArrayList<>( Arrays.asList( "update", "-p", getEnvDir(envName) ) ); + final List< String > cmd = new ArrayList<>( Arrays.asList( "update", "--prefix", envDir.getAbsolutePath() ) ); cmd.addAll( Arrays.asList( args ) ); if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); runMamba(cmd.toArray(new String[0])); From 0d96b4f3e02b399dc7c364915b8fc18a5528b7e2 Mon Sep 17 00:00:00 2001 From: Curtis Ruedencmd = new ArrayList<>( Arrays.asList( "create", "-p", envsdir + File.separator + envName ) ); - if (channels == null) channels = new ArrayList (); + if (channels == null) channels = new ArrayList<>(); for (String chan : channels) { cmd.add("-c"); cmd.add(chan);} - if (packages == null) packages = new ArrayList (); - for (String pack : packages) { cmd.add(pack);} + if (packages == null) packages = new ArrayList<>(); + cmd.addAll(packages); if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); - runMamba( cmd.stream().toArray( String[]::new ) ); + runMamba(cmd.toArray(new String[0])); if (this.checkDependencyInEnv(envsdir + File.separator + envName, "python")) installApposeFromSource(envsdir + File.separator + envName); } @@ -713,7 +709,7 @@ public void create( final String envName, final boolean isForceCreation, List channels, List if (!installed) throw new MambaInstallException("Micromamba is not installed"); Objects.requireNonNull(envName, "The name of the environment of interest needs to be provided."); final List< String > cmd = new ArrayList<>( Arrays.asList( "install", "-y", "-p", this.envsdir + File.separator + envName ) ); - if (channels == null) channels = new ArrayList (); + if (channels == null) channels = new ArrayList<>(); for (String chan : channels) { cmd.add("-c"); cmd.add(chan);} - if (packages == null) packages = new ArrayList (); - for (String pack : packages) { cmd.add(pack);} - runMamba( cmd.stream().toArray( String[]::new ) ); + if (packages == null) packages = new ArrayList<>(); + cmd.addAll(packages); + runMamba(cmd.toArray(new String[0])); } /** @@ -862,7 +858,7 @@ public void installIn( final String envName, final String... args ) throws IOExc final List< String > cmd = new ArrayList<>( Arrays.asList( "install", "-p", this.envsdir + File.separator + envName ) ); cmd.addAll( Arrays.asList( args ) ); if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); - runMamba( cmd.stream().toArray( String[]::new ) ); + runMamba(cmd.toArray(new String[0])); } /** @@ -910,7 +906,7 @@ public void pipInstallIn( final String envName, final String... args ) throws IO if (!installed) throw new MambaInstallException("Micromamba is not installed"); final List< String > cmd = new ArrayList<>( Arrays.asList( "-m", "pip", "install" ) ); cmd.addAll( Arrays.asList( args ) ); - runPythonIn( envName, cmd.stream().toArray( String[]::new ) ); + runPythonIn( envName, cmd.toArray(new String[0])); } /** @@ -937,17 +933,18 @@ public void runPython( final String... args ) throws IOException, InterruptedExc } /** + * Runs a Python command in the specified environment. This method automatically + * sets environment variables associated with the specified environment. In + * Windows, this method also sets the {@code PATH} environment variable so that + * the specified environment runs as expected. + * * TODO stop process if the thread is interrupted, same as with mamba, look for runmamna method for example * TODO stop process if the thread is interrupted, same as with mamba, look for runmamna method for example * TODO stop process if the thread is interrupted, same as with mamba, look for runmamna method for example * TODO stop process if the thread is interrupted, same as with mamba, look for runmamna method for example * TODO stop process if the thread is interrupted, same as with mamba, look for runmamna method for example - * - * Run a Python command in the specified environment. This method automatically - * sets environment variables associated with the specified environment. In - * Windows, this method also sets the {@code PATH} environment variable so that - * the specified environment runs as expected. - * + *
+ * * @param envName * The environment name used to run the Python command. * @param args @@ -965,7 +962,7 @@ public void runPythonIn( final String envName, final String... args ) throws IOE checkMambaInstalled(); if (!installed) throw new MambaInstallException("Micromamba is not installed"); final List< String > cmd = getBaseCommand(); - ListargsList = new ArrayList (); + List argsList = new ArrayList<>(); String envDir; if (new File(envName, PYTHON_COMMAND).isFile()) { argsList.add( coverArgWithDoubleQuotes(Paths.get( envName, PYTHON_COMMAND ).toAbsolutePath().toString()) ); @@ -976,11 +973,11 @@ public void runPythonIn( final String envName, final String... args ) throws IOE } else throw new IOException("The environment provided (" + envName + ") does not exist or does not contain a Python executable (" + PYTHON_COMMAND + ")."); - argsList.addAll( Arrays.asList( args ).stream().map(aa -> { + argsList.addAll( Arrays.stream( args ).map(aa -> { if (aa.contains(" ") && SystemUtils.IS_OS_WINDOWS) return coverArgWithDoubleQuotes(aa); else return aa; }).collect(Collectors.toList()) ); - boolean containsSpaces = argsList.stream().filter(aa -> aa.contains(" ")).collect(Collectors.toList()).size() > 0; + boolean containsSpaces = argsList.stream().anyMatch(aa -> aa.contains(" ")); if (!containsSpaces || !SystemUtils.IS_OS_WINDOWS) cmd.addAll(argsList); else cmd.add(surroundWithQuotes(argsList)); @@ -990,9 +987,9 @@ public void runPythonIn( final String envName, final String... args ) throws IOE { final Map< String, String > envs = builder.environment(); envs.put( "Path", envDir + ";" + envs.get( "Path" ) ); - envs.put( "Path", Paths.get( envDir, "Scripts" ).toString() + ";" + envs.get( "Path" ) ); - envs.put( "Path", Paths.get( envDir, "Library" ).toString() + ";" + envs.get( "Path" ) ); - envs.put( "Path", Paths.get( envDir, "Library", "Bin" ).toString() + ";" + envs.get( "Path" ) ); + envs.put( "Path", Paths.get( envDir, "Scripts" ) + ";" + envs.get( "Path" ) ); + envs.put( "Path", Paths.get( envDir, "Library" ) + ";" + envs.get( "Path" ) ); + envs.put( "Path", Paths.get( envDir, "Library", "Bin" ) + ";" + envs.get( "Path" ) ); } // TODO find way to get env vars in micromamba builder.environment().putAll( getEnvironmentVariables( envName ) ); if ( builder.command( cmd ).start().waitFor() != 0 ) @@ -1022,13 +1019,13 @@ public static void runPythonIn( final File envFile, final String... args ) throw throw new IOException("No Python found in the environment provided. The following " + "file does not exist: " + Paths.get( envFile.getAbsolutePath(), PYTHON_COMMAND ).toAbsolutePath()); final List< String > cmd = getBaseCommand(); - List argsList = new ArrayList (); + List argsList = new ArrayList<>(); argsList.add( coverArgWithDoubleQuotes(Paths.get( envFile.getAbsolutePath(), PYTHON_COMMAND ).toAbsolutePath().toString()) ); - argsList.addAll( Arrays.asList( args ).stream().map(aa -> { + argsList.addAll( Arrays.stream( args ).map(aa -> { if (Platform.isWindows() && aa.contains(" ")) return coverArgWithDoubleQuotes(aa); else return aa; }).collect(Collectors.toList()) ); - boolean containsSpaces = argsList.stream().filter(aa -> aa.contains(" ")).collect(Collectors.toList()).size() > 0; + boolean containsSpaces = argsList.stream().anyMatch(aa -> aa.contains(" ")); if (!containsSpaces || !SystemUtils.IS_OS_WINDOWS) cmd.addAll(argsList); else cmd.add(surroundWithQuotes(argsList)); @@ -1041,9 +1038,9 @@ public static void runPythonIn( final File envFile, final String... args ) throw final Map< String, String > envs = builder.environment(); final String envDir = envFile.getAbsolutePath(); envs.put( "Path", envDir + ";" + envs.get( "Path" ) ); - envs.put( "Path", Paths.get( envDir, "Scripts" ).toString() + ";" + envs.get( "Path" ) ); - envs.put( "Path", Paths.get( envDir, "Library" ).toString() + ";" + envs.get( "Path" ) ); - envs.put( "Path", Paths.get( envDir, "Library", "Bin" ).toString() + ";" + envs.get( "Path" ) ); + envs.put( "Path", Paths.get( envDir, "Scripts" ) + ";" + envs.get( "Path" ) ); + envs.put( "Path", Paths.get( envDir, "Library" ) + ";" + envs.get( "Path" ) ); + envs.put( "Path", Paths.get( envDir, "Library", "Bin" ) + ";" + envs.get( "Path" ) ); } // TODO find way to get env vars in micromamba builder.environment().putAll( getEnvironmentVariables( envName ) ); if ( builder.command( cmd ).start().waitFor() != 0 ) @@ -1060,10 +1057,8 @@ public static void runPythonIn( final File envFile, final String... args ) throw * If the current thread is interrupted by another thread while it * is waiting, then the wait is ended and an InterruptedException is * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used */ - public String getVersion() throws IOException, InterruptedException, MambaInstallException - { + public String getVersion() throws IOException, InterruptedException { final List< String > cmd = getBaseCommand(); if (mambaCommand.contains(" ") && SystemUtils.IS_OS_WINDOWS) cmd.add( surroundWithQuotes(Arrays.asList( coverArgWithDoubleQuotes(mambaCommand), "--version" )) ); @@ -1101,13 +1096,13 @@ public void runMamba(boolean isInheritIO, final String... args ) throws RuntimeE SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); final List< String > cmd = getBaseCommand(); - List argsList = new ArrayList (); + List argsList = new ArrayList<>(); argsList.add( coverArgWithDoubleQuotes(mambaCommand) ); - argsList.addAll( Arrays.asList( args ).stream().map(aa -> { + argsList.addAll( Arrays.stream( args ).map(aa -> { if (aa.contains(" ") && SystemUtils.IS_OS_WINDOWS) return coverArgWithDoubleQuotes(aa); else return aa; }).collect(Collectors.toList()) ); - boolean containsSpaces = argsList.stream().filter(aa -> aa.contains(" ")).collect(Collectors.toList()).size() > 0; + boolean containsSpaces = argsList.stream().anyMatch(aa -> aa.contains(" ")); if (!containsSpaces || !SystemUtils.IS_OS_WINDOWS) cmd.addAll(argsList); else cmd.add(surroundWithQuotes(argsList)); @@ -1120,12 +1115,12 @@ public void runMamba(boolean isInheritIO, final String... args ) throws RuntimeE Thread outputThread = new Thread(() -> { try ( InputStream inputStream = process.getInputStream(); - InputStream errStream = process.getErrorStream(); + InputStream errStream = process.getErrorStream() ){ byte[] buffer = new byte[1024]; // Buffer size can be adjusted StringBuilder processBuff = new StringBuilder(); StringBuilder errBuff = new StringBuilder(); - String processChunk = ""; + String processChunk = ""; String errChunk = ""; int newLineIndex; long t0 = System.currentTimeMillis(); @@ -1137,8 +1132,8 @@ public void runMamba(boolean isInheritIO, final String... args ) throws RuntimeE if (inputStream.available() > 0) { processBuff.append(new String(buffer, 0, inputStream.read(buffer))); while ((newLineIndex = processBuff.indexOf(System.lineSeparator())) != -1) { - processChunk += sdf.format(Calendar.getInstance().getTime()) + " -- " - + processBuff.substring(0, newLineIndex + 1).trim() + System.lineSeparator(); + processChunk += sdf.format(Calendar.getInstance().getTime()) + " -- " + + processBuff.substring(0, newLineIndex + 1).trim() + System.lineSeparator(); processBuff.delete(0, newLineIndex + 1); } } @@ -1279,7 +1274,7 @@ public List< String > getEnvironmentNames() throws IOException, MambaInstallExce { checkMambaInstalled(); if (!installed) throw new MambaInstallException("Micromamba is not installed"); - final List< String > envs = new ArrayList<>( Arrays.asList( DEFAULT_ENVIRONMENT_NAME ) ); + final List< String > envs = new ArrayList<>(Collections.singletonList(DEFAULT_ENVIRONMENT_NAME)); envs.addAll( Files.list( Paths.get( envsdir ) ) .map( p -> p.getFileName().toString() ) .filter( p -> !p.startsWith( "." ) ) @@ -1304,7 +1299,7 @@ public List< String > getEnvironmentNames() throws IOException, MambaInstallExce public boolean checkAllDependenciesInEnv(String envName, List dependencies) throws MambaInstallException { checkMambaInstalled(); if (!installed) throw new MambaInstallException("Micromamba is not installed"); - return checkUninstalledDependenciesInEnv(envName, dependencies).size() == 0; + return checkUninstalledDependenciesInEnv(envName, dependencies).isEmpty(); } /** @@ -1319,7 +1314,7 @@ public boolean checkAllDependenciesInEnv(String envName, List dependenci * They can contain version requirements. The names should be the ones used to import the package inside python, * "skimage", not "scikit-image" or "sklearn", not "scikit-learn" * An example list: "numpy", "numba>=0.43.1", "torch==1.6", "torch>=1.6, <2.0" - * @return true if the packages are installed or false otherwise + * @return the list of packages that are not already installed * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used */ public List checkUninstalledDependenciesInEnv(String envName, List dependencies) throws MambaInstallException { @@ -1329,14 +1324,13 @@ public List checkUninstalledDependenciesInEnv(String envName, List uninstalled = dependencies.stream().filter(dep -> { + return dependencies.stream().filter(dep -> { try { return !checkDependencyInEnv(envName, dep); } catch (Exception ex) { return true; } }).collect(Collectors.toList()); - return uninstalled; } /** @@ -1604,7 +1598,7 @@ else if (!envFile.isDirectory()) public boolean checkEnvFromYamlExists(String envYaml) throws MambaInstallException { checkMambaInstalled(); if (!installed) throw new MambaInstallException("Micromamba is not installed"); - if (envYaml == null || new File(envYaml).isFile() == false + if (envYaml == null || !new File(envYaml).isFile() || (envYaml.endsWith(".yaml") && envYaml.endsWith(".yml"))) { return false; } @@ -1651,8 +1645,8 @@ public static void main(String[] args) throws IOException, InterruptedException, Mamba m = new Mamba("C:\\Users\\angel\\Desktop\\Fiji app\\appose_x86_64"); String envName = "efficientvit_sam_env"; - m.pipInstallIn(envName, new String[] {m.getEnvsDir() + File.separator + envName - + File.separator + "appose-python"}); + m.pipInstallIn(envName, + m.getEnvsDir() + File.separator + envName + File.separator + "appose-python"); } /** @@ -1670,28 +1664,28 @@ private void installApposeFromSource(String envName) throws IOException, Interru String zipResourcePath = "appose-python.zip"; String outputDirectory = this.getEnvsDir() + File.separator + envName; if (new File(envName).isDirectory()) outputDirectory = new File(envName).getAbsolutePath(); - try ( - InputStream zipInputStream = Mamba.class.getClassLoader().getResourceAsStream(zipResourcePath); - ZipInputStream zipInput = new ZipInputStream(zipInputStream); - ) { - ZipEntry entry; - while ((entry = zipInput.getNextEntry()) != null) { - File entryFile = new File(outputDirectory + File.separator + entry.getName()); - if (entry.isDirectory()) { - entryFile.mkdirs(); - continue; - } - entryFile.getParentFile().mkdirs(); - try (OutputStream entryOutput = new FileOutputStream(entryFile)) { - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = zipInput.read(buffer)) != -1) { - entryOutput.write(buffer, 0, bytesRead); - } - } - } - } - this.pipInstallIn(envName, new String[] {outputDirectory + File.separator + "appose-python"}); + try ( + InputStream zipInputStream = Mamba.class.getClassLoader().getResourceAsStream(zipResourcePath); + ZipInputStream zipInput = new ZipInputStream(zipInputStream) + ) { + ZipEntry entry; + while ((entry = zipInput.getNextEntry()) != null) { + File entryFile = new File(outputDirectory + File.separator + entry.getName()); + if (entry.isDirectory()) { + entryFile.mkdirs(); + continue; + } + entryFile.getParentFile().mkdirs(); + try (OutputStream entryOutput = new FileOutputStream(entryFile)) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = zipInput.read(buffer)) != -1) { + entryOutput.write(buffer, 0, bytesRead); + } + } + } + } + this.pipInstallIn(envName, outputDirectory + File.separator + "appose-python"); } } diff --git a/src/main/java/org/apposed/appose/NDArray.java b/src/main/java/org/apposed/appose/NDArray.java index 62e19b4..525b915 100644 --- a/src/main/java/org/apposed/appose/NDArray.java +++ b/src/main/java/org/apposed/appose/NDArray.java @@ -143,7 +143,8 @@ private static int safeInt(final long value) { /** * Enumerates possible data type of {@link NDArray} elements. */ - public static enum DType { + @SuppressWarnings("unused") + public enum DType { INT8("int8", Byte.BYTES), // INT16("int16", Short.BYTES), // INT32("int32", Integer.BYTES), // @@ -179,8 +180,8 @@ public int bytesPerElement() /** * Get the label of this {@code DType}. * - * The label can used as a {@code dtype} in Python. It is also used for JSON - * serialization. + * The label can be used as a {@code dtype} in Python. + * It is also used for JSON serialization. * * @return the label. */ @@ -203,7 +204,7 @@ public static DType fromLabel(final String label) throws IllegalArgumentExceptio } /** - * The shape of a multi-dimensional array. + * The shape of a multidimensional array. */ public static class Shape { @@ -282,6 +283,7 @@ public long numElements() { * * @return dimensions array */ + @SuppressWarnings("unused") public int[] toIntArray() { return shape; } @@ -296,9 +298,9 @@ public int[] toIntArray(final Order order) { return shape; } else { - final int[] ishape = new int[shape.length]; - Arrays.setAll(ishape, i -> shape[shape.length - i - 1]); - return ishape; + final int[] iShape = new int[shape.length]; + Arrays.setAll(iShape, i -> shape[shape.length - i - 1]); + return iShape; } } @@ -308,6 +310,7 @@ public int[] toIntArray(final Order order) { * * @return dimensions array */ + @SuppressWarnings("unused") public long[] toLongArray() { return toLongArray(order); } @@ -318,14 +321,14 @@ public long[] toLongArray() { * @return dimensions array */ public long[] toLongArray(final Order order) { - final long[] lshape = new long[shape.length]; + final long[] lShape = new long[shape.length]; if (order.equals(this.order)) { - Arrays.setAll(lshape, i -> shape[i]); + Arrays.setAll(lShape, i -> shape[i]); } else { - Arrays.setAll(lshape, i -> shape[shape.length - i - 1]); + Arrays.setAll(lShape, i -> shape[shape.length - i - 1]); } - return lshape; + return lShape; } /** From 9cdf301237aae5071ddce52575d2397140a7e8b5 Mon Sep 17 00:00:00 2001 From: Curtis Rueden
Date: Tue, 13 Aug 2024 17:39:11 -0500 Subject: [PATCH 066/120] Remove pinned ivy version Version 38.0.1 of the pom-scijava parent brings in Ivy 2.5.2, so the pin is no longer necessary. And Ivy 2.5.1 suffers from CVE-2022-46751. --- pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pom.xml b/pom.xml index ed0093a..c88f64d 100644 --- a/pom.xml +++ b/pom.xml @@ -88,8 +88,6 @@ bsd_2 Appose developers. - -2.5.1 From f2a974d4b82cb074e82ed3692e3b3a5a2dea0c69 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 14 Aug 2024 15:14:10 -0500 Subject: [PATCH 067/120] Add aspirational conda builder test, for TDD --- .../java/org/apposed/appose/ApposeTest.java | 28 ++++++++++++++----- src/test/resources/envs/cowsay.yml | 9 ++++++ 2 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/envs/cowsay.yml diff --git a/src/test/java/org/apposed/appose/ApposeTest.java b/src/test/java/org/apposed/appose/ApposeTest.java index e4322a6..b2107b6 100644 --- a/src/test/java/org/apposed/appose/ApposeTest.java +++ b/src/test/java/org/apposed/appose/ApposeTest.java @@ -87,14 +87,28 @@ public void testPython() throws IOException, InterruptedException { } } - public void testConda() { - Environment env = Appose.conda(new File("appose-environment.yml")).build(); + @Test + public void testConda() throws IOException, InterruptedException { + Environment env = Appose.conda(new File("src/test/resources/envs/cowsay.yml")).build(); try (Service service = env.python()) { - service.debug(System.err::println); - executeAndAssert(service, "import cowsay; "); - } catch (IOException | InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + Task task = service.task( + "import cowsay\n" + + "moo = cowsay.get_output_string(\"cow\", \"moo\")\n" + ); + task.waitFor(); + String expectedMoo = + " ___\n" + + "| moo |\n" + + "===\n" + + " \\\n" + + " \\\n" + + " ^__^\n" + + " (oo)\\_______\n" + + " (__)\\ )\\/\\\n" + + " ||----w |\n" + + " || ||"; + String actualMoo = (String) task.outputs.get("moo"); + assertEquals(expectedMoo, actualMoo); } } diff --git a/src/test/resources/envs/cowsay.yml b/src/test/resources/envs/cowsay.yml new file mode 100644 index 0000000..a35704e --- /dev/null +++ b/src/test/resources/envs/cowsay.yml @@ -0,0 +1,9 @@ +name: appose-cowsay +channels: + - conda-forge +dependencies: + - python >= 3.8 + - pip + - appose + - pip: + - cowsay==6.1 From 7a8091f357781664143c236517ccc98dbe44f8a0 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Aug 2024 06:28:18 -0500 Subject: [PATCH 068/120] POM: bump major/minor version The Mamba logic is new. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c88f64d..719d553 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ org.apposed appose -0.2.1-SNAPSHOT +0.3.0-SNAPSHOT Appose Appose: multi-language interprocess cooperation with shared memory. From 62106976b163de53be7a60b10ed5051aa240a0ed Mon Sep 17 00:00:00 2001 From: Curtis RuedenDate: Fri, 16 Aug 2024 06:29:06 -0500 Subject: [PATCH 069/120] Fix up the license headers --- .../org/apposed/appose/CondaException.java | 28 ++++++++++++++++ .../org/apposed/appose/FileDownloader.java | 28 ++++++++++++++++ src/main/java/org/apposed/appose/Mamba.java | 33 +++++++++++++++++++ .../apposed/appose/MambaInstallException.java | 28 ++++++++++++++++ .../apposed/appose/MambaInstallerUtils.java | 2 +- 5 files changed, 118 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apposed/appose/CondaException.java b/src/main/java/org/apposed/appose/CondaException.java index 632fc7b..2efc7b3 100644 --- a/src/main/java/org/apposed/appose/CondaException.java +++ b/src/main/java/org/apposed/appose/CondaException.java @@ -1,3 +1,31 @@ +/*- + * #%L + * Appose: multi-language interprocess cooperation with shared memory. + * %% + * Copyright (C) 2023 - 2024 Appose developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ package org.apposed.appose; public class CondaException diff --git a/src/main/java/org/apposed/appose/FileDownloader.java b/src/main/java/org/apposed/appose/FileDownloader.java index 83cc956..bcb0f5a 100644 --- a/src/main/java/org/apposed/appose/FileDownloader.java +++ b/src/main/java/org/apposed/appose/FileDownloader.java @@ -1,3 +1,31 @@ +/*- + * #%L + * Appose: multi-language interprocess cooperation with shared memory. + * %% + * Copyright (C) 2023 - 2024 Appose developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ package org.apposed.appose; import java.io.FileOutputStream; diff --git a/src/main/java/org/apposed/appose/Mamba.java b/src/main/java/org/apposed/appose/Mamba.java index 3f49e96..e0bad74 100644 --- a/src/main/java/org/apposed/appose/Mamba.java +++ b/src/main/java/org/apposed/appose/Mamba.java @@ -1,3 +1,35 @@ +/*- + * #%L + * Appose: multi-language interprocess cooperation with shared memory. + * %% + * Copyright (C) 2023 - 2024 Appose developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +// Adapted from JavaConda (https://github.com/javaconda/javaconda), +// which has the following license: + /******************************************************************************* * Copyright (C) 2021, Ko Sugawara * All rights reserved. @@ -24,6 +56,7 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ + package org.apposed.appose; import org.apache.commons.compress.archivers.ArchiveException; diff --git a/src/main/java/org/apposed/appose/MambaInstallException.java b/src/main/java/org/apposed/appose/MambaInstallException.java index 33f856e..efbd5d6 100644 --- a/src/main/java/org/apposed/appose/MambaInstallException.java +++ b/src/main/java/org/apposed/appose/MambaInstallException.java @@ -1,3 +1,31 @@ +/*- + * #%L + * Appose: multi-language interprocess cooperation with shared memory. + * %% + * Copyright (C) 2023 - 2024 Appose developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ package org.apposed.appose; /** diff --git a/src/main/java/org/apposed/appose/MambaInstallerUtils.java b/src/main/java/org/apposed/appose/MambaInstallerUtils.java index 4bd5c71..3b7949e 100644 --- a/src/main/java/org/apposed/appose/MambaInstallerUtils.java +++ b/src/main/java/org/apposed/appose/MambaInstallerUtils.java @@ -2,7 +2,7 @@ * #%L * Appose: multi-language interprocess cooperation with shared memory. * %% - * Copyright (C) 2023 Appose developers. + * Copyright (C) 2023 - 2024 Appose developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: From e52a7ccf37b6a04ad13fe69085b7b7790c18b52f Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 12 Aug 2024 15:37:04 -0500 Subject: [PATCH 070/120] Add notes on how the Builder API should work --- notes.md | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 notes.md diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..cbb3b98 --- /dev/null +++ b/notes.md @@ -0,0 +1,234 @@ +## Builder API + +* Want an API to create an environment from an envFile: i.e. `pixi.toml` or `environment.yml`. +* Want an API to build up an environment piecemeal: adding dependencies one by one. +* Do we need an API to mix and match these two things? I.e. start from envFile but then add on? + - An argument for this: what if you want to mix in Java JARs? pixi.toml can't do that yet. +* Want an API to create an environment from a *string* representation of an envFile. + +Most flexible to put all these things into the Builder, not only directly in Appose (like `system()`). + +What sorts of dependencies do we want to support adding? + +1. conda-forge packages +2. PyPI packages +3. Maven coords +4. Java itself + +Pixi gets us (1) and (2). + +* Maven coords can be gotten by Groovy Grape in Java, by jgo in Python. (jgo needs work) +* Java itself can be gotten by cjdk in Python; what about from Java? Port cjdk? Or hack it with openjdk conda for now? + +Should we make the API more general than the above? Yes! We can use `ServiceLoader`, same as we do with `ShmFactory`. + +The interface is `BuildHandler`: +* `boolean include(String content, String scheme)` +* `boolean channel(String name, String location)` + +And the implementations + supported schemes are: +* `PixiHandler` -- `environment.yml`, `pixi.toml`, `pypi`, `conda`, and null. +* `MavenHandler` -- `maven` +* `OpenJDKHandler` -- `openjdk` + +Although the term "scheme" might be confused with URI scheme, other terms are problematic too: +* "platform" will be confused with OS/arch. +* "method" will be confused with functions of a class. +* "system" might be confused with the computer itself, and/or system environment, path, etc. +* "paradigm" sounds too pretentious. +* "repoType" is rather clunky. + +The `Builder` then has its own `include` and `channel` methods that delegate to +all discovered `BuildHandler` plugins. The `Builder` can also have more +convenience methods: + +* `Builder file(String filePath) { return file(new File(filePath)); }` +* `Builder file(String filePath, String scheme) { return file(new File(filePath), scheme); }` +* `Builder file(File file) { return file(file, file.getName()); }` +* `Builder file(File file, String scheme) { return include(readContentsAsString(file), scheme); }` + +For the `file`-to-`include` trick to work with files like `requirements.txt`, +the handling of `conda`/null scheme should split the content string into lines, +and process them in a loop. + +Here are some example API calls made possible by the above design: +```java +Appose.env() + .file("/path/to/environment.yml") + // OR: .file("/path/to/pixi.toml") + // OR: .file("/path/to/requirements.txt", "pypi") + .include("cowsay", "pypi") + .include("openjdk>=17") // Install OpenJDK from conda-forge! + .include("maven") // Install Maven from conda-forge... confusing, yeah? + .include("conda-forge::maven") // Specify channel explicitly with environment.yml syntax. + .include("org.scijava:parsington", "maven") + // OR: .include("org.scijava:parsington") i.e. infer `maven` from the colon? + // OR: .include("org.scijava:parsington:2.0.0", "maven") + .channel("scijava", "maven:https://maven.scijava.org/content/groups/public") + .include("sc.fiji:fiji", "maven") + .include("zulu:17", "openjdk") // Install an OpenJDK from the Coursier index. + + .channel("bioconda") // Add a conda channel + .channel(name: str, location: str = None) + .build() // Whew! +``` + +## Pixi + +Is even better than micromamba. It's a great fit for Appose's requirements. + +### Setup for Appose + +```shell +# Install a copy of Pixi into Appose's workspace. +mkdir -p ~/.local/share/appose/tmp +cd ~/.local/share/appose/tmp +curl -fsLO https://github.com/prefix-dev/pixi/releases/download/v0.27.1/pixi-x86_64-unknown-linux-musl.tar.gz +mkdir -p ../.pixi/bin +cd ../.pixi/bin +tar xf ../tmp/pixi-x86_64-unknown-linux-musl.tar.gz +alias pixi=~/.local/share/appose/.pixi/bin/pixi +``` + +And/or consider setting `PIXI_HOME` to `$HOME/.local/share/appose` +when using the `$HOME/.local/share/appose/.pixi/bin/pixi` binary. +This would let us, in the future, tweak Pixi's Appose-wide configuration +by adding a `$HOME/.local/share/appose/.pixi/config.toml` file. + +#### Create an Appose environment + +```shell +mkdir -p ~/.local/share/appose/sc-fiji-spiff +pixi init ~/.local/share/appose/sc-fiji-spiff +``` + +#### Add channels to the project/environment + +```shell +cd ~/.local/share/appose/sc-fiji-spiff +pixi project channel add bioconda pytorch +``` + +Doing them all in one command will have less overhead. + +#### Add dependencies to the project/environment + +```shell +cd ~/.local/share/appose/sc-fiji-spiff +pixi add python pip +pixi add --pypi cowsay +``` + +Doing them all in two commands (one for conda, one for pypi) will have less overhead. + +#### Use it + +```shell +pixi run python -c 'import cowsay; cowsay.cow("moo")' +``` + +One awesome thing is that Appose will be able to launch the +child process using `pixi run ...`, which takes care of running +activation scripts before launch—so the child program should +work as though run from an activated environment (or `pixi shell`). + +### Bugs + +#### Invalid environment names do not fail fast + +```shell +pixi project environment add sc.fiji.spiff +pixi tree +``` +Fails with: `Failed to parse environment name 'sc.fiji.spiff', please use only lowercase letters, numbers and dashes` + +### Multiple environments in one pixi project? + +I explored making a single Appose project and using pixi's multi-environment +support to manage Appose environments, all within that one project, as follows: + +```shell +# Initialize the shared Appose project. +pixi init +pixi project description set "Appose: multi-language interprocess cooperation with shared memory." + +# Create a new environment within the Appose project. +pixi project environment add sc-fiji-spiff +# Install dependencies into a feature with matching name. +pixi add --feature sc-fiji-spiff python pip +pixi add --feature sc-fiji-spiff --pypi cowsay +# No known command to link sc-fiji-spiff feature with sc-fiji-spiff project... +mv pixi.toml pixi.toml.old +sed 's/sc-fiji-spiff = \[\]/sc-fiji-spiff = ["sc-fiji-spiff"]/' pixi.toml.old > pixi.toml +# Finally, we can use the environment! +pixi run --environment sc-fiji-spiff python -c 'import cowsay; cowsay.cow("moo")' +``` + +This works, but a single `pixi.toml` file for all of Appose is probably too +fragile, whereas a separate project folder for each Appose environment should +be more robust, reducing the chance that one Appose-based project (e.g. JDLL) +might stomp on another Appose-based project (e.g. TrackMate) due to their usage +of the same `pixi.toml`. + +So we'll just settle for pixi's standard behavior here: a single environment +named `default` per pixi project, with one pixi project per Appose environment. +Unfortunately, that means our environment directory structure will be: +``` +~/.local/share/appose/sc-fiji-spiff/envs/default +``` +for an Appose environment named `sc-fiji-spiff`. +(Note that environment names cannot contain dots, only alphameric and dash.) + +To attain a better structure, I tried creating a `~/.local/share/appose/sc-fiji-spiff/.pixi/config.toml` with contents: +```toml +detached-environments: "/home/curtis/.local/share/appose" +``` + +It works, but then the environment folder from above ends up being: +``` +~/.local/share/appose/sc-fiji-spiff- /envs/sc-fiji-spiff +``` +Looks like pixi creates one folder under `envs` for each project, with a +numerical hash to reduce collisions between multiple projects with the same +name... and then still makes an `envs` folder for that project beneath it. +So there is no escape from pixi's directory convention of: +``` + /envs/ +``` +Which of these is the least annoying? +``` +~/.local/share/appose/sc-fiji-spiff/envs/default +~/.local/share/appose/sc-fiji-spiff-782634298734/envs/default +~/.local/share/appose/sc-fiji-spiff/envs/sc-fiji-spiff +~/.local/share/appose/sc-fiji-spiff-782634298734/envs/sc-fiji-spiff +``` +The detached-environments approach is actually longer, and entails +additional configuration and more potential for confusion; the +shortest path ends up being the first one, which is pixi's standard +behavior anyway. + +The only shorter one would be: +``` +~/.local/share/appose/envs/sc-fiji-spiff +``` +if we opted to keep `~/.local/share/appose` as a single Pixi project +root with multiple environments... but the inconvenience and risks +around a single shared `pixi.toml`, and hassle of multi-environment +configuration, outweigh the benefit of slightly shorter paths. + +With separate Pixi projects we can also let Appose users specify their own +`pixi.toml` (maybe a partial one?), directly. Or an `environment.yml` that gets +used via `pixi init --import`. Maybe someday even a `requirements.txt`, if the +request (https://github.com/prefix-dev/pixi/issues/1410) gets implemented. + +## Next steps + +1. Add tests for the current Mamba builder. +2. Make the tests pass. +3. Introduce `BuildHandler` design and migrate Mamba logic to a build handler. +4. Implement a build handler built on pixi, to replace the micromamba one. +5. Implement build handlers for maven and openjdk. +6. Implement pixi, maven, and openjdk build handlers in appose-python, too. +7. Once it all works: release 0.3.0. + +And: update https://github.com/imglib/imglib2-appose to work with appose 0.2.0+. From c5efc9ee5a279d654044e513a6cbb3f253f14f3c Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Aug 2024 15:42:39 -0500 Subject: [PATCH 071/120] Remove unused java(vendor, version) builder method --- src/main/java/org/apposed/appose/Appose.java | 4 ---- src/main/java/org/apposed/appose/Builder.java | 11 ----------- 2 files changed, 15 deletions(-) diff --git a/src/main/java/org/apposed/appose/Appose.java b/src/main/java/org/apposed/appose/Appose.java index 8bf7dd4..b12be19 100644 --- a/src/main/java/org/apposed/appose/Appose.java +++ b/src/main/java/org/apposed/appose/Appose.java @@ -160,10 +160,6 @@ public static Builder base(String directory) { return base(new File(directory)); } - public static Builder java(String vendor, String version) { - return new Builder().java(vendor, version); - } - public static Builder conda(File environmentYaml) { return new Builder().conda(environmentYaml); } diff --git a/src/main/java/org/apposed/appose/Builder.java b/src/main/java/org/apposed/appose/Builder.java index 8b0370a..cbfcef2 100644 --- a/src/main/java/org/apposed/appose/Builder.java +++ b/src/main/java/org/apposed/appose/Builder.java @@ -92,15 +92,4 @@ public Builder conda(File environmentYaml) { this.condaEnvironmentYaml = environmentYaml; return this; } - - // -- Java -- - - private String javaVendor; - private String javaVersion; - - public Builder java(String vendor, String version) { - this.javaVendor = vendor; - this.javaVersion = version; - return this; - } } From d2463ba7626c7c519fd834c0a1a67fc575d6bc0e Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Aug 2024 15:44:45 -0500 Subject: [PATCH 072/120] Disallow direct construction of Builder objects Environments should be built via the Appose utility class. --- src/main/java/org/apposed/appose/Builder.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/apposed/appose/Builder.java b/src/main/java/org/apposed/appose/Builder.java index cbfcef2..c59128a 100644 --- a/src/main/java/org/apposed/appose/Builder.java +++ b/src/main/java/org/apposed/appose/Builder.java @@ -37,6 +37,10 @@ public class Builder { + Builder() { + // Prevent external instantiation. + } + public Environment build() { String base = baseDir.getPath(); boolean useSystemPath = systemPath; From 405d27fbe5d5ec730801d70c089bfb714117a4c0 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 19 Aug 2024 17:37:50 -0500 Subject: [PATCH 073/120] Fix bugs in the conda builder test --- .../java/org/apposed/appose/ApposeTest.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/apposed/appose/ApposeTest.java b/src/test/java/org/apposed/appose/ApposeTest.java index b2107b6..263180d 100644 --- a/src/test/java/org/apposed/appose/ApposeTest.java +++ b/src/test/java/org/apposed/appose/ApposeTest.java @@ -93,20 +93,20 @@ public void testConda() throws IOException, InterruptedException { try (Service service = env.python()) { Task task = service.task( "import cowsay\n" + - "moo = cowsay.get_output_string(\"cow\", \"moo\")\n" + "task.outputs['moo'] = cowsay.get_output_string('cow', 'moo')\n" ); task.waitFor(); String expectedMoo = " ___\n" + "| moo |\n" + - "===\n" + - " \\\n" + - " \\\n" + - " ^__^\n" + - " (oo)\\_______\n" + - " (__)\\ )\\/\\\n" + - " ||----w |\n" + - " || ||"; + " ===\n" + + " \\\n" + + " \\\n" + + " ^__^\n" + + " (oo)\\_______\n" + + " (__)\\ )\\/\\\n" + + " ||----w |\n" + + " || ||"; String actualMoo = (String) task.outputs.get("moo"); assertEquals(expectedMoo, actualMoo); } From 893c7fbe769ab9a97a0fd85bbad95c8d6caad855 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 19 Aug 2024 17:42:39 -0500 Subject: [PATCH 074/120] Remove "install appose from source" hack Appose 0.2.0 has been released, which works fine. --- src/main/java/org/apposed/appose/Mamba.java | 48 --------------------- 1 file changed, 48 deletions(-) diff --git a/src/main/java/org/apposed/appose/Mamba.java b/src/main/java/org/apposed/appose/Mamba.java index e0bad74..99fa275 100644 --- a/src/main/java/org/apposed/appose/Mamba.java +++ b/src/main/java/org/apposed/appose/Mamba.java @@ -576,8 +576,6 @@ public void createWithYaml( final String envName, final String envYaml, final bo throw new EnvironmentExistsException(); runMamba("env", "create", "--prefix", envsdir + File.separator + envName, "-f", envYaml, "-y", "-vv" ); - if (this.checkDependencyInEnv(envsdir + File.separator + envName, "python")) - installApposeFromSource(envsdir + File.separator + envName); } /** @@ -626,8 +624,6 @@ public void create( final String envName, final boolean isForceCreation ) throws if ( !isForceCreation && getEnvironmentNames().contains( envName ) ) throw new EnvironmentExistsException(); runMamba( "create", "-y", "-p", envsdir + File.separator + envName ); - if (this.checkDependencyInEnv(envsdir + File.separator + envName, "python")) - installApposeFromSource(envsdir + File.separator + envName); } /** @@ -685,8 +681,6 @@ public void create( final String envName, final boolean isForceCreation, final S cmd.addAll( Arrays.asList( args ) ); if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); runMamba(cmd.toArray(new String[0])); - if (this.checkDependencyInEnv(envsdir + File.separator + envName, "python")) - installApposeFromSource(envsdir + File.separator + envName); } /** @@ -728,8 +722,6 @@ public void create( final String envName, final boolean isForceCreation, List Date: Mon, 19 Aug 2024 17:44:30 -0500 Subject: [PATCH 075/120] Add helper method to get env dir for a given name --- src/main/java/org/apposed/appose/Mamba.java | 26 ++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/apposed/appose/Mamba.java b/src/main/java/org/apposed/appose/Mamba.java index 99fa275..5066d40 100644 --- a/src/main/java/org/apposed/appose/Mamba.java +++ b/src/main/java/org/apposed/appose/Mamba.java @@ -459,6 +459,10 @@ public String getEnvsDir() { return this.envsdir; } + public String getEnvDir(String envName) { + return getEnvsDir() + File.separator + envName; + } + /** * Returns {@code \{"cmd.exe", "/c"\}} for Windows and an empty list for * Mac/Linux. @@ -517,7 +521,7 @@ public void updateIn( final String envName, final String... args ) throws IOExce { checkMambaInstalled(); if (!installed) throw new MambaInstallException("Micromamba is not installed"); - final List< String > cmd = new ArrayList<>( Arrays.asList( "update", "-p", this.envsdir + File.separator + envName ) ); + final List< String > cmd = new ArrayList<>( Arrays.asList( "update", "-p", getEnvDir(envName) ) ); cmd.addAll( Arrays.asList( args ) ); if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); runMamba(cmd.toArray(new String[0])); @@ -575,7 +579,7 @@ public void createWithYaml( final String envName, final String envYaml, final bo if ( !isForceCreation && getEnvironmentNames().contains( envName ) ) throw new EnvironmentExistsException(); runMamba("env", "create", "--prefix", - envsdir + File.separator + envName, "-f", envYaml, "-y", "-vv" ); + getEnvDir(envName), "-f", envYaml, "-y", "-vv" ); } /** @@ -623,7 +627,7 @@ public void create( final String envName, final boolean isForceCreation ) throws if (!installed) throw new MambaInstallException("Micromamba is not installed"); if ( !isForceCreation && getEnvironmentNames().contains( envName ) ) throw new EnvironmentExistsException(); - runMamba( "create", "-y", "-p", envsdir + File.separator + envName ); + runMamba( "create", "-y", "-p", getEnvDir(envName) ); } /** @@ -677,7 +681,7 @@ public void create( final String envName, final boolean isForceCreation, final S if (!installed) throw new MambaInstallException("Micromamba is not installed"); if ( !isForceCreation && getEnvironmentNames().contains( envName ) ) throw new EnvironmentExistsException(); - final List< String > cmd = new ArrayList<>( Arrays.asList( "create", "-p", envsdir + File.separator + envName ) ); + final List< String > cmd = new ArrayList<>( Arrays.asList( "create", "-p", getEnvDir(envName) ) ); cmd.addAll( Arrays.asList( args ) ); if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); runMamba(cmd.toArray(new String[0])); @@ -715,7 +719,7 @@ public void create( final String envName, final boolean isForceCreation, List cmd = new ArrayList<>( Arrays.asList( "create", "-p", envsdir + File.separator + envName ) ); + final List< String > cmd = new ArrayList<>( Arrays.asList( "create", "-p", getEnvDir(envName) ) ); if (channels == null) channels = new ArrayList<>(); for (String chan : channels) { cmd.add("-c"); cmd.add(chan);} if (packages == null) packages = new ArrayList<>(); @@ -851,7 +855,7 @@ public void installIn( final String envName, List channels, List checkMambaInstalled(); if (!installed) throw new MambaInstallException("Micromamba is not installed"); Objects.requireNonNull(envName, "The name of the environment of interest needs to be provided."); - final List< String > cmd = new ArrayList<>( Arrays.asList( "install", "-y", "-p", this.envsdir + File.separator + envName ) ); + final List< String > cmd = new ArrayList<>( Arrays.asList( "install", "-y", "-p", getEnvDir(envName) ) ); if (channels == null) channels = new ArrayList<>(); for (String chan : channels) { cmd.add("-c"); cmd.add(chan);} if (packages == null) packages = new ArrayList<>(); @@ -880,7 +884,7 @@ public void installIn( final String envName, final String... args ) throws IOExc { checkMambaInstalled(); if (!installed) throw new MambaInstallException("Micromamba is not installed"); - final List< String > cmd = new ArrayList<>( Arrays.asList( "install", "-p", this.envsdir + File.separator + envName ) ); + final List< String > cmd = new ArrayList<>( Arrays.asList( "install", "-p", getEnvDir(envName) ) ); cmd.addAll( Arrays.asList( args ) ); if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); runMamba(cmd.toArray(new String[0])); @@ -992,9 +996,9 @@ public void runPythonIn( final String envName, final String... args ) throws IOE if (new File(envName, PYTHON_COMMAND).isFile()) { argsList.add( coverArgWithDoubleQuotes(Paths.get( envName, PYTHON_COMMAND ).toAbsolutePath().toString()) ); envDir = Paths.get( envName ).toAbsolutePath().toString(); - } else if (Paths.get( this.envsdir, envName, PYTHON_COMMAND ).toFile().isFile()) { - argsList.add( coverArgWithDoubleQuotes(Paths.get( this.envsdir, envName, PYTHON_COMMAND ).toAbsolutePath().toString()) ); - envDir = Paths.get( envsdir, envName ).toAbsolutePath().toString(); + } else if (Paths.get( getEnvDir(envName), PYTHON_COMMAND ).toFile().isFile()) { + argsList.add( coverArgWithDoubleQuotes(Paths.get( getEnvDir(envName), PYTHON_COMMAND ).toAbsolutePath().toString()) ); + envDir = Paths.get( getEnvDir(envName) ).toAbsolutePath().toString(); } else throw new IOException("The environment provided (" + envName + ") does not exist or does not contain a Python executable (" + PYTHON_COMMAND + ")."); @@ -1671,6 +1675,6 @@ public static void main(String[] args) throws IOException, InterruptedException, Mamba m = new Mamba("C:\\Users\\angel\\Desktop\\Fiji app\\appose_x86_64"); String envName = "efficientvit_sam_env"; m.pipInstallIn(envName, - m.getEnvsDir() + File.separator + envName + File.separator + "appose-python"); + m.getEnvDir(envName) + File.separator + "appose-python"); } } From e106e2d6dfb5398ae42df9f030f36419e1da9a98 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 19 Aug 2024 17:47:25 -0500 Subject: [PATCH 076/120] Add helper method to get env name from YAML file --- src/main/java/org/apposed/appose/Mamba.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/apposed/appose/Mamba.java b/src/main/java/org/apposed/appose/Mamba.java index 5066d40..be7fe61 100644 --- a/src/main/java/org/apposed/appose/Mamba.java +++ b/src/main/java/org/apposed/appose/Mamba.java @@ -60,6 +60,7 @@ package org.apposed.appose; import org.apache.commons.compress.archivers.ArchiveException; +import org.apache.commons.compress.utils.FileNameUtils; import org.apache.commons.lang3.SystemUtils; import com.sun.jna.Platform; @@ -455,6 +456,15 @@ public void installMicromamba() throws IOException, InterruptedException, Archiv checkMambaInstalled(); } + public static String envNameFromYaml(File condaEnvironmentYaml) throws IOException { + List lines = Files.readAllLines(condaEnvironmentYaml.toPath()); + return lines.stream() + .filter(line -> line.startsWith("name:")) + .map(line -> line.substring(5).trim()) + .findFirst() + .orElseGet(() -> FileNameUtils.getBaseName(condaEnvironmentYaml.toPath())); + } + public String getEnvsDir() { return this.envsdir; } From 146dd694cdc1fa403e72d29192c0c31e3b72315f Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 19 Aug 2024 17:47:43 -0500 Subject: [PATCH 077/120] Fix NPE when building conda env if baseDir unset --- src/main/java/org/apposed/appose/Builder.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/apposed/appose/Builder.java b/src/main/java/org/apposed/appose/Builder.java index c59128a..c6cfcf6 100644 --- a/src/main/java/org/apposed/appose/Builder.java +++ b/src/main/java/org/apposed/appose/Builder.java @@ -42,29 +42,31 @@ public class Builder { } public Environment build() { - String base = baseDir.getPath(); - boolean useSystemPath = systemPath; - // TODO Build the thing!~ // Hash the state to make a base directory name. // - Construct conda environment from condaEnvironmentYaml. // - Download and unpack JVM of the given vendor+version. // - Populate ${baseDirectory}/jars with Maven artifacts? + final String base; if (condaEnvironmentYaml != null) { try { Mamba conda = new Mamba(Mamba.BASE_PATH); conda.installMicromamba(); - String envName = "appose"; + String envName = Mamba.envNameFromYaml(condaEnvironmentYaml); if (conda.getEnvironmentNames().contains(envName)) { // TODO: Should we update it? For now, we just use it. } else { conda.createWithYaml(envName, condaEnvironmentYaml.getAbsolutePath()); } + base = conda.getEnvDir(envName); + // TODO: If baseDir is already set, we should use that directory instead. } catch (IOException | InterruptedException | ArchiveException | URISyntaxException | MambaInstallException e) { throw new RuntimeException(e); } } + else base = baseDir.getPath(); + final boolean useSystemPath = systemPath; return new Environment() { @Override public String base() { return base; } From 05a02c0b077b99a7d2be3b6ece96858b529179b2 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Tue, 20 Aug 2024 19:24:59 -0500 Subject: [PATCH 078/120] Move mamba logic into subpackage --- src/main/java/org/apposed/appose/Builder.java | 2 ++ .../org/apposed/appose/{ => mamba}/CondaException.java | 2 +- .../org/apposed/appose/{ => mamba}/FileDownloader.java | 2 +- src/main/java/org/apposed/appose/{ => mamba}/Mamba.java | 7 ++----- .../apposed/appose/{ => mamba}/MambaInstallException.java | 2 +- .../apposed/appose/{ => mamba}/MambaInstallerUtils.java | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) rename src/main/java/org/apposed/appose/{ => mamba}/CondaException.java (99%) rename src/main/java/org/apposed/appose/{ => mamba}/FileDownloader.java (98%) rename src/main/java/org/apposed/appose/{ => mamba}/Mamba.java (99%) rename src/main/java/org/apposed/appose/{ => mamba}/MambaInstallException.java (98%) rename src/main/java/org/apposed/appose/{ => mamba}/MambaInstallerUtils.java (99%) diff --git a/src/main/java/org/apposed/appose/Builder.java b/src/main/java/org/apposed/appose/Builder.java index c6cfcf6..5730a47 100644 --- a/src/main/java/org/apposed/appose/Builder.java +++ b/src/main/java/org/apposed/appose/Builder.java @@ -34,6 +34,8 @@ import java.net.URISyntaxException; import org.apache.commons.compress.archivers.ArchiveException; +import org.apposed.appose.mamba.Mamba; +import org.apposed.appose.mamba.MambaInstallException; public class Builder { diff --git a/src/main/java/org/apposed/appose/CondaException.java b/src/main/java/org/apposed/appose/mamba/CondaException.java similarity index 99% rename from src/main/java/org/apposed/appose/CondaException.java rename to src/main/java/org/apposed/appose/mamba/CondaException.java index 2efc7b3..74fe23f 100644 --- a/src/main/java/org/apposed/appose/CondaException.java +++ b/src/main/java/org/apposed/appose/mamba/CondaException.java @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package org.apposed.appose; +package org.apposed.appose.mamba; public class CondaException { diff --git a/src/main/java/org/apposed/appose/FileDownloader.java b/src/main/java/org/apposed/appose/mamba/FileDownloader.java similarity index 98% rename from src/main/java/org/apposed/appose/FileDownloader.java rename to src/main/java/org/apposed/appose/mamba/FileDownloader.java index bcb0f5a..b636726 100644 --- a/src/main/java/org/apposed/appose/FileDownloader.java +++ b/src/main/java/org/apposed/appose/mamba/FileDownloader.java @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package org.apposed.appose; +package org.apposed.appose.mamba; import java.io.FileOutputStream; import java.io.IOException; diff --git a/src/main/java/org/apposed/appose/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java similarity index 99% rename from src/main/java/org/apposed/appose/Mamba.java rename to src/main/java/org/apposed/appose/mamba/Mamba.java index be7fe61..cdf587c 100644 --- a/src/main/java/org/apposed/appose/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -57,7 +57,7 @@ * POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -package org.apposed.appose; +package org.apposed.appose.mamba; import org.apache.commons.compress.archivers.ArchiveException; import org.apache.commons.compress.utils.FileNameUtils; @@ -65,7 +65,7 @@ import com.sun.jna.Platform; -import org.apposed.appose.CondaException.EnvironmentExistsException; +import org.apposed.appose.mamba.CondaException.EnvironmentExistsException; import java.io.BufferedReader; import java.io.File; @@ -73,7 +73,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.OutputStream; import java.net.URISyntaxException; import java.net.URL; import java.nio.channels.Channels; @@ -91,8 +90,6 @@ import java.util.UUID; import java.util.function.Consumer; import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; /** * Python environment manager, implemented by delegating to micromamba. diff --git a/src/main/java/org/apposed/appose/MambaInstallException.java b/src/main/java/org/apposed/appose/mamba/MambaInstallException.java similarity index 98% rename from src/main/java/org/apposed/appose/MambaInstallException.java rename to src/main/java/org/apposed/appose/mamba/MambaInstallException.java index efbd5d6..d0dc3f8 100644 --- a/src/main/java/org/apposed/appose/MambaInstallException.java +++ b/src/main/java/org/apposed/appose/mamba/MambaInstallException.java @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package org.apposed.appose; +package org.apposed.appose.mamba; /** * Exception to be thrown when Micromamba is not found in the wanted directory diff --git a/src/main/java/org/apposed/appose/MambaInstallerUtils.java b/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java similarity index 99% rename from src/main/java/org/apposed/appose/MambaInstallerUtils.java rename to src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java index 3b7949e..6a90b23 100644 --- a/src/main/java/org/apposed/appose/MambaInstallerUtils.java +++ b/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java @@ -27,7 +27,7 @@ * #L% */ -package org.apposed.appose; +package org.apposed.appose.mamba; import java.io.BufferedInputStream; import java.io.File; From 0c57e9848d91db58a50f5e4c713005c1da526ee5 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 21 Aug 2024 17:04:42 -0500 Subject: [PATCH 079/120] Revise builder notes to address design wrinkles --- notes.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/notes.md b/notes.md index cbb3b98..07c0ebf 100644 --- a/notes.md +++ b/notes.md @@ -29,7 +29,7 @@ The interface is `BuildHandler`: And the implementations + supported schemes are: * `PixiHandler` -- `environment.yml`, `pixi.toml`, `pypi`, `conda`, and null. * `MavenHandler` -- `maven` -* `OpenJDKHandler` -- `openjdk` +* `JDKHandler` -- `openjdk` Although the term "scheme" might be confused with URI scheme, other terms are problematic too: * "platform" will be confused with OS/arch. @@ -73,6 +73,63 @@ Appose.env() .build() // Whew! ``` +### 2024-08-20 update + +One tricky thing is the base directory when combining paradigms: +* Get rid of "base directory" naming (in conda, the "base" environment is something else, so it's a confusing word here) in favor of `${appose-cache-dir}/${env-name}` convention. By default, `appose-cache-dir` equals `~/.local/share/appose`, but we could provide a way to override it... +* Similarly, get rid of the `base(...)` builder method in favor of adding a `build(String envName) -> Environment` signature. +* But what to name `Environment#base` property now? I really like `base`... Maybe `basedir`? Or `prefix``? +* Each `BuildHandler` catalogs the `include` and `channel` calls it feels are relevant, but does not do anything until `build` is finally called. +* If `build()` is called with no args, then the build handlers are queried sequentially (`default String envName() { return null; }`?). The first non-null name that comes back is taken as truth and then `build(thatName)` is passed to all handlers. Otherwise, an exception is raised "No environment name given". +* Environments all live in `~/.local/share/appose/ `, where ` ` is the name of the environment. If `name:` is given in a `pixi.toml` or `environment.yml`, great, the `PixiBuildHandler` can parse out that string when its `envName()` method is called. + +What about starting child processes via `pixi run`? That's not agnostic of the builder... +* What if the build handlers are also involved in child process launches? The `service(exes, args)` method could delegate to them... +* `BuildHandler` → `EnvHandler`? +* In `Environment`, how about replacing `use_system_path` with just a `path` list of dirs to check for executables being launched by `service`? Then we could dispense with the boilerplate `python`, `bin/python` repetition. +* Each env handler gets a chance to influence the worker launch args... and/or bin dirs... + - The pixi handler could prepend `.../pixi run` when a `pixi.toml` is present. + But this is tricky, because it goes *before* the selected exe... pre-args vs post-args uhhh + +So, environment has: +* path (list of directories -- only used if `all_args[0]` is not already an absolute path to an executable program already?) +* launcher (list of string args to prepend) +* classpath (list of elements to include when running java) + +* `Map >` is what's returned by the `build(...)` step of each `BuildHandler`. + - Relevant keys include: "path", "classpath", "launcher" + - The `new Environment() { ... }` invocation will aggregate the values given here into its accessors. + +* When running a service, it first uses the path to search for the requested exe, before prepending the launcher args. + - What about pixi + cjdk? We'll need the full path to java... + - How can we tell the difference between that and pixi alone with openjdk from conda-forge? + - In the former case, we need the full path, in the latter case, no. + - Pixi should just put `${envDir}/bin` onto the path, no? + - There is an edge case where `pixi run foo` works, even though `foo` is not an executable on the path... in which case, the environment service can just plow ahead with it when it can't find a `foo` executable on the path. But it should *first* make an attempt to reify the `foo` from the environment `path` before punting in that way. + +#### pixi + +During its build step, it prepends `[/path/to/pixi, run]` to the environment's launcher list, and `${env-dir}/bin` to the environment's path list. + +#### cjdk + +``` +cjdk -j adoptium:21 java-home +/home/curtis/.cache/cjdk/v0/jdks/d217ee819493b9c56beed2e4d481e4c370de993d/jdk-21.0.4+7 +/home/curtis/.cache/cjdk/v0/jdks/d217ee819493b9c56beed2e4d481e4c370de993d/jdk-21.0.4+7/bin/java -version +openjdk version "21.0.4" 2024-07-16 LTS +OpenJDK Runtime Environment Temurin-21.0.4+7 (build 21.0.4+7-LTS) +OpenJDK 64-Bit Server VM Temurin-21.0.4+7 (build 21.0.4+7-LTS, mixed mode, sharing) +``` + +So `JDKHandler`, during its build step, prepends the java-home directory to the environment's path: `$(cjdk -j adoptium:21 java-home)/bin` + +#### Maven + +No need to add any directories to the environment path! + +However, if Maven artifacts are added via `includes`, they should not only be downloaded, but also be part of the class path when launching java-based programs. We could do that by putting classpath into the `Environment` class directly, along side `path`... it's only a little hacky ;_; + ## Pixi Is even better than micromamba. It's a great fit for Appose's requirements. @@ -174,7 +231,7 @@ So we'll just settle for pixi's standard behavior here: a single environment named `default` per pixi project, with one pixi project per Appose environment. Unfortunately, that means our environment directory structure will be: ``` -~/.local/share/appose/sc-fiji-spiff/envs/default +~/.local/share/appose/sc-fiji-spiff/.pixi/envs/default ``` for an Appose environment named `sc-fiji-spiff`. (Note that environment names cannot contain dots, only alphameric and dash.) @@ -197,7 +254,7 @@ So there is no escape from pixi's directory convention of: ``` Which of these is the least annoying? ``` -~/.local/share/appose/sc-fiji-spiff/envs/default +~/.local/share/appose/sc-fiji-spiff/.pixi/envs/default ~/.local/share/appose/sc-fiji-spiff-782634298734/envs/default ~/.local/share/appose/sc-fiji-spiff/envs/sc-fiji-spiff ~/.local/share/appose/sc-fiji-spiff-782634298734/envs/sc-fiji-spiff @@ -209,7 +266,7 @@ behavior anyway. The only shorter one would be: ``` -~/.local/share/appose/envs/sc-fiji-spiff +~/.local/share/appose/.pixi/envs/sc-fiji-spiff ``` if we opted to keep `~/.local/share/appose` as a single Pixi project root with multiple environments... but the inconvenience and risks From 7fee9af1b4e6d672eae3be362aa1773bda35690b Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 22 Aug 2024 15:22:16 -0500 Subject: [PATCH 080/120] Builder: remove unused imports --- src/main/java/org/apposed/appose/Builder.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/org/apposed/appose/Builder.java b/src/main/java/org/apposed/appose/Builder.java index 5730a47..d8185ef 100644 --- a/src/main/java/org/apposed/appose/Builder.java +++ b/src/main/java/org/apposed/appose/Builder.java @@ -33,10 +33,6 @@ import java.io.IOException; import java.net.URISyntaxException; -import org.apache.commons.compress.archivers.ArchiveException; -import org.apposed.appose.mamba.Mamba; -import org.apposed.appose.mamba.MambaInstallException; - public class Builder { Builder() { From ba75fa04c1e685851194d12f4f0febe6a6a2eae4 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 22 Aug 2024 15:22:44 -0500 Subject: [PATCH 081/120] Encapsulate ArchiveException from unTar failures IOException is close enough. --- src/main/java/org/apposed/appose/Builder.java | 2 +- src/main/java/org/apposed/appose/mamba/Mamba.java | 6 ++---- .../org/apposed/appose/mamba/MambaInstallerUtils.java | 10 +++++----- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/apposed/appose/Builder.java b/src/main/java/org/apposed/appose/Builder.java index d8185ef..1131ba9 100644 --- a/src/main/java/org/apposed/appose/Builder.java +++ b/src/main/java/org/apposed/appose/Builder.java @@ -59,7 +59,7 @@ public Environment build() { } base = conda.getEnvDir(envName); // TODO: If baseDir is already set, we should use that directory instead. - } catch (IOException | InterruptedException | ArchiveException | URISyntaxException | MambaInstallException e) { + } catch (IOException | InterruptedException | URISyntaxException | MambaInstallException e) { throw new RuntimeException(e); } } diff --git a/src/main/java/org/apposed/appose/mamba/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java index cdf587c..5610df7 100644 --- a/src/main/java/org/apposed/appose/mamba/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -59,7 +59,6 @@ package org.apposed.appose.mamba; -import org.apache.commons.compress.archivers.ArchiveException; import org.apache.commons.compress.utils.FileNameUtils; import org.apache.commons.lang3.SystemUtils; @@ -418,7 +417,7 @@ private File downloadMicromamba() throws IOException, URISyntaxException { } private void decompressMicromamba(final File tempFile) - throws IOException, ArchiveException, InterruptedException { + throws IOException, InterruptedException { final File tempTarFile = File.createTempFile( "micromamba", ".tar" ); tempTarFile.deleteOnExit(); MambaInstallerUtils.unBZip2(tempFile, tempTarFile); @@ -443,10 +442,9 @@ private void decompressMicromamba(final File tempFile) * If the current thread is interrupted by another thread while it * is waiting, then the wait is ended and an InterruptedException is * thrown. - * @throws ArchiveException if there is any error decompressing * @throws URISyntaxException if there is any error with the micromamba url */ - public void installMicromamba() throws IOException, InterruptedException, ArchiveException, URISyntaxException { + public void installMicromamba() throws IOException, InterruptedException, URISyntaxException { checkMambaInstalled(); if (installed) return; decompressMicromamba(downloadMicromamba()); diff --git a/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java b/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java index 6a90b23..1e0acdb 100644 --- a/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java +++ b/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java @@ -116,9 +116,8 @@ private static long copy(final InputStream input, final OutputStream output) thr * @param outputDir the output directory file. * @throws IOException * @throws FileNotFoundException - * @throws ArchiveException */ - public static void unTar(final File inputFile, final File outputDir) throws FileNotFoundException, IOException, ArchiveException, InterruptedException { + public static void unTar(final File inputFile, final File outputDir) throws FileNotFoundException, IOException, InterruptedException { try ( InputStream is = new FileInputStream(inputFile); @@ -143,7 +142,9 @@ public static void unTar(final File inputFile, final File outputDir) throws File } } } - } + } catch (ArchiveException e) { + throw new IOException(e); + } } @@ -153,11 +154,10 @@ public static void unTar(final File inputFile, final File outputDir) throws File * no args are required * @throws FileNotFoundException if some file is not found * @throws IOException if there is any error reading or writting - * @throws ArchiveException if there is any error decompressing * @throws URISyntaxException if the url is wrong or there is no internet connection * @throws InterruptedException if there is interrruption */ - public static void main(String[] args) throws FileNotFoundException, IOException, ArchiveException, URISyntaxException, InterruptedException { + public static void main(String[] args) throws FileNotFoundException, IOException, URISyntaxException, InterruptedException { String url = Mamba.MICROMAMBA_URL; final File tempFile = File.createTempFile( "miniconda", ".tar.bz2" ); tempFile.deleteOnExit(); From 73b1518cbedd774cdb7a3733c8b1734cff593b26 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 22 Aug 2024 15:23:15 -0500 Subject: [PATCH 082/120] Add TODO to fix the user agent It should use the current version string of Appose, not hardcoded 0.1.0. And it's missing a terminating right paren. --- src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java b/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java index 1e0acdb..64353bb 100644 --- a/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java +++ b/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java @@ -231,6 +231,7 @@ public static long getFileSize(URL url) { HttpURLConnection conn = null; try { conn = (HttpURLConnection) url.openConnection(); + // TODO: Fix user agent. conn.setRequestProperty("User-Agent", "Appose/0.1.0(" + System.getProperty("os.name") + "; Java " + System.getProperty("java.version")); if (conn.getResponseCode() >= 300 && conn.getResponseCode() <= 308) return getFileSize(redirectedURL(url)); From f8b8248186074d3bedd73851014191a195a1e8ad Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 26 Aug 2024 22:18:39 -0500 Subject: [PATCH 083/120] Add FilePaths methods to merge directories It's tricky when opinionated build handlers want total control of the environment directory. With this functionality, we can give each handler what it wants, and then merge everything back together after the fact. --- .../java/org/apposed/appose/FilePaths.java | 114 +++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apposed/appose/FilePaths.java b/src/main/java/org/apposed/appose/FilePaths.java index 1a3d2ee..876db7e 100644 --- a/src/main/java/org/apposed/appose/FilePaths.java +++ b/src/main/java/org/apposed/appose/FilePaths.java @@ -30,12 +30,16 @@ package org.apposed.appose; import java.io.File; +import java.io.IOException; import java.net.URISyntaxException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; /** - * Utility methods for working with file paths. + * Utility methods for working with files. */ public final class FilePaths { @@ -78,4 +82,112 @@ public static File findExe(List dirs, List exes) { } return null; } + + /** + * Merges the files of the given source directory into the specified destination directory. + * + * For example, {@code moveDirectory(foo, bar)} would move: + *
+ *+ *
+ * + * @param srcDir TODO + * @param destDir TODO + * @param overwrite TODO + */ + public static void moveDirectory(File srcDir, File destDir, boolean overwrite) throws IOException { + if (!srcDir.isDirectory()) throw new IllegalArgumentException("Not a directory: " + srcDir); + if (!destDir.isDirectory()) throw new IllegalArgumentException("Not a directory: " + destDir); + try (DirectoryStream- {@code foo/a.txt} → {@code bar/a.txt}
+ *- {@code foo/b.dat} → {@code bar/b.dat}
+ *- {@code foo/c.csv} → {@code bar/c.csv}
+ *- {@code foo/subfoo/d.doc} → {@code bar/subfoo/d.doc}
+ *- etc.
+ *stream = Files.newDirectoryStream(srcDir.toPath())) { + for (Path srcPath : stream) moveFile(srcPath.toFile(), destDir, overwrite); + } + if (!srcDir.delete()) throw new IOException("Could not remove directory " + destDir); + } + + /** + * Moves the given source file to the destination directory, + * creating intermediate destination directories as needed. + * + * If the destination {@code file.ext} already exists, one of two things will happen: either + * A) the existing destination file will be renamed as a backup to {@code file.ext.old}—or + * {@code file.ext.0.old}, {@code file.ext.1.old}, etc., if {@code file.ext.old} also already exists—or + * B) the source file will be renamed as a backup in this manner. + * Which behavior occurs depends on the value of the {@code overwrite} flag: + * true to back up the destination file, or false to back up the source file. + *
+ * + * @param srcFile Source file to move. + * @param destDir Destination directory into which the file will be moved. + * @param overwrite If true, "overwrite" the destination file with the source file, + * backing up any existing destination file first; if false, + * leave the original destination file in place, instead moving + * the source file to a backup destination as a "previous" version. + * @throws IOException If something goes wrong with the needed I/O operations. + */ + public static void moveFile(File srcFile, File destDir, boolean overwrite) throws IOException { + File destFile = new File(destDir, srcFile.getName()); + if (srcFile.isDirectory()) { + // Create matching destination directory as needed. + if (!destFile.exists() && !destFile.mkdirs()) + throw new IOException("Failed to create destination directory: " + destDir); + // Recurse over source directory contents. + moveDirectory(srcFile, destFile, overwrite); + return; + } + // Source file is not a directory; move it into the destination directory. + if (destDir.exists() && !destDir.isDirectory()) throw new IllegalArgumentException("Non-directory destination path: " + destDir); + if (!destDir.exists() && !destDir.mkdirs()) throw new IOException("Failed to create destination directory: " + destDir); + if (destFile.exists() && !overwrite) { + // Destination already exists, and we aren't allowed to rename it. So we instead + // rename the source file directly to a backup filename in the destination directory. + renameToBackup(srcFile, destDir); + return; + } + + // Rename the existing destination file (if any) to a + // backup file, then move the source file into place. + renameToBackup(destFile); + if (!srcFile.renameTo(destFile)) throw new IOException("Failed to move file: " + srcFile + " -> " + destFile); + } + + /** + * TODO + * + * @param srcFile TODO + * @throws IOException If something goes wrong with the needed I/O operations. + */ + public static void renameToBackup(File srcFile) throws IOException { + renameToBackup(srcFile, srcFile.getParentFile()); + } + + /** + * TODO + * + * @param srcFile TODO + * @param destDir TODO + * @throws IOException If something goes wrong with the needed I/O operations. + */ + public static void renameToBackup(File srcFile, File destDir) throws IOException { + if (!srcFile.exists()) return; // Nothing to back up! + String prefix = srcFile.getName(); + String suffix = "old"; + File backupFile = new File(destDir, prefix + "." + suffix); + for (int i = 0; i < 1000; i++) { + if (!backupFile.exists()) break; + // The .old backup file already exists! Try .0.old, .1.old, and so on. + backupFile = new File(destDir, prefix + "." + i + "." + suffix); + } + if (backupFile.exists()) { + File failedTarget = new File(destDir, prefix + "." + suffix); + throw new UnsupportedOperationException("Too many backup files already exist for target: " + failedTarget); + } + if (!srcFile.renameTo(backupFile)) { + throw new IOException("Failed to rename file:" + srcFile + " -> " + backupFile); + } + } } From c5e0db2fe0ef3c60566f31011b9460d499b3d746 Mon Sep 17 00:00:00 2001 From: Curtis RuedenDate: Mon, 26 Aug 2024 14:31:29 -0500 Subject: [PATCH 084/120] Test the FilePaths utility methods --- .../org/apposed/appose/FilePathsTest.java | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 src/test/java/org/apposed/appose/FilePathsTest.java diff --git a/src/test/java/org/apposed/appose/FilePathsTest.java b/src/test/java/org/apposed/appose/FilePathsTest.java new file mode 100644 index 0000000..24396fa --- /dev/null +++ b/src/test/java/org/apposed/appose/FilePathsTest.java @@ -0,0 +1,195 @@ +/*- + * #%L + * Appose: multi-language interprocess cooperation with shared memory. + * %% + * Copyright (C) 2023 - 2024 Appose developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.apposed.appose; + +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests {@link FilePaths}. + * + * @author Curtis Rueden + */ +public class FilePathsTest { + + /** Tests {@link FilePaths#findExe}. */ + @Test + public void testFindExe() throws IOException { + File tmpDir = Files.createTempDirectory("appose-FilePathsTest-testFindExe-").toFile(); + try { + // Set up some red herrings. + File walk = createStubFile(tmpDir, "walk"); + File fly = createStubFile(tmpDir, "fly"); + File binDir = createDirectory(tmpDir, "bin"); + File binFly = createStubFile(binDir, "fly"); + // Mark the desired match as executable. + assertTrue(binFly.setExecutable(true)); + assertTrue(binFly.canExecute()); + + // Search for the desired match. + List dirs = Arrays.asList(tmpDir.getAbsolutePath(), binDir.getAbsolutePath()); + List exes = Arrays.asList("walk", "fly", "swim"); + File exe = FilePaths.findExe(dirs, exes); + + // Check that we found the right file. + assertEquals(binFly, exe); + } + finally { + FileUtils.deleteDirectory(tmpDir); + } + } + + /** Tests {@link FilePaths#location}. */ + @Test + public void testLocation() { + // NB: Will fail if this test is run in a weird way (e.g. + // from inside the tests JAR), but I don't care right now. :-P + File expected = Paths.get(System.getProperty("user.dir"), "target", "test-classes").toFile(); + File actual = FilePaths.location(getClass()); + assertEquals(expected, actual); + } + + /** Tests {@link FilePaths#moveDirectory}. */ + @Test + public void testMoveDirectory() throws IOException { + File tmpDir = Files.createTempDirectory("appose-FilePathsTest-testMoveDirectory-").toFile(); + try { + // Set up a decently weighty directory structure. + File srcDir = createDirectory(tmpDir, "src"); + File breakfast = createStubFile(srcDir, "breakfast"); + File lunchDir = createDirectory(srcDir, "lunch"); + File lunchFile1 = createStubFile(lunchDir, "apples", "fuji"); + File lunchFile2 = createStubFile(lunchDir, "bananas"); + File dinnerDir = createDirectory(srcDir, "dinner"); + File dinnerFile1 = createStubFile(dinnerDir, "bread"); + File dinnerFile2 = createStubFile(dinnerDir, "wine"); + File destDir = createDirectory(tmpDir, "dest"); + File destLunchDir = createDirectory(destDir, "lunch"); + File destLunchFile1 = createStubFile(destLunchDir, "apples", "gala"); + + // Move the source directory to the destination. + FilePaths.moveDirectory(srcDir, destDir, false); + + // Check whether everything worked. + assertFalse(srcDir.exists()); + assertMoved(breakfast, destDir, " "); + assertMoved(lunchFile1, destLunchDir, "gala"); + File backupLunchFile1 = new File(destLunchDir, "apples.old"); + assertContent(backupLunchFile1, "fuji"); + assertMoved(lunchFile2, destLunchDir, " "); + File destDinnerDir = new File(destDir, dinnerDir.getName()); + assertMoved(dinnerFile1, destDinnerDir, " "); + assertMoved(dinnerFile2, destDinnerDir, " "); + } + finally { + FileUtils.deleteDirectory(tmpDir); + } + } + + /** Tests {@link FilePaths#moveFile}. */ + @Test + public void testMoveFile() throws IOException { + File tmpDir = Files.createTempDirectory("appose-FilePathsTest-testMoveFile-").toFile(); + try { + File srcDir = createDirectory(tmpDir, "from"); + File srcFile = createStubFile(srcDir, "stuff.txt", "shiny"); + File destDir = createDirectory(tmpDir, "to"); + File destFile = createStubFile(destDir, "stuff.txt", "obsolete"); + boolean overwrite = true; + + FilePaths.moveFile(srcFile, destDir, overwrite); + + assertTrue(srcDir.exists()); + assertFalse(srcFile.exists()); + assertContent(destFile, "shiny"); + File backupFile = new File(destDir, "stuff.txt.old"); + assertContent(backupFile, "obsolete"); + } + finally { + FileUtils.deleteDirectory(tmpDir); + } + } + + /** Tests {@link FilePaths#renameToBackup}. */ + @Test + public void testRenameToBackup() throws IOException { + File tmpFile = Files.createTempFile("appose-FilePathsTest-testRenameToBackup-", "").toFile(); + assertTrue(tmpFile.exists()); + tmpFile.deleteOnExit(); + FilePaths.renameToBackup(tmpFile); + File backupFile = new File(tmpFile.getParent(), tmpFile.getName() + ".old"); + backupFile.deleteOnExit(); + assertFalse(tmpFile.exists()); + assertTrue(backupFile.exists()); + } + + private File createDirectory(File parent, String name) { + File dir = new File(parent, name); + assertTrue(dir.mkdir()); + assertTrue(dir.exists()); + return dir; + } + + private File createStubFile(File dir, String name) throws IOException { + return createStubFile(dir, name, "<" + name + ">"); + } + + private File createStubFile(File dir, String name, String content) throws IOException { + File stubFile = new File(dir, name); + try (PrintWriter pw = new PrintWriter(new FileWriter(stubFile))) { + pw.print(content); + } + assertTrue(stubFile.exists()); + return stubFile; + } + + private void assertMoved(File srcFile, File destDir, String expectedContent) throws IOException { + assertFalse(srcFile.exists()); + File destFile = new File(destDir, srcFile.getName()); + assertContent(destFile, expectedContent); + } + + private void assertContent(File file, String expectedContent) throws IOException { + assertTrue(file.exists()); + String actualContent = new String(Files.readAllBytes(file.toPath())); + assertEquals(expectedContent, actualContent); + } +} From 18a313e7c5ed4477c86ff3267d50d5cca9361a81 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 26 Aug 2024 22:21:07 -0500 Subject: [PATCH 085/120] Add BuildHandler interface --- .../java/org/apposed/appose/BuildHandler.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/main/java/org/apposed/appose/BuildHandler.java diff --git a/src/main/java/org/apposed/appose/BuildHandler.java b/src/main/java/org/apposed/appose/BuildHandler.java new file mode 100644 index 0000000..7315f53 --- /dev/null +++ b/src/main/java/org/apposed/appose/BuildHandler.java @@ -0,0 +1,72 @@ +/*- + * #%L + * Appose: multi-language interprocess cooperation with shared memory. + * %% + * Copyright (C) 2023 - 2024 Appose developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.apposed.appose; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public interface BuildHandler { + + /** + * Registers a channel from which elements of the environment can be obtained. + * + * @param name The name of the channel to register. + * @param location The location of the channel (e.g. a URI), or {@code null} if the + * name alone is sufficient to unambiguously identify the channel. + * @return true iff the channel is understood by this build handler implementation. + * @see Builder#channel + */ + boolean channel(String name, String location); + + /** + * Registers content to be included within the environment. + * + * @param content The content to include in the environment, fetching if needed. + * @param scheme The type of content, which serves as a hint for + * how to interpret the content in some scenarios. + * @see Builder#include + */ + boolean include(String content, String scheme); + + /** Suggests a name for the environment currently being built. */ + String envName(); + + /** + * Executes the environment build, according to the configured channels and includes. + * + * @param envDir The directory into which the environment will be built. + * @param config The table into which environment configuration will be recorded. + * @see Builder#build(String) + * @throws IOException If something goes wrong building the environment. + */ + void build(File envDir, Map > config) throws IOException; +} From c3dcf0d455290b7d869675812a24fd06354691fb Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Tue, 27 Aug 2024 07:59:55 -0500 Subject: [PATCH 086/120] Fix Mamba class authors --- src/main/java/org/apposed/appose/mamba/Mamba.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/apposed/appose/mamba/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java index 5610df7..73c81e2 100644 --- a/src/main/java/org/apposed/appose/mamba/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -94,7 +94,7 @@ * Python environment manager, implemented by delegating to micromamba. * * @author Ko Sugawara - * @author Curtis Rueden + * @author Carlos Garcia */ public class Mamba { From 391ac2d6ae91cdeda9d569a6ec185c769169918a Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Tue, 27 Aug 2024 08:00:08 -0500 Subject: [PATCH 087/120] Replace non-breaking spaces with regular spaces --- .../java/org/apposed/appose/mamba/Mamba.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/apposed/appose/mamba/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java index 73c81e2..85cdf42 100644 --- a/src/main/java/org/apposed/appose/mamba/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -112,11 +112,11 @@ public class Mamba { * * rootdir * ├── bin - * │ ├── micromamba(.exe) - * │ ... + * │ ├── micromamba(.exe) + * │ ... * ├── envs - * │ ├── your_env - * │ │ ├── python(.exe) + * │ ├── your_env + * │ │ ├── python(.exe) **/ private final String rootdir; @@ -276,11 +276,11 @@ private ProcessBuilder getBuilder( final boolean isInheritIO ) ** MAMBA_ROOT * ├── bin - * │ ├── micromamba(.exe) - * │ ... + * │ ├── micromamba(.exe) + * │ ... * ├── envs - * │ ├── your_env - * │ │ ├── python(.exe) + * │ ├── your_env + * │ │ ├── python(.exe) **/ public Mamba() { @@ -298,11 +298,11 @@ public Mamba() { ** MAMBA_ROOT * ├── bin - * │ ├── micromamba(.exe) - * │ ... + * │ ├── micromamba(.exe) + * │ ... * ├── envs - * │ ├── your_env - * │ │ ├── python(.exe) + * │ ├── your_env + * │ │ ├── python(.exe) ** * @param rootdir From 48885672ea10c2bece91f2c97c92665fa449ed8e Mon Sep 17 00:00:00 2001 From: Curtis RuedenDate: Tue, 27 Aug 2024 08:01:59 -0500 Subject: [PATCH 088/120] Make copyright notice not be a javadoc --- src/main/java/org/apposed/appose/mamba/Mamba.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apposed/appose/mamba/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java index 85cdf42..f97e020 100644 --- a/src/main/java/org/apposed/appose/mamba/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -30,7 +30,7 @@ // Adapted from JavaConda (https://github.com/javaconda/javaconda), // which has the following license: -/******************************************************************************* +/*-***************************************************************************** * Copyright (C) 2021, Ko Sugawara * All rights reserved. * @@ -55,7 +55,7 @@ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ + ****************************************************************************-*/ package org.apposed.appose.mamba; From e6b238299b08547bd11bc19cb9596c2fff91b7b0 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 30 Aug 2024 12:56:10 -0500 Subject: [PATCH 089/120] Migrate Mamba builder to a BuildHandler plugin --- src/main/java/org/apposed/appose/Appose.java | 58 +++- src/main/java/org/apposed/appose/Builder.java | 317 +++++++++++++++--- .../java/org/apposed/appose/Environment.java | 34 +- .../java/org/apposed/appose/mamba/Mamba.java | 24 +- .../apposed/appose/mamba/MambaHandler.java | 203 +++++++++++ .../services/org.apposed.appose.BuildHandler | 1 + .../java/org/apposed/appose/ApposeTest.java | 4 +- .../apposed/appose/NDArrayExamplePython.java | 2 +- 8 files changed, 552 insertions(+), 91 deletions(-) create mode 100644 src/main/java/org/apposed/appose/mamba/MambaHandler.java create mode 100644 src/main/resources/META-INF/services/org.apposed.appose.BuildHandler diff --git a/src/main/java/org/apposed/appose/Appose.java b/src/main/java/org/apposed/appose/Appose.java index b12be19..a84d5f6 100644 --- a/src/main/java/org/apposed/appose/Appose.java +++ b/src/main/java/org/apposed/appose/Appose.java @@ -30,6 +30,7 @@ package org.apposed.appose; import java.io.File; +import java.io.IOException; /** * Appose is a library for interprocess cooperation with shared memory. The @@ -152,27 +153,64 @@ */ public class Appose { - public static Builder base(File directory) { - return new Builder().base(directory); + public static Builder scheme(String scheme) { + return new Builder().scheme(scheme); } - public static Builder base(String directory) { - return base(new File(directory)); + public static Builder file(String filePath) throws IOException { + return new Builder().file(filePath); } - public static Builder conda(File environmentYaml) { - return new Builder().conda(environmentYaml); + public static Builder file(String filePath, String scheme) throws IOException { + return new Builder().file(filePath, scheme); } - public static Environment system() { + public static Builder file(File file) throws IOException { + return new Builder().file(file); + } + + public static Builder file(File file, String scheme) throws IOException { + return new Builder().file(file, scheme); + } + + public static Builder channel(String name) { + return new Builder().channel(name); + } + + public static Builder channel(String name, String location) { + return new Builder().channel(name, location); + } + + public static Builder include(String content) { + return new Builder().include(content); + } + + public static Builder include(String content, String scheme) { + return new Builder().include(content, scheme); + } + + @Deprecated + public static Builder conda(File environmentYaml) throws IOException { + return file(environmentYaml, "environment.yml"); + } + + public static Environment build(File directory) throws IOException { + return new Builder().build(directory); + } + + public static Environment build(String directory) throws IOException { + return build(new File(directory)); + } + + public static Environment system() throws IOException { return system(new File(".")); } - public static Environment system(File directory) { - return new Builder().base(directory).useSystemPath().build(); + public static Environment system(File directory) throws IOException { + return new Builder().useSystemPath().build(directory); } - public static Environment system(String directory) { + public static Environment system(String directory) throws IOException { return system(new File(directory)); } } diff --git a/src/main/java/org/apposed/appose/Builder.java b/src/main/java/org/apposed/appose/Builder.java index 1131ba9..b10958f 100644 --- a/src/main/java/org/apposed/appose/Builder.java +++ b/src/main/java/org/apposed/appose/Builder.java @@ -31,69 +31,288 @@ import java.io.File; import java.io.IOException; -import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.function.Function; +import java.util.stream.Collectors; +/** + * TODO + * + * @author Curtis Rueden + */ public class Builder { - - Builder() { - // Prevent external instantiation. - } - - public Environment build() { - // TODO Build the thing!~ - // Hash the state to make a base directory name. - // - Construct conda environment from condaEnvironmentYaml. - // - Download and unpack JVM of the given vendor+version. - // - Populate ${baseDirectory}/jars with Maven artifacts? - - final String base; - if (condaEnvironmentYaml != null) { - try { - Mamba conda = new Mamba(Mamba.BASE_PATH); - conda.installMicromamba(); - String envName = Mamba.envNameFromYaml(condaEnvironmentYaml); - if (conda.getEnvironmentNames().contains(envName)) { - // TODO: Should we update it? For now, we just use it. - } else { - conda.createWithYaml(envName, condaEnvironmentYaml.getAbsolutePath()); - } - base = conda.getEnvDir(envName); - // TODO: If baseDir is already set, we should use that directory instead. - } catch (IOException | InterruptedException | URISyntaxException | MambaInstallException e) { - throw new RuntimeException(e); - } - } - else base = baseDir.getPath(); - final boolean useSystemPath = systemPath; - return new Environment() { - @Override public String base() { return base; } - @Override public boolean useSystemPath() { return useSystemPath; } - }; - } + private final List handlers; - // -- Configuration -- + private boolean includeSystemPath; + private String scheme = "conda"; - private boolean systemPath; + Builder() { + handlers = new ArrayList<>(); + ServiceLoader.load(BuildHandler.class).forEach(handlers::add); + } + /** + * TODO + * + * @return This {@code Builder} instance, for fluent-style programming. + */ public Builder useSystemPath() { - systemPath = true; + includeSystemPath = true; return this; } - private File baseDir; - - public Builder base(File directory) { - baseDir = directory; + /** + * Sets the scheme to use with subsequent {@link #channel(String)} and + * {@link #include(String)} directives. + * + * @param scheme TODO + * @return This {@code Builder} instance, for fluent-style programming. + */ + public Builder scheme(String scheme) { + this.scheme = scheme; return this; } - // -- Conda -- + /** + * TODO + * + * @return This {@code Builder} instance, for fluent-style programming. + */ + public Builder file(String filePath) throws IOException { + return file(new File(filePath)); + } - private File condaEnvironmentYaml; + /** + * TODO + * + * @return This {@code Builder} instance, for fluent-style programming. + */ + public Builder file(String filePath, String scheme) throws IOException { + return file(new File(filePath), scheme); + } - public Builder conda(File environmentYaml) { - this.condaEnvironmentYaml = environmentYaml; - return this; + /** + * TODO + * + * @return This {@code Builder} instance, for fluent-style programming. + */ + public Builder file(File file) throws IOException { + return file(file, file.getName()); + } + + /** + * TODO + * + * @return This {@code Builder} instance, for fluent-style programming. + */ + public Builder file(File file, String scheme) throws IOException { + byte[] bytes = Files.readAllBytes(file.toPath()); + return include(new String(bytes), scheme); + } + + /** + * Registers a channel that provides components of the environment, + * according to the currently configured scheme ("conda" by default). + * + * For example, {@code channel("bioconda")} registers the {@code bioconda} + * channel as a source for conda packages. + *
+ * + * @param name The name of the channel to register. + * @return This {@code Builder} instance, for fluent-style programming. + * @see #channel(String, String) + * @see #scheme(String) + */ + public Builder channel(String name) { + return channel(name, scheme); + } + + /** + * Registers a channel that provides components of the environment. + * How to specify a channel is implementation-dependent. Examples: + * + *+ *
+ * + * @param name The name of the channel to register. + * @param location The location of the channel (e.g. a URI), or {@code null} if the + * name alone is sufficient to unambiguously identify the channel. + * @return This {@code Builder} instance, for fluent-style programming. + * @throws IllegalArgumentException if the channel is not understood by any of the available build handlers. + */ + public Builder channel(String name, String location) { + // Pass the channel directive to all handlers. + if (handle(handler -> handler.channel(name, location))) return this; + // None of the handlers accepted the directive. + throw new IllegalArgumentException("Unsupported channel: " + name + + (location == null ? "" : "=" + location)); + } + + /** + * TODO + * + * @param content TODO + * @return This {@code Builder} instance, for fluent-style programming. + * @see #include(String, String) + * @see #scheme(String) + */ + public Builder include(String content) { + return include(content, scheme); + } + + /** + * Registers content to be included within the environment. + * How to specify the content is implementation-dependent. Examples: + *- {@code channel("bioconda")} - + * to register the {@code bioconda} channel as a source for conda packages.
+ *- {@code channel("scijava", "maven:https://maven.scijava.org/content/groups/public")} - + * to register the SciJava Maven repository as a source for Maven artifacts.
+ *+ *
+ *- {@code include("cowsay", "pypi")} - + * Install {@code cowsay} from the Python package index.
+ *- {@code include("openjdk=17")} - + * Install {@code openjdk} version 17 from conda-forge.
+ *- {@code include("bioconda::sourmash")} - + * Specify a conda channel explicitly using environment.yml syntax.
+ *- {@code include("org.scijava:parsington", "maven")} - + * Install the latest version of Parsington from Maven Central.
+ *- {@code include("org.scijava:parsington:2.0.0", "maven")} - + * Install Parsington 2.0.0 from Maven Central.
+ *- {@code include("sc.fiji:fiji", "maven")} - + * Install the latest version of Fiji from registered Maven repositories.
+ *- {@code include("zulu:17", "jdk")} - + * Install version 17 of Azul Zulu OpenJDK.
+ *- {@code include(yamlString, "environment.yml")} - + * Provide the literal contents of a conda {@code environment.yml} file, + * indicating a set of packages to include. + *
+ * Note that content is not actually fetched or installed until + * {@link #build} is called at the end of the builder chain. + *
+ * + * @param content The content (e.g. a package name, or perhaps the contents of an environment + * configuration file) to include in the environment, fetching if needed. + * @param scheme The type of content, which serves as a hint for how to interpret + * the content in some scenarios; see above for examples. + * @return This {@code Builder} instance, for fluent-style programming. + * @throws IllegalArgumentException if the include directive is not understood by any of the available build handlers. + */ + public Builder include(String content, String scheme) { + // Pass the include directive to all handlers. + if (handle(handler -> handler.include(content, scheme))) return this; + // None of the handlers accepted the directive. + throw new IllegalArgumentException("Unsupported '" + scheme + "' content: " + content); + } + + /** + * Executes the environment build, according to the configured channels and includes, + * with a name inferred from the configuration registered earlier. For example, if + * {@code environment.yml} content was registered, the name from that configuration will be used. + * + * @return The newly constructed Appose {@link Environment}, + * from which {@link Service}s can be launched. + * @see #build(String) + * @throws IllegalStateException if no name can be inferred from included content. + * @throws IOException If something goes wrong building the environment. + */ + public Environment build() throws IOException { + // Autodetect the environment name from the available build handlers. + return build(handlers.stream() + .map(BuildHandler::envName) + .filter(Objects::nonNull) + .findFirst() + .orElse(null)); + } + + /** + * Executes the environment build, according to the configured channels and includes. + * with a base directory inferred from the given name. + * + * @param envName The name of the environment to build. + * @return The newly constructed Appose {@link Environment}, + * from which {@link Service}s can be launched. + * @throws IOException If something goes wrong building the environment. + */ + public Environment build(String envName) throws IOException { + if (envName == null || envName.isEmpty()) { + throw new IllegalArgumentException("No environment name given."); + } + // TODO: Make Appose's root directory configurable. + Path apposeRoot = Paths.get(System.getProperty("user.home"), ".local", "share", "appose"); + return build(apposeRoot.resolve(envName).toFile()); + } + + /** + * Executes the environment build, according to the configured channels and includes. + * with the given base directory. + * + * @param envDir The directory in which to construct the environment. + * @return The newly constructed Appose {@link Environment}, + * from which {@link Service}s can be launched. + * @throws IOException If something goes wrong building the environment. + */ + public Environment build(File envDir) throws IOException { + if (envDir == null) { + throw new IllegalArgumentException("No environment base directory given."); + } + if (!envDir.exists()) { + if (!envDir.mkdirs()) { + throw new RuntimeException("Failed to create environment base directory: " + envDir); + } + } + if (!envDir.isDirectory()) { + throw new IllegalArgumentException("Not a directory: " + envDir); + } + + Map> config = new HashMap<>(); + for (BuildHandler handler : handlers) handler.build(envDir, config); + + String base = envDir.getAbsolutePath(); + + List launchArgs = listFromConfig("launchArgs", config); + List binPaths = listFromConfig("binPaths", config); + List classpath = listFromConfig("classpath", config); + + // Always add environment directory itself to the binPaths. + // Especially important on Windows, where python.exe is not tucked into a bin subdirectory. + binPaths.add(envDir.getAbsolutePath()); + + if (includeSystemPath) { + List systemPaths = Arrays.asList(System.getenv("PATH").split(File.pathSeparator)); + binPaths.addAll(systemPaths); + } + + return new Environment() { + @Override public String base() { return base; } + @Override public List binPaths() { return binPaths; } + @Override public List classpath() { return classpath; } + @Override public List launchArgs() { return launchArgs; } + }; + } + + // -- Helper methods -- + + private boolean handle(Function handlerFunction) { + boolean handled = false; + for (BuildHandler handler : handlers) + handled |= handlerFunction.apply(handler); + return handled; + } + + private static List listFromConfig(String key, Map > config) { + List> value = config.getOrDefault(key, Collections.emptyList()); + return value.stream().map(Object::toString).collect(Collectors.toList()); } } diff --git a/src/main/java/org/apposed/appose/Environment.java b/src/main/java/org/apposed/appose/Environment.java index 4891b65..d442a6e 100644 --- a/src/main/java/org/apposed/appose/Environment.java +++ b/src/main/java/org/apposed/appose/Environment.java @@ -40,8 +40,10 @@ public interface Environment { - default String base() { return "."; } - default boolean useSystemPath() { return false; } + String base(); + List binPaths(); + List classpath(); + List launchArgs(); /** * Creates a Python script service. @@ -56,10 +58,7 @@ public interface Environment { * @throws IOException If something goes wrong starting the worker process. */ default Service python() throws IOException { - List pythonExes = Arrays.asList( - "python", "python3", "python.exe", - "bin/python", "bin/python.exe" - ); + List pythonExes = Arrays.asList("python", "python3", "python.exe"); return service(pythonExes, "-c", "import appose.python_worker; appose.python_worker.main()"); } @@ -179,19 +178,20 @@ default Service java(String mainClass, List classPath, * @throws IOException If something goes wrong starting the worker process. */ default Service service(List exes, String... args) throws IOException { - if (args.length == 0) throw new IllegalArgumentException("No executable given"); - - List dirs = useSystemPath() // - ? Arrays.asList(System.getenv("PATH").split(File.pathSeparator)) // - : Collections.singletonList(base()); + if (exes == null || exes.isEmpty()) throw new IllegalArgumentException("No executable given"); - File exeFile = FilePaths.findExe(dirs, exes); - if (exeFile == null) throw new IllegalArgumentException("No executables found amongst candidates: " + exes); + // Discern path to executable by searching the environment's binPaths. + File exeFile = FilePaths.findExe(binPaths(), exes); + // If exeFile is null, just use the first executable bare, because there + // are scenarios like `pixi run python` where the intended executable will + // only by part of the system path while within the activated environment. + String exe = exeFile == null ? exes.get(0) : exeFile.getCanonicalPath(); - String[] allArgs = new String[args.length + 1]; - System.arraycopy(args, 0, allArgs, 1, args.length); - allArgs[0] = exeFile.getCanonicalPath(); + // Construct final args list: launchArgs + exe + args + List allArgs = new ArrayList<>(launchArgs()); + allArgs.add(exe); + allArgs.addAll(Arrays.asList(args)); - return new Service(new File(base()), allArgs); + return new Service(new File(base()), allArgs.toArray(new String[0])); } } diff --git a/src/main/java/org/apposed/appose/mamba/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java index f97e020..eaac23e 100644 --- a/src/main/java/org/apposed/appose/mamba/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -535,8 +535,8 @@ public void updateIn( final String envName, final String... args ) throws IOExce /** * Run {@code conda create} to create a conda environment defined by the input environment yaml file. * - * @param envName - * The environment name to be created. + * @param envDir + * The directory within which the environment will be created. * @param envYaml * The environment yaml file containing the information required to build it * @throws IOException @@ -547,18 +547,18 @@ public void updateIn( final String envName, final String... args ) throws IOExce * thrown. * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used */ - public void createWithYaml( final String envName, final String envYaml ) throws IOException, InterruptedException, MambaInstallException + public void createWithYaml( final File envDir, final String envYaml ) throws IOException, InterruptedException, MambaInstallException { checkMambaInstalled(); if (!installed) throw new MambaInstallException("Micromamba is not installed"); - createWithYaml(envName, envYaml, false); + createWithYaml(envDir, envYaml, false); } /** * Run {@code conda create} to create a conda environment defined by the input environment yaml file. * - * @param envName - * The environment name to be created. It should not be a path, just the name. + * @param envDir + * The directory within which the environment will be created. * @param envYaml * The environment yaml file containing the information required to build it * @param isForceCreation @@ -574,17 +574,14 @@ public void createWithYaml( final String envName, final String envYaml ) throws * @throws RuntimeException if the process to create the env of the yaml file is not terminated correctly. If there is any error running the commands * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used */ - public void createWithYaml( final String envName, final String envYaml, final boolean isForceCreation) throws IOException, InterruptedException, RuntimeException, MambaInstallException + public void createWithYaml( final File envDir, final String envYaml, final boolean isForceCreation) throws IOException, InterruptedException, RuntimeException, MambaInstallException { - if (envName.contains(File.pathSeparator)) - throw new IllegalArgumentException("The environment name should not contain the file separator character: '" - + File.separator + "'"); checkMambaInstalled(); if (!installed) throw new MambaInstallException("Micromamba is not installed"); if ( !isForceCreation && getEnvironmentNames().contains( envName ) ) throw new EnvironmentExistsException(); runMamba("env", "create", "--prefix", - getEnvDir(envName), "-f", envYaml, "-y", "-vv" ); + envDir.getAbsolutePath(), "-f", envYaml, "-y", "-vv" ); } /** @@ -780,9 +777,9 @@ private void setEnvName( final String envName ) /** * Returns the active environment name. - * + * * @return The active environment name. - * + * */ public String getEnvName() { @@ -1312,6 +1309,7 @@ public List< String > getEnvironmentNames() throws IOException, MambaInstallExce envs.addAll( Files.list( Paths.get( envsdir ) ) .map( p -> p.getFileName().toString() ) .filter( p -> !p.startsWith( "." ) ) + .filter( p -> Paths.get(p, "conda-meta").toFile().isDirectory() ) .collect( Collectors.toList() ) ); return envs; } diff --git a/src/main/java/org/apposed/appose/mamba/MambaHandler.java b/src/main/java/org/apposed/appose/mamba/MambaHandler.java new file mode 100644 index 0000000..79f88c1 --- /dev/null +++ b/src/main/java/org/apposed/appose/mamba/MambaHandler.java @@ -0,0 +1,203 @@ +/*- + * #%L + * Appose: multi-language interprocess cooperation with shared memory. + * %% + * Copyright (C) 2023 - 2024 Appose developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.apposed.appose.mamba; + +import org.apposed.appose.BuildHandler; +import org.apposed.appose.FilePaths; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.stream.Collectors; + +/** A {@link BuildHandler} plugin powered by micromamba. */ +public class MambaHandler implements BuildHandler { + + private final List channels = new ArrayList<>(); + private final List condaIncludes = new ArrayList<>(); + private final List yamlIncludes = new ArrayList<>(); + private final List pypiIncludes = new ArrayList<>(); + + @Override + public boolean channel(String name, String location) { + if (location == null) { + // Assume it's a conda channel. + channels.add(name); + return true; + } + return false; + } + + @Override + public boolean include(String content, String scheme) { + if (content == null) throw new NullPointerException("content must not be null"); + if (scheme == null) throw new NullPointerException("scheme must not be null"); + switch (scheme) { + case "conda": + // It's a conda package (or newline-separated package list). + condaIncludes.addAll(lines(content)); + return true; + case "pypi": + // It's a PyPI package (or newline-separated package list). + pypiIncludes.addAll(lines(content)); + return true; + case "environment.yml": + yamlIncludes.add(content); + return true; + } + return false; + } + + @Override + public String envName() { + for (String yaml : yamlIncludes) { + String[] lines = yaml.split("(\r\n|\n|\r)"); + Optional name = Arrays.stream(lines) + .filter(line -> line.startsWith("name:")) + .map(line -> line.substring(5).trim().replace("\"", "")) + .findFirst(); + if (name.isPresent()) return name.get(); + } + return null; + } + + @Override + public void build(File envDir, Map > config) throws IOException { + if (!channels.isEmpty() || !condaIncludes.isEmpty() || !pypiIncludes.isEmpty()) { + throw new UnsupportedOperationException( + "Sorry, I don't know how to mix in additional packages from conda or PyPI yet." + + " Please put them in your environment.yml for now."); + } + if (yamlIncludes.isEmpty()) { + // Nothing for this handler to do. + fillConfig(envDir, config); + return; + } + if (yamlIncludes.size() > 1) { + throw new UnsupportedOperationException( + "Sorry, I can't synthesize micromamba environments from multiple environment.yml files yet." + + " Please use a single environment.yml for now."); + } + + // Is this envDir an already-existing conda directory? + // If so, we can update it. + if (new File(envDir, "conda-meta").isDirectory()) { + // This environment has already been populated. + // TODO: Should we update it? For now, we just use it. + fillConfig(envDir, config); + return; + } + + // Micromamba refuses to create an environment into an existing non-conda directory: + // + // "Non-conda folder exists at prefix" + // + // So if a non-conda directory already exists, we need to perform some + // contortions to make micromamba integrate with other build handlers: + // + // 1. If envDir already exists, rename it temporarily. + // 2. Run the micromamba command to create the environment. + // 3. Recursively move any previously existing contents from the + // temporary directory into the newly constructed one. + // 4. If moving an old file would overwrite a new file, put the old + // file back with a .old extension, so nothing is permanently lost. + // 5. As part of the move, remove the temp directories as they empty out. + + // Write out environment.yml from input content. + // We cannot write it into envDir, because mamba needs the directory to + // not exist yet in order to create the environment there. So we write it + // into a temporary work directory with a hopefully unique name. + Path envPath = envDir.getAbsoluteFile().toPath(); + String antiCollision = "" + (new Random().nextInt(90000000) + 10000000); + File workDir = envPath.resolveSibling(envPath.getFileName() + "." + antiCollision + ".tmp").toFile(); + if (envDir.exists()) { + if (envDir.isDirectory()) { + // Move aside the existing non-conda directory. + if (!envDir.renameTo(workDir)) { + throw new IOException("Failed to rename directory: " + envDir + " -> " + workDir); + } + } + else throw new IllegalArgumentException("Non-directory file already exists: " + envDir.getAbsolutePath()); + } + else if (!workDir.mkdirs()) { + throw new IOException("Failed to create work directory: " + workDir); + } + + // At this point, workDir exists and envDir does not. + // We want to write the environment.yml file into the work dir. + // But what if there is an existing environment.yml file in the work dir? + // Let's move it out of the way, rather than stomping on it. + File environmentYaml = new File(workDir, "environment.yml"); + FilePaths.renameToBackup(environmentYaml); + + // It should be safe to write out the environment.yml file now. + try (FileWriter fout = new FileWriter(environmentYaml)) { + fout.write(yamlIncludes.get(0)); + } + + // Finally, we can build the environment from the environment.yml file. + try { + Mamba conda = new Mamba(Mamba.BASE_PATH); + conda.installMicromamba(); + conda.createWithYaml(envDir, environmentYaml.getAbsolutePath()); + } catch (InterruptedException | URISyntaxException | MambaInstallException e) { + throw new IOException(e); + } + + // Lastly, we merge the contents of workDir into envDir. This will be + // at least the environment.yml file, and maybe other files from other handlers. + FilePaths.moveDirectory(workDir, envDir, false); + + fillConfig(envDir, config); + } + + private List lines(String content) { + return Arrays.stream(content.split("(\r\n|\n|\r)")) + .map(String::trim) + .filter(s -> !s.isEmpty() && !s.startsWith("#")) + .collect(Collectors.toList()); + } + + public void fillConfig(File envDir, Map > config) { + // If ${envDir}/bin directory exists, add it to binDirs. + File binDir = new File(envDir, "bin"); + if (binDir.isDirectory()) { + config.computeIfAbsent("binDirs", k -> new ArrayList<>()); + config.get("binDirs").add(binDir.getAbsolutePath()); + } + } +} diff --git a/src/main/resources/META-INF/services/org.apposed.appose.BuildHandler b/src/main/resources/META-INF/services/org.apposed.appose.BuildHandler new file mode 100644 index 0000000..4a96693 --- /dev/null +++ b/src/main/resources/META-INF/services/org.apposed.appose.BuildHandler @@ -0,0 +1 @@ +org.apposed.appose.mamba.MambaHandler diff --git a/src/test/java/org/apposed/appose/ApposeTest.java b/src/test/java/org/apposed/appose/ApposeTest.java index 263180d..d5cf6e5 100644 --- a/src/test/java/org/apposed/appose/ApposeTest.java +++ b/src/test/java/org/apposed/appose/ApposeTest.java @@ -91,11 +91,13 @@ public void testPython() throws IOException, InterruptedException { public void testConda() throws IOException, InterruptedException { Environment env = Appose.conda(new File("src/test/resources/envs/cowsay.yml")).build(); try (Service service = env.python()) { + //service.debug(System.err::println); Task task = service.task( "import cowsay\n" + "task.outputs['moo'] = cowsay.get_output_string('cow', 'moo')\n" ); task.waitFor(); + assertEquals(TaskStatus.COMPLETE, task.status); String expectedMoo = " ___\n" + "| moo |\n" + @@ -114,7 +116,7 @@ public void testConda() throws IOException, InterruptedException { @Test public void testServiceStartupFailure() throws IOException { - Environment env = Appose.base("no-pythons-to-be-found-here").build(); + Environment env = Appose.build("no-pythons-to-be-found-here"); try (Service service = env.python()) { fail("Python worker process started successfully!?"); } diff --git a/src/test/java/org/apposed/appose/NDArrayExamplePython.java b/src/test/java/org/apposed/appose/NDArrayExamplePython.java index e37582c..bf8fe92 100644 --- a/src/test/java/org/apposed/appose/NDArrayExamplePython.java +++ b/src/test/java/org/apposed/appose/NDArrayExamplePython.java @@ -52,7 +52,7 @@ public static void main(String[] args) throws Exception { } // pass to python (will be wrapped as numpy ndarray - final Environment env = Appose.base( "/opt/homebrew/Caskroom/miniforge/base/envs/appose/" ).build(); + final Environment env = Appose.build("/opt/homebrew/Caskroom/miniforge/base/envs/appose/"); try ( Service service = env.python() ) { final Map< String, Object > inputs = new HashMap<>(); inputs.put( "img", ndArray); From 59c2a47a5e1e3db796d79af85e6b960528592341 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 30 Aug 2024 14:55:17 -0500 Subject: [PATCH 090/120] Remove unused mamba-related functionality --- .../apposed/appose/mamba/CondaException.java | 124 --- .../java/org/apposed/appose/mamba/Mamba.java | 941 +----------------- 2 files changed, 17 insertions(+), 1048 deletions(-) delete mode 100644 src/main/java/org/apposed/appose/mamba/CondaException.java diff --git a/src/main/java/org/apposed/appose/mamba/CondaException.java b/src/main/java/org/apposed/appose/mamba/CondaException.java deleted file mode 100644 index 74fe23f..0000000 --- a/src/main/java/org/apposed/appose/mamba/CondaException.java +++ /dev/null @@ -1,124 +0,0 @@ -/*- - * #%L - * Appose: multi-language interprocess cooperation with shared memory. - * %% - * Copyright (C) 2023 - 2024 Appose developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package org.apposed.appose.mamba; - -public class CondaException -{ - - public static class EnvironmentExistsException extends RuntimeException - { - private static final long serialVersionUID = -1625119813967214783L; - - /** - * Constructs a new exception with {@code null} as its detail message. The cause - * is not initialized, and may subsequently be initialized by a call to - * {@link #initCause}. - */ - public EnvironmentExistsException() - { - super(); - } - - /** - * Constructs a new exception with the specified detail message. The cause is - * not initialized, and may subsequently be initialized by a call to - * {@link #initCause}. - * - * @param msg - * the detail message. The detail message is saved for later - * retrieval by the {@link #getMessage()} method. - */ - public EnvironmentExistsException( String msg ) - { - super( msg ); - } - - /** - * Constructs a new exception with the specified detail message and cause. - * - * Note that the detail message associated with {@code cause} is not - * automatically incorporated in this exception's detail message. - * - * @param message - * the detail message (which is saved for later retrieval by the - * {@link #getMessage()} method). - * @param cause - * the cause (which is saved for later retrieval by the - * {@link #getCause()} method). (A null value is permitted, - * and indicates that the cause is nonexistent or unknown.) - * @since 1.4 - */ - public EnvironmentExistsException( String message, Throwable cause ) - { - super( message, cause ); - } - - /** - * Constructs a new exception with the specified cause and a detail message of - * (cause==null ? null : cause.toString()) (which typically contains - * the class and detail message of cause). This constructor is useful - * for exceptions that are little more than wrappers for other throwables (for - * example, {@link java.security.PrivilegedActionException}). - * - * @param cause - * the cause (which is saved for later retrieval by the - * {@link #getCause()} method). (A null value is permitted, - * and indicates that the cause is nonexistent or unknown.) - * @since 1.4 - */ - public EnvironmentExistsException( Throwable cause ) - { - super( cause ); - } - - /** - * Constructs a new exception with the specified detail message, cause, - * suppression enabled or disabled, and writable stack trace enabled or - * disabled. - * - * @param message - * the detail message. - * @param cause - * the cause. (A {@code null} value is permitted, and indicates that - * the cause is nonexistent or unknown.) - * @param enableSuppression - * whether or not suppression is enabled or disabled - * @param writableStackTrace - * whether or not the stack trace should be writable - * @since 1.7 - */ - protected EnvironmentExistsException( String message, Throwable cause, - boolean enableSuppression, - boolean writableStackTrace ) - { - super( message, cause, enableSuppression, writableStackTrace ); - } - } - -} diff --git a/src/main/java/org/apposed/appose/mamba/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java index eaac23e..b5004ca 100644 --- a/src/main/java/org/apposed/appose/mamba/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -59,13 +59,8 @@ package org.apposed.appose.mamba; -import org.apache.commons.compress.utils.FileNameUtils; import org.apache.commons.lang3.SystemUtils; -import com.sun.jna.Platform; - -import org.apposed.appose.mamba.CondaException.EnvironmentExistsException; - import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; @@ -82,9 +77,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.function.Consumer; @@ -102,10 +95,6 @@ public class Mamba { * String containing the path that points to the micromamba executable */ final String mambaCommand; - /** - * Name of the environment where the changes are going to be applied - */ - private String envName; /** * Root directory of micromamba that also contains the environments folder * @@ -124,10 +113,6 @@ public class Mamba { * Path to the folder that contains the directories */ private final String envsdir; - /** - * Whether Micromamba is installed or not - */ - private boolean installed = false; /** * Progress made on the download from the Internet of the micromamba software. VAlue between 0 and 1. * @@ -173,14 +158,6 @@ public class Mamba { * This consumer saves all the log of every micromamba execution */ private final Consumer
@@ -268,7 +267,7 @@ public Mamba() { * Create a new Conda object. The root dir for Conda installation can be * specified as {@code String}. * If there is no Micromamba found at the specified path, it will be installed automatically - * if the parameter 'installIfNeeded' is true. If not a {@link MambaInstallException} will be thrown. + * if the parameter 'installIfNeeded' is true. If not an {@link IllegalStateException} will be thrown. *errConsumer = this::updateErrorConsumer; - /* - * Path to Python executable from the environment directory - */ - final static String PYTHON_COMMAND = SystemUtils.IS_OS_WINDOWS ? "python.exe" : "bin/python"; - /** - * Default name for a Python environment - */ - public final static String DEFAULT_ENVIRONMENT_NAME = "base"; /** * Relative path to the micromamba executable from the micromamba {@link #rootdir} */ @@ -323,24 +300,29 @@ public Mamba(final String rootdir) { } catch (Exception ex) { return; } - installed = true; } - + /** - * Check whether micromamba is installed or not to be able to use the instance of {@link Mamba} + * Gets whether micromamba is installed or not to be able to use the instance of {@link Mamba} * @return whether micromamba is installed or not to be able to use the instance of {@link Mamba} */ - public boolean checkMambaInstalled() { + public boolean isMambaInstalled() { try { getVersion(); - this.installed = true; - } catch (Exception ex) { - this.installed = false; + return true; + } catch (IOException | InterruptedException e) { return false; - } - return true; + } + } + + /** + * Check whether micromamba is installed or not to be able to use the instance of {@link Mamba} + * @throws MambaInstallException if micromamba is not installed + */ + private void checkMambaInstalled() throws MambaInstallException { + if (!isMambaInstalled()) throw new MambaInstallException("Micromamba is not installed"); } - + /** * * @return the progress made on the download from the Internet of the micromamba software. VAlue between 0 and 1. @@ -445,21 +427,10 @@ private void decompressMicromamba(final File tempFile) * @throws URISyntaxException if there is any error with the micromamba url */ public void installMicromamba() throws IOException, InterruptedException, URISyntaxException { - checkMambaInstalled(); - if (installed) return; + if (isMambaInstalled()) return; decompressMicromamba(downloadMicromamba()); - checkMambaInstalled(); } - public static String envNameFromYaml(File condaEnvironmentYaml) throws IOException { - List lines = Files.readAllLines(condaEnvironmentYaml.toPath()); - return lines.stream() - .filter(line -> line.startsWith("name:")) - .map(line -> line.substring(5).trim()) - .findFirst() - .orElseGet(() -> FileNameUtils.getBaseName(condaEnvironmentYaml.toPath())); - } - public String getEnvsDir() { return this.envsdir; } @@ -483,28 +454,6 @@ private static List< String > getBaseCommand() return cmd; } - /** - * Run {@code conda update} in the activated environment. A list of packages to - * be updated and extra parameters can be specified as {@code args}. - * - * @param args - * The list of packages to be updated and extra parameters as - * {@code String...}. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void update( final String... args ) throws IOException, InterruptedException, MambaInstallException - { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - updateIn( envName, args ); - } - /** * Run {@code conda update} in the specified environment. A list of packages to * update and extra parameters can be specified as {@code args}. @@ -525,7 +474,6 @@ public void update( final String... args ) throws IOException, InterruptedExcept public void updateIn( final String envName, final String... args ) throws IOException, InterruptedException, MambaInstallException { checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); final List< String > cmd = new ArrayList<>( Arrays.asList( "update", "-p", getEnvDir(envName) ) ); cmd.addAll( Arrays.asList( args ) ); if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); @@ -550,36 +498,6 @@ public void updateIn( final String envName, final String... args ) throws IOExce public void createWithYaml( final File envDir, final String envYaml ) throws IOException, InterruptedException, MambaInstallException { checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - createWithYaml(envDir, envYaml, false); - } - - /** - * Run {@code conda create} to create a conda environment defined by the input environment yaml file. - * - * @param envDir - * The directory within which the environment will be created. - * @param envYaml - * The environment yaml file containing the information required to build it - * @param isForceCreation - * Force creation of the environment if {@code true}. If this value - * is {@code false} and an environment with the specified name - * already exists, throw an {@link EnvironmentExistsException}. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws RuntimeException if the process to create the env of the yaml file is not terminated correctly. If there is any error running the commands - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void createWithYaml( final File envDir, final String envYaml, final boolean isForceCreation) throws IOException, InterruptedException, RuntimeException, MambaInstallException - { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - if ( !isForceCreation && getEnvironmentNames().contains( envName ) ) - throw new EnvironmentExistsException(); runMamba("env", "create", "--prefix", envDir.getAbsolutePath(), "-f", envYaml, "-y", "-vv" ); } @@ -600,35 +518,6 @@ public void createWithYaml( final File envDir, final String envYaml, final boole public void create( final String envName ) throws IOException, InterruptedException, MambaInstallException { checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - create( envName, false ); - } - - /** - * Run {@code conda create} to create an empty conda environment. - * - * @param envName - * The environment name to be created. - * @param isForceCreation - * Force creation of the environment if {@code true}. If this value - * is {@code false} and an environment with the specified name - * already exists, throw an {@link EnvironmentExistsException}. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws RuntimeException - * If there is any error running the commands - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void create( final String envName, final boolean isForceCreation ) throws IOException, InterruptedException, RuntimeException, MambaInstallException - { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - if ( !isForceCreation && getEnvironmentNames().contains( envName ) ) - throw new EnvironmentExistsException(); runMamba( "create", "-y", "-p", getEnvDir(envName) ); } @@ -652,37 +541,6 @@ public void create( final String envName, final boolean isForceCreation ) throws public void create( final String envName, final String... args ) throws IOException, InterruptedException, MambaInstallException { checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - create( envName, false, args ); - } - - /** - * Run {@code conda create} to create a new conda environment with a list of - * specified packages. - * - * @param envName - * The environment name to be created. - * @param isForceCreation - * Force creation of the environment if {@code true}. If this value - * is {@code false} and an environment with the specified name - * already exists, throw an {@link EnvironmentExistsException}. - * @param args - * The list of packages to be installed on environment creation and - * extra parameters as {@code String...}. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void create( final String envName, final boolean isForceCreation, final String... args ) throws IOException, InterruptedException, MambaInstallException - { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - if ( !isForceCreation && getEnvironmentNames().contains( envName ) ) - throw new EnvironmentExistsException(); final List< String > cmd = new ArrayList<>( Arrays.asList( "create", "-p", getEnvDir(envName) ) ); cmd.addAll( Arrays.asList( args ) ); if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); @@ -695,10 +553,6 @@ public void create( final String envName, final boolean isForceCreation, final S * * @param envName * The environment name to be created. CAnnot be null. - * @param isForceCreation - * Force creation of the environment if {@code true}. If this value - * is {@code false} and an environment with the specified name - * already exists, throw an {@link EnvironmentExistsException}. * @param channels * the channels from where the packages can be installed. Can be null * @param packages @@ -714,13 +568,10 @@ public void create( final String envName, final boolean isForceCreation, final S * If there is any error running the commands * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used */ - public void create( final String envName, final boolean isForceCreation, List channels, List packages ) throws IOException, InterruptedException, RuntimeException, MambaInstallException + public void create( final String envName, List channels, List packages ) throws IOException, InterruptedException, RuntimeException, MambaInstallException { checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); Objects.requireNonNull(envName, "The name of the environment of interest needs to be provided."); - if ( !isForceCreation && getEnvironmentNames().contains( envName ) ) - throw new EnvironmentExistsException(); final List< String > cmd = new ArrayList<>( Arrays.asList( "create", "-p", getEnvDir(envName) ) ); if (channels == null) channels = new ArrayList<>(); for (String chan : channels) { cmd.add("-c"); cmd.add(chan);} @@ -730,354 +581,6 @@ public void create( final String envName, final boolean isForceCreation, List channels, List packages ) throws IOException, InterruptedException, MambaInstallException - { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - installIn( envName, channels, packages ); - } - - /** - * Run {@code conda install} in the specified environment. A list of packages to - * install and extra parameters can be specified as {@code args}. - * - * @param envName - * The environment name to be used for the install command. - * @param channels - * the channels from where the packages can be installed. Can be null - * @param packages - * the packages that want to be installed during env creation. They can contain the version. - * For example, "python" or "python=3.10.1", "numpy" or "numpy=1.20.1". CAn be null if no packages want to be installed - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws RuntimeException if the process to create the env of the yaml file is not terminated correctly. If there is any error running the commands - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void installIn( final String envName, List channels, List packages ) throws IOException, InterruptedException, RuntimeException, MambaInstallException - { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - Objects.requireNonNull(envName, "The name of the environment of interest needs to be provided."); - final List< String > cmd = new ArrayList<>( Arrays.asList( "install", "-y", "-p", getEnvDir(envName) ) ); - if (channels == null) channels = new ArrayList<>(); - for (String chan : channels) { cmd.add("-c"); cmd.add(chan);} - if (packages == null) packages = new ArrayList<>(); - cmd.addAll(packages); - runMamba(cmd.toArray(new String[0])); - } - - /** - * Run {@code conda install} in the specified environment. A list of packages to - * install and extra parameters can be specified as {@code args}. - * - * @param envName - * The environment name to be used for the install command. - * @param args - * The list of packages to be installed and extra parameters as - * {@code String...}. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void installIn( final String envName, final String... args ) throws IOException, InterruptedException, MambaInstallException - { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - final List< String > cmd = new ArrayList<>( Arrays.asList( "install", "-p", getEnvDir(envName) ) ); - cmd.addAll( Arrays.asList( args ) ); - if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); - runMamba(cmd.toArray(new String[0])); - } - - /** - * Run {@code pip install} in the activated environment. A list of packages to - * install and extra parameters can be specified as {@code args}. - * - * @param args - * The list of packages to be installed and extra parameters as - * {@code String...}. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void pipInstall( final String... args ) throws IOException, InterruptedException, MambaInstallException - { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - pipInstallIn( envName, args ); - } - - /** - * Run {@code pip install} in the specified environment. A list of packages to - * install and extra parameters can be specified as {@code args}. - * - * @param envName - * The environment name to be used for the install command. - * @param args - * The list of packages to be installed and extra parameters as - * {@code String...}. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void pipInstallIn( final String envName, final String... args ) throws IOException, InterruptedException, MambaInstallException - { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - final List< String > cmd = new ArrayList<>( Arrays.asList( "-m", "pip", "install" ) ); - cmd.addAll( Arrays.asList( args ) ); - runPythonIn( envName, cmd.toArray(new String[0])); - } - - /** - * Run a Python command in the activated environment. This method automatically - * sets environment variables associated with the activated environment. In - * Windows, this method also sets the {@code PATH} environment variable so that - * the specified environment runs as expected. - * - * @param args - * One or more arguments for the Python command. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void runPython( final String... args ) throws IOException, InterruptedException, MambaInstallException - { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - runPythonIn( envName, args ); - } - - /** - * Runs a Python command in the specified environment. This method automatically - * sets environment variables associated with the specified environment. In - * Windows, this method also sets the {@code PATH} environment variable so that - * the specified environment runs as expected. - * - * TODO stop process if the thread is interrupted, same as with mamba, look for runmamna method for example - * TODO stop process if the thread is interrupted, same as with mamba, look for runmamna method for example - * TODO stop process if the thread is interrupted, same as with mamba, look for runmamna method for example - * TODO stop process if the thread is interrupted, same as with mamba, look for runmamna method for example - * TODO stop process if the thread is interrupted, same as with mamba, look for runmamna method for example - *
- * - * @param envName - * The environment name used to run the Python command. - * @param args - * One or more arguments for the Python command. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void runPythonIn( final String envName, final String... args ) throws IOException, InterruptedException, MambaInstallException - { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - final List< String > cmd = getBaseCommand(); - ListargsList = new ArrayList<>(); - String envDir; - if (new File(envName, PYTHON_COMMAND).isFile()) { - argsList.add( coverArgWithDoubleQuotes(Paths.get( envName, PYTHON_COMMAND ).toAbsolutePath().toString()) ); - envDir = Paths.get( envName ).toAbsolutePath().toString(); - } else if (Paths.get( getEnvDir(envName), PYTHON_COMMAND ).toFile().isFile()) { - argsList.add( coverArgWithDoubleQuotes(Paths.get( getEnvDir(envName), PYTHON_COMMAND ).toAbsolutePath().toString()) ); - envDir = Paths.get( getEnvDir(envName) ).toAbsolutePath().toString(); - } else - throw new IOException("The environment provided (" - + envName + ") does not exist or does not contain a Python executable (" + PYTHON_COMMAND + ")."); - argsList.addAll( Arrays.stream( args ).map(aa -> { - if (aa.contains(" ") && SystemUtils.IS_OS_WINDOWS) return coverArgWithDoubleQuotes(aa); - else return aa; - }).collect(Collectors.toList()) ); - boolean containsSpaces = argsList.stream().anyMatch(aa -> aa.contains(" ")); - - if (!containsSpaces || !SystemUtils.IS_OS_WINDOWS) cmd.addAll(argsList); - else cmd.add(surroundWithQuotes(argsList)); - - final ProcessBuilder builder = getBuilder( true ); - if ( SystemUtils.IS_OS_WINDOWS ) - { - final Map< String, String > envs = builder.environment(); - envs.put( "Path", envDir + ";" + envs.get( "Path" ) ); - envs.put( "Path", Paths.get( envDir, "Scripts" ) + ";" + envs.get( "Path" ) ); - envs.put( "Path", Paths.get( envDir, "Library" ) + ";" + envs.get( "Path" ) ); - envs.put( "Path", Paths.get( envDir, "Library", "Bin" ) + ";" + envs.get( "Path" ) ); - } - // TODO find way to get env vars in micromamba builder.environment().putAll( getEnvironmentVariables( envName ) ); - if ( builder.command( cmd ).start().waitFor() != 0 ) - throw new RuntimeException("Error executing the following command: " + builder.command()); - } - - /** - * Run a Python command in the specified environment. This method automatically - * sets environment variables associated with the specified environment. In - * Windows, this method also sets the {@code PATH} environment variable so that - * the specified environment runs as expected. - * - * @param envFile - * file corresponding to the environment directory - * @param args - * One or more arguments for the Python command. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - */ - public static void runPythonIn( final File envFile, final String... args ) throws IOException, InterruptedException - { - if (!Paths.get( envFile.getAbsolutePath(), PYTHON_COMMAND ).toFile().isFile()) - throw new IOException("No Python found in the environment provided. The following " - + "file does not exist: " + Paths.get( envFile.getAbsolutePath(), PYTHON_COMMAND ).toAbsolutePath()); - final List< String > cmd = getBaseCommand(); - List argsList = new ArrayList<>(); - argsList.add( coverArgWithDoubleQuotes(Paths.get( envFile.getAbsolutePath(), PYTHON_COMMAND ).toAbsolutePath().toString()) ); - argsList.addAll( Arrays.stream( args ).map(aa -> { - if (Platform.isWindows() && aa.contains(" ")) return coverArgWithDoubleQuotes(aa); - else return aa; - }).collect(Collectors.toList()) ); - boolean containsSpaces = argsList.stream().anyMatch(aa -> aa.contains(" ")); - - if (!containsSpaces || !SystemUtils.IS_OS_WINDOWS) cmd.addAll(argsList); - else cmd.add(surroundWithQuotes(argsList)); - - - final ProcessBuilder builder = new ProcessBuilder().directory( envFile ); - builder.inheritIO(); - if ( SystemUtils.IS_OS_WINDOWS ) - { - final Map< String, String > envs = builder.environment(); - final String envDir = envFile.getAbsolutePath(); - envs.put( "Path", envDir + ";" + envs.get( "Path" ) ); - envs.put( "Path", Paths.get( envDir, "Scripts" ) + ";" + envs.get( "Path" ) ); - envs.put( "Path", Paths.get( envDir, "Library" ) + ";" + envs.get( "Path" ) ); - envs.put( "Path", Paths.get( envDir, "Library", "Bin" ) + ";" + envs.get( "Path" ) ); - } - // TODO find way to get env vars in micromamba builder.environment().putAll( getEnvironmentVariables( envName ) ); - if ( builder.command( cmd ).start().waitFor() != 0 ) - throw new RuntimeException("Error executing the following command: " + builder.command()); - } - /** * Returns Conda version as a {@code String}. * @@ -1122,7 +625,6 @@ public String getVersion() throws IOException, InterruptedException { public void runMamba(boolean isInheritIO, final String... args ) throws RuntimeException, IOException, InterruptedException, MambaInstallException { checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); Thread mainThread = Thread.currentThread(); SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); @@ -1233,410 +735,9 @@ public void runMamba(boolean isInheritIO, final String... args ) throws RuntimeE public void runMamba(final String... args ) throws RuntimeException, IOException, InterruptedException, MambaInstallException { checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); runMamba(false, args); } - /** - * Returns environment variables associated with the activated environment as - * {@code Map< String, String >}. - * - * @return The environment variables as {@code Map< String, String >}. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - */ - /* TODO find equivalent in mamba - public Map< String, String > getEnvironmentVariables() throws IOException, InterruptedException - { - return getEnvironmentVariables( envName ); - } - */ - - /** - * Returns environment variables associated with the specified environment as - * {@code Map< String, String >}. - * - * @param envName - * The environment name used to run the Python command. - * @return The environment variables as {@code Map< String, String >}. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - */ - /** - * TODO find equivalent in mamba - public Map< String, String > getEnvironmentVariables( final String envName ) throws IOException, InterruptedException - { - final List< String > cmd = getBaseCommand(); - cmd.addAll( Arrays.asList( condaCommand, "env", "config", "vars", "list", "-n", envName ) ); - final Process process = getBuilder( false ).command( cmd ).start(); - if ( process.waitFor() != 0 ) - throw new RuntimeException(); - final Map< String, String > map = new HashMap<>(); - try (final BufferedReader reader = new BufferedReader( new InputStreamReader( process.getInputStream() ) )) - { - String line; - - while ( ( line = reader.readLine() ) != null ) - { - final String[] keyVal = line.split( " = " ); - map.put( keyVal[ 0 ], keyVal[ 1 ] ); - } - } - return map; - } - */ - - /** - * Returns a list of the Mamba environment names as {@code List< String >}. - * - * @return The list of the Mamba environment names as {@code List< String >}. - * @throws IOException If an I/O error occurs. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public List< String > getEnvironmentNames() throws IOException, MambaInstallException - { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - final List< String > envs = new ArrayList<>(Collections.singletonList(DEFAULT_ENVIRONMENT_NAME)); - envs.addAll( Files.list( Paths.get( envsdir ) ) - .map( p -> p.getFileName().toString() ) - .filter( p -> !p.startsWith( "." ) ) - .filter( p -> Paths.get(p, "conda-meta").toFile().isDirectory() ) - .collect( Collectors.toList() ) ); - return envs; - } - - /** - * Check whether a list of dependencies provided is installed in the wanted environment. - * - * @param envName - * The name of the environment of interest. Should be one of the environments of the current Mamba instance. - * This parameter can also be the full path to an independent environment. - * @param dependencies - * The list of dependencies that should be installed in the environment. - * They can contain version requirements. The names should be the ones used to import the package inside python, - * "skimage", not "scikit-image" or "sklearn", not "scikit-learn" - * An example list: "numpy", "numba>=0.43.1", "torch==1.6", "torch>=1.6, <2.0" - * @return true if the packages are installed or false otherwise - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public boolean checkAllDependenciesInEnv(String envName, List dependencies) throws MambaInstallException { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - return checkUninstalledDependenciesInEnv(envName, dependencies).isEmpty(); - } - - /** - * Returns a list containing the packages that are not installed in the wanted environment - * from the list of dependencies provided - * - * @param envName - * The name of the environment of interest. Should be one of the environments of the current Mamba instance. - * This parameter can also be the full path to an independent environment. - * @param dependencies - * The list of dependencies that should be installed in the environment. - * They can contain version requirements. The names should be the ones used to import the package inside python, - * "skimage", not "scikit-image" or "sklearn", not "scikit-learn" - * An example list: "numpy", "numba>=0.43.1", "torch==1.6", "torch>=1.6, <2.0" - * @return the list of packages that are not already installed - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public List checkUninstalledDependenciesInEnv(String envName, List dependencies) throws MambaInstallException { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - File envFile = new File(this.envsdir, envName); - File envFile2 = new File(envName); - if (!envFile.isDirectory() && !envFile2.isDirectory()) - return dependencies; - return dependencies.stream().filter(dep -> { - try { - return !checkDependencyInEnv(envName, dep); - } catch (Exception ex) { - return true; - } - }).collect(Collectors.toList()); - } - - /** - * Checks whether a package is installed in the wanted environment. - * - * @param envName - * The name of the environment of interest. Should be one of the environments of the current Mamba instance. - * This parameter can also be the full path to an independent environment. - * @param dependency - * The name of the package that should be installed in the env - * They can contain version requirements. The names should be the ones used to import the package inside python, - * "skimage", not "scikit-image" or "sklearn", not "scikit-learn" - * An example list: "numpy", "numba>=0.43.1", "torch==1.6", "torch>=1.6, <2.0" - * @return true if the package is installed or false otherwise - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public boolean checkDependencyInEnv(String envName, String dependency) throws MambaInstallException { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - if (dependency.contains("==")) { - int ind = dependency.indexOf("=="); - return checkDependencyInEnv(envName, dependency.substring(0, ind).trim(), dependency.substring(ind + 2).trim()); - } else if (dependency.contains(">=") && dependency.contains("<=") && dependency.contains(",")) { - int commaInd = dependency.indexOf(","); - int highInd = dependency.indexOf(">="); - int lowInd = dependency.indexOf("<="); - int minInd = Math.min(Math.min(commaInd, lowInd), highInd); - String packName = dependency.substring(0, minInd).trim(); - String minV = dependency.substring(lowInd + 1, lowInd < highInd ? commaInd : dependency.length()); - String maxV = dependency.substring(highInd + 1, lowInd < highInd ? dependency.length() : commaInd); - return checkDependencyInEnv(envName, packName, minV, maxV, false); - } else if (dependency.contains(">=") && dependency.contains("<") && dependency.contains(",")) { - int commaInd = dependency.indexOf(","); - int highInd = dependency.indexOf(">="); - int lowInd = dependency.indexOf("<"); - int minInd = Math.min(Math.min(commaInd, lowInd), highInd); - String packName = dependency.substring(0, minInd).trim(); - String minV = dependency.substring(lowInd + 1, lowInd < highInd ? commaInd : dependency.length()); - String maxV = dependency.substring(highInd + 1, lowInd < highInd ? dependency.length() : commaInd); - return checkDependencyInEnv(envName, packName, minV, null, false) && checkDependencyInEnv(envName, packName, null, maxV, true); - } else if (dependency.contains(">") && dependency.contains("<=") && dependency.contains(",")) { - int commaInd = dependency.indexOf(","); - int highInd = dependency.indexOf(">"); - int lowInd = dependency.indexOf("<="); - int minInd = Math.min(Math.min(commaInd, lowInd), highInd); - String packName = dependency.substring(0, minInd).trim(); - String minV = dependency.substring(lowInd + 1, lowInd < highInd ? commaInd : dependency.length()); - String maxV = dependency.substring(highInd + 1, lowInd < highInd ? dependency.length() : commaInd); - return checkDependencyInEnv(envName, packName, minV, null, true) && checkDependencyInEnv(envName, packName, null, maxV, false); - } else if (dependency.contains(">") && dependency.contains("<") && dependency.contains(",")) { - int commaInd = dependency.indexOf(","); - int highInd = dependency.indexOf(">"); - int lowInd = dependency.indexOf(">"); - int minInd = Math.min(Math.min(commaInd, lowInd), highInd); - String packName = dependency.substring(0, minInd).trim(); - String minV = dependency.substring(lowInd + 1, lowInd < highInd ? commaInd : dependency.length()); - String maxV = dependency.substring(highInd + 1, lowInd < highInd ? dependency.length() : commaInd); - return checkDependencyInEnv(envName, packName, minV, maxV, true); - } else if (dependency.contains(">")) { - int ind = dependency.indexOf(">"); - return checkDependencyInEnv(envName, null, dependency.substring(0, ind).trim(), dependency.substring(ind + 2).trim(), true); - } else if (dependency.contains(">=")) { - int ind = dependency.indexOf(">="); - return checkDependencyInEnv(envName, null, dependency.substring(0, ind).trim(), dependency.substring(ind + 2).trim(), false); - } else if (dependency.contains("<=")) { - int ind = dependency.indexOf("<="); - return checkDependencyInEnv(envName, dependency.substring(0, ind).trim(), dependency.substring(ind + 2).trim(), null, false); - } else if (dependency.contains("<")) { - int ind = dependency.indexOf("<"); - return checkDependencyInEnv(envName, dependency.substring(0, ind).trim(), dependency.substring(ind + 1).trim(), null, true); - } else if (dependency.contains("=")) { - int ind = dependency.indexOf("="); - return checkDependencyInEnv(envName, dependency.substring(0, ind).trim(), dependency.substring(ind + 1).trim()); - }else { - return checkDependencyInEnv(envName, dependency, null); - } - } - - /** - * Checks whether a package of a specific version is installed in the wanted environment. - * - * @param envDir - * The directory of the environment of interest. Should be one of the environments of the current Mamba instance. - * This parameter can also be the full path to an independent environment. - * @param dependency - * The name of the package that should be installed in the env. The String should only contain the name, no version, - * and the name should be the one used to import the package inside python. For example, "skimage", not "scikit-image" - * or "sklearn", not "scikit-learn". - * @param version - * the specific version of the package that needs to be installed. For example:, "0.43.1", "1.6", "2.0" - * @return true if the package is installed or false otherwise - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public boolean checkDependencyInEnv(String envDir, String dependency, String version) throws MambaInstallException { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - return checkDependencyInEnv(envDir, dependency, version, version, true); - } - - /** - * Checks whether a package with specific version constraints is installed in the wanted environment. - * In this method the minversion argument should be strictly smaller than the version of interest and - * the maxversion strictly bigger. - * This method checks that: dependency >minversion, <maxversion - * For smaller or equal or bigger or equal (dependency >=minversion, <=maxversion) look at the method - * {@link #checkDependencyInEnv(String, String, String, String, boolean)} with the lst parameter set to false. - * - * @param envDir - * The directory of the environment of interest. Should be one of the environments of the current Mamba instance. - * This parameter can also be the full path to an independent environment. - * @param dependency - * The name of the package that should be installed in the env. The String should only contain the name, no version, - * and the name should be the one used to import the package inside python. For example, "skimage", not "scikit-image" - * or "sklearn", not "scikit-learn". - * @param minversion - * the minimum required version of the package that needs to be installed. For example:, "0.43.1", "1.6", "2.0". - * This version should be strictly smaller than the one of interest, if for example "1.9" is given, it is assumed that - * package_version>1.9. - * If there is no minimum version requirement for the package of interest, set this argument to null. - * @param maxversion - * the maximum required version of the package that needs to be installed. For example:, "0.43.1", "1.6", "2.0". - * This version should be strictly bigger than the one of interest, if for example "1.9" is given, it is assumed that - * package_version<1.9. - * If there is no maximum version requirement for the package of interest, set this argument to null. - * @return true if the package is installed or false otherwise - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public boolean checkDependencyInEnv(String envDir, String dependency, String minversion, String maxversion) throws MambaInstallException { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - return checkDependencyInEnv(envDir, dependency, minversion, maxversion, true); - } - - /** - * Checks whether a package with specific version constraints is installed in the wanted environment. - * Depending on the last argument ('strictlyBiggerOrSmaller') 'minversion' and 'maxversion' - * will be strictly bigger(>=) or smaller(<) or bigger or equal >=) or smaller or equal<=) - * In this method the minversion argument should be strictly smaller than the version of interest and - * the maxversion strictly bigger. - * - * @param envDir - * The directory of the environment of interest. Should be one of the environments of the current Mamba instance. - * This parameter can also be the full path to an independent environment. - * @param dependency - * The name of the package that should be installed in the env. The String should only contain the name, no version, - * and the name should be the one used to import the package inside python. For example, "skimage", not "scikit-image" - * or "sklearn", not "scikit-learn". - * @param minversion - * the minimum required version of the package that needs to be installed. For example:, "0.43.1", "1.6", "2.0". - * If there is no minimum version requirement for the package of interest, set this argument to null. - * @param maxversion - * the maximum required version of the package that needs to be installed. For example:, "0.43.1", "1.6", "2.0". - * If there is no maximum version requirement for the package of interest, set this argument to null. - * @param strictlyBiggerOrSmaller - * Whether the minversion and maxversion shuld be strictly smaller and bigger or not - * @return true if the package is installed or false otherwise - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public boolean checkDependencyInEnv(String envDir, String dependency, String minversion, - String maxversion, boolean strictlyBiggerOrSmaller) throws MambaInstallException { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - File envFile = new File(this.envsdir, envDir); - File envFile2 = new File(envDir); - if (!envFile.isDirectory() && !envFile2.isDirectory()) - return false; - else if (!envFile.isDirectory()) - envFile = envFile2; - if (dependency.trim().equals("python")) return checkPythonInstallation(envDir, minversion, maxversion, strictlyBiggerOrSmaller); - String checkDepCode; - if (minversion != null && maxversion != null && minversion.equals(maxversion)) { - checkDepCode = "import importlib.util, sys; " - + "from importlib.metadata import version; " - + "from packaging import version as vv; " - + "pkg = '%s'; wanted_v = '%s'; " - + "spec = importlib.util.find_spec(pkg); " - + "sys.exit(0) if spec and vv.parse(version(pkg)) == vv.parse(wanted_v) else sys.exit(1)"; - checkDepCode = String.format(checkDepCode, dependency, maxversion); - } else if (minversion == null && maxversion == null) { - checkDepCode = "import importlib.util, sys; sys.exit(0) if importlib.util.find_spec('%s') else sys.exit(1)"; - checkDepCode = String.format(checkDepCode, dependency); - } else if (maxversion == null) { - checkDepCode = "import importlib.util, sys; " - + "from importlib.metadata import version; " - + "from packaging import version as vv; " - + "pkg = '%s'; desired_version = '%s'; " - + "spec = importlib.util.find_spec(pkg); " - + "sys.exit(0) if spec and vv.parse(version(pkg)) %s vv.parse(desired_version) else sys.exit(1)"; - checkDepCode = String.format(checkDepCode, dependency, minversion, strictlyBiggerOrSmaller ? ">" : ">="); - } else if (minversion == null) { - checkDepCode = "import importlib.util, sys; " - + "from importlib.metadata import version; " - + "from packaging import version as vv; " - + "pkg = '%s'; desired_version = '%s'; " - + "spec = importlib.util.find_spec(pkg); " - + "sys.exit(0) if spec and vv.parse(version(pkg)) %s vv.parse(desired_version) else sys.exit(1)"; - checkDepCode = String.format(checkDepCode, dependency, maxversion, strictlyBiggerOrSmaller ? "<" : "<="); - } else { - checkDepCode = "import importlib.util, sys; " - + "from importlib.metadata import version; " - + "from packaging import version as vv; " - + "pkg = '%s'; min_v = '%s'; max_v = '%s'; " - + "spec = importlib.util.find_spec(pkg); " - + "sys.exit(0) if spec and vv.parse(version(pkg)) %s vv.parse(min_v) and vv.parse(version(pkg)) %s vv.parse(max_v) else sys.exit(1)"; - checkDepCode = String.format(checkDepCode, dependency, minversion, maxversion, strictlyBiggerOrSmaller ? ">" : ">=", strictlyBiggerOrSmaller ? "<" : ">="); - } - try { - runPythonIn(envFile, "-c", checkDepCode); - } catch (RuntimeException | IOException | InterruptedException e) { - return false; - } - return true; - } - - private boolean checkPythonInstallation(String envDir, String minversion, String maxversion, boolean strictlyBiggerOrSmaller) throws MambaInstallException { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - File envFile = new File(this.envsdir, envDir); - File envFile2 = new File(envDir); - if (!envFile.isDirectory() && !envFile2.isDirectory()) - return false; - else if (!envFile.isDirectory()) - envFile = envFile2; - String checkDepCode; - if (minversion != null && maxversion != null && minversion.equals(maxversion)) { - checkDepCode = "import sys; import platform; from packaging import version as vv; desired_version = '%s'; " - + "sys.exit(0) if vv.parse(platform.python_version()).major == vv.parse(desired_version).major" - + " and vv.parse(platform.python_version()).minor == vv.parse(desired_version).minor else sys.exit(1)"; - checkDepCode = String.format(checkDepCode, maxversion); - } else if (minversion == null && maxversion == null) { - checkDepCode = "2 + 2"; - } else if (maxversion == null) { - checkDepCode = "import sys; import platform; from packaging import version as vv; desired_version = '%s'; " - + "sys.exit(0) if vv.parse(platform.python_version()).major == vv.parse(desired_version).major " - + "and vv.parse(platform.python_version()).minor %s vv.parse(desired_version).minor else sys.exit(1)"; - checkDepCode = String.format(checkDepCode, minversion, strictlyBiggerOrSmaller ? ">" : ">="); - } else if (minversion == null) { - checkDepCode = "import sys; import platform; from packaging import version as vv; desired_version = '%s'; " - + "sys.exit(0) if vv.parse(platform.python_version()).major == vv.parse(desired_version).major " - + "and vv.parse(platform.python_version()).minor %s vv.parse(desired_version).minor else sys.exit(1)"; - checkDepCode = String.format(checkDepCode, maxversion, strictlyBiggerOrSmaller ? "<" : "<="); - } else { - checkDepCode = "import platform; " - + "from packaging import version as vv; min_v = '%s'; max_v = '%s'; " - + "sys.exit(0) if vv.parse(platform.python_version()).major == vv.parse(desired_version).major " - + "and vv.parse(platform.python_version()).minor %s vv.parse(min_v).minor " - + "and vv.parse(platform.python_version()).minor %s vv.parse(max_v).minor else sys.exit(1)"; - checkDepCode = String.format(checkDepCode, minversion, maxversion, strictlyBiggerOrSmaller ? ">" : ">=", strictlyBiggerOrSmaller ? "<" : ">="); - } - try { - runPythonIn(envFile, "-c", checkDepCode); - } catch (RuntimeException | IOException | InterruptedException e) { - return false; - } - return true; - } - - /** - * TODO figure out whether to use a dependency or not to parse the yaml file - * @param envYaml - * the path to the yaml file where a Python environment should be specified - * @return true if the env exists or false otherwise - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public boolean checkEnvFromYamlExists(String envYaml) throws MambaInstallException { - checkMambaInstalled(); - if (!installed) throw new MambaInstallException("Micromamba is not installed"); - if (envYaml == null || !new File(envYaml).isFile() - || (envYaml.endsWith(".yaml") && envYaml.endsWith(".yml"))) { - return false; - } - return false; - } - /** * In Windows, if a command prompt argument contains and space " " it needs to * start and end with double quotes @@ -1672,12 +773,4 @@ private static String surroundWithQuotes(List args) { arg += "\""; return arg; } - - public static void main(String[] args) throws IOException, InterruptedException, MambaInstallException { - - Mamba m = new Mamba("C:\\Users\\angel\\Desktop\\Fiji app\\appose_x86_64"); - String envName = "efficientvit_sam_env"; - m.pipInstallIn(envName, - m.getEnvDir(envName) + File.separator + "appose-python"); - } } From ab26a3a8050817f5c6fc20b0645163e406ec66b2 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 30 Aug 2024 15:05:10 -0500 Subject: [PATCH 091/120] Make internal classes package-private So that we don't leak them as public API usable externally. --- src/main/java/org/apposed/appose/mamba/FileDownloader.java | 2 +- src/main/java/org/apposed/appose/mamba/Mamba.java | 2 +- src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/apposed/appose/mamba/FileDownloader.java b/src/main/java/org/apposed/appose/mamba/FileDownloader.java index b636726..315e6b4 100644 --- a/src/main/java/org/apposed/appose/mamba/FileDownloader.java +++ b/src/main/java/org/apposed/appose/mamba/FileDownloader.java @@ -32,7 +32,7 @@ import java.io.IOException; import java.nio.channels.ReadableByteChannel; -public class FileDownloader { +class FileDownloader { private static final long CHUNK_SIZE = 1024 * 1024 * 5; private final ReadableByteChannel rbc; diff --git a/src/main/java/org/apposed/appose/mamba/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java index b5004ca..f57f18d 100644 --- a/src/main/java/org/apposed/appose/mamba/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -89,7 +89,7 @@ * @author Ko Sugawara * @author Carlos Garcia */ -public class Mamba { +class Mamba { /** * String containing the path that points to the micromamba executable diff --git a/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java b/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java index 64353bb..3fe3ce8 100644 --- a/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java +++ b/src/main/java/org/apposed/appose/mamba/MambaInstallerUtils.java @@ -54,7 +54,7 @@ /** * Utility methods unzip bzip2 files and to enable the download of micromamba */ -public final class MambaInstallerUtils { +final class MambaInstallerUtils { private MambaInstallerUtils() { // Prevent instantiation of utility class. From 40e81f79cc797097f3a955263fa41b4303f900e9 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 30 Aug 2024 15:07:10 -0500 Subject: [PATCH 092/120] Generalize mamba doc from "Python" to "Conda" Conda has packages beyond only Python ones. --- src/main/java/org/apposed/appose/mamba/Mamba.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/apposed/appose/mamba/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java index f57f18d..58991f6 100644 --- a/src/main/java/org/apposed/appose/mamba/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -84,7 +84,7 @@ import java.util.stream.Collectors; /** - * Python environment manager, implemented by delegating to micromamba. + * Conda-based environment manager, implemented by delegating to micromamba. * * @author Ko Sugawara * @author Carlos Garcia @@ -125,7 +125,7 @@ class Mamba { private Double mambaDecompressProgress = 0.0; /** * Consumer that tracks the progress in the download of micromamba, the software used - * by this class to manage Python environments + * by this class to manage Conda environments */ private final Consumer mambaDnwldProgressConsumer = this::updateMambaDnwldProgress; /** @@ -169,7 +169,7 @@ class Mamba { */ final public static String BASE_PATH = Paths.get(System.getProperty("user.home"), ".local", "share", "appose", "micromamba").toString(); /** - * Name of the folder inside the {@link #rootdir} that contains the different Python environments created by the Appose Micromamba + * Name of the folder inside the {@link #rootdir} that contains the different Conda environments created by the Appose Micromamba */ final public static String ENVS_NAME = "envs"; /** @@ -481,7 +481,7 @@ public void updateIn( final String envName, final String... args ) throws IOExce } /** - * Run {@code conda create} to create a conda environment defined by the input environment yaml file. + * Run {@code conda create} to create a Conda environment defined by the input environment yaml file. * * @param envDir * The directory within which the environment will be created. From 642ac7fdba55cceb1b185d4981be835ede5464f0 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 30 Aug 2024 15:09:12 -0500 Subject: [PATCH 093/120] Eschew use of platform-sensitive File.separator We can use the Paths utility class instead. --- src/main/java/org/apposed/appose/mamba/Mamba.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/apposed/appose/mamba/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java index 58991f6..e4414ec 100644 --- a/src/main/java/org/apposed/appose/mamba/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -162,8 +162,8 @@ class Mamba { * Relative path to the micromamba executable from the micromamba {@link #rootdir} */ private final static String MICROMAMBA_RELATIVE_PATH = SystemUtils.IS_OS_WINDOWS ? - File.separator + "Library" + File.separator + "bin" + File.separator + "micromamba.exe" - : File.separator + "bin" + File.separator + "micromamba"; + Paths.get("Library", "bin", "micromamba.exe").toString() : + Paths.get("bin", "micromamba").toString(); /** * Path where Appose installs Micromamba by default */ @@ -436,7 +436,7 @@ public String getEnvsDir() { } public String getEnvDir(String envName) { - return getEnvsDir() + File.separator + envName; + return Paths.get(getEnvsDir(), envName).toString(); } /** From fa91a8c8f55222b888eff3faaa406c941db63e1e Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 30 Aug 2024 15:12:14 -0500 Subject: [PATCH 094/120] Slim down the Mamba code some more --- .../java/org/apposed/appose/mamba/Mamba.java | 109 +++--------------- .../apposed/appose/mamba/MambaHandler.java | 2 +- .../appose/mamba/MambaInstallException.java | 56 --------- 3 files changed, 15 insertions(+), 152 deletions(-) delete mode 100644 src/main/java/org/apposed/appose/mamba/MambaInstallException.java diff --git a/src/main/java/org/apposed/appose/mamba/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java index e4414ec..f78fddd 100644 --- a/src/main/java/org/apposed/appose/mamba/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -78,7 +78,6 @@ import java.util.Arrays; import java.util.Calendar; import java.util.List; -import java.util.Objects; import java.util.UUID; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -246,7 +245,7 @@ private ProcessBuilder getBuilder( final boolean isInheritIO ) /** * Create a new {@link Mamba} object. The root dir for the Micromamba installation * will be the default base path defined at {@link #BASE_PATH} - * If there is no Micromamba found at the base path {@link #BASE_PATH}, a {@link MambaInstallException} will be thrown + * If there is no Micromamba found at the base path {@link #BASE_PATH}, an {@link IllegalStateException} will be thrown * * It is expected that the Micromamba installation has executable commands as shown below: * * It is expected that the Conda installation has executable commands as shown below: *
@@ -317,10 +316,10 @@ public boolean isMambaInstalled() { /** * Check whether micromamba is installed or not to be able to use the instance of {@link Mamba} - * @throws MambaInstallException if micromamba is not installed + * @throws IllegalStateException if micromamba is not installed */ - private void checkMambaInstalled() throws MambaInstallException { - if (!isMambaInstalled()) throw new MambaInstallException("Micromamba is not installed"); + private void checkMambaInstalled() { + if (!isMambaInstalled()) throw new IllegalStateException("Micromamba is not installed"); } /** @@ -398,8 +397,7 @@ private File downloadMicromamba() throws IOException, URISyntaxException { return tempFile; } - private void decompressMicromamba(final File tempFile) - throws IOException, InterruptedException { + private void decompressMicromamba(final File tempFile) throws IOException, InterruptedException { final File tempTarFile = File.createTempFile( "micromamba", ".tar" ); tempTarFile.deleteOnExit(); MambaInstallerUtils.unBZip2(tempFile, tempTarFile); @@ -469,9 +467,9 @@ private static List< String > getBaseCommand() * If the current thread is interrupted by another thread while it * is waiting, then the wait is ended and an InterruptedException is * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used + * @throws IllegalStateException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used */ - public void updateIn( final String envName, final String... args ) throws IOException, InterruptedException, MambaInstallException + public void updateIn( final String envName, final String... args ) throws IOException, InterruptedException { checkMambaInstalled(); final List< String > cmd = new ArrayList<>( Arrays.asList( "update", "-p", getEnvDir(envName) ) ); @@ -493,94 +491,15 @@ public void updateIn( final String envName, final String... args ) throws IOExce * If the current thread is interrupted by another thread while it * is waiting, then the wait is ended and an InterruptedException is * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used + * @throws IllegalStateException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used */ - public void createWithYaml( final File envDir, final String envYaml ) throws IOException, InterruptedException, MambaInstallException + public void createWithYaml( final File envDir, final String envYaml ) throws IOException, InterruptedException { checkMambaInstalled(); runMamba("env", "create", "--prefix", envDir.getAbsolutePath(), "-f", envYaml, "-y", "-vv" ); } - /** - * Run {@code conda create} to create an empty conda environment. - * - * @param envName - * The environment name to be created. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void create( final String envName ) throws IOException, InterruptedException, MambaInstallException - { - checkMambaInstalled(); - runMamba( "create", "-y", "-p", getEnvDir(envName) ); - } - - /** - * Run {@code conda create} to create a new mamba environment with a list of - * specified packages. - * - * @param envName - * The environment name to be created. - * @param args - * The list of packages to be installed on environment creation and - * extra parameters as {@code String...}. - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void create( final String envName, final String... args ) throws IOException, InterruptedException, MambaInstallException - { - checkMambaInstalled(); - final List< String > cmd = new ArrayList<>( Arrays.asList( "create", "-p", getEnvDir(envName) ) ); - cmd.addAll( Arrays.asList( args ) ); - if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); - runMamba(cmd.toArray(new String[0])); - } - - /** - * Run {@code conda create} to create a new conda environment with a list of - * specified packages. - * - * @param envName - * The environment name to be created. CAnnot be null. - * @param channels - * the channels from where the packages can be installed. Can be null - * @param packages - * the packages that want to be installed during env creation. They can contain the version. - * For example, "python" or "python=3.10.1", "numpy" or "numpy=1.20.1". CAn be null if no packages want to be installed - * @throws IOException - * If an I/O error occurs. - * @throws InterruptedException - * If the current thread is interrupted by another thread while it - * is waiting, then the wait is ended and an InterruptedException is - * thrown. - * @throws RuntimeException - * If there is any error running the commands - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used - */ - public void create( final String envName, Listchannels, List packages ) throws IOException, InterruptedException, RuntimeException, MambaInstallException - { - checkMambaInstalled(); - Objects.requireNonNull(envName, "The name of the environment of interest needs to be provided."); - final List< String > cmd = new ArrayList<>( Arrays.asList( "create", "-p", getEnvDir(envName) ) ); - if (channels == null) channels = new ArrayList<>(); - for (String chan : channels) { cmd.add("-c"); cmd.add(chan);} - if (packages == null) packages = new ArrayList<>(); - cmd.addAll(packages); - if (!cmd.contains("--yes") && !cmd.contains("-y")) cmd.add("--yes"); - runMamba(cmd.toArray(new String[0])); - } - /** * Returns Conda version as a {@code String}. * @@ -620,9 +539,9 @@ public String getVersion() throws IOException, InterruptedException { * If the current thread is interrupted by another thread while it * is waiting, then the wait is ended and an InterruptedException is * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used + * @throws IllegalStateException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used */ - public void runMamba(boolean isInheritIO, final String... args ) throws RuntimeException, IOException, InterruptedException, MambaInstallException + public void runMamba(boolean isInheritIO, final String... args ) throws RuntimeException, IOException, InterruptedException { checkMambaInstalled(); Thread mainThread = Thread.currentThread(); @@ -730,9 +649,9 @@ public void runMamba(boolean isInheritIO, final String... args ) throws RuntimeE * If the current thread is interrupted by another thread while it * is waiting, then the wait is ended and an InterruptedException is * thrown. - * @throws MambaInstallException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used + * @throws IllegalStateException if Micromamba has not been installed, thus the instance of {@link Mamba} cannot be used */ - public void runMamba(final String... args ) throws RuntimeException, IOException, InterruptedException, MambaInstallException + public void runMamba(final String... args ) throws RuntimeException, IOException, InterruptedException { checkMambaInstalled(); runMamba(false, args); diff --git a/src/main/java/org/apposed/appose/mamba/MambaHandler.java b/src/main/java/org/apposed/appose/mamba/MambaHandler.java index 79f88c1..c32371c 100644 --- a/src/main/java/org/apposed/appose/mamba/MambaHandler.java +++ b/src/main/java/org/apposed/appose/mamba/MambaHandler.java @@ -174,7 +174,7 @@ else if (!workDir.mkdirs()) { Mamba conda = new Mamba(Mamba.BASE_PATH); conda.installMicromamba(); conda.createWithYaml(envDir, environmentYaml.getAbsolutePath()); - } catch (InterruptedException | URISyntaxException | MambaInstallException e) { + } catch (InterruptedException | URISyntaxException e) { throw new IOException(e); } diff --git a/src/main/java/org/apposed/appose/mamba/MambaInstallException.java b/src/main/java/org/apposed/appose/mamba/MambaInstallException.java deleted file mode 100644 index d0dc3f8..0000000 --- a/src/main/java/org/apposed/appose/mamba/MambaInstallException.java +++ /dev/null @@ -1,56 +0,0 @@ -/*- - * #%L - * Appose: multi-language interprocess cooperation with shared memory. - * %% - * Copyright (C) 2023 - 2024 Appose developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package org.apposed.appose.mamba; - -/** - * Exception to be thrown when Micromamba is not found in the wanted directory - * - * @author Carlos Javier Garcia Lopez de Haro - */ -public class MambaInstallException extends Exception { - - private static final long serialVersionUID = 1L; - - /** - * Constructs a new exception with the default detail message - */ - public MambaInstallException() { - super("Micromamba installation not found in the provided directory."); - } - - /** - * Constructs a new exception with the specified detail message - * @param message - * the detail message. - */ - public MambaInstallException(String message) { - super(message); - } - -} From bb87fec61d9259d62810fe44a77721d8d4e88396 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 30 Aug 2024 15:18:58 -0500 Subject: [PATCH 095/120] Fix Mamba.downloadMicromamba exception handling --- .../java/org/apposed/appose/mamba/Mamba.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/apposed/appose/mamba/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java index f78fddd..d3e64ff 100644 --- a/src/main/java/org/apposed/appose/mamba/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -373,26 +373,32 @@ public void setErrorOutputConsumer(Consumer custom) { this.customErrorConsumer = custom; } - private File downloadMicromamba() throws IOException, URISyntaxException { + private File downloadMicromamba() throws IOException, InterruptedException, URISyntaxException { final File tempFile = File.createTempFile( "micromamba", ".tar.bz2" ); tempFile.deleteOnExit(); URL website = MambaInstallerUtils.redirectedURL(new URL(MICROMAMBA_URL)); long size = MambaInstallerUtils.getFileSize(website); Thread currentThread = Thread.currentThread(); + IOException[] ioe = {null}; + InterruptedException[] ie = {null}; Thread dwnldThread = new Thread(() -> { try ( ReadableByteChannel rbc = Channels.newChannel(website.openStream()); FileOutputStream fos = new FileOutputStream(tempFile) ) { new FileDownloader(rbc, fos).call(currentThread); - } catch (IOException | InterruptedException e) { - e.printStackTrace(); } + catch (IOException e) { ioe[0] = e; } + catch (InterruptedException e) { ie[0] = e; } }); dwnldThread.start(); - while (dwnldThread.isAlive()) + while (dwnldThread.isAlive()) { + Thread.sleep(20); // 50 FPS update rate this.mambaDnwldProgressConsumer.accept(((double) tempFile.length()) / ((double) size)); - if ((((double) tempFile.length()) / ((double) size)) < 1) + } + if (ioe[0] != null) throw ioe[0]; + if (ie[0] != null) throw ie[0]; + if ((((double) tempFile.length()) / size) < 1) throw new IOException("Error downloading micromamba from: " + MICROMAMBA_URL); return tempFile; } From 8df1818d1c4882d75dccfbdecc27edbc19907c67 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 30 Aug 2024 15:31:25 -0500 Subject: [PATCH 096/120] Remove more unnecessary mamba code --- .../java/org/apposed/appose/mamba/Mamba.java | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/apposed/appose/mamba/Mamba.java b/src/main/java/org/apposed/appose/mamba/Mamba.java index d3e64ff..f4425fe 100644 --- a/src/main/java/org/apposed/appose/mamba/Mamba.java +++ b/src/main/java/org/apposed/appose/mamba/Mamba.java @@ -108,10 +108,6 @@ class Mamba { *
- * TODO separate unlink and close - *
* * @author Carlos Garcia Lopez de Haro * @author Tobias Pietzsch @@ -85,7 +82,7 @@ private static ShmInfo* Here is a very simple example written in Java: *
@@ -144,11 +140,104 @@ *- * TODO - write up the request and response formats in detail here! - * JSON, one line per request/response. + * A request is a single line of JSON sent to the worker process via + * its standard input stream. It has a {@code task} key taking the form of a + * UUID, + * and a {@code requestType} key with one of the following values: *
+ *+ * Asynchronously execute a script within the worker process. E.g.: + *
+ *
+ * {
+ * "task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
+ * "requestType" : "EXECUTE",
+ * "script" : "task.outputs[\"result\"] = computeResult(gamma)\n",
+ * "inputs" : {"gamma": 2.2}
+ * }
+ *
+ * + * Cancel a running script. E.g.: + *
+ *
+ * {
+ * "task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
+ * "requestType" : "CANCEL"
+ * }
+ *
*
+ * + * A response is a single line of JSON with a {@code task} key + * taking the form of a + * UUID, + * and a {@code responseType} key with one of the following values: + *
+ *+ * A LAUNCH response is issued to confirm the success of an EXECUTE request. + *
+ *
+ * {
+ * "task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
+ * "responseType" : "LAUNCH"
+ * }
+ *
+ * + * An UPDATE response is issued to convey that a task has somehow made + * progress. The UPDATE response typically comes bundled with a {@code message} + * string indicating what has changed, {@code current} and/or {@code maximum} + * progress indicators conveying the step the task has reached, or both. + *
+ *
+ * {
+ * "task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
+ * "responseType" : "UPDATE",
+ * "message" : "Processing step 0 of 91",
+ * "current" : 0,
+ * "maximum" : 91
+ * }
+ *
+ * + * A COMPLETION response is issued to convey that a task has successfully + * completed execution, as well as report the values of any task outputs. + *
+ *
+ * {
+ * "task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
+ * "responseType" : "COMPLETION",
+ * "outputs" : {"result" : 91}
+ * }
+ *
+ * + * A CANCELATION response is issued to confirm the success of a CANCEL request. + *
+ *
+ * {
+ * "task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
+ * "responseType" : "CANCELATION"
+ * }
+ *
+ * + * A FAILURE response is issued to convey that a task did not completely + * and successfully execute, such as an exception being raised. + *
+ *
+ * {
+ * "task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
+ * "responseType" : "FAILURE",
+ * "error", "Invalid gamma value"
+ * }
+ *
+ *
* @author Curtis Rueden
*/
public class Appose {
From 4ea317bbd675533af281ac9339ffe242167adc98 Mon Sep 17 00:00:00 2001
From: Curtis Rueden