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 “Goal”).
For operating rules (build commands, workflow, guardrails) see 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 (a full first-party DLL with SDI, RSC, shielded includes, implib export). Foundational library example: main/sal/BUILD.bazel.
A first-party module DLL is built in this order. Copy the shape, not the names.
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).
_DEFINES and _COPTS lists at the top of the file so every target in the module shares them. Don't inline per-target./Zc:wchar_t-, snprintf=_snprintf, stlport dep, Z_PREFIX, CURL_STATICLIB, …) are catalogued in 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)._SLO_DEFINES = _DEFINES + ["SHAREDLIB", "_DLL_"] derived list when a module builds both shared- and static-lib object sets (see main/sal)._inc) libraries — beat header-shadowing by nameMany 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/#included data files so Bazel doesn’t try to compile-check them. Reference: main/basctl/BUILD.bazel basctl_inc / basctl_source_inc, and the cui/basctl milestones in MEMORY.md.
/I copts for flat sibling includesSource 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 contractA 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).
cc_librarySDI/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 #included rather than compiled — goes in textual_hdrs.
| 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.
A first-party DLL has three linking concerns that must stay consistent:
<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).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.filegroup(name="<mod>_implib", srcs=[":<mod>"], output_group="interface_library") so downstream modules can link you.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)).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..bzl custom rulesWhen 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:
ctx.actions.run with explicit tool inputs over run_shell; use ctx.actions.symlink (pure Bazel) for staging, not a shell cp./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.includes provider/attr so the wrapper cc_library can surface it.readme.md and a one-line MEMORY.md pointer — that is where future-you will look.prj/. First-party modules need glob() over sources → BUILD lives at main/<mod>/BUILD.bazel.cc_binary to get its headers. Use _headers.images_root. Always "main/default_images"._HAS_ITERATOR_DEBUGGING=0 per module (global already).additional_linker_inputs (also needs linkopts).bazel mod deps --lockfile_mode=refresh or ask the user.Per 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.