diff --git a/src/main/resources/scala-native/argv0.c b/src/main/resources/scala-native/argv0.c new file mode 100644 index 0000000..f073dd8 --- /dev/null +++ b/src/main/resources/scala-native/argv0.c @@ -0,0 +1,40 @@ + +#include + +#if defined(__linux__) +#include +#include +#elif defined(__APPLE__) +#include +#include +#elif defined(_WIN32) +#include +#endif + +size_t path_max() { +#ifdef _WIN32 + return MAX_PATH; +#else + return PATH_MAX + 1; +#endif +} + +void get_exe_path(char* exe_path, size_t size) { + + exe_path[0] = 0; + +#if defined(__linux__) +// char arg1[20]; +// sprintf( arg1, "/proc/%d/exe", getpid() ); +// printf("arg1 = %d\n", arg1); +// readlink( arg1, exepath, PATH_MAX ); + readlink( "/proc/self/exe", exe_path, size ); +#elif defined(__APPLE__) + if (_NSGetExecutablePath(exe_path, &size) != 0) + printf("ERROR: buffer too small; need size %lu\n", size); +#elif defined(_WIN32) + unsigned int len = GetModuleFileNameA(GetModuleHandleA(0x0), exe_path, size); + if (len == 0) // memory not sufficient or general error occurred + printf("ERROR: buffer too small or general error.\n"); +#endif +} \ No newline at end of file diff --git a/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala b/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala new file mode 100644 index 0000000..3442012 --- /dev/null +++ b/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala @@ -0,0 +1,58 @@ +package ij_plugins.imagej_launcher + +import ij_plugins.imagej_launcher.Main.Config +import os.{FilePath, Path, RelPath, SubPath} + +import scala.util.control.NonFatal + +object IJDir: + + /** Name of an ImageJ sub-directory containing jars */ + val jarsDirName = "jars" + + /** Locate ImageJ directory */ + def locate(config: Config, logger: Logger): Either[String, Path] = + logger.debug("Looking for ImageJ directory") + + config.ijDir match + case Some(d) => + logger.debug(s" Considering provided ij-dir: '$d''") + asPath(d.getPath).flatMap: p => + logger.debug(s" '$p''") + if isIJDir(p) then Right(p) + else Left(s"ij-dir is not an ImageJ directory [$p]") + case None => + logger.debug(" Considering application directory") + val appPath = Native.applicationPath() + logger.debug(s" Application directory: '$appPath'") + if isIJDir(appPath) then + Right(appPath) + else + logger.debug(" Application directory is not an ImageJ directory") + logger.debug(" Considering current working directory") + val cwd = os.pwd + logger.debug(s" Current working directory: '$cwd'") + if isIJDir(cwd) then + Right(cwd) + else + logger.debug(" Current working directory is not an ImageJ directory.") + Left("Cannot locate ImageJ directory.") + + private def isIJDir(path: Path): Boolean = + os.exists(path) && + os.isDir(path) && + os.list(path).exists(f => f.last == jarsDirName && os.isDir(f)) + + private def asPath(filePath: String): Either[String, Path] = + if filePath.trim.startsWith("~") then + try + Right(Path.expandUser(filePath)) + catch + case NonFatal(ex) => + Left(s"Not an absolute path: '$filePath' [${ex.getMessage}]") + else + FilePath(filePath) match + case p: Path => Right(p) + case _ => Left(s"Not an absolute path: '$filePath''") + +end IJDir diff --git a/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala b/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala index 2c8d654..2cb704c 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala @@ -5,6 +5,7 @@ package ij_plugins.imagej_launcher +import ij_plugins.imagej_launcher.IJDir.jarsDirName import ij_plugins.imagej_launcher.Launcher.javaExeFileName import ij_plugins.imagej_launcher.Main.Config import os.Path @@ -14,8 +15,6 @@ import java.lang.ProcessBuilder.Redirect class Launcher(logger: Logger): - private val jarsDirName = "jars" - def run(config: Config): Unit = prepareLaunch(config) match case Right(commandLine) => @@ -29,33 +28,15 @@ class Launcher(logger: Logger): private def prepareLaunch(config: Config): Either[String, Seq[String]] = for - ijDir <- locateIJDir(config) - _ <- Updater.update(Path(ijDir), config.dryRun, logger) - launcherJar <- findImageJLauncherJar(ijDir) - javaExe <- locateJavaExecutable(config, ijDir) + ijDir <- IJDir.locate(config, logger) + _ <- Updater.update(ijDir, config.dryRun, logger) + launcherJar <- findImageJLauncherJar(ijDir.toIO) + javaExe <- locateJavaExecutable(config, ijDir.toIO) systemType <- determineSystemType() yield val maxMemoryMB = determineMaxMemoryMB() logger.info(s"Max memory to use: ${maxMemoryMB}MB") - buildCommandLine(ijDir, javaExe, launcherJar, systemType, maxMemoryMB) - - private def locateIJDir(config: Config): Either[String, File] = - logger.debug("Looking for ImageJ directory") - - val dir = config.ijDir match - case Some(d) => - logger.debug(" Considering provided ij-dir") - d - case None => - logger.debug(" Considering current directory") - new File(".").getCanonicalFile - - dir.listFiles().find(f => f.getName == jarsDirName && f.isDirectory) match - case Some(_) => - logger.info(s" ImageJ directory set to: '$dir'") - Right(dir) - case None => - Left(s"Cannot locate ImageJ directory. No subdirectory '$jarsDirName' in '$dir''") + buildCommandLine(ijDir.toIO, javaExe, launcherJar, systemType, maxMemoryMB) private def findImageJLauncherJar(ijDir: File): Either[String, File] = logger.debug("Looking for 'imagej-launcher*.jar'") @@ -119,7 +100,7 @@ class Launcher(logger: Logger): p.toIO.getName == javaExeFileName && p.toIO.getParentFile.getName == "bin" ) - logger.debug(" Candidates: " + c2.mkString(", ")) + logger.debug(" Candidates: " + c2.mkString(", ")) c2.map(_.toIO.getParentFile.getParentFile) .headOption else diff --git a/src/main/scala/ij_plugins/imagej_launcher/Native.scala b/src/main/scala/ij_plugins/imagej_launcher/Native.scala index 2e847c4..3c82706 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Native.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Native.scala @@ -1,9 +1,29 @@ package ij_plugins.imagej_launcher -import scala.scalanative.unsafe.extern +import os.{Path, up} + +import scala.scalanative.unsafe +import scala.scalanative.unsafe.* object Native: + def applicationPath(): Path = + val maxPath: CSize = argv0.path_max() + // use Zone to manage native memory + val exePath = + Zone { implicit z => + val buffer: CString = alloc[CChar](maxPath) + argv0.get_exe_path(buffer, maxPath) + unsafe.fromCString(buffer) + } + Path(exePath) / up + @extern object mem: def determineTotalSystemMemory(): Long = extern + + @extern + private object argv0: + def path_max(): CSize = extern + + def get_exe_path(exe_path: CString, size: CSize): Unit = extern