Skip to content

Commit

Permalink
feat: documentation about rrgcli, gradle plugin and plugins api
Browse files Browse the repository at this point in the history
  • Loading branch information
y9vad9 committed Nov 15, 2024
1 parent 2dda837 commit 257fe52
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 0 deletions.
11 changes: 11 additions & 0 deletions Writerside/in.tree
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@
<toc-element topic="Instances.md"/>
<toc-element topic="ProtoBuf-Options.md"/>
</toc-element>

<toc-element toc-title="Code generation">
<toc-element toc-title="Instruments">
<toc-element topic="CodeGen-Gradle.md" />
<toc-element topic="CodeGen-CLI.md" />
</toc-element>
<toc-element toc-title="Plugins">
<toc-element topic="CodeGen-Plugins.md" />
</toc-element>
</toc-element>

<toc-element topic="Compatibility-with-ProtoBuf.md"/>
<toc-element topic="Implementation.md"/>
<toc-element topic="Plans.md"/>
Expand Down
96 changes: 96 additions & 0 deletions Writerside/topics/CodeGen-CLI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# CLI
The `rrgcli` tool allows you to generate code from `.proto` files for use with the rRPC framework. It supports creating
Kotlin-based (and other, in future) client stubs, server stubs, data types, and metadata.

<note>
If you're using Gradle, it's a much more convenient to use <a href="CodeGen-Gradle.md"/> for generating the code.
</note>

## Installation

Download the latest release of `rrgcli` from the [rRPC GitHub Releases](https://github.com/timemates/rrpc-kotlin/releases).
Once downloaded, ensure the binary is executable and optionally add it to your system's PATH:

```Bash
chmod +x rrgcli
mv rrgcli /usr/local/bin
```
## Usage
The tool processes .proto files and outputs generated code based on the options you provide.
Use the --help option to see available parameters:
```
Usage: rrgcli [<options>]
Options:
--protos_input=<text> Folder with `.proto` files to be used for generation
(repeatable).
--permit_package_cycles=true|false
Indicates whether package cycles should be ignored
while parsing `.proto` files.
--kotlin_output=<text> Specifies the output path for generated Kotlin files.
--kotlin_client_generation=true|false
Indicates whether client stubs should be generated
for Kotlin. Default is `false`.
--kotlin_server_generation=true|false
Indicates whether server stubs should be generated
for Kotlin. Default is `false`.
--kotlin_type_generation=true|false
Indicates whether data types should be generated for
Kotlin. Default is `false`.
--kotlin_metadata_generation=true|false
Specifies whether metadata should be generated.
--kotlin_metadata_scope_name=<text>
Specifies the scope name for metadata generation. If
not specified, a global instance of Metadata Container
is used.
-h, --help Show this message and exit.
```
> **Additionally about metadata generation**
>
> Enabling the `--kotlin_metadata_generation` flag generates debugging information about the whole schema, meaning that,
> factually, it provides the same information as any generator have on generation time, but on app's runtime.
> This can be helpful for servers that need details about options attached to specific levels (e.g., file-level options for a service declaration). Metadata may also provide insight into the
> organization and layout of services for tooling.
>
>
> The `--kotlin_metadata_scope_name` flag is used to make metadata scoped to a specific instance rather than placing it
> in the global object. This allows you to:
> - Filter out unnecessary schema information from your SchemaService.
> - Avoid polluting the global scope with unrelated or redundant metadata.
> - Tailor metadata for specific use cases or service groups.
>
> By scoping metadata, you can maintain cleaner separation of concerns, particularly in larger projects with diverse requirements.

## Examples
### Generate Client Stubs and Types
This example generates Kotlin client stubs and data types from .proto files located in the protos directory:
```Bash
rrgcli \
--protos_input=protos \
--kotlin_output=build/generated \
--kotlin_client_generation=true \
--kotlin_type_generation=true
```

### Handle Package Cycles and Metadata
This example resolves package cycles in .proto files and generates metadata with a custom scope name:
```Bash
rrgcli \
--protos_input=protos \
--kotlin_output=build/generated \
--permit_package_cycles=true \
--kotlin_metadata_generation=true \
--kotlin_metadata_scope_name=my_custom_scope
```

### Multiple `.proto` Input Directories
To include .proto files from multiple locations:
```Bash
rrgcli \
--protos_input=protos1 \
--protos_input=protos2 \
--kotlin_output=build/generated \
--kotlin_server_generation=true
```
_______
84 changes: 84 additions & 0 deletions Writerside/topics/CodeGen-Gradle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Gradle Plugin
The rRPC Gradle Plugin helps automate code generation for rRPC services in Gradle-based projects using `.proto` files.

<note>
If your project is not Gradle-based, you can use the <a href="CodeGen-CLI.md">rrgcli</a> for generating code.
</note>

## Applying the Plugin
Add the plugin to your `build.gradle.kts`:
```kotlin
plugins {
id("org.timemates.rrpc") version "$latestVersion"
}
```

<note>
Make sure that you have Gradle Plugin Portal specified in your repositories.
</note>

## Configuring the Plugin
The plugin is configured using the rrpc extension, allowing customization for .proto file locations, output configurations, and generation behavior.

### Basic Example
```Kotlin
rrpc {
targetSourceSet.set("commonMain") // Specify the source set for generated code
protosInput.set(listOf("src/main/proto")) // Define where to find .proto files

kotlin {
output = "build/generated/kotlin" // Specify output directory
clientGeneration = true // Generate client stubs
serverGeneration = true // Generate server stubs
typeGeneration = true // Generate data types
metadataEnabled = true // Enable metadata generation
metadataScopeName = "MyScope" // Define metadata scope
}
}
```

### Configuration Properties

#### `targetSourceSet`
- **Purpose:** Specifies the source set for code generation.
- **Why it's important:** Code generation tasks depend on this source set to correctly manage input and output directories for the build process. For instance, setting `commonMain` ensures that the generated files are included in the correct build phase. Especially useful for the custom source-set layouts.
- **Default:** `null` (falls back to default source sets like `commonMain` or `main`).

```Kotlin
targetSourceSet.set("commonMain")
```
#### `protosInput`
- **Purpose**: Specifies the list of directories containing .proto files for generation.
- **Default**: An empty list.

#### Kotlin Configuration Options

These options are defined under the `kotlin` block:

- **`output`**: Specifies the directory where Kotlin code will be generated.
- **Example**:
`output = "build/generated/kotlin"`
- **`clientGeneration`**: Enables or disables generation of client stubs.
- **Default**: `false`.
- **Example**:
`clientGeneration = true`
- **`serverGeneration`**: Enables or disables generation of server stubs.
- **Default**: `false`.
- **Example**:
`serverGeneration = true`
- **`typeGeneration`**: Enables or disables generation of data types from `.proto` definitions.
- **Default**: `false`.
- **Example**:
`typeGeneration = true`
- **`metadataEnabled`**: Enables generation of metadata. This can help in debugging (for rRPC Inspector) or providing additional context to the server about file-level or declaration-level options.
- **Default**: `false`.
- **Example**:
`metadataEnabled = true`
- **`metadataScopeName`**: Specifies a scope for metadata, allowing you to filter or separate metadata from the global scope. Useful if you don’t want all metadata combined in a single container.
- **Default**: Empty string (uses global scope).
- **Example**:
`metadataScopeName = "PotatoSchema"`

## Additional Notes
- For projects with unusual directory structures or source sets, make sure to adjust targetSourceSet and protosInput accordingly.
- Generated files should not be modified manually. They are regenerated during each build.
115 changes: 115 additions & 0 deletions Writerside/topics/CodeGen-Plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Developing your own plugins
Plugins are a powerful way to extend the functionality of a generator, enabling dynamic behavior, custom workflows or
generating code for different languages. By developing your own plugin, you can tailor the generator to suit specific
needs, such as processing custom inputs, providing options, or integrating with external systems.

This guide will walk you through the steps of creating your own plugin, covering everything from adding dependencies to
handling specific signals and sending back appropriate responses. Whether you are building a plugin to modify the behavior of an existing generator or to introduce new options, this tutorial will help you get started with the Plugin API.

## Step 1: Add Dependencies

Before starting development, you need to add the necessary dependencies for the plugin to interact with the host system and exchange data. Ensure that your project includes the required libraries for communication via the Plugin API.

In your `build.gradle.kts` file (for Gradle projects), include the following dependencies:
```Kotlin
dependencies {
implementation("org.timemates.rrpc:generator-core:$latestVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
}
```
Ensure you use the correct version numbers according to your setup.

## Step 2: Define the Adapter

The adapter is used to map the available options and process the input received from the generator. Its purpose
is to generate code, based on the input received:
```Kotlin
public interface SchemaAdapter {
/**
* List of available options for schema adapter with description. For CLI and Gradle plugin.
*/
public val options: List<GenerationOption>

/**
* This method is used for generating the code usually, but can be used for other
* purposes, for an example, – logging.
*
* @return RMResolver that might be the same as [resolver] or new in cases
* when you want to modify incoming data to the following adapters.
*/
public fun process(
options: GenerationOptions,
resolver: RSResolver,
): RSResolver
}
```
For the reference, you might want to see the implementation of [Kotlin Code Generator](https://github.com/timemates/rrpc-kotlin/blob/0.7.0/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/adapter/KotlinSchemaAdapter.kt).

<warning>
This section is yet to be finalized.
</warning>

Once you implemented the `SchemaAdapter` for your needs, we can continue to the setup of communication between Generator
and the Plugin:

## Step 3: Setup Plugin Communication
The next step is setting up the communication between your plugin and the generator.
The plugin will send and receive signals, which are the core units of communication.

To begin, create an instance of the PluginCommunication class to handle the input and output streams:
```Kotlin
val client = PluginCommunication(
System.`in`.source().buffer(), // Input stream from the generator
System.out.sink().buffer() // Output stream to the generator
)
```

> [```rrcli```](CodeGen-CLI.md) as well as a Gradle Plugin, communicate with plugins through stdin/stdout. In the
> example we will use java.lang.System to obtain it, but it's not locked up – we use okio for ability to target non-JVM
> targets.
## Step 4: Handling Incoming Signals
When your plugin receives a signal, it processes the signal based on its type. A plugin will always receive a G
eneratorSignal.FetchOptionsList at the very beginning, which requests the available options.
In response, you can send a list of options back to the generator.

The generator might also send a GeneratorSignal.SendInput, but it is up to the generator to decide whether this signal
is needed, for example, when requesting input for certain options, such as --help.

Here’s an example of how to handle these signals:
```Kotlin
client.receive { signal ->
when (signal) {
GeneratorSignal.FetchOptionsList -> listOf(
PluginSignal.SendOptions(
schemaAdapter.options.map { it.toOptionDescriptor() }
),
PluginSignal.RequestInput,
)
is GeneratorSignal.SendInput -> {
schemaAdapter.process(
options = GenerationOptions(signal.args),
resolver = RSResolver(signal.files),
)
emptyList() // No further response needed
}
}
}
```
In this example:
- GeneratorSignal.FetchOptionsList is always received and is responded to by sending a list of available options and potentially a RequestInput signal.
- GeneratorSignal.SendInput may or may not be received, depending on the generator’s requirements.

## Step 5: Testing the Plugin

Before deploying your plugin, it’s essential to test it to ensure that it reacts correctly to signals and sends the appropriate responses. You can use unit tests or a local testing environment to simulate the interactions between your plugin and the generator.

<warning>
This section is yet to be finalized.
</warning>

## Conclusion

By following these steps, you can develop a plugin that communicates with the generator using the Plugin API. The key steps include adding dependencies, setting up communication, handling signals (particularly FetchOptionsList), and sending appropriate responses. This allows your plugin to seamlessly integrate with the generator’s functionality.

For more details and further examples, refer to the Plugin API documentation.

0 comments on commit 257fe52

Please sign in to comment.