Skip to content

Commit 8b61fa4

Browse files
committed
Added support for custom maven repositories (fixes #22)
1 parent 9273c01 commit 8b61fa4

File tree

10 files changed

+167
-50
lines changed

10 files changed

+167
-50
lines changed

NEWS.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
## v2.2
22

3-
* Logging of maven artifact downloads to stderr and high level status information (fixes [#23](https://github.com/holgerbrandl/kscript/issues/23))
3+
* Logging of maven artifact downloads to stderr (fixes [#23](https://github.com/holgerbrandl/kscript/issues/23))
44
* Added `-s` / `--silent` to suppress all logging
55
* Fixed [#55](https://github.com/holgerbrandl/kscript/issues/55): dependency resolution fails on travis ci and within docker containers
6+
* Added alternative `@DependsOnMaven(val artifactId: String)` annotaiton to declare dependencies. This has been implemented to make kscripts compatible with https://github.com/ligee/kotlin-jupyter
7+
* Added support for custom maven repositories (fixes [#22](https://github.com/holgerbrandl/kscript/issues/22))
8+
9+
10+
See [README.md](README.md) for usage details.
11+
612

713
## v2.1
814

README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,20 @@ Using annotations instead of comment directives to configure scripts is cleaner
266266
```kotlin
267267
#!/usr/bin/env kscript
268268

269-
// declare dependencies
269+
// Declare dependencies
270270
@file:DependsOn("de.mpicbg.scicomp:kutils:0.4")
271271
@file:DependsOn("com.beust:klaxon:0.24", "com.github.kittinunf.fuel:fuel:1.3.1")
272272

273-
// include helper scripts without deployment or prior compilation
273+
274+
// To use a custom maven repository you can declare it with
275+
@file:MavenRepository("imagej-releases","http://maven.imagej.net/content/repositories/releases" )
276+
277+
// For compatibility with https://github.com/ligee/kotlin-jupyter kscript supports also
278+
// Note that for compatibility reasons, only one locator argument is allowed here
279+
@file:DependsOnMaven("net.clearvolume:cleargl:2.0.1")
280+
281+
282+
// Include helper scripts without deployment or prior compilation
274283
@file:Include("util.kt")
275284

276285

@@ -286,7 +295,7 @@ print("1+1")
286295

287296
To enable the use of these annotations in Intellij, the user must add the following artifact (hosted on jcenter) to the project dependencies:
288297
```
289-
com.github.holgerbrandl:kscript-annotations:1.0
298+
com.github.holgerbrandl:kscript-annotations:1.1
290299
```
291300

292301
`kscript` will automatically detect an annotation-driven script, and if so will declare a dependency on this artifact internally.
@@ -340,6 +349,12 @@ In order to use cygwin you need to use windows paths to provide your scripts. Yo
340349
kscript $(cygpath -w /cygdrive/z/some/path/my_script.kts)
341350
```
342351

352+
353+
## Can I use custom artifact repositories?
354+
355+
Yes, via the `@MavenRepository` annotation. See [annotations section](#annotation-driven-script-configuration) or [custom_mvn_repo_annot](test/resources/custom_mvn_repo_annot.kts) for a complete example
356+
357+
343358
Support
344359
-------
345360

src/main/kotlin/kscript/app/AppHelpers.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ fun numLines(str: String) = str.split("\r\n|\r|\n".toRegex()).dropLastWhile { it
188188
fun info(msg: String) = System.err.println(msg)
189189

190190

191-
fun launchIdeaWithKscriptlet(scriptFile: File, dependencies: List<String>): String {
191+
fun launchIdeaWithKscriptlet(scriptFile: File, dependencies: List<String>, customRepos: List<MavenRepo>): String {
192192
System.err.println("Setting up idea project from ${scriptFile}")
193193

194194
// val tmpProjectDir = createTempDir("edit_kscript", suffix="")
@@ -198,13 +198,14 @@ fun launchIdeaWithKscriptlet(scriptFile: File, dependencies: List<String>): Stri
198198

199199
// fixme use tmp instead of cachdir. Fails for now because idea gradle import does not seem to like tmp
200200
val tmpProjectDir = KSCRIPT_CACHE_DIR
201-
.run { File(this, "kscript_tmp_project__${scriptFile.name}") }
201+
.run { File(this, "kscript_tmp_project__${scriptFile.name}_${System.currentTimeMillis()}") }
202202
.apply { mkdir() }
203203
// val tmpProjectDir = File("/Users/brandl/Desktop/")
204204
// .run { File(this, "kscript_tmp_project") }
205205
// .apply { mkdir() }
206206

207207
val stringifiedDeps = dependencies.map { " compile \"$it\"" }.joinToString("\n")
208+
val stringifiedRepos = customRepos.map { " maven {\n url '${it.url}'\n }\n" }.joinToString("\n")
208209

209210
val gradleScript = """
210211
group 'com.github.holgerbrandl.kscript.editor'
@@ -221,7 +222,9 @@ $stringifiedDeps
221222
222223
repositories {
223224
mavenCentral()
225+
mavenLocal()
224226
jcenter()
227+
$stringifiedRepos
225228
}
226229
227230
sourceSets {
@@ -233,7 +236,7 @@ sourceSets {
233236
}
234237
235238
buildscript {
236-
ext.kotlin_version = '1.1.4'
239+
ext.kotlin_version = '1.1.60'
237240
238241
repositories {
239242
jcenter()

src/main/kotlin/kscript/app/DependencyUtil.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ val DEP_LOOKUP_CACHE_FILE = File(KSCRIPT_CACHE_DIR, "dependency_cache.txt")
88
val CP_SEPARATOR_CHAR = if (System.getProperty("os.name").toLowerCase().contains("windows")) ";" else ":"
99

1010

11-
fun resolveDependencies(depIds: List<String>, loggingEnabled: Boolean): String? {
11+
fun resolveDependencies(depIds: List<String>, customRepos: List<MavenRepo> = emptyList(), loggingEnabled: Boolean): String? {
1212

1313
// if no dependencies were provided we stop here
1414
if (depIds.isEmpty()) {
@@ -51,6 +51,17 @@ fun resolveDependencies(depIds: List<String>, loggingEnabled: Boolean): String?
5151
"""
5252
}
5353

54+
// see https://github.com/holgerbrandl/kscript/issues/22
55+
val repoTags = customRepos.map {
56+
"""
57+
<repository>
58+
<id>${it.id}</id>
59+
<url>${it.url}</url>
60+
</repository>
61+
"""
62+
63+
}
64+
5465
val pom = """
5566
<?xml version="1.0" encoding="UTF-8"?>
5667
<project xmlns="http://maven.apache.org/POM/4.0.0"
@@ -68,6 +79,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs
6879
<id>jcenter</id>
6980
<url>http://jcenter.bintray.com/</url>
7081
</repository>
82+
${repoTags.joinToString("\n")}
7183
</repositories>
7284
7385
<dependencies>
@@ -141,5 +153,5 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs
141153

142154
// called by unit tests
143155
fun main(args: Array<String>) {
144-
System.err.println(resolveDependencies(args.toList(), false))
156+
System.err.println(resolveDependencies(args.toList(), loggingEnabled = false))
145157
}

src/main/kotlin/kscript/app/Kscript.kt

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -117,16 +117,17 @@ fun main(args: Array<String>) {
117117

118118
// Find all //DEPS directives and concatenate their values
119119
val dependencies = collectDependencies(scriptText)
120+
val customRepos = collectRepos(scriptText)
120121

121122

122123
// Create temopary dev environment
123124
if (docopt.getBoolean("idea")) {
124-
println(launchIdeaWithKscriptlet(scriptFile, dependencies))
125+
println(launchIdeaWithKscriptlet(scriptFile, dependencies, customRepos))
125126
exitProcess(0)
126127
}
127128

128129

129-
val classpath = resolveDependencies(dependencies, loggingEnabled)
130+
val classpath = resolveDependencies(dependencies, customRepos, loggingEnabled)
130131

131132
// Extract kotlin arguments
132133
val kotlinOpts = collectRuntimeOptions(scriptText)
@@ -148,8 +149,8 @@ fun main(args: Array<String>) {
148149
// Even if we just need and support the //ENTRY directive in case of kt-class
149150
// files, we extract it here to fail if it was used in kts files.
150151
val entryDirective = scriptText
151-
.find { it.contains("^//ENTRY ".toRegex()) }
152-
?.replace("//ENTRY ", "")?.trim()
152+
.find { it.contains("^//ENTRY ".toRegex()) }
153+
?.replace("//ENTRY ", "")?.trim()
153154

154155
errorIf(entryDirective != null && scriptFileExt == "kts") {
155156
"//ENTRY directive is just supported for kt class files"
@@ -164,8 +165,8 @@ fun main(args: Array<String>) {
164165

165166
// Capitalize first letter and get rid of dashes (since this is what kotlin compiler is doing for the wrapper to create a valid java class name)
166167
val className = scriptFile.nameWithoutExtension
167-
.replace("[.-]".toRegex(), "_")
168-
.capitalize()
168+
.replace("[.-]".toRegex(), "_")
169+
.capitalize()
169170

170171

171172
// Define the entrypoint for the scriptlet jar
@@ -174,7 +175,7 @@ fun main(args: Array<String>) {
174175
} else {
175176
// extract package from kt-file
176177
val pckg = scriptText.find { it.startsWith("package ") }
177-
?.split("[ ]+".toRegex())?.get(1)?.run { this + "." }
178+
?.split("[ ]+".toRegex())?.get(1)?.run { this + "." }
178179

179180
"""${pckg ?: ""}${entryDirective ?: "${className}Kt"}"""
180181
}
@@ -244,44 +245,70 @@ fun main(args: Array<String>) {
244245
}
245246

246247
fun collectDependencies(scriptText: List<String>): List<String> {
247-
val deps = scriptText
248-
.filter { it.startsWith("//DEPS ") }
249-
.flatMap { it.split("[ ;,]+".toRegex()).drop(1) }
250-
.map(String::trim)
251-
252-
val annotatonPrefix = "@file:DependsOn("
248+
val dependsOnPrefix = "@file:DependsOn("
253249
var annotDeps = scriptText
254-
.filter { it.startsWith(annotatonPrefix) }
255-
.map { it.replaceFirst(annotatonPrefix, "").split(")")[0] }
256-
.flatMap { it.split(",") }
257-
.map { it.trim(' ', '"') }
250+
.filter { it.startsWith(dependsOnPrefix) }
251+
.map { it.replaceFirst(dependsOnPrefix, "").split(")")[0] }
252+
.flatMap { it.split(",") }
253+
.map { it.trim(' ', '"') }
254+
255+
256+
val dependsOnMavenPrefix = "@file:DependsOnMaven("
257+
annotDeps += scriptText
258+
.filter { it.startsWith(dependsOnMavenPrefix) }
259+
.map { it.replaceFirst(dependsOnMavenPrefix, "").split(")")[0] }
260+
.map { it.trim(' ', '"') }
258261

259-
// if annotations are used add dependency
262+
263+
// if annotations are used add dependency on kscript-annotations
260264
if (scriptText.any { containsKscriptAnnotation(it) }) {
261-
annotDeps += "com.github.holgerbrandl:kscript-annotations:1.0"
265+
annotDeps += "com.github.holgerbrandl:kscript-annotations:1.1"
262266
}
263267

268+
269+
// also extract classical comment directives
270+
val deps = scriptText
271+
.filter { it.startsWith("//DEPS ") }
272+
.flatMap { it.split("[ ;,]+".toRegex()).drop(1) }
273+
.map(String::trim)
274+
264275
return (deps + annotDeps).distinct()
265276
}
266277

278+
data class MavenRepo(val id: String, val url: String)
279+
280+
fun collectRepos(scriptText: List<String>): List<MavenRepo> {
281+
val dependsOnMavenPrefix = "@file:MavenRepository("
282+
// only supported annotation format for now
283+
284+
// @file:MavenRepository("imagej", "http://maven.imagej.net/content/repositories/releases/")
285+
return scriptText
286+
.filter { it.startsWith(dependsOnMavenPrefix) }
287+
.map { it.replaceFirst(dependsOnMavenPrefix, "").split(")")[0] }
288+
.map { it.split(",").map { it.trim(' ', '"') }.let { MavenRepo(it[0], it[1]) } }
289+
290+
// todo add credential support https://stackoverflow.com/questions/36282168/how-to-add-custom-maven-repository-to-gradle
291+
}
292+
267293

268294
fun containsKscriptAnnotation(line: String) =
269-
listOf("DependsOn", "KotlinOpts", "Include", "EntryPoint").any { line.startsWith("@file:${it}(") }
295+
listOf("DependsOn", "KotlinOpts", "Include", "EntryPoint", "MavenRepository", "DependsOnMaven")
296+
.any { line.startsWith("@file:${it}(") }
270297

271298

272299
fun collectRuntimeOptions(scriptText: List<String>): String {
273300
val koptsPrefix = "//KOTLIN_OPTS "
274301

275302
var kotlinOpts = scriptText.
276-
filter { it.startsWith(koptsPrefix) }.
277-
map { it.replaceFirst(koptsPrefix, "").trim() }
303+
filter { it.startsWith(koptsPrefix) }.
304+
map { it.replaceFirst(koptsPrefix, "").trim() }
278305

279306
//support for @file:KotlinOpts see #47
280307
val annotatonPrefix = "@file:KotlinOpts("
281308
kotlinOpts += scriptText
282-
.filter { it.startsWith(annotatonPrefix) }
283-
.map { it.replaceFirst(annotatonPrefix, "").split(")")[0] }
284-
.map { it.trim(' ', '"') }
309+
.filter { it.startsWith(annotatonPrefix) }
310+
.map { it.replaceFirst(annotatonPrefix, "").split(")")[0] }
311+
.map { it.trim(' ', '"') }
285312

286313

287314
// Append $KSCRIPT_KOTLIN_OPTS if defined in the parent environment

src/test/kotlin/Tests.kt

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import io.kotlintest.matchers.shouldBe
2+
import kscript.app.MavenRepo
23
import kscript.app.collectDependencies
4+
import kscript.app.collectRepos
35
import kscript.app.collectRuntimeOptions
46
import org.junit.Test
57

@@ -13,14 +15,14 @@ class Tests {
1315
@Test
1416
fun directiveDependencyCollect() {
1517
val lines = listOf(
16-
"//DEPS de.mpicbg.scicomp.joblist:joblist-kotlin:1.1, de.mpicbg.scicomp:kutils:0.7",
17-
"//DEPS log4j:log4j:1.2.14"
18+
"//DEPS de.mpicbg.scicomp.joblist:joblist-kotlin:1.1, de.mpicbg.scicomp:kutils:0.7",
19+
"//DEPS log4j:log4j:1.2.14"
1820
)
1921

2022
val expected = listOf(
21-
"de.mpicbg.scicomp.joblist:joblist-kotlin:1.1",
22-
"de.mpicbg.scicomp:kutils:0.7",
23-
"log4j:log4j:1.2.14"
23+
"de.mpicbg.scicomp.joblist:joblist-kotlin:1.1",
24+
"de.mpicbg.scicomp:kutils:0.7",
25+
"log4j:log4j:1.2.14"
2426
)
2527

2628
collectDependencies(lines) shouldBe expected
@@ -29,28 +31,43 @@ class Tests {
2931
@Test
3032
fun mixedDependencyCollect() {
3133
val lines = listOf(
32-
"//DEPS de.mpicbg.scicomp.joblist:joblist-kotlin:1.1, de.mpicbg.scicomp:kutils:0.7",
33-
"""@file:DependsOn("log4j:log4j:1.2.14")"""
34+
"//DEPS de.mpicbg.scicomp.joblist:joblist-kotlin:1.1, de.mpicbg.scicomp:kutils:0.7",
35+
"""@file:DependsOn("log4j:log4j:1.2.14")"""
3436
)
3537

3638
val expected = listOf(
37-
"de.mpicbg.scicomp.joblist:joblist-kotlin:1.1",
38-
"de.mpicbg.scicomp:kutils:0.7",
39-
"log4j:log4j:1.2.14",
40-
"com.github.holgerbrandl:kscript-annotations:1.0"
39+
"de.mpicbg.scicomp.joblist:joblist-kotlin:1.1",
40+
"de.mpicbg.scicomp:kutils:0.7",
41+
"log4j:log4j:1.2.14",
42+
"com.github.holgerbrandl:kscript-annotations:1.1"
4143
)
4244

4345
collectDependencies(lines) shouldBe expected
4446
}
4547

4648

49+
@Test
50+
fun customRepo() {
51+
val lines = listOf(
52+
"""@file:MavenRepository("imagej-releases", "http://maven.imagej.net/content/repositories/releases" ) // crazy comment""",
53+
"""@file:DependsOnMaven("net.clearvolume:cleargl:2.0.1")""",
54+
"""println("foo")"""
55+
)
56+
57+
val expected = listOf(
58+
MavenRepo("imagej-releases", "http://maven.imagej.net/content/repositories/releases")
59+
)
60+
61+
collectRepos(lines) shouldBe expected
62+
}
63+
4764

4865
// combine kotlin opts spread over multiple lines
4966
@Test
5067
fun optsCollect() {
5168
val lines = listOf(
52-
"//KOTLIN_OPTS -foo 3 'some file.txt'",
53-
"//KOTLIN_OPTS --bar"
69+
"//KOTLIN_OPTS -foo 3 'some file.txt'",
70+
"//KOTLIN_OPTS --bar"
5471
)
5572

5673
collectRuntimeOptions(lines) shouldBe "-foo 3 'some file.txt' --bar"
@@ -59,8 +76,8 @@ class Tests {
5976
@Test
6077
fun annotOptsCollect() {
6178
val lines = listOf(
62-
"//KOTLIN_OPTS -foo 3 'some file.txt'",
63-
"""@file:KotlinOpts("--bar")"""
79+
"//KOTLIN_OPTS -foo 3 'some file.txt'",
80+
"""@file:KotlinOpts("--bar")"""
6481
)
6582

6683
collectRuntimeOptions(lines) shouldBe "-foo 3 'some file.txt' --bar"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env kscript
2+
3+
@file:MavenRepository("imagej-releases","http://maven.imagej.net/content/repositories/releases" )
4+
5+
@file:DependsOnMaven("net.clearvolume:cleargl:2.0.1")
6+
7+
import cleargl.GLVector
8+
9+
GLVector(1.3f)
10+
11+
12+
println("kscript with annotations rocks!")

0 commit comments

Comments
 (0)