| # BUILD-file Styleguide — AOO Bazel Migration |
| |
| How to author `BUILD.bazel` and `.bzl` files so a new module looks like the |
| ones already migrated. This is the **code** styleguide for this repo — the |
| actual C++/Java source is out of scope (see [CLAUDE.md](CLAUDE.md) "Goal"). |
| |
| For *operating* rules (build commands, workflow, guardrails) see |
| [CLAUDE.md](CLAUDE.md). For *why* a given pattern exists, the |
| `MEMORY.md` milestone for that module is the primary record. This file is |
| the **generalization** of those ~50 milestones into copy-pasteable shape. |
| |
| Canonical reference example to imitate: [main/basctl/BUILD.bazel](main/basctl/BUILD.bazel) |
| (a full first-party DLL with SDI, RSC, shielded includes, implib export). |
| Foundational library example: [main/sal/BUILD.bazel](main/sal/BUILD.bazel). |
| |
| --- |
| |
| ## 1. Module BUILD skeleton |
| |
| A first-party module DLL is built in this order. Copy the shape, not the names. |
| |
| ```python |
| package(default_visibility = ["//visibility:public"]) |
| |
| load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") |
| # load only the pipelines this module actually uses (rsc_res, sdi_target, ...) |
| |
| # 1. Defines and copts as module-local _DEFINES / _COPTS lists (see §2) |
| _DEFINES = [ "WNT", "GUI", "WIN32", ... , "snprintf=_snprintf", "SUPD=680" ] |
| _COPTS = [ "/Zc:wchar_t-", "/Imain/<mod>/inc", "/Imain/<mod>/source/inc", ... ] |
| |
| # 2. Header / include "shield" libraries (see §3) |
| cc_library(name = "<mod>_inc", textual_hdrs = glob(["inc/**"]), strip_include_prefix = "inc") |
| cc_library(name = "<mod>_source_inc", textual_hdrs = glob(["source/inc/**"]), strip_include_prefix = "source/inc") |
| |
| # 3. Generated inputs (SDI slots, IDL, etc.) — only if the module uses them |
| sdi_target(name = "<mod>slots_sdi", ...) |
| cc_library(name = "<mod>slots_headers", hdrs = [":<mod>slots_sdi"], includes = ["<mod>slots_sdi_inc"]) |
| |
| # 4. The DLL itself |
| cc_binary( |
| name = "<mod>", |
| srcs = ["inc/pch/precompiled_<mod>.cxx"] + glob(["source/**/*.cxx", "source/**/*.hxx"]), |
| copts = _COPTS, |
| defines = _DEFINES, |
| linkshared = True, |
| win_def_file = "util/<mod>.def", # see §5 |
| deps = [ ":<mod>_inc", ... , "//main/sal:sal_headers", ... ], # headers only |
| additional_linker_inputs = [ "//main/sal:sal_implib", ... ], # implibs (see §5) |
| linkopts = [ "$(execpath //main/sal:sal_implib)", ... , "shell32.lib" ], |
| ) |
| |
| # 5. Re-export this module's own implib for downstream consumers (see §5) |
| filegroup(name = "<mod>_implib", srcs = [":<mod>"], output_group = "interface_library") |
| |
| # 6. Resources / config, if any (see §6) |
| rsc_res(name = "<mod>_res", ...) |
| filegroup(name = "uiconfig_files", srcs = glob(["uiconfig/**/*.xml"]), visibility = ["//main/postprocess:__pkg__"]) |
| exports_files(glob(["**/*.component"])) |
| ``` |
| |
| **Driving the dep graph:** parse the module's legacy `build.lst` / |
| `makefile.mk` to derive `deps`. `SLOFILES`/`OBJFILES` lists tell you exactly |
| which sources belong in the library — prefer matching them over a blind |
| `glob`, and `exclude` the ones a glob over-collects (see `main/sal` rtl: it |
| excludes `gen_makefile.cxx`, `alloc_fini.cxx`, `strtmpl.c` because they are |
| not in `SLOFILES`). |
| |
| --- |
| |
| ## 2. Defines & copts conventions |
| |
| - Hoist into module-local `_DEFINES` and `_COPTS` lists at the top of the file |
| so every target in the module shares them. Don't inline per-target. |
| - The cross-cutting flags (`/Zc:wchar_t-`, `snprintf=_snprintf`, |
| `stlport` dep, `Z_PREFIX`, `CURL_STATICLIB`, …) are catalogued in |
| [CLAUDE.md](CLAUDE.md) "Cross-cutting compiler flags & defines". **Check that |
| list before building any new module** — most "mysterious" link/ABI failures |
| are a missing entry from it. |
| - `_HAS_ITERATOR_DEBUGGING=0` is set **globally** in the toolchain — do **not** |
| re-add it per module (redundant; see CLAUDE.md). |
| - Use a `_SLO_DEFINES = _DEFINES + ["SHAREDLIB", "_DLL_"]` derived list when a |
| module builds both shared- and static-lib object sets (see `main/sal`). |
| |
| --- |
| |
| ## 3. Idioms to copy |
| |
| ### Shield (`_inc`) libraries — beat header-shadowing by name |
| Many modules have identically-named headers (`helpid.hrc`, `*sh.hrc`). Expose a |
| module's own `inc/` and `source/inc/` as **separate** `cc_library` targets with |
| `strip_include_prefix`, listed **first** in `deps`, so they resolve before any |
| transitively-added same-named header. Use `textual_hdrs` (not `hdrs`) for |
| `.hrc`/`.tab`/`#include`d data files so Bazel doesn't try to compile-check them. |
| Reference: [main/basctl/BUILD.bazel](main/basctl/BUILD.bazel) `basctl_inc` / |
| `basctl_source_inc`, and the cui/basctl milestones in `MEMORY.md`. |
| |
| ### Per-subdir `/I` copts for flat sibling includes |
| Source that includes a sibling header by bare name (`<baside2.hrc>`, |
| `"precompiled_sal.hxx"`) needs that exact directory on the include path. Add a |
| `/Imain/<mod>/<subdir>` copt per such subdir (basctl adds `/Imain/basctl/source/basicide`). |
| |
| ### `_headers` libraries are the public contract |
| A module exposes its API to others as `//main/<mod>:<mod>_headers` (headers + |
| `strip_include_prefix` + `includes`), never by depending on its `cc_binary`. |
| Downstream `deps` carry **only** `_headers`; linking is via implibs (§5). |
| |
| ### Generated headers get a wrapper `cc_library` |
| SDI/IDL/MIDL output is wrapped: `hdrs = [":<gen>"]`, `includes = |
| ["<gen>_inc"]` (or `includes = ["."]` / `[".."]` for generated-header dirs). |
| Reference: `basslots_headers`, and the winaccessibility/writerfilter milestones. |
| |
| ### `textual_hdrs` for included data tables |
| `.tab` files (`main/sal` textenc), `hash.cxx` (sdext pdfimport), private |
| `types.idl`-derived headers — anything `#include`d rather than compiled — goes |
| in `textual_hdrs`. |
| |
| --- |
| |
| ## 4. Naming conventions |
| |
| | Target | Pattern | Example | |
| |---|---|---| |
| | Public header lib | `<mod>_headers` | `sal_headers`, `tools_headers` | |
| | Private include shield | `<mod>_inc`, `<mod>_source_inc` | `basctl_inc` | |
| | Resource-constant shield | `<mod>_source_hrc`, `<mod>_hrc` | `basctl_source_hrc`, `svl_hrc` | |
| | The DLL | `<mod>` (bare) | `basctl`, `sal` | |
| | Re-exported implib | `<mod>_implib` (or `<linkname>_implib`) | `basctl_implib`, `comphelpMSC_implib` | |
| | SDI slot gen | `<name>slots_sdi` + `<name>slots_headers` | `basslots_sdi` | |
| | RSC resource | `<mod>_res` | `basctl_res` | |
| | DEF file | `util/<linkname>.def` | `util/basctl.def` | |
| |
| The **implib name often differs from the module name** because it matches the |
| upstream link-name, not the directory (`comphelpMSC_implib`, `tl_implib`, |
| `utl_implib`, `svxcore_implib`). When in doubt, check the consumer's |
| `additional_linker_inputs` list. |
| |
| --- |
| |
| ## 5. The DLL link triad (DEF + implib in + implib out) |
| |
| A first-party DLL has three linking concerns that must stay consistent: |
| |
| 1. **Exports** — convert the upstream `<name>.map` GNU-ld script to a Windows |
| `util/<name>.def` and set `win_def_file`. UNO component DLLs export exactly |
| `component_getImplementationEnvironment`, `component_getFactory`, |
| `component_canUnload`. If there is no `.map`, hand-write a minimal `.def` |
| (see starmath milestone: `sm.def`/`smd.def`, no `canUnload`). |
| 2. **Consuming others' implibs** — list each dependency's implib in **both** |
| `additional_linker_inputs` (so Bazel stages the file) **and** `linkopts` as |
| `$(execpath //main/<dep>:<dep>_implib)`. Both are required; the |
| `additional_linker_inputs` entry alone won't put it on the link line. |
| Plain system libs (`shell32.lib`, `user32.lib`, `ws2_32.lib`) go straight |
| in `linkopts`. |
| 3. **Exposing your own implib** — end the file with |
| `filegroup(name="<mod>_implib", srcs=[":<mod>"], output_group="interface_library")` |
| so downstream modules can link you. |
| |
| --- |
| |
| ## 6. Resources, config, components |
| |
| - **RSC** (`rsc_res`): `srcs` = `.src`, `hdrs` = `.hrc` (own + cross-module |
| `*_inc_hrc` filegroups), `includes` = every `.hrc` search dir, and |
| **`images_root = "main/default_images"`** — this exact value is mandatory for |
| any module with `Bitmap { File = "x.png" }` (see CLAUDE.md; a module-specific |
| `images_root` silently empties every `BitmapEx(ResId)`). |
| - **uiconfig**: expose as a `filegroup` visible to `//main/postprocess` only; |
| it is staged as a **folder tree**, never a zip. |
| - **`.component`** files: `exports_files(glob(["**/*.component"]))` so the |
| postprocess/services.rdb pipeline can read them. |
| |
| --- |
| |
| ## 7. `.bzl` custom rules |
| |
| When a build step has no `cc_*`/`genrule` fit, it gets a Starlark rule under |
| `build/rules/<thing>.bzl` (existing: `idl_pipeline`, `rsc_pipeline`, |
| `sdi_target`, `images`, `scp2`, `localedata_pipeline`, `gtest_test`, …). |
| Conventions, by example: |
| |
| - Prefer `ctx.actions.run` with explicit tool inputs over `run_shell`; use |
| `ctx.actions.symlink` (pure Bazel) for staging, not a shell `cp`. |
| - Tools that link `/MD` need their CRT DLLs + **external** `.manifest` staged |
| beside the EXE (the `svidl_bundle` pattern — replicated in `gtest_test.bzl`; |
| miss it and you get R6034). See the test-infra and IDL milestones. |
| - A rule that emits headers must advertise the include dir via an `includes` |
| provider/attr so the wrapper `cc_library` can surface it. |
| - Keep the rule's migration rationale in its module `readme.md` and a one-line |
| `MEMORY.md` pointer — that is where future-you will look. |
| |
| --- |
| |
| ## 8. Anti-patterns (do NOT do) |
| |
| - ❌ Put a BUILD file in `prj/`. First-party modules need `glob()` over sources |
| → BUILD lives at `main/<mod>/BUILD.bazel`. |
| - ❌ Depend on another module's `cc_binary` to get its headers. Use `_headers`. |
| - ❌ Module-specific `images_root`. Always `"main/default_images"`. |
| - ❌ Re-add `_HAS_ITERATOR_DEBUGGING=0` per module (global already). |
| - ❌ Put an implib only in `additional_linker_inputs` (also needs `linkopts`). |
| - ❌ Stage uiconfig as a zip (must be a folder tree). |
| - ❌ Manipulate the Bazel cache directly. Use |
| `bazel mod deps --lockfile_mode=refresh` or ask the user. |
| - ❌ Modify C++/Java source to make a build pass. Source is out of scope; fix |
| the build description instead. (If a source bug is genuinely blocking, raise |
| it — see the latent-UAF precedent — don't silently patch.) |
| |
| --- |
| |
| ## 9. After a module builds |
| |
| Per [CLAUDE.md](CLAUDE.md) workflow: **let the user build** (don't run the build |
| yourself), add a `readme.md` migration summary to the module, add a one-line |
| `MEMORY.md` milestone pointer, and **only then** update the frontier in |
| CLAUDE.md. The `MEMORY.md` entry is what makes the next similar module fast. |