diff --git a/.bloop/bloop.settings.json b/.bloop/bloop.settings.json deleted file mode 100644 index bd7b5a7..0000000 --- a/.bloop/bloop.settings.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "javaSemanticDBVersion": "0.7.4", - "semanticDBVersion": "4.5.0", - "supportedScalaVersions": [ - "2.13.8", - "2.12.15", - "2.12.14", - "2.12.13", - "2.12.12", - "2.12.11", - "2.13.5", - "2.13.6", - "2.13.7", - "2.11.12", - "2.12.8", - "2.12.9", - "2.12.10", - "2.13.1", - "2.13.2", - "2.13.3", - "2.13.4" - ] -} \ No newline at end of file diff --git a/.mill-version b/.mill-version index 42624f3..56e9df1 100644 --- a/.mill-version +++ b/.mill-version @@ -1 +1 @@ -0.10.2 \ No newline at end of file +0.10.3 \ No newline at end of file diff --git a/README.md b/README.md index af097ed..8cfc06c 100644 --- a/README.md +++ b/README.md @@ -5,32 +5,20 @@ ## Introduction -This project shows how to set up a Mill project that uses -[OpenJFX (JavaFx)](https://openjfx.io/) library. More specifically it shows how -one needs to configure the use of the module system for JDK1.9+. Unlike other -modules, the JavaFX cannot be used as regular JAR libraries. This means the JVM -parameters for the module path and module names must be used. +This project shows how to set up a Mill project that uses [OpenJFX (JavaFx)](https://openjfx.io/) library. More specifically it shows how one needs to configure the use of the module system for JDK1.9+. Unlike other modules, the JavaFX cannot be used as regular JAR libraries. This means the JVM parameters for the module path and module names must be used. -The project contains two examples of a very simple JavaFX application that -shows a button. In the first application, when pressed the message: +The project contains two examples of a very simple JavaFX application that shows a button. In the first application, when pressed the message: Hello World! -is written to console. One can exit the application when desired. The second -application starts up a very small dialogue box. Pressing the button terminates -the application. Each of these demonstrates alternate ways of starting up a -JavaFX/OpenFX application from Scala. +is written to console. One can exit the application when desired. The second application starts up a very small dialogue box. Pressing the button terminates the application. Each of these demonstrates alternate ways of starting up a JavaFX/OpenFX application from Scala. ## Getting started -The project has three modules: `javafx`, `managed`and `unmanaged` that you can -find in the build script `build.sc`. Both the `javafx` and `managed` module -shows how to set up the modules using Mill's support for managed libraries. In -these cases, you need only add the library to use, the dependent JavaFX modules -will be downloaded for you. +The project has three modules: `javafx`, `managed`and `unmanaged` that you can find in the build script `build.sc`. Both the `javafx` and `managed` module shows how to set up the modules using Mill's support for managed libraries. In these cases, you need only add the library to use, the dependent JavaFX modules will be downloaded for you. + +The `javafx` module is an example of the use of pure Java code. To execute the application use any of these command: -The `javafx` module is an example of the use of pure Java code. To execute the -application use any of these command: * `./mill -i javafx.run` * `./mill -i javafx.runMain helloworld.HelloWorld` * `./mill -i --watch javafx.run` @@ -38,25 +26,18 @@ application use any of these command: * `./mill -i --watch javafx.runMain button.Main` -The `managed` module has two applications, each exemplifying 2 different ways to -start Scala JavaFX (OpenFX) applications (*one cannot use the class instance -directly*). To execute the application use any of these command: +The `managed` module has two applications, each exemplifying 2 different ways to start Scala JavaFX (OpenFX) applications (*one cannot use the class instance directly*). To execute the application use any of these command: + * `./mill -i managed.run` * `./mill -i managed.runMain helloworld.HelloWorld` * `./mill -i --watch managed.run` * `./mill -i managed.runMain button.Main` * `./mill -i --watch managed.runMain button.Main` -To use the IDE: - `./mill mill.scalalib.GenIdea/idea` - -The `unmanaged` module requires that you list all the dependent JavaFX -libraries that are required. You can use both managed and unmanaged libraries -simultaneously, so you need list those libraries you wish to use. This module -has the exact same applications, so the command are the same save for the -module name. +The `unmanaged` module requires that you list all the dependent JavaFX libraries that are required. You can use both managed and unmanaged libraries simultaneously, so you need list those libraries you wish to use. This module has the exact same applications, so the command are the same save for the module name. To execute the application use any of these command: + * `./mill -i unmanaged.run` * `./mill -i unmanaged.runMain helloworld.HelloWorld` * `./mill -i --watch unmanaged.run` @@ -65,10 +46,7 @@ To execute the application use any of these command: ## Setting up the Modules -The `build.sc` tries to automate as much of the JVM module parameters as -possible. More concretely, it uses Mill's `runClasspath()` method to obtain the -list of libraries (contains bot managed and unmanaged archives). It then filters -these to obtain the JavaFX libraries. +The `build.sc` tries to automate as much of the JVM module parameters as possible. More concretely, it uses Mill's `runClasspath()` method to obtain the list of libraries (contains bot managed and unmanaged archives). It then filters these to obtain the JavaFX libraries. ```scala override def forkArgs: Target[Seq[String]] = T { @@ -83,134 +61,18 @@ these to obtain the JavaFX libraries. } ``` -However, it may be necessary to tweak those parameters according -to you use case. More concretely you may need to add module exports or -patches (a patch allows you to substitute a boot module with another drop-in -replacement, for example using [TestFX/Monocle](https://github.com/TestFX/Monocle)). -So tale a look at the `override def forkArgs: Target[Seq[String]]` method and -change that accordingly. +However, it may be necessary to tweak those parameters according to your use case. More concretely you may need to add module exports or patches (a patch allows you to substitute a boot module with another drop-in replacement, for example using [TestFX/Monocle](https://github.com/TestFX/Monocle)). So take a look at the `override def forkArgs: Target[Seq[String]]` method and change that accordingly. ## Using IntelliJ To use this project in IntelliJ execute the following command: - ./mill mill.scalalib.GenIdea/idea + + `./mill mill.scalalib.GenIdea/idea` Note that Mill has support for [Bloop](https://scalacenter.github.io/bloop/). However, this needs to be set up in IntelliJ first. ## Using VSCode -In VSCode one need only open and import the Mill build script. Because Mill -supports [Bloop](https://scalacenter.github.io/bloop/), this should -automatically work for you provided you have installed the [Metals extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=scalameta.metals#overview). -See also [metals-vscode in Github](https://github.com/scalameta/metals-vscode). - - -# Historical notes: Issue with using JavaFX libraries - -After version `0.5.3` attempting to run a JavaFX application breaks. -See: - -* Issue [767](https://github.com/lihaoyi/mill/issues/767) -* Issue [775](https://github.com/lihaoyi/mill/pull/775) -* Issue [759](https://github.com/lihaoyi/mill/issues/759) -* Issue [1947](https://github.com/coursier/coursier/issues/1947) -* Issue [1928](https://github.com/lihaoyi/mill/issues/928) - -Discussion on a workaround is [here](https://github.com/lihaoyi/mill/discussions/1105). -Unfortunately, this also did not work. It seems like it is not only the artifact downloads -that may be causing problems. More specifically in version `0.5.3` _we cannot run a -class that is **not** marked as a main class_. - -## Launching Mill with the OpenFX modules - -### Version 0.5.3 - -These execute correctly: - * `./mill -i javafx.run` - * `./mill -i javafx.runMain helloworld.HelloWorld` - * `./mill -i --watch javafx.run` - -This fails: - `` - -with the error: - -``` -Error: JavaFX runtime components are missing, and are required to run this application -``` - -Previous versions also work. - -###Version 0.9.4 - -Mill fails to resolve the dependencies: -``` -./mill -i javafx.run -[17/31] javafx.resolvedIvyDeps -1 targets failed -javafx.resolvedIvyDeps Failed to load source dependencies -not found: https://repo1.maven.org/maven2/org/openjfx/javafx-controls/13.0.2/javafx-controls-13.0.2-${javafx.platform}.jar -not found: https://repo1.maven.org/maven2/org/openjfx/javafx-graphics/13.0.2/javafx-graphics-13.0.2-${javafx.platform}.jar -not found: https://repo1.maven.org/maven2/org/openjfx/javafx-base/13.0.2/javafx-base-13.0.2-${javafx.platform}.jar -``` - -Mill fails to resolve the dependencies: -``` -./mill -i javafx.runMain helloworld.HelloWorld -[16/28] javafx.resolvedIvyDeps -1 targets failed -javafx.resolvedIvyDeps Failed to load source dependencies - not found: https://repo1.maven.org/maven2/org/openjfx/javafx-controls/13.0.2/javafx-controls-13.0.2-${javafx.platform}.jar - not found: https://repo1.maven.org/maven2/org/openjfx/javafx-graphics/13.0.2/javafx-graphics-13.0.2-${javafx.platform}.jar - not found: https://repo1.maven.org/maven2/org/openjfx/javafx-base/13.0.2/javafx-base-13.0.2-${javafx.platform}.jar - -``` - -## Adding the classifier - -We added a classifier automatically. In the working Mill versions we see that it makes -no difference to the downloaded artifacts. - -## Java Installation - -### JDK Installation - -Tested on JDK 11. because the JDK does not include the JavaFX run-time, this -has to be installed. For Ubuntu, we need to: - -* sudo apt install openjfx -* dpkg-query -L openjfx -* java --module-path /usr/share/openjfx/lib --add-modules=javafx.controls,javafx.fxml,javafx.base,javafx.media,javafx.web,javafx.swing -jar '/home/lotfi/Documents/MyAppfolder/my_application.jar' - -The `dpkg-query` provides us with the path to the OpenJFX run-time. This -path and the required modules must be added to the `java` comma nd line -arguments. - -### JavaFX Installation - -You can install and use the system wide JavaFX. But we need to use and indicate the -module path. - -# Note on configuring Java modules - -In Ubuntu linux - -``` -user@machine:~$ update-alternatives --list java -/usr/lib/jvm/java-11-openjdk-amd64/bin/java -/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java -/usr/lib/jvm/java-8-oracle/jre/bin/java -``` - -The following gets us the current active path: - -``` -user@machine:~$readlink -f /usr/bin/java -/usr/lib/jvm/java-11-openjdk-amd64/bin/java -``` - -Look at [Gradle JavaFX plugin](https://github.com/openjfx/javafx-gradle-plugin) -It provides the module paths - +In VSCode one need only open and import the Mill build script. Because Mill supports [Bloop](https://scalacenter.github.io/bloop/), this should automatically work for you provided you have installed the [Metals extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=scalameta.metals#overview). See also [metals-vscode in Github](https://github.com/scalameta/metals-vscode). diff --git a/allOS/src/button/ButtonApp.scala b/allOS/src/button/ButtonApp.scala new file mode 100644 index 0000000..66fe9ad --- /dev/null +++ b/allOS/src/button/ButtonApp.scala @@ -0,0 +1,27 @@ +package button + +import javafx.application.Application +import javafx.stage.Stage +import javafx.scene.Scene +import javafx.scene.layout.VBox +import javafx.scene.control.Button + +class ButtonApp extends Application { + + override def start(stage: Stage) = { + System.out.println("STARTED un-managed Scala!") + var button = new Button("Please allOS Scala Click Me!") + button.setOnAction(e => stage.close()) + var box = new VBox() + box.getChildren().add(button) + var scene = new Scene(box) + stage.setScene(scene) + stage.setTitle("Just an allOS Scala button") + stage.show() + } + + def launchIt(args: Array[String]):Unit={ + Application.launch(args:_*) + } + +} \ No newline at end of file diff --git a/allOS/src/button/Main.scala b/allOS/src/button/Main.scala new file mode 100644 index 0000000..0b79b22 --- /dev/null +++ b/allOS/src/button/Main.scala @@ -0,0 +1,17 @@ +package button + +/** + * + * ./mill mill.scalalib.GenIdea/idea + * + * ./mill -i allOS.runMain button.Main + * ./mill -i --watch allOS.runMain button.Main + * + * @see https://stackoverflow.com/questions/12124657/getting-started-on-scala-javafx-desktop-application-development + */ +object Main { + def main(args: Array[String]) = + val app = new ButtonApp + app.launchIt(args) + +} \ No newline at end of file diff --git a/allOS/src/helloworld/HelloWorld.scala b/allOS/src/helloworld/HelloWorld.scala new file mode 100644 index 0000000..78457e4 --- /dev/null +++ b/allOS/src/helloworld/HelloWorld.scala @@ -0,0 +1,44 @@ +package helloworld + +// cSpell:ignore helloworld + +import javafx.application.Application +import javafx.event.ActionEvent +import javafx.event.EventHandler +import javafx.scene.Scene +import javafx.scene.control.Button +import javafx.scene.layout.StackPane +import javafx.stage.Stage + +/** + * + * ./mill mill.scalalib.GenIdea/idea + * + * ./mill -i allOS.run + * ./mill -i allOS.runMain helloworld.HelloWorld + * ./mill -i --watch allOS.run + * + * @see https://stackoverflow.com/questions/12124657/getting-started-on-scala-javafx-desktop-application-development + */ +class HelloWorld extends Application { + + override def start(primaryStage: Stage) = { + primaryStage.setTitle("Hello allOS Scala World!") + val btn = new Button() + btn.setText("Say 'Hello allOS Scala World'") + btn.setOnAction(new EventHandler[ActionEvent]() { + override def handle(event: ActionEvent) = { + System.out.println("Hello allOS Scala World!") + } + }) + + val root = new StackPane() + root.getChildren().add(btn) + primaryStage.setScene(new Scene(root, 300, 250)) + primaryStage.show() + } +} + +object HelloWorld: + def main(args: Array[String]) = + Application.launch(classOf[HelloWorld], args: _*) diff --git a/managed/test/src/PlotSpec.scala b/allOS/test/src/ExampleSpec.scala similarity index 64% rename from managed/test/src/PlotSpec.scala rename to allOS/test/src/ExampleSpec.scala index 03eaebd..486a7ca 100644 --- a/managed/test/src/PlotSpec.scala +++ b/allOS/test/src/ExampleSpec.scala @@ -8,8 +8,8 @@ package managed * * ./mill -i managed.test * ./mill -i managed.test.testLocal - * ./mill -i managed.test splotly.PlotSpec.* - * ./mill -i managed.test splotly.PlotSpec.test1 + * ./mill -i managed.test managed.ExampleSpec.* + * ./mill -i managed.test managed.ExampleSpec.test1 * * Extending `TestCase` to get access to `setUp` * @@ -18,9 +18,15 @@ package managed * * */ -class PlotSpec extends munit.FunSuite { +class ExampleSpec extends munit.FunSuite { - test("hello") { + test("test_ok") { + val obtained = 42 + val expected = 42 + assertEquals(obtained, expected) + } + + test("test_fails") { val obtained = 42 val expected = 43 assertEquals(obtained, expected) diff --git a/build.sc b/build.sc index b169f41..8f6da6c 100644 --- a/build.sc +++ b/build.sc @@ -1,23 +1,39 @@ // cSpell:ignore scalalib, helloworld, coursier, Deps, unmanaged, classpath -// cSpell:ignore javafx, controlsfx, openjfx +// cSpell:ignore javafx, controlsfx, openjfx, Dprism -import coursier.core.Resolution +import coursier.{Dependency, Module, ModuleName, Resolve} import mill._ import mill.api.Loose import mill.define.{Target, Task} import scalalib._ -val ScalaVersion = "3.1.0" +import java.io.File +import scala.concurrent.{Await, ExecutionContext} +import scala.concurrent.duration.Duration +import coursier.cache.{Cache, FileCache} +import coursier.core.Resolution +import coursier.error.CoursierError +import coursier.internal.FetchCache +import coursier.params.{Mirror, ResolutionParams} +import coursier.util.{Artifact, Sync, Task => CTask} +import coursier.util.Monad.ops._ + +import scala.concurrent.duration.Duration +import scala.concurrent.{Await, ExecutionContext, Future} + +val ScalaVersion = "3.1.1" //val javaFXVersion = "11.0.2" //val javaFXVersion = "12" //val javaFXVersion = "13.0.2" val javaFXVersion = "16" -val mUnitVersion = "0.7.27" +val mUnitVersion = "1.0.0-M3" //"0.7.27" val controlsFXVersion = "11.1.0" val hanSoloChartsVersion = "16.0.12" +val ivyMunit = ivy"org.scalameta::munit::$mUnitVersion" +val ivyMunitInterface = "munit.Framework" /** * When working with JavaFX/OpenFX in JDK 1.9 and later, the libraries are @@ -61,14 +77,14 @@ trait OpenJFX extends JavaModule { val controlsFXModule = "org.controlsfx.controls" // Module libraries - val BASE = s"org.openjfx:javafx-$BASE_:$javaFXVersion" - val CONTROLS = s"org.openjfx:javafx-$CONTROLS_:$javaFXVersion" - val FXML = s"org.openjfx:javafx-$FXML_:$javaFXVersion" - val GRAPHICS = s"org.openjfx:javafx-$GRAPHICS_:$javaFXVersion" - val MEDIA = s"org.openjfx:javafx-$MEDIA_:$javaFXVersion" - val SWING = s"org.openjfx:javafx-$SWING_:$javaFXVersion" - val WEB = s"org.openjfx:javafx-$WEB_:$javaFXVersion" - val CONTROLSFX = s"org.controlsfx:$CONTROLSFX_:$controlsFXVersion" + val BASE = s"org.openjfx:javafx-${BASE_}:$javaFXVersion" + val CONTROLS = s"org.openjfx:javafx-${CONTROLS_}:$javaFXVersion" + val FXML = s"org.openjfx:javafx-${FXML_}:$javaFXVersion" + val GRAPHICS = s"org.openjfx:javafx-${GRAPHICS_}:$javaFXVersion" + val MEDIA = s"org.openjfx:javafx-${MEDIA_}:$javaFXVersion" + val SWING = s"org.openjfx:javafx-${SWING_}:$javaFXVersion" + val WEB = s"org.openjfx:javafx-${WEB_}:$javaFXVersion" + val CONTROLSFX = s"org.controlsfx:${CONTROLSFX_}:$controlsFXVersion" // OpenFX/JavaFX libraries val javaFXModuleNames = Seq(BASE_, CONTROLS_, FXML_, GRAPHICS_, MEDIA_, SWING_, WEB_) @@ -91,9 +107,28 @@ trait OpenJFX extends JavaModule { println(modules) */ - // TODO: after version 0.10.0 iof Mill put test in the managed/unmanaged classes - val ivyMunit = ivy"org.scalameta::munit::$mUnitVersion" - val ivyMunitInterface = "munit.Framework" + // The osAll module downloads these OS native versions of the libraries + val supported = Set("mac", "linux", "win") + // Get the name of the current (host) OS + val osName = coursier.core.Activation.Os.fromProperties(sys.props.toMap).name.get.toLowerCase + // Filter for removing incompatible native OS libraries + // If we have several native libraries for different OS, JavaFX cannot select the correct one + val tag = osName match { + case "linux" => "linux" + case "mac os x" => "mac" + case "windows" => "win" + } + val remove = supported - tag + + def validOS(artifact: String): Boolean = { + if (supported.exists(s => artifact.contains(s))) { + // Not a native OS Jar for this OS + artifact.contains( tag ) + } else { + // Not a native OS Jar + true + } + } /** * In order to use OS specific libraries (such as JavaFX or OpenJFX), we @@ -103,9 +138,11 @@ trait OpenJFX extends JavaModule { * @see https://github.com/com-lihaoyi/mill/pull/775 (commit ab4d61a) * @return OS specific resolution mapping */ - override def resolutionCustomizer: Task[Option[Resolution => Resolution]] = T.task { - Some((_: coursier.core.Resolution).withOsInfo(coursier.core.Activation.Os.fromProperties(sys.props.toMap))) - } + override def resolutionCustomizer: Task[Option[Resolution => Resolution]] = T.task { + Some((_: coursier.core.Resolution).withOsInfo(coursier.core.Activation.Os.fromProperties(sys.props.toMap))) + } + + val pathSeparator= File.pathSeparator /** * Here we setup the Java modules so that they can be loaded prior to @@ -144,31 +181,33 @@ trait OpenJFX extends JavaModule { Seq(controlsFXModule) // no standard convention, so add it manually // Add to the modules list - Seq( - "--module-path", s.iterator.mkString(":"), - "--add-modules", modulesNames.iterator.mkString(","), + val t = Seq( + "--module-path", s.filter( validOS ).iterator.mkString( pathSeparator ), + "--add-modules", modulesNames.iterator.mkString(","), // "javafx.controls,javafx.graphics,javafx.base,org.controlsfx.controls", "--add-exports=javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls", "--add-exports=javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls", "--add-exports=javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls" ) ++ // add standard parameters Seq("-Dprism.verbose = true", "-ea") + t } - // TODO: after version 0.10.0 of Mill put test in the managed/unmanaged classes - object test extends Tests { + // // TODO: after version 0.10.0 of Mill put test in the managed/unmanaged classes + // object test extends Tests { - // TODO: after version 0.10.0 of Mill remove this - // sse https://github.com/com-lihaoyi/mill/issues/1406 - override def resolutionCustomizer: Task[Option[Resolution => Resolution]] = T.task { - Some((_: coursier.core.Resolution).withOsInfo(coursier.core.Activation.Os.fromProperties(sys.props.toMap))) - } + // // // TODO: after version 0.10.0 of Mill remove this + // // // sse https://github.com/com-lihaoyi/mill/issues/1406 + // // override def resolutionCustomizer: Task[Option[Resolution => Resolution]] = T.task { + // // Some((_: coursier.core.Resolution).withOsInfo(coursier.core.Activation.Os.fromProperties(sys.props.toMap))) + // // } - // https://github.com/com-lihaoyi/mill#097---2021-05-14 - //def testFrameworks = Seq(ivyMunitInterface) - def testFramework = ivyMunitInterface - } + // // https://github.com/com-lihaoyi/mill#097---2021-05-14 + // //def testFrameworks = Seq(ivyMunitInterface) + // def ivyDeps = Agg(ivyMunit) + // def testFramework = ivyMunitInterface + // } } @@ -201,7 +240,10 @@ object managed extends OpenJFX with ScalaModule { //ivy"${modules(CONTROLSFX_)}", ) - + object test extends Tests { + override def ivyDeps = Agg(ivyMunit) + override def testFramework = ivyMunitInterface + } } @@ -227,6 +269,8 @@ object managed extends OpenJFX with ScalaModule { * @see https://github.com/com-lihaoyi/mill/pull/775#issuecomment-826091576 */ object unmanaged extends OpenJFX with ScalaModule { + + def scalaVersion = T{ ScalaVersion } override def mainClass: T[Option[String]] = Some("helloworld.HelloWorld") @@ -240,6 +284,9 @@ object unmanaged extends OpenJFX with ScalaModule { * Managed libraries can also be used by overriding `ivyDeps` * * @return List of path references to the libraries + * + * @see https://github.com/coursier/coursier/discussions/2401 + * @see https://github.com/com-lihaoyi/mill/discussions/1842 */ override def unmanagedClasspath: Target[Loose.Agg[PathRef]] = T{ import coursier._ @@ -251,20 +298,293 @@ object unmanaged extends OpenJFX with ScalaModule { val controlsFXModule = dep"org.controlsfx:controlsfx:11.1.0" // Generate the dependencies + val javaFXModuleNames = Seq( CONTROLS_ ) val javaFXModules = javaFXModuleNames.map( m => Dependency(Module(org"org.openjfx", ModuleName(s"javafx-$m")), javaFXVersion) ) ++ Seq(controlsFXModule) + // Check if the libraries exist and download if they don't - val files = Fetch().addDependencies(javaFXModules: _*).run() - // Return the list f libraries + val files = Fetch() + .addDependencies(javaFXModules: _*) + .addArtifactTypes(Type.all) + .run() + // Return the list of libraries + val pathRefs = files.map(f => PathRef(os.Path(f))) + Agg(pathRefs : _*) + } + + object test extends Tests { + override def ivyDeps = Agg(ivyMunit) + override def testFramework = ivyMunitInterface + } + +} + + + +/** + * This module show how one can download the native OS binaries for multiple + * operating systems. This allows one to create "fat" Jars so that the resulting + * application is cross platform. In other words it supports multiple platforms. + * Be aware that this significantly increases the application's size. + * Alternatively use something like Coursier to make your application available. + * + * @see https://get-coursier.io/ + * https://github.com/coursier/coursier + */ +object allOS extends OpenJFX with ScalaModule { + + + def scalaVersion = T{ ScalaVersion } + + override def mainClass: T[Option[String]] = Some("helloworld.HelloWorld") + + override def ivyDeps = Agg( ivy"$CONTROLS", ivy"$CONTROLSFX" ) + + import coursier._ + import coursier.core.{Activation, Resolution} + + /** + * Resolution parameter for macOS operating system + */ + val macOSx64 =Activation.Os( + Some("x86_64"), // same as amd64 or x86-64 + Set("mac", "unix"), + Some("mac os x"), + None + ) + + /** + * Resolution parameter for Windows operating system. + * Alternate form of defining this parameter: + * + * {{ + * val windowsOs = core.Activation.Os.fromProperties(Map( + * "os.name" -> "Windows 10", + * "os.arch" -> "amd64", + * "os.version" -> "10.0", + * "path.separator" -> ";" + * )) + * }} + * + */ + val winX64 =Activation.Os( + Some("x86_64"), + Set("windows"), + Some("windows"), + None + ) + + /** + * Resolution parameter for Windows operating system. + * Alternate form of defining this parameter: + * + * {{ + * val windowsOs = coursier.core.Activation.Os.fromProperties(Map( + * "os.name" -> "Linux", + * "os.arch" -> "amd64", + * "os.version" -> "4.9.125", + * "path.separator" -> ":" + * )) + * }} + * + * [[coursier.core.Activation.Os]] definition: + * OS( arch : Option[String], + * families : Set[String], + * name : Option[String], + * version : Option[String]) : + * + * Determining the full OS member values: + * + * {{ + * val current = coursier.core.Activation.Os.fromProperties(sys.props.toMap) + * }} + * + * Output example: + * Os(Some(amd64), HashSet(unix), Some(linux), Some(5.13.0-40-generic)) + */ + val linuxX64 =Activation.Os( + Some("x86_64"), + Set("unix"), + Some("linux"), + None + ) + + /** + * Setup the Ivy resolution for the Maven artifacts for windows. + * Here is an example for MacOS: + * + * {{ + * val resolveMac = Resolve() + * .noMirrors + * .withCache(cache) + * .withResolutionParams( + * ResolutionParams() + * .withOsInfo { + * Activation.Os( + * Some("x86_64"), + * Set("mac", "unix"), + * Some("mac os x"), + * Some("10.15.1") + * ) + * } + * .withJdkVersion("1.8.0_121") + * ) + * }} + * + * + */ + val resolveWin = Resolve() + .withResolutionParams( + ResolutionParams() + .withOsInfo { + winX64 + } + ) + + /** + * Setup the Ivy resolution for the Maven artifacts for MacOS. + */ + val resolveMac = Resolve() + .withResolutionParams( + ResolutionParams() + .withOsInfo { + macOSx64 + } + ) + + /** + * Setup the Ivy resolution for the Maven artifacts for Linux. + */ + val resolveLinux = Resolve() + .withResolutionParams( + ResolutionParams() + .withOsInfo { + linuxX64 + } + ) + + /** + * Here we manually download the modules' jars. No need to install them + * separately in the OS. This allows us to determine the paths to the + * libraries so they can be used later in the JVM parameters. Note that this + * is a Mill command that is cached, so it can be called repeatedly. + * + * Managed libraries can also be used by overriding `ivyDeps` + * + * @return List of path references to the libraries + * + * @see https://github.com/coursier/coursier/discussions/2401 + * @see https://github.com/com-lihaoyi/mill/discussions/1842 + */ + override def unmanagedClasspath: Target[Loose.Agg[PathRef]] = T{ + + import coursier.params.ResolutionParams + + // Get the name of the current (host) OS + //val props = sys.props.toMap + //val osName = props("os.name") + val osName = coursier.core.Activation.Os.fromProperties(sys.props.toMap).name.get + + // Extra OpenFX library + // Coursier: only a single String literal is allowed here, so cannot decouple version + //val controlsFXModuleName = s"org.controlsfx:controlsfx:$controlsFXVersion" + val controlsFXModule = dep"org.controlsfx:controlsfx:11.1.0" + + // Generate the dependencies + val javaFXModuleNames = Seq( CONTROLS_ ) + val javaFXModules = javaFXModuleNames.map( + m => Dependency(Module(org"org.openjfx", ModuleName(s"javafx-$m")), javaFXVersion) + ) ++ + Seq(controlsFXModule) + + // Setup resolution Windows downloads (if not current OS) + val filesWin = + if (osName != winX64.name.get) { + Fetch() + .addDependencies(javaFXModules: _*) + .withResolutionParams( ResolutionParams().withOsInfo{ winX64 }) + .addArtifactTypes(Type.all) + .run() + .toSet + } else Set[File]() + + // Setup resolution MacOS downloads (if not current OS) + val filesMac = + if (osName != macOSx64.name.get) { + Fetch() + .addDependencies(javaFXModules: _*) + .withResolutionParams(ResolutionParams().withOsInfo { macOSx64 }) + .addArtifactTypes(Type.all) + .run() + .toSet + } else Set[File]() + + // Setup resolution Linux downloads (if not current OS) + val filesLinux = + if (osName != linuxX64.name.get) { + Fetch() + .addDependencies(javaFXModules: _*) + .withResolutionParams(ResolutionParams().withOsInfo { linuxX64 }) + .addArtifactTypes(Type.all) + .run() + .toSet + } else Set[File]() + + val deps = resolvedRunIvyDeps().map(_.path.toIO).iterator.toSet + val allOS = filesWin ++ filesMac ++ filesLinux -- deps + val files = allOS.toSeq + + // Return the list of libraries val pathRefs = files.map(f => PathRef(os.Path(f))) Agg(pathRefs : _*) } + def osClasspath: Target[Seq[String]] = T{ + implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global + + // Extra OpenFX library + // Coursier: only a single String literal is allowed here, so cannot decouple version + //val controlsFXModuleName = s"org.controlsfx:controlsfx:$controlsFXVersion" + val controlsFXModule = dep"org.controlsfx:controlsfx:11.1.0" + + val current = coursier.core.Activation.Os.fromProperties(sys.props.toMap) + // Generate the dependencies + val javaFXModules = javaFXModuleNames.map( + m => Dependency(Module(org"org.openjfx", ModuleName(s"javafx-$m")), javaFXVersion) + ) ++ + Seq(controlsFXModule) + + val deps = javaFXModules + val resWin: Future[Resolution] = + resolveWin + .addDependencies(deps: _*) + .future() + val resMac: Future[Resolution] = + resolveMac + .addDependencies(deps: _*) + .future() + val resLinux: Future[Resolution] = + resolveLinux + .addDependencies(deps: _*) + .future() + val res = Future.sequence( List(resWin, resMac, resLinux) ) + val result = Await.result(res, Duration.Inf) + val urls = result.map(_.dependencyArtifacts().map(_._3.url).toSet).reduceLeft((acc,s) => acc ++ s) + + s"Current = $current" :: urls.toList + } + + object test extends Tests { + override def ivyDeps = Agg(ivyMunit) + override def testFramework = ivyMunitInterface + } + } + /** * Same as the `managed`target, but here we just use Java. */ diff --git a/docs/historical_notes.md b/docs/historical_notes.md new file mode 100644 index 0000000..28efd93 --- /dev/null +++ b/docs/historical_notes.md @@ -0,0 +1,101 @@ + +# Historical notes: Issue with using JavaFX libraries + +After version `0.5.3` attempting to run a JavaFX application breaks. +See: + +* Issue [767](https://github.com/lihaoyi/mill/issues/767) +* Issue [775](https://github.com/lihaoyi/mill/pull/775) +* Issue [759](https://github.com/lihaoyi/mill/issues/759) +* Issue [1947](https://github.com/coursier/coursier/issues/1947) +* Issue [1928](https://github.com/lihaoyi/mill/issues/928) + +Discussion on a workaround is [here](https://github.com/lihaoyi/mill/discussions/1105). Unfortunately, this also did not work. It seems like it is not only the artifact downloads that may be causing problems. More specifically in version `0.5.3` _we cannot run a class that is **not** marked as a main class_. + +## Launching Mill with the OpenFX modules + +### Version 0.5.3 + +These execute correctly: + * `./mill -i javafx.run` + * `./mill -i javafx.runMain helloworld.HelloWorld` + * `./mill -i --watch javafx.run` + +This fails: + `` + +with the error: + +``` +Error: JavaFX runtime components are missing, and are required to run this application +``` + +Previous versions also work. + +###Version 0.9.4 + +Mill fails to resolve the dependencies: + +```bash +./mill -i javafx.run +[17/31] javafx.resolvedIvyDeps +1 targets failed +javafx.resolvedIvyDeps Failed to load source dependencies +not found: https://repo1.maven.org/maven2/org/openjfx/javafx-controls/13.0.2/javafx-controls-13.0.2-${javafx.platform}.jar +not found: https://repo1.maven.org/maven2/org/openjfx/javafx-graphics/13.0.2/javafx-graphics-13.0.2-${javafx.platform}.jar +not found: https://repo1.maven.org/maven2/org/openjfx/javafx-base/13.0.2/javafx-base-13.0.2-${javafx.platform}.jar +``` + +Mill fails to resolve the dependencies: + +```bash +./mill -i javafx.runMain helloworld.HelloWorld +[16/28] javafx.resolvedIvyDeps +1 targets failed +javafx.resolvedIvyDeps Failed to load source dependencies + not found: https://repo1.maven.org/maven2/org/openjfx/javafx-controls/13.0.2/javafx-controls-13.0.2-${javafx.platform}.jar + not found: https://repo1.maven.org/maven2/org/openjfx/javafx-graphics/13.0.2/javafx-graphics-13.0.2-${javafx.platform}.jar + not found: https://repo1.maven.org/maven2/org/openjfx/javafx-base/13.0.2/javafx-base-13.0.2-${javafx.platform}.jar + +``` + +## Adding the classifier + +We added a classifier automatically. In the working Mill versions we see that it makes no difference to the downloaded artifacts. + +## Java Installation + +### JDK Installation + +Tested on JDK 11. Because the JDK does not include the JavaFX run-time, this has to be installed. For Ubuntu, we need to: + +* sudo apt install openjfx +* dpkg-query -L openjfx +* java --module-path /usr/share/openjfx/lib --add-modules=javafx.controls,javafx.fxml,javafx.base,javafx.media,javafx.web,javafx.swing -jar '/home/lotfi/Documents/MyAppfolder/my_application.jar' + +The `dpkg-query` provides us with the path to the OpenJFX run-time. This path and the required modules must be added to the `java` comma nd line arguments. + +### JavaFX Installation + +You can install and use the system wide JavaFX. But we need to use and indicate the module path. + +# Note on configuring Java modules + +In Ubuntu linux + +```basch +user@machine:~$ update-alternatives --list java +/usr/lib/jvm/java-11-openjdk-amd64/bin/java +/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java +/usr/lib/jvm/java-8-oracle/jre/bin/java +``` + +The following gets us the current active path: + +```bash +user@machine:~$readlink -f /usr/bin/java +/usr/lib/jvm/java-11-openjdk-amd64/bin/java +``` + +Look at [Gradle JavaFX plugin](https://github.com/openjfx/javafx-gradle-plugin). It provides the module paths. + diff --git a/unmanaged/test/src/PlotSpec.scala b/managed/test/src/ExampleSpec.scala similarity index 64% rename from unmanaged/test/src/PlotSpec.scala rename to managed/test/src/ExampleSpec.scala index 03eaebd..486a7ca 100644 --- a/unmanaged/test/src/PlotSpec.scala +++ b/managed/test/src/ExampleSpec.scala @@ -8,8 +8,8 @@ package managed * * ./mill -i managed.test * ./mill -i managed.test.testLocal - * ./mill -i managed.test splotly.PlotSpec.* - * ./mill -i managed.test splotly.PlotSpec.test1 + * ./mill -i managed.test managed.ExampleSpec.* + * ./mill -i managed.test managed.ExampleSpec.test1 * * Extending `TestCase` to get access to `setUp` * @@ -18,9 +18,15 @@ package managed * * */ -class PlotSpec extends munit.FunSuite { +class ExampleSpec extends munit.FunSuite { - test("hello") { + test("test_ok") { + val obtained = 42 + val expected = 42 + assertEquals(obtained, expected) + } + + test("test_fails") { val obtained = 42 val expected = 43 assertEquals(obtained, expected) diff --git a/unmanaged/test/src/ExampleSpec.scala b/unmanaged/test/src/ExampleSpec.scala new file mode 100644 index 0000000..486a7ca --- /dev/null +++ b/unmanaged/test/src/ExampleSpec.scala @@ -0,0 +1,37 @@ +/* cspell: disable-next-line */ +package managed + +// cSpell:ignore splotly, munit + +/** + * ./mill mill.scalalib.GenIdea/idea + * + * ./mill -i managed.test + * ./mill -i managed.test.testLocal + * ./mill -i managed.test managed.ExampleSpec.* + * ./mill -i managed.test managed.ExampleSpec.test1 + * + * Extending `TestCase` to get access to `setUp` + * + * https://github.com/sbt/junit-interface#junit-interface + * ./mill -i managed.test --tests=test1 Only matches test names (not classes) + * + * + */ +class ExampleSpec extends munit.FunSuite { + + test("test_ok") { + val obtained = 42 + val expected = 42 + assertEquals(obtained, expected) + } + + test("test_fails") { + val obtained = 42 + val expected = 43 + assertEquals(obtained, expected) + } + +} + +