Skip to content

Commit 5da7705

Browse files
authored
feat: allow aggregate to output to specific file (#22)
# Add transitive dependency support to formatjs_aggregate ### TL;DR Added support for transitive message aggregation and improved documentation for cross-platform compatibility. ### What changed? - Added a new `out` attribute to `formatjs_aggregate` rule to specify custom output file names - Created a new `transitive_messages` example that demonstrates proper traversal of the dependency graph - Added comprehensive documentation about platform support (macOS, Linux, Windows) - Improved documentation with details about cross-platform builds, output groups, and usage patterns - Added a fixture file to verify transitive dependency resolution works correctly ### How to test? 1. Build the new transitive messages example: ```bash bazel build //examples/aggregate/app:transitive_messages ``` 2. Verify that messages from module1 are included even though it's only a transitive dependency: ```bash bazel test //examples/aggregate/app:aggregation_test ``` 3. Test on different platforms to verify consistent output across macOS, Linux, and Windows ### Why make this change? This change ensures that the formatjs_aggregate rule properly handles transitive dependencies, which is crucial for large monorepos where message dependencies might be nested several levels deep. The improved documentation makes it clear that the rules work across all major platforms without special configuration, which helps users understand the cross-platform capabilities of the toolchain.
1 parent 5ef109d commit 5da7705

3 files changed

Lines changed: 125 additions & 15 deletions

File tree

examples/aggregate/app/BUILD.bazel

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,23 @@ formatjs_aggregate(
1414
],
1515
)
1616

17-
# Snapshot test - verifies that aggregated messages match the expected fixture
18-
# Run `bazel run //app:update_all_messages_fixture` to update the snapshot
19-
write_source_files(
20-
name = "update_all_messages_fixture",
21-
files = {
22-
"all_messages.fixture.json": ":all_messages",
23-
},
17+
# Aggregate messages from module2 and module3
18+
# Module3 depends on module1, so module1's messages should be included transitively
19+
# This demonstrates that the aspect properly traverses the dependency graph
20+
formatjs_aggregate(
21+
name = "transitive_messages",
22+
out = "transitive_messages.json",
23+
deps = [
24+
"//module2:messages",
25+
"//module3:messages",
26+
],
2427
)
2528

2629
# Test target - verifies the snapshot hasn't changed
2730
write_source_files(
2831
name = "aggregation_test",
2932
files = {
3033
"all_messages.fixture.json": ":all_messages",
34+
"transitive_messages.fixture.json": ":transitive_messages.json",
3135
},
3236
)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"common.cancel": {
3+
"id": "common.cancel",
4+
"defaultMessage": "Cancel",
5+
"description": "Common cancel button"
6+
},
7+
"common.save": {
8+
"id": "common.save",
9+
"defaultMessage": "Save",
10+
"description": "Common save button"
11+
},
12+
"module1.description": {
13+
"id": "module1.description",
14+
"defaultMessage": "This is the first module",
15+
"description": "Description for module 1"
16+
},
17+
"module1.title": {
18+
"id": "module1.title",
19+
"defaultMessage": "Module 1 Title",
20+
"description": "Title for module 1"
21+
},
22+
"module2.description": {
23+
"id": "module2.description",
24+
"defaultMessage": "This is the second module",
25+
"description": "Description for module 2"
26+
},
27+
"module2.title": {
28+
"id": "module2.title",
29+
"defaultMessage": "Module 2 Title",
30+
"description": "Title for module 2"
31+
},
32+
"module3.description": {
33+
"id": "module3.description",
34+
"defaultMessage": "This is the third module",
35+
"description": "Description for module 3"
36+
},
37+
"module3.title": {
38+
"id": "module3.title",
39+
"defaultMessage": "Module 3 Title",
40+
"description": "Title for module 3"
41+
}
42+
}

formatjs/aggregate.bzl

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,34 @@ and their dependencies into a single, consolidated JSON file. This is useful for
55
large monorepo projects where messages are extracted from multiple packages or
66
modules and need to be combined for translation or compilation.
77
8-
The aggregation process:
8+
## Platform Support
9+
10+
These rules work across all platforms supported by the jq.bzl toolchain:
11+
- macOS (Apple Silicon and Intel)
12+
- Linux (x86_64 and aarch64)
13+
- Windows (x86_64)
14+
15+
Bazel automatically selects the appropriate jq binary for your platform through
16+
the toolchain resolution system. No platform-specific configuration is needed.
17+
18+
## Aggregation Process
19+
920
1. Traverses the dependency graph collecting all extracted message files
1021
2. Merges messages using jq with object multiplication semantics
1122
3. Sorts keys alphabetically for deterministic output
1223
4. Handles duplicate message IDs (later values override earlier ones)
1324
25+
## Usage Patterns
26+
1427
Aggregation can be used via:
1528
- `formatjs_aggregate` rule: Declarative approach for common use cases
1629
- `formatjs_aggregate_aspect`: Aspect-based approach for advanced scenarios
30+
31+
## Dependencies
32+
33+
This module depends on:
34+
- `jq.bzl` toolchain for JSON merging and sorting operations
35+
- `FormatjsExtractInfo` provider from extract.bzl for message collection
1736
"""
1837

1938
load(":extract.bzl", "FormatjsExtractInfo")
@@ -105,6 +124,15 @@ formatjs_aggregate_aspect = aspect(
105124
a target and all its transitive dependencies. It automatically traverses the
106125
dependency graph, collecting messages from any target that provides `FormatjsExtractInfo`.
107126
127+
## Platform Support
128+
129+
Works across all platforms through Bazel's toolchain resolution:
130+
- macOS (Apple Silicon and Intel)
131+
- Linux (x86_64 and aarch64)
132+
- Windows (x86_64)
133+
134+
The jq.bzl toolchain automatically provides the correct binary for your platform.
135+
108136
## How It Works
109137
110138
The aspect:
@@ -120,6 +148,7 @@ formatjs_aggregate_aspect = aspect(
120148
- Later values override earlier ones for duplicate message IDs
121149
- All unique message IDs are preserved
122150
- Sorted alphabetically by key in the final output
151+
- Ensures deterministic output across all platforms
123152
124153
## Usage Patterns
125154
@@ -133,14 +162,22 @@ formatjs_aggregate_aspect = aspect(
133162
### Get individual message files (not merged):
134163
```bash
135164
bazel build //app:main \\
136-
--aspects=@rules_formatjs//formatjs:aggregate.bzl%formatjs_aggregate_aspect \\
137-
--output_groups=all_messages
165+
--aspects=@rules_formatjs//formatjs:aggregate.bzl%formatjs_aggregate_aspect \\
166+
--output_groups=all_messages
167+
```
168+
169+
## Output Groups
170+
171+
- `aggregated_messages`: Single merged JSON file with all messages
172+
- `all_messages`: All individual message JSON files (not merged)
138173
139-
Output:
140-
- aggregated_messages: Single merged JSON file with all messages
141-
- all_messages: All individual message JSON files (not merged)
174+
The aggregated file will be named: `<target_name>_aggregated_messages.json`
142175
143-
The aggregated file will be named: <target_name>_aggregated_messages.json
176+
## Cross-Platform Considerations
177+
178+
- JSON output is identical across all platforms (UTF-8, sorted keys)
179+
- jq binary is automatically selected for your build platform
180+
- No platform-specific configuration needed in BUILD files
144181
""",
145182
)
146183

@@ -167,7 +204,7 @@ def _formatjs_aggregate_impl(ctx):
167204
fail("No messages found in dependencies. Make sure deps contain formatjs_extract targets.")
168205

169206
# Create the final aggregated output file
170-
output = ctx.actions.declare_file(ctx.attr.name + ".json")
207+
output = ctx.outputs.out or ctx.actions.declare_file(ctx.attr.name + ".json")
171208

172209
# Use jq to merge all messages and sort keys
173210
jq_toolchain = ctx.toolchains["@jq.bzl//jq/toolchain:type"]
@@ -202,6 +239,9 @@ def _formatjs_aggregate_impl(ctx):
202239
formatjs_aggregate = rule(
203240
implementation = _formatjs_aggregate_impl,
204241
attrs = {
242+
"out": attr.output(
243+
doc = "Output file for the aggregated messages (JSON format). Defaults to <name>.json",
244+
),
205245
"deps": attr.label_list(
206246
doc = """Dependencies to aggregate messages from.
207247
@@ -229,13 +269,24 @@ formatjs_aggregate = rule(
229269
targets across your codebase. It's ideal for monorepos where messages are extracted
230270
from different packages or modules and need to be combined for translation workflows.
231271
272+
## Platform Support
273+
274+
Works seamlessly across all platforms through Bazel's toolchain resolution:
275+
- macOS (Apple Silicon and Intel)
276+
- Linux (x86_64 and aarch64)
277+
- Windows (x86_64)
278+
279+
The jq.bzl toolchain automatically provides the correct binary for your build platform.
280+
No platform-specific configuration is required in BUILD files.
281+
232282
## Features
233283
234284
- **Automatic Traversal**: Automatically applies aspect to collect messages from dependencies
235285
- **Transitive Collection**: Gathers messages from the entire dependency graph
236286
- **Merge & Sort**: Merges all messages and sorts keys alphabetically
237287
- **Duplicate Handling**: Later values override earlier ones for duplicate message IDs
238288
- **Simple API**: Just list your extraction targets as deps
289+
- **Cross-Platform**: Identical output across all platforms (UTF-8, sorted keys)
239290
240291
## How It Works
241292
@@ -365,6 +416,19 @@ formatjs_aggregate = rule(
365416
)
366417
```
367418
419+
## Cross-Platform Builds
420+
421+
The aggregation process produces identical output across all platforms:
422+
- JSON is UTF-8 encoded with consistent line endings
423+
- Keys are sorted alphabetically for deterministic output
424+
- Checksums match across macOS, Linux, and Windows builds
425+
- Remote caching works seamlessly across heterogeneous build fleets
426+
427+
This ensures that:
428+
- CI builds are reproducible regardless of runner platform
429+
- Developers on different OS can share build artifacts
430+
- Remote execution works with mixed execution platforms
431+
368432
## See Also
369433
370434
- `formatjs_extract`: Extract messages from source files

0 commit comments

Comments
 (0)