diff --git a/.gitignore b/.gitignore
index 6a15e2a0..a680191b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,15 @@
.gradle
annotations
.idea/*
-!.idea/modules.xml
-buildScript
+*.iml
+nonBuildScript
kobaltBuild
-test-output
local.properties
classes
libs
.kobalt/
-build/
+out
+.DS_Store
+lib/kotlin-*
+build
+.history
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 82188f15..00000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index b9fa0cde..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-cache:
- directories:
- - $HOME/.m2
-
-language: java
-
-jdk:
- - oraclejdk8
-
-install: true
-
-script: ./kobaltw test --log 1
diff --git a/README.md b/README.md
index 29bc3bd1..d5d7cbe0 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,15 @@
# Kobalt
+[
](https://teamcity.jetbrains.com/project.html?projectId=OpenSourceProjects_Kobalt&tab=projectOverview)
+
+
Kobalt is a universal build system.
To build it:
```
-./kobaltw assemble
+$ ./kobaltw assemble
```
Please see [the web site](http://beust.com/kobalt/) for the full documentation.
+
diff --git a/TODO.md b/TODO.md
index 2bd27c88..c4625308 100644
--- a/TODO.md
+++ b/TODO.md
@@ -2,48 +2,31 @@ To do:
Android:
-- [ ] Dex dependencies into kobaltBuild/intermediates/pre-dexed and preserve those across builds
-- [ ] Move the calculated applicationId back into the merged AndroidManifest.xml
-- [ ] Dex from android builder
-- [ ] Keep exploded aars between runs
-- [ ] aars keep being refetched
-- [ ] See if there is an android manifest file in builder
General
+- [ ] KFiles.findSourceFiles should cache its results
+- [ ] createCompilerAction should calculate source files once and for all, including contributors and interceptors
+- [ ] Need a -resolve with no dependency which gives the tree for the whole project
+- [ ] If jitpack specified with http and not https, 301 is not handled correctly
- [ ] Apt should run from serviceloader
-- [ ] Auto add variant
-- [ ] The test runner only selects classes with a parameterless constructor, which works for JUnit but not for TestNG
- factories
-- [ ] Add a "Auto complete Build.kt" menu in the plug-in
-- [ ] "All artifacts successfully uploaded" is shown before the upload is actually done
-- [ ] use groupId/artifactId
- [ ] Console mode with watch service, so recompilation can occur as soon as a source file is modified
- [ ] ProjectGenerator: support migration from pom.xml (starting with dependencies)
- [ ] Specify where to upload snapshots
-- [ ] Upload in a thread pool
- [ ] repos() must appear before plugins(): fix that
-- [ ] Support version ranges
- [ ] Generate .idea and other IDEA files
-- [ ] logs for users should not show any timestamp, class file or thread id, this should only be in --dev mode
- [ ] Fetch .pom with DynamicGraph
- [ ] Centralize all the executors
- [ ] Archetypes (e.g. "--initWith kobalt-plug-in")
-- [ ] Compile TestNG (including generating Version.java and OSGi headers)
+- [ ] Compile TestNG (last piece missing: OSGi headers)
- [ ] Support additional .kt files in ~/.kobalt/src
-- [ ] generateArchive() should use Path instead of File
- [ ] --init: import dependencies from build.gradle
- [ ] --init: also extract kobalt.bat (or generate it along with kobaltw)
-- [ ] Bug: --tasks displays multiple tasks when there are multiple projects
-- [ ] Bug: ./kobaltw --dryRun kobalt:uploadJcenter runs "kobalt-wrapper:clean" twice
-- [ ] Replace File with java.nio.Files and Path
-- [ ] Create a wiki page for plugins
-- [ ] Make kobaltw executable in the zip file
- [ ] Encapsulate ProcessBuilder code
-- [ ] --resolve
Done:
+- [x] Dex dependencies into kobaltBuild/intermediates/pre-dexed and preserve those across builds
- [x] Compile with javax.tool
- [x] Android: multiple -source/-target flags
- [x] Dokka: allow multiple format outputs e.g `outputFormat("html", "javadoc")`
@@ -84,5 +67,24 @@ just a straight Java class with minimal dependencies for fast start up
- [x] Upload non maven files
- [x] Jar packaging: include the jar in the jar (not supported by JarFile)
- [x] Encapsulate BuildFile for better log messages
+- [x] The test runner only selects classes with a parameterless constructor, which works for JUnit but not for TestNG
+- [x] Add a "Auto complete Build.kt" menu in the plug-in
+- [x] "All artifacts successfully uploaded" is shown before the upload is actually done
+- [x] use groupId/artifactId
+ factories
+- [x] Support version ranges
+- [x] Upload in a thread pool
+- [x] logs for users should not show any timestamp, class file or thread id, this should only be in --dev mode
+- [x] Bug: --tasks displays multiple tasks when there are multiple projects
+- [x] Bug: ./kobaltw --dryRun kobalt:uploadJcenter runs "kobalt-wrapper:clean" twice
+- [x] Create a wiki page for plugins
+- [x] Make kobaltw executable in the zip file
+- [x] --resolve
+- [x] Move the calculated applicationId back into the merged AndroidManifest.xml
+- [x] Dex from android builder
+- [x] Keep exploded aars between runs
+- [x] aars keep being refetched
+- [x] See if there is an android manifest file in builder
+- [x] Auto add variant
diff --git a/build-travis.sh b/build-travis.sh
new file mode 100755
index 00000000..c7cb1152
--- /dev/null
+++ b/build-travis.sh
@@ -0,0 +1,8 @@
+ulimit -s 1082768
+
+#java -Xmx2048m -jar $(dirname $0)/kobalt/wrapper/kobalt-wrapper.jar $* clean
+java -Xmx2048m -jar $(dirname $0)/kobalt/wrapper/kobalt-wrapper.jar $* clean assemble test --parallel
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 8167f7b8..3f0053cc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,92 +1,58 @@
-buildscript {
- ext.kotlin_version = '1.0.0-beta-2423'
+allprojects {
+ group = 'com.beust'
+ version = '1.1.0'
+}
+
+subprojects {
+ apply plugin: 'java'
+ apply plugin: 'maven-publish'
+
+ ext {
+ bndlib = '3.5.0'
+ findbugs = '3.0.2'
+ groovy = '2.4.12'
+ gson = '2.8.2'
+ guice = '4.2.2'
+ inject = '1'
+ jaxb = '2.3.0'
+ jcommander = '1.72'
+ kotlin = '1.2.71'
+ maven = '3.5.2'
+ mavenResolver = '1.1.0'
+ okhttp = '3.9.1'
+ okio = '1.13.0'
+ retrofit = '2.3.0'
+ slf4j = '1.7.3'
+ spark = '2.6.0'
+ testng = '6.12'
+
+ junit = '4.12'
+ junitJupiter = '5.1.0'
+ junitPlatform = '1.1.0'
+ }
repositories {
+ mavenCentral()
+ mavenLocal()
jcenter()
- }
- dependencies {
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}"
- }
-}
+ maven {
+ url = 'https://dl.bintray.com/cbeust/maven'
+ }
-plugins {
- id "com.jfrog.bintray" version "1.2"
-}
-
-version = '0.121'
-
-//apply plugin: 'java'
-apply plugin: 'kotlin'
-apply plugin: 'com.jfrog.bintray'
-
-apply from: 'gradle/publishing.gradle'
-
-repositories {
- jcenter()
-}
-
-dependencies {
- compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}",
- "org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlin_version}",
- 'org.jetbrains.kotlinx:kotlinx.dom:0.0.4',
- 'org.jetbrains.dokka:dokka-fatjar:0.9.2',
-// "org.jetbrains.kotlin:kotlin-compiler:${kotlin_version}",
-
- "com.android.tools.build:builder:1.5.0",
-
- 'com.beust:jcommander:1.48',
- 'com.squareup.okhttp:okhttp:2.5.0',
- 'org.jsoup:jsoup:1.8.3',
- 'com.google.inject:guice:4.0',
- 'com.google.inject.extensions:guice-assistedinject:4.0',
- 'javax.inject:javax.inject:1',
- 'com.google.guava:guava:19.0-rc2',
- 'org.apache.maven:maven-model:3.3.3',
- 'com.github.spullara.mustache.java:compiler:0.9.1',
- 'io.reactivex:rxjava:1.0.16',
- 'com.google.code.gson:gson:2.4',
- 'com.squareup.retrofit:retrofit:1.9.0',
- 'com.squareup.okio:okio:1.6.0',
- project("modules/wrapper")
-
-// compile files("/Users/beust/.kobalt/repository/com/beust/kobalt-example-plugin/build/libs/kobalt-example-plugin.jar")
- testCompile 'org.testng:testng:6.9.9'
-// testCompile 'junit:junit:4.12'
-}
-
-task sourceJar(type: Jar) {
- group 'Build'
- description 'An archive of the source code'
- classifier 'sources'
- from sourceSets.main.allSource
-}
-
-artifacts {
- file('build/libs/kobalt.jar')
- sourceJar
-}
-
-test {
- useTestNG()
- beforeTest { descriptor ->
- logger.lifecycle(" Running test: " + descriptor)
- }
-}
-
-compileKotlin {
- kotlinOptions.suppressWarnings = true
-}
-
-apply plugin: 'application'
-mainClassName = 'com.beust.kobalt.KobaltPackage'
-
-jar {
- manifest {
- attributes "Main-Class": "$mainClassName"
+ maven {
+ url = 'https://repo.maven.apache.org/maven2'
+ }
}
- from {
- configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
+ sourceCompatibility = '1.7'
+
+ task sourcesJar(type: Jar) {
+ from sourceSets.main.allJava
+ archiveClassifier = 'sources'
+ }
+
+ task javadocJar(type: Jar) {
+ from javadoc
+ archiveClassifier = 'javadoc'
}
}
-
diff --git a/dist/kobaltw b/dist/kobaltw
new file mode 100755
index 00000000..333738df
--- /dev/null
+++ b/dist/kobaltw
@@ -0,0 +1,11 @@
+#!/usr/bin/env sh
+
+case "$(uname)" in
+ CYGWIN*) DIRNAME=$(cygpath -d "$(dirname "$(readlink -f "$0")")");;
+ Darwin*) DIRNAME=$(dirname "$(readlink "$0")");;
+ *) DIRNAME=$(dirname "$(readlink -f "$0")");;
+esac
+if [ "$DIRNAME" = "." ]; then
+ DIRNAME="$(dirname "$0")"
+fi
+java -jar "${DIRNAME}/../kobalt/wrapper/kobalt-wrapper.jar" $*
\ No newline at end of file
diff --git a/dist/kobaltw.bat b/dist/kobaltw.bat
new file mode 100644
index 00000000..2d95345e
--- /dev/null
+++ b/dist/kobaltw.bat
@@ -0,0 +1,4 @@
+@echo off
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+java -jar "%~dp0/../kobalt/wrapper/kobalt-wrapper.jar" %*
diff --git a/gradle/buildWithTravis.sh b/gradle/buildWithTravis.sh
deleted file mode 100644
index 9f0ddf10..00000000
--- a/gradle/buildWithTravis.sh
+++ /dev/null
@@ -1 +0,0 @@
-../gradlew check
diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle
deleted file mode 100644
index 3e9e7c42..00000000
--- a/gradle/publishing.gradle
+++ /dev/null
@@ -1,60 +0,0 @@
-import java.text.SimpleDateFormat
-
-Date buildTimeAndDate = new Date()
-ext {
- buildTime = new SimpleDateFormat('yyyy-MM-dd').format(buildTimeAndDate)
- buildDate = new SimpleDateFormat('HH:mm:ss.SSSZ').format(buildTimeAndDate)
-}
-
-apply plugin: 'maven-publish'
-apply plugin: 'com.jfrog.bintray'
-
-jar {
- manifest {
- attributes(
- 'Built-By': System.properties['user.name'],
- 'Created-By': System.properties['java.version'] + " (" + System.properties['java.vendor'] + " " + System.getProperty("java.vm.version") + ")",
- 'Build-Date': project.buildTime,
- 'Build-Time': project.buildDate,
- 'Specification-Title': project.name,
- 'Specification-Version': project.version,
- )
- }
-}
-
-publishing {
- publications {
- mavenCustom(MavenPublication) {
- from components.java
- artifact sourceJar
-
- groupId 'com.beust'
- artifactId 'kobalt'
- version project.version
- }
- }
-}
-
-task install(dependsOn: publishToMavenLocal)
-
-Properties properties = new Properties()
-if (new File('local.properties').exists()) {
- properties.load(project.rootProject.file('local.properties').newDataInputStream())
-}
-
-bintray {
- user = properties.getProperty("bintray.user")
- key = properties.getProperty("bintray.apikey")
- publications = ['mavenCustom']
- pkg {
- repo = 'maven'
- name = 'klaxon'
- desc = 'JSON parsing for Kotlin'
- licenses = ['Apache-2.0']
- labels = ['kotlin']
-
- version {
- name = project.version //Bintray logical version name
- }
- }
-}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 30d399d8..5c2d1cf0 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 5724a1d2..838e6bc8 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Sun Oct 04 21:38:45 EDT 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-all.zip
diff --git a/gradlew b/gradlew
index 91a7e269..b0d6d0ab 100755
--- a/gradlew
+++ b/gradlew
@@ -1,4 +1,20 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
##############################################################################
##
@@ -6,47 +22,6 @@
##
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
- echo "$*"
-}
-
-die ( ) {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
-esac
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -61,9 +36,49 @@ while [ -h "$PRG" ] ; do
fi
done
SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >&-
+cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
-cd "$SAVED" >&-
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -90,7 +105,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -114,6 +129,7 @@ fi
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
@@ -154,11 +170,19 @@ if $cygwin ; then
esac
fi
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=$(save "$@")
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index 8a0b282a..9991c503 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,3 +1,19 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem http://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -8,14 +24,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +62,9 @@ echo location of your Java installation.
goto fail
:init
-@rem Get command-line arguments, handling Windowz variants
+@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +75,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
diff --git a/kobalt-incremental-tasks.md b/kobalt-incremental-tasks.md
new file mode 100644
index 00000000..f799a81a
--- /dev/null
+++ b/kobalt-incremental-tasks.md
@@ -0,0 +1,78 @@
+Kobalt's incremental task algorithm is not based on timestamps but on checksums.
+
+You make a task incremental by declaring it `@IncrementalTask` instead of `@Task`. The only other difference is that instead of returning a `TaskResult`, incremental tasks return an `IncrementalTaskInfo`:
+
+```
+class IncrementalTaskInfo(
+ val inputChecksum: String?,
+ val outputChecksum: String?,
+ val task: (Project) -> TaskResult)
+```
+
+This class contains three fields:
+
+- A task closure, which is your effective task: `(Project) -> TaskResult`
+- An input checksum (`String?`)
+- An output checksum (`String?`)
+
+These checksums are numbers that each task calculates for their input and output. For example, the `"compile"` task calculates an MD5 checksum of all the source files. Similarly, the output checksum is for produced artifacts, e.g. checksum of `.class` files or `.jar` files, etc...
+
+Example of an incremental task:
+
+```
+ @IncrementalTask(name = JvmCompilerPlugin.TASK_COMPILE, description = "Compile the project")
+ fun taskCompile(project: Project) : IncrementalTaskInfo {
+ val inputChecksum = Md5.toMd5Directories(project.sourceDirectories.map {
+ File(project.directory, it)
+ })
+ return IncrementalTaskInfo(
+ inputChecksum = inputChecksum,
+ outputChecksum = "1",
+ task = { project -> doTaskCompile(project) }
+ )
+ }
+```
+
+The advantage of checksums is that they take care of all the scenarios that would cause that task to run:
+
+- A file was modified
+- A file was added
+- A file was removed
+
+The output checksum covers the case where the input is unchanged but the output files were deleted or modified. If
+both the input and output checksums match the previous run, it's extremely likely that the task has nothing to do.
+
+Another advantage of checksums is that they are generic and not necessarily tied to files. For example, a Kobalt task might perform some network operations and return a checksum based on a network result to avoid performing a more expensive operation (e.g. don't download a file from a server if it hasn't changed).
+
+Internally, Kobalt maintains information about all the checksums and tasks that it has seen in a file `.kobalt/build-info.json`. Whenever an incremental task is about to run, Kobalt compares its input and output checksums to the ones from the previous run and if any differs, that task is run. Otherwise, it's skipped.
+
+Example timings for Kobalt:
+
+| Task | First run | Second run |
+| ---- | --------- | ---------- |
+| kobalt-wrapper:compile | 627 ms | 22 ms |
+| kobalt-wrapper:assemble | 9 ms | 9 ms |
+| kobalt-plugin-api:compile | 10983 ms | 54 ms |
+| kobalt-plugin-api:assemble | 1763 ms | 154 ms |
+| kobalt:compile | 11758 ms | 11 ms |
+| kobalt:assemble | 42333 ms | 2130 ms |
+| | 70 seconds | 2 seconds |
+
+Android (u2020):
+
+| Task | First run | Second run |
+| ---- | --------- | ---------- |
+| u2020:generateRInternalDebug | 32350 ms | 1652 ms |
+| u2020:compileInternalDebug | 3629 ms | 24 ms |
+| u2020:retrolambdaInternalDebug | 668 ms | 473 ms |
+| u2020:generateDexInternalDebug | 6130 ms |55 ms |
+| u2020:signApkInternalDebug | 449 ms | 404 ms |
+| u2020:assembleInternalDebug | 0 ms | 0 ms |
+| | 43 seconds | 2 seconds |
+
+
+
+
+
+
+
diff --git a/kobalt.iml b/kobalt.iml
deleted file mode 100644
index 80485be5..00000000
--- a/kobalt.iml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/kobalt/src/Build.kt b/kobalt/src/Build.kt
index c06c3c40..0d09844a 100644
--- a/kobalt/src/Build.kt
+++ b/kobalt/src/Build.kt
@@ -1,24 +1,61 @@
import com.beust.kobalt.*
-import com.beust.kobalt.api.*
+import com.beust.kobalt.api.Project
import com.beust.kobalt.api.annotation.Task
import com.beust.kobalt.plugin.application.application
-import com.beust.kobalt.plugin.java.*
-import com.beust.kobalt.plugin.kotlin.*
+import com.beust.kobalt.plugin.java.javaCompiler
+import com.beust.kobalt.plugin.kotlin.kotlinCompiler
import com.beust.kobalt.plugin.packaging.assemble
+import com.beust.kobalt.plugin.publish.autoGitTag
+import com.beust.kobalt.plugin.publish.bintray
import com.beust.kobalt.plugin.publish.github
-import com.beust.kobalt.plugin.publish.jcenter
-import com.beust.kobalt.plugin.retrolambda.*
-import com.beust.kobalt.test
+import org.apache.maven.model.Developer
+import org.apache.maven.model.License
+import org.apache.maven.model.Model
+import org.apache.maven.model.Scm
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
-val r = repos("http://dl.bintray.com/kotlin/kotlinx.dom")
+val bs = buildScript {
+ repos("https://dl.bintray.com/cbeust/maven")
+}
-val wrapper = javaProject {
+object Versions {
+ val kotlin = "1.2.71"
+ val okhttp = "3.9.1"
+ val okio = "1.13.0"
+ val retrofit = "2.3.0"
+ val gson = "2.8.2"
+ val guice = "4.2.2"
+ val maven = "3.5.2"
+ val mavenResolver = "1.1.0"
+ val slf4j = "1.7.3"
+ val aether = "1.0.2.v20150114"
+ val testng = "6.12"
+ val jcommander = "1.72"
+
+ // JUnit 5
+ val junit = "4.12"
+ val junitPlatform = "1.1.0"
+ val junitJupiter = "5.1.0"
+}
+
+fun mavenResolver(vararg m: String)
+ = m.map { "org.apache.maven.resolver:maven-resolver-$it:${Versions.mavenResolver}" }
+ .toTypedArray()
+
+fun aether(vararg m: String)
+ = m.map { "org.eclipse.aether:aether-$it:${Versions.aether}" }
+ .toTypedArray()
+
+val wrapper = project {
name = "kobalt-wrapper"
+ group = "com.beust"
+ artifactId = name
version = readVersion()
directory = "modules/wrapper"
@@ -27,6 +64,7 @@ val wrapper = javaProject {
}
assemble {
+ jar { }
jar {
name = projectName + ".jar"
manifest {
@@ -35,58 +73,63 @@ val wrapper = javaProject {
}
}
-// retrolambda {
-// }
-
- productFlavor("dev") {
- }
-
- buildType("debug") {
- }
-
application {
mainClass = "com.beust.kobalt.wrapper.Main"
}
+
+ bintray {
+ publish = true
+ sign = true
+ }
+
+ pom = createPom(name, "Wrapper for Kobalt")
}
-val kobalt = kotlinProject(wrapper) {
- name = "kobalt"
+val kobaltPluginApi = project {
+ name = "kobalt-plugin-api"
group = "com.beust"
artifactId = name
version = readVersion()
+ directory = "modules/kobalt-plugin-api"
description = "A build system in Kotlin"
- url = "http://beust.com/kobalt"
- licenses = arrayListOf(License("Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0"))
- scm = Scm(url = "http://github.com/cbeust/kobalt",
- connection = "https://github.com/cbeust/kobalt.git",
- developerConnection = "git@github.com:cbeust/kobalt.git")
+ url = "https://beust.com/kobalt"
- dependenciesTest {
- compile("org.testng:testng:6.9.9")
- }
+ pom = createPom(name, "A build system in Kotlin")
dependencies {
- compile("org.jetbrains.kotlin:kotlin-stdlib:1.0.0-beta-3595",
- "org.jetbrains.kotlin:kotlin-compiler-embeddable:1.0.0-beta-3595",
- "org.jetbrains.dokka:dokka-fatjar:0.9.2",
- "org.jetbrains.kotlinx:kotlinx.dom:0.0.4",
-
- "com.android.tools.build:builder:1.5.0",
-
- "com.beust:jcommander:1.48",
- "com.squareup.okhttp:okhttp:2.5.0",
- "org.jsoup:jsoup:1.8.3",
- "com.google.inject:guice:4.0",
- "com.google.inject.extensions:guice-assistedinject:4.0",
+ compile(
+ "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}",
+ "com.google.inject:guice:${Versions.guice}",
+ "com.google.inject.extensions:guice-assistedinject:4.1.0",
"javax.inject:javax.inject:1",
- "com.google.guava:guava:19.0-rc2",
- "org.apache.maven:maven-model:3.3.3",
- "com.github.spullara.mustache.java:compiler:0.9.1",
- "io.reactivex:rxjava:1.0.16",
- "com.google.code.gson:gson:2.4",
- "com.squareup.retrofit:retrofit:1.9.0",
- "com.squareup.okio:okio:1.6.0"
- )
+ "com.google.guava:guava:27.0.1-jre",
+ "org.apache.maven:maven-model:${Versions.maven}",
+ "io.reactivex:rxjava:1.3.3",
+ "com.squareup.okio:okio:${Versions.okio}",
+ "com.google.code.gson:gson:${Versions.gson}",
+ "com.squareup.okhttp3:okhttp:${Versions.okhttp}",
+ "com.squareup.retrofit2:retrofit:${Versions.retrofit}",
+ "com.squareup.retrofit2:converter-gson:${Versions.retrofit}",
+ "com.beust:jcommander:${Versions.jcommander}",
+ "org.eclipse.jgit:org.eclipse.jgit:4.9.0.201710071750-r",
+ "org.slf4j:slf4j-simple:${Versions.slf4j}",
+ *mavenResolver("api", "spi", "util", "impl", "connector-basic", "transport-http", "transport-file"),
+ "org.apache.maven:maven-aether-provider:3.3.9",
+ "org.testng.testng-remote:testng-remote:1.3.2",
+ "org.testng:testng:${Versions.testng}",
+ "org.junit.platform:junit-platform-surefire-provider:${Versions.junitPlatform}",
+ "org.junit.platform:junit-platform-runner:${Versions.junitPlatform}",
+ "org.junit.platform:junit-platform-engine:${Versions.junitPlatform}",
+ "org.junit.platform:junit-platform-console:${Versions.junitPlatform}",
+ "org.junit.jupiter:junit-jupiter-engine:${Versions.junitJupiter}",
+ "org.junit.vintage:junit-vintage-engine:${Versions.junitJupiter}",
+ "org.apache.commons:commons-compress:1.15",
+ "commons-io:commons-io:2.6",
+
+ // Java 9
+ "javax.xml.bind:jaxb-api:2.3.0"
+ )
+ exclude(*aether("impl", "spi", "util", "api"))
}
@@ -97,64 +140,187 @@ val kobalt = kotlinProject(wrapper) {
attributes("Main-Class", "com.beust.kobalt.MainKt")
}
}
- zip {
- include("kobaltw")
- include(from("$buildDirectory/libs"), to("kobalt/wrapper"),
- "$projectName-$version.jar")
- include(from("modules/wrapper/$buildDirectory/libs"), to("kobalt/wrapper"),
- "$projectName-wrapper.jar")
- }
- }
-
-// install {
-// libDir = "lib-test"
-// }
-
- test {
- args("-log", "1", "src/test/resources/testng.xml")
}
kotlinCompiler {
- args("-nowarn")
+ args("nowarn")
}
-// dokka {
-// outputFormat = "markdown"
-// }
-//
-// dokka {
-// outputFormat = "html"
-// }
+ bintray {
+ publish = true
+ }
+}
+
+val kobaltApp = project(kobaltPluginApi, wrapper) {
+ name = "kobalt"
+ group = "com.beust"
+ artifactId = name
+ version = readVersion()
+
+ dependencies {
+ // Used by the plugins
+ compile("org.jetbrains.kotlin:kotlin-compiler-embeddable:${Versions.kotlin}")
+
+ // Used by the main app
+ compile(
+ "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}",
+ "com.github.spullara.mustache.java:compiler:0.9.5",
+ "javax.inject:javax.inject:1",
+ "com.google.inject:guice:${Versions.guice}",
+ "com.google.inject.extensions:guice-assistedinject:${Versions.guice}",
+ "com.beust:jcommander:${Versions.jcommander}",
+ "org.apache.maven:maven-model:${Versions.maven}",
+ "com.google.code.findbugs:jsr305:3.0.2",
+ "com.google.code.gson:gson:${Versions.gson}",
+ "com.squareup.retrofit2:retrofit:${Versions.retrofit}",
+ "com.squareup.retrofit2:converter-gson:${Versions.retrofit}",
+// "com.squareup.okhttp3:okhttp-ws:3.4.2",
+ "biz.aQute.bnd:biz.aQute.bndlib:3.5.0",
+ *mavenResolver("spi"),
+
+ "com.squareup.okhttp3:logging-interceptor:3.9.0",
+
+ "com.sparkjava:spark-core:2.6.0",
+ "org.codehaus.groovy:groovy:2.4.12",
+
+ // Java 9
+ "javax.xml.bind:jaxb-api:2.3.0",
+ "com.sun.xml.bind:jaxb-impl:2.3.0",
+ "com.sun.xml.bind:jaxb-core:2.3.0",
+ "com.sun.activation:javax.activation:1.2.0"
+
+// "org.eclipse.jetty:jetty-server:${Versions.jetty}",
+// "org.eclipse.jetty:jetty-servlet:${Versions.jetty}",
+// "org.glassfish.jersey.core:jersey-server:${Versions.jersey}",
+// "org.glassfish.jersey.containers:jersey-container-servlet-core:${Versions.jersey}",
+// "org.glassfish.jersey.containers:jersey-container-jetty-http:${Versions.jersey}",
+// "org.glassfish.jersey.media:jersey-media-moxy:${Versions.jersey}",
+// "org.wasabi:wasabi:0.1.182"
+ )
+
+ }
+
+ dependenciesTest {
+ compile("org.jetbrains.kotlin:kotlin-test:${Versions.kotlin}",
+ "org.testng:testng:${Versions.testng}",
+ "org.assertj:assertj-core:3.8.0",
+ *mavenResolver("util")
+ )
+ }
+
+ assemble {
+ mavenJars {
+ fatJar = true
+ manifest {
+ attributes("Main-Class", "com.beust.kobalt.MainKt")
+ }
+ }
+ zip {
+ val dir = "kobalt-$version"
+ val files = listOf(
+ "dist", "$dir/bin", "kobaltw",
+ "dist", "$dir/bin", "kobaltw.bat",
+ "$buildDirectory/libs", "$dir/kobalt/wrapper", "$projectName-$version.jar",
+ "modules/wrapper/$buildDirectory/libs", "$dir/kobalt/wrapper", "$projectName-wrapper.jar")
+
+ (0 .. files.size - 1 step 3).forEach { i ->
+ include(from(files[i]), to(files[i + 1]), files[i + 2])
+ }
+
+ // Package the sources
+ val currentDir = Paths.get(".").toAbsolutePath().normalize().toString()
+ zipFolders("$currentDir/$buildDirectory/libs/all-sources/$projectName-$version-sources.jar",
+ "$currentDir/$directory/src/main/kotlin",
+ "$currentDir/${kobaltPluginApi.directory}/src/main/kotlin")
+ include(from("$buildDirectory/libs/all-sources"), to("$dir/kobalt/wrapper"), "$projectName-$version-sources.jar")
+ }
+ }
+
+ kotlinCompiler {
+ args("nowarn")
+ }
+
+ bintray {
+ publish = true
+ }
github {
file("$buildDirectory/libs/$name-$version.zip", "$name/$version/$name-$version.zip")
}
- jcenter {
- publish = true
+ test {
+ args("-log", "2", "src/test/resources/testng.xml")
+ }
+
+ autoGitTag {
+ enabled = true
+ }
+}
+
+fun zipFolders(zipFilePath: String, vararg foldersPath: String) {
+ val zip = Paths.get(zipFilePath)
+ Files.deleteIfExists(zip)
+ Files.createDirectories(zip.parent)
+ val zipPath = Files.createFile(zip)
+ ZipOutputStream(Files.newOutputStream(zipPath)).use {
+ foldersPath.map {Paths.get(it)}.forEach { folderPath ->
+ Files.walk(folderPath)
+ .filter { path -> !Files.isDirectory(path) }
+ .forEach { path ->
+ val zipEntry = ZipEntry(folderPath.relativize(path).toString())
+ try {
+ it.putNextEntry(zipEntry)
+ Files.copy(path, it)
+ it.closeEntry()
+ } catch (e: Exception) {
+ }
+ }
+ }
}
}
fun readVersion() : String {
- val p = java.util.Properties()
- var localFile = java.io.File("src/main/resources/kobalt.properties")
- if (! localFile.exists()) {
- localFile = File(homeDir("kotlin", "kobalt", "src/main/resources/kobalt.properties"))
+ val localFile =
+ listOf("src/main/resources/kobalt.properties",
+ homeDir("kotlin", "kobalt", "src/main/resources/kobalt.properties")).first { File(it).exists() }
+ with(java.util.Properties()) {
+ load(java.io.FileReader(localFile))
+ return getProperty("kobalt.version")
}
- p.load(java.io.FileReader(localFile))
- return p.getProperty("kobalt.version")
}
-@Task(name = "copyVersionForWrapper", runBefore = arrayOf("assemble"), runAfter = arrayOf("compile"), description = "")
+@Task(name = "copyVersionForWrapper", reverseDependsOn = arrayOf("assemble"), runAfter = arrayOf("clean"))
fun taskCopyVersionForWrapper(project: Project) : TaskResult {
if (project.name == "kobalt-wrapper") {
val toString = "modules/wrapper/kobaltBuild/classes"
File(toString).mkdirs()
val from = Paths.get("src/main/resources/kobalt.properties")
val to = Paths.get("$toString/kobalt.properties")
- Files.copy(from,
- to,
- StandardCopyOption.REPLACE_EXISTING)
+ // Only copy if necessary so we don't break incremental compilation
+ if (! to.toFile().exists() || (from.toFile().readLines() != to.toFile().readLines())) {
+ Files.copy(from,
+ to,
+ StandardCopyOption.REPLACE_EXISTING)
+ }
}
return TaskResult()
}
+
+fun createPom(projectName: String, projectDescription: String) = Model().apply {
+ name = projectName
+ description = projectDescription
+ url = "https://beust.com/kobalt"
+ licenses = listOf(License().apply {
+ name = "Apache-2.0"
+ url = "https://www.apache.org/licenses/LICENSE-2.0"
+ })
+ scm = Scm().apply {
+ url = "https://github.com/cbeust/kobalt"
+ connection = "https://github.com/cbeust/kobalt.git"
+ developerConnection = "git@github.com:cbeust/kobalt.git"
+ }
+ developers = listOf(Developer().apply {
+ name = "Cedric Beust"
+ email = "cedric@beust.com"
+ })
+}
diff --git a/kobalt/wrapper/kobalt-wrapper.jar b/kobalt/wrapper/kobalt-wrapper.jar
index e596833f..848fb463 100644
Binary files a/kobalt/wrapper/kobalt-wrapper.jar and b/kobalt/wrapper/kobalt-wrapper.jar differ
diff --git a/kobalt/wrapper/kobalt-wrapper.properties b/kobalt/wrapper/kobalt-wrapper.properties
index c66b786f..0ca8045f 100644
--- a/kobalt/wrapper/kobalt-wrapper.properties
+++ b/kobalt/wrapper/kobalt-wrapper.properties
@@ -1 +1 @@
-kobalt.version=0.325
\ No newline at end of file
+kobalt.version=1.0.122
\ No newline at end of file
diff --git a/kobaltw b/kobaltw
index 1fd228db..c5186d5a 100755
--- a/kobaltw
+++ b/kobaltw
@@ -1,2 +1,2 @@
-#!/usr/bin/env bash
-java -jar $(dirname $0)/kobalt/wrapper/kobalt-wrapper.jar $*
+#!/usr/bin/env sh
+java -jar "`dirname "$0"`/kobalt/wrapper/kobalt-wrapper.jar" $*
diff --git a/kobaltw-test b/kobaltw-test
new file mode 100755
index 00000000..2693c3aa
--- /dev/null
+++ b/kobaltw-test
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+JAR=$(ls -1 -t kobaltBuild/libs/*.jar | grep -Ev "(sources|javadoc)" | head -1)
+TEMPDIR=$(mktemp -d)
+cp -pf "$JAR" "$TEMPDIR"
+TEMPJAR=$TEMPDIR/$(basename "$JAR")
+export KOBALT_JAR=$TEMPJAR
+java -jar "$TEMPJAR" "$@"
+rm -rf "$TEMPDIR"
\ No newline at end of file
diff --git a/kobaltw.bat b/kobaltw.bat
index 8caea129..2887a567 100644
--- a/kobaltw.bat
+++ b/kobaltw.bat
@@ -1,2 +1,4 @@
-@echo off
-java -jar %~dp0/kobalt/wrapper/kobalt-wrapper.jar %*
+@echo off
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+java -jar "%DIRNAME%/kobalt/wrapper/kobalt-wrapper.jar" %*
diff --git a/modules/kobalt-plugin-api/build.gradle b/modules/kobalt-plugin-api/build.gradle
new file mode 100644
index 00000000..56085220
--- /dev/null
+++ b/modules/kobalt-plugin-api/build.gradle
@@ -0,0 +1,85 @@
+plugins {
+ id 'org.jetbrains.kotlin.jvm' version '1.2.71'
+ id 'com.github.johnrengelman.shadow' version '5.0.0'
+}
+
+dependencies {
+ implementation "biz.aQute.bnd:biz.aQute.bndlib:$bndlib"
+ implementation "com.google.code.findbugs:jsr305:$findbugs"
+ implementation "com.sparkjava:spark-core:$spark"
+ implementation "com.squareup.okhttp3:logging-interceptor:$okhttp"
+ implementation 'commons-io:commons-io:2.6'
+ implementation 'io.reactivex:rxjava:1.3.3'
+ implementation "javax.inject:javax.inject:$inject"
+ implementation "javax.xml.bind:jaxb-api:$jaxb"
+ implementation 'org.apache.commons:commons-compress:1.15'
+ implementation 'org.apache.maven:maven-aether-provider:3.3.9'
+ implementation "org.apache.maven.resolver:maven-resolver-api:$mavenResolver"
+ implementation "org.apache.maven.resolver:maven-resolver-connector-basic:$mavenResolver"
+ implementation "org.apache.maven.resolver:maven-resolver-impl:$mavenResolver"
+ implementation "org.apache.maven.resolver:maven-resolver-spi:$mavenResolver"
+ implementation "org.apache.maven.resolver:maven-resolver-transport-file:$mavenResolver"
+ implementation "org.apache.maven.resolver:maven-resolver-transport-http:$mavenResolver"
+ implementation "org.apache.maven.resolver:maven-resolver-util:$mavenResolver"
+ implementation "org.codehaus.groovy:groovy:$groovy"
+ implementation 'org.eclipse.jgit:org.eclipse.jgit:4.9.0.201710071750-r'
+ implementation "org.junit.jupiter:junit-jupiter-engine:$junitJupiter"
+ implementation "org.junit.platform:junit-platform-console:$junitPlatform"
+ implementation "org.junit.platform:junit-platform-engine:$junitPlatform"
+ implementation "org.junit.platform:junit-platform-runner:$junitPlatform"
+ implementation "org.junit.platform:junit-platform-surefire-provider:$junitPlatform"
+ implementation "org.junit.vintage:junit-vintage-engine:$junitJupiter"
+ implementation "org.slf4j:slf4j-simple:$slf4j"
+ implementation "org.testng:testng:$testng"
+ implementation 'org.testng.testng-remote:testng-remote:1.3.2'
+ implementation "com.beust:jcommander:$jcommander"
+ implementation "com.google.code.gson:gson:$gson"
+ implementation "com.google.inject:guice:$guice"
+ implementation "com.google.inject.extensions:guice-assistedinject:$guice"
+ implementation "com.squareup.okio:okio:$okio"
+ implementation "com.squareup.retrofit2:converter-gson:$retrofit"
+ implementation "com.squareup.retrofit2:retrofit:$retrofit"
+ implementation "org.apache.maven:maven-model:$maven"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin"
+}
+
+shadowJar {
+ classifier = null
+}
+
+test {
+ useTestNG()
+}
+
+publishing {
+ publications {
+ shadow(MavenPublication) { publication ->
+ project.shadow.component(publication)
+ artifact sourcesJar
+ artifact javadocJar
+
+ pom {
+ name = project.name
+ description = 'A build system in Kotlin'
+ url = 'https://beust.com/kobalt'
+ licenses {
+ license {
+ name = 'Apache-2.0'
+ url = 'https://www.apache.org/licenses/LICENSE-2.0'
+ }
+ }
+ developers {
+ developer {
+ name = 'Cedric Beust'
+ email = 'cedric@beust.com'
+ }
+ }
+ scm {
+ connection = 'scm:https://github.com/cbeust/kobalt.git'
+ developerConnection = 'scm:git@github.com:cbeust/kobalt.git'
+ url = 'https://github.com/cbeust/kobalt'
+ }
+ }
+ }
+ }
+}
diff --git a/modules/kobalt-plugin-api/pom.xml b/modules/kobalt-plugin-api/pom.xml
new file mode 100644
index 00000000..f9026387
--- /dev/null
+++ b/modules/kobalt-plugin-api/pom.xml
@@ -0,0 +1,279 @@
+
+ 4.0.0
+
+ com.beust
+ kobalt-pom
+ 1.1.0
+ ../..
+
+
+ kobalt-plugin-api
+ jar
+ 1.1.0
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ ${kotlin.version}
+
+
+ org.apache.maven
+ maven-aether-provider
+ 3.3.9
+
+
+ org.eclipse.aether
+ impl
+
+
+ org.eclipse.aether
+ spi
+
+
+ org.eclipse.aether
+ util
+
+
+ org.eclipse.aether
+ api
+
+
+
+
+ org.apache.maven.resolver
+ maven-resolver-api
+ ${mavenresolver.version}
+
+
+ org.apache.maven.resolver
+ maven-resolver-spi
+ ${mavenresolver.version}
+
+
+ org.apache.maven.resolver
+ maven-resolver-util
+ ${mavenresolver.version}
+
+
+ org.apache.maven.resolver
+ maven-resolver-impl
+ ${mavenresolver.version}
+
+
+ org.apache.maven.resolver
+ maven-resolver-connector-basic
+ ${mavenresolver.version}
+
+
+ org.apache.maven.resolver
+ maven-resolver-transport-http
+ ${mavenresolver.version}
+
+
+ org.apache.maven.resolver
+ maven-resolver-transport-file
+ ${mavenresolver.version}
+
+
+ io.reactivex
+ rxjava
+ 1.3.3
+
+
+ com.squareup.okio
+ okio
+ ${okio.version}
+
+
+ javax.inject
+ javax.inject
+ 1
+ compile
+
+
+ com.google.inject
+ guice
+ 4.2.2
+
+
+ com.google.inject.extensions
+ guice-assistedinject
+ 4.2.2
+
+
+ com.beust
+ jcommander
+ 1.72
+
+
+ org.apache.maven
+ maven-model
+ 3.5.2
+
+
+ com.google.code.findbugs
+ jsr305
+ 3.0.2
+
+
+ com.google.code.gson
+ gson
+ 2.8.2
+
+
+ com.squareup.retrofit2
+ retrofit
+ 2.3.0
+
+
+ com.squareup.retrofit2
+ converter-gson
+ 2.3.0
+
+
+ biz.aQute.bnd
+ biz.aQute.bndlib
+ 3.5.0
+
+
+ com.squareup.okhttp3
+ logging-interceptor
+ ${okhttp3.version}
+
+
+ com.sparkjava
+ spark-core
+ 2.6.0
+
+
+ org.codehaus.groovy
+ groovy
+ 2.4.12
+
+
+ org.apache.commons
+ commons-compress
+ 1.15
+
+
+ commons-io
+ commons-io
+ 2.6
+
+
+ org.junit.platform
+ junit-platform-surefire-provider
+ ${junit.version}
+
+
+ org.junit.platform
+ junit-platform-runner
+ ${junit.version}
+
+
+ org.junit.platform
+ junit-platform-engine
+ ${junit.version}
+
+
+ org.junit.platform
+ junit-platform-console
+ ${junit.version}
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junitJupiter.version}
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ ${junitJupiter.version}
+
+
+ org.testng.testng-remote
+ testng-remote
+ 1.3.2
+
+
+ org.testng
+ testng
+ ${testng.version}
+
+
+ org.eclipse.jgit
+ org.eclipse.jgit
+ 4.9.0.201710071750-r
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j.version}
+
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.0
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+
+ ${project.basedir}/src/main/kotlin
+
+
+
+
+ test-compile
+ test-compile
+
+
+ ${project.basedir}/src/test/kotlin
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.5.1
+
+
+
+ default-compile
+ none
+
+
+
+ default-testCompile
+ none
+
+
+ java-compile
+ compile
+ compile
+
+
+ java-test-compile
+ test-compile
+ testCompile
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/ArchiveGenerator.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/ArchiveGenerator.kt
new file mode 100644
index 00000000..8158c642
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/ArchiveGenerator.kt
@@ -0,0 +1,21 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.api.KobaltContext
+import com.beust.kobalt.api.Project
+import com.beust.kobalt.archive.Zip
+import com.beust.kobalt.misc.KFiles
+import java.io.File
+
+interface ArchiveGenerator {
+ fun findIncludedFiles(project: Project, context: KobaltContext, zip: Zip) : List
+ val suffix: String
+ fun generateArchive(project: Project, context: KobaltContext, zip: Zip, files: List) : File
+
+ fun fullArchiveName(project: Project, context: KobaltContext, archiveName: String?) : File {
+ val fullArchiveName = context.variant.archiveName(project, archiveName, suffix)
+ val archiveDir = File(KFiles.libsDir(project))
+ val result = File(archiveDir.path, fullArchiveName)
+ return result
+ }
+
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Args.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Args.kt
new file mode 100644
index 00000000..372f1ba1
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Args.kt
@@ -0,0 +1,111 @@
+package com.beust.kobalt
+
+import com.beust.jcommander.Parameter
+
+class Args {
+ @Parameter
+ var targets: List = arrayListOf()
+
+ @Parameter(names = arrayOf("-bf", "--buildFile"), description = "The build file")
+ var buildFile: String? = "kobalt/src/Build.kt"
+
+ @Parameter(names = arrayOf("--checkVersions"), description = "Check if there are any newer versions of the " +
+ "dependencies")
+ var checkVersions = false
+
+ @Parameter(names = arrayOf("--client"))
+ var client: Boolean = false
+
+ @Parameter(names = arrayOf("--dev"), description = "Turn on dev mode, resulting in a more verbose log output")
+ var dev: Boolean = false
+
+ @Parameter(names = arrayOf("--download"), description = "Force a download from the downloadUrl in the wrapper")
+ var download: Boolean = false
+
+ @Parameter(names = arrayOf("--downloadSources"),
+ description = "Force a download of sources and javadocs when resolving dependencies")
+ var downloadSources: Boolean = false
+
+ @Parameter(names = arrayOf("--dryRun"), description = "Display all the tasks that will get run without " +
+ "actually running them")
+ var dryRun: Boolean = false
+
+ @Parameter(names = arrayOf("--force"), description = "Force a new server to be launched even if another one" +
+ " is already running")
+ var force: Boolean = false
+
+ @Parameter(names = arrayOf("--gc"), description = "Delete old files")
+ var gc: Boolean = false
+
+ @Parameter(names = arrayOf("--help", "--usage"), description = "Display the help")
+ var usage: Boolean = false
+
+ @Parameter(names = arrayOf("-i", "--init"), description = "Invoke the templates named, separated by a comma")
+ var templates: String? = null
+
+ @Parameter(names = arrayOf("--listTemplates"), description = "List the available templates")
+ var listTemplates: Boolean = false
+
+ @Parameter(names = arrayOf("--log"), description = "Define the log level " +
+ "(${Constants.LOG_QUIET_LEVEL}-${Constants.LOG_MAX_LEVEL})")
+ var log: Int = Constants.LOG_DEFAULT_LEVEL
+
+ @Parameter(names = arrayOf("--logTags"),
+ description = "Comma-separated list of tags to enable logging for")
+ var logTags: String = ""
+
+ @Parameter(names = arrayOf("--forceIncremental"),
+ description = "Force the build to be incremental even if the build file was modified")
+ var forceIncremental: Boolean = false
+
+ @Parameter(names = arrayOf("--noIncremental"), description = "Turn off incremental builds")
+ var noIncremental: Boolean = false
+
+ @Parameter(names = arrayOf("--offline"), description = "Don't try to download dependencies even if there is no cached version")
+ var offline: Boolean = false
+
+ @Parameter(names = arrayOf("--plugins"), description = "Comma-separated list of plug-in Maven id's")
+ var pluginIds: String? = null
+
+ @Parameter(names = arrayOf("--pluginJarFiles"), description = "Comma-separated list of plug-in jar files")
+ var pluginJarFiles: String? = null
+
+ @Parameter(names = arrayOf("--port"), description = "Port, if --server was specified")
+ var port: Int? = null
+
+ @Parameter(names = arrayOf("--profiles"), description = "Comma-separated list of profiles to run")
+ var profiles: String? = null
+
+ @Parameter(names = arrayOf("--profiling"), description = "Display task timings at the end of the build")
+ var profiling: Boolean = false
+
+ @Parameter(names = arrayOf("--resolve"),
+ description = "Resolve the given dependency and display its tree")
+ var dependency: String? = null
+
+ @Parameter(names = arrayOf("--projectInfo"), description = "Display information about the current projects")
+ var projectInfo: Boolean = false
+
+ @Parameter(names = arrayOf("--noIncrementalKotlin"), description = "Disable incremental Kotlin compilation")
+ var noIncrementalKotlin: Boolean = false
+
+ companion object {
+ const val SEQUENTIAL = "--sequential"
+ }
+
+ @Parameter(names = arrayOf(Args.SEQUENTIAL), description = "Build all the projects in sequence")
+ var sequential: Boolean = false
+
+ @Parameter(names = arrayOf("--server"), description = "Run in server mode")
+ var serverMode: Boolean = false
+
+ @Parameter(names = arrayOf("--tasks"), description = "Display the tasks available for this build")
+ var tasks: Boolean = false
+
+ @Parameter(names = arrayOf("--update"), description = "Update to the latest version of Kobalt")
+ var update: Boolean = false
+
+ @Parameter(names = arrayOf("--version"), description = "Display the current version of Kobalt")
+ var version: Boolean = false
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/AsciiArt.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/AsciiArt.kt
new file mode 100644
index 00000000..e138fabc
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/AsciiArt.kt
@@ -0,0 +1,164 @@
+package com.beust.kobalt
+
+import java.util.*
+
+/**
+ * Make Kobalt's output awesome and unique.
+ *
+ * I spend so much time staring at build outputs I decided I might as well make them pretty.
+ * Note that I also experimented with colors but it's hard to come up with a color scheme that
+ * will work with all the various backgrounds developers use, so I decided to be conservative
+ * and stick to simple red/yellow for errors and warnings.
+ *
+ * @author Cedric Beust
+ * @since 10/1/2015
+ */
+class AsciiArt {
+ companion object {
+ private val BANNERS = arrayOf(
+ " __ __ __ __ __ \n" +
+ " / //_/ ____ / /_ ____ _ / / / /_\n" +
+ " / ,< / __ \\ / __ \\ / __ `/ / / / __/\n" +
+ " / /| | / /_/ / / /_/ // /_/ / / / / /_ \n" +
+ " /_/ |_| \\____/ /_.___/ \\__,_/ /_/ \\__/ ",
+
+ " _ __ _ _ _ \n" +
+ " | |/ / ___ | |__ __ _ | | | |_ \n" +
+ " | ' / / _ \\ | '_ \\ / _` | | | | __|\n" +
+ " | . \\ | (_) | | |_) | | (_| | | | | |_ \n" +
+ " |_|\\_\\ \\___/ |_.__/ \\__,_| |_| \\__| "
+ )
+
+ val banner : String get() = BANNERS[Random().nextInt(BANNERS.size)]
+
+// fun box(s: String) : List = box(listOf(s))
+
+ val horizontalSingleLine = "\u2500\u2500\u2500\u2500\u2500"
+ val horizontalDoubleLine = "\u2550\u2550\u2550\u2550\u2550"
+ val verticalBar = "\u2551"
+
+// fun horizontalLine(n: Int) = StringBuffer().apply {
+// repeat(n, { append("\u2500") })
+// }.toString()
+
+ // Repeat
+ fun r(n: Int, w: String) : String {
+ with(StringBuffer()) {
+ repeat(n, { append(w) })
+ return toString()
+ }
+ }
+
+ val h = "\u2550"
+ val ul = "\u2554"
+ val ur = "\u2557"
+ val bottomLeft = "\u255a"
+ val bottomRight = "\u255d"
+
+ // Bottom left with continuation
+ val bottomLeft2 = "\u2560"
+ // Bottom right with continuation
+ val bottomRight2 = "\u2563"
+
+ fun upperBox(max: Int) = ul + r(max + 2, h) + ur
+ fun lowerBox(max: Int, bl: String = bottomLeft, br : String = bottomRight) = bl + r(max + 2, h) + br
+
+ private fun box(strings: List, bl: String = bottomLeft, br: String = bottomRight) : List {
+ val v = verticalBar
+
+ val maxString: String = strings.maxBy { it.length } ?: ""
+ val max = maxString.length
+ val result = arrayListOf(upperBox(max))
+ result.addAll(strings.map { "$v ${center(it, max - 2)} $v" })
+ result.add(lowerBox(max, bl, br))
+ return result
+ }
+
+ fun logBox(strings: List, bl: String = bottomLeft, br: String = bottomRight, indent: Int = 0): String {
+ return buildString {
+ val boxLines = box(strings, bl, br)
+ boxLines.withIndex().forEach { iv ->
+ append(fill(indent)).append(iv.value)
+ if (iv.index < boxLines.size - 1) append("\n")
+ }
+ }
+ }
+
+ fun logBox(s: String, bl: String = bottomLeft, br: String = bottomRight, indent: Int = 0)
+ = logBox(listOf(s), bl, br, indent)
+
+ fun fill(n: Int) = buildString { repeat(n, { append(" ")})}.toString()
+
+ fun center(s: String, width: Int) : String {
+ val diff = width - s.length
+ val spaces = diff / 2 + 1
+ return fill(spaces) + s + fill(spaces + if (diff % 2 == 1) 1 else 0)
+ }
+
+ const val RESET = "\u001B[0m"
+ const val BLACK = "\u001B[30m"
+ const val RED = "\u001B[31m"
+ const val GREEN = "\u001B[32m"
+ const val YELLOW = "\u001B[33m";
+ const val BLUE = "\u001B[34m"
+ const val PURPLE = "\u001B[35m"
+ const val CYAN = "\u001B[36m"
+ const val WHITE = "\u001B[37m"
+
+ fun wrap(s: CharSequence, color: String) = color + s + RESET
+ private fun blue(s: CharSequence) = wrap(s, BLUE)
+ private fun red(s: CharSequence) = wrap(s, RED)
+ private fun yellow(s: CharSequence) = wrap(s, YELLOW)
+
+ fun taskColor(s: CharSequence) = s
+ fun errorColor(s: CharSequence) = red(s)
+ fun warnColor(s: CharSequence) = red(s)
+ }
+}
+
+class AsciiTable {
+ class Builder {
+ private val headers = arrayListOf()
+ fun header(name: String) = headers.add(name)
+ fun headers(vararg names: String) = headers.addAll(names)
+
+ private val widths = arrayListOf()
+ fun columnWidth(w: Int) : Builder {
+ widths.add(w)
+ return this
+ }
+
+ private val rows = arrayListOf>()
+ fun addRow(row: List) = rows.add(row)
+
+ private fun col(width: Int, s: String) : String {
+ val format = " %1\$-${width.toString()}s"
+ val result = String.format(format, s)
+ return result
+ }
+
+ val vb = AsciiArt.verticalBar
+
+ fun build() : String {
+ val formattedHeaders =
+ headers.mapIndexed { index, s ->
+ val s2 = col(widths[index], s)
+ s2
+ }.joinToString(vb)
+ val result = StringBuffer().apply {
+ append(AsciiArt.logBox(formattedHeaders, AsciiArt.bottomLeft2, AsciiArt.bottomRight2))
+ append("\n")
+ }
+ var lineLength = 0
+ rows.forEachIndexed { _, row ->
+ val formattedRow = row.mapIndexed { i, s -> col(widths[i], s) }.joinToString(vb)
+ val line = "$vb $formattedRow $vb"
+ result.append(line).append("\n")
+ lineLength = line.length
+ }
+ result.append(AsciiArt.lowerBox(lineLength - 4))
+ return result.toString()
+ }
+
+ }
+}
diff --git a/src/main/kotlin/com/beust/kobalt/BasePluginTask.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/BasePluginTask.kt
similarity index 73%
rename from src/main/kotlin/com/beust/kobalt/BasePluginTask.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/BasePluginTask.kt
index 855d4271..e55f1b2a 100644
--- a/src/main/kotlin/com/beust/kobalt/BasePluginTask.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/BasePluginTask.kt
@@ -4,8 +4,9 @@ import com.beust.kobalt.api.IPlugin
import com.beust.kobalt.api.PluginTask
import com.beust.kobalt.api.Project
-public abstract class BasePluginTask(override val plugin: IPlugin,
+abstract class BasePluginTask(override val plugin: IPlugin,
override val name: String,
override val doc: String,
+ override val group: String,
override val project: Project)
: PluginTask()
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/BuildScript.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/BuildScript.kt
new file mode 100644
index 00000000..4c35b9ed
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/BuildScript.kt
@@ -0,0 +1,145 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.api.IClasspathDependency
+import com.beust.kobalt.api.Kobalt
+import com.beust.kobalt.api.annotation.Directive
+import com.beust.kobalt.internal.KobaltSettings
+import com.beust.kobalt.internal.PluginInfo
+import com.beust.kobalt.maven.DependencyManager
+import com.beust.kobalt.maven.dependency.FileDependency
+import com.beust.kobalt.misc.KobaltLogger
+import org.eclipse.aether.repository.Proxy
+import java.io.File
+import java.net.InetSocketAddress
+
+var BUILD_SCRIPT_CONFIG : BuildScriptConfig? = null
+
+class BuildScriptConfig {
+ /** The list of repos used to locate plug-ins. */
+ @Directive
+ fun repos(vararg r: String) = newRepos(*r)
+
+ /** The list of plug-ins to use for this build file. */
+ @Directive
+ fun plugins(vararg pl: String) = newPlugins(*pl)
+
+ /** The build file classpath. */
+ @Directive
+ fun buildFileClasspath(vararg bfc: String) = newBuildFileClasspath(*bfc)
+
+ /** Options passed to Kobalt */
+ @Directive
+ fun kobaltOptions(vararg options: String) = Kobalt.addKobaltOptions(options)
+
+ /** Where to find additional build files */
+ @Directive
+ fun buildSourceDirs(vararg dirs: String) = Kobalt.addBuildSourceDirs(dirs)
+
+ // The following settings modify the compiler used to compile the build file, which regular users should
+ // probably never need to do. Projects should use kotlinCompiler { compilerVersion } to configure the
+ // Kotin compiler for their source files.
+ var kobaltCompilerVersion : String? = null
+ var kobaltCompilerRepo: String? = null
+ var kobaltCompilerFlags: String? = null
+}
+
+@Directive
+fun homeDir(vararg dirs: String) : String = SystemProperties.homeDir +
+ File.separator + dirs.toMutableList().joinToString(File.separator)
+
+@Directive
+fun file(file: String) : String = FileDependency.PREFIX_FILE + file
+
+fun plugins(vararg dependency : IClasspathDependency) {
+ dependency.forEach { Plugins.addDynamicPlugin(it) }
+}
+
+fun plugins(vararg dependencies : String) {
+ KobaltLogger.logger.warn("Build.kt",
+ "Invoking plugins() directly is deprecated, use the buildScript{} directive")
+ newPlugins(*dependencies)
+}
+
+@Directive
+fun newPlugins(vararg dependencies : String) {
+ val factory = Kobalt.INJECTOR.getInstance(DependencyManager::class.java)
+ dependencies.forEach {
+ Plugins.addDynamicPlugin(factory.create(it))
+ }
+}
+
+data class ProxyConfig(val host: String = "", val port: Int = 0, val type: String = "", val nonProxyHosts: String = "") {
+ fun toProxy() = java.net.Proxy(java.net.Proxy.Type.HTTP, InetSocketAddress(host, port))
+
+ fun toAetherProxy() = Proxy(type, host, port) // TODO make support for proxy auth
+}
+
+data class HostConfig(var url: String = "", var name: String = HostConfig.createRepoName(url),
+ var username: String? = null, var password: String? = null) {
+
+ companion object {
+ /**
+ * For repos specified in the build file (repos()) that don't have an associated unique name,
+ * create such a name from the URL. This is a requirement from Maven Resolver, and failing to do
+ * this leads to very weird resolution errors.
+ */
+ private fun createRepoName(url: String) = url.replace("/", "_").replace("\\", "_").replace(":", "_")
+ }
+
+ fun hasAuth() : Boolean {
+ return (! username.isNullOrBlank()) && (! password.isNullOrBlank())
+ }
+
+ override fun toString() : String {
+ return url + if (username != null) {
+ "username: $username, password: ***"
+ } else {
+ ""
+ }
+ }
+}
+
+fun repos(vararg repos : String) {
+ KobaltLogger.logger.warn("Build.kt",
+ "Invoking repos() directly is deprecated, use the buildScript{} directive")
+ newRepos(*repos)
+}
+
+fun newRepos(vararg repos: String) {
+ repos.forEach { Kobalt.addRepo(HostConfig(it)) }
+}
+
+fun buildFileClasspath(vararg deps: String) {
+ KobaltLogger.logger.warn("Build.kt",
+ "Invoking buildFileClasspath() directly is deprecated, use the buildScript{} directive")
+ newBuildFileClasspath(*deps)
+}
+
+fun newBuildFileClasspath(vararg deps: String) {
+ //FIXME newBuildFileClasspath called twice
+ deps.forEach { Kobalt.addBuildFileClasspath(it) }
+}
+
+@Directive
+fun authRepos(vararg repos : HostConfig) {
+ repos.forEach { Kobalt.addRepo(it) }
+}
+
+@Directive
+fun authRepo(init: HostConfig.() -> Unit) = HostConfig(name = "").apply { init() }
+
+@Directive
+fun glob(g: String) : IFileSpec.GlobSpec = IFileSpec.GlobSpec(g)
+
+/**
+ * The location of the local Maven repository.
+ */
+@Directive
+fun localMaven() : String {
+ val pluginInfo = Kobalt.INJECTOR.getInstance(PluginInfo::class.java)
+ val initial = Kobalt.INJECTOR.getInstance(KobaltSettings::class.java).localMavenRepo
+ val result = pluginInfo.localMavenRepoPathInterceptors.fold(initial) { current, interceptor ->
+ File(interceptor.repoPath(current.absolutePath))
+ }
+ return result.toURI().toString()
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Constants.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Constants.kt
new file mode 100644
index 00000000..8eb73c84
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Constants.kt
@@ -0,0 +1,25 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.misc.KFiles
+
+object Constants {
+ const val LOG_QUIET_LEVEL = 0
+ const val LOG_DEFAULT_LEVEL = 1
+ const val LOG_MAX_LEVEL = 3
+ val BUILD_FILE_NAME = "Build.kt"
+ val BUILD_FILE_DIRECTORY = "kobalt/src"
+ val BUILD_FILE_PATH = KFiles.joinDir(BUILD_FILE_DIRECTORY, BUILD_FILE_NAME)
+ val KOTLIN_COMPILER_VERSION = "1.2.70"
+
+ internal val DEFAULT_REPOS = listOf(
+ // "https://maven-central.storage.googleapis.com/",
+ HostConfig("https://repo1.maven.org/maven2/", "Maven"),
+ HostConfig("https://jcenter.bintray.com/", "JCenter")
+// "https://repository.jetbrains.com/all/", // <-- contains snapshots
+
+ // snapshots
+// "https://oss.sonatype.org/content/repositories/snapshots/"
+// , "https://repository.jboss.org/nexus/content/repositories/root_repository/"
+ )
+
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Directives.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Directives.kt
new file mode 100644
index 00000000..93d9434c
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Directives.kt
@@ -0,0 +1,39 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.api.Kobalt
+import com.beust.kobalt.api.Project
+import com.beust.kobalt.api.annotation.Directive
+import com.beust.kobalt.internal.JvmCompilerPlugin
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+@Directive
+fun project(vararg projects: Project, init: Project.() -> Unit): Project {
+ return Project("").apply {
+ init()
+ (Kobalt.findPlugin(JvmCompilerPlugin.PLUGIN_NAME) as JvmCompilerPlugin)
+ .addDependentProjects(this, projects.toList())
+ }
+}
+
+@Directive
+fun buildScript(init: BuildScriptConfig.() -> Unit): BuildScriptConfig {
+ val buildScriptConfig = BuildScriptConfig().apply { init() }
+ BUILD_SCRIPT_CONFIG = buildScriptConfig
+ return buildScriptConfig
+}
+
+@Directive
+fun profile(): ReadWriteProperty {
+ val result = object: ReadWriteProperty {
+ var value: Boolean = false
+ override operator fun getValue(thisRef: Nothing?, property: KProperty<*>): Boolean {
+ return value
+ }
+
+ override operator fun setValue(thisRef: Nothing?, property: KProperty<*>, value: Boolean) {
+ this.value = value
+ }
+ }
+ return result
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Features.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Features.kt
new file mode 100644
index 00000000..83e01827
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Features.kt
@@ -0,0 +1,8 @@
+package com.beust.kobalt
+
+class Features {
+ companion object {
+ /** If true, uses timestamps to speed up the tasks */
+ const val USE_TIMESTAMPS = true
+ }
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/FileSpec.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/FileSpec.kt
new file mode 100644
index 00000000..1eb409f4
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/FileSpec.kt
@@ -0,0 +1,102 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.misc.kobaltLog
+import java.io.File
+import java.nio.file.*
+import java.nio.file.attribute.BasicFileAttributes
+
+/**
+ * Subclasses of IFileSpec can be turned into a list of files. There are two kings: FileSpec (a single file)
+ * and GlobSpec (a spec defined by a glob, e.g. ** slash *Test.class)
+ */
+sealed class IFileSpec {
+ abstract fun toFiles(baseDir: String?, filePath: String, excludes: List = emptyList()): List
+
+ class FileSpec(val spec: String) : IFileSpec() {
+ override fun toFiles(baseDir: String?, filePath: String, excludes: List) = listOf(File(spec))
+
+ override fun toString() = spec
+ }
+
+ /**
+ * A glob matcher, see http://docs.oracle.com/javase/7/docs/api/java/nio/file/FileSystem.html#getPathMatcher%28java.lang.String%29
+ */
+ class GlobSpec(val spec: List) : IFileSpec() {
+
+ constructor(spec: String) : this(arrayListOf(spec))
+
+ private fun isIncluded(includeMatchers: Glob, excludes: List, rel: Path) : Boolean {
+ excludes.forEach {
+ if (it.matches(rel)) {
+ kobaltLog(3, " Excluding ${rel.toFile()}")
+ return false
+ }
+ }
+ if (includeMatchers.matches(rel)) {
+ kobaltLog(3, " Including ${rel.toFile().path}")
+ return true
+ }
+ kobaltLog(2, " Excluding ${rel.toFile()} (not matching any include pattern")
+ return false
+ }
+
+ override fun toFiles(baseDir: String?, filePath: String, excludes: List): List {
+ val result = arrayListOf()
+ val includes = Glob(*spec.toTypedArray())
+
+ if (File(baseDir, filePath).isDirectory) {
+ val orgRootDir = (if (File(filePath).isAbsolute) Paths.get(filePath)
+ else if (baseDir != null) Paths.get(baseDir, filePath)
+ else Paths.get(filePath)).run { normalize() }
+ // Paths.get(".").normalize() returns an empty string, which is not a valid file :-(
+ val rootDir = if (orgRootDir.toFile().path.isEmpty()) Paths.get("./") else orgRootDir
+ if (rootDir.toFile().exists()) {
+ Files.walkFileTree(rootDir, object : SimpleFileVisitor() {
+ override fun visitFile(p: Path, attrs: BasicFileAttributes): FileVisitResult {
+ val path = p.normalize()
+ val rel = orgRootDir.relativize(path)
+ if (isIncluded(includes, excludes, path)) {
+ kobaltLog(3, " including file " + rel.toFile() + " from rootDir $rootDir")
+ result.add(rel.toFile())
+ }
+ return FileVisitResult.CONTINUE
+ }
+ })
+ } else {
+ throw AssertionError("Directory \"$rootDir\" should exist")
+ }
+ } else {
+ if (isIncluded(includes, excludes, Paths.get(filePath))) {
+ result.add(File(filePath))
+ }
+ }
+
+ return result
+ }
+
+ override fun toString(): String {
+ var result = ""
+ spec.apply {
+ if (!isEmpty()) {
+ result += "Included files: " + joinToString { ", " }
+ }
+ }
+ return result
+ }
+ }
+
+}
+
+/**
+ * A Glob is a simple file name matcher.
+ */
+class Glob(vararg specs: String) {
+ val matchers = prepareMatchers(specs.toList())
+
+ private fun prepareMatchers(specs: List): List =
+ specs.map { it -> FileSystems.getDefault().getPathMatcher("glob:$it") }
+
+ fun matches(s: String) = matches(Paths.get(s))
+
+ fun matches(path: Path) = matchers.any { it.matches(path) }
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/IncludeFromTo.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/IncludeFromTo.kt
new file mode 100644
index 00000000..ea189851
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/IncludeFromTo.kt
@@ -0,0 +1,43 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.api.annotation.Directive
+import java.io.File
+
+/**
+ * Base classes for directives that support install(from,to) (e.g. install{} or jar{}).
+ */
+open class IncludeFromTo {
+ /**
+ * Prefix path to be removed from the zip file. For example, if you add "build/lib/a.jar" to the zip
+ * file and the excludePrefix is "build/lib", then "a.jar" will be added at the root of the zip file.
+ */
+ val includedFiles = arrayListOf()
+
+ @Directive
+ fun from(s: String) = From(s)
+
+ @Directive
+ fun to(s: String) = To(s)
+
+ @Directive
+ fun copy(from: From, to: To) {
+ val dir = File(from.path).absoluteFile.parentFile
+ includedFiles.add(IncludedFile(from(dir.absolutePath), to, listOf(IFileSpec.FileSpec(from.path))))
+ }
+
+ @Directive
+ fun include(vararg files: String) {
+ includedFiles.add(IncludedFile(files.map { IFileSpec.FileSpec(it) }))
+ }
+
+ @Directive
+ fun include(from: From, to: To, vararg specs: String) {
+ includedFiles.add(IncludedFile(from, to, specs.map { IFileSpec.FileSpec(it) }))
+ }
+
+ @Directive
+ fun include(from: From, to: To, vararg specs: IFileSpec.GlobSpec) {
+ includedFiles.add(IncludedFile(from, to, listOf(*specs)))
+ }
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/IncludedFile.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/IncludedFile.kt
new file mode 100644
index 00000000..46dea15e
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/IncludedFile.kt
@@ -0,0 +1,44 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.misc.KFiles
+import com.beust.kobalt.misc.toString
+import java.io.File
+import java.nio.file.Paths
+
+class IncludedFile(val fromOriginal: From, val toOriginal: To, val specs: List,
+ val expandJarFiles: Boolean = false) {
+ constructor(specs: List, expandJarFiles: Boolean = false) : this(From(""), To(""), specs, expandJarFiles)
+ fun from(s: String) = File(if (fromOriginal.isCurrentDir()) s else KFiles.joinDir(from, s))
+ val from: String get() = fromOriginal.path.replace("\\", "/")
+ fun to(s: String) = File(if (toOriginal.isCurrentDir()) s else KFiles.joinDir(to, s))
+ val to: String get() = toOriginal.path.replace("\\", "/")
+ override fun toString() = toString("IncludedFile",
+ "files - ", specs.map { it.toString() },
+ "from", from,
+ "to", to)
+
+ fun allFromFiles(directory: String? = null): List {
+ val result = arrayListOf()
+ specs.forEach { spec ->
+// val fullDir = if (directory == null) from else KFiles.joinDir(directory, from)
+ spec.toFiles(directory, from).forEach { source ->
+ result.add(if (source.isAbsolute) source else File(source.path))
+ }
+ }
+ return result.map { Paths.get(it.path).normalize().toFile()}
+ }
+}
+
+open class Direction(open val p: String) {
+ override fun toString() = path
+ fun isCurrentDir() = path == "./"
+
+ val path: String get() =
+ if (p.isEmpty()) "./"
+ else if (p.startsWith("/") || p.endsWith("/")) p
+ else p + "/"
+}
+
+class From(override val p: String) : Direction(p)
+
+class To(override val p: String) : Direction(p)
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/IncrementalTaskInfo.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/IncrementalTaskInfo.kt
new file mode 100644
index 00000000..b1c56e5a
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/IncrementalTaskInfo.kt
@@ -0,0 +1,17 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.api.KobaltContext
+import com.beust.kobalt.api.Project
+
+/**
+ * @param inputChecksum The checksum for the input to this task. It gets compared against the previous checksum
+ * calculated by Kobalt. If they differ, the task gets run. If they are equal, outputChecksums are then compared.
+ * @param outputChecksum The checksum for the output of this task. If null, the output is absent
+ * and the task will be run. If non null, it gets compared against the checksum of the previous run and
+ * if they differ, the task gets run.
+ * @param task The task to run.
+ */
+class IncrementalTaskInfo(val inputChecksum: () -> String?,
+ val outputChecksum: () -> String?,
+ val task: (Project) -> TaskResult,
+ val context: KobaltContext)
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt
new file mode 100644
index 00000000..19bb52c6
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt
@@ -0,0 +1,162 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.api.KobaltContext
+import com.beust.kobalt.api.Project
+import com.beust.kobalt.archive.Archives
+import com.beust.kobalt.archive.MetaArchive
+import com.beust.kobalt.archive.Zip
+import com.beust.kobalt.maven.DependencyManager
+import com.beust.kobalt.maven.aether.Scope
+import com.beust.kobalt.misc.KFiles
+import com.beust.kobalt.misc.kobaltLog
+import com.google.inject.Inject
+import java.io.File
+import java.io.FileInputStream
+import java.nio.file.Paths
+import java.util.jar.Manifest
+
+class JarGenerator @Inject constructor(val dependencyManager: DependencyManager) : ArchiveGenerator {
+ companion object {
+ fun findIncludedFiles(directory: String, files: List, excludes: List,
+ throwOnError: Boolean = true)
+ : List {
+ val result = arrayListOf()
+ files.forEach { includedFile ->
+ val includedSpecs = arrayListOf()
+ includedFile.specs.forEach { spec ->
+ val fromPath = includedFile.from
+ if (File(directory, fromPath).exists()) {
+ spec.toFiles(directory, fromPath).forEach { file ->
+ val fullFile = File(KFiles.joinDir(directory, fromPath, file.path))
+ if (! fullFile.exists() && throwOnError) {
+ throw AssertionError("File should exist: $fullFile")
+ }
+
+ if (!KFiles.isExcluded(fullFile, excludes)) {
+ val normalized = Paths.get(file.path).normalize().toFile().path
+ includedSpecs.add(IFileSpec.FileSpec(normalized))
+ } else {
+ kobaltLog(2, "Not adding ${file.path} to jar file because it's excluded")
+ }
+
+ }
+ } else {
+ kobaltLog(2, " Directory $fromPath doesn't exist, not including it in the jar")
+ }
+ }
+ if (includedSpecs.size > 0) {
+ kobaltLog(3, "Including specs $includedSpecs")
+ result.add(IncludedFile(From(includedFile.from), To(includedFile.to), includedSpecs))
+ }
+ }
+ return result
+ }
+ }
+
+ override val suffix = ".jar"
+
+ override fun findIncludedFiles(project: Project, context: KobaltContext, zip: Zip) : List {
+ //
+ // Add all the applicable files for the current project
+ //
+ val buildDir = KFiles.buildDir(project)
+ val result = arrayListOf()
+ val classesDir = KFiles.makeDir(buildDir.path, "classes")
+
+ if (zip.includedFiles.isEmpty()) {
+ // If no includes were specified, assume the user wants a simple jar file made of the
+ // classes of the project, so we specify a From("build/classes/"), To("") and
+ // a list of files containing everything under it
+ val relClassesDir = Paths.get(project.directory).relativize(Paths.get(classesDir.path))
+ val prefixPath = Paths.get(project.directory).relativize(Paths.get(classesDir.path + "/"))
+
+ // Class files
+ val files = KFiles.findRecursively(classesDir).map { File(relClassesDir.toFile(), it) }
+ val filesNotExcluded : List = files.filter {
+ ! KFiles.Companion.isExcluded(KFiles.joinDir(project.directory, it.path), zip.excludes)
+ }
+ val fileSpecs = arrayListOf()
+ filesNotExcluded.forEach {
+ fileSpecs.add(IFileSpec.FileSpec(it.path.toString().substring(prefixPath.toString().length + 1)))
+ }
+ result.add(IncludedFile(From(prefixPath.toString()), To(""), fileSpecs))
+
+ // Resources, if applicable
+ context.variant.resourceDirectories(project).forEach {
+ result.add(IncludedFile(From(it.path), To(""), listOf(IFileSpec.GlobSpec("**"))))
+ }
+ } else {
+ //
+ // The user specified an include, just use it verbatim
+ //
+ val includedFiles = findIncludedFiles(project.directory, zip.includedFiles, zip.excludes, false)
+ result.addAll(includedFiles)
+ }
+
+ //
+ // If fatJar is true, add all the transitive dependencies as well: compile, runtime and dependent projects
+ //
+ if (zip.fatJar) {
+ val seen = hashSetOf()
+ @Suppress("UNCHECKED_CAST")
+ val allDependencies = project.compileDependencies + project.compileRuntimeDependencies +
+ context.variant.buildType.compileDependencies +
+ context.variant.buildType.compileRuntimeDependencies +
+ context.variant.productFlavor.compileDependencies +
+ context.variant.productFlavor.compileRuntimeDependencies
+ val transitiveDependencies = dependencyManager.calculateDependencies(project, context,
+ scopes = listOf(Scope.COMPILE), passedDependencies = allDependencies)
+ transitiveDependencies.map {
+ it.jarFile.get()
+ }.forEach { file : File ->
+ if (! seen.contains(file.path)) {
+ seen.add(file.path)
+ if (! KFiles.Companion.isExcluded(file, zip.excludes)) {
+ result.add(IncludedFile(specs = arrayListOf(IFileSpec.FileSpec(file.absolutePath)),
+ expandJarFiles = true))
+ }
+ }
+ }
+ }
+
+ return result
+ }
+
+ override fun generateArchive(project: Project, context: KobaltContext, zip: Zip,
+ includedFiles: List) : File {
+ //
+ // Generate the manifest
+ // If manifest attributes were specified in the build file, use those to generateAndSave the manifest. Otherwise,
+ // try to find a META-INF/MANIFEST.MF and use that one if we find any. Otherwise, use the default manifest.
+ //
+ val manifest =
+ if (zip.attributes.size > 1) {
+ context.logger.log(project.name, 2, "Creating MANIFEST.MF from " + zip.attributes.size + " attributes")
+ Manifest().apply {
+ zip.attributes.forEach { attribute ->
+ mainAttributes.putValue(attribute.first, attribute.second)
+ }
+ }
+ } else {
+ fun findManifestFile(project: Project, includedFiles: List): File? {
+ val allFiles = includedFiles.flatMap { file ->
+ file.allFromFiles(project.directory).map { file.from(it.path) }
+ }
+ val manifestFiles = allFiles.filter { it.path.contains(MetaArchive.MANIFEST_MF) }
+ return if (manifestFiles.any()) manifestFiles[0] else null
+ }
+
+ val manifestFile = findManifestFile(project, includedFiles)
+ if (manifestFile != null) {
+ context.logger.log(project.name, 2, "Including MANIFEST.MF file $manifestFile")
+ Manifest(FileInputStream(manifestFile))
+ } else {
+ null
+ }
+ }
+
+ return Archives.generateArchive(project, context, zip.name, ".jar", includedFiles,
+ true /* expandJarFiles */, manifest)
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/beust/kobalt/JavaInfo.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JavaInfo.kt
similarity index 66%
rename from src/main/kotlin/com/beust/kobalt/JavaInfo.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JavaInfo.kt
index 53c4e010..3c957454 100644
--- a/src/main/kotlin/com/beust/kobalt/JavaInfo.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JavaInfo.kt
@@ -2,18 +2,18 @@ package com.beust.kobalt
import java.io.File
-abstract public class JavaInfo {
- public var javaExecutable: File? = null
+abstract class JavaInfo {
+ val javaExecutable: File?
get() = findExecutable("java")
- public var javacExecutable: File? = null
+ val javacExecutable: File?
get() = findExecutable("javac")
- public var javadocExecutable: File? = null
+ val javadocExecutable: File?
get() = findExecutable("javadoc")
- abstract public var javaHome: File?
- abstract public var runtimeJar: File?
- abstract public var toolsJar: File?
+ abstract var javaHome: File?
+ abstract var runtimeJar: File?
+ abstract var toolsJar: File?
- abstract public fun findExecutable(command: String) : File
+ abstract fun findExecutable(command: String) : File
companion object {
fun create(javaBase: File?): Jvm {
diff --git a/src/main/kotlin/com/beust/kobalt/Jvm.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Jvm.kt
similarity index 89%
rename from src/main/kotlin/com/beust/kobalt/Jvm.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Jvm.kt
index 5780a9c9..14c55efd 100644
--- a/src/main/kotlin/com/beust/kobalt/Jvm.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Jvm.kt
@@ -1,18 +1,18 @@
package com.beust.kobalt
-import com.beust.kobalt.misc.log
+import com.beust.kobalt.misc.kobaltLog
import com.beust.kobalt.misc.warn
import java.io.File
import java.io.IOException
-public open class Jvm constructor(
+open class Jvm constructor(
val os: OperatingSystem,
var javaBase: File? = null) : JavaInfo() {
private var _javaHome: File? = null
- override public var javaHome: File? = null
+ override var javaHome: File? = null
get() = _javaHome!!
- override public var runtimeJar: File? = null
+ override var runtimeJar: File? = null
private fun findRuntimeJar() : File? {
var runtimeJar = File(javaBase, "lib/rt.jar")
if (runtimeJar.exists()) {
@@ -21,7 +21,7 @@ public open class Jvm constructor(
runtimeJar = File(javaBase, "jre/lib/rt.jar")
return if (runtimeJar.exists()) runtimeJar else null
}
- override public var toolsJar: File? = null
+ override var toolsJar: File? = null
private var userSupplied: Boolean? = false
private var javaVersion: String? = null
@@ -67,7 +67,7 @@ public open class Jvm constructor(
return toolsJar
}
if (javaHome!!.name.equals("jre", true)) {
- javaHome = javaHome!!.parentFile
+ _javaHome = javaHome!!.parentFile
toolsJar = File(javaHome, "lib/tools.jar")
if (toolsJar.exists()) {
return toolsJar
@@ -78,7 +78,7 @@ public open class Jvm constructor(
val version = SystemProperties.Companion.javaVersion
if (javaHome!!.name.toRegex().matches("jre\\d+")
|| javaHome!!.name == "jre$version") {
- javaHome = File(javaHome!!.parentFile, "jdk$version")
+ _javaHome = File(javaHome!!.parentFile, "jdk$version")
toolsJar = File(javaHome, "lib/tools.jar")
if (toolsJar.exists()) {
return toolsJar
@@ -89,15 +89,15 @@ public open class Jvm constructor(
return null
}
-// open public fun isIbmJvm(): Boolean {
+// open fun isIbmJvm(): Boolean {
// return false
// }
- override public fun findExecutable(command: String): File {
+ override fun findExecutable(command: String): File {
if (javaHome != null) {
val jdkHome = if (javaHome!!.endsWith("jre")) javaHome!!.parentFile else javaHome
val exec = File(jdkHome, "bin/" + command)
- var executable = File(os.getExecutableName(exec.absolutePath))
+ val executable = File(os.getExecutableName(exec.absolutePath))
if (executable.isFile) {
return executable
}
@@ -110,7 +110,7 @@ public open class Jvm constructor(
val pathExecutable = os.findInPath(command)
if (pathExecutable != null) {
- log(1, "Unable to find the $command executable using home: " +
+ kobaltLog(2, "Unable to find the $command executable using home: " +
"$javaHome but found it on the PATH: $pathExecutable.")
return pathExecutable
}
diff --git a/src/main/kotlin/com/beust/kobalt/KobaltException.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/KobaltException.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/KobaltException.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/KobaltException.kt
diff --git a/src/main/kotlin/com/beust/kobalt/OperatingSystem.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/OperatingSystem.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/OperatingSystem.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/OperatingSystem.kt
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Plugins.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Plugins.kt
new file mode 100644
index 00000000..0102dd8b
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Plugins.kt
@@ -0,0 +1,212 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.api.*
+import com.beust.kobalt.api.annotation.IncrementalTask
+import com.beust.kobalt.api.annotation.Task
+import com.beust.kobalt.internal.IncrementalManager
+import com.beust.kobalt.internal.KobaltSettings
+import com.beust.kobalt.internal.PluginInfo
+import com.beust.kobalt.internal.TaskManager
+import com.beust.kobalt.maven.DependencyManager
+import com.beust.kobalt.misc.JarUtils
+import com.beust.kobalt.misc.KFiles
+import com.beust.kobalt.misc.KobaltExecutors
+import com.beust.kobalt.misc.kobaltLog
+import com.google.inject.Provider
+import java.io.File
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import java.net.URLClassLoader
+import java.util.*
+import java.util.jar.JarFile
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class Plugins @Inject constructor (val taskManagerProvider : Provider,
+ val files: KFiles,
+ val depManager: DependencyManager,
+ val settings: KobaltSettings,
+ val executors: KobaltExecutors,
+ val incrementalManagerFactory: IncrementalManager.IFactory,
+ val taskManager: TaskManager) {
+
+ companion object {
+ private var pluginMap = hashMapOf()
+
+ fun addPluginInstance(plugin: IPlugin) {
+ pluginMap.put(plugin.name, plugin)
+ }
+
+ val plugins : List
+ get() = ArrayList(pluginMap.values)
+
+ /**
+ * The list of plugins found in the build file.
+ */
+ val dynamicPlugins : ArrayList = arrayListOf()
+ fun addDynamicPlugin(plugin: IClasspathDependency) = dynamicPlugins.add(plugin)
+
+ fun findPlugin(name: String) : IPlugin? = pluginMap[name]
+ }
+
+ fun shutdownPlugins() = plugins.forEach { it.shutdown() }
+
+ fun applyPlugins(context: KobaltContext, projects: List) {
+ plugins.forEach { plugin ->
+ addPluginInstance(plugin)
+ // We could inject this in the plug-in but since these will be written by external users,
+ // I want to keep the verbosity of plugins to a minimum, so instead, we do the injection
+ // manually here
+ if (plugin is BasePlugin) {
+ plugin.taskManager = taskManagerProvider.get()
+ plugin.plugins = this
+ }
+
+ // Call apply() on each plug-in that accepts a project
+ kobaltLog(2, "Applying plug-in \"${plugin.name}\"")
+ projects.filter { plugin.accept(it) }.forEach { project ->
+ plugin.apply(project, context)
+ }
+
+ findStaticTasks(plugin, Task::class.java, { method -> isValidTaskMethod(method)}).forEach {
+ taskManager.addAnnotationTask(plugin, it.first, it.second)
+ }
+ findStaticTasks(plugin, IncrementalTask::class.java,
+ { method -> isValidIncrementalTaskMethod(method)}).forEach {
+ taskManager.addIncrementalTask(plugin, it.first, it.second)
+ }
+ }
+
+ // Collect all the tasks from the task contributors
+ context.pluginInfo.taskContributors.forEach {
+ projects.forEach { project ->
+ taskManager.dynamicTasks.addAll(it.tasksFor(project, context))
+ }
+ }
+
+ // ... and from the incremental task contributors
+ val incrementalManager = incrementalManagerFactory.create()
+ context.pluginInfo.incrementalTaskContributors.forEach {
+ projects.forEach { project ->
+ it.incrementalTasksFor(project, context).forEach {
+ // Convert the closure (Project) -> IncrementalTaskInfo to (Project) -> TaskResult
+ // and create a DynamicTask out of it
+ val closure =
+ incrementalManager.toIncrementalTaskClosure(it.name, it.incrementalClosure, Variant())
+ val task = DynamicTask(it.plugin, it.name, it.doc, it.group, it.project, it.dependsOn,
+ it.reverseDependsOn, it.runBefore, it.runAfter, it.alwaysRunAfter,
+ closure)
+ taskManager.dynamicTasks.add(task)
+ }
+ }
+ }
+
+ // Now that we have collected all static and dynamic tasks, turn them all into plug-in tasks
+ taskManager.computePluginTasks(projects)
+ }
+
+ private fun findStaticTasks(plugin: IPlugin, klass: Class, validate: (Method) -> Boolean)
+ : List> {
+ val result = arrayListOf>()
+
+ var currentClass : Class? = plugin.javaClass
+
+ // Tasks can come from two different places: plugin classes and build files.
+ // When a task is read from a build file, ScriptCompiler adds it right away to plugin.methodTasks.
+ // The following loop introspects the current plugin, finds all the tasks using the @Task annotation
+ // and adds them to plugin.methodTasks
+ while (currentClass != null && ! (klass.equals(currentClass))) {
+ currentClass.declaredMethods.map {
+ Pair(it, it.getAnnotation(klass))
+ }.filter {
+ it.second != null
+ }.filter {
+ validate(it.first)
+ }.forEach {
+ if (Modifier.isPrivate(it.first.modifiers)) {
+ throw KobaltException("A task method cannot be private: ${it.first}")
+ }
+ result.add(it)
+ }
+
+ currentClass = currentClass.superclass
+ }
+ return result
+ }
+
+ /**
+ * Make sure this task method has the right signature.
+ */
+ private fun isValidIncrementalTaskMethod(method: Method): Boolean {
+ val t = "Task ${method.declaringClass.simpleName}.${method.name}: "
+
+ if (method.returnType != IncrementalTaskInfo::class.java) {
+ throw IllegalArgumentException("${t}should return a IncrementalTaskInfo")
+ }
+ return true
+ }
+
+ /**
+ * Make sure this task method has the right signature.
+ */
+ private fun isValidTaskMethod(method: Method): Boolean {
+ val t = "Task ${method.declaringClass.simpleName}.${method.name}: "
+
+ if (method.returnType != TaskResult::class.java) {
+ throw IllegalArgumentException("${t}should return a TaskResult")
+ }
+ if (method.parameterTypes.size != 1) {
+ throw IllegalArgumentException("${t}should take exactly one parameter of type a Project")
+ }
+ with(method.parameterTypes) {
+ if (! Project::class.java.isAssignableFrom(get(0))) {
+ throw IllegalArgumentException("${t}first parameter should be of type Project," +
+ "not ${get(0)}")
+ }
+ }
+ return true
+ }
+
+ val dependencies = arrayListOf()
+
+// @Inject
+// lateinit var pluginInfo: PluginInfo
+
+ fun installPlugins(dependencies: List, scriptClassLoader: ClassLoader) {
+ val executor = executors.newExecutor("Plugins", 5)
+ dependencies.forEach {
+ //
+ // Load all the jar files synchronously (can't compile the build script until
+ // they are installed locally).
+ depManager.create(it.id)
+
+ //
+ // Open the jar, parse its kobalt-plugin.xml and add the resulting PluginInfo to pluginInfo
+ //
+ val file = it.jarFile.get()
+ val pluginXml = if (file.isDirectory) {
+ // The plug-in can point to a directory (e.g. plugin("classes")), in which case we just
+ // read kobalt-plugin.xml directly
+ File(file, PluginInfo.PLUGIN_XML).readText()
+ } else {
+ // The plug-in is pointing to a jar file, read kobalt-plugin.xml from it
+ JarUtils.extractTextFile(JarFile(file), PluginInfo.PLUGIN_XML)
+ }
+
+ val pluginInfo = Kobalt.INJECTOR.getInstance(PluginInfo::class.java)
+ if (pluginXml != null) {
+ val pluginClassLoader = URLClassLoader(arrayOf(file.toURI().toURL()))
+ val thisPluginInfo = PluginInfo.readPluginXml(pluginXml, pluginClassLoader, scriptClassLoader)
+ pluginInfo.addPluginInfo(thisPluginInfo)
+ thisPluginInfo.plugins.forEach {
+ Plugins.addPluginInstance(it)
+ }
+ } else {
+ throw KobaltException("Plugin $it doesn't contain a ${PluginInfo.PLUGIN_XML} file")
+ }
+ }
+ executor.shutdown()
+ }
+
+}
\ No newline at end of file
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/ResolveDependency.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/ResolveDependency.kt
new file mode 100644
index 00000000..0d1223e9
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/ResolveDependency.kt
@@ -0,0 +1,117 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.api.IClasspathDependency
+import com.beust.kobalt.maven.LocalRepo
+import com.beust.kobalt.maven.MavenId
+import com.beust.kobalt.maven.aether.*
+import com.beust.kobalt.misc.*
+import com.google.inject.Inject
+import org.eclipse.aether.artifact.DefaultArtifact
+import org.eclipse.aether.graph.DependencyNode
+import java.util.*
+
+/**
+ * Display information about a Maven id.
+ */
+class ResolveDependency @Inject constructor(
+ val localRepo: LocalRepo,
+ val aether: KobaltMavenResolver,
+ val executors: KobaltExecutors) {
+ val increment = 8
+ val leftFirst = "\u2558"
+ val leftMiddle = "\u255f"
+ val leftLast = "\u2559"
+ val vertical = "\u2551"
+
+ class Dep(val dep: IClasspathDependency, val level: Int)
+
+ fun run(id: String) = displayDependenciesFor(id)
+
+ private fun latestMavenArtifact(group: String, artifactId: String, extension: String = "jar"): DependencyNode {
+ val artifact = DefaultArtifact(group, artifactId, extension, "(0,]")
+ val resolved = aether.resolveRange(artifact)
+ if (resolved != null) {
+ val newArtifact = DefaultArtifact(artifact.groupId, artifact.artifactId, artifact.extension,
+ resolved.highestVersion.toString())
+ val artifactResult = aether.resolve(KobaltMavenResolver.artifactToId(newArtifact), null)
+ return artifactResult.root
+ } else {
+ throw KobaltException("Couldn't find latest artifact for $group:$artifactId")
+ }
+ }
+
+ class PairResult(val dependency: IClasspathDependency, val repoUrl: String)
+
+ fun latestArtifact(group: String, artifactId: String, extension: String = "jar"): PairResult
+ = latestMavenArtifact(group, artifactId, extension).let {
+ PairResult(AetherDependency(it.artifact), "(TBD repo)")
+ }
+
+ private fun displayDependenciesFor(id: String) {
+ val mavenId = MavenId.create(id)
+ val resolved : PairResult =
+ if (mavenId.hasVersion) {
+ val node = aether.resolve(id, filter = Filters.EXCLUDE_OPTIONAL_FILTER)
+ PairResult(AetherDependency(node.root.artifact), node.artifactResults[0].repository.toString())
+ } else {
+ latestArtifact(mavenId.groupId, mavenId.artifactId)
+ }
+
+ displayDependencies(resolved.dependency, resolved.repoUrl)
+ }
+
+ private fun displayDependencies(dep: IClasspathDependency, url: String) {
+ val indent = -1
+ val root = Node(Dep(dep, indent))
+ val seen = hashSetOf(dep.id)
+ root.addChildren(findChildren(root, seen))
+
+ kobaltLog(1, AsciiArt.logBox(listOf(dep.id, url, dep.jarFile.get()).map { " $it" }))
+
+ display(root.children)
+ kobaltLog(1, "")
+ }
+
+ private fun display(nodes: List>) {
+ nodes.withIndex().forEach { indexNode ->
+ val node = indexNode.value
+ with(node.value) {
+ val left =
+ if (indexNode.index == nodes.size - 1) leftLast
+ else leftMiddle
+ val indent = level * increment
+ for(i in 0..indent - 2) {
+ if (!KobaltLogger.isQuiet) {
+ if (i == 0 || ((i + 1) % increment == 0)) print(vertical)
+ else print(" ")
+ }
+ }
+ kobaltLog(1, left + " " + dep.id + (if (dep.optional) " (optional)" else ""))
+ display(node.children)
+ }
+ }
+
+ }
+
+ private fun findChildren(root: Node, seen: HashSet): List> {
+ val result = arrayListOf>()
+ root.value.dep.directDependencies().forEach {
+ if (! seen.contains(it.id)) {
+ val dep = Dep(it, root.value.level + 1)
+ val node = Node(dep)
+ kobaltLog(2, "Found dependency ${dep.dep.id} level: ${dep.level}")
+ result.add(node)
+ seen.add(it.id)
+ try {
+ node.addChildren(findChildren(node, seen))
+ } catch(ex: Exception) {
+ if (! it.optional) warn("Couldn't resolve " + node)
+ // else don't warn about missing optional dependencies
+ }
+ }
+ }
+ kobaltLog(2, "Children for ${root.value.dep.id}: ${result.size}")
+ return result
+ }
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/SystemProperties.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/SystemProperties.kt
new file mode 100644
index 00000000..d5507497
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/SystemProperties.kt
@@ -0,0 +1,22 @@
+package com.beust.kobalt
+
+class SystemProperties {
+ companion object {
+ val javaBase : String
+ get() {
+ val jh = System.getenv("JAVA_HOME")
+ ?: System.getProperty("java.home")
+ ?: throw IllegalArgumentException("JAVA_HOME not defined")
+ val result =
+ if (jh.toLowerCase().endsWith("jre")) jh.substring(0, jh.length - 4)
+ else jh
+ return result
+ }
+ val javaVersion = System.getProperty("java.version")
+ val homeDir = System.getProperty("user.home")
+ val tmpDir = System.getProperty("java.io.tmpdir")
+ val currentDir = System.getProperty("user.dir")
+ val username = System.getProperty("user.name")
+ }
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/TaskResult.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/TaskResult.kt
new file mode 100644
index 00000000..241bc045
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/TaskResult.kt
@@ -0,0 +1,8 @@
+package com.beust.kobalt
+
+class TestResult(val success: Boolean, val shortMessage: String? = null, val longMessage: String? = null)
+
+open class TaskResult(val success: Boolean = true,
+ val testResult: TestResult? = null,
+ val errorMessage: String? = null
+)
diff --git a/src/main/kotlin/com/beust/kobalt/Template.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Template.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/Template.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Template.kt
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/TestConfig.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/TestConfig.kt
new file mode 100644
index 00000000..f84b094d
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/TestConfig.kt
@@ -0,0 +1,40 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.api.Project
+import com.beust.kobalt.api.annotation.Directive
+
+class TestConfig(val project: Project, val isDefault : Boolean = false) {
+ val testArgs = arrayListOf()
+ val jvmArgs = arrayListOf()
+ val testIncludes = arrayListOf("**/*Test.class")
+ val testExcludes = arrayListOf()
+
+ @Directive
+ var name: String = ""
+
+ @Directive
+ fun args(vararg arg: String) {
+ testArgs.addAll(arg)
+ }
+
+ @Directive
+ fun jvmArgs(vararg arg: String) {
+ jvmArgs.addAll(arg)
+ }
+
+ @Directive
+ fun include(vararg arg: String) {
+ testIncludes.apply {
+ clear()
+ addAll(arg)
+ }
+ }
+
+ @Directive
+ fun exclude(vararg arg: String) {
+ testExcludes.apply {
+ clear()
+ addAll(arg)
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/TestDirective.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/TestDirective.kt
new file mode 100644
index 00000000..ad026380
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/TestDirective.kt
@@ -0,0 +1,18 @@
+package com.beust.kobalt
+
+import com.beust.kobalt.api.Project
+import com.beust.kobalt.api.annotation.Directive
+
+@Directive
+fun Project.test(init: TestConfig.() -> Unit): TestConfig = let { project ->
+ with(testConfigs) {
+ val tf = TestConfig(project).apply { init() }
+ if (! map { it.name }.contains(tf.name)) {
+ add(tf)
+ tf
+ } else {
+ throw KobaltException("Test configuration \"${tf.name}\" already exists, give it a different "
+ + "name with test { name = ... }")
+ }
+ }
+}
diff --git a/src/main/kotlin/com/beust/kobalt/Variant.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Variant.kt
similarity index 50%
rename from src/main/kotlin/com/beust/kobalt/Variant.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Variant.kt
index 54f93862..13120fa0 100644
--- a/src/main/kotlin/com/beust/kobalt/Variant.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Variant.kt
@@ -1,11 +1,12 @@
package com.beust.kobalt
import com.beust.kobalt.api.*
+import com.beust.kobalt.internal.ActorUtils
+import com.beust.kobalt.internal.ParallelLogger
+import com.beust.kobalt.internal.SourceSet
import com.beust.kobalt.misc.KFiles
-import com.beust.kobalt.misc.log
-import com.beust.kobalt.plugin.android.AndroidConfig
-import com.beust.kobalt.plugin.android.AndroidPlugin
import java.io.File
+import java.util.*
/**
* Capture the product flavor and the build type of a build.
@@ -28,49 +29,71 @@ class Variant(val initialProductFlavor: ProductFlavorConfig? = null,
/**
* for {internal, release}, return [internalRelease, internal, release]
*/
- fun allDirectories(project: Project): List {
- val result = arrayListOf()
- result.add(toCamelcaseDir())
- if (productFlavor != null) result.add(productFlavor.name)
- if (buildType != null) result.add(buildType.name)
- return result
+ fun allDirectories(): List {
+ return arrayListOf().apply {
+ add(toCamelcaseDir())
+ add(productFlavor.name)
+ add(buildType.name)
+ }
}
- fun resDirectories(project: Project) : List = sourceDirectories(project, "res")
+ fun sourceDirectories(project: Project, context: KobaltContext, sourceSet: SourceSet) : List {
+ val result = arrayListOf()
+ val compilerContributors = ActorUtils.selectAffinityActors(project, context,
+ context.pluginInfo.compilerContributors)
+ compilerContributors.forEach {
+ it.compilersFor(project, context).forEach { compiler ->
+ result.addAll(sourceDirectories(project, compiler.sourceDirectory, variantFirst = true,
+ sourceSet = sourceSet))
+ }
- fun sourceDirectories(project: Project) : List =
- sourceDirectories(project, project.projectInfo.sourceDirectory)
+ }
+ return result.filter { ! KFiles.isResource(it.path) }.toList()
+ }
/**
- * suffix is either "java" (to find source files) or "res" (to find resources)
+ * Might be used by plug-ins.
*/
- private fun sourceDirectories(project: Project, suffix: String) : List {
+ fun resourceDirectories(project: Project, sourceSet: SourceSet = SourceSet.MAIN)
+ = sourceDirectories(project, "resources", variantFirst = false, sourceSet = sourceSet)
+ .filter { KFiles.isResource(it.path) }
+
+ /**
+ * suffix is either "java" (to find source files) or "resources" (to find resources).
+ * The priority directory is always returned first. For example, if a "pro" product flavor
+ * is requested, "src/pro/kotlin" will appear in the result before "src/main/kotlin". Later,
+ * files that have already been seen get skipped, which is how compilation and resources
+ * receive the correct priority in the final jar.
+ */
+ private fun sourceDirectories(project: Project, suffix: String, variantFirst: Boolean, sourceSet: SourceSet)
+ : List {
val result = arrayListOf()
- val sourceDirectories = project.sourceDirectories.map { File(it) }
+ val sourceDirectories = sourceSet.correctSourceSet(project)
+ .filter { File(project.directory, it).exists() }
+ .map(::File)
+
if (isDefault) {
result.addAll(sourceDirectories)
} else {
- result.addAll(allDirectories(project).map {
- File(KFiles.joinDir("src", it, suffix))
- }.filter {
- it.exists()
- })
-
// // The ordering of files is: 1) build type 2) product flavor 3) default
+ val kobaltLog = Kobalt.INJECTOR.getInstance(ParallelLogger::class.java)
buildType.let {
- val dir = File(KFiles.joinDir("src", it.name, project.projectInfo.sourceDirectory))
- log(3, "Adding source for build type ${it.name}: ${dir.path}")
+ val dir = File(KFiles.joinDir("src", it.name, suffix))
+ kobaltLog.log(project.name, 3, "Adding source for build type ${it.name}: ${dir.path}")
result.add(dir)
}
productFlavor.let {
- val dir = File(KFiles.joinDir("src", it.name, project.projectInfo.sourceDirectory))
- log(3, "Adding source for product flavor ${it.name}: ${dir.path}")
+ val dir = File(KFiles.joinDir("src", it.name, suffix))
+ kobaltLog.log(project.name, 3, "Adding source for product flavor ${it.name}: ${dir.path}")
result.add(dir)
}
+ result.addAll(allDirectories()
+ .map { File(KFiles.joinDir("src", it, suffix)) }
+ .filter(File::exists))
+
// Now that all the variant source directories have been added, add the project's default ones
result.addAll(sourceDirectories)
- return result
}
// Generated directory, if applicable
@@ -78,7 +101,11 @@ class Variant(val initialProductFlavor: ProductFlavorConfig? = null,
result.add(it)
}
- return result
+ val filteredResult = result.filter { File(project.directory, it.path).exists() }
+ val sortedResult = if (variantFirst) filteredResult
+ else filteredResult.reversed().toList()
+ val deduplicatedResult = LinkedHashSet(sortedResult).toList()
+ return deduplicatedResult
}
fun archiveName(project: Project, archiveName: String?, suffix: String) : String {
@@ -86,28 +113,27 @@ class Variant(val initialProductFlavor: ProductFlavorConfig? = null,
if (isDefault) {
archiveName ?: project.name + "-" + project.version + suffix
} else {
- val base = if (archiveName != null) archiveName.substring(0, archiveName.length - suffix.length)
- else project.name + "-" + project.version
- val result: String =
- base + "-${productFlavor.name}" + "-${buildType.name}"
+ val base = archiveName?.substring(0, archiveName.length - suffix.length)
+ ?: project.name + "-" + project.version
+ val flavor = if (productFlavor.name.isEmpty()) "" else "-" + productFlavor.name
+ val type = if (buildType.name.isEmpty()) "" else "-" + buildType.name
+ val result: String = base + flavor + type + suffix
result
}
return result
}
- val shortArchiveName = if (isDefault) "" else "-" + productFlavor.name + "-" + buildType.name
-
var generatedSourceDirectory: File? = null
private fun findBuildTypeBuildConfig(project: Project, variant: Variant?) : BuildConfig? {
val buildTypeName = variant?.buildType?.name
- return project.buildTypes.getRaw(buildTypeName)?.buildConfig ?: null
+ return project.buildTypes[buildTypeName]?.buildConfig
}
private fun findProductFlavorBuildConfig(project: Project, variant: Variant?) : BuildConfig? {
val buildTypeName = variant?.productFlavor?.name
- return project.productFlavors.getRaw(buildTypeName)?.buildConfig ?: null
+ return project.productFlavors[buildTypeName]?.buildConfig
}
/**
@@ -123,43 +149,35 @@ class Variant(val initialProductFlavor: ProductFlavorConfig? = null,
return result
}
- fun applicationId(androidConfig: AndroidConfig?): String? {
- val mainId = productFlavor.applicationId ?: androidConfig?.applicationId
- val result =
- if (mainId != null) {
- mainId + (buildType.applicationIdSuffix ?: "")
- } else {
- null
- }
-
- return result
- }
-
/**
* Generate BuildConfig.java if requested. Also look up if any BuildConfig is defined on the current build type,
- * product flavor or main project, and use them to generate any additional field (in that order to
+ * product flavor or main project, and use them to generateAndSave any additional field (in that order to
* respect the priorities). Return the generated file if it was generated, null otherwise.
*/
fun maybeGenerateBuildConfig(project: Project, context: KobaltContext) : File? {
val buildConfigs = findBuildConfigs(project, this)
if (buildConfigs.size > 0) {
- val androidConfig = (Kobalt.findPlugin(AndroidPlugin.PLUGIN_NAME) as AndroidPlugin)
- .configurationFor(project)
val pkg = project.packageName ?: project.group
?: throw KobaltException(
- "packageName needs to be defined on the project in order to generate BuildConfig")
+ "packageName needs to be defined on the project in order to generateAndSave BuildConfig")
- val code = project.projectInfo.generateBuildConfig(project, context, pkg, this, buildConfigs)
- val result = KFiles.makeDir(KFiles.generatedSourceDir(project, this, "buildConfig"))
- // Make sure the generatedSourceDirectory doesn't contain the project.directory since
- // that directory will be added when trying to find recursively all the sources in it
- generatedSourceDirectory = File(result.relativeTo(File(project.directory)))
- val outputGeneratedSourceDirectory = File(result, pkg.replace('.', File.separatorChar))
- val outputDir = File(outputGeneratedSourceDirectory, "BuildConfig" + project.sourceSuffix)
- KFiles.saveFile(outputDir, code)
- log(2, "Generated ${outputDir.path}")
- return result
+ val contributor = ActorUtils.selectAffinityActor(project, context,
+ context.pluginInfo.buildConfigContributors)
+ if (contributor != null) {
+ val code = contributor.generateBuildConfig(project, context, pkg, this, buildConfigs)
+ val result = KFiles.makeDir(KFiles.generatedSourceDir(project, this, "buildConfig"))
+ // Make sure the generatedSourceDirectory doesn't contain the project.directory since
+ // that directory will be added when trying to find recursively all the sources in it
+ generatedSourceDirectory = result.relativeTo(File(project.directory))
+ val outputGeneratedSourceDirectory = File(result, pkg.replace('.', File.separatorChar))
+ val outputDir = File(outputGeneratedSourceDirectory, "BuildConfig." + contributor.buildConfigSuffix)
+ KFiles.saveFile(outputDir, code)
+ context.logger.log(project.name, 2, "Generated ${outputDir.path}")
+ return result
+ } else {
+ throw KobaltException("Couldn't find a contributor to generateAndSave BuildConfig")
+ }
} else {
return null
}
@@ -167,9 +185,10 @@ class Variant(val initialProductFlavor: ProductFlavorConfig? = null,
override fun toString() = toTask("")
+
companion object {
val DEFAULT_PRODUCT_FLAVOR = ProductFlavorConfig("")
- val DEFAULT_BUILD_TYPE = BuildTypeConfig(null, "")
+ val DEFAULT_BUILD_TYPE = BuildTypeConfig("")
fun allVariants(project: Project): List {
val result = arrayListOf()
@@ -202,9 +221,9 @@ class Variant(val initialProductFlavor: ProductFlavorConfig? = null,
}
fun toCamelcaseDir() : String {
- val pfName = productFlavor.name
- val btName = buildType.name
- return pfName[0].toLowerCase() + pfName.substring(1) + btName[0].toUpperCase() + btName.substring(1)
+ fun lci(s : String) = if (s.isEmpty() || s.length == 1) s else s[0].toLowerCase() + s.substring(1)
+
+ return lci(productFlavor.name) + buildType.name.capitalize()
}
fun toIntermediateDir() : String {
diff --git a/src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt
similarity index 52%
rename from src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt
index c74bfa71..ff6d1e95 100644
--- a/src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt
@@ -3,7 +3,7 @@ package com.beust.kobalt.api
import com.beust.kobalt.Plugins
import com.beust.kobalt.internal.TaskManager
-abstract public class BasePlugin : IPlugin {
+abstract class BasePlugin : IPlugin {
lateinit var context: KobaltContext
override fun accept(project: Project) = true
@@ -12,13 +12,7 @@ abstract public class BasePlugin : IPlugin {
this.context = context
}
- /**
- * The list of projects depended upon (e.g. val p = javaProject(dependentProject)).
- */
- protected val projects = arrayListOf()
-
- fun addProject(project: Project, dependsOn: Array) =
- projects.add(ProjectDescription(project, dependsOn.toList()))
+ override fun shutdown() {}
override lateinit var taskManager: TaskManager
lateinit var plugins: Plugins
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/BuildTypeConfig.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/BuildTypeConfig.kt
new file mode 100644
index 00000000..2db3d939
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/BuildTypeConfig.kt
@@ -0,0 +1,11 @@
+package com.beust.kobalt.api
+
+class BuildTypeConfig(val name: String) : IBuildConfig, IDependencyHolder by DependencyHolder() {
+
+ var minifyEnabled = false
+ var applicationIdSuffix: String? = null
+ var proguardFile: String? = null
+
+ override var buildConfig : BuildConfig? = BuildConfig()
+}
+
diff --git a/src/main/kotlin/com/beust/kobalt/api/CompilerActionInfo.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/CompilerActionInfo.kt
similarity index 57%
rename from src/main/kotlin/com/beust/kobalt/api/CompilerActionInfo.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/CompilerActionInfo.kt
index 9a1d374d..e323e474 100644
--- a/src/main/kotlin/com/beust/kobalt/api/CompilerActionInfo.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/CompilerActionInfo.kt
@@ -8,7 +8,9 @@ import java.io.File
data class CompilerActionInfo(val directory: String?,
val dependencies: List,
val sourceFiles: List,
+ val suffixesBeingCompiled: List,
val outputDir: File,
- val compilerArgs: List)
-
-
+ val compilerArgs: List,
+ val friendPaths: List,
+ val forceRecompile: Boolean,
+ val compilerSeparateProcess: Boolean = false)
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ConfigActor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ConfigActor.kt
new file mode 100644
index 00000000..cd4ae745
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ConfigActor.kt
@@ -0,0 +1,19 @@
+package com.beust.kobalt.api
+
+import java.util.*
+
+/**
+ * Actors that have one config object per project can implement `IConfigActor` by delegating to
+ * `ConfigActor`. Then they can easily add and look up configurations per project.
+ */
+interface IConfigActor {
+ val configurations : HashMap
+
+ fun configurationFor(project: Project?) = if (project != null) configurations[project.name] else null
+
+ fun addConfiguration(project: Project, configuration: T) = configurations.put(project.name, configuration)
+}
+
+open class ConfigActor: IConfigActor {
+ override val configurations : HashMap = hashMapOf()
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ConfigsActor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ConfigsActor.kt
new file mode 100644
index 00000000..5ec4ce53
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ConfigsActor.kt
@@ -0,0 +1,19 @@
+package com.beust.kobalt.api
+
+import com.google.common.collect.ArrayListMultimap
+import com.google.common.collect.ListMultimap
+
+/**
+ * Actors that have more than config object per project can use this helper class.
+ */
+interface IConfigsActor {
+ val configurations : ListMultimap
+
+ fun configurationFor(project: Project?) = if (project != null) configurations[project.name] else null
+
+ fun addConfiguration(project: Project, configuration: T) = configurations.put(project.name, configuration)
+}
+
+open class ConfigsActor: IConfigsActor {
+ override val configurations = ArrayListMultimap.create()
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/DependencyHolder.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/DependencyHolder.kt
new file mode 100644
index 00000000..e1195ca3
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/DependencyHolder.kt
@@ -0,0 +1,47 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.api.annotation.Directive
+import java.util.*
+
+/**
+ * Various elements in a build file let you specify dependencies: projects, buildType and productFlavor.
+ * They all implement this interface and delegate to an instance of the `DependencyHolder` concrete class.
+ */
+interface IDependencyHolder {
+ var project: Project
+
+ val compileDependencies : ArrayList
+ val optionalDependencies : ArrayList
+ val compileProvidedDependencies : ArrayList
+ val compileOnlyDependencies : ArrayList
+ val compileRuntimeDependencies : ArrayList
+ val excludedDependencies : ArrayList
+ val nativeDependencies : ArrayList
+
+ @Directive
+ var dependencies: Dependencies?
+
+ @Directive
+ fun dependencies(init: Dependencies.() -> Unit) : Dependencies
+}
+
+open class DependencyHolder : IDependencyHolder {
+ override lateinit var project: Project
+ override val compileDependencies : ArrayList = arrayListOf()
+ override val optionalDependencies : ArrayList = arrayListOf()
+ override val compileProvidedDependencies : ArrayList = arrayListOf()
+ override val compileOnlyDependencies : ArrayList = arrayListOf()
+ override val compileRuntimeDependencies : ArrayList = arrayListOf()
+ override val excludedDependencies : ArrayList = arrayListOf()
+ override val nativeDependencies : ArrayList = arrayListOf()
+
+ override var dependencies : Dependencies? = null
+
+ override fun dependencies(init: Dependencies.() -> Unit) : Dependencies {
+ dependencies = Dependencies(project, compileDependencies, optionalDependencies, compileProvidedDependencies,
+ compileOnlyDependencies, compileRuntimeDependencies, excludedDependencies, nativeDependencies)
+ dependencies!!.init()
+ return dependencies!!
+ }
+}
+
diff --git a/src/main/kotlin/com/beust/kobalt/api/IAffinity.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IAffinity.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/api/IAffinity.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IAffinity.kt
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IAssemblyContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IAssemblyContributor.kt
new file mode 100644
index 00000000..6c35fc1d
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IAssemblyContributor.kt
@@ -0,0 +1,10 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.TaskResult
+
+/**
+ * Plug-ins that will be invoked during the "assemble" task.
+ */
+interface IAssemblyContributor : IContributor {
+ fun assemble(project: Project, context: KobaltContext) : TaskResult
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildConfigContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildConfigContributor.kt
new file mode 100644
index 00000000..ef9d3b4d
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildConfigContributor.kt
@@ -0,0 +1,16 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.Variant
+
+/**
+ * Plug-ins that can generate a BuildConfig file.
+ */
+interface IBuildConfigContributor : IProjectAffinity {
+ fun generateBuildConfig(project: Project, context: KobaltContext, packageName: String, variant: Variant,
+ buildConfigs: List) : String
+
+ /**
+ * The suffix of the generated BuildConfig, e.g. ".java".
+ */
+ val buildConfigSuffix: String
+}
diff --git a/src/main/kotlin/com/beust/kobalt/api/IBuildConfigFieldContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildConfigFieldContributor.kt
similarity index 83%
rename from src/main/kotlin/com/beust/kobalt/api/IBuildConfigFieldContributor.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildConfigFieldContributor.kt
index 8c4dc843..0d26032f 100644
--- a/src/main/kotlin/com/beust/kobalt/api/IBuildConfigFieldContributor.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildConfigFieldContributor.kt
@@ -5,6 +5,6 @@ class BuildConfigField(val type: String, val name: String, val value: Any)
/**
* Plug-ins that want to add fields to BuildConfig need to implement this interface.
*/
-interface IBuildConfigFieldContributor {
+interface IBuildConfigFieldContributor : IContributor {
fun fieldsFor(project: Project, context: KobaltContext) : List
}
diff --git a/src/main/kotlin/com/beust/kobalt/api/IBuildDirectoryInterceptor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildDirectoryInterceptor.kt
similarity index 79%
rename from src/main/kotlin/com/beust/kobalt/api/IBuildDirectoryInterceptor.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildDirectoryInterceptor.kt
index 5a317c0f..afb0f1ef 100644
--- a/src/main/kotlin/com/beust/kobalt/api/IBuildDirectoryInterceptor.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildDirectoryInterceptor.kt
@@ -3,7 +3,7 @@ package com.beust.kobalt.api
/**
* Plug-ins can alter the build directory by implementing this interface.
*/
-interface IBuildDirectoryIncerceptor : IInterceptor {
+interface IBuildDirectoryInterceptor : IInterceptor {
fun intercept(project: Project, context: KobaltContext, buildDirectory: String) : String
}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildListener.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildListener.kt
new file mode 100644
index 00000000..2b0fdadb
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildListener.kt
@@ -0,0 +1,20 @@
+package com.beust.kobalt.api
+
+/**
+ * Plug-ins that listen to build events.
+ */
+interface IBuildListener : IListener {
+
+ class TaskEndInfo(val success: Boolean, val shortMessage: String? = null,
+ val longMessage: String? = null)
+
+ fun taskStart(project: Project, context: KobaltContext, taskName: String) {}
+ fun taskEnd(project: Project, context: KobaltContext, taskName: String, info: TaskEndInfo) {}
+
+ fun projectStart(project: Project, context: KobaltContext) {}
+ fun projectEnd(project: Project, context: KobaltContext, status: ProjectBuildStatus) {}
+}
+
+enum class ProjectBuildStatus {
+ SUCCESS, FAILED, SKIPPED
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildReportContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildReportContributor.kt
new file mode 100644
index 00000000..130c50d2
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IBuildReportContributor.kt
@@ -0,0 +1,8 @@
+package com.beust.kobalt.api
+
+/**
+ * Plug-ins that produce build reports.
+ */
+interface IBuildReportContributor : IContributor {
+ fun generateReport(context: KobaltContext)
+}
diff --git a/src/main/kotlin/com/beust/kobalt/api/IClasspathContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IClasspathContributor.kt
similarity index 57%
rename from src/main/kotlin/com/beust/kobalt/api/IClasspathContributor.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IClasspathContributor.kt
index 1474d342..ce9afc39 100644
--- a/src/main/kotlin/com/beust/kobalt/api/IClasspathContributor.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IClasspathContributor.kt
@@ -1,12 +1,10 @@
package com.beust.kobalt.api
-import com.beust.kobalt.api.IClasspathDependency
-
/**
* Plugins that export classpath entries need to implement this interface.
*/
interface IClasspathContributor : IContributor {
- fun entriesFor(project: Project?) : Collection
+ fun classpathEntriesFor(project: Project?, context: KobaltContext) : Collection
}
diff --git a/src/main/kotlin/com/beust/kobalt/api/IClasspathDependency.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IClasspathDependency.kt
similarity index 61%
rename from src/main/kotlin/com/beust/kobalt/api/IClasspathDependency.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IClasspathDependency.kt
index 9ee3f4dc..527e6f13 100644
--- a/src/main/kotlin/com/beust/kobalt/api/IClasspathDependency.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IClasspathDependency.kt
@@ -8,20 +8,34 @@ import java.util.concurrent.Future
* Encapsulate a dependency that can be put on the classpath. This interface
* has two subclasses: FileDependency, a physical file, and MavenDependency,
* which represents a dependency living in a Maven repo.
+ *
+ * You can instantiate either of these concrete classes with DependencyManager#createMaven
+ * and DependencyManager#createFile.
*/
interface IClasspathDependency {
/** Identifier for this dependency */
val id: String
+ /** Version for this identifier */
+ val version: String
+
+ /** @return true if this dependency represents a Maven coordinate */
+ val isMaven: Boolean
+
+ /** @return true if this dependency is optional */
+ val optional: Boolean
+
/** Absolute path to the jar file on the local file system */
val jarFile: Future
/** Convert to a Maven model tag */
- fun toMavenDependencies() : Dependency
+ fun toMavenDependencies(scope: String? = null) : Dependency
- /** The list of dependencies for this element (not the transitive closure */
+ /** The list of dependencies for this element (not the transitive closure) */
fun directDependencies(): List
/** Used to only keep the most recent version for an artifact if no version was specified */
val shortId: String
+
+ val excluded: ArrayList
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/beust/kobalt/api/IClasspathInterceptor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IClasspathInterceptor.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/api/IClasspathInterceptor.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IClasspathInterceptor.kt
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ICompilerContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ICompilerContributor.kt
new file mode 100644
index 00000000..b6ce8fd2
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ICompilerContributor.kt
@@ -0,0 +1,68 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.TaskResult
+
+interface ICompilerDescription : Comparable {
+ /**
+ * The name of the language compiled by this compiler.
+ */
+ val name: String
+
+ /**
+ * The suffixes handled by this compiler (without the dot, e.g. "java" or "kt").
+ */
+ val sourceSuffixes: List
+
+ /**
+ * The trailing end of the source directory (e.g. "kotlin" in "src/main/kotlin")
+ */
+ val sourceDirectory: String
+
+ /**
+ * Run the compilation based on the info.
+ */
+ fun compile(project: Project, context: KobaltContext, info: CompilerActionInfo) : TaskResult
+
+ companion object {
+ val DEFAULT_PRIORITY: Int = 10
+ }
+
+ /**
+ * The priority of this compiler. Lower priority compilers are run first.
+ */
+ val priority: Int get() = DEFAULT_PRIORITY
+
+ override fun compareTo(other: ICompilerDescription) = priority.compareTo(other.priority)
+
+ /**
+ * Can this compiler be passed directories or does it need individual source files?
+ */
+ val canCompileDirectories: Boolean get() = false
+}
+
+interface ICompilerContributor : IProjectAffinity, IContributor {
+ fun compilersFor(project: Project, context: KobaltContext): List
+}
+
+interface ICompiler {
+ fun compile(project: Project, context: KobaltContext, info: CompilerActionInfo): TaskResult
+}
+
+class CompilerDescription(override val name: String, override val sourceDirectory: String,
+ override val sourceSuffixes: List, val compiler: ICompiler,
+ override val priority: Int = ICompilerDescription.DEFAULT_PRIORITY,
+ override val canCompileDirectories: Boolean = false) : ICompilerDescription {
+ override fun compile(project: Project, context: KobaltContext, info: CompilerActionInfo): TaskResult {
+ val result =
+ if (info.sourceFiles.isNotEmpty()) {
+ compiler.compile(project, context, info)
+ } else {
+ context.logger.log(project.name, 2, "$name couldn't find any source files to compile")
+ TaskResult()
+ }
+ return result
+ }
+
+ override fun toString() = name + " compiler"
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ICompilerFlagContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ICompilerFlagContributor.kt
new file mode 100644
index 00000000..2d8febe4
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ICompilerFlagContributor.kt
@@ -0,0 +1,29 @@
+package com.beust.kobalt.api
+
+/**
+ * Plugins that add compiler flags.
+ */
+class FlagContributor(val flagPriority: Int = DEFAULT_FLAG_PRIORITY,
+ val closure: (project: Project, context: KobaltContext, currentFlags: List,
+ suffixesBeingCompiled: List) -> List) : IContributor {
+ companion object {
+ val DEFAULT_FLAG_PRIORITY = 20
+ }
+
+ fun flagsFor(project: Project, context: KobaltContext, currentFlags: List,
+ suffixesBeingCompiled: List) = closure(project, context, currentFlags, suffixesBeingCompiled)
+}
+
+interface IFlagBase {
+ val flagPriority: Int get() = FlagContributor.DEFAULT_FLAG_PRIORITY
+}
+
+interface ICompilerFlagContributor : IContributor, IFlagBase {
+ fun compilerFlagsFor(project: Project, context: KobaltContext, currentFlags: List,
+ suffixesBeingCompiled: List): List
+}
+
+interface IDocFlagContributor : IContributor, IFlagBase {
+ fun docFlagsFor(project: Project, context: KobaltContext, currentFlags: List,
+ suffixesBeingCompiled: List): List
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/beust/kobalt/api/ICompilerInterceptor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ICompilerInterceptor.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/api/ICompilerInterceptor.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ICompilerInterceptor.kt
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDependencyManager.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDependencyManager.kt
new file mode 100644
index 00000000..3a66f980
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDependencyManager.kt
@@ -0,0 +1,88 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.maven.aether.Filters.EXCLUDE_OPTIONAL_FILTER
+import com.beust.kobalt.maven.aether.KobaltMavenResolver
+import com.beust.kobalt.maven.aether.Scope
+import com.beust.kobalt.misc.kobaltLog
+import org.eclipse.aether.graph.DependencyFilter
+import org.eclipse.aether.graph.DependencyNode
+
+/**
+ * Manage the creation of dependencies and also provide dependencies for projects.
+ */
+interface IDependencyManager {
+ /**
+ * Parse the id and return the correct IClasspathDependency
+ */
+ fun create(id: String, optional: Boolean = false, projectDirectory: String? = null): IClasspathDependency
+
+ /**
+ * Create an IClasspathDependency from a Maven id.
+ */
+ fun createMaven(id: String, optional: Boolean = false): IClasspathDependency
+
+ /**
+ * Create an IClasspathDependency from a path.
+ */
+ fun createFile(path: String): IClasspathDependency
+
+ /**
+ * @return the source dependencies for this project, including the contributors.
+ */
+ fun dependencies(project: Project, context: KobaltContext, scopes: List): List
+
+ /**
+ * @return the test dependencies for this project, including the contributors.
+ */
+ fun testDependencies(project: Project, context: KobaltContext): List
+
+ /**
+ * @return the classpath for this project, including the IClasspathContributors.
+ * allDependencies is typically either compileDependencies or testDependencies
+ */
+ fun calculateDependencies(project: Project?, context: KobaltContext,
+ dependencyFilter: DependencyFilter =
+ createDependencyFilter(project, project?.compileDependencies ?: emptyList()),
+ scopes: List = listOf(Scope.COMPILE),
+ vararg passedDependencies: List): List
+
+ /**
+ * Create an Aether dependency filter that uses the dependency configuration included in each
+ * IClasspathDependency.
+ */
+ fun createDependencyFilter(project: Project?, dependencies: List) : DependencyFilter {
+ return DependencyFilter { p0, p1 ->
+ fun isNodeExcluded(node: DependencyNode, passedDep: IClasspathDependency) : Boolean {
+ val dep = create(KobaltMavenResolver.artifactToId(node.artifact))
+ return passedDep.excluded.any { ex -> ex.isExcluded(dep)}
+ }
+ fun isDepExcluded(node: DependencyNode, excluded: List?) : Boolean {
+ val dep = create(KobaltMavenResolver.artifactToId(node.artifact))
+ return excluded?.map { it.id }?.contains(dep.id) ?: false
+ }
+
+ val accept = dependencies.isEmpty() || dependencies.any {
+ // Is this dependency excluded?
+ val isExcluded = isNodeExcluded(p0, it) || isDepExcluded(p0, project?.excludedDependencies)
+
+ // Is the parent dependency excluded?
+ val isParentExcluded =
+ if (p1.any()) {
+ isNodeExcluded(p1[0], it) || isDepExcluded(p1[0], project?.excludedDependencies)
+ } else {
+ false
+ }
+
+ // Only accept if no exclusions were found
+ ! isExcluded && ! isParentExcluded
+ }
+
+ if (! accept) {
+ kobaltLog(2, "Excluding $p0")
+ }
+
+ if (accept) EXCLUDE_OPTIONAL_FILTER.accept(p0, p1)
+ else accept
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/beust/kobalt/api/IDocContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDocContributor.kt
similarity index 73%
rename from src/main/kotlin/com/beust/kobalt/api/IDocContributor.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDocContributor.kt
index 8979783f..48797530 100644
--- a/src/main/kotlin/com/beust/kobalt/api/IDocContributor.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDocContributor.kt
@@ -2,7 +2,7 @@ package com.beust.kobalt.api
import com.beust.kobalt.TaskResult
-interface IDocContributor : IProjectAffinity {
+interface IDocContributor : IProjectAffinity, IContributor {
fun generateDoc(project: Project, context: KobaltContext, info: CompilerActionInfo) : TaskResult
}
diff --git a/src/main/kotlin/com/beust/kobalt/api/IFactory.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IFactory.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/api/IFactory.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IFactory.kt
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IIncrementalAssemblyContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IIncrementalAssemblyContributor.kt
new file mode 100644
index 00000000..cd730171
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IIncrementalAssemblyContributor.kt
@@ -0,0 +1,12 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.IncrementalTaskInfo
+
+/**
+ * Plug-ins that will be invoked during the "assemble" task and wish to return an incremental task instead
+ * of a regular one.
+ */
+interface IIncrementalAssemblyContributor : IContributor {
+ fun assemble(project: Project, context: KobaltContext) : IncrementalTaskInfo
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IIncrementalTaskContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IIncrementalTaskContributor.kt
new file mode 100644
index 00000000..1decac22
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IIncrementalTaskContributor.kt
@@ -0,0 +1,28 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.IncrementalTaskInfo
+
+/**
+ * Plug-ins that need to add incremental dynamic tasks (tasks that are not methods annotated with @Task) need
+ * to implement this interface.
+ */
+interface IIncrementalTaskContributor : IContributor {
+ fun incrementalTasksFor(project: Project, context: KobaltContext) : List
+}
+
+class IncrementalDynamicTask(val context: KobaltContext,
+ val plugin: IPlugin,
+ val name: String,
+ val doc: String,
+ val group: String,
+ val project: Project,
+ val dependsOn: List = listOf(),
+ val reverseDependsOn: List = listOf(),
+ val runBefore: List = listOf(),
+ val runAfter: List = listOf(),
+ val alwaysRunAfter: List = listOf(),
+ val incrementalClosure: (Project) -> IncrementalTaskInfo) {
+ override fun toString() = "[IncrementalDynamicTask $name dependsOn=$dependsOn reverseDependsOn=$reverseDependsOn]"
+}
+
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IJvmFlagContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IJvmFlagContributor.kt
new file mode 100644
index 00000000..f912ae1d
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IJvmFlagContributor.kt
@@ -0,0 +1,13 @@
+package com.beust.kobalt.api
+
+/**
+ * Plug-ins that add flags to the JVM used to run apps should implement this interface.
+ */
+interface IJvmFlagContributor : IContributor {
+ /**
+ * The list of JVM flags that will be added to the JVM when the app gets run. @param[currentFlags] is only here
+ * for convenience, in case you need to look at the current JVM flags before adding your own flags.
+ */
+ fun jvmFlagsFor(project: Project, context: KobaltContext, currentFlags: List) : List
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ILocalMavenRepoPathInterceptor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ILocalMavenRepoPathInterceptor.kt
new file mode 100644
index 00000000..1993f130
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ILocalMavenRepoPathInterceptor.kt
@@ -0,0 +1,8 @@
+package com.beust.kobalt.api
+
+/**
+ * Plug-ins that want to override the local maven repo path.
+ */
+interface ILocalMavenRepoPathInterceptor : IInterceptor {
+ fun repoPath(currentPath: String) : String
+}
diff --git a/src/main/kotlin/com/beust/kobalt/api/IMavenIdInterceptor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IMavenIdInterceptor.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/api/IMavenIdInterceptor.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IMavenIdInterceptor.kt
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IPlugin.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IPlugin.kt
new file mode 100644
index 00000000..e9d7acb6
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IPlugin.kt
@@ -0,0 +1,30 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.internal.TaskManager
+
+interface IPlugin : IPluginActor {
+ /**
+ * The name of this plug-in.
+ */
+ val name: String
+
+ /**
+ * @return true if this plug-in decided it should be enabled for this project.
+ */
+ fun accept(project: Project) : Boolean
+
+ /**
+ * Invoked on all plug-ins before the Kobalt execution stops.
+ */
+ fun shutdown()
+
+ /**
+ * Main entry point for a plug-in to initialize itself based on a project and a context.
+ */
+ fun apply(project: Project, context: KobaltContext) {}
+
+ /**
+ * Injected by Kobalt to manage tasks.
+ */
+ var taskManager : TaskManager
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IPluginActor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IPluginActor.kt
new file mode 100644
index 00000000..96d54218
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IPluginActor.kt
@@ -0,0 +1,15 @@
+package com.beust.kobalt.api
+
+interface IPluginActor {
+ /**
+ * Clean up any state that your actor might have saved so it can be run again.
+ */
+ fun cleanUpActors() {}
+}
+
+interface IContributor : IPluginActor
+
+interface IInterceptor : IPluginActor
+
+interface IListener : IPluginActor
+
diff --git a/src/main/kotlin/com/beust/kobalt/api/IProjectAffinity.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IProjectAffinity.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/api/IProjectAffinity.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IProjectAffinity.kt
diff --git a/src/main/kotlin/com/beust/kobalt/api/IProjectContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IProjectContributor.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/api/IProjectContributor.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IProjectContributor.kt
diff --git a/src/main/kotlin/com/beust/kobalt/api/IRepoContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IRepoContributor.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/api/IRepoContributor.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IRepoContributor.kt
diff --git a/src/main/kotlin/com/beust/kobalt/api/IRunnerContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IRunnerContributor.kt
similarity index 88%
rename from src/main/kotlin/com/beust/kobalt/api/IRunnerContributor.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IRunnerContributor.kt
index 5ab88cb0..f8c28b52 100644
--- a/src/main/kotlin/com/beust/kobalt/api/IRunnerContributor.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IRunnerContributor.kt
@@ -1,10 +1,11 @@
package com.beust.kobalt.api
import com.beust.kobalt.TaskResult
-import com.beust.kobalt.api.IClasspathDependency
/**
* Plugins that can run a project (task "run" or "test") should implement this interface.
+ *
+ * Currently not used.
*/
interface IRunnerContributor : IContributor, IProjectAffinity {
/**
diff --git a/src/main/kotlin/com/beust/kobalt/api/ISimpleAffinity.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISimpleAffinity.kt
similarity index 89%
rename from src/main/kotlin/com/beust/kobalt/api/ISimpleAffinity.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISimpleAffinity.kt
index 2711248b..b9c7cdfe 100644
--- a/src/main/kotlin/com/beust/kobalt/api/ISimpleAffinity.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISimpleAffinity.kt
@@ -8,5 +8,5 @@ interface ISimpleAffinity : IAffinity {
* @return an integer indicating the affinity of your actor. The actor that returns
* the highest affinity gets selected.
*/
- fun affinity(arg: T) : Int
+ fun affinity(project: T) : Int
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/beust/kobalt/api/ISourceDirectoriesInterceptor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISourceDirectoriesInterceptor.kt
similarity index 81%
rename from src/main/kotlin/com/beust/kobalt/api/ISourceDirectoriesInterceptor.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISourceDirectoriesInterceptor.kt
index aa39712b..33ec6e7c 100644
--- a/src/main/kotlin/com/beust/kobalt/api/ISourceDirectoriesInterceptor.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISourceDirectoriesInterceptor.kt
@@ -5,7 +5,7 @@ import java.io.File
/**
* Plug-ins can alter the source directories by implementing this interface.
*/
-interface ISourceDirectoryIncerceptor : IInterceptor {
+interface ISourceDirectoryInterceptor : IInterceptor {
fun intercept(project: Project, context: KobaltContext, sourceDirectories: List) : List
}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISourceDirectoryContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISourceDirectoryContributor.kt
new file mode 100644
index 00000000..fced0771
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISourceDirectoryContributor.kt
@@ -0,0 +1,20 @@
+package com.beust.kobalt.api
+
+import java.io.File
+
+/**
+ * Plug-ins that add source directories to be compiled need to implement this interface.
+ */
+interface ISourceDirectoryContributor : IContributor {
+ fun sourceDirectoriesFor(project: Project, context: KobaltContext): List
+}
+
+/**
+ * @return the source directories for this project including source contributors.
+ */
+fun KobaltContext.sourceDirectories(project: Project) : Set {
+ val result = pluginInfo.sourceDirContributors.flatMap {
+ it.sourceDirectoriesFor(project, this)
+ }
+ return result.toSet()
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITaskContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITaskContributor.kt
new file mode 100644
index 00000000..c606d54f
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITaskContributor.kt
@@ -0,0 +1,32 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.TaskResult
+import com.beust.kobalt.internal.TaskResult2
+
+/**
+ * Plug-ins that need to add dynamic tasks (tasks that are not methods annotated with @Task) need
+ * to implement this interface.
+ */
+interface ITaskContributor : IContributor {
+ fun tasksFor(project: Project, context: KobaltContext) : List
+}
+
+class DynamicTask(override val plugin: IPlugin, override val name: String, override val doc: String,
+ override val group: String,
+ override val project: Project,
+ val dependsOn: List = listOf(),
+ val reverseDependsOn: List = listOf(),
+ val runBefore: List = listOf(),
+ val runAfter: List = listOf(),
+ val alwaysRunAfter: List = listOf(),
+ val closure: (Project) -> TaskResult) : ITask {
+
+ override fun call(): TaskResult2 {
+ val taskResult = closure.invoke(project)
+ return TaskResult2(taskResult.success, errorMessage = taskResult.errorMessage, value = this)
+ }
+
+ override fun toString() =
+ "[DynamicTask ${project.name}:$name dependsOn=$dependsOn reverseDependsOn=$reverseDependsOn]"
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITemplateContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITemplateContributor.kt
new file mode 100644
index 00000000..9af4bb7d
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITemplateContributor.kt
@@ -0,0 +1,45 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.Args
+
+/**
+ * Plugins that want to participate in the --init process (they can generate files to initialize
+ * a new project).
+ */
+interface ITemplateContributor : IContributor {
+ companion object {
+ val DIRECTORY_NAME = "templates"
+ }
+
+ val templates: List
+}
+
+interface ITemplate {
+ /**
+ * The name of this template. This is the name that will be looked up when passed to the --init
+ * argument.
+ */
+ val templateName: String
+
+ /**
+ * Description of this template.
+ */
+ val templateDescription: String
+
+ /**
+ * The plug-in this template belongs to.
+ */
+ val pluginName: String
+
+ /**
+ * Instructions to display to the user after a template has been generated.
+ */
+ val instructions : String get() = "Build this project with `./kobaltw assemble`"
+
+ /**
+ * Generate the files for this template. The parameter is the arguments that were passed to the kobaltw
+ * command.
+ */
+ fun generateTemplate(args: Args, classLoader: ClassLoader)
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITestJvmFlagContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITestJvmFlagContributor.kt
new file mode 100644
index 00000000..6f1c0bfb
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITestJvmFlagContributor.kt
@@ -0,0 +1,12 @@
+package com.beust.kobalt.api
+
+/**
+ * Plug-ins that add flags to the JVM used to run tests should implement this interface.
+ */
+interface ITestJvmFlagContributor : IContributor {
+ /**
+ * The list of JVM flags that will be added to the JVM when the tests get run. @param[currentFlags] is only here
+ * for convenience, in case you need to look at the current JVM flags before adding your own flags.
+ */
+ fun testJvmFlagsFor(project: Project, context: KobaltContext, currentFlags: List) : List
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITestJvmFlagInterceptor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITestJvmFlagInterceptor.kt
new file mode 100644
index 00000000..389f4704
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITestJvmFlagInterceptor.kt
@@ -0,0 +1,14 @@
+package com.beust.kobalt.api
+
+/**
+ * Plug-ins that add flags to the JVM used to run tests should implement this interface.
+ */
+interface ITestJvmFlagInterceptor : IInterceptor {
+ /**
+ * @return the list of all flags that should be used. If you only want to add flags to the current list,
+ * just return the concatenation of @param[currentFlags] and your own list (or use ITestJvmFlagContributor).
+ * If you actually alter the list of flags, make sure you don't remove anything critical from @param[currentFlags].
+ */
+ fun testJvmFlagsFor(project: Project, context: KobaltContext, currentFlags: List) : List
+}
+
diff --git a/src/main/kotlin/com/beust/kobalt/api/ITestRunnerContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITestRunnerContributor.kt
similarity index 51%
rename from src/main/kotlin/com/beust/kobalt/api/ITestRunnerContributor.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITestRunnerContributor.kt
index 52bc9551..b8b860f6 100644
--- a/src/main/kotlin/com/beust/kobalt/api/ITestRunnerContributor.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITestRunnerContributor.kt
@@ -7,8 +7,9 @@ import com.beust.kobalt.TaskResult
*/
interface ITestRunnerContributor : IContributor, IProjectAffinity {
/**
- * Run the project.
+ * Run the tests. If [[configName]] is not empty, a specific test configuration is requested.
*/
- fun run(project: Project, context: KobaltContext, classpath: List) : TaskResult
+ fun run(project: Project, context: KobaltContext, configName: String,
+ classpath: List) : TaskResult
}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITestSourceDirectoryContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITestSourceDirectoryContributor.kt
new file mode 100644
index 00000000..646d9dd2
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITestSourceDirectoryContributor.kt
@@ -0,0 +1,18 @@
+package com.beust.kobalt.api
+
+import java.io.File
+
+/**
+ * Plug-ins that add test source directories to be compiled need to implement this interface.
+ */
+interface ITestSourceDirectoryContributor : IContributor {
+ fun testSourceDirectoriesFor(project: Project, context: KobaltContext): List
+}
+
+fun KobaltContext.testSourceDirectories(project: Project) : List {
+ val result = pluginInfo.testSourceDirContributors.flatMap {
+ it.testSourceDirectoriesFor(project, this)
+ }
+ return result
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/InputStreamJarTemplate.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/InputStreamJarTemplate.kt
new file mode 100644
index 00000000..222a2829
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/InputStreamJarTemplate.kt
@@ -0,0 +1,55 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.Args
+import com.beust.kobalt.misc.KFiles
+import com.beust.kobalt.misc.kobaltLog
+import java.io.*
+import java.net.URL
+import java.util.jar.JarInputStream
+
+/**
+ * Base class for templates that decompress a jar file.
+ */
+interface InputStreamJarTemplate : ITemplate {
+ val inputStream: InputStream
+
+ override fun generateTemplate(args: Args, classLoader: ClassLoader) {
+ val destDir = File(".")
+ JarInputStream(inputStream).use { ins ->
+ var entry = ins.nextEntry
+ while (entry != null) {
+ val f = File(destDir.path + File.separator + entry.name)
+ if (entry.isDirectory) {
+ f.mkdir()
+ entry = ins.nextEntry
+ continue
+ }
+
+ kobaltLog(2, " Extracting: $entry to ${f.absolutePath}")
+ FileOutputStream(f).use { fos ->
+ KFiles.copy(ins, fos)
+ }
+ entry = ins.nextEntry
+ }
+ }
+ }
+}
+
+abstract class ResourceJarTemplate(jarName: String, val classLoader: ClassLoader) : InputStreamJarTemplate {
+ override val inputStream : InputStream = classLoader.getResource(jarName).openConnection().inputStream
+}
+
+abstract class FileJarTemplate(val fileName: String) : InputStreamJarTemplate {
+ override val inputStream = FileInputStream(File(fileName))
+}
+
+abstract class HttpJarTemplate(val url: String) : InputStreamJarTemplate {
+ override val inputStream : InputStream
+ get() {
+ try {
+ return URL(url).openConnection().inputStream
+ } catch(ex: IOException) {
+ throw IllegalArgumentException("Couldn't connect to $url")
+ }
+ }
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/JarFinder.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/JarFinder.kt
new file mode 100644
index 00000000..08f08924
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/JarFinder.kt
@@ -0,0 +1,19 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.maven.DependencyManager
+import java.io.File
+import java.util.concurrent.Future
+
+class JarFinder {
+ companion object {
+ /**
+ * @return a Future for the jar file corresponding to this id.
+ */
+ fun byIdFuture(id: String) : Future = DependencyManager.create(id).jarFile
+
+ /**
+ * @return the jar file corresponding to this id. This might cause a network call.
+ */
+ fun byId(id: String) = byIdFuture(id).get()
+ }
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Kobalt.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Kobalt.kt
new file mode 100644
index 00000000..7d37a0b8
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Kobalt.kt
@@ -0,0 +1,141 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.Constants
+import com.beust.kobalt.HostConfig
+import com.beust.kobalt.Plugins
+import com.beust.kobalt.internal.PluginInfo
+import com.beust.kobalt.maven.DependencyManager
+import com.beust.kobalt.maven.aether.KobaltMavenResolver
+import com.google.inject.Guice
+import com.google.inject.Injector
+import com.google.inject.Module
+import java.io.InputStream
+import java.time.Duration
+import java.util.*
+
+class Kobalt {
+ companion object {
+ lateinit var INJECTOR : Injector
+
+ fun init(module: Module) {
+ Kobalt.INJECTOR = Guice.createInjector(module)
+
+ //
+ // Add all the plugins read in kobalt-plugin.xml to the Plugins singleton, so that code
+ // in the build file that calls Plugins.findPlugin() can find them (code in the
+ // build file do not have access to the KobaltContext).
+ //
+ val pluginInfo = Kobalt.INJECTOR.getInstance(PluginInfo::class.java)
+ pluginInfo.plugins.forEach { Plugins.addPluginInstance(it) }
+ }
+
+ var context: KobaltContext? = null
+
+ /**
+ * @return the repos calculated from various places where repos can be specified.
+ */
+ val repos : Set
+ get() {
+ val settingsRepos = Kobalt.context?.settings?.defaultRepos?.map { HostConfig(it) } ?: emptyList()
+ // Repos from in the settings
+ val result = ArrayList(
+ (if (settingsRepos.isEmpty()) Constants.DEFAULT_REPOS
+ else settingsRepos)
+ )
+
+ // Repo from in the settings
+ Kobalt.context?.settings?.kobaltCompilerRepo?.let {
+ result.add(HostConfig(it))
+ }
+
+ // Repos from the repo contributors
+ Kobalt.context?.pluginInfo?.repoContributors?.forEach {
+ result.addAll(it.reposFor(null))
+ }
+
+ // Repos from the build file
+ result.addAll(reposFromBuildFiles)
+
+ result.forEach {
+ KobaltMavenResolver.initAuthentication(it)
+ }
+ return result.toHashSet()
+ }
+
+ val reposFromBuildFiles = hashSetOf()
+
+ fun addRepo(repo: HostConfig) = reposFromBuildFiles.add(
+ if (repo.url.endsWith("/")) repo
+ else repo.copy(url = (repo.url + "/")))
+
+ val buildFileClasspath = arrayListOf()
+
+ fun addBuildFileClasspath(dep: String) {
+ val dependencyManager = Kobalt.INJECTOR.getInstance(DependencyManager::class.java)
+ buildFileClasspath.add(dependencyManager.create(dep))
+ }
+
+ private val KOBALT_PROPERTIES = "kobalt.properties"
+ private val PROPERTY_KOBALT_VERSION = "kobalt.version"
+ private val PROPERTY_KOBALT_VERSION_CHECK_TIMEOUT = "kobalt.version.checkTimeout" // ISO-8601
+
+ /** kobalt.properties */
+ private val kobaltProperties: Properties by lazy { readProperties() }
+
+ /**
+ * Read the content of kobalt.properties
+ */
+ private fun readProperties() : Properties {
+ val result = Properties()
+
+ // kobalt.properties is internal to Kobalt
+ val url = Kobalt::class.java.classLoader.getResource(KOBALT_PROPERTIES)
+ if (url != null) {
+ readProperties(result, url.openConnection().inputStream)
+ } else {
+ throw AssertionError("Couldn't find $KOBALT_PROPERTIES")
+ }
+
+ // local.properties can be used by external users
+// Paths.get(LOCAL_PROPERTIES).let { path ->
+// if (Files.exists(path)) {
+// Files.newInputStream(path).use {
+// readProperties(result, it)
+// }
+// }
+// }
+
+ return result
+ }
+
+ private fun readProperties(properties: Properties, ins: InputStream) {
+ properties.load(ins)
+ ins.close()
+ properties.forEach { es -> System.setProperty(es.key.toString(), es.value.toString()) }
+ }
+
+ val version: String
+ get() = kobaltProperties.getProperty(PROPERTY_KOBALT_VERSION)
+
+ // Note: Duration is Java 8 only, might need an alternative if we want to support Java < 8
+ val versionCheckTimeout: Duration
+ get() = Duration.parse( kobaltProperties.getProperty(PROPERTY_KOBALT_VERSION_CHECK_TIMEOUT) ?: "P1D")
+
+ fun findPlugin(name: String) = Plugins.findPlugin(name)
+
+ val optionsFromBuild = arrayListOf()
+ fun addKobaltOptions(options: Array) {
+ optionsFromBuild.addAll(options)
+ }
+
+ val buildSourceDirs = arrayListOf()
+ fun addBuildSourceDirs(dirs: Array) {
+ buildSourceDirs.addAll(dirs)
+ }
+
+ fun cleanUp() {
+ buildSourceDirs.clear()
+ buildFileClasspath.clear()
+ }
+ }
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/KobaltContext.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/KobaltContext.kt
new file mode 100644
index 00000000..b7e5ace8
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/KobaltContext.kt
@@ -0,0 +1,116 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.Args
+import com.beust.kobalt.KobaltException
+import com.beust.kobalt.Plugins
+import com.beust.kobalt.Variant
+import com.beust.kobalt.internal.ILogger
+import com.beust.kobalt.internal.IncrementalManager
+import com.beust.kobalt.internal.KobaltSettings
+import com.beust.kobalt.internal.PluginInfo
+import com.beust.kobalt.maven.DependencyManager
+import com.beust.kobalt.maven.MavenId
+import com.beust.kobalt.maven.PomGenerator
+import com.beust.kobalt.maven.SimpleDep
+import com.beust.kobalt.maven.aether.KobaltMavenResolver
+import com.beust.kobalt.misc.KobaltExecutors
+import java.io.File
+
+class KobaltContext(val args: Args) {
+ lateinit var variant: Variant
+ val profiles = arrayListOf()
+
+ init {
+ args.profiles?.split(',')?.filterNotNull()?.forEach {
+ profiles.add(it)
+ }
+ }
+
+ fun findPlugin(name: String) = Plugins.findPlugin(name)
+
+ /**
+ * Files that can be resolved in the local cache.
+ */
+ enum class FileType { JAR, POM, SOURCES, JAVADOC, OTHER }
+
+ /**
+ * @param{id} is the Maven coordinate (e.g. "org.testng:testng:6.9.11"). If you are looking for a file
+ * that is not described by the enum (e.g. "aar"), use OTHER and make sure your @param{id} contains
+ * the fully qualified id (e.g. "com.example:example::aar:1.0").
+ */
+ fun fileFor(id: String, fileType: FileType) : File {
+ val dep = SimpleDep(MavenId.create(id))
+ fun toQualifier(dep: SimpleDep, ext: String, qualifier: String?) =
+ dep.groupId + ":" + dep.artifactId +
+ ":$ext" +
+ (if (qualifier != null) ":$qualifier" else "") +
+ ":" + dep.version
+ val fullId =
+ when (fileType) {
+ FileType.JAR -> toQualifier(dep, "jar", null)
+ FileType.POM -> toQualifier(dep, "pom", null)
+ FileType.SOURCES -> toQualifier(dep, "", "sources")
+ FileType.JAVADOC -> toQualifier(dep, "", "javadoc")
+ FileType.OTHER -> id
+ }
+ val resolved = resolver.resolveToArtifact(fullId)
+ if (resolved != null) {
+ return resolved.file
+ } else {
+ throw KobaltException("Couldn't resolve $id")
+ }
+ }
+
+ /**
+ * @return the content of the pom.xml for the given project.
+ */
+ fun generatePom(project: Project) = pomGeneratorFactory.create(project).generate()
+
+ /** All the projects that are being built during this run */
+ val allProjects = arrayListOf()
+
+ /** For internal use only */
+ val internalContext = InternalContext()
+
+ //
+ // Injected
+ //
+ lateinit var pluginInfo: PluginInfo
+ lateinit var pluginProperties: PluginProperties
+ lateinit var dependencyManager: DependencyManager
+ lateinit var executors: KobaltExecutors
+ lateinit var settings: KobaltSettings
+ lateinit var incrementalManager: IncrementalManager
+ lateinit var resolver: KobaltMavenResolver
+ lateinit var pomGeneratorFactory: PomGenerator.IFactory
+ lateinit var logger: ILogger
+}
+
+class InternalContext {
+ /**
+ * When an incremental task decides it's up to date, it sets this boolean to true so that subsequent
+ * tasks in that project can be skipped as well. This is an internal field that should only be set by Kobalt.
+ */
+ private val incrementalSuccesses = hashSetOf()
+ fun previousTaskWasIncrementalSuccess(projectName: String) = incrementalSuccesses.contains(projectName) ?: false
+ fun setIncrementalSuccess(projectName: String) = incrementalSuccesses.add(projectName)
+
+ /**
+ * Keep track of whether the build file was modified. If this boolean is true, incremental compilation
+ * will be disabled.
+ */
+ var buildFileOutOfDate: Boolean = false
+
+ /**
+ * The absolute directory of the current project.
+ */
+ var absoluteDir: File? = null
+
+ /**
+ * If true, will force a recompile of the files even if using the incremental compile
+ */
+ var forceRecompile: Boolean = false
+
+ var noIncrementalKotlin: Boolean = false
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/beust/kobalt/api/PluginProperties.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/PluginProperties.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/api/PluginProperties.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/PluginProperties.kt
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/PluginTask.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/PluginTask.kt
new file mode 100644
index 00000000..b416f96b
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/PluginTask.kt
@@ -0,0 +1,20 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.internal.TaskResult2
+import java.util.concurrent.Callable
+
+interface ITask : Callable> {
+ val plugin: IPlugin
+ val project: Project
+ val name: String
+ val doc: String
+ val group: String
+}
+
+abstract class PluginTask : ITask {
+ override val name: String = ""
+ override open val doc: String = ""
+ override open val group: String = "other"
+
+ override fun toString() = project.name + ":" + name
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ProductFlavorConfig.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ProductFlavorConfig.kt
new file mode 100644
index 00000000..86c69301
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ProductFlavorConfig.kt
@@ -0,0 +1,9 @@
+package com.beust.kobalt.api
+
+class ProductFlavorConfig(val name: String) : IBuildConfig,
+ IDependencyHolder by DependencyHolder() {
+ var applicationId: String? = null
+ override var buildConfig : BuildConfig? = BuildConfig()
+}
+
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Project.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Project.kt
new file mode 100644
index 00000000..e54e30ec
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Project.kt
@@ -0,0 +1,314 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.TestConfig
+import com.beust.kobalt.api.annotation.Directive
+import com.beust.kobalt.maven.DependencyManager
+import com.beust.kobalt.maven.aether.AetherDependency
+import com.beust.kobalt.maven.aether.KobaltMavenResolver
+import com.beust.kobalt.misc.KFiles
+import com.beust.kobalt.misc.kobaltLog
+import org.apache.maven.model.Model
+import java.io.File
+import java.util.*
+import java.util.concurrent.Future
+import java.util.concurrent.FutureTask
+import java.util.regex.Pattern
+
+open class Project(
+ @Directive open var name: String = "",
+ @Directive open var version: String? = null,
+ @Directive open var directory: String = ".",
+ @Directive open var buildDirectory: String = KFiles.KOBALT_BUILD_DIR,
+ @Directive open var group: String? = null,
+ @Directive open var artifactId: String? = null,
+ @Directive open var packaging: String? = null,
+ @Directive open var description : String = "",
+ @Directive open var url: String? = null,
+ @Directive open var pom: Model? = null,
+ @Directive open var dependsOn: ArrayList = arrayListOf(),
+ @Directive open var testsDependOn: ArrayList = arrayListOf(),
+ @Directive open var packageName: String? = group)
+ : IBuildConfig, IDependencyHolder by DependencyHolder() {
+
+ init {
+ this.project = this
+ }
+
+ fun allProjectDependedOn() = project.dependsOn + project.testsDependOn
+
+ class ProjectExtra(project: Project) {
+ var isDirty = false
+
+ /**
+ * @return true if any of the projects we depend on is dirty.
+ */
+ fun dependsOnDirtyProjects(project: Project) = project.allProjectDependedOn().any { it.projectExtra.isDirty }
+ }
+
+ /**
+ * This field caches a bunch of things we don't want to recalculate all the time, such as the list of suffixes
+ * found in this project.
+ */
+ val projectExtra = ProjectExtra(this)
+
+ val testConfigs = arrayListOf()
+
+ // If one is specified by default, we only generateAndSave a BuildConfig, find a way to fix that
+ override var buildConfig : BuildConfig? = null // BuildConfig()
+
+ val projectProperties = ProjectProperties()
+
+ override fun equals(other: Any?) = name == (other as Project).name
+ override fun hashCode() = name.hashCode()
+
+ companion object {
+ val DEFAULT_SOURCE_DIRECTORIES = setOf("src/main/java", "src/main/kotlin", "src/main/resources")
+ val DEFAULT_SOURCE_DIRECTORIES_TEST = setOf("src/test/java", "src/test/kotlin", "src/test/resources")
+ }
+
+ //
+ // Directories
+ //
+
+ @Directive
+ fun sourceDirectories(init: Sources.() -> Unit) : Sources {
+ return Sources(this, sourceDirectories).apply { init() }
+ }
+
+ var sourceDirectories = hashSetOf().apply { addAll(DEFAULT_SOURCE_DIRECTORIES)}
+
+ @Directive
+ fun sourceDirectoriesTest(init: Sources.() -> Unit) : Sources {
+ return Sources(this, sourceDirectoriesTest).apply { init() }
+ }
+
+ var sourceDirectoriesTest = hashSetOf().apply { addAll(DEFAULT_SOURCE_DIRECTORIES_TEST)}
+
+ //
+ // Dependencies
+ //
+
+ @Directive
+ fun dependenciesTest(init: Dependencies.() -> Unit) : Dependencies {
+ dependencies = Dependencies(this, testDependencies, arrayListOf(),
+ testProvidedDependencies, compileOnlyDependencies, compileRuntimeDependencies,
+ excludedDependencies, nativeDependencies)
+ dependencies!!.init()
+ return dependencies!!
+ }
+
+ val testDependencies : ArrayList = arrayListOf()
+ val testProvidedDependencies : ArrayList = arrayListOf()
+
+ fun testsDependOn(vararg projects: Project) = testsDependOn.addAll(projects)
+ fun dependsOn(vararg projects: Project) = dependsOn.addAll(projects)
+
+ /** Used to disambiguate various name properties */
+ @Directive
+ val projectName: String get() = name
+
+ val productFlavors = hashMapOf()
+
+ fun addProductFlavor(name: String, pf: ProductFlavorConfig) {
+ productFlavors.put(name, pf)
+ }
+
+ var defaultConfig : BuildConfig? = null
+
+ val buildTypes = hashMapOf()
+
+ fun addBuildType(name: String, bt: BuildTypeConfig) {
+ buildTypes.put(name, bt)
+ }
+
+ fun classesDir(context: KobaltContext): String {
+ val initial = KFiles.joinDir(buildDirectory, "classes")
+ val result = context.pluginInfo.buildDirectoryInterceptors.fold(initial, { dir, intercept ->
+ intercept.intercept(this, context, dir)
+ })
+ return result
+ }
+
+ class Dep(val file: File, val id: String)
+
+ /**
+ * @return a list of the transitive dependencies (absolute paths to jar files) for the given dependencies.
+ * Can be used for example as `collect(compileDependencies)`.
+ */
+ @Directive
+ fun collect(dependencies: List) : List {
+ return (Kobalt.context?.dependencyManager?.transitiveClosure(dependencies) ?: emptyList())
+ .map { Dep(it.jarFile.get(), it.id) }
+ }
+
+ override fun toString() = "[Project $name]"
+}
+
+class Sources(val project: Project, val sources: HashSet) {
+ @Directive
+ fun path(vararg paths: String) {
+ sources.addAll(paths)
+ }
+}
+
+class Dependencies(val project: Project,
+ val dependencies: ArrayList,
+ val optionalDependencies: ArrayList,
+ val providedDependencies: ArrayList,
+ val compileOnlyDependencies: ArrayList,
+ val runtimeDependencies: ArrayList,
+ val excludedDependencies: ArrayList,
+ val nativeDependencies: ArrayList) {
+
+ /**
+ * Add the dependencies to the given ArrayList and return a list of future jar files corresponding to
+ * these dependencies. Futures are necessary here since this code is invoked from the build file and
+ * we might not have set up the extra IRepositoryContributors just yet. By the time these
+ * future tasks receive a get(), the repos will be correct.
+ */
+ private fun addToDependencies(project: Project, dependencies: ArrayList,
+ dep: Array, optional: Boolean = false, excludeConfig: ExcludeConfig? = null): List>
+ = with(dep.map {
+ val resolved =
+ if (KobaltMavenResolver.isRangeVersion(it)) {
+ // Range id
+ val node = Kobalt.INJECTOR.getInstance(KobaltMavenResolver::class.java).resolveToArtifact(it)
+ val result = KobaltMavenResolver.artifactToId(node)
+ kobaltLog(2, "Resolved range id $it to $result")
+ result
+ } else {
+ it
+ }
+ DependencyManager.create(resolved, optional, project.directory)
+ }) {
+ dependencies.addAll(this)
+ if (excludeConfig != null) {
+ this.forEach { it.excluded.add(excludeConfig) }
+ }
+
+ this.map { FutureTask { it.jarFile.get() } }
+ }
+
+ @Directive
+ fun compile(vararg dep: String) = addToDependencies(project, dependencies, dep)
+
+ class ExcludeConfig {
+ val ids = arrayListOf()
+
+ @Directive
+ fun exclude(vararg passedIds: String) = ids.addAll(passedIds)
+
+ class ArtifactConfig(
+ var groupId: String? = null,
+ var artifactId: String? = null,
+ var version: String? = null
+ )
+
+ val artifacts = arrayListOf()
+
+ @Directive
+ fun exclude(groupId: String? = null, artifactId: String? = null, version: String? = null)
+ = artifacts.add(ArtifactConfig(groupId, artifactId, version))
+
+ fun match(pattern: String?, id: String) : Boolean {
+ return pattern == null || Pattern.compile(pattern).matcher(id).matches()
+ }
+
+ /**
+ * @return true if the dependency is excluded with any of the exclude() directives. The matches
+ * are performed by a regular expression match against the dependency.
+ */
+ fun isExcluded(dep: IClasspathDependency) : Boolean {
+ // Straight id match
+ var result = ids.any { match(it, dep.id) }
+
+ // Match on any combination of (groupId, artifactId, version)
+ if (! result && dep.isMaven) {
+ val mavenDep = dep as AetherDependency
+ val artifact = mavenDep.artifact
+ result = artifacts.any {
+ val match1 = it.groupId == null || match(it.groupId, artifact.groupId)
+ val match2 = it.artifactId == null || match(it.artifactId, artifact.artifactId)
+ val match3 = it.version == null || match(it.version, artifact.version)
+ match1 && match2 && match3
+ }
+ }
+
+ return result
+ }
+ }
+
+ @Directive
+ fun compile(dep: String, init: ExcludeConfig.() -> Unit) {
+ val excludeConfig = ExcludeConfig().apply {
+ init()
+ }
+ addToDependencies(project, dependencies, arrayOf(dep), excludeConfig = excludeConfig)
+ }
+
+ @Directive
+ fun compileOnly(vararg dep: String) = addToDependencies(project, compileOnlyDependencies, dep)
+
+ @Directive
+ fun compileOptional(vararg dep: String) {
+ addToDependencies(project, optionalDependencies, dep, optional = true)
+ addToDependencies(project, dependencies, dep, optional = true)
+ }
+
+ @Directive
+ fun provided(vararg dep: String) {
+ addToDependencies(project, providedDependencies, dep)
+ }
+
+ @Directive
+ fun runtime(vararg dep: String) = addToDependencies(project, runtimeDependencies, dep)
+
+ @Directive
+ fun exclude(vararg dep: String) = addToDependencies(project, excludedDependencies, dep)
+
+ @Directive
+ fun native(vararg dep: String) = addToDependencies(project, nativeDependencies, dep)
+}
+
+class BuildConfig {
+ class Field(val name: String, val type: String, val value: Any) {
+ override fun hashCode() = name.hashCode()
+ override fun equals(other: Any?) = (other as Field).name == name
+ }
+
+ val fields = arrayListOf()
+
+ fun field(type: String, name: String, value: Any) {
+ fields.add(Field(name, type, value))
+ }
+}
+
+interface IBuildConfig {
+ var buildConfig: BuildConfig?
+
+ fun buildConfig(init: BuildConfig.() -> Unit) {
+ buildConfig = BuildConfig().apply {
+ init()
+ }
+ }
+}
+
+fun Project.defaultConfig(init: BuildConfig.() -> Unit) = let { project ->
+ BuildConfig().apply {
+ init()
+ project.defaultConfig = this
+ }
+}
+
+@Directive
+fun Project.buildType(name: String, init: BuildTypeConfig.() -> Unit) = BuildTypeConfig(name).apply {
+ init()
+ addBuildType(name, this)
+}
+
+
+@Directive
+fun Project.productFlavor(name: String, init: ProductFlavorConfig.() -> Unit) = ProductFlavorConfig(name).apply {
+ init()
+ addProductFlavor(name, this)
+}
diff --git a/src/main/kotlin/com/beust/kobalt/api/ProjectProperties.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ProjectProperties.kt
similarity index 100%
rename from src/main/kotlin/com/beust/kobalt/api/ProjectProperties.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ProjectProperties.kt
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Task.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Task.kt
new file mode 100644
index 00000000..c45a820d
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Task.kt
@@ -0,0 +1,7 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.misc.toString
+
+data class Task(val pluginName: String, val taskName: String) {
+ override fun toString() = toString("Task", pluginName, taskName)
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/TaskContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/TaskContributor.kt
new file mode 100644
index 00000000..8c68be94
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/TaskContributor.kt
@@ -0,0 +1,86 @@
+package com.beust.kobalt.api
+
+import com.beust.kobalt.IncrementalTaskInfo
+import com.beust.kobalt.TaskResult
+import com.beust.kobalt.Variant
+import com.beust.kobalt.api.annotation.AnnotationDefault
+import com.beust.kobalt.internal.IncrementalManager
+import com.google.inject.Inject
+
+/**
+ * Plug-ins that are ITaskContributor can use this class to manage their collection of tasks and
+ * implement the interface by delegating to an instance of this class (if injection permits).
+ */
+class TaskContributor @Inject constructor(val incrementalManagerFactory: IncrementalManager.IFactory)
+ : ITaskContributor {
+ val dynamicTasks = arrayListOf()
+
+ /**
+ * Register dynamic tasks corresponding to the variants found in the project,e.g. assembleDevDebug,
+ * assembleDevRelease, etc...
+ *
+ * TODO: this should be done automatically so that users don't have to invoke it themselves.
+ * Certain tasks could have a boolean flag "hasVariants" and any task that depends on it automatically
+ * depends on variants of that task.
+ */
+ fun addVariantTasks(plugin: IPlugin, project: Project, context: KobaltContext, taskName: String,
+ group: String = AnnotationDefault.GROUP,
+ dependsOn: List = emptyList(),
+ reverseDependsOn : List = emptyList(),
+ runBefore : List = emptyList(),
+ runAfter : List = emptyList(),
+ runTask: (Project) -> TaskResult) {
+ Variant.allVariants(project).forEach { variant ->
+ val variantTaskName = variant.toTask(taskName)
+ dynamicTasks.add(DynamicTask(plugin, variantTaskName, variantTaskName, group, project,
+ dependsOn = dependsOn.map { variant.toTask(it) },
+ reverseDependsOn = reverseDependsOn.map { variant.toTask(it) },
+ runBefore = runBefore.map { variant.toTask(it) },
+ runAfter = runAfter.map { variant.toTask(it) },
+ closure = { p: Project ->
+ context.variant = variant
+ runTask(project)
+ }))
+ }
+ }
+
+ fun addTask(plugin: IPlugin, project: Project, taskName: String, description: String,
+ group: String = AnnotationDefault.GROUP,
+ dependsOn: List = emptyList(),
+ reverseDependsOn : List = emptyList(),
+ runBefore : List = emptyList(),
+ runAfter : List = emptyList(),
+ alwaysRunAfter: List = emptyList(),
+ runTask: (Project) -> TaskResult) {
+ dynamicTasks.add(DynamicTask(plugin, taskName, description, group, project,
+ dependsOn = dependsOn,
+ reverseDependsOn = reverseDependsOn,
+ runBefore = runBefore,
+ runAfter = runAfter,
+ alwaysRunAfter = alwaysRunAfter,
+ closure = { p: Project ->
+ runTask(project)
+ }))
+ }
+
+ fun addIncrementalVariantTasks(plugin: IPlugin, project: Project, context: KobaltContext, taskName: String,
+ group: String = AnnotationDefault.GROUP,
+ dependsOn: List = emptyList(),
+ reverseDependsOn : List = emptyList(),
+ runBefore : List = emptyList(),
+ runAfter : List = emptyList(),
+ runTask: (Project) -> IncrementalTaskInfo) {
+ Variant.allVariants(project).forEach { variant ->
+ val variantTaskName = variant.toTask(taskName)
+ context.variant = variant
+ dynamicTasks.add(DynamicTask(plugin, variantTaskName, variantTaskName, group, project,
+ dependsOn = dependsOn.map { variant.toTask(it) },
+ reverseDependsOn = reverseDependsOn.map { variant.toTask(it) },
+ runBefore = runBefore.map { variant.toTask(it) },
+ runAfter = runAfter.map { variant.toTask(it) },
+ closure = incrementalManagerFactory.create().toIncrementalTaskClosure(taskName, runTask, variant)))
+ }
+ }
+
+ override fun tasksFor(project: Project, context: KobaltContext) : List = dynamicTasks
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/annotation/Annotations.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/annotation/Annotations.kt
new file mode 100644
index 00000000..f4269d47
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/annotation/Annotations.kt
@@ -0,0 +1,90 @@
+package com.beust.kobalt.api.annotation
+
+/**
+ * Plugins that export directives should annotated those with this annotation so they can be documented and also
+ * receive special treatment for auto completion in the plug-in.
+ */
+annotation class Directive
+
+object AnnotationDefault {
+ const val GROUP = "other"
+}
+
+@Retention(AnnotationRetention.RUNTIME)
+annotation class Task(
+ /* This task's name */
+ val name: String,
+
+ /* The documentation for this task */
+ val description: String = "",
+
+ /** Used to show the task in the correct group in the IDE */
+ val group: String = AnnotationDefault.GROUP,
+
+ /** Dependency: tasks this task depends on */
+ val dependsOn: Array = arrayOf(),
+
+ /** Dependency: tasks this task will be made dependend upon */
+ val reverseDependsOn: Array = arrayOf(),
+
+ /** Ordering: tasks that need to be run before this one */
+ val runBefore: Array = arrayOf(),
+
+ /** Ordering: tasks this task runs after */
+ val runAfter: Array = arrayOf(),
+
+ /** Wrapper tasks */
+ val alwaysRunAfter: Array = arrayOf()
+)
+
+@Retention(AnnotationRetention.RUNTIME)
+annotation class IncrementalTask(
+ /* This task's name */
+ val name: String,
+
+ /* The documentation for this task */
+ val description: String = "",
+
+ /** Used to show the task in the correct group in the IDE */
+ val group: String = AnnotationDefault.GROUP,
+
+ /** Dependency: tasks this task depends on */
+ val dependsOn: Array = arrayOf(),
+
+ /** Dependency: tasks this task will be made dependend upon */
+ val reverseDependsOn: Array = arrayOf(),
+
+ /** Tasks that this task depends on */
+ val runBefore: Array = arrayOf(),
+
+ /** Ordering: tasks this task runs after */
+ val runAfter: Array = arrayOf(),
+
+ /** Wrapper tasks */
+ val alwaysRunAfter: Array = arrayOf()
+)
+
+/**
+ * Plugins that export properties should annotate those with this annotation so they can be documented.
+ */
+@Retention(AnnotationRetention.RUNTIME)
+annotation class ExportedPluginProperty(
+ /** Documentation for this property */
+ val doc: String = "",
+
+ /** The type of this property */
+ val type: String = ""
+)
+
+/**
+ * Plugins that export properties on the Project instance should annotate those with this annotation so
+ * they can be documented.
+ */
+@Retention(AnnotationRetention.RUNTIME)
+annotation class ExportedProjectProperty(
+ /** Documentation for this property */
+ val doc: String = "",
+
+ /** The type of this property */
+ val type: String = ""
+)
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt
new file mode 100644
index 00000000..5334e09f
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt
@@ -0,0 +1,83 @@
+package com.beust.kobalt.archive
+
+import com.beust.kobalt.*
+import com.beust.kobalt.api.KobaltContext
+import com.beust.kobalt.api.Project
+import com.beust.kobalt.api.annotation.ExportedProjectProperty
+import com.beust.kobalt.misc.JarUtils
+import com.beust.kobalt.misc.KFiles
+import com.beust.kobalt.misc.kobaltLog
+import java.io.File
+import java.util.*
+
+class Archives {
+ companion object {
+ @ExportedProjectProperty(doc = "The name of the jar file", type = "String")
+ const val JAR_NAME = "jarName"
+ @ExportedProjectProperty(doc = "The name of the a jar file with a main() method", type = "String")
+ const val JAR_NAME_WITH_MAIN_CLASS = "jarNameWithMainClass"
+
+ fun defaultArchiveName(project: Project) = project.name +
+ if (project.version.isNullOrBlank()) "" else "-${project.version}"
+
+ fun generateArchive(project: Project,
+ context: KobaltContext,
+ archiveName: String?,
+ suffix: String,
+ includedFiles: List,
+ expandJarFiles : Boolean = false,
+ manifest: java.util.jar.Manifest? = null) : File {
+ val fullArchiveName = context.variant.archiveName(project, archiveName, suffix)
+ val archiveDir = File(KFiles.libsDir(project))
+ val result = File(archiveDir.path, fullArchiveName)
+ context.logger.log(project.name, 3, "Creating $result")
+ if (! Features.USE_TIMESTAMPS || isOutdated(project.directory, includedFiles, result)) {
+ try {
+ MetaArchive(result, manifest).use { metaArchive ->
+ JarUtils.addFiles(project.directory, includedFiles, metaArchive, expandJarFiles)
+ context.logger.log(project.name, 2, "Added ${includedFiles.size} files to $result")
+ context.logger.log(project.name, 1, " Created $result")
+ }
+ } catch (e: Throwable) {
+ // make sure that incomplete archive is deleted
+ // otherwise incremental build does not work on next run
+ result.delete()
+ throw e
+ }
+
+ } else {
+ context.logger.log(project.name, 3, " $result is up to date")
+ }
+
+ return result
+ }
+
+ private fun isOutdated(directory: String, includedFiles: List, output: File): Boolean {
+ if (! output.exists()) return true
+
+ val lastModified = output.lastModified()
+ includedFiles.forEach { root ->
+ val allFiles = root.allFromFiles(directory)
+ allFiles.forEach { relFile ->
+ val file = if (relFile.isAbsolute)
+ relFile // e.g. jar file or classes folder (of another project) when building a fat jar
+ else
+ File(KFiles.joinDir(directory, root.from, relFile.path))
+ if (file.isFile) {
+ if (file.lastModified() > lastModified) {
+ kobaltLog(3, " TS - Outdated $file and $output "
+ + Date(file.lastModified()) + " " + Date(output.lastModified()))
+ return true
+ }
+ } else if (file.isDirectory) {
+ // e.g. classes folder (of another project) when building a fat jar
+ val includedFile = IncludedFile(From(""), To(""), listOf(IFileSpec.GlobSpec("**")))
+ if (isOutdated(file.absolutePath, listOf(includedFile), output))
+ return true
+ }
+ }
+ }
+ return false
+ }
+ }
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/AttributeHolder.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/AttributeHolder.kt
new file mode 100644
index 00000000..4abffb21
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/AttributeHolder.kt
@@ -0,0 +1,6 @@
+package com.beust.kobalt.archive
+
+interface AttributeHolder {
+ fun addAttribute(k: String, v: String)
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Jar.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Jar.kt
new file mode 100644
index 00000000..d5086cbd
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Jar.kt
@@ -0,0 +1,28 @@
+package com.beust.kobalt.archive
+
+import com.beust.kobalt.api.Project
+import com.beust.kobalt.api.annotation.Directive
+
+/**
+ * A jar is exactly like a zip with the addition of a manifest and an optional fatJar boolean.
+ */
+open class Jar(override val project: Project,
+ override var name : String = Archives.defaultArchiveName(project) + ".jar",
+ override var fatJar: Boolean = false) : Zip(project, name, fatJar), AttributeHolder {
+ @Directive
+ fun manifest(init: Manifest.(p: Manifest) -> Unit) : Manifest {
+ val m = Manifest(this)
+ m.init(m)
+ return m
+ }
+
+ // Need to specify the version or attributes will just be dropped
+ @Directive
+ override val attributes = arrayListOf(Pair("Manifest-Version", "1.0"))
+
+ override fun addAttribute(k: String, v: String) {
+ attributes.add(Pair(k, v))
+ }
+}
+
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Manifest.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Manifest.kt
new file mode 100644
index 00000000..1771dbe8
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Manifest.kt
@@ -0,0 +1,11 @@
+package com.beust.kobalt.archive
+
+import com.beust.kobalt.api.annotation.Directive
+
+class Manifest(val jar: AttributeHolder) {
+ @Directive
+ fun attributes(k: String, v: String) {
+ jar.addAttribute(k, v)
+ }
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/MetaArchive.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/MetaArchive.kt
new file mode 100644
index 00000000..c217c83e
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/MetaArchive.kt
@@ -0,0 +1,125 @@
+package com.beust.kobalt.archive
+
+import com.beust.kobalt.Glob
+import com.beust.kobalt.misc.KFiles
+import org.apache.commons.compress.archivers.ArchiveEntry
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
+import java.io.Closeable
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.util.jar.Manifest
+import org.apache.commons.compress.archivers.zip.ZipFile as ApacheZipFile
+
+/**
+ * Abstraction of a zip/jar/war archive that automatically manages the addition of expanded jar files.
+ * Uses ZipArchiveOutputStream for fast inclusion of expanded jar files.
+ */
+class MetaArchive(outputFile: File, val manifest: Manifest?) : Closeable {
+ companion object {
+ const val MANIFEST_MF = "META-INF/MANIFEST.MF"
+ }
+
+ private val zos= ZipArchiveOutputStream(outputFile).apply {
+ encoding = "UTF-8"
+ }
+
+ init {
+ // If no manifest was passed, create an empty one so it's the first one in the archive
+ val m = manifest ?: Manifest()
+ val manifestFile = File.createTempFile("kobalt", "tmpManifest")
+ addEntry(ZipArchiveEntry("META-INF/"), null)
+ if (manifest != null) {
+ FileOutputStream(manifestFile).use { fos ->
+ m.write(fos)
+ }
+ }
+ val entry = zos.createArchiveEntry(manifestFile, MetaArchive.MANIFEST_MF)
+ addEntry(entry, FileInputStream(manifestFile))
+ }
+
+
+ fun addFile(f: File, entryFile: File, path: String?) {
+ maybeCreateParentDirectories(f)
+ addFile2(f, entryFile, path)
+ }
+
+ private fun addFile2(f: File, entryFile: File, path: String?) {
+ val file = f.normalize()
+ FileInputStream(file).use { inputStream ->
+ val actualPath = KFiles.fixSlashes(if (path != null) path + entryFile.path else entryFile.path)
+ ZipArchiveEntry(actualPath).let { entry ->
+ maybeCreateParentDirectories(File(actualPath))
+ maybeAddEntry(entry) {
+ addEntry(entry, inputStream)
+ }
+ }
+ }
+ }
+
+ private val createdDirs = hashSetOf()
+
+ /**
+ * For an entry a/b/c/File, an entry needs to be created for each individual directory:
+ * a/
+ * a/b/
+ * a/b/c
+ * a/b/c/File
+ */
+ private fun maybeCreateParentDirectories(file: File) {
+ val toCreate = arrayListOf()
+ var current = file.parentFile
+ while (current != null && current.path != ".") {
+ if (!createdDirs.contains(current.path)) {
+ toCreate.add(0, KFiles.fixSlashes(current) + "/")
+ createdDirs.add(current.path)
+ }
+ current = current.parentFile
+ }
+ toCreate.forEach { dir ->
+ addEntry(ZipArchiveEntry(dir), null)
+ }
+ }
+
+ fun addArchive(jarFile: File) {
+ ApacheZipFile(jarFile).use { jar ->
+ val jarEntries = jar.entries
+ for (entry in jarEntries) {
+ maybeAddEntry(entry) {
+ zos.addRawArchiveEntry(entry, jar.getRawInputStream(entry))
+ }
+ }
+ }
+ }
+
+
+
+ private fun okToAdd(name: String) : Boolean {
+ val result = !KFiles.isExcluded(name,
+ Glob("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", MANIFEST_MF))
+// if (name.startsWith("META-INF")) println((if (result) "ADDING" else "NOT ADDING") + " $name")
+ return result
+ }
+
+ override fun close() = zos.close()
+
+ private fun addEntry(entry: ArchiveEntry, inputStream: FileInputStream?) {
+ zos.putArchiveEntry(entry)
+ inputStream?.use { ins ->
+ ins.copyTo(zos, 50 * 1024)
+ }
+ zos.closeArchiveEntry()
+ }
+
+ private val seen = hashSetOf()
+
+ private fun maybeAddEntry(entry: ArchiveEntry, action:() -> Unit) {
+ entry.name.let { name ->
+ if (!seen.contains(name) && okToAdd(name)) {
+ action()
+ }
+ seen.add(name)
+ }
+ }
+}
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/War.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/War.kt
new file mode 100644
index 00000000..978f21bf
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/War.kt
@@ -0,0 +1,13 @@
+package com.beust.kobalt.archive
+
+import com.beust.kobalt.api.Project
+import com.beust.kobalt.glob
+
+class War(override val project: Project, override var name: String = Archives.defaultArchiveName(project) + ".war")
+ : Jar(project, name), AttributeHolder {
+ init {
+ include(from("src/main/webapp"), to(""), glob("**"))
+ include(from("kobaltBuild/classes"), to("WEB-INF/classes"), glob("**"))
+ }
+}
+
diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Zip.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Zip.kt
new file mode 100644
index 00000000..41957218
--- /dev/null
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Zip.kt
@@ -0,0 +1,27 @@
+package com.beust.kobalt.archive
+
+import com.beust.kobalt.*
+import com.beust.kobalt.api.Project
+import com.beust.kobalt.api.annotation.Directive
+
+open class Zip(open val project: Project, open var name: String = Archives.defaultArchiveName(project) + ".zip",
+ open var fatJar: Boolean = false): AttributeHolder, IncludeFromTo() {
+ val excludes = arrayListOf()
+
+ @Directive
+ fun exclude(vararg files: String) {
+ files.forEach { excludes.add(Glob(it)) }
+ }
+
+ @Directive
+ fun exclude(vararg specs: Glob) {
+ specs.forEach { excludes.add(it) }
+ }
+
+ @Directive
+ open val attributes = arrayListOf(Pair("Manifest-Version", "1.0"))
+
+ override fun addAttribute(k: String, v: String) {
+ attributes.add(Pair(k, v))
+ }
+}
diff --git a/src/main/kotlin/com/beust/kobalt/internal/ActorUtils.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/ActorUtils.kt
similarity index 50%
rename from src/main/kotlin/com/beust/kobalt/internal/ActorUtils.kt
rename to modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/ActorUtils.kt
index 6216ecfb..e9b315a5 100644
--- a/src/main/kotlin/com/beust/kobalt/internal/ActorUtils.kt
+++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/ActorUtils.kt
@@ -1,7 +1,6 @@
package com.beust.kobalt.internal
import com.beust.kobalt.api.IProjectAffinity
-import com.beust.kobalt.api.ISimpleAffinity
import com.beust.kobalt.api.KobaltContext
import com.beust.kobalt.api.Project
@@ -11,12 +10,13 @@ class ActorUtils {
* Return the plug-in actor with the highest affinity.
*/
fun selectAffinityActor(project: Project, context: KobaltContext, actors: List