diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..123ee94 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,8 @@ +IJP ImageJ Launcher +=================== + + +Features +-------- + +* Launches ImageJ2 or Fiji \ No newline at end of file diff --git a/build.sbt b/build.sbt index 29a3e7c..30a2749 100644 --- a/build.sbt +++ b/build.sbt @@ -6,14 +6,14 @@ enablePlugins(ScalaNativePlugin) logLevel := Level.Info libraryDependencies ++= Seq( - "com.github.scopt" %%% "scopt" % "4.1.0" + "com.github.scopt" %%% "scopt" % "4.1.0", + "com.lihaoyi" %%% "os-lib" % "0.9.1" ) -Compile/run/mainClass := Some("ij_plugins.imagej_launcher.Main") +Compile / run / mainClass := Some("ij_plugins.imagej_launcher.Main") // import to add Scala Native options -import scala.scalanative.build._ - +import scala.scalanative.build.* // defaults set with common options shown nativeConfig ~= { c => diff --git a/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala b/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala index eb94bb4..7486d8b 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala @@ -5,10 +5,195 @@ package ij_plugins.imagej_launcher +import ij_plugins.imagej_launcher.Launcher.javaExeFileName import ij_plugins.imagej_launcher.Main.Config +import os.Path + +import java.io.{BufferedReader, File, InputStreamReader} +import java.lang.ProcessBuilder.Redirect +import java.util.Locale class Launcher(logger: Logger) { + + private val jarsDirName = "jars" + def run(config: Config): ErrorCode = { - ErrorCode.NotImplemented + createCommandLine(config) match + case Right(commandLine) => + if config.dryRun then + logger.debug("dry-run") + println(commandLine.mkString(" ")) + ErrorCode.OK + else + launch(commandLine) + case Left(errorMessage) => + logger.error(errorMessage) + ErrorCode.GeneralError + } + + private def createCommandLine(config: Config): Either[String, Seq[String]] = + for { + ijDir <- locateIJDir(config) + launcherJar <- findImageJLauncherJar(ijDir) + javaExe <- locateJavaExecutable(config, ijDir) + systemType <- determineSystemType() + } yield { + buildCommandLine(ijDir, javaExe, launcherJar, systemType) + } + + 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.debug(s" ImageJ directory set to: $dir") + Right(dir) + case None => + Left(s"Cannot locate ImageJ directory. No subdirectory '$jarsDirName' in '$dir''") + + private def launch(command: Seq[String]): ErrorCode = + logger.debug("launchImageJ ...") + + logger.debug(s"Executing command:\n" + command.mkString(" ")) + + val builder = new ProcessBuilder(command*) + builder.redirectOutput(ProcessBuilder.Redirect.INHERIT) + builder.redirectError(ProcessBuilder.Redirect.INHERIT) + + val process = builder.start() + ErrorCode.OK + + private def locateJavaExecutable(config: Config, ijDir: File): Either[String, File] = + logger.debug("Looking for Java executable") + + val javaHomeE: Either[String, File] = config.javaHome match + case Some(d) => + logger.debug(s" Setting java-home to provided: '$d'") + Right(d) + case None => + locateJavaHomeIn(new File(ijDir, "java")) match + case Some(d) => + logger.debug(s" Located java dir in ImageJ home") + Right(d) + case None => + Option(System.getenv("JAVA_HOME")) match + case Some(d) => + logger.debug(" Using JAVA_HOME environment variable") + Right(new File(d).getAbsoluteFile) + case None => + Left("Unable to determine Java home") + + javaHomeE.flatMap(javaHome => + logger.debug(s" Java home set as: $javaHome") + logger.debug(s" Looking for java executable bin/$javaExeFileName") + val javaExe = new File(javaHome, s"bin/$javaExeFileName") + if javaExe.exists() then + logger.debug(s" Found '$javaExeFileName' in '$javaHome/bin'") + Right(javaExe) + else + Left("cannot find 'java.exe' in 'path'") + ) + + private def locateJavaHomeIn(dir: File): Option[File] = + if dir.exists() && dir.isDirectory then + val base = os.Path(dir.toPath) + val candidates: Seq[Path] = os.walk(path = base) + val c2 = candidates.filter(p => + p.toIO.isFile && + p.toIO.getName == javaExeFileName && + p.toIO.getParentFile.getName == "bin" + ) + logger.debug(" Candidates: " + c2.mkString(", ")) + c2.map(_.toIO.getParentFile.getParentFile) + .headOption + else + None + + private def findImageJLauncherJar(ijDir: File): Either[String, File] = + logger.debug("Looking for 'imagej-launcher*.jar'") + val jarsDir = new File(ijDir, jarsDirName) + if jarsDir.exists() then + if jarsDir.isDirectory then + // Locate launcher + jarsDir + .listFiles() + .find { f => + val name = f.getName + name.startsWith("imagej-launcher") && name.endsWith(".jar") + } match + case Some(f) => + logger.debug(s"Found: '$f'") + Right(f) + case None => + Left(s"Cannot find 'imagej-launcher*.jar' in '$jarsDir'") + else + Left(s"'$jarsDir' is not a directory'") + else + Left(s"Cannot find subdirectory '$jarsDirName' [$jarsDir]") + + private def determineSystemType(): Either[String, String] = { + val osName = System.getProperty("os.name") + val osArch = System.getProperty("os.arch") + logger.debug("os.name: " + osName) + logger.debug("os.arch: " + osArch) + + val notSupportedError = s"$osName $osArch not supported" + + if osName.toLowerCase.startsWith("windows") then + if osArch.toLowerCase.contains("64") then + Right("win64") + else + Left(notSupportedError) + else if osName.toLowerCase.contains("mac os x") then + if osArch.toLowerCase.contains("aarch64") then + Right("aarch64") + else + Left(notSupportedError) + else + Left(notSupportedError) + } + + private def buildCommandLine(ijDir: File, javaExe: File, launcherJar: File, systemType: String): Seq[String] = { + val ijDirPath = ijDir.getAbsolutePath + + Seq( + javaExe.getAbsolutePath, + "--add-opens", + "java.base/java.lang=ALL-UNNAMED", + "--add-opens", + "java.base/java.util=ALL-UNNAMED", + "--add-opens", + "java.desktop/sun.awt=ALL-UNNAMED", + "-Dpython.cachedir.skip=true", + s"-Dplugins.dir=$ijDirPath", + "-Xmx2048m", + "-Dimagej.splash=true", + s"-Djava.class.path=${launcherJar.getAbsolutePath}", + s"-Dimagej.dir=$ijDirPath", + s"-Dij.dir=$ijDirPath", + s"-Dfiji.dir=$ijDirPath", + s"-Dfiji.executable=$ijDirPath/ImageJ-$systemType.exe", + s"-Dij.executable=$ijDirPath/ImageJ-$systemType.exe", + s"-Djava.library.path=$ijDirPath/lib/$systemType;$ijDirPath/mm/$systemType", + "-Dscijava.context.strict=false", + "-Dpython.console.encoding=UTF-8", + "net.imagej.launcher.ClassLauncher", + "-ijjarpath", + "jars", + "-ijjarpath", + "plugins", + "net.imagej.Main" + ) } } + +object Launcher: + lazy val javaExeFileName: String = if Utils.isWindows then "java.exe" else "java" diff --git a/src/main/scala/ij_plugins/imagej_launcher/Main.scala b/src/main/scala/ij_plugins/imagej_launcher/Main.scala index 0332ed6..722c71e 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Main.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Main.scala @@ -23,12 +23,15 @@ object Main { case class Config( logLevel: Logger.Level = Logger.Level.Error, dryRun: Boolean = false, - javaHome: Option[File] = None + javaHome: Option[File] = None, + ijDir: Option[File] = None ) def main(args: Array[String]): Unit = setupLogger(Logger.Level.All) + printInfo() + val ret: ErrorCode = parseCommandLine(args) match case Some(config) => @@ -47,6 +50,11 @@ object Main { // System.exit(ret.value) end main + private def printInfo(): Unit = + logger.debug("os.arch: " + System.getProperty("os.arch")) + logger.debug("os.name: " + System.getProperty("os.name")) +// logger.debug("os.version: " + System.getProperty("os.version")) + private def parseCommandLine(args: Array[String]): Option[Config] = val builder = OParser.builder[Config] val parser1 = { @@ -79,7 +87,12 @@ object Main { // opt[Unit]("print-java-home") .action((_, c) => c.copy(dryRun = true)) - .text("print ImageJ's idea of JAVA_HOME") + .text("print ImageJ's idea of JAVA_HOME"), + // + opt[File]("ij-dir") + .valueName("") + .action((path, c) => c.copy(ijDir = Option(path))) + .text("set the ImageJ directory to (used to find jars/, plugins/ and macros/)") ) } diff --git a/src/main/scala/ij_plugins/imagej_launcher/Utils.scala b/src/main/scala/ij_plugins/imagej_launcher/Utils.scala new file mode 100644 index 0000000..076aa08 --- /dev/null +++ b/src/main/scala/ij_plugins/imagej_launcher/Utils.scala @@ -0,0 +1,8 @@ +package ij_plugins.imagej_launcher + +object Utils: + def isWindows: Boolean = + System + .getProperty("os.name") + .toLowerCase() + .startsWith("windows")