Skip to content

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 Makefiles 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.

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_name
build_app:
npm run build
# @grog
# inputs:
# - proto/*.proto
# outputs:
# - dir::src/generated
generate_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.

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 pattern
class 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.