1
0
Fork 0
mirror of https://github.com/ethauvin/kobalt.git synced 2025-04-26 16:28:12 -07:00

Fold --init into the new plug-in architecture.

This commit is contained in:
Cedric Beust 2015-11-05 11:18:52 -08:00
parent 3addfac859
commit ad9caf1222
19 changed files with 245 additions and 202 deletions

View file

@ -56,7 +56,8 @@ private class Main @Inject constructor(
val updateKobalt: UpdateKobalt,
val client: KobaltClient,
val server: KobaltServer,
val pluginInfo: PluginInfo) {
val pluginInfo: PluginInfo,
val projectGenerator: ProjectGenerator) {
data class RunInfo(val jc: JCommander, val args: Args)
@ -117,7 +118,7 @@ private class Main @Inject constructor(
// --init: create a new build project and install the wrapper
//
Wrapper().install()
ProjectGenerator().run(args)
projectGenerator.run(args)
} else if (args.usage) {
jc.usage()
} else if (args.serverMode) {

View file

@ -1,133 +1,57 @@
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.api.IInitContributor
import com.beust.kobalt.api.PluginInfo
import com.beust.kobalt.misc.log
import com.github.mustachejava.DefaultMustacheFactory
import com.google.inject.Inject
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
import java.io.FileOutputStream
import java.util.*
/**
* Generate a new project.
* Invoked with --init. Generate a new project.
*/
public class ProjectGenerator {
public class ProjectGenerator @Inject constructor(val pluginInfo: PluginInfo){
companion object {
/**
* Turns a dot property into a proper Kotlin identifier, e.g. common.version -> commonVersion
*/
fun translate(key: String): String {
return key.split('.').mapIndexed( { index, value -> if (index == 0) value else value.upperFirst() })
fun toIdentifier(key: String): String {
fun upperFirst(s: String) = if (s.isBlank()) s else s.substring(0, 1).toUpperCase() + s.substring(1)
return key.split('.').mapIndexed( { index, value -> if (index == 0) value else upperFirst(value) })
.joinToString("")
}
}
fun run(args: Args) {
if (File(args.buildFile).exists()) {
log(1, "Build file ${args.buildFile} already exists, not overwriting it")
return
val contributor = findBestInitContributor(File("."))
if (contributor != null) {
contributor.generateBuildFile(FileOutputStream(File(args.buildFile)))
log(1, "Created ${args.buildFile}")
} else {
log(1, "Couldn't identify project, not generating any build file")
}
}
val compilerInfos = detect(File("."))
if (compilerInfos.size > 1) {
log(1, "Multi language project detected, not supported yet")
}
val map = hashMapOf<String, Any?>()
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
with(map) {
put("name", currentDir.name)
put("group", "com.example")
put("version", "0.1")
put("directory", currentDir.absolutePath)
put("sourceDirectories", it.defaultSourceDirectories)
put("sourceDirectoriesTest", it.defaultTestDirectories)
put("imports", "import com.beust.kobalt.plugin.${it.name}.*")
put("directive", it.name + "Project")
/**
* Run through all the IInitContributors and return the best one.
*/
private fun findBestInitContributor(dir: File) : IInitContributor? {
val result = arrayListOf<Pair<IInitContributor, Int>>()
pluginInfo.initContributors.forEach {
it.filesManaged(dir).let { count ->
if (count > 0) {
result.add(Pair(it, count))
}
}
}
var mainDeps = arrayListOf<Dependency>()
var testDeps = arrayListOf<Dependency>()
map.put("mainDependencies", mainDeps)
map.put("testDependencies", testDeps)
File("pom.xml").let {
if (it.absoluteFile.exists()) {
importPom(it, mainDeps, testDeps, map)
}
}
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(pomFile: File, mainDeps: ArrayList<Dependency>, testDeps: ArrayList<Dependency>,
map: HashMap<String, Any?>) {
var pom = Pom("imported", pomFile.absoluteFile)
with(map) {
put("group", pom.groupId ?: "com.example")
put("artifactId", pom.artifactId ?: "com.example")
put("version", pom.version ?: "0.1")
put("name", pom.name ?: pom.artifactId)
put("repositories", pom.repositories.map({ "\"${it}\"" }).joinToString(","))
}
val properties = pom.properties
val mapped = properties.entries.toMap({it.key}, {translate(it.key)})
map.put("properties", properties.entries.map({ Pair(mapped.get(it.key), it.value) }))
val partition = pom.dependencies.groupBy { it.scope }
.flatMap { it.value }
.map { updateVersion(it, mapped) }
.sortedBy { it.groupId + ":" + it.artifactId }
.partition { it.scope != "test" }
mainDeps.addAll(partition.first)
testDeps.addAll(partition.second)
}
private fun updateVersion(dep: Dependency, mapped: Map<String, String>) =
if ( dep.version.startsWith("\${")) {
val property = dep.version.substring(2, dep.version.length - 1)
Dependency(dep.groupId, dep.artifactId, dep.packaging, "\${${mapped.get(property)}}", dep.optional,
dep.scope)
if (result.size > 0) {
Collections.sort(result, { p1, p2 -> p2.second.compareTo(p1.second) })
return result[0].first
} else {
dep
return null
}
/**
* Detect all the languages contained in this project.
*/
private fun detect(dir: File) : List<ICompilerInfo> {
val result = arrayListOf<Pair<ICompilerInfo, List<File>>>()
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 }
}
}
private fun String.upperFirst(): String {
return if (this.isBlank()) this else this.substring(0, 1).toUpperCase() + this.substring(1)
}

View file

@ -2,38 +2,17 @@ package com.beust.kobalt.api
import com.beust.kobalt.Plugins
import com.google.inject.Injector
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<File>
/** Used to generate the imports */
val name: String
/** Used to generate the imports */
val directive: String
val defaultSourceDirectories : ArrayList<String>
val defaultTestDirectories : ArrayList<String>
}
public class Kobalt {
companion object {
lateinit var INJECTOR : Injector
public val compilers : ArrayList<ICompilerInfo> = arrayListOf()
var context: KobaltContext? = null
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/",

View file

@ -2,26 +2,32 @@ package com.beust.kobalt.api
import com.beust.kobalt.maven.IClasspathDependency
import com.beust.kobalt.misc.KFiles
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import javax.xml.bind.JAXBContext
import javax.xml.bind.annotation.XmlElement
import javax.xml.bind.annotation.XmlRootElement
//
// Operations related to the parsing of plugin.xml: contributors, XML mapping, etc...
//
/////
// Contributors
//
class ProjectDescription(val project: Project, val dependsOn: List<Project>)
/**
* Implement this interface in order to add your own projects.
* Plugins that create project need to implement this interface.
*/
interface IProjectContributor {
fun projects() : List<ProjectDescription>
}
class ProjectDescription(val project: Project, val dependsOn: List<Project>)
/**
* Implement this interface to add your own entries to the classpath.
* Plugins that export classpath entries need to implement this interface.
*/
interface IClasspathContributor {
fun entriesFor(project: Project) : Collection<IClasspathDependency>
@ -39,6 +45,23 @@ class ContributorFactory : IFactory {
override fun <T> instanceOf(c: Class<T>) : T = Kobalt.INJECTOR.getInstance(c)
}
/**
* Plugins that want to participate in the --init process (they can generate files to initialize
* a new project).
*/
interface IInitContributor {
/**
* How many files your plug-in understands in the given directory. The contributor with the
* highest number will be asked to generate the build file.
*/
fun filesManaged(dir: File): Int
/**
* Generate the Build.kt file into the given OutputStream.
*/
fun generateBuildFile(os: OutputStream)
}
/////
// XML parsing
//
@ -60,6 +83,9 @@ class KobaltPluginXml {
@XmlElement(name = "project-contributors") @JvmField
var projectContributors : ContributorsXml? = null
@XmlElement(name = "init-contributors") @JvmField
var initContributors : ContributorsXml? = null
}
class ContributorXml {
@ -69,21 +95,23 @@ class ContributorXml {
class ContributorsXml {
@XmlElement(name = "class-name") @JvmField
var className: List<String> = arrayListOf<String>()
var className: List<String> = arrayListOf()
}
/**
* Turn a KobaltPluginXml (the raw content of plugin.xml) into a PluginInfo object, which contains
* all the contributors instantiated and other information that Kobalt can actually use.
* Turn a KobaltPluginXml (the raw content of plugin.xml mapped to POJO's) into a PluginInfo object, which contains
* all the contributors instantiated and other information that Kobalt can actually use. Kobalt code that
* needs to access plug-in info can then just inject a PluginInfo object.
*/
class PluginInfo(val xml: KobaltPluginXml) {
val projectContributors = arrayListOf<IProjectContributor>()
val classpathContributors = arrayListOf<IClasspathContributor>()
val initContributors = arrayListOf<IInitContributor>()
// Future contributors:
// compilerArgs
// source files
// compilers
// --init
// repos
companion object {
@ -119,6 +147,9 @@ class PluginInfo(val xml: KobaltPluginXml) {
xml.projectContributors?.className?.forEach {
projectContributors.add(factory.instanceOf(Class.forName(it)) as IProjectContributor)
}
xml.initContributors?.className?.forEach {
initContributors.add(factory.instanceOf(Class.forName(it)) as IInitContributor)
}
}
}

View file

@ -1,6 +1,7 @@
package com.beust.kobalt.api
import com.beust.kobalt.api.annotation.Directive
import com.beust.kobalt.internal.IProjectInfo
import com.beust.kobalt.maven.IClasspathDependency
import com.beust.kobalt.maven.MavenDependency
import com.beust.kobalt.misc.KFiles
@ -16,11 +17,11 @@ open public class Project(
@Directive open var packaging: String? = null,
@Directive open var dependencies: Dependencies? = null,
@Directive open var sourceSuffix : String = "",
@Directive open var compilerInfo : ICompilerInfo,
@Directive open var description : String = "",
@Directive open var scm : Scm? = null,
@Directive open var url: String? = null,
@Directive open var licenses: List<License> = arrayListOf<License>()) {
@Directive open var licenses: List<License> = arrayListOf<License>(),
val projectInfo: IProjectInfo) {
var testArgs: ArrayList<String> = arrayListOf()
@ -44,7 +45,7 @@ open public class Project(
}
var sourceDirectories : ArrayList<String> = arrayListOf()
get() = if (field.isEmpty()) compilerInfo.defaultSourceDirectories else field
get() = if (field.isEmpty()) projectInfo.defaultSourceDirectories else field
set(value) {
field = value
}
@ -57,7 +58,7 @@ open public class Project(
}
var sourceDirectoriesTest : ArrayList<String> = arrayListOf()
get() = if (field.isEmpty()) compilerInfo.defaultTestDirectories
get() = if (field.isEmpty()) projectInfo.defaultTestDirectories
else field
set(value) {
field = value

View file

@ -0,0 +1,99 @@
package com.beust.kobalt.internal
import com.beust.kobalt.ProjectGenerator
import com.beust.kobalt.api.IInitContributor
import com.beust.kobalt.maven.Pom
import com.beust.kobalt.misc.KFiles
import com.github.mustachejava.DefaultMustacheFactory
import java.io.*
import java.util.*
/**
* Abstract base class for the build generators that use build-template.mustache.
*/
abstract class BuildGenerator : IInitContributor {
abstract val defaultSourceDirectories : ArrayList<String>
abstract val defaultTestDirectories : ArrayList<String>
abstract val directive : String
abstract val name : String
abstract val fileMatch : (String) -> Boolean
override fun generateBuildFile(os: OutputStream) {
PrintWriter(os).use {
it.print(buildFileContent)
}
}
override fun filesManaged(dir: File) = KFiles.findRecursively(dir, fileMatch).size
private fun importPom(pomFile: File, mainDeps: ArrayList<Pom.Dependency>, testDeps: ArrayList<Pom.Dependency>,
map: HashMap<String, Any?>) {
var pom = Pom("imported", pomFile.absoluteFile)
with(map) {
put("group", pom.groupId ?: "com.example")
put("artifactId", pom.artifactId ?: "com.example")
put("version", pom.version ?: "0.1")
put("name", pom.name ?: pom.artifactId)
put("repositories", pom.repositories.map({ "\"$it\"" }).joinToString(","))
}
val properties = pom.properties
val mapped = properties.entries.toMap({it.key}, { ProjectGenerator.toIdentifier(it.key) })
map.put("properties", properties.entries.map({ Pair(mapped[it.key], it.value) }))
val partition = pom.dependencies.groupBy { it.scope }
.flatMap { it.value }
.map { updateVersion(it, mapped) }
.sortedBy { it.groupId + ":" + it.artifactId }
.partition { it.scope != "test" }
mainDeps.addAll(partition.first)
testDeps.addAll(partition.second)
}
private fun updateVersion(dep: Pom.Dependency, mapped: Map<String, String>) =
if ( dep.version.startsWith("\${")) {
val property = dep.version.substring(2, dep.version.length - 1)
Pom.Dependency(dep.groupId, dep.artifactId, dep.packaging, "\${${mapped[property]}}", dep.optional,
dep.scope)
} else {
dep
}
private val buildFileContent: String
get() {
val map = hashMapOf<String, Any?>()
map.put("directive", directive)
val currentDir = File(".").absoluteFile.parentFile
with(map) {
put("name", currentDir.name)
put("group", "com.example")
put("version", "0.1")
put("directory", currentDir.absolutePath)
put("sourceDirectories", defaultSourceDirectories)
put("sourceDirectoriesTest", defaultTestDirectories)
put("imports", "import com.beust.kobalt.plugin.$name.*")
put("directive", name + "Project")
}
var mainDeps = arrayListOf<Pom.Dependency>()
var testDeps = arrayListOf<Pom.Dependency>()
map.put("mainDependencies", mainDeps)
map.put("testDependencies", testDeps)
File("pom.xml").let {
if (it.absoluteFile.exists()) {
importPom(it, mainDeps, testDeps, map)
}
}
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();
return sw.toString()
}
}

View file

@ -0,0 +1,11 @@
package com.beust.kobalt.internal
import java.util.*
/**
* Data that is useful for projects to have but should not be specified in the DSL.
*/
interface IProjectInfo {
val defaultSourceDirectories: ArrayList<String>
val defaultTestDirectories: ArrayList<String>
}

View file

@ -0,0 +1,12 @@
package com.beust.kobalt.plugin.java
import com.beust.kobalt.internal.BuildGenerator
import com.google.inject.Inject
public class JavaBuildGenerator @Inject constructor (val projectInfo: JavaProjectInfo) : BuildGenerator() {
override val defaultSourceDirectories = projectInfo.defaultSourceDirectories
override val defaultTestDirectories = projectInfo.defaultTestDirectories
override val directive = "javaProject"
override val name = "java"
override val fileMatch = { f: String -> f.endsWith(".java") }
}

View file

@ -1,24 +0,0 @@
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<File> {
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"
}

View file

@ -31,11 +31,6 @@ public class JavaPlugin @Inject constructor(
override val jvmCompiler: JvmCompiler)
: JvmCompilerPlugin(localRepo, files, depFactory, dependencyManager, executors, jvmCompiler),
IProjectContributor {
init {
Kobalt.registerCompiler(JavaCompilerInfo())
}
companion object {
public const val TASK_COMPILE : String = "compile"
public const val TASK_JAVADOC : String = "javadoc"

View file

@ -25,7 +25,7 @@ public class JavaProject(
@Directive
override var packaging: String? = null)
: Project(name, version, directory, buildDirectory, group, artifactId, packaging, dependencies,
".java", JavaCompilerInfo()) {
".java", projectInfo = JavaProjectInfo()) {
override public fun toString() = toString("JavaProject", "name", name!!)
}

View file

@ -0,0 +1,10 @@
package com.beust.kobalt.plugin.java
import com.beust.kobalt.internal.IProjectInfo
import com.google.inject.Singleton
@Singleton
class JavaProjectInfo : IProjectInfo {
override val defaultSourceDirectories = arrayListOf("src/main/java", "src/main/resources")
override val defaultTestDirectories = arrayListOf("src/test/java", "src/test/resources")
}

View file

@ -0,0 +1,13 @@
package com.beust.kobalt.plugin.kotlin
import com.beust.kobalt.internal.BuildGenerator
import com.google.inject.Inject
public class KotlinBuildGenerator @Inject constructor (val projectInfo: KotlinProjectInfo) : BuildGenerator() {
override val defaultSourceDirectories = projectInfo.defaultSourceDirectories
override val defaultTestDirectories = projectInfo.defaultTestDirectories
override val directive = "kotlinProject"
override val name = "kotlin"
override val fileMatch = { f: String -> f.endsWith(".kt") }
}

View file

@ -1,22 +0,0 @@
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<File> {
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"
}

View file

@ -24,10 +24,6 @@ class KotlinPlugin @Inject constructor(
: JvmCompilerPlugin(localRepo, files, depFactory, dependencyManager, executors, jvmCompiler),
IProjectContributor, IClasspathContributor {
init {
Kobalt.registerCompiler(KotlinCompilerInfo())
}
companion object {
public const val TASK_COMPILE: String = "compile"
public const val TASK_COMPILE_TEST: String = "compileTest"

View file

@ -25,7 +25,7 @@ public class KotlinProject(
@Directive
override var packaging: String? = null)
: Project(name, version, directory, buildDirectory, group, artifactId, packaging, dependencies, ".kt",
KotlinCompilerInfo()) {
projectInfo = KotlinProjectInfo()) {
override public fun toString() = toString("KotlinProject", "name", name!!)
}

View file

@ -0,0 +1,11 @@
package com.beust.kobalt.plugin.kotlin
import com.beust.kobalt.internal.IProjectInfo
import com.google.inject.Singleton
@Singleton
class KotlinProjectInfo : IProjectInfo {
override val defaultSourceDirectories = arrayListOf("src/main/kotlin", "src/main/resources")
override val defaultTestDirectories = arrayListOf("src/test/kotlin", "src/test/resources")
}

View file

@ -9,4 +9,8 @@
<class-name>com.beust.kobalt.plugin.java.JavaPlugin</class-name>
<class-name>com.beust.kobalt.plugin.kotlin.KotlinPlugin</class-name>
</project-contributors>
<init-contributors>
<class-name>com.beust.kobalt.plugin.java.JavaBuildGenerator</class-name>
<class-name>com.beust.kobalt.plugin.kotlin.KotlinBuildGenerator</class-name>
</init-contributors>
</kobalt-plugin>

View file

@ -2,11 +2,13 @@ package com.beust.kobalt.maven
import com.beust.kobalt.Args
import com.beust.kobalt.ProjectGenerator
import com.beust.kobalt.api.PluginInfo
import com.google.inject.Inject
import org.testng.Assert
import org.testng.annotations.Test
import java.io.File
class PomTest {
class PomTest @Inject constructor(val pluginInfo: PluginInfo){
@Test
fun importPom() {
val pomSrc = File("src/test/resources/pom.xml")
@ -52,13 +54,13 @@ class PomTest {
val args = Args()
args.buildFile = file.absolutePath
args.init = true
ProjectGenerator().run(args)
ProjectGenerator(pluginInfo).run(args)
var contents = file.readText()
Assert.assertTrue(contents.contains("group = \"${pom.groupId}\""), "Should find the group defined")
Assert.assertTrue(contents.contains("name = \"${pom.name}\""), "Should find the name defined")
Assert.assertTrue(contents.contains("version = \"${pom.version}\""), "Should find the version defined")
pom.properties.forEach {
Assert.assertTrue(contents.contains("val ${ProjectGenerator.translate(it.key)} = \"${it.value}\""), "Should find the " +
Assert.assertTrue(contents.contains("val ${ProjectGenerator.toIdentifier(it.key)} = \"${it.value}\""), "Should find the " +
"property defined")
}
pom.repositories.forEach {