├── ide-plugins ├── wemi ├── build │ ├── version.kt │ └── ide.kt └── intellij │ ├── src │ └── main │ │ ├── resources │ │ └── icons │ │ │ ├── action_icon.png │ │ │ ├── script_icon.png │ │ │ ├── action_icon@2x.png │ │ │ ├── launcher_icon.png │ │ │ ├── script_icon2x.png │ │ │ ├── launcher_icon@2x.png │ │ │ ├── script_icon_dark.png │ │ │ ├── launcher_icon_dark.png │ │ │ ├── script_icon@2x_dark.png │ │ │ └── launcher_icon@2x_dark.png │ │ └── kotlin │ │ ├── icons │ │ └── WemiIcons.kt │ │ └── com │ │ ├── darkyen │ │ └── wemi │ │ │ └── intellij │ │ │ ├── options │ │ │ ├── BeforeRunOptions.kt │ │ │ ├── RunOptions.kt │ │ │ ├── RunConfigOptions.kt │ │ │ ├── ProjectImportOptions.kt │ │ │ └── WemiLauncherOptions.kt │ │ │ ├── Notifications.kt │ │ │ ├── util │ │ │ ├── OSProcessHandlerForWemi.kt │ │ │ ├── SessionActivityTracker.kt │ │ │ ├── Version.kt │ │ │ ├── Streams.kt │ │ │ ├── Sdks.kt │ │ │ ├── Failable.kt │ │ │ └── SessionState.kt │ │ │ ├── file │ │ │ ├── WemiLauncherFileType.kt │ │ │ ├── ScriptIconProvider.kt │ │ │ └── Util.kt │ │ │ ├── execution │ │ │ ├── WemiTaskConfigurationEditor.kt │ │ │ ├── WemiTaskConfigurationType.kt │ │ │ ├── WemiTaskConfigurationProducer.kt │ │ │ ├── WemiModuleBuildTaskRunner.kt │ │ │ └── WemiProgramRunner.kt │ │ │ ├── projectImport │ │ │ ├── ImportFromWemiProvider.kt │ │ │ ├── ConfigureWemiProjectStep.kt │ │ │ ├── ImportFromWemiControl.kt │ │ │ └── ImportFromWemiBuilder.kt │ │ │ ├── settings │ │ │ ├── WemiModuleService.kt │ │ │ ├── WemiProjectServiceConfigurable.kt │ │ │ └── WemiProjectService.kt │ │ │ └── importing │ │ │ └── actions │ │ │ ├── ImportProjectAction.kt │ │ │ ├── ReloadProjectAction.kt │ │ │ └── InstallWemiLauncherAction.kt │ │ └── esotericsoftware │ │ └── tablelayout │ │ ├── swing │ │ ├── Stack.java │ │ ├── TableLayout.java │ │ └── SwingToolkit.java │ │ └── Toolkit.java │ └── LICENSE ├── test-repositories ├── java │ ├── wemi │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── MANIFEST.MF │ │ │ └── java │ │ │ └── hello │ │ │ └── HelloWemi.java │ └── build │ │ └── build.kt ├── web │ ├── wemi │ ├── src │ │ └── main │ │ │ └── kotlin │ │ │ └── hello │ │ │ └── HelloWemi.kt │ └── build │ │ └── build.kt ├── basics │ ├── wemi │ └── src │ │ ├── main │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── MANIFEST.MF │ │ ├── kotlin │ │ │ └── basics │ │ │ │ └── GreeterMain.kt │ │ └── java │ │ │ └── basics │ │ │ └── Greeter.java │ │ └── test │ │ └── kotlin │ │ └── basics │ │ └── GreeterTests.kt ├── evaluate │ ├── wemi │ └── errors │ │ └── src │ │ ├── BrokenKotlin.kt │ │ └── BrokenJava.java ├── hello │ ├── wemi │ ├── src │ │ └── main │ │ │ └── kotlin │ │ │ └── hello │ │ │ └── HelloWemi.kt │ └── build │ │ └── build.kt ├── libgdx_game │ ├── wemi │ ├── lwjgl2 │ │ └── src │ │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── MANIFEST.MF │ │ │ └── java │ │ │ └── game │ │ │ └── Main.java │ ├── lwjgl3 │ │ └── src │ │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── MANIFEST.MF │ │ │ └── java │ │ │ └── game │ │ │ └── Main.java │ ├── core │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── game │ │ │ └── Game.java │ └── build │ │ └── build.kt ├── intellij_plugin │ ├── wemi │ ├── src │ │ ├── main │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── darkyen │ │ │ │ └── example │ │ │ │ ├── DemoService.java │ │ │ │ ├── DemoComponent.java │ │ │ │ └── ExampleAction.java │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── darkyen │ │ │ │ └── example │ │ │ │ └── ComponentServiceTest.java │ │ └── plugin.xml │ └── build │ │ └── build.kt └── launch-dev.sh ├── src ├── main │ ├── resources │ │ └── META-INF │ │ │ └── MANIFEST.MF │ └── kotlin │ │ ├── wemi │ │ ├── util │ │ │ ├── WithDescriptiveString.kt │ │ │ ├── StackTraceUtil.kt │ │ │ ├── Partial.kt │ │ │ ├── CycleChecker.kt │ │ │ ├── ScopedLocatedPath.kt │ │ │ ├── Version.kt │ │ │ ├── Magic.kt │ │ │ ├── Failable.kt │ │ │ ├── StackTraceUtil.java │ │ │ ├── JavaEnvironment.kt │ │ │ └── LocatedPath.kt │ │ ├── compile │ │ │ ├── JavaCompiler.kt │ │ │ └── CompilerFlag.kt │ │ ├── test │ │ │ ├── TestStatus.java │ │ │ ├── TestReport.java │ │ │ ├── forked │ │ │ │ └── ForkSerialization.java │ │ │ ├── TestData.java │ │ │ └── TestIdentifier.java │ │ ├── WithExitCode.kt │ │ ├── Commands.kt │ │ ├── assembly │ │ │ ├── MergeStrategy.kt │ │ │ ├── FileRecognition.kt │ │ │ └── AssemblySource.kt │ │ ├── collections │ │ │ └── WCollections.kt │ │ ├── plugin │ │ │ └── PluginEnvironment.kt │ │ ├── WemiException.kt │ │ ├── publish │ │ │ └── Publish.kt │ │ ├── dependency │ │ │ ├── ProjectDependency.kt │ │ │ ├── internal │ │ │ │ └── MavenOS.kt │ │ │ └── Repositories.kt │ │ └── run │ │ │ └── System.kt │ │ └── AutoImport.kt └── test │ ├── resources │ └── wemi │ │ └── boot │ │ └── directivesOk.txt │ └── kotlin │ └── wemi │ ├── util │ └── MagicTests.kt │ ├── boot │ ├── DirectiveTests.kt │ └── TaskParserTests.kt │ └── dependency │ └── internal │ └── MavenVersionRangeTest.kt ├── plugins ├── dokka │ └── src │ │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── wemi.plugin.PluginEnvironment │ │ └── kotlin │ │ └── wemiplugin │ │ └── dokka │ │ └── Options.kt ├── jvm-hotswap │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ └── META-INF │ │ │ │ │ ├── services │ │ │ │ │ └── wemi.plugin.PluginEnvironment │ │ │ │ │ └── MANIFEST.MF │ │ │ ├── java │ │ │ │ └── wemiplugin │ │ │ │ │ └── jvmhotswap │ │ │ │ │ └── agent │ │ │ │ │ └── package-info.java │ │ │ └── kotlin │ │ │ │ └── wemiplugin │ │ │ │ └── jvmhotswap │ │ │ │ ├── JvmHotswapPluginEnvironment.kt │ │ │ │ └── FileTreeUtil.kt │ │ └── test │ │ │ └── kotlin │ │ │ ├── dummypackage │ │ │ └── MyClass.java │ │ │ └── BytecodeUtilTests.kt │ └── README.md └── intellij │ └── src │ └── main │ └── kotlin │ └── wemiplugin │ └── intellij │ ├── instrumentation │ └── Instrumentation.java │ ├── PublishPlugin.kt │ ├── PatchPluginXML.kt │ ├── utils │ └── ExtraIntellijDependency.kt │ └── PrepareSandbox.kt ├── .gitignore ├── jitpack.yml └── docs ├── NOTES.md └── UPDATE_PROTOCOLS.md /ide-plugins/wemi: -------------------------------------------------------------------------------- 1 | ../wemi -------------------------------------------------------------------------------- /test-repositories/java/wemi: -------------------------------------------------------------------------------- 1 | ../launch-dev.sh -------------------------------------------------------------------------------- /test-repositories/web/wemi: -------------------------------------------------------------------------------- 1 | ../launch-dev.sh -------------------------------------------------------------------------------- /test-repositories/basics/wemi: -------------------------------------------------------------------------------- 1 | ../launch-dev.sh -------------------------------------------------------------------------------- /test-repositories/evaluate/wemi: -------------------------------------------------------------------------------- 1 | ../launch-dev.sh -------------------------------------------------------------------------------- /test-repositories/hello/wemi: -------------------------------------------------------------------------------- 1 | ../launch-dev.sh -------------------------------------------------------------------------------- /ide-plugins/build/version.kt: -------------------------------------------------------------------------------- 1 | ../../build/version.kt -------------------------------------------------------------------------------- /test-repositories/libgdx_game/wemi: -------------------------------------------------------------------------------- 1 | ../launch-dev.sh -------------------------------------------------------------------------------- /test-repositories/intellij_plugin/wemi: -------------------------------------------------------------------------------- 1 | ../launch-dev.sh -------------------------------------------------------------------------------- /src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: wemi.boot.Launch 3 | -------------------------------------------------------------------------------- /plugins/dokka/src/main/resources/META-INF/services/wemi.plugin.PluginEnvironment: -------------------------------------------------------------------------------- 1 | wemiplugin.dokka.DokkaPluginEnvironment -------------------------------------------------------------------------------- /test-repositories/java/src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: hello.HelloWemi 3 | -------------------------------------------------------------------------------- /test-repositories/basics/src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: basics.GreeterMainKt 3 | -------------------------------------------------------------------------------- /test-repositories/libgdx_game/lwjgl2/src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: game.Main 3 | -------------------------------------------------------------------------------- /test-repositories/libgdx_game/lwjgl3/src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: game.Main 3 | -------------------------------------------------------------------------------- /plugins/jvm-hotswap/src/main/resources/META-INF/services/wemi.plugin.PluginEnvironment: -------------------------------------------------------------------------------- 1 | wemiplugin.jvmhotswap.JvmHotswapPluginEnvironment -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Wemi supporting folders 3 | **/build/*/ 4 | **/build-generated/ 5 | 6 | # Wemi result jars 7 | **/build/*.jar 8 | 9 | # IDE 10 | /classes -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/resources/icons/action_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darkyenus/wemi/HEAD/ide-plugins/intellij/src/main/resources/icons/action_icon.png -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/resources/icons/script_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darkyenus/wemi/HEAD/ide-plugins/intellij/src/main/resources/icons/script_icon.png -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/resources/icons/action_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darkyenus/wemi/HEAD/ide-plugins/intellij/src/main/resources/icons/action_icon@2x.png -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/resources/icons/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darkyenus/wemi/HEAD/ide-plugins/intellij/src/main/resources/icons/launcher_icon.png -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/resources/icons/script_icon2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darkyenus/wemi/HEAD/ide-plugins/intellij/src/main/resources/icons/script_icon2x.png -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/resources/icons/launcher_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darkyenus/wemi/HEAD/ide-plugins/intellij/src/main/resources/icons/launcher_icon@2x.png -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/resources/icons/script_icon_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darkyenus/wemi/HEAD/ide-plugins/intellij/src/main/resources/icons/script_icon_dark.png -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/resources/icons/launcher_icon_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darkyenus/wemi/HEAD/ide-plugins/intellij/src/main/resources/icons/launcher_icon_dark.png -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/resources/icons/script_icon@2x_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darkyenus/wemi/HEAD/ide-plugins/intellij/src/main/resources/icons/script_icon@2x_dark.png -------------------------------------------------------------------------------- /test-repositories/web/src/main/kotlin/hello/HelloWemi.kt: -------------------------------------------------------------------------------- 1 | package hello 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | fun main(args: Array) { 7 | println("Hello Wemi!") 8 | } 9 | -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/resources/icons/launcher_icon@2x_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darkyenus/wemi/HEAD/ide-plugins/intellij/src/main/resources/icons/launcher_icon@2x_dark.png -------------------------------------------------------------------------------- /test-repositories/hello/src/main/kotlin/hello/HelloWemi.kt: -------------------------------------------------------------------------------- 1 | package hello 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | fun main(args: Array) { 7 | println("Hello Wemi!") 8 | } 9 | -------------------------------------------------------------------------------- /test-repositories/evaluate/errors/src/BrokenKotlin.kt: -------------------------------------------------------------------------------- 1 | 2 | class BrokenJava { 3 | fun thing() { 4 | //missingReference() // Blocks java compilation from proceeding, comment in/out to see it 5 | } 6 | } -------------------------------------------------------------------------------- /plugins/jvm-hotswap/src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Agent-Class: wemiplugin.jvmhotswap.agent.AgentMain 3 | Premain-Class: wemiplugin.jvmhotswap.agent.AgentMain 4 | Can-Redefine-Classes: true 5 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | # See https://jitpack.io/docs/BUILDING/ 2 | 3 | # Need to publish to a local directory first and then copy the results over to the ~/.m2/repo, 4 | # for Jitpack to detect it as artifacts. 5 | jdk: 6 | - oraclejdk8 7 | install: 8 | - ./wemi "wemi/jitpack:publish" 9 | -------------------------------------------------------------------------------- /test-repositories/web/build/build.kt: -------------------------------------------------------------------------------- 1 | import wemi.* 2 | import wemi.keys.* 3 | import wemi.archetypes.* 4 | 5 | val hello by project(KotlinJSProject) { 6 | 7 | projectGroup set { "com.darkyen" } 8 | projectName set { "web" } 9 | projectVersion set { "1.0-SNAPSHOT" } 10 | 11 | } -------------------------------------------------------------------------------- /plugins/jvm-hotswap/src/main/java/wemiplugin/jvmhotswap/agent/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes in this package will be injected as JVM agent into the process that will be hotswapped. 3 | * 4 | * Therefore, they MUST NOT depend on any other libraries, than what is available on vanilla JVM. 5 | */ 6 | package wemiplugin.jvmhotswap.agent; -------------------------------------------------------------------------------- /test-repositories/evaluate/errors/src/BrokenJava.java: -------------------------------------------------------------------------------- 1 | 2 | public class BrokenJava { 3 | public void error() { 4 | missingReference(); 5 | } 6 | 7 | @Deprecated 8 | public void deprecated() { 9 | java.util.List bla = new java.util.ArrayList(); 10 | } 11 | 12 | public void warning() { 13 | deprecated(); 14 | } 15 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/icons/WemiIcons.kt: -------------------------------------------------------------------------------- 1 | package icons 2 | 3 | import com.intellij.openapi.util.IconLoader 4 | 5 | object WemiIcons { 6 | @JvmField val ACTION = IconLoader.getIcon("/icons/action_icon.png") 7 | @JvmField val LAUNCHER = IconLoader.getIcon("/icons/launcher_icon.png") 8 | @JvmField val SCRIPT = IconLoader.getIcon("/icons/script_icon.png") 9 | } -------------------------------------------------------------------------------- /plugins/jvm-hotswap/src/main/kotlin/wemiplugin/jvmhotswap/JvmHotswapPluginEnvironment.kt: -------------------------------------------------------------------------------- 1 | package wemiplugin.jvmhotswap 2 | 3 | import wemi.plugin.PluginEnvironment 4 | 5 | /** 6 | * Initializes the plugin environment. 7 | */ 8 | class JvmHotswapPluginEnvironment : PluginEnvironment { 9 | 10 | override fun initialize() { 11 | JvmHotswap//Init 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /test-repositories/hello/build/build.kt: -------------------------------------------------------------------------------- 1 | import wemi.* 2 | import wemi.keys.* 3 | 4 | val hello by project { 5 | 6 | projectGroup set { "com.darkyen" } 7 | projectName set { "hello" } 8 | projectVersion set { "1.0-SNAPSHOT" } 9 | 10 | libraryDependencies add { dependency("com.google.guava:guava:25.1-jre") } 11 | 12 | mainClass set { "hello.HelloWemiKt" } 13 | } -------------------------------------------------------------------------------- /test-repositories/intellij_plugin/src/main/java/com/darkyen/example/DemoService.java: -------------------------------------------------------------------------------- 1 | package com.darkyen.example; 2 | 3 | public class DemoService { 4 | public DemoService() { 5 | System.out.println("Service " + getClass().getClassLoader().getClass().getSimpleName()); 6 | System.out.println("Loaded from " + getClass().getProtectionDomain().getCodeSource().getLocation()); 7 | } 8 | } -------------------------------------------------------------------------------- /docs/NOTES.md: -------------------------------------------------------------------------------- 1 | # Kotlin Standard Library 2 | - `kotlin-stdlib` base JVM standard library 3 | - Versions with `-jreN` suffix are deprecated, renamed to `-jdkN` 4 | - Versions with `-jdkN` are extensions of normal standard library, 5 | with transitive dependency on `-jdkN-1` or directly on `kotlin-stdlib` 6 | - `kotlin-stdlib-common` is an conjunction of `kotlin-stdlib` and `kotlin-stdlib-js`, for platform independent projects 7 | -------------------------------------------------------------------------------- /test-repositories/java/build/build.kt: -------------------------------------------------------------------------------- 1 | import wemi.archetypes.* 2 | import wemi.keys.* 3 | import wemi.* 4 | 5 | val hello by project(JavaProject) { 6 | 7 | projectGroup set { "com.darkyen" } 8 | projectName set { "hello-java" } 9 | projectVersion set { "1.0-SNAPSHOT" } 10 | 11 | mainClass set { "hello.HelloWemi" } 12 | 13 | libraryDependencies add { dependency("com.esotericsoftware:minlog:1.3.1") } 14 | } 15 | -------------------------------------------------------------------------------- /test-repositories/java/src/main/java/hello/HelloWemi.java: -------------------------------------------------------------------------------- 1 | package hello; 2 | 3 | import com.esotericsoftware.minlog.Log; 4 | 5 | /** 6 | * Class that greets the world from Wemi 7 | */ 8 | public class HelloWemi { 9 | 10 | /** 11 | * Main method, does the greeting. 12 | * @param args ignored 13 | */ 14 | public static void main(String[] args){ 15 | System.out.println("Hello from Wemi!"); 16 | Log.info("Hello from MinLog!"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/util/WithDescriptiveString.kt: -------------------------------------------------------------------------------- 1 | package wemi.util 2 | 3 | /** 4 | * Declares that this object has a different [Object.toString] implementation, 5 | * that is more suited for user and may contain ANSI terminal colors. 6 | */ 7 | interface WithDescriptiveString { 8 | /** 9 | * Return the human readable [String] representation of this object. 10 | * May contain ANSI colors. 11 | * 12 | * @see format 13 | */ 14 | fun toDescriptiveAnsiString(): String 15 | } -------------------------------------------------------------------------------- /test-repositories/intellij_plugin/src/main/java/com/darkyen/example/DemoComponent.java: -------------------------------------------------------------------------------- 1 | package com.darkyen.example; 2 | 3 | public class DemoComponent { 4 | private final DemoService service; 5 | 6 | public DemoComponent(DemoService service) { 7 | System.out.println("Component " + getClass().getClassLoader().getClass().getSimpleName()); 8 | System.out.println("Loaded from " + getClass().getProtectionDomain().getCodeSource().getLocation()); 9 | this.service = service; 10 | } 11 | 12 | public DemoService getService() { 13 | return service; 14 | } 15 | } -------------------------------------------------------------------------------- /test-repositories/intellij_plugin/src/test/java/com/darkyen/example/ComponentServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.darkyen.example; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.testFramework.fixtures.BasePlatformTestCase; 5 | 6 | public class ComponentServiceTest extends BasePlatformTestCase { 7 | 8 | public void testComponentService() { 9 | DemoComponent component = ApplicationManager.getApplication().getComponent(DemoComponent.class); 10 | assertInstanceOf(component.getService(), DemoService.class); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /test-repositories/libgdx_game/lwjgl2/src/main/java/game/Main.java: -------------------------------------------------------------------------------- 1 | package game; 2 | 3 | import com.badlogic.gdx.backends.lwjgl.LwjglApplication; 4 | import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; 5 | 6 | public class Main { 7 | 8 | public static void main(String[] args) { 9 | LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); 10 | config.width = 800; 11 | config.height = 600; 12 | config.vSyncEnabled = true; 13 | 14 | new LwjglApplication(new Game(), config); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/util/StackTraceUtil.kt: -------------------------------------------------------------------------------- 1 | package wemi.util 2 | 3 | /** 4 | * Append the given throwable to the [StringBuilder]. 5 | * 6 | * The throwable is printed with the same formatting as [Throwable.printStackTrace] has, 7 | * but the printed stack traces can be modified by the [stackMapper]. 8 | */ 9 | inline fun StringBuilder.appendWithStackTrace(throwable: Throwable, crossinline stackMapper: ((Array) -> List) = { it.toList() }) { 10 | StackTraceUtil.appendWithStackTrace(this, throwable) { 11 | stackMapper(it) 12 | } 13 | } -------------------------------------------------------------------------------- /test-repositories/libgdx_game/lwjgl3/src/main/java/game/Main.java: -------------------------------------------------------------------------------- 1 | package game; 2 | 3 | import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; 4 | import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; 5 | 6 | public class Main { 7 | 8 | public static void main(String[] args) { 9 | Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); 10 | config.setWindowedMode(800, 600); 11 | config.disableAudio(true); 12 | config.useVsync(true); 13 | 14 | new Lwjgl3Application(new Game(), config); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/options/BeforeRunOptions.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.options 2 | 3 | /** Options for Before Run configurations. */ 4 | class BeforeRunOptions : RunOptions() { 5 | 6 | fun copyTo(o: BeforeRunOptions) { 7 | copyTo(o as RunOptions) 8 | } 9 | 10 | override fun equals(other: Any?): Boolean { 11 | if (this === other) return true 12 | if (other !is BeforeRunOptions) return false 13 | if (!super.equals(other)) return false 14 | return true 15 | } 16 | 17 | @Suppress("RedundantOverride") 18 | override fun hashCode(): Int { 19 | return super.hashCode() 20 | } 21 | } -------------------------------------------------------------------------------- /test-repositories/launch-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Tests should symlink their `wemi` launchers to this file to run on development versions of Wemi 4 | 5 | wemi_root="$(dirname "$0")" 6 | wemi_home="$(dirname "$0")/../.." 7 | # It does not matter that it is a template - only version is templated, and we sidestep that 8 | wemi_launcher="${wemi_home}/src/launcher-template.sh" 9 | wemi_dist="${wemi_home}/build/cache/-distribution-archive" 10 | 11 | if [ ! -d "$wemi_dist" ]; then 12 | "${wemi_home}/wemi" 'distributionArchive' 13 | fi 14 | 15 | export WEMI_DIST="$wemi_dist" 16 | export WEMI_ROOT="$wemi_root" 17 | export WEMI_JAVA_OPTS="-ea" 18 | 19 | exec "$wemi_launcher" "$@" -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/Notifications.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij 2 | 3 | import com.intellij.notification.NotificationGroup 4 | import com.intellij.notification.NotificationListener 5 | import com.intellij.notification.NotificationType 6 | import com.intellij.openapi.project.Project 7 | 8 | /** 9 | * Notification group for the Wemi plugin 10 | */ 11 | val WemiNotificationGroup = NotificationGroup.balloonGroup("Wemi Notification Group") 12 | 13 | fun NotificationGroup.showBalloon(project:Project?, title:String, message: String, type:NotificationType, listener:NotificationListener? = null) { 14 | this.createNotification(title, message, type, listener).notify(project) 15 | } -------------------------------------------------------------------------------- /test-repositories/intellij_plugin/src/main/java/com/darkyen/example/ExampleAction.java: -------------------------------------------------------------------------------- 1 | package com.darkyen.example; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import org.jetbrains.annotations.NotNull; 6 | import javax.swing.JOptionPane; 7 | 8 | public class ExampleAction extends AnAction { 9 | 10 | @Override 11 | public void update(@NotNull AnActionEvent e) { 12 | e.getPresentation().setEnabledAndVisible(true); 13 | } 14 | 15 | @Override 16 | public void actionPerformed(@NotNull AnActionEvent e) { 17 | JOptionPane.showMessageDialog(null, "It works!\n"+e.getProject().getService(com.darkyen.TimeTrackerService.class).toString(), "Alert", JOptionPane.PLAIN_MESSAGE); 18 | } 19 | } -------------------------------------------------------------------------------- /plugins/jvm-hotswap/src/test/kotlin/dummypackage/MyClass.java: -------------------------------------------------------------------------------- 1 | package dummypackage; 2 | 3 | public class MyClass { 4 | 5 | // Dummy values to pollute constant pool 6 | int nine = 9; 7 | long many = 100000000000L; 8 | double almostZero = 0.1; 9 | String string = "WORKER"; 10 | 11 | public class MyInnerClass { 12 | double 𝓁𝒾𝓉𝓁ℯ = 31415926535897932384.6e-10; 13 | } 14 | 15 | /* 16 | * Last part of name contains UTF16 surrogate pairs, which are broken in JVM. 17 | * https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8079633 18 | * 19 | * Uncomment when fixed. 20 | */ 21 | public class ÓťhéřHíghłýŮñÏçộḓễ/*𝓒𝕃𝙰𝔰𝖲*/ { 22 | double 𝓛𝓸𝓽𝓼 = 31415926535897932384.6e10; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/compile/JavaCompiler.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("RemoveExplicitTypeArguments") 2 | 3 | package wemi.compile 4 | 5 | /** Extensions that a valid Java source file can have */ 6 | val JavaSourceFileExtensions = arrayOf("java") 7 | 8 | /** 9 | * Flags used by the Java compiler. 10 | */ 11 | object JavaCompilerFlags { 12 | val customFlags = CompilerFlag>("javacCustomFlags", "Custom flags to be parsed by the javac CLI", emptyList()) 13 | val sourceVersion = CompilerFlag("javacSourceVersion", "Version of the source files", "") 14 | val targetVersion = CompilerFlag("javacTargetVersion", "Version of the created class files", "") 15 | val encoding = CompilerFlag("encoding", "Charset encoding of source files", "") 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/wemi/boot/directivesOk.txt: -------------------------------------------------------------------------------- 1 | 2 | @file:BuildClasspathDependency("BCD") 3 | 4 | @file:BuildDependency("GOF") 5 | 6 | @file:BuildDependency("G", "N", "V") 7 | 8 | @file:BuildDependency(groupOrFull = "G", name="N",version="V") 9 | @file:BuildDependency(name="N", version="V", groupOrFull = "G") 10 | @file:BuildDependency(version="V", name="N", groupOrFull = "G") 11 | 12 | @file:BuildDependencyRepository("NAME", url= "\tCRAZY:U\\R\"L\t") 13 | 14 | @file:BuildDependencyPlugin("wemi-plugin-foo") 15 | @file:BuildDependencyPlugin("wemi-plugin-bar", "com.example.group") 16 | 17 | import wemi.Keys.runOptions 18 | import wemi.dependency.Repository 19 | import wemi.util.executable 20 | 21 | val variable = "abcd" 22 | 23 | val core by project(path("core")) { 24 | 25 | 26 | } -------------------------------------------------------------------------------- /test-repositories/intellij_plugin/build/build.kt: -------------------------------------------------------------------------------- 1 | @file:BuildDependencyPlugin("wemi-plugin-intellij") 2 | @file:BuildDependencyRepository("jcenter", "https://jcenter.bintray.com/") 3 | 4 | import wemi.* 5 | import wemi.keys.* 6 | import wemi.archetypes.* 7 | import wemi.util.* 8 | import wemiplugin.intellij.* 9 | 10 | val testPlugin by project(JavaProject, IntelliJPluginLayer) { 11 | 12 | projectGroup set { "com.darkyen" } 13 | projectName set { "test-plugin" } 14 | projectVersion set { "1.0-SNAPSHOT" } 15 | 16 | IntelliJ.intellijIdeDependency set { IntelliJIDE.External(version = "201.8743.12") } 17 | IntelliJ.intellijPluginXmlFiles add { LocatedPath(path("src/plugin.xml")) } 18 | IntelliJ.intellijPluginDependencies add { IntelliJPluginDependency.External("com.darkyen.darkyenustimetracker", "1.5.1") } 19 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/util/OSProcessHandlerForWemi.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.util 2 | 3 | import com.intellij.execution.process.OSProcessHandler 4 | import com.intellij.util.io.BaseDataReader 5 | import com.intellij.util.io.BaseOutputReader 6 | 7 | class OSProcessHandlerForWemi(process: Process, commandLine: String) : OSProcessHandler(process, commandLine, Charsets.UTF_8) { 8 | 9 | private object SaneOptions: BaseOutputReader.Options() { 10 | override fun splitToLines(): Boolean = false 11 | 12 | override fun sendIncompleteLines(): Boolean = true 13 | 14 | override fun policy(): BaseDataReader.SleepingPolicy = BaseDataReader.SleepingPolicy.BLOCKING 15 | 16 | override fun withSeparators(): Boolean = true 17 | } 18 | 19 | override fun readerOptions(): BaseOutputReader.Options = SaneOptions 20 | } -------------------------------------------------------------------------------- /plugins/intellij/src/main/kotlin/wemiplugin/intellij/instrumentation/Instrumentation.java: -------------------------------------------------------------------------------- 1 | package wemiplugin.intellij.instrumentation; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.nio.file.Path; 6 | import java.util.List; 7 | 8 | /** 9 | * Utility methods that do class instrumentation and generation: 10 | * - adding null checks to annotated methods 11 | * - compiling forms 12 | */ 13 | public interface Instrumentation { 14 | 15 | boolean instrument( 16 | // Compile classpath inspection 17 | @NotNull Path javaHome, @NotNull List compilationClasspath, 18 | // Not null instrumentation 19 | @NotNull List notNullClasses, @NotNull List notNullClassSkipPatterns, @NotNull String[] notNullAnnotations, 20 | // Form compilation 21 | @NotNull List formFiles, @NotNull Path classRoot); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /test-repositories/basics/src/main/kotlin/basics/GreeterMain.kt: -------------------------------------------------------------------------------- 1 | package basics 2 | 3 | /** 4 | * [java.util.Random] instance. Used from [basics.Greeter]! 5 | */ 6 | val Random = java.util.Random() 7 | 8 | /** 9 | * Main. Greets the user of this computer with random personalized greeting. 10 | */ 11 | fun main(args: Array) { 12 | println("Hello from compiled Wemi file!") 13 | 14 | println("Kotlin version is ${KotlinVersion.CURRENT}, Greeter version is ${Version.VERSION}, built at ${Version.BUILD_TIME}") 15 | println("The random number for today is: $RANDOM_NUMBER") 16 | println("Is the generated file generated? Answer is: ${generated.Generated.REALLY_GENERATED}") 17 | 18 | val greeter = Greeter("Hello {}!", "Hi {}", "{}, welcome!", "Ahoy, {}!") 19 | greeter.greet(System.getProperty("user.name")) 20 | 21 | println("Art for today is: ${greeter.artName}") 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/test/TestStatus.java: -------------------------------------------------------------------------------- 1 | package wemi.test; 2 | 3 | /** 4 | * Status of {@link TestData}. 5 | */ 6 | public enum TestStatus { 7 | /** 8 | * Test/collection execution was successful. 9 | * 10 | * Note that collection may be successful even if tests contained were not. 11 | */ 12 | SUCCESSFUL, 13 | /** 14 | * Aborted by the user, i.e. the test would be run, but has been stopped for some reason. 15 | * This does not indicate failure nor success. 16 | */ 17 | ABORTED, 18 | /** 19 | * Test has not been run because it was skipped for some reason. 20 | * @see TestData#skipReason 21 | */ 22 | SKIPPED, 23 | /** 24 | * Test has been run and failed. 25 | * @see TestData#stackTrace 26 | */ 27 | FAILED, 28 | /** 29 | * Test has not been run. 30 | * This is not a standard result and indicates a problem somewhere. 31 | */ 32 | NOT_RUN; 33 | 34 | static final TestStatus[] VALUES = values(); 35 | } 36 | -------------------------------------------------------------------------------- /test-repositories/basics/src/test/kotlin/basics/GreeterTests.kt: -------------------------------------------------------------------------------- 1 | package basics 2 | 3 | import org.junit.jupiter.api.Test 4 | import org.junit.jupiter.api.Assertions.* 5 | import org.junit.jupiter.api.Disabled 6 | import org.junit.jupiter.api.DisplayName 7 | import org.junit.jupiter.api.Assumptions 8 | 9 | class GreeterTests { 10 | 11 | @Test 12 | @DisplayName("Greeter should greet") 13 | fun testGreeting() { 14 | assertEquals("hello", Greeter("{}").createGreeting("hello")); 15 | } 16 | 17 | @Test 18 | fun pickyTest() { 19 | val time = System.currentTimeMillis() 20 | Assumptions.assumeTrue(time == 1522665955743) 21 | 22 | assertEquals(2, time % 3) 23 | } 24 | 25 | @Test 26 | @Disabled("Too stupid") 27 | fun stupidTest() { 28 | assertEquals(true, true) 29 | } 30 | 31 | @Test 32 | fun failingTest() { 33 | assertEquals("cat", "dog") 34 | } 35 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/file/WemiLauncherFileType.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.file 2 | 3 | import com.intellij.openapi.fileTypes.FileType 4 | import com.intellij.openapi.vfs.VirtualFile 5 | import icons.WemiIcons 6 | import javax.swing.Icon 7 | 8 | /** 9 | * File type for Wemi launcher executable jar. 10 | */ 11 | object WemiLauncherFileType : FileType { 12 | 13 | override fun getDefaultExtension(): String { 14 | return "" 15 | } 16 | 17 | override fun getIcon(): Icon? = WemiIcons.LAUNCHER 18 | 19 | override fun getCharset(file: VirtualFile, content: ByteArray): String? { 20 | return null 21 | } 22 | 23 | override fun getName(): String { 24 | return "Wemi Launcher" 25 | } 26 | 27 | override fun getDescription(): String { 28 | return "Wemi Launcher executable jar" 29 | } 30 | 31 | override fun isBinary(): Boolean = true 32 | 33 | override fun isReadOnly(): Boolean = true 34 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/util/Partial.kt: -------------------------------------------------------------------------------- 1 | package wemi.util 2 | 3 | import com.esotericsoftware.jsonbeans.JsonWriter 4 | 5 | /** 6 | * Value that exists, but may be incomplete. 7 | * Some consumers don't care when the value is incomplete and can work with them. Some don't. 8 | * 9 | * @param value that may be partial 10 | * @param complete true if the [value] is not partial 11 | */ 12 | class Partial(val value: T, val complete: Boolean) : JsonWritable { 13 | 14 | operator fun component1() = value 15 | operator fun component2() = complete 16 | 17 | inline fun map(map:(T) -> N):Partial { 18 | return Partial(map(value), complete) 19 | } 20 | 21 | override fun JsonWriter.write() { 22 | writeObject { 23 | field("complete", complete) 24 | name("value").writeValue(value, null) 25 | } 26 | } 27 | 28 | override fun toString(): String { 29 | return "$value (${if (complete) "complete" else "incomplete"})" 30 | } 31 | } -------------------------------------------------------------------------------- /docs/UPDATE_PROTOCOLS.md: -------------------------------------------------------------------------------- 1 | *Procedures to follow, when updating something.* 2 | 3 | # Wemi Version update 4 | 0. Update dependencies 5 | 1. Update changes in 6 | - [Changelog](../CHANGES.md) 7 | - [IDEA plugin changelog](../ide-plugins/intellij/src/main/plugin.xml) 8 | 2. Create and push commit named "Version 0.0" + version tag "0.0" 9 | 3. Run `./build/publish-version.sh` 10 | 4. Create github release, with binary builds of IDEA plugin and wemi itself 11 | 5. Update wemi used by Wemi, update build script 12 | 13 | # New Kotlin Version 14 | 1. Add it to [CompilerProjects in build script](../build/build.kt) 15 | 2. Create relevant [plugin interface project](../src/main-kotlinc) 16 | 3. Add it to [KotlinCompilerVersion enum](../src/main/kotlin/wemi/compile/KotlinCompiler.kt) 17 | 4. Update Dokka, if necessary 18 | 19 | # New Java Version 20 | - Check [BytecodeUtil](../plugins/jvm-hotswap/src/main/java/wemiplugin/jvmhotswap/agent/BytecodeUtil.java) 21 | and update `.class` parsing logic. 22 | - Update `javadocUrl` in [KeyDefaults](../src/main/kotlin/wemi/KeyDefaults.kt) 23 | -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/execution/WemiTaskConfigurationEditor.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.execution 2 | 3 | import com.darkyen.wemi.intellij.options.RunConfigOptions 4 | import com.darkyen.wemi.intellij.ui.PropertyEditorPanel 5 | import com.intellij.openapi.options.SettingsEditor 6 | import javax.swing.JComponent 7 | 8 | /** Editor for WemiTaskConfiguration */ 9 | class WemiTaskConfigurationEditor : SettingsEditor() { 10 | 11 | private val options = RunConfigOptions() 12 | private val propertyEditor = PropertyEditorPanel() 13 | 14 | init { 15 | options.createUi(propertyEditor) 16 | } 17 | 18 | override fun resetEditorFrom(s: WemiTaskConfiguration) { 19 | s.options.copyTo(options) 20 | propertyEditor.loadFromProperties() 21 | } 22 | 23 | override fun applyEditorTo(s: WemiTaskConfiguration) { 24 | propertyEditor.saveToProperties() 25 | options.copyTo(s.options) 26 | } 27 | 28 | override fun createEditor(): JComponent = propertyEditor 29 | } 30 | -------------------------------------------------------------------------------- /plugins/jvm-hotswap/src/test/kotlin/BytecodeUtilTests.kt: -------------------------------------------------------------------------------- 1 | import dummypackage.MyClass 2 | import org.junit.jupiter.api.Assertions.assertEquals 3 | import org.junit.jupiter.api.Test 4 | import wemiplugin.jvmhotswap.agent.BytecodeUtil 5 | 6 | /** 7 | * 8 | */ 9 | class BytecodeUtilTests { 10 | 11 | private fun testSingleClass(c:Class<*>) { 12 | val name = c.name 13 | val classNameStart = name.lastIndexOf('.') 14 | val resourceName = "${if (classNameStart == -1) name else name.substring(classNameStart + 1)}.class" 15 | val bytes = c.getResource(resourceName).readBytes() 16 | 17 | val className = BytecodeUtil.javaClassName(bytes) 18 | assertEquals(name, BytecodeUtil.bytecodeToNormalClassName(className)) 19 | } 20 | 21 | @Test 22 | fun javaClassNameTest() { 23 | testSingleClass(MyClass::class.java) 24 | testSingleClass(MyClass.MyInnerClass::class.java) 25 | testSingleClass(MyClass.ÓťhéřHíghłýŮñÏçộḓễ/*𝓒𝕃𝙰𝔰𝖲*/::class.java) 26 | testSingleClass(BytecodeUtilTests::class.java) 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/WithExitCode.kt: -------------------------------------------------------------------------------- 1 | package wemi 2 | 3 | /** 4 | * [Key]s may return objects that implement this interface. 5 | * Under certain circumstances, Wemi can take its process' exit code from that returned object. 6 | * 7 | * For example, [wemi.keys.test] uses this to exit with non-zero exit code when tests fail. 8 | * 9 | * Conditions for the [processExitCode] to be used: 10 | * 1. Task must be run non-interactively, supplied as process argument 11 | * 2. Task (that is, its key evaluation) must be the last task in the passed task list 12 | * 3. Wemi must be running in standard, i.e. not machine readable mode, or in shell-format machine readable mode 13 | */ 14 | interface WithExitCode { 15 | /** 16 | * Return the exit code that the Wemi process should end with, if it would depend on this instance. 17 | * 18 | * As per convention, 0 exit code is reserved for success and non-zero for failure. 19 | * See [wemi.boot.EXIT_CODE_SUCCESS], [wemi.boot.EXIT_CODE_TASK_FAILURE] and other predefined exit codes. 20 | */ 21 | fun processExitCode(): Int 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/Commands.kt: -------------------------------------------------------------------------------- 1 | package wemi.commands 2 | 3 | import wemi.Command 4 | import wemi.* 5 | import wemi.keys.mainClass 6 | import wemi.keys.testParameters 7 | import wemi.run.ExitCode 8 | import wemi.test.TestReport 9 | import wemi.test.prettyPrint 10 | 11 | /* [Command]s that are included with Wemi. */ 12 | 13 | val run: Command by command("Proxy for the run key, with ability to specify main class") { 14 | val mainClassName = read("main", "Main class to start", ClassNameValidator, ask = false) 15 | if (mainClassName != null) { 16 | mainClass put mainClassName 17 | } 18 | 19 | evaluate { wemi.keys.run.get() } 20 | } 21 | 22 | val test: Command by command("Proxy for test key with ability to specify class filter", prettyPrinter = { it, _ -> it.prettyPrint() }) { 23 | val classFilter = read("class", "Include classes, whose fully classified name match this regex", StringValidator, ask = false) 24 | if (classFilter != null) { 25 | testParameters modify { 26 | it.filterClassNamePatterns.included.add(classFilter) 27 | it 28 | } 29 | } 30 | 31 | evaluate { wemi.keys.test.get() } 32 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/file/ScriptIconProvider.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.file 2 | 3 | import com.intellij.ide.FileIconProvider 4 | import com.intellij.ide.IconProvider 5 | import com.intellij.openapi.project.DumbAware 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.openapi.vfs.VirtualFile 8 | import com.intellij.psi.PsiElement 9 | import com.intellij.psi.PsiFile 10 | import icons.WemiIcons 11 | import javax.swing.Icon 12 | 13 | /** 14 | * Provides script icons for Wemi build scripts 15 | */ 16 | class ScriptIconProvider : IconProvider(), FileIconProvider, DumbAware { 17 | 18 | override fun getIcon(element: PsiElement, flags: Int): Icon? { 19 | if ((element as? PsiFile)?.virtualFile?.isWemiScriptSource(true) == true) { 20 | return WemiIcons.SCRIPT 21 | } 22 | return null 23 | } 24 | 25 | override fun getIcon(file: VirtualFile, flags: Int, project: Project?): Icon? { 26 | if (file.isWemiScriptSource(true)) { 27 | return WemiIcons.SCRIPT 28 | } 29 | 30 | return null 31 | } 32 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/util/SessionActivityTracker.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.util 2 | 3 | import com.intellij.openapi.util.Key 4 | 5 | interface SessionActivityTracker { 6 | fun stageBegin(name:String) 7 | fun stageProgress(done:Int, outOf:Int) 8 | fun stageEnd() 9 | 10 | fun taskBegin(name:String) 11 | fun taskEnd(output:String?, success:Boolean) 12 | 13 | fun sessionOutput(text:String, outputType: Key<*>) 14 | } 15 | 16 | inline fun SessionActivityTracker.stage(name:String, action:()->T):T { 17 | stageBegin(name) 18 | try { 19 | return action() 20 | } finally { 21 | stageEnd() 22 | } 23 | } 24 | 25 | inline fun SessionActivityTracker.stagesFor(name:String, items:Collection, itemName:(T)->String, action:(T) -> Unit) { 26 | stage(name) { 27 | val total = items.size 28 | stageProgress(0, total) 29 | for ((index, item) in items.withIndex()) { 30 | stage(itemName(item)) { 31 | action(item) 32 | } 33 | stageProgress(index + 1, total) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ide-plugins/intellij/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jan Polák 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/kotlin/wemi/util/MagicTests.kt: -------------------------------------------------------------------------------- 1 | package wemi.util 2 | 3 | import org.junit.jupiter.api.Assertions.* 4 | import org.junit.jupiter.api.Test 5 | 6 | /** 7 | * Tests Magic 8 | */ 9 | class MagicTests { 10 | 11 | @Test 12 | fun classpathFileOfTest() { 13 | val myPath = Magic.classpathFileOf(this.javaClass) ?: fail("Path to $this should not be null") 14 | val innerClassPath = Magic.classpathFileOf(InnerClass::class.java) ?: fail("Path to ${InnerClass::class} should not be null") 15 | 16 | val randomLambda = {println("Do not invoke me!")} 17 | val lambdaPath = Magic.classpathFileOf(randomLambda.javaClass) ?: fail("Path to $randomLambda should not be null") 18 | 19 | assertEquals(myPath, innerClassPath) 20 | assertEquals(myPath, lambdaPath) 21 | 22 | if (myPath.isRegularFile()) { 23 | assertTrue(myPath.name.pathHasExtension("jar")) {"Path is not a jar file: $myPath"} 24 | } else if (myPath.isDirectory()) { 25 | assertTrue(myPath.resolve("wemi")?.isDirectory() == true) {"Path is not a classpath root file: $myPath"} 26 | } else { 27 | fail("My path is neither jar nor valid directory: $myPath") 28 | } 29 | } 30 | 31 | class InnerClass 32 | 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/assembly/MergeStrategy.kt: -------------------------------------------------------------------------------- 1 | package wemi.assembly 2 | 3 | /** 4 | * Strategies to use when merging files during assembly. 5 | */ 6 | enum class MergeStrategy { 7 | /** Only the first copy is kept */ 8 | First, 9 | /** Only the last copy is kept */ 10 | Last, 11 | /** Only own copy (the from [wemi.keys.internalClasspath] or aggregate) is used, others are discarded, error if multiple own */ 12 | SingleOwn, 13 | /** Only one copy is expected, error if multiple */ 14 | SingleOrError, 15 | /** Concatenate all copies byte by byte */ 16 | Concatenate, 17 | /** Line-aware [Concatenate]. Adds trailing newline, attempts to use present line endings, reverts to \n if ambiguous. 18 | * Data is assumed to be in UTF-8. */ 19 | Lines, 20 | /** Like [Lines], but duplicate lines are dropped */ 21 | UniqueLines, 22 | /** If more than one copy, discard all of them */ 23 | Discard, 24 | /** Only one value is expected, take the copy only if all other copies hold equal data */ 25 | Deduplicate, 26 | /** Move the file to different path, specified by [wemi.keys.assemblyRenameFunction] */ 27 | Rename, 28 | /** Like [Rename], but multiple files with equal content are allowed */ 29 | RenameDeduplicate 30 | } -------------------------------------------------------------------------------- /test-repositories/intellij_plugin/src/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.darkyen.example 3 | Example 4 | 5 | Example Vendor 6 | 7 | 8 | 9 | An example plugin that does pretty much nothing. 10 | 11 | Just some bold text! 13 | ]]> 14 | 15 | 16 | 17 | 18 | com.intellij.modules.platform 19 | com.darkyen.darkyenustimetracker 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | com.darkyen.example.DemoComponent 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/collections/WCollections.kt: -------------------------------------------------------------------------------- 1 | package wemi.collections 2 | 3 | /** 4 | * Ordered set that can be freely modified 5 | */ 6 | class WMutableSet : LinkedHashSet { 7 | constructor(initialCapacity: Int, loadFactor: Float) : super(initialCapacity, loadFactor) 8 | constructor(initialCapacity: Int) : super(initialCapacity) 9 | constructor() : super() 10 | constructor(c: Collection) : super(c) 11 | } 12 | 13 | /** 14 | * List that can be freely modified 15 | */ 16 | class WMutableList : ArrayList { 17 | constructor(initialCapacity: Int) : super(initialCapacity) 18 | constructor() : super() 19 | constructor(c: Collection) : super(c) 20 | } 21 | 22 | /** 23 | * Convert collection to one of WMutable* collections, unless it already is, then it returns itself. 24 | */ 25 | fun Collection.toMutable(): MutableCollection { 26 | return when (this) { 27 | is WMutableSet -> this 28 | is WMutableList -> this 29 | is Set -> WMutableSet(this) 30 | else -> WMutableList(this) 31 | } 32 | } 33 | 34 | /** 35 | * @see toMutable 36 | */ 37 | fun Set.toMutable(): WMutableSet { 38 | return this as? WMutableSet ?: WMutableSet(this) 39 | } 40 | 41 | /** 42 | * @see toMutable 43 | */ 44 | fun List.toMutable(): WMutableList { 45 | return this as? WMutableList ?: WMutableList(this) 46 | } -------------------------------------------------------------------------------- /src/main/kotlin/AutoImport.kt: -------------------------------------------------------------------------------- 1 | import wemi.ArchetypeDelegate 2 | import wemi.CommandDelegate 3 | import wemi.ConfigurationDelegate 4 | import wemi.KeyDelegate 5 | import wemi.ProjectDelegate 6 | 7 | /* 8 | * Types that should be visible in build scripts without any explicit imports 9 | */ 10 | 11 | // Types 12 | typealias Key = wemi.Key 13 | typealias Archetype = wemi.Archetype 14 | typealias Configuration = wemi.Configuration 15 | typealias Project = wemi.Project 16 | typealias Scope = wemi.Scope 17 | typealias EvalScope = wemi.EvalScope 18 | typealias Repository = wemi.dependency.Repository 19 | typealias DependencyId = wemi.dependency.DependencyId 20 | typealias Dependency = wemi.dependency.Dependency 21 | typealias DependencyExclusion = wemi.dependency.DependencyExclusion 22 | typealias LocatedPath = wemi.util.LocatedPath 23 | 24 | typealias project = ProjectDelegate 25 | typealias key = KeyDelegate 26 | typealias configuration = ConfigurationDelegate 27 | typealias archetype = ArchetypeDelegate 28 | typealias command = CommandDelegate 29 | 30 | typealias Path = java.nio.file.Path 31 | typealias Paths = java.nio.file.Paths 32 | typealias Files = java.nio.file.Files 33 | 34 | // Build script directive annotations 35 | typealias BuildDependency = wemi.boot.BuildDependency 36 | typealias BuildDependencyRepository = wemi.boot.BuildDependencyRepository 37 | typealias BuildClasspathDependency = wemi.boot.BuildClasspathDependency 38 | typealias BuildDependencyPlugin = wemi.boot.BuildDependencyPlugin 39 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/plugin/PluginEnvironment.kt: -------------------------------------------------------------------------------- 1 | package wemi.plugin 2 | 3 | /** 4 | * Interface to be implemented by a Wemi plugin, that allows it to inject its own settings into the environment 5 | * and perform other initialization. 6 | * 7 | * Uses [java.util.ServiceLoader], so when implementing, add fully classified name of the class into your 8 | * plugin's `META-INF/services/wemi.plugin.PluginEnvironment` file. Such classes must have no-arg constructor! 9 | */ 10 | interface PluginEnvironment { 11 | 12 | /** 13 | * Perform whatever global initialization this plugin needs. 14 | * This initialization MUST NOT be dependent on the system environment, such as OS, installed programs or SDKs, 15 | * internet connection, etc. 16 | * Initialization that may fail should be done in (optionally cached) [wemi.Key]-bound functions. 17 | * 18 | * For example, bind whatever keys are needed to plugin's normal function. 19 | * Do not modify bindings of keys that do not belong to this plugin! 20 | * 21 | * Use this ONLY for keys that are independently usable by all projects, that is, generic tasks and settings, 22 | * that must be invoked explicitly and don't change the characteristics of the project. 23 | * 24 | * Example: 25 | * ``` 26 | * fun initialize() { 27 | * ::Base.inject { 28 | * myKey set { "foo" } 29 | * } 30 | * } 31 | * ``` 32 | * 33 | * @see wemi.inject 34 | */ 35 | fun initialize() 36 | 37 | } -------------------------------------------------------------------------------- /plugins/intellij/src/main/kotlin/wemiplugin/intellij/PublishPlugin.kt: -------------------------------------------------------------------------------- 1 | package wemiplugin.intellij 2 | 3 | import org.jetbrains.intellij.pluginRepository.PluginRepositoryFactory 4 | import org.slf4j.LoggerFactory 5 | import wemi.Value 6 | import wemi.WemiException 7 | import wemiplugin.intellij.utils.Utils 8 | import java.nio.file.Path 9 | 10 | private val LOG = LoggerFactory.getLogger("PublishPlugin") 11 | 12 | val DefaultIntellijPublishPluginToRepository: Value = { 13 | val distributionFile: Path = IntelliJ.intellijPluginArchive.get() 14 | val host = IntelliJ.intellijPublishPluginRepository.get() 15 | val token: String = IntelliJ.intellijPublishPluginToken.get() 16 | val channels = IntelliJ.intellijPublishPluginChannels.get() 17 | 18 | if (channels.isEmpty()) { 19 | throw WemiException("Publish channels are empty", false) 20 | } 21 | 22 | val plugin = Utils.createPlugin(distributionFile, validatePluginXml = true, logProblems = true) 23 | ?: throw WemiException("Cannot upload plugin, archive is invalid", false) 24 | 25 | val pluginId = plugin.pluginId!! 26 | for (channel in channels) { 27 | LOG.info("Uploading plugin {} from {} to {}, channel: {}", pluginId, distributionFile, host, channel) 28 | try { 29 | val repoClient = PluginRepositoryFactory.create(host, token) 30 | repoClient.uploader.uploadPlugin(pluginId, distributionFile.toFile(), if (channel == "default") null else channel, null) 31 | LOG.info("Uploaded successfully") 32 | } catch (e: Exception) { 33 | throw WemiException("Failed to upload plugin", e) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /plugins/jvm-hotswap/README.md: -------------------------------------------------------------------------------- 1 | # Wemi JVM-Hotswap Plugin 2 | 3 | Add this to your Wemi build script to add `runHotswap` key, which works like ordinary `run` task, 4 | but watches for changes to sources and automatically recompiles when something changes and reloads the changes 5 | into the running process. 6 | 7 | ```kotlin 8 | // Add before imports 9 | @file:BuildDependencyRepository("jitpack", "https://jitpack.io/") 10 | @file:BuildDependency("com.darkyen.wemi", "wemi-plugin-jvm-hotswap", WemiVersion) 11 | ``` 12 | 13 | ## How it works 14 | - `wemiplugin.jvmhotswap.Keys.runHotswap` 15 | 1. Compiles what is needed (in configuration `running:hotswapping:`) 16 | 2. Launches the compiled project, like `run`, but with hotswap agent 17 | 3. Wemi communicates with the hotswap agent via network port defined in `hotswapAgentPort` (5015 by default) 18 | 4. Wemi continuously watches sources for changes, when changes are detected, the project is recompiled 19 | 5. If the recompilation is successful, agent is notified about changed classes and reloads them 20 | 21 | The hotswap capability depends on JVM API `java.lang.instrument.Instrumentation.redefineClasses` which has some limited 22 | capabilities, especially around changing class structure, but in general, changing bodies of methods should always work. 23 | 24 | Hotswap agent also sets Java property `wemi.hotswap.iteration` to `"0"` and increments it on each successful hotswap. 25 | You may watch this property in your application to detect hotswapping and re-run some initialization code, 26 | so that changes in it can manifest themselves. 27 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/util/CycleChecker.kt: -------------------------------------------------------------------------------- 1 | package wemi.util 2 | 3 | import java.util.* 4 | import kotlin.collections.ArrayList 5 | 6 | /** 7 | * Checks that the thread won't enter the same block twice with the same token. 8 | * 9 | * Basically a single-thread [kotlin.synchronized]. 10 | */ 11 | internal class CycleChecker { 12 | 13 | private val tokens: MutableMap> = Collections.synchronizedMap(HashMap()) 14 | 15 | internal fun enter(token: Token): List? { 16 | val thread = Thread.currentThread() 17 | val tokens = tokens.getOrPut(thread) { ArrayList() } 18 | val index = tokens.indexOf(token) 19 | if (index < 0) { 20 | tokens.add(token) 21 | return null 22 | } 23 | return tokens.subList(index, tokens.size) 24 | } 25 | 26 | internal fun leave() { 27 | val thread = Thread.currentThread() 28 | val tokenStack = tokens[thread] ?: throw IllegalStateException("Unbalanced enter-leave") 29 | tokenStack.removeAt(tokenStack.lastIndex) 30 | if (tokenStack.isEmpty()) { 31 | tokens.remove(thread) 32 | } 33 | } 34 | 35 | internal inline fun block(token: Token, failure: (List) -> Result, action: () -> Result): Result { 36 | val loop = enter(token) 37 | return if (loop == null) { 38 | try { 39 | action() 40 | } finally { 41 | leave() 42 | } 43 | } else { 44 | failure(loop) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/projectImport/ImportFromWemiProvider.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.projectImport 2 | 3 | import com.darkyen.wemi.intellij.wemiDirectoryToImport 4 | import com.intellij.ide.util.projectWizard.ModuleWizardStep 5 | import com.intellij.ide.util.projectWizard.WizardContext 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.openapi.vfs.VirtualFile 8 | import com.intellij.projectImport.ProjectImportProvider 9 | 10 | /** 11 | * Provides 'import from external model' functionality. 12 | */ 13 | class ImportFromWemiProvider : ProjectImportProvider(ImportFromWemiBuilder()) { 14 | 15 | override fun getId(): String = "com.darkyen.wemi.intellij.projectImport.WemiProjectImportProvider" 16 | 17 | override fun canCreateNewProject(): Boolean = true 18 | 19 | override fun canImportModule(): Boolean = false 20 | 21 | override fun canImport(fileOrDirectory: VirtualFile, project: Project?): Boolean { 22 | return wemiDirectoryToImport(fileOrDirectory) != null 23 | } 24 | 25 | override fun canImportFromFile(file: VirtualFile?): Boolean { 26 | return wemiDirectoryToImport(file ?: return false) != null 27 | } 28 | 29 | override fun getPathToBeImported(file: VirtualFile): String { 30 | return (wemiDirectoryToImport(file) ?: file).path 31 | } 32 | 33 | override fun createSteps(context: WizardContext?): Array? { 34 | return arrayOf(ConfigureWemiProjectStep(context!!)) 35 | } 36 | 37 | override fun getFileSample(): String = "WEMI project directory (wemi, build/*.kt)" 38 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/WemiException.kt: -------------------------------------------------------------------------------- 1 | package wemi 2 | 3 | /** 4 | * Exception thrown when Wemi does not like how its APIs are used. 5 | */ 6 | @Suppress("unused") 7 | open class WemiException : RuntimeException { 8 | 9 | /** 10 | * If stacktrace of this exception should be shown. 11 | * 12 | * Set to false if the stacktrace would only confuse the user and is not important to the problem resolution. 13 | */ 14 | val showStacktrace: Boolean 15 | 16 | constructor(message: String, showStacktrace: Boolean = true) : super(message) { 17 | this.showStacktrace = showStacktrace 18 | } 19 | 20 | constructor(message: String, cause: Throwable, showStacktrace: Boolean = true) : super(message, cause) { 21 | this.showStacktrace = showStacktrace 22 | } 23 | 24 | /** 25 | * Special version of the [WemiException], thrown when the [key] that is being evaluated is not set in [scope] 26 | * it is being evaluated in. 27 | * 28 | * @see EvalScope.get can throw this 29 | */ 30 | class KeyNotAssignedException(val key: Key<*>, val scope: Scope) : WemiException("'${key.name}' not assigned in $scope", showStacktrace = false) 31 | 32 | /** 33 | * Special version of the [WemiException], thrown typically by implementations of [wemi.keys.compile] to indicate 34 | * compilation error, caused by invalid source code. 35 | */ 36 | class CompilationException : WemiException { 37 | constructor(message:String) : super(message, showStacktrace = false) 38 | constructor(message: String, cause: Throwable) : super(message, cause, showStacktrace = false) 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/assembly/FileRecognition.kt: -------------------------------------------------------------------------------- 1 | package wemi.assembly 2 | 3 | import wemi.util.pathHasExtension 4 | 5 | /** 6 | * Contains utilities for recognizing file type classes, from the [wemi.keys.assembly] point of view. 7 | * 8 | * Based on https://github.com/sbt/sbt-assembly 9 | */ 10 | object FileRecognition { 11 | 12 | private val TextExtensions = listOf("txt", "md", "markdown", "html") 13 | 14 | /** 15 | * @return true if [name] appears to be a name of a readme file 16 | */ 17 | fun isReadme(name: String): Boolean { 18 | if (name.contains("readme", ignoreCase = true) || 19 | name.contains("about", ignoreCase = true)) { 20 | 21 | return name.pathHasExtension(TextExtensions) 22 | } 23 | return false 24 | } 25 | 26 | /** 27 | * @return true if [name] appears to be a name of a license file 28 | */ 29 | fun isLicenseFile(name: String): Boolean { 30 | if (name.contains("license", ignoreCase = true) || 31 | name.contains("licence", ignoreCase = true) || 32 | name.contains("notice", ignoreCase = true) || 33 | name.contains("copying", ignoreCase = true)) { 34 | 35 | return name.pathHasExtension(TextExtensions) 36 | } 37 | return false 38 | } 39 | 40 | /** 41 | * @return true if [name] appears to be a name of a system junk file that can be safely discarded 42 | */ 43 | fun isSystemJunkFile(name: String): Boolean { 44 | return name.equals(".DS_Store", ignoreCase = true) || 45 | name.equals("Thumbs.db", ignoreCase = true) 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/settings/WemiModuleService.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.settings 2 | 3 | import com.darkyen.wemi.intellij.util.StringXmlSerializer 4 | import com.darkyen.wemi.intellij.util.XmlSerializable 5 | import com.darkyen.wemi.intellij.util.enumXmlSerializer 6 | import com.intellij.openapi.components.Service 7 | import com.intellij.openapi.components.State 8 | import com.intellij.openapi.module.Module 9 | 10 | /** Modules with this component belong to Wemi. */ 11 | @Service 12 | @State(name = "wemi.WemiModuleService") 13 | class WemiModuleService : XmlSerializable() { 14 | 15 | /** Name of Wemi project corresponding to this module. */ 16 | var wemiProjectName:String = "" 17 | 18 | /** Type of Wemi module this represents. Null if this module is not a wemi module. */ 19 | var wemiModuleType: WemiModuleType = WemiModuleType.NON_WEMI_MODULE 20 | 21 | init { 22 | register(WemiModuleService::wemiProjectName, StringXmlSerializer::class) 23 | register(WemiModuleService::wemiModuleType, enumXmlSerializer()) 24 | } 25 | } 26 | 27 | enum class WemiModuleType { 28 | /** This module does not belong to Wemi. */ 29 | NON_WEMI_MODULE, 30 | /** Standard Wemi project module. */ 31 | PROJECT, 32 | /** Build script-holding module. 33 | * For example, it is not possible to execute tasks on these modules. */ 34 | BUILD_SCRIPT 35 | } 36 | 37 | fun Module.getWemiModuleType():WemiModuleType { 38 | return getService(WemiModuleService::class.java)?.wemiModuleType ?: WemiModuleType.NON_WEMI_MODULE 39 | } 40 | 41 | fun Module.isWemiModule():Boolean { 42 | return getWemiModuleType() != WemiModuleType.NON_WEMI_MODULE 43 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/options/RunOptions.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.options 2 | 3 | import com.darkyen.wemi.intellij.ui.PropertyEditorPanel 4 | import com.darkyen.wemi.intellij.ui.TaskListPropertyEditor 5 | import com.darkyen.wemi.intellij.util.ListOfStringArraysXmlSerializer 6 | 7 | /** General base for configurations that run tasks. */ 8 | abstract class RunOptions : WemiLauncherOptions() { 9 | 10 | var tasks:List> = emptyList() 11 | 12 | init { 13 | register(RunOptions::tasks, ListOfStringArraysXmlSerializer::class) 14 | } 15 | 16 | fun shortTaskSummary():String { 17 | val tasks = tasks 18 | return if (tasks.isEmpty()) { 19 | "No Wemi tasks" 20 | } else if (tasks.size == 1) { 21 | tasks[0].joinToString(" ") 22 | } else { 23 | "${tasks[0].joinToString(" ")}; (${tasks.size - 1} more)" 24 | } 25 | } 26 | 27 | fun copyTo(o: RunOptions) { 28 | o.tasks = tasks 29 | copyTo(o as WemiLauncherOptions) 30 | } 31 | 32 | override fun createUi(panel: PropertyEditorPanel) { 33 | panel.editRow(TaskListPropertyEditor(this::tasks)) 34 | panel.gap(5) 35 | super.createUi(panel) 36 | } 37 | 38 | override fun equals(other: Any?): Boolean { 39 | if (this === other) return true 40 | if (other !is RunOptions) return false 41 | if (!super.equals(other)) return false 42 | 43 | if (tasks != other.tasks) return false 44 | 45 | return true 46 | } 47 | 48 | override fun hashCode(): Int { 49 | var result = super.hashCode() 50 | result = 31 * result + tasks.hashCode() 51 | return result 52 | } 53 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/settings/WemiProjectServiceConfigurable.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.settings 2 | 3 | import com.darkyen.wemi.intellij.options.ProjectImportOptions 4 | import com.darkyen.wemi.intellij.ui.PropertyEditorPanel 5 | import com.intellij.openapi.options.SearchableConfigurable 6 | import com.intellij.openapi.project.DumbAware 7 | import com.intellij.openapi.project.Project 8 | import javax.swing.JComponent 9 | 10 | /** Allows to configure Wemi settings per project, presents the UI in settings. */ 11 | class WemiProjectServiceConfigurable(private val project: Project) : SearchableConfigurable, DumbAware { 12 | 13 | override fun getId() = "com.darkyen.wemi.configurable-settings" 14 | 15 | override fun getDisplayName(): String = "Wemi" 16 | 17 | private val effectiveProjectSettings:ProjectImportOptions? 18 | get() = project.getService(WemiProjectService::class.java)?.options 19 | 20 | private val editorProjectSettings = ProjectImportOptions().also { editOptions -> 21 | project.getService(WemiProjectService::class.java)?.updateWindowsShellExe() 22 | effectiveProjectSettings?.copyTo(editOptions) 23 | } 24 | 25 | private val editPanel = PropertyEditorPanel().also { panel -> 26 | editorProjectSettings.createUi(panel) 27 | panel.loadFromProperties() 28 | } 29 | 30 | override fun createComponent(): JComponent? = editPanel 31 | 32 | override fun isModified(): Boolean { 33 | editPanel.saveToProperties() 34 | return editorProjectSettings != effectiveProjectSettings 35 | } 36 | 37 | override fun apply() { 38 | editPanel.saveToProperties() 39 | editorProjectSettings.copyTo(effectiveProjectSettings ?: return) 40 | } 41 | } -------------------------------------------------------------------------------- /test-repositories/libgdx_game/core/src/main/java/game/Game.java: -------------------------------------------------------------------------------- 1 | package game; 2 | 3 | import com.badlogic.gdx.ApplicationAdapter; 4 | import com.badlogic.gdx.Gdx; 5 | import com.badlogic.gdx.graphics.GL20; 6 | import com.badlogic.gdx.graphics.PerspectiveCamera; 7 | import com.badlogic.gdx.graphics.glutils.ShapeRenderer; 8 | import com.badlogic.gdx.math.Vector2; 9 | import com.badlogic.gdx.utils.viewport.ScreenViewport; 10 | 11 | public class Game extends ApplicationAdapter { 12 | 13 | private ScreenViewport viewport; 14 | private ShapeRenderer shapeRenderer; 15 | 16 | @Override 17 | public void create() { 18 | viewport = new ScreenViewport(new PerspectiveCamera(90, Gdx.graphics.getWidth(), Gdx.graphics.getHeight())); 19 | shapeRenderer = new ShapeRenderer(); 20 | } 21 | 22 | @Override 23 | public void render() { 24 | Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); 25 | Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); 26 | 27 | shapeRenderer.setProjectionMatrix(viewport.getCamera().combined); 28 | viewport.apply(); 29 | final Vector2 mouse = new Vector2(Gdx.input.getX(), Gdx.input.getY()); 30 | viewport.unproject(mouse); 31 | 32 | shapeRenderer.setColor(mouse.x / Gdx.graphics.getWidth(), mouse.y / Gdx.graphics.getWidth(), 1.0f, 1.0f); 33 | shapeRenderer.begin(ShapeRenderer.ShapeType.Filled); 34 | shapeRenderer.box(mouse.x-10, mouse.y-10, -50, 20, 20, 80); 35 | shapeRenderer.end(); 36 | } 37 | 38 | @Override 39 | public void resize(int width, int height) { 40 | viewport.update(width, height, true); 41 | } 42 | 43 | @Override 44 | public void dispose() { 45 | shapeRenderer.dispose(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/util/Version.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.util 2 | 3 | /** 4 | * Wraps version string and provides comparison capability over it. 5 | * Newer versions are larger. 6 | * 7 | * Version string is divided into parts (separated by dots). 8 | * Each part has its numeric value taken (from any leading digits), otherwise it is treated as 0. 9 | * When comparing two versions, missing parts are treated as 0. 10 | */ 11 | class Version private constructor(private val parts:List) : Comparable { 12 | 13 | constructor(text:String):this(text.split('.').map { cell -> 14 | cell.takeWhile { it in '0'..'9' }.toIntOrNull() ?: 0 15 | }) 16 | 17 | override fun compareTo(other: Version): Int { 18 | for (cell in 0 until maxOf(parts.size, other.parts.size)) { 19 | val my = if (cell in parts.indices) parts[cell] else 0 20 | val others = if (cell in other.parts.indices) other.parts[cell] else 0 21 | val comparison = my.compareTo(others) 22 | if (comparison != 0) { 23 | return comparison 24 | } 25 | } 26 | return 0 27 | } 28 | 29 | override fun equals(other: Any?): Boolean { 30 | if (this === other) return true 31 | if (javaClass != other?.javaClass) return false 32 | 33 | other as Version 34 | 35 | if (parts != other.parts) return false 36 | 37 | return true 38 | } 39 | 40 | override fun hashCode(): Int { 41 | return parts.hashCode() 42 | } 43 | 44 | override fun toString(): String { 45 | return parts.toString() 46 | } 47 | 48 | companion object { 49 | val NONE = Version(emptyList()) 50 | 51 | val WEMI_0_14 = Version("0.14") 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/test/TestReport.java: -------------------------------------------------------------------------------- 1 | package wemi.test; 2 | 3 | import wemi.WithExitCode; 4 | import wemi.util.Json; 5 | 6 | import java.io.DataInputStream; 7 | import java.io.DataOutputStream; 8 | import java.io.IOException; 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * Returned by the test. 14 | * 15 | * In execution order contains {@link TestIdentifier}s that were run and {@link TestData} informing about their execution. 16 | * 17 | * Has no dependencies - pure Java. 18 | * (With the exception of serializer, which is not used in the forked process.) 19 | */ 20 | @SuppressWarnings("serial") 21 | @Json(serializer = TestReportSerializer.class) 22 | public final class TestReport extends LinkedHashMap implements WithExitCode { 23 | 24 | /** Returns {@link wemi.boot.Launch#EXIT_CODE_SUCCESS} when no tests failed, {@link wemi.boot.Launch#EXIT_CODE_TASK_FAILURE} otherwise. */ 25 | @Override 26 | public int processExitCode() { 27 | for (TestData value : values()) { 28 | if (value.status == TestStatus.FAILED) { 29 | return wemi.boot.Launch.EXIT_CODE_TASK_FAILURE; 30 | } 31 | } 32 | return wemi.boot.Launch.EXIT_CODE_SUCCESS; 33 | } 34 | 35 | public void writeTo(DataOutputStream out) throws IOException { 36 | out.writeInt(size()); 37 | for (Map.Entry entries : entrySet()) { 38 | entries.getKey().writeTo(out); 39 | entries.getValue().writeTo(out); 40 | } 41 | } 42 | 43 | public void readFrom(DataInputStream in) throws IOException { 44 | clear(); 45 | 46 | final int entryCount = in.readInt(); 47 | for (int i = 0; i < entryCount; i++) { 48 | final TestIdentifier identifier = TestIdentifier.readFrom(in); 49 | final TestData data = new TestData(); 50 | data.readFrom(in); 51 | put(identifier, data); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/assembly/AssemblySource.kt: -------------------------------------------------------------------------------- 1 | package wemi.assembly 2 | 3 | import java.nio.file.Path 4 | 5 | /** Used by [wemi.keys.assemblyMergeStrategy] and [wemi.keys.assemblyRenameFunction] as a representation of data source. 6 | * Most fields are internally cached. */ 7 | class AssemblySource private constructor( 8 | /** Debug name of the source */ 9 | private val name:String, 10 | /** File from which the data somehow originated, if any. 11 | * This is just a hint, for example for [wemi.keys.assemblyRenameFunction]. 12 | * File may even be a directory, archive, etc.*/ 13 | val sourceFile: Path?, 14 | /** Time of last modification. -1 if unknown */ 15 | val lastModifiedMs: Long, 16 | /** Is this from the [wemi.keys.internalClasspath] or aggregate? */ 17 | val own: Boolean) { 18 | 19 | constructor(name:String, sourceFile:Path?, lastModifiedMs:Long, own:Boolean, data:ByteArray) 20 | : this (name, sourceFile, lastModifiedMs, own) { 21 | this._data = data 22 | } 23 | 24 | constructor(name:String, sourceFile:Path?, lastModifiedMs:Long, own:Boolean, loadData:() -> ByteArray) 25 | : this (name, sourceFile, lastModifiedMs, own) { 26 | this.dataRetriever = loadData 27 | } 28 | 29 | private var _data:ByteArray? = null 30 | private var dataRetriever:(() -> ByteArray)? = null 31 | 32 | /** 33 | * Lazily loaded data of the source that should be included in the assembled archive 34 | */ 35 | val data: ByteArray 36 | get() { 37 | var result = _data 38 | if (result == null) { 39 | result = dataRetriever!!() 40 | _data = result 41 | dataRetriever = null 42 | } 43 | return result 44 | } 45 | 46 | override fun toString(): String = name 47 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/esotericsoftware/tablelayout/swing/Stack.java: -------------------------------------------------------------------------------- 1 | 2 | package com.esotericsoftware.tablelayout.swing; 3 | 4 | import java.awt.Component; 5 | import java.awt.Container; 6 | import java.awt.Dimension; 7 | import java.awt.Graphics; 8 | import java.awt.LayoutManager; 9 | 10 | import javax.swing.JPanel; 11 | 12 | public class Stack extends JPanel { 13 | public Stack () { 14 | super(new LayoutManager() { 15 | public void layoutContainer (Container parent) { 16 | int width = parent.getWidth(); 17 | int height = parent.getHeight(); 18 | for (int i = 0, n = parent.getComponentCount(); i < n; i++) { 19 | parent.getComponent(i).setLocation(0, 0); 20 | parent.getComponent(i).setSize(width, height); 21 | } 22 | } 23 | 24 | public Dimension preferredLayoutSize (Container parent) { 25 | Dimension size = new Dimension(); 26 | for (int i = 0, n = parent.getComponentCount(); i < n; i++) { 27 | Dimension pref = parent.getComponent(i).getPreferredSize(); 28 | size.width = Math.max(size.width, pref.width); 29 | size.height = Math.max(size.height, pref.height); 30 | } 31 | return size; 32 | } 33 | 34 | public Dimension minimumLayoutSize (Container parent) { 35 | Dimension size = new Dimension(); 36 | for (int i = 0, n = parent.getComponentCount(); i < n; i++) { 37 | Dimension min = parent.getComponent(i).getMinimumSize(); 38 | size.width = Math.max(size.width, min.width); 39 | size.height = Math.max(size.height, min.height); 40 | } 41 | return size; 42 | } 43 | 44 | public void addLayoutComponent (String name, Component comp) { 45 | } 46 | 47 | public void removeLayoutComponent (Component comp) { 48 | } 49 | }); 50 | } 51 | 52 | protected void paintChildren (Graphics g) { 53 | super.paintChildren(g); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ide-plugins/build/ide.kt: -------------------------------------------------------------------------------- 1 | @file:BuildDependencyPlugin("wemi-plugin-intellij") 2 | // TODO(jp): version.kt is currently added via symlink, add a Import directive to do it properly 3 | 4 | import wemi.boot.BuildDependencyPlugin 5 | import wemi.dependency.Jitpack 6 | import wemi.util.FileSet 7 | import wemi.util.plus 8 | import wemiplugin.intellij.IntelliJPluginLayer 9 | import wemiplugin.intellij.IntelliJIDE 10 | import wemiplugin.intellij.IntelliJPluginDependency 11 | import wemiplugin.intellij.IntelliJ.intellijIdeDependency 12 | import wemiplugin.intellij.IntelliJ.intelliJPluginXmlFiles 13 | import wemiplugin.intellij.IntelliJ.intellijPluginDependencies 14 | import wemiplugin.intellij.IntelliJ.resolvedIntellijPluginDependencies 15 | import wemiplugin.intellij.IntelliJ.intellijPluginArchive 16 | import wemi.key 17 | import wemi.compile.KotlinCompilerVersion 18 | 19 | val ideIntellij by project(path("intellij"), Archetypes.JavaKotlinProject, IntelliJPluginLayer) { 20 | 21 | projectGroup set { "com.darkyen.wemi.intellij" } 22 | projectName set { "Wemi" } 23 | projectVersion set { gatherProjectVersion() } 24 | 25 | repositories add { Jitpack } 26 | libraryDependencies add { dependency("com.github.EsotericSoftware", "jsonbeans", "0.9") } 27 | 28 | // IntelliJ already contains its own Kotlin stdlib 29 | Keys.automaticKotlinStdlib set { false } 30 | // Keep in sync with whatever is shipped with the IDE 31 | Keys.kotlinVersion set { KotlinCompilerVersion.Version1_3_72 } 32 | 33 | resources modify { 34 | // TODO(jp): Use system to get wemi/wemiLauncher 35 | val launcher = path("../build/dist/wemi") 36 | it + FileSet(launcher) 37 | } 38 | 39 | intellijPluginDependencies add { IntelliJPluginDependency.Bundled("com.intellij.java") } 40 | intellijPluginDependencies add { IntelliJPluginDependency.Bundled("org.jetbrains.kotlin") } 41 | 42 | intellijIdeDependency set { IntelliJIDE.External(version = "201.8743.12") } 43 | intelliJPluginXmlFiles add { LocatedPath(Keys.projectRoot.get() / "src/main/plugin.xml") } 44 | } -------------------------------------------------------------------------------- /src/test/kotlin/wemi/boot/DirectiveTests.kt: -------------------------------------------------------------------------------- 1 | package wemi.boot 2 | 3 | import org.junit.jupiter.api.Assertions 4 | import org.junit.jupiter.api.Assertions.* 5 | import org.junit.jupiter.api.Test 6 | import java.io.InputStreamReader 7 | import java.util.* 8 | 9 | /** 10 | * 11 | */ 12 | class DirectiveTests { 13 | 14 | @Test 15 | fun directiveParsingOk() { 16 | val expected = listOf( 17 | BuildClasspathDependency::class.java to arrayOf("BCD"), 18 | BuildDependency::class.java to arrayOf("GOF", "", ""), 19 | BuildDependency::class.java to arrayOf("G", "N", "V"), 20 | BuildDependency::class.java to arrayOf("G", "N", "V"), 21 | BuildDependency::class.java to arrayOf("G", "N", "V"), 22 | BuildDependency::class.java to arrayOf("G", "N", "V"), 23 | BuildDependencyRepository::class.java to arrayOf("NAME", "\tCRAZY:U\\R\"L\t"), 24 | BuildDependencyPlugin::class.java to arrayOf("wemi-plugin-foo", "com.darkyen.wemi"), 25 | BuildDependencyPlugin::class.java to arrayOf("wemi-plugin-bar", "com.example.group") 26 | ) 27 | val expectedIterator = expected.iterator() 28 | 29 | var directiveIndex = 0 30 | val result = InputStreamReader(DirectiveFields::class.java.getResourceAsStream("directivesOk.txt"), Charsets.UTF_8).use { 31 | parseFileDirectives(it.buffered(), SupportedDirectives) {annotation, arguments -> 32 | directiveIndex++ 33 | assertTrue(expectedIterator.hasNext()) 34 | val (eClass, eArguments) = expectedIterator.next() 35 | 36 | assertEquals(eClass, annotation) {"Directive $directiveIndex $eClass -> ${eArguments.contentToString()}"} 37 | assertArrayEquals(eArguments, arguments) {"Directive $directiveIndex $eClass -> ${eArguments.contentToString()}"} 38 | } 39 | } 40 | 41 | assertFalse(expectedIterator.hasNext()) 42 | assertTrue(result) 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/publish/Publish.kt: -------------------------------------------------------------------------------- 1 | package wemi.publish 2 | 3 | import wemi.EvalScope 4 | import wemi.dependency.Classifier 5 | import wemi.dependency.JavadocClassifier 6 | import wemi.dependency.SourcesClassifier 7 | import wemi.dependency.joinClassifiers 8 | import wemi.keys.archive 9 | import wemi.keys.archiveDocs 10 | import wemi.keys.archiveSources 11 | import wemi.keys.publishArtifacts 12 | import java.nio.file.Path 13 | 14 | /** 15 | * Artifact to [Classifier] pairing used in [publishArtifacts]. 16 | */ 17 | typealias ArtifactEntry = Pair 18 | 19 | /** 20 | * Collect artifact entries to be added to [publishArtifacts]. 21 | * 22 | * ### Example: 23 | * ``` 24 | * Keys.publishArtifacts addAll { using(buildingLegacy) { artifacts("legacy", true, true) } } 25 | * ``` 26 | * 27 | * @param classifier which should be used for the artifacts (it will be prefixed to source and documentation 28 | * classifiers, if any). May be [wemi.dependency.NoClassifier] for no prefix, however note that the default, classifier-less artifacts 29 | * may be already added by default by the [wemi.Archetype]. 30 | * @param includeSources true to package sources and add them to resulting artifacts under [SourcesClassifier]. 31 | * @param includeDocumentation true to package documentation and add it to resulting artifacts under [JavadocClassifier]. 32 | */ 33 | fun EvalScope.artifacts(classifier: Classifier, includeSources:Boolean = true, includeDocumentation:Boolean = true):List { 34 | val result = ArrayList(3) 35 | 36 | val artifact = archive.get() 37 | 38 | if (artifact != null) { 39 | result.add(artifact to classifier) 40 | } 41 | 42 | if (includeSources) { 43 | val sourceArtifact = archiveSources.get() 44 | result.add(sourceArtifact to joinClassifiers(classifier, SourcesClassifier)) 45 | } 46 | 47 | if (includeDocumentation) { 48 | val docsArtifact = archiveDocs.get() 49 | result.add(docsArtifact to joinClassifiers(classifier, JavadocClassifier)) 50 | } 51 | 52 | return result 53 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/projectImport/ConfigureWemiProjectStep.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.projectImport 2 | 3 | import com.darkyen.wemi.intellij.util.getWemiCompatibleSdk 4 | import com.intellij.ide.util.projectWizard.WizardContext 5 | import com.intellij.openapi.options.ConfigurationException 6 | import com.intellij.projectImport.ProjectImportWizardStep 7 | import java.awt.BorderLayout 8 | import java.lang.IllegalArgumentException 9 | import javax.swing.JComponent 10 | import javax.swing.JPanel 11 | 12 | class ConfigureWemiProjectStep(context: WizardContext) : ProjectImportWizardStep(context) { 13 | 14 | private val optionsControl = ImportFromWemiControl() 15 | 16 | private val component:JPanel by lazy { 17 | val panel = JPanel(BorderLayout()) 18 | panel.add(optionsControl.uiComponent) 19 | panel 20 | } 21 | 22 | override fun getComponent(): JComponent = component 23 | 24 | private var builderPrepared = false 25 | 26 | override fun updateStep() { 27 | if (!builderPrepared) { 28 | val context = wizardContext 29 | 30 | context.projectJdk = context.projectJdk ?: getWemiCompatibleSdk() 31 | optionsControl.reset(context, null) 32 | 33 | builderPrepared = true 34 | } 35 | } 36 | 37 | override fun updateDataModel() { 38 | val wizardContext = wizardContext 39 | optionsControl.apply(wizardContext) 40 | } 41 | 42 | @Throws(ConfigurationException::class) 43 | override fun validate(): Boolean { 44 | builder.ensureProjectIsDefined(this.wizardContext, optionsControl.getProjectImportOptions()) 45 | return true 46 | } 47 | 48 | override fun getBuilder(): ImportFromWemiBuilder { 49 | var projectBuilder = wizardContext.projectBuilder 50 | if (projectBuilder == null) { 51 | projectBuilder = ImportFromWemiBuilder() 52 | wizardContext.projectBuilder = projectBuilder 53 | return projectBuilder 54 | } else if (projectBuilder !is ImportFromWemiBuilder) { 55 | throw IllegalArgumentException("Invalid project builder: got $projectBuilder, expected ImportFromWemiBuilder") 56 | } 57 | return projectBuilder 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/test/forked/ForkSerialization.java: -------------------------------------------------------------------------------- 1 | package wemi.test.forked; 2 | 3 | import java.io.DataInputStream; 4 | import java.io.DataOutputStream; 5 | import java.io.IOException; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | /** 12 | * Serialization utilities for communication with forked processes. 13 | * Pure Java with no dependencies. 14 | */ 15 | public final class ForkSerialization { 16 | 17 | public static void writeTo(DataOutputStream out, List list) throws IOException { 18 | out.writeInt(list.size()); 19 | for (String s : list) { 20 | out.writeUTF(s); 21 | } 22 | } 23 | 24 | public static void readFrom(DataInputStream in, List list) throws IOException { 25 | list.clear(); 26 | final int length = in.readInt(); 27 | if (list instanceof ArrayList) { 28 | ((ArrayList) list).ensureCapacity(length); 29 | } 30 | for (int i = 0; i < length; i++) { 31 | list.add(in.readUTF()); 32 | } 33 | } 34 | 35 | public static void writeTo(DataOutputStream out, Set list) throws IOException { 36 | out.writeInt(list.size()); 37 | for (String s : list) { 38 | out.writeUTF(s); 39 | } 40 | } 41 | 42 | public static void readFrom(DataInputStream in, Set list) throws IOException { 43 | list.clear(); 44 | final int length = in.readInt(); 45 | for (int i = 0; i < length; i++) { 46 | list.add(in.readUTF()); 47 | } 48 | } 49 | 50 | public static void writeTo(DataOutputStream out, Map map) throws IOException { 51 | out.writeInt(map.size()); 52 | for (Map.Entry entry : map.entrySet()) { 53 | out.writeUTF(entry.getKey()); 54 | out.writeUTF(entry.getValue()); 55 | } 56 | } 57 | 58 | public static void readFrom(DataInputStream in, Map map) throws IOException { 59 | map.clear(); 60 | final int length = in.readInt(); 61 | for (int i = 0; i < length; i++) { 62 | final String key = in.readUTF(); 63 | final String value = in.readUTF(); 64 | map.put(key, value); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/execution/WemiTaskConfigurationType.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.execution 2 | 3 | import com.darkyen.wemi.intellij.importing.isWemiLinked 4 | import com.darkyen.wemi.intellij.settings.WemiProjectService 5 | import com.intellij.execution.configurations.ConfigurationFactory 6 | import com.intellij.execution.configurations.ConfigurationType 7 | import com.intellij.execution.configurations.ConfigurationTypeUtil 8 | import com.intellij.openapi.project.Project 9 | import icons.WemiIcons 10 | import javax.swing.Icon 11 | 12 | /** Provides Run Configuration for Wemi tasks */ 13 | class WemiTaskConfigurationType : ConfigurationType { 14 | 15 | /** Displayed in the left list of configuration types */ 16 | override fun getDisplayName(): String = "Wemi Task" 17 | 18 | /** For persistency */ 19 | override fun getId(): String = ID 20 | 21 | override fun getIcon(): Icon = WemiIcons.ACTION 22 | 23 | override fun getConfigurationTypeDescription(): String = "Wemi build system task" 24 | 25 | val taskConfigurationFactory = WemiTaskConfigurationFactory(this) 26 | 27 | class WemiTaskConfigurationFactory(configurationType:ConfigurationType) : ConfigurationFactory(configurationType) { 28 | override fun getId(): String = ID 29 | 30 | override fun isApplicable(project: Project): Boolean { 31 | return isWemiLinked(project) 32 | } 33 | 34 | override fun createTemplateConfiguration(project: Project): WemiTaskConfiguration { 35 | return WemiTaskConfiguration(project, this, "") 36 | } 37 | } 38 | 39 | private val CONFIGURATION_FACTORIES:Array = arrayOf(taskConfigurationFactory) 40 | 41 | override fun getConfigurationFactories(): Array = CONFIGURATION_FACTORIES 42 | 43 | companion object { 44 | const val ID = "WemiRunConfiguration" 45 | 46 | val INSTANCE:WemiTaskConfigurationType 47 | get() = ConfigurationTypeUtil.findConfigurationType(WemiTaskConfigurationType::class.java) 48 | } 49 | } -------------------------------------------------------------------------------- /test-repositories/basics/src/main/java/basics/Greeter.java: -------------------------------------------------------------------------------- 1 | package basics; 2 | 3 | import java.util.Random; 4 | import org.slf4j.LoggerFactory; 5 | import org.slf4j.Logger; 6 | import org.bukkit.Art; 7 | 8 | /** 9 | * This is a Java class for greeting people. 10 | * This documentation is here just to demo the Dokka integration. 11 | *
12 | * It can be created by executing archivingDocs:archive. 13 | */ 14 | public class Greeter { 15 | 16 | /** 17 | * Private logger. Not visible in generated documentation, by default. 18 | */ 19 | private static final Logger LOG = LoggerFactory.getLogger(Greeter.class); 20 | 21 | private final String[] greetings; 22 | 23 | /** 24 | * Create greeter that uses given greetings. 25 | * @param greetings parametrized strings, all {}'s will be replaced with the greeted person's name. 26 | */ 27 | public Greeter(String...greetings) { 28 | this.greetings = greetings; 29 | } 30 | 31 | /** 32 | * Create a random greeting. 33 | * @param name to be greeted 34 | * @return personalized greeting 35 | */ 36 | public String createGreeting(String name) { 37 | final Random random = GreeterMainKt.getRandom(); 38 | return this.greetings[random.nextInt(this.greetings.length)].replace("{}", name); 39 | } 40 | 41 | /** 42 | * Greet person named name to the stdout. 43 | */ 44 | public void greet(String name) { 45 | final String greeting = createGreeting(name); 46 | System.out.println(greeting); 47 | LOG.warn("Greeted {}", name); 48 | } 49 | 50 | public String getArtName() { 51 | final Random random = GreeterMainKt.getRandom(); 52 | final Art[] arts = Art.values(); 53 | return arts[random.nextInt(arts.length)].toString(); 54 | } 55 | 56 | /** 57 | * Main. Does nothing useful. 58 | * 59 | * @param args ignored 60 | */ 61 | public static void main(String[] args){ 62 | System.out.println("I am a Greeter! Hi!"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/util/ScopedLocatedPath.kt: -------------------------------------------------------------------------------- 1 | package wemi.util 2 | 3 | import com.darkyen.tproll.util.PrettyPrinter 4 | import com.esotericsoftware.jsonbeans.JsonWriter 5 | import wemi.dependency.ScopeCompile 6 | 7 | /** 8 | * A [LocatedPath] that is a part of classpath and carries a (Maven) scope information when that is. 9 | * Scope determines when exactly is the value a part of the classpath. 10 | * @see wemi.dependency.ScopeCompile (default) 11 | * @see wemi.dependency.ScopeRuntime 12 | * @see wemi.dependency.ScopeProvided 13 | * @see wemi.dependency.ScopeTest 14 | */ 15 | class ScopedLocatedPath(val value: LocatedPath, val scope: String = ScopeCompile) : WithDescriptiveString, JsonWritable { 16 | operator fun component1():LocatedPath = value 17 | operator fun component2():String = scope 18 | 19 | override fun JsonWriter.write() { 20 | writeObject { 21 | // Copies LocatedPath write, to get nicer structure 22 | field("root", value.root) 23 | field("file", value.file) 24 | field("scope", scope) 25 | } 26 | } 27 | 28 | override fun toDescriptiveAnsiString(): String { 29 | val sb = StringBuilder(120) 30 | sb.format(Color.Blue) 31 | PrettyPrinter.append(sb, value) 32 | sb.format() 33 | if (scope != ScopeCompile) { 34 | sb.format(Color.White).append(" $").format(Color.Black).append(scope).format() 35 | } 36 | return sb.toString() 37 | } 38 | 39 | override fun toString(): String { 40 | if (scope == ScopeCompile) { 41 | return value.toString() 42 | } else { 43 | return "$value $$scope" 44 | } 45 | } 46 | 47 | override fun equals(other: Any?): Boolean { 48 | if (this === other) return true 49 | if (other !is ScopedLocatedPath) return false 50 | 51 | if (value != other.value) return false 52 | if (scope != other.scope) return false 53 | 54 | return true 55 | } 56 | 57 | override fun hashCode(): Int { 58 | var result = value.hashCode() 59 | result = 31 * result + scope.hashCode() 60 | return result 61 | } 62 | } 63 | 64 | /** Give [this] a Maven [scope]. */ 65 | fun LocatedPath.scoped(scope: String = ScopeCompile): ScopedLocatedPath { 66 | return ScopedLocatedPath(this, scope) 67 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/dependency/ProjectDependency.kt: -------------------------------------------------------------------------------- 1 | package wemi.dependency 2 | 3 | import com.esotericsoftware.jsonbeans.JsonWriter 4 | import wemi.Configuration 5 | import wemi.Project 6 | import wemi.util.JsonWritable 7 | import wemi.util.field 8 | import wemi.util.writeArray 9 | import wemi.util.writeObject 10 | import wemi.util.writeValue 11 | 12 | /** 13 | * Defines a dependency on a [project] defined in the same build script. 14 | * 15 | * Dependency pulls [project]s [wemi.keys.internalClasspath] and [wemi.keys.externalClasspath] into 16 | * this project's external classpath. 17 | * 18 | * To create an aggregate project dependency, set [scope] to [ScopeAggregate]. 19 | * Aggregated project's internal classpath will end up in this projects archive, as if it was on its internal classpath. 20 | * Non-aggregate projects behave like normal libraries, archived separately. 21 | */ 22 | data class ProjectDependency(val project: Project, val configurations: List, val scope:DepScope = ScopeCompile) 23 | : JsonWritable { 24 | 25 | constructor(project:Project, vararg configurations: Configuration, scope:DepScope = ScopeCompile) : this(project, listOf(*configurations), scope) 26 | 27 | @Deprecated("Use ScopeAggregate instead") 28 | constructor(project: Project, aggregate:Boolean, vararg configurations: Configuration, scope:DepScope = ScopeCompile) : this(project, *configurations, scope = if (aggregate) ScopeAggregate else scope) 29 | 30 | override fun JsonWriter.write() { 31 | writeObject { 32 | field("project", project.name) 33 | field("scope", scope) 34 | name("configurations").writeArray { 35 | for (configuration in configurations) { 36 | writeValue(configuration.name, String::class.java) 37 | } 38 | } 39 | } 40 | } 41 | 42 | override fun toString(): String { 43 | val sb = StringBuilder() 44 | sb.append(project.name).append('/') 45 | for (c in configurations) { 46 | sb.append(c.name).append(':') 47 | } 48 | sb.append(" scope=").append(scope) 49 | 50 | return sb.toString() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/file/Util.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.file 2 | 3 | import com.darkyen.wemi.intellij.WemiBuildDirectoryName 4 | import com.darkyen.wemi.intellij.WemiBuildFileExtensions 5 | import com.darkyen.wemi.intellij.WemiLauncherFileName 6 | import com.intellij.openapi.vfs.VirtualFile 7 | import java.nio.file.Files 8 | import java.nio.file.Path 9 | 10 | internal fun VirtualFile?.isWemiLauncher():Boolean { 11 | if (this == null || this.isDirectory || !this.exists()) { 12 | return false 13 | } 14 | return this.name == WemiLauncherFileName 15 | } 16 | 17 | internal fun VirtualFile?.isWemiScriptSource(deepCheck:Boolean = false):Boolean { 18 | if (this == null || this.isDirectory || this.name.startsWith('.')) { 19 | return false 20 | } 21 | 22 | if (!WemiBuildFileExtensions.contains(this.extension?.toLowerCase() ?: "")) { 23 | return false 24 | } 25 | 26 | if (deepCheck) { 27 | val buildDirectory = this.parent ?: return false 28 | if (!buildDirectory.name.equals(WemiBuildDirectoryName, ignoreCase = true)) { 29 | return false 30 | } 31 | val projectDirectory = buildDirectory.parent ?: return false 32 | val wemiLauncher = projectDirectory.findChild(WemiLauncherFileName) ?: return false 33 | return wemiLauncher.isWemiLauncher() 34 | } 35 | 36 | return true 37 | } 38 | 39 | internal fun Path?.isWemiLauncher():Boolean { 40 | if (this == null || Files.isDirectory(this) || !Files.exists(this)) { 41 | return false 42 | } 43 | return this.fileName.toString() == WemiLauncherFileName 44 | } 45 | 46 | /** 47 | * Return true if this file path has any of the specified extensions. 48 | * 49 | * Not case sensitive. 50 | */ 51 | internal fun String.pathHasExtension(extensions: Iterable): Boolean { 52 | val name = this 53 | val length = name.length 54 | for (extension in extensions) { 55 | if (length >= extension.length + 1 56 | && name.endsWith(extension, ignoreCase = true) 57 | && name[length - extension.length - 1] == '.') { 58 | return true 59 | } 60 | } 61 | return false 62 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/options/RunConfigOptions.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.options 2 | 3 | import com.darkyen.wemi.intellij.ui.BooleanPropertyEditor 4 | import com.darkyen.wemi.intellij.ui.PropertyEditorPanel 5 | import com.darkyen.wemi.intellij.util.BooleanXmlSerializer 6 | 7 | /** Options for Run configurations. */ 8 | class RunConfigOptions : RunOptions() { 9 | // Bookkeeping settings 10 | var allowRunningInParallel:Boolean = false 11 | var generatedName:Boolean = false 12 | 13 | // Run settings 14 | var debugWemiItself:Boolean = false 15 | 16 | init { 17 | register(RunConfigOptions::allowRunningInParallel, BooleanXmlSerializer::class) 18 | register(RunConfigOptions::generatedName, BooleanXmlSerializer::class) 19 | register(RunConfigOptions::debugWemiItself, BooleanXmlSerializer::class) 20 | } 21 | 22 | fun copyTo(o: RunConfigOptions) { 23 | copyTo(o as RunOptions) 24 | o.allowRunningInParallel = allowRunningInParallel 25 | o.generatedName = generatedName 26 | o.debugWemiItself = debugWemiItself 27 | } 28 | 29 | override fun createUi(panel: PropertyEditorPanel) { 30 | super.createUi(panel) 31 | panel.editRow(BooleanPropertyEditor(this::debugWemiItself, "Debug build scripts", "Debugger will be attached to the Wemi process itself", "Debugger will be attached to any forked process")) 32 | } 33 | 34 | override fun equals(other: Any?): Boolean { 35 | if (this === other) return true 36 | if (other !is RunConfigOptions) return false 37 | if (!super.equals(other)) return false 38 | 39 | if (allowRunningInParallel != other.allowRunningInParallel) return false 40 | if (generatedName != other.generatedName) return false 41 | if (debugWemiItself != other.debugWemiItself) return false 42 | 43 | return true 44 | } 45 | 46 | override fun hashCode(): Int { 47 | var result = super.hashCode() 48 | result = 31 * result + allowRunningInParallel.hashCode() 49 | result = 31 * result + generatedName.hashCode() 50 | result = 31 * result + debugWemiItself.hashCode() 51 | return result 52 | } 53 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/importing/actions/ImportProjectAction.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.importing.actions 2 | 3 | import com.darkyen.wemi.intellij.WemiLauncher 4 | import com.darkyen.wemi.intellij.importing.hasWemiFiles 5 | import com.darkyen.wemi.intellij.importing.isImportable 6 | import com.darkyen.wemi.intellij.importing.isWemiLinked 7 | import com.darkyen.wemi.intellij.importing.withWemiLauncher 8 | import com.darkyen.wemi.intellij.projectImport.ImportFromWemiProvider 9 | import com.intellij.ide.actions.ImportModuleAction 10 | import com.intellij.ide.util.newProjectWizard.AddModuleWizard 11 | import com.intellij.openapi.actionSystem.AnAction 12 | import com.intellij.openapi.actionSystem.AnActionEvent 13 | import com.intellij.openapi.project.Project 14 | import icons.WemiIcons 15 | 16 | /** 17 | * Action to import the project. 18 | * 19 | * This is usually the first point at which user meets this plugin. 20 | */ 21 | class ImportProjectAction : AnAction("Import Wemi Project", 22 | "Import an unlinked Wemi project in the project's root into the IDE", WemiIcons.ACTION) { 23 | 24 | override fun update(e: AnActionEvent) { 25 | val project = e.project 26 | e.presentation.isEnabledAndVisible = project != null && isImportable(project) && !isWemiLinked(project) && hasWemiFiles(project) 27 | } 28 | 29 | override fun actionPerformed(e: AnActionEvent) { 30 | val project = e.project 31 | if (!(project != null && isImportable(project) && !isWemiLinked(project) && hasWemiFiles(project))) { 32 | e.presentation.isEnabledAndVisible = false 33 | return 34 | } 35 | 36 | project.withWemiLauncher("Wemi project import") { launcher -> 37 | importUnlinkedProject(project, launcher) 38 | } 39 | } 40 | 41 | companion object { 42 | fun importUnlinkedProject(project: Project, launcher: WemiLauncher) { 43 | val projectDirectory = launcher.file.parent 44 | val wizard = AddModuleWizard(project, projectDirectory.toString(), ImportFromWemiProvider()) 45 | if (wizard.stepCount <= 0 || wizard.showAndGet()) { 46 | ImportModuleAction.createFromWizard(project, wizard) 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/importing/actions/ReloadProjectAction.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.importing.actions 2 | 3 | import com.darkyen.wemi.intellij.file.isWemiScriptSource 4 | import com.darkyen.wemi.intellij.importing.isImportable 5 | import com.darkyen.wemi.intellij.importing.isWemiLinked 6 | import com.darkyen.wemi.intellij.projectImport.importWemiProject 7 | import com.intellij.openapi.actionSystem.ActionPlaces 8 | import com.intellij.openapi.actionSystem.AnAction 9 | import com.intellij.openapi.actionSystem.AnActionEvent 10 | import com.intellij.openapi.application.ApplicationManager 11 | import com.intellij.openapi.application.ModalityState 12 | import com.intellij.openapi.fileEditor.FileDocumentManager 13 | import icons.WemiIcons 14 | import org.jetbrains.kotlin.idea.refactoring.psiElement 15 | 16 | /** Action to re-import the project explicitly. */ 17 | class ReloadProjectAction : AnAction("Reload Wemi Project", 18 | "Re-import Wemi project in the project's root into the IDE", 19 | WemiIcons.ACTION) { 20 | 21 | override fun update(e: AnActionEvent) { 22 | val project = e.project 23 | if (project == null || !isImportable(project) || !isWemiLinked(project)) { 24 | e.presentation.isEnabledAndVisible = false 25 | return 26 | } 27 | 28 | if (e.place == ActionPlaces.PROJECT_VIEW_POPUP) { 29 | // Right-clicking a file - is it a Wemi script file? 30 | e.presentation.isEnabledAndVisible = e.dataContext.psiElement 31 | ?.containingFile?.originalFile?.virtualFile.isWemiScriptSource(false) 32 | } else { 33 | // Elsewhere, possibly Tools 34 | e.presentation.isEnabledAndVisible = true 35 | } 36 | } 37 | 38 | override fun actionPerformed(e: AnActionEvent) { 39 | val project = e.project 40 | if (project == null || !isImportable(project) || !isWemiLinked(project)) { 41 | e.presentation.isEnabledAndVisible = false 42 | return 43 | } 44 | 45 | ApplicationManager.getApplication().invokeLater({ 46 | FileDocumentManager.getInstance().saveAllDocuments() 47 | importWemiProject(project, initial = false) 48 | }, ModalityState.NON_MODAL) 49 | } 50 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/esotericsoftware/tablelayout/swing/TableLayout.java: -------------------------------------------------------------------------------- 1 | 2 | package com.esotericsoftware.tablelayout.swing; 3 | 4 | import java.awt.Color; 5 | import java.awt.Component; 6 | import java.awt.Graphics2D; 7 | import java.awt.Insets; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import com.esotericsoftware.tablelayout.BaseTableLayout; 12 | import com.esotericsoftware.tablelayout.Cell; 13 | import com.esotericsoftware.tablelayout.swing.SwingToolkit.DebugRect; 14 | 15 | class TableLayout extends BaseTableLayout { 16 | ArrayList debugRects; 17 | 18 | public TableLayout () { 19 | super((SwingToolkit)SwingToolkit.instance); 20 | } 21 | 22 | public TableLayout (SwingToolkit toolkit) { 23 | super(toolkit); 24 | } 25 | 26 | public void layout () { 27 | Table table = getTable(); 28 | Insets insets = table.getInsets(); 29 | super.layout(insets.left, insets.top, // 30 | table.getWidth() - insets.left - insets.right, // 31 | table.getHeight() - insets.top - insets.bottom); 32 | 33 | List cells = getCells(); 34 | for (int i = 0, n = cells.size(); i < n; i++) { 35 | Cell c = cells.get(i); 36 | if (c.getIgnore()) continue; 37 | Component component = (Component)c.getWidget(); 38 | if (component == null) continue; 39 | component.setLocation((int)c.getWidgetX(), (int)c.getWidgetY()); 40 | component.setSize((int)c.getWidgetWidth(), (int)c.getWidgetHeight()); 41 | } 42 | 43 | if (getDebug() != Debug.none) SwingToolkit.startDebugTimer(); 44 | } 45 | 46 | public void invalidate () { 47 | super.invalidate(); 48 | if (getTable().isValid()) getTable().invalidate(); 49 | } 50 | 51 | public void invalidateHierarchy () { 52 | if (getTable().isValid()) getTable().invalidate(); 53 | } 54 | 55 | void drawDebug () { 56 | Graphics2D g = (Graphics2D)getTable().getGraphics(); 57 | if (g == null) return; 58 | g.setColor(Color.red); 59 | for (DebugRect rect : debugRects) { 60 | if (rect.type == Debug.cell) g.setColor(Color.red); 61 | if (rect.type == Debug.widget) g.setColor(Color.green); 62 | if (rect.type == Debug.table) g.setColor(Color.blue); 63 | g.drawRect(rect.x, rect.y, rect.width, rect.height); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/importing/actions/InstallWemiLauncherAction.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.importing.actions 2 | 3 | import com.darkyen.wemi.intellij.WemiNotificationGroup 4 | import com.darkyen.wemi.intellij.importing.defaultWemiRootPathFor 5 | import com.darkyen.wemi.intellij.importing.getWemiLauncher 6 | import com.darkyen.wemi.intellij.importing.isImportable 7 | import com.darkyen.wemi.intellij.importing.reinstallWemiLauncher 8 | import com.darkyen.wemi.intellij.showBalloon 9 | import com.intellij.notification.NotificationType 10 | import com.intellij.openapi.actionSystem.AnAction 11 | import com.intellij.openapi.actionSystem.AnActionEvent 12 | import com.intellij.openapi.vfs.LocalFileSystem 13 | import icons.WemiIcons 14 | 15 | /** 16 | * Action to convert foreign project to Wemi and import it. 17 | * 18 | * This is usually the first point at which user meets this plugin. 19 | */ 20 | class InstallWemiLauncherAction : AnAction(INSTALL_TITLE, 21 | "Place plugin's 'wemi' launcher file into the project's root, updating the existing one", WemiIcons.ACTION) { 22 | 23 | override fun update(e: AnActionEvent) { 24 | val project = e.project 25 | if (project == null || !isImportable(project)) { 26 | e.presentation.isEnabledAndVisible = false 27 | return 28 | } 29 | 30 | e.presentation.isEnabledAndVisible = true 31 | e.presentation.text = if (project.getWemiLauncher() == null) { 32 | INSTALL_TITLE 33 | } else { 34 | REINSTALL_TITLE 35 | } 36 | } 37 | 38 | override fun actionPerformed(e: AnActionEvent) { 39 | val project = e.project ?: return 40 | val basePath = defaultWemiRootPathFor(project) ?: return 41 | val reinstalled = reinstallWemiLauncher(basePath, "Could not (re)install Wemi launcher", project) 42 | if (reinstalled != null) { 43 | LocalFileSystem.getInstance().refreshAndFindFileByPath(basePath.toString()) 44 | WemiNotificationGroup.showBalloon(project, "Success", 45 | "Wemi launcher (re)created", 46 | NotificationType.INFORMATION) 47 | } 48 | } 49 | 50 | companion object { 51 | private const val INSTALL_TITLE = "Install Wemi launcher" 52 | private const val REINSTALL_TITLE = "Reinstall Wemi launcher" 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/util/Version.kt: -------------------------------------------------------------------------------- 1 | package wemi.util 2 | 3 | import java.lang.NumberFormatException 4 | 5 | /** 6 | * Wraps version string and provides comparison capability over it. 7 | * Newer versions are larger. 8 | * 9 | * Version string is divided into parts (separated by dots, hyphens or underscores). 10 | * Each part has its numeric value taken (from any leading digits), otherwise it is sorted lexicographically. 11 | * Textual parts are always treated as older than numerical. 12 | * When comparing two versions, missing parts are treated as numerical 0. 13 | */ 14 | class Version(val version:String) : Comparable { 15 | 16 | private val text:Array 17 | private val numbers:IntArray 18 | 19 | init { 20 | val parts = version.split('.', '_', '-') 21 | text = arrayOfNulls(parts.size) 22 | numbers = IntArray(parts.size) 23 | for ((i, part) in parts.withIndex()) { 24 | try { 25 | numbers[i] = part.toInt() 26 | } catch (e:NumberFormatException) { 27 | text[i] = part 28 | } 29 | } 30 | } 31 | 32 | override fun compareTo(other: Version): Int { 33 | for (cell in 0 until maxOf(numbers.size, other.numbers.size)) { 34 | val myText = text.getOrNull(cell) 35 | val otherText = other.text.getOrNull(cell) 36 | if (myText != null && otherText != null) { 37 | val cmp = myText.compareTo(otherText) 38 | if (cmp != 0) { 39 | return cmp 40 | } 41 | continue 42 | } 43 | if (myText != null) { 44 | // My version has some text, therefore it is older / smaller 45 | return -1 46 | } 47 | if (otherText != null) { 48 | // Other version has some text, therefore it is older / smaller 49 | return 1 50 | } 51 | 52 | val my = if (cell in numbers.indices) numbers[cell] else 0 53 | val others = if (cell in other.numbers.indices) other.numbers[cell] else 0 54 | 55 | val comparison = my.compareTo(others) 56 | if (comparison != 0) { 57 | return comparison 58 | } 59 | } 60 | return 0 61 | } 62 | 63 | override fun equals(other: Any?): Boolean { 64 | if (this === other) return true 65 | if (javaClass != other?.javaClass) return false 66 | 67 | other as Version 68 | return numbers.contentEquals(other.numbers) && text.contentEquals(other.text) 69 | } 70 | 71 | override fun hashCode(): Int { 72 | return numbers.hashCode() * 31 + text.hashCode() 73 | } 74 | 75 | override fun toString(): String = version 76 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/options/ProjectImportOptions.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.options 2 | 3 | import com.darkyen.wemi.intellij.ui.BooleanPropertyEditor 4 | import com.darkyen.wemi.intellij.ui.CommandArgumentEditor 5 | import com.darkyen.wemi.intellij.ui.PropertyEditorPanel 6 | import com.darkyen.wemi.intellij.util.BooleanXmlSerializer 7 | import com.darkyen.wemi.intellij.util.ListOfStringXmlSerializer 8 | import com.esotericsoftware.tablelayout.BaseTableLayout 9 | 10 | /** Options for project importing */ 11 | class ProjectImportOptions : WemiLauncherOptions() { 12 | 13 | var prefixConfigurations:List = emptyList() 14 | var downloadSources:Boolean = true 15 | var downloadDocumentation:Boolean = true 16 | 17 | init { 18 | register(ProjectImportOptions::prefixConfigurations, ListOfStringXmlSerializer::class) 19 | register(ProjectImportOptions::downloadSources, BooleanXmlSerializer::class) 20 | register(ProjectImportOptions::downloadDocumentation, BooleanXmlSerializer::class) 21 | } 22 | 23 | fun copyTo(o: ProjectImportOptions) { 24 | copyTo(o as WemiLauncherOptions) 25 | o.prefixConfigurations = prefixConfigurations 26 | o.downloadSources = downloadSources 27 | o.downloadDocumentation = downloadDocumentation 28 | } 29 | 30 | override fun createUi(panel: PropertyEditorPanel) { 31 | super.createUi(panel) 32 | 33 | panel.editRow(CommandArgumentEditor(this::prefixConfigurations, "Prefix configurations"))// TODO(jp): Better editor 34 | panel.edit(BooleanPropertyEditor(this::downloadSources, "Download sources")).align(BaseTableLayout.RIGHT).spaceRight(20f) 35 | panel.edit(BooleanPropertyEditor(this::downloadDocumentation, "Download documentation")).align(BaseTableLayout.LEFT).row() 36 | } 37 | 38 | override fun equals(other: Any?): Boolean { 39 | if (this === other) return true 40 | if (other !is ProjectImportOptions) return false 41 | if (!super.equals(other)) return false 42 | 43 | if (prefixConfigurations != other.prefixConfigurations) return false 44 | if (downloadSources != other.downloadSources) return false 45 | if (downloadDocumentation != other.downloadDocumentation) return false 46 | 47 | return true 48 | } 49 | 50 | override fun hashCode(): Int { 51 | var result = super.hashCode() 52 | result = 31 * result + prefixConfigurations.hashCode() 53 | result = 31 * result + downloadSources.hashCode() 54 | result = 31 * result + downloadDocumentation.hashCode() 55 | return result 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/settings/WemiProjectService.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.settings 2 | 3 | import com.darkyen.wemi.intellij.WemiLauncher 4 | import com.darkyen.wemi.intellij.WemiLauncherFileName 5 | import com.darkyen.wemi.intellij.importing.defaultWemiRootPathFor 6 | import com.darkyen.wemi.intellij.options.ProjectImportOptions 7 | import com.darkyen.wemi.intellij.util.MapStringStringXmlSerializer 8 | import com.darkyen.wemi.intellij.util.StringXmlSerializer 9 | import com.darkyen.wemi.intellij.util.XmlSerializable 10 | import com.darkyen.wemi.intellij.util.findWindowsShellExe 11 | import com.darkyen.wemi.intellij.util.xmlSerializableSerializer 12 | import com.intellij.openapi.components.Service 13 | import com.intellij.openapi.components.State 14 | import com.intellij.openapi.project.Project 15 | import com.intellij.openapi.util.SystemInfo 16 | import java.nio.file.Files 17 | import java.nio.file.Paths 18 | 19 | /** Stuff saved per IDE project */ 20 | @Service 21 | @State(name = "wemi.WemiProjectService") 22 | class WemiProjectService(project: Project) : XmlSerializable() { 23 | 24 | private var wemiLauncherPath:String = defaultWemiRootPathFor(project)?.resolve(WemiLauncherFileName)?.toString() ?: "" 25 | var options = ProjectImportOptions() 26 | var tasks = emptyMap() 27 | 28 | fun updateWindowsShellExe() { 29 | if (SystemInfo.isWindows && options.windowsShellExecutable.isBlank()) { 30 | findWindowsShellExe()?.let { options.windowsShellExecutable = it } 31 | } 32 | } 33 | 34 | init { 35 | updateWindowsShellExe() 36 | } 37 | 38 | private var wemiLauncherCache:WemiLauncher? = if (wemiLauncherPath.isBlank() || (SystemInfo.isWindows && options.windowsShellExecutable.isBlank())) { 39 | null 40 | } else WemiLauncher(Paths.get(wemiLauncherPath), options.windowsShellExecutable) 41 | 42 | var wemiLauncher:WemiLauncher? 43 | get() { 44 | val cache = wemiLauncherCache ?: return null 45 | return if (Files.isRegularFile(cache.file)) { 46 | cache 47 | } else { 48 | null 49 | } 50 | } 51 | set(value) { 52 | wemiLauncherPath = value?.file?.toAbsolutePath()?.normalize()?.toString() ?: "" 53 | wemiLauncherCache = value 54 | } 55 | 56 | init { 57 | register(WemiProjectService::wemiLauncherPath, StringXmlSerializer::class) 58 | register(WemiProjectService::options, xmlSerializableSerializer()) 59 | register(WemiProjectService::tasks, MapStringStringXmlSerializer::class) 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/dependency/internal/MavenOS.kt: -------------------------------------------------------------------------------- 1 | package wemi.dependency.internal 2 | 3 | import java.util.* 4 | 5 | /* 6 | * Utilities for Maven's OS detection capabilities. 7 | * Based on https://github.com/codehaus-plexus/plexus-utils/blob/master/src/main/java/org/codehaus/plexus/util/Os.java 8 | */ 9 | 10 | internal const val OS_FAMILY_DOS = "dos" 11 | internal const val OS_FAMILY_MAC = "mac" 12 | internal const val OS_FAMILY_NETWARE = "netware" 13 | internal const val OS_FAMILY_OS2 = "os/2" 14 | internal const val OS_FAMILY_TANDEM = "tandem" 15 | internal const val OS_FAMILY_UNIX = "unix" 16 | internal const val OS_FAMILY_WINDOWS = "windows" 17 | internal const val OS_FAMILY_WIN9X = "win9x" 18 | internal const val OS_FAMILY_ZOS = "z/os" 19 | internal const val OS_FAMILY_OS400 = "os/400" 20 | internal const val OS_FAMILY_OPENVMS = "openvms" 21 | 22 | internal val OS_NAME:String = System.getenv("WEMI_MAVEN_OS_NAME") ?: System.getProperty("os.name").toLowerCase(Locale.US) 23 | internal val OS_ARCH:String = System.getenv("WEMI_MAVEN_OS_ARCH") ?: System.getProperty("os.arch").toLowerCase(Locale.US) 24 | internal val OS_VERSION:String = System.getenv("WEMI_MAVEN_OS_VERSION") ?: System.getProperty("os.version").toLowerCase(Locale.US) 25 | 26 | internal val OS_FAMILY:String = System.getenv("WEMI_MAVEN_OS_FAMILY") ?: run { 27 | val pathSeparator = System.getProperty("path.separator") 28 | val osName = OS_NAME 29 | 30 | if (osName.contains(OS_FAMILY_WINDOWS)) { 31 | if ((osName.contains("95") || osName.contains("98") || osName.contains("me") || osName.contains("ce"))) { 32 | OS_FAMILY_WIN9X 33 | } else { 34 | OS_FAMILY_WINDOWS 35 | } 36 | } 37 | 38 | else if (osName.contains(OS_FAMILY_OS2)) { 39 | OS_FAMILY_OS2 40 | } else if (osName.contains(OS_FAMILY_NETWARE)) { 41 | OS_FAMILY_NETWARE 42 | } else if (osName.contains(OS_FAMILY_OPENVMS)) { 43 | OS_FAMILY_OPENVMS 44 | } else if (osName.contains(OS_FAMILY_ZOS) || osName.contains("os/390")) { 45 | OS_FAMILY_ZOS 46 | } else if (osName.contains(OS_FAMILY_OS400)) { 47 | OS_FAMILY_OS400 48 | } else if (osName.contains("nonstop_kernel")) { 49 | OS_FAMILY_TANDEM 50 | } 51 | 52 | // Must be after OS_FAMILY_NETWARE, OS_FAMILY_WINDOWS and OS_FAMILY_WIN9X 53 | else if (pathSeparator == ";") { 54 | OS_FAMILY_DOS 55 | } 56 | 57 | // Must be after OS_FAMILY_OPENVMS 58 | else if (pathSeparator == ":" && (!osName.contains(OS_FAMILY_MAC) || osName.endsWith("x"))) { 59 | OS_FAMILY_UNIX 60 | } 61 | 62 | // Must be after OS_FAMILY_UNIX 63 | else if (osName.contains(OS_FAMILY_MAC)) { 64 | OS_FAMILY_MAC 65 | } 66 | 67 | else osName 68 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/projectImport/ImportFromWemiControl.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.projectImport 2 | 3 | import com.darkyen.wemi.intellij.options.ProjectImportOptions 4 | import com.darkyen.wemi.intellij.settings.WemiProjectService 5 | import com.darkyen.wemi.intellij.ui.PropertyEditorPanel 6 | import com.intellij.ide.util.projectWizard.WizardContext 7 | import com.intellij.openapi.externalSystem.service.settings.AbstractSettingsControl 8 | import com.intellij.openapi.externalSystem.util.ExternalSystemBundle 9 | import com.intellij.openapi.externalSystem.util.ExternalSystemUiUtil 10 | import com.intellij.openapi.externalSystem.util.PaintAwarePanel 11 | import com.intellij.openapi.project.Project 12 | import com.intellij.projectImport.ProjectFormatPanel 13 | import java.awt.GridBagLayout 14 | import javax.swing.JLabel 15 | 16 | /** 17 | * Window that is shown when WEMI project is being imported. 18 | */ 19 | class ImportFromWemiControl : AbstractSettingsControl() { 20 | 21 | private val options = ProjectImportOptions() 22 | private val optionsEditor = PropertyEditorPanel().also { panel -> 23 | options.createUi(panel) 24 | panel.loadFromProperties() 25 | } 26 | 27 | val uiComponent = PaintAwarePanel(GridBagLayout()) 28 | 29 | private val projectFormatPanel = ProjectFormatPanel() 30 | 31 | init { 32 | uiComponent.add(optionsEditor, ExternalSystemUiUtil.getFillLineConstraints(0)) 33 | 34 | val myProjectFormatLabel = JLabel(ExternalSystemBundle.message("settings.label.project.format")) 35 | uiComponent.add(myProjectFormatLabel, ExternalSystemUiUtil.getLabelConstraints(0)) 36 | uiComponent.add(projectFormatPanel.storageFormatComboBox, ExternalSystemUiUtil.getFillLineConstraints(0)) 37 | ExternalSystemUiUtil.fillBottom(uiComponent) 38 | } 39 | 40 | public override fun reset(wizardContext: WizardContext?, project: Project?) { 41 | super.reset(wizardContext, project) 42 | this.project?.getService(WemiProjectService::class.java)?.options?.copyTo(options) 43 | optionsEditor.loadFromProperties() 44 | } 45 | 46 | fun apply(wizardContext:WizardContext) { 47 | this.project?.getService(WemiProjectService::class.java)?.let { projectState -> 48 | optionsEditor.saveToProperties() 49 | options.copyTo(projectState.options) 50 | } 51 | projectFormatPanel.updateData(wizardContext) 52 | } 53 | 54 | fun getProjectImportOptions():ProjectImportOptions { 55 | optionsEditor.saveToProperties() 56 | return options 57 | } 58 | } -------------------------------------------------------------------------------- /test-repositories/libgdx_game/build/build.kt: -------------------------------------------------------------------------------- 1 | @file:BuildDependencyPlugin("wemi-plugin-jvm-hotswap") 2 | 3 | import wemi.keys.* 4 | import wemi.util.executable 5 | import wemi.compile.JavaCompilerFlags 6 | import wemi.dependency.* 7 | import wemi.* 8 | import wemi.util.* 9 | import wemi.boot.CLI.makeDefault 10 | 11 | val gdxVersion = "1.9.7" 12 | 13 | val core by project(path("core")) { 14 | projectName set {"LibGDX Demo"} 15 | projectGroup set {"wemi"} 16 | projectVersion set {"1.0"} 17 | 18 | libraryDependencies add { dependency("com.badlogicgames.gdx", "gdx", gdxVersion) } 19 | } 20 | 21 | val lwjgl3 by project(path("lwjgl3")) { 22 | 23 | makeDefault() 24 | 25 | projectName set {"LibGDX Demo"} 26 | projectGroup set {"wemi"} 27 | projectVersion set {"1.0"} 28 | 29 | libraryDependencies add { dependency("com.badlogicgames.gdx", "gdx-backend-lwjgl3", gdxVersion) } 30 | libraryDependencies add { dependency("com.badlogicgames.gdx", "gdx-platform", gdxVersion, classifier = "natives-desktop") } 31 | 32 | projectDependencies add { ProjectDependency(core, scope = ScopeAggregate) } 33 | 34 | mainClass set {"game.Main"} 35 | 36 | val onMac = System.getProperty("os.name").toLowerCase().contains("mac") 37 | if (onMac) { 38 | runOptions add {"-XstartOnFirstThread"} 39 | } 40 | 41 | assemblyPrependData set { 42 | // NOTE: This does mean that the jar built on Mac OS is not portable, but real application would deal with this differently anyway 43 | if (onMac) { 44 | "#!/usr/bin/env sh\nexec java -XstartOnFirstThread -jar \"$0\" \"$@\"\n".toByteArray(Charsets.UTF_8) 45 | } else { 46 | "#!/usr/bin/env sh\nexec java -jar \"$0\" \"$@\"\n".toByteArray(Charsets.UTF_8) 47 | } 48 | } 49 | 50 | assemblyOutputFile set { 51 | buildDirectory.get() / "game" 52 | } 53 | 54 | assembly modify { assembled -> 55 | assembled.executable = true 56 | assembled 57 | } 58 | } 59 | 60 | val lwjgl2 by project(path("./lwjgl2/")) { 61 | projectName set {"LibGDX Demo"} 62 | projectGroup set {"wemi"} 63 | projectVersion set {"1.0"} 64 | 65 | 66 | libraryDependencies add { dependency("com.badlogicgames.gdx", "gdx-backend-lwjgl", gdxVersion) } 67 | libraryDependencies add { dependency("com.badlogicgames.gdx", "gdx-platform", gdxVersion, classifier = "natives-desktop") } 68 | 69 | projectDependencies add { ProjectDependency(core, scope = ScopeAggregate) } 70 | 71 | mainClass set {"game.Main"} 72 | 73 | compilerOptions[JavaCompilerFlags.customFlags] = { it + "-Xlint:unchecked" } 74 | } 75 | -------------------------------------------------------------------------------- /plugins/intellij/src/main/kotlin/wemiplugin/intellij/PatchPluginXML.kt: -------------------------------------------------------------------------------- 1 | package wemiplugin.intellij 2 | 3 | import Files 4 | import org.slf4j.LoggerFactory 5 | import wemi.EvalScope 6 | import wemi.Value 7 | import wemi.util.LocatedPath 8 | import wemi.util.div 9 | import wemi.keys.* 10 | import wemiplugin.intellij.utils.Patch 11 | import wemiplugin.intellij.utils.parseXml 12 | import wemiplugin.intellij.utils.patchInPlace 13 | import wemiplugin.intellij.utils.saveXml 14 | import java.nio.file.Path 15 | 16 | private val LOG = LoggerFactory.getLogger("PatchPluginXML") 17 | 18 | // See https://jetbrains.org/intellij/sdk/docs/basics/plugin_structure/plugin_configuration_file.html 19 | 20 | fun EvalScope.generatePatchedPluginXmlFiles(root: Path) { 21 | val filesToPatch = IntelliJ.intellijPluginXmlFiles.get() 22 | if (filesToPatch.isEmpty()) { 23 | return 24 | } 25 | val patchedPaths = ArrayList() 26 | for (unpatched in filesToPatch) { 27 | val pluginXml = parseXml(unpatched.file) ?: continue 28 | 29 | pluginXml.patchInPlace("idea-plugin", IntelliJ.intellijPluginXmlPatches.get()) 30 | 31 | val patchedPath = root / "META-INF" / unpatched.path 32 | if (saveXml(patchedPath, pluginXml)) { 33 | patchedPaths.add(LocatedPath(root, patchedPath)) 34 | } 35 | } 36 | } 37 | 38 | val DefaultIntelliJPluginXmlPatches : Value> = { 39 | val namePatch = projectName.getOrElse(null)?.let { Patch("name", content = it) } 40 | val idPatch = projectGroup.getOrElse(null)?.let { Patch("id", content = it) } 41 | val versionPatch = projectVersion.getOrElse(null)?.let { Patch("version", content = it) } 42 | val ideVersion = IntelliJ.intellijResolvedIdeDependency.get().version 43 | 44 | val sinceBuildPatch = pluginXmlSinceBuildPatch("${ideVersion.baselineVersion}.${ideVersion.build}") 45 | // Not added automatically, because smaller/less maintained plugins are generally better without it 46 | //val untilBuildPatch = pluginXmlUntilBuildPatch("${ideVersion.baselineVersion}.*") 47 | 48 | // TODO(jp): Also handle module dependencies? 49 | 50 | arrayOf(namePatch, idPatch, versionPatch, sinceBuildPatch).filterNotNull() 51 | } 52 | 53 | fun pluginXmlSinceBuildPatch(sinceBuildVersion:String):Patch = Patch("idea-version", attribute = "since-build", content = sinceBuildVersion) 54 | fun pluginXmlUntilBuildPatch(untilBuildVersion:String):Patch = Patch("idea-version", attribute = "until-build", content = untilBuildVersion) 55 | fun pluginXmlDescriptionPatch(description:Path):Patch = Patch("description", content = String(Files.readAllBytes(description), Charsets.UTF_8)) 56 | fun pluginXmlChangeNotesPatch(changeNotes:Path):Patch = Patch("change-notes", content = String(Files.readAllBytes(changeNotes), Charsets.UTF_8)) 57 | 58 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/dependency/Repositories.kt: -------------------------------------------------------------------------------- 1 | package wemi.dependency 2 | 3 | import wemi.boot.WemiSystemCacheFolder 4 | import wemi.util.div 5 | import java.net.URL 6 | import java.nio.file.Path 7 | import java.nio.file.Paths 8 | 9 | /** Default local Maven repository stored in `~/.m2/repository`. Used for local releases. */ 10 | val LocalM2Repository = Repository("local", Paths.get(System.getProperty("user.home")) / ".m2/repository/", null) 11 | 12 | /** Construct a path to the cache directory for a repository named [repositoryName]. */ 13 | fun repositoryCachePath(repositoryName:String): Path { 14 | val safePath = StringBuilder() 15 | safePath.append("maven-cache/") 16 | for (c in repositoryName) { 17 | // https://superuser.com/questions/358855/what-characters-are-safe-in-cross-platform-file-names-for-linux-windows-and-os 18 | if (c in "\\/:*?\"<>|" || Character.isISOControl(c)) { 19 | safePath.append("%04x".format(c.toInt())) 20 | } else { 21 | safePath.append(c) 22 | } 23 | } 24 | safePath.append('/') 25 | return WemiSystemCacheFolder / safePath 26 | } 27 | 28 | /** Maven Central repository at [maven.org](https://maven.org). Cached by [LocalM2Repository]. */ 29 | val MavenCentral = Repository("central", URL("https://repo.maven.apache.org/maven2/"), snapshots = false, authoritative = true) 30 | 31 | /** [Bintray JCenter repository](https://bintray.com/bintray/jcenter). Cached by [LocalM2Repository]. */ 32 | @Deprecated("JCenter service has been shut down") 33 | val JCenter = Repository("jcenter", URL("https://jcenter.bintray.com/"), snapshots = false) 34 | 35 | /** [Jitpack repository](https://jitpack.io). Cached by [LocalM2Repository]. */ 36 | @Suppress("unused") 37 | val Jitpack = Repository("jitpack", URL("https://jitpack.io/")) 38 | 39 | /** [Sonatype Oss](https://oss.sonatype.org/) repository. Cached by [LocalM2Repository]. 40 | * Most used [repository]-ies are `"releases"` and `"snapshots"`. */ 41 | @Suppress("unused") 42 | fun sonatypeOss(repository:String): Repository { 43 | val releases:Boolean 44 | val snapshots:Boolean 45 | if (repository.contains("release", ignoreCase = true)) { 46 | releases = true 47 | snapshots = false 48 | } else if (repository.contains("snapshot", ignoreCase = true)) { 49 | releases = false 50 | snapshots = true 51 | } else { 52 | releases = true 53 | snapshots = true 54 | } 55 | 56 | return Repository("sonatype-oss-$repository", URL("https://oss.sonatype.org/content/repositories/$repository/"), releases = releases, snapshots = snapshots) 57 | } 58 | 59 | /** Repositories to use by default ([MavenCentral], [LocalM2Repository]) */ 60 | val DefaultRepositories:Set = setOf(MavenCentral, LocalM2Repository) -------------------------------------------------------------------------------- /src/main/kotlin/wemi/util/Magic.kt: -------------------------------------------------------------------------------- 1 | package wemi.util 2 | 3 | import org.slf4j.LoggerFactory 4 | import java.nio.file.Path 5 | 6 | /* 7 | * File full of black magic. 8 | * No muggles allowed. 9 | */ 10 | object Magic { 11 | 12 | private val LOG = LoggerFactory.getLogger("Magic") 13 | 14 | /** 15 | * Returns the classpath file, from which given class has been loaded. 16 | * This may be a jar file, or a directory in which it resides. 17 | */ 18 | fun classpathFileOf(c:Class<*>):Path? { 19 | val name = c.name 20 | val classNameStart = name.lastIndexOf('.') 21 | val resourceName = "${if (classNameStart == -1) name else name.substring(classNameStart + 1)}.class" 22 | var path:Path = c.getResource(resourceName)?.toPath() ?: return null 23 | 24 | if (path.name == resourceName) { 25 | // Not a jar, but a file inside directory 26 | path = path.parent ?: return null // To directory 27 | if (classNameStart != -1) { 28 | // Amount of dots (package separators) is the same as the amount of steps to parent 29 | for (i in 0..classNameStart) { 30 | if (name[i] == '.') { 31 | path = path.parent ?: return null 32 | } 33 | } 34 | } // else in default directory, so the path is correct 35 | } 36 | 37 | return path 38 | } 39 | 40 | /** 41 | * .jar or folder that is a classpath entry that contains Wemi 42 | */ 43 | internal val WemiLauncherFile: Path = classpathFileOf(this.javaClass).let { 44 | val result = it ?: throw IllegalStateException("Wemi must be launched from filesystem (current URL: $it)") 45 | 46 | LOG.debug("WemiLauncherFile found at {}", result) 47 | result 48 | } 49 | 50 | /** 51 | * Class loader with which Wemi core was loaded 52 | */ 53 | internal val WemiDefaultClassLoader: ClassLoader = javaClass.classLoader 54 | 55 | /** 56 | * Transform the [fileNameWithoutExtension] into the name of class that will Kotlin compiler produce (without .class). 57 | */ 58 | internal fun transformFileNameToKotlinClassName(fileNameWithoutExtension: String): String { 59 | val sb = StringBuilder() 60 | // If file name starts with digit, _ is prepended 61 | if (fileNameWithoutExtension.isNotEmpty() && fileNameWithoutExtension[0] in '0'..'9') { 62 | sb.append('_') 63 | } 64 | // Everything is valid java identifier 65 | for (c in fileNameWithoutExtension) { 66 | if (c.isJavaIdentifierPart()) { 67 | sb.append(c) 68 | } else { 69 | sb.append("_") 70 | } 71 | } 72 | // First letter is capitalized 73 | if (sb.isNotEmpty()) { 74 | sb[0] = sb[0].toUpperCase() 75 | } 76 | // Kt is appended 77 | sb.append("Kt") 78 | return sb.toString() 79 | } 80 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/compile/CompilerFlag.kt: -------------------------------------------------------------------------------- 1 | package wemi.compile 2 | 3 | import com.esotericsoftware.jsonbeans.JsonWriter 4 | import org.slf4j.LoggerFactory 5 | import wemi.util.JsonWritable 6 | import wemi.util.writeMap 7 | import wemi.util.writeValue 8 | 9 | /** 10 | * Key for compiler flags. Each compiler may have different flags and may need different code to apply them. 11 | * This allows for unified abstraction. Do not create instances of [CompilerFlag], use the ones provided by the compiler. 12 | * 13 | * @see CompilerFlags 14 | * @param default used only as a base for modifications 15 | */ 16 | class CompilerFlag(val name: String, val description: String, val default:Type) : JsonWritable { 17 | 18 | override fun JsonWriter.write() { 19 | writeValue(name, String::class.java) 20 | } 21 | 22 | override fun toString(): String { 23 | return "$name - $description" 24 | } 25 | } 26 | 27 | private val LOG = LoggerFactory.getLogger("CompilerFlag") 28 | 29 | /** A mutable container that holds bindings of [CompilerFlag] to their values. 30 | * The standard API for setting keys with [CompilerFlags] value is: 31 | * `compilerOptions[SomeOption ] = { previousValue -> "value" }` 32 | * for example: 33 | * `compilerOptions[JavaCompilerFlags.customFlags] = { it + "-Xlint" }`. 34 | */ 35 | class CompilerFlags : JsonWritable { 36 | private val map = HashMap, Any?>() 37 | 38 | /** Set the value associated with given flag */ 39 | operator fun set(flag: CompilerFlag, value: T) { 40 | map[flag] = value 41 | } 42 | 43 | /** Get the value associated with given flag */ 44 | fun getOrDefault(flag: CompilerFlag): T { 45 | @Suppress("UNCHECKED_CAST") 46 | return map.getOrDefault(flag, flag.default) as T 47 | } 48 | 49 | /** Get the value associated with given flag */ 50 | fun getOrNull(flag: CompilerFlag): T? { 51 | @Suppress("UNCHECKED_CAST") 52 | return map[flag] as T? 53 | } 54 | 55 | fun unset(flag: CompilerFlag) { 56 | map.remove(flag) 57 | } 58 | 59 | /** @param action called if flag is set, if it is set */ 60 | fun use(flag: CompilerFlag, action: (T) -> Unit) { 61 | if (map.containsKey(flag)) { 62 | @Suppress("UNCHECKED_CAST") 63 | action(map[flag] as T) 64 | } 65 | } 66 | 67 | override fun toString(): String { 68 | val sb = StringBuilder() 69 | sb.append("{") 70 | var first = true 71 | for ((k, v) in map) { 72 | if (first) { 73 | first = false 74 | } else { 75 | sb.append(", ") 76 | } 77 | sb.append(k.name) 78 | sb.append(" -> ") 79 | sb.append(v) 80 | } 81 | sb.append("}") 82 | return sb.toString() 83 | } 84 | 85 | /** 86 | * Written out as a [Map]. 87 | */ 88 | override fun JsonWriter.write() { 89 | writeMap(CompilerFlag::class.java, null, map) 90 | } 91 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/execution/WemiTaskConfigurationProducer.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.execution 2 | 3 | import com.darkyen.wemi.intellij.settings.WemiModuleService 4 | import com.darkyen.wemi.intellij.settings.WemiModuleType 5 | import com.intellij.execution.JavaExecutionUtil 6 | import com.intellij.execution.actions.ConfigurationContext 7 | import com.intellij.execution.actions.LazyRunConfigurationProducer 8 | import com.intellij.execution.application.ApplicationConfigurationType 9 | import com.intellij.execution.configurations.ConfigurationFactory 10 | import com.intellij.openapi.module.ModuleUtilCore 11 | import com.intellij.openapi.util.Ref 12 | import com.intellij.psi.PsiElement 13 | import org.jetbrains.kotlin.idea.run.KotlinRunConfigurationProducer 14 | 15 | /** 16 | * Provides ability to right-click a file/main method/something (?) and have "run" configuration automatically 17 | * generated. 18 | */ 19 | class WemiTaskConfigurationProducer : LazyRunConfigurationProducer() { 20 | 21 | override fun getConfigurationFactory(): ConfigurationFactory { 22 | return WemiTaskConfigurationType.INSTANCE.taskConfigurationFactory 23 | } 24 | 25 | override fun isConfigurationFromContext(configuration: WemiTaskConfiguration, context: ConfigurationContext): Boolean { 26 | val psiElement = context.psiLocation ?: return false 27 | val moduleName = getWemiModuleName(psiElement) ?: return false 28 | val mainClass = getMainClassName(psiElement) ?: return false 29 | return configuration.options.tasks == buildRunMainTaskForClassName(moduleName, mainClass) 30 | } 31 | 32 | private fun buildRunMainTaskForClassName(moduleName:String, className:String):List> { 33 | return listOf(arrayOf("$moduleName/runMain", className)) 34 | } 35 | 36 | private fun getWemiModuleName(from:PsiElement):String? { 37 | val moduleService = ModuleUtilCore.findModuleForPsiElement(from)?.getService(WemiModuleService::class.java) ?: return null 38 | if (moduleService.wemiModuleType == WemiModuleType.PROJECT) { 39 | return moduleService.wemiProjectName 40 | } 41 | return null 42 | } 43 | 44 | private fun getMainClassName(from:PsiElement):String? { 45 | // Java class 46 | ApplicationConfigurationType.getMainClass(from) 47 | ?.let { return JavaExecutionUtil.getRuntimeQualifiedName(it) } 48 | 49 | // Kotlin file or class 50 | KotlinRunConfigurationProducer.getEntryPointContainer(from) 51 | ?.let { KotlinRunConfigurationProducer.getStartClassFqName(it) } 52 | ?.let { return it } 53 | 54 | // Unknown 55 | return null 56 | } 57 | 58 | override fun setupConfigurationFromContext(configuration: WemiTaskConfiguration, context: ConfigurationContext, sourceElement: Ref): Boolean { 59 | val psiElement = sourceElement.get() ?: return false 60 | val moduleName = getWemiModuleName(psiElement) ?: return false 61 | val mainClass = getMainClassName(psiElement) ?: return false 62 | configuration.options.tasks = buildRunMainTaskForClassName(moduleName, mainClass) 63 | configuration.useSuggestedName() 64 | return true 65 | } 66 | 67 | 68 | } -------------------------------------------------------------------------------- /src/test/kotlin/wemi/boot/TaskParserTests.kt: -------------------------------------------------------------------------------- 1 | package wemi.boot 2 | 3 | import org.junit.jupiter.api.Assertions.* 4 | import org.junit.jupiter.api.Test 5 | 6 | /** 7 | * 8 | */ 9 | class TaskParserTests { 10 | 11 | private val Whitespace = Regex("\\s+") 12 | 13 | private fun assertEquals(line:String, vararg tasks:Task) { 14 | val parsed = TaskParser.PartitionedLine(line, false) 15 | assertArrayEquals(tasks, parsed.tasks.toTypedArray()) 16 | 17 | if (line.indexOf('"') == -1) { 18 | // Test it as if it came from command line 19 | // (simple test does not handle quotes yet) 20 | val parsedFromCommandLine = TaskParser.PartitionedLine(line, false) 21 | assertArrayEquals(tasks, parsedFromCommandLine.tasks.toTypedArray()) 22 | } 23 | } 24 | 25 | private fun assertEqualsMulti(line: String, vararg tasks: Task) { 26 | assertEquals(line, *tasks) 27 | val twoTasks = (tasks.toList() + tasks.toList()).toTypedArray() 28 | assertEquals("$line;$line", *twoTasks) 29 | assertEquals("$line ;$line", *twoTasks) 30 | assertEquals("$line; $line", *twoTasks) 31 | assertEquals("$line;$line;$line", *(tasks.toList() + tasks.toList() + tasks.toList()).toTypedArray()) 32 | } 33 | 34 | @Test 35 | fun testTaskParsing() { 36 | assertEqualsMulti("key", Task(null, emptyList(),"key", emptyArray())) 37 | assertEqualsMulti("project/key", Task("project", emptyList(),"key", emptyArray())) 38 | assertEqualsMulti("conf:key", Task(null, listOf("conf"),"key", emptyArray())) 39 | assertEqualsMulti("conf1:conf2:key", Task(null, listOf("conf1", "conf2"),"key", emptyArray())) 40 | assertEqualsMulti("project/conf1:conf2:key", Task("project", listOf("conf1", "conf2"),"key", emptyArray())) 41 | assertEqualsMulti("project / conf1 : conf2:key ", Task("project", listOf("conf1", "conf2"),"key", emptyArray())) 42 | assertEqualsMulti("project/conf1:conf2:key", Task("project", listOf("conf1", "conf2"),"key", emptyArray())) 43 | assertEqualsMulti("key free", Task(null, emptyList(),"key", arrayOf("" to "free"))) 44 | assertEqualsMulti("key free free", Task(null, emptyList(),"key", arrayOf("" to "free", "" to "free"))) 45 | assertEqualsMulti("key free1 free2", Task(null, emptyList(),"key", arrayOf("" to "free1", "" to "free2"))) 46 | assertEqualsMulti("key k=f", Task(null, emptyList(),"key", arrayOf("k" to "f"))) 47 | assertEqualsMulti("key k = f ", Task(null, emptyList(),"key", arrayOf("k" to "f"))) 48 | assertEqualsMulti("key k=f k=f", Task(null, emptyList(),"key", arrayOf("k" to "f", "k" to "f"))) 49 | assertEqualsMulti("key free k=f", Task(null, emptyList(),"key", arrayOf("" to "free", "k" to "f"))) 50 | assertEqualsMulti("key k=f free", Task(null, emptyList(),"key", arrayOf("k" to "f", "" to "free"))) 51 | assertEqualsMulti("project / conf1 : conf2:key k=f free free2 kk=ff", 52 | Task("project", listOf("conf1", "conf2"),"key", arrayOf("k" to "f", "" to "free", "" to "free2", "kk" to "ff"))) 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/util/Streams.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.util 2 | 3 | import com.intellij.execution.process.ProcessIOExecutorService 4 | import com.intellij.openapi.util.SystemInfo 5 | import com.intellij.openapi.util.text.StringUtil 6 | import com.intellij.openapi.vfs.VirtualFile 7 | import com.intellij.openapi.vfs.newvfs.ArchiveFileSystem 8 | import com.intellij.util.ConcurrencyUtil 9 | import org.slf4j.LoggerFactory 10 | import java.nio.file.Path 11 | import java.nio.file.Paths 12 | import java.util.concurrent.Semaphore 13 | import java.util.concurrent.TimeUnit 14 | 15 | private val LOG = LoggerFactory.getLogger("Streams") 16 | 17 | /** Collect all output of the process, log error. If the process does not quit until timeout, kill it. */ 18 | fun Process.collectOutputLineAndKill(timeout:Long, timeoutUnit: TimeUnit, useStdOut:Boolean):String? { 19 | var result:String? = null 20 | val resultSemaphore = Semaphore(0, false) 21 | 22 | ProcessIOExecutorService.INSTANCE.submit { 23 | try { 24 | ConcurrencyUtil.runUnderThreadName("collectOutputAndKill-output($this)") { 25 | (if (useStdOut) inputStream else errorStream).reader(Charsets.UTF_8).useLines { lines -> 26 | result = lines.firstOrNull() 27 | } 28 | } 29 | } finally { 30 | resultSemaphore.release() 31 | } 32 | } 33 | 34 | ProcessIOExecutorService.INSTANCE.submit { 35 | ConcurrencyUtil.runUnderThreadName("collectOutputAndKill-error($this)") { 36 | (if (useStdOut) errorStream else inputStream).reader(Charsets.UTF_8).useLines { lines -> 37 | for (s in lines) { 38 | if (s.isNotBlank()) { 39 | LOG.warn("Process {} stderr: {}", this, s) 40 | } 41 | } 42 | } 43 | } 44 | } 45 | 46 | if (!waitFor(timeout, timeoutUnit)) { 47 | destroy() 48 | if (!waitFor(5, TimeUnit.SECONDS)) { 49 | destroyForcibly() 50 | } 51 | } 52 | if (!resultSemaphore.tryAcquire(10, TimeUnit.SECONDS)) { 53 | LOG.error("collectOutputAndKill {} - process output still open", this) 54 | } 55 | 56 | if (!isAlive && exitValue() != 0) { 57 | LOG.warn("collectOutputAndKill {} - non-zero exit value ({})", this, exitValue()) 58 | return null 59 | } 60 | 61 | return result 62 | } 63 | 64 | fun VirtualFile?.toPath(): Path? { 65 | val localPath = this?.let { wrappedFile -> 66 | val wrapFileSystem = wrappedFile.fileSystem 67 | if (wrapFileSystem is ArchiveFileSystem) { 68 | wrapFileSystem.getLocalByEntry(wrappedFile) 69 | } else { 70 | wrappedFile 71 | } 72 | } 73 | 74 | if (localPath?.isInLocalFileSystem != true) { 75 | return null 76 | } 77 | 78 | // Based on LocalFileSystemBase.java 79 | var path = localPath.path 80 | if (StringUtil.endsWithChar(path, ':') && path.length == 2 && SystemInfo.isWindows) { 81 | path += "/" 82 | } 83 | 84 | return Paths.get(path) 85 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/util/Sdks.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.util 2 | 3 | import com.intellij.openapi.projectRoots.* 4 | import com.intellij.openapi.projectRoots.impl.SdkVersionUtil 5 | import com.intellij.pom.java.LanguageLevel 6 | import org.jetbrains.kotlin.idea.util.projectStructure.version 7 | import java.nio.file.Files 8 | import java.nio.file.Path 9 | import java.nio.file.Paths 10 | 11 | const val MIN_JAVA_VERSION_FOR_WEMI = 8 12 | val MIN_JAVA_SDK_VERSION_FOR_WEMI = JavaSdkVersion.JDK_1_8 13 | const val MAX_JAVA_VERSION_FOR_WEMI_HINT = 13 14 | 15 | private fun getJavaSdkVersion(sdk:Sdk):JavaSdkVersion? { 16 | val jdkType = sdk.sdkType as? JavaSdkType ?: return null 17 | if (jdkType is JavaSdk) { 18 | jdkType.getVersion(sdk)?.let { return it } 19 | } 20 | 21 | val jdkJavaVersion = SdkVersionUtil.getJdkVersionInfo(sdk.homePath ?: return null)?.version ?: return null 22 | return JavaSdkVersion.fromJavaVersion(jdkJavaVersion) 23 | } 24 | 25 | fun getJavaExecutable(sdk:Sdk):Path? { 26 | val jdkType = sdk.sdkType as? JavaSdkType 27 | if (jdkType != null) { 28 | jdkType.getVMExecutablePath(sdk)?.let { return Paths.get(it) } 29 | jdkType.getBinPath(sdk)?.let { bin -> 30 | findExistingFileFromAlternatives(Paths.get(bin), "java", "java.exe")?.let { return it } 31 | } 32 | } 33 | 34 | return getJavaExecutable(sdk.homePath) 35 | } 36 | 37 | fun getJavaExecutable(jreHomeOrBin:String?):Path? { 38 | return findExistingFileFromAlternatives(Paths.get(jreHomeOrBin ?: return null), 39 | "bin/java", "bin/java.exe", "java", "java.exe", "jre/bin/java", "jre/bin/java.exe") 40 | } 41 | 42 | /** Get list of SDKs under which Wemi can run. */ 43 | fun getWemiCompatibleSdkList(): List { 44 | return ProjectJdkTable.getInstance().allJdks.filter { sdk -> 45 | val javaVersion = getJavaSdkVersion(sdk) ?: return@filter false 46 | sdk.homePath != null && javaVersion.isAtLeast(MIN_JAVA_SDK_VERSION_FOR_WEMI) 47 | } // Intentionally allowing JREs 48 | } 49 | 50 | fun getWemiCompatibleSdk(version:JavaSdkVersion? = null):Sdk? { 51 | val list = ArrayList(getWemiCompatibleSdkList()) 52 | list.sortBy { it.version?.ordinal ?: -1 } 53 | 54 | val targetVersion = version ?: JavaSdkVersion.fromLanguageLevel(LanguageLevel.HIGHEST) 55 | 56 | // Find first version that is same or smallest larger. If no such version, return the most recent version. 57 | return list.find { it.version?.isAtLeast(targetVersion) ?: false } ?: list.lastOrNull() 58 | } 59 | 60 | fun createWemiCompatibleSdk(javaHome: Path):Sdk { 61 | var home = javaHome 62 | var jdk = JdkUtil.checkForJdk(home.toString()) 63 | 64 | // Sometimes JRE is inside JDK 65 | if (!jdk && JdkUtil.checkForJdk(home.parent.toString())) { 66 | home = home.parent 67 | jdk = true 68 | } 69 | 70 | val sdk = JavaSdk.getInstance().createJdk(home.normalize().fileName.toString(), home.toString(), jdk) 71 | ProjectJdkTable.getInstance().addJdk(sdk) 72 | return sdk 73 | } 74 | 75 | 76 | private fun findExistingFileFromAlternatives(base:Path, vararg alternatives:String): Path? { 77 | for (s in alternatives) { 78 | val f = base.resolve(s) 79 | if (Files.exists(f)) { 80 | return f.toAbsolutePath() 81 | } 82 | } 83 | return null 84 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/util/Failable.kt: -------------------------------------------------------------------------------- 1 | package wemi.util 2 | 3 | @Suppress("unused") 4 | /** 5 | * Represents a value that may have failed to be computed. 6 | * Similar to Either in other languages. 7 | * 8 | * @param successful if [value] contains the successful result, otherwise [failure] contains reason why not 9 | */ 10 | class Failable 11 | private constructor(val successful: Boolean, val value: Success?, val failure: Failure?) { 12 | 13 | /** Run [action] if this [Failable] has been successful. */ 14 | inline fun success(action: (Success) -> Unit) { 15 | if (successful) { 16 | @Suppress("UNCHECKED_CAST") 17 | action(value as Success) 18 | } 19 | } 20 | 21 | /** Run [action] if this [Failable] has not been successful. */ 22 | inline fun failure(action: (Failure) -> Unit) { 23 | if (!successful) { 24 | @Suppress("UNCHECKED_CAST") 25 | action(failure as Failure) 26 | } 27 | } 28 | 29 | /** Run [successAction] if this [Failable] has been successful and [failureAction] if not. */ 30 | inline fun use(successAction: (Success) -> Result, failureAction: (Failure) -> Result): Result { 31 | return if (successful) { 32 | @Suppress("UNCHECKED_CAST") 33 | successAction(value as Success) 34 | } else { 35 | @Suppress("UNCHECKED_CAST") 36 | failureAction(failure as Failure) 37 | } 38 | } 39 | 40 | inline fun map(operation: (Success) -> Result):Failable { 41 | if (successful) { 42 | @Suppress("UNCHECKED_CAST") 43 | return success(operation(value as Success)) 44 | } else { 45 | return this.reFail() 46 | } 47 | } 48 | 49 | inline fun fold(operation: (Success) -> Failable):Failable { 50 | if (successful) { 51 | @Suppress("UNCHECKED_CAST") 52 | return operation(value as Success) 53 | } else { 54 | return this.reFail() 55 | } 56 | } 57 | 58 | /** Re-cast this as a [Failable] with different [Success] type. */ 59 | fun reFail():Failable { 60 | @Suppress("UNCHECKED_CAST") 61 | return this as Failable 62 | } 63 | 64 | companion object { 65 | /** Create successful [Failable] with [success] as its result. */ 66 | fun success(success: Success): Failable { 67 | return Failable(true, success, null) 68 | } 69 | 70 | /** Create failed [Failable] with [failure] as its result. */ 71 | fun failure(failure: Failure): Failable { 72 | return Failable(false, null, failure) 73 | } 74 | 75 | /** 76 | * Create [Failable] that is successful if [success] is not null. 77 | * Otherwise use [failure] to create failed failable. 78 | */ 79 | fun failNull(success: Success?, failure: Failure): Failable { 80 | return if (success == null) { 81 | Failable(false, null, failure) 82 | } else { 83 | Failable(true, success, null) 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/util/Failable.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.util 2 | 3 | @Suppress("unused") 4 | /** 5 | * Represents a value that may have failed to be computed. 6 | * Similar to Either in other languages. 7 | * 8 | * @param successful if [value] contains the successful result, otherwise [failure] contains reason why not 9 | */ 10 | class Failable 11 | private constructor(val successful: Boolean, val value: Success?, val failure: Failure?) { 12 | 13 | /** Run [action] if this [Failable] has been successful. */ 14 | inline fun success(action: (Success) -> Unit) { 15 | if (successful) { 16 | @Suppress("UNCHECKED_CAST") 17 | action(value as Success) 18 | } 19 | } 20 | 21 | /** Run [action] if this [Failable] has not been successful. */ 22 | inline fun failure(action: (Failure) -> Unit) { 23 | if (!successful) { 24 | @Suppress("UNCHECKED_CAST") 25 | action(failure as Failure) 26 | } 27 | } 28 | 29 | /** Run [successAction] if this [Failable] has been successful and [failureAction] if not. */ 30 | inline fun use(successAction: (Success) -> Result, failureAction: (Failure) -> Result): Result { 31 | return if (successful) { 32 | @Suppress("UNCHECKED_CAST") 33 | successAction(value as Success) 34 | } else { 35 | @Suppress("UNCHECKED_CAST") 36 | failureAction(failure as Failure) 37 | } 38 | } 39 | 40 | inline fun map(operation: (Success) -> Result):Failable { 41 | if (successful) { 42 | @Suppress("UNCHECKED_CAST") 43 | return success(operation(value as Success)) 44 | } else { 45 | return this.reFail() 46 | } 47 | } 48 | 49 | inline fun fold(operation: (Success) -> Failable):Failable { 50 | if (successful) { 51 | @Suppress("UNCHECKED_CAST") 52 | return operation(value as Success) 53 | } else { 54 | return this.reFail() 55 | } 56 | } 57 | 58 | /** Re-cast this as a [Failable] with different [Success] type. */ 59 | fun reFail():Failable { 60 | @Suppress("UNCHECKED_CAST") 61 | return this as Failable 62 | } 63 | 64 | companion object { 65 | /** Create successful [Failable] with [success] as its result. */ 66 | fun success(success: Success): Failable { 67 | return Failable(true, success, null) 68 | } 69 | 70 | /** Create failed [Failable] with [failure] as its result. */ 71 | fun failure(failure: Failure): Failable { 72 | return Failable(false, null, failure) 73 | } 74 | 75 | /** 76 | * Create [Failable] that is successful if [success] is not null. 77 | * Otherwise use [failure] to create failed failable. 78 | */ 79 | fun failNull(success: Success?, failure: Failure): Failable { 80 | return if (success == null) { 81 | Failable(false, null, failure) 82 | } else { 83 | Failable(true, success, null) 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /plugins/jvm-hotswap/src/main/kotlin/wemiplugin/jvmhotswap/FileTreeUtil.kt: -------------------------------------------------------------------------------- 1 | package wemiplugin.jvmhotswap 2 | 3 | import org.slf4j.LoggerFactory 4 | import wemi.util.LocatedPath 5 | import wemi.util.isDirectory 6 | import java.nio.file.Files 7 | import java.nio.file.Path 8 | import java.security.MessageDigest 9 | import java.util.* 10 | 11 | private val LOG = LoggerFactory.getLogger("FileTreeWatcher") 12 | 13 | typealias FileSnapshot = Map> 14 | typealias MutableFileSnapshot = MutableMap> 15 | 16 | /** 17 | * Compute digest of given file. 18 | * Returns null if file fails to be read or digested, for any reason. 19 | */ 20 | private fun MessageDigest.digest(path:Path):ByteArray? { 21 | try { 22 | return Files.newInputStream(path).use { 23 | val buffer = ByteArray(4096) 24 | while (true) { 25 | val read = it.read(buffer) 26 | if (read <= 0) { 27 | break 28 | } 29 | this@digest.update(buffer, 0, read) 30 | } 31 | 32 | this@digest.digest() 33 | } 34 | } catch (e:Exception) { 35 | LOG.debug("Failed to digest {}", path, e) 36 | return null 37 | } 38 | } 39 | 40 | /** 41 | * Used by [snapshotFiles]. 42 | */ 43 | private fun snapshotFile(digest:MessageDigest, result:MutableFileSnapshot, isIncluded: (LocatedPath) -> Boolean, path:LocatedPath) { 44 | val pathClasspathName = path.path 45 | if (result.containsKey(pathClasspathName) || !isIncluded(path)) { 46 | // Already processed, probably nested roots or symlinks, or filtered out 47 | return 48 | } 49 | 50 | val file = path.file 51 | if (file.isDirectory()) { 52 | Files.list(file).forEachOrdered { 53 | snapshotFile(digest, result, isIncluded, LocatedPath(path.root, it)) 54 | } 55 | } else { 56 | result[pathClasspathName] = file to (digest.digest(file) ?: return /* Ignore weird files */) 57 | }// else dunno, we don't care 58 | } 59 | 60 | /** 61 | * Creates a snapshot of a file tree, starting at given roots, following links. 62 | * 63 | * Result is a map from file path to its digest. Digest is null for directories, not null for files. 64 | * Files that are not readable/traversable are silently ignored and not included in the result. 65 | * 66 | * @param roots files or directories (that are searched recursively for files) to be included in the report 67 | * @param isIncluded function to filter out files, that should not be included in the report 68 | */ 69 | fun snapshotFiles(roots:Collection, isIncluded:(LocatedPath) -> Boolean):FileSnapshot { 70 | val digest = MessageDigest.getInstance("MD5")!! // Fast digest, guaranteed to be present 71 | val result = HashMap>(roots.size + roots.size / 2) 72 | 73 | for (root in roots) { 74 | snapshotFile(digest, result, isIncluded, root) 75 | } 76 | 77 | return result 78 | } 79 | 80 | fun snapshotsAreEqual(first:FileSnapshot, second:FileSnapshot):Boolean { 81 | if (first.size != second.size) { 82 | return false 83 | } 84 | 85 | for ((key, value) in first) { 86 | if (!second.containsKey(key)) { 87 | return false 88 | } 89 | // Not using MessageDigest.isEqual because we don't need time-constant comparisons 90 | if (!Arrays.equals(value.second, second[key]?.second)) { 91 | return false 92 | } 93 | } 94 | 95 | return true 96 | } 97 | 98 | -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/execution/WemiModuleBuildTaskRunner.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.execution 2 | 3 | import com.darkyen.wemi.intellij.settings.WemiModuleService 4 | import com.darkyen.wemi.intellij.settings.WemiModuleType 5 | import com.darkyen.wemi.intellij.settings.isWemiModule 6 | import com.intellij.execution.RunManager 7 | import com.intellij.execution.executors.DefaultRunExecutor 8 | import com.intellij.execution.impl.RunConfigurationBeforeRunProvider 9 | import com.intellij.execution.runners.ExecutionEnvironment 10 | import com.intellij.openapi.project.Project 11 | import com.intellij.task.ModuleBuildTask 12 | import com.intellij.task.ProjectTask 13 | import com.intellij.task.ProjectTaskContext 14 | import com.intellij.task.ProjectTaskRunner 15 | import org.jetbrains.concurrency.Promise 16 | import org.jetbrains.concurrency.resolvedPromise 17 | 18 | /** 19 | * Catches requests to build modules/project and handles them. 20 | * These requests come from "make" button in top, right-click on module->build, etc. 21 | */ 22 | class WemiModuleBuildTaskRunner : ProjectTaskRunner() { 23 | 24 | override fun run(project: Project, context: ProjectTaskContext, vararg tasks: ProjectTask?): Promise { 25 | var compileBuildScript = false 26 | val projectsToCompile = ArrayList() 27 | 28 | for (task in tasks) { 29 | if (task is ModuleBuildTask) { 30 | val moduleComponent = task.module.getService(WemiModuleService::class.java) ?: throw AssertionError("Module ${task.module} does not have WemiModuleComponent") 31 | @Suppress("UNUSED_VARIABLE") 32 | val exhaust:Any = when (moduleComponent.wemiModuleType) { 33 | WemiModuleType.BUILD_SCRIPT -> compileBuildScript = true 34 | WemiModuleType.PROJECT -> projectsToCompile.add(moduleComponent.wemiProjectName) 35 | WemiModuleType.NON_WEMI_MODULE -> throw AssertionError("Module ${task.module} is not a Wemi module") 36 | } 37 | } else { 38 | throw AssertionError("WemiModuleBuildTaskRunner can't run $task") 39 | } 40 | } 41 | 42 | val configuration = WemiTaskConfigurationType.INSTANCE.taskConfigurationFactory.createTemplateConfiguration(project) 43 | if (projectsToCompile.isEmpty() && !compileBuildScript) { 44 | return resolvedPromise(object : Result { 45 | override fun hasErrors(): Boolean = false 46 | 47 | override fun isAborted(): Boolean = false 48 | }) 49 | } 50 | configuration.options.tasks = projectsToCompile.map { arrayOf("$it/compile") } 51 | configuration.useSuggestedName() 52 | 53 | val runner = WemiProgramRunner.instance() 54 | val executor = DefaultRunExecutor.getRunExecutorInstance() 55 | val environment = ExecutionEnvironment(executor, runner, RunManager.getInstance(project).createConfiguration(configuration, configuration.factory), project) 56 | environment.assignNewExecutionId() 57 | 58 | val result = RunConfigurationBeforeRunProvider.doRunTask(executor.id, environment, runner) 59 | return resolvedPromise(object : Result { 60 | override fun hasErrors(): Boolean = !result 61 | 62 | override fun isAborted(): Boolean = false 63 | }) 64 | } 65 | 66 | override fun canRun(projectTask: ProjectTask): Boolean { 67 | return when (projectTask) { 68 | is ModuleBuildTask -> projectTask.module.isWemiModule() 69 | else -> false 70 | } 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/util/SessionState.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.util 2 | 3 | import com.intellij.execution.process.ProcessIOExecutorService 4 | import com.intellij.openapi.progress.ProcessCanceledException 5 | import com.intellij.openapi.progress.ProgressIndicator 6 | import com.intellij.util.ConcurrencyUtil 7 | import java.util.concurrent.atomic.AtomicInteger 8 | 9 | /** 10 | * Thread-safe class which consolidates information about cancellation from two different sources, 11 | * that is from [ProgressIndicator] (which is polled) and from manual calls. 12 | */ 13 | class SessionState(private val indicator: ProgressIndicator) { 14 | 15 | private val state = AtomicInteger(State.RUNNING.ordinal) 16 | var exitCode:Int = Int.MIN_VALUE 17 | private set 18 | 19 | private val indicatorLock = Object() 20 | private val indicatorPoller = ProcessIOExecutorService.INSTANCE.submit { 21 | ConcurrencyUtil.runUnderThreadName("Wemi SessionCancellation ProgressIndicator checker") { 22 | while (state.get() == State.RUNNING.ordinal) { 23 | if (synchronized(indicatorLock) { indicator.isCanceled }) { 24 | cancel(false) 25 | break 26 | } 27 | 28 | try { 29 | Thread.sleep(100) 30 | } catch (e:InterruptedException) { 31 | break 32 | } 33 | } 34 | } 35 | } 36 | 37 | private val listeners = ArrayList() 38 | 39 | fun addListener(listener:Listener) { 40 | synchronized(listeners) { 41 | assert(listener !in listeners) 42 | listeners.add(listener) 43 | } 44 | } 45 | 46 | fun removeListener(listener:Listener) { 47 | synchronized(listeners) { 48 | listeners.remove(listener) 49 | } 50 | } 51 | 52 | fun cancel(force:Boolean) { 53 | val newState = if (force) State.CANCELLED_FORCE else State.CANCELLED 54 | 55 | // Can really cancel only if running or upgrading severity of cancellation 56 | if (state.compareAndSet(State.RUNNING.ordinal, newState.ordinal) 57 | || (force && state.compareAndSet(State.CANCELLED.ordinal, State.CANCELLED_FORCE.ordinal))) { 58 | // Cancelled! 59 | 60 | // Update indicator 61 | synchronized(indicatorLock) { 62 | if (!indicator.isCanceled) { 63 | indicator.cancel() 64 | } 65 | } 66 | 67 | // Notify listeners 68 | notifyStateChanged(newState) 69 | } 70 | } 71 | 72 | private fun notifyStateChanged(newState:State) { 73 | synchronized(listeners) { 74 | for (listener in listeners) { 75 | listener.sessionStateChange(newState) 76 | } 77 | } 78 | } 79 | 80 | fun finish(code:Int) { 81 | if (state.compareAndSet(State.RUNNING.ordinal, State.FINISHED.ordinal)) { 82 | exitCode = code 83 | notifyStateChanged(State.FINISHED) 84 | } else if (state.compareAndSet(State.CANCELLED.ordinal, State.FINISHED_CANCELLED.ordinal) 85 | || state.compareAndSet(State.CANCELLED_FORCE.ordinal, State.FINISHED_CANCELLED.ordinal)) { 86 | exitCode = code 87 | notifyStateChanged(State.FINISHED_CANCELLED) 88 | } 89 | } 90 | 91 | fun isCancelled():Boolean { 92 | val state = state.get() 93 | return state == State.CANCELLED.ordinal || state == State.CANCELLED_FORCE.ordinal 94 | } 95 | 96 | /** @throws ProcessCanceledException if cancelled */ 97 | fun checkCancelled() { 98 | if (isCancelled()) { 99 | throw ProcessCanceledException() 100 | } 101 | } 102 | 103 | enum class State { 104 | RUNNING, 105 | CANCELLED, 106 | CANCELLED_FORCE, 107 | FINISHED_CANCELLED, 108 | FINISHED 109 | } 110 | 111 | interface Listener { 112 | fun sessionStateChange(newState:State) 113 | } 114 | } -------------------------------------------------------------------------------- /plugins/dokka/src/main/kotlin/wemiplugin/dokka/Options.kt: -------------------------------------------------------------------------------- 1 | package wemiplugin.dokka 2 | 3 | import Path 4 | import wemi.util.div 5 | import java.net.URL 6 | 7 | /** See https://kotlin.github.io/dokka/1.4.30/user_guide/introduction/ for more info. */ 8 | data class DokkaConfig( 9 | val moduleName: String = "root", 10 | val moduleVersion: String? = null, 11 | val offlineMode: Boolean = false, 12 | val sourceSets: List = emptyList(), 13 | /** Plugins to automatically add to pluginsClasspath. */ 14 | val plugins:List = listOf(DokkaPlugin.HTML), 15 | val pluginsClasspath: List = emptyList(), 16 | val pluginsConfiguration: List = emptyList(), 17 | val modules: List = emptyList(), 18 | val failOnWarning: Boolean = false, 19 | val delayTemplateSubstitution: Boolean = false, 20 | val suppressObviousFunctions: Boolean = true 21 | ) 22 | 23 | enum class DokkaPlugin(val artifactName:String) { 24 | HTML("dokka-base"), 25 | MARKDOWN("gfm-plugin"), 26 | JEKYLL("jekyll-plugin"), 27 | JAVADOC("javadoc-plugin"), 28 | KOTLIN_AS_JAVA("kotlin-as-java-plugin"), 29 | ANDROID_DOC("android-documentation-plugin"), 30 | } 31 | 32 | enum class PluginConfigSerializationFormat { 33 | JSON, XML 34 | } 35 | 36 | data class PluginConfig( 37 | val fqPluginName: String, 38 | val serializationFormat: PluginConfigSerializationFormat, 39 | val values: String 40 | ) 41 | 42 | data class DokkaSourceSet( 43 | val displayName: String = "JVM", 44 | val sourceSetID: String = displayName, 45 | val classpath: List = emptyList(), 46 | val sourceRoots: Set = emptySet(), 47 | val dependentSourceSets: Set = emptySet(), 48 | val samples: Set = emptySet(), 49 | val includes: Set = emptySet(), 50 | val includeNonPublic: Boolean = false, 51 | val reportUndocumented: Boolean = false, 52 | val skipEmptyPackages: Boolean = true, 53 | val skipDeprecated: Boolean = false, 54 | val jdkVersion: Int = 8, 55 | val sourceLinks: Set = emptySet(), 56 | val perPackageOptions: List = emptyList(), 57 | val externalDocumentationLinks: Set = emptySet(), 58 | val languageVersion: String? = null, 59 | val apiVersion: String? = null, 60 | val noStdlibLink: Boolean = false, 61 | val noJdkLink: Boolean = false, 62 | val suppressedFiles: Set = emptySet(), 63 | val analysisPlatform: DokkaAnalysisPlatform = DokkaAnalysisPlatform.JVM 64 | ) 65 | 66 | enum class DokkaAnalysisPlatform { 67 | JVM, 68 | JS, 69 | NATIVE, 70 | COMMON; 71 | 72 | companion object { 73 | fun fromString(key: String): DokkaAnalysisPlatform? { 74 | return when (key.toLowerCase()) { 75 | "jvm", "androidjvm", "android" -> JVM 76 | "js" -> JS 77 | "native" -> NATIVE 78 | "common", "metadata" -> COMMON 79 | else -> null 80 | } 81 | } 82 | } 83 | } 84 | 85 | 86 | data class DokkaModuleDesc( 87 | val name: String, 88 | val relativePathToOutputDirectory: Path, 89 | val includes: Set, 90 | val sourceOutputDirectory: Path 91 | ) 92 | 93 | data class SourceLinkDef( 94 | val localDirectory: String, 95 | val remoteUrl: URL, 96 | val remoteLineSuffix: String? 97 | ) 98 | 99 | data class PackageOptions( 100 | val matchingRegex: String, 101 | val includeNonPublic: Boolean = true, 102 | val reportUndocumented: Boolean? = null, 103 | val skipDeprecated: Boolean = false, 104 | val suppress: Boolean = false 105 | ) 106 | 107 | data class ExternalDocumentationLink( 108 | val url: URL, 109 | val packageListUrl: URL = url / "package-list" 110 | ) 111 | -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/esotericsoftware/tablelayout/swing/SwingToolkit.java: -------------------------------------------------------------------------------- 1 | 2 | package com.esotericsoftware.tablelayout.swing; 3 | 4 | import com.esotericsoftware.tablelayout.BaseTableLayout.Debug; 5 | import com.esotericsoftware.tablelayout.Cell; 6 | import com.esotericsoftware.tablelayout.Toolkit; 7 | 8 | import java.awt.Component; 9 | import java.awt.Container; 10 | import java.awt.EventQueue; 11 | import java.util.ArrayList; 12 | import java.util.Timer; 13 | import java.util.TimerTask; 14 | 15 | import javax.swing.JScrollPane; 16 | 17 | class SwingToolkit extends Toolkit { 18 | static Timer timer; 19 | static ArrayList debugLayouts = new ArrayList(0); 20 | 21 | public Cell obtainCell (TableLayout layout) { 22 | Cell cell = new Cell(); 23 | cell.setLayout(layout); 24 | return cell; 25 | } 26 | 27 | public void freeCell (Cell cell) { 28 | } 29 | 30 | public void addChild (Component parent, Component child) { 31 | if (parent instanceof JScrollPane) 32 | ((JScrollPane)parent).setViewportView(child); 33 | else 34 | ((Container)parent).add(child); 35 | } 36 | 37 | public void removeChild (Component parent, Component child) { 38 | ((Container)parent).remove(child); 39 | } 40 | 41 | public float getMinWidth (Component widget) { 42 | return widget.getMinimumSize().width; 43 | } 44 | 45 | public float getMinHeight (Component widget) { 46 | return widget.getMinimumSize().height; 47 | } 48 | 49 | public float getPrefWidth (Component widget) { 50 | return widget.getPreferredSize().width; 51 | } 52 | 53 | public float getPrefHeight (Component widget) { 54 | return widget.getPreferredSize().height; 55 | } 56 | 57 | public float getMaxWidth (Component widget) { 58 | return widget.getMaximumSize().width; 59 | } 60 | 61 | public float getMaxHeight (Component widget) { 62 | return widget.getMaximumSize().height; 63 | } 64 | 65 | public float getWidth (Component widget) { 66 | return widget.getWidth(); 67 | } 68 | 69 | public float getHeight (Component widget) { 70 | return widget.getHeight(); 71 | } 72 | 73 | public void clearDebugRectangles (TableLayout layout) { 74 | if (layout.debugRects != null) debugLayouts.remove(layout); 75 | layout.debugRects = null; 76 | } 77 | 78 | public void addDebugRectangle (TableLayout layout, Debug type, float x, float y, float w, float h) { 79 | if (layout.debugRects == null) { 80 | layout.debugRects = new ArrayList(); 81 | debugLayouts.add(layout); 82 | } 83 | layout.debugRects.add(new DebugRect(type, x, y, w, h)); 84 | } 85 | 86 | static void startDebugTimer () { 87 | if (timer != null) return; 88 | timer = new Timer("TableLayout Debug", true); 89 | timer.schedule(newDebugTask(), 100); 90 | } 91 | 92 | static TimerTask newDebugTask () { 93 | return new TimerTask() { 94 | public void run () { 95 | if (!EventQueue.isDispatchThread()) { 96 | EventQueue.invokeLater(this); 97 | return; 98 | } 99 | for (TableLayout layout : debugLayouts) 100 | layout.drawDebug(); 101 | timer.schedule(newDebugTask(), 250); 102 | } 103 | }; 104 | } 105 | 106 | static class DebugRect { 107 | final Debug type; 108 | final int x, y, width, height; 109 | 110 | public DebugRect (Debug type, float x, float y, float width, float height) { 111 | this.x = (int)x; 112 | this.y = (int)y; 113 | this.width = (int)(width - 1); 114 | this.height = (int)(height - 1); 115 | this.type = type; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/run/System.kt: -------------------------------------------------------------------------------- 1 | package wemi.run 2 | 3 | import org.slf4j.LoggerFactory 4 | import wemi.boot.CLI 5 | import wemi.boot.WemiRootFolder 6 | import java.nio.charset.Charset 7 | import java.nio.file.Path 8 | import java.util.concurrent.Callable 9 | import java.util.concurrent.ForkJoinPool 10 | import java.util.concurrent.TimeUnit 11 | import java.util.concurrent.TimeoutException 12 | 13 | private val LOG = LoggerFactory.getLogger("System") 14 | 15 | /** 16 | * Create and run a process from [command], wait for its completion and return its output stream as [String]. 17 | * The standard error and input stream are inherited, to allow interactive processes and to log any errors out. 18 | * The process is run through [wemi.boot.CLI.forwardSignalsTo], so it is user interruptible. 19 | * 20 | * @param command the command and its parameters, see [ProcessBuilder.command] for more info 21 | * @param workingDirectory of the process, [WemiRootFolder] by default 22 | * @param timeoutMs if the process takes more than this many milliseconds to complete, consider it frozen and forcefully kill it. No timeout by default 23 | * @param collectOutput if the function should collect the output (`true` by default). If `false`, the output is inherited, like the other two streams, and the result is considered `null` 24 | * @param outputCharset the charset of the output stream, when it is being collected. [Charsets.UTF_8] by default 25 | * @param onFail callback called when the process exits with a non-zero code. Its result is used as a result for this whole function. The parameter `code` is `null`, if the process is terminated due to [timeoutMs] and the `output` may be `null` as well 26 | * @return if [collectOutput] is `true` and the process exited cleanly with a zero code, the output of the process, as [String], with trailing whitespace (including new-lines) trimmed. Otherwise the result of [onFail] 27 | */ 28 | fun system(vararg command: String, 29 | workingDirectory: Path = WemiRootFolder, 30 | environment: Map = emptyMap(), 31 | timeoutMs: Long = Long.MAX_VALUE, 32 | collectOutput: Boolean = true, 33 | outputCharset: Charset = Charsets.UTF_8, 34 | onFail: (code: Int?, output: String?) -> String? = { _, _ -> null }): String? { 35 | val process = ProcessBuilder(*command).run { 36 | directory(workingDirectory.toFile()) 37 | redirectError(ProcessBuilder.Redirect.INHERIT) 38 | redirectInput(ProcessBuilder.Redirect.INHERIT) 39 | if (collectOutput) { 40 | redirectOutput(ProcessBuilder.Redirect.PIPE) 41 | } else { 42 | redirectOutput(ProcessBuilder.Redirect.INHERIT) 43 | } 44 | if (environment.isNotEmpty()) { 45 | environment().putAll(environment) 46 | } 47 | start() 48 | } 49 | 50 | val commandString = command.joinToString(" ") 51 | 52 | val result = if (collectOutput) { 53 | ForkJoinPool.commonPool().submit(Callable { 54 | process.inputStream.reader(outputCharset).readText() 55 | }) 56 | } else { 57 | null 58 | } 59 | 60 | if (!CLI.forwardSignalsTo(process) { 61 | if (timeoutMs == Long.MAX_VALUE) { 62 | process.waitFor(); true 63 | } else { 64 | process.waitFor(timeoutMs, TimeUnit.MILLISECONDS) 65 | } 66 | }) { 67 | LOG.error("Process $process ($commandString) timed out") 68 | process.destroyForcibly() 69 | val resultString = try { 70 | result?.get(5, TimeUnit.SECONDS) 71 | } catch (e: TimeoutException) { 72 | null 73 | } 74 | return onFail(null, resultString) 75 | } 76 | 77 | val resultString = result?.get()?.trimEnd() 78 | val exitValue = process.exitValue() 79 | if (exitValue != 0) { 80 | return onFail(exitValue, resultString) 81 | } 82 | 83 | return resultString 84 | } 85 | -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/execution/WemiProgramRunner.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.execution 2 | 3 | import com.intellij.debugger.DebugEnvironment 4 | import com.intellij.debugger.DebuggerManagerEx 5 | import com.intellij.debugger.DefaultDebugEnvironment 6 | import com.intellij.debugger.engine.JavaDebugProcess 7 | import com.intellij.execution.DefaultExecutionResult 8 | import com.intellij.execution.configurations.RemoteConnection 9 | import com.intellij.execution.configurations.RunProfile 10 | import com.intellij.execution.configurations.RunProfileState 11 | import com.intellij.execution.configurations.RunnerSettings 12 | import com.intellij.execution.executors.DefaultDebugExecutor 13 | import com.intellij.execution.executors.DefaultRunExecutor 14 | import com.intellij.execution.runners.AsyncProgramRunner 15 | import com.intellij.execution.runners.ExecutionEnvironment 16 | import com.intellij.execution.runners.ProgramRunner 17 | import com.intellij.execution.runners.RunContentBuilder 18 | import com.intellij.execution.ui.RunContentDescriptor 19 | import com.intellij.openapi.fileEditor.FileDocumentManager 20 | import com.intellij.xdebugger.XDebugProcess 21 | import com.intellij.xdebugger.XDebugProcessStarter 22 | import com.intellij.xdebugger.XDebugSession 23 | import com.intellij.xdebugger.XDebuggerManager 24 | import com.intellij.xdebugger.impl.XDebugSessionImpl 25 | import org.jetbrains.concurrency.Promise 26 | import org.jetbrains.concurrency.resolvedPromise 27 | 28 | /** I don't even know what it does. One more abstraction layer I guess. */ 29 | class WemiProgramRunner : AsyncProgramRunner() { 30 | 31 | override fun getRunnerId(): String = ID 32 | 33 | override fun canRun(executorId: String, profile: RunProfile): Boolean { 34 | return (executorId == DefaultRunExecutor.EXECUTOR_ID || executorId == DefaultDebugExecutor.EXECUTOR_ID) 35 | && profile is WemiTaskConfiguration 36 | } 37 | 38 | override fun execute(environment: ExecutionEnvironment, state: RunProfileState): Promise { 39 | if (state !is WemiTaskConfiguration.WemiRunProfileState) { 40 | throw AssertionError("Only WemiRunProfileState is supported, got $state") 41 | } 42 | FileDocumentManager.getInstance().saveAllDocuments() 43 | 44 | if (state.debugPort <= 0) { 45 | val result = state.execute(environment.executor, this) 46 | return resolvedPromise(RunContentBuilder(result, environment).showRunContent(environment.contentToReuse)) 47 | } else { 48 | val connection = RemoteConnection(true, "localhost", state.debugPort.toString(), 49 | /* Wemi is a server when debugging build scripts, but forked processes connect to IDE */ !state.options.debugWemiItself) 50 | val debugEnvironment: DebugEnvironment = DefaultDebugEnvironment(environment, state, connection, Long.MAX_VALUE) 51 | val debuggerSession = DebuggerManagerEx.getInstanceEx(environment.project).attachVirtualMachine(debugEnvironment) ?: return resolvedPromise(null) 52 | val executionResult = debuggerSession.process.executionResult 53 | 54 | return resolvedPromise(XDebuggerManager.getInstance(environment.project).startSession(environment, object : XDebugProcessStarter() { 55 | override fun start(session: XDebugSession): XDebugProcess { 56 | val sessionImpl = session as XDebugSessionImpl 57 | sessionImpl.addExtraActions(*executionResult.actions) 58 | if (executionResult is DefaultExecutionResult) { 59 | sessionImpl.addRestartActions(*executionResult.restartActions) 60 | } 61 | return JavaDebugProcess.create(session, debuggerSession) 62 | } 63 | }).runContentDescriptor) 64 | } 65 | } 66 | 67 | companion object { 68 | const val ID = "WemiProgramRunner" 69 | 70 | fun instance():WemiProgramRunner { 71 | return ProgramRunner.PROGRAM_RUNNER_EP.findExtensionOrFail(WemiProgramRunner::class.java) 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/util/StackTraceUtil.java: -------------------------------------------------------------------------------- 1 | package wemi.util; 2 | 3 | import java.util.Collections; 4 | import java.util.IdentityHashMap; 5 | import java.util.List; 6 | import java.util.Set; 7 | import java.util.function.Function; 8 | 9 | /** Pure Java (with no dependencies) version of {@link StackTraceUtilKt}. */ 10 | public class StackTraceUtil { 11 | 12 | /** 13 | * Append the given throwable to the {@link StringBuilder}. 14 | * 15 | * The throwable is printed with the same formatting as {@link Throwable#printStackTrace()} has, 16 | * but the printed stack traces can be modified by the `stackMapper`. 17 | */ 18 | public static void appendWithStackTrace(StringBuilder sb, Throwable throwable, Function> stackTraceMapper) { 19 | final Set alreadyPrinted = Collections.newSetFromMap(new IdentityHashMap<>()); 20 | alreadyPrinted.add(throwable); 21 | 22 | final List trace = stackTraceMapper.apply(throwable.getStackTrace()); 23 | final Throwable cause = throwable.getCause(); 24 | final Throwable[] suppressed = throwable.getSuppressed(); 25 | 26 | // Print our stack trace 27 | sb.append(throwable).append('\n'); 28 | for (StackTraceElement traceElement : trace) { 29 | sb.append("\tat ").append(traceElement).append('\n'); 30 | } 31 | 32 | // Print suppressed exceptions, if any 33 | for (Throwable se : suppressed) { 34 | appendAdditionalStackTrace(se, sb, trace, "Suppressed: ", "\t", alreadyPrinted, stackTraceMapper); 35 | } 36 | 37 | // Print cause, if any 38 | if (cause != null) { 39 | appendAdditionalStackTrace(cause, sb, trace, "Caused by: ", "", alreadyPrinted, stackTraceMapper); 40 | } 41 | } 42 | 43 | 44 | /** Print our stack trace as an enclosed exception for the specified stack trace. */ 45 | private static void appendAdditionalStackTrace(Throwable throwable, 46 | StringBuilder sb, 47 | List enclosingTrace, 48 | String caption, 49 | String prefix, 50 | Set alreadyPrinted, 51 | Function> stackMapper) { 52 | 53 | if (alreadyPrinted.contains(throwable)) { 54 | sb.append("\t[CIRCULAR REFERENCE:").append(throwable).append("]\n"); 55 | } else { 56 | alreadyPrinted.add(throwable); 57 | 58 | final List trace = stackMapper.apply(throwable.getStackTrace()); 59 | final Throwable cause = throwable.getCause(); 60 | final Throwable[] suppressed = throwable.getSuppressed(); 61 | 62 | // Compute number of frames in common between this and enclosing trace 63 | int m = trace.size() - 1; 64 | int n = enclosingTrace.size() - 1; 65 | while (m >= 0 && n >= 0 && trace.get(m) == enclosingTrace.get(n)) { 66 | m--; 67 | n--; 68 | } 69 | int framesInCommon = trace.size() - 1 - m; 70 | 71 | // Print our stack trace 72 | sb.append(prefix).append(caption).append(throwable).append('\n'); 73 | for (int i = 0; i < m; i++) { 74 | sb.append(prefix).append("\tat ").append(trace.get(i)).append('\n'); 75 | } 76 | 77 | if (framesInCommon != 0) { 78 | sb.append(prefix).append("\t... ").append(framesInCommon).append(" more").append('\n'); 79 | } 80 | 81 | // Print suppressed exceptions, if any 82 | for (Throwable se : suppressed) { 83 | appendAdditionalStackTrace(se, sb, trace, "Suppressed: ", prefix + "\t", alreadyPrinted, stackMapper); 84 | } 85 | 86 | // Print cause, if any 87 | if (cause != null) { 88 | appendAdditionalStackTrace(cause, sb, trace, "Caused by: ", prefix, alreadyPrinted, stackMapper); 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/test/TestData.java: -------------------------------------------------------------------------------- 1 | package wemi.test; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import wemi.util.Json; 5 | 6 | import java.io.DataInputStream; 7 | import java.io.DataOutputStream; 8 | import java.io.IOException; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * Results of run of test identified by {@link TestIdentifier}. 14 | * 15 | * Mutable. 16 | */ 17 | @Json(serializer = TestDataSerializer.class) 18 | public final class TestData { 19 | 20 | /** Status of this {@link TestIdentifier}'s run */ 21 | @NotNull 22 | public TestStatus status = TestStatus.NOT_RUN; 23 | 24 | /** Duration of the run, in ms. -1 when unknown. */ 25 | public long duration = -1L; 26 | 27 | /** If {@link #status} is {@link TestStatus#SKIPPED}, this may contain why it was skipped */ 28 | @NotNull 29 | public String skipReason = ""; 30 | 31 | /** If {@link #status} is {@link TestStatus#FAILED}, this may contain the (possibly filtered) stack trace of the error */ 32 | @NotNull 33 | public String stackTrace = ""; 34 | 35 | /** 36 | * Custom reports made by the test execution. 37 | * 38 | * Those contain user data reported by the test. 39 | * 40 | * See http://junit.org/junit5/docs/current/user-guide/#writing-tests-dependency-injection TestReporter. 41 | */ 42 | @NotNull 43 | public final ArrayList reports = new ArrayList<>(); 44 | 45 | @Json(serializer = ReportEntrySerializer.class) 46 | public static final class ReportEntry { 47 | public final long timestamp; 48 | @NotNull 49 | public final String key; 50 | @NotNull 51 | public final String value; 52 | 53 | public ReportEntry(long timestamp, @NotNull String key, @NotNull String value) { 54 | this.timestamp = timestamp; 55 | this.key = key; 56 | this.value = value; 57 | } 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | final StringBuilder sb = new StringBuilder(); 63 | sb.append('{').append(status); 64 | if (duration != -1L) { 65 | sb.append(" in ").append(duration).append(" ms"); 66 | } 67 | if (!skipReason.isEmpty()) { 68 | if (status == TestStatus.SKIPPED) { 69 | sb.append(" reason: "); 70 | } else { 71 | sb.append(" skip reason: "); 72 | } 73 | sb.append(skipReason); 74 | } 75 | final String stackTrace = this.stackTrace; 76 | if (!stackTrace.isEmpty()) { 77 | if (stackTrace.indexOf('\n') >= 0) { 78 | sb.append(" exception:\n").append(stackTrace).append("\n"); 79 | } else { 80 | sb.append(" exception:").append(stackTrace).append(' '); 81 | } 82 | } 83 | final List reports = this.reports; 84 | if (!reports.isEmpty()) { 85 | sb.append("reports=").append(reports); 86 | } 87 | sb.append('}'); 88 | 89 | return sb.toString(); 90 | } 91 | 92 | public void writeTo(DataOutputStream out) throws IOException { 93 | out.writeByte(status.ordinal()); 94 | out.writeLong(duration); 95 | out.writeUTF(skipReason); 96 | out.writeUTF(stackTrace); 97 | 98 | out.writeInt(reports.size()); 99 | for (ReportEntry report : reports) { 100 | out.writeLong(report.timestamp); 101 | out.writeUTF(report.key); 102 | out.writeUTF(report.value); 103 | } 104 | } 105 | 106 | public void readFrom(DataInputStream in) throws IOException { 107 | status = TestStatus.VALUES[in.readUnsignedByte()]; 108 | duration = in.readLong(); 109 | skipReason = in.readUTF(); 110 | stackTrace = in.readUTF(); 111 | 112 | final int reportCount = in.readInt(); 113 | final ArrayList reports = this.reports; 114 | reports.clear(); 115 | reports.ensureCapacity(reportCount); 116 | for (int i = 0; i < reportCount; i++) { 117 | final long timestamp = in.readLong(); 118 | final String key = in.readUTF(); 119 | final String value = in.readUTF(); 120 | reports.add(new ReportEntry(timestamp, key, value)); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /plugins/intellij/src/main/kotlin/wemiplugin/intellij/utils/ExtraIntellijDependency.kt: -------------------------------------------------------------------------------- 1 | package wemiplugin.intellij.utils 2 | 3 | import org.slf4j.LoggerFactory 4 | import wemi.ActivityListener 5 | import wemi.EvalScope 6 | import wemi.dependency 7 | import wemi.dependency.Repository 8 | import wemi.dependency.TypeChooseByPackaging 9 | import wemi.dependency.resolveDependencyArtifacts 10 | import wemi.util.isDirectory 11 | import wemi.util.name 12 | import wemiplugin.intellij.DEFAULT_INTELLIJ_IDE_VERSION 13 | import wemiplugin.intellij.IntelliJ 14 | import wemiplugin.intellij.IntelliJIDE 15 | import wemiplugin.intellij.getZipCacheDirectory 16 | import wemiplugin.intellij.intellijIDERepository 17 | import wemiplugin.intellij.unzipDependencyFile 18 | import java.nio.file.Path 19 | import java.util.stream.Collectors 20 | 21 | 22 | private val LOG = LoggerFactory.getLogger("ExtraIntelliJDependency") 23 | 24 | /** 25 | * A resolved dependency on something packaged like an IDE dependency, but not an IDE. 26 | * Honestly, I don't even know what this is. 27 | * If you need this, let me know so that I can test and document it properly. 28 | */ 29 | class ResolvedIntelliJExtraDependency(val name:String, val classes: Path) { 30 | 31 | val jarFiles:Collection = if (classes.isDirectory()) { 32 | Utils.collectJars(classes).collect(Collectors.toList()) 33 | } else { 34 | setOf(classes) 35 | } 36 | 37 | override fun equals(other: Any?): Boolean { 38 | if (this === other) return true 39 | if (other !is ResolvedIntelliJExtraDependency) return false 40 | 41 | if (name != other.name) return false 42 | if (classes != other.classes) return false 43 | if (jarFiles != other.jarFiles) return false 44 | 45 | return true 46 | } 47 | 48 | override fun hashCode(): Int { 49 | var result = name.hashCode() 50 | result = 31 * result + classes.hashCode() 51 | result = 31 * result + jarFiles.hashCode() 52 | return result 53 | } 54 | 55 | override fun toString(): String { 56 | return "ResolvedIntelliJExtraDependency(name='$name', classes=$classes, jarFiles=$jarFiles)" 57 | } 58 | } 59 | 60 | fun EvalScope.resolveExtraIntelliJDependencies(extraDependencies: List):List { 61 | val version = (IntelliJ.intellijIdeDependency.get() as? IntelliJIDE.External)?.version ?: DEFAULT_INTELLIJ_IDE_VERSION 62 | val repository = intellijIDERepository(IntelliJ.intellijIdeRepository.get(), version) 63 | 64 | val resolvedExtraDependencies = ArrayList() 65 | for (name in extraDependencies) { 66 | val dependencyFile = resolveExtraIntelliJDependency(version, name, repository, progressListener) ?: continue 67 | val extraDependency = ResolvedIntelliJExtraDependency(name, dependencyFile) 68 | LOG.debug("IDE extra dependency $name in $dependencyFile files: ${extraDependency.jarFiles}") 69 | resolvedExtraDependencies.add(extraDependency) 70 | } 71 | return resolvedExtraDependencies 72 | } 73 | 74 | fun resolveExtraIntelliJDependency(version: String, name: String, repository: Repository, progressListener: ActivityListener?) : Path? { 75 | val dependency = dependency("com.jetbrains.intellij.idea", name, version, type = TypeChooseByPackaging) 76 | val files = resolveDependencyArtifacts(listOf(dependency), listOf(repository), progressListener) 77 | if (files == null || files.isEmpty()) { 78 | LOG.warn("Cannot resolve IntelliJ extra dependency {}:{}", name, version) 79 | return null 80 | } 81 | 82 | if (files.size > 1) { 83 | LOG.warn("Resolving IntelliJ extra dependency {}:{} yielded more than one file - using only the first ({})", name, version, files) 84 | } 85 | 86 | val depFile = files.first() 87 | if (depFile.name.endsWith(".zip")) { 88 | val cacheDirectory = getZipCacheDirectory(depFile, "IC") 89 | LOG.debug("IDE extra dependency {}: {}", name, cacheDirectory) 90 | return unzipDependencyFile(cacheDirectory, depFile, "IC", version.endsWith("-SNAPSHOT")) 91 | } else { 92 | LOG.debug("IDE extra dependency {}: {}", name, depFile) 93 | return depFile 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/test/TestIdentifier.java: -------------------------------------------------------------------------------- 1 | package wemi.test; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | import wemi.test.forked.ForkSerialization; 6 | import wemi.util.Json; 7 | 8 | import java.io.DataInputStream; 9 | import java.io.DataOutputStream; 10 | import java.io.IOException; 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | 14 | /** Unique, immutable, test identifier. */ 15 | @Json(serializer = TestIdentifierSerializer.class) 16 | public final class TestIdentifier { 17 | 18 | /** ID of the test, not human readable, but unique among other [TestIdentifier]s of the run */ 19 | @NotNull 20 | public final String id; 21 | 22 | /** ID of the parent {@link TestIdentifier}. Blank if no parent exists. */ 23 | @NotNull 24 | public final String parentId; 25 | 26 | /** Name which should be displayed to the user */ 27 | @NotNull 28 | public final String displayName; 29 | 30 | /** If this identifies a test that was executed */ 31 | public final boolean isTest; 32 | /** If this identifies a collection of tests (such collection can still have {@link TestData}) */ 33 | public final boolean isContainer; 34 | 35 | /** Assigned tags */ 36 | @NotNull 37 | public final Set tags; 38 | 39 | /** Source in which this test/container has been found. No content/format is guaranteed, used for debugging. May be blank. */ 40 | @NotNull 41 | public final String testSource; 42 | 43 | public TestIdentifier(@NotNull String id, @Nullable String parentId, @NotNull String displayName, boolean isTest, boolean isContainer, @NotNull Set tags, @Nullable String testSource) { 44 | this.id = id; 45 | this.parentId = parentId == null ? "" : parentId; 46 | this.displayName = displayName; 47 | this.isTest = isTest; 48 | this.isContainer = isContainer; 49 | this.tags = tags; 50 | this.testSource = testSource == null ? "" : testSource; 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (!(o instanceof TestIdentifier)) return false; 57 | 58 | TestIdentifier that = (TestIdentifier) o; 59 | 60 | if (!id.equals(that.id)) return false; 61 | return parentId.equals(that.parentId); 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | int result = id.hashCode(); 67 | result = 31 * result + parentId.hashCode(); 68 | return result; 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | final StringBuilder sb = new StringBuilder(); 74 | sb.append(id).append(':').append(displayName).append('{'); 75 | if (isTest && isContainer) { 76 | sb.append("test & container"); 77 | } else if (isTest) { 78 | sb.append("test"); 79 | } else if (isContainer) { 80 | sb.append("container"); 81 | } else { 82 | sb.append("nor test nor container"); 83 | } 84 | 85 | if (!tags.isEmpty()) { 86 | sb.append(", tags=").append(tags); 87 | } 88 | 89 | if (!testSource.isEmpty()) { 90 | sb.append(", source=").append(testSource); 91 | } 92 | sb.append('}'); 93 | 94 | return sb.toString(); 95 | } 96 | 97 | public void writeTo(DataOutputStream out) throws IOException { 98 | out.writeUTF(id); 99 | out.writeUTF(parentId); 100 | out.writeUTF(displayName); 101 | out.writeBoolean(isTest); 102 | out.writeBoolean(isContainer); 103 | ForkSerialization.writeTo(out, tags); 104 | out.writeUTF(testSource); 105 | } 106 | 107 | public static TestIdentifier readFrom(DataInputStream in) throws IOException { 108 | final String id = in.readUTF(); 109 | final String parentId = in.readUTF(); 110 | final String displayName = in.readUTF(); 111 | final boolean isTest = in.readBoolean(); 112 | final boolean isContainer = in.readBoolean(); 113 | final HashSet tags = new HashSet<>(); 114 | ForkSerialization.readFrom(in, tags); 115 | final String testSource = in.readUTF(); 116 | return new TestIdentifier(id, parentId, displayName, isTest, isContainer, tags, testSource); 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/esotericsoftware/tablelayout/Toolkit.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2011, Nathan Sweet 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | ******************************************************************************/ 27 | 28 | package com.esotericsoftware.tablelayout; 29 | 30 | import com.esotericsoftware.tablelayout.BaseTableLayout.Debug; 31 | 32 | /** Base class for UI toolkit. 33 | * @author Nathan Sweet */ 34 | public abstract class Toolkit { 35 | static public Toolkit instance; 36 | 37 | abstract public Cell obtainCell (L layout); 38 | 39 | abstract public void freeCell (Cell cell); 40 | 41 | abstract public void addChild (C parent, C child); 42 | 43 | abstract public void removeChild (C parent, C child); 44 | 45 | abstract public float getMinWidth (C widget); 46 | 47 | abstract public float getMinHeight (C widget); 48 | 49 | abstract public float getPrefWidth (C widget); 50 | 51 | abstract public float getPrefHeight (C widget); 52 | 53 | abstract public float getMaxWidth (C widget); 54 | 55 | abstract public float getMaxHeight (C widget); 56 | 57 | abstract public float getWidth (C widget); 58 | 59 | abstract public float getHeight (C widget); 60 | 61 | /** Clears all debugging rectangles. */ 62 | abstract public void clearDebugRectangles (L layout); 63 | 64 | /** Adds a rectangle that should be drawn for debugging. */ 65 | abstract public void addDebugRectangle (L layout, Debug type, float x, float y, float w, float h); 66 | 67 | /** @param widget May be null. */ 68 | public void setWidget (L layout, Cell cell, C widget) { 69 | if (cell.widget == widget) return; 70 | removeChild((T)layout.table, (C)cell.widget); 71 | cell.widget = widget; 72 | if (widget != null) addChild((T)layout.table, widget); 73 | } 74 | 75 | /** Interprets the specified value as a size. This can be used to scale all sizes applied to a table. The default implementation 76 | * returns the value unmodified. 77 | * @see Value#width(Object) 78 | * @see Value#width(Cell) */ 79 | public float width (float value) { 80 | return value; 81 | } 82 | 83 | /** Interprets the specified value as a size. This can be used to scale all sizes applied to a table. The default implementation 84 | * returns the value unmodified. 85 | * @see Value#height(Object) 86 | * @see Value#height(Cell) */ 87 | public float height (float value) { 88 | return value; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/kotlin/wemi/util/JavaEnvironment.kt: -------------------------------------------------------------------------------- 1 | package wemi.util 2 | 3 | import com.esotericsoftware.jsonbeans.JsonWriter 4 | import wemi.WemiException 5 | import java.nio.file.Files 6 | import java.nio.file.Path 7 | import java.nio.file.Paths 8 | import java.util.* 9 | 10 | 11 | /** 12 | * Represents a combination of Java home directory 13 | * and a path of corresponding java executable for the current platform. 14 | */ 15 | data class JavaHome(val home:Path, val javaExecutable: Path = javaExecutable(home), val toolsJar:Path? = jdkToolsJar(home)) : JsonWritable, WithDescriptiveString { 16 | 17 | override fun JsonWriter.write() { 18 | writeObject { 19 | field("home", home) 20 | field("javaExecutable", javaExecutable) 21 | field("toolsJar", toolsJar) 22 | } 23 | } 24 | 25 | override fun toDescriptiveAnsiString(): String { 26 | return StringBuilder() 27 | .format(Color.Blue).append(home) 28 | .format(Color.White).append('/') 29 | .format(Color.Blue).append(home.relativize(javaExecutable)) 30 | .apply { 31 | if (toolsJar != null) { 32 | format(Color.White).append(" (").append(home.relativize(toolsJar)).append(')') 33 | } 34 | }.format().toString() 35 | } 36 | } 37 | 38 | /** 39 | * Java home, as set by the java.home property. 40 | */ 41 | val CurrentProcessJavaHome:JavaHome = JavaHome(Paths.get( 42 | System.getProperty("java.home", null) 43 | ?: throw WemiException("java.home property is not set, can't find java executable") 44 | ).toAbsolutePath()) 45 | 46 | /** 47 | * Retrieve path to the java executable (starter of JVM), assuming that [javaHome] is the path to a valid JAVA_HOME. 48 | */ 49 | fun javaExecutable(javaHome: Path): Path { 50 | val windowsFile = (javaHome / "bin/java.exe").toAbsolutePath() 51 | val unixFile = (javaHome / "bin/java").toAbsolutePath() 52 | val winExists = Files.exists(windowsFile) 53 | val unixExists = Files.exists(unixFile) 54 | 55 | if (winExists && !unixExists) { 56 | return windowsFile 57 | } else if (!winExists && unixExists) { 58 | return unixFile 59 | } else if (!winExists && !unixExists) { 60 | if (SystemInfo.IS_WINDOWS) { 61 | throw WemiException("Java executable should be at $windowsFile, but it does not exist") 62 | } else { 63 | throw WemiException("Java executable should be at $unixFile, but it does not exist") 64 | } 65 | } else { 66 | return if (SystemInfo.IS_WINDOWS) { 67 | windowsFile 68 | } else { 69 | unixFile 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * When java is run from "jre" part of the JDK install, "tools.jar" is not in the classpath by default. 76 | * This can locate the tools.jar in for standard Java homes for explicit loading. 77 | */ 78 | fun jdkToolsJar(javaHome: Path): Path? { 79 | // Gradle logic: https://github.com/gradle/gradle/blob/master/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jvm.java#L330 80 | return javaHome.resolve("lib/tools.jar").takeIf { it.exists() } 81 | ?: (if (javaHome.name == "jre") javaHome.resolve("../lib/tools.jar").takeIf { it.exists() } else null) 82 | ?: (if (javaHome.name.startsWith("jre")) { 83 | val matchingVersion = javaHome.parent.resolve("jdk${javaHome.name.removePrefix("jre")}/lib/tools.jar") 84 | if (matchingVersion.exists()) { 85 | matchingVersion 86 | } else { 87 | (Files.list(javaHome.parent) 88 | .filter { it.name.startsWith("jdk") } 89 | .map { it.resolve("lib/tools.jar") } 90 | .filter { it.exists() } 91 | .findAny() as Optional /* because otherwise next line gives warnings */) 92 | .orElse(null) 93 | } 94 | } else null) 95 | } 96 | 97 | /** Return URL at which Java SE Javadoc is hosted for given [javaVersion]. */ 98 | fun javadocUrl(javaVersion:Int?):String { 99 | val version = javaVersion ?: 14 // = newest 100 | return when { 101 | // These versions don't have API uploaded, so fall back to 1.5 (first one that is online) 102 | version <= 5 -> "https://docs.oracle.com/javase/1.5.0/docs/api/" 103 | version in 6..10 -> "https://docs.oracle.com/javase/${version}/docs/api/" 104 | else -> "https://docs.oracle.com/en/java/javase/${version}/docs/api/" 105 | } 106 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/options/WemiLauncherOptions.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.options 2 | 3 | import com.darkyen.wemi.intellij.settings.WemiProjectService 4 | import com.darkyen.wemi.intellij.ui.CommandArgumentEditor 5 | import com.darkyen.wemi.intellij.ui.EnvironmentVariablesEditor 6 | import com.darkyen.wemi.intellij.ui.PropertyEditorPanel 7 | import com.darkyen.wemi.intellij.ui.WemiJavaExecutableEditor 8 | import com.darkyen.wemi.intellij.ui.WindowsShellExecutableEditor 9 | import com.darkyen.wemi.intellij.util.BooleanXmlSerializer 10 | import com.darkyen.wemi.intellij.util.ListOfStringXmlSerializer 11 | import com.darkyen.wemi.intellij.util.MapStringStringXmlSerializer 12 | import com.darkyen.wemi.intellij.util.StringXmlSerializer 13 | import com.darkyen.wemi.intellij.util.XmlSerializable 14 | import com.darkyen.wemi.intellij.util.findWindowsShellExe 15 | import com.intellij.openapi.project.Project 16 | import com.intellij.openapi.util.SystemInfo 17 | import java.nio.file.Files 18 | import java.nio.file.Paths 19 | 20 | /** Base options for Wemi launching. */ 21 | abstract class WemiLauncherOptions : XmlSerializable() { 22 | 23 | var environmentVariables: Map = emptyMap() 24 | var passParentEnvironmentVariables:Boolean = true 25 | 26 | var javaExecutable:String = "" 27 | var javaOptions:List = emptyList() 28 | 29 | var windowsShellExecutable:String = "" 30 | 31 | fun getWindowsShellExecutable(project: Project?):String? { 32 | if (!SystemInfo.isWindows) { 33 | return "" 34 | } 35 | 36 | val exe = windowsShellExecutable 37 | if (exe.isNotBlank()) { 38 | val path = Paths.get(exe) 39 | if (Files.exists(path)) { 40 | return exe 41 | } 42 | } 43 | 44 | if (project != null) { 45 | val global = project.getService(WemiProjectService::class.java).options.getWindowsShellExecutable(null) 46 | if (global != null) { 47 | return global 48 | } 49 | } 50 | 51 | val detected = findWindowsShellExe() 52 | if (detected != null) { 53 | windowsShellExecutable = detected 54 | return detected 55 | } 56 | 57 | return null 58 | } 59 | 60 | init { 61 | register(WemiLauncherOptions::environmentVariables, MapStringStringXmlSerializer::class) 62 | register(WemiLauncherOptions::passParentEnvironmentVariables, BooleanXmlSerializer::class) 63 | register(WemiLauncherOptions::javaExecutable, StringXmlSerializer::class) 64 | register(WemiLauncherOptions::javaOptions, ListOfStringXmlSerializer::class) 65 | register(WemiLauncherOptions::windowsShellExecutable, StringXmlSerializer::class) 66 | } 67 | 68 | fun copyTo(o: WemiLauncherOptions) { 69 | o.environmentVariables = environmentVariables 70 | o.passParentEnvironmentVariables = passParentEnvironmentVariables 71 | o.javaExecutable = javaExecutable 72 | o.javaOptions = javaOptions 73 | o.windowsShellExecutable = windowsShellExecutable 74 | } 75 | 76 | open fun createUi(panel: PropertyEditorPanel) { 77 | panel.editRow(EnvironmentVariablesEditor(this::environmentVariables, this::passParentEnvironmentVariables)) 78 | panel.gap(5) 79 | panel.editRow(WemiJavaExecutableEditor(this::javaExecutable)) 80 | panel.editRow(CommandArgumentEditor(this::javaOptions)) 81 | 82 | if (SystemInfo.isWindows && this is ProjectImportOptions) { 83 | panel.editRow(WindowsShellExecutableEditor(this::windowsShellExecutable)) 84 | } 85 | } 86 | 87 | override fun equals(other: Any?): Boolean { 88 | if (this === other) return true 89 | if (other !is WemiLauncherOptions) return false 90 | 91 | if (environmentVariables != other.environmentVariables) return false 92 | if (passParentEnvironmentVariables != other.passParentEnvironmentVariables) return false 93 | if (javaExecutable != other.javaExecutable) return false 94 | if (javaOptions != other.javaOptions) return false 95 | if (windowsShellExecutable != other.windowsShellExecutable) return false 96 | 97 | return true 98 | } 99 | 100 | override fun hashCode(): Int { 101 | var result = environmentVariables.hashCode() 102 | result = 31 * result + passParentEnvironmentVariables.hashCode() 103 | result = 31 * result + javaExecutable.hashCode() 104 | result = 31 * result + javaOptions.hashCode() 105 | result = 31 * result + windowsShellExecutable.hashCode() 106 | return result 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /plugins/intellij/src/main/kotlin/wemiplugin/intellij/PrepareSandbox.kt: -------------------------------------------------------------------------------- 1 | package wemiplugin.intellij 2 | 3 | import Files 4 | import org.slf4j.LoggerFactory 5 | import org.w3c.dom.Document 6 | import org.w3c.dom.Element 7 | import wemi.EvalScope 8 | import wemi.keys.cacheDirectory 9 | import wemi.util.deleteRecursively 10 | import wemi.util.div 11 | import wemi.util.ensureEmptyDirectory 12 | import wemi.util.exists 13 | import wemi.util.linkOrCopyRecursively 14 | import wemi.util.name 15 | import wemiplugin.intellij.utils.namedElements 16 | import wemiplugin.intellij.utils.parseXml 17 | import wemiplugin.intellij.utils.saveXml 18 | import java.nio.file.Path 19 | import java.nio.file.StandardOpenOption 20 | import javax.xml.parsers.DocumentBuilderFactory 21 | 22 | private val LOG = LoggerFactory.getLogger("PrepareSandbox") 23 | 24 | @Suppress("MemberVisibilityCanBePrivate") 25 | class IntelliJIDESandbox(val base:Path, val config:Path, val plugins:Path, val system:Path) { 26 | override fun toString(): String { 27 | return "IntelliJIDESandbox(base=$base, config=${base.relativize(config)}, plugins=${base.relativize(plugins)}, system=${base.relativize(system)})" 28 | } 29 | } 30 | 31 | fun EvalScope.prepareIntelliJIDESandbox(sandboxDir: Path = cacheDirectory.get() / "idea-sandbox", testSuffix:String = "", vararg extraPluginDirectories: Path): IntelliJIDESandbox { 32 | val destinationDir = sandboxDir / "plugins$testSuffix" 33 | 34 | // A file which contains names of files added by us to clear. 35 | // We do not want to delete everything, because user might have added custom plugins from marketplace etc. 36 | val customContentManifest = destinationDir / "wemiContentManifest.txt" 37 | if (customContentManifest.exists()) { 38 | for (line in Files.readAllLines(customContentManifest)) { 39 | val dir = destinationDir / line.trim() 40 | dir.deleteRecursively() 41 | } 42 | } else { 43 | destinationDir.ensureEmptyDirectory() 44 | } 45 | 46 | val customContentNames = ArrayList() 47 | 48 | val pluginJar = IntelliJ.intellijPluginFolder.get() 49 | pluginJar.linkOrCopyRecursively(destinationDir / pluginJar.name) 50 | customContentNames.add(pluginJar.name) 51 | 52 | for (dependency in IntelliJ.intellijResolvedPluginDependencies.get()) { 53 | if (dependency.isBuiltin) { 54 | continue 55 | } 56 | dependency.artifact.linkOrCopyRecursively(destinationDir / dependency.artifact.name) 57 | customContentNames.add(dependency.artifact.name) 58 | } 59 | for (path in extraPluginDirectories) { 60 | path.linkOrCopyRecursively(destinationDir / path.name) 61 | customContentNames.add(path.name) 62 | } 63 | 64 | Files.write(customContentManifest, customContentNames, Charsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING) 65 | 66 | // Disable IDE update 67 | val configDir = sandboxDir / "config$testSuffix" 68 | try { 69 | disableIdeUpdate(configDir) 70 | } catch (e:Exception) { 71 | LOG.warn("Failed to disallow IDE update check", e) 72 | } 73 | 74 | return IntelliJIDESandbox(sandboxDir, configDir, destinationDir, sandboxDir / "system$testSuffix") 75 | } 76 | 77 | private fun disableIdeUpdate(configDir: Path) { 78 | val optionsDir = configDir / "options" 79 | try { 80 | Files.createDirectories(optionsDir) 81 | } catch (e:Exception) { 82 | LOG.warn("Failed to disable update checking in host IDE", e) 83 | return 84 | } 85 | 86 | val updatesConfig = optionsDir / "updates.xml" 87 | val updatesXml = parseXml(updatesConfig) ?: DocumentBuilderFactory.newInstance().run { 88 | isNamespaceAware = false 89 | isValidating = false 90 | isXIncludeAware = false 91 | val document: Document = newDocumentBuilder().newDocument() 92 | document.appendChild(document.createElement("application")) 93 | document 94 | } 95 | 96 | val application = updatesXml.documentElement!! 97 | val updatesConfigurable = application.namedElements("component") 98 | .find { it.getAttribute("name") == "UpdatesConfigurable" } 99 | ?: run { 100 | val uc: Element = updatesXml.createElement("component") 101 | uc.setAttribute("name", "UpdatesConfigurable") 102 | application.appendChild(uc) 103 | uc 104 | } 105 | val checkNeeded = updatesConfigurable.namedElements("option") 106 | .find { it.getAttribute("name") == "CHECK_NEEDED" } 107 | ?: run { 108 | val cn: Element = updatesXml.createElement("option") 109 | cn.setAttribute("name", "CHECK_NEEDED") 110 | updatesConfigurable.appendChild(cn) 111 | cn 112 | } 113 | 114 | checkNeeded.setAttribute("value", "false") 115 | 116 | saveXml(updatesConfig, updatesXml) 117 | } 118 | -------------------------------------------------------------------------------- /src/test/kotlin/wemi/dependency/internal/MavenVersionRangeTest.kt: -------------------------------------------------------------------------------- 1 | package wemi.dependency.internal 2 | 3 | import org.junit.jupiter.api.Assertions.* 4 | import org.junit.jupiter.api.Test 5 | 6 | /** 7 | * 8 | */ 9 | class MavenVersionRangeTest { 10 | @Test 11 | fun mavenVersionParsing() { 12 | assertArrayEquals(intArrayOf(1, 0, 0), parseMavenVersion("1.0").numeric) 13 | assertArrayEquals(intArrayOf(1, 15, 0), parseMavenVersion("1.15").numeric) 14 | assertArrayEquals(intArrayOf(42, 15, 0), parseMavenVersion("42.15").numeric) 15 | assertArrayEquals(intArrayOf(42, 15, 145), parseMavenVersion("42.15.145").numeric) 16 | assertArrayEquals(intArrayOf(42, 15, 145, 99), parseMavenVersion("42.15.145.99").numeric) 17 | 18 | assertEquals("", parseMavenVersion("1.0").qualifier) 19 | assertEquals("", parseMavenVersion("1.15").qualifier) 20 | assertEquals("", parseMavenVersion("42.15").qualifier) 21 | assertEquals("", parseMavenVersion("42.15.145").qualifier) 22 | assertEquals("", parseMavenVersion("42.15.145.99").qualifier) 23 | 24 | assertEquals("SNAPSHOT", parseMavenVersion("1.0-SNAPSHOT").qualifier) 25 | assertEquals("SNAPSHOT", parseMavenVersion("1.15-SNAPSHOT").qualifier) 26 | assertEquals("SNAPSHOT", parseMavenVersion("42.15-SNAPSHOT").qualifier) 27 | assertEquals("SNAPSHOT", parseMavenVersion("42.15.145-SNAPSHOT").qualifier) 28 | assertEquals("SNAPSHOT", parseMavenVersion("42.15.145.99-SNAPSHOT").qualifier) 29 | assertEquals("", parseMavenVersion("42.15.145.99-").qualifier) 30 | 31 | assertEquals("alpha", parseMavenVersion("1-alpha").qualifier) 32 | } 33 | 34 | @Test 35 | fun mavenVersionComparison() { 36 | assertTrue(parseMavenVersion("1.0").compareTo(parseMavenVersion("1.0")) == 0) 37 | assertTrue(parseMavenVersion("1.0") < parseMavenVersion("1.1")) 38 | assertTrue(parseMavenVersion("1.0") < parseMavenVersion("1.0.1")) 39 | assertTrue(parseMavenVersion("1") < parseMavenVersion("1.0.1")) 40 | assertTrue(parseMavenVersion("1") < parseMavenVersion("1.0.1-alpha")) 41 | assertTrue(parseMavenVersion("1") > parseMavenVersion("1-alpha")) 42 | assertTrue(parseMavenVersion("1-alpha") < parseMavenVersion("1-beta")) 43 | assertTrue(parseMavenVersion("1").compareTo(parseMavenVersion("1-0")) == 0) 44 | } 45 | 46 | private fun range(range:String, contains:Array, doesNotContain:Array) { 47 | val r = parseMavenVersionRange(range) 48 | for (contain in contains) { 49 | assertTrue(contain in r, contain) 50 | } 51 | for (notContain in doesNotContain) { 52 | assertFalse(notContain in r, notContain) 53 | } 54 | } 55 | 56 | @Test 57 | fun mavenRangeComparison() { 58 | range("1.0", arrayOf("1.0", "2.0"), arrayOf("0.9.9", "1.0-alpha")) 59 | range("(,1.0]", arrayOf("1.0", "0.9.9", "1.0-alpha"), arrayOf("2.0")) 60 | range("(,1.0)", arrayOf("0.9.9", "1.0-alpha"), arrayOf("1.0", "2.0")) 61 | range("[1.0]", arrayOf("1.0"), arrayOf("0.9.9", "1.0-alpha", "2.0")) 62 | range("[1.0,)", arrayOf("1.0", "2.0"), arrayOf("0.9.9", "1.0-alpha")) 63 | range("(1.0,)", arrayOf("2.0"), arrayOf("1.0", "0.9.9", "1.0-alpha")) 64 | range("(1.0,2.0)", arrayOf("1.1"), arrayOf("1.0", "0.9.9", "1.0-alpha", "2.0")) 65 | range("[1.0,2.0]", arrayOf("1.0", "1.1", "2.0"), arrayOf("0.9.9", "1.0-alpha")) 66 | range("(,1.0],[1.2,)", arrayOf("1.0", "0.9.9", "1.0-alpha", "2.0"), arrayOf("1.1", "1.0.1")) 67 | range("(,1.1),(1.1,)", arrayOf("1.0", "0.9.9", "1.0-alpha", "2.0", "1.0.1"), arrayOf("1.1")) 68 | } 69 | 70 | @Test 71 | fun mavenRangeExtraction() { 72 | assertEquals(null, extractSingleVersionFromVersionRange("1.0")) 73 | assertEquals(null, extractSingleVersionFromVersionRange("1,0-SNAPSHOT[foo]")) 74 | assertEquals(null, extractSingleVersionFromVersionRange("[1.0")) 75 | assertEquals("1.0", extractSingleVersionFromVersionRange("[1.0]")) 76 | assertEquals(null, extractSingleVersionFromVersionRange("(1.0]")) 77 | assertEquals("1.0", extractSingleVersionFromVersionRange("(,1.0]")) 78 | assertEquals("1.3", extractSingleVersionFromVersionRange("[1.2,1.3]")) 79 | assertEquals("1.0", extractSingleVersionFromVersionRange("[1.0,2.0)")) 80 | assertEquals("1.5", extractSingleVersionFromVersionRange("[1.5,)")) 81 | assertEquals("1.2", extractSingleVersionFromVersionRange("(,1.0],[1.2,)")) 82 | assertEquals(null, extractSingleVersionFromVersionRange("(,1.0][1.2,)")) 83 | assertEquals(null, extractSingleVersionFromVersionRange("(,1.0],,[1.2,)")) 84 | assertEquals(null, extractSingleVersionFromVersionRange("(,1.0], [1.2,)")) 85 | assertEquals("1.2", extractSingleVersionFromVersionRange("[0.9],(,1.0],[1.2,)")) 86 | assertEquals(null, extractSingleVersionFromVersionRange("(,1.1),(1.1,)")) 87 | assertEquals(null, extractSingleVersionFromVersionRange("(1.1,)")) 88 | assertEquals(null, extractSingleVersionFromVersionRange("(1.1)")) 89 | } 90 | } -------------------------------------------------------------------------------- /ide-plugins/intellij/src/main/kotlin/com/darkyen/wemi/intellij/projectImport/ImportFromWemiBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.darkyen.wemi.intellij.projectImport 2 | 3 | import com.darkyen.wemi.intellij.WemiLauncher 4 | import com.darkyen.wemi.intellij.WemiLauncherFileName 5 | import com.darkyen.wemi.intellij.importing.reinstallWemiLauncher 6 | import com.darkyen.wemi.intellij.options.ProjectImportOptions 7 | import com.darkyen.wemi.intellij.util.executable 8 | import com.darkyen.wemi.intellij.util.getWemiCompatibleSdk 9 | import com.intellij.ide.util.projectWizard.WizardContext 10 | import com.intellij.openapi.application.WriteAction 11 | import com.intellij.openapi.module.ModifiableModuleModel 12 | import com.intellij.openapi.module.Module 13 | import com.intellij.openapi.options.ConfigurationException 14 | import com.intellij.openapi.project.Project 15 | import com.intellij.openapi.projectRoots.JavaSdkType 16 | import com.intellij.openapi.projectRoots.SdkTypeId 17 | import com.intellij.openapi.roots.ui.configuration.ModulesProvider 18 | import com.intellij.packaging.artifacts.ModifiableArtifactModel 19 | import com.intellij.projectImport.ProjectImportBuilder 20 | import icons.WemiIcons 21 | import java.nio.file.Files 22 | import java.nio.file.Paths 23 | import java.util.concurrent.CancellationException 24 | 25 | /** 26 | * Used when importing (unlinked?) project. 27 | */ 28 | class ImportFromWemiBuilder : ProjectImportBuilder() { 29 | 30 | private var projectNode: ProjectNode? = null 31 | 32 | override fun getName() = "Wemi" 33 | 34 | override fun getIcon() = WemiIcons.ACTION 35 | 36 | override fun setOpenProjectSettingsAfter(on: Boolean) {} 37 | 38 | override fun isSuitableSdkType(sdkType: SdkTypeId?): Boolean { 39 | return sdkType is JavaSdkType && !(sdkType as JavaSdkType).isDependent 40 | } 41 | 42 | override fun commit(project: Project, model: ModifiableModuleModel?, modulesProvider: ModulesProvider?, artifactModel: ModifiableArtifactModel?): List { 43 | return WriteAction.compute, Nothing> { 44 | importProjectStructureToIDE(this.projectNode!!, project, model) 45 | } 46 | } 47 | 48 | /** 49 | * Asks the current builder to ensure that target external project is defined. 50 | * 51 | * @param wizardContext current wizard context 52 | * @throws ConfigurationException if external project is not defined and can't be constructed 53 | */ 54 | @Throws(ConfigurationException::class) 55 | fun ensureProjectIsDefined(wizardContext: WizardContext, projectImportOptions:ProjectImportOptions) { 56 | val project = wizardContext.project 57 | 58 | val projectFileDirectory = Paths.get(wizardContext.projectFileDirectory) 59 | val launcher = run { 60 | val wemiJar = projectFileDirectory.resolve(WemiLauncherFileName).toAbsolutePath() 61 | if (!Files.exists(wemiJar)) { 62 | return@run null 63 | } 64 | if (!Files.isRegularFile(wemiJar)) { 65 | throw ConfigurationException("Wemi launcher exists (file named \"wemi\"), but is not a file. Remove it to continue.") 66 | } 67 | try { 68 | wemiJar.executable = true 69 | } catch (e : Exception) { 70 | // Filesystem may not support the executable permission 71 | } 72 | 73 | val windowsShell = projectImportOptions.getWindowsShellExecutable(project) 74 | ?: throw ConfigurationException("POSIX shell is not configured. Set it up to continue.") 75 | 76 | WemiLauncher(wemiJar, windowsShell) 77 | } ?: reinstallWemiLauncher(projectFileDirectory, "Failed to put Wemi launcher in the project directory", project)?.second 78 | ?: return 79 | 80 | val projectNode:ProjectNode = try { 81 | importWemiProjectStructure(project, launcher, projectImportOptions, activateToolWindow = false, modal = true).get() 82 | } catch (cancelled: CancellationException) { 83 | throw ConfigurationException("Cancelled") 84 | } 85 | this.projectNode = projectNode 86 | 87 | wizardContext.projectName = projectNode.name 88 | wizardContext.setProjectFileDirectory(projectNode.root.toAbsolutePath().toString()) 89 | wizardContext.compilerOutputDirectory = projectNode.compileOutputPath?.toAbsolutePath()?.toString() 90 | getWemiCompatibleSdk(projectNode.javaTargetVersion)?.let { wizardContext.projectJdk = it } 91 | } 92 | 93 | // Not relevant 94 | override fun getList(): List? = projectNode?.let { listOf(it) } ?: emptyList() 95 | override fun setList(list: List?) {} 96 | override fun isMarked(element: ProjectNode?): Boolean = true 97 | } -------------------------------------------------------------------------------- /src/main/kotlin/wemi/util/LocatedPath.kt: -------------------------------------------------------------------------------- 1 | package wemi.util 2 | 3 | import com.esotericsoftware.jsonbeans.JsonWriter 4 | import org.slf4j.Logger 5 | import org.slf4j.LoggerFactory 6 | import java.io.IOException 7 | import java.nio.file.FileVisitResult 8 | import java.nio.file.FileVisitor 9 | import java.nio.file.Files 10 | import java.nio.file.Path 11 | import java.nio.file.attribute.BasicFileAttributes 12 | 13 | private val LOG: Logger = LoggerFactory.getLogger("LocatedPath") 14 | 15 | /** 16 | * Path that may have an explicit root location in the file tree. 17 | * 18 | * Used when handling classpath entries, where not only the file name and content, but also file's location in 19 | * the (re)source root is important. For example, *.class files are added to classpath by their root, not by their 20 | * full path. 21 | */ 22 | class LocatedPath( 23 | /** Explicit root. Must be prefix of [file] when not null. */ 24 | val root: Path?, 25 | /** Represented file */ 26 | val file: Path) 27 | : JsonWritable { 28 | 29 | /** 30 | * Create without explicit root. [file] itself is an classpath entry, or this distinction is not relevant. 31 | * [root] will be reported as the [file]'s direct parent. 32 | * [classpathEntry] is considered to be [file]. 33 | * Used, for example, for *.jar files. 34 | */ 35 | constructor(file: Path) : this(null, file) 36 | 37 | init { 38 | assert(root == null || file.startsWith(root)) { "root must be prefix of the file" } 39 | } 40 | 41 | /** 42 | * Path to the file from root, *without* leading '/', including the file name 43 | */ 44 | val path: String 45 | get() = root?.relativize(file)?.toString() ?: file.name 46 | 47 | val classpathEntry: Path 48 | get() = root ?: file 49 | 50 | override fun toString(): String { 51 | return if (root == null) { 52 | file.absolutePath 53 | } else { 54 | "${root.absolutePath}//$path" 55 | } 56 | } 57 | 58 | override fun JsonWriter.write() { 59 | // ScopedLocatedPath duplicates this code 60 | writeObject { 61 | field("root", root) 62 | field("file", file) 63 | } 64 | } 65 | 66 | override fun equals(other: Any?): Boolean { 67 | if (this === other) return true 68 | if (javaClass != other?.javaClass) return false 69 | 70 | other as LocatedPath 71 | 72 | if (root != other.root) return false 73 | if (file != other.file) return false 74 | 75 | return true 76 | } 77 | 78 | override fun hashCode(): Int { 79 | var result = root?.hashCode() ?: 0 80 | result = 31 * result + file.hashCode() 81 | return result 82 | } 83 | } 84 | 85 | /** 86 | * Walk the filesystem, starting at [from] and create a [LocatedPath] for each encountered file, 87 | * that fulfills the [predicate]. Then put the created file to [to]. 88 | * 89 | * Created files will have their root at [from]. 90 | */ 91 | fun constructLocatedFiles(from: Path, to: MutableCollection, predicate: (Path) -> Boolean = { true }) { 92 | // Walking is hard, don't do it if we don't have to 93 | if (!from.exists()) { 94 | return 95 | } 96 | 97 | if (!from.isDirectory()) { 98 | if (!from.isHidden() && predicate(from)) { 99 | to.add(LocatedPath(from)) 100 | } 101 | return 102 | } 103 | 104 | val pathStack = StringBuilder(64) 105 | 106 | Files.walkFileTree(from, object : FileVisitor { 107 | override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes?): FileVisitResult { 108 | if (dir !== from) { 109 | pathStack.append(dir.name).append('/') 110 | } 111 | return FileVisitResult.CONTINUE 112 | } 113 | 114 | override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { 115 | if (!attrs.isDirectory && !file.isHidden() && predicate(file)) { 116 | to.add(LocatedPath(from, file)) 117 | } 118 | 119 | return FileVisitResult.CONTINUE 120 | } 121 | 122 | override fun postVisitDirectory(dir: Path, exc: IOException?): FileVisitResult { 123 | if (dir !== from) { 124 | pathStack.setLength(pathStack.length - dir.name.length - 1) 125 | } 126 | return FileVisitResult.CONTINUE 127 | } 128 | 129 | override fun visitFileFailed(file: Path?, exc: IOException?): FileVisitResult { 130 | LOG.warn("Can't visit {} to constructLocatedFiles", file, exc) 131 | return FileVisitResult.CONTINUE 132 | } 133 | }) 134 | } 135 | --------------------------------------------------------------------------------