Extension codestart

本指南将说明如何为扩展创建并配置 Quarkus Codestart。

This guide explains how to create and configure a Quarkus Codestart for an extension.

Description

“扩展 Codestart”是我们赋予 Quarkus 扩展“开始”代码生成系统的名称。它旨在提供使用 Quarkus 的个性化入门体验。Quarkus 扩展可以提供一个或多个明确定义的 Codestart,其中将包含使用特定扩展所需的/推荐的资源和代码。

"Extension Codestarts" is the name we give to our Quarkus extension "getting started" code generation system. It aims to provide a personalized getting started experience with Quarkus. A Quarkus extension is able to provide one or more well-defined codestarts which will contain the resources and code required/recommended to start using that particular extension.

使用 Quarkus 工具时,默认情况下将应用扩展 Codestart(如果选定的扩展包含任何 Codestart):

Extension codestarts are applied by default when using the Quarkus tooling (if the chosen extensions contain any):

  • code.quarkus.io (find the extensions tagged with [code])

  • The Quarkus Maven plugin:[source, bash]

mvn {quarkus-platform-groupid}:quarkus-maven-plugin:create
  • The Quarkus CLI:[source, bash]

quarkus create app

How it works

开始一个项目时,您选择语言、构建工具、框架,然后添加 Dockerfile、CI、依赖关系和代码。

When starting a project, you choose the language, the build tool, the framework, then you add Dockerfiles, CI, dependencies and code.

在为项目的生成做出贡献时,Codestart 的工作方式相同,它们分为两类:

Codestarts are working the same way when contributing to the generation of a project, they are split in two categories:

The "Base" codestarts (you choose a combination of those):

  • project: The project skeleton (e.g. a Quarkus project)

  • buildtool: The build tool (e.g. Maven, Gradle, Gradle with Kotlin DSL)

  • language: The coding language (e.g. Java, Kotlin, Scala)

  • config: The config type (e.g. yaml, properties)

Extra codestarts (as much as wanted, added on top of the base ones):

  • tooling: Anything that can be added to improve the project (e.g. Dockerfiles, GitHub Actions workflows)

  • code: Any Quarkus extension can provide starter code. The user can decide to activate it or not.

每个 codestart 包含:

Each codestart consists of:

  1. A codestart unique name, ie my-codestart

  2. A directory for the codestart files, ie my-codestart/

  3. A codestart.yml file

  4. Optionally some templates that are following a common structure and naming conventions

Where are the Quarkus Extension Codestarts located

  • In the Quarkus core repository, the extension codestarts are all in the same module.

  • Quarkus REST (formerly RESTEasy Reactive), RESTEasy and Spring Web extension codestarts are part of the base codestarts.

  • For other extensions, the codestart will typically be located in the runtime module (with special instruction in the pom.xml to generate a separate codestart artifact).

Base codestarts

base codestarts 包含用于创建项目、构建工具、语言、配置和工具文件的模板。

The base codestarts contains templates to create project, buildtool, languages, config and tooling files.

此外,Quarkus 还提供了以下方法来使用 Codestart 初始化新扩展项目:

In addition, Quarkus also provides the following ways to initialize a new extension project with a Codestart:

CLI

要使用 Codestart 框架创建新扩展,请为 create extension 命令提供 --codestart 标志:

To create a new extension with a Codestart skeleton provide the --codestart flag to the create extension command:

quarkus create extension --codestart org.acme:greeting-extension
Detected layout type is 'standalone'
Generated runtime artifactId is 'greeting-extension'


applying codestarts...
📚  java
🔨  maven
📦  quarkus-extension
🚀  devmode-test
🚀  extension-base
🚀  extension-codestart
🚀  integration-tests
🚀  unit-test

-----------
 👍  extension has been successfully generated in:
--> /Users/.../greeting-extension
-----------
Navigate into this directory and get started: quarkus build

For more information about how to install the Quarkus CLI and use it, please refer to the Quarkus CLI guide.

Maven

Quarkus 提供 create-extension Maven Mojo 来初始化扩展项目。

Quarkus provides the create-extension Maven Mojo to initialize an extension project.

要使用 Codestart 框架生成新扩展,请为此 Mojo 提供 -DwithCodestart 标志:

To generate a new extension with a Codestart skeleton provide the -DwithCodestart flag to this Mojo:

mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create-extension -N \
    -DgroupId=org.acme \
    -DextensionId=greeting-extension \
    -DwithCodestart
[INFO] --- quarkus-maven-plugin:{quarkus-version}:create-extension (default-cli) @ standalone-pom ---

Detected layout type is 'standalone'
Generated runtime artifactId is 'greeting-extension'


applying codestarts...
📚  java
🔨  maven
📦  quarkus-extension
🚀  devmode-test
🚀  extension-base
🚀  extension-codestart
🚀  integration-tests
🚀  unit-test

-----------
 👍  extension has been successfully generated in:
--> /Users/.../greeting-extension
-----------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.638 s
[INFO] Finished at: 2022-10-24T21:27:51+02:00
[INFO] ------------------------------------------------------------------------

Writing an Extension Codestart

以下是一份分步指南,用于编写扩展 codestart。你还可以观看带有现场编码环节的 Quarkus Insight #99

Here is a step-by-step guide to write an extension codestart. You may also watch the Quarkus Insight #99 with a live-coding session.

如之前所述,基本项目文件(pom.xml、Dockerfile 等)已由 Quarkus 核心提供的基本 codestart 生成。得益于此,我们只需关注特定于扩展的启动代码即可。

As was mentioned previously, the base project files (pom.xml, Dockerfiles, …​) are already generated by the base codestarts provided by the Quarkus core. Thanks to this, we can only focus on the starter code specific to the extension.

让我们以 io.quarkiverse.aloha:quarkus-aloha 为例扩展 GAV(不要寻找此扩展,它不存在)。

Let’s take io.quarkiverse.aloha:quarkus-aloha as an example extension GAV (don’t look for this extension, it doesn’t exist).

The code

Codestart 是用于构建新项目的模板。

A Codestart is a template for scaffolding new project.

本教程中,通过 Quarkus 项目创建一个 Codestart 项目并添加所需的模板。

In this tutorial a Codestart project is created from a Quarkus project and adding the needed templates.

因此,转到 code.quarkus.io,使用 aloha 扩展创建一个新项目,并将 org.acme 作为组(如 org.acme placeholder for package name)。准备一个不错的启动器。它不应包含任何业务逻辑,而应包含一些可编译且概述如何使用扩展名的存根数据/hello world。这个想法是引入一些最为常见的扩展名起始点代码。

Therefore, go to code.quarkus.io, create a new project with the aloha extension and org.acme as Group (i.e org.acme placeholder for package name). Prepare a nice starter. It should not include any business logic, instead, it should contain some stub data/hello world that compiles and gives an overview of how to use the extension. The idea is to bring code that is the most common starting point for the extension.

对代码满意?让我们用它来创建一个 Codestart。

Happy with the code? Let’s make a Codestart out of it.

The Codestart (Quarkiverse or Standalone extensions)

在您的扩展中:

In your extension:

  • Create the runtime/src/main/codestarts/quarkus/aloha-codestart directory

  • Move the src/main/java from your generated project to runtime/src/main/codestarts/quarkus/aloha-codestart/java/src/main/java

  • (Optional) Move the config using this convention: application config application.yml.

  • Create a codestart.yml file in runtime/src/main/codestarts/quarkus/aloha-codestart:[source, yaml]

name: aloha-codestart
ref: aloha
type: code
tags: extension-codestart
metadata:
  title: Aloha
  description: Start to code with the Aloha extension.
  related-guide-section: https://quarkiverse.github.io/quarkiverse-docs/quarkus-aloha/dev/
  path: /aloha # (optional) for web extensions providing HTTP resources
  • Add the Maven build plugin configuration in runtime/pom.xml (to generate the codestart artifact: /target/quarkus-aloha-VERSION-codestarts.jar):[source, xml]

      <plugin>
        <artifactId>maven-jar-plugin</artifactId>
        <executions>
          <execution>
            <id>generate-codestart-jar</id>
            <phase>generate-resources</phase>
            <goals>
              <goal>jar</goal>
            </goals>
            <configuration>
              <classesDirectory>${project.basedir}/src/main</classesDirectory>
              <includes>
                <include>codestarts/**</include>
              </includes>
              <classifier>codestarts</classifier>
              <skipIfEmpty>true</skipIfEmpty>
            </configuration>
          </execution>
        </executions>
      </plugin>
  • Add the codestart binding in the extension metadata runtime/src/main/resources/META-INF/quarkus-extension.yaml. Without this, your codestart won’t be added when your extension is picked:[source, yaml]

name: ...
description: ...
metadata:
  ...
  codestart:
    name: "aloha"
    languages:
    - "java"
    artifact: "io.quarkiverse.aloha:quarkus-aloha:codestarts:jar:${project.version}"
  • Add the readme README.md section template in base/README.tpl.qute.md:[source, html]

{#include readme-header /}
  • Run mvn clean install in the extension root (or just runtime).

  • Now we can check that the codestart actually works by creating a project consuming our extension (make sure the snapshot version is correct):[source, bash]

quarkus create app aloha-app -x=io.quarkiverse.aloha:quarkus-aloha:999-SNAPSHOT

...
applying codestarts...
📚  java
🔨  maven
📦  quarkus
📝  config-properties
🔧  dockerfiles
🔧  maven-wrapper
🚀  aloha-codestart <<<<<<<<<<<<<<<<
...

Testing

  • Add this dependency to the integration-tests:[source, xml]

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-devtools-testing</artifactId>
  <scope>test</scope>
</dependency>
  • Create a AlohaCodestartTest in the integration-tests:[source, java]

public class AlohaCodestartTest {

    @RegisterExtension
    public static QuarkusCodestartTest codestartTest = QuarkusCodestartTest.builder()
            .languages(Language.JAVA)
            .setupStandaloneExtensionTest("io.quarkiverse.aloha:quarkus-aloha")
            .build();

    @Test
    void testContent() throws Throwable {
        codestartTest.checkGeneratedSource("org.acme.AlohaResource");
    }

    @Test
    void buildAllProjects() throws Throwable {
        codestartTest.buildAllProjects();
    }
}

Going further

  • If the extension provides some web resources, add the base/src/main/resources/META-INF/resources/index.entry.qute.html template (index.html and web extension codestarts).

  • Add another language (it is recommended to provide Java and Kotlin).

  • You may add some other resources (in the ./base directory if they are not language-specific).

Extensions codestarts in Quarkus Core

  • The codestarts are all grouped in a specific module.

  • No extra Maven configuration is needed.

  • The extension metadata references the artifact containing all the core codestarts.

  • The tests are also grouped. You don’t need to test the build as there is a specific grouped test for it. e.g.:[source, java]

public class ConfigYamlCodestartTest {

    @RegisterExtension
    public static QuarkusCodestartTest codestartTest = QuarkusCodestartTest.builder()
            .codestarts("config-yaml")
            .languages(JAVA, KOTLIN)
            .build();

    @Test
    void testContent() throws Throwable {
        codestartTest.checkGeneratedSource("org.acme.GreetingConfig");
        codestartTest.assertThatGeneratedFileMatchSnapshot(JAVA, "src/main/resources/application.yml");
    }

    @Test
    @EnabledIfSystemProperty(named = "build-projects", matches = "true")
    void buildAllProjectsForLocalUse() throws Throwable {
        codestartTest.buildAllProjects();
    }

}

Specific topics

org.acme placeholder for package name

您必须使用 org.acme`作为扩展 codestart 源中的包名称。在生成的项目中,将使用用户指定的包(或组)(并自动替换 `org.acme)。

You have to use org.acme as the package name in your extension codestart sources. In the generated project, the user specified package (or Group) will be used (and automatically replace org.acme).

包将在所有源文件(.java、.kt、.scala)中自动替换。包目录也将自动调整。如果出于某种原因,另一种类型的文件需要用户包名,则您应该为其使用Templates (Qute)和`{project.package-name}`数据占位符(https://github.com/quarkusio/quarkus/blob/main/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/grpc-codestart/base/src/main/proto/hello.tpl.qute.proto#L4[find an example in the grpc proto file, window=_blank])。

The package will be automatically replaced in all the source files (.java, .kt, .scala). The package directory will also be automatically adjusted. If for some reason, another type of file needs the user package name then you should use a Templates (Qute) for it and the {project.package-name} data placeholder (find an example in the grpc proto file).

codestart.yml

# codestart unique name
name: resteasy-example
# codestart reference, use the extension id
ref: resteasy
# use 'code' (other types are for base codestarts)
type: code
# use 'extension-codestart'
tags: extension-codestart
# public metadata for this example (accessible as data in the templates e.g. {title})
metadata:
  title: RESTEasy Jakarta REST example
  description: Rest is easy peasy with this Hello World RESTEasy resource.
  related-guide-section: https://quarkus.io/guides/getting-started#the-jax-rs-resources
  # (optional) use this in web extensions with a specific path (and also add the index page)
  path: /some-path

Directory Structure

`codestart.yml`是唯一必需的文件。

codestart.yml is the only required file.

  • codestart.yml must be at the root of the codestart

  • ./base contains all the files that will be processed independently of the specified language

  • ./[java/kotlin/scala] contains all the files that will be processed only if the specified language has been selected (overriding base)

Dynamic Config Keys in Codestart

gen-info.time = generation time (in milliseconds)
input.selected-extensions[].name|description|guide = list of selected extensions with info
input.selected-extensions-ga = Set of Strings containing the list of extensions groupId:artifactId, useful for dynamic codestarts depending on selected extensions
input.provided-code[].name|tags|title|description|related-guide: list of selected codestarts with info

Static Config Keys in Codestart

quarkus.platform.group-id = BOM groupId
quarkus.platform.artifact-id = BOM artifactId
quarkus.platform.version = BOM version
project.group-id = Project groupId
project.artifact-id = Project artifactId
project.version = Project version
project.name = Project name (if specified)
project.description = Project description (if specified)
project.package-name = Project package name
quarkus.maven-plugin.group-id = Quarkus Maven plugin groupId
quarkus.maven-plugin.artifact-id = Quarkus Maven plugin artifactId
quarkus.maven-plugin.version = Quarkus Maven plugin version
quarkus.gradle-plugin.id = Quarkus Gradle pluginId
quarkus.gradle-plugin.version = Quarkus Gradle plugin version
quarkus.version = Quarkus version
java.version = Java version
kotlin.version = Kotlin version
scala.version = Scala version
scala-maven-plugin.version = Scala Maven plugin version
maven-compiler-plugin.version = Maven compiler plugin version
maven-surefire-plugin.version = Maven Surefire plugin version

Naming Convention for files

  • .tpl.qute will be processed with Qute and can use data (.tpl.qute will be removed from the output file name).

  • certain common files, such as readme.md, src/main/resources/application.yml, src/main/resources/META-INF/resources/index.html are generated from the collected fragments found in the selected codestarts for the project

  • other files are copied.

Templates (Qute)

Codestarts 可以使用 Qute 模板`MyClass.tpl.qute.java`进行动态渲染。

Codestarts may use Qute templates MyClass.tpl.qute.java for dynamic rendering.

这些模板可以使用包含以下内容的数据:

Those templates are able to use data which contains:

  • The data (and public metadata) of the codestart to generate (specified in the codestart.yml)

  • A merge of the shared-data from the all the codestarts used to generate the project

  • The user input

  • Some dynamically generated data (e.g. dependencies and test-dependencies)

README.md

您可以在`base`目录中添加`README.md`或`README.tpl.qute.md`,它将附加到其他目录。所以只需添加您扩展 codestart 的相关信息。

You may add a README.md or README.tpl.qute.md in the base directory, it will be appended to the others. So just add the info relative to your extension codestart.

base/README.tpl.qute.md

{#include readme-header /}

[Optionally, Here you may add information about how to use the example, settings, ...]

`{#include readme-header /}`将使用位于 Quarkus 项目 codestart 中的模板,该模板会显示来自 `codestart.yml`元数据的标准信息。

The {#include readme-header /} will use a template located in the Quarkus project codestart which displays standard info from the codestart.yml metadata.

application config application.yml

根据惯例,您应始终将 Quarkus 配置提供为 yaml 文件(base/src/main/resources/application.yml)。

As a convention, you should always provide the Quarkus configuration as a yaml file (base/src/main/resources/application.yml).

以下将发生:

It is going to be:

  • merged with the other extension codestarts configs

  • automatically converted to the selected config type (yaml or properties) at generation time depending on the selected extensions

index.html and web extension codestarts

扩展 codestarts 可以通过添加以下文件来为生成的 index.html 提供代码段:

Extension codestarts may provide a snippet for the generated index.html by adding this file:

base/src/main/resources/META-INF/resources/index.entry.qute.html

base/src/main/resources/META-INF/resources/index.entry.qute.html:

{#include index-entry /}

{#include index-entry /} 将使用位于 Quarkus 项目 codestart 中的模板,该模板显示来自 codestart.yml 元数据的标准信息。

The {#include index-entry /} will use a template located in the Quarkus project codestart which displays standard info from the codestart.yml metadata.

Integration test

有一个扩展可以帮助测试扩展 codestarts QuarkusCodestartTest

An extension is available to help test extension codestarts QuarkusCodestartTest:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-devtools-testing</artifactId>
  <scope>test</scope>
</dependency>

它提供了一种测试方法:

It provides a way to test:

  • the generated project content (with immutable mocked data) using snapshot testing

  • the generated project build/run (with real data) with helpers to run the build

在所有测试之前,该扩展将使用模拟数据和实际数据用指定语言在 Quarkus 项目中生成项目。你可以在 target/quarkus-codestart-test 目录中找到这些生成项目。你可以在 IDE 中打开 real-data 或使用终端对其进行操作。The real data is the easiest way to iterate on your extension codestart development.

Before all the tests, the extension will generate Quarkus projects in the specified languages with the given codestart using mocked data and real data. You can find those generated projects in the target/quarkus-codestart-test directory. You can open the real-data ones in your IDE or play with them using the terminal. The real data is the easiest way to iterate on your extension codestart development.

该扩展提供了测试项目构建 buildAllProjects 或仅测试特定语言项目 buildProject(Language language) 的帮助程序。它还提供了使用 Snapshot testing 测试内容的帮助程序。

The extension provides helpers to test that the projects build buildAllProjects or just a specific language project buildProject(Language language). It also provides helpers to test the content with Snapshot testing.

ConfigYamlCodestartTest 是 Quarkus 核心中的一个好示例。

The ConfigYamlCodestartTest is a good example in Quarkus core.

Snapshot testing

Snapshot 测试是一种确保测试生成的文本不会因版本而异的方法,即版本之间。这意味着每次提交的生成内容应不可变且确定(这是使用模拟数据的原因)。为了执行此类检查,我们自动生成生成内容的快照并将其作为后续测试运行的预期输出的引用进行提交。当模板发生更改时,我们还会提交感应的快照更改。这样,在审查期间,我们可以确保应用的代码更改对生成输出产生预期效果。

Snapshot testing is a way to make sure the content generated by a test doesn’t change from one revision to another, i.e. between commits. That means, the generated content for each commit needs to be immutable and deterministic (this is the reason for using mocked data). To be able to perform such checks, we auto-generate snapshots of the generated content and commit them as the references of the expected output for subsequent test runs. When the templates change, we also commit the induced snapshots changes. This way, during the review, we can make sure the applied code changes have the expected effects on the generated output.

该扩展提供了检查内容的帮助程序:

The extension provides helpers to check the content:

  • checkGeneratedSource() validate a class against the snapshots for all languages (or a specific one).

  • checkGeneratedTestSource() validate a test class against the snapshots for all languages (or a specific one).

  • assertThatGeneratedFileMatchSnapshot() check a project file against the snapshot.

  • You can use AbstractPathAssert.satisfies(checkContains("some content")) or any Path assert on the return of the methods above to also check the file contains a specific content.

  • assertThatGeneratedTreeMatchSnapshots() lets you compare the project file structure (tree) for a specific language against its snapshot.

要在本地文件系统上首次生成或更新现有的快照文件,您需要在本地运行测试并开发 codestart 时添加 -Dsnap。它们需要添加为提交的一部分,否则测试将不会通过 CI。

In order to first generate or update existing snapshots files on your local filesystem, you need to add -Dsnap when running the tests locally while developing the codestart. They need to be added as part of the commit, else the tests will not pass on the CI.

Writing tips

  • Your extension codestart must/should be independent of buildtool and dockerfiles.

  • Extension codestarts should be able to work alongside each other without interference (in combination).

  • Make sure your class names are unique across all extension codestarts.

  • Only use org.acme as package name.

  • Use a unique path /[unique] for your REST paths

  • Write the config in yml src/main/resources/application.yml.[.iokays-translated-0bb22f614dc2bc3d80c031f549a6590a] 它将与其他 codestart 的配置合并,并自动转换为选定的配置类型(yaml 或 properties)。

It is going to be merged with the other codestarts config and automatically converted to the selected config type (yaml or properties). * You can start with java and add kotlin later in another PR (create an issue so you don’t forget). * If you have a question, ping me @ia3andy on [role="bare"]https://quarkusio.zulipchat.com/.