diff --git a/api/src/main/kotlin/io/spine/protodata/plugin/Plugin.kt b/api/src/main/kotlin/io/spine/protodata/plugin/Plugin.kt index 7093893dd..326723796 100644 --- a/api/src/main/kotlin/io/spine/protodata/plugin/Plugin.kt +++ b/api/src/main/kotlin/io/spine/protodata/plugin/Plugin.kt @@ -157,7 +157,9 @@ public fun Plugin.render( renderers().forEach { r -> r.registerWith(codegenContext) r.withTypeConventions(conventionSet) - sources.forEach(r::renderSources) + sources.asSequence() + .filter { r.supports(it.label) } + .forEach { r.renderSources(it) } } } diff --git a/api/src/main/kotlin/io/spine/protodata/renderer/Renderer.kt b/api/src/main/kotlin/io/spine/protodata/renderer/Renderer.kt index 95b675540..f3994a5ca 100644 --- a/api/src/main/kotlin/io/spine/protodata/renderer/Renderer.kt +++ b/api/src/main/kotlin/io/spine/protodata/renderer/Renderer.kt @@ -34,6 +34,7 @@ import io.spine.protodata.type.TypeNameElement import io.spine.server.BoundedContext import io.spine.server.ContextAware import io.spine.server.query.QueryingClient +import io.spine.tools.code.AnyLanguage import io.spine.tools.code.Language /** @@ -61,11 +62,23 @@ protected constructor( sources.mergeBack(relevantFiles) } + /** + * Checks if this renderer supports a given source file set. + * + * By default, a renderer supports all source sets of the [supportedLanguage]. A renderer that + * supports [AnyLanguage] also supports any source file set. + * + * Note that, even if a source file set is supported, the renderer will still only receive + * a portion of it with the file that match the [language filter][Language.matches]. + */ + public open fun supports(label: SourceFileSetLabel): Boolean = + supportedLanguage == AnyLanguage || supportedLanguage == label.language + /** * Makes changes to the given source set. * - * The source set is guaranteed to consist only of the files, containing the code in - * the [supportedLanguage]. + * The source set is guaranteed to be [supported][supports] and to consist only of the files + * containing the code in the [supportedLanguage]. * * This method may be called several times, if ProtoData is called with multiple source and * target directories. diff --git a/api/src/main/kotlin/io/spine/protodata/renderer/SourceFile.kt b/api/src/main/kotlin/io/spine/protodata/renderer/SourceFile.kt index fb006a32c..e9d86ee0a 100644 --- a/api/src/main/kotlin/io/spine/protodata/renderer/SourceFile.kt +++ b/api/src/main/kotlin/io/spine/protodata/renderer/SourceFile.kt @@ -69,9 +69,7 @@ private constructor( /** * The FS path to the file relative to the source root. */ - public val relativePath: Path, - - private var changed: Boolean = false + public val relativePath: Path ) { private lateinit var sources: SourceFileSet @@ -109,7 +107,7 @@ private constructor( * the source code. */ internal fun fromCode(relativePath: Path, code: String): SourceFile = - SourceFile(code, relativePath, changed = true) + SourceFile(code, relativePath) } /** @@ -178,7 +176,6 @@ private constructor( */ public fun overwrite(newCode: String) { this.code = newCode - this.changed = true } /** @@ -215,15 +212,12 @@ private constructor( internal fun write( baseDir: Path, charset: Charset = Charsets.UTF_8, - forceWrite: Boolean = false ) { - if (changed || forceWrite) { - val targetPath = baseDir / relativePath - targetPath.toFile() - .parentFile - .mkdirs() - targetPath.writeText(code, charset, WRITE, TRUNCATE_EXISTING, CREATE) - } + val targetPath = baseDir / relativePath + targetPath.toFile() + .parentFile + .mkdirs() + targetPath.writeText(code, charset, WRITE, TRUNCATE_EXISTING, CREATE) } /** diff --git a/api/src/main/kotlin/io/spine/protodata/renderer/SourceFileSet.kt b/api/src/main/kotlin/io/spine/protodata/renderer/SourceFileSet.kt index 75550c17f..3e9b4a490 100644 --- a/api/src/main/kotlin/io/spine/protodata/renderer/SourceFileSet.kt +++ b/api/src/main/kotlin/io/spine/protodata/renderer/SourceFileSet.kt @@ -29,6 +29,7 @@ package io.spine.protodata.renderer import com.google.common.collect.ImmutableSet.toImmutableSet import io.spine.annotation.Internal import io.spine.protodata.TypeName +import io.spine.protodata.renderer.SourceFileSet.Companion.create import io.spine.protodata.type.TypeConvention import io.spine.protodata.type.TypeNameElement import io.spine.server.query.Querying @@ -39,7 +40,6 @@ import java.nio.charset.Charset import java.nio.file.Files.walk import java.nio.file.Path import java.util.* -import kotlin.DeprecationLevel.ERROR import kotlin.io.path.absolutePathString import kotlin.io.path.exists import kotlin.io.path.isRegularFile @@ -68,6 +68,8 @@ import kotlin.text.Charsets.UTF_8 @Suppress("TooManyFunctions") // All part of the public API. public class SourceFileSet internal constructor( + public val label: SourceFileSetLabel, + files: Set, /** @@ -79,7 +81,7 @@ internal constructor( * @see outputRoot */ @get:JvmName("inputRoot") - public val inputRoot: Path, + public val inputRoot: Path?, /** * A directory where the source set should be placed after code generation. @@ -97,8 +99,9 @@ internal constructor( internal lateinit var querying: Querying init { - require(inputRoot.absolutePathString() != outputRoot.absolutePathString()) { - "Input and output roots cannot be the same, but was '${inputRoot.absolutePathString()}'" + require(inputRoot?.absolutePathString() != outputRoot.absolutePathString()) { + "Input and output roots cannot be the same, " + + "but was '${inputRoot!!.absolutePathString()}'" } val map = HashMap(files.size) this.files = files.associateByTo(map) { it.relativePath } @@ -108,14 +111,6 @@ internal constructor( @Internal public companion object { - @Deprecated( - "Use `create(..)` instead.", - replaceWith = ReplaceWith("create"), - level = ERROR - ) - public fun from(inputRoot: Path, outputRoot: Path): SourceFileSet = - create(inputRoot, outputRoot) - /** * Collects a source set from the given [input][inputRoot], assigning * the [output][outputRoot]. @@ -128,27 +123,29 @@ internal constructor( * If different from the `sourceRoot`, the files in `sourceRoot` * will not be changed. */ - public fun create(inputRoot: Path, outputRoot: Path): SourceFileSet { + public fun create( + label: SourceFileSetLabel, + inputRoot: Path, + outputRoot: Path + ): SourceFileSet { val source = inputRoot.canonical() val target = outputRoot.canonical() - if (source != target) { - checkTarget(target) - } + checkTarget(target) val files = walk(source) .filter { it.isRegularFile() } .map { SourceFile.read(source.relativize(it), source) } .collect(toImmutableSet()) - return SourceFileSet(files, source, target) + return SourceFileSet(label, files, source, target) } /** * Creates an empty source set which can be appended with new files and * written to the given target directory. */ - public fun empty(target: Path): SourceFileSet { + public fun empty(label: SourceFileSetLabel, target: Path): SourceFileSet { checkTarget(target) val files = setOf() - return SourceFileSet(files, target, target) + return SourceFileSet(label, files, null, target) } } @@ -249,9 +246,8 @@ internal constructor( it.rm(rootDir = outputRoot) } outputRoot.toFile().mkdirs() - val forceWriteFiles = inputRoot != outputRoot files.values.forEach { - it.write(outputRoot, charset, forceWriteFiles) + it.write(outputRoot, charset) } } @@ -301,7 +297,7 @@ internal constructor( * matching the given [predicate]. */ internal fun SourceFileSet.subsetWhere(predicate: (SourceFile) -> Boolean) = - SourceFileSet(this.filter(predicate).toSet(), inputRoot, outputRoot) + SourceFileSet(this.label, this.filter(predicate).toSet(), inputRoot, outputRoot) /** * Obtains absolute [normalized][normalize] version of this path. diff --git a/api/src/main/kotlin/io/spine/protodata/renderer/SourceFileSetLabel.kt b/api/src/main/kotlin/io/spine/protodata/renderer/SourceFileSetLabel.kt new file mode 100644 index 000000000..b037849d4 --- /dev/null +++ b/api/src/main/kotlin/io/spine/protodata/renderer/SourceFileSetLabel.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.protodata.renderer + +import io.spine.tools.code.Language + +/** + * A label for a source file set. + * + * The label consists of the programming language that the files use and the name of the code + * generator that created the files. + * + * A label signifies the type of code structures found in the source files. This helps renderers + * that process those sources to know what to expect from the source file set before ever checking + * the contents of the files themselves. + */ +public data class SourceFileSetLabel( + public val language: Language, + public val generator: SourceGeneratorName = DefaultGenerator +) { + + /** + * Creates a new `SourceFileSetLabel` with the given language and a custom generator name. + */ + public constructor(language: Language, generatorName: String) + : this(language, CustomGenerator(generatorName)) + + override fun toString(): String = + "${language.name}(${generator.name})" +} diff --git a/api/src/main/kotlin/io/spine/protodata/renderer/SourceGeneratorName.kt b/api/src/main/kotlin/io/spine/protodata/renderer/SourceGeneratorName.kt new file mode 100644 index 000000000..06fab20ff --- /dev/null +++ b/api/src/main/kotlin/io/spine/protodata/renderer/SourceGeneratorName.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.protodata.renderer + +/** + * A name of a source code generator. + * + * This can be a `protoc` plugin or builtin, or a custom code generator tool. + */ +public sealed interface SourceGeneratorName { + + public val name: String + get() = javaClass.simpleName.lowercase() +} + +/** + * The default generator is the default way for the Protobuf compiler to generate source code for + * a given language. + * + * For example, in Java, the default generator, given a message `Foo`, would generate a message + * classes and auxiliary types, such as classes `Foo`, `Foo.Builder`, `Foo.Parser`, + * and the interface `FooOrBuilder`. + * + * Most ProtoData plugins will likely work with code generated by the default generator. + * + * Since the Protobuf compiler does not support all the existing programming languages, + * the `DefaultGenerator` is only defined for those languages that are supported, such as Java, JS, + * C++, etc. For other languages, as well as for other code generation scenarios, + * see [CustomGenerator]. + */ +public object DefaultGenerator : SourceGeneratorName + +/** + * A name of a custom source code generator. + * + * May represent a Protobuf compiler plugin, or any other code generator. + * + * Conventionally, the name of the generator should coincide with the name of the directory where + * the generated files are placed. Users should follow this convention where possible, yet diverge + * when necessary. For example, Java gRPC stubs should be marked with the `grpc` name. However, + * files generated for Dart should be marked with the name `dart`, not `lib`. + */ +public class CustomGenerator( + override val name: String +) : SourceGeneratorName diff --git a/api/src/test/kotlin/io/spine/protodata/renderer/SourceFileSetSpec.kt b/api/src/test/kotlin/io/spine/protodata/renderer/SourceFileSetSpec.kt index 4f75f4665..45e61227c 100644 --- a/api/src/test/kotlin/io/spine/protodata/renderer/SourceFileSetSpec.kt +++ b/api/src/test/kotlin/io/spine/protodata/renderer/SourceFileSetSpec.kt @@ -31,6 +31,7 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.spine.protodata.renderer.given.PlainTextConvention import io.spine.protodata.typeName +import io.spine.tools.code.AnyLanguage import java.nio.file.Path import kotlin.io.path.Path import kotlin.io.path.div @@ -62,7 +63,7 @@ class SourceFileSetSpec { textFile.writeText("this is a non-empty file") } } - set = SourceFileSet.create(input, output) + set = SourceFileSet.create(SourceFileSetLabel(AnyLanguage), input, output) } @Test diff --git a/cli-api/src/main/kotlin/io/spine/protodata/cli/CommandLineInterface.kt b/cli-api/src/main/kotlin/io/spine/protodata/cli/CommandLineInterface.kt index 8c9c5d611..2b1a05c03 100644 --- a/cli-api/src/main/kotlin/io/spine/protodata/cli/CommandLineInterface.kt +++ b/cli-api/src/main/kotlin/io/spine/protodata/cli/CommandLineInterface.kt @@ -35,6 +35,7 @@ import io.spine.protodata.config.ConfigurationFormat.PROTO_JSON import io.spine.protodata.config.ConfigurationFormat.YAML import io.spine.protodata.plugin.Plugin import io.spine.protodata.renderer.Renderer +import io.spine.tools.code.Language import java.io.File.pathSeparator /** @@ -77,43 +78,57 @@ public object RequestParam : Parameter( ) /** - * The command-line parameter for specifying the path to the directory with - * source files to be processed. + * The command-line parameter for specifying the source and target paths. */ -public object SourceRootParam : Parameter( - name = "--source-root", - shortName = "--src", +public object PathsParam : Parameter( + name = "--paths", + shortName = "-P", help = """ - The path to a directory which contains the source files to be processed. - Skip this argument if there is no initial source to modify. + Paths specifying where the files for processing are located and where to put them after + processing. - Multiple directories can be listed separated by the system-dependent path separator (`$ps`). - In such a case, the number of directories must match the number of ${ddash.tr} - directories; source and target directories are paired up according to the order - they are provided in, so that the files from first source are written to - the first target and so on. + The value should consist of three parts, separated by the path separator character (`$ps`): + 1. The language that the files use and the name of the code generator that created + the files. The language should be a fully qualified name of + a `${Language::class.qualifiedName}` subclass that represents the language. + Alternatively, the language can be one of the preset values: + ${knownLanguages.keys.joinToString()}. + The code generator name is a plain string that identifies the name of the software + component, a `protoc` plugin or a separate program, that generated these code files. + The code generator name should go after the language in parentheses with no + white space. When using the default Protobuf code generation, the code generator name + may be omitted. + 2. The source path. Must be an absolute path where the code files are located. If there + are no code files, i.e. ProtoData should generate all the code from scratch, this part + may be left blank. + 3. The target path. Must be an absolute path where the code files should be placed after + processing. This path must either lead to an empty directory, or not lead to any + existing FS object. + + For specifying several source sets, supply this param multiple times. - When specifying multiple directories, some of them are allowed to be non-existent. - They will just be ignored along with their paired targets. But at least one directory - must exist. Otherwise, the process will end up with an error. - """ -) - -/** - * The command-line parameter for specifying the path to the directory where - * to put the processed files. - */ -public object TargetRootParam : Parameter( - name = "--target-root", - shortName = "--target", - help = """ - The path where the processed files should be placed. - May be the same as `${SourceRootParam.name}`. For editing files in-place, skip this option. + Example 1. + `<...> ${ddash.path} java(grpc):/Users/me/foo/temp/generated/main/grpc:/Users/me/foo/gen/main/grpc` + This example provides a source set for Java gRPC stubs placed under + `./temp/generated/main/grpc`. After ProtoData finishes processing the files, it should + place them under `./gen/main/grpc`. + + Example 2. + `<...> ${ddash.path} org.example.Fortran(4gen):/a/b/c:a/b/d \ + ${ddash.path} org.example.Cobol(cobolmine):/x/y:/x/w` + In this example we supply two source sets with different languages to be processed in + the same run. + + Example 3. + `<...> ${ddash.path} kotlin;D:\Foo\Bar;D:\Foo\Baz` + In this example we supply the Kotlin source set generated by the default. Note the + absence of the code generator name after the language name. - Multiple directories can be listed separated by the system-dependent path separator (`$ps`). - In such a case, the number of directories must match the number of `${dash.src}` directories. - Source and target directories are paired up according to the order they are provided in, - so that the files from first source are written to the first target and so on. + Example 4. + `<...> ${ddash.path} javascript(myself)::/a/b/c` + In this example we supply a JS source set that does not provide any existing files. + Here, it is expected that ProtoData will generate all the files into the target + directory `/a/b/c` from scratch. """ ) @@ -209,7 +224,6 @@ public object DebugLoggingParam : Parameter( private object dash { val p = lazy { PluginParam.shortName } val r = lazy { RendererParam.shortName } - val src = lazy { SourceRootParam.shortName } } /** @@ -217,10 +231,10 @@ private object dash { */ @Suppress("ClassName", "SpellCheckingInspection") // for better readability in `help` texts. private object ddash { - val tr = lazy { TargetRootParam.name } val confVal = lazy { ConfigValueParam.name } val confFmt = lazy { ConfigFormatParam.name } val renderer = lazy { RendererParam.name } + val path = lazy { PathsParam.name } } /** diff --git a/cli-api/src/main/kotlin/io/spine/protodata/cli/KnownLanguages.kt b/cli-api/src/main/kotlin/io/spine/protodata/cli/KnownLanguages.kt new file mode 100644 index 000000000..27ca05c00 --- /dev/null +++ b/cli-api/src/main/kotlin/io/spine/protodata/cli/KnownLanguages.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.protodata.cli + +import io.spine.annotation.Internal +import io.spine.tools.code.AnyLanguage +import io.spine.tools.code.Java +import io.spine.tools.code.JavaScript +import io.spine.tools.code.Kotlin +import io.spine.tools.code.Language + +/** + * The well-known languages processed by ProtoData. + */ +@Internal +public val knownLanguages: Map = mapOf( + Java.name.lowercase() to Java, + Kotlin.name.lowercase() to Kotlin, + JavaScript.name.lowercase() to JavaScript, + "any" to AnyLanguage +) diff --git a/cli/src/main/kotlin/io/spine/protodata/cli/app/Languages.kt b/cli/src/main/kotlin/io/spine/protodata/cli/app/Languages.kt new file mode 100644 index 000000000..2326c7ce5 --- /dev/null +++ b/cli/src/main/kotlin/io/spine/protodata/cli/app/Languages.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.protodata.cli.app + +import io.spine.protodata.cli.knownLanguages +import io.spine.tools.code.Language +import kotlin.reflect.KClass +import kotlin.reflect.full.isSubclassOf +import kotlin.reflect.jvm.isAccessible + +/** + * Parses a [Language] from this string. + * + * If the string represents a [well-known language name][knownLanguages], uses that language. + * Otherwise, attempts to use this string as a class name and load an instance of that class. + * If the class represents a Kotlin `object`, loads the instance of the object. Otherwise, calls + * the no-argument constructor. + */ +internal fun String.toLanguage(): Language { + require(this.isNotBlank()) { "Expected a language name of class, but got `$this`." } + return toKnownLanguage() ?: loadLanguage() +} + +private fun String.toKnownLanguage(): Language? { + val key = lowercase() + return knownLanguages[key] +} + +private fun String.loadLanguage(): Language { + val cls = languageClass() + return cls.objectInstance ?: cls.instantiate() +} + +private fun String.languageClass(): KClass { + val javaClass = Class.forName(this) + val ktClass = javaClass.kotlin + require(ktClass.isSubclassOf(Language::class)) { "Expected a language class, but got `$this`." } + @Suppress("UNCHECKED_CAST") // Ensured by the precondition above. + return ktClass as KClass +} + +private fun KClass.instantiate(): Language { + val ctor = constructors.firstOrNull { it.parameters.isEmpty() } + require(ctor != null) { "Language class `$this` must have a zero-parameter constructor." } + ctor.isAccessible = true + val instance = ctor.call() + return instance +} diff --git a/cli/src/main/kotlin/io/spine/protodata/cli/app/Main.kt b/cli/src/main/kotlin/io/spine/protodata/cli/app/Main.kt index 72d74ff26..3c1e5b92e 100644 --- a/cli/src/main/kotlin/io/spine/protodata/cli/app/Main.kt +++ b/cli/src/main/kotlin/io/spine/protodata/cli/app/Main.kt @@ -39,6 +39,7 @@ import com.github.ajalt.clikt.parameters.options.required import com.github.ajalt.clikt.parameters.options.split import com.github.ajalt.clikt.parameters.types.file import com.github.ajalt.clikt.parameters.types.path +import com.google.common.base.Splitter import com.google.protobuf.ExtensionRegistry import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest import io.spine.code.proto.FileSet @@ -54,15 +55,17 @@ import io.spine.protodata.cli.ConfigValueParam import io.spine.protodata.cli.DebugLoggingParam import io.spine.protodata.cli.InfoLoggingParam import io.spine.protodata.cli.Parameter +import io.spine.protodata.cli.PathsParam import io.spine.protodata.cli.PluginParam import io.spine.protodata.cli.RendererParam import io.spine.protodata.cli.RequestParam -import io.spine.protodata.cli.SourceRootParam -import io.spine.protodata.cli.TargetRootParam import io.spine.protodata.cli.UserClasspathParam import io.spine.protodata.config.Configuration import io.spine.protodata.config.ConfigurationFormat +import io.spine.protodata.renderer.CustomGenerator +import io.spine.protodata.renderer.DefaultGenerator import io.spine.protodata.renderer.SourceFileSet +import io.spine.protodata.renderer.SourceFileSetLabel import io.spine.string.Separator.Companion.nl import io.spine.string.pi import io.spine.string.ti @@ -70,6 +73,7 @@ import io.spine.tools.code.manifest.Version import java.io.File import java.io.File.pathSeparator import java.nio.file.Path +import kotlin.io.path.Path import kotlin.io.path.exists import kotlin.system.exitProcess @@ -111,6 +115,10 @@ internal class Run(version: String) : CliktCommand( epilog = "https://github.com/SpineEventEngine/ProtoData/", printHelpOnEmptyArgs = true ), WithLogging { + + private val pathSplitter = Splitter.on(pathSeparator) + private val labelRegex = Regex("(?[\\w.]+)(\\((?\\w+)\\))?") + private fun Parameter.toOption(completionCandidates: CompletionCandidates? = null) = option( name, shortName, help = help, @@ -135,17 +143,9 @@ internal class Run(version: String) : CliktCommand( mustBeReadable = true ).required() - private val sourceRoots: List? - by SourceRootParam.toOption().path( - canBeFile = false, - canBeSymlink = false - ).splitPaths() - - private val targetRoots: List? - by TargetRootParam.toOption().path( - canBeFile = false, - canBeSymlink = false - ).splitPaths().required() + private val paths: List + by PathsParam.toOption() + .multiple(required = true) private val classpath: List? by UserClasspathParam.toOption().path( @@ -230,30 +230,48 @@ internal class Run(version: String) : CliktCommand( } private fun createSourceFileSets(): List { - checkPaths() - val sources = sourceRoots - val targets = (targetRoots ?: sources)!! - return sources - ?.zip(targets) - ?.filter { (s, _) -> s.exists() } - ?.map { (s, t) -> SourceFileSet.create(s, t) } - ?: targets.oneSetWithNoFiles() - } - - private fun checkPaths() { - if (sourceRoots == null) { - checkUsage(targetRoots!!.size == 1) { - "When not providing a source directory, only one target directory must be present." + val rawPaths = paths.map { pathSplitter.splitToList(it) } + val existingPaths = rawPaths.filter { + val srcPath = it[1] + srcPath.isNotBlank() && Path(srcPath).exists() + }.toList() + if (existingPaths.isEmpty()) { + logger.atInfo().log { + "No existing source paths supplied." } + require(paths.size == 1) { "Expected exactly one target path, but was ${paths}." } + val (rawLabel, _, rawTarget) = rawPaths.first() + val label = loadLabel(rawLabel) + val targetPath = Path(rawTarget) + return listOf(SourceFileSet.empty(label, targetPath)) } - if (sourceRoots != null && targetRoots != null) { - checkUsage(sourceRoots!!.size == targetRoots!!.size) { - "Mismatched amount of directories. Given ${sourceRoots!!.size} sources " + - "and ${targetRoots!!.size} targets." + return existingPaths.map { + val (rawLabel, rawSrc, rawTarget) = it + logger.atInfo().log { + "Source file set: label: `$rawLabel`; source: `$rawSrc`; target: `$rawTarget`." } + val sourceLabel = loadLabel(rawLabel) + val srcPath = Path(rawSrc) + val targetPath = Path(rawTarget) + SourceFileSet.create(sourceLabel, srcPath, targetPath) } } + private fun loadLabel(label: String): SourceFileSetLabel { + val match = labelRegex.matchEntire(label) + require(match != null) { "Could not load source label: `$label`." } + val language = match.groups["lang"]!!.value.toLanguage() + val generatorMatch = match.groups["generator"] + val generator = if (generatorMatch != null) { + val rawGeneratorName = generatorMatch.value + CustomGenerator(rawGeneratorName) + } else { + DefaultGenerator + } + val sourceLabel = SourceFileSetLabel(language, generator) + return sourceLabel + } + private fun loadPlugins() = load(PluginBuilder(), plugins) private fun loadRenderers() = load(RendererBuilder(), renderers) @@ -327,12 +345,6 @@ internal class Run(version: String) : CliktCommand( private fun printError(message: String?) = echo(message, trailingNewline = true, err = true) } -/** - * Creates a list that contain a single, empty source set. - */ -private fun List.oneSetWithNoFiles(): List = - listOf(SourceFileSet.empty(first())) - /** * Throws an [UsageError] with the result of calling [lazyMessage] if the [condition] isn't met. */ diff --git a/cli/src/test/kotlin/io/spine/protodata/cli/app/CliTest.kt b/cli/src/test/kotlin/io/spine/protodata/cli/app/CliTest.kt index 605ea2d29..40272598d 100644 --- a/cli/src/test/kotlin/io/spine/protodata/cli/app/CliTest.kt +++ b/cli/src/test/kotlin/io/spine/protodata/cli/app/CliTest.kt @@ -49,10 +49,12 @@ import io.spine.protodata.test.ProtoEchoRenderer import io.spine.protodata.test.TestPlugin import io.spine.protodata.test.UnderscorePrefixRenderer import io.spine.protodata.test.echo +import io.spine.protodata.test.pathsForJava import io.spine.time.LocalDates import io.spine.time.Month.SEPTEMBER import io.spine.time.toInstant import io.spine.type.toCompactJson +import java.io.File import java.nio.file.Path import kotlin.io.path.createFile import kotlin.io.path.name @@ -114,8 +116,7 @@ class `Command line application should` { launchApp( "-p", TestPlugin::class.jvmName, "-r", UnderscorePrefixRenderer::class.jvmName, - "--src", srcRoot.toString(), - "--target", targetRoot.toString(), + "--paths", pathsForJava(srcRoot, targetRoot), "-t", codegenRequestFile.toString() ) targetFile.readText() shouldBe "_${Project::class.simpleName}.getUuid() " @@ -126,8 +127,7 @@ class `Command line application should` { launchApp( "-p", DefaultOptionsCounterPlugin::class.jvmName, "-r", DefaultOptionsCounterRenderer::class.jvmName, - "--src", srcRoot.toString(), - "--target", targetRoot.toString(), + "--paths", pathsForJava(srcRoot, targetRoot), "-t", codegenRequestFile.toString(), ) val generatedFile = targetRoot.resolve(DefaultOptionsCounterRenderer.FILE_NAME) @@ -148,8 +148,7 @@ class `Command line application should` { launchApp( "-r", EchoRenderer::class.jvmName, - "--src", srcRoot.toString(), - "--target", targetRoot.toString(), + "--paths", pathsForJava(srcRoot, targetRoot), "-t", codegenRequestFile.toString(), "-c", configFile.pathString ) @@ -161,8 +160,7 @@ class `Command line application should` { val name = "Mr. World" launchApp( "-r", EchoRenderer::class.jvmName, - "--src", srcRoot.toString(), - "--target", targetRoot.toString(), + "--paths", pathsForJava(srcRoot, targetRoot), "-t", codegenRequestFile.toString(), "--cv", """{ "value": "$name" }""", "--cf", "json" @@ -185,8 +183,7 @@ class `Command line application should` { launchApp( "-r", EchoRenderer::class.jvmName, - "--src", srcRoot.toString(), - "--target", targetRoot.toString(), + "--paths", pathsForJava(srcRoot, targetRoot), "-t", codegenRequestFile.toString(), "-c", configFile.pathString ) @@ -204,8 +201,7 @@ class `Command line application should` { }.toCompactJson() launchApp( "-r", ProtoEchoRenderer::class.jvmName, - "--src", srcRoot.toString(), - "--target", targetRoot.toString(), + "--paths", pathsForJava(srcRoot, targetRoot), "-t", codegenRequestFile.toString(), "--cv", json, "--cf", "proto_json" @@ -232,8 +228,7 @@ class `Command line application should` { launchApp( "-r", ProtoEchoRenderer::class.jvmName, - "--src", srcRoot.toString(), - "--target", targetRoot.toString(), + "--paths", pathsForJava(srcRoot, targetRoot), "-t", codegenRequestFile.toString(), "-c", configFile.pathString ) @@ -255,8 +250,7 @@ class `Command line application should` { """.trimIndent()) launchApp( "-r", EchoRenderer::class.jvmName, - "--src", srcRoot.toString(), - "--target", targetRoot.toString(), + "--paths", pathsForJava(srcRoot, targetRoot), "-t", codegenRequestFile.toString(), "-c", configFile.pathString ) @@ -268,8 +262,7 @@ class `Command line application should` { val plainString = "dont.mail.me:42@example.org" launchApp( "-r", PlainStringRenderer::class.jvmName, - "--src", srcRoot.toString(), - "--target", targetRoot.toString(), + "--paths", pathsForJava(srcRoot, targetRoot), "-t", codegenRequestFile.toString(), "--cv", plainString, "--cf", "plain" @@ -287,32 +280,19 @@ class `Command line application should` { assertMissingOption { launchApp( "-p", TestPlugin::class.jvmName, - "--src", srcRoot.toString(), - "--target", targetRoot.toString(), + "--paths", pathsForJava(srcRoot, targetRoot), "-t", codegenRequestFile.toString() ) } } - @Test - fun `target dir is missing`() { - assertThrows { - launchApp( - "-p", TestPlugin::class.jvmName, - "-r", UnderscorePrefixRenderer::class.jvmName, - "-t", codegenRequestFile.toString(), - "--src", srcRoot.toString() - ) - } - } - @Test fun `code generator request file is missing`() { assertMissingOption { launchApp( "-p", TestPlugin::class.jvmName, "-r", UnderscorePrefixRenderer::class.jvmName, - "--src", srcRoot.toString() + "--paths", pathsForJava(srcRoot, targetRoot) ) } } diff --git a/cli/src/test/kotlin/io/spine/protodata/cli/app/LoggingLevelSpec.kt b/cli/src/test/kotlin/io/spine/protodata/cli/app/LoggingLevelSpec.kt index fd8287d47..354d7a3be 100644 --- a/cli/src/test/kotlin/io/spine/protodata/cli/app/LoggingLevelSpec.kt +++ b/cli/src/test/kotlin/io/spine/protodata/cli/app/LoggingLevelSpec.kt @@ -39,6 +39,7 @@ import io.spine.protodata.renderer.Renderer import io.spine.protodata.renderer.SourceFileSet import io.spine.protodata.test.Project import io.spine.protodata.test.ProjectProto +import io.spine.protodata.test.pathsForJava import io.spine.tools.code.AnyLanguage import java.nio.file.Path import kotlin.io.path.writeBytes @@ -118,8 +119,7 @@ class `ProtoData CLI logging levels should` { private fun launchWithLoggingParams(vararg argv: String) { val params = mutableListOf( "-r", LoggingLevelAsserter::class.jvmName, - "--src", srcRoot.toString(), - "--target", targetRoot.toString(), + "--paths", pathsForJava(srcRoot, targetRoot), "-t", codegenRequestFile.toString(), "--cv", "testing-logging-levels", "--cf", "plain", diff --git a/codegen/java/src/main/kotlin/io/spine/protodata/codegen/java/JavaRenderer.kt b/codegen/java/src/main/kotlin/io/spine/protodata/codegen/java/JavaRenderer.kt index 9b349aab5..4b4a7473f 100644 --- a/codegen/java/src/main/kotlin/io/spine/protodata/codegen/java/JavaRenderer.kt +++ b/codegen/java/src/main/kotlin/io/spine/protodata/codegen/java/JavaRenderer.kt @@ -30,7 +30,9 @@ import io.spine.protodata.File import io.spine.protodata.FilePath import io.spine.protodata.ProtobufSourceFile import io.spine.protodata.TypeName +import io.spine.protodata.renderer.DefaultGenerator import io.spine.protodata.renderer.Renderer +import io.spine.protodata.renderer.SourceFileSetLabel import io.spine.tools.code.Java import java.nio.file.Path @@ -47,6 +49,13 @@ public abstract class JavaRenderer : Renderer(Java) { return type.javaClassName(file) } + /** + * A `JavaRenderer` supports only the default Java sources, i.e. the source files generated + * by the vanilla Protobuf compiler for the given Proto definitions. + */ + override fun supports(label: SourceFileSetLabel): Boolean = + label.language == Java && label.generator == DefaultGenerator + /** * Obtains the path the `.java` file generated from the given type. * diff --git a/codegen/java/src/test/kotlin/io/spine/protodata/codegen/java/WithSourceFileSet.kt b/codegen/java/src/test/kotlin/io/spine/protodata/codegen/java/WithSourceFileSet.kt index 7844ba9e4..21416e4ef 100644 --- a/codegen/java/src/test/kotlin/io/spine/protodata/codegen/java/WithSourceFileSet.kt +++ b/codegen/java/src/test/kotlin/io/spine/protodata/codegen/java/WithSourceFileSet.kt @@ -27,6 +27,8 @@ package io.spine.protodata.codegen.java import io.spine.protodata.renderer.SourceFileSet +import io.spine.protodata.renderer.SourceFileSetLabel +import io.spine.tools.code.Java import java.nio.file.Path import java.nio.file.StandardOpenOption import kotlin.io.path.writeText @@ -51,6 +53,7 @@ open class WithSourceFileSet protected constructor() { val contents = javaClass.classLoader.getResource(JAVA_FILE)!!.readText() sourceFile.parent.toFile().mkdirs() sourceFile.writeText(contents, options = arrayOf(StandardOpenOption.CREATE_NEW)) - sources = listOf(SourceFileSet.create(sourceRoot, targetRoot)) + val label = SourceFileSetLabel(Java) + sources = listOf(SourceFileSet.create(label, sourceRoot, targetRoot)) } } diff --git a/codegen/java/src/test/kotlin/io/spine/protodata/codegen/java/annotation/GeneratedTypeAnnotationSpec.kt b/codegen/java/src/test/kotlin/io/spine/protodata/codegen/java/annotation/GeneratedTypeAnnotationSpec.kt index 32e0ef209..fb6cbe95e 100644 --- a/codegen/java/src/test/kotlin/io/spine/protodata/codegen/java/annotation/GeneratedTypeAnnotationSpec.kt +++ b/codegen/java/src/test/kotlin/io/spine/protodata/codegen/java/annotation/GeneratedTypeAnnotationSpec.kt @@ -35,7 +35,6 @@ import io.spine.protodata.backend.Pipeline import io.spine.protodata.codegen.java.JAVA_FILE import io.spine.protodata.codegen.java.WithSourceFileSet import io.spine.protodata.codegen.java.annotation.GeneratedTypeAnnotation.Companion.currentDateTime -import io.spine.protodata.codegen.java.file.PrintBeforePrimaryDeclaration import io.spine.protodata.renderer.SourceFile import io.spine.time.testing.FrozenMadHatterParty import io.spine.time.toTimestamp diff --git a/compiler/src/test/kotlin/io/spine/protodata/InsertionPointSpec.kt b/compiler/src/test/kotlin/io/spine/protodata/InsertionPointSpec.kt index eb1e4f533..9f554e8b2 100644 --- a/compiler/src/test/kotlin/io/spine/protodata/InsertionPointSpec.kt +++ b/compiler/src/test/kotlin/io/spine/protodata/InsertionPointSpec.kt @@ -34,6 +34,7 @@ import io.kotest.matchers.collections.shouldNotHaveSize import io.kotest.matchers.string.shouldContain import io.spine.protodata.backend.Pipeline import io.spine.protodata.renderer.SourceFileSet +import io.spine.protodata.renderer.SourceFileSetLabel import io.spine.protodata.renderer.codeLine import io.spine.protodata.test.CatOutOfTheBoxEmancipator import io.spine.protodata.test.CompanionFramer @@ -46,6 +47,8 @@ import io.spine.protodata.test.KotlinInsertionPoint.FILE_START import io.spine.protodata.test.KotlinInsertionPoint.LINE_FOUR_COL_THIRTY_THREE import io.spine.protodata.test.NonVoidMethodPrinter import io.spine.protodata.test.VariousKtInsertionPointsPrinter +import io.spine.tools.code.Java +import io.spine.tools.code.Kotlin import java.lang.System.lineSeparator import java.nio.file.Path import kotlin.io.path.createFile @@ -68,12 +71,11 @@ class InsertionPointsSpec { @BeforeEach fun preparePipeline(@TempDir input: Path, @TempDir output: Path) { - val inputKtFile = input / "sources.kt" - val inputJavaFile = input / "Source.java" - - inputKtFile.createFile() - inputJavaFile.createFile() - inputKtFile.writeText(""" + val kt = "kt" + val java = "jj" + val inputKtFile = input / "$kt/sources.kt" + val inputJavaFile = input / "$java/Source.java" + inputKtFile.create(""" class LabMouse { companion object { const val I_AM_CONSTANT: String = "!!" @@ -85,7 +87,7 @@ class InsertionPointsSpec { } """.trimIndent() ) - inputJavaFile.writeText(""" + inputJavaFile.create(""" package com.example; public class Source { @@ -102,6 +104,8 @@ class InsertionPointsSpec { } """.trimIndent() ) + val javaSet = SourceFileSet.create(SourceFileSetLabel(Java), input / java, output / java) + val ktSet = SourceFileSet.create(SourceFileSetLabel(Kotlin), input / kt, output / kt) Pipeline( plugins = listOf(), renderers = listOf( @@ -109,11 +113,11 @@ class InsertionPointsSpec { NonVoidMethodPrinter(), IgnoreValueAnnotator(), CompanionFramer(), CompanionLalalaRenderer() ), - sources = listOf(SourceFileSet.create(input, output)), + sources = listOf(javaSet, ktSet), request = PluginProtos.CodeGeneratorRequest.getDefaultInstance(), )() - kotlinFile = output / inputKtFile.name - javaFile = output / inputJavaFile.name + kotlinFile = output / kt / inputKtFile.name + javaFile = output / java / inputJavaFile.name } @Test @@ -155,3 +159,9 @@ class InsertionPointsSpec { lines[2] shouldContain Regex("$LALALA\\s+companion.+$LALALA\\s+object", DOT_MATCHES_ALL) } } + +private fun Path.create(content: String) { + parent.toFile().mkdirs() + createFile() + writeText(content) +} diff --git a/compiler/src/test/kotlin/io/spine/protodata/backend/PipelineSpec.kt b/compiler/src/test/kotlin/io/spine/protodata/backend/PipelineSpec.kt index ddcd66f88..2a0ad05d1 100644 --- a/compiler/src/test/kotlin/io/spine/protodata/backend/PipelineSpec.kt +++ b/compiler/src/test/kotlin/io/spine/protodata/backend/PipelineSpec.kt @@ -31,14 +31,13 @@ import com.google.common.truth.Truth.assertThat import com.google.errorprone.annotations.CanIgnoreReturnValue import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest import com.google.protobuf.compiler.codeGeneratorRequest -import io.kotest.assertions.shouldFail -import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.spine.protodata.ConfigurationError import io.spine.protodata.config.Configuration import io.spine.protodata.config.ConfigurationFormat import io.spine.protodata.renderer.SourceFileSet +import io.spine.protodata.renderer.SourceFileSetLabel import io.spine.protodata.renderer.codeLine import io.spine.protodata.test.AnnotationInsertionPointPrinter import io.spine.protodata.test.CatOutOfTheBoxEmancipator @@ -64,7 +63,9 @@ import io.spine.protodata.test.TestPlugin import io.spine.protodata.test.UnderscorePrefixRenderer import io.spine.testing.assertDoesNotExist import io.spine.testing.assertExists -import java.io.IOException +import io.spine.tools.code.Java +import io.spine.tools.code.JavaScript +import io.spine.tools.code.Kotlin import java.nio.file.Path import kotlin.io.path.createFile import kotlin.io.path.div @@ -83,21 +84,23 @@ import org.junit.jupiter.api.io.TempDir @DisplayName("`Pipeline` should") class PipelineSpec { - private lateinit var srcRoot : Path - private lateinit var targetRoot : Path + private val label = SourceFileSetLabel(Java) + private lateinit var srcRoot: Path + private lateinit var targetRoot: Path private lateinit var codegenRequestFile: Path private lateinit var targetFile: Path private lateinit var request: CodeGeneratorRequest private lateinit var renderer: UnderscorePrefixRenderer - private lateinit var overwritingSourceSet: SourceFileSet + private val sourceFileSet: SourceFileSet + get() = SourceFileSet.create(label, srcRoot, targetRoot) @BeforeEach fun prepareSources(@TempDir sandbox: Path) { - srcRoot = sandbox.resolve("src") + srcRoot = sandbox / "src" srcRoot.toFile().mkdirs() - targetRoot = sandbox.resolve("target") + targetRoot = sandbox / "target" targetRoot.toFile().mkdirs() - codegenRequestFile = sandbox.resolve("code-gen-request.bin") + codegenRequestFile = sandbox / "code-gen-request.bin" // Correctness of the Java source code is of no importance for this test suite. val sourceFileName = "SourceCode.java" @@ -113,13 +116,12 @@ class PipelineSpec { codegenRequestFile.writeBytes(request.toByteArray()) renderer = UnderscorePrefixRenderer() - overwritingSourceSet = SourceFileSet.create(srcRoot, targetRoot) - targetFile = targetRoot.resolve(sourceFileName) + targetFile = targetRoot / sourceFileName } @CanIgnoreReturnValue private fun write(path: String, code: String) { - val file = srcRoot.resolve(path) + val file = srcRoot / path file.parent.toFile().mkdirs() file.writeText(code) } @@ -129,7 +131,7 @@ class PipelineSpec { Pipeline( plugin = TestPlugin(), renderer = renderer, - sources = overwritingSourceSet, + sources = sourceFileSet, request )() assertTextIn(targetFile).isEqualTo("_Journey worth taking") @@ -140,10 +142,10 @@ class PipelineSpec { Pipeline( plugin = TestPlugin(), renderer = InternalAccessRenderer(), - sources = overwritingSourceSet, + sources = sourceFileSet, request )() - val newClass = targetRoot.resolve("spine/protodata/test/JourneyInternal.java") + val newClass = targetRoot / "spine/protodata/test/JourneyInternal.java" assertExists(newClass) assertTextIn(newClass).contains("class JourneyInternal") } @@ -155,7 +157,7 @@ class PipelineSpec { Pipeline( plugin = TestPlugin(), renderer = DeletingRenderer(), - sources = SourceFileSet.create(srcRoot, targetRoot), + sources = sourceFileSet, request )() assertDoesNotExist(targetRoot / path) @@ -173,7 +175,7 @@ class PipelineSpec { JavaGenericInsertionPointPrinter(), renderer ), - sources = SourceFileSet.create(srcRoot, targetRoot), + sources = sourceFileSet, request )() @@ -197,7 +199,7 @@ class PipelineSpec { renderers = listOf( renderer ), - sources = SourceFileSet.create(srcRoot, targetRoot), + sources = sourceFileSet, request )() textIn(targetRoot / path) shouldBe textIn(srcRoot / path) @@ -216,7 +218,7 @@ class PipelineSpec { Pipeline( plugins = listOf(), renderers = listOf(AnnotationInsertionPointPrinter(), NullableAnnotationRenderer()), - sources = listOf(SourceFileSet.create(srcRoot, targetRoot)), + sources = listOf(sourceFileSet), request = CodeGeneratorRequest.getDefaultInstance() )() assertTextIn(targetRoot / path) @@ -232,7 +234,7 @@ class PipelineSpec { Pipeline( plugin = TestPlugin(), renderers = listOf(renderer), - sources = SourceFileSet.create(srcRoot, targetRoot), + sources = sourceFileSet, request )() textIn(targetRoot / path) shouldBe textIn(srcRoot / path) @@ -240,21 +242,33 @@ class PipelineSpec { @Test fun `use different renderers for different files`() { - val jsPath = "test/source.js" - val ktPath = "corp/acme/test/Source.kt" + val js = "js" + val kt = "kt" + val jsPath = "$js/test/source.js" + val ktPath = "$kt/corp/acme/test/Source.kt" write(jsPath, "alert('Hello')") write(ktPath, "println(\"Hello\")") + val jsSet = SourceFileSet.create( + SourceFileSetLabel(JavaScript), + srcRoot / js, + targetRoot / js + ) + val ktSet = SourceFileSet.create( + SourceFileSetLabel(Kotlin), + srcRoot / kt, + targetRoot / kt + ) Pipeline( - plugin = TestPlugin(), + plugins = listOf(TestPlugin()), renderers = listOf( JsRenderer(), KtRenderer() ), - sources = SourceFileSet.create(srcRoot, targetRoot), + sources = listOf(jsSet, ktSet), request )() - assertTextIn(targetRoot / jsPath).contains("Hello JavaScript") - assertTextIn(targetRoot / ktPath).contains("Hello Kotlin") + textIn(targetRoot / jsPath) shouldContain "Hello JavaScript" + textIn(targetRoot / ktPath) shouldContain "Hello Kotlin" } @Test @@ -265,7 +279,7 @@ class PipelineSpec { JavaGenericInsertionPointPrinter(), CatOutOfTheBoxEmancipator() ), - sources = overwritingSourceSet, + sources = sourceFileSet, request )() assertTextIn(targetFile).run { @@ -283,7 +297,7 @@ class PipelineSpec { JavaGenericInsertionPointPrinter(), JsRenderer() ), - sources = overwritingSourceSet, + sources = sourceFileSet, request )() assertTextIn(targetFile).run { @@ -298,17 +312,17 @@ class PipelineSpec { Pipeline( plugin = TestPlugin(), renderer = InternalAccessRenderer(), - sources = SourceFileSet.create(srcRoot, destination), + sources = SourceFileSet.create(label, srcRoot, destination), request )() val path = "spine/protodata/test/JourneyInternal.java" - val newClass = destination.resolve(path) + val newClass = destination / path assertExists(newClass) assertTextIn(newClass).contains("class JourneyInternal") - val newClassInSourceRoot = srcRoot.resolve(path) + val newClassInSourceRoot = srcRoot / path assertDoesNotExist(newClassInSourceRoot) } @@ -317,7 +331,7 @@ class PipelineSpec { Pipeline( TestPlugin(), NoOpRenderer(), - SourceFileSet.create(srcRoot, targetRoot), + sourceFileSet, request )() assertExists(targetFile) @@ -341,20 +355,20 @@ class PipelineSpec { TestPlugin(), NoOpRenderer(), listOf( - SourceFileSet.create(srcRoot, destination1), - SourceFileSet.create(source2, destination2) + SourceFileSet.create(label, srcRoot, destination1), + SourceFileSet.create(label, source2, destination2) ), request )() - assertExists(destination1.resolve(targetFile.name)) - assertExists(destination2.resolve(secondSourceFile.name)) + assertExists(destination1 / targetFile.name) + assertExists(destination2 / secondSourceFile.name) - assertTextIn(destination2.resolve(secondSourceFile.name)) + assertTextIn(destination2 / secondSourceFile.name) .isEqualTo(secondSourceFile.readText()) - assertDoesNotExist(destination1.resolve(secondSourceFile.name)) - assertDoesNotExist(destination2.resolve(targetFile.name)) + assertDoesNotExist(destination1 / secondSourceFile.name) + assertDoesNotExist(destination2 / targetFile.name) } @Test @@ -370,15 +384,15 @@ class PipelineSpec { plugin = TestPlugin(), renderer = PlainStringRenderer(), listOf( - SourceFileSet.create(srcRoot, destination1), - SourceFileSet.create(source2, destination2) + SourceFileSet.create(label, srcRoot, destination1), + SourceFileSet.create(label, source2, destination2) ), request, Configuration.rawValue(expectedContent, ConfigurationFormat.PLAIN) )() - val firstFile = destination1.resolve(ECHO_FILE) - val secondFile = destination2.resolve(ECHO_FILE) + val firstFile = destination1 / ECHO_FILE + val secondFile = destination2 / ECHO_FILE assertExists(firstFile) assertExists(secondFile) @@ -403,15 +417,15 @@ class PipelineSpec { PrependingRenderer() ), sources = listOf( - SourceFileSet.create(srcRoot, destination1), - SourceFileSet.create(source2, destination2) + SourceFileSet.create(label, srcRoot, destination1), + SourceFileSet.create(label, source2, destination2) ), request )() - assertDoesNotExist(destination2.resolve(existingFilePath)) + assertDoesNotExist(destination2 / existingFilePath) - val writtenFile = destination1.resolve(existingFilePath) + val writtenFile = destination1 / existingFilePath assertExists(writtenFile) assertTextIn(writtenFile).contains(expectedContent) } @@ -426,7 +440,7 @@ class PipelineSpec { val pipeline = Pipeline( plugin = DocilePlugin(policies = setOf(policy)), renderer = renderer, - sources = overwritingSourceSet, + sources = sourceFileSet, request ) val error = assertThrows { pipeline() } @@ -444,11 +458,11 @@ class PipelineSpec { viewRepositories = setOf(DeletedTypeRepository()) ), renderer = renderer, - sources = overwritingSourceSet, + sources = sourceFileSet, request ) val error = assertThrows { pipeline() } - error.message shouldContain(viewClass.name) + error.message shouldContain viewClass.name } } } diff --git a/config b/config index d4f232214..327bc212a 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit d4f232214236e9bca746ab2984ce6e46c5d79600 +Subproject commit 327bc212a335bf4badd560394364def291c0282e diff --git a/gradle-api/src/main/kotlin/io/spine/protodata/gradle/CodegenSettings.kt b/gradle-api/src/main/kotlin/io/spine/protodata/gradle/CodegenSettings.kt index 9cfebeaa7..886ee0c24 100644 --- a/gradle-api/src/main/kotlin/io/spine/protodata/gradle/CodegenSettings.kt +++ b/gradle-api/src/main/kotlin/io/spine/protodata/gradle/CodegenSettings.kt @@ -26,6 +26,10 @@ package io.spine.protodata.gradle +import com.google.common.collect.Multimap +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.SourceSet + /** * Configures code generation process performed by ProtoData. */ @@ -37,12 +41,6 @@ public interface CodegenSettings { */ public fun plugins(vararg classNames: String) - /** - * Passes given names of Java classes to ProtoData as - * the `io.spine.protodata.renderer.Renderer` classes. - */ - public fun renderers(vararg classNames: String) - /** * Passes given names of Java classes to ProtoData as * the `io.spine.protodata.option.OptionsProvider` classes. @@ -74,4 +72,40 @@ public interface CodegenSettings { * By default, points at the `$projectDir/generated/` directory. */ public var targetBaseDir: Any + + /** + * `SourcePaths` to run ProtoData on. + * + * The keys to the multimap are the Gradle's source set names, such as `main` and `test`. + */ + public val paths: Multimap + + /** + * Configures a particular launch of ProtoData for the given source set. + * + * @param sourceSet the source set for which ProtoData is launched. + * @param configure the block configuring the launch. + */ + public fun launchFor(sourceSet: Provider, configure: Launch.() -> Unit): Unit = + launchFor(sourceSet.get().name, configure) + + /** + * Configures a particular launch of ProtoData for the given source set. + * + * @param sourceSet the source set for which ProtoData is launched. + * @param configure the block configuring the launch. + */ + public fun launchFor(sourceSet: SourceSet, configure: Launch.() -> Unit): Unit = + launchFor(sourceSet.name, configure) + + /** + * Configures a particular launch of ProtoData for the source set with the given name. + * + * @param sourceSetName the name of the source set for which ProtoData is launched. + * @param configure the block configuring the launch. + */ + public fun launchFor(sourceSetName: String, configure: Launch.() -> Unit) { + val l = Launch(sourceSetName, this) + configure(l) + } } diff --git a/gradle-api/src/main/kotlin/io/spine/protodata/gradle/Launch.kt b/gradle-api/src/main/kotlin/io/spine/protodata/gradle/Launch.kt new file mode 100644 index 000000000..51c91393a --- /dev/null +++ b/gradle-api/src/main/kotlin/io/spine/protodata/gradle/Launch.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.protodata.gradle + +/** + * A DSL component configuring a particular ProtoData launch. + * + * ProtoData is launched separately for each Gradle source set. This part of the DSL configures + * the source paths that are used by ProtoData in the given launch. + */ +public class Launch +internal constructor( + private val sourceSetName: String, + private val settings: CodegenSettings +) { + + /** + * Adds the configured [SourcePaths] to the launch. + * + * @param configure the code block configuring the paths. + */ + public fun sourceFileSet(configure: SourcePaths.() -> Unit) { + val paths = SourcePaths() + configure(paths) + settings.paths.put(sourceSetName, paths) + } +} diff --git a/gradle-api/src/main/kotlin/io/spine/protodata/gradle/SourcePaths.kt b/gradle-api/src/main/kotlin/io/spine/protodata/gradle/SourcePaths.kt new file mode 100644 index 000000000..909f9987b --- /dev/null +++ b/gradle-api/src/main/kotlin/io/spine/protodata/gradle/SourcePaths.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.protodata.gradle + +import io.spine.annotation.Internal +import io.spine.tools.code.Language +import java.io.File + +/** + * The paths to source and target dirs that constitute a single source file set. + * + * This is a part of the Gradle plugin's DSL for configuring ProtoData. Additional `SourcePaths` + * instances may be created for ProtoData to pick up. + */ +public data class SourcePaths( + public var source: String? = null, + public var target: String? = null, + public var language: String? = null, + public var generatorName: String = "" +) { + + /** + * Creates new `SourcePaths` from the given directories, language and generator name. + */ + public constructor( + source: File, + target: File, + language: Language, + generator: String + ) : this( + source.absolutePath, + target.absolutePath, + language::class.qualifiedName, + generator + ) + + /** + * Ensures that all the necessary properties are present. + */ + @Internal + public fun checkAllSet() { + require(!target.isNullOrBlank()) { missingMessage("target") } + require(!language.isNullOrBlank()) { missingMessage("language") } + } + + private fun missingMessage(propName: String) = "Source file set requires the `$propName`." +} diff --git a/gradle-plugin/src/functionalTest/kotlin/io/spine/protodata/gradle/plugin/PluginSpec.kt b/gradle-plugin/src/functionalTest/kotlin/io/spine/protodata/gradle/plugin/PluginSpec.kt index ec5d8b872..44a5cd588 100644 --- a/gradle-plugin/src/functionalTest/kotlin/io/spine/protodata/gradle/plugin/PluginSpec.kt +++ b/gradle-plugin/src/functionalTest/kotlin/io/spine/protodata/gradle/plugin/PluginSpec.kt @@ -26,6 +26,7 @@ package io.spine.protodata.gradle.plugin +import io.kotest.assertions.withClue import io.kotest.matchers.shouldBe import io.spine.protodata.gradle.Names.GRADLE_PLUGIN_ID import io.spine.testing.SlowTest @@ -163,6 +164,19 @@ class PluginSpec { assertExists(generatedGrpcDir.resolve(serviceClass)) } + @Test + fun `launch with custom paths`() { + createProject("custom-paths") + launchAndExpectResult(SUCCESS) + + val files = projectDir.walk().filter { + !it.path.contains("buildSrc") && !it.path.contains(".gradle") + }.toList() + withClue(files) { + projectDir.resolve("build/foomain/io/spine/protodata/test/Test.java").exists() shouldBe true + } + } + private fun createEmptyProject() { createProject("empty-test") } diff --git a/gradle-plugin/src/functionalTest/resources/android-library/build.gradle.kts b/gradle-plugin/src/functionalTest/resources/android-library/build.gradle.kts index 4e6565aa7..b58a5cf9d 100644 --- a/gradle-plugin/src/functionalTest/resources/android-library/build.gradle.kts +++ b/gradle-plugin/src/functionalTest/resources/android-library/build.gradle.kts @@ -43,8 +43,7 @@ repositories { } protoData { - renderers("io.spine.protodata.test.UnderscorePrefixRenderer") - plugins("io.spine.protodata.test.TestPlugin") + plugins("io.spine.protodata.test.TestPlugin", "io.spine.protodata.test.UnderscorePrefixPlugin") } dependencies { diff --git a/gradle-plugin/src/functionalTest/resources/custom-paths/build.gradle.kts b/gradle-plugin/src/functionalTest/resources/custom-paths/build.gradle.kts new file mode 100644 index 000000000..a98a6fa20 --- /dev/null +++ b/gradle-plugin/src/functionalTest/resources/custom-paths/build.gradle.kts @@ -0,0 +1,73 @@ +/* + * Copyright 2022, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import com.google.protobuf.gradle.protobuf +import io.spine.internal.gradle.standardToSpineSdk + +buildscript { + standardSpineSdkRepositories() +} + +plugins { + java + id("com.google.protobuf") + id("@PROTODATA_PLUGIN_ID@") version "@PROTODATA_VERSION@" +} + +repositories { + mavenLocal() // Must come first for `protodata-test-env`. + standardToSpineSdk() +} + +dependencies { + protoData("io.spine.protodata:protodata-test-env:+") +} + +protobuf { + protoc { + artifact = io.spine.internal.dependency.Protobuf.compiler + } +} + +protoData { + plugins("io.spine.protodata.test.TestPlugin", "io.spine.protodata.test.UnderscorePrefixPlugin") + + launchFor("main") { + sourceFileSet { + source = "$buildDir/generated/source/proto/main/java" + target = "$buildDir/foomain" + language = "java" + } + } + + launchFor(sourceSets.test) { + sourceFileSet { + source = "$buildDir/generated-proto/test/kotlin" + target = "$buildDir/footest" + language = "java" + } + } +} diff --git a/gradle-plugin/src/functionalTest/resources/custom-paths/settings.gradle.kts b/gradle-plugin/src/functionalTest/resources/custom-paths/settings.gradle.kts new file mode 100644 index 000000000..f5ecc1b72 --- /dev/null +++ b/gradle-plugin/src/functionalTest/resources/custom-paths/settings.gradle.kts @@ -0,0 +1,31 @@ +/* + * Copyright 2022, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +pluginManagement { + repositories { + mavenLocal() + } +} diff --git a/gradle-plugin/src/functionalTest/resources/custom-paths/src/main/proto/test.proto b/gradle-plugin/src/functionalTest/resources/custom-paths/src/main/proto/test.proto new file mode 100644 index 000000000..6a60feb41 --- /dev/null +++ b/gradle-plugin/src/functionalTest/resources/custom-paths/src/main/proto/test.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package spine.protodata.test; + +option java_package = "io.spine.protodata.test"; +option java_outer_classname = "TestProto"; +option java_multiple_files = true; + +// We need there to be at least some definitions. We don't care which ones for this test. + +message Test { +} diff --git a/gradle-plugin/src/functionalTest/resources/empty-test/build.gradle.kts b/gradle-plugin/src/functionalTest/resources/empty-test/build.gradle.kts index 4a2e3d07e..4a4599a13 100644 --- a/gradle-plugin/src/functionalTest/resources/empty-test/build.gradle.kts +++ b/gradle-plugin/src/functionalTest/resources/empty-test/build.gradle.kts @@ -53,6 +53,5 @@ protobuf { } protoData { - renderers("io.spine.protodata.test.UnderscorePrefixRenderer") - plugins("io.spine.protodata.test.TestPlugin") + plugins("io.spine.protodata.test.TestPlugin", "io.spine.protodata.test.UnderscorePrefixPlugin") } diff --git a/gradle-plugin/src/functionalTest/resources/java-kotlin-test/build.gradle.kts b/gradle-plugin/src/functionalTest/resources/java-kotlin-test/build.gradle.kts index 94aee809d..587f81b52 100644 --- a/gradle-plugin/src/functionalTest/resources/java-kotlin-test/build.gradle.kts +++ b/gradle-plugin/src/functionalTest/resources/java-kotlin-test/build.gradle.kts @@ -44,8 +44,7 @@ repositories { } protoData { - renderers("io.spine.protodata.test.NoOpRenderer") - plugins("io.spine.protodata.test.TestPlugin") + plugins("io.spine.protodata.test.TestPlugin", "io.spine.protodata.test.NoOpPlugin") } dependencies { diff --git a/gradle-plugin/src/functionalTest/resources/java-library-kotlin-jvm/build.gradle.kts b/gradle-plugin/src/functionalTest/resources/java-library-kotlin-jvm/build.gradle.kts index dec4bf6fa..087c59430 100644 --- a/gradle-plugin/src/functionalTest/resources/java-library-kotlin-jvm/build.gradle.kts +++ b/gradle-plugin/src/functionalTest/resources/java-library-kotlin-jvm/build.gradle.kts @@ -45,8 +45,7 @@ repositories { } protoData { - renderers("io.spine.protodata.test.NoOpRenderer") - plugins("io.spine.protodata.test.TestPlugin") + plugins("io.spine.protodata.test.TestPlugin", "io.spine.protodata.test.NoOpPlugin") } dependencies { diff --git a/gradle-plugin/src/functionalTest/resources/kotlin-test/build.gradle.kts b/gradle-plugin/src/functionalTest/resources/kotlin-test/build.gradle.kts index 47f6c671c..b731434a8 100644 --- a/gradle-plugin/src/functionalTest/resources/kotlin-test/build.gradle.kts +++ b/gradle-plugin/src/functionalTest/resources/kotlin-test/build.gradle.kts @@ -44,8 +44,7 @@ repositories { } protoData { - renderers("io.spine.protodata.test.NoOpRenderer") - plugins("io.spine.protodata.test.TestPlugin") + plugins("io.spine.protodata.test.TestPlugin", "io.spine.protodata.test.NoOpPlugin") } dependencies { diff --git a/gradle-plugin/src/functionalTest/resources/launch-test/build.gradle.kts b/gradle-plugin/src/functionalTest/resources/launch-test/build.gradle.kts index 4a2e3d07e..4a4599a13 100644 --- a/gradle-plugin/src/functionalTest/resources/launch-test/build.gradle.kts +++ b/gradle-plugin/src/functionalTest/resources/launch-test/build.gradle.kts @@ -53,6 +53,5 @@ protobuf { } protoData { - renderers("io.spine.protodata.test.UnderscorePrefixRenderer") - plugins("io.spine.protodata.test.TestPlugin") + plugins("io.spine.protodata.test.TestPlugin", "io.spine.protodata.test.UnderscorePrefixPlugin") } diff --git a/gradle-plugin/src/functionalTest/resources/with-functional-test/build.gradle.kts b/gradle-plugin/src/functionalTest/resources/with-functional-test/build.gradle.kts index 834f13aa5..87e3cbb8c 100644 --- a/gradle-plugin/src/functionalTest/resources/with-functional-test/build.gradle.kts +++ b/gradle-plugin/src/functionalTest/resources/with-functional-test/build.gradle.kts @@ -45,8 +45,7 @@ repositories { } protoData { - renderers("io.spine.protodata.test.NoOpRenderer") - plugins("io.spine.protodata.test.TestPlugin") + plugins("io.spine.protodata.test.TestPlugin", "io.spine.protodata.test.NoOpPlugin") } dependencies { diff --git a/gradle-plugin/src/main/kotlin/io/spine/protodata/gradle/plugin/Extension.kt b/gradle-plugin/src/main/kotlin/io/spine/protodata/gradle/plugin/Extension.kt index 92e62898d..94eccd055 100644 --- a/gradle-plugin/src/main/kotlin/io/spine/protodata/gradle/plugin/Extension.kt +++ b/gradle-plugin/src/main/kotlin/io/spine/protodata/gradle/plugin/Extension.kt @@ -26,11 +26,18 @@ package io.spine.protodata.gradle.plugin +import com.google.common.collect.HashMultimap +import com.google.common.collect.Multimap import io.spine.protodata.gradle.CodeGeneratorRequestFile import io.spine.protodata.gradle.CodeGeneratorRequestFile.DEFAULT_DIRECTORY import io.spine.protodata.gradle.CodegenSettings +import io.spine.protodata.gradle.SourcePaths +import io.spine.tools.code.Java +import io.spine.tools.code.Kotlin import io.spine.tools.fs.DirectoryName.generated import io.spine.tools.gradle.protobuf.generatedSourceProtoDir +import kotlin.DeprecationLevel.ERROR +import kotlin.io.path.name import org.gradle.api.Project import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty @@ -53,11 +60,6 @@ public class Extension(internal val project: Project): CodegenSettings { internal val plugins: ListProperty = factory.listProperty(String::class.java).convention(listOf()) - @Suppress("DeprecatedCallableAddReplaceWith") // Not that simple ;) - @Deprecated("Supply Renderers via Plugins instead.") - public override fun renderers(vararg classNames: String): Unit = - renderers.addAll(classNames.toList()) - @Deprecated("Supply Renderers via Plugins instead.") internal val renderers: ListProperty = factory.listProperty().convention(listOf()) @@ -81,12 +83,65 @@ public class Extension(internal val project: Project): CodegenSettings { internal fun requestFile(forSourceSet: SourceSet): Provider = requestFilesDirProperty.file(CodeGeneratorRequestFile.name(forSourceSet)) + override val paths: Multimap = HashMultimap.create() + + /** + * Obtains the source configured paths. + * + * If the deprecated [subDirs] and [targetBaseDir] are used, constructs [SourcePaths] instances + * from the present data for backward compatibility. However, in this compatibility mode, + * only Java and Kotlin source file sets can be constructed. + * + * This method exists for backward compatibility reasons only. Once the old way of providing + * source set paths is no longer available, we remove it and simply query [paths] instead. + */ + internal fun pathsOrCompat(sourceSet: SourceSet): Set { + if (!paths.isEmpty) { + return paths[sourceSet.name].toSet() + } + val srcRoots = sourceDirs(sourceSet).get() + val targetRoots = targetDirs(sourceSet).get() + return srcRoots.zip(targetRoots) + .map { (src, target) -> + val pathSuffix = src.asFile.toPath().name + val lang = if (pathSuffix.equals(Kotlin.name, ignoreCase = true)) Kotlin else Java + val generatorName = if (pathSuffix.equals(lang.name, ignoreCase = true)) { + "" + } else { + pathSuffix + } + SourcePaths( + src.asFile, + target.asFile, + lang, + generatorName + ) + }.toSet() + } + + public companion object { + + /** + * Default subdirectories expected by ProtoData under a generated source set. + * + * @see subDirs + */ + public val defaultSubdirectories: List = listOf( + "java", + "kotlin", + "grpc", + "js", + "dart" + ) + } + /** * Synthetic property for providing the source directories for the given * source set under [Project.generatedSourceProtoDir]. * * @see sourceDirs */ + @Deprecated("Use `paths` instead.") private val srcBaseDirProperty: DirectoryProperty = with(project) { objects.directoryProperty().convention(provider { layout.projectDirectory.dir(generatedSourceProtoDir.toString()) @@ -98,21 +153,26 @@ public class Extension(internal val project: Project): CodegenSettings { * * Defaults to [defaultSubdirectories]. */ + @Deprecated("Use `paths` instead.") public override var subDirs: List get() = subDirProperty.get() set(value) { if (value.isNotEmpty()) { subDirProperty.set(value) } + } + @Deprecated("Use `paths` instead.") private val subDirProperty: ListProperty = factory.listProperty().convention(defaultSubdirectories) + @Deprecated("Use `paths` instead.", level = ERROR) public override var targetBaseDir: Any get() = targetBaseDirProperty.get() set(value) = targetBaseDirProperty.set(project.file(value)) + @Deprecated("Use `paths` instead.") private val targetBaseDirProperty: DirectoryProperty = with(project) { objects.directoryProperty().convention( layout.projectDirectory.dir(generated.name) @@ -122,6 +182,7 @@ public class Extension(internal val project: Project): CodegenSettings { /** * Obtains the source directories for the given source set. */ + @Deprecated("Use `paths` instead.") internal fun sourceDirs(sourceSet: SourceSet): Provider> = compileDir(sourceSet, srcBaseDirProperty) @@ -130,9 +191,11 @@ public class Extension(internal val project: Project): CodegenSettings { * * @see targetBaseDir for the rules for the target dir construction */ + @Deprecated("Use `paths` instead.") internal fun targetDirs(sourceSet: SourceSet): Provider> = compileDir(sourceSet, targetBaseDirProperty) + @Deprecated("Use `paths` instead.") private fun compileDir( sourceSet: SourceSet, base: DirectoryProperty @@ -142,22 +205,4 @@ public class Extension(internal val project: Project): CodegenSettings { subDirs.map { root.dir(it) } } } - - public companion object { - - /** - * Default subdirectories expected by ProtoData under a generated source set. - * - * @see subDirs - */ - public val defaultSubdirectories: List = listOf( - "java", - "kotlin", - "grpc", - "js", - "dart", - "spine", - "protodata" - ) - } } diff --git a/gradle-plugin/src/main/kotlin/io/spine/protodata/gradle/plugin/LaunchProtoData.kt b/gradle-plugin/src/main/kotlin/io/spine/protodata/gradle/plugin/LaunchProtoData.kt index 6cc0bc56f..622fa27d7 100644 --- a/gradle-plugin/src/main/kotlin/io/spine/protodata/gradle/plugin/LaunchProtoData.kt +++ b/gradle-plugin/src/main/kotlin/io/spine/protodata/gradle/plugin/LaunchProtoData.kt @@ -28,13 +28,14 @@ package io.spine.protodata.gradle.plugin import io.spine.protodata.CLI_APP_CLASS import io.spine.protodata.cli.ConfigFileParam +import io.spine.protodata.cli.PathsParam import io.spine.protodata.cli.PluginParam -import io.spine.protodata.cli.RendererParam import io.spine.protodata.cli.RequestParam -import io.spine.protodata.cli.SourceRootParam -import io.spine.protodata.cli.TargetRootParam import io.spine.protodata.cli.UserClasspathParam +import io.spine.protodata.gradle.SourcePaths +import io.spine.protodata.renderer.DefaultGenerator import io.spine.tools.gradle.protobuf.containsProtoFiles +import java.io.File import java.io.File.pathSeparator import org.gradle.api.Action import org.gradle.api.Task @@ -70,25 +71,14 @@ public abstract class LaunchProtoData : JavaExec() { @get:Internal public abstract val configurationFile: RegularFileProperty - @Deprecated("Supply Renderers via Plugins instead.") - @get:Input - internal lateinit var renderers: Provider> - @get:Input internal lateinit var plugins: Provider> @get:Input internal lateinit var optionProviders: Provider> - /** - * The paths to the directories with the generated source code. - * - * May not be available, if `protoc` built-ins were turned off, resulting in no source code - * being generated. In such a mode `protoc` worked only generating descriptor set files. - */ - @get:InputFiles - @get:Optional - internal lateinit var sources: Provider> + @get:Internal + internal lateinit var paths: Set @get:InputFiles internal lateinit var userClasspathConfig: Configuration @@ -99,11 +89,24 @@ public abstract class LaunchProtoData : JavaExec() { @get:InputFiles internal lateinit var protoDataConfig: Configuration - /** - * The paths to the directories where the source code processed by ProtoData should go. - */ + @Suppress("unused") // Used by Gradle for incremental compilation. + @get:InputFiles + @get:Optional + internal val sources: Set + get() = paths.asSequence() + .map { it.source } + .filter { it != null } + .map { project.layout.projectDirectory.dir(it!!) } + .toSet() + + @Suppress("unused") // Used by Gradle for incremental compilation. @get:OutputDirectories - internal lateinit var targets: Provider> + internal val targets: Set + get() = paths.asSequence() + .map { it.target } + .filter { it != null } + .map { project.layout.projectDirectory.dir(it!!) } + .toSet() /** * Configures the CLI command for this task. @@ -116,21 +119,13 @@ public abstract class LaunchProtoData : JavaExec() { yield(PluginParam.name) yield(it) } - renderers.get().forEach { - yield(RendererParam.name) - yield(it) - } yield(RequestParam.name) yield(project.file(requestFile).absolutePath) - - if (sources.isPresent) { - yield(SourceRootParam.name) - yield(sources.absolutePaths()) + paths.forEach { + yield(PathsParam.name) + yield(it.toCliParam()) } - yield(TargetRootParam.name) - yield(targets.absolutePaths()) - val userCp = userClasspathConfig.asPath if (userCp.isNotEmpty()) { yield(UserClasspathParam.name) @@ -161,8 +156,8 @@ public abstract class LaunchProtoData : JavaExec() { private inner class CleanAction : Action { override fun execute(t: Task) { - val sourceDirs = sources.absoluteDirs() - val targetDirs = targets.absoluteDirs() + val sourceDirs = paths.map { File(it.source!!) } + val targetDirs = paths.map { File(it.target!!) } if (sourceDirs.isEmpty()) { return @@ -180,13 +175,18 @@ public abstract class LaunchProtoData : JavaExec() { } } -private fun Provider>.absoluteDirs() = takeIf { it.isPresent } - ?.get() - ?.map { it.asFile.absoluteFile } - ?: listOf() - -private fun Provider>.absolutePaths(): String = - absoluteDirs().joinToString(pathSeparator) +private fun SourcePaths.toCliParam(): String { + checkAllSet() + val label = if (generatorName != DefaultGenerator.name && generatorName.isNotBlank()) { + "$language($generatorName)" + } else { + language + } + val sourcePath = source ?: "" + val targetPath = target!! + val parts = listOf(label, sourcePath, targetPath) + return parts.joinToString(pathSeparator) +} /** * Tells if the request file for this task exists. diff --git a/gradle-plugin/src/main/kotlin/io/spine/protodata/gradle/plugin/Plugin.kt b/gradle-plugin/src/main/kotlin/io/spine/protodata/gradle/plugin/Plugin.kt index afe266e21..2cfc48bbf 100644 --- a/gradle-plugin/src/main/kotlin/io/spine/protodata/gradle/plugin/Plugin.kt +++ b/gradle-plugin/src/main/kotlin/io/spine/protodata/gradle/plugin/Plugin.kt @@ -166,16 +166,19 @@ private fun Project.createTasks(ext: Extension) { private fun Project.createLaunchTask(sourceSet: SourceSet, ext: Extension): LaunchProtoData { val taskName = LaunchTask.nameFor(sourceSet) val result = tasks.create(taskName) { - renderers = ext.renderers plugins = ext.plugins optionProviders = ext.optionProviders requestFile = ext.requestFile(sourceSet) protoDataConfig = protoDataRawArtifact userClasspathConfig = userClasspath project.afterEvaluate { - sources = ext.sourceDirs(sourceSet) - targets = ext.targetDirs(sourceSet) + paths = ext.pathsOrCompat(sourceSet) compileCommandLine() + + val java: SourceDirectorySet = sourceSet.java + paths.map { it.target!! }.forEach { targetPath -> + java.srcDir(targetPath) + } } setPreLaunchCleanup() onlyIf { @@ -189,6 +192,7 @@ private fun Project.createLaunchTask(sourceSet: SourceSet, ext: Extension): Laun javaCompileFor(sourceSet)?.dependsOn(launchTask) kotlinCompileFor(sourceSet)?.dependsOn(launchTask) } + return result } @@ -319,31 +323,8 @@ private fun GenerateProtoTask.excludeProtocOutput() { // Add the filtered directories back to the Java source set. java.srcDirs(newSourceDirectories) - - // Add copied files to the Java source set. - java.srcDir(generatedDir("java")) - java.srcDir(generatedDir("kotlin")) -} - -/** - * Obtains the `generated` directory for the source set of the task. - * - * If [language] is specified returns the subdirectory for this language. - */ -private fun GenerateProtoTask.generatedDir(language: String = ""): File { - val path = "${project.targetBaseDir}/${sourceSet.name}/$language" - return File(path) } -/** - * Obtains the name of the directory where ProtoData places generated files. - */ -private val Project.targetBaseDir: String - get() { - val ext = extensions.getByType(CodegenSettings::class.java) - return ext.targetBaseDir.toString() - } - /** * Makes a [LaunchProtoData], if it exists for the given [sourceSet], depend on * the given [GenerateProtoTask]. @@ -399,4 +380,3 @@ private fun Project.configureIdea() { */ private fun File.residesIn(directory: File): Boolean = canonicalFile.startsWith(directory.absolutePath) - diff --git a/gradle-plugin/src/test/kotlin/io/spine/protodata/gradle/plugin/ExtensionSpec.kt b/gradle-plugin/src/test/kotlin/io/spine/protodata/gradle/plugin/ExtensionSpec.kt index 3eac71300..d9ec891d2 100644 --- a/gradle-plugin/src/test/kotlin/io/spine/protodata/gradle/plugin/ExtensionSpec.kt +++ b/gradle-plugin/src/test/kotlin/io/spine/protodata/gradle/plugin/ExtensionSpec.kt @@ -74,16 +74,6 @@ class ExtensionSpec { .containsExactly(className) } - @Suppress("DEPRECATION") - @Test - fun `add 'Renderer' class names`() { - val className1 = "com.acme.MyRenderer1" - val className2 = "com.acme.MyRenderer2" - extension.renderers(className1, className2) - assertThat(extension.renderers.get()) - .containsExactly(className1, className2) - } - @Test fun `add 'OptionProvider' class names`() { val className = "com.acme.MyOptions" @@ -98,22 +88,4 @@ class ExtensionSpec { extension.requestFilesDir = path extension.requestFilesDirProperty.get().asFile shouldBe project.projectDir.resolve(path) } - - @Test - fun `produce target directory`() { - val basePath = "my/path" - val expected = listOf("foo", "bar") - - extension.targetBaseDir = basePath - extension.subDirs = expected - - val sourceSet = project.sourceSets.getByName(MAIN_SOURCE_SET_NAME) - val targetDirs = extension.targetDirs(sourceSet).get() - - val mainDir = project.projectDir.toPath() / basePath / MAIN_SOURCE_SET_NAME - targetDirs[0].toPath() shouldBe mainDir / expected[0] - targetDirs[1].toPath() shouldBe mainDir / expected[1] - } } - -private fun Directory.toPath() = asFile.toPath() diff --git a/license-report.md b/license-report.md index 1c7844089..bc57552fd 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine.protodata:protodata-api:0.11.6` +# Dependencies of `io.spine.protodata:protodata-api:0.12.0` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.15.2.**No license information found** @@ -925,12 +925,12 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Sep 13 20:12:06 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Sep 22 16:47:12 EEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.protodata:protodata-cli:0.11.6` +# Dependencies of `io.spine.protodata:protodata-cli:0.12.0` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.15.2.**No license information found** @@ -1871,12 +1871,12 @@ This report was generated on **Wed Sep 13 20:12:06 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Sep 13 20:12:07 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Sep 22 16:47:14 EEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.protodata:protodata-cli-api:0.11.6` +# Dependencies of `io.spine.protodata:protodata-cli-api:0.12.0` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.15.2.**No license information found** @@ -2789,12 +2789,12 @@ This report was generated on **Wed Sep 13 20:12:07 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Sep 13 20:12:07 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Sep 22 16:47:15 EEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.protodata:protodata-codegen-java:0.11.6` +# Dependencies of `io.spine.protodata:protodata-codegen-java:0.12.0` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.15.2.**No license information found** @@ -3718,12 +3718,12 @@ This report was generated on **Wed Sep 13 20:12:07 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Sep 13 20:12:08 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Sep 22 16:47:17 EEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.protodata:protodata-compiler:0.11.6` +# Dependencies of `io.spine.protodata:protodata-compiler:0.12.0` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.15.2.**No license information found** @@ -4647,12 +4647,12 @@ This report was generated on **Wed Sep 13 20:12:08 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Sep 13 20:12:08 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Sep 22 16:47:18 EEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.protodata:protodata-gradle-api:0.11.6` +# Dependencies of `io.spine.protodata:protodata-gradle-api:0.12.0` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -5457,12 +5457,12 @@ This report was generated on **Wed Sep 13 20:12:08 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Sep 13 20:12:08 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Sep 22 16:47:19 EEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.protodata:protodata-gradle-plugin:0.11.6` +# Dependencies of `io.spine.protodata:protodata-gradle-plugin:0.12.0` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.15.2.**No license information found** @@ -6550,12 +6550,12 @@ This report was generated on **Wed Sep 13 20:12:08 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Sep 13 20:12:09 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Sep 22 16:47:20 EEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.protodata:protodata-protoc:0.11.6` +# Dependencies of `io.spine.protodata:protodata-protoc:0.12.0` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -7319,12 +7319,12 @@ This report was generated on **Wed Sep 13 20:12:09 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Sep 13 20:12:09 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Sep 22 16:47:21 EEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.protodata:protodata-test-env:0.11.6` +# Dependencies of `io.spine.protodata:protodata-test-env:0.12.0` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.15.2.**No license information found** @@ -8255,4 +8255,4 @@ This report was generated on **Wed Sep 13 20:12:09 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Sep 13 20:12:09 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Fri Sep 22 16:47:22 EEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1707f7a69..93bf1e09b 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine.protodata ProtoData -0.11.6 +0.12.0 2015 @@ -170,7 +170,7 @@ all modules and does not describe the project structure per-subproject. io.spine.tools spine-testutil-server - 2.0.0-SNAPSHOT.156 + 2.0.0-SNAPSHOT.157 test diff --git a/test-env/src/main/kotlin/io/spine/protodata/test/NoOpPlugin.kt b/test-env/src/main/kotlin/io/spine/protodata/test/NoOpPlugin.kt new file mode 100644 index 000000000..6ae7dc306 --- /dev/null +++ b/test-env/src/main/kotlin/io/spine/protodata/test/NoOpPlugin.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.protodata.test + +import io.spine.protodata.plugin.AbstractPlugin + +/** + * A plugin with the [NoOpRenderer]. + */ +public class NoOpPlugin : AbstractPlugin( + renderers = listOf(NoOpRenderer()) +) diff --git a/test-env/src/main/kotlin/io/spine/protodata/test/PathParams.kt b/test-env/src/main/kotlin/io/spine/protodata/test/PathParams.kt new file mode 100644 index 000000000..126c37704 --- /dev/null +++ b/test-env/src/main/kotlin/io/spine/protodata/test/PathParams.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.protodata.test + +import java.io.File + +/** + * Constructs a `--path` CLI param with the given parts. + * + * See the CLI help message for the format details. + */ +public fun paths(label: String, src: Any, target: Any): String { + val ps = File.pathSeparator + return label + ps + src + ps + target +} + +/** + * Constructs a `--path` CLI param with the Java language and the given source and target paths. + */ +public fun pathsForJava(src: Any, target: Any): String = + paths("java", src, target) diff --git a/test-env/src/main/kotlin/io/spine/protodata/test/UnderscorePrefixPlugin.kt b/test-env/src/main/kotlin/io/spine/protodata/test/UnderscorePrefixPlugin.kt new file mode 100644 index 000000000..e757bd74c --- /dev/null +++ b/test-env/src/main/kotlin/io/spine/protodata/test/UnderscorePrefixPlugin.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.protodata.test + +import io.spine.protodata.plugin.AbstractPlugin + +/** + * A plugin with the [UnderscorePrefixRenderer]. + */ +public class UnderscorePrefixPlugin : AbstractPlugin( + renderers = listOf(UnderscorePrefixRenderer()) +) diff --git a/tests/consumer/src/test/proto/protodata/test/task.proto b/tests/consumer/src/test/proto/protodata/test/task.proto index 2ddc12961..9f3c00634 100644 --- a/tests/consumer/src/test/proto/protodata/test/task.proto +++ b/tests/consumer/src/test/proto/protodata/test/task.proto @@ -3,7 +3,7 @@ syntax = "proto3"; package protodata.test; option java_package = "io.spine.protodata.test"; -option java_outer_classname = "TaskProto"; +option java_outer_classname = "TaskIdProto"; option java_multiple_files = true; message TaskId { diff --git a/tests/protodata-extension/src/main/java/io/spine/protodata/test/uuid/UuidJavaRenderer.java b/tests/protodata-extension/src/main/java/io/spine/protodata/test/uuid/UuidJavaRenderer.java index eb6ca9b46..f226c6571 100644 --- a/tests/protodata-extension/src/main/java/io/spine/protodata/test/uuid/UuidJavaRenderer.java +++ b/tests/protodata-extension/src/main/java/io/spine/protodata/test/uuid/UuidJavaRenderer.java @@ -78,13 +78,6 @@ protected void render(SourceFileSet sources) { InsertionPoint classScope = new ClassScope(typeName); ImmutableList lines = METHOD_FORMAT.format(className, UUID.class.getName()); Path javaFilePath = javaFileOf(typeName, file); - - // If there are no Java files, we deal with another language. - // Have this workaround until we get access to the `sourceRoot` property. - if (sources.findFile(javaFilePath).isEmpty()) { - continue; - } - sources.file(javaFilePath) .at(classScope) .withExtraIndentation(INDENT_LEVEL) diff --git a/version.gradle.kts b/version.gradle.kts index 0c5be73bd..6d56fab7a 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -32,4 +32,4 @@ * * For dependencies on Spine SDK module please see [io.spine.internal.dependency.Spine]. */ -val protoDataVersion: String by extra("0.11.6") +val protoDataVersion: String by extra("0.12.0")