Code generation
Pkl code may turn into Go code by way of code generation.
Running code generation
Code generation is done through the pkl-gen-go
binary. To install:
go install github.com/apple/pkl-go/cmd/pkl-gen-go@latest
Once installed, Go may be generated from Pkl:
pkl-gen-go config/AppConfig.pkl
Configuring the code generator
Options may be provided to the code generator either using CLI flags, or using a settings file.
Settings file
The settings file is a Pkl file that amends module package://pkg.pkl-lang.org/pkl-go/pkl.golang@<VERSION>#/GeneratorSettings.pkl
.
By default, the code generator will look for a generator-settings.pkl
file residing in the current working directory.
If found, it is used to configure the code generator.
The settings file may also be set using the --generator-settings
flag.
Base path
The base path setting determines the relative path in which files get written to the working directory. It also acts as a filter to skip codegen for any modules that are external to the project.
For example, if a Pkl module corresponds to Go package github.com/myorg/myproject/appconfig
, and the base path is github.com/myorg/myproject
, the generated files are placed into output directory appconfig/
.
If a base path is not explicitly set, pkl-gen-go
will also look for a go.mod
file in the current working directory and use the Go module name as the base path.
If the base path cannot be determined, the full package path is written to the current working directory.
The base path may be set in the settings file, and also via the --base-path
CLI flag.
If the determined package name is not prefixed by the base path, it is skipped from code generation. |
Package mappings
During codegen, the generator requires a mapping from a Pkl module name to a Go package name.
In normal cases, this should be provided using the @go.Package
annotation. However, there are times when this is infeasible, for example, if the Pkl module is external to the project. For these situations, mappings can be provided using the packageMappings property in the settings file, or using the --mapping
CLI flag.
Struct tags
By default, all struct fields receive a pkl
tag. Additional tags can be configured either on a per-property basis, or globally for all properties.
To configure tags on a per-property basis, use the @go.Field
annotation on a property.
To configure tags globally for all properties, use the structTags
property the generator settings file.
Here is an example for setting struct tags for a single property:
@go.Field {
structTags {
["toml"] = "%{name},omitempty" (1)
}
}
firstName: String
1 | Add toml:"firstName,omitempty" as a struct tag |
To share struct tag settings across multiple properties, define a child class of go.Field
.
class TomlField extends go.Field {
structTags {
["toml"] = "%{name},omitempty"
}
}
@TomlField
firstName: String
@TomlField
lastName: String
How Pkl is turned into Go
Basic types
The below table describes how Pkl types are mapped into Go types.
Pkl type |
Go type |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Unions ( |
|
pkl.Class , pkl.TypeAlias , pkl.IntSeq and pkl.Pair only exist for compatibilty purposes because they are possible Pkl runtime values.
|
Classes
Classes turn into a variation of structs and interfaces, depending on inheritance. Interfaces get generated because Go cannot model polymorphism with structs alone (e.g. a value that is a Dog
struct is not assignable when an Animal
struct is expected).
When generating interfaces, a getter is generated for every property by prefixing Get
to the property name.
The below table describes how classes get generated.
Pkl class |
Go interface |
Go struct |
|
<none> |
|
|
|
|
|
|
<none> |
|
|
|
In the case of inheritance, the parent’s struct and interface are embedded.
Enums
If a typealias is defined as a union of string literals, it is turned into a new type backed by string
. Each member of the union is generated as its own constant.
The new type is placed into a subpackage of the module’s mapped Go package.
For example, the following Pkl code:
typealias City = "San Francisco"|"Cupertino"|"London"
Turns into something like this:
package city
type City string
const (
SanFrancisco City = "San Francisco"
Cupertino City = "Cupertino"
London City = "London"
)
If the names of the determined constants conflicts due to normalization rules, an enum is not generated, and a string
type is instead inlined into the usage locations.
If a typealias isn’t a union of string literals, its resolved type is inlined into the usage locations.
The Pkl type nothing is ignored when a member of a union. Therefore, typealias City = nothing|"San Francisco"|"Cupertino"|"London" is still considered an enum.
|
Resolving name conflicts
When turning Pkl names into Go names, the code generator follows these rules:
-
Any non-letter and non-digit characters get stripped, and each proceding letter gets capitalized.
-
If a name does not start with a latin alphabet character, prefix with
N
. -
Capitalize so they get exported.
As a result, it is possible that two names collide and turn into the same Go name.
To resolve these conflicts, the @go.Name
annotation must be used on at least one of these declaractions so the resulting names are distinct.
For example:
@go.Name { value = "MyCoolApplication" }
class My_Application
class MyApplication