Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5eae93f
Bump version 0.12.0
dmdashenkov Sep 7, 2023
d140c06
Introduce `SourceFileSetMarker`
dmdashenkov Sep 7, 2023
91a084b
Add stub markers
dmdashenkov Sep 8, 2023
368b73e
Simplify a precondition check
dmdashenkov Sep 11, 2023
436df68
Reduce repetition in tests
dmdashenkov Sep 11, 2023
e7bea69
Use `paths` in the Gradle plugin (WIP)
dmdashenkov Sep 18, 2023
162cc35
Fix CLI param syntax
dmdashenkov Sep 18, 2023
068c938
Fix alignment
dmdashenkov Sep 18, 2023
5204e0c
Check if a renderer supports the given sources file set
dmdashenkov Sep 19, 2023
4bcdce0
Make regex parsing more resilient
dmdashenkov Sep 19, 2023
162142f
Merge branch 'master' into fix-filtering
dmdashenkov Sep 19, 2023
1dd7385
Update Gradle plugin tests
dmdashenkov Sep 19, 2023
186e1c2
Set up incremental build
dmdashenkov Sep 20, 2023
0fcecf9
Add some doc
dmdashenkov Sep 20, 2023
368ae6e
More doc
dmdashenkov Sep 20, 2023
1c32f45
More doc
dmdashenkov Sep 20, 2023
454ca48
More doc and stricter deprecation
dmdashenkov Sep 20, 2023
6d69dbc
Yet more doc
dmdashenkov Sep 20, 2023
5ddd630
More doc for test env
dmdashenkov Sep 20, 2023
2793061
Remove debug output
dmdashenkov Sep 20, 2023
dffa31e
Remove redundant checks, add logging and another test
dmdashenkov Sep 20, 2023
8cf7162
Remove unwanted generated subdirs
dmdashenkov Sep 21, 2023
0742a7e
Fix numbering
dmdashenkov Sep 21, 2023
2370ae3
Better doc
dmdashenkov Sep 21, 2023
484e0d3
More doc
dmdashenkov Sep 21, 2023
8747165
Better error message
dmdashenkov Sep 21, 2023
09ccc19
Remove a duplicate string literal
dmdashenkov Sep 21, 2023
b006441
More doc
dmdashenkov Sep 21, 2023
44c297d
Make language reference for `AnyLanguage` simpler
dmdashenkov Sep 21, 2023
54bcd5a
Rename new DSL components
dmdashenkov Sep 22, 2023
9a3158e
Add a shortcut for Gradle's providers
dmdashenkov Sep 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion api/src/main/kotlin/io/spine/protodata/plugin/Plugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}
}

Expand Down
17 changes: 15 additions & 2 deletions api/src/main/kotlin/io/spine/protodata/renderer/Renderer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -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.
Expand Down
20 changes: 7 additions & 13 deletions api/src/main/kotlin/io/spine/protodata/renderer/SourceFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -178,7 +176,6 @@ private constructor(
*/
public fun overwrite(newCode: String) {
this.code = newCode
this.changed = true
}

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down
40 changes: 18 additions & 22 deletions api/src/main/kotlin/io/spine/protodata/renderer/SourceFileSet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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<SourceFile>,

/**
Expand All @@ -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.
Expand All @@ -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<Path, SourceFile>(files.size)
this.files = files.associateByTo(map) { it.relativePath }
Expand All @@ -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].
Expand All @@ -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<SourceFile>()
return SourceFileSet(files, target, target)
return SourceFileSet(label, files, null, target)
}
}

Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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})"
}
Original file line number Diff line number Diff line change
@@ -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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something strange.

"Generator" in all cases is the thing producing the code. Why would we need a "default" and "custom" generators? They both generate something, and why would we care about their origin?

And again, in this piece of documentation, you mention "labels". Which still does not add up: "generator"s, source set labels and "language" all dance around :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Default" simply references the case most users will likely face most of the time. And it is the default way in which Protoc generated code, as opposed to custom generators/Protoc plugins.

I've cleared up the doc's wording a bit so that there is no confusion with the labels.

*
* 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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading