Extension codestart
This guide explains how to create and configure a Quarkus Codestart for an extension.
Description
"Extension Codestarts" is the name we gave to our Quarkus extension quickstart 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 necessary resources and code required to start using that particular extension.
You can apply extension codestarts in the Quarkus tooling:
-
code.quarkus.io (find the extensions tagged with [code])
-
The Quarkus Maven plugin:
mvn io.quarkus.platform:quarkus-maven-plugin:create
-
The Quarkus CLI:
quarkus create app
How it works
When starting a project, you choose the language, the build tool, the framework, then you add dockerfiles, CI, dependencies and code.
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, to put on top):
-
tooling: Anything that can be added to improve the project (e.g. dockerfiles, github-action)
-
code: Any Quarkus extension can provide starter code. The user can decide to activate it or not.
Each codestart consists of:
-
A codestart unique name, ie
my-codestart
-
A directory for the codestart files, ie
my-codestart/
-
A
codestart.yml
file -
Optionally some templates that are following a common structure and naming conventions
Where are the Quarkus Extension Codestarts located
-
In Quarkus core repo, the extension codestarts are all in the same module.
-
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
The base codestarts contains templates to create project, buildtool, languages, config & tooling files.
Writing an Extension Codestart
As was mentioned previously, the base project files (pom.xml, dockerfiles, …) are already generated by base codestarts provided by the Quarkus core. Thanks to this, we can only focus on the important - the starter code for the extension.
The codestart should not include any business logic, instead, it should contain some stub data/hello world to compile. The idea is to bring code that is the starting point to everyone using the extension.
Writing an Extension Codestart in Quarkus Core
-
Copy one of the existing Quarkus core extension codestarts. If the code needs to expose a web resource,
resteasy-qute-codestart
could be a good base Otherwise,config-yaml-codestart
could be a better starting point. More info on the Directory Structure. -
Edit the codestart.yml:
-
Create the extension binding in the extension metadata (example). Thanks to this, the codestart is added when the user selects the extension
-
Add the readme README.md section template.
-
Add the code in the language folder (it is recommended to at least provide java and kotlin). You have to use
org.acme
as the package name: Dynamic package name generation from org.acme. It is possible to use Templates (Qute) if needed. -
Optionally, Add the
index.html
section template (index.html and web extension codestarts). -
Optionally, add some resources (
./base
directory if they are non language specific) -
Optionally, add the application config application.yml.
-
Create an Integration test.
Writing an Extension Codestart in the Quarkiverse or standalone
For extensions hosted outside the Quarkus core[https://github.com/quarkusio/quarkus] repository, codestarts will typically be located in the runtime module (with special instruction in the pom.xml
to generate a separate codestart artifact). Here is an example extension with a codestart and its tests.
Generating your Extension Codestart
You need to build your codestart with Maven to make it available in the tooling:
-
First add the codestart and update the relevant extension’s metadata yml file, and build it all (the codestart and the extension if in core).
-
In Quarkus core, you also have to rebuild the
devtools/bom-descriptor-json
module to bind the codestart with the extension in the platform descriptor.
With the tests
You can use the Integration test to help develop your codestart with buildAllProjects
(In Quarkus core we added @EnabledIfSystemProperty(named = "build-projects", matches = "true")
because codestarts are already built together in another test from QuarkusCodestartBuildIT
).
Use -Dbuild-projects=true
when running this test to generate the real project with your codestart. Open it with your IDE, then change the code and copy it back to the codestart (and iterate until you are happy with the result).
With the Quarkus tooling
Using the tooling to generate your local extension codestart during dev is not yet available Quarkiverse/Standalone extension (Until then, you may use the tests and follow #21165 for updates). |
Using the CLI or Maven plugin to generate a project with your codestart:
-
If using the CLI, you’ll probably need to add
-P=io.quarkus:quarkus-bom:999-SNAPSHOT
to the CLI’s arguments to use your snapshot of the platform -
Example CLI command:
quarkus create app -x smallrye-health --code --java -P=io.quarkus:quarkus-bom:999-SNAPSHOT
-
Equivalent for the Maven plugin:
mvn io.quarkus:quarkus-maven-plugin:2.3.0.Final:create -Dextensions=smallrye-health -DplatformVersion=999-SNAPSHOT
Specific topics
Dynamic package name generation from 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 will be used (and auto-replace org.acme
).
It will be auto-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 {project.package-name}
data placeholder (find an example in the grpc proto file).
codestart.yml
# the codestart unique name
name: resteasy-example
# the codestart reference (the name is used if not set)
ref: resteasy
# the type of codestart (other types are used for other project files)
type: code
# public metadata for this example (they will also be accessible from this codestart qute templates by using the key: {title})
metadata:
title: RESTEasy JAX-RS 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
# the path is optional and used by the generated index.html if present
path: /some-path
language:
base:
# Specify the extension and possibly other required dependencies
dependencies:
- io.quarkus:quarkus-resteasy
# And maybe test dependencies?
test-dependencies:
- io.rest-assured:rest-assured
Directory Structure
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: time of generation (in millis)
input.selected-extensions[].name|description|guide: list of selected extensions with info
input.selected-extensions-ga: Set of String with the list of extensions groupId:artifactId, usefull 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.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 may use Qute templates MyClass.tpl.qute.java
for dynamic rendering.
Those templates are able to use data which contains:
-
The
data
(and publicmetadata
) of the codestart to generate (specified in thecodestart.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
andtest-dependencies
)
README.md
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, ...]
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
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
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:
{#include index-entry /}
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
An extension is available to help test extension codestarts QuarkusCodestartTest
. 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
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.
|
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.
The ConfigYamlCodestartTest is a good example in Quarkus core.
Snapshot testing
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.
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
.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 https://quarkusio.zulipchat.com/.