Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .claude/settings.local.json

This file was deleted.

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
__pycache__/
Open Sourcing/
.claude/settings.local.json
167 changes: 167 additions & 0 deletions Documentation/CodeGeneration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Code Generation with GYB

Some source files in DevConfiguration are generated using
[GYB][GYB] (Generate Your Boilerplate), the same template tool used by
[swift-configuration][SwiftConfiguration]. GYB lets us define repetitive API patterns once in a
template and generate type-specific overloads automatically, keeping the codebase consistent and
reducing the maintenance burden of boilerplate code.

[GYB]: https://github.com/apple/swift/blob/main/utils/gyb.py
[SwiftConfiguration]: https://github.com/apple/swift-configuration


## Why GYB?

`ConfigVariableReader` exposes `value(for:)`, `fetchValue(for:)`, `watchValue(for:)`, and
subscript accessors for every supported value type: `Bool`, `Float64`, `Int`, `String`,
`[UInt8]`, their array variants, and generic overloads for `RawRepresentable` and
`ExpressibleByConfig*` types. Each overload follows the same structure but delegates to a
different `ConfigReader` method. Writing and maintaining these by hand would be error-prone and
tedious. GYB lets us define the pattern once and generate all the overloads from a shared list
of type definitions.


## File Structure

Scripts/
generate-gyb # Entry point script; regenerates all .gyb templates
gyb/
gyb.py # The GYB tool itself (from the Swift project)
gyb_utils.py # Shared type definitions and helper functions

Sources/DevConfiguration/Core/
ConfigVariableReader+Functions.swift.gyb # Source template
ConfigVariableReader+Functions.swift # Generated output (checked in)

Tests/DevConfigurationTests/Unit Tests/Core/
ConfigVariableReader+FunctionsTests.swift.gyb # Test template
ConfigVariableReader+FunctionsTests.swift # Generated output (checked in)

The generated `.swift` files are checked in so that contributors can build and test without
needing to run GYB. The `.gyb` templates are the source of truth.


## GYB Template Syntax

GYB templates are plain text files (in our case, Swift code) with embedded Python. There are
three constructs:

### Code Blocks (`%{ ... }%`)

Multi-line Python code, typically used at the top of a template for imports and setup:

%{
import sys
import os

scripts_dir = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'..', '..', '..', 'Scripts', 'gyb'
)
sys.path.insert(0, scripts_dir)

from gyb_utils import *
}%

### Line Directives (`%`)

Single-line Python statements, used for loops and conditionals. These must start at the
beginning of the line:

% for i, p in enumerate(primitive_types):
% if i > 0:

% end
public func value(for variable: ConfigVariable<${p["type"]}>) -> ${p["type"]} {
// ...
}
% end

### Substitutions (`${ ... }`)

Inline Python expressions that are evaluated and inserted into the output:

/// Gets the value for the specified `${p["name"]}` config variable.
public func value(
for variable: ConfigVariable<${p["type"]}>,
// ...
) -> ${p["type"]} {

Note that `% end` closes both `for` and `if` blocks. GYB uses indentation-independent `end`
markers rather than Python's indentation-based scoping.


## Type Definitions in `gyb_utils.py`

The `Scripts/gyb/gyb_utils.py` file defines the type lists that drive code generation. Each
list is an array of dictionaries, where each dictionary describes one type or type category:

### `primitive_types`

Types with dedicated `ConfigReader` methods (`bool`, `int`, `double`, `string`, `bytes`,
and their array variants). Each entry has:

- **`name`**: Display name for doc comments (e.g., `"Bool"`, `"[String]"`)
- **`type`**: The Swift type used in signatures (e.g., `"Bool"`, `"[String]"`)
- **`reader_method`**: The `ConfigReader` method name (e.g., `"bool"`, `"stringArray"`)

### `string_convertible_types`

Generic types that use `ConfigReader.string(forKey:as:...)` and
`ConfigReader.stringArray(forKey:as:...)`. Each entry has:

- **`protocol`**: The Swift protocol constraint (e.g., `"RawRepresentable<String>"`)
- **`doc_name`**: Display name for doc comments

### `int_convertible_types`

Generic types that use `ConfigReader.int(forKey:as:...)` and
`ConfigReader.intArray(forKey:as:...)`. Same structure as `string_convertible_types`.

### Helper Functions

- **`autogenerated_warning()`**: Returns the "DO NOT EDIT" header comment for generated files
- **`upper_first(s)`**: Capitalizes the first character of a string, used to construct
method names like `fetchBool` from `bool`


## How to Make Changes

### Editing Existing Overloads

If you need to change the implementation pattern shared by all overloads (e.g., adding a new
parameter, changing how `isSecret` is passed):

1. Edit the `.gyb` template file
2. Run `Scripts/generate-gyb`
3. Review the generated `.swift` file to verify the changes

### Adding a New Value Type

If swift-configuration adds a new primitive type:

1. Add an entry to the appropriate list in `Scripts/gyb/gyb_utils.py`
2. If adding test support, add a corresponding entry to the test template's helper list
3. Run `Scripts/generate-gyb`
4. Review the generated files

### Adding a New API Pattern

If you need a new category of methods (beyond get, fetch, watch, and subscript):

1. Add a new `% for` loop section in the source `.gyb` template
2. If adding tests, add entries to the `api_tests` list in the test `.gyb` template
3. Run `Scripts/generate-gyb`
4. Review the generated files

### Important Rules

- **Never edit generated `.swift` files directly.** Your changes will be overwritten the
next time someone runs `Scripts/generate-gyb`. Always edit the `.gyb` template instead.
- **Always regenerate after changes.** Run `Scripts/generate-gyb` after editing any `.gyb`
template or `gyb_utils.py`.
- **Review the diff.** Generated code can be subtle. Always review the generated output
to make sure the changes are what you expect.
- **Keep `gyb_utils.py` in sync.** The source and test templates both import from
`gyb_utils.py`. If you add a type to `gyb_utils.py`, make sure the test template
accounts for it too.
6 changes: 3 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ let package = Package(
),
],
dependencies: [
.package(url: "https://github.com/apple/swift-configuration", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-configuration", from: "1.1.0"),
.package(url: "https://github.com/DevKitOrganization/DevFoundation.git", from: "1.7.0"),
.package(url: "https://github.com/DevKitOrganization/DevTesting", from: "1.5.0"),
],
Expand All @@ -34,6 +34,9 @@ let package = Package(
.product(name: "Configuration", package: "swift-configuration"),
.product(name: "DevFoundation", package: "DevFoundation"),
],
exclude: [
"Core/ConfigVariableReader+Functions.swift.gyb",
],
swiftSettings: swiftSettings
),
.testTarget(
Expand All @@ -42,6 +45,9 @@ let package = Package(
"DevConfiguration",
"DevTesting",
],
exclude: [
"Unit Tests/Core/ConfigVariableReader+FunctionsTests.swift.gyb",
],
swiftSettings: swiftSettings
),
]
Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,26 @@ public interfaces are fully documented and tested. We aim for overall test cover

To set up the development environment:

1. Run `Scripts/install-git-hooks` to install pre-commit hooks that automatically check code
formatting.
1. Run `Scripts/install-git-hooks` to install pre-commit hooks that automatically check
code formatting.
2. Use `Scripts/lint` to manually check code formatting at any time.
3. Use `Scripts/format` to automatically format code.

### Code Generation with GYB

Some source files are generated using [GYB][GYB] (Generate Your Boilerplate) to reduce
repetitive boilerplate. Generated `.swift` files are checked in so that contributors don't
need to run GYB unless they modify a template. To regenerate after changing a `.gyb` template
or `Scripts/gyb/gyb_utils.py`:

Scripts/generate-gyb

Do not edit generated files directly — edit the `.gyb` template instead. See
[Documentation/CodeGeneration.md](Documentation/CodeGeneration.md) for details on template
syntax, type definitions, and workflows.

[GYB]: https://github.com/apple/swift/blob/main/utils/gyb.py


## Bugs and Feature Requests

Expand Down
12 changes: 12 additions & 0 deletions Scripts/generate-gyb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
set -eu

SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPTS_DIR/.." && pwd)"

cd "$PROJECT_DIR"

find . -name '*.gyb' | grep -v '.build/' | \
while read -r file; do \
"$SCRIPTS_DIR/gyb/gyb.py" --line-directive '' -o "${file%.gyb}" "$file"; \
done
Loading
Loading