Build Configuration
Configuration Approaches
As your monorepo grows and you adopt more advanced tooling, build configuration can become complex. One of the reasons Grog was created was to provide an alternative to the steep learning curve of tools like Bazel’s Starlark configuration language.
Grog takes a different approach to configuration that emphasizes simplicity and extensibility. It has minimal opinions about how you define builds, as long as each package can output a structured definition of its build targets.
You can configure Grog builds in three different ways:
- Static configuration: Simple
BUILD.{json,yaml}
files that define build targets for each package. Great for getting started. - Makefiles: Add special comments to your existing
Makefile
s to run make goals while benefiting from Grog’s execution model. Perfect for gradually adopting Grog when you already use Makefiles. - Pkl (recommended): Use the Pkl configuration language to create reusable configuration elements. Ideal for scaling your build configuration and keeping things DRY.
Static Configuration
The simplest way to define your build targets is to create a BUILD.json
or BUILD.yaml
file in your package directory. This file defines all build targets for that package in your monorepo.
targets: - name: build_app command: npm run build dependencies: - :generate_proto - //path/to/other/package:target_name inputs: - src/**/*.js - package.json outputs: - dist/bundle.js
- name: generate_proto command: "protoc --js_out=src/generated proto/\*.proto" inputs: - proto/\*.proto outputs: - dir::src/generated
Save this as BUILD.yaml
in your project directory.
{ "targets": [ { "name": "build_app", "command": "npm run build", "dependencies": [ ":generate_proto", "//path/to/other/package:target_name" ], "inputs": ["src/**/*.js", "package.json"], "outputs": ["dist/bundle.js"] }, { "name": "generate_proto", "command": "protoc --js_out=src/generated proto/*.proto", "inputs": ["proto/*.proto"], "outputs": ["dir::src/generated"] } ]}
Save this as BUILD.json
in your project directory.
For a complete list of available options, see the target configuration reference.
Makefile Integration
If you already use Makefiles extensively and want to continue using them while benefiting from Grog’s execution model, you can add special comments to your existing Makefiles.
The schema is straightforward:
- Grog looks for a line that starts with
# @grog
- Everything between that line and the next make goal is parsed as YAML configuration
- The command will be
make <goal>
- If the name is left empty, the goal name becomes the target name
Here’s an example:
# @grog# inputs:# - src/**/*.js# - package.json# outputs:# - dist/bundle.js# dependencies:# - :generate_proto# - //path/to/other/package:target_namebuild_app: npm run build
# @grog# inputs:# - proto/*.proto# outputs:# - dir::src/generatedgenerate_proto: protoc --js_out=src/generated proto/*.proto
This Makefile exposes the targets build_app
and generate_proto
to Grog, allowing you to run them with grog build :build_app
or grog build :generate_proto
.
Pkl Configuration (Recommended)
As your codebase grows, maintaining consistent build configurations across packages becomes tedious. For example, if you want a generic Java build process that’s consistent across your entire monorepo, copying configurations between packages is error-prone and difficult to maintain.
Pkl is a configuration language designed specifically for creating reusable configuration macros. With Pkl, you can:
- Define configuration macros that can be shared within your monorepo or even across the internet
- Get semantic highlighting and auto-completion with IDE plugins
- Create modular, reusable build configurations
Here’s the same build configuration as above, but using Pkl:
amends "package://grog.build/releases/v0.2.2/grog@0.2.2#/package.pkl"
targets { new { name = "build_app" command = "npm run build" dependencies { ":generate_proto" "//path/to/other/package:target_name" } inputs { "src/**/*.js" "package.json" } outputs { "dist/bundle.js" } }
new { name = "generate_proto" command = "protoc --js_out=src/generated proto/*.proto" inputs { "proto/*.proto" } outputs { "dir::src/generated" } }}
Creating Reusable Build Macros
One of Pkl’s strengths is the ability to create reusable build macros. For example, if you want a standard setup for Next.js projects that includes installing dependencies and building the app, you can create a reusable macro:
import "package://grog.build/releases/v0.2.2/grog@0.2.2#/package.pkl"
// Create a function that returns// a list of targets for a Next.js app// using pkl's class-as-a-function patternclass App { app_name: String
fixed targets: Listing<package.Target> = new Listing<package.Target> { new { name = app_name + "_install" command = "npm install" inputs { "package.json" "package-lock.json" } }
new { name = app_name + "_build" command = "npm run build" dependencies { ":" + app_name + "_install" } inputs { "src/**/*.js" "src/**/*.jsx" "src/**/*.ts" "src/**/*.tsx" } outputs { "dir::.next" } } }}
You can store this in a file called nextjs.pkl
somewhere in your repository and import it wherever you need it:
import "nextjs.pkl"
targets { // This will add both the install and build targets // with the prefix "my-app" ...(nextjs.App) { app_name = "my-app" }.targets
// You can also add custom targets new { name = "custom_target" command = "echo 'Hello, world!'" }}
This approach allows you to standardize build configurations across your monorepo while still allowing for customization where needed.