From 1312fa24b108096a5fa8fbfcacaa1f03dc21cb61 Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Thu, 1 Jun 2023 23:59:24 -0400 Subject: [PATCH 01/20] Correct typo --- ...4.command => IJP-ImageJ-Launcher-0.1.0-macosx-arm64.command} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename extras/{IJP-ImageL-Launcher-0.1.0-macosx-arm64.command => IJP-ImageJ-Launcher-0.1.0-macosx-arm64.command} (51%) diff --git a/extras/IJP-ImageL-Launcher-0.1.0-macosx-arm64.command b/extras/IJP-ImageJ-Launcher-0.1.0-macosx-arm64.command similarity index 51% rename from extras/IJP-ImageL-Launcher-0.1.0-macosx-arm64.command rename to extras/IJP-ImageJ-Launcher-0.1.0-macosx-arm64.command index 4432ceb..ebcfb9e 100755 --- a/extras/IJP-ImageL-Launcher-0.1.0-macosx-arm64.command +++ b/extras/IJP-ImageJ-Launcher-0.1.0-macosx-arm64.command @@ -1,4 +1,4 @@ #!/bin/bash DIR=$(cd "$(dirname "$0")" && pwd -P) echo "$DIR" -"$DIR"/IJP-ImageL-Launcher-0.1.0-macosx-arm64 --debug --ij-dir "$DIR" +"$DIR"/IJP-ImageJ-Launcher-0.1.0-macosx-arm64 --debug --ij-dir "$DIR" From e2b6e2d550c9f9f963e235f92403da3d9d11fc1c Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Fri, 2 Jun 2023 00:01:13 -0400 Subject: [PATCH 02/20] Mark next development version --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index a81f4c8..a4b298a 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ scalaVersion := "3.3.0" //name := "IJP-ImageJ-Launcher" -version := "0.1.0" +version := "0.1.1-SNAPSHOT" versionScheme := Some("early-semver") organization := "net.sf.ij-plugins" homepage := Some(new URI("https://github.com/ij-plugins/ijp-imagej-launcher").toURL) From 63b3ad6f5236fbc239ead034c00ffee789791eb1 Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Fri, 2 Jun 2023 17:52:42 -0400 Subject: [PATCH 03/20] Readme: Add info about Java 11+ focus --- ReadMe.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index ac323cc..26ead8b 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -33,7 +33,10 @@ the logic flow is too complex to correct without a significant rewrite. Features -------- -* Uses similar options to the original ImageJ Launcher, si IJP Launcher can be drop-in replacement +Here are the futures that are already implemented (see release notes for futures ofa specific release): + +* Uses similar options to the original ImageJ Launcher, so IJP Launcher can be used as a drop-in replacement +* Intended to be used with Java 11 or newer (the original launcher can be used for Java 8) * Provides native executable for various OS/Hardware systems - Windows - Mac OS X Arm64 (Apple Silicon) @@ -48,7 +51,7 @@ Features - Search ImageJ directory for available Java executables * Determines the amount of memory used by JVM based on total system memory use 75% of the max * Determines available `imagej-launcher*.jar` -* Performs updates pending after the last time ImageJ was closed +* **Performs updates** pending after the last time ImageJ was closed Full List of Command Line Options --------------------------------- From 231540bce562bb8d30d355e2f7a1fb1dea9c9f91 Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Fri, 2 Jun 2023 17:53:15 -0400 Subject: [PATCH 04/20] Bump SBT to 1.9.0 --- project/build.properties | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index ef3d266..6c37b7b 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1,6 @@ -sbt.version = 1.8.3 +# +# Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved. +# Author's e-mail: jpsacha at gmail.com +# + +sbt.version = 1.9.0 From 1f2c915159799d3ff58ba5838c56882fe1144e8b Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Fri, 2 Jun 2023 17:54:09 -0400 Subject: [PATCH 05/20] Code style: add comment with plugin site --- project/plugins.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/project/plugins.sbt b/project/plugins.sbt index f07f01d..de64519 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1 +1,2 @@ +// https://github.com/scala-native/scala-native addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.12") From 1d5b8887a395ce52c4acf2517df7db7275b32abb Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Sat, 3 Jun 2023 00:04:20 -0400 Subject: [PATCH 06/20] Readme: add info on setting executable permissions --- ReadMe.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 26ead8b..334c6c2 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -119,9 +119,22 @@ and "IJP-ImageJ-Launcher-0.1.0-macosx-arm64.command", save them to the `Fiji.app The "*.command" file is a helper that can be used to launch Fiji without using command prompt. Future versions of the IJP Launcher, after v.0.1.0, may eliminate the need for using this file. -**7. Start ImageJ** +**7. Set Executable Permissions** -In the `Fiji.app` folder double-click on `IJP-ImageL-Launcher-0.1.0-macosx-arm64.command` file (note the extension "* +When you download launcher files they may be saved without executable permissions. + +* Open terminal +* Navigate to the Fiji.app folder, for instance, `cd ~/Download/Fiji.app` +* Add executable permission to the launcher and the "*.command" file using + +```shell +chmod +x IJP-ImageJ-Launcher-0.1.0-macosx-arm64 +chmod +x IJP-ImageJ-Launcher-0.1.0-macosx-arm64.command +``` + +**8. Start ImageJ** + +You can start `IJP-ImageL-Launcher-0.1.0-macosx-arm64` from command line or in the `Fiji.app` folder double-click on `IJP-ImageL-Launcher-0.1.0-macosx-arm64.command` file (note the extension "* .command") That should start Fiji. You may need to open Settings and allow the IJP ImageJ Launcher to run. From 87882dc6e35ba1ef28c35ffe8481c313b9da6b43 Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Sun, 4 Jun 2023 18:25:59 -0400 Subject: [PATCH 07/20] Fixed: Use launcher location to infer ij-dir, similar to path in argv[0] #5 --- src/main/resources/scala-native/argv0.c | 40 +++++++++++++ .../ij_plugins/imagej_launcher/IJDir.scala | 58 +++++++++++++++++++ .../ij_plugins/imagej_launcher/Launcher.scala | 33 +++-------- .../ij_plugins/imagej_launcher/Native.scala | 22 ++++++- 4 files changed, 126 insertions(+), 27 deletions(-) create mode 100644 src/main/resources/scala-native/argv0.c create mode 100644 src/main/scala/ij_plugins/imagej_launcher/IJDir.scala 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 From 47674de560c42ccd96b4f6c284bcb3f07229e767 Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Mon, 5 Jun 2023 18:42:01 -0400 Subject: [PATCH 08/20] Update to Scala Native 0.4.13 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index de64519..3335ff7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,2 +1,2 @@ // https://github.com/scala-native/scala-native -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.12") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.13") From c30b853f64ef8986dbe99c45fe752ad301c7b253 Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Mon, 5 Jun 2023 18:51:09 -0400 Subject: [PATCH 09/20] Add test for Scala Native #3293 that should be resolved in v.0.4.13 --- build.sbt | 9 +++--- .../ij_plugins/imagej_launcher/PathSpec.scala | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 src/test/scala/ij_plugins/imagej_launcher/PathSpec.scala diff --git a/build.sbt b/build.sbt index a4b298a..6c6313b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -scalaVersion := "3.3.0" +scalaVersion := "3.3.0" //name := "IJP-ImageJ-Launcher" version := "0.1.1-SNAPSHOT" versionScheme := Some("early-semver") @@ -16,14 +16,15 @@ enablePlugins(ScalaNativePlugin) logLevel := Level.Info libraryDependencies ++= Seq( - "com.github.scopt" %%% "scopt" % "4.1.0", - "com.lihaoyi" %%% "os-lib" % "0.9.1" + "com.github.scopt" %%% "scopt" % "4.1.0", + "com.lihaoyi" %%% "os-lib" % "0.9.1", + "org.scalatest" %%% "scalatest" % "3.2.16" % Test ) 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/test/scala/ij_plugins/imagej_launcher/PathSpec.scala b/src/test/scala/ij_plugins/imagej_launcher/PathSpec.scala new file mode 100644 index 0000000..36845bb --- /dev/null +++ b/src/test/scala/ij_plugins/imagej_launcher/PathSpec.scala @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved. + * Author's e-mail: jpsacha at gmail.com + */ + +package ij_plugins.imagej_launcher + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should + +import java.nio.file.FileSystems +import scala.scalanative.runtime.Platform.isWindows + +class PathSpec extends AnyFlatSpec with should.Matchers: + + "A Path" should "should `relativize` jars on Windows (Scala Native #3293)" in { + if isWindows() then + // This to test that fix for Scala Native #3293 implemented, it should be part of Scala Native 0.4.13 + // See https://github.com/scala-native/scala-native/issues/3293 + + // val src = Path.of("C:\\a\\b\\c.jar") + val src = FileSystems.getDefault.getPath("C:\\a\\b\\c.jar") + // val base = Path.of("C:\\a") + val base = FileSystems.getDefault.getPath("C:\\a") + + val rel = base.relativize(src) + rel.toString should be("b\\c.jar") + } From 8718c9310c0a287c2fbfe6431ef28ceb6507e2a1 Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Mon, 5 Jun 2023 18:52:53 -0400 Subject: [PATCH 10/20] Remove workaround for Scala Native issue #3293, it was resolved in v.0.4.13 See https://github.com/scala-native/scala-native/issues/3293 --- .../ij_plugins/imagej_launcher/Updater.scala | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/main/scala/ij_plugins/imagej_launcher/Updater.scala b/src/main/scala/ij_plugins/imagej_launcher/Updater.scala index d73e70c..62219c4 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Updater.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Updater.scala @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved. + * Author's e-mail: jpsacha at gmail.com + */ + package ij_plugins.imagej_launcher import os.{Path, RelPath} @@ -23,8 +28,7 @@ object Updater: os.walk(updateDir) .filter(os.isFile) .foreach: src => -// val relativeDir = src.relativeTo(updateDir) - val dst = ijDir / relativeTo(src, updateDir) + val dst = ijDir / src.relativeTo(updateDir) if os.size(src) == 0 then logger.debug(s"remove: $dst") if !dryRun then os.remove(dst) @@ -45,25 +49,6 @@ object Updater: ex.printStackTrace() Left(s"Failed to perform update: ${ex.getMessage} - ${ex.getClass.getSimpleName}") - private def relativeTo(src: Path, base: Path): RelPath = - // This does what src.relativeTo(base) should do - // Problems is in the native code on Windows, - // os.Path#relativeTo creates relative path by adding `../` at the beginning of the absolute path, - // co you may get `../C:\a\b` which leads to a exception soon after. - // The issue is with Scala Native implementation of java.nio.file.Path#relativize on Windows, - // See https://github.com/scala-native/scala-native/issues/3293 - - // This implementation is very limited but sufficient for our use. - // It assumes specific relation between src and base. - - val srcStr = src.toString - val baseStr = base.toString - require(baseStr.nonEmpty) - require(srcStr.startsWith(baseStr)) - - val relStr = srcStr.drop(baseStr.length + 1) - RelPath(relStr) - private def deleteEmptyDirs(dir: Path, logger: Logger): Unit = logger.debug(s"Cleaning directory: $dir") os.list(dir) From 60ad1b18160866367f487ddfae8df67ea5ff173a Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Mon, 5 Jun 2023 18:54:04 -0400 Subject: [PATCH 11/20] Correct type, remove extra single quote character --- src/main/scala/ij_plugins/imagej_launcher/IJDir.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala b/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala index 3442012..096cc5d 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved. + * Author's e-mail: jpsacha at gmail.com + */ + package ij_plugins.imagej_launcher import ij_plugins.imagej_launcher.Main.Config @@ -16,9 +21,9 @@ object IJDir: config.ijDir match case Some(d) => - logger.debug(s" Considering provided ij-dir: '$d''") + logger.debug(s" Considering provided ij-dir: '$d'") asPath(d.getPath).flatMap: p => - logger.debug(s" '$p''") + logger.debug(s" '$p'") if isIJDir(p) then Right(p) else Left(s"ij-dir is not an ImageJ directory [$p]") case None => @@ -53,6 +58,6 @@ object IJDir: else FilePath(filePath) match case p: Path => Right(p) - case _ => Left(s"Not an absolute path: '$filePath''") + case _ => Left(s"Not an absolute path: '$filePath'") end IJDir From faf6a879e57fb02743ab59944fed61110deea6ca Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Mon, 5 Jun 2023 21:02:57 -0400 Subject: [PATCH 12/20] [WIP] Read ImageJ.cfg #3 --- .../imagej_launcher/IJConfigFile.scala | 114 ++++++++++++++++++ .../ij_plugins/imagej_launcher/Launcher.scala | 4 +- .../ij_plugins/imagej_launcher/Main.scala | 2 +- .../imagej_launcher/IJConfigFileTest.scala | 33 +++++ test/data/config_invalid/ImageJ.cfg | 4 + test/data/config_valid/ImageJ.cfg | 4 + 6 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/ij_plugins/imagej_launcher/IJConfigFile.scala create mode 100644 src/test/scala/ij_plugins/imagej_launcher/IJConfigFileTest.scala create mode 100644 test/data/config_invalid/ImageJ.cfg create mode 100644 test/data/config_valid/ImageJ.cfg diff --git a/src/main/scala/ij_plugins/imagej_launcher/IJConfigFile.scala b/src/main/scala/ij_plugins/imagej_launcher/IJConfigFile.scala new file mode 100644 index 0000000..203d9ca --- /dev/null +++ b/src/main/scala/ij_plugins/imagej_launcher/IJConfigFile.scala @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved. + * Author's e-mail: jpsacha at gmail.com + */ + +package ij_plugins.imagej_launcher + +import os.Path + +object IJConfigFile: + + private val FileName: String = "ImageJ.cfg" + private val MagicHeader: String = "# ImageJ startup properties" + private val MaxHeapMBKey: String = "maxheap.mb" + private val JvmArgsKey: String = "jvmargs" + private val LegacyModeKey: String = "legacy.mode" + private val AssignmentSymbol: String = "=" + private val CommentSymbol: String = "#" + + /** + * Read `ImageJ.cfg` from a specified directory. + * The configuration file must be in ImageJ2 format (with magic header "# ImageJ startup properties"). + * + * @param dir directory where `ImageJ.cfg` is located + * @param logger logger + * @return option containing the configuration file (Right). + * The option is empty if file is not present in input `dir`. + * An error message is returned if file is present but cannot be read without errors (Left). + */ + def readFromDir(dir: Path)(using logger: Logger): Either[String, Option[IJCConfig]] = + logger.debug(s"Reading ImageJ configuration from directory: $dir") + val path = dir / FileName + logger.debug(s" Looking for $FileName in: '$path'") + readFromFile(path) + + private[imagej_launcher] def readFromFile(ijConfigFile: Path)(using logger: Logger): Either[String, Option[IJCConfig]] = + if os.exists(ijConfigFile) then + val lines = os.read.lines(ijConfigFile) + val cfgE = + lines.headOption match + case Some(line) => + if line.trim.startsWith(MagicHeader) then + logger.debug(s" Parsing $FileName") + for + props <- parseProps(lines) + maxHeapMB <- parseLong(props, MaxHeapMBKey) + jvmArgs <- parseString(props, JvmArgsKey) + legacyMode <- parseBoolean(props, LegacyModeKey) + yield Option(IJCConfig(maxHeapMB, jvmArgs, legacyMode)) + else + Left(s"$FileName is invalid, does not contain header: '$MagicHeader'") + case None => + Left(s"$FileName is empty.") + + // Add context to the error message, if there is one + cfgE.left.map(e => s"Failed to read $FileName. $e") + else + logger.debug(s" $FileName not found: $ijConfigFile") + Right(None) + + private[imagej_launcher] def parseProps(lines: Seq[String]): Either[String, Map[String, String]] = + // Parse each line + val lineResults: Seq[Either[String, (String, String)]] = + lines + .zipWithIndex + .filter { case (line, _) => !line.startsWith(CommentSymbol) } + .map { case (line, lineNumber) => + val index = line.indexOf(AssignmentSymbol) + if index > 0 then + val key = line.substring(0, index).trim + if !key.isBlank then + Right(key -> line.substring(index + 1).trim) + else + Left(s"Error in line $lineNumber: key cannot be empty: '$line'") + else if index == 0 then + Left(s"Error in line $lineNumber: line cannot start with an assignment symbol '$AssignmentSymbol': '$line'") + else + Left(s"Error in line $lineNumber: no assignment symbol '$AssignmentSymbol' present: '$line'") + } + + // Separate lines with errors + val errors = lineResults.collect { case e: Left[?, ?] => e } + if errors.isEmpty then + // Convert key->value pairs to a map + val r = + lineResults + .collect { case e: Right[?, ?] => e } + .map(_.value) + .toMap + Right(r) + else + Left(errors.map(_.value).mkString("\n")) + + private def parseLong(props: Map[String, String], key: String): Either[String, Long] = + parseString(props, key).flatMap: s => + s.toLongOption match + case Some(v) => Right(v) + case None => Left(s"Failed to parse '$s' as an integer.") + + private def parseString(props: Map[String, String], key: String): Either[String, String] = + props.get(key) match + case Some(v) => Right(v) + case None => Left(s"No key named '$key'.") + + private def parseBoolean(props: Map[String, String], key: String): Either[String, Boolean] = + parseString(props, key).flatMap: s => + s.toBooleanOption match + case Some(v) => Right(v) + case None => Left(s"Failed to parse '$s' as a boolean.") + + /** Configuration loaded from `ImageJ.cfg`. */ + case class IJCConfig(maxHeapMB: Long, jvmArgs: String, legacyMode: Boolean) + +end IJConfigFile diff --git a/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala b/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala index 2cb704c..56dde7b 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala @@ -13,7 +13,7 @@ import os.Path import java.io.File import java.lang.ProcessBuilder.Redirect -class Launcher(logger: Logger): +class Launcher(using logger: Logger): def run(config: Config): Unit = prepareLaunch(config) match @@ -30,6 +30,7 @@ class Launcher(logger: Logger): for ijDir <- IJDir.locate(config, logger) _ <- Updater.update(ijDir, config.dryRun, logger) + ijConfig <- IJConfigFile.readFromDir(ijDir) launcherJar <- findImageJLauncherJar(ijDir.toIO) javaExe <- locateJavaExecutable(config, ijDir.toIO) systemType <- determineSystemType() @@ -178,6 +179,7 @@ class Launcher(logger: Logger): "plugins", "net.imagej.Main" ) + end buildCommandLine private def launch(command: Seq[String]): Unit = logger.debug("launchImageJ ...") diff --git a/src/main/scala/ij_plugins/imagej_launcher/Main.scala b/src/main/scala/ij_plugins/imagej_launcher/Main.scala index 355f25c..1ab084a 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Main.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Main.scala @@ -69,7 +69,7 @@ object Main: private def setupLogger(logLevel: Logger.Level): Unit = logger = new Logger(logLevel) - private def runLauncher(config: Config): Unit = new Launcher(logger).run(config) + private def runLauncher(config: Config): Unit = new Launcher(using logger).run(config) case class Config( logLevel: Logger.Level = Logger.Level.Error, diff --git a/src/test/scala/ij_plugins/imagej_launcher/IJConfigFileTest.scala b/src/test/scala/ij_plugins/imagej_launcher/IJConfigFileTest.scala new file mode 100644 index 0000000..f0260fe --- /dev/null +++ b/src/test/scala/ij_plugins/imagej_launcher/IJConfigFileTest.scala @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2000-2023 Jarek Sacha. All Rights Reserved. + * Author's e-mail: jpsacha at gmail.com + */ + +package ij_plugins.imagej_launcher + +import org.scalatest.flatspec.AnyFlatSpec + +class IJConfigFileTest extends AnyFlatSpec: + + given logger:Logger = new Logger(Logger.Level.All) + + "IJConfigFile" should "read valid config files" in: + val path = os.pwd / "test" / "data" / "config_valid" / "ImageJ.cfg" + + IJConfigFile.readFromFile(path) match + case Right(cfg) => + logger.debug(cfg.toString) + assert(cfg.nonEmpty) + case Left(err) => + logger.debug(s"Unexpected error: $err") + fail() + + it should "fail reading invalid config files" in: + val path = os.pwd / "test" / "data" / "config_invalid" / "ImageJ.cfg" + + IJConfigFile.readFromFile(path) match + case Right(cfg) => + logger.debug(cfg.toString) + fail(s"Expected to return errors when reading config, but read config as: $cfg") + case Left(err) => + logger.debug(s"Expected error\n:$err") diff --git a/test/data/config_invalid/ImageJ.cfg b/test/data/config_invalid/ImageJ.cfg new file mode 100644 index 0000000..9fc162e --- /dev/null +++ b/test/data/config_invalid/ImageJ.cfg @@ -0,0 +1,4 @@ +# ImageJ startup properties +maxheap.mb = 1024m +jvmargs = -XX:+HeapDumpOnOutOfMemoryError -Xincgc +legacy.mode = false \ No newline at end of file diff --git a/test/data/config_valid/ImageJ.cfg b/test/data/config_valid/ImageJ.cfg new file mode 100644 index 0000000..5a40166 --- /dev/null +++ b/test/data/config_valid/ImageJ.cfg @@ -0,0 +1,4 @@ +# ImageJ startup properties +maxheap.mb = 1024 +jvmargs = -XX:+HeapDumpOnOutOfMemoryError -Xincgc +legacy.mode = false \ No newline at end of file From eb8b6a9a17693eb13405c6288fa185612f038a63 Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Mon, 5 Jun 2023 21:10:39 -0400 Subject: [PATCH 13/20] Refactor: `using` `given` logger --- src/main/scala/ij_plugins/imagej_launcher/IJDir.scala | 2 +- src/main/scala/ij_plugins/imagej_launcher/Launcher.scala | 4 ++-- src/main/scala/ij_plugins/imagej_launcher/Updater.scala | 8 ++++---- .../scala/ij_plugins/imagej_launcher/UpdaterDemo.scala | 3 ++- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala b/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala index 096cc5d..36260af 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala @@ -16,7 +16,7 @@ object IJDir: val jarsDirName = "jars" /** Locate ImageJ directory */ - def locate(config: Config, logger: Logger): Either[String, Path] = + def locate(config: Config)(using logger: Logger): Either[String, Path] = logger.debug("Looking for ImageJ directory") config.ijDir match diff --git a/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala b/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala index 56dde7b..694d183 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Launcher.scala @@ -28,8 +28,8 @@ class Launcher(using logger: Logger): private def prepareLaunch(config: Config): Either[String, Seq[String]] = for - ijDir <- IJDir.locate(config, logger) - _ <- Updater.update(ijDir, config.dryRun, logger) + ijDir <- IJDir.locate(config) + _ <- Updater.update(ijDir, config.dryRun) ijConfig <- IJConfigFile.readFromDir(ijDir) launcherJar <- findImageJLauncherJar(ijDir.toIO) javaExe <- locateJavaExecutable(config, ijDir.toIO) diff --git a/src/main/scala/ij_plugins/imagej_launcher/Updater.scala b/src/main/scala/ij_plugins/imagej_launcher/Updater.scala index 62219c4..beb5e75 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Updater.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Updater.scala @@ -18,7 +18,7 @@ object Updater: * @param logger configured logger * @return Number of files processed or an error message. */ - def update(ijDir: Path, dryRun: Boolean, logger: Logger): Either[String, Long] = + def update(ijDir: Path, dryRun: Boolean)(using logger: Logger): Either[String, Long] = try val updateDir = ijDir / "update" // Count used only for debug info @@ -39,7 +39,7 @@ object Updater: if !dryRun then os.move(src, dst, replaceExisting = true, createFolders = true) count += 1 logger.debug(s"Delete update directory: $updateDir") - if !dryRun then deleteEmptyDirs(updateDir, logger) + if !dryRun then deleteEmptyDirs(updateDir) Right(count) else logger.info("No update found") @@ -49,11 +49,11 @@ object Updater: ex.printStackTrace() Left(s"Failed to perform update: ${ex.getMessage} - ${ex.getClass.getSimpleName}") - private def deleteEmptyDirs(dir: Path, logger: Logger): Unit = + private def deleteEmptyDirs(dir: Path)(using logger: Logger): Unit = logger.debug(s"Cleaning directory: $dir") os.list(dir) .filter(os.isDir) - .foreach(p => deleteEmptyDirs(p, logger)) + .foreach(p => deleteEmptyDirs(p)) if os.list(dir).isEmpty then logger.debug(s"Removing empty dir: $dir") diff --git a/src/test/scala/ij_plugins/imagej_launcher/UpdaterDemo.scala b/src/test/scala/ij_plugins/imagej_launcher/UpdaterDemo.scala index 6e7f24a..49f674a 100644 --- a/src/test/scala/ij_plugins/imagej_launcher/UpdaterDemo.scala +++ b/src/test/scala/ij_plugins/imagej_launcher/UpdaterDemo.scala @@ -4,6 +4,7 @@ import os.Path @main def updaterDemo(ijDir: String): Unit = - Updater.update(Path(ijDir), dryRun = false, new Logger(Logger.Level.Debug)) match + given logger: Logger = new Logger(Logger.Level.Debug) + Updater.update(Path(ijDir), dryRun = false) match case Right(count) => println(s"Processed $count files") case Left(error) => println(s"Failed with error: $error") From f2bb93871fafe4f6b3c0d515cfa9224b3ac6f08b Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Wed, 7 Jun 2023 18:30:38 -0400 Subject: [PATCH 14/20] Add compiler warnings to help with the coding style --- build.sbt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/build.sbt b/build.sbt index 6c6313b..838ed5e 100644 --- a/build.sbt +++ b/build.sbt @@ -21,6 +21,17 @@ libraryDependencies ++= Seq( "org.scalatest" %%% "scalatest" % "3.2.16" % Test ) +scalacOptions ++= Seq( + "-unchecked", + "-deprecation", + "-explain", + "-explain-types", + "-rewrite", + "-source:3.3-migration", + "-Wvalue-discard", + "-Wunused:all" +) + Compile / run / mainClass := Some("ij_plugins.imagej_launcher.Main") // import to add Scala Native options From ce0c49c5150a2c4ff10ce9df394880663a518f5b Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Wed, 7 Jun 2023 20:24:16 -0400 Subject: [PATCH 15/20] Update to Scala Native 0.4.14 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 3335ff7..eb2c08d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,2 +1,2 @@ // https://github.com/scala-native/scala-native -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.13") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14") From 11b248fc1ffa854c9a2f50b5fb62cdb29c2c8d5d Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Wed, 7 Jun 2023 20:25:56 -0400 Subject: [PATCH 16/20] Log session to ~/.ijp_imagej_launcher.log #6 --- notes/v.0.2.0.md | 5 +++ .../ij_plugins/imagej_launcher/Logger.scala | 42 ++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 notes/v.0.2.0.md diff --git a/notes/v.0.2.0.md b/notes/v.0.2.0.md new file mode 100644 index 0000000..3be1e00 --- /dev/null +++ b/notes/v.0.2.0.md @@ -0,0 +1,5 @@ +### New Features + +* Use launcher location to infer `ij-dir`. #5 +* Session log is saved to `~/.ijp_imagej_launcher.log` to facilitate troubleshooting when not running from command + prompt. The log is reset for each session. #6 diff --git a/src/main/scala/ij_plugins/imagej_launcher/Logger.scala b/src/main/scala/ij_plugins/imagej_launcher/Logger.scala index baa5390..9963f40 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Logger.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Logger.scala @@ -5,20 +5,60 @@ package ij_plugins.imagej_launcher -import ij_plugins.imagej_launcher.Logger.Level +import ij_plugins.imagej_launcher.Logger.{Level, logToFile} +import os.Path + +import java.nio.file.{Files, StandardOpenOption} +import scala.util.control.NonFatal class Logger(val level: Level = Level.Info): + def debug(msg: String): Unit = pprint(Level.Debug, msg) def info(msg: String): Unit = pprint(Level.Info, msg) def error(msg: String): Unit = pprint(Level.Error, msg) private def pprint(l: Level, message: String): Unit = + val m = f"${l.name}%-5s: $message" if l.level <= level.level then println(f"${l.name}%-5s: $message") + logToFile(m) object Logger: + + private val LogFile: Path = os.home / ".ijp_imagej_launcher.log" + private var logFileReset: Boolean = true + enum Level(val level: Int, val name: String): case Off extends Level(0, "OFF") case Error extends Level(200, "ERROR") case Info extends Level(400, "INFO") case Debug extends Level(500, "DEBUG") case All extends Level(Int.MaxValue, "DEBUG") + + private def logToFile(message: String): Unit = + try + if logFileReset && os.exists(LogFile) then + val _ = os.remove(LogFile) + logFileReset = false + + val data = readableTimeStamp() + ": " + message + "\n" + val lines = data.getBytes() + val _ = Files.write( + LogFile.toNIO, + lines, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND, + StandardOpenOption.WRITE + ) + catch + case NonFatal(_) => + // Ignore exceptions + + private def readableTimeStamp(): String = + // Simple implementation returns GMT time (no date) + // Use custom code as DateTimeFormatter is not available in Scala Native 0.4.14 + val t = System.currentTimeMillis() + val sss = t % 1000 + val ss = (t / 1000L).toInt % 60 + val mm = (t / (1000L * 60L)).toInt % 60 + val hh = (t / (1000L * 60L * 60L)).toInt % 24 + f"$hh%02d-$mm%02d-$ss%02d_$sss%03dZ" From 374a162f299d52dcf160b1e842f66efcefc026af Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Wed, 7 Jun 2023 20:32:06 -0400 Subject: [PATCH 17/20] Remove unused imports --- build.sbt | 2 +- src/main/scala/ij_plugins/imagej_launcher/IJDir.scala | 2 +- src/main/scala/ij_plugins/imagej_launcher/Main.scala | 2 +- src/main/scala/ij_plugins/imagej_launcher/Updater.scala | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 838ed5e..cc982bb 100644 --- a/build.sbt +++ b/build.sbt @@ -28,7 +28,7 @@ scalacOptions ++= Seq( "-explain-types", "-rewrite", "-source:3.3-migration", - "-Wvalue-discard", +// "-Wvalue-discard", "-Wunused:all" ) diff --git a/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala b/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala index 36260af..0239b36 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala @@ -6,7 +6,7 @@ package ij_plugins.imagej_launcher import ij_plugins.imagej_launcher.Main.Config -import os.{FilePath, Path, RelPath, SubPath} +import os.{FilePath, Path} import scala.util.control.NonFatal diff --git a/src/main/scala/ij_plugins/imagej_launcher/Main.scala b/src/main/scala/ij_plugins/imagej_launcher/Main.scala index 1ab084a..dc44fb3 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Main.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Main.scala @@ -5,7 +5,7 @@ package ij_plugins.imagej_launcher -import scopt.{DefaultOEffectSetup, OParser} +import scopt.OParser import java.io.File diff --git a/src/main/scala/ij_plugins/imagej_launcher/Updater.scala b/src/main/scala/ij_plugins/imagej_launcher/Updater.scala index beb5e75..c855f82 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Updater.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Updater.scala @@ -5,7 +5,7 @@ package ij_plugins.imagej_launcher -import os.{Path, RelPath} +import os.Path import scala.util.control.NonFatal From 95638a462ab39cbcb22dbc088d6f648fa0d1f517 Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Sat, 10 Jun 2023 08:47:22 -0400 Subject: [PATCH 18/20] Log command line arguments Effectively, at this point this is only sent to log file --- src/main/scala/ij_plugins/imagej_launcher/Main.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/ij_plugins/imagej_launcher/Main.scala b/src/main/scala/ij_plugins/imagej_launcher/Main.scala index dc44fb3..c136255 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Main.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Main.scala @@ -29,6 +29,7 @@ object Main: case None => private def parseCommandLine(args: Array[String]): Option[Config] = + logger.debug("Command line: " + args.map("'" + _ + "'").mkString(", ")) val builder = OParser.builder[Config] val parser1 = import builder.* From 9a0cb694f72aa258044a35903bbeac7ce2bbb9e3 Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Sat, 10 Jun 2023 09:06:17 -0400 Subject: [PATCH 19/20] Better inference of ImageJ directory on macOS - consider launcher being in subdirectory "Contents/MacOS" #7 --- .../ij_plugins/imagej_launcher/IJDir.scala | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala b/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala index 0239b36..e871ea1 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/IJDir.scala @@ -30,18 +30,28 @@ object IJDir: 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.") + inferIJDir(appPath) match + case Some(p) => + Right(p) + case None => + 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'") + inferIJDir(cwd) match + case Some(p) => + Right(p) + case None => + logger.debug(" Current working directory is not an ImageJ directory.") + Left("Cannot locate ImageJ directory.") + + private def inferIJDir(path: Path): Option[Path] = + if isIJDir(path) then + Option(path) + else if path.endsWith(os.rel / "Contents" / "MacOS") then + Option(path / os.up / os.up) + else + None private def isIJDir(path: Path): Boolean = os.exists(path) && From 8a417ceedb8dc647927e7863298861745a4adf31 Mon Sep 17 00:00:00 2001 From: Jarek Sacha Date: Sat, 10 Jun 2023 09:51:17 -0400 Subject: [PATCH 20/20] Ensure that SBT project version and application version are in sync --- build.sbt | 5 +++++ project/buildinfo.sbt | 2 ++ src/main/scala/ij_plugins/imagej_launcher/Main.scala | 7 +++---- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 project/buildinfo.sbt diff --git a/build.sbt b/build.sbt index cc982bb..e6e6f49 100644 --- a/build.sbt +++ b/build.sbt @@ -48,3 +48,8 @@ nativeConfig ~= { c => //nativeConfig ~= { c => // c.withCompileOptions(c.compileOptions ++ Seq("-v")) //} + +// Version info generation from SBT configuration +enablePlugins(BuildInfoPlugin) +buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion) +buildInfoPackage := "ij_plugins.imagej_launcher" diff --git a/project/buildinfo.sbt b/project/buildinfo.sbt new file mode 100644 index 0000000..b9ae828 --- /dev/null +++ b/project/buildinfo.sbt @@ -0,0 +1,2 @@ +// https://github.com/sbt/sbt-buildinfo +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") \ No newline at end of file diff --git a/src/main/scala/ij_plugins/imagej_launcher/Main.scala b/src/main/scala/ij_plugins/imagej_launcher/Main.scala index c136255..d2afaa8 100644 --- a/src/main/scala/ij_plugins/imagej_launcher/Main.scala +++ b/src/main/scala/ij_plugins/imagej_launcher/Main.scala @@ -10,10 +10,9 @@ import scopt.OParser import java.io.File object Main: - private var logger = new Logger() - private val AppName = "ijp-imagej-launcher" - // private val AppVersion = s"${Version.version} [${Version.buildTimeStr}]" - private val AppVersion = s"0.1.0" + private var logger = new Logger() + private val AppName = "IJP-ImageJ-Launcher" + private val AppVersion = BuildInfo.version private val VersionMessage = s"v.$AppVersion" private val AppDescription = """Native launcher for ImageJ2