Skip to content

Commit c551db6

Browse files
committed
Add simple-main-kts project
1 parent d20f4fa commit c551db6

21 files changed

+713
-0
lines changed

ReadMe.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ when needed
2020
- [Simple script definition](jvm/basic/jvm-simple-script/SimpleScript.md)
2121
- [Script with Dynamic dependencies from Maven](jvm/basic/jvm-maven-deps/MavenDeps.md)
2222
- [Scripting Host with Kotlin Compiler Embeddable](jvm/basic/jvm-embeddable-host/EmbeddableCompiler.md)
23+
- [Simplified main-kts](jvm/simple-main-kts/SimpleMainKts.md)
2324

2425
## External examples
2526

jvm/simple-main-kts/SimpleMainKts.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
# Kotlin Scripting Examples: Simplified main-kts
3+
4+
This is a simplified version of the `main-kts` script support, distributed with the Kotlin compiler.
5+
6+
*Simplifications mostly related to the removal of the JSR-223 support, since it uses non-public API. In the future,
7+
when a public API will become available, the support could be added back. Also the packaging into a singe jar is not
8+
implemented.*
9+
10+
## Description
11+
12+
The purpose of the `simple-main-kts` (as well as the original `main-kts`) is to allow writing simple but extendable
13+
utility scripts for the usage in the command line, replacing the simple Kotlin programs with `main` function (hence
14+
the `main` in its name).
15+
16+
The script definition implementation demonstrates many aspects of the script definition functionality and
17+
scripting API usage, in particular:
18+
- refinement of the compilation configuration with the parameters passed to the annotations
19+
- dynamic script dependencies resolved via ivy library
20+
- import scripts with possibility of sharing instances (diamond-shaped import)
21+
- compilation cache, where cached compiled script are saved as executable jars
22+
- discovery file configuration, that could be used with IntelliJ IDEA and command-line compiler
23+
24+
and others.
25+
26+
The original implementation of the `kotlin-main-kts` contains all the dependencies in the single jar, that simplify
27+
the usage in the command line. This simplified version does not implement it, therefore all dependencies should be
28+
specified explicitly in the command line, to use it with the `kotlinc`.
29+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
plugins {
3+
kotlin("jvm")
4+
}
5+
6+
val kotlinVersion: String by rootProject.extra
7+
8+
dependencies {
9+
testImplementation(project(":jvm:simple-main-kts:simple-main-kts"))
10+
testImplementation("org.jetbrains.kotlin:kotlin-scripting-jvm-host:$kotlinVersion")
11+
testImplementation("junit:junit:4.12")
12+
}
13+
14+
sourceSets {
15+
main {}
16+
}
17+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
package org.jetbrains.kotlin.script.examples.simpleMainKts.test
6+
7+
import org.jetbrains.kotlin.script.examples.simpleMainKts.COMPILED_SCRIPTS_CACHE_DIR_PROPERTY
8+
import org.jetbrains.kotlin.script.examples.simpleMainKts.SimpleMainKtsScript
9+
import org.junit.Assert
10+
import org.junit.Test
11+
import java.io.*
12+
import java.net.URLClassLoader
13+
import kotlin.script.experimental.api.*
14+
import kotlin.script.experimental.host.toScriptSource
15+
import kotlin.script.experimental.jvm.baseClassLoader
16+
import kotlin.script.experimental.jvm.jvm
17+
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
18+
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
19+
20+
fun evalFile(scriptFile: File, cacheDir: File? = null): ResultWithDiagnostics<EvaluationResult> =
21+
withMainKtsCacheDir(cacheDir?.absolutePath ?: "") {
22+
val scriptDefinition = createJvmCompilationConfigurationFromTemplate<SimpleMainKtsScript>()
23+
24+
val evaluationEnv = ScriptEvaluationConfiguration {
25+
jvm {
26+
baseClassLoader(null)
27+
}
28+
constructorArgs(emptyArray<String>())
29+
enableScriptsInstancesSharing()
30+
}
31+
32+
BasicJvmScriptingHost().eval(scriptFile.toScriptSource(), scriptDefinition, evaluationEnv)
33+
}
34+
35+
36+
const val TEST_DATA_ROOT = "testData"
37+
38+
class SimpleMainKtsTest {
39+
40+
@Test
41+
fun testResolveJunit() {
42+
val res = evalFile(File("$TEST_DATA_ROOT/hello-resolve-junit.smain.kts"))
43+
assertSucceeded(res)
44+
}
45+
46+
@Test
47+
fun testResolveJunitDynamicVer() {
48+
val errRes = evalFile(File("$TEST_DATA_ROOT/hello-resolve-junit-dynver-error.smain.kts"))
49+
assertFailed("Unresolved reference: assertThrows", errRes)
50+
51+
val res = evalFile(File("$TEST_DATA_ROOT/hello-resolve-junit-dynver.smain.kts"))
52+
assertSucceeded(res)
53+
}
54+
55+
@Test
56+
fun testUnresolvedJunit() {
57+
val res = evalFile(File("$TEST_DATA_ROOT/hello-unresolved-junit.smain.kts"))
58+
assertFailed("Unresolved reference: junit", res)
59+
}
60+
61+
@Test
62+
fun testResolveError() {
63+
val res = evalFile(File("$TEST_DATA_ROOT/hello-resolve-error.smain.kts"))
64+
assertFailed("File 'abracadabra' not found", res)
65+
}
66+
67+
@Test
68+
fun testResolveLog4jAndDocopt() {
69+
val res = evalFile(File("$TEST_DATA_ROOT/resolve-log4j-and-docopt.smain.kts"))
70+
assertSucceeded(res)
71+
}
72+
73+
private val outFromImportTest = listOf("Hi from common", "Hi from middle", "sharedVar == 5")
74+
75+
@Test
76+
fun testImport() {
77+
78+
val out = captureOut {
79+
val res = evalFile(File("$TEST_DATA_ROOT/import-test.smain.kts"))
80+
assertSucceeded(res)
81+
}.lines()
82+
83+
Assert.assertEquals(outFromImportTest, out)
84+
}
85+
86+
@Test
87+
fun testCompilerOptions() {
88+
89+
val out = captureOut {
90+
val res = evalFile(File("$TEST_DATA_ROOT/compile-java6.smain.kts"))
91+
assertSucceeded(res)
92+
assertIsJava6Bytecode(res)
93+
}.lines()
94+
95+
Assert.assertEquals(listOf("Hi from sub", "Hi from super", "Hi from random"), out)
96+
}
97+
98+
@Test
99+
fun testCache() {
100+
val script = File("$TEST_DATA_ROOT/import-test.smain.kts")
101+
val cache = createTempDir("main.kts.test")
102+
103+
try {
104+
Assert.assertTrue(cache.exists() && cache.listFiles { f: File -> f.extension == "jar" }?.isEmpty() == true)
105+
val out1 = evalSuccessWithOut(script)
106+
Assert.assertEquals(outFromImportTest, out1)
107+
Assert.assertTrue(cache.listFiles { f: File -> f.extension.equals("jar", ignoreCase = true) }?.isEmpty() == true)
108+
109+
val out2 = evalSuccessWithOut(script, cache)
110+
Assert.assertEquals(outFromImportTest, out2)
111+
val casheFile = cache.listFiles { f: File -> f.extension.equals("jar", ignoreCase = true) }?.firstOrNull()
112+
Assert.assertTrue(casheFile != null && casheFile.exists())
113+
114+
val out3 = captureOut {
115+
val classLoader = URLClassLoader(arrayOf(casheFile!!.toURI().toURL()), null)
116+
val clazz = classLoader.loadClass("Import_test_smain")
117+
val mainFn = clazz.getDeclaredMethod("main", Array<String>::class.java)
118+
mainFn.invoke(null, arrayOf<String>())
119+
}.lines()
120+
Assert.assertEquals(outFromImportTest, out3)
121+
} finally {
122+
cache.deleteRecursively()
123+
}
124+
}
125+
126+
@Test
127+
fun testKotlinxHtml() {
128+
val out = captureOut {
129+
val res = evalFile(File("$TEST_DATA_ROOT/kotlinx-html.smain.kts"))
130+
assertSucceeded(res)
131+
}.lines()
132+
133+
Assert.assertEquals(listOf("<html>", " <body>", " <h1>Hello, World!</h1>", " </body>", "</html>"), out)
134+
}
135+
136+
private fun assertIsJava6Bytecode(res: ResultWithDiagnostics<EvaluationResult>) {
137+
val scriptClassResource = res.valueOrThrow().returnValue.scriptClass!!.java.run {
138+
getResource("$simpleName.class")
139+
}
140+
141+
DataInputStream(ByteArrayInputStream(scriptClassResource.readBytes())).use { stream ->
142+
val header = stream.readInt()
143+
if (0xCAFEBABE.toInt() != header) throw IOException("Invalid header class header: $header")
144+
stream.readUnsignedShort() // minor
145+
val major = stream.readUnsignedShort()
146+
Assert.assertTrue(major == 50)
147+
}
148+
}
149+
150+
private fun assertSucceeded(res: ResultWithDiagnostics<EvaluationResult>) {
151+
Assert.assertTrue(
152+
"test failed:\n ${res.reports.joinToString("\n ") { it.message + if (it.exception == null) "" else ": ${it.exception}" }}",
153+
res is ResultWithDiagnostics.Success
154+
)
155+
}
156+
157+
private fun assertFailed(expectedError: String, res: ResultWithDiagnostics<EvaluationResult>) {
158+
Assert.assertTrue(
159+
"test failed - expecting a failure with the message \"$expectedError\" but received " +
160+
(if (res is ResultWithDiagnostics.Failure) "failure" else "success") +
161+
":\n ${res.reports.joinToString("\n ") { it.message + if (it.exception == null) "" else ": ${it.exception}" }}",
162+
res is ResultWithDiagnostics.Failure && res.reports.any { it.message.contains(expectedError) }
163+
)
164+
}
165+
166+
private fun evalSuccessWithOut(scriptFile: File, cacheDir: File? = null): List<String> =
167+
captureOut {
168+
val res = evalFile(scriptFile, cacheDir)
169+
assertSucceeded(res)
170+
}.lines()
171+
}
172+
173+
private fun captureOut(body: () -> Unit): String {
174+
val outStream = ByteArrayOutputStream()
175+
val prevOut = System.out
176+
System.setOut(PrintStream(outStream))
177+
try {
178+
body()
179+
} finally {
180+
System.out.flush()
181+
System.setOut(prevOut)
182+
}
183+
return outStream.toString().trim()
184+
}
185+
186+
private fun <T> withMainKtsCacheDir(value: String?, body: () -> T): T {
187+
val prevCacheDir = System.getProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY)
188+
if (value == null) System.clearProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY)
189+
else System.setProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY, value)
190+
try {
191+
return body()
192+
} finally {
193+
if (prevCacheDir == null) System.clearProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY)
194+
else System.setProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY, prevCacheDir)
195+
}
196+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
@file:CompilerOptions("-jvm-target", "1.6")
2+
3+
interface Test {
4+
fun print()
5+
fun printSuper() = println("Hi from super")
6+
}
7+
8+
class TestImpl : Test {
9+
override fun print() = println("Hi from sub")
10+
}
11+
12+
inline fun printRandom() = println("Hi from random")
13+
14+
TestImpl().run {
15+
print()
16+
printSuper()
17+
printRandom()
18+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
@file:DependsOn("abracadabra")
3+
4+
println("Hello, World!")
5+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
@file:DependsOn("junit:junit:(4.11,4.12]")
3+
4+
org.junit.Assert.assertThrows(NullPointerException::class.java) {
5+
throw null!!
6+
}
7+
8+
println("Hello, World!")
9+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
@file:DependsOn("junit:junit:(4.12,5.0)")
3+
4+
org.junit.Assert.assertThrows(NullPointerException::class.java) {
5+
throw null!!
6+
}
7+
8+
println("Hello, World!")
9+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
@file:DependsOn("junit:junit:4.11")
3+
4+
org.junit.Assert.assertTrue(true)
5+
6+
println("Hello, World!")
7+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
org.junit.Assert.assertTrue(true)
3+
4+
println("Hello, World!")
5+

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy