First version that can launch Fiji natively on Windows and Mac

This commit is contained in:
Jarek Sacha 2023-05-19 19:30:55 -04:00
parent d694d564fd
commit ab28e2daf7
No known key found for this signature in database
GPG key ID: F29625CE62288163
5 changed files with 221 additions and 7 deletions

8
ReadMe.md Normal file
View file

@ -0,0 +1,8 @@
IJP ImageJ Launcher
===================
Features
--------
* Launches ImageJ2 or Fiji

View file

@ -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 =>

View file

@ -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"

View file

@ -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("<path>")
.action((path, c) => c.copy(ijDir = Option(path)))
.text("set the ImageJ directory to <path> (used to find jars/, plugins/ and macros/)")
)
}

View file

@ -0,0 +1,8 @@
package ij_plugins.imagej_launcher
object Utils:
def isWindows: Boolean =
System
.getProperty("os.name")
.toLowerCase()
.startsWith("windows")