From c061e7df853a4deb795f7f1382855764e722612a Mon Sep 17 00:00:00 2001 From: Cedric Beust Date: Sat, 3 Oct 2015 21:38:15 -0700 Subject: [PATCH] First commit --- .gitignore | 10 + README.md | 3 + TODO | 43 +++ build-apt-plugin | 17 + build.gradle | 101 +++++ gradle/buildWithTravis.sh | 1 + gradle/publishing.gradle | 58 +++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 52271 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 ++++++++ gradlew.bat | 90 +++++ kobalt.iml | 42 +++ kobalt/src/Build.kt | 153 ++++++++ kobalt/wrapper/kobalt-wrapper.jar | Bin 0 -> 6070 bytes kobalt/wrapper/kobalt-wrapper.properties | 1 + kobaltw | 1 + kobaltw-windows | 3 + .../java/com/beust/kobalt/wrapper/Main.java | 260 +++++++++++++ modules/wrapper/wrapper.iml | 11 + src/main/kotlin/com/beust/kobalt/Args.kt | 25 ++ src/main/kotlin/com/beust/kobalt/Banner.kt | 25 ++ .../kotlin/com/beust/kobalt/BasePluginTask.kt | 13 + .../kotlin/com/beust/kobalt/BuildScript.kt | 40 ++ src/main/kotlin/com/beust/kobalt/FileSpec.kt | 39 ++ src/main/kotlin/com/beust/kobalt/Main.kt | 180 +++++++++ src/main/kotlin/com/beust/kobalt/Plugins.kt | 319 ++++++++++++++++ .../com/beust/kobalt/ProjectGenerator.kt | 95 +++++ src/main/kotlin/com/beust/kobalt/Template.kt | 20 + .../kotlin/com/beust/kobalt/api/BasePlugin.kt | 20 + .../kotlin/com/beust/kobalt/api/Kobalt.kt | 93 +++++ .../com/beust/kobalt/api/KobaltContext.kt | 6 + .../kotlin/com/beust/kobalt/api/Plugin.kt | 34 ++ .../kotlin/com/beust/kobalt/api/PluginTask.kt | 17 + .../kotlin/com/beust/kobalt/api/Project.kt | 96 +++++ src/main/kotlin/com/beust/kobalt/api/Task.kt | 9 + .../kobalt/api/annotation/Annotations.kt | 11 + .../com/beust/kobalt/internal/DynamicGraph.kt | 231 ++++++++++++ .../beust/kobalt/internal/GenericRunner.kt | 50 +++ .../com/beust/kobalt/internal/JUnitRunner.kt | 17 + .../kobalt/internal/JvmCompilerPlugin.kt | 120 ++++++ .../com/beust/kobalt/internal/PluginLoader.kt | 7 + .../com/beust/kobalt/internal/RunnableTask.kt | 9 + .../com/beust/kobalt/internal/TaskManager.kt | 169 +++++++++ .../com/beust/kobalt/internal/TestNgRunner.kt | 34 ++ .../com/beust/kobalt/kotlin/BuildFile.kt | 18 + .../com/beust/kobalt/kotlin/ScriptCompiler.kt | 129 +++++++ .../com/beust/kobalt/maven/ArtifactFetcher.kt | 102 +++++ .../com/beust/kobalt/maven/CompletedFuture.kt | 13 + .../com/beust/kobalt/maven/DepFactory.kt | 49 +++ .../beust/kobalt/maven/DependencyManager.kt | 55 +++ .../kotlin/com/beust/kobalt/maven/Http.kt | 81 ++++ .../kobalt/maven/IClasspathDependency.kt | 75 ++++ .../kotlin/com/beust/kobalt/maven/LocalDep.kt | 19 + .../com/beust/kobalt/maven/LocalRepo.kt | 61 +++ .../com/beust/kobalt/maven/MavenDependency.kt | 99 +++++ src/main/kotlin/com/beust/kobalt/maven/Pom.kt | 95 +++++ .../com/beust/kobalt/maven/PomGenerator.kt | 51 +++ .../com/beust/kobalt/maven/RepoFinder.kt | 145 +++++++ .../com/beust/kobalt/maven/SimpleDep.kt | 33 ++ .../com/beust/kobalt/maven/UnversionedDep.kt | 24 ++ .../com/beust/kobalt/misc/Benchmarks.kt | 8 + .../com/beust/kobalt/misc/CheckVersions.kt | 44 +++ .../kotlin/com/beust/kobalt/misc/KFiles.kt | 181 +++++++++ .../com/beust/kobalt/misc/KobaltExecutors.kt | 84 +++++ .../com/beust/kobalt/misc/KobaltLogger.kt | 48 +++ .../com/beust/kobalt/misc/MainModule.kt | 67 ++++ src/main/kotlin/com/beust/kobalt/misc/Node.kt | 26 ++ .../kotlin/com/beust/kobalt/misc/Strings.kt | 31 ++ .../kotlin/com/beust/kobalt/misc/ToString.kt | 14 + .../com/beust/kobalt/misc/Topological.kt | 44 +++ .../kotlin/com/beust/kobalt/misc/Versions.kt | 34 ++ .../com/beust/kobalt/plugin/DefaultPlugin.kt | 23 ++ .../com/beust/kobalt/plugin/apt/AptPlugin.kt | 37 ++ .../kobalt/plugin/java/JavaCompilerInfo.kt | 24 ++ .../com/beust/kobalt/plugin/java/JavaInfo.kt | 30 ++ .../beust/kobalt/plugin/java/JavaPlugin.kt | 138 +++++++ .../beust/kobalt/plugin/java/JavaProject.kt | 31 ++ .../com/beust/kobalt/plugin/java/Jvm.kt | 158 ++++++++ .../kobalt/plugin/java/OperatingSystem.kt | 304 +++++++++++++++ .../kobalt/plugin/java/SystemProperties.kt | 16 + .../kobalt/plugin/kotlin/KotlinCompiler.kt | 87 +++++ .../plugin/kotlin/KotlinCompilerInfo.kt | 22 ++ .../kobalt/plugin/kotlin/KotlinPlugin.kt | 123 ++++++ .../kobalt/plugin/kotlin/KotlinProject.kt | 32 ++ .../beust/kobalt/plugin/packaging/JarUtils.kt | 167 ++++++++ .../plugin/packaging/PackagingPlugin.kt | 356 ++++++++++++++++++ .../beust/kobalt/plugin/publish/JCenterApi.kt | 134 +++++++ .../kobalt/plugin/publish/PublishPlugin.kt | 128 +++++++ .../kobalt/wrapper/ParentLastClassLoader.kt | 56 +++ .../com/beust/kobalt/wrapper/Wrapper.kt | 177 +++++++++ .../META-INF/kobalt-plugins/kobalt.properties | 2 + src/main/resources/build-template.mustache | 42 +++ src/main/resources/kobalt.properties | 1 + .../kotlin/com/beust/kobalt/ResourceTest.kt | 21 ++ .../kotlin/com/beust/kobalt/TestModule.kt | 15 + .../beust/kobalt/internal/DynamicGraphTest.kt | 138 +++++++ .../com/beust/kobalt/maven/DependencyTest.kt | 48 +++ .../com/beust/kobalt/maven/DownloadTest.kt | 80 ++++ .../com/beust/kobalt/maven/JUnitTest.kt | 10 + .../com/beust/kobalt/maven/RemoteRepoTest.kt | 30 ++ src/test/resources/kobalt.properties | 1 + src/test/resources/testng.xml | 13 + 102 files changed, 6717 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 TODO create mode 100644 build-apt-plugin create mode 100644 build.gradle create mode 100644 gradle/buildWithTravis.sh create mode 100644 gradle/publishing.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 kobalt.iml create mode 100644 kobalt/src/Build.kt create mode 100644 kobalt/wrapper/kobalt-wrapper.jar create mode 100644 kobalt/wrapper/kobalt-wrapper.properties create mode 100644 kobaltw create mode 100644 kobaltw-windows create mode 100644 modules/wrapper/src/main/java/com/beust/kobalt/wrapper/Main.java create mode 100644 modules/wrapper/wrapper.iml create mode 100644 src/main/kotlin/com/beust/kobalt/Args.kt create mode 100644 src/main/kotlin/com/beust/kobalt/Banner.kt create mode 100644 src/main/kotlin/com/beust/kobalt/BasePluginTask.kt create mode 100644 src/main/kotlin/com/beust/kobalt/BuildScript.kt create mode 100644 src/main/kotlin/com/beust/kobalt/FileSpec.kt create mode 100644 src/main/kotlin/com/beust/kobalt/Main.kt create mode 100644 src/main/kotlin/com/beust/kobalt/Plugins.kt create mode 100644 src/main/kotlin/com/beust/kobalt/ProjectGenerator.kt create mode 100644 src/main/kotlin/com/beust/kobalt/Template.kt create mode 100644 src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt create mode 100644 src/main/kotlin/com/beust/kobalt/api/Kobalt.kt create mode 100644 src/main/kotlin/com/beust/kobalt/api/KobaltContext.kt create mode 100644 src/main/kotlin/com/beust/kobalt/api/Plugin.kt create mode 100644 src/main/kotlin/com/beust/kobalt/api/PluginTask.kt create mode 100644 src/main/kotlin/com/beust/kobalt/api/Project.kt create mode 100644 src/main/kotlin/com/beust/kobalt/api/Task.kt create mode 100644 src/main/kotlin/com/beust/kobalt/api/annotation/Annotations.kt create mode 100644 src/main/kotlin/com/beust/kobalt/internal/DynamicGraph.kt create mode 100644 src/main/kotlin/com/beust/kobalt/internal/GenericRunner.kt create mode 100644 src/main/kotlin/com/beust/kobalt/internal/JUnitRunner.kt create mode 100644 src/main/kotlin/com/beust/kobalt/internal/JvmCompilerPlugin.kt create mode 100644 src/main/kotlin/com/beust/kobalt/internal/PluginLoader.kt create mode 100644 src/main/kotlin/com/beust/kobalt/internal/RunnableTask.kt create mode 100644 src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt create mode 100644 src/main/kotlin/com/beust/kobalt/internal/TestNgRunner.kt create mode 100644 src/main/kotlin/com/beust/kobalt/kotlin/BuildFile.kt create mode 100644 src/main/kotlin/com/beust/kobalt/kotlin/ScriptCompiler.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/ArtifactFetcher.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/CompletedFuture.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/DepFactory.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/DependencyManager.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/Http.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/IClasspathDependency.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/LocalDep.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/LocalRepo.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/MavenDependency.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/Pom.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/PomGenerator.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/RepoFinder.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/SimpleDep.kt create mode 100644 src/main/kotlin/com/beust/kobalt/maven/UnversionedDep.kt create mode 100644 src/main/kotlin/com/beust/kobalt/misc/Benchmarks.kt create mode 100644 src/main/kotlin/com/beust/kobalt/misc/CheckVersions.kt create mode 100644 src/main/kotlin/com/beust/kobalt/misc/KFiles.kt create mode 100644 src/main/kotlin/com/beust/kobalt/misc/KobaltExecutors.kt create mode 100644 src/main/kotlin/com/beust/kobalt/misc/KobaltLogger.kt create mode 100644 src/main/kotlin/com/beust/kobalt/misc/MainModule.kt create mode 100644 src/main/kotlin/com/beust/kobalt/misc/Node.kt create mode 100644 src/main/kotlin/com/beust/kobalt/misc/Strings.kt create mode 100644 src/main/kotlin/com/beust/kobalt/misc/ToString.kt create mode 100644 src/main/kotlin/com/beust/kobalt/misc/Topological.kt create mode 100644 src/main/kotlin/com/beust/kobalt/misc/Versions.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/DefaultPlugin.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/apt/AptPlugin.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/java/JavaCompilerInfo.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/java/JavaInfo.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/java/JavaPlugin.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/java/JavaProject.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/java/Jvm.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/java/OperatingSystem.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/java/SystemProperties.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompilerInfo.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinPlugin.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinProject.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/packaging/JarUtils.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/publish/JCenterApi.kt create mode 100644 src/main/kotlin/com/beust/kobalt/plugin/publish/PublishPlugin.kt create mode 100644 src/main/kotlin/com/beust/kobalt/wrapper/ParentLastClassLoader.kt create mode 100644 src/main/kotlin/com/beust/kobalt/wrapper/Wrapper.kt create mode 100644 src/main/resources/META-INF/kobalt-plugins/kobalt.properties create mode 100644 src/main/resources/build-template.mustache create mode 100644 src/main/resources/kobalt.properties create mode 100644 src/test/kotlin/com/beust/kobalt/ResourceTest.kt create mode 100644 src/test/kotlin/com/beust/kobalt/TestModule.kt create mode 100644 src/test/kotlin/com/beust/kobalt/internal/DynamicGraphTest.kt create mode 100644 src/test/kotlin/com/beust/kobalt/maven/DependencyTest.kt create mode 100644 src/test/kotlin/com/beust/kobalt/maven/DownloadTest.kt create mode 100644 src/test/kotlin/com/beust/kobalt/maven/JUnitTest.kt create mode 100644 src/test/kotlin/com/beust/kobalt/maven/RemoteRepoTest.kt create mode 100644 src/test/resources/kobalt.properties create mode 100644 src/test/resources/testng.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0b23fe57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.gradle +annotations +.idea +build +buildScript +kobaltBuild +test-output +.kobalt/dist +local.properties + diff --git a/README.md b/README.md new file mode 100644 index 00000000..741005aa --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Kobalt + +Kobalt is a universal build system, please see [the web site](http://beust.com/kobalt/) for the full documentation. diff --git a/TODO b/TODO new file mode 100644 index 00000000..e3c6b046 --- /dev/null +++ b/TODO @@ -0,0 +1,43 @@ +To do: + +- --dryRun +- ProjectGenerator: support migration from pom.xml (starting with dependencies) +- Make files appear in download list automatically on bintray (undocumented API) +- logs for users should not show any timestamp, class file or thread id, this should only be in --dev mode +- Project ordering: kotlinProject(wrapper) {} +- Fetch .pom with DynamicGraph +- Centralize all the executors +- Compile TestNG (including generating Version.java and OSGi headers) +- Storage API: Plugins.storage("myplugin").get/set() +- Support additional .kt files in ~/.kobalt/src +- generateArchive() should use Path instead of File +- uploadMaven + + Create sources.jar + + Create javadoc.jar +- Bug: --tasks displays multiple tasks when there are multiple projects +- Replace File with java.nio.Files and Path +- Create a wiki page for plugins +- Make kobaltw executable in the zip file + +Done: + +- --checkVersions: displays which plugins have a newer version than the one specified in the build +- Make it possible to target jar for individual projects: ./kobaltw kobalt:uploadJcenter +- --buildFile doesn't use the local .kobalt directory +- Better plugin() parsing +- kobalt-wrapper.jar contains too much stuff, should be just com.beust.kobalt.wrapper.* + kotlin runtime or maybe +just a straight Java class with minimal dependencies for fast start up +- Add repos from the build file +- Support tasks added in the build file +- Replace "removePrefixes" with an overloaded include(prefix, file) +- --init: Don't overwrite existing file +- Handle snapshots/metadata: https://repository.jboss.org/nexus/content/repositories/root_repository//commons-lang/commons-lang/2.7-SNAPSHOT/commons-lang-2.7-SNAPSHOT.jar +- JUnit +- Compiler nowarn section +- Parse plugins and repos in build files +- Stop always recompiling preBuildScript.jar +- Upload non maven files +- Jar packaging: include the jar in the jar (not supported by JarFile) +- Encapsulate BuildFile for better log messages + + diff --git a/build-apt-plugin b/build-apt-plugin new file mode 100644 index 00000000..5e68356a --- /dev/null +++ b/build-apt-plugin @@ -0,0 +1,17 @@ +jar=$HOME/t/kobalt-apt-0.3.jar +rm -f $jar +cd $HOME/kotlin/kobalt +jar cf $jar -C build/classes/main com/beust/kobalt/plugin/apt +jar uf $jar -C src/main/resources . + +rm -rf /tmp/META-INF +mkdir -p /tmp/META-INF +echo > /tmp/META-INF/MANIFEST.MF Manifest-Version: 1.0 +echo >>/tmp/META-INF/MANIFEST.MF "Created-By: 1.8.0_25 (Oracle Corporation)" +echo >>/tmp/META-INF/MANIFEST.MF Kobalt-Plugin-Class: com.beust.kobalt.plugin.apt.AptPlugin + +(cd /tmp && jar uf $jar META-INF) + + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..c21d918f --- /dev/null +++ b/build.gradle @@ -0,0 +1,101 @@ +buildscript { + ext.kotlin_version = '0.14.449' + + repositories { + mavenCentral() + jcenter() + maven { + url 'http://oss.sonatype.org/content/repositories/snapshots' + } + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}" + classpath "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" + } +} + +plugins { + id "com.jfrog.bintray" version "1.2" +} + +version = '0.121' + +//apply plugin: 'java' +apply plugin: 'kotlin' +//apply plugin: 'application' +apply plugin: 'com.jfrog.bintray' + +apply from: 'gradle/publishing.gradle' + +repositories { + mavenCentral() + jcenter() + maven { + url 'http://oss.sonatype.org/content/repositories/snapshots' + } +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}", + "org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlin_version}", +// "org.jetbrains.kotlin:kotlin-compiler:${kotlin_version}", + + 'com.beust:jcommander:1.48', + 'com.beust:klaxon:0.16', + 'com.squareup.okhttp:okhttp:2.4.0', + 'org.slf4j:slf4j-api:1.7.12', + 'org.slf4j:slf4j-simple:1.7.12', + 'ch.qos.logback:logback-classic:1.1.2', + 'org.jsoup:jsoup:1.8.2', + 'com.google.inject:guice:4.0', + 'com.google.inject.extensions:guice-assistedinject:4.0', + 'com.google.guava:guava:18.0', + 'org.apache.maven:maven-model:3.3.3', + 'com.github.spullara.mustache.java:compiler:0.8.18' + +// compile files("/Users/beust/.kobalt/repository/com/beust/kobalt-example-plugin/build/libs/kobalt-example-plugin.jar") + testCompile 'org.testng:testng:6.9.4' + compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" +// 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 +} +sourceSets { + main.java.srcDirs += 'src/main/kotlin' + test.java.srcDirs += 'src/test/kotlin' +} + +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" + } + + from { + configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } + } +} + diff --git a/gradle/buildWithTravis.sh b/gradle/buildWithTravis.sh new file mode 100644 index 00000000..9f0ddf10 --- /dev/null +++ b/gradle/buildWithTravis.sh @@ -0,0 +1 @@ +../gradlew check diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle new file mode 100644 index 00000000..f33106a5 --- /dev/null +++ b/gradle/publishing.gradle @@ -0,0 +1,58 @@ +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() +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 new file mode 100644 index 0000000000000000000000000000000000000000..30d399d8d2bf522ff5de94bf434a7cc43a9a74b5 GIT binary patch literal 52271 zcmafaW0a=B^559DjdyI@wy|T|wr$(CJv+9!W822gY&N+!|K#4>Bz;ajPk*RBjZ;RV75EK*;p4^!@(BB5~-#>pF^k0$_Qx&35mhPenc zNjoahrs}{XFFPtR8Xs)MInR7>x_1Kpw+a8w@n0(g``fp7GXFmo^}qAL{*%Yt$3(FfIbReeZ6|xbrftHf0>dl5l+$$VLbG+m|;Uk##see6$CK4I^ ziDe}0)5eiLr!R5hk6u9aKT36^C>3`nJ0l07RQ1h438axccsJk z{kKyd*$G`m`zrtre~(!7|FcIGPiGfXTSX`PzlY^wY3ls9=iw>j>SAGP=VEDW=wk2m zk3%R`v9(7LLh{1^gpVy8R2tN#ZmfE#9!J?P7~nw1MnW^mRmsT;*cyVG*SVY6CqC3a zMccC8L%tQqGz+E@0i)gy&0g_7PV@3~zaE~h-2zQ|SdqjALBoQBT2pPYH^#-Hv8!mV z-r%F^bXb!hjQwm2^oEuNkVelqJLf029>h5N1XzEvYb=HA`@uO_*rgQZG`tKgMrKh~aq~ z6oX{k?;tz&tW3rPe+`Q8F5(m5dJHyv`VX0of2nf;*UaVsiMR!)TjB`jnN2)6z~3CK@xZ_0x>|31=5G$w!HcYiYRDdK3mtO1GgiFavDsn&1zs zF|lz}sx*wA(IJoVYnkC+jmhbirgPO_Y1{luB>!3Jr2eOB{X?e2Vh8>z7F^h$>GKmb z?mzET;(r({HD^;NNqbvUS$lhHSBHOWI#xwT0Y?b!TRic{ z>a%hUpta3P2TbRe_O;s5@KjZ#Dijg4f=MWJ9euZnmd$UCUNS4I#WDUT2{yhVWt#Ee z?upJB_de&7>FHYm0Y4DU!Kxso=?RabJ*qsZ2r4K8J#pQ)NF?zFqW#XG1fX6dFC}qh z3%NlVXc@Re3vkXi*-&m)~SYS?OA8J?ygD3?N}Pq zrt_G*8B7^(uS7$OrAFL5LvQdQE2o40(6v`se%21Njk4FoLV-L0BN%%w40%k6Z1ydO zb@T(MiW@?G-j^j5Ypl@!r`Vw&lkJtR3B#%N~=C z@>#A{z8xFL=2)?mzv;5#+HAFR7$3BMS-F=U<&^217zGkGFFvNktqX z3z79GH^!htJe$D-`^(+kG*);7qocnfnPr^ieTpx&P;Z$+{aC8@h<0DDPkVx`_J~J> zdvwQxbiM1B{J6_V?~PNusoB5B88S%q#$F@Fxs4&l==UW@>9w2iU?9qMOgQWCl@7C* zsbi$wiEQEnaum!v49B_|^IjgM-TqMW!vBhhvP?oB!Ll4o-j?u3JLLFHM4ZVfl9Y_L zAjz@_3X5r=uaf|nFreX#gCtWU44~pA!yjZNXiZkoHhE$l@=ZTuxcLh53KdMOfanVe zPEX(#8GM7#%2*2}5rrdBk8p#FmzpIC>%1I9!2nRakS|^I*QHbG_^4<=p)(YOKvsTp zE#DzUI>Y&g)4mMaU6Bhrm8rSC{F_4J9sJlF0S5y5_=^l!{?W_n&SPj&7!dEvLzNIRMZBYyYU@Qftts7Zr7r>W- zqqk46|LEF|&6bn#CE~yMbiF&vEoLUA(}WzwmXH_=<~|I(9~{AE$ireF7~XBqPV2)* zcqjOCdi&>tUEuq31s(|TFqx>Wuo(ooWO(sd!W~Hu@AXg=iQgq^O3Lv9xH$vx*vrgDAirQqs9_DLS1e45HcUPdEMziO?Mm1v!)n93L%REy=7 zUxcX!jo!vyl_l0)O(Y~OT``;8mB(tcf}`Rh^weqPnDVDe-ngsZ~C z`onh0WLdaShAAb-3b{hT5ej9a$POQ9;RlPy}IYzKyv+8-HzB7fV!6X@a_T61qZ zWqb&&ip*@{;D-1vR3F2Q&}%Q>TFH&2n?2w8u8g=Y{!|;>P%<@AlshvM;?r7I)yXG% z^IpXZ(~)V*j^~sOG#cWCa+b8LC1IgqFx+Mq$I`6VYGE#AUajA9^$u-{0X#4h49a77 zH>d>h3P@u!{7h2>1j+*KYSNrKE-Q(z`C;n9N>mfdrlWo$!dB35;G4eTWA}(aUj&mNyi-N+lcYGpA zt1<~&u`$tIurZ2-%Tzb1>mb(~B8;f^0?FoPVdJ`NCAOE~hjEPS) z&r7EY4JrG~azq$9$V*bhKxeC;tbBnMds48pDuRy=pHoP*GfkO(UI;rT;Lg9ZH;JU~ zO6gTCRuyEbZ97jQyV7hM!Nfwr=jKjYsR;u8o(`(;qJ(MVo(yA<3kJximtAJjOqT=3 z8Bv-^`)t{h)WUo&t3alsZRJXGPOk&eYf}k2JO!7Au8>cvdJ3wkFE3*WP!m_glB-Rt z!uB>HV9WGcR#2n(rm=s}ulY7tXn5hC#UrNob)-1gzn-KH8T?GEs+JBEU!~9Vg*f6x z_^m1N20Do}>UIURE4srAMM6fAdzygdCLwHe$>CsoWE;S2x@C=1PRwT438P@Vt(Nk` zF~yz7O0RCS!%hMmUSsKwK$)ZtC#wO|L4GjyC?|vzagOP#7;W3*;;k?pc!CA=_U8>% z%G^&5MtFhvKq}RcAl))WF8I#w$So?>+_VEdDm_2=l^K320w~Bn2}p+4zEOt#OjZ6b zxEYoTYzvs$%+ZYwj;mZ@fF42F1-Hb<&72{1J)(D~VyVpo4!dq259t-_Oo3Yg7*R`N zUg!js4NRyfMbS*NLEF}rGrlXz0lHz))&&+B#Tdo@wlh-Q8wr7~9)$;s9+yJH0|m=F zSD9mUW>@HLt}mhAApYrhdviKhW`BfNU3bPSz=hD+!q`t*IhG+Z4XK;_e#AkF5 z&(W7iUWF4PNQ+N!-b-^3B$J4KeA1}&ta@HK=o2khx!I&g#2Y&SWo-;|KXDw!Xb)mP z$`WzPA!F(h*E=QP4;hu7@8J&T|ZPQ2H({7Vau6&g;mer3q?1K!!^`|0ld26 zq|J&h7L-!zn!GnYhjp`c7rG>kd1Y%8yJE9M0-KtN=)8mXh45d&i*bEmm%(4~f&}q@ z1uq)^@SQ~L?aVCAU7ZYFEbZ<730{&m?Un?Q!pxI7DwA^*?HloDysHW{L!JY!oQ8WMK(vT z@fFakL6Ijo$S$GH;cfXcoNvwVc8R7bQnOX2N1s$2fbX@qzTv>748In?JUSk@41;-8 zBw`fUVf$Jxguy{m1t_Z&Q6N$Ww*L9e%6V*r3Yp8&jVpxyM+W?l0km=pwm21ch9}+q z$Z&eb9BARV1?HVgjAzhy);(y1l6)+YZ3+u%f@Y3stu5sSYjQl;3DsM719wz98y4uClWqeD>l(n@ce)pal~-24U~{wq!1Z_ z2`t+)Hjy@nlMYnUu@C`_kopLb7Qqp+6~P=36$O!d2oW=46CGG54Md`6LV3lnTwrBs z!PN}$Kd}EQs!G22mdAfFHuhft!}y;8%)h&@l7@DF0|oy?FR|*E&Zuf=e{8c&hTNu# z6{V#^p+GD@A_CBDV5sM%OA*NwX@k1t?2|)HIBeKk(9!eX#J>jN;)XQ%xq^qVe$I}& z{{cL^a}>@*ZD$Ve)sJVYC!nrAHpV~JiCH3b7AQfAsEfzB$?RgU%+x7jQ_5XQ8Gf*N`i<1mZE zg6*_1dR3B`$&9CxHzk{&&Hf1EHD*JJF2glyBR+hBPnwP@PurN`F80!5{J57z;=kAc za65ouFAve7QEOmfcKg*~HZ04-Ze%9f)9pgrVMf7jcVvOdS{rf+MOsayTFPT}3}YuH z$`%^f$}lBC8IGAma+=j9ruB&42ynhH!5)$xu`tu7idwGOr&t=)a=Y2Sib&Di`^u9X zHQ=liR@by^O`ph|A~{#yG3hHXkO>V|(%=lUmf3vnJa#c%Hc>UNDJZRJ91k%?wnCnF zLJzR5MXCp)Vwu3Ew{OKUb?PFEl6kBOqCd&Qa4q=QDD-N$;F36Z_%SG}6{h2GX6*57 zRQIbqtpQeEIc4v{OI+qzMg_lH=!~Ow%Xx9U+%r9jhMU=7$;L7yJt)q+CF#lHydiPP zQSD=AtDqdsr4G!m%%IauT@{MQs+n7zk)^q5!VQrp?mFajX%NQT#yG9%PTFP>QNtfTM%6+b^n%O`Bk74Ih| zb>Fh1ic{a<8g<{oJzd|@J)fVVqs&^DGPR-*mj?!Z?nr<f)C8^oI(N4feAst}o?y z-9Ne339xN7Lt|Tc50a48C*{21Ii$0a-fzG1KNwDxfO9wkvVTRuAaF41CyVgT?b46; zQvjU!6L0pZM%DH&;`u`!x+!;LaPBfT8{<_OsEC5>>MoJQ5L+#3cmoiH9=67gZa;rvlDJ7_(CYt3KSR$Q#UR*+0hyk z>Dkd2R$q~_^IL2^LtY|xNZR(XzMZJ_IFVeNSsy;CeEVH|xuS#>itf+~;XXYSZ9t%1moPWayiX=iA z!aU~)WgV!vNTU=N;SpQ((yz#I1R#rZ&q!XD=wdlJk4L&BRcq(>6asB_j$7NKLR%v; z9SSp$oL7O|kne`e@>Bdf7!sJ*MqAtBlyt9;OP3UU1O=u6eGnFWKT%2?VHlR86@ugy z>K)(@ICcok6NTTr-Jh7rk=3jr9`ao!tjF;r~GXtH~_&Wb9J^ zd%FYu_4^3_v&odTH~%mHE;RYmeo+x^tUrB>x}Is&K{f+57e-7Y%$|uN%mf;l5Za95 zvojcY`uSCH~kno zs4pMlci*Y>O_pcxZY#?gt1^b-;f(1l9}Ov7ZpHtxfbVMHbX;579A>16C&H5Q>pVpH5LLr<_=!7ZfX23b1L4^WhtD?5WG;^zM}T>FUHRJv zK~xq88?P);SX-DS*1LmYUkC?LNwPRXLYNoh0Qwj@mw9OP&u{w=bKPQ)_F0-ptGcL0 zhPPLKIbHq|SZ`@1@P5=G^_@i+U2QOp@MX#G9OI20NzJm60^OE;^n?A8CH+XMS&3ek zP#E7Y==p;4UucIV{^B`LaH~>g6WqcfeuB#1&=l!@L=UMoQ0$U*q|y(}M(Y&P$Xs&| zJ&|dUymE?`x$DBj27PcDTJJn0`H8>7EPTV(nLEIsO&9Cw1Dc&3(&XFt9FTc{-_(F+ z-}h1wWjyG5(ihWu_3qwi; zAccCjB3fJjK`p=0VQo!nPkr0fT|FG;gbH}|1p`U>guv9M8g2phJBkPC`}ISoje6+? zvX|r5a%Y-@WjDM1&-dIH2XM}4{{d&zAVJQEG9HB8FjX&+h*H=wK=xOgNh8WgwBxW+ z0=^CzC4|O_GM>^_%C!!2jd&x*n2--yT>PZJ`Mok6Vf4YFqYp@a%)W}F4^DpKh`Cr7 z{>Z7xw-4UfT@##s#6h%@4^s^7~$}p2$v^iR5uJljApd9%#>QuxvX+CSZv18MPeXPCizQ*bm);q zWhnVEeM}dlCQP*^8;Q7OM|SSgP+J;DQy|bBhuFwJ2y*^|dBwz96-H;~RNsc}#i= zwu`Tp4$bwRVb7dxGr_e1+bJEc=mxLxN_f>hwb#^|hNdewcYdqXPrOxDE;|mP#H|a% z{u8#Vn}zVP(yJ}+-dx;!8<1in=Q8KsU%Q5CFV%5mGi8L;)*m%Vs0+S`ZY(z7aZ$VCjp?{r>C<9@$zVN;LVhxzPEdDPdb8g<)pckA z?mG@Ri>ode(r|hjNwV#*{!B^l2KO@4A+!X;#PW#?v2U!ydYIFHiXC3>i2k7{VTfji>h z8-(^;x!>f)Qh$mlD-z^1Nxu})XPbN=AUsb%qhmTKjd=1BjKr(L9gb1w4Y8p+duWfS zU>%C>*lCR@+(ku!(>_SA6=4CeM|$k4-zv|3!wHy+H&Oc$SHr%QM(IaBS@#s}O?R7j ztiQ>j^{X)jmTPq-%fFDxtm%p|^*M;>yA;3WM(rLV_PiB~#Eaicp!*NztJNH;q5BW$ zqqlfSq@C0A7@#?oRbzrZTNgP1*TWt(1qHii6cp5U@n|vsFxJ|AG5;)3qdrM4JElmN z+$u4wOW7(>$mMVRVJHsR8roIe8Vif+ml3~-?mpRos62r0k#YjdjmK;rHd{;QxB?JV zyoIBkfqYBZ!LZDdOZArQlgXUGmbpe7B-y7MftT;>%aM1fy3?^CuC{al$2-tfcA?d) z<=t7}BWsxH3ElE^?E&|f{ODX&bs+Ax>axcdY5oQ`8hT)YfF%_1-|p*a9$R~C=-sT| zRA~-Q$_9|G(Pf9I+y!zc>fu)&JACoq&;PMB^E;gIj6WeU=I!+scfSr}I%oD1fh+AQ zB^Q^b@ti5`bhx+(5XG5*+##vV>30UCR>QLYxHYY~k!AR`O6O_a3&wuW61eyHaq;HL zqy@?I*fmB)XY;Z@RH^IR|6m1nwWv>PDONtZV-{3@RkM_JcroRNLTM9?=CI}l%p86A zdxv|{zFWNI;L8K9hFSxD+`-pwvnyS|O?{H-rg6dPH<3oXgF0vU5;~yXtBUXd>lDs~ zX!y3-Pr9l;1Q^Z<15_k1kg|fR%aJKzwkIyED%CdxoXql=^QB;^*=2nVfi{w?0c@Dj z_MQEYjDpf^`%)$|4h>XnnKw05e5p4Jy69{uJ5p|PzY+S?FF~KWAd0$W<`;?=M+^d zhH&>)@D9v1JH2DP?tsjABL+OLE2@IB)sa@R!iKTz4AHYhMiArm)d-*zitT+1e4=B( zUpObeG_s*FMg$#?Kn4%GKd{(2HnXx*@phT7rEV?dhE>LGR3!C9!M>3DgjkVR>W)p3 zCD0L3Ex5-#aJQS6lJXP9_VsQaki5#jx}+mM1`#(C8ga~rPL{2Z;^^b+0{X)_618Sw z0y6LTkk;)quIAYpPY{)fHJLk?)(vxt?roO24{C!ck}A)_$gGS>g!V^@`F#wg+%Cok zzt6hJE|ESs@S^oHMp3H?3SzqBh4AN(5SGi#(HCarl^(Jli#(%PaSP9sPJ-9plwZv{ z1lkTGk4UAXYP^>V+4;nQ4A~n-<+1N)1lPzXIbG{Q;e3~T_=Trak{WyjW+n!zhT*%)q?gx zTl4(Gf6Y|ALS!H$8O?=}AlN=^3yZCTX@)9g5b_fif_E{lWS~0t`KpH8kkSnWWz+G1 zjFrz}gTnQ2k-`oag*031Nj7=MZfP}gvrNvv_crWzf9Cdzv^LyBeEyF2#hGg8_C8jW)NCAhsm2W_P21DeX7x$4EDD){~vBiLoby=d+&(;_f(?PMfamC zI_z%>Nq-rC%#z#1UC49j4@m63@_7LWD$ze=1%GPh`%@PB7yGH6Zh=1#L%&%hU7z%Y zs!IN(ef@!+|1YR28@#kw^XR= zxB$*nNZm7Y@L0&IlmoN}kEI?dBee+z+!MWCy+e4P4MYpOgr}2Q(wnR1ZiA>5_P*Cg zB4BMlcx?(v*+V3O+p~Buk;wIN6v!Ut?gYpl+KFu~elf}{E4`9+lcR0k$bC>+I zWxO5jD8sYPbMS)4c3i2UojI4T7uzE*Zz;POw{0d0`*iHJ%(Pb=sa^pV{t_JtHoPeC zX+t_k*=D%+Sv#+5CeoRfI)G`T90~AE@K9RaFR%8*w#*x9>H$ahFd>PUg_zP`VVPSR zr#Rb;I--8Rq;eTBju;dx2cmZ9Al>aiDY z#7(4S(A#aRvl7jm78sQ+O^S5eUS8|W%5@Pt9fm?J=r`~=l-gdv(LB~C-Gi#srwEDQ z4cCvA*XiRj9VDR6Ccy2k(Nvxic;~%YrfNeWl$cJpa%WO_4k?wxKZ{&`V#!&#jV@x+ z7!!YxOskc;cAF~`&aRWp8E)fnELtvb3-eHkeBPb~lR&iH=lZd^ZB(T6jDg5PnkJQFu9? z+24ww5L%opvEkE$LUHkZDd0ljo!W}0clObhAz`cPFx2)X3Sk91#yLL}N6AE0_O`l| z7ZhaKuAi7$?8uuZAFL(G0x3wE<-~^neGm=*HgJa(((J;yQI$NB)J;i0?vr`M1v+R? zd+{rD^zK}0Gi!2lXo0P+jVQ$HNYn^sRMONYVZPPT@enUb1pHHYgZMo5GN~SIz*;gv z1H<4(%53!6$4+VX_@Kp!>A9wwo{(KdWx)ja>x3&4=H(Urbn?0Vh}W3%ly5SgJ<+X5?N7-B=byoKyICr>3 zIFXe;chMk7-cak~YKL8Bf>VbZbX{5L9ygP_XS?oByNL*zmp8&n9{D42I^=W=TTM4X zwb_0axNK?kQ;)QUg?4FvxxV7L@sndJL0O12M6TMorI&cAL%Q464id6?Tbd_H!;=SRW9w2M*wc00yKVFslv|WN( zY7=Yikt+VY@DpzKq7@z_bVqr7D5B3xRbMrU5IO7;~w2nNyP7J_Gp>>7z?3!#uT4%-~h6)Ee1H z&^g}vZ{g}DIs@FDzE$QG_smSuEyso@I#ID3-kkYXR=nYuaa0{%;$WzZC@j)MDi+jC z!8KC;1mGCHGKr>dR;3;eDyp^0%DH`1?c7JcsCx$=m(cs^4G& zl@Fi8z|>J`^Z-faK{mhsK|;m%9?luacM+~uhN@<20dfp4ZN@qsi%gM67zZ`OHw=PE zr95O@U(HheB7OBYtyF=*Z5V&m?WDvIQ`edwpnT?bV`boB z!wPf&-@7 z0SoTB^Cy>rDHm%^b0cv@xBO%02~^=M79S}TG8cbVhj72!yN_87}iA1;J$_xTb+Zi@76a{<{OP0h&*Yx`U+mkA#x3YQ} zPmJsUz}U0r?foPOWd5JFI_hs_%wHNa_@)?(QJXg>@=W_S23#0{chEio`80k%1S?FWp1U;4#$xlI-5%PEzJcm zxjp$&(9f2xEx!&CyZZw|PGx&4$gQbVM|<2J&H7rpu;@Mc$YmF9sz}-k0QZ!YT$DUw z_I=P(NWFl!G-}aofV?5egW%oyhhdVp^TZH%Q4 zA2gia^vW{}T19^8q9&jtsgGO4R70}XzC-x?W0dBo+P+J8ik=6}CdPUq-VxQ#u4JVJ zo7bigUNyEcjG432-Epy)Rp_WDgwjoYP%W|&U~Gq-r`XK=jsnWGmXW6F}c7eg;$PHh>KZ@{cbTI<`ZP>s(M@zy=aHMA2nb(L0COlVcl8UXK+6`@Di+Wai;lJf^7s6V%NkKcad zDYY%2utqcw#CJFT9*V9U_{DyP&VYb)(6y`Z%Rq& z!PTtuI#psBgLPoNu{xvs^y26`oY;p!fE=bJW!cP^T>bUE*UKBV5Bd%!U{Q5{bKwN> zv)pn@Oc{6RyIS>!@Yvkv+hVLe+bmQ6fY2L}tT)Vbewg8`A`PFYyP+@QmL?b{RED;; zR6fwAAD}Ogejah(58bv{VG&WJhll7X-hjO9dK`8m5uFvthD1+FkJtT_>*{yKA(lXx zKucHMz#F_G)yTJw!)I3XQ7^9ydSlr9D)z?e*jKYE?xTKjR|ci30McU^4unzPsHGKN zMqwGd{W_1_jBQ_oeU^4!Ih}*#AKF%7txXZ0GD}Jzcf+i*?WLAe6#R_R-bSr17K%If z8O2SwYwMviXiJ?+$% zse=E~rK*PH@1Md4PFP)t(NhV%L3$657FUMap?fugnm3|N z79w3|qE%QyqZB}2WG&yc>iOaweUb`5o5p9PgyjqdU*sXP=pi$-1$9fGXYgS2?grS6 zwo#J~)tUTa0tmGNk!bg*Pss&uthJDJ$n)EgE>GAWRGOXeygh;f@HGAi4f){s40n?k z=6IO?H1_Z9XGzBIYESSEPCJQrmru?=DG_47*>STd@5s;1Y|r*+(7s4|t+RHvH<2!K z%leY$lIA{>PD_0bptxA`NZx-L!v}T4JecK#92kr*swa}@IVsyk{x(S}eI)5X+uhpS z8x~2mNLf$>ZCBxqUo(>~Yy4Z3LMYahA0S6NW;rB%)9Q z8@37&h7T$v2%L|&#dkP}N$&Jn*Eqv81Y*#vDw~2rM7*&nWf&wHeAwyfdRd%`>ykby zC*W9p2UbiX>R^-!H-ubrR;5Z}og8xx!%)^&CMl(*!F%or1y&({bg?6((#og-6Hey&3th3S%!n3N|Z2ZCZHJxvQ9rt zv|N#i*1=qehIz_=n*TWC6x-ab)fGr8cu!oYV+N)}3M;H4%$jwO>L!e53sxmJC~;O; zhJw|^&=2p!b8uk{-M|Z*J9n0{(8^>P+Y7vlFLc8#weQMg2iB8MFCe-*^BJV6uVWjg zWZe{-t0f67J<|IIn4{wsKlG*Amy{-yOWMMW)g}rh>uEE;jbkS-om>uAjeTzCg51683UTmY4+yT zW!qe`?~F{~1Y>mPJ9M0hNRBW$%ZwOA-NdIeaE6_K z>y8D3tAD7{3FouIXX9_MbY;zq%Ce0}VmT;aO~=*Mk4mflb_i4CApxEtZ^TDNoOzy_ z-eIE(&n1Vz*j&(BjO*fVvSCozTJU4?tWC8m4=d|D{WV0k+0M2!F1=T}z7V4-JA*y( z!;H(sOBmg=%7p&LLf%z%>VgtdN6jl2y95aXY}v9U;m~YWx{2#lwLpEJWGgs`sE*15 zvK`DtH-Q^ix>9@qVG+d*-C{lYPBbts1|%3!CkLP1t4iz%LO-di4lY%{8>jd{turVrD*_lLv!ShQC~S#SXjCO?##c zh2aZKVAHDf1sQpZiH^C7NRu?44JuEp?%W4-?d;Dg z;`gKA9$oC{WlQuT?fex!ci3GJhU;1J!YLHbyh8B-jsZ~pl59LGannKg9}1qxlbOOq zaJhTl zEJ`2Xd_ffdK^EE1v>8kUZG`eMXw(9S+?Lxx#yTUo?WdV}5kjC|glSJqX zv8RO|m#Ed@hW=};Yfl&2_@11Xm}pz0*SRx%OH_NODo@>e$cMAv(0u`~Yo|qbQ~mzA zMKt^U+GIXKH^xuD9n}NfU|?ZTOSS>XJwlg`lYHgea)!ZR?m^=oj+qyKBd6SJvPZk* zwc-2$b%%V~k$5{=(rG!OcR{;u2V3um|C+oT5F?rt`CER|iU9-!_|GxMe^!f$d6*iz z{?~JnR84mS+!gFUxugG?g9uGFI(?Q0SADS8=n=#aCK^`6@rm4r=LJTBm;)cY zm_6c5!ni$SWFOuj36eKau>6=kl_p=-7>VL_fJuJZI}0=3kASf|t;B~;Mt(vuhCU+c zKCF@SJ5#1>8YLfe{pf?sH*v6C)rOvO1~%@+wN}#>dkcrLw8U@xAySc{UeaP?7^AQ5 zmThfw^(i@*GMlM!xf+dzhRtbo8#;6Ql_s$t15q%*KeCm3`JrXnU*T^hV-aGX)bmxF z;O%jGc{6G+$gZ$YvOM2bZ!?>X<^-D zbT+YCx722}NY88YhKnw?yjF1#vo1v+pjId;cdyT*SH@Bc>6(GV*IBkddKx%b?y!r6 z=?0sTwf`I_Jcm(J8D~X@ESiO`X&i53!9}5l}PXzSYf9 zd&=h`{8BP-R?E*Nk$yzSSFhz2uVerdhbcCWF{S7reTkzXB;U@{9`hvC0AscwoqqU( zKQavt5OPm9y1UpKL%O(SWSSX=eo2rky_8jJ-ew7>iw~T=Xrt3EEzc!slebwG)FrE> z>ASkjJk%#@%SFWs-X4)?TzbBtDuwF#;WVw}?(K`UYqm`3vKbFKuqQ8uL2Y5}%T0y5 zia#E?tyZgnuk$LD^ihIn(i~|1qs(%NpH844QX-2S5E)E7lSM=V56o>5vLB^7??Vy_ zgEIztL|85kDrYF(VUnJ$^5hA;|41_6k-zO#<7gdprPj;eY_Et)Wexf!udXbBkCUA)>vi1E!r2P_NTw6Vl6)%M!WiK+jLRKEoHMR zinUK!i4qkppano|OyK(5p(Dv3DW`<#wQVfDMXH~H(jJdP47Y~`% z#ue|pQaVSv^h#bToy|pL!rWz8FQ53tnbEQ5j#7op?#c#(tj@SM2X*uH!;v8KtS5Fo zW_HE8)jSL zYO}ii#_KujRL4G*5peU)-lDW0%E}!YwL#IKUX_1l9ijy~GTFhO?W^=vEBe?m+tvBe zLaGWcoKg==%dO#6R}`U0>M)2+{b*~uamlaUNN<_NVZTGY4-(ORqK6|HvKFMKwp6^L zR+MC^`6^|^=u^Do;wy8mUp^Oct9~=vQ74vfO-m&Q0#~-mkqkpw&dMkVJ(So<)tf3h z46~mW_3T@Mzh<2XZYO7@F4j|BbhhXjs*hayIjTKyGoYO}`jEFn^!4Y! zL30ubp4U(r>Nx&RhaJkGXuRe%%f%D;1-Zdw2-9^Mq{rP-ZNLMpi~m+v?L=sPSAGcc z{j+Y!3CVrm);@{ z;T?sp1|%lk1Q&`&bz+#6#NFT*?Zv3k!hEnMBRfN47vcpR20yJAYT(5MQ@k;5Xv@+J zLjFd{X_il?74aOAMr~6XUh7sT4^yyLl%D89Io`m5=qK_pimk+af+T^EF>Y)Z{^#b# zt%%Bj9>JW!1Zx_1exoU~obfxHy6mBA{V6E)12gLp-3=21=O82wENQ}H@{=SO89z&c*S8Veq8`a3l@EQO zqaNR8IItz4^}>9d+Oj%YUQlb;;*C0!iC&8gaiDJ)bqg(92<>RbXiqFI3t#jqI%3Y( zPop=j=AyLA?pMYaqp0eHbDViOWV-5IUVwx+Fl6M54*?i+MadJHIRjiQoUe?v-1XdQ z5S305nVbg|sy~qPr2C6}q!v)8E%$i~p5_jGPA0%3*F%>XW6g)@4-z73pVcvWs$J2m zpLeW4!!31%k#VUG76V__S**9oC{-&P6=^fGM$2q<+1eC}Fa2EB3^s{ru^hI}e^KPM zMyj;bLtsRex^QMcgF)1U0biJ|ATXX`YuhzWMwP73e0U?P=>L|R?+13$8(PB23(4Js zy@KS0vvS~rk*^07Bd4}^gpc|e5%248Mei_y^mrD;zUYniPazU>1Dun%bVQ0T7DNXr zMq4Y09V_Dr1OQ$ni)BSyXJZ+D7 zXHh02bToWd;4AlF-G`mk23kD=$9B)}*I@kF9$WcOHc%d6BdemN(!^z0B3rvR>NPQ? z+vv#Qa~Ht|BiTdcN;g6;eb6!Jso)MFD3{sf{T;!fM^OwcEtoJI#ta?+R>|R;Ty2E% zjF8@wgWC=}Kkv52c@8Psigo4#G#E?T(;i}rq+t}E(I(gAekZX;HbTR5ukI>8n5}oC zXXTcy>tC{sG$yFf?bIqBAK3C^X3OAY^Too{qI_uZga0cK4Z$g?Zu$#Eg|UEusQ)t% z{l}Zjf5OrK?wkKJ?X3yvfi{Nz4Jp5|WTnOlT{4sc3cH*z8xY(06G;n&C;_R!EYP+m z2jl$iTz%_W=^)Lhd_8hWvN4&HPyPTchm-PGl-v~>rM$b>?aX;E&%3$1EB7{?uznxn z%yp0FSFh(SyaNB@T`|yVbS!n-K0P|_9dl=oE`7b?oisW)if(`g73bkt^_NHNR_|XU z=g?00`gZRHZm+0B(KvZ0?&(n<#j!sFvr|;G2;8qWg3u%P;M1+UL!9nj)q!}cd}jxK zdw=K$?NuLj?2#YzTCEw1SfLr#3`3x(MB2F(j!6BMK!{jXF%qs;!bIFpar}^=OYmYm z86RJ9cZl5SuR6emPB>yrO)xg5>VucBcrV3UxTgZcUu(pYr+Sa=vl>4ql{NQy4-T%M zlCPf>t}rpgAS15uevdwJR_*5_H?USp=RR?a>$gSk-+w;VuIhukt9186ppP=Lzy1L7 ztx(smiwEKL>hkjH7Y))GcUk`Y z5ECCi%1tZE!rM4TU=lk^UdvMlTfvxem>?j&r?OZ>W4w?APw@uZ8qL`fTtS zQtB<7SczI&5ZKELNH8DU6UNe1SFyvU%S#WTlf%`QC8Z+*k{IQx`J}f79r+Sj-x|4f<|Jux>{!M|pWYf+ z-ST5a#Kn+V{DNZ0224A_ddrj3nA#XfsiTE9S+P9jnY<}MtGSKvVl|Em)=o#A607CfVjjA9S%vhb@C~*a2EQP= zy%omjzEs5x58jMrb>4HOurbxT7SUM@$dcH_k6U7LsyzmU9Bx3>q_Ct|QX{Zxr4Fz@ zGJYP!*yY~eryK`JRpCpC84p3mL?Gk0Gh48K+R$+<|KOB+nBL`QDC%?)zHXgyxS2}o zf!(A9x9Wgcv%(sn!?7Ec!-?CcP%no4K?dJHyyT)*$AiuGoyt=pM`gqw%S^@k8>V0V z4i~0?c>K{$I?NY;_`hy_j6Q{m~KDzkiGK z_ffu;1bT+d;{6`SacCO z!z#1#uQP5`*%p&Urrk=&0`h1PBJxx*71yfl$|0Lt5_Lu$sO+F4>trJ6BS{J-of(R; znqrX@GUAyelkAOB;AqN)kur^1$g*t8&pGsyNZ|n42P$;s}e=Ef0&U zeA`jZs*E%l;3wd$oo^8Kh+#$+NzBNTi(70iEH)=Otim-ufx?&1Fe!w}-a_WL z3b9@#v&pt7wVF#bkr-YWhG|rhfwMABMZ<*Ku}@(4l8Aw|vSX#w9;23Ms1w zSC<+Ir!HNnF0m<+sQEdpqfFZn$+xA08nrn>k%Grb^0QdkgbOV;Kit2W`YwlfP5RRT2G3s4h?t5)!UZt~ ztK#FBL&P1pKsrye8S{&w@^ExelK;!LKh>=_q@VYF? z;_>~#$&OM13&!w@lx3P~g8~N3^wGM$Ybs$gFU+qlyxpp`?%oPWZNF-V;}NI47Q3^L z6zQ5TW`2EtX}l&7$2>xy4$xi;EXMN9^>l^O zpX}dt^G-p)6VSPIUolW9$svfNPfx=thP`;1S+wNs+PSh6QZ=X3FEu=#Ih!t_jC#tY z7t4@L1kbqL!4$7DY4QrHWPRfRvrE1hZcJR!wneIey(qiO(&qR5njE7~Vx5a{vafU= z)ya$}INqMlnsl?CHs*Gm@?JIPF$yE8pr2XE$;!z~-)=K?U$T3tT|t*z%Y~?_FuuG# zdxk5YL7D5##gr{wj@q_8USae@D&~NiU&5b$mcj$)ciL;Pm?1INBK8<9Uy##y@F;CU zG{5BquPJ2$`&r0uq3sHTD{+s!8^B47^RipsiHgpRoUp)5`1Om|oJQYZFd->&WM-2Y z+jMSmGg#v0-K{lm@K7En;FAw9nqm8(_94>4itl{!&h$c5Jhb(>aE;^WG5a0ho_P#k z=`>n+Y4`!6VFcFp<(fDGn0XZI%j$-p+V`Wfsdx5gviUanQCQKMLC02L-kZhqAFDJKEt24JM32 zX>A|&bwLR-xGzX@mrw_b>J0xDVriQ#YH{AYpBzPxW*}IViqyF8u~q zU?C~D8N<#3QCgHa! z%i?KtB+B&v;W5W8oy2USy=LKTj+&_Z`QpJr`GcqVwtDRmc6|RBE?NV#eo})g*6rN} zhVAR1l^#prL+5!{^P0NZ+RejdQ+Ik@^7pH{{xCL;z5Ef)do(8!08u9ieL2#1dVKMYKYZxBy98#CFs?lUx*#_eEO!>K!DVcH zdGN^HncO_w*;SJDV*_W|+&${EN7qQ1S1yi}H5b=0yu!PJ`dqxvn|pgs`A^1u$=l`! z7AEW-85?pZc4n>skM$;VkgurkG)2ecbYIlvN>b%UaLQareR0du>kXIMne04Rjh>ja zOJm_v=A~pE$}gH^TK6G5iT7xseUX#3keV|HJR9+g$u1o)wk^sTKGu+^WK4Dd6|PCC z*&kMT2?F_IS8|8B=Pgvkp`~)4nQ&T0-*6`YgSiY(GYn4))c1*2(ByIjf}HX8)B7rC z&d5F1D8EZT|BW`XU*~9w2)wL&5BLA(s{AwN`Cq`IT#a9vsG4Y>{48Y5F*r`NXsH?- zVTMpq8!(pQLZuRFNJ`bUqAX!QjVN;EgzPSiZEP^R9oBqXv+2Lf41bTiXwO@$_dEag z)4$-NHxpbc;(k6S`E9%V_Z7f<$NO$<=f@U!1BT{FA;w$gJM_RPC15g24TclHHNn= z%3))Msl?FP(v#6f=JB3R3(=~4{1-z9c(u5S4a?YsMm`I{<$RtS!4}}}Ls16B*~;RA zCFE^3T{I0u&U)AygIU#$7lBjVWRxt%JD|3mUGu4?1k3&FxUGkmjn>V`{dku=<;nM6H?3 z8xw;O<`w#tgfx@pCrNvj1x6M;bIoMn)ImU<%Z(~Dvg^o_X`D1>gDTAF1JlQ` z?Y0Rk=%+L12xR2Um(UM}Q!Uv+W%0yiatJP4)MXpxqnE?ceur3dpWVT$$C7W(Ad7OQ zW(07FjoY#!D~GG+S__T8FK&rdV8o2D$m<$v|3OeBckZrXV6vJB?+I0Q&55akuCrPQ zZU*OQXVhoj-{S`xTc(oCS}h)dA5qXgY;`LeY~fN~j3}d%Wj}YsHH!*FgWWVKtEo7% zHJCka&s(kt!Ix0uOwK~ysoe-RpANP#;|q6T$^GHRvO+{woF|P1&w_Kq=aoSqGzz;$ z*Wd$VhR9xrypy(YpJ6@06_07w6Ovvj^KcA}U4Pw$jA_~vwQAZkdkBBr8`%yn^BXnF zY|1lx{c2Y~DyMp-ZA=8M4nE-5zQ0V;O>J}Y+q0W4x)$_;wo<8D%n z!`fVX#C)T*rrWYPfxn@Q6qUT_)*!tiSediBO-cWahFdGUC+AFOSeqs;VqMXEvu z*%o*tngNJ+?;X}x>R4%u!~{AX)S}i#{yd>aw4uJZu8tysnfsX->l#F&^>#dTfy;r$ z9&&l4K^kS`n=Z?f{iVrgD@h2mp&`v~L{?|ix`67n;1n!!9Q9;ZT8{Z%tjs%KO;cRe zPUo=>|D{SI8*Zta^OK+@3{;6}Prl^Xo^!LgN89!4j#^fkSbG(fbc|}r9kfF?xK6Xn z1YQ@5h8GS>!!w45QHt_v&=*8WKMCyg^sG1>yC2jI6$OMH3*2k5pYYxNp2ruxMERnP zt>?dmG`|IjgqE?Y zfm?|c1z(LRCd0xBr_~~k6@@Vn{e_;CW=N{cxgOB7t*8bx)NVks2EHMQr1{_-@iJ4Yow z&jrCB7?wL1L^MwKQ<}W8nuXleT$a{lrIC+Lh^3X%lVS-Jj*O+ZeScuA=u{mU3<%Ru z?1Ta~3{lxdLZaLB{rnA*1cW#L6jcEUfR8x&{D2H-1!dw^=@(e4V zBXPJ#v7Vw?G}0~t&j@4v@@(6bhC0Wq;*N=}g9R&l+ltUp+C|&cLHD8B64iDaD#Ufm zzBugB@HF5v-1b26O3@fuv`ye?Q@;2{aG^N4zvx1n3|nzp+b3F$EEwVhHfn!wWrHgRcNDg+Ls6o&2!~fr|<5?3~C$xM40nq>h0pa?ejgP_Um+osTtap#sTgEz{+V!DVgg2c|zr&qy`*v|%k2qN4o$ zG~S$V&%H9mvmN_*yjnif&S_LWiH3GhJ<5yURu!%M^{oke1@N`vWL^&A({Dt^_*?zF zlEwE&e!1B;B=VjSvmW&#RI9p;59vL-zmfhqVSAUbyVBG~M#rW`BM9#;U-<(X5@k?g z1!baee)903$R-8_!>)ezvDF&ECABnUmq@;}jy$N;%haQ)b&?*%Pj@Zx<&(TSPsQ!- z_%e!bOqU&-@>_GE{lssw9He!Q4iIrZC?rGvemrxq=ZuF&VNVbL`14U6X|at+LC)@` zR8$!C=E++&j+(pty&FMQAxl0-G#pW(N>jQG1P2tvmz#rF&e3`|lwl z_vYYFF~1Qo=)yCVr!-;LzgT&I7&7|z9fN9h9n@0MDUi3~0_6bOhc@D2&^ z3duiUjQ;{H{ue#*zw_EcH6#7eEU^8|o4Z+g;kYqSw5Srw;B7BSV3Jyv$P(N)*#_vK z^_85Oc-QFw)3z4o&}w$QRS)*91nMOQ=(_P~ZMIbN`|4_ZI<*?Q@0jnHODEZYb7YNa z#+SIKx9tP({1fk!sZ{@be~5nfcU3c!&;~H>pIeMLx@HGdj_QX_a-&5s5M$~&{a`c# zA&Ak(q{ef>Gz5c^Ws>UyiFa*j#b4!CQU-ibzM|cGDhWsZV zPSM2}nveE~=5PtYB;8~Plz235H}`j{M)BvqI^wQGEc z9rbH|h#k#qFbKto=fbGP=fs$DGd|LTF%%-<=*%*scyqTgW;|&88`L-(y7Tth9HVaR zp}o`R$h{t3hYWj)%I-A!LZ{EALwwb@{TtF^4+X_7df_N(Eq?3Fxa#anAZ860o$rDoQyT;#i?`Kwurj4}BKysK7>nVQmatS5Nsshp{j zyS7G_fo*7u(Q+P%>ZN*aCp~9=tjao5cGcNm4 zx^?@S<p-aIyE;r_=AYe)b9h zzj^rv6QQ-}v0Cf7A|#5k>wLX}mH8FX52>q6R``I5aj(>*f3i+(F`6LcB&TwV1f zpOPb`4mv{k7WTW=>?1?FmVkn5!big+_SX>=c}=YQa&e+ez~sI1NEr5z9CTehje?9U zeQGJpCSAGIe8Q0$Z1}|?U+hS2PcEBSm6v21_B`XcXFU*4cyc40;{?Dg}W`~c$C^r1u0R%RqHCJ>{7(eSO$^7u3m~WQPS^$-(q&7a_2fFWJdGZdcs!8Yp93#wJGXC#+@-XFx|>~ zWg5SUiLzII8_j2bhj18wt_C_~^6>s+zj6K$qg)Pb`PYDVX=J7L+tMgt(x9w6zse)J zrWWHgUJmp%E@Gd$ZWQOvCOmDbvme4&D>*tpQvISkpoe!jph2$(V=}62#;K-r=px{4 zV=SM&(@pKFvW$W==2-~S-Tw&1LunP`!S#K40}R=1o4hYtUAAOR^O1p%&9v1;e~Mv!?1a_tMZAvG7he; zE(!g+ibYMAV|59+8DrA`A5jc3-gU&9%Ehp+qlG849RhUfZbL>lW#RoS2DMsm_Ux=T z|K|#Hv5ed&H*>KDzXXiopOce3I3(3%28T)wg51@M4yl?`judhBRFQ^Vxk)BpzD!Gdf#ou14?8X#gV$8aQC5b!&aX#wKA5qk_*wO!kHj9#S3 zfpfT#SU6nAV|8c)SSQA-8;;j_hf|h4AmqgK#I6X|Bi^JQUvhn%9ZFX#PLyfSQu$;$ zzM^i?+bX!Uuk9@9_E&+n1OxbcWwm-2^nejN=dF`W8^)>>#Cc$L@=1?vuQ#K}JjXsYEEOT{m5D-P)P}ys7UNH36m!HX{b7{zuY4R~4pfGV5Vi^-?R147 zD%l%2-?es1+bV6G4n$6GR4p(3ko&IXA+~(xQE|GL`XUzQacBze?)~!~HQF&6=utZ0 z$Wf?>HaxHaz7Vdtqw>KzA8y(;k}a|po=YGKx1k_^^zUDdNeGE>hyCRQSXcu*jL_YU zN!=4suP9`?J6XnmB6T|AChiP{Y{!9n6(*xTCBh?gJ`=4!L#e({8F5LQ^NHK@iL&LB zgD@%`@R`-CxQ8~aQh5hAwL^!2&`ZWwUt^g&CcMWa%{?u|%Q0S+=Zk`S=5!;nMj;)A zUkgmCf6>4`t~Sf4PcwYnqZbg3OF+Q)geEkt@yolApC*~;%L4b=P0^y0Dri{El=}4S z$X4s4+!}Hx*_v{nC%i<}C)#4{GV~O3b$(7WKQgmbWK*gp&bxjZMh%oA%7c;!x(UHc zJb*6c%(FyzY$UeZKe>)OnXJ6J#+#kL>6H@(rRUrJPT&TM*qJ(Zen2c1RTdSPih#F! zhNn89$nUneJz{GFdfXdLUFQ%+Dp(t{OZ5rb!Y)=Jk+Cg+kyn#$K#0-9B_~2J6CFQ) z1(JpSx*^=Z{P{OsfeXY>FUNrUD+Bd}BJlGUV)>t%g8pBcg8m;&Wk(?Kfx+?rP={4# zXB4Stq}8RQ<)@~n=q9G;4pa~n<(02#W|Wy4l$aV?SeP4F*wr1~;SrRXSeV$3Xs9OV zWaJsB+vFK#C#L0Fk3jzx>V*bA5$Nc!#SHLCaDciOczy_C>}F+a zO7CoDVrJ#&`nShmSM0V2BSt!Z(j+N{2qK1%?~(#uI1gQ1s>&W^0~xV~$nW z4pqV9;_`dmw}E=^?_$ry*6P1uvj2Kx3FG%^d_azjDv%??{GVSJHvTIB zZQ?5GU}py;Zpm5Mn*nKY?m&d}e?_5F)%1b9Xf%E>*l60e2)o*ydBme)*G+*;5h2RXO{)0P3jBG!L33uaJwzU(K(pv6~PPVzduR2|hw*i9w{(m4H zBS^uZ&rjFbkp|+v;LoK#iFk42d*MUii-&oRJm_hgMI7Ij!|4F79K)8we%~Y;)z64e zS$jZBbNXza<>?Hnzd=__%v}Z)E?tM3@C=^0c3OGpH?ILc;6K7CJHRW^0o;XM&? zRyJSjn0{#e%)dIN5KGml)+6Tt5Rk%+b&h7b*=OocxlFgC6=_Yeu5~|Rx0`VjhDk+} z<1I9`MFiDJFW4|F^V5yTKG8Gp1{v8H^iL1$d}T)KJxxi)uAvV7%^lcAWo61_;M?f+ zt*ei7zH!X4`WH_gd3aFWxuF$D(d1WGLYmrxhA3;SE)ls3ScyeKnCu_!>V(aj4|d;{ zr3d@%!lvC;Q^la)q%*jr_6ZQMqc}5=!j^g{!Y;_gLZ_z1mP1(2ofH+aMc@mO-w%0& zMcrLi=K@|Aj0dKfdi1zjUc8csnps7~J^oOr(crZ%-P>rt(vk^@obDhK%gz+COLyaF zOK@m(fV>GSpm|uvel^6QZJ`+Zq9q=64v>|~qAQ-QRn9AVlh7dTet}Jl$Bf8BlOeSX zRdEVg+lIQiT7;oB750LzS@a{VP{TS=prLli-EQdbR#XfrQuPc7PpO_wgy!O)Ji!_h z%o-Ied!{_J3E>-Q7Wy8R*O)${Vc7n6e#~E8k>#6Nd>OC{o&rDr7D4^1=l-n=Dj7Kg zfy@8pf`-Nj|AlQA|Fmq?fptIXim(x#Q$hn5A3z;;ub{UAm40w!;0p*xQPt~m6u1*4 zG~fRH;R!m96b>aS7IJE9-?nR4o6#^XzbT`CX){A=WdX)s+j*4Jw{yysmET<5g zhm~p#fBsf^D;F0ldkaO!zc%K=&KAJy z2(D)T$~~m&D=r$MjeX8>bk+VgEg0531O;L47sQCx5<0@n!Uiwkdzo^@5myP^w&}xH>73_@ODfWks~GrQLlMjj(6T=VkhF~X=S9fNiHaa$-%?#Z1=j=+S= zuh=Bar9-re^IBgu-N?L&pE2gF)wsS4Hk}wSgKhO1FhZhMJ$QNnak zc_Wg5E#j$$od&Rmk2X^SPW82|hAD%CQdfv%199y+R!Md+Y%xnNa!ceFR9YkOTTG2X z@degv0a@FP( zQGp(nd6$`yUEyu9VQY|1p^_;z5irnE5((Xij0zXIU3O6hr|mv*nf6@YKau^_`vx?U zVzk*ma1d%XK^Zsn6?b(_#C5Y>sgU1np+JAL$q#%lcx_5fq7N~y8$%Y1b@+qlZD)GRtqHiH64d1`M|6%gSI z7E)Ka;0tb#V2V7kP2N5ve8?RHqQI+D^S;>(^p{w&^T-`9T8M^17^E zj64Ug&h1ngxbO5^%8Q*oM^ZU3ix>(+wxqIv#20;@gRteOC|}HiWCLR4chOZ?sIl#j z?HWCs7ES&pYvD@XBAlD2DNS!N?o{H^RV<{m-)}D?NnIgZpCH&_k7h&2!m5!?4~$ha zLL0|~NL2^L;1mhwQu-$|4NgN=T`D#77(jGn_Ram-(H2Uz$; zf+hAb__g8npk=#_HZo1EbdbJvfPcy%j6v0c(TuA~CFWa#IpQ8DxrpD2g$oi(I2o2Z z24*~d>3T%gvGu;W0(7PE2QwGulFsU`yBy^a*R}SEcuz4PGa`L2Shn)X|0CKj$vi!l zaCDGyggSmFjrM}3;YC5#vSN>etg=m3CX&S4Axc2$Ts^+a@NfA#fKQutd*pd^(A_V@omWc_Wn z2hQwncEE}pKwi7qKc@PBPVuRUGcsVzXrYR)ti`QuI(D>YgTN!EudAs+5kX8H4W)0c zIAw{MVl1p@Hk~vb*I#_7n5AXW>4UVl4)eC&0I0WrZeAgG;bu@^)>w=-#R1~M{oE%( z<@`afh5m|!m6*!N-#^rxklo|Mz(ZxZ&B4|4VcoMwNXsBy(X2|3rvfBIt2!o5jEQrv zLw1MLY3@bD$B^%WBD~XC;wrIl$3tP7Ga~QLxD64h(~D$xN9m+3Eh~TMA+@A?zLmjI z$OvS($*mc z>-7O^ek3#vj<28l;F`DCy?7}nY;gV&6-Qpp;dX?e@leTJz3`e<%0*?O&k9$~VgWeC z_Ui4vn7u*k%x~Zav^W@jZEk{?&K;VrjDojuT6A9(_?togSE~qOT7HfJd3E8yiZcJJ z8A#S1STN?F)6hQ^$ln%WfR>FX+7Y_n57T6A3b3$HkU)*{tOQdR#4pkFEyP77VM4fa zF)bTL9&(VJtectZ;O8SUx)%V0c@7QlMyQSNfifr}Jxc}+MGq@Qil2{OuYA6*JNdQz z7Uu5F*?@*f!MBs_yWFd-K9{%I%aPAK|1Uzk+o_EZ9(4ue#Kov4D00}uS~1eMw_XOe z26zT~Ws1^Rh$bR~$k?m96>tz9%=e*8eOiHxdsA|*?Q;7+1~xE5egC=U=gHTn_#;&3_e5qQ+jz( z#pK^U8DYooTFAZK!MuY$$v%@;d#Mf91Ko0^ni3nW;{Y4nNn%=+D(z|A1>5cFT8s;)$qzErjML0 ziD7u7Hr$LASvu{+u9@x_)!~Z@iA6lGvb93@ox@E}w&Xc2)i=D=sh0f+Cvrt#$my5u zNC303wf!W;06T1)$Lm{&d0Y$R)1|S~WyRi7i~gVEJ_xzqMJD)m*o@XwEOICXt`la4cZ3VE78XZw0i9+>*DdZq@D`>yv7e({AvkT zkND$hT?3sR$7&DkeK`u(N14p@CQx#T*#3>0o^v-hT^IV<8ki~k{hDQ=f{o2MNPL zvoYAK@+7+xM*b3hZU-Nmf#%Wt(5PKm=5e#$TEJg!(OX`=TvDG=Tg2WG`EU|Ac*5tY z85?if*_GzFqJ~gBzz)m>lvTx(1B$UZ+(cZKO6+2Bo%rjvjn=Jgk(cRF6ll4EcW62w zIB7jGL}6x)r3O>_+lm-=Y`752QuDc8j|%+N(1)967Rg$7UWvkJG6uMzn_*^66b4*8 zB?j+c4Em#C{Kf`OH?n0qAeXHrx{4J}+xkpj826q~{uJ!Sp9c%>iNsxf+$vwQbbriw ziVukQ&@}iFkJP0kM*QY@SOY8Ws@i3L4^3Z%;3!$fj>B0^ZX+PgA6_;m`3_bu<*7QL zOZRT~u0FT}zGR$QwTrTi-0=wZXdM_w-WG>fwhZAoGj%2mDnDgKbYF(a=o{Fz-^*gj zwzOeIUv7)FSh489crAf{uB+vCZ;S5vy$Yt+fsU^*oAk1xygJ<=eG5BmUWczQfVVcx zAQy^X0uUL(p6C^S+L#7s!HM}|hC1}4ynle4i}drxpbCt(MN7^jC+l&R!+M=xb|n=X z1jf^Ouk_Xc9|v~A>R0)F8)zKkpO&Loh-m(PwZ1qf%wJnQY>+H*#vE8NEs3vT?}hFr z6cxV&Qqi{>kYkYUEsvNiVlfhZ=*&hcj<2^wA+xtF?0iN2RGh~5Z(jDwqHH?_EQL)! z63nv=^p9CAjFTguG~%8f$>GQYv4*SxiY!~i*;ix1?P+pn6s3MH0|SnU=3ORVK8nz} z6$#yIU7NL4`_Y{Bl02XZ7RIqTH#BItO&v$-W^XBo`_< zp;G;l+!qwLoy9y$h^PitL!U|q2HzHJ_k67`3tq0i2gx>cHzkFm$2W&qVDh|>T@Z*- z8wHeE9-zq-8AF!-x~s$f*t5rM;F5bByGh54r^&yPhggy z!rZr6i;^ia)kRBidKTcwqxnG7*JoIDr!?Y{$1{S7R)NY#4k^RKS6X2CER#1qPHoZS zNgXYiv-gACuEa9{Pg()P?0j5$$xQpyySA%fRpa^(9>=Q==fjIFVbM=F9Ky$dxln}? z2R}0&P)+o>emVfEceeQrvWBjB|8kIdz0E6bcDb_4*@yp&u{C2sa6yvG8ece%%-E~c z5L*$Q9ZqZ_1);e}P?>NK{hvNJ3_EQYjuP~ir#tzGx`U;+Pco%E#6dSS$Ou?1QiHOZ zUa3ZZ^!DggCSrpzryEF$k!(+`p3vldJ3W;2>pah|pU77#bbl_nd!o1ebDZ5Xnu^e# z3{mYzgp)o9Aof@d!ajp(M#d8Fg8N;6Vm)hbK`KL6Nzy|#$~TcA7`HT5cJip{bAUOS z3uh4Cv|Qf&V$rVLMOtpZF3?gkg4q`irJfIlQFRR0G=hsYT>AYrtbC72;EY_GyKN7v zE;J^7@d=gq5AHdZnJ=_`IU~)Gmf}u*;HMRD*qF%e-@$u-DFi$ljK&$DX4?er(mDV4 zdz63QousPUDK09Z`Pr}jROZ2QP`!o_gTr+&3m}3+&N0ToWXdGIF~Odp`=ztsKAgXY zxEKAcU&{FTJf0+Plf$J!W>3_6j{k&vuJfs<#lOz)15&9!E{5&c^!`>85g2G2M{1-p zfu2G!kkLv^+Z|^tZ7WxZwT2>`wwXK5$c-7hA-dNxaC#qapj1lhuOQWy<6hy>U@zLp{i>v0goz%WXZfJyM zAMcRmS{A?{94u@#r(Sga6JB##GIpf(C(KEmYBHlqV4p)T8=vpJ8yfL-S}_3RLQTi2 zE+I!C{5lx?OYr^WzKnY)aZ)NsfDs>fz7UP_>3i;YQcK-*4zbgh8(3b+Tgom5;)_}L zij@)AlIK2edojLXpN*)MXmCtss`*^-f%q;wrf}uXd#L!28(5NJmVOj@>Amj zvdBz39zgT8E8&DlkCft^UXevw9xGLOq9z_{a;nr#DeIUmB*`SPGJ;LYufmmDBd6c~Z?xdA z5prm}Ot}XfA@)EW{a1m>zv?{xD_ZbBdv@yfHvc~=x>tQl1-Osr=bs=mViAHux(SV- znm~fuDBFW_@`bagNmm$R#(hd&br zS%lna?|A!i^C_p#_j2a&ePj@OM&C;GzNo1w2szUebw_|!!>W~Bq=b(^OLr_1;37?%(##A z9QqVTl#IL`v(s%~0|Vz+8R>R@70%rCf(8>+;Bolb=5|toH%qQnyJD0H;lj36f&FF- zv%vwW^W=7uE3+{tR{!;xAX|f%`?f<<3qQ4-K?b!^8McJZm&K`-oG9J-tIVR0N)v9> z{aBjsKPjhsqU_1k?ujZzgwvyp;3OIg_9-xmJ4TqE<`xH-meDprmKKT9>?BQJ_c$=4 zjMxCytYKO3UqmSxF|O>r8NQupgg$=6j<$YTZlq-vBOF9{)e1{MgD+H9X&HZ7BELnJ zD)MD({Ai*5$spJF&E#uBOCx_s%Q?Z|#xuboK2JgdNp_GN>mOv6H}Ftj3C_15fk*W6 zQ@LssLl6rPe{u%XKQemMFSN>X5k(eG3>`eO2By+`tF7K7B!hjx!dnk)yJlSR10b2O z2~BPBdu&x5k6P<_Aq3zO_HpDFn zm7Q;ii%GQB6o=RAyOL1UHO{0M8NTY_mJt1l&frMH7X;blR$2Z^D5yG9sg6FBDs+M+ z0hVhb^~MveK6(`s!kkYZt#CVp7HNWEt@Um)yU(WX70HKUY-{esU-SNNJ5ZAE6FNyi z|0@&zKZxo7HhTWK>-?ABtD)<%sDbn+1#7BN90hK8kANt^1a%7oG^Iods$EDbphQ}< zK)g|1QY}$W`*`84_XD=)zV@gTu|;*TWZLz0Sk&T`@>O)hPg28ly-Bt#IdV2{IS=6A z@q_=C(EsxlHz57S4v&|K+=M5NL(a{Rcl)#-&OG$K%yXLD5$q0nYncAVQ+9L{dMk{^ zL|8%~ZuYD)D1nW*m$anFlWw$N%u$kRCw2g-iri@h4N+D?dej@mwEFNgO*?I#-A}T& z`j{rp{;-VALQ7;U#ehw{+}H-?apebor9J#I-EkS7E@$)*rI(2Eg|V45YwoYF?N6q-{yTyLb+>FoKRhs zx~U5_mvk~*TTmNK(Va!L7;yCIocCK5tt};4p-zA$3c$EM%1K#z7s{cmSPeB?LNvCOf8`?3{m|5el48Wx=_l*sG13tpH0Nx;9;ROU zRxz`t)G=g})nwWgNEf6ix%fGhE;~$JZG6&t*Hz%HIDVFJUA0SOyU>EMSEOTLiUz^k zC@Y~I7~Bi<7$GTPNdt4apBM86LtrR3@b)Yu;$fm_>Qk{x>NAb7q8I<$tc`cMXcOkq z=tq#^b!8Bk$SYia^abWU^EVrj9YaFKR$Z6{EW^DM8xMT9Z^mi^n$J1|oFwi$(KPDe zKF)h_X&!ni(>43<-=?*Aya_Y&y1&Qq!+e84G4ArPYMgiLMbtB&Xh_S)x%C$5o~uA! z)ISR^g^3JbT~!XiS`I2O;jyKK!dI6ipD7tIT(q*{w^tTrjSd>98OR8^`1SL%DUMr1 zoty*%29FrQC84%B%?K&EpagbmC9S3#$NlcEJ9y`nDk;d!u(-pfxKAEwX6NZHKgaP1 zYB$t_?F>eqRsQr2>Uw z_(OydVzS-~dc-l>{X`EmXAFX|Rdv9?J-mu_z(Aqxv^0Ze@0{dC$IX3^)}7NO##x~+ z9M3C6>Mb5#EE{I2d$azj^w@8$olxgF)9&oV`R*{O@bEZuYX)Ni|2j$bO%CT)Xd-hQ zwM1mrelZiLpY+Xh)RzFFoN=AYS10)wSREU_e&dln{ z-QKeQ4Br0Rtp2Za%>Rd_n5v@xSMZj?<>`xC}e-2KbVN?1otV0?Gf8uQuiI;twFnF0IOGq z?peO7GocyicU|yBF~GmL;iO|tCQBMo$&+-Fe;;HxPY*S*AkpOSf(S8XHh=UVc##ea zUQaRg{R~7zJCOi?eunC3;h-z&h)|?vFybC5n!%)VF{ASnIgJ@v|1lCxIw-{#tI?R2 zR$KlKZ;d!&&ucn3VFOuYA0z&9T-#_62%0Il%L~~x-znb z^P#1s5Ls!ytkHobY|s>fX`IhDv$zgD*P2LuysS8~D;>;?tiXW96Yq(SMdt#r2AZN7nB( zY5D1c_=t}FcIrtKLhQ>N&i0f&^^xW4qbG2fc#aFXFkfGhFLpNdT4{4F9?z|eK1<@! zYJFJPZP6h}oM)-VgkP@H$qGr1{U!-8lV*r59HgUqeo))HmDcBxVN^SQ=c^=M!;7bF-Vp_D#LR%hU=jFqOXEPi{` zviQDBaVvs_Og+?TFK!#hKwRuun0>tT>GTS9P6N9v|F;E+*IB6uxeN$-&$(;!s^}B; z-_SSmBHt%-G-WN+WHD_Vnn#XuC_+S%<)Mjv>q8!SuJBCStZuSZ+@D>+QWF3)fS95C z+4FTz3MpP=#?w>~0EN%lq3aHC!_fBisQ)?c_lB#r=EUDTW&A4A0 zp*joPiR%T|ptP>8Q(b|7+UP1$b@(sFIc)BKX0JdjS9dPjmnRYt;BuzfPeLlK zOxIUiI;BB2mqZ4H`HIu3HYo0!^@?RLpD@l=q5OG-o-U6*{X?odL|e`4%dJ+x3l>+0 zYqVRBTTQwwuj445KL)KJ!f!aB^(lXK=xFbT78!!PWeYf7)Al$ZQgMZVpOIi{)`?jQ6EGt zN1Fli^1-fQ_AW6%$y~nM{){i_1&A>$M_X2zsV>$$W{(fgty9e0&XaK%Wx9|P?(RQ@ zeG?yL81E?C<W zZN5#>k7@jMrYLPHOIeH1CpOsju9{rH0jI4h`qTq_mOfmrj9}zlOFZ7zYZvFJnE758=N6laV5R<(K#1Kyo z1+WD$nO^oJbwf~l;1+i3LhT5J7^fJYLms*@D>Q~0??Wbi*eH?7ovb#<531*sBqUvH z+U9r0YMiyeOG4U{^oDtp!AW)(StJi2q)@BV3s*IOD-`=*=AY#uTmJ(1^>p@7EIoXFwrc%;%KzWnF5|D26z! z{AaY}HS?db4Dx-hI3$OpXH?G=cY?vO+%f#1#0cmsw{|TTqcs z$L7$Vd%UAhzcx=P+Mg68NA>=MlLqmJuZxP@X2f28{~GD@+LyiN#*x2$(bHArR(-uT znfv3!VgHYf0N^cm@>CR$o9t9P4L#kW7TQA!Pz27Z)<^kRut0`|$oqMS&?>DUdp73?Z9UCZntcGFK-dt^CpAZwmX=VV5T+Ypb^d`CxT@_i6szTlgx ztHgj-1grdsMplBJC`(f}U?U7w`@!%?6;+hmt2Bm_otM`4-fLydBDZ8CKnE9@vHAfX zUoP+WRBN7IyU=;_AFV#%$PL^L-qDLfLgOq&dAd2pPISue{D)>YPcvn&qPdp07-1eU zzJDfttKVorH42n3Q|=R@#KfayWiZSYWe}uptFi1wI=ahv%D{2W04pkz=4cbEtRpWX zD8LmDRE(7XP!T*dRX`z0B$_?w?IiTG$iAuQgQD*ULx_(FGl2j^*?Pb)?RU*2QuMbo zEq&RT8!jCtp>^bPXv!Co^65#Q-Q9T?rJPHk$4=06@MVVAqn~Rm-r(mRmHh48Umucd zs|mYU8p8A|L;auv@pA^4^Y&>0!1Cqe;Qp%&JNaQCa%Cgj=*fBm6^-mmiT`Q zOy(xZDh>*vh0Z~Mi}?sD4HcdDgX5sO9gr%=&=!$lJ&E$BG24a1fkA)DXi_k|fB8do zfL6u4CU!t~`74Ke=ia@{;fk>ynq<)>f_A2MBjx5jg4-*-&yS3@lJS?O*9Tl&(@{Hdun>V2VjoU!p4XJ!u z`sV`b;DAv378}(tQWIx4Ijx6h3rnBHRgtieSnJw{eu?Qv?bCJqTCvm2)7kh_@>RL# zE%Fr9705W0o4C+8Jeu%tkrhY1f)6VZJX9p%e1RJw#{M$Pv5(N0_;s~wQLeYYb@ned&te6Ox{l{(K2M7ESVja1Hb3MN5H12SzFVU&LuBa|JH>666&HxE@r?=J7)GS zR<2g=X8&^*sZ{l!fml`_x?SVMwrA~;s5Hjz(pO`mSQ%pxGHa2=r!SB>=IeIu>A=c# z{=5HQXq0iHFD2-WqV8lzQdX zpKGm1w&DoY#gCFXaYu!X#7~p8CZu^?wQ)Uhs+>J)#PBJe#i}`uWi7Ph0;s#YAz5Jw zw~`e9sp-JY!2B>YhrZ0WjIK*AfMrTq0Qy6cjwymsTqkw_Pg9>xqdU!Lpb?z0#YoJ^ zmSnyN*RguGR$M-9oW0O`yzbsk*yHGP8Q-bGzsI|JiQKmLCN~M z8*#-Cx#tXmK@Ref1SrpIQOnx39dW4^ZlAs~Z@hb&J9NHS#1U;BPiUoAwAd!c9Mj2$ z24#}W2~M5TEN!HZrU{wJ)beG8>6LyKM^9yK@zbEC3o|AQ@u=;&qX>f8xF-JY%P^=s zs8pS7oUnskDO7)cj-gy6M#OT*+zct6a5@B{(0$cU44XEFrn39Q^6T6;+xR{Rn>kr9 zQrP5C&;*oe71IpJJo7gZJ)_U>PCxolSD^3)lF2{qW?^i^sZ!ZVK`FVcQ-G%3vW?@F zb7r)Kt4A4b%}sUAO|?dOLlj*$<3+4c_y7@Goq)wK>Kl%#zS!GZDT>Lnd5SL?sxSJ* zk1i@+wA z`hcof6#rthes>nC!?`F;*Xq!oamK}gk;Q=c^O7PB8pMJK`+Q;+Rf-2^gboUJk(7(| z9ekdg0;2FXcZ%jhp(Iz=Q?;l}MNBG0p|tEo-?GGWiQnSn=wexO!QI+@!OdKAul+J5 z<^6L+ip!0SLq7M4)|vT()00}~*wCtQ|btkyWthyh~dUKeakz#nBpKn!2FunJ_|0?lFez^B?l?~^x~Im2#$gf9FHTua z1}8l|>iSq5U>Ui}f#UQ);$8!wiJM-YCKP)2#6*@>h$>*IGFdW_8OlqBK@ED7?wf@mzih}MD&(oPbMp8oa&M-Vn;!CTRO(PmSZvNd#Vsw&m>#UVlWeC z^B%U}?{rm;HZ6pDMJJ=pif6JxrhB0~MqAI_t`;X!eY~#$r=As2XuY>Exy0Cr?AUUQvr1tQBLDCBVIjO5f1?rZ~# zk(mUxN>!87(fn2tE8~r-6^nDKvi7O& zTN<-k_2v?lG+Pr4odH%FecI+yo}bR-h7pR3=LZiKW-1BS{9S6Fm-WaCRRj>rU)k8u{Jt9)P_v57J2?b z@}gr5rVKk=Ep8KcoyK^rFth^g(-DA41`fi|Nl!Mow2BglypUaG%16C zd-UKWwM_DMf(5=s?}UXyn72%-pv{0e;WbPrq6J9Curr6|pid9sc2b@~nGZ!(_gW}R zd>4#2(+JK4?j)oUQiDsG4IDG%v5xOp7}h_6`JjAN-GmoJ-4NfDjb@t4%hh%3kM$sOK}rVT+G%cLU3MeygHY~yq>H5 zXF*6%U(^`%5(K2pjha}Yh;&dL)d&@mR?T3%_i`4C09IJ%CJ_~ESs{CN3lFp<cEHYvvZxsME}pi^r~`wE zR(Zgs-l?`OOui2RwdVOqNP`MB5%Y(uCqdyuh6XYj&SY`ji&KT8yGk_s0Q+i;aM?5- zdy2{P*c_p3bO^!G;}kI3o#7$-plZ7pE(%o1`*$eB4({rt=cR}Juz3?$kt1+a8 z;q2}fG$OYb{8u2zQ0y)_IOhEnw(C5*RB+CwEeoqwZ4=qSdrSrEIj{YN4rBUoUm1NO zT&9H=c$!s`QXI^CiGQG>?ity42j7-hG3nCYnYDF*aF4$Nl0N*J-rsr?EW|$y)?eTQ z2a_^9HEZiWraH$4_S?5}E;s8VTaYVVQ1ERD?Yf^Vzlix;@9=<_kjoh4!-VxF7(uQK zLIv(V^FP@Z0kLFbm}Hg-?lE-@eHS*8U?e%r$|a%#0Z_k6BX9S^=%5-5q} zh~z!E>VCuTe}W~#+u@A;g;>DwQ@6*!D#Iinq(E1cnMcoR1$4ay6ygxOKhZ`71sEw> zJGoa|#@cGF!myuz3IL(n2d_ac)Ull+s~^G3uRU|o7<8(8p)66!W)zR&>`*4XQ~t9e zj%HD$_=pu3GpiS_FA5d=Zqhlee^l6$tTkf<{yurrMT0T<#@W>k^xkDdjEaprF($T6A#m{3NEFeK?V9UJASIzNF-3;$ZW2DJ1C4 z+60`Xih-PF4DJWLECu}lbSQ&f05tU2g!ZBzDX~SZQWz#fXiB^3r+P9xv;FrroTv=! zni^qGP0eLX5hx{6EmPGNBl^OfAvTVBS!e)CxDIej#izrN?OhdSUs4TwE}r8B55D6> zMRdgCkm#~y!4AsJI09fVghHl;r!B0#0|cnSpHf#TRU3(KQ9_m;c|^YAxJFPg6do+d zcV~ChQN{yZX~k1)4WmyRmPYW3LupYAiXhiQ93_Y~8QAfM5UJu^lIgNpU%JWgHN7ls zmq36DlRpz@a(1!d-W}9$xJmzN(}{k~nv}n`>bdFY2191lQLW$AV2&x8P!Ei+Liqi$XVbQ7&w{*$& zBHO=doIpiDJSm~dY3K#HiD;6*m2T)nhf=X>PTeJhI;iIu&I7GXoptfm;HrW%yy~^2(-j6zk z@fCK+fx#(HG}>f7O`gwf~?U2yt7x2NojM1imx}>oPJI*zX!^ugOE9eJm@Nz$D(bQ5 z9agonHaTb_)4q&ACr{}2`YDuuMA#_TpUF$Q1-FNdsn__Yh78DTE8KH7(ym_t#UbWjpCo-UXKEbpHc=OFO?@3(pH!ps znXe3cF}&h+q6u|mp8X#GIec3BaUoO)dI=O-DSMp6xE$Rd;av z>pJ!+$cC^ag+|Z`Xl2P87>7($#y&tSGI4A3E=kCo1kz*@ld*Zmo40nuLs63hgt!+< zVP&d&^)!*nR$fDWM&@16<>xA3~$dOR_D`4x?e5|#72UnM4tjLE?IvvDb>|Jd#9OqP* zw6YtaPywLJwr9UwZ?y@R(Rb#;RlZfC=aw07;)8ivdEwqd-83jsbjXO|+k`(AOkI%$ z`bnubTn#iAx58rKeIF*#Eo^Hs z2p9*oIW;U{LhUdprOLtN9Z-OjpM<XPqNMAh;5WRA{JA@-VUBE2Asuc$Qh;|2))eC{&v8byr*cob)JHUV#1(swddDYOX=T{0x@Ug9EETtB>jv5?5pBU- zAjHz08TgDn1JYD+_u!mt4_{-Vax!}|+rM=tIOFS+88_5+ z^BXQVNIs;5GoH#GCaDX2XJ({vcktV_nT~cbD*}l`xvf_UM0`+bSCmZR3Vc~HW$Znz zKKC$gOupRqOr$s!35_HL79h|Tt4(;)_|jm{=pnSAGSoNW^=%o{7I!-IiDJK!r$IF5 zGzPts^}}ne$!=@OSr@HcP(GsmjNV8jERE?3m~{agTr3{!bi&#myZuVobHV`XSrbx} z(*=o!s~OV~+v~^ZOQ>PDIdx|Q#>53NLqVK^RF?wY{9aTOfuYowXr}uE-YUnqGujt6 z7+YO;F$pqnpiDx?XVhCvlSL)L$+axX%5Ju7mlU1OIeo$M>-YJbWbf?JT8k?ug9p43 zmOn_j4iUPF;GD|d)>)#=(tH9-{jB-5rlzPRX%xa^22>@9?Fqzz+g?jh7<${~xLtB? z)@bnFv$wXYROVA4-KdwG)U5$RE$nG&1{o+zHlcU7|8r3vOV&e$uM3&`RRUB%UY;45}9WNEqN@ph8b!( zQ8Oi5($^`zUBinEFBIcIO{SV6`D#$`G>|2ajnV2}f{!g|xiq#?%R{=x@pO*sxa?B| ztR)sIlDLqA$_P?m!5m7!CJ8rxlw6&LhC?&O6Hh%BPL)nvLMoFZKEH=}a%mqheg~bj zLK46)Jm&G7QoXPqBy?rX!!2!R%=t#^mT-3bsxfkTP5b=WinPF{>TdrR?ymvzeln=b zh`IWl)VgA`Aj#y0_9S;qZg4GZlIc)JNUaPvQG^(xui-MI;A$iJ$g0Nr_Wc17S#S^YWjl3PusxQ!)wU8b8 zFDF#aeJM!o$?`DADxMHNAZEJ~37%z9K|H`EELfXxd1kk~1D^+fVfB^vE8gX{gus(q zP8#n>$2_-_?mAGc;a!1_r%;Q5A2Rl`D|Ws8XM%2#K&mA6>S3ZSgN+PlDTfZgC=(ls zm&A@kk;cmfW89r0B}hsr6~eFYifW50>0>}L`!=SQWrUPCV>cIK&lak8qFzeUO^%DK zb;G1evX6LifZX+YX)KcE8#6f0K%rmfZCvGrDbX}1=o|~8K3Rr?$7h&k1ziysH@RgY z{wk6x@9k^JpF6y3O+|Vy=g#O%A7KZ_!Z*svG$;09pWmGH?5PE+@IJ+K63A3G zRxQj3C%h%n3+a83X?IpT9C|j9f%VX-U^n`S?1AX(xE>Rd2=n1Z;Z)gMjS=KX0e`3S z7wBro{K8hVEJ`ZaJaVVTROdCtB#>bNW}5@N=l7*#o*|`}5%^--4HcpKSh-7)JenNy zz(_n1cZ_*HlPkY|<1wAGFAe^ejgC#2M~>K80Zsz*A97m>&%{gwf-fO!IGXHtLFPaB z-&53Z_*)T-ofB9e3q0E0{0fPG;tkNTN)22HXZaVdDl#DeP*32mFbMm<{8nWN|B0FI zf2hYh*oDNS3i$x%CkPjxlN-XM-~l}-islg7!sKjDFkQ~(EOz?zTHAvpR5~}5r~}D} zx4z^}Rg52#tlI~!tHl+ron`xltoF9AATRpDATcI!tCII9rBskRRh8cTef438rEkUHMhEA+zg*XY08C@c<&hLhWA^8_Fv^SZM)W~Il7h@#hDRC z;D_T-kWj22P#@^WwO4$^dx9mjFu=&H?b^FyH@T(Ly$Bt!!KMOW$9bv6YG|h&2M^YU zCGxhRi*YJ(LBW(c8<*WZ+Pz2mS#CJ})k@Uo4>!wACtr&wu2dnN-KP`r83?6%l_42R z3D%P12Dd6P;xiy_Xjq=(8^QS3tyzaReeH-TW18P$VF-W!G`Ph>d-x4eY8ZLYmgp_Z zN$pPinOpkuoSq_cpCbmxXSF`rphklW;_gG+x-7lZ>m?x$PFGc&f+o51$}<}B8zzt4 z>4S$Hz4fx|ian>^e7yJc2lsNsE(y&Gmn1~KG}7n2?}h6gDi5h+Z?gyZpALhVB1tKl zyx+4x3bXPMGD}i|@INOM4O5vJ>)#(s4g~!uzHm&n4vs91I=ssj8Ux)V`sV!QOCp|9 z_)YS~Fs67!5t8AeXr`cQlns=!>|H7kiQC2;Z*ghB+|?dPB@U>Ja>Z)GbHAgb_$sMgr~G)JhY{!TEY52na@|#S?S|HmaH06E?59!Gbui(%>6w`R-#h5uMX! z0J{rT_9=QD=D~G4vDNy`P7OnhnumO|Y1EcXWM(=djE1uos--9OP5}>zC!E4gpZ6C( zuD8)|P^CaSANdHayg=YFqVm{k>Z;)4g$6&;Fwb16N#(cZ>?-D|Q$Ew6KV~-!=U7Av zc*Pk>`6Q(P`qiA!!dlj>Yxr#hrp(uX0^y1cbC&^-pjoU5SN^QxRI$TJKUQT^OdMFO zPA2$MH*IjCoTeJVPa3DO`**Oi)^2xR+ATF(WBu+l?`1+>>tS=-VaII8yrzTK*C{e_ zDK)^Mg-2V;&pKI<6S?Nj)K%_Bc+ONA_WB@s;!}K%9rZqZA28~b$32&j`F*+oi`%dm zm(`mzf;~jxBz~Y%;XJ4j-}z{o22D(mZ_g%+g5vo1aLV+J7s4Zz$Rv2aRq=+G7Y??8rDt!e1iy& z)&NN*U#B+|7pcEFX(?*S{}x+~sr_k;458jCT!EMH0>8L)kbk^!4L-?NjJOB(piv7C zo;6lt^LKi^A}3RkE{r$mxtW+{b_}M3LMM<>S)i0Wx*}mC5~~QY5?whdTa5-ih)t`h zerXv`DOtuC2}T6FBT{|Ot#W)CV!A9B_w>Zqn^H`TlVwXLnBLQ9_T)9iVlN%@X^G)- zmP+cbr6;F!2gQm)O=+EcU{cTlHh>V(2mh1uE%#RkaF$v!s##wN?hzfce2EP! z^VPf7wJtvzpICd}rF&j)RJ`(rvVjng(NWe)8b0JPO|bK*)vOO2Y;VeV19|}&w>9@ zA2~5HcZe}|+`+L`Ww2!1ll&Eh6tMw%{O3e{Gmm9d*vm`+lhy}p0JRQtg1&kr){q8o zLcN6|^;}wkg0ifpVwusKmkQ^k9L*NHP-IFY;N5Ccd@9_FZ|75USR#U-rg&}%h9+UO zqJNk#C`giY?8LjC5LY*DcR_PR!90NpCku;h)jY;Y5l+yID$8tEr}DajdRla|C!JZ9jS7ZNR?01x z(29C1wdrL=YOxVlG-&JGxru#`LvRr*x#&9t!iYKezI~KPJOY0uOXC!x^tjzoC!+N3 z{nNF^nX*)eZU>pfhV}$EAxl#9Qv@T9k_3ldr>eURyt9vm3j@@h<(CKp9~)y4yxE9;sUsj8c(7knL%j`1o#`5%Ch&^Sez!sOEPdI&6 zVDw&BqsIW}LMCTJ0HjFlnA&Wa9t9CkDK zXj`8X!ztT=v=f|BhhEyJey-fUg*2Mzmw1dvGsk1nDft>e$HrwSAlXa1HpdRnYj;#G zFAKPvbfbS-by>00KuvT{tAU}ryQZXM^I6aXWk~r!SM*_jo%ySU?%sRWqRO$7btT1h z66E7j5S)>9RjUTgF2?NIVycAJas+~Dw$;R!gXH%!)4&kKZlqnk=?tkW#kscq+yboW z+rDQal~@?2_heHhcafFu&RM;HvEow^*-ICyJ%;E*c@nCl&L(6RdZ}o1F*QZG!QBbI>Sga6MhY zJtASBj*zP)0>ULKMME%=^Q|Ms0&OsoOrGh&Ur|9MWn9}GUE7^opMeEm;Hx)FpK6=$ z_{v~P*=6*BN?ENw4Q@|+L;X1+8)Zi~fzB>%!h`h^bpruB>*Bp-oO;obx^UH&dKbO$ z(q8}M=W`~0+uJFDUkz7WMhiv@aBe0B&dqec8?N7iGXK8YB2rQFKhh#~_4G%i`C8~g zR9HFmLt$7gFG|3fNKAY3ApNaHc+`WwP0I8r-mo7i+OD%hrK3eXflK-y4xi>e$|6?A{B10 zD#AtKv}EPe(^Pt9YGbX4`+_lK8F{KDoVv&%CLAH+g@SXJvA)2b~P z>boypUaQ}6JuuS^2rJSMnz?|-^5S+$xt5PJ^Nq8*`Z&O7bQv`9F3GXQpNe)XQkz^p z^tlEZ8Mr6Sz70+qeI0ZhLc0vns#%y2L@V)bnd_D~!9l`QSKA-FOWT~a)${p8 z+TfUfuJ7Qp31=TU6nIiOcQdZCB3(X$(~<*+*oXDli+H*V(s*JYkt(*HH9Gn}#lFCK`}qFL#aAdF*HX&p9s~sLs?VmvZ?e*GDVXv}phS9WATfZe zCv0Slh59;TF(m5tX|l&tGKmJv5lLF(RIK0?3xFJeW?;XT3&8UX36MatEl}Tbs72&} zRjy4%<~CwS_wcN{yU50+!K1t@+oH+QjGY{erwlNSF7Gm3Fz{lq%(l5Jko+t0+W{vW z<|v)p!~=_#ZPFLCcZ-EBZAY91b2W`SDFK>@N6ZUZq4(xZgDWbsp98!@^srNCj!sou zbnOcjsP4M#a7!8s;T4|YR;^`{MfNy4Y3+m%yOw^u`?}l3!@pdh;-r}iuu}i*!pyg; zUX=Ybu;z8O+89#^3%8YlQg7~Sa=H?=@poZtL4hx}B8}Uq>*&^Qwp7?8S>UhWWNLZf zStvJnd5Lh7mye_o=WBZvN25s|7>tY73Bj-_x>b32R&1Sh^7j=AQ_eI-&RY(<@U<61(X_-G^BC@j6ZrN%T3o%&$Ta80FN_$+ds*mg z4Bl+7KLj8820g-KM9N!88(EefeLyXEr}f1E>FQgJV$ad{#7w~3$WkRnHjdjU+s z@8GxI1|5oJe8gu!J%r%-m&`dt~ z8U?WpmRwOb!9-7yLjq=~7tZ;VEK{yu_+COu9zvF1zI#(71z8uuskuKv@8l5fYXv^L zz_!sKI77Te=J{%r7KM8lznuCrZJbCZGE5c3daD@b-nI3whMy8#5*`N_wP*az8S%T} z|67FDqaeLV1zDMHL1a&04E9t-G35tRR#@>0S!ziIbWm8B<@&uQ3n`AOrTBYxqb{{P3i5k_Xu+7pGy6q}2>-lt{55ZSh?$Q8V533IZ8e z)AAPOU+%Rt@$JMZu%|Jx!Q{_3Rv!@LvA30H^aZ1fEvRDXhrTq~?Qo|&hqP@s<1Nj2 z8NbE7CeK`Zi$&fz?gpc^Qmz&-d^DO?5pe7c*EQm_?vHsBL0kP%DNWEs*D;k|7>z#d z=wqqTDLXzMTjeXI#Z>8j6+|1g9`jA;{$BUbP`~!C$T;TqJ}@HE1NcSouVn0mjR4km zM&hP+_6~}U`rrHiudm-;6-z~6G7~SWDjVBs6G?=Gx;aUIK^PBaUs4kAs7XX+*cG0V2~ddK#KcXI~0Ehk(PZ!Zia~Iclre z2g#qn6e9aNJp#Fo^D}-u&h633g_}c=9-Xm9f>Q5G=Ms%#t!YK|Y8A!ErF1KkdgYRG zbsS*^;3fhFrc!yg?pG3=+e_?P0JAiqq10yFZXCTivnlCRM+ti6LDZoXquQo2jizLd z$k^;*WS#Njw8XjsO~>XjDmG7MD!iZ^^^e6G73Sb+XJj}>`yq0;R78T!A(O6{K|+&M zbHzqGL?4?>Z9GO9H(xKQ)tJOpWDG8XT|luZD@RHf>uNSB3_55Ov=ljCQy_Xx7enuH ze;Kc5A>a+&L|lYO-A0mCY=yMqA~cJmS&6XKVsA`_m+*Z8kF+99<614pv$yTe{4}-3 z1b~yqt4#IQ$kj@ev6tR?MtCvcQNwIbUA z!;4kuj~H{_U;^a5I`?#33lH9fZunudyVD4_>d>guC)K*~adU_y9lS)kavh4CuDmeY zPrQ{x{~!WMV~8;VXqc0m9En$TUyy}@--hr%)xkcriO%#D*}tEYO{jn2HgE1wkqY_B zSQsPyWpzO;-I=z_GLKG?N-d)EN80tTXOKp78?&olk*?c&WYc?SNzb!kCwU?u{Bv6- z2avMfUY=jMMFBWWj|+7|d%Xi0Fy#+BA6P~_U9#pU^&_=Kh%|+LwELk9@e0_w4B|by zaTIFF@wz1%=FV?9Ajc$H>yV1Dodg-LD6w-it5zgtvTlzMgKb3#R7iCcy33OlRFoKAEQIE;yRz}PME$62;E1Bs8Wu2 z$3`~C&1~Vn9L^PdZ z33{h&m3EtM%nU{*tO?j|CYgN}V~4?UnTTf_20QLrwjNr&!BZ8{PR4s&9+`9s`~Bpn zS~`O1I=$5UDEK}u&x}b3yWtwd8W=CKr1(8#zjDNWA^O#Z#DVane2c990<_UwzuRa< zS9=E|%YWlj$cP=5?iNH3`Y=~wSz9+_HZ8WuCX6Q96NnX!iS?4<#hzCx;baUM8pWjW zvb3rn98pIwDy1oMkx-9%I?LIIhmrKg7Vnm}Cml~Ll8BKaNiEQG)B{F9Eikghh`on+ zDL%j$&fi80)(!VdX3rZFEd8qsA)NQ<`4s)1i>B33S;BQuw>+VM(+vPt`H6QJyj@l;B#6*A|Sezu|o?d)gbzUWi2?e>*W zToiD2)QPw&zook6cb8t$CH{hz!)qy@4sh5G3|M^kBB#VHCS)$< zfjGZ}yA4_-2}yHFFfu&`Rb<5xvTet~?^JCdr#yO7xo~13pi9kTui2t#cUN%}BDPZJ zBr{xQ?OOPCx=tQ1ml=l~j5=H? zXt+&1;);Q`jM)zp_OP2u13X+cV`M%rN*IE;O%5#ava-;MAJAkg-8%zu8&3FIuOm~E z6RoI_;MDz;z0ue&HD%%4T@T-whr@q!s3-(ow@f_L(#(B<8?X!6F^4BLDc(jlf_kfzXp@Daq@}O$vpcE`Z zOprA1o(s;W8=33^s4ob%XEhnqnBI${#&-0~;~x8B+Ylh>uLe_zym~D$dzkueR^k)qj?i{>RJ4!OO`P$oF!Z(0Na!A$oZ9jk4)$AW$k@ zsFk0+q*4_|yWUfVko^Ac)hMNGpt+1R#KgsN=QE&Yts2Nw4g zf#f>$@4|ta(=M^M#a&}v5NDcrv|*=8I)iaNSrgTEUQ+BzZ49t{i`qeTJ?4r`6v}UO z0d*>2(eM)y1=Qlq3|O$R>XDqc*qn&L>*oL@`Y0(`S2B3nrbH&A?&sF2#pN)P%r)~Z zo*2}!U2Y%KG~!lYKNO2}#)M~Y8P3#=H;;`SWCPw1RYvB-jaxGO+7D@}tU>Qxf zwOXQKeTsepe_;H1Eu%YJy?4zGYfC1A!5`jNW0WZb$8&gqCXS{e`89LelT1Pwuk^T8 zkrE#XR0<|?U5zeyLKX)uBY(a3<1xnbO$FBG{qcgv- zbcA@3bg-F81b;J2{c|>=lsJx?DNfRC#8GMr5&6An$%;~Hb^8a4BFPTW$l|9ttpZjp z=|Vh-qbV9`&UFO}s@oEP`1`(2bmVpw0dGFTr&Zg`ftxB_%F7qr!c9#|=qwx-ptY z#J~DLx`a^pWv$+V%3ss&YhC-^-rQ$>IuTMsj42=)a2ju@hO$jrIO=T1hmDimUr}X0 z!f#mL@j2wu_y|{1Z3I3?JDid2Iqu5?qb0%7*x88J(@3>T1=;{pANA%OQ~SB1$(KCc z-uH+Gq0vkDB-zOVX&Yk5Ybqnd5 z6{OV1e&TJ`i%i*?w5$C|LIWO+5DO4mz`OqH*QZi5c2-jYXynC!ClT=co&^B7)&2h? z13=A-KV$&d`bGEu2`D-kFi$u%GzdO$(>;**zq0p0^YHyZ200S?_ET0&Nr+xbP8_&X z|JPz&pmmGibc>XLC;GSl{C?#5e*0YfZ!uXRIVo{5MWtu5;*Sx&6#!0k|2cru-S-0- zE8h zKm$d8EgbEE8_UE^EsTT=42c7XPc_ z`L2vjD!__^0DI?~$@p>9_}*ds5&gNf@&D|FQM-dM3}B#%6|l|U_C@_TYJ6V&%)x*XiFW>LwkUonE*6Q zzuqTahCiYSTU$GP%e!GCt7mEjbh`e`w()ofbczuVi2(0WE#_Z26ModS##e^*kI>(T zfS8Msf#ZMW(;uS-;O3Q70a1m49Z2&7@;}X=;{PM+Uk}B1>~EF+b4NVRaQg$g#&=Ze zkGS8v^?#Y4$0-hf;t{;~Bi=8!{(mJreB2w4)93wUp?vvAmj7*W{**Q6C!Dv&e`n9{ z2KbLN=-=!2O>gFL(wm=vD4PE}17FHlHU&C$p3zPo5#?#ere@54V%Y>A7_#I zQM|@iW2al;9OU?hJdTaDgRR2SG{xSSx&Get}{Ko$T z|NTzkB1KdE%B{{_`wo%Vlq*JJ(4pCo>E|AOS7)hr*k=&{`2PqGfje&+o?LU+wvS%=vh)_D{~E(EpqB&*tiJQ0-65Stm4}a^s|D!>Voy|XKl52jW`5Wx_2K{yU2iy19>-ZD@r0!qf|8F1U p \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 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=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@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 Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz 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. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +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 + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/kobalt.iml b/kobalt.iml new file mode 100644 index 00000000..58cf671b --- /dev/null +++ b/kobalt.iml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kobalt/src/Build.kt b/kobalt/src/Build.kt new file mode 100644 index 00000000..f554cefa --- /dev/null +++ b/kobalt/src/Build.kt @@ -0,0 +1,153 @@ +import com.beust.kobalt.* +import com.beust.kobalt.internal.test +import com.beust.kobalt.plugin.java.javaProject +import com.beust.kobalt.plugin.kotlin.kotlinProject +import com.beust.kobalt.plugin.packaging.assemble +import com.beust.kobalt.plugin.kotlin.kotlinCompiler +import com.beust.kobalt.plugin.publish.jcenter + +val repos = repos("https://dl.bintray.com/cbeust/maven/") + +//val plugins = plugins( +// "com.beust:kobalt-example-plugin:0.42") +//val plugins2 = plugins( +// "com.beust.kobalt:kobalt-line-count:0.2" +//// file(homeDir("kotlin/kobalt-line-count/kobaltBuild/libs/kobalt-line-count-0.1.jar")) +//// file(homeDir("kotlin/kobalt-example-plugin/kobaltBuild/libs/kobalt-example-plugin-0.17.jar")) +//) + + +fun readVersion() : String { + val p = java.util.Properties() + p.load(java.io.FileReader(java.io.File("src/main/resources/kobalt.properties"))) + return p.getProperty("kobalt.version") +} + +val wrapper = javaProject { + name = "kobalt-wrapper" + version = readVersion() + directory = homeDir("kotlin/kobalt/modules/wrapper") +} + +val assembleWrapper = assemble(wrapper) { + jar { + name = wrapper.name + ".jar" + manifest { + attributes("Main-Class", "com.beust.kobalt.wrapper.Main") + } + } +} + +val kobalt = kotlinProject(wrapper) { + name = "kobalt" + group = "com.beust" + artifactId = name + version = readVersion() + + dependenciesTest { +// compile("junit:junit:4.12") + compile("org.testng:testng:6.9.5") + } + +// sourceDirectories { +// path("src/main/kotlin") +// path("src/main/resources") +// path("src/main/java") +// } + + dependencies { + compile("org.jetbrains.kotlin:kotlin-stdlib:0.14.449", + "org.jetbrains.kotlin:kotlin-compiler-embeddable:0.14.449", +// "org.jetbrains.kotlin:kotlin-compiler:0.14.449", + +// file(homeDir("java/jcommander/target/jcommander-1.47.jar")), + "com.beust:jcommander:1.48", + "com.beust:klaxon:0.16", + "com.squareup.okhttp:okhttp:2.4.0", + "org.slf4j:slf4j-api:1.7.12", + "org.slf4j:slf4j-simple:1.7.12", + "ch.qos.logback:logback-classic:1.1.2", + "org.jsoup:jsoup:1.8.2", + "com.google.inject:guice:4.0", + "com.google.inject.extensions:guice-assistedinject:4.0", + "com.google.guava:guava:18.0", + "org.apache.maven:maven-model:3.3.3", + "com.github.spullara.mustache.java:compiler:0.8.18" + ) + } +} + +val testKobalt = test(kobalt) { + args("-log", "2", "src/test/resources/testng.xml") +} + +val assembleKobalt = assemble(kobalt) { + mavenJars { + fatJar = true + manifest { + attributes("Main-Class", "com.beust.kobalt.KobaltPackage") + } + } +// jar { +// fatJar = true +// name = "${kobalt.name}-wrapper.jar" +// manifest { +// attributes("Main-Class", "com.beust.kobalt.wrapper.WrapperPackage") +// } +// } + zip { + include("kobaltw") + include(from("${kobalt.buildDirectory}/libs"), to("kobalt/wrapper"), + "${kobalt.name}-${kobalt.version}.jar") + include(from("modules/wrapper/${kobalt.buildDirectory}/libs"), to("kobalt/wrapper"), + "${kobalt.name}-wrapper.jar") + } +} + +val cs = kotlinCompiler { + args("-nowarn") +} + + +val jc = jcenter(kobalt) { + publish = true + file("${kobalt.buildDirectory}/libs/${kobalt.name}-${kobalt.version}.zip", + "${kobalt.name}/${kobalt.version}/${kobalt.name}-${kobalt.version}.zip") +} + +//val testng = javaProject { +// name = "testng" +// group = "org.testng" +// artifactId = name +// version = "6.9.6-SNAPSHOT" +// directory = homeDir("java/testng") +// buildDirectory = "kobaltBuild" +// +// sourceDirectoriesTest { +// path("src/test/java") +// path("src/test/resources") +// } +// sourceDirectories { +// path("src/main/java") +// path("src/generated/java") +// } +// dependencies { +// compile("org.apache.ant:ant:1.7.0", +// "junit:junit:4.10", +// "org.beanshell:bsh:2.0b4", +// "com.google.inject:guice:4.0:no_aop", +// "com.beust:jcommander:1.48", +// "org.yaml:snakeyaml:1.15") +// } +//} +// +//@Task(name = "generateVersionFile", description = "Generate the Version.java file", runBefore = arrayOf("compile")) +//fun createVersionFile(project: Project) : com.beust.kobalt.internal.TaskResult { +// val dirFrom = testng.directory + "/src/main/resources/org/testng/internal/" +// val dirTo = testng.directory + "/src/generated/java/org/testng/internal/" +// println("COPYING VERSION FILE") +// Files.copy(Paths.get(dirFrom + "VersionTemplateJava"), Paths.get(dirTo + "Version.java"), +// StandardCopyOption.REPLACE_EXISTING) +// return com.beust.kobalt.internal.TaskResult() +//} +// diff --git a/kobalt/wrapper/kobalt-wrapper.jar b/kobalt/wrapper/kobalt-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e9c7f6a2c1b0b7fc65d4ccf3ac6b4ef1bc9dfc2d GIT binary patch literal 6070 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 literal 0 HcmV?d00001 diff --git a/kobalt/wrapper/kobalt-wrapper.properties b/kobalt/wrapper/kobalt-wrapper.properties new file mode 100644 index 00000000..ad2d4847 --- /dev/null +++ b/kobalt/wrapper/kobalt-wrapper.properties @@ -0,0 +1 @@ +kobalt.version=0.144 diff --git a/kobaltw b/kobaltw new file mode 100644 index 00000000..7921741f --- /dev/null +++ b/kobaltw @@ -0,0 +1 @@ +java -jar kobalt/wrapper/kobalt-wrapper.jar $* diff --git a/kobaltw-windows b/kobaltw-windows new file mode 100644 index 00000000..54f185e5 --- /dev/null +++ b/kobaltw-windows @@ -0,0 +1,3 @@ +jar=`ls -ltr $HOME/kotlin/kobalt/build/libs/*jar|grep -v sources|awk '{print $9}'` +java -jar $jar $* + diff --git a/modules/wrapper/src/main/java/com/beust/kobalt/wrapper/Main.java b/modules/wrapper/src/main/java/com/beust/kobalt/wrapper/Main.java new file mode 100644 index 00000000..8eca42cc --- /dev/null +++ b/modules/wrapper/src/main/java/com/beust/kobalt/wrapper/Main.java @@ -0,0 +1,260 @@ +package com.beust.kobalt.wrapper; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class Main { + public static void main(String[] argv) throws IOException, InterruptedException { + new Main().installAndLaunchMain(argv); + } + + private static final String KOBALT_PROPERTIES = "kobalt.properties"; + private static final String KOBALTW = "kobaltw"; + private static final String KOBALT_WRAPPER_PROPERTIES = "kobalt-wrapper.properties"; + private static final String PROPERTY_VERSION = "kobalt.version"; + private static final String URL = "https://dl.bintray.com/cbeust/generic/"; + private static final String FILE_NAME = "kobalt"; + private static final String DISTRIBUTIONS_DIR = + System.getProperty("user.home") + "/.kobalt/wrapper/dist"; + + private final Properties properties = new Properties(); + + private static int logLevel = 1; + + private void installAndLaunchMain(String[] argv) throws IOException, InterruptedException { + for (int i = 0; i < argv.length; i++) { + switch(argv[i]) { + case "--log": + logLevel = Integer.parseInt(argv[i + 1]); + i++; + break; + } + } + Path kobaltJarFile = installJarFile(); + launchMain(kobaltJarFile, argv); + } + + private void readProperties(Properties properties, InputStream ins) throws IOException { + properties.load(ins); + ins.close(); + } + + private Properties maybeCreateProperties() throws IOException { + Properties result = new Properties(); + URL url = getClass().getClassLoader().getResource(KOBALT_PROPERTIES); + if (url != null) { + readProperties(result, url.openConnection().getInputStream()); + } else { + throw new IllegalArgumentException("Couldn't find " + KOBALT_PROPERTIES); + } + return result; + } + + private File getWrapperDir() { + return new File("kobalt", "wrapper"); + } + + private void initWrapperFile(String version) throws IOException { + File config = new File(getWrapperDir(), KOBALT_WRAPPER_PROPERTIES); + if (! config.exists()) { + saveFile(config, PROPERTY_VERSION + "=" + version); + } + properties.load(new FileReader(config)); + } + + private String getWrapperVersion() { + return properties.getProperty(PROPERTY_VERSION); + } + + private boolean isWindows() { + return System.getProperty("os.name").contains("Windows"); + } + + private Path installJarFile() throws IOException { + Properties properties = maybeCreateProperties(); + String version = properties.getProperty(PROPERTY_VERSION); + initWrapperFile(version); + + log(2, "Wrapper version: " + getWrapperVersion()); + + String fileName = FILE_NAME + "-" + getWrapperVersion() + ".zip"; + new File(DISTRIBUTIONS_DIR).mkdirs(); + Path localZipFile = Paths.get(DISTRIBUTIONS_DIR, fileName); + String zipOutputDir = DISTRIBUTIONS_DIR + "/" + getWrapperVersion(); + Path kobaltJarFile = Paths.get(zipOutputDir, + getWrapperDir().getPath() + "/" + FILE_NAME + "-" + getWrapperVersion() + ".jar"); + if (! Files.exists(localZipFile) || ! Files.exists(kobaltJarFile)) { + if (!Files.exists(localZipFile)) { + String fullUrl = URL + "/" + fileName; + download(fullUrl, localZipFile.toFile()); + if (!Files.exists(localZipFile)) { + log(2, localZipFile + " downloaded, extracting it"); + } else { + log(2, localZipFile + " already exists, extracting it"); + } + } + + // + // Extract all the zip files + // + ZipFile zipFile = new ZipFile(localZipFile.toFile()); + Enumeration entries = zipFile.entries(); + File outputDirectory = new File(DISTRIBUTIONS_DIR); + outputDirectory.mkdirs(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + File entryFile = new File(entry.getName()); + if (entry.isDirectory()) { + entryFile.mkdirs(); + } else { + Path dest = Paths.get(zipOutputDir, entryFile.getPath()); + log(2, " Writing " + entry.getName() + " to " + dest); + Files.createDirectories(dest.getParent()); + Files.copy(zipFile.getInputStream(entry), dest, StandardCopyOption.REPLACE_EXISTING); + } + } + } + + // + // Copy the wrapper files in the current kobalt/wrapper directory + // + log(2, "Copying the wrapper files"); + for (String file : FILES) { + Path from = Paths.get(zipOutputDir, file); + Path to = Paths.get(new File(".").getAbsolutePath(), file); + try { + if (isWindows() && to.toFile().exists()) { + log(1, "Windows detected, not overwriting " + to); + } else { + Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING); + } + } catch(IOException ex) { + log(1, "Couldn't copy " + from + " to " + to + ": " + ex.getMessage()); + } + } + new File(KOBALTW).setExecutable(true); + return kobaltJarFile; + } + + private static final String[] FILES = new String[] { KOBALTW, "kobalt/wrapper/" + FILE_NAME + "-wrapper.jar" }; + + private void download(String fileUrl, File file) throws IOException { + URL url = new URL(fileUrl); + HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); + int responseCode = httpConn.getResponseCode(); + + // always check HTTP response code first + if (responseCode == HttpURLConnection.HTTP_OK) { + String fileName = ""; + String disposition = httpConn.getHeaderField("Content-Disposition"); + String contentType = httpConn.getContentType(); + int contentLength = httpConn.getContentLength(); + + if (disposition != null) { + // extracts file name from header field + int index = disposition.indexOf("filename="); + if (index > 0) { + fileName = disposition.substring(index + 10, + disposition.length() - 1); + } + } else { + // extracts file name from URL + fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1, + fileUrl.length()); + } + + log(2, "Content-Type = " + contentType); + log(2, "Content-Disposition = " + disposition); + log(2, "Content-Length = " + contentLength); + log(2, "fileName = " + fileName); + + // opens input stream from the HTTP connection + InputStream inputStream = httpConn.getInputStream(); + + // opens an output stream to save into file + FileOutputStream outputStream = new FileOutputStream(file); + + int bytesRead = -1; + long bytesSoFar = 0; + byte[] buffer = new byte[100_000]; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + bytesSoFar += bytesRead; + if (bytesRead > 0) { + if (contentLength > 0) { + float percent = bytesSoFar * 100 / contentLength; + log2(1, "\rDownloading " + url + " " + percent + "%"); + } else { + log2(1, "."); + } + } + } + log2(1, "\n"); + + outputStream.close(); + inputStream.close(); + + log(1, "Downloaded " + fileUrl); + } else { + error("No file to download. Server replied HTTP code: " + responseCode); + } + httpConn.disconnect(); + } + + private void saveFile(File file, String text) throws IOException { + file.getAbsoluteFile().getParentFile().mkdirs(); + file.delete(); + log(2, "Wrote " + file); + Files.write(Paths.get(file.toURI()), text.getBytes()); + } + + private static void log2(int level, String s) { + p(level, s, false); + } + + private static void log(int level, String s) { + p(level, "[Wrapper] " + s, true); + } + + private static void p(int level, String s, boolean newLine) { + if (level <= logLevel) { + if (newLine) System.out.println(s); + else System.out.print(s); + } + } + + private void error(String s) { + System.out.println("[Wrapper error] *** " + s); + } + + private static final String KOBALT_MAIN_CLASS = "com.beust.kobalt.KobaltPackage"; + + private void launchMain(Path kobaltJarFile, String[] argv) throws IOException, InterruptedException { + List args = new ArrayList<>(); + args.add("java"); + args.add("-jar"); + args.add(kobaltJarFile.toFile().getAbsolutePath()); + for (String arg : argv) { + args.add(arg); + } + + ProcessBuilder pb = new ProcessBuilder(args); + pb.inheritIO(); + log(2, "Launching " + args); + Process process = pb.start(); + process.waitFor(); + + } + +} \ No newline at end of file diff --git a/modules/wrapper/wrapper.iml b/modules/wrapper/wrapper.iml new file mode 100644 index 00000000..8ad8042a --- /dev/null +++ b/modules/wrapper/wrapper.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/kotlin/com/beust/kobalt/Args.kt b/src/main/kotlin/com/beust/kobalt/Args.kt new file mode 100644 index 00000000..1e454d72 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/Args.kt @@ -0,0 +1,25 @@ +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? = null + + @Parameter(names = arrayOf("--tasks"), description = "Display the tasks available for this build") + var tasks: Boolean = false + + @Parameter(names = arrayOf("--log"), description = "Define the log level (1-3)") + var log: Int = 1 + + @Parameter(names = arrayOf("-i", "--init"), description = "Create a new build file based on the current project") + var init: Boolean = false + + @Parameter(names = arrayOf("--checkVersions"), description = "Check if there are any newer versions of the " + + "dependencies") + var checkVersions = false +} + diff --git a/src/main/kotlin/com/beust/kobalt/Banner.kt b/src/main/kotlin/com/beust/kobalt/Banner.kt new file mode 100644 index 00000000..4884034c --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/Banner.kt @@ -0,0 +1,25 @@ +package com.beust.kobalt + +import java.util.* + + +class Banner { + companion object { + val BANNERS = arrayOf( + " __ __ __ __ __ \n" + + " / //_/ ____ / /_ ____ _ / / / /_\n" + + " / ,< / __ \\ / __ \\ / __ `/ / / / __/\n" + + " / /| | / /_/ / / /_/ // /_/ / / / / /_ \n" + + " /_/ |_| \\____/ /_.___/ \\__,_/ /_/ \\__/ ", + + " _ __ _ _ _ \n" + + " | |/ / ___ | |__ __ _ | | | |_ \n" + + " | ' / / _ \\ | '_ \\ / _` | | | | __|\n" + + " | . \\ | (_) | | |_) | | (_| | | | | |_ \n" + + " |_|\\_\\ \\___/ |_.__/ \\__,_| |_| \\__| " + ) + + fun get() = BANNERS.get(Random().nextInt(BANNERS.size())) + } +} + diff --git a/src/main/kotlin/com/beust/kobalt/BasePluginTask.kt b/src/main/kotlin/com/beust/kobalt/BasePluginTask.kt new file mode 100644 index 00000000..1c4285b3 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/BasePluginTask.kt @@ -0,0 +1,13 @@ +package com.beust.kobalt + +import com.beust.kobalt.api.Plugin +import com.beust.kobalt.api.PluginTask +import com.beust.kobalt.api.Project + +public abstract class BasePluginTask(override val plugin: Plugin, + override val name: String, + override val doc: String, + override val project: Project) + : PluginTask { + override val dependsOn = arrayListOf() +} diff --git a/src/main/kotlin/com/beust/kobalt/BuildScript.kt b/src/main/kotlin/com/beust/kobalt/BuildScript.kt new file mode 100644 index 00000000..118f0c1d --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/BuildScript.kt @@ -0,0 +1,40 @@ +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.maven.DepFactory +import com.beust.kobalt.maven.IClasspathDependency +import com.beust.kobalt.misc.KobaltExecutors +import com.beust.kobalt.plugin.java.SystemProperties +import java.io.File + +@Directive +fun homeDir(vararg dirs: String) : String = SystemProperties.homeDir + + File.separator + dirs.toArrayList().join(File.separator) + +@Directive +fun file(file: String) : String = IClasspathDependency.PREFIX_FILE + file + +@Directive +fun plugins(vararg dependency : IClasspathDependency) { + Plugins.dynamicPlugins.addAll(dependency) +} + +@Directive +fun plugins(vararg dependencies : String) { + val executor = INJECTOR.getInstance(KobaltExecutors::class.java) + .newExecutor("BuildScript", 5) + val factory = INJECTOR.getInstance(DepFactory::class.java) + dependencies.forEach { + Plugins.dynamicPlugins.add(factory.create(it, executor)) + } +} + +@Directive +fun repos(vararg repos : String) { + repos.forEach { Kobalt.addRepo(it) } +} + +@Directive +fun glob(g: String) : IFileSpec.Glob = IFileSpec.Glob(g) diff --git a/src/main/kotlin/com/beust/kobalt/FileSpec.kt b/src/main/kotlin/com/beust/kobalt/FileSpec.kt new file mode 100644 index 00000000..cb23380b --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/FileSpec.kt @@ -0,0 +1,39 @@ +package com.beust.kobalt + +import com.beust.kobalt.api.Project +import com.beust.kobalt.misc.KobaltLogger +import java.io.File +import java.nio.file.* +import java.nio.file.attribute.BasicFileAttributes + +sealed class IFileSpec { + abstract fun toFiles(directory: String): List + + class FileSpec(val spec: String) : IFileSpec() { + override public fun toFiles(directory: String) = arrayListOf(File(spec)) + + override public fun toString() = spec + } + + class Glob(val spec: String) : IFileSpec(), KobaltLogger { + override public fun toFiles(directory: String): List { + val result = arrayListOf() + + val matcher = FileSystems.getDefault().getPathMatcher("glob:${spec}") + Files.walkFileTree(Paths.get(directory), object : SimpleFileVisitor() { + override public fun visitFile(path: Path, attrs: BasicFileAttributes): FileVisitResult { + val rel = Paths.get(directory).relativize(path) + if (matcher.matches(rel)) { + log(3, "Adding ${rel.toFile()}") + result.add(rel.toFile()) + } + return FileVisitResult.CONTINUE + } + }) + return result + } + + override public fun toString() = spec + } + +} diff --git a/src/main/kotlin/com/beust/kobalt/Main.kt b/src/main/kotlin/com/beust/kobalt/Main.kt new file mode 100644 index 00000000..9decf8aa --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/Main.kt @@ -0,0 +1,180 @@ +package com.beust.kobalt + +import com.beust.jcommander.JCommander +import com.beust.kobalt.api.Kobalt +import com.beust.kobalt.api.KobaltContext +import com.beust.kobalt.internal.* +import com.beust.kobalt.kotlin.BuildFile +import com.beust.kobalt.kotlin.ScriptCompiler +import com.beust.kobalt.maven.* +import com.beust.kobalt.misc.* +import com.beust.kobalt.plugin.java.SystemProperties +import com.beust.kobalt.plugin.publish.JCenterApi +import com.beust.kobalt.plugin.publish.UnauthenticatedJCenterApi +import com.beust.kobalt.wrapper.Wrapper +import com.google.inject.Guice +import java.io.File +import java.nio.file.Paths +import java.util.* +import javax.inject.Inject + +val INJECTOR = Guice.createInjector(MainModule()) + +public fun main(argv: Array) { + INJECTOR.getInstance(Main::class.java).run(argv) +} + +private class Main @Inject constructor( + val scriptCompilerFactory: ScriptCompiler.IFactory, + val plugins: Plugins, + val taskManager: TaskManager, + val http: Http, + val files: KFiles, + val executors: KobaltExecutors, + val localRepo: LocalRepo, + val depFactory: DepFactory, + val checkVersions: CheckVersions, + val jcenter: UnauthenticatedJCenterApi) + : KobaltLogger { + + data class RunInfo(val jc: JCommander, val args: Args) + + public fun run(argv: Array) { + // Check for new version + // Commented out until I can find a way to get the latest available download + // from bintray. Right now, it always returns all the versions uploaded, not + // just the one I mark +// val p = jcenter.kobaltPackage +// val current = Versions.toLongVersion(Kobalt.version) +// val remote = Versions.toLongVersion(p.latestPublishedVersion) +// if (remote > current) { +// log(1, "*****") +// log(1, "***** New Kobalt version available: ${p.latestPublishedVersion}") +// log(1, "*****") +// } + + benchmark("Build", { + println(Banner.get() + Kobalt.version + "\n") +// runTest() + val (jc, args) = parseArgs(argv) + runWithArgs(jc, args) + executors.shutdown() + debug("All done") + }) + } + + public class Worker(val runNodes: ArrayList, val n: T) : IWorker, KobaltLogger { + override val priority = 0 + + override fun call() : TaskResult2 { + log(2, "Running node ${n}") + runNodes.add(n) + return TaskResult2(n != 3, n) + } + } + + private fun runTest() { + val dg = Topological() + dg.addEdge("b1", "a1") + dg.addEdge("b1", "a2") + dg.addEdge("b2", "a1") + dg.addEdge("b2", "a2") + dg.addEdge("c1", "b1") + dg.addEdge("c1", "b2") + val sorted = dg.sort(arrayListOf("a1", "a2", "b1", "b2", "c1", "x", "y")) + println("Sorted: ${sorted}") + } + + private fun parseArgs(argv: Array): RunInfo { + val args = Args() + val result = JCommander(args) + result.parse(*argv) + KobaltLogger.LOG_LEVEL = args.log + return RunInfo(result, args) + } + + private val SCRIPT_JAR = "buildScript.jar" + + private fun runWithArgs(jc: JCommander, args: Args) { + val p = if (args.buildFile != null) File(args.buildFile) else findBuildFile() + args.buildFile = p.absolutePath + val buildFile = BuildFile(Paths.get(p.absolutePath), p.name) + + if (args.init) { + // + // --init: create a new build project and install the wrapper + // + Wrapper().install() + ProjectGenerator().run(args) + } else { + if (! buildFile.exists()) { + jc.usage() + } else { + // Install all the plugins found + plugins.installDynamicPlugins(arrayListOf(buildFile)) + + // Compile the build script + val output = scriptCompilerFactory.create(plugins.pluginJarFiles, + { n: String, j: File? -> + plugins.instantiateClassName(n, j) + }) + .compile(buildFile, buildFile.lastModified(), KFiles.findBuildScriptLocation(buildFile, SCRIPT_JAR)) + + // + // Force each project.directory to be an absolute path, if it's not already + // + output.projects.forEach { + val fd = File(it.directory) + if (! fd.isAbsolute) { + it.directory = + if (args.buildFile != null) { + KFiles.findDotDir(File(args.buildFile)).parentFile.absolutePath + } else { + fd.absolutePath + } + } + } + + plugins.applyPlugins(KobaltContext(args), output.projects) + + if (args.tasks) { + // + // List of tasks + // + val sb = StringBuffer("List of tasks\n") + Plugins.plugins.forEach { plugin -> + if (plugin.tasks.size() > 0) { + sb.append("\n ===== ${plugin.name} =====\n") + plugin.tasks.forEach { task -> + sb.append(" ${task.name}\t\t${task.doc}\n") + } + } + } + println(sb.toString()) + } else if (args.checkVersions) { + checkVersions.run(output.projects) + } else { + // + // Launch the build + // + taskManager.runTargets(args.targets, output.projects) + } + } + } + } + + private fun findBuildFile(): File { + val files = arrayListOf("Build.kt", "build.kobalt", KFiles.src("build.kobalt"), + KFiles.src("Build.kt")) + try { + return files.map { + File(SystemProperties.currentDir, it) + }.first { + it.exists() + } + } catch(ex: NoSuchElementException) { + return File("Build.kt") + } + } +} + diff --git a/src/main/kotlin/com/beust/kobalt/Plugins.kt b/src/main/kotlin/com/beust/kobalt/Plugins.kt new file mode 100644 index 00000000..a8125a4f --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/Plugins.kt @@ -0,0 +1,319 @@ +package com.beust.kobalt + +import com.beust.kobalt.api.* +import com.beust.kobalt.api.annotation.Task +import com.beust.kobalt.internal.TaskManager +import com.beust.kobalt.internal.TaskResult +import com.beust.kobalt.kotlin.BuildFile +import com.beust.kobalt.kotlin.ScriptCompiler +import com.beust.kobalt.maven.DepFactory +import com.beust.kobalt.maven.IClasspathDependency +import com.beust.kobalt.maven.KobaltException +import com.beust.kobalt.maven.LocalRepo +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltExecutors +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.misc.countChar +import com.beust.kobalt.plugin.DefaultPlugin +import com.beust.kobalt.plugin.java.JavaPlugin +import com.beust.kobalt.plugin.kotlin.KotlinPlugin +import com.beust.kobalt.plugin.packaging.PackagingPlugin +import com.beust.kobalt.plugin.publish.PublishPlugin +import com.google.inject.Provider +import java.io.File +import java.io.FileInputStream +import java.lang.reflect.Method +import java.lang.reflect.Modifier +import java.net.URL +import java.net.URLClassLoader +import java.nio.charset.Charset +import java.nio.file.Paths +import java.util.ArrayList +import java.util.HashMap +import java.util.jar.JarInputStream +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +public class Plugins @Inject constructor (val taskManagerProvider : Provider, + val files: KFiles, + val depFactory: DepFactory, + val localRepo: LocalRepo, + val executors: KobaltExecutors, + val scriptCompilerFactory: ScriptCompiler.IFactory): KobaltLogger { + + companion object { + public val MANIFEST_PLUGIN_CLASS : String = "Kobalt-Plugin-Class" + + private var pluginMap = hashMapOf() + private var storageMap = HashMap>() + + fun storeValue(pluginName: String, key: String, value: Any) { + var values = storageMap.get(pluginName) + if (values == null) { + values = hashMapOf() + storageMap.put(pluginName, values) + } + values.put(key, value) + } + + fun getValue(pluginName: String, key: String) : Any? { + return storageMap.get(pluginName)?.get(key) + } + + val defaultPlugin : Plugin get() = getPlugin(DefaultPlugin.NAME)!! + + fun addPlugin(pluginClass : Class) { + addPluginInstance(INJECTOR.getInstance(pluginClass)) + } + + private fun addPluginInstance(plugin: Plugin) { + pluginMap.put(plugin.name, plugin) + } + + init { + arrayListOf>( + DefaultPlugin::class.java, + JavaPlugin::class.java, + KotlinPlugin::class.java, + PackagingPlugin::class.java, + PublishPlugin::class.java +// AptPlugin::class.java + ).map { + addPluginInstance(INJECTOR.getInstance(it)) + } + } + + public fun getPlugin(name: String) : Plugin? = pluginMap.get(name) + + public val plugins : List + get() = ArrayList(pluginMap.values()) + + /** + * The list of plugins found in the build file. + */ + public val dynamicPlugins : ArrayList = arrayListOf() + } + + 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 + } + log(2, "Applying plug-in \"${plugin.name}\"") + + 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.equals(Any::class.java))) { + currentClass.declaredMethods.map { + Pair(it, it.getAnnotation(Task::class.java)) + }.filter { + it.second != null + }.filter { + isValidTaskMethod(it.first) + }.forEach { + if (Modifier.isPrivate(it.first.modifiers)) { + throw KobaltException("A task method cannot be private: ${it.first}") + } + val annotation = it.second + + log(3, "Adding MethodTask from @Task: ${it.first} $annotation") + plugin.methodTasks.add(Plugin.MethodTask(it.first, annotation)) + } + + currentClass = currentClass.superclass + } + + // Now plugin.methodTasks contains both tasks from the build file and the plug-ins, we + // can create the whole set of tasks and set up their dependencies + plugin.methodTasks.forEach { methodTask -> + val method = methodTask.method + val annotation = methodTask.taskAnnotation + + fun toTask(m: Method, project: Project, plugin: Plugin): (Project) -> TaskResult { + val result: (Project) -> TaskResult = { + m.invoke(plugin, project) as TaskResult + } + return result + } + + projects.filter { plugin.accept(it) }.forEach { project -> + plugin.addTask(annotation, project, toTask(method, project, plugin)) + annotation.runBefore.forEach { plugin.dependsOn(it, annotation.name) } + annotation.runAfter.forEach { plugin.dependsOn(annotation.name, it) } + + plugin.apply(project, context) + } + } + } + } + + /** + * 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.parameterCount != 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 + } + + /** + * Jar files for all the plugins. + */ + public val pluginJarFiles : ArrayList = arrayListOf() + + val dependencies = arrayListOf() + + /** + * Parse the build files, locate all the plugins, download them and make them available to be + * used on the classpath of the build file. + */ + fun installDynamicPlugins(files: List) { + // + // Extract all the plugin() and repos() code into a separate script (pluginCode) + // + files.forEach { + val pluginCode = arrayListOf() + var parenCount = 0 + it.path.toFile().forEachLine(Charset.defaultCharset()) { line -> + if (line.startsWith("import")) { + pluginCode.add(line) + } + var index = line.indexOf("plugins(") + if (index == -1) index = line.indexOf("repos(") + if (parenCount > 0 || index >= 0) { + if (index == -1) index = 0 + with(line.substring(index)) { + parenCount += line countChar '(' + if (parenCount > 0) { + pluginCode.add(line) + } + parenCount -= line countChar ')' + } + } + } + + // + // Compile and run pluginCode, which contains all the plugins() calls extracted. This + // will add all the dynamic plugins found in this code to Plugins.dynamicPlugins + // + val pluginSourceFile = KFiles.createTempFile(".kt") + pluginSourceFile.writeText(pluginCode.join("\n"), Charset.defaultCharset()) + log(2, "Saved ${pluginSourceFile.absolutePath}") + scriptCompilerFactory.create(pluginJarFiles, + { n: String, j: File? -> instantiateClassName(n, j) + }).compile(BuildFile(Paths.get(pluginSourceFile.absolutePath), "Plugins"), + it.lastModified(), + KFiles.findBuildScriptLocation(it, "preBuildScript.jar")) + } + + // + // Locate all the jar files for the dynamic plugins we just discovered + // + dependencies.addAll(dynamicPlugins.map { + pluginJarFiles.add(it.jarFile.get().absolutePath) + it + }) + + // + // Materialize all the jar files, instantiate their plugin main class and add it to Plugins + // + 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). + depFactory.create(it.id, executor) + + // + // Inspect the jar, open the manifest, instantiate the main class and add it to the plugin repo + // + var fis: FileInputStream? = null + var jis: JarInputStream? = null + try { + fis = FileInputStream(it.jarFile.get()) + jis = JarInputStream(fis) + val manifest = jis.getManifest() + val mainClass = manifest.getMainAttributes().getValue(Plugins.MANIFEST_PLUGIN_CLASS) ?: + throw KobaltException("Couldn't find \"${Plugins.MANIFEST_PLUGIN_CLASS}\" in the " + + "manifest of ${it}") + + val pluginClassName = mainClass.removeSuffix(" ") + val c = instantiateClassName(pluginClassName) + @Suppress("UNCHECKED_CAST") + Plugins.addPlugin(c as Class) + log(1, "Added plugin ${c}") + } finally { + jis?.close() + fis?.close() + } + } + executor.shutdown() + } + + public fun instantiateClassName(className : String, buildScriptJarFile: File? = null) : Class<*> { +// fun jarToUrl(jarAbsolutePath: String) = URL("file://" + jarAbsolutePath) + + fun jarToUrl(path: String) = URL("jar", "", "file:${path}!/") + + // We need the jar files to be first in the url list otherwise the Build.kt files resolved + // might be Kobalt's own + val urls = arrayListOf() + buildScriptJarFile?.let { + urls.add(jarToUrl(it.absolutePath)) + } + urls.add(jarToUrl(files.kobaltJar)) + urls.addAll(pluginJarFiles.map { jarToUrl(it) }) + val classLoader = URLClassLoader(urls.toArray(arrayOfNulls(urls.size()))) + + try { + log(2, "Instantiating ${className}") + return classLoader.loadClass(className) + } catch(ex: Exception) { + throw KobaltException("Couldn't instantiate ${className}: ${ex}") + } + } + + val allTasks : List + get() { + val result = arrayListOf() + Plugins.plugins.forEach { plugin -> + result.addAll(plugin.tasks) + } + return result + } + + /** + * @return the tasks accepted by at least one project + */ + fun findTasks(task: String): List { + val tasks = allTasks.filter { task == it.name } + if (tasks.isEmpty()) { + throw KobaltException("Couldn't find task ${task}") + } else { + return tasks + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/beust/kobalt/ProjectGenerator.kt b/src/main/kotlin/com/beust/kobalt/ProjectGenerator.kt new file mode 100644 index 00000000..7842dcc1 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/ProjectGenerator.kt @@ -0,0 +1,95 @@ +package com.beust.kobalt + +import com.beust.kobalt.api.ICompilerInfo +import com.beust.kobalt.api.Kobalt +import com.beust.kobalt.maven.Pom +import com.beust.kobalt.maven.Pom.Dependency +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltLogger +import com.github.mustachejava.DefaultMustacheFactory +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import java.io.PrintWriter +import java.io.StringWriter +import java.util.ArrayList +import java.util.Collections +import java.util.HashMap + +/** + * Generate a new project. + */ +public class ProjectGenerator : KobaltLogger { + fun run(args: Args) { + if (File(args.buildFile).exists()) { + log(1, "Build file ${args.buildFile} already exists, not overwriting it") + return + } + + val compilerInfos = detect(File(".")) + if (compilerInfos.size() > 1) { + log(1, "Multi language project detected, not supported yet") + } + val map = hashMapOf() + map.put("directive", if (compilerInfos.isEmpty()) "project" else compilerInfos.get(0).directive) + if (compilerInfos.size() > 0) { + compilerInfos.get(0).let { + val currentDir = File(".").absoluteFile.parentFile + map.put("name", currentDir.name) + map.put("group", "com.example") + map.put("version", "0.1") + map.put("directory", currentDir.absolutePath) + map.put("sourceDirectories", it.defaultSourceDirectories) + map.put("sourceDirectoriesTest", it.defaultTestDirectories) + map.put("imports", "import com.beust.kobalt.plugin.${it.name}.*") + map.put("directive", it.name + "Project") + } + } + + var mainDeps = arrayListOf() + var testDeps = arrayListOf() + map.put("mainDependencies", mainDeps) + map.put("testDependencies", testDeps) + if(File("pom.xml").exists()) { + importPom(mainDeps, map, testDeps) + } + + val fileInputStream = javaClass.classLoader.getResource("build-template.mustache").openStream() + val sw = StringWriter() + val pw = PrintWriter(sw) + var mf = DefaultMustacheFactory(); + var mustache = mf.compile(InputStreamReader(fileInputStream), "kobalt"); + mustache.execute(pw, map).flush(); + KFiles.saveFile(File(args.buildFile), sw.toString()) + } + + private fun importPom(mainDeps: ArrayList, map: HashMap, testDeps: ArrayList) { + var pom = Pom("imported", File("pom.xml")) + map.put("group", pom.groupId ?: "com.example") + map.put("artifactId", pom.artifactId ?: "com.example") + map.put("version", pom.version ?: "0.1") + map.put("name", pom.name ?: pom.artifactId) + val partition = pom.dependencies.groupBy { it.scope } + // .filter { it.key == null } + .flatMap { it.value } + .sortedBy { it.groupId + ":" + it.artifactId } + .partition { it.scope != "test" } + mainDeps.addAll(partition.first) + testDeps.addAll(partition.second) + } + + /** + * Detect all the languages contained in this project. + */ + private fun detect(dir: File) : List { + val result = arrayListOf>>() + Kobalt.compilers.forEach { + val managedFiles = it.findManagedFiles(dir) + if (managedFiles.size() > 0) { + result.add(Pair(it, managedFiles)) + } + } + Collections.sort(result, { p1, p2 -> p1.second.size().compareTo(p2.second.size()) }) + return result.map { it.first } + } +} diff --git a/src/main/kotlin/com/beust/kobalt/Template.kt b/src/main/kotlin/com/beust/kobalt/Template.kt new file mode 100644 index 00000000..a62649fb --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/Template.kt @@ -0,0 +1,20 @@ +package com.beust.kobalt + +import java.io.BufferedReader +import java.io.PrintWriter +import java.io.Reader + +public class Template(val reader: Reader, val writer: PrintWriter, val map: Map) { + + public fun execute() { + BufferedReader(reader).let { + it.forEachLine { line -> + var replacedLine = line + map.keySet().forEach { key -> + replacedLine = replacedLine.replace("{{${key}}}", map.get(key).toString(), false) + } + writer.append(replacedLine).append("\n") + } + } + } +} diff --git a/src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt b/src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt new file mode 100644 index 00000000..0f2d9247 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/api/BasePlugin.kt @@ -0,0 +1,20 @@ +package com.beust.kobalt.api + +import com.beust.kobalt.BasePluginTask +import com.beust.kobalt.Plugins +import com.beust.kobalt.internal.TaskManager +import com.beust.kobalt.internal.TaskResult +import java.util.ArrayList +import java.util.concurrent.Callable +import kotlin.properties.Delegates + +abstract public class BasePlugin : Plugin { + override val tasks: ArrayList = arrayListOf() + override var taskManager : TaskManager by Delegates.notNull() + override var methodTasks = arrayListOf() + + override fun accept(project: Project) = true + + var plugins : Plugins by Delegates.notNull() + +} diff --git a/src/main/kotlin/com/beust/kobalt/api/Kobalt.kt b/src/main/kotlin/com/beust/kobalt/api/Kobalt.kt new file mode 100644 index 00000000..624418a3 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/api/Kobalt.kt @@ -0,0 +1,93 @@ +package com.beust.kobalt.api + +import com.beust.kobalt.misc.Topological +import com.google.common.collect.ArrayListMultimap +import java.io.File +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Paths +import java.util.* + +public interface ICompilerInfo { + /** Used to detect what kind of language this project is */ + fun findManagedFiles(dir: File) : List + + /** Used to generate the imports */ + val name: String + + /** Used to generate the imports */ + val directive: String + + val defaultSourceDirectories : ArrayList + val defaultTestDirectories : ArrayList +} + +public class Kobalt { + companion object { + public val compilers : ArrayList = arrayListOf() + + public fun registerCompiler(c: ICompilerInfo) { + compilers.add(c) + } + + private val DEFAULT_REPOS = arrayListOf( + "http://repo1.maven.org/maven2/", + "https://repository.jboss.org/nexus/content/repositories/root_repository/", + "https://jcenter.bintray.com/" + ) + + val repos = ArrayList(DEFAULT_REPOS) + + fun addRepo(repo: String) = repos.add(repo) + + private val PROPERTY_KOBALT_VERSION = "kobalt.version" + private val KOBALT_PROPERTIES = "kobalt.properties" + private val LOCAL_PROPERTIES = "local.properties" + + private val properties : Properties + get() = readProperties() + + 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 IllegalArgumentException("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).let { + readProperties(result, it) + } + } + } + + return result + } + + private fun readProperties(properties: Properties, ins: InputStream) { + properties.load(ins) + ins.close() + properties.forEach { es -> System.setProperty(es.getKey().toString(), es.getValue().toString()) } + } + + val version = properties.getProperty(PROPERTY_KOBALT_VERSION) + + val topological = Topological() + + fun declareProjectDependencies(project: Project, projects: Array) { + topological.addEdge(project, projects) + } + + /** + * @return the projects sorted topologically. + */ + fun sortProjects(allProjects: ArrayList) : List + = topological.sort(allProjects) + } +} diff --git a/src/main/kotlin/com/beust/kobalt/api/KobaltContext.kt b/src/main/kotlin/com/beust/kobalt/api/KobaltContext.kt new file mode 100644 index 00000000..e98360b4 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/api/KobaltContext.kt @@ -0,0 +1,6 @@ +package com.beust.kobalt.api + +import com.beust.kobalt.Args + +public class KobaltContext(val args: Args) + diff --git a/src/main/kotlin/com/beust/kobalt/api/Plugin.kt b/src/main/kotlin/com/beust/kobalt/api/Plugin.kt new file mode 100644 index 00000000..961f7292 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/api/Plugin.kt @@ -0,0 +1,34 @@ +package com.beust.kobalt.api + +import com.beust.kobalt.BasePluginTask +import com.beust.kobalt.api.annotation.Task +import com.beust.kobalt.internal.TaskManager +import com.beust.kobalt.internal.TaskResult +import com.beust.kobalt.internal.TaskResult2 +import java.lang.reflect.Method +import java.util.ArrayList + +public interface Plugin { + val name: String + val tasks : ArrayList + fun accept(project: Project) : Boolean + fun apply(project: Project, context: KobaltContext) {} + + class MethodTask(val method: Method, val taskAnnotation: Task) + val methodTasks : ArrayList + + fun addTask(annotation: Task, project: Project, task: (Project) -> TaskResult) { + tasks.add(object : BasePluginTask(this, annotation.name, annotation.description, project) { + override fun call(): TaskResult2 { + val taskResult = task(project) + return TaskResult2(taskResult.success, this) + } + }) + } + + var taskManager : TaskManager + + fun dependsOn(task1: String, task2: String) { + taskManager.dependsOn(task1, task2) + } +} diff --git a/src/main/kotlin/com/beust/kobalt/api/PluginTask.kt b/src/main/kotlin/com/beust/kobalt/api/PluginTask.kt new file mode 100644 index 00000000..8f1b7378 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/api/PluginTask.kt @@ -0,0 +1,17 @@ +package com.beust.kobalt.api + +import com.beust.kobalt.internal.TaskResult2 +import com.beust.kobalt.misc.ToString +import java.util.concurrent.Callable + +public interface PluginTask : Callable> { + val plugin: Plugin + val name: String + val doc: String + val project: Project + val dependsOn : List + + override public fun toString() : String { + return ToString("PluginTask", "id", project.name + ":" + name).s + } +} diff --git a/src/main/kotlin/com/beust/kobalt/api/Project.kt b/src/main/kotlin/com/beust/kobalt/api/Project.kt new file mode 100644 index 00000000..a9914542 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/api/Project.kt @@ -0,0 +1,96 @@ +package com.beust.kobalt.api + +import com.beust.kobalt.api.annotation.Directive +import com.beust.kobalt.maven.MavenDependency +import com.beust.kobalt.maven.IClasspathDependency +import com.google.common.base.Preconditions +import java.util.ArrayList + +open public class Project( + open var name: String? = null, + open var version: String? = null, + open var directory: String = ".", + open var buildDirectory: String? = "kobaltBuild", + open var group: String? = null, + open var artifactId: String? = null, + open var dependencies: Dependencies? = null, + open var sourceSuffix : String = "", + open var compilerInfo : ICompilerInfo ) { + + var testArgs: ArrayList = arrayListOf() + + override fun equals(other: Any?): Boolean { + return name == (other as Project).name + } + + override fun hashCode(): Int { + return name!!.hashCode() + } + + // + // Directories + // + @Directive + public fun sourceDirectories(init: Sources.() -> Unit) : Sources { + val sources = Sources(this, sourceDirectories) + sources.init() + return sources + } + + var sourceDirectories : ArrayList = arrayListOf() + get() = if (field.isEmpty()) compilerInfo.defaultSourceDirectories else field + set(value) { + field = value + } + + @Directive + public fun sourceDirectoriesTest(init: Sources.() -> Unit) : Sources { + val sources = Sources(this, sourceDirectoriesTest) + sources.init() + return sources + } + + var sourceDirectoriesTest : ArrayList = arrayListOf() + get() = if (field.isEmpty()) compilerInfo.defaultTestDirectories + else field + set(value) { + field = value + } + + // + // Dependencies + // + + @Directive + public fun dependencies(init: Dependencies.() -> Unit) : Dependencies { + dependencies = Dependencies(this, compileDependencies) + dependencies!!.init() + return dependencies!! + } + + public val compileDependencies : ArrayList = arrayListOf() + + @Directive + public fun dependenciesTest(init: Dependencies.() -> Unit) : Dependencies { + dependencies = Dependencies(this, testDependencies) + dependencies!!.init() + return dependencies!! + } + + public val testDependencies : ArrayList = arrayListOf() +} + +public class Sources(val project: Project, val sources: ArrayList) { + @Directive + public fun path(vararg paths: String) { + sources.addAll(paths) + } +} + +public class Dependencies(val project: Project, val dependencies: ArrayList) { + @Directive + fun compile(vararg dep: String) { + dep.forEach { dependencies.add(MavenDependency.create(it)) } + } +} + diff --git a/src/main/kotlin/com/beust/kobalt/api/Task.kt b/src/main/kotlin/com/beust/kobalt/api/Task.kt new file mode 100644 index 00000000..696596a6 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/api/Task.kt @@ -0,0 +1,9 @@ +package com.beust.kobalt.api + +import com.beust.kobalt.misc.ToString + +data public class Task(val pluginName: String, val taskName: String) { + override public fun toString() : String { + return ToString("Task", pluginName, taskName).s + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/beust/kobalt/api/annotation/Annotations.kt b/src/main/kotlin/com/beust/kobalt/api/annotation/Annotations.kt new file mode 100644 index 00000000..6896a5cc --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/api/annotation/Annotations.kt @@ -0,0 +1,11 @@ +package com.beust.kobalt.api.annotation + +import kotlin.annotation.Retention + +annotation class Directive + +@Retention(AnnotationRetention.RUNTIME) +annotation class Task(val name: String, + val description: String, + val runBefore: Array = arrayOf(), + val runAfter: Array = arrayOf()) diff --git a/src/main/kotlin/com/beust/kobalt/internal/DynamicGraph.kt b/src/main/kotlin/com/beust/kobalt/internal/DynamicGraph.kt new file mode 100644 index 00000000..081ddb97 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/internal/DynamicGraph.kt @@ -0,0 +1,231 @@ +package com.beust.kobalt.internal + +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.misc.NamedThreadFactory +import com.beust.kobalt.misc.ToString +import com.google.common.collect.ArrayListMultimap +import java.util.* +import java.util.concurrent.* + +open class TaskResult2(success: Boolean, val value: T) : TaskResult(success) { + override fun toString() = ToString("TaskResult", "success", success, "value", value).s +} + +public interface IWorker : Callable> { + /** + * @return list of tasks this worker is working on. + */ + // val tasks : List + + /** + * @return the priority of this task. + */ + val priority : Int +} + +public interface IThreadWorkerFactory { + + /** + * Creates {@code IWorker} for specified set of tasks. It is not necessary that + * number of workers returned be same as number of tasks entered. + * + * @param nodes tasks that need to be executed + * @return list of workers + */ + fun createWorkers(nodes: List) : List> +} + +public class DynamicGraphExecutor(val graph: DynamicGraph, + val factory: IThreadWorkerFactory) : KobaltLogger { + val executor = Executors.newFixedThreadPool(5, NamedThreadFactory("DynamicGraphExecutor")) + val completion = ExecutorCompletionService>(executor) + + public fun run() { + while (graph.freeNodes.size() > 0) { + log(2, "Current count: ${graph.nodeCount}") + synchronized(graph) { + val freeNodes = graph.freeNodes + freeNodes.forEach { graph.setStatus(it, DynamicGraph.Status.RUNNING)} + log(2, "submitting free nodes ${freeNodes}") + val callables : List> = factory.createWorkers(freeNodes) + callables.forEach { completion.submit(it) } + var n = callables.size() + + // When a callable ends, see if it freed a node. If not, keep looping + while (n > 0 && graph.freeNodes.size() == 0) { + try { + val future = completion.take() + val taskResult = future.get(2, TimeUnit.SECONDS) + log(2, "Received task result ${taskResult}") + n-- + graph.setStatus(taskResult.value, + if (taskResult.success) DynamicGraph.Status.FINISHED else DynamicGraph.Status.ERROR) + } catch(ex: TimeoutException) { + log(2, "Time out") + } + } + } + } + executor.shutdown() + } +} + +/** + * Representation of the graph of methods. + */ +public class DynamicGraph : KobaltLogger { + private val DEBUG = false + + private val nodesReady = linkedSetOf() + private val nodesRunning = linkedSetOf() + private val nodesFinished = linkedSetOf() + private val nodesInError = linkedSetOf() + private val nodesSkipped = linkedSetOf() + private val dependedUpon = ArrayListMultimap.create() + private val dependingOn = ArrayListMultimap.create() + + /** + * Define a comparator for the nodes of this graph, which will be used + * to order the free nodes when they are asked. + */ + public val comparator : Comparator? = null + + enum class Status { + READY, RUNNING, FINISHED, ERROR, SKIPPED + } + + /** + * Add a node to the graph. + */ + public fun addNode(value: T) : T { + nodesReady.add(value) + return value + } + + /** + * Add an edge between two nodes, which don't have to already be in the graph + * (they will be added by this method). Makes "to" depend on "from". + */ + public fun addEdge(from: T, to: T) { + val fromNode = addNode(from) + val toNode = addNode(to) + dependingOn.put(toNode, fromNode) + dependedUpon.put(fromNode, toNode) + } + + /** + * @return a set of all the nodes that don't depend on any other nodes. + */ + public val freeNodes : List + get() { + val result = arrayListOf() + nodesReady.forEach { m -> + // A node is free if... + + val du = dependedUpon.get(m) + // - no other nodes depend on it + if (! dependedUpon.containsKey(m)) { + result.add(m) + } else if (getUnfinishedNodes(du).size() == 0) { + result.add(m) + } + } + + // Sort the free nodes if requested (e.g. priorities) + // if (! result.isEmpty()) { + // if (comparator != null) { + // Collections.sort(result, comparator) + // debug("Nodes after sorting:" + result.get(0)) + // } + // } + + log(2, "freeNodes: ${result}") + return result + } + + /** + * @return a list of all the nodes that have a status other than FINISHED. + */ + private fun getUnfinishedNodes(nodes: List) : Collection { + val result = hashSetOf() + nodes.forEach { node -> + if (nodesReady.contains(node) || nodesRunning.contains(node)) { + result.add(node); + } + } + return result; + } + + /** + * Set the status for a set of nodes. + */ + public fun setStatus(nodes: Collection, status: Status) { + nodes.forEach { setStatus(it, status) } + } + + /** + * Mark all dependees of this node SKIPPED + */ + private fun setSkipStatus(node: T, status: Status) { + dependingOn.get(node).forEach { + if (! nodesSkipped.contains(it)) { + log(2, "Node skipped: ${it}") + nodesSkipped.add(it) + nodesReady.remove(it) + setSkipStatus(it, status) + } + } + } + + /** + * Set the status for a node. + */ + public fun setStatus(node: T, status: Status) { + removeNode(node); + when(status) { + Status.READY -> nodesReady.add(node) + Status.RUNNING -> nodesRunning.add(node) + Status.FINISHED -> nodesFinished.add(node) + Status.ERROR -> { + log(2, "Node in error: ${node}") + nodesReady.remove(node) + nodesInError.add(node) + setSkipStatus(node, status) + } + else -> { + throw IllegalArgumentException() + } + } + } + + private fun removeNode(node: T) { + if (! nodesReady.remove(node)) { + if (! nodesRunning.remove(node)) { + nodesFinished.remove(node) + } + } + } + + /** + * @return the number of nodes in this graph. + */ + public val nodeCount: Int + get() = nodesReady.size() + nodesRunning.size() + nodesFinished.size() + + override public fun toString() : String { + val result = StringBuilder("[DynamicGraph ") + result.append("\n Ready:" + nodesReady) + result.append("\n Running:" + nodesRunning) + result.append("\n Finished:" + nodesFinished) + result.append("\n Edges:\n") + // dependingOn.entrySet().forEach { es -> + // result.append(" " + es.getKey() + "\n"); + // es.getValue().forEach { t -> + // result.append(" " + t + "\n"); + // } + // } + result.append("]"); + return result.toString(); + } +} + diff --git a/src/main/kotlin/com/beust/kobalt/internal/GenericRunner.kt b/src/main/kotlin/com/beust/kobalt/internal/GenericRunner.kt new file mode 100644 index 00000000..e753b389 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/internal/GenericRunner.kt @@ -0,0 +1,50 @@ +package com.beust.kobalt.internal + +import com.beust.kobalt.api.Project +import com.beust.kobalt.maven.IClasspathDependency +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.plugin.java.JavaInfo +import com.beust.kobalt.plugin.java.SystemProperties +import java.io.File + +abstract class GenericTestRunner(open val project: Project, open val classpath: List) + : KobaltLogger { + abstract val mainClass: String + abstract val args: List + + protected fun findTestClasses(): List { + val path = KFiles.joinDir(project.directory, project.buildDirectory!!, KFiles.TEST_CLASSES_DIR) + val result = KFiles.findRecursively(File(path), + arrayListOf(File("."))) { file -> file.endsWith("Test.class") + }.map { + it.replace("/", ".").replace(".class", "").substring(2) + } + return result + } + + fun runTests() { + val jvm = JavaInfo.create(File(SystemProperties.javaBase)) + val java = jvm.javaExecutable + val allArgs = arrayListOf() + allArgs.add(java!!.getAbsolutePath()) + allArgs.add("-classpath") + allArgs.add(classpath.map { it.jarFile.get().getAbsolutePath() }.join(File.pathSeparator)) + allArgs.add(mainClass) + allArgs.addAll(args) + + val pb = ProcessBuilder(allArgs) + pb.directory(File(project.directory)) + pb.inheritIO() + log(1, "Running tests with classpath size ${classpath.size()}") + val process = pb.start() + val errorCode = process.waitFor() + if (errorCode == 0) { + log(1, "All tests passed") + } else { + log(1, "Test failures") + } + + } +} + diff --git a/src/main/kotlin/com/beust/kobalt/internal/JUnitRunner.kt b/src/main/kotlin/com/beust/kobalt/internal/JUnitRunner.kt new file mode 100644 index 00000000..b357cffc --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/internal/JUnitRunner.kt @@ -0,0 +1,17 @@ +package com.beust.kobalt.internal + +import com.beust.kobalt.api.Project +import com.beust.kobalt.maven.IClasspathDependency +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.plugin.java.JavaInfo +import com.beust.kobalt.plugin.java.SystemProperties +import java.io.File + +public class JUnitRunner(override val project: Project, override val classpath: List) + : GenericTestRunner(project, classpath) { + + override val mainClass = "org.junit.runner.JUnitCore" + override val args = findTestClasses() +} + diff --git a/src/main/kotlin/com/beust/kobalt/internal/JvmCompilerPlugin.kt b/src/main/kotlin/com/beust/kobalt/internal/JvmCompilerPlugin.kt new file mode 100644 index 00000000..fa50c1b3 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/internal/JvmCompilerPlugin.kt @@ -0,0 +1,120 @@ +package com.beust.kobalt.internal + +import com.beust.kobalt.api.BasePlugin +import com.beust.kobalt.api.Project +import com.beust.kobalt.api.annotation.Directive +import com.beust.kobalt.api.annotation.Task +import com.beust.kobalt.maven.* +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltExecutors +import com.beust.kobalt.misc.KobaltLogger +import java.io.File +import java.util.ArrayList +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +abstract public class JvmCompilerPlugin @Inject constructor( + open val localRepo: LocalRepo, + open val files: KFiles, + open val depFactory: DepFactory, + open val dependencyManager: DependencyManager, + open val executors: KobaltExecutors) : BasePlugin(), KobaltLogger { + + companion object { + const val TASK_CLEAN = "clean" + const val TASK_TEST = "test" + + const val SOURCE_SET_MAIN = "main" + const val SOURCE_SET_TEST = "test" + const val DOCS_DIRECTORY = "docs/javadoc" + } + + /** + * Log with a project. + */ + protected fun lp(project: Project, s: String) { + log(1, "${project.name}: ${s}") + } + + fun calculateClasspath(dependencies : List): List { + return dependencyManager.transitiveClosure(dependencies) + } + + protected fun testDependencies(project: Project) : List { + val result = arrayListOf() + result.add(FileDependency(makeOutputDir(project).getAbsolutePath())) + result.add(FileDependency(makeOutputTestDir(project).getAbsolutePath())) + result.addAll(calculateClasspath(project.compileDependencies)) + result.addAll(calculateClasspath(project.testDependencies)) + return dependencyManager.reorderDependencies(result) + } + + @Task(name = TASK_TEST, description = "Run the tests", runAfter = arrayOf("compile", "compileTest")) + fun taskTest(project: Project) : TaskResult { + lp(project, "Running tests") + if (project.testDependencies.any { it.id.contains("testng")} ) { + TestNgRunner(project, testDependencies(project)).runTests() + } else { + JUnitRunner(project, testDependencies(project)).runTests() + } + return TaskResult() + } + + @Task(name = TASK_CLEAN, description = "Clean the project", runBefore = arrayOf("compile")) + fun taskClean(project : Project ) : TaskResult { + java.io.File(project.buildDirectory).deleteRecursively() + return TaskResult() + } + + protected fun makeOutputDir(project: Project) : File = makeDir(project, KFiles.CLASSES_DIR) + + protected fun makeOutputTestDir(project: Project) : File = makeDir(project, KFiles.TEST_CLASSES_DIR) + + private fun makeDir(project: Project, suffix: String) : File { + return File(project.directory, project.buildDirectory + File.separator + suffix) + } + + /** + * Copy the resources from a source directory to the build one + */ + protected fun copyResources(project: Project, sourceSet: String) { + val sourceDirs: ArrayList = arrayListOf() + var outputDir: String? + if (sourceSet == "main") { + sourceDirs.addAll(project.sourceDirectories.filter { it.contains("resources") }) + outputDir = KFiles.CLASSES_DIR + } else if (sourceSet == "test") { + sourceDirs.addAll(project.sourceDirectoriesTest.filter { it.contains("resources") }) + outputDir = KFiles.TEST_CLASSES_DIR + } else { + throw IllegalArgumentException("Custom source sets not supported yet: ${sourceSet}") + } + + if (sourceDirs.size() > 0) { + lp(project, "Copying ${sourceSet} resources") + val absOutputDir = File(KFiles.joinDir(project.directory, project.buildDirectory!!, outputDir)) + sourceDirs.map { File(it) }.filter { it.exists() } .forEach { + log(2, "Copying from ${sourceDirs} to ${absOutputDir}") + KFiles.copyRecursively(it, absOutputDir) + } + } else { + lp(project, "No resources to copy for ${sourceSet}") + } + } +} + + +public class TestConfig(val project: Project) { + fun args(vararg arg: String) { + project.testArgs.addAll(arg) + } +} + +@Directive +public fun test(project: Project, init: TestConfig.() -> Unit) : TestConfig { + val result = TestConfig(project) + result.init() + return result +} + diff --git a/src/main/kotlin/com/beust/kobalt/internal/PluginLoader.kt b/src/main/kotlin/com/beust/kobalt/internal/PluginLoader.kt new file mode 100644 index 00000000..cb0d3471 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/internal/PluginLoader.kt @@ -0,0 +1,7 @@ +package com.beust.kobalt.internal + +import java.util.jar.JarInputStream + +public class PluginLoader(val jarStream: JarInputStream) { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/beust/kobalt/internal/RunnableTask.kt b/src/main/kotlin/com/beust/kobalt/internal/RunnableTask.kt new file mode 100644 index 00000000..ad93be6e --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/internal/RunnableTask.kt @@ -0,0 +1,9 @@ +package com.beust.kobalt.internal + +import com.beust.kobalt.Plugins +import com.beust.kobalt.api.BasePlugin +import com.beust.kobalt.maven.KobaltException +import java.lang.reflect.Method +import java.util.concurrent.Callable + +open public class TaskResult(val success: Boolean = true, val errorMessage: String? = null) diff --git a/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt b/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt new file mode 100644 index 00000000..1b336e67 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt @@ -0,0 +1,169 @@ +package com.beust.kobalt.internal + +import com.beust.kobalt.Plugins +import com.beust.kobalt.api.PluginTask +import com.beust.kobalt.api.Project +import com.beust.kobalt.api.Task +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.maven.KobaltException +import com.google.common.collect.ArrayListMultimap +import com.google.common.collect.HashMultimap +import com.google.common.collect.TreeMultimap +import java.util.HashSet +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +public class TaskManager @Inject constructor(val plugins: Plugins) : KobaltLogger { + private val dependentTaskMap = TreeMultimap.create() + + /** + * Called by plugins to indicate task dependencies defined at runtime. Keys depend on values. + * Declare that `task1` depends on `task2`. + */ + fun dependsOn(task1: String, task2: String) { + dependentTaskMap.put(task1, task2) + } + + public fun runTargets(targets: List, projects: List) { + val tasksByNames = HashMultimap.create() + + projects.forEach { project -> + log(1, "") + log(1, " Building project ${project.name}") + log(1, "") + + // + // Locate all the tasks + // + plugins.allTasks.filter { it.project.name == project.name }.forEach { rt -> + tasksByNames.put(rt.name, rt) + if (rt.dependsOn.size() > 0) { + rt.dependsOn.forEach { d -> + dependentTaskMap.put(rt.name, d) + } + } + } + + val freeTaskMap = hashMapOf() + tasksByNames.keySet().forEach { + if (!dependentTaskMap.containsKey(it)) freeTaskMap.put(it, tasksByNames.get(it).elementAt(0)) + } + + log(2, "Free tasks: ${freeTaskMap.keySet()}") + log(2, "Dependent tasks:") + dependentTaskMap.keySet().forEach { t -> + log(2, " ${t} -> ${dependentTaskMap.get(t)}}") + } + + // + // Find the tasks required to run the targets and add them to the dynamic graph + // + val transitiveClosure = hashSetOf() + val seen = HashSet(targets) + val toProcess = HashSet(targets) + var done = false + while (!done) { + val newToProcess = hashSetOf() + log(3, "toProcess size: " + toProcess.size()) + toProcess.forEach { target -> + log(3, "Processing ${target}") + val actualTarget = + if (target.contains(":")) { + // The target specifies a project explicitly + target.split(":").let { + val projectName = it[0] + if (projectName == project.name) { + it[1] + } else { + null + } + } + } else { + target + } + if (actualTarget != null) { + transitiveClosure.add(actualTarget) + val tasks = tasksByNames.get(actualTarget) + if (tasks.isEmpty()) { + throw KobaltException("Unknown task: ${target}") + } + tasks.forEach { task -> + val dependencyNames = dependentTaskMap.get(task.name) + dependencyNames.forEach { dependencyName -> + if (!seen.contains(dependencyName)) { + newToProcess.add(dependencyName) + seen.add(dependencyName) + } + } + } + } else { + log(2, "Target ${target} specified so not running it for project ${project.name}") + } + } + done = newToProcess.isEmpty() + toProcess.clear() + toProcess.addAll(newToProcess) + } + + // + // Create a dynamic graph with the transitive closure + // + val graph = DynamicGraph() + freeTaskMap.values().filter { transitiveClosure.contains(it.name) } forEach { graph.addNode(it) } + dependentTaskMap.entries().filter { + transitiveClosure.contains(it.key) + }.forEach { entry -> + plugins.findTasks(entry.key).filter { it.project.name == project.name }.forEach { from -> + plugins.findTasks(entry.value).filter { it.project.name == project.name }.forEach { to -> + if (from.project.name == to.project.name) { + graph.addEdge(from, to) + } + } + } + } + + // + // Run the dynamic graph + // + val factory = object : IThreadWorkerFactory { + override public fun createWorkers(nodes: List): List> { + val result = arrayListOf>() + nodes.forEach { + result.add(TaskWorker(arrayListOf(it))) + } + return result + } + } + + val executor = DynamicGraphExecutor(graph, factory) + executor.run() + } + } +} + +class TaskWorker(val tasks: List) : IWorker, KobaltLogger { +// override fun compareTo(other: IWorker2): Int { +// return priority.compareTo(other.priority) +// } + + override fun call() : TaskResult2 { + if (tasks.size() > 0) { + tasks.get(0).let { + log(1, "========== ${it.project.name}:${it.name}") + } + } + var success = true + tasks.forEach { + val tr = it.call() + success = success and tr.success + } + return TaskResult2(success, tasks.get(0)) + } + +// override val timeOut : Long = 10000 + + override val priority: Int = 0 +} \ No newline at end of file diff --git a/src/main/kotlin/com/beust/kobalt/internal/TestNgRunner.kt b/src/main/kotlin/com/beust/kobalt/internal/TestNgRunner.kt new file mode 100644 index 00000000..1dff75f4 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/internal/TestNgRunner.kt @@ -0,0 +1,34 @@ +package com.beust.kobalt.internal + +import com.beust.kobalt.api.Project +import com.beust.kobalt.maven.IClasspathDependency +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.plugin.java.JavaInfo +import com.beust.kobalt.plugin.java.SystemProperties +import java.io.File +import java.util.* + +public class TestNgRunner(override val project: Project, override val classpath: List) + : GenericTestRunner(project, classpath) { + override val mainClass = "org.testng.TestNG" + + override val args: List + get() { + arrayListOf().let { + if (project.testArgs.size() > 0) { + it.addAll(project.testArgs) + } else { + val testngXml = File(project.directory, KFiles.joinDir("src", "test", "resources", "testng.xml")) + if (testngXml.exists()) { + it.add(testngXml.getAbsolutePath()) + } else { + it.add("-testclass") + it.addAll(findTestClasses()) + } + } + return it + } + + } +} diff --git a/src/main/kotlin/com/beust/kobalt/kotlin/BuildFile.kt b/src/main/kotlin/com/beust/kobalt/kotlin/BuildFile.kt new file mode 100644 index 00000000..1a946b9b --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/kotlin/BuildFile.kt @@ -0,0 +1,18 @@ +package com.beust.kobalt.kotlin + +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.attribute.BasicFileAttributes + +/** + * Sometimes, build files are moved to temporary files, so we give them a specific name for clarity. + */ +public class BuildFile(val path: Path, val name: String) { + public fun exists() : Boolean = Files.exists(path) + + public fun lastModified() : Long = Files.readAttributes(path, BasicFileAttributes::class.java).lastModifiedTime() + .toMillis() + + public val directory : File get() = path.toFile().directory +} diff --git a/src/main/kotlin/com/beust/kobalt/kotlin/ScriptCompiler.kt b/src/main/kotlin/com/beust/kobalt/kotlin/ScriptCompiler.kt new file mode 100644 index 00000000..bd890601 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/kotlin/ScriptCompiler.kt @@ -0,0 +1,129 @@ +package com.beust.kobalt.kotlin + +import com.beust.kobalt.Plugins +import com.beust.kobalt.api.Kobalt +import com.beust.kobalt.api.Plugin +import com.beust.kobalt.api.Project +import com.beust.kobalt.api.annotation.Task +import com.beust.kobalt.maven.KobaltException +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.plugin.kotlin.kotlinCompiler +import com.beust.kobalt.plugin.kotlin.kotlinCompilePrivate +import com.google.inject.assistedinject.Assisted +import java.io.File +import java.io.FileInputStream +import java.io.InputStream +import java.lang.reflect.Modifier +import java.util.jar.JarInputStream +import javax.inject.Inject +import kotlin.properties.Delegates + +/** + * Compile Build.kt into a jar file + */ +public class ScriptCompiler @Inject constructor( + @Assisted("jarFiles") val jarFiles: List, + @Assisted("instantiate") val instantiate: (String, File?) -> Class<*>, + val files: KFiles) : KobaltLogger { + + interface IFactory { + fun create(@Assisted("jarFiles") jarFiles: List, + @Assisted("instantiate") instantiate: (String, File?) -> Class<*>) : ScriptCompiler + } + + private var buildScriptJarFile by Delegates.notNull() + + public class CompileOutput(val projects: List, val plugins: List) + + public fun compile(buildFile: BuildFile, lastModified: Long, jarFileName: String) : CompileOutput { + + if (! buildFile.exists()) { + throw KobaltException("Couldn't find ${buildFile.name}") + } + + buildScriptJarFile = File(jarFileName) + buildScriptJarFile.parentFile.mkdirs() + + log(2, "Running build file ${buildFile.name} jar: ${buildScriptJarFile}") + + if (buildFile.exists() && buildScriptJarFile.exists() + && lastModified < buildScriptJarFile.lastModified()) { + log(2, "Build file is up to date") + } else { + log(2, "Need to recompile ${buildFile.name}") + generateJarFile(buildFile) + } + return CompileOutput(instantiateBuildFile(), arrayListOf()) + } + + private fun generateJarFile(buildFile: BuildFile) { + kotlinCompilePrivate { + classpath(files.kobaltJar) + classpath(jarFiles) + sourceFiles(buildFile.path.toFile().absolutePath) + output = buildScriptJarFile.absolutePath + }.compile() + } + + private fun instantiateBuildFile() : List { + val result = arrayListOf() + var stream : InputStream? = null + try { + stream = JarInputStream(FileInputStream(buildScriptJarFile)) + var entry = stream.nextJarEntry + + val classes = hashSetOf>() + while (entry != null) { + val name = entry.name; + if (name.endsWith(".class")) { + val className = name.substring(0, name.length() - 6).replace("/", ".") + var cl : Class<*>? = instantiate(className, buildScriptJarFile) + if (cl != null) { + classes.add(cl) + } else { + throw KobaltException("Couldn't instantiate ${className}") + } + } + entry = stream.nextJarEntry; + } + + // Invoke all the "val" found on the _DefaultPackage class (the Build.kt file) + classes.filter { cls -> + cls.name != "_DefaultPackage" + }.forEach { cls -> + cls.methods.forEach { method -> + // Invoke vals and see if they return a Project + if (method.name.startsWith("get") && Modifier.isStatic(method.modifiers)) { + val r = method.invoke(null) + if (r is Project) { + log(2, "Found project ${r} in class ${cls}") + result.add(r) + } + } else { + val taskAnnotation = method.getAnnotation(Task::class.java) + if (taskAnnotation != null) { +// Plugins.defaultPlugin.addTask(taskAnnotation, ) + Plugins.defaultPlugin.methodTasks.add(Plugin.MethodTask(method, taskAnnotation)) + } + + }} +// cls.methods.filter { method -> +// method.getName().startsWith("get") && Modifier.isStatic(method.getModifiers()) +// }.forEach { +// val r = it.invoke(null) +// if (r is Project) { +// log(2, "Found project ${r} in class ${cls}") +// result.add(r) +// } +// } + } + } finally { + stream?.close() + } + + // Now that we all the projects, sort them topologically + return Kobalt.sortProjects(result) + } +} + diff --git a/src/main/kotlin/com/beust/kobalt/maven/ArtifactFetcher.kt b/src/main/kotlin/com/beust/kobalt/maven/ArtifactFetcher.kt new file mode 100644 index 00000000..a7cf8b48 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/ArtifactFetcher.kt @@ -0,0 +1,102 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.file +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.plugin.packaging.JarUtils +import com.google.common.cache.CacheBuilder +import com.google.common.cache.CacheLoader +import com.google.common.cache.LoadingCache +import com.google.inject.assistedinject.Assisted +import java.io.ByteArrayOutputStream +import java.io.File +import java.security.MessageDigest +import java.util.concurrent.Callable +import java.util.concurrent.ExecutorService +import java.util.concurrent.Future +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class DownloadManager @Inject constructor(val factory: ArtifactFetcher.IFactory) { + class Key(val url: String, val fileName: String, val executor: ExecutorService) { + override fun equals(other: Any?): Boolean { + return (other as Key).url == url + } + + override fun hashCode(): Int { + return url.hashCode() + } + } + + private val CACHE : LoadingCache> = CacheBuilder.newBuilder() + .build(object : CacheLoader>() { + override fun load(key: Key): Future { + return key.executor.submit(factory.create(key.url, key.fileName)) + } + }) + + public fun download(url: String, fileName: String, executor: ExecutorService) + : Future = CACHE.get(Key(url, fileName, executor)) +} + +/** + * Fetches an artifact (a file in a Maven repo, .jar, -javadoc.jar, ...) to the given local file. + */ +class ArtifactFetcher @Inject constructor(@Assisted("url") val url: String, + @Assisted("fileName") val fileName: String, + val files: KFiles, val http: Http) : Callable, KobaltLogger { + interface IFactory { + fun create(@Assisted("url") url: String, @Assisted("fileName") fileName: String) : ArtifactFetcher + } + + /** The Kotlin compiler is about 17M and downloading it with the default buffer size takes forever */ + private val estimatedSize: Int + get() = if (url.contains("kotlin-compiler")) 18000000 else 1000000 + + private fun toMd5(bytes: ByteArray) : String { + val result = StringBuilder() + val md5 = MessageDigest.getInstance("MD5").digest(bytes) + md5.forEach { + val byte = it.toInt() and 0xff + if (byte < 16) result.append("0") + result.append(Integer.toHexString(byte)) + } + return result.toString() + } + + private fun getBytes(url: String) : ByteArray { + log(2, "${url}: downloading to ${fileName}") + val body = http.get(url) + if (body.code == 200) { + val buffer = ByteArrayOutputStream(estimatedSize) + body.getAsStream().copyTo(buffer, estimatedSize) + return buffer.toByteArray() + } else { + throw KobaltException("${url}: failed to download, code: ${body.code}") + } + } + + override fun call() : File { + val md5Body = http.get(url + ".md5") + val remoteMd5 = if (md5Body.code == 200) { + md5Body.getAsString().trim(' ', '\t', '\n').substring(0, 32) + } else { + null + } + + val file = File(fileName) + file.parentFile.mkdirs() + val bytes = getBytes(url) + if (remoteMd5 != null && remoteMd5 != toMd5(bytes)) { + throw KobaltException("MD5 not matching for ${url}") + } else { + log(2, "No md5 found for ${url}, skipping md5 check") + } + files.saveFile(file, bytes) + + log(1, "${url}: DOWNLOADED") + + return file + } +} diff --git a/src/main/kotlin/com/beust/kobalt/maven/CompletedFuture.kt b/src/main/kotlin/com/beust/kobalt/maven/CompletedFuture.kt new file mode 100644 index 00000000..f64c32b5 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/CompletedFuture.kt @@ -0,0 +1,13 @@ +package com.beust.kobalt.maven + +import java.util.concurrent.Future +import java.util.concurrent.TimeUnit + +public class CompletedFuture(val value: T) : Future { + override fun cancel(mayInterruptIfRunning: Boolean) = true + override fun get(): T = value + override fun get(timeout: Long, unit: TimeUnit): T = value + override fun isCancelled(): Boolean = false + override fun isDone(): Boolean = true +} + diff --git a/src/main/kotlin/com/beust/kobalt/maven/DepFactory.kt b/src/main/kotlin/com/beust/kobalt/maven/DepFactory.kt new file mode 100644 index 00000000..f67eda89 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/DepFactory.kt @@ -0,0 +1,49 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.maven.DownloadManager +import com.beust.kobalt.maven.KobaltException +import com.beust.kobalt.maven.Pom +import com.beust.kobalt.misc.KobaltExecutors +import com.beust.kobalt.misc.KobaltLogger +import java.util.ArrayList +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ExecutorService +import javax.inject.Inject + +public class DepFactory @Inject constructor(val localRepo: LocalRepo, + val repoFinder: RepoFinder, + val executors: KobaltExecutors, + val downloadManager: DownloadManager, + val pomFactory: Pom.IFactory) : KobaltLogger { + + /** + * Parse the id and return the correct IClasspathDependency + */ + public fun create(id: String, executor: ExecutorService, + localFirst : Boolean = true) : IClasspathDependency { + if (id.startsWith(IClasspathDependency.PREFIX_FILE)) { + return FileDependency(id.substring(IClasspathDependency.PREFIX_FILE.length())) + } else { + val c = id.split(":") + var repoResult: RepoFinder.RepoResult? + var version: String? = null + + if (! MavenDependency.hasVersion(id)) { + if (localFirst) version = localRepo.findLocalVersion(c[0], c[1]) + if (! localFirst || version == null) { + repoResult = repoFinder.findCorrectRepo(id) + if (!repoResult.found) { + throw KobaltException("Couldn't resolve ${id}") + } else { + version = repoResult.version + } + } + } else { + version = c[2] + } + + return MavenDependency(c[0], c[1], version, executor, localRepo, repoFinder, + pomFactory, downloadManager) + } + } +} diff --git a/src/main/kotlin/com/beust/kobalt/maven/DependencyManager.kt b/src/main/kotlin/com/beust/kobalt/maven/DependencyManager.kt new file mode 100644 index 00000000..bf709c40 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/DependencyManager.kt @@ -0,0 +1,55 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.misc.KobaltExecutors +import com.google.common.collect.ArrayListMultimap +import java.util.Collections +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +public class DependencyManager @Inject constructor(val executors: KobaltExecutors, + val depFactory: DepFactory){ + fun transitiveClosure(dependencies : List): List { + var executor = executors.newExecutor("JvmCompiler}", 10) + + var result = hashSetOf() + + dependencies.forEach { projectDependency -> + projectDependency.id.let { + result.add(depFactory.create(it, executor)) + val downloaded = projectDependency.transitiveDependencies(executor) + + result.addAll(downloaded) + } + } + + val result2 = reorderDependencies(result).filter { + // Only keep existent files (nonexistent files are probably optional dependencies) + it.jarFile.get().exists() + } + + executor.shutdown() + + return result2 + } + + /** + * Reorder dependencies so that if an artifact appears several times, only the one with the higest version + * is included. + */ + public fun reorderDependencies(dependencies: Collection): List { + val result = arrayListOf() + val map : ArrayListMultimap = ArrayListMultimap.create() + // The multilist maps each artifact to a list of all the versions found + // (e.g. {org.testng:testng -> (6.9.5, 6.9.4, 6.1.1)}), then we return just the first one + dependencies.forEach { + map.put(it.shortId, it) + } + for (k in map.keySet()) { + val l = map.get(k) + Collections.sort(l, Collections.reverseOrder()) + result.add(l.get(0)) + } + return result + } +} diff --git a/src/main/kotlin/com/beust/kobalt/maven/Http.kt b/src/main/kotlin/com/beust/kobalt/maven/Http.kt new file mode 100644 index 00000000..b727a2a5 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/Http.kt @@ -0,0 +1,81 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.misc.KobaltLogger +import com.squareup.okhttp.* +import java.io.File +import java.io.IOException +import java.io.InputStream +import javax.inject.Singleton + +@Singleton +public class Http : KobaltLogger { + class Body(val body: ResponseBody, val code: Int) { + public fun getAsString() : String { + return body.string() + } + public fun getAsStream() : InputStream { + return body.byteStream() + } + } + + public fun get(user: String?, password: String?, url: String) : Body { + val client = OkHttpClient(); + val request = Request.Builder().url(url) + if (user != null) { + request.header("Authorization", Credentials.basic(user, password)) + } + + try { + val response = client.newCall(request.build()).execute() + return Body(response.body(), response.code()) + } catch(ex: IOException) { + throw KobaltException("Could not load URL ${url}, error: " + ex.getMessage(), ex) + } + } + + private val MEDIA_TYPE_BINARY = MediaType.parse("application/octet-stream") + + public fun get(url: String) : Body { + return get(null, null, url) + } + + private fun builder(user: String?, password: String?) : Request.Builder { + val result = Request.Builder() + user?.let { + result.header("Authorization", Credentials.basic(user, password)) + } + return result + } + + public fun uploadFile(user: String?, password: String?, url: String, file: File, + success: (Response) -> Unit, + error: (Response) -> Unit) { + log(2, "Uploading ${file} to ${url}") + val request = builder(user, password) + .url(url) + .put(RequestBody.create(MEDIA_TYPE_BINARY, file)) + .build() + + val response = OkHttpClient().newCall(request).execute() + if (! response.isSuccessful()) { + error(response) + } else { + success(response) + } + } + + private val JSON = MediaType.parse("application/json; charset=utf-8") + + fun post(user: String?, password: String?, url: String, payload: String) : String { + val request = builder(user, password) + .url(url) + .post(RequestBody.create(JSON, payload)) + .build() + val response = OkHttpClient().newCall(request).execute() + return response.body().string() + } +} + +class KobaltException(s: String? = null, ex: Throwable? = null) : RuntimeException(s, ex) { + constructor(ex: Throwable?) : this(null, ex) +} diff --git a/src/main/kotlin/com/beust/kobalt/maven/IClasspathDependency.kt b/src/main/kotlin/com/beust/kobalt/maven/IClasspathDependency.kt new file mode 100644 index 00000000..3ca91115 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/IClasspathDependency.kt @@ -0,0 +1,75 @@ +package com.beust.kobalt.maven + +import java.io.File +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ExecutorService +import java.util.concurrent.Future + +interface IClasspathDependency { + companion object { + val PREFIX_FILE: String = "file:" + } + + /** Identifier for this dependency */ + val id: String + + /** Absolute path to the jar file on the local file system */ + val jarFile: Future + + /** Convert to a Maven model tag */ + fun toMavenDependencies() : org.apache.maven.model.Dependency + + /** 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 + + fun transitiveDependencies(executor: ExecutorService) : List { + /** + * All the dependencies we have already downloaded. + */ + val seen = ConcurrentHashMap() + + val thisDep = MavenDependency.create(id, executor) + + var result = ArrayList(transitiveDependencies(thisDep, seen, executor)) + result.add(thisDep) + return result + } + + private fun transitiveDependencies(dep: IClasspathDependency, seen: ConcurrentHashMap, + executor: ExecutorService) : List { + val result = arrayListOf() + seen.put(dep.id, dep.id) + dep.directDependencies().filter { + ! seen.containsKey(it.id) + }.forEach { + seen.put(it.id, it.id) + val thisDep = MavenDependency.create(it.id, executor) + result.add(thisDep) + result.addAll(transitiveDependencies(thisDep, seen, executor)) + } + return result + } +} + +open public class FileDependency(open val fileName: String) : IClasspathDependency, Comparable { + override val id = IClasspathDependency.PREFIX_FILE + fileName + + override val jarFile = CompletedFuture(File(fileName)) + + override fun toMavenDependencies(): org.apache.maven.model.Dependency { + with(org.apache.maven.model.Dependency()) { + setSystemPath(jarFile.get().getAbsolutePath()) + return this + } + } + + override val shortId = fileName + + override fun directDependencies() = arrayListOf() + + override fun compareTo(other: FileDependency) = fileName.compareTo(other.fileName) +} diff --git a/src/main/kotlin/com/beust/kobalt/maven/LocalDep.kt b/src/main/kotlin/com/beust/kobalt/maven/LocalDep.kt new file mode 100644 index 00000000..7d9553f6 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/LocalDep.kt @@ -0,0 +1,19 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.maven.CompletedFuture +import com.beust.kobalt.misc.Strings +import java.io.File +import java.util.concurrent.Future +import kotlin.properties.Delegates + +open public class LocalDep(override val groupId: String, override val artifactId: String, + override val version: String, + open val localRepo: LocalRepo) : SimpleDep(groupId, artifactId, version) { + + fun toAbsoluteJarFilePath(v: String) = localRepo.toFullPath(toJarFile(v)) + + fun toAbsolutePomFile(v: String): String { + return localRepo.toFullPath(Strings.Companion.join("/", arrayListOf(toPomFile(v)))) + } + +} diff --git a/src/main/kotlin/com/beust/kobalt/maven/LocalRepo.kt b/src/main/kotlin/com/beust/kobalt/maven/LocalRepo.kt new file mode 100644 index 00000000..06f80177 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/LocalRepo.kt @@ -0,0 +1,61 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.misc.Versions +import com.beust.kobalt.plugin.java.SystemProperties +import java.io.File +import java.util.Collections +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +open public class LocalRepo(open val localRepo: String = KFiles.localRepo) : KobaltLogger { + init { + val l = File(localRepo) + if (! l.exists()) { + l.mkdirs() + } + } + + fun existsPom(d: LocalDep, v: String) : Boolean { + return File(d.toAbsolutePomFile(v)).exists() + } + + fun existsJar(d: LocalDep, v: String) : Boolean { + return File(d.toAbsoluteJarFilePath(v)).exists() + } + + /** + * If the dependency is local, return the correct version for it + */ + fun findLocalVersion(groupId: String, artifactId: String) : String? { + // No version: look at all the directories under group/artifactId, pick the latest and see + // if it contains a maven and jar file + val dir = toFullPath(KFiles.joinDir(groupId.replace(".", File.separator), artifactId)) + val files = File(dir).listFiles() + + if (files != null) { + val directories = files.filter { it.isDirectory } + if (directories.size() > 0) { + Collections.sort(directories, { f1, f2 -> + val v1 = Versions.toLongVersion(f1.name) + val v2 = Versions.toLongVersion(f2.name) + v2.compareTo(v1) // we want the most recent at position 0 + }) + val result = directories.get(0).name + val newDep = LocalDep(groupId, artifactId, result, this) + if (existsPom(newDep, result) && existsJar(newDep, result)) { + return result + } + } + } + return null + } + + fun toFullPath(path: String) : String { + return localRepo + File.separatorChar + path + } +} + + diff --git a/src/main/kotlin/com/beust/kobalt/maven/MavenDependency.kt b/src/main/kotlin/com/beust/kobalt/maven/MavenDependency.kt new file mode 100644 index 00000000..209f310a --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/MavenDependency.kt @@ -0,0 +1,99 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.INJECTOR +import com.beust.kobalt.misc.* +import com.google.common.base.CharMatcher +import com.google.inject.Key +import com.google.inject.assistedinject.Assisted +import java.io.File +import java.util.concurrent.ExecutorService +import java.util.concurrent.Future +import javax.inject.Inject +import kotlin.properties.Delegates + +public class MavenDependency @Inject constructor(override @Assisted("groupId") val groupId : String, + override @Assisted("artifactId") val artifactId : String, + override @Assisted("version") val version : String, + val executor: ExecutorService, + override val localRepo: LocalRepo, + val repoFinder: RepoFinder, + val pomFactory: Pom.IFactory, + val downloadManager: DownloadManager) + : LocalDep(groupId, artifactId, version, localRepo), KobaltLogger, IClasspathDependency, + Comparable { + override var jarFile: Future by Delegates.notNull() + var pomFile: Future by Delegates.notNull() + + init { + val jar = File(localRepo.toFullPath(toJarFile(version))) + val pom = File(localRepo.toFullPath(toPomFile(version))) + if (jar.exists() && pom.exists()) { + jarFile = CompletedFuture(jar) + pomFile = CompletedFuture(pom) + } else { + val repoResult = repoFinder.findCorrectRepo(toId(groupId, artifactId, version)) + if (repoResult.found) { + jarFile = downloadManager.download(repoResult.repoUrl + toJarFile(repoResult), jar.absolutePath, + executor) + pomFile = downloadManager.download(repoResult.repoUrl + toPomFile(repoResult), pom.absolutePath, + executor) + } else { + throw KobaltException("Couldn't resolve ${toId(groupId, artifactId, version)}") + } + } + } + +// interface IFactory { +// fun _create(@Assisted("groupId") groupId: String, +// @Assisted("artifactId") artifactId: String, +// @Assisted("version") version: String = "", +// executor: ExecutorService) : MavenDependency +// } + + companion object { + val executor = INJECTOR.getInstance(Key.get(ExecutorService::class.java, DependencyExecutor::class.java)) + val depFactory = INJECTOR.getInstance(DepFactory::class.java) + + fun create(id: String, ex: ExecutorService = executor) : IClasspathDependency { + return depFactory.create(id, ex) + } + + fun hasVersion(id: String) : Boolean { + val c = id.split(":") + return c.size() == 3 && !Strings.isEmpty(c[2]) + } + + fun toId(g: String, a: String, v: String) = "$g:$a:$v" + } + + + public override fun toString() = toId(groupId, artifactId, version) + + override val id = toId(groupId, artifactId, version) + + override fun toMavenDependencies(): org.apache.maven.model.Dependency { + with(org.apache.maven.model.Dependency()) { + setGroupId(groupId) + setArtifactId(artifactId) + setVersion(version) + return this + } + } + + override fun compareTo(other: MavenDependency): Int { + return Versions.toLongVersion(version).compareTo(Versions.toLongVersion(other.version)) + } + + override val shortId = groupId + ":" + artifactId + + override fun directDependencies() : List { + val result = arrayListOf() + pomFactory.create(id, pomFile.get()).dependencies.filter { + it.mustDownload && it.isValid + }.forEach { + result.add(create(toId(it.groupId, it.artifactId, it.version))) + } + return result + } +} + diff --git a/src/main/kotlin/com/beust/kobalt/maven/Pom.kt b/src/main/kotlin/com/beust/kobalt/maven/Pom.kt new file mode 100644 index 00000000..d5537735 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/Pom.kt @@ -0,0 +1,95 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.misc.ToString +import com.google.inject.assistedinject.Assisted +import org.w3c.dom.Element +import org.w3c.dom.NodeList +import org.w3c.dom.Text +import org.xml.sax.InputSource +import java.io.FileReader +import javax.xml.xpath.XPathConstants +import kotlin.dom.get +import kotlin.dom.toXmlString + +public class Pom @javax.inject.Inject constructor(@Assisted val id: String, + @Assisted documentFile: java.io.File) : KobaltLogger { + val XPATH_FACTORY = javax.xml.xpath.XPathFactory.newInstance(); + val XPATH = XPATH_FACTORY.newXPath(); + var groupId: String? = null + var artifactId: String? = null + var version: String? = null + var name: String? = null + var properties = sortedMapOf() + + public interface IFactory { + fun create(@Assisted id: String, @Assisted documentFile : java.io.File) : Pom + } + + data public class Dependency(val groupId: String, val artifactId: String, val version: String, + val optional: Boolean = false, val scope: String? = null) : KobaltLogger { + + /** When a variable is used in a maven file, e.g. ${version} */ + private val VAR = "$" + "{" + + val mustDownload: Boolean + get() = ! optional && "provided" != scope && "test" != scope + + val isValid : Boolean + get() { + var result = false + if (version.contains(VAR)) { + log(3, "Skipping variable version ${this}") + } else if (groupId.contains(VAR)) { + log(3, "Skipping variable groupId ${this}") + } else if (artifactId.contains(VAR)) { + log(3, "Skipping variable artifactId ${this}") + } else { + result = true + } + return result + } + + + val id: String = "${groupId}:${artifactId}:${version}" + } + + var dependencies = arrayListOf() + + init { + val DEPENDENCIES = XPATH.compile("/project/dependencies/dependency") + + val document = kotlin.dom.parseXml(InputSource(FileReader(documentFile))) + groupId = XPATH.compile("/project/groupId").evaluate(document) + artifactId = XPATH.compile("/project/artifactId").evaluate(document) + version = XPATH.compile("/project/version").evaluate(document) + name = XPATH.compile("/project/name").evaluate(document) + + val deps = DEPENDENCIES.evaluate(document, XPathConstants.NODESET) as NodeList + for (i in 0..deps.getLength() - 1) { + val d = deps.item(i) as NodeList + var groupId: String? = null + var artifactId: String? = null + var version: String = "" + var optional: Boolean? = false + var scope: String? = null + for (j in 0..d.getLength() - 1) { + val e = d.item(j) + if (e is Element) { + when (e.getTagName()) { + "groupId" -> groupId = e.getTextContent () + "artifactId" -> artifactId = e.getTextContent() + "version" -> version = e.getTextContent() + "optional" -> optional = "true".equals(e.getTextContent(), true) + "scope" -> scope = e.getTextContent() + } + } + } + log(3, "Done parsing: ${groupId} ${artifactId} ${version}") + val tmpDependency = Dependency(groupId!!, artifactId!!, version, optional!!, scope) + dependencies.add(tmpDependency) + } + } + + override public fun toString() = ToString("Pom", id, "id").s +} diff --git a/src/main/kotlin/com/beust/kobalt/maven/PomGenerator.kt b/src/main/kotlin/com/beust/kobalt/maven/PomGenerator.kt new file mode 100644 index 00000000..a6be2dbe --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/PomGenerator.kt @@ -0,0 +1,51 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.api.Project +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.plugin.java.SystemProperties +import com.google.common.base.Preconditions +import com.google.inject.assistedinject.Assisted +import org.apache.maven.model.Developer +import org.apache.maven.model.Model +import org.apache.maven.model.io.xpp3.MavenXpp3Writer +import java.io.File +import java.io.StringWriter +import java.nio.charset.Charset +import javax.inject.Inject + +public class PomGenerator @Inject constructor(@Assisted val project: Project) : KobaltLogger { + interface IFactory { + fun create(project: Project) : PomGenerator + } + + fun generate() { + Preconditions.checkNotNull(project.version, "version mandatory on project ${project.name}") + Preconditions.checkNotNull(project.artifactId, "artifactId mandatory on project ${project.name}") + val m = Model().apply { + name = project.name + artifactId = project.artifactId + groupId = project.group + version = project.version + } + with(Developer()) { + name = SystemProperties.username + m.addDeveloper(this) + } + + val dependencies = arrayListOf() + m.dependencies = dependencies + project.compileDependencies.forEach { dep -> + dependencies.add(dep.toMavenDependencies()) + } + + val s = StringWriter() + MavenXpp3Writer().write(s, m) + + val buildDir = com.beust.kobalt.misc.KFiles.makeDir(project.directory, project.buildDirectory!!) + val outputDir = com.beust.kobalt.misc.KFiles.makeDir(buildDir.path, "libs") + val pomFile = SimpleDep(project.group!!, project.artifactId!!, project.version!!).toPomFileName() + val outputFile = File(outputDir, pomFile) + outputFile.writeText(s.toString(), Charset.defaultCharset()) + log(1, "Wrote ${outputFile}") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/beust/kobalt/maven/RepoFinder.kt b/src/main/kotlin/com/beust/kobalt/maven/RepoFinder.kt new file mode 100644 index 00000000..7559e173 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/RepoFinder.kt @@ -0,0 +1,145 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.api.Kobalt +import com.beust.kobalt.misc.KobaltExecutors +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.misc.Strings +import com.google.common.cache.CacheBuilder +import com.google.common.cache.CacheLoader +import com.google.common.cache.LoadingCache +import java.util.concurrent.Callable +import java.util.concurrent.ExecutorCompletionService +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.xml.xpath.XPathConstants +import javax.xml.xpath.XPathFactory +import kotlin.dom.parseXml + +/** + * Find the repo that contains the given dependency among a list of repos. Searches are performed in parallel and + * cached so we never make a network call for the same dependency more than once. + */ +public class RepoFinder @Inject constructor(val http: Http, val executors: KobaltExecutors) : KobaltLogger { + public fun findCorrectRepo(id: String): RepoResult { + return FOUND_REPOS.get(id) + } + + data class RepoResult(val repoUrl: String, val found: Boolean, val version: String, + val snapshotVersion: String = "") + + private val FOUND_REPOS: LoadingCache = CacheBuilder.newBuilder() + .build(object : CacheLoader() { + override fun load(key: String): RepoResult { + return loadCorrectRepo(key) + }}) + + /** + * Schedule an HTTP request to each repo in its own thread. + */ + private fun loadCorrectRepo(id: String): RepoResult { + val executor = executors.newExecutor("RepoFinder-${id}", Kobalt.repos.size()) + val cs = ExecutorCompletionService(executor) + + try { + log(2, "Looking for ${id}") + Kobalt.repos.forEach { cs.submit(RepoFinderCallable(id, it)) } + for (i in 0..Kobalt.repos.size() - 1) { + try { + val result = cs.take().get(2000, TimeUnit.MILLISECONDS) + log(2, "Result for repo #${i}: ${result}") + if (result.found) { + log(2, "Located ${id} in ${result.repoUrl}") + return result + } + } catch(ex: Exception) { + warn("Error: ${ex}") + } + } + return RepoResult("", false, id) + } finally { + executor.shutdownNow() + } + } + + /** + * Execute a single HTTP request to one repo. + */ + + inner class RepoFinderCallable(val id: String, val repoUrl: String) : Callable { + override fun call(): RepoResult { + log(2, "Checking ${repoUrl} for ${id}") + + val c = id.split(":") + if (! MavenDependency.hasVersion(id)) { + val ud = UnversionedDep(c[0], c[1]) + val foundVersion = findCorrectVersionRelease(ud.toMetadataXmlPath(false), repoUrl) + if (foundVersion != null) { + return RepoResult(repoUrl, true, foundVersion) + } else { + return RepoResult(repoUrl, false, "") + } + } else { + if (c[2].contains("SNAPSHOT")) { + val dep = SimpleDep(c[0], c[1], c[2]) + val snapshotVersion = findSnapshotVersion(dep.toMetadataXmlPath(false), repoUrl) + if (snapshotVersion != null) { + return RepoResult(repoUrl, true, c[2], snapshotVersion) + } else { + return RepoResult(repoUrl, false, "") + } + } else { + val dep = SimpleDep(c[0], c[1], c[2]) + val url = repoUrl + "/" + dep.toJarFile(dep.version) + val body = http.get(url) + log(2, "Result for ${repoUrl} for ${id}: ${body.code == 200}") + return RepoResult(repoUrl, body.code == 200, dep.version) + } + } + } + } + + val XPATH_FACTORY = XPathFactory.newInstance(); + val XPATH = XPATH_FACTORY.newXPath(); + + fun findCorrectVersionRelease(metadataPath: String, repoUrl: String): String? { + val XPATHS = arrayListOf( + XPATH.compile("/metadata/version"), + XPATH.compile("/metadata/versioning/latest"), + XPATH.compile("/metadata/versioning/release")) + // No version in this dependency, find out the most recent one by parsing maven-metadata.xml, if it exists + val url = repoUrl + metadataPath + try { + val doc = parseXml(url) + arrayListOf(XPATHS.forEach { + val result = it.evaluate(doc, XPathConstants.STRING) as String + if (! result.isEmpty()) { + return result + } + }) + } catch(ex: Exception) { + log(2, "Couldn't find metadata at ${url}") + } + return null + } + + fun findSnapshotVersion(metadataPath: String, repoUrl: String): String? { + val timestamp = XPATH.compile("/metadata/versioning/snapshot/timestamp") + val buildNumber = XPATH.compile("/metadata/versioning/snapshot/buildNumber") + // No version in this dependency, find out the most recent one by parsing maven-metadata.xml, if it exists + val url = repoUrl + metadataPath + try { + val doc = parseXml(url) + val ts = timestamp.evaluate(doc, XPathConstants.STRING) + val bn = buildNumber.evaluate(doc, XPathConstants.STRING) + if (! Strings.isEmpty(ts.toString()) && ! Strings.isEmpty(bn.toString())) { + return ts.toString() + "-" + bn.toString() + } + } catch(ex: Exception) { + log(2, "Couldn't find metadata at ${url}") + } + return null + } + + + +} diff --git a/src/main/kotlin/com/beust/kobalt/maven/SimpleDep.kt b/src/main/kotlin/com/beust/kobalt/maven/SimpleDep.kt new file mode 100644 index 00000000..d39795dd --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/SimpleDep.kt @@ -0,0 +1,33 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.misc.Strings +import com.google.common.base.CharMatcher +import java.io.File +import kotlin.properties.Delegates + +open public class SimpleDep(override val groupId: String, override val artifactId: String, + open val version: String) : UnversionedDep(groupId, artifactId) { + companion object { + fun create(id: String) = id.split(":").let { SimpleDep(it[0], it[1], it[2])} + } + + override public fun toMetadataXmlPath(fileSystem: Boolean): String { + return toDirectory(version, fileSystem) + "/maven-metadata.xml" + } + + private fun toFile(v: String, s: String, suffix: String) : String { + val fv = if (v.contains("SNAPSHOT")) v.replace("SNAPSHOT", "") else v + return Strings.join("/", arrayListOf(toDirectory(v), + artifactId + "-" + fv + s + suffix)) + } + + fun toPomFile(v: String) = toFile(v, "", ".pom") + + fun toPomFile(r: RepoFinder.RepoResult) = toFile(r.version, r.snapshotVersion, ".pom") + + fun toJarFile(v: String) = toFile(v, "", ".jar") + + fun toJarFile(r: RepoFinder.RepoResult) = toFile(r.version, r.snapshotVersion, ".jar") + + fun toPomFileName() = "${artifactId}-${version}.pom" +} diff --git a/src/main/kotlin/com/beust/kobalt/maven/UnversionedDep.kt b/src/main/kotlin/com/beust/kobalt/maven/UnversionedDep.kt new file mode 100644 index 00000000..96d8a7d5 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/maven/UnversionedDep.kt @@ -0,0 +1,24 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.misc.Strings +import java.io.File + +/** + * Represents a dependency that doesn't have a version: "org.testng:testng:". Such dependencies + * eventually resolve to the latest version of the artifact. + */ +open public class UnversionedDep(open val groupId: String, open val artifactId: String) { + open public fun toMetadataXmlPath(fileSystem: Boolean = true): String { + return toDirectory("", fileSystem) + "/maven-metadata.xml" + } + + /** + * Turn this dependency to a directory. If fileSystem is true, use the file system + * dependent path separator, otherwise, use '/' (used to create URL's) + */ + public fun toDirectory(v: String, fileSystem: Boolean = true): String { + val sep = if (fileSystem) File.separator else "/" + val l = listOf(groupId.replace(".", sep), artifactId, v) + return Strings.Companion.join(sep, l) + } +} diff --git a/src/main/kotlin/com/beust/kobalt/misc/Benchmarks.kt b/src/main/kotlin/com/beust/kobalt/misc/Benchmarks.kt new file mode 100644 index 00000000..8452b1bd --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/misc/Benchmarks.kt @@ -0,0 +1,8 @@ +package com.beust.kobalt.misc + +public fun benchmark(message: String, run: () -> Unit) { + val start = System.currentTimeMillis() + run() + println("############# Time to ${message}: ${System.currentTimeMillis() - start} ms") +} + diff --git a/src/main/kotlin/com/beust/kobalt/misc/CheckVersions.kt b/src/main/kotlin/com/beust/kobalt/misc/CheckVersions.kt new file mode 100644 index 00000000..76f94712 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/misc/CheckVersions.kt @@ -0,0 +1,44 @@ +package com.beust.kobalt.misc + +import com.beust.kobalt.api.Kobalt +import com.beust.kobalt.api.Project +import com.beust.kobalt.maven.DepFactory +import com.beust.kobalt.maven.MavenDependency +import com.google.inject.Inject + +/** + * Find out if any newer versions of the dependencies are available. + */ +public class CheckVersions @Inject constructor(val depFactory : DepFactory, + val executors : KobaltExecutors) : KobaltLogger { + + fun run(projects: List) { + val executor = executors.newExecutor("CheckVersions", 5) + + val newVersions = hashSetOf() + projects.forEach { + val kobaltDependency = arrayListOf( + MavenDependency.create("com.beust:kobalt:" + Kobalt.version, executor)) + arrayListOf(kobaltDependency, it.compileDependencies, it.testDependencies).forEach { cd -> + cd.forEach { + val dep = depFactory.create(it.shortId, executor, false /* go remote */) + if (dep is MavenDependency && it is MavenDependency) { + if (dep.id != it.id + && Versions.toLongVersion(dep.version) + > Versions.toLongVersion(it.version)) { + newVersions.add(dep.id) + } + } + } + } + } + + if (newVersions.size() > 0) { + log(1, "New versions found:") + newVersions.forEach { log(1, " ${it}") } + } else { + log(1, "All dependencies up to date") + } + executor.shutdown() + } +} diff --git a/src/main/kotlin/com/beust/kobalt/misc/KFiles.kt b/src/main/kotlin/com/beust/kobalt/misc/KFiles.kt new file mode 100644 index 00000000..33cd310a --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/misc/KFiles.kt @@ -0,0 +1,181 @@ +package com.beust.kobalt.misc + +import com.beust.kobalt.api.Kobalt +import com.beust.kobalt.homeDir +import com.beust.kobalt.kotlin.BuildFile +import com.beust.kobalt.maven.KobaltException +import com.beust.kobalt.plugin.java.SystemProperties +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption + +public class KFiles { + val kobaltJar : String + get() = joinDir(distributionsDir, Kobalt.version, "kobalt/wrapper/kobalt-" + Kobalt.version + ".jar") + + init { + File(KOBALT_DOT_DIR).mkdirs() + } + + companion object { + private const val KOBALT_DOT_DIR : String = ".kobalt" + const val KOBALT_DIR : String = "kobalt" + + // Directories under ~/.kobalt + public val localRepo = homeDir(KOBALT_DOT_DIR, "repository") + + /** Where all the .zip files are extracted */ + public val distributionsDir = homeDir(KOBALT_DOT_DIR, "wrapper", "dist") + + // Directories under ./.kobalt + public val SCRIPT_BUILD_DIR : String = "build" + public val CLASSES_DIR : String = "classes" + + /** Where build file and support source files go, under KOBALT_DIR */ + private val SRC = "src" + + public val TEST_CLASSES_DIR : String = "test-classes" + + public fun joinDir(vararg ts: String): String = ts.toArrayList().join(File.separator) + + public fun makeDir(dir: String, s: String) : File { + val result = File(dir, s) + result.mkdirs() + return result + } + + public fun findRecursively(rootDir: File) : List = + findRecursively(rootDir, arrayListOf(), { s -> true }) + + public fun findRecursively(rootDir: File, directories: List, + function: Function1): List { + var result = arrayListOf() + + val allDirs = arrayListOf() + if (directories.isEmpty()) { + allDirs.add(rootDir) + } else { + allDirs.addAll(directories.map { File(rootDir, it.getPath()) }) + } + + allDirs.forEach { + if (! it.exists()) { + KobaltLogger.log(2, "Couldn't find directory ${it}") + } else { + result.addAll(findRecursively(it, function)) + } + } + // Return files relative to rootDir + val r = result.map { it.substring(rootDir.getAbsolutePath().length() + 1)} + return r + } + + public fun findRecursively(directory: File, function: Function1): List { + var result = arrayListOf() + directory.listFiles().forEach { + if (it.isFile && function(it.absolutePath)) { + result.add(it.absolutePath) + } else if (it.isDirectory) { + result.addAll(findRecursively(it, function)) + } + } + return result + } + + fun copyRecursively(from: File, to: File) { + // Need to wait until copyRecursively supports an overwrite: Boolean = false parameter + // Until then, wipe everything first + to.deleteRecursively() + to.mkdirs() + from.copyRecursively(to) + } + + fun findDotDir(startDir: File) : File { + var result = startDir + while (result != null && ! File(result, KOBALT_DOT_DIR).exists()) { + result = result.parentFile + } + if (result == null) { + throw IllegalArgumentException("Couldn't locate ${KOBALT_DOT_DIR} in ${startDir}") + } + return File(result, KOBALT_DOT_DIR) + } + + /** + * The build location for build scripts is .kobalt/build + */ + fun findBuildScriptLocation(buildFile: BuildFile, jarFile: String) : String { + val result = joinDir(findDotDir(buildFile.directory).absolutePath, KFiles.SCRIPT_BUILD_DIR, jarFile) + KobaltLogger.log(2, "Script jar file: ${result}") + return result + } + + public fun saveFile(file: File, text: String) { + file.absoluteFile.parentFile.mkdirs() + file.delete() + KobaltLogger.log(2, "Wrote ${file}") + file.appendText(text) + } + + private fun isWindows() = System.getProperty("os.name").contains("Windows"); + + public fun copy(from: Path?, to: Path?, option: StandardCopyOption) { + if (isWindows() && to!!.toFile().exists()) { + KobaltLogger.log(2, "Windows detected, not overwriting ${to!!}") + } else { + try { + Files.copy(from, to, option) + } catch(ex: IOException) { + // Windows is anal about this + KobaltLogger.log(1, "Couldn't copy ${from} to ${to}: ${ex.getMessage()}") + } + } + } + + public fun copy(from: InputStream, to: OutputStream, bufSize: Int): Long { + val buf = ByteArray(bufSize) + var total: Long = 0 + while (true) { + val r = from.read(buf, 0, buf.size()) + if (r == -1) { + break + } + to.write(buf, 0, r) + total += r.toLong() + } + return total + } + + public fun createTempFile(suffix : String = "", deleteOnExit: Boolean = false) : File = + File.createTempFile("kobalt", suffix, File(SystemProperties.tmpDir)).let { + if (deleteOnExit) it.deleteOnExit() + return it + } + + fun src(filePath: String): String = KFiles.joinDir(KOBALT_DIR, SRC, filePath) + } + + public fun findRecursively(directory: File, function: Function1): List { + return KFiles.findRecursively(directory, function) + } + + public fun findRecursively(rootDir: File, directories: List, + function: Function1): List { + return KFiles.findRecursively(rootDir, directories, function) + } + + public fun saveFile(file: File, bytes: ByteArray) { + file.parentFile.mkdirs() + val os = file.outputStream() + try { + os.write(bytes) + } finally { + os.close() + } + } + +} diff --git a/src/main/kotlin/com/beust/kobalt/misc/KobaltExecutors.kt b/src/main/kotlin/com/beust/kobalt/misc/KobaltExecutors.kt new file mode 100644 index 00000000..3f2976f0 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/misc/KobaltExecutors.kt @@ -0,0 +1,84 @@ +package com.beust.kobalt.misc + +import com.beust.kobalt.maven.KobaltException +import com.google.inject.Provides +import java.util.concurrent.* +import javax.inject.Singleton +import kotlin.properties.Delegates + +class NamedThreadFactory(val n: String) : ThreadFactory { + private val PREFIX = "K-" + + public val name: String + get() = PREFIX + n + + override + public fun newThread(r: Runnable) : Thread { + val result = Thread(r) + result.setName(name + "-" + result.getId()) + return result + } +} + +class KobaltExecutor(name: String, threadCount: Int) + : KobaltLogger, ThreadPoolExecutor(threadCount, threadCount, 5L, TimeUnit.SECONDS, + LinkedBlockingQueue(), NamedThreadFactory(name)) { + + override protected fun afterExecute(r: Runnable, t: Throwable?) { + super.afterExecute(r, t) + var ex : Throwable? = null + if (t == null && r is Future<*>) { + try { + if (r.isDone()) r.get(); + } catch (ce: CancellationException) { + ex = ce; + } catch (ee: ExecutionException) { + ex = ee.getCause(); + } catch (ie: InterruptedException) { + Thread.currentThread().interrupt(); // ignore/reset + } + } + if (ex != null) { + error(if (ex.getMessage() != null) ex.getMessage()!! else ex.javaClass.toString()) + } + } +} + +public class KobaltExecutors : KobaltLogger { + public fun newExecutor(name: String, threadCount: Int) : ExecutorService + = KobaltExecutor(name, threadCount) + + var dependencyExecutor = newExecutor("Dependency", 5) + + public fun shutdown() { + dependencyExecutor.shutdown() + } + + public fun completionService(name: String, threadCount: Int, + maxMs: Long, tasks: List>) : List { + val result = arrayListOf() + val executor = newExecutor(name, threadCount) + val cs = ExecutorCompletionService(executor) + tasks.map { cs.submit(it) } + + var remainingMs = maxMs + var i = 0 + while (i < tasks.size() && remainingMs >= 0) { + var start = System.currentTimeMillis() + val r = cs.take().get(remainingMs, TimeUnit.MILLISECONDS) + result.add(r) + remainingMs -= (System.currentTimeMillis() - start) + log(2, "Received ${r}, remaining: ${remainingMs} ms") + i++ + } + + if (remainingMs < 0) { + warn("Didn't receive all the results in time: ${i} / ${tasks.size()}") + } else { + log(2, "Received all results in ${maxMs - remainingMs} ms") + } + + executor.shutdown() + return result + } +} diff --git a/src/main/kotlin/com/beust/kobalt/misc/KobaltLogger.kt b/src/main/kotlin/com/beust/kobalt/misc/KobaltLogger.kt new file mode 100644 index 00000000..17fcc907 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/misc/KobaltLogger.kt @@ -0,0 +1,48 @@ +package com.beust.kobalt.misc + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +public interface KobaltLogger { + val logger : Logger + get() = LoggerFactory.getLogger(javaClass.getSimpleName()) + +// private fun log(method: Function1, message: String) = +// method.invoke(message) + + companion object { + + public var LOG_LEVEL : Int = 1 + + fun log(level: Int, s: String) { + if (level <= LOG_LEVEL) { + LoggerFactory.getLogger(KobaltLogger::class.java.getSimpleName()).info(s) + } + } + + fun warn(s: String, e: Throwable? = null) { + LoggerFactory.getLogger(KobaltLogger::class.java.getSimpleName()).warn(s, e) + } + } + + final fun log(level: Int = 1, message: String) { + // Compiler crashing if I use LOG_LEVEL here + // Caused by: java.lang.VerifyError: Bad invokespecial instruction: current class isn't + // assignable to reference class. + if (level <= LOG_LEVEL) { + logger.info(message) + } + } + + final fun debug(message: String) { + logger.debug(message) + } + + final fun error(message: String, e: Throwable? = null) { + logger.error("***** ${message}", e) + } + + final fun warn(message: String, e: Throwable? = null) { + logger.warn(message, e) + } +} diff --git a/src/main/kotlin/com/beust/kobalt/misc/MainModule.kt b/src/main/kotlin/com/beust/kobalt/misc/MainModule.kt new file mode 100644 index 00000000..8981abf2 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/misc/MainModule.kt @@ -0,0 +1,67 @@ +package com.beust.kobalt.misc + +import com.beust.kobalt.kotlin.ScriptCompiler +import com.beust.kobalt.maven.ArtifactFetcher +import com.beust.kobalt.maven.LocalRepo +import com.beust.kobalt.maven.Pom +import com.beust.kobalt.maven.PomGenerator +import com.beust.kobalt.plugin.publish.JCenterApi +import com.google.inject.AbstractModule +import com.google.inject.BindingAnnotation +import com.google.inject.TypeLiteral +import com.google.inject.assistedinject.FactoryModuleBuilder +import java.lang.annotation.RetentionPolicy +import java.util.concurrent.ExecutorService + +//@Singleton +//class TaskManagerProvider @Inject constructor(val plugins: Plugins) : Provider { +// override fun get(): TaskManager? { +// return TaskManager(plugins) +// } +//} + +@BindingAnnotation +@Retention(AnnotationRetention.RUNTIME) +annotation class DependencyExecutor + +public open class MainModule : AbstractModule() { + val executors = KobaltExecutors() + + open fun configureTest() { + bind(LocalRepo::class.java) + } + + override fun configure() { + configureTest() + val builder = FactoryModuleBuilder() + arrayListOf( + PomGenerator.IFactory::class.java, + JCenterApi.IFactory::class.java, + Pom.IFactory::class.java, + ScriptCompiler.IFactory::class.java, + ArtifactFetcher.IFactory::class.java) + .forEach { + install(builder.build(it)) + } + +// bind(javaClass()).toProvider(javaClass()) +// .`in`(Scopes.SINGLETON) + bind(object: TypeLiteral() {}).toInstance(executors) + bind(object: TypeLiteral() {}).annotatedWith(DependencyExecutor::class.java) + .toInstance(executors.dependencyExecutor) + + + // bindListener(Matchers.any(), object: TypeListener { + // override fun hear(typeLiteral: TypeLiteral?, typeEncounter: TypeEncounter?) { + // val bean = object: InjectionListener { + // override public fun afterInjection(injectee: I) { + // if (Scopes.isCircularProxy(injectee)) { + // println("CYCLE: " + typeLiteral?.getRawType()?.getName()); + // } + // } + // } + // typeEncounter?.register(bean) + // } + // }) + } +} diff --git a/src/main/kotlin/com/beust/kobalt/misc/Node.kt b/src/main/kotlin/com/beust/kobalt/misc/Node.kt new file mode 100644 index 00000000..4fd2f216 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/misc/Node.kt @@ -0,0 +1,26 @@ +package com.beust.kobalt.misc + +public data class Node(val value: T) { + val children = arrayListOf>() + var parent: Node? = null + + public fun addChildren(values: List>) { + values.forEach { + it.parent = this + children.add(it) + } + } + + private fun p(s: String) { + println(s) + } + + public fun dump(r: T, children: List>, indent: Int) { + p(" ".repeat(indent) + r) + children.forEach { dump(it.value, it.children, indent + 2) } + } + + public fun dump(indent: Int = 0) { + dump(value, children, indent) + } +} diff --git a/src/main/kotlin/com/beust/kobalt/misc/Strings.kt b/src/main/kotlin/com/beust/kobalt/misc/Strings.kt new file mode 100644 index 00000000..10b36098 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/misc/Strings.kt @@ -0,0 +1,31 @@ +package com.beust.kobalt.misc + +import com.google.common.base.CharMatcher + +public class Strings { + companion object { + fun join(separator: String, strings: List) : String { + var result = StringBuffer() + var i = 0 + strings.forEach { + if (i++ > 0) { + result.append(separator) + } + result.append(it) + } + return result.toString() + } + + fun isEmpty(s: String?): Boolean { + return s == null || s.isEmpty() + } + } + +} + +/** + * @Return the number of times the given character occurs in the string + */ +public fun String.countChar(c: Char) : Int { + return CharMatcher.`is`(c).countIn(this) +} diff --git a/src/main/kotlin/com/beust/kobalt/misc/ToString.kt b/src/main/kotlin/com/beust/kobalt/misc/ToString.kt new file mode 100644 index 00000000..da032c53 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/misc/ToString.kt @@ -0,0 +1,14 @@ +package com.beust.kobalt.misc + +public class ToString(val name: String, vararg o: T) { + val sb = StringBuffer() + + init { + for (i in 0..o.size() - 1 step 2) { + if (i > 0) sb.append(", ") + sb.append(o.get(i).toString() + ":" + o.get(i + 1)) + } + } + + val s : String get() = "{${name} ${sb}}" +} diff --git a/src/main/kotlin/com/beust/kobalt/misc/Topological.kt b/src/main/kotlin/com/beust/kobalt/misc/Topological.kt new file mode 100644 index 00000000..67b40b23 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/misc/Topological.kt @@ -0,0 +1,44 @@ +package com.beust.kobalt.misc + +import com.google.common.collect.ArrayListMultimap +import com.google.common.collect.HashMultimap +import java.util.* + +/** + * Sort items topologically. These items need to have overridden hashCode() and equals(). + */ +class Topological { + private val dependingOn = ArrayListMultimap.create() + + fun addEdge(t: T, other: T) { + dependingOn.put(t, other) + } + + fun addEdge(t: T, others: Array) { + dependingOn.putAll(t, others.toArrayList()) + } + + /** + * @return the Ts sorted topologically. + */ + fun sort(all: ArrayList) : List { + val result = arrayListOf() + var dependMap = HashMultimap.create() + dependingOn.keySet().forEach { dependMap.putAll(it, dependingOn.get(it))} + while (all.size() > 0) { + val freeNodes = all.filter { + dependMap.get(it).isEmpty() + } + result.addAll(freeNodes) + all.removeAll(freeNodes) + val newMap = HashMultimap.create() + dependMap.keySet().forEach { + val l = dependingOn.get(it) + l.removeAll(freeNodes) + newMap.putAll(it, l) + } + dependMap = newMap + } + return result + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/beust/kobalt/misc/Versions.kt b/src/main/kotlin/com/beust/kobalt/misc/Versions.kt new file mode 100644 index 00000000..3af2b8ff --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/misc/Versions.kt @@ -0,0 +1,34 @@ +package com.beust.kobalt.misc + +import com.google.common.base.CharMatcher + +public class Versions { + companion object { + /** + * Turn "6.9.4" into 600090004 + */ + public fun toLongVersion(version: String) : Long { + val count = version.countChar('.') + val normalizedVersion = if (count == 2) version else if (count == 1) version + ".0" + else version + ".0.0" + + fun parseLong(s: String, radix: Int) : Long { + try { + return java.lang.Long.parseLong(s, radix) + } catch(ex: NumberFormatException) { + KobaltLogger.warn("Couldn't parse version \"${version}\"") + return 0L + } + } + + return normalizedVersion + .split(".") + .take(3) + .map { + val s = CharMatcher.inRange('0', '9').or(CharMatcher.`is`('.')).retainFrom(it) + parseLong(s, 10) + } + .fold(0L, { n, s -> s + n * 10000 }) + } + } +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/DefaultPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/DefaultPlugin.kt new file mode 100644 index 00000000..f58f025a --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/DefaultPlugin.kt @@ -0,0 +1,23 @@ +package com.beust.kobalt.plugin + +import com.beust.kobalt.Plugins +import com.beust.kobalt.api.BasePlugin +import com.beust.kobalt.api.Dependencies +import com.beust.kobalt.api.Project +import com.beust.kobalt.api.annotation.Directive +import com.beust.kobalt.api.annotation.Task +import com.beust.kobalt.internal.TaskResult +import com.beust.kobalt.misc.KobaltLogger +import javax.inject.Singleton + +/** + * This plugin is used to gather tasks defined in build files, since these tasks don't really belong to any plugin. + */ +@Singleton +public class DefaultPlugin : BasePlugin(), KobaltLogger { + companion object { + public val NAME = "Default" + } + + override val name: String get() = NAME +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/apt/AptPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/apt/AptPlugin.kt new file mode 100644 index 00000000..68f0011e --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/apt/AptPlugin.kt @@ -0,0 +1,37 @@ +package com.beust.kobalt.plugin.apt + +import com.beust.kobalt.Plugins +import com.beust.kobalt.api.BasePlugin +import com.beust.kobalt.api.Dependencies +import com.beust.kobalt.api.Project +import com.beust.kobalt.api.annotation.Directive +import com.beust.kobalt.api.annotation.Task +import com.beust.kobalt.internal.TaskResult +import com.beust.kobalt.misc.KobaltLogger +import javax.inject.Singleton + +@Singleton +public class AptPlugin : BasePlugin(), KobaltLogger { + companion object { + public const val TASK_APT: String = "runApt" + } + + override val name = "apt" + + @Task(name = TASK_APT, description = "Run apt", runBefore = arrayOf("compile")) + fun taskApt(project: Project) : TaskResult { + log(1, "apt called on ${project} with processors ${processors}") + return TaskResult() + } + + private val processors = arrayListOf() + + fun addApt(dep: String) { + processors.add(dep) + } +} + +@Directive +public fun Dependencies.apt(dep: String) { + (Plugins.getPlugin("apt") as AptPlugin).addApt(dep) +} \ No newline at end of file diff --git a/src/main/kotlin/com/beust/kobalt/plugin/java/JavaCompilerInfo.kt b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaCompilerInfo.kt new file mode 100644 index 00000000..c4c3ab63 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaCompilerInfo.kt @@ -0,0 +1,24 @@ +package com.beust.kobalt.plugin.java + +import com.beust.kobalt.api.ICompilerInfo +import com.beust.kobalt.misc.KFiles +import com.google.inject.Singleton +import java.io.File + +@Singleton +public class JavaCompilerInfo : ICompilerInfo { + override val name = "java" + + override fun findManagedFiles(dir: File) : List { + val result = KFiles.findRecursively(dir, { it.endsWith(".java") }) + .map { File(it) } + return result + } + + override val defaultSourceDirectories = arrayListOf("src/main/java", "src/main/resources") + + override val defaultTestDirectories = arrayListOf("src/test/java", "src/test/resources") + + override val directive = "javaProject" + +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/java/JavaInfo.kt b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaInfo.kt new file mode 100644 index 00000000..8c4dbb89 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaInfo.kt @@ -0,0 +1,30 @@ +package com.beust.kobalt.plugin.java + +import java.io.File + +abstract public class JavaInfo { + public var javaExecutable: File? = null + get() = findExecutable("java") + public var javacExecutable: File? = null + get() = findExecutable("javac") + public var javadocExecutable: File? = null + get() = findExecutable("javadoc") + abstract public var javaHome: File? + abstract public var runtimeJar: File? + abstract public var toolsJar: File? + + abstract public fun findExecutable(command: String) : File + + companion object { + fun create(javaBase: File?): Jvm { + val vendor = System.getProperty("java.vm.vendor") + if (vendor.toLowerCase().startsWith("apple inc.")) { + return AppleJvm(OperatingSystem.Companion.current(), javaBase!!) + } + if (vendor.toLowerCase().startsWith("ibm corporation")) { + return IbmJvm(OperatingSystem.Companion.current(), javaBase!!) + } + return Jvm(OperatingSystem.Companion.current(), javaBase) + } + } +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/java/JavaPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaPlugin.kt new file mode 100644 index 00000000..f521f2f5 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaPlugin.kt @@ -0,0 +1,138 @@ +package com.beust.kobalt.plugin.java + +import com.beust.kobalt.api.ICompilerInfo +import com.beust.kobalt.api.Kobalt +import com.beust.kobalt.api.Project +import com.beust.kobalt.api.annotation.Directive +import com.beust.kobalt.api.annotation.Task +import com.beust.kobalt.internal.JvmCompilerPlugin +import com.beust.kobalt.internal.TaskResult +import com.beust.kobalt.maven.* +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltExecutors +import com.beust.kobalt.misc.KobaltLogger +import java.io.File +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +public class JavaPlugin @Inject constructor( + override val localRepo: LocalRepo, + override val files: KFiles, + override val depFactory: DepFactory, + override val dependencyManager: DependencyManager, + override val executors: KobaltExecutors) + : JvmCompilerPlugin(localRepo, files, depFactory, dependencyManager, executors), KobaltLogger { + + init { + Kobalt.registerCompiler(JavaCompilerInfo()) + } + + companion object { + public const val TASK_COMPILE : String = "compile" + public const val TASK_JAVADOC : String = "javadoc" + public const val TASK_COMPILE_TEST: String = "compileTest" + } + + override val name = "java" + + override fun accept(project: Project) = project is JavaProject + + private fun compilePrivate(project: Project, cpList: List, sourceFiles: List, + outputDirectory: File): TaskResult { + outputDirectory.mkdirs() + val jvm = JavaInfo.create(File(SystemProperties.javaBase)) + val javac = jvm.javacExecutable + + val args = arrayListOf( + javac!!.absolutePath, + "-d", outputDirectory.absolutePath) + if (cpList.size() > 0) { + args.add("-classpath") + args.add(cpList.map { it.jarFile.get().absolutePath }.join(File.pathSeparator)) + } + args.addAll(sourceFiles) + + val pb = ProcessBuilder(args) + pb.directory(File(project.directory)) + pb.inheritIO() + // pb.redirectErrorStream(true) + // pb.redirectError(File("/tmp/kobalt-err")) + // pb.redirectOutput(File("/tmp/kobalt-out")) + val line = args.join(" ") + lp(project, "Compiling ${sourceFiles.size()} files with classpath size ${cpList.size()}") + log(2, "Compiling ${project}:\n${line}") + val process = pb.start() + val errorCode = process.waitFor() + + return if (errorCode == 0) TaskResult(true, "Compilation succeeded") + else TaskResult(false, "There were errors") + } + + @Task(name = TASK_JAVADOC, description = "Run Javadoc") + fun taskJavadoc(project: Project) : TaskResult { + val projectDir = File(project.directory) + val outputDir = File(projectDir, + project.buildDirectory + File.separator + JvmCompilerPlugin.DOCS_DIRECTORY) + outputDir.mkdirs() + val jvm = JavaInfo.create(File(SystemProperties.javaBase)) + val javadoc = jvm.javadocExecutable + + val sourceFiles = files.findRecursively(projectDir, project.sourceDirectories.map { File(it) }) + { it: String -> it.endsWith(".java") } + .map { File(projectDir, it).absolutePath } + val classpath = calculateClasspath(project.compileDependencies) + val args = arrayListOf( + javadoc!!.absolutePath, + "-classpath", classpath.map { it.jarFile.get().absolutePath }.join(File.pathSeparator), + "-d", outputDir.absolutePath) + args.addAll(sourceFiles) + + val pb = ProcessBuilder(args) + pb.directory(File(project.directory)) + pb.inheritIO() + val process = pb.start() + val errorCode = process.waitFor() + + return if (errorCode == 0) TaskResult(true, "Compilation succeeded") + else TaskResult(false, "There were errors") + + } + + @Task(name = TASK_COMPILE, description = "Compile the project") + fun taskCompile(project: Project) : TaskResult { + copyResources(project, JvmCompilerPlugin.SOURCE_SET_MAIN) + val projectDir = File(project.directory) + val buildDir = File(projectDir, + project.buildDirectory + File.separator + "classes") + val sourceFiles = files.findRecursively(projectDir, project.sourceDirectories.map { File(it) }) + { it: String -> it.endsWith(".java") } + .map { File(projectDir, it).absolutePath } + val classpath = calculateClasspath(project.compileDependencies) + return compilePrivate(project, classpath, sourceFiles, buildDir) + } + + @Task(name = TASK_COMPILE_TEST, description = "Compile the tests", runAfter = arrayOf("compile")) + fun taskCompileTest(project: Project): TaskResult { + copyResources(project, JvmCompilerPlugin.SOURCE_SET_TEST) + val projectDir = File(project.directory) + + val absoluteSourceFiles = files.findRecursively(projectDir, project.sourceDirectoriesTest.map { File(it) }) + { it: String -> it.endsWith(".java") } + .map { File(projectDir, it).absolutePath } + + return compilePrivate(project, + testDependencies(project), + absoluteSourceFiles, + makeOutputTestDir(project)) + } + +} + + +@Directive +public fun javaProject(init: JavaProject.() -> Unit): JavaProject { + val pd = JavaProject() + pd.init() + return pd +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/java/JavaProject.kt b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaProject.kt new file mode 100644 index 00000000..33a1c6d1 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaProject.kt @@ -0,0 +1,31 @@ +package com.beust.kobalt.plugin.java + +import com.beust.kobalt.api.Dependencies +import com.beust.kobalt.api.Project +import com.beust.kobalt.api.annotation.Directive +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.ToString +import java.io.File + +public class JavaProject( + @Directive + override var name: String? = null, + @Directive + override var version: String? = null, + /** The absolute directory location of this project */ + @Directive + override var directory: String = ".", + /** The build directory, relative to the project directory */ + @Directive + override var buildDirectory: String? = "kobaltBuild", + @Directive + override var group: String? = null, + @Directive + override var artifactId: String? = null, + @Directive + override var dependencies: Dependencies? = null) + : Project(name, version, directory, buildDirectory, group, artifactId, dependencies, ".java", JavaCompilerInfo()) { + + override public fun toString() = ToString("JavaProject", name!!, "name").s +} + diff --git a/src/main/kotlin/com/beust/kobalt/plugin/java/Jvm.kt b/src/main/kotlin/com/beust/kobalt/plugin/java/Jvm.kt new file mode 100644 index 00000000..df94a5d0 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/java/Jvm.kt @@ -0,0 +1,158 @@ +package com.beust.kobalt.plugin.java + +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.maven.KobaltException +import java.io.File +import java.io.IOException +import java.util.HashMap + +public open class Jvm constructor( + val os: com.beust.kobalt.plugin.java.OperatingSystem, + var javaBase: File? = null) : JavaInfo(), KobaltLogger { + + private var _javaHome: File? = null + override public var javaHome: File? = null + get() = _javaHome!! + override public var runtimeJar: File? = null + private fun findRuntimeJar() : File? { + var runtimeJar = File(javaBase, "lib/rt.jar") + if (runtimeJar.exists()) { + return runtimeJar + } + runtimeJar = File(javaBase, "jre/lib/rt.jar") + return if (runtimeJar.exists()) runtimeJar else null + } + override public var toolsJar: File? = null + + private var userSupplied: Boolean? = false + private var javaVersion: String? = null + + init { + if (javaBase == null) { + //discover based on what's in the sys. property + try { + javaBase = File(System.getProperty("java.home")).getCanonicalFile() + } catch (e: IOException) { + throw KobaltException(e) + } + + _javaHome = findJavaHome(javaBase!!) + javaVersion = SystemProperties.Companion.javaVersion + userSupplied = false + } else { + //precisely use what the user wants and validate strictly further on + _javaHome = javaBase!! + userSupplied = true + javaVersion = null + } + toolsJar = findToolsJar(javaBase!!) + runtimeJar = findRuntimeJar() + } + + private fun findJavaHome(javaBase: File): File { + val toolsJar = findToolsJar(javaBase) + if (toolsJar != null) { + return toolsJar.getParentFile().getParentFile() + } else if (javaBase.getName().equals("jre", true) && File(javaBase.getParentFile(), + "bin/java").exists()) { + return javaBase.getParentFile() + } else { + return javaBase + } + } + + private fun findToolsJar(jh: File): File? { + javaHome = jh + var toolsJar = File(javaHome, "lib/tools.jar") + if (toolsJar.exists()) { + return toolsJar + } + if (javaHome!!.getName().equals("jre", true)) { + javaHome = javaHome!!.parentFile + toolsJar = File(javaHome, "lib/tools.jar") + if (toolsJar.exists()) { + return toolsJar + } + } + + if (os.isWindows()) { + val version = SystemProperties.Companion.javaVersion + if (javaHome!!.name.toRegex().matches("jre\\d+") + || javaHome!!.getName() == "jre${version}") { + javaHome = File(javaHome!!.parentFile, "jdk${version}") + toolsJar = File(javaHome, "lib/tools.jar") + if (toolsJar.exists()) { + return toolsJar + } + } + } + + return null + } + +// open public fun isIbmJvm(): Boolean { +// return false +// } + + override public fun findExecutable(command: String): File { + val exec = File(javaHome, "bin/" + command) + val executable = java.io.File(os.getExecutableName(exec.getAbsolutePath())) + if (executable.isFile()) { + return executable + } + +// if (userSupplied) { +// //then we want to validate strictly +// throw JavaHomeException(String.format("The supplied javaHome seems to be invalid." + " I cannot find the %s executable. Tried location: %s", command, executable.getAbsolutePath())) +// } + + val pathExecutable = os.findInPath(command) + if (pathExecutable != null) { + log(1, "Unable to find the ${command} executable using home: " + + "%{javaHome}. We found it on the PATH: ${pathExecutable}.") + return pathExecutable + } + + warn("Unable to find the ${command} executable. Tried the java home: ${javaHome}" + + " and the PATH. We will assume the executable can be ran in the current " + + "working folder.") + return java.io.File(os.getExecutableName(command)) + } + +} + +class AppleJvm : Jvm { + override var runtimeJar: File? = File(javaHome!!.getParentFile(), "Classes/classes.jar") + override var toolsJar: File? = File(javaHome!!.getParentFile(), "Classes/tools.jar") + + constructor(os: OperatingSystem) : super(os) { + } + + constructor(current: OperatingSystem, javaHome: File) : super(current, javaHome) { + } + + /** + * {@inheritDoc} + */ +// fun getInheritableEnvironmentVariables(envVars: Map): Map { +// val vars = HashMap() +// for (entry in envVars.entrySet()) { +// if (entry.getKey().toRegex().matches("APP_NAME_\\d+") || +// entry.getKey().toRegex().matches("JAVA_MAIN_CLASS_\\d+")) { +// continue +// } +// vars.put(entry.getKey(), entry.getValue()) +// } +// return vars +// } +} + +class IbmJvm(os: OperatingSystem, suppliedJavaBase: File) : Jvm(os, suppliedJavaBase) { + override var runtimeJar: File? = throw IllegalArgumentException("Not implemented") + override var toolsJar: File? = throw IllegalArgumentException("Not implemented") + +// override fun isIbmJvm(): Boolean { +// return true +// } +} + diff --git a/src/main/kotlin/com/beust/kobalt/plugin/java/OperatingSystem.kt b/src/main/kotlin/com/beust/kobalt/plugin/java/OperatingSystem.kt new file mode 100644 index 00000000..94228a07 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/java/OperatingSystem.kt @@ -0,0 +1,304 @@ +package com.beust.kobalt.plugin.java + +import java.io.File +import java.util.* +import java.util.regex.Pattern + +public abstract class OperatingSystem { + + override fun toString(): String { + return getName() + " " + getVersion() + " " + System.getProperty("os.arch") + } + + public fun getName(): String { + return System.getProperty("os.name") + } + + public fun getVersion(): String { + return System.getProperty("os.version") + } + + public open fun isWindows(): Boolean { + return false + } + + public open fun isUnix(): Boolean { + return false + } + + public open fun isMacOsX(): Boolean { + return false + } + + public open fun isLinux(): Boolean { + return false + } + + public abstract fun getNativePrefix(): String + + public abstract fun getScriptName(scriptPath: String): String + + public abstract fun getExecutableName(executablePath: String): String + + public abstract fun getSharedLibraryName(libraryName: String): String + + public abstract fun getStaticLibraryName(libraryName: String): String + + public abstract fun getFamilyName(): String + + /** + * Locates the given executable in the system path. Returns null if not found. + */ + public fun findInPath(name: String): File? { + val exeName = getExecutableName(name) + if (exeName.contains(File.separator)) { + val candidate = File(exeName) + if (candidate.isFile()) { + return candidate + } + return null + } + for (dir in getPath()) { + val candidate = File(dir, exeName) + if (candidate.isFile()) { + return candidate + } + } + + return null + } + + public fun findAllInPath(name: String): List { + val all = LinkedList() + + for (dir in getPath()) { + val candidate = File(dir, name) + if (candidate.isFile()) { + all.add(candidate) + } + } + + return all + } + + public fun getPath(): List { + val path = System.getenv(getPathVar()) ?: return emptyList() + val entries = ArrayList() + for (entry in path.split(Pattern.quote(File.pathSeparator))) { + entries.add(File(entry)) + } + return entries + } + + public open fun getPathVar(): String { + return "PATH" + } + + class Windows : OperatingSystem() { + override fun isWindows(): Boolean { + return true + } + + override fun getFamilyName(): String { + return "windows" + } + + override fun getScriptName(scriptPath: String): String { + return withSuffix(scriptPath, ".bat") + } + + override fun getExecutableName(executablePath: String): String { + return withSuffix(executablePath, ".exe") + } + + override fun getSharedLibraryName(libraryName: String): String { + return withSuffix(libraryName, ".dll") + } + + override fun getStaticLibraryName(libraryName: String): String { + return withSuffix(libraryName, ".lib") + } + + override fun getNativePrefix(): String { + var arch = System.getProperty("os.arch") + if ("i386" == arch) { + arch = "x86" + } + return "win32-" + arch + } + + private fun withSuffix(executablePath: String, extension: String): String { + if (executablePath.toLowerCase().endsWith(extension)) { + return executablePath + } + return removeExtension(executablePath) + extension + } + + private fun removeExtension(executablePath: String): String { + val fileNameStart = Math.max(executablePath.lastIndexOf('/'), executablePath.lastIndexOf('\\')) + val extensionPos = executablePath.lastIndexOf('.') + + if (extensionPos > fileNameStart) { + return executablePath.substring(0, extensionPos) + } + return executablePath + } + + + override fun getPathVar(): String { + return "Path" + } + } + + open class Unix : OperatingSystem() { + override fun getScriptName(scriptPath: String): String { + return scriptPath + } + + override fun getFamilyName(): String { + return "unknown" + } + + override fun getExecutableName(executablePath: String): String { + return executablePath + } + + override fun getSharedLibraryName(libraryName: String): String { + return getLibraryName(libraryName, getSharedLibSuffix()) + } + + private fun getLibraryName(libraryName: String, suffix: String): String { + if (libraryName.endsWith(suffix)) { + return libraryName + } + val pos = libraryName.lastIndexOf('/') + if (pos >= 0) { + return libraryName.substring(0, pos + 1) + "lib" + libraryName.substring(pos + 1) + suffix + } else { + return "lib" + libraryName + suffix + } + } + + protected open fun getSharedLibSuffix(): String { + return ".so" + } + + override fun getStaticLibraryName(libraryName: String): String { + return getLibraryName(libraryName, ".a") + } + + override fun isUnix(): Boolean { + return true + } + + override fun getNativePrefix(): String { + val arch = getArch() + var osPrefix = getOsPrefix() + osPrefix += "-" + arch + return osPrefix + } + + protected open fun getArch(): String { + var arch = System.getProperty("os.arch") + if ("x86" == arch) { + arch = "i386" + } + if ("x86_64" == arch) { + arch = "amd64" + } + if ("powerpc" == arch) { + arch = "ppc" + } + return arch + } + + protected open fun getOsPrefix(): String { + var osPrefix = getName().toLowerCase() + val space = osPrefix.indexOf(" ") + if (space != -1) { + osPrefix = osPrefix.substring(0, space) + } + return osPrefix + } + } + + class MacOs : Unix() { + override fun isMacOsX(): Boolean { + return true + } + + override fun getFamilyName(): String { + return "os x" + } + + override fun getSharedLibSuffix(): String { + return ".dylib" + } + + override fun getNativePrefix(): String { + return "darwin" + } + } + + class Linux : Unix() { + override fun isLinux(): Boolean { + return true + } + + override fun getFamilyName(): String { + return "linux" + } + } + + class FreeBSD : Unix() + + class Solaris : Unix() { + override fun getFamilyName(): String { + return "solaris" + } + + override fun getOsPrefix(): String { + return "sunos" + } + + override fun getArch(): String { + val arch = System.getProperty("os.arch") + if (arch == "i386" || arch == "x86") { + return "x86" + } + return super.getArch() + } + } + + companion object { + public val WINDOWS: Windows = Windows() + public val MAC_OS: MacOs = MacOs() + public val SOLARIS: Solaris = Solaris() + public val LINUX: Linux = Linux() + public val FREE_BSD: FreeBSD = FreeBSD() + public val UNIX: Unix = Unix() + + public fun current(): OperatingSystem { + return forName(System.getProperty("os.name")) + } + + public fun forName(os: String): OperatingSystem { + val osName = os.toLowerCase() + if (osName.contains("windows")) { + return WINDOWS + } else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) { + return MAC_OS + } else if (osName.contains("sunos") || osName.contains("solaris")) { + return SOLARIS + } else if (osName.contains("linux")) { + return LINUX + } else if (osName.contains("freebsd")) { + return FREE_BSD + } else { + // Not strictly true + return UNIX + } + } + } +} + diff --git a/src/main/kotlin/com/beust/kobalt/plugin/java/SystemProperties.kt b/src/main/kotlin/com/beust/kobalt/plugin/java/SystemProperties.kt new file mode 100644 index 00000000..549463f1 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/java/SystemProperties.kt @@ -0,0 +1,16 @@ +package com.beust.kobalt.plugin.java + +import java.util.concurrent.locks.ReentrantLock +import javax.inject.Inject + +public class SystemProperties { + companion object { + val javaBase = System.getenv("JAVA_HOME") ?: throw IllegalArgumentException("JAVA_HOME not defined") + 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/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt new file mode 100644 index 00000000..8f1c05b0 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt @@ -0,0 +1,87 @@ +package com.beust.kobalt.plugin.kotlin; + +import com.beust.kobalt.INJECTOR +import com.beust.kobalt.internal.JvmCompilerPlugin +import com.beust.kobalt.internal.TaskResult +import com.beust.kobalt.maven.* +import com.beust.kobalt.misc.KobaltExecutors +import com.beust.kobalt.misc.KobaltLogger +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler +import java.io.File +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.properties.Delegates + +/** + * @author Cedric Beust + * @since 08 03, 2015 + */ +@Singleton +private class KotlinCompiler @Inject constructor(override val localRepo : LocalRepo, + override val files: com.beust.kobalt.misc.KFiles, + override val depFactory: DepFactory, + override val dependencyManager: DependencyManager, + override val executors: KobaltExecutors) + : JvmCompilerPlugin(localRepo, files, depFactory, dependencyManager, executors), KobaltLogger { + private val KOTLIN_VERSION = "0.14.449" + + override val name = "kotlin" + + private fun getKotlinCompilerJar(name: String) : String { + return com.beust.kobalt.misc.KFiles.joinDir(localRepo.toFullPath(""), + File("org/jetbrains/kotlin/${name}").path, + File("${KOTLIN_VERSION}/${name}-${KOTLIN_VERSION}.jar").getPath()) + } + + fun compile(compileDependencies: List, otherClasspath: List, + source: List, output: String, args: List) : TaskResult { + val executor = executors.newExecutor("KotlinCompiler", 10) + val compilerDep = depFactory.create("org.jetbrains.kotlin:kotlin-compiler-embeddable:${KOTLIN_VERSION}", + executor) + val deps = compilerDep.transitiveDependencies(executor) + deps.forEach { it.jarFile.get() } + + val classpathList = arrayListOf( + getKotlinCompilerJar("kotlin-stdlib"), + getKotlinCompilerJar("kotlin-compiler-embeddable")) + + classpathList.addAll(otherClasspath) + classpathList.addAll(calculateClasspath(compileDependencies).map { it.id }) + + log(2, "Compiling ${source.size()} files with classpath:\n " + classpathList.join("\n ")) + K2JVMCompiler.main(arrayOf( + "-d", output, + "-classpath", classpathList.join(File.pathSeparator), *source.toTypedArray(), + *args.toTypedArray())) + executor.shutdown() + return TaskResult() + } +} + +class KConfiguration @Inject constructor(val compiler: KotlinCompiler){ + val classpath = arrayListOf() + val dependencies = arrayListOf() + var source = arrayListOf() + var output: String by Delegates.notNull() + val args = arrayListOf() + + fun sourceFiles(s: String) = source.add(s) + + fun sourceFiles(s: List) = source.addAll(s) + + fun classpath(s: String) = classpath.add(s) + + fun classpath(s: List) = classpath.addAll(s) + + fun compilerArgs(s: List) = args.addAll(s) + + public fun compile() : TaskResult { + return compiler.compile(dependencies, classpath, source, output, args) + } +} + +fun kotlinCompilePrivate(ini: KConfiguration.() -> Unit) : KConfiguration { + val result = INJECTOR.getInstance(KConfiguration::class.java) + result.ini() + return result +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompilerInfo.kt b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompilerInfo.kt new file mode 100644 index 00000000..22bf436d --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompilerInfo.kt @@ -0,0 +1,22 @@ +package com.beust.kobalt.plugin.kotlin + +import com.beust.kobalt.api.ICompilerInfo +import com.beust.kobalt.misc.KFiles +import java.io.File + +public class KotlinCompilerInfo : ICompilerInfo { + override val name = "kotlin" + + override fun findManagedFiles(dir: File): List { + val result = KFiles.findRecursively(dir, { it.endsWith(".kt") }) + .map { File(it) } + return result + } + + override val defaultSourceDirectories = arrayListOf("src/main/kotlin", "src/main/resources") + + override val defaultTestDirectories = arrayListOf("src/test/kotlin", "src/test/resources") + + override val directive = "javaProject" +} + diff --git a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinPlugin.kt new file mode 100644 index 00000000..034aee87 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinPlugin.kt @@ -0,0 +1,123 @@ +package com.beust.kobalt.plugin.kotlin + +import com.beust.kobalt.Plugins +import com.beust.kobalt.api.ICompilerInfo +import com.beust.kobalt.api.Kobalt +import com.beust.kobalt.api.Project +import com.beust.kobalt.api.annotation.Directive +import com.beust.kobalt.api.annotation.Task +import com.beust.kobalt.internal.JvmCompilerPlugin +import com.beust.kobalt.internal.TaskResult +import com.beust.kobalt.maven.* +import com.beust.kobalt.misc.KobaltExecutors +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.plugin.java.JavaProject +import java.io.File +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +public class KotlinPlugin @Inject constructor( + override val localRepo: LocalRepo, + override val files: com.beust.kobalt.misc.KFiles, + override val depFactory: DepFactory, + override val dependencyManager: DependencyManager, + override val executors: KobaltExecutors) + : JvmCompilerPlugin(localRepo, files, depFactory, dependencyManager, executors), KobaltLogger { + + init { + Kobalt.registerCompiler(KotlinCompilerInfo()) + } + + companion object { + public const val TASK_COMPILE: String = "compile" + public const val TASK_COMPILE_TEST: String = "compileTest" + } + + override val name = "kotlin" + + override fun accept(project: Project) = project is KotlinProject + + private val compilerArgs = arrayListOf() + + @Task(name = TASK_COMPILE, description = "Compile the project") + fun taskCompile(project: Project): TaskResult { + copyResources(project, JvmCompilerPlugin.SOURCE_SET_MAIN) + val classpath = calculateClasspath(project.compileDependencies) + + val projectDirectory = java.io.File(project.directory) + val buildDirectory = File(projectDirectory, project.buildDirectory + File.separator + "classes") + buildDirectory.mkdirs() + + val sourceFiles = files.findRecursively(projectDirectory, + project.sourceDirectories.map { File(it) }, { it.endsWith(".kt") }) + val absoluteSourceFiles = sourceFiles.map { + File(projectDirectory, it).absolutePath + } + + compilePrivate(classpath, absoluteSourceFiles, buildDirectory.getAbsolutePath()) + lp(project, "Compilation succeeded") + return TaskResult() + } + + fun addCompilerArgs(vararg args: String) { + compilerArgs.addAll(args) + } + + @Task(name = TASK_COMPILE_TEST, description = "Compile the tests", runAfter = arrayOf(TASK_COMPILE)) + fun taskCompileTest(project: Project): TaskResult { + copyResources(project, JvmCompilerPlugin.SOURCE_SET_TEST) + val projectDir = File(project.directory) + + val absoluteSourceFiles = files.findRecursively(projectDir, project.sourceDirectoriesTest.map { File(it) }) + { it: String -> it.endsWith(".kt") } + .map { File(projectDir, it).getAbsolutePath() } + + compilePrivate(testDependencies(project), + absoluteSourceFiles, + makeOutputTestDir(project).absolutePath) + + lp(project, "Compilation of tests succeeded") + return TaskResult() + } + + private fun compilePrivate(cpList: List, sources: List, + outputDirectory: String): TaskResult { + File(outputDirectory).mkdirs() + +// lp(project, "Compiling ${sources.size()} files with classpath size ${cpList.size()}") + + return kotlinCompilePrivate { + classpath(cpList.map { it.jarFile.get().absolutePath }) + sourceFiles(sources) + compilerArgs(compilerArgs) + output = outputDirectory + }.compile() + } +} + +/** + * @param project: the list of projects that need to be built before this one. + */ +@Directive +public fun kotlinProject(vararg project: Project, init: KotlinProject.() -> Unit): KotlinProject { + with(KotlinProject()) { + init() + Kobalt.declareProjectDependencies(this, project) + return this + } +} + +class KotlinCompilerConfig { + fun args(vararg options: String) { + (Plugins.getPlugin("kotlin") as KotlinPlugin).addCompilerArgs(*options) + } +} + +@Directive +fun kotlinCompiler(init: KotlinCompilerConfig.() -> Unit) : KotlinCompilerConfig { + with (KotlinCompilerConfig()) { + init() + return this + } +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinProject.kt b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinProject.kt new file mode 100644 index 00000000..20930fc3 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinProject.kt @@ -0,0 +1,32 @@ +package com.beust.kobalt.plugin.kotlin + +import com.beust.kobalt.api.Dependencies +import com.beust.kobalt.api.ICompilerInfo +import com.beust.kobalt.api.Project +import com.beust.kobalt.api.annotation.Directive +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.ToString +import java.io.File + +public class KotlinProject( + @Directive + override var name: String? = null, + @Directive + override var version: String? = null, + /** The absolute directory location of this project */ + @Directive + override var directory: String = ".", + /** The build directory, relative to the project directory */ + @Directive + override var buildDirectory: String? = "kobaltBuild", + @Directive + override var group: String? = null, + @Directive + override var artifactId: String? = name, + @Directive + override var dependencies: Dependencies? = null) + : Project(name, version, directory, buildDirectory, group, artifactId, dependencies, ".kt", + KotlinCompilerInfo()) { + + override public fun toString() = ToString("KotlinProject", "name", name!!).s +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/packaging/JarUtils.kt b/src/main/kotlin/com/beust/kobalt/plugin/packaging/JarUtils.kt new file mode 100644 index 00000000..92a45563 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/packaging/JarUtils.kt @@ -0,0 +1,167 @@ +package com.beust.kobalt.plugin.packaging + +import com.beust.kobalt.IFileSpec +import com.beust.kobalt.misc.KobaltLogger +import java.io.* +import java.util.jar.JarEntry +import java.util.jar.JarFile +import java.util.jar.JarInputStream +import java.util.jar.JarOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +public class JarUtils : KobaltLogger { + companion object { +// private fun isExcluded(entryName: String) : Boolean { +// val isAuth = entryName.startsWith("META-INF") and ( +// entryName.endsWith(".SF") or entryName.endsWith(".DSA") or entryName.endsWith("RSA")) +// val isSource = entryName.endsWith(".java") +// return isAuth or isSource +// } + + /** + * Add the content of a jar file to another jar file. + */ +// private fun addJarFile(inFile: File, outStream: ZipOutputStream, seen: HashSet) { +// val inJarFile = JarFile(inFile) +// val stream = JarInputStream(FileInputStream(inFile)) +// var entry = stream.getNextEntry() +// // Quick and dirty benchmarks to assess the impact of the byte array size (milliseconds): +// // 10000: 7883 7873 +// // 50000: 7947 +// // 20000: 7858 7737 7730 +// // 10000: 7939 7924 +// // Probably need to do this more formally but it doesn't seem to matter that much +// val buf = ByteArray(20000) +// +// while (entry != null) { +// if (! entry.isDirectory()) { +// val entryName = entry.getName() +// if (! seen.contains(entryName) && ! isExcluded(entryName)) { +// seen.add(entryName) +// outStream.putNextEntry(entry) +// val zis = inJarFile.getInputStream(entry) +// +// var len = zis.read(buf) +// while (len >= 0) { +// outStream.write(buf, 0, len); +// len = zis.read(buf) +// } +// } +// } +// entry = stream.getNextJarEntry() +// } +// +// stream.close() +// } + + val defaultHandler: (Exception) -> Unit = { ex: Exception -> + // Ignore duplicate entry exceptions + if (! ex.getMessage()?.contains("duplicate")!!) { + throw ex + } + } + + public fun addFiles(directory: String, files: List, target: ZipOutputStream, + expandJarFiles: Boolean, + onError: (Exception) -> Unit = defaultHandler) { + files.forEach { + addSingleFile(directory, it, target, expandJarFiles, onError) + } + } + + public fun addSingleFile(directory: String, file: IncludedFile, outputStream: ZipOutputStream, + expandJarFiles: Boolean, onError: (Exception) -> Unit = defaultHandler) { + file.specs.forEach { spec -> + val fromPath = (file.from + "/" + spec).replace("\\", "/") + val path = spec.toString() + spec.toFiles(directory).forEach { source -> + // Remove the "from" from the path +// val path = fixedPath.substring(file.from.length()) + + if (source.isDirectory) { + // Directory + var name = path + if (!name.isEmpty()) { + if (!name.endsWith("/")) name += "/" + val entry = JarEntry(name) + entry.time = source.lastModified() + outputStream.putNextEntry(entry) + outputStream.closeEntry() + } + val fileSpecs: List = source.listFiles().map { IFileSpec.FileSpec(it.name) } + val subFiles = IncludedFile(From(file.from), To(file.to), fileSpecs) + addSingleFile(directory, subFiles, outputStream, expandJarFiles) + } else { + if (expandJarFiles and source.name.endsWith(".jar")) { + KobaltLogger.log(2, "Writing contents of jar file ${source}") + val stream = JarInputStream(FileInputStream(source)) + var entry = stream.nextEntry + while (entry != null) { + if (!entry.isDirectory) { + val ins = JarFile(source).getInputStream(entry) + addEntry(ins, JarEntry(entry), outputStream, path, onError) + } + entry = stream.nextEntry + } + } else { + val entry = JarEntry((file.to + path).replace("\\", "/")) + entry.time = source.lastModified() + val entryFile = File(directory, fromPath) + if (! entryFile.exists()) { + throw AssertionError("File should exist: ${entryFile}") + } + addEntry(FileInputStream(entryFile), entry, outputStream, path, onError) + } + } + } + } + } + + private fun addEntry(inputStream: InputStream, entry: ZipEntry, outputStream: ZipOutputStream, + path: String, + onError: (Exception) -> Unit = defaultHandler) { + // This jar file is not shaded and includes its own copy of guava, so don't add the guava + // files from there +// if (entry.name.contains("com/google/common") && path.contains("kotlin-compiler")) { +// return +// } + var bis: BufferedInputStream? = null + try { + outputStream.putNextEntry(entry) + bis = BufferedInputStream(inputStream) + + val buffer = ByteArray(50 * 1024) + while (true) { + val count = bis.read(buffer) + if (count == -1) break + outputStream.write(buffer, 0, count) + } + outputStream.closeEntry() + } catch(ex: Exception) { + onError(ex) + } finally { + bis?.close() + } + } + + fun removeDuplicateEntries(fromJarFile: File, toFile: File) { + val fromFile = JarFile(fromJarFile) + var entries = fromFile.entries() + val os = JarOutputStream(FileOutputStream(toFile)) + val seen = hashSetOf() + while (entries.hasMoreElements()) { + val entry = entries.nextElement() + if (! seen.contains(entry.name)) { + val ins = fromFile.getInputStream(entry) + addEntry(ins, JarEntry(entry), os, fromFile.name) + } + seen.add(entry.name) + } + os.close() + + KobaltLogger.log(1, "Deduplicated $fromFile.name") + } + + } +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt new file mode 100644 index 00000000..282d3c21 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt @@ -0,0 +1,356 @@ +package com.beust.kobalt.plugin.packaging + +import com.beust.kobalt.IFileSpec.FileSpec +import com.beust.kobalt.IFileSpec.Glob +import com.beust.kobalt.IFileSpec +import com.beust.kobalt.Plugins +import com.beust.kobalt.api.BasePlugin +import com.beust.kobalt.api.Project +import com.beust.kobalt.api.annotation.Directive +import com.beust.kobalt.api.annotation.Task +import com.beust.kobalt.glob +import com.beust.kobalt.internal.JvmCompilerPlugin +import com.beust.kobalt.internal.TaskResult +import com.beust.kobalt.maven.DependencyManager +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltExecutors +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.misc.ToString +import com.beust.kobalt.plugin.java.JavaPlugin +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStream +import java.nio.file.FileSystems +import java.nio.file.PathMatcher +import java.nio.file.Paths +import java.util.ArrayList +import java.util.jar.JarOutputStream +import java.util.zip.ZipOutputStream +import javax.inject.Inject +import javax.inject.Singleton + +@Directive +public fun assemble(project: Project, init: Package.(p: Project) -> Unit): Package { + val pd = Package(project) + pd.init(project) + return pd +} + +@Singleton +public class PackagingPlugin @Inject constructor(val dependencyManager : DependencyManager, + val executors: KobaltExecutors) : BasePlugin(), KobaltLogger { + + companion object { + public const val TASK_ASSEMBLE : String = "assemble" + } + + override val name = "packaging" + + private val packages = arrayListOf() + + @Task(name = TASK_ASSEMBLE, description = "Package the artifacts", runAfter = arrayOf(JavaPlugin.TASK_COMPILE)) + fun taskAssemble(project: Project) : TaskResult { + packages.filter { it.project.name == project.name }.forEach { pkg -> + pkg.jars.forEach { generateJar(pkg.project, it) } + pkg.zips.forEach { generateZip(pkg.project, it) } + } + return TaskResult() + } + + private fun isExcluded(file: File, excludes: List) : Boolean { + if (excludes.isEmpty()) { + return false + } else { + val ex = arrayListOf() + excludes.forEach { + ex.add(FileSystems.getDefault().getPathMatcher("glob:${it.spec}")) + } + ex.forEach { + if (it.matches(Paths.get(file.getName()))) { + log(2, "Excluding ${file}") + return true + } + } + } + return false + } + + private fun generateJar(project: Project, jar: Jar) : File { + // + // Add all the applicable files for the current project + // + val buildDir = KFiles.makeDir(project.directory, project.buildDirectory!!) + val allFiles = arrayListOf() + val classesDir = KFiles.makeDir(buildDir.getPath(), "classes") + + if (jar.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.absolutePath + "/")) + 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 { ! isExcluded(it, jar.excludes) } + val fileSpecs = arrayListOf() + filesNotExcluded.forEach { + fileSpecs.add(FileSpec(it.path.toString().substring(prefixPath.toString().length() + 1))) + } + allFiles.add(IncludedFile(From(prefixPath.toString() + "/"), To(""), fileSpecs)) + } else { + allFiles.addAll(findIncludedFiles(project.directory, jar.includedFiles, jar.excludes)) + } + + // + // If fatJar is true, add all the transitive dependencies too + // + if (jar.fatJar) { + log(2, "Creating fat jar") + val allDependencies = dependencyManager.transitiveClosure(project.compileDependencies) + allDependencies.map { it.jarFile.get() }.forEach { + if (! isExcluded(it, jar.excludes)) { + allFiles.add(IncludedFile(arrayListOf(FileSpec(it.path)))) + } + } + } + + // + // Generate the manifest + // + val manifest = java.util.jar.Manifest()//FileInputStream(mf)) + jar.attributes.forEach { attribute -> + manifest.mainAttributes.putValue(attribute.first, attribute.second) + } + val jarFactory = { os:OutputStream -> JarOutputStream(os, manifest) } + + return generateArchive(project, jar.name, ".jar", allFiles, + true /* expandJarFiles */, jarFactory) + } + + private fun findIncludedFiles(directory: String, files: List, excludes: List) + : List { + val result = arrayListOf() + files.forEach { includedFile -> + val includedSpecs = arrayListOf() + includedFile.specs.forEach { spec -> + val fromPath = directory + "/" + includedFile.from + if (File(fromPath).exists()) { + spec.toFiles(fromPath).forEach { file -> + if (!File(fromPath, file.path).exists()) { + throw AssertionError("File should exist: ${file}") + } + + if (!isExcluded(file, excludes)) { + includedSpecs.add(FileSpec(file.path)) + } else { + log(2, "Not adding ${file.path} to jar file because it's excluded") + } + + } + } else { + warn("Directory ${fromPath} doesn't exist, not including it in the jar") + } + } + if (includedSpecs.size() > 0) { + log(3, "Including specs ${includedSpecs}") + result.add(IncludedFile(From(includedFile.from), To(includedFile.to), includedSpecs)) + } + } + return result + } + + private fun generateZip(project: Project, zip: Zip) { + val allFiles = findIncludedFiles(project.directory, zip.includedFiles, zip.excludes) + generateArchive(project, zip.name, ".zip", allFiles) + } + + private val DEFAULT_STREAM_FACTORY = { os : OutputStream -> ZipOutputStream(os) } + + private fun generateArchive(project: Project, archiveName: String?, suffix: String, + includedFiles: List, + expandJarFiles : Boolean = false, + outputStreamFactory: (OutputStream) -> ZipOutputStream = DEFAULT_STREAM_FACTORY) : File { + val buildDir = KFiles.makeDir(project.directory, project.buildDirectory!!) + val archiveDir = KFiles.makeDir(buildDir.path, "libs") + val fullArchiveName = archiveName ?: arrayListOf(project.name!!, project.version!!).join("-") + suffix + val result = File(archiveDir.path, fullArchiveName) + val outStream = outputStreamFactory(FileOutputStream(result)) + log(2, "Creating ${result}") + JarUtils.addFiles(project.directory, includedFiles, outStream, expandJarFiles) + log(2, "Added ${includedFiles.size()} files to ${result}") + outStream.flush() + outStream.close() + log(1, "Created ${result}") + return result + } + + fun addPackage(p: Package) { + packages.add(p) + } +} + +class Package(val project: Project) : AttributeHolder { + val jars = arrayListOf() + val zips = arrayListOf() + + init { + (Plugins.getPlugin("packaging") as PackagingPlugin).addPackage(this) + } + + @Directive + fun jar(init: Jar.(p: Jar) -> Unit) : Jar { + val jar = Jar() + jar.init(jar) + jars.add(jar) + return jar + } + + @Directive + fun zip(init: Zip.(p: Zip) -> Unit) : Zip { + val zip = Zip() + zip.init(zip) + zips.add(zip) + return zip + } + + /** + * Package all the jar files necessary for a maven repo: classes, sources, javadocs. + */ + public fun mavenJars(init: MavenJars.(p: MavenJars) -> Unit) : MavenJars { + val m = MavenJars(this) + m.init(m) + + val mainJar = jar { + fatJar = m.fatJar + } + jar { + name = "${project.name}-${project.version}-sources.jar" + project.sourceDirectories.forEach { + include(from(it), to(""), glob("**${project.sourceSuffix}")) + } + } + jar { + name = "${project.name}-${project.version}-javadoc.jar" + include(from(project.buildDirectory + "/" + JvmCompilerPlugin.DOCS_DIRECTORY), to(""), glob("**")) + } + + mainJarAttributes.forEach { + mainJar.addAttribute(it.first, it.second) + } + + return m + } + + val mainJarAttributes = arrayListOf>() + + override fun addAttribute(k: String, v: String) { + mainJarAttributes.add(Pair(k, v)) + } + + class MavenJars(val ah: AttributeHolder, var fatJar: Boolean = false, var manifest: Manifest? = null) : + AttributeHolder by ah { + public fun manifest(init: Manifest.(p: Manifest) -> Unit) : Manifest { + val m = Manifest(this) + m.init(m) + return m + } + } +} + +open class Zip(open var name: String? = null) { +// internal val includes = arrayListOf() + internal val excludes = arrayListOf() + + @Directive + public fun from(s: String) = From(s) + + @Directive + public fun to(s: String) = To(s) + + @Directive + public fun exclude(vararg files: String) { + files.forEach { excludes.add(Glob(it)) } + } + + @Directive + public fun exclude(vararg specs: Glob) { + specs.forEach { excludes.add(it) } + } + + @Directive + public fun include(vararg files: String) { + includedFiles.add(IncludedFile(files.map { FileSpec(it) })) + } + + @Directive + public fun include(from: From, to: To, vararg specs: String) { + includedFiles.add(IncludedFile(from, to, specs.map { FileSpec(it) })) + } + + @Directive + public fun include(from: From, to: To, vararg specs: Glob) { + includedFiles.add(IncludedFile(from, to, listOf(*specs))) + } + + /** + * 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() +} + +private open class Direction(open val p: String) { + override public fun toString() = path + public val path: String get() = if (p.isEmpty() or p.endsWith("/")) p else p + "/" +} + +class From(override val p: String) : Direction(p) + +class To(override val p: String) : Direction(p) + +class IncludedFile(val fromOriginal: From, val toOriginal: To, val specs: List) { + constructor(specs: List) : this(From(""), To(""), specs) + public val from: String get() = fromOriginal.path.replace("\\", "/") + public val to: String get() = toOriginal.path.replace("\\", "/") + override public fun toString() = ToString("IncludedFile", + "files", specs.map { it.toString() }.join(", "), + "from", from, + "to", to) + .s +} + +interface AttributeHolder { + fun addAttribute(k: String, v: String) +} + +/** + * A jar is exactly like a zip with the addition of a manifest and an optional fatJar boolean. + */ +class Jar(override var name: String? = null, var fatJar: Boolean = false) : Zip(name), AttributeHolder { + @Directive + public 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 + val attributes = arrayListOf(Pair("Manifest-Version", "1.0")) + + override fun addAttribute(k: String, v: String) { + attributes.add(Pair(k, v)) + } +} + +class Pom { + +} + +class Manifest(val jar: AttributeHolder) { + @Directive + public fun attributes(k: String, v: String) { + jar.addAttribute(k, v) + } +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/publish/JCenterApi.kt b/src/main/kotlin/com/beust/kobalt/plugin/publish/JCenterApi.kt new file mode 100644 index 00000000..e2a8ea4b --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/publish/JCenterApi.kt @@ -0,0 +1,134 @@ +package com.beust.kobalt.plugin.publish + +import com.beust.klaxon.* +import com.beust.kobalt.api.Project +import com.beust.kobalt.internal.TaskResult +import com.beust.kobalt.maven.Http +import com.beust.kobalt.maven.KobaltException +import com.beust.kobalt.misc.KobaltLogger +import com.google.inject.assistedinject.Assisted +import com.squareup.okhttp.Response +import org.jetbrains.annotations.Nullable +import java.io.ByteArrayInputStream +import java.io.File +import java.nio.charset.Charset +import javax.inject.Inject + +data class JCenterPackage(val jo: JsonObject) { + val latestPublishedVersion = (jo.get("versions") as JsonArray).get(0) +} + +open public class UnauthenticatedJCenterApi @Inject constructor(open val http: Http){ + companion object { + const val BINTRAY_URL_API = "https://api.bintray.com" + const val BINTRAY_URL_API_CONTENT = BINTRAY_URL_API + "/content" + } + + fun parseResponse(response: String) : JsonObject { + return Parser().parse(ByteArrayInputStream(response.toByteArray(Charset.defaultCharset()))) as JsonObject + } + + fun getPackage(name: String) : JCenterPackage { + val url = arrayListOf(BINTRAY_URL_API, "packages", "cbeust", "maven", "kobalt").join("/") + val response = http.get(url).getAsString() + val result = parseResponse(response) + return JCenterPackage(result) + } + + val kobaltPackage : JCenterPackage + get() = getPackage("kobalt") +} + +public class JCenterApi @Inject constructor (@Nullable @Assisted("username") val username: String?, + @Nullable @Assisted("password") val password: String?, + override val http: Http) : UnauthenticatedJCenterApi(http), KobaltLogger { + + interface IFactory { + fun create(@Nullable @Assisted("username") username: String?, + @Nullable @Assisted("password") password: String?) : JCenterApi + } + + fun packageExists(packageName: String) : Boolean { + val url = arrayListOf(UnauthenticatedJCenterApi.BINTRAY_URL_API, "packages", username!!, "maven", packageName) + .join("/") + val response = http.get(username, password, url).getAsString() + val jo = parseResponse(response) + + return jo.string("name") == packageName + } + + fun createPackage(packageName: String) : String { + val url = arrayListOf(UnauthenticatedJCenterApi.BINTRAY_URL_API, "packages", username!!, "maven").join("/") + val jo = json { + obj("name" to packageName) + obj("license" to array("Apache 2.0")) + } + return http.post(username, password, url, jo.toJsonString()) + } + + fun uploadMaven(project: Project, files: List, configuration : JCenterConfiguration?) : TaskResult { + if (! packageExists(project.name!!)) { + throw KobaltException("Couldn't find a package called ${project.name} on bintray, please create one first" + + " as explained at https://bintray.com/docs/usermanual/uploads/uploads_creatinganewpackage.html") + } + + val fileToPath: (File) -> String = { f: File -> + arrayListOf( + UnauthenticatedJCenterApi.BINTRAY_URL_API_CONTENT, + username!!, + "maven", + project.name!!, + project.version!!, + project.group!!.replace(".", "/"), + project.artifactId!!, + project.version!!, + f.getName()) + .join("/") + } + + return upload(files, configuration, fileToPath) + } + + fun uploadFile(file: File, url: String, configuration: JCenterConfiguration) = + upload(arrayListOf(file), configuration, { + f: File -> "${UnauthenticatedJCenterApi.BINTRAY_URL_API_CONTENT}/${username}/generic/${url}" + }) + + private fun upload(files: List, configuration : JCenterConfiguration?, fileToPath: (File) -> String) + : TaskResult { + val successes = arrayListOf() + val failures = hashMapOf() + files.forEach { + var path = fileToPath(it) + + // Apply the configurations for this project, if any + val options = arrayListOf() + if (configuration?.publish == true) options.add("publish=1") + // This actually needs to be done +// options.add("list_in_downloads=1") + + path += "?" + options.join("&") + + http.uploadFile(username, password, path, it, + { r: Response -> successes.add(it) }, + { r: Response -> + val jo = parseResponse(r.body().string()) + failures.put(it, jo.string("message") ?: "No message found") + }) + } + + val result: TaskResult + if (successes.size() == files.size()) { + log(1, "All artifacts successfully uploaded") + result = TaskResult(true) + } else { + result = TaskResult(false, failures.values().join(" ")) + error("Failed to upload ${failures.size()} files:") + failures.forEach { k, v -> + error(" - ${k} : ${v}") + } + } + + return result + } +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/publish/PublishPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/publish/PublishPlugin.kt new file mode 100644 index 00000000..19e16420 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/plugin/publish/PublishPlugin.kt @@ -0,0 +1,128 @@ +package com.beust.kobalt.plugin.publish + +import com.beust.klaxon.string +import com.beust.kobalt.Plugins +import com.beust.kobalt.api.BasePlugin +import com.beust.kobalt.api.Project +import com.beust.kobalt.api.annotation.Directive +import com.beust.kobalt.api.annotation.Task +import com.beust.kobalt.internal.TaskResult +import com.beust.kobalt.maven.Http +import com.beust.kobalt.maven.KobaltException +import com.beust.kobalt.misc.KobaltLogger +import com.google.common.base.Preconditions +import org.jetbrains.kotlin.utils.sure +import java.io.File +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +public class PublishPlugin @Inject constructor(val http: Http, val files: com.beust.kobalt.misc.KFiles, + val factory: com.beust.kobalt.maven.PomGenerator.IFactory, + val jcenterFactory: JCenterApi.IFactory) + : BasePlugin(), KobaltLogger { + + override val name = "publish" + + companion object { + private const val TASK_UPLOAD_JCENTER = "uploadJcenter" + private const val TASK_GENERATE_POM = "generatePom" + + private const val PROPERTY_BINTRAY_USER = "bintray.user" + private const val PROPERTY_BINTRAY_PASSWORD = "bintray.apikey" + } + + @Task(name = TASK_GENERATE_POM, description = "Generate the .pom file", runAfter = arrayOf("assemble")) + fun taskGeneratePom(project: Project): TaskResult { + factory.create(project).generate() + return TaskResult() + } + + private fun validateProject(project: Project) { + Preconditions.checkNotNull(project.name, "Project ${project} should have a name") + Preconditions.checkNotNull(project.version, "Project ${project} should have a version") + Preconditions.checkNotNull(project.group, "Project ${project} should have a group") + Preconditions.checkNotNull(project.artifactId, "Project ${project} should have a artifactId") + } + + private val VALID = arrayListOf(".jar", ".pom") + + private fun findArtifactFiles(project: Project) : List { + val result = files.findRecursively(File(project.directory, project.buildDirectory)) { file -> + VALID.any { file.endsWith(it)} and file.contains(project.version!!) + }.map { it -> File(it) } + log(1, "${project.name}: Found ${result.size()} artifacts to upload") + return result + } + + private fun checkAuthentication(value: String, key: String) { + Preconditions.checkNotNull(value, "Couldn't find user in property ${key}, make sure you specified" + + "your credentials in local.properties") + } + + @Task(name = TASK_UPLOAD_JCENTER, description = "Upload the artifacts to JCenter", + runAfter = arrayOf(TASK_GENERATE_POM)) + fun taskUploadJcenter(project: Project): TaskResult { + val user = System.getProperty(PROPERTY_BINTRAY_USER) + val password = System.getProperty(PROPERTY_BINTRAY_PASSWORD) + checkAuthentication(user, PROPERTY_BINTRAY_USER) + checkAuthentication(password, PROPERTY_BINTRAY_PASSWORD) + + validateProject(project) + + val jcenter = jcenterFactory.create(user, password) + + val configuration = configurations.get(project.name) + + // + // Upload to Maven + // + val trMaven = jcenter.uploadMaven(project, findArtifactFiles(project), configuration) + var success = trMaven.success + val messages = arrayListOf() + if (! success) messages.add(trMaven.errorMessage!!) + + // + // Upload individual files, if applicable + // + configuration?.let { conf : JCenterConfiguration -> + conf.files.forEach { + val taskResult = jcenter.uploadFile(File(project.directory, it.first), it.second /* url */, + conf) + success = success and taskResult.success + if (!taskResult.success) { + messages.add(taskResult.errorMessage!!) + } + } + } + return TaskResult(success, messages.join("\n ")) + } + + /** + * Map of project name -> JCenterConfiguration + */ + private val configurations = hashMapOf() + fun addConfiguration(projectName: String, config: JCenterConfiguration) { + configurations.put(projectName, config) + } + +} + +data class JCenterConfiguration(val project: Project) { + var publish: Boolean = false + val files = arrayListOf>() + + @Directive + public fun file(filePath: String, url: String) { + files.add(Pair(filePath, url)) + } +} + +@Directive +public fun jcenter(project: Project, ini: JCenterConfiguration.() -> Unit) + : JCenterConfiguration { + val pd = JCenterConfiguration(project) + pd.ini() + (Plugins.getPlugin("publish") as PublishPlugin).addConfiguration(project.name!!, pd) + return pd +} diff --git a/src/main/kotlin/com/beust/kobalt/wrapper/ParentLastClassLoader.kt b/src/main/kotlin/com/beust/kobalt/wrapper/ParentLastClassLoader.kt new file mode 100644 index 00000000..a8a98c02 --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/wrapper/ParentLastClassLoader.kt @@ -0,0 +1,56 @@ +package com.beust.kobalt.wrapper + +import java.net.URL +import java.net.URLClassLoader + +/** + * A parent-last classloader that will try the child classloader first and then the parent. + * Used by the wrapper to launch a new Kobalt with not interferences from its own classes. + * Will probably be made obsolete by making the wrapper a standalone module instead of + * being inside Kobalt itself. + */ +public class ParentLastClassLoader(val classpath: List) + : ClassLoader(Thread.currentThread().getContextClassLoader()) { + private val childClassLoader: ChildURLClassLoader + + init { + val urls : Array = classpath.toTypedArray() + childClassLoader = ChildURLClassLoader(urls, FindClassClassLoader(this.getParent()) ) + } + + +/** + * This class allows me to call findClass on a classloader + */ + private class FindClassClassLoader(parent: ClassLoader) : ClassLoader(parent) { + override public fun findClass(name: String) = super.findClass(name) + } + + /** + * This class delegates (child then parent) for the findClass method for a URLClassLoader. + * We need this because findClass is protected in URLClassLoader + */ + private class ChildURLClassLoader(urls: Array, val realParent: FindClassClassLoader) + : URLClassLoader(urls, null) { + + override public fun findClass(name: String) : Class<*> { + try { + // first try to use the URLClassLoader findClass + return super.findClass(name) + } catch(e: ClassNotFoundException) { + // if that fails, we ask our real parent classloader to load the class (we give up) + return realParent.loadClass(name) + } + } + } + + override public @Synchronized fun loadClass(name: String, resolve: Boolean) : Class<*> { + try { + // first we try to find a class inside the child classloader + return childClassLoader.findClass(name) + } catch(e: ClassNotFoundException) { + // didn't find it, try the parent + return super.loadClass(name, resolve) + } + } +} diff --git a/src/main/kotlin/com/beust/kobalt/wrapper/Wrapper.kt b/src/main/kotlin/com/beust/kobalt/wrapper/Wrapper.kt new file mode 100644 index 00000000..5d0f2c8c --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/wrapper/Wrapper.kt @@ -0,0 +1,177 @@ +package com.beust.kobalt.wrapper + +import com.beust.kobalt.maven.Http +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.misc.benchmark +import com.beust.kobalt.plugin.java.JavaInfo +import com.beust.kobalt.plugin.java.SystemProperties +import java.io.File +import java.io.FileReader +import java.io.IOException +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.util.Properties +import java.util.zip.ZipFile + +public fun main(argv: Array) { + Wrapper().installAndLaunchMain(argv) +} + +/** + * Download and install a new wrapper if requested. + */ +public class Wrapper : KobaltLogger { + // kobalt.properties + private val KOBALT_PROPERTIES = "kobalt.properties" + private val KOBALTW = "kobaltw" + private val WRAPPER_DIR = KFiles.KOBALT_DIR + "/wrapper" + + private val KOBALT_WRAPPER_PROPERTIES = "kobalt-wrapper.properties" + private val PROPERTY_VERSION = "kobalt.version" + + val URL = "https://dl.bintray.com/cbeust/generic/" + val FILE_NAME = "kobalt" + + private val properties = Properties() + + public fun installAndLaunchMain(argv: Array) { + val kobaltJarFile = install() + launchMain(kobaltJarFile, argv) + } + + private fun readProperties(properties: Properties, ins: InputStream) { + properties.load(ins) + ins.close() + properties.forEach { es -> System.setProperty(es.getKey().toString(), es.getValue().toString()) } + } + + private fun maybeCreateProperties() : Properties { + val result = Properties() + + // kobalt.properties is internal to Kobalt + val url = javaClass.classLoader.getResource(KOBALT_PROPERTIES) + if (url != null) { + readProperties(result, url.openConnection().inputStream) + } else { + throw IllegalArgumentException("Couldn't find ${KOBALT_PROPERTIES}") + } + + return result + } + + private fun initWrapperFile(version: String) { + val config = File(WRAPPER_DIR, KOBALT_WRAPPER_PROPERTIES) + if (! config.exists()) { + KFiles.saveFile(config, "${PROPERTY_VERSION}=${version}") + } + properties.load(FileReader(config)) + } + + private val wrapperVersion : String + get() { + return properties.getProperty(PROPERTY_VERSION) + } + + /** + * Install a new version if requested in .kobalt/wrapper/kobalt-wrapper.properties + * + * @return the path to the Kobalt jar file + */ + public fun install() : Path { + val properties = maybeCreateProperties() + val version = properties.getProperty(PROPERTY_VERSION) + initWrapperFile(version) + + log(2, "Wrapper version: ${wrapperVersion}") + + val fileName = "${FILE_NAME}-${wrapperVersion}.zip" + File(KFiles.distributionsDir).mkdirs() + val localZipFile = Paths.get(KFiles.distributionsDir, fileName) + val zipOutputDir = KFiles.distributionsDir + "/" + wrapperVersion + val kobaltJarFile = Paths.get(zipOutputDir, "kobalt/wrapper/${FILE_NAME}-${wrapperVersion}.jar") + if (!Files.exists(localZipFile) || !Files.exists(kobaltJarFile)) { + log(1, "Downloading ${fileName}") + val fullUrl = "${URL}/${fileName}" + val body = Http().get(fullUrl) + if (body.code == 200) { + if (!Files.exists(localZipFile)) { + val target = localZipFile.toAbsolutePath() + val ins = body.getAsStream() + benchmark("Download .zip file") { + // This takes about eight seconds for a 21M file because of the extra copying, not good. + // Should use Okio.sink(file) to create a Sink and then call readAll(fileSink) on + // the BufferedSource returned in the ResponseBody + Files.copy(ins, target) + } + log(2, "${localZipFile} downloaded, extracting it") + } else { + log(2, "${localZipFile} already exists, extracting it") + } + + // + // Extract all the zip files + // + val zipFile = ZipFile(localZipFile.toFile()) + val entries = zipFile.entries() + val outputDirectory = File(KFiles.distributionsDir) + outputDirectory.mkdirs() + while (entries.hasMoreElements()) { + val entry = entries.nextElement() + val entryFile = File(entry.name) + if (entry.isDirectory) { + entryFile.mkdirs() + } else { + val dest = Paths.get(zipOutputDir, entryFile.path) + log(2, " Writing ${entry.name} to ${dest}") + Files.createDirectories(dest.parent) + Files.copy(zipFile.getInputStream(entry), + dest, + java.nio.file.StandardCopyOption.REPLACE_EXISTING) + } + } + log(2, "${localZipFile} extracted") + } else { + error("Couldn't download ${URL}") + } + } + + // + // Copy the wrapper files in the current kobalt/wrapper directory + // + log(2, "Copying the wrapper files...") + arrayListOf(KOBALTW, "kobalt/wrapper/${FILE_NAME}-wrapper.jar").forEach { + val from = Paths.get(zipOutputDir, it) + val to = Paths.get(File(".").absolutePath, it) + KFiles.copy(from, to, java.nio.file.StandardCopyOption.REPLACE_EXISTING) + } + File(KOBALTW).setExecutable(true) + + return kobaltJarFile + } + + /** + * Launch kobalt-xxx.jar + * + * Note: currently launching it in a separate VM because both this jar file and the wrapper contain + * the same classes, so the old classes will be run. Once wrapper.jar contains only the + * wrapper class and nothing else from the Kobalt distribution, we can just invoke main from the same JVM here, + * which will speed up the start up + */ + private fun launchMain(kobaltJarFile: Path, argv: Array) { + val jvm = JavaInfo.create(File(SystemProperties.javaBase)) + val java = jvm.javaExecutable + + val args = arrayListOf( + java!!.absolutePath, + "-jar", kobaltJarFile.toFile().absolutePath) + args.addAll(argv) + val pb = ProcessBuilder(args) + pb.inheritIO() + log(1, "Launching\n ${args.join(" ")}") + val process = pb.start() + process.waitFor() + } +} diff --git a/src/main/resources/META-INF/kobalt-plugins/kobalt.properties b/src/main/resources/META-INF/kobalt-plugins/kobalt.properties new file mode 100644 index 00000000..c6ce7d53 --- /dev/null +++ b/src/main/resources/META-INF/kobalt-plugins/kobalt.properties @@ -0,0 +1,2 @@ +plugin-class=com.beust.kobalt.plugin.apt.AptPlugin + diff --git a/src/main/resources/build-template.mustache b/src/main/resources/build-template.mustache new file mode 100644 index 00000000..89a96586 --- /dev/null +++ b/src/main/resources/build-template.mustache @@ -0,0 +1,42 @@ +import com.beust.kobalt.* +import com.beust.kobalt.plugin.packaging.assemble +{{imports}} + +val p = {{directive}} { + name = "{{name}}" + group = "{{group}}" + artifactId = name + version = "{{version}}" + + sourceDirectories { + {{#sourceDirectories}} + path("{{toString}}") + {{/sourceDirectories}} + } + + sourceDirectoriesTest { + {{#sourceDirectoriesTest}} + path("{{toString}}") + {{/sourceDirectoriesTest}} + } + + dependencies { +// compile("com.beust:jcommander:1.48") + {{#mainDependencies}} + compile("{{groupId}}:{{artifactId}}:{{version}}") + {{/mainDependencies}} + } + + dependenciesTest { +// compile("org.testng:testng:6.9.5") + {{#testDependencies}} + compile("{{groupId}}:{{artifactId}}:{{version}}") + {{/testDependencies}} + + } +} + +val packProject = assemble(p) { + jar { + } +} diff --git a/src/main/resources/kobalt.properties b/src/main/resources/kobalt.properties new file mode 100644 index 00000000..0f029e85 --- /dev/null +++ b/src/main/resources/kobalt.properties @@ -0,0 +1 @@ +kobalt.version=0.144 \ No newline at end of file diff --git a/src/test/kotlin/com/beust/kobalt/ResourceTest.kt b/src/test/kotlin/com/beust/kobalt/ResourceTest.kt new file mode 100644 index 00000000..8b572aa8 --- /dev/null +++ b/src/test/kotlin/com/beust/kobalt/ResourceTest.kt @@ -0,0 +1,21 @@ +package com.beust.kobalt + +import org.testng.Assert +import org.testng.annotations.Test +import java.util.Properties + +@Test +public class ResourceTest { + val fileName = "kobalt.properties" + + fun shouldLoadResources() { + val properties = Properties() + val res = ClassLoader.getSystemResource(fileName) + if (res != null) { + properties.load(res.openStream()) + Assert.assertTrue(properties.get("foo") == "bar") + } else { + Assert.fail("Couldn't load ${fileName}") + } + } +} diff --git a/src/test/kotlin/com/beust/kobalt/TestModule.kt b/src/test/kotlin/com/beust/kobalt/TestModule.kt new file mode 100644 index 00000000..94b2f531 --- /dev/null +++ b/src/test/kotlin/com/beust/kobalt/TestModule.kt @@ -0,0 +1,15 @@ +package com.beust.kobalt + +import com.beust.kobalt.maven.LocalRepo +import com.beust.kobalt.plugin.java.SystemProperties +import com.google.inject.Scopes +import java.io.File + +class TestLocalRepo: LocalRepo(localRepo = SystemProperties.homeDir + File.separatorChar + ".kobalt-test") + +public class TestModule : com.beust.kobalt.misc.MainModule() { + override fun configureTest() { + bind(LocalRepo::class.java).to(TestLocalRepo::class.java).`in`(Scopes.SINGLETON) + } + +} diff --git a/src/test/kotlin/com/beust/kobalt/internal/DynamicGraphTest.kt b/src/test/kotlin/com/beust/kobalt/internal/DynamicGraphTest.kt new file mode 100644 index 00000000..19084fec --- /dev/null +++ b/src/test/kotlin/com/beust/kobalt/internal/DynamicGraphTest.kt @@ -0,0 +1,138 @@ +package com.beust.kobalt.internal + +import com.beust.kobalt.maven.KobaltException +import com.beust.kobalt.misc.KobaltLogger +import com.beust.kobalt.misc.Topological +import javafx.concurrent.Worker +import org.testng.Assert +import org.testng.annotations.Test +import java.util.ArrayList +import java.util.HashSet +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.TimeUnit + +public class DynamicGraphTest { + + private fun assertFreeNodesEquals(graph: DynamicGraph, expected: Array) { + val h = HashSet(graph.freeNodes) + val e = HashSet(expected.toList()) + Assert.assertEquals(h, e) + } + + private fun createFactory(runNodes: ArrayList, errorFunction: (T) -> Boolean) : IThreadWorkerFactory { + return object: IThreadWorkerFactory { + override fun createWorkers(nodes: List): List> { + val result = arrayListOf>() + nodes.forEach { result.add(Worker(runNodes, it, errorFunction)) } + return result + } + } + } + + public class Worker(val runNodes: ArrayList, val n: T, + val errorFunction: (T) -> Boolean) : IWorker, KobaltLogger { + override val priority = 0 + + override fun call() : TaskResult2 { + log(2, "Running node $n") + runNodes.add(n) + return TaskResult2(errorFunction(n), n) + } + } + + @Test + public fun testExecutor() { + val dg = DynamicGraph(); + dg.addEdge("compile", "runApt") + dg.addEdge("compile", "generateVersion") + + val runNodes = arrayListOf() + val factory = createFactory(runNodes, { true }) + + DynamicGraphExecutor(dg, factory).run() + Assert.assertEquals(runNodes.size(), 3) + } + + + @Test + private fun testExecutorWithSkip() { + + val g = DynamicGraph() + // 2 and 3 depend on 1, 4 depend on 3, 10 depends on 4 + // 3 will blow up, which should make 4 and 10 skipped + g.addEdge(2, 1) + g.addEdge(3, 1) + g.addEdge(4, 3) + g.addEdge(10, 4) + g.addEdge(5, 2) + val runNodes = arrayListOf() + val factory = createFactory(runNodes, { n -> n != 3 }) + val ex = DynamicGraphExecutor(g, factory) + ex.run() + Thread.yield() + Assert.assertEquals(runNodes, listOf(1, 2, 3, 5)) + } + + @Test + public fun test8() { + val dg = DynamicGraph(); + dg.addEdge("b1", "a1") + dg.addEdge("b1", "a2") + dg.addEdge("b2", "a1") + dg.addEdge("b2", "a2") + dg.addEdge("c1", "b1") + dg.addEdge("c1", "b2") + dg.addNode("x") + dg.addNode("y") + val freeNodes = dg.freeNodes + assertFreeNodesEquals(dg, arrayOf("a1", "a2", "y", "x")) + + dg.setStatus(freeNodes, DynamicGraph.Status.RUNNING) + dg.setStatus("a1", DynamicGraph.Status.FINISHED) + assertFreeNodesEquals(dg, arrayOf()) + + dg.setStatus("a2", DynamicGraph.Status.FINISHED) + assertFreeNodesEquals(dg, arrayOf("b1", "b2")) + + dg.setStatus("b2", DynamicGraph.Status.RUNNING) + dg.setStatus("b1", DynamicGraph.Status.FINISHED) + assertFreeNodesEquals(dg, arrayOf()) + + dg.setStatus("b2", DynamicGraph.Status.FINISHED) + assertFreeNodesEquals(dg, arrayOf("c1")) + } + + @Test + public fun test2() { + val dg = DynamicGraph() + dg.addEdge("b1", "a1") + dg.addEdge("b1", "a2") + dg.addNode("x") + val freeNodes = dg.freeNodes + assertFreeNodesEquals(dg, arrayOf("a1", "a2", "x" )) + + dg.setStatus(freeNodes, DynamicGraph.Status.RUNNING) + dg.setStatus("a1", DynamicGraph.Status.FINISHED) + assertFreeNodesEquals(dg, arrayOf()) + + dg.setStatus("a2", DynamicGraph.Status.FINISHED) + assertFreeNodesEquals(dg, arrayOf("b1")) + + dg.setStatus("b2", DynamicGraph.Status.RUNNING) + dg.setStatus("b1", DynamicGraph.Status.FINISHED) + assertFreeNodesEquals(dg, arrayOf()) + } + + @Test + fun topologicalSort() { + val dg = Topological() + dg.addEdge("b1", "a1") + dg.addEdge("b1", "a2") + dg.addEdge("b2", "a1") + dg.addEdge("b2", "a2") + dg.addEdge("c1", "b1") + dg.addEdge("c1", "b2") + val sorted = dg.sort(arrayListOf("a1", "a2", "b1", "b2", "c1", "x", "y")) + Assert.assertEquals(sorted, arrayListOf("a1", "a2", "x", "y", "b1", "b2", "c1")) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/beust/kobalt/maven/DependencyTest.kt b/src/test/kotlin/com/beust/kobalt/maven/DependencyTest.kt new file mode 100644 index 00000000..f5edcf97 --- /dev/null +++ b/src/test/kotlin/com/beust/kobalt/maven/DependencyTest.kt @@ -0,0 +1,48 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.TestModule +import com.beust.kobalt.misc.KobaltExecutors +import com.beust.kobalt.misc.Versions +import org.testng.Assert +import org.testng.annotations.* +import java.util.concurrent.ExecutorService +import javax.inject.Inject +import kotlin.properties.Delegates + +@Guice(modules = arrayOf(TestModule::class)) +public class DependencyTest @Inject constructor(val depFactory: DepFactory, + val executors: KobaltExecutors) { + + @DataProvider + fun dpVersions(): Array> { + return arrayOf( + arrayOf("6.9.4", "6.9.5"), + arrayOf("1.7", "1.38"), + arrayOf("1.70", "1.380"), + arrayOf("3.8.1", "4.5"), + arrayOf("18.0-rc1", "19.0"), + arrayOf("3.0.5.RELEASE", "3.0.6") + ) + } + + private var executor: ExecutorService by Delegates.notNull() + + @BeforeClass + public fun bc() { + executor = executors.newExecutor("DependencyTest", 5) + } + + @AfterClass + public fun ac() { + executor.shutdown() + } + + @Test(dataProvider = "dpVersions") + public fun versionSorting(k: String, v: String) { + val dep1 = Versions.toLongVersion(k) + val dep2 = Versions.toLongVersion(v) + Assert.assertTrue(dep1.compareTo(dep2) < 0) + Assert.assertTrue(dep2.compareTo(dep1) > 0) + } +} + diff --git a/src/test/kotlin/com/beust/kobalt/maven/DownloadTest.kt b/src/test/kotlin/com/beust/kobalt/maven/DownloadTest.kt new file mode 100644 index 00000000..e299bb2e --- /dev/null +++ b/src/test/kotlin/com/beust/kobalt/maven/DownloadTest.kt @@ -0,0 +1,80 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.maven.CompletedFuture +import com.beust.kobalt.maven.DepFactory +import com.beust.kobalt.maven.LocalRepo +import com.beust.kobalt.misc.KobaltExecutors +import com.beust.kobalt.misc.MainModule +import com.beust.kobalt.TestModule +import com.google.inject.Module +import com.google.inject.util.Modules +import org.testng.Assert +import org.testng.IModuleFactory +import org.testng.ITestContext +import org.testng.annotations.BeforeClass +import org.testng.annotations.Guice +import org.testng.annotations.Test +import java.io.File +import java.util.concurrent.ExecutorService +import javax.inject.Inject +import kotlin.properties.Delegates + +/** + * TODO: test snapshots https://repository.jboss.org/nexus/content/repositories/root_repository//commons-lang/commons-lang/2.7-SNAPSHOT/commons-lang-2.7-SNAPSHOT.jar + */ +@Guice(modules = arrayOf(TestModule::class)) +public class DownloadTest @Inject constructor( + val depFactory: DepFactory, + val localRepo: LocalRepo, + val executors: KobaltExecutors) { + var executor: ExecutorService by Delegates.notNull() + + @BeforeClass + public fun bc() { + executor = executors.newExecutor("DependentTest", 5) + } + + @Test + public fun shouldDownloadWithVersion() { + File(localRepo.toFullPath("org/testng/testng")).deleteRecursively() + + arrayListOf("org.testng:testng:6.9.4", "org.testng:testng:6.9.5").forEach { + val dep = depFactory.create(it, executor) + val future = dep.jarFile + Assert.assertFalse(future is CompletedFuture) + val file = future.get() + Assert.assertTrue(file.exists()) + } + } + + @Test + public fun shouldDownloadNoVersion() { + File(localRepo.toFullPath("org/testng/testng")).deleteRecursively() + + val dep = depFactory.create("org.testng:testng:", executor) + + val future = dep.jarFile + val file = future.get() + Assert.assertFalse(future is CompletedFuture) + Assert.assertEquals(file.getName(), "testng-6.9.6.jar") + Assert.assertTrue(file.exists()) + } + + @Test(dependsOnMethods = arrayOf("shouldDownloadWithVersion")) + public fun shouldFindLocalJar() { + val dep = depFactory.create("org.testng:testng:6.9.6", executor) + val future = dep.jarFile + Assert.assertTrue(future is CompletedFuture) + val file = future.get() + Assert.assertTrue(file.exists()) + } + + @Test(dependsOnMethods = arrayOf("shouldDownloadWithVersion")) + public fun shouldFindLocalJarNoVersion() { + val dep = depFactory.create("org.testng:testng:", executor) + val future = dep.jarFile + val file = future.get() + Assert.assertEquals(file.getName(), "testng-6.9.6.jar") + Assert.assertTrue(file.exists()) + } +} diff --git a/src/test/kotlin/com/beust/kobalt/maven/JUnitTest.kt b/src/test/kotlin/com/beust/kobalt/maven/JUnitTest.kt new file mode 100644 index 00000000..1d0532f8 --- /dev/null +++ b/src/test/kotlin/com/beust/kobalt/maven/JUnitTest.kt @@ -0,0 +1,10 @@ +package com.beust.kobalt.maven + +//import org.junit.Test +// +//public class JUnitTest { +// @Test +// public fun simpleTestForJUnit() { +// println("Works") +// } +//} diff --git a/src/test/kotlin/com/beust/kobalt/maven/RemoteRepoTest.kt b/src/test/kotlin/com/beust/kobalt/maven/RemoteRepoTest.kt new file mode 100644 index 00000000..900fc24f --- /dev/null +++ b/src/test/kotlin/com/beust/kobalt/maven/RemoteRepoTest.kt @@ -0,0 +1,30 @@ +package com.beust.kobalt.maven + +import com.beust.kobalt.TestModule +import com.beust.kobalt.misc.DependencyExecutor +import com.beust.kobalt.misc.MainModule +import com.google.inject.Guice +import org.testng.Assert +import org.testng.annotations.Test +import java.util.concurrent.ExecutorService +import javax.inject.Inject + +@org.testng.annotations.Guice(modules = arrayOf(TestModule::class)) +public class RemoteRepoTest @Inject constructor(val repoFinder: RepoFinder, + @DependencyExecutor val executor: ExecutorService){ + + val INJECTOR = Guice.createInjector(MainModule()) + + @Test + public fun mavenMetadata() { + val dep = MavenDependency.create("org.codehaus.groovy:groovy-all:") + Assert.assertEquals(dep.id.split(":")[2], "2.4.4") + } + + @Test + public fun metadataForSnapshots() { + val jar = MavenDependency.create("org.apache.maven.wagon:wagon-provider-test:2.10-SNAPSHOT", executor) + .jarFile + Assert.assertTrue(jar.get().exists()) + } +} diff --git a/src/test/resources/kobalt.properties b/src/test/resources/kobalt.properties new file mode 100644 index 00000000..74d0a43f --- /dev/null +++ b/src/test/resources/kobalt.properties @@ -0,0 +1 @@ +foo=bar diff --git a/src/test/resources/testng.xml b/src/test/resources/testng.xml new file mode 100644 index 00000000..121fb5ee --- /dev/null +++ b/src/test/resources/testng.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + +