rules_cc_autoconf

This module provides a rule to mimic GNU Autoconf functionality, allowing you to generate config.h files by running compilation checks against a cc_toolchain.

Setup

bazel_dep(name = "rules_cc_autoconf", version = "{version}")

Use the version that matches the release you depend on in your root MODULE.bazel.

Overview

At a high level, Autoconf is a system for asking the C/C++ toolchain questions like “does this header exist?”, “is this function available?” or “what is the size of this type?” and then encoding the answers into a generated config.h. Projects can then use preprocessor conditionals around those defines instead of hard‑coding assumptions about the platform.

rules_cc_autoconf brings this style of configuration into Bazel. Rather than running ./configure as an opaque shell script, you describe checks in Starlark with the checks struct from @rules_cc_autoconf//autoconf:checks.bzl (singular macros such as AC_CHECK_HEADER) and, when you need GNU-style plural helpers, the macros struct from the same file. You can also load("@rules_cc_autoconf//autoconf:defs.bzl", ...) to import the common rules and symbols in one place. Run those checks with the autoconf rule. The rule talks directly to the Bazel C/C++ toolchain, so it respects things like platforms, features, remote execution and sandboxing.

Running traditional Autoconf inside Bazel (for example with a genrule that invokes ./configure) tends to be brittle: it often assumes a POSIX shell, mutates the source tree, and performs many unrelated checks in one big step. That makes caching and debugging harder, and is awkward on non‑Unix platforms or in remote execution environments.

In contrast, this rule generates a small JSON config for each autoconf target and runs a dedicated C++ tool that executes only the checks you requested. That gives you:

  • granular actions that cache well and are easy to reason about
  • hermetic behaviour (no in‑tree config.status, config.log, etc.)
  • configuration that is driven by Bazel’s toolchain and platform selection

The result is the same style of config.h you would expect from GNU Autoconf, but integrated cleanly into Bazel builds.

  • autoconf_hdr — Renders config.h (or other templates) from check results.
  • autoconf_srcs — Generates wrapped C/C++ sources and provides CcInfo so libraries and binaries compile against autoconf output.
  • autoconf_linkopts — Resolves subst values (for example from AC_SEARCH_LIBS) into linker inputs: on GCC/Clang, flags are passed via a response file; on msvc-cl / clang-cl, a small generated translation unit is compiled so library dependencies are applied in an MSVC-compatible way. See the Stardoc page autoconf_linkopts.
  • autoconf_toolchain — Supplies default check results for the workspace toolchain; optional override point. See autoconf_toolchain.

Why not just run Autoconf?

GNU Autoconf is excellent at probing platform capabilities and generating Make-based build trees. However, dropping ./configure into a Bazel build has a few downsides:

  • Non-hermetic: traditional Autoconf expects to mutate the source tree (creating config.status, config.log, generated headers, etc.), which conflicts with Bazel’s sandboxing and caching model.
  • Opaque, coarse-grained actions: a single configure step can do dozens of unrelated checks at once, making cache behaviour and debugging harder.
  • Environment-sensitive: running shell scripts and toolchain discovery inside a Bazel action is fragile across platforms, remote execution, and containerized builds.

Projects in the Bazel Central Registry often reimplement the build logic in Bazel, but they still miss Autoconf’s convenient config.h generation. rules_cc_autoconf focuses on that gap: it provides autoconf-like checks in a hermetic, deterministic way, driven entirely by Bazel’s C/C++ toolchain configuration.

Rules

autoconf

Functions

autoconf

load("@rules_cc_autoconf//autoconf:autoconf.bzl", "autoconf")

autoconf(*, name, build_settings, cc_deps, checks, conlyopts, copts, cxxopts, default_deps, deps,
         **kwargs)

Run autoconf-like checks and produce results.

This rule resolves the autoconf_toolchain (when registered) to skip redundant checker actions. If a check's cache variable name already has a result in the toolchain or in transitive deps, the existing result file is reused.

Use autoconf_cache instead for targets that feed into autoconf_toolchain to avoid a dependency cycle.

Example:

load("@rules_cc_autoconf//autoconf:autoconf.bzl", "autoconf")
load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

autoconf(
    name = "config",
    checks = [
        checks.AC_CHECK_HEADER("stdio.h"),
        checks.AC_CHECK_HEADER("stdlib.h"),
        checks.AC_CHECK_FUNC("printf"),
    ],
)

The results can then be used by autoconf_hdr or autoconf_srcs to generate headers or wrapped source files.

PARAMETERS

NameDescriptionDefault Value
nameA unique name for this target.none
build_settingsList of checks.AC_BUILD_SETTING(...) entries associating Bazel build setting targets (any rule providing BuildSettingInfo) with autoconf-style defines and substitutions.None
cc_depsLibraries the checks compile against and link, as configure's CPPFLAGS and LIBS carry a project's dependencies: every check sees their include directories and defines, and every link probe links their archives after its own object. Their flags are part of each check's identity, so a check never shares a result between targets with different cc_deps. Prefer cc_deps = ["@gmp//:gmp"] with a real AC_CHECK_LIB over a static AC_DEFINE.None
checksList of JSON-encoded checks from checks (e.g., checks.AC_CHECK_HEADER('stdio.h')).None
conlyoptsFlags for the C compiles of this target's checks only, after copts (as on a cc_library). Not transitive: flags for every check of a build, the gnulib ports included, are --copt.None
coptsFlags for every compile of this target's checks, after the toolchain's and --copt / --cxxopt and before a check's own copts (the order of a cc_library's), so they win over the command line where the last flag counts. Not transitive.None
cxxoptsFlags for the C++ compiles of this target's checks only, after copts (as on a cc_library). Not transitive.None
default_depsautoconf_defaults targets. Their values stand where no result of the name comes through deps; a result overrides a default silently, and two defaults of one name must agree. The defaults a deps entry carries are read as well, and dependents of this target read all of them in turn. This is gnulib's *_H_DEFAULTS pattern (HAVE_X=1, REPLACE_X=0 before the probes may reassign them): list a header's :defaults here where the m4 AC_REQUIREs gl_<HEADER>_H_DEFAULTS.None
depsAdditional autoconf, autoconf_cache, or autoconf_package_info dependencies.None
kwargsStandard Bazel attributes (e.g. visibility, tags).none

autoconf_defaults

Rules

autoconf_defaults

load("@rules_cc_autoconf//autoconf:autoconf_defaults.bzl", "autoconf_defaults")

autoconf_defaults(name, deps, defines, substs, variables)

Default values for autoconf results.

A default is not a check: nothing is probed, no toolchain is resolved, and the values are written at analysis time. A target reads defaults through default_deps, beneath every ordinary result, so a probe of the same name overrides the default silently where a second probe would be a duplicate error. deps rejects defaults targets; they only enter through default_deps.

This is how gnulib's *_H_DEFAULTS macros work: gl_UNISTD_H_DEFAULTS sets HAVE_DUP3=1, REPLACE_DUP2=0 and so on, and the probes that run later reassign only what they find wanting. Port such a macro as one autoconf_defaults target and list it in the default_deps of every target whose m4 AC_REQUIREs it:

load("@rules_cc_autoconf//autoconf:autoconf.bzl", "autoconf")
load("@rules_cc_autoconf//autoconf:autoconf_defaults.bzl", "autoconf_defaults")
load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

# gl_UNISTD_H_DEFAULTS
autoconf_defaults(
    name = "defaults",
    substs = {
        "HAVE_DUP3": "1",
        "REPLACE_DUP2": "0",
    },
)

# gl_FUNC_DUP2: AC_REQUIRE([gl_UNISTD_H_DEFAULTS]), then may set REPLACE_DUP2=1
autoconf(
    name = "dup2",
    checks = [
        checks.AC_SUBST("REPLACE_DUP2", 1, condition = "!gl_cv_func_dup2_works"),
    ],
    default_deps = [":defaults"],
)

Defaults compose: deps lists other autoconf_defaults targets whose values this one carries along (gnulib's _H_REQUIRE_DEFAULTS on top of _H_DEFAULTS), and an autoconf target passes the defaults it reads on to its dependents. Two defaults of one name must agree.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
depsOther autoconf_defaults targets whose values this target carries along.List of labelsoptional[]
definesDefault AC_DEFINE values: define name to value. Decimal integers are stored as numbers, as checks.AC_DEFINE("X", 1) stores them; anything else as text.Dictionary: String -> Stringoptional{}
substsDefault AC_SUBST values: variable name to value ({"HAVE_DUP3": "1"}), stored under the cache name ac_cv_subst_<NAME> as checks.AC_SUBST does. Decimal integers are stored as numbers; anything else as text.Dictionary: String -> Stringoptional{}
variablesDefault shell variables that are neither defines nor substitutions (checks.M4_VARIABLE): name to value, read by requires and condition expressions and by cc_gnulib_conditional_hdrs conditions, e.g. {"GL_GENERATE_ICONV_H": "false"}.Dictionary: String -> Stringoptional{}

autoconf_hdr

Rules

autoconf_hdr

load("@rules_cc_autoconf//autoconf:autoconf_hdr.bzl", "autoconf_hdr")

autoconf_hdr(name, deps, out, default_deps, defaults, inlines, mode, substitutions, template)

Generate configuration headers from results provided by autoconf targets.

This rule takes check results from autoconf targets (via deps) and generates a header file. The template file (if provided) is used to format the output header.

Example:

load("@rules_cc_autoconf//autoconf:autoconf.bzl", "autoconf")
load("@rules_cc_autoconf//autoconf:autoconf_hdr.bzl", "autoconf_hdr")
load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

autoconf(
    name = "config_checks",
    checks = [
        checks.AC_CHECK_HEADER("stdio.h"),
        checks.AC_CHECK_HEADER("stdlib.h"),
        checks.AC_CHECK_FUNC("printf"),
    ],
)

autoconf_hdr(
    name = "config",
    out = "config.h",
    deps = [":config_checks"],
    template = "config.h.in",
)

Valid mode values:

  • "defines" (default): Only process defines (AC_DEFINE, AC_CHECK_*, etc.), not substitution variables (AC_SUBST). This is for config.h files.
  • "subst": Only process substitution variables (AC_SUBST), not defines. This is for subst.h files.
  • "all": Process both defines and substitution variables.

This allows you to run checks once and generate multiple header files from the same results.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
depsList of autoconf targets which provide check results. Results from all deps will be merged together, and duplicate define names will produce an error. If not provided, an empty results file will be created.List of labelsoptional[]
outThe output config file (typically config.h).Label; nonconfigurablerequired
default_depsautoconf_defaults targets whose values stand where no result of the name comes through deps (a result overrides a default silently; two defaults of one name must agree). The defaults the deps carry are read as well.List of labelsoptional[]
defaultsDeprecated and ignored: defaults come through deps and default_deps; the autoconf toolchain no longer carries any. Accepted so existing BUILD files keep loading.BooleanoptionalFalse
inlinesA mapping of strings to files for replace within the content of the given template attribute.

The exact string in the template is replaced with the content of the associated file. Any @VAR@ style placeholders in the inline file content will be replaced with their corresponding define values (package info, check results, etc.) after insertion.
Dictionary: String -> Labeloptional{}
modeProcessing mode that determines what should be replaced within the file.Stringoptional"defines"
substitutionsA mapping of exact strings to replacement values.

Each entry performs an exact text replacement in the template - the key string is replaced with the value string. No special patterns or wrappers are added.

Example:
autoconf_hdr(
    name = "config",
    out = "config.h",
    template = "config.h.in",
    substitutions = {
        "@MY_VERSION@": "1.2.3",
        "@BUILD_TYPE@": "release",
        "PLACEHOLDER_TEXT": "actual_value",
    },
    deps = [":checks"],
)


This would replace the exact string @MY_VERSION@ with 1.2.3, @BUILD_TYPE@ with release, and PLACEHOLDER_TEXT with actual_value.
Dictionary: String -> Stringoptional{}
templateTemplate file (config.h.in) to use as base for generating the header file. The template is used to format the output header, but does not generate any checks.Labelrequired

autoconf_srcs

Rules

autoconf_srcs

load("@rules_cc_autoconf//autoconf:autoconf_srcs.bzl", "autoconf_srcs")

autoconf_srcs(name, deps, srcs, default_deps, defaults, naming, wrap)

Generate wrapper sources that are conditionally enabled by autoconf results.

Typical use case:

load("@rules_cc_autoconf//autoconf:autoconf_srcs.bzl", "autoconf_srcs")
load("@rules_cc_autoconf//autoconf:autoconf.bzl", "autoconf")
load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")
load("@rules_cc//cc:cc_library.bzl", "cc_library")

autoconf(
    name = "config",
    out = "config.h",
    checks = [
        # Feature is present on this platform/toolchain.
        checks.AC_CHECK_HEADER("foo.h", define = "HAVE_FOO"),
    ],
)

autoconf_srcs(
    name = "foo_srcs",
    deps = [":config"],
    srcs = {
        "uses_foo.c": "HAVE_FOO",
    },
)

cc_library(
    name = "foo",
    srcs = [":foo_srcs"],
    hdrs = [":config.h"],
)

In this setup autoconf_srcs reads the results from :config (or multiple targets via deps) and generates a wrapped copy of uses_foo.c whose contents are effectively:

#define HAVE_FOO 1      // when the check for HAVE_FOO succeeds
/* or: #undef HAVE_FOO  // when the check fails */
#ifdef HAVE_FOO
/* original uses_foo.c contents ... */
#endif

This is useful when you have platform-specific or optional sources that should only be compiled when a particular autoconf check passes, without having to manually maintain #ifdef guards in every source file.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
depsList of autoconf targets which provide defines. Results from all deps will be merged together, and duplicate define names will produce an error.List of labelsrequired
srcsA mapping of source file to define required to compile the file.Dictionary: Label -> Stringrequired
default_depsautoconf_defaults targets whose values stand where no result of the name comes through deps (a result overrides a default silently; two defaults of one name must agree). The defaults the deps carry are read as well.List of labelsoptional[]
defaultsDeprecated and ignored: defaults come through deps and default_deps; the autoconf toolchain no longer carries any. Accepted so existing BUILD files keep loading.BooleanoptionalFalse
namingHow to name generated sources: package_relative keeps the original package-relative path, ac_suffix inserts .ac. before the final suffix (e.g. foo.cc -> foo.ac.cc), and per_target prefixes outputs with {ctx.label.name} to namespace them per rule. Under wrap = "include" the output always carries the .ac infix as well (foo.c -> foo.ac.c, per_target: {ctx.label.name}/foo.ac.c): a wrapper named like a root-level source would resolve its own #include "foo.c" to itself.Stringoptional"package_relative"
wrapHow a generated file stands for its source. copy (the default) carries a copy of the source's contents. include carries only #include "<path of the source>", so the source's own quoted includes (#include "sibling.h") still resolve in its directory and __FILE__ and diagnostics name the source; the sources must then reach the compiler, which they do when the consumer lists this target in deps as well as srcs (the target provides them as headers of a CcInfo).Stringoptional"copy"

autoconf_linkopts

Resolve AC_SEARCH_LIBS / AC_SUBST results into linker flags at build time.

This rule reads the JSON result files from autoconf subst variables and produces a linker response file containing only the non-empty flags. The response file is returned via CcInfo so that cc_binary/cc_library targets can depend on it to get the correct platform-specific linkopts.

On GCC/Clang the flags are passed via a @response-file in user_link_flags. On MSVC/clang-cl (where nested @file is unsupported), a .c file with #pragma comment(lib, ...) directives is compiled and the resulting object is linked in, achieving the same effect through MSVC's native mechanism. That object's one variable, the anchor that keeps it alive, is a pick-any COMDAT (__declspec(selectany)), so a binary linking several autoconf_linkopts targets (a module's root and sub-configure ones) does not define it twice.

Example:

load("@rules_cc_autoconf//autoconf:autoconf_linkopts.bzl", "autoconf_linkopts")

autoconf_linkopts(
    name = "linkopts",
    vars = [
        "LIBPTHREAD",
        "FDATASYNC_LIB",
        "CLOCK_TIME_LIB",
    ],
    deps = [":autoconf"],
)

cc_binary(
    name = "my_binary",
    srcs = ["main.c"],
    deps = [":gnulib", ":linkopts"],
)

On modern Linux (glibc 2.34+) where pthread/rt functions are in libc, AC_SEARCH_LIBS produces empty values and no extra -l flags are added. On older systems, the appropriate flags (-lpthread, -lrt) are added automatically.

Rules

autoconf_linkopts

load("@rules_cc_autoconf//autoconf:autoconf_linkopts.bzl", "autoconf_linkopts")

autoconf_linkopts(name, deps, default_deps, defaults, vars)

Resolve AC_SEARCH_LIBS / AC_SUBST results into linker flags.

Reads subst result JSON files from autoconf deps and produces a linker response file. Returns CcInfo with the flags, so consumers can simply add this target to their deps list.

On GCC/Clang the flags are delivered via a @response-file. On MSVC/clang-cl a .c file with #pragma comment(lib, ...) directives is compiled instead, since MSVC's link.exe does not support nested response files.

Variables not found in the deps are silently ignored (they may be provided by a different module or not needed on this platform).

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
depsList of autoconf targets providing check results.List of labelsrequired
default_depsautoconf_defaults targets whose values stand where no result of the name comes through deps (a result overrides a default silently; two defaults of one name must agree). The defaults the deps carry are read as well.List of labelsoptional[]
defaultsDeprecated and ignored: defaults come through deps and default_deps; the autoconf toolchain no longer carries any. Accepted so existing BUILD files keep loading.BooleanoptionalFalse
varsList of AC_SUBST variable names to resolve (e.g., ["LIBPTHREAD", "FDATASYNC_LIB"]). Each variable is looked up in the subst results from deps. Non-empty values (like "-lpthread") are added to linkopts; empty values are skipped.List of stringsrequired

autoconf_toolchain

Provides default autoconf checks that can be overridden by targets.

Rules

Functions

autoconf_toolchain

load("@rules_cc_autoconf//autoconf:autoconf_toolchain.bzl", "autoconf_toolchain")

autoconf_toolchain(name, cache_deps)

Define an autoconf toolchain that shares check results across targets.

The toolchain carries one thing: the content cache of its cache_deps. When an autoconf target lists a check whose implementation already has a result there, the checker action is skipped and the existing result file is reused. Nothing else crosses the toolchain boundary: not the results as defines or substs, and not defaults. Defaults are autoconf_defaults targets that a target reads through its own default_deps (see autoconf_defaults); the toolchain neither carries nor injects any.

Targets listed in cache_deps must use autoconf_cache (not autoconf) to avoid a dependency cycle.

Example:

load("@rules_cc_autoconf//autoconf:defs.bzl", "autoconf_cache", "autoconf_toolchain")
load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

autoconf_cache(
    name = "common_checks",
    checks = [
        checks.AC_CHECK_HEADER("stdio.h"),
        checks.AC_CHECK_FUNC("strerror"),
    ],
)

autoconf_toolchain(
    name = "my_toolchain_impl",
    cache_deps = [":common_checks"],
)

toolchain(
    name = "my_toolchain",
    toolchain = ":my_toolchain_impl",
    toolchain_type = "@rules_cc_autoconf//autoconf:toolchain_type",
)

Caching behavior

Content-key deduplication

Every check is fingerprinted by a content key derived from its implementation fields: type, code, language, define_value, includes, libraries, requires, condition, compile_defines, and similar. Consumer metadata (name, define, subst, unquote) is excluded, so two checks with different consumer names but identical implementations share one content key and produce a single checker action and result file.

When a new check is processed, its content key is looked up in order:

  1. Transitive deps' content_cache
  2. Toolchain content cache (cache_deps)
  3. Current target's local content_cache

If a hit is found at any level the existing result file is reused and no new checker action is declared. Identical checks within the same target are idempotent (silently skipped on second occurrence).

Conflict detection

Three layers, from strictest to most relaxed:

  • Within one target -- duplicate define or subst symbol names in the same checks list always fail, even if the content keys match. This prevents accidental duplication in a single target.
  • Local vs dependencies / deps vs deps -- the same symbol appearing with a different result file is an error, unless both files share the same content key (a benign duplicate from identical implementations).
  • Cache variable names (cache_results) have no cross-dep conflict detection; content-based dedup is the identity signal.

cache_deps on the toolchain itself is intentionally exempt from define/subst conflict detection: it contributes only to the content cache and is meant to absorb broad sweeps of cache providers (e.g., every gnulib m4 module) that legitimately publish overlapping symbols from different files. Per-target deps still enforce conflicts.

Resolving conflicts

The typical fix for "Define 'X' is defined both locally and in dependencies with different result files":

  • Remove the local check and depend on the target that already provides it.
  • Or factor the shared check into its own autoconf / autoconf_cache target and depend on it from both consumers.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
cache_depsTargets whose check results are available for action deduplication in autoconf targets. Only the content cache is exposed downstream; define/subst conflicts among these targets are NOT detected, so a broad sweep of cache providers with overlapping symbols (e.g., every gnulib m4 module) can be passed safely.List of labelsoptional[]

autoconf_cache

load("@rules_cc_autoconf//autoconf:autoconf_toolchain.bzl", "autoconf_cache")

autoconf_cache(*, name, build_settings, checks, deps, **kwargs)

Run autoconf-like checks without resolving the autoconf toolchain.

Identical to autoconf except that it does not resolve the autoconf_toolchain. Use this rule for targets that are listed as cache_deps of an autoconf_toolchain -- using the regular autoconf rule in that position would create a dependency cycle.

Dep-level caching still applies: if a check's cache variable name already has a result in transitive deps, the action is skipped.

PARAMETERS

NameDescriptionDefault Value
nameA unique name for this target.none
build_settingsList of checks.AC_BUILD_SETTING(...) entries associating Bazel build setting targets (any rule providing BuildSettingInfo) with autoconf-style defines and substitutions.None
checksList of JSON-encoded checks from checks (e.g., checks.AC_CHECK_HEADER('stdio.h')).None
depsAdditional autoconf, autoconf_cache, or autoconf_package_info dependencies.None
kwargsStandard Bazel attributes (e.g. visibility, tags).none

autoconf_package_info

Rules

autoconf_package_info

load("@rules_cc_autoconf//autoconf:autoconf_package_info.bzl", "autoconf_package_info")

autoconf_package_info(name, aliases, module_bazel, package_bugreport, package_name, package_tarname,
                      package_url, package_version, strip_bcr_version)

A rule providing package metadata, equivalent to GNU autoconf's AC_INIT.

It emits the PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_STRING, PACKAGE_TARNAME, PACKAGE_BUGREPORT, and PACKAGE_URL defines, either from explicit attributes or by parsing a MODULE.bazel file.

This rule is most useful when paired with the autoconf rule.

Example:

load("@rules_cc_autoconf//autoconf:autoconf.bzl", "autoconf")
load("@rules_cc_autoconf//autoconf:autoconf_package_info.bzl", "autoconf_package_info")

autoconf_package_info(
    name = "package",
    module_bazel = "//:MODULE.bazel",
)

autoconf(
    name = "config_h",
    out = "config.h",
    checks = [
        # ...
    ],
    deps = [
        ":package",
    ],
)

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
aliasesAdditional variables to define that are mirrored by the provided value variable.Dictionary: String -> Stringoptional{}
module_bazelA MODULE.bazel file to parse module information from. Mutually exclusive with package_name and package_version.LabeloptionalNone
package_bugreportThe package bug report email/URL. Used to populate PACKAGE_BUGREPORT define.Stringoptional""
package_nameThe package name. Must be provided together with package_version if module_bazel is not provided.Stringoptional""
package_tarnameExactly tarname, possibly generated from package.Stringoptional""
package_urlThe package home page URL. Used to populate PACKAGE_URL define.Stringoptional""
package_versionThe package version. Must be provided together with package_name if module_bazel is not provided.Stringoptional""
strip_bcr_versionWhether or not to strip .bcr.* suffixes from module_bazel parsed versions.BooleanoptionalFalse

checks

This module provides Bazel macros that mirror GNU Autoconf's behavior.

Cache Variables, Defines, and Subst Values

Checks (AC_CHECK_FUNC, AC_CHECK_HEADER, etc.) create cache variables by default, following autoconf's naming convention:

  • AC_CHECK_HEADER("foo.h") -> cache variable "ac_cv_header_foo_h"
  • AC_CHECK_FUNC("foo") -> cache variable "ac_cv_func_foo"
  • AC_CHECK_DECL("foo") -> cache variable "ac_cv_have_decl_foo"

Defines and subst values are created explicitly using AC_DEFINE and AC_SUBST.

Condition Resolution

When AC_DEFINE or AC_SUBST use a condition parameter that references a cache variable (e.g., condition="ac_cv_header_foo_h"), the condition is evaluated as a truthy check:

  • Condition is true if:

    • The cache variable exists (check was run)
    • The check succeeded (success = true)
    • The value is truthy (non-empty string, not "0")
  • Condition is false if:

    • The cache variable doesn't exist (check wasn't run)
    • The check failed (success = false)
    • The value is falsy (empty string or "0")

If the condition includes a comparison operator (e.g., condition="ac_cv_header_foo_h==1"), it performs value comparison instead of truthy check. A define that produced no value (/* #undef X */ in config.h) compares as 0, as it would in #if X == 0; a substitution whose value is the empty string does not.

Example:

# Check creates cache variable
macros.AC_CHECK_HEADER("alloca.h")
# -> Creates cache variable: "ac_cv_header_alloca_h" with value "1" (if header exists)

# Define with truthy condition
macros.AC_DEFINE(
    "HAVE_ALLOCA_H",
    condition="ac_cv_header_alloca_h",  # Truthy: true if check succeeded and value is truthy
    if_true="1",
    if_false=None,  # Don't define if condition is false
)

# Subst with truthy condition
macros.AC_SUBST(
    "HAVE_ALLOCA_H",
    condition="ac_cv_header_alloca_h",  # Truthy check
    if_true="1",
    if_false="0",  # Always set subst value
)

Default Includes

Default includes used by AC_CHECK_DECL, AC_CHECK_TYPE, etc. when no includes are specified.

GNU Autoconf's AC_INCLUDES_DEFAULT uses #ifdef HAVE_* guards, but that relies on AC_CHECK_HEADERS_ONCE being called earlier to define those macros. In Bazel, each compile test runs independently without access to results from other checks, so we use unconditional includes instead. This matches what would happen on a modern POSIX system where all the standard headers exist.

See: https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.72/autoconf.html#Default-Includes

Header / includes parameter

Checks that need to include headers (AC_CHECK_DECL, AC_CHECK_TYPE, AC_CHECK_SIZEOF, AC_CHECK_MEMBER, etc.) take an includes parameter. AC_CHECK_FUNC does not take includes (standard Autoconf link test only); use GL_CHECK_FUNCS_ANDROID (or other GL_CHECK_FUNCS* in gnulib/macros.bzl) when includes are needed. Both of these forms are supported:

Use include directives as strings, e.g. "#include <stdlib.h>" or "#include <sys/stat.h>", matching Autoconf/gnulib (e.g. gl_CHECK_FUNCS_ANDROID([faccessat], [[#include <unistd.h>]])). The list is joined with newlines to form the prologue of the test program.

The parameter headers is a deprecated alias for includes; prefer includes.

Functions

checks.AC_BUILD_SETTING

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_BUILD_SETTING(*, target, name, define, subst, unquote)

Associate a Bazel build setting target with an autoconf define/subst.

Reads the value of target (any rule providing BuildSettingInfo) at analysis time and renders it as a pre-computed check result -- the same shape AC_DEFINE / AC_SUBST produce -- so it flows through autoconf_hdr / autoconf_srcs like any other check. No compilation is performed.

The returned value is consumed by the autoconf (and autoconf_cache) macro's build_settings parameter, which collects a list of these and forwards them to the underlying rule.

Example:

load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
load("@rules_cc_autoconf//autoconf:autoconf.bzl", "autoconf")
load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

string_flag(name = "package_name", build_setting_default = "myproj")

autoconf(
    name = "config",
    build_settings = [
        checks.AC_BUILD_SETTING(
            target = ":package_name",
            name = "ac_cv_package_name",
            define = "PACKAGE_NAME",
            subst = True,
        ),
    ],
)

PARAMETERS

NameDescriptionDefault Value
targetLabel of a target providing BuildSettingInfo (e.g. a string_flag, bool_flag, or int_flag).none
nameCache variable name (e.g. "ac_cv_package_name").none
defineDefine name to set in the generated header, or True to reuse name. At least one of define or subst is required.None
substSubstitution variable name for @VAR@ replacement, or True to reuse define/name. At least one of define or subst is required.None
unquoteIf True, emit the define with AC_DEFINE_UNQUOTED-style (trailing space) instead of /**/.False

RETURNS

A struct consumed by the autoconf / autoconf_cache macro's build_settings parameter.

checks.AC_CHECK_ALIGNOF

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_CHECK_ALIGNOF(type_name, *, define, includes, language, copts, requires, subst)

Check the alignment of a type.

Original m4 example:

AC_CHECK_ALIGNOF([int])
AC_CHECK_ALIGNOF([double], [[#include <stddef.h>]])

Example:

macros.AC_CHECK_ALIGNOF("int")
macros.AC_CHECK_ALIGNOF("double", includes = ["stddef.h"])

Cross-Compile Warning: This macro is NOT cross-compile friendly. It requires compiling and running code to determine the alignment, which doesn't work when cross-compiling.

PARAMETERS

NameDescriptionDefault Value
type_nameName of the typenone
defineCustom define name (defaults to ALIGNOF_<TYPE>)None
includesOptional list of header names to include (e.g. ["stddef.h"]). Without it the probe compiles with autoconf's default includes (AC_INCLUDES_DEFAULT), as the macro does.None
languageLanguage to use for check ("c" or "cpp")"c"
coptsOptional list of extra compiler flags for this check (e.g., ["-std=c11"]). Appended after the autoconf toolchain's own copts so they take precedence on last-wins conflicts.None
requiresList of requirements that must be met before this check runs. Can be simple define names (e.g., "HAVE_FOO") or value-based requirements (e.g., "REPLACE_FSTAT=1" to require specific value)None
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_CHECK_CXX_COMPILER_FLAG

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_CHECK_CXX_COMPILER_FLAG(flag, define, language, requires, subst)

Check if the C++ compiler supports a specific flag.

Original m4 example:

AC_CHECK_CXX_COMPILER_FLAG([-std=c++17], [CXXFLAGS="$CXXFLAGS -std=c++17"])

Example:

macros.AC_CHECK_CXX_COMPILER_FLAG("-std=c++17")
macros.AC_CHECK_CXX_COMPILER_FLAG("-Wall")

PARAMETERS

NameDescriptionDefault Value
flagCompiler flag to test (e.g., "-Wall", "-std=c++17")none
defineCustom define name (defaults to HAVE_FLAG_<FLAG>)None
languageLanguage to use for check ("c" or "cpp")"cpp"
requiresList of requirements that must be met before this check runs. Can be simple define names (e.g., "HAVE_FOO") or value-based requirements (e.g., "REPLACE_FSTAT=1" to require specific value)None
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_CHECK_C_COMPILER_FLAG

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_CHECK_C_COMPILER_FLAG(flag, define, language, requires, subst)

Check if the C compiler supports a specific flag.

Original m4 example:

AC_CHECK_C_COMPILER_FLAG([-Wall], [CFLAGS="$CFLAGS -Wall"])

Example:

macros.AC_CHECK_C_COMPILER_FLAG("-Wall")
macros.AC_CHECK_C_COMPILER_FLAG("-std=c99")

PARAMETERS

NameDescriptionDefault Value
flagCompiler flag to test (e.g., "-Wall", "-std=c99")none
defineCustom define name (defaults to HAVE_FLAG_<FLAG>)None
languageLanguage to use for check ("c" or "cpp")"c"
requiresList of requirements that must be met before this check runs. Can be simple define names (e.g., "HAVE_FOO") or value-based requirements (e.g., "REPLACE_FSTAT=1" to require specific value)None
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_CHECK_DECL

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_CHECK_DECL(symbol, *, name, define, includes, compile_defines, copts, language, requires,
                     if_true, if_false, subst)

Check if a symbol is declared.

Original m4 example:

AC_CHECK_DECL([NULL], [AC_DEFINE([HAVE_DECL_NULL], [1])], [], [[#include <stddef.h>]])
AC_CHECK_DECL([stdout], [AC_DEFINE([HAVE_DECL_STDOUT], [1])], [], [[#include <stdio.h>]])

Example:

# Cache variable only (no define, no subst)
macros.AC_CHECK_DECL("NULL", includes = ["stddef.h"])
# -> Creates cache variable: "ac_cv_have_decl_NULL"

# Cache variable + define (explicit name)
macros.AC_CHECK_DECL("stdout", includes = ["stdio.h"], define = "HAVE_DECL_STDOUT")
# -> Creates cache variable: "ac_cv_have_decl_stdout"
# -> Creates define: "HAVE_DECL_STDOUT" in config.h

Note: This is different from AC_CHECK_SYMBOL - it checks if something is declared (not just #defined).

When no includes are specified, the standard default includes
are used (AC_INCLUDES_DEFAULT from GNU Autoconf).

Use header names like `"stdlib.h"` (not `"#include <stdlib.h>"`).

PARAMETERS

NameDescriptionDefault Value
symbolName of the symbol to checknone
nameCache variable name (defaults to ac_cv_have_decl_<SYMBOL> following autoconf convention)None
defineDefine behavior: - None (default): No define is created, only cache variable - True: Create define using the cache variable name (name) - "HAVE_FOO": Create define with explicit name HAVE_FOO (implies define=True)None
includesOptional list of header names to include (e.g. ["stdlib.h"]). If not specified and headers is not specified, uses AC_INCLUDES_DEFAULT.None
compile_definesOptional list of preprocessor define names from previous checks to add before includes (e.g., ["_GNU_SOURCE", "_DARWIN_C_SOURCE"]). Each string must match the define name of a previous check. The values from those checks will be used automatically.None
coptsOptional list of extra compiler flags for this check (e.g., ["-std=c11"]). Appended after the autoconf toolchain's own copts so they take precedence on last-wins conflicts.None
languageLanguage to use for check ("c" or "cpp")"c"
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
if_trueValue to use when check succeeds (currently not used for this check type).None
if_falseValue to use when check fails (currently not used for this check type).None
substSubst behavior: - None (default): No subst is created, only cache variable - True: Create subst using the cache variable name (name) - "HAVE_FOO": Create subst with explicit name HAVE_FOO (implies subst=True)None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_CHECK_FUNC

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_CHECK_FUNC(function, name, *, define, code, language, compile_defines, copts, linkopts,
                     requires, subst)

Check for a function.

Original m4 example:

AC_CHECK_FUNC([malloc])
AC_CHECK_FUNC([mmap], [AC_DEFINE([HAVE_MMAP_FEATURE], [1])])

The default code is a list of two programs, tried in order. The first is GNU autoconf's (utils.AC_LANG_FUNC_LINK_TRY): declare the function with a dummy prototype, call it, and link against the default libraries. The second runs only if that fails and only under _MSC_VER: it includes AC_INCLUDES_DEFAULT and takes the function's address, because the UCRT implements the printf and scanf families as header-inline functions with no linker symbol. GNU autoconf itself reports those absent on MSVC; this check reports them present, which is what a HAVE_<FUNCTION> consumer needs. Results on every other compiler are exactly GNU's. Passing code replaces the list: a check that wants GNU's literal answer on MSVC passes code = utils.AC_LANG_FUNC_LINK_TRY(function) alone. gnulib ports verified against GNU autoconf do so for the printf family, because gnulib documents that answer as MSVC 14's state and builds its replacement logic on it:

  • https://learn.microsoft.com/en-us/cpp/porting/visual-cpp-change-history-2003-2015#stdioh-and-conioh
  • https://github.com/coreutils/gnulib/blob/1039a5f2cee3cda1c11f64a5eb3a15b2e87cd2f0/doc/posix-functions/snprintf.texi#L15-L16
  • https://github.com/coreutils/gnulib/blob/1039a5f2cee3cda1c11f64a5eb3a15b2e87cd2f0/m4/snprintf.m4#L45-L62

Example:

# Cache variable only (no define, no subst)
macros.AC_CHECK_FUNC("malloc")
# -> Creates cache variable: "ac_cv_func_malloc"

# Cache variable + define (explicit name)
macros.AC_CHECK_FUNC("mmap", define = "HAVE_MMAP")
# -> Creates cache variable: "ac_cv_func_mmap"
# -> Creates define: "HAVE_MMAP" in config.h

# Cache variable + define (using cache var name)
macros.AC_CHECK_FUNC("mmap", define = True)
# -> Creates cache variable: "ac_cv_func_mmap"
# -> Creates define: "ac_cv_func_mmap" in config.h

# Cache variable + subst
macros.AC_CHECK_FUNC("_Exit", subst = True)
# -> Creates cache variable: "ac_cv_func__Exit"
# -> Creates subst: "ac_cv_func__Exit" in subst.h

PARAMETERS

NameDescriptionDefault Value
functionName of the function (e.g., "printf")none
nameCache variable name (defaults to ac_cv_func_<FUNCTION> following autoconf convention)None
defineDefine behavior: - None (default): No define is created, only cache variable - True: Create define using the cache variable name (name) - "HAVE_FOO": Create define with explicit name HAVE_FOO (implies define=True)None
codeCustom program to compile and link, or a list of programs tried in order until one links (optional). Replaces the default list described above.None
languageLanguage to use for check ("c" or "cpp")"c"
compile_definesOptional list of preprocessor define names from previous checks to add before includes (e.g., ["_GNU_SOURCE", "_DARWIN_C_SOURCE"]). Each string must match the define name of a previous check. The values from those checks will be used automatically.None
coptsOptional list of extra compiler flags for this check (e.g., ["-std=c11"]). Appended after the autoconf toolchain's own copts so they take precedence on last-wins conflicts.None
linkoptsOptional list of extra linker flags for this check (e.g., ["-pthread"]). Appended after the toolchain's link flags.None
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
substSubst behavior: - None (default): No subst is created, only cache variable - True: Create subst using the cache variable name (name) - "HAVE_FOO": Create subst with explicit name HAVE_FOO (implies subst=True)None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_CHECK_HEADER

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_CHECK_HEADER(header, name, define, includes, language, compile_defines, copts, requires,
                       subst)

Check for a header file.

Original m4 example:

AC_CHECK_HEADER([stdio.h])
AC_CHECK_HEADER([pthread.h], [AC_CHECK_FUNC([pthread_create])])

Example:

# Cache variable only (no define, no subst)
macros.AC_CHECK_HEADER("stdio.h")
# -> Creates cache variable: "ac_cv_header_stdio_h"

# Cache variable + define (explicit name)
macros.AC_CHECK_HEADER("pthread.h", define = "HAVE_PTHREAD_H")
# -> Creates cache variable: "ac_cv_header_pthread_h"
# -> Creates define: "HAVE_PTHREAD_H" in config.h

# Cache variable + define (using cache var name)
macros.AC_CHECK_HEADER("pthread.h", define = True)
# -> Creates cache variable: "ac_cv_header_pthread_h"
# -> Creates define: "ac_cv_header_pthread_h" in config.h

PARAMETERS

NameDescriptionDefault Value
headerName of the header file (e.g., "stdio.h")none
nameCache variable name (defaults to ac_cv_header_<HEADER> following autoconf convention)None
defineDefine behavior: - None (default): No define is created, only cache variable - True: Create define using the cache variable name (name) - "HAVE_FOO": Create define with explicit name HAVE_FOO (implies define=True)None
includesOptional list of headers to include.None
languageLanguage to use for check ("c" or "cpp")"c"
compile_definesOptional list of preprocessor define names from previous checks to add before includes (e.g., ["_GNU_SOURCE", "_DARWIN_C_SOURCE"]). Each string must match the define name of a previous check. The values from those checks will be used automatically.None
coptsOptional list of extra compiler flags for this check (e.g., ["-std=c11", "-D_POSIX_C_SOURCE=200809L"]). Appended after the autoconf toolchain's own copts so they take precedence on last-wins conflicts.None
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
substSubst behavior: - None (default): No subst is created, only cache variable - True: Create subst using the cache variable name (name) - "HAVE_FOO": Create subst with explicit name HAVE_FOO (implies subst=True)None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_CHECK_LIB

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_CHECK_LIB(library, function, *, name, define, code, language, copts, linkopts, requires,
                    if_true, if_false, subst)

Check for a function in a library.

Original m4 example:

AC_CHECK_LIB([m], [cos])
AC_CHECK_LIB([pthread], [pthread_create])

Example:

macros.AC_CHECK_LIB("m", "cos")
macros.AC_CHECK_LIB("pthread", "pthread_create")

Note: This checks if the specified function is available in the given library. It attempts to link against -l<library> to verify the library provides the function.

GNU Autoconf treats `function = "main"` specially. The generated program
supplies its own `main`, so this form tests whether the complete link
command accepts `-l<library>`; it does not require the library to export
a symbol named `main`.

PARAMETERS

NameDescriptionDefault Value
libraryLibrary name without the -l prefix (e.g., "m", "pthread")none
functionFunction name to check for in the library (e.g., "cos", "pthread_create")none
nameThe name of the cache variable. Defaults to ac_cv_lib_library_functionNone
defineCustom define name (defaults to HAVE_LIB<LIBRARY>)None
codeCustom code to compile and link (optional)None
languageLanguage to use for check ("c" or "cpp")"c"
coptsOptional list of extra compiler flags for this check (e.g., ["-std=c11"]). Appended after the autoconf toolchain's own copts so they take precedence on last-wins conflicts.None
linkoptsOptional list of extra linker flags for this check (e.g., ["-pthread"]). Appended after the toolchain's link flags AND after the -l<library> argument.None
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
if_trueValue to use when check succeeds (currently not used for this check type).None
if_falseValue to use when check fails (currently not used for this check type).None
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_CHECK_MEMBER

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_CHECK_MEMBER(aggregate_member, *, name, define, includes, language, compile_defines,
                       copts, requires, if_true, if_false, subst)

Check if a struct or union has a member.

Original m4 example:

AC_CHECK_MEMBER([struct stat.st_rdev], [AC_DEFINE([HAVE_STRUCT_STAT_ST_RDEV], [1])], [], [[#include <sys/stat.h>]])
AC_CHECK_MEMBER([struct tm.tm_zone], [AC_DEFINE([HAVE_STRUCT_TM_TM_ZONE], [1])], [], [[#include <time.h>]])

Example:

macros.AC_CHECK_MEMBER("struct stat.st_rdev", includes = ["sys/stat.h"])
macros.AC_CHECK_MEMBER("struct tm.tm_zone", includes = ["time.h"])

PARAMETERS

NameDescriptionDefault Value
aggregate_memberStruct or union name with (e.g., struct stat.st_rdev)none
nameCache variable name (defaults to ac_cv_member_aggregate_member following autoconf convention)None
defineCustom define name (defaults to HAVE_<AGGREGATE>_<MEMBER>)None
includesOptional list of header names to include (e.g. ["sys/stat.h"])None
languageLanguage to use for check ("c" or "cpp")"c"
compile_definesOptional list of preprocessor define names from previous checks to add before includes (e.g., ["_GNU_SOURCE", "_DARWIN_C_SOURCE"]). Each string must match the define name of a previous check. The values from those checks will be used automatically.None
coptsOptional list of extra compiler flags for this check (e.g., ["-std=c11"]). Appended after the autoconf toolchain's own copts so they take precedence on last-wins conflicts.None
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
if_trueValue to use when check succeeds (currently not used for this check type).None
if_falseValue to use when check fails (currently not used for this check type).None
substIf True, automatically create a substitution variable with the same name that will be set to "1" if the check succeeds, "0" if it fails.None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_CHECK_SIZEOF

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_CHECK_SIZEOF(type_name, *, name, define, includes, language, copts, requires, if_true,
                       if_false, subst)

Check the size of a type.

Original m4 example:

AC_CHECK_SIZEOF([int])
AC_CHECK_SIZEOF([size_t], [], [[#include <stddef.h>]])

Example:

# Cache variable only (no define, no subst)
macros.AC_CHECK_SIZEOF("int")
# -> Creates cache variable: "ac_cv_sizeof_int"

# Cache variable + define (explicit name)
macros.AC_CHECK_SIZEOF("size_t", includes = ["#include <stddef.h>"], define = "SIZEOF_SIZE_T")
# -> Creates cache variable: "ac_cv_sizeof_size_t"
# -> Creates define: "SIZEOF_SIZE_T" in config.h

Cross-Compile Warning: This macro is NOT cross-compile friendly. It requires compiling and running code to determine the size, which doesn't work when cross-compiling.

PARAMETERS

NameDescriptionDefault Value
type_nameName of the type (e.g., int, size_t, void*)none
nameCache variable name (defaults to ac_cv_sizeof_<TYPE> following autoconf convention)None
defineDefine behavior: - None (default): No define is created, only cache variable - True: Create define using the cache variable name (name) - "SIZEOF_FOO": Create define with explicit name SIZEOF_FOO (implies define=True)None
includesOptional list of header names to include (e.g. ["stddef.h"]). Without it the probe compiles with autoconf's default includes (AC_INCLUDES_DEFAULT), as the macro does.None
languageLanguage to use for check ("c" or "cpp")"c"
coptsOptional list of extra compiler flags for this check (e.g., ["-std=c11"]). Appended after the autoconf toolchain's own copts so they take precedence on last-wins conflicts.None
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
if_trueValue to use when check succeeds (currently not used for this check type).None
if_falseValue to use when check fails (currently not used for this check type).None
substSubst behavior: - None (default): No subst is created, only cache variable - True: Create subst using the cache variable name (name) - "SIZEOF_FOO": Create subst with explicit name SIZEOF_FOO (implies subst=True)None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_CHECK_TYPE

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_CHECK_TYPE(type_name, *, name, define, includes, language, compile_defines, copts,
                     requires, if_true, if_false, subst)

Check for a type.

Original m4 example:

AC_CHECK_TYPE([size_t])
AC_CHECK_TYPE([pthread_t], [], [], [[#include <pthread.h>]])

Example:

# Cache variable only (no define, no subst)
macros.AC_CHECK_TYPE("size_t")
# -> Creates cache variable: "ac_cv_type_size_t"

# Cache variable + define (explicit name)
macros.AC_CHECK_TYPE("pthread_t", includes = ["#include <pthread.h>"], define = "HAVE_PTHREAD_T")
# -> Creates cache variable: "ac_cv_type_pthread_t"
# -> Creates define: "HAVE_PTHREAD_T" in config.h

PARAMETERS

NameDescriptionDefault Value
type_nameName of the type (e.g., size_t)none
nameCache variable name (defaults to ac_cv_type_<TYPE> following autoconf convention)None
defineDefine behavior: - None (default): No define is created, only cache variable - True: Create define using the cache variable name (name) - "HAVE_FOO": Create define with explicit name HAVE_FOO (implies define=True)None
includesOptional list of headers to include (e.g. ["#include <pthread.h>"]). If not specified, uses AC_INCLUDES_DEFAULT.None
languageLanguage to use for check ("c" or "cpp")"c"
compile_definesOptional list of preprocessor define names from previous checks to add before includes (e.g., ["_GNU_SOURCE", "_DARWIN_C_SOURCE"]). Each string must match the define name of a previous check. The values from those checks will be used automatically.None
coptsOptional list of extra compiler flags for this check (e.g., ["-std=c11"]). Appended after the autoconf toolchain's own copts so they take precedence on last-wins conflicts.None
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
if_trueValue to use when check succeeds (currently not used for this check type).None
if_falseValue to use when check fails (currently not used for this check type).None
substSubst behavior: - None (default): No subst is created, only cache variable - True: Create subst using the cache variable name (name) - "HAVE_FOO": Create subst with explicit name HAVE_FOO (implies subst=True)None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_COMPUTE_INT

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_COMPUTE_INT(define, expression, *, includes, language, copts, requires, subst)

Compute an integer value at compile time.

Original m4 example:

AC_COMPUTE_INT([SIZEOF_INT], [sizeof(int)])
AC_COMPUTE_INT([MAX_VALUE], [1 << 16])

Example:

macros.AC_COMPUTE_INT("SIZEOF_INT", "sizeof(int)")
macros.AC_COMPUTE_INT("MAX_VALUE", "1 << 16")

Cross-Compile Warning: This macro is NOT cross-compile friendly. It requires compiling and running code to compute the value, which doesn't work when cross-compiling.

PARAMETERS

NameDescriptionDefault Value
defineDefine name for the result (first arg to match autoconf)none
expressionC expression that evaluates to an integer (second arg)none
includesOptional list of header names to include (e.g. ["stdlib.h"])None
languageLanguage to use for check ("c" or "cpp")"c"
coptsOptional list of extra compiler flags for this check (e.g., ["-std=c11"]). Appended after the autoconf toolchain's own copts for every probe in the binary-search loop.None
requiresList of requirements that must be met before this check runs. Can be simple define names (e.g., "HAVE_FOO") or value-based requirements (e.g., "REPLACE_FSTAT=1" to require specific value)None
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_C_RESTRICT

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_C_RESTRICT(define, language, requires, subst)

Check if the compiler supports restrict keyword.

Original m4 example:

AC_C_RESTRICT

Example:

checks.AC_C_RESTRICT()

Note: If restrict is not supported, the define is set to empty string.

PARAMETERS

NameDescriptionDefault Value
defineDefine name (defaults to restrict)"restrict"
languageLanguage to use for check ("c" or "cpp")"c"
requiresList of requirements that must be met before this check runs. Can be simple define names (e.g., "HAVE_FOO") or value-based requirements (e.g., "REPLACE_FSTAT=1" to require specific value)None
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_DEFINE

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_DEFINE(define, value, requires, condition, if_true, if_false, subst)

Define a configuration macro.

Original m4 example:

AC_DEFINE([CUSTOM_VALUE], [42])
AC_DEFINE([ENABLE_FEATURE], [1])
AC_DEFINE([PROJECT_NAME], ["MyProject"])
AC_DEFINE([EMPTY_VALUE], [])

Conditional example (m4):

if test $gl_cv_foo = yes; then
  AC_DEFINE([FOO_WORKS], [1])
fi

Example:

# Simple (always define)
macros.AC_DEFINE("CUSTOM_VALUE", "42")

# Conditional (define based on another check's result)
macros.AC_DEFINE(
    "FOO_WORKS",
    condition = "HAVE_FOO",
    if_true = "1",      # Value when HAVE_FOO is true
    if_false = None,    # Don't define when HAVE_FOO is false
)

Note: This is equivalent to GNU Autoconf's AC_DEFINE macro. When used without condition, it creates a define that will always be set. When used with condition, the define is set based on the condition's result.

**Condition Resolution**: If `condition` references a cache variable (e.g.,
`condition="ac_cv_header_foo_h"`), it's evaluated as a truthy check:
- Condition is `true` if the check succeeded AND the value is truthy (non-empty, non-zero)
- Condition is `false` if the check failed OR the value is falsy (empty, "0")
- If condition includes a comparison (e.g., `condition="ac_cv_header_foo_h==1"`),
  it performs value comparison instead.

PARAMETERS

NameDescriptionDefault Value
defineDefine name (e.g., "CUSTOM_VALUE")none
valueValue to assign when no condition is specified (defaults to "1")1
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
conditionSelects which value to use: if_true when condition is true, if_false when false. Does not affect whether the check runs.None
if_trueValue when condition is true (requires condition).1
if_falseValue when condition is false (requires condition). Use None to not define the macro when condition is false.0
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_DEFINE_UNQUOTED

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_DEFINE_UNQUOTED(define, value, requires, condition, if_true, if_false, subst)

Define a configuration macro without quoting (AC_DEFINE_UNQUOTED).

Original m4 example:

AC_DEFINE_UNQUOTED([ICONV_CONST], [$iconv_arg1])
AC_DEFINE_UNQUOTED([VERSION], ["$PACKAGE_VERSION"])

This is similar to AC_DEFINE, but with one key difference:

  • AC_DEFINE with empty value generates: #define NAME /**/
  • AC_DEFINE_UNQUOTED with empty value generates: #define NAME (trailing space)

Example:

# Simple (always define)
macros.AC_DEFINE_UNQUOTED("ICONV_CONST", "")

# Conditional (define based on another check's result)
macros.AC_DEFINE_UNQUOTED(
    "ICONV_CONST",
    condition = "_gl_cv_iconv_nonconst",
    if_true = "",      # Empty value - will generate "#define ICONV_CONST " (trailing space)
    if_false = "const",  # Non-empty value
)

Note: This is equivalent to GNU Autoconf's AC_DEFINE_UNQUOTED macro. The main difference from AC_DEFINE is how empty values are rendered in config.h: AC_DEFINE uses /**/ while AC_DEFINE_UNQUOTED uses a trailing space.

PARAMETERS

NameDescriptionDefault Value
defineDefine name (e.g., "ICONV_CONST")none
valueValue to assign when no condition is specified (defaults to "1")1
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
conditionSelects which value to use: if_true when condition is true, if_false when false. Does not affect whether the check runs.None
if_trueValue when condition is true (requires condition).None
if_falseValue when condition is false (requires condition). Use None to not define the macro when condition is false.None
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_FAIL

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_FAIL(define, *, name, subst)

A check that always fails, producing /* #undef NAME */ in config.h.

This is useful for platform-specific checks (like WORDS_BIGENDIAN) where the value is only defined on some platforms but should result in an explicit #undef on others.

Original m4 example:

dnl AC_C_BIGENDIAN sets WORDS_BIGENDIAN=1 on big-endian platforms.
dnl On little-endian platforms it is left undefined.

Example:

# In a select() for the default (little-endian) case:
checks.AC_FAIL("WORDS_BIGENDIAN")
# -> Produces /* #undef WORDS_BIGENDIAN */ in config.h

PARAMETERS

NameDescriptionDefault Value
defineDefine name to produce an #undef for (e.g., "WORDS_BIGENDIAN").none
nameCache variable name (defaults to ac_cv_fail_<DEFINE>).None
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_LANG_STANDARD

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_LANG_STANDARD(standard, *, name, compare, define, error, copts, requires)

Check the language standard the compiler is running in.

The probe compares __STDC_VERSION__ (C) or __cplusplus -- _MSVC_LANG on MSVC, whose __cplusplus stays at 199711L -- with the value of standard, under the flags every check sees: the toolchain's, --copt / --conlyopt / --cxxopt, and the target's own copts / conlyopts / cxxopts. It is the test behind AC_PROG_CC_C99, AC_PROG_CC_STDC and AX_CXX_COMPILE_STDCXX's mandatory mode, as one check: configure switches the compiler into the mode it needs and stops when it cannot; a Bazel build takes its standard from the root module, so the check reports, or stops the build, instead.

Original m4 example:

AC_PROG_CC_C99
AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory])

Example:

# Stop the build unless the C compiler is in C11 mode or later.
checks.AC_LANG_STANDARD("c11", error = True)
# Record whether the C++ compiler is in C++17 mode or later.
checks.AC_LANG_STANDARD("c++17", define = "HAVE_CXX17")
# Exactly C99 (the GNU dialect reports the same value).
checks.AC_LANG_STANDARD("c99", compare = "==", error = "C99 required")

Note: This is a rules_cc_autoconf extension. Dialects compare equal to the standard they extend (gnu11 is c11), and the standard is the build's: a -std of the target's own conlyopts / cxxopts is seen, a dependency's is not.

PARAMETERS

NameDescriptionDefault Value
standardc89 .. c23 or c++98 .. c++26, with or without a gnu prefix.none
nameCache variable name. Defaults to ac_cv_std_<standard> plus a suffix for comparisons other than >= (ac_cv_std_c99_exact).None
compareHow the compiler's standard must relate to standard: >= (default), ==, <=, > or <.">="
defineDefine name to set when the comparison holds.None
errorStop the build when the comparison does not hold: True for a message naming the flags to add (GNU and MSVC spellings), or a message of your own.None
coptsOptional list of extra compiler flags for this check.None
requiresRequirements that must be met for this check to run.None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_PROG_CC

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_PROG_CC(requires)

Check that a C compiler is available.

Original m4 example:

AC_PROG_CC
AC_PROG_CC([gcc clang])

Example:

macros.AC_PROG_CC()

Note: This is mostly a no-op in Bazel since the toolchain must be configured, but returns a check that will verify the compiler works.

PARAMETERS

NameDescriptionDefault Value
requiresList of requirements that must be met before this check runs. Can be simple define names (e.g., "HAVE_FOO") or value-based requirements (e.g., "REPLACE_FSTAT=1" to require specific value)None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_PROG_CC_C_O

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_PROG_CC_C_O(define, language, requires, subst)

Check if the compiler supports -c and -o flags simultaneously.

Original m4 example:

AC_PROG_CC_C_O

Example:

macros.AC_PROG_CC_C_O()

Note: If the compiler does NOT support both flags together, the define is set.

PARAMETERS

NameDescriptionDefault Value
defineDefine name (defaults to "NO_MINUS_C_MINUS_O")"NO_MINUS_C_MINUS_O"
languageLanguage to use for check ("c" or "cpp")"c"
requiresList of requirements that must be met before this check runs. Can be simple define names (e.g., "HAVE_FOO") or value-based requirements (e.g., "REPLACE_FSTAT=1" to require specific value)None
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_PROG_CXX

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_PROG_CXX(requires)

Check that a C++ compiler is available.

Original m4 example:

AC_PROG_CXX
AC_PROG_CXX([g++ clang++])

Example:

macros.AC_PROG_CXX()

Note: This is mostly a no-op in Bazel since the toolchain must be configured, but returns a check that will verify the compiler works.

PARAMETERS

NameDescriptionDefault Value
requiresList of requirements that must be met before this check runs. Can be simple define names (e.g., "HAVE_FOO") or value-based requirements (e.g., "REPLACE_FSTAT=1" to require specific value)None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_SEARCH_LIBS

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_SEARCH_LIBS(function, libraries, *, name, code, language, copts, linkopts, requires,
                      subst)

Search for a function in libc, then in a list of libraries.

Mirrors GNU autoconf's AC_SEARCH_LIBS. The checker tries linking the function without any extra library first, then with each library in order.

For function = "main", the generated program supplies its own main. Consequently, a working linker succeeds on the initial no-library attempt and no candidate library is selected, matching GNU Autoconf.

When subst is specified, a substitution variable is produced with the library flag value: "" if the function is in libc or not found, "-l<lib>" if found in a library.

Original m4 example:

AC_SEARCH_LIBS([clock_gettime], [rt posix4])

Example:

checks.AC_SEARCH_LIBS("clock_gettime", ["rt", "posix4"],
                      subst = "CLOCK_TIME_LIB")

PARAMETERS

NameDescriptionDefault Value
functionFunction name to search for (e.g., "clock_gettime")none
librariesList of library names (without -l prefix) to try in order (e.g., ["rt", "posix4"])none
nameCache variable name. Defaults to "ac_cv_search_".None
codeCustom code to compile and link (optional).None
languageLanguage to use for check ("c" or "cpp")."c"
coptsOptional list of extra compiler flags for this check (e.g., ["-std=c11"]). Applied to every link attempt in the search loop.None
linkoptsOptional list of extra linker flags for this check (e.g., ["-pthread"]). Applied to every link attempt; appended after the optional -l<library> argument.None
requiresRequirements that must be met for this check to run.None
substSubstitution variable name for the library flag result.None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_SUBST

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_SUBST(variable, value, requires, condition, if_true, if_false)

Substitute a variable value (equivalent to AC_SUBST in GNU Autoconf).

Original m4 example:

HAVE_DECL_WCSDUP=1;   AC_SUBST([HAVE_DECL_WCSDUP])
HAVE_DECL_WCWIDTH=1;  AC_SUBST([HAVE_DECL_WCWIDTH])

Conditional example (m4):

if test $gl_cv_sys_struct_lconv_ok = no; then
  REPLACE_STRUCT_LCONV=1
else
  REPLACE_STRUCT_LCONV=0
fi
AC_SUBST([REPLACE_STRUCT_LCONV])

Example:

# Simple (always set)
macros.AC_SUBST("HAVE_DECL_WCSDUP", "1")

# Conditional (set based on another check's result)
macros.AC_SUBST(
    "REPLACE_STRUCT_LCONV",
    condition = "HAVE_STRUCT_LCONV_OK",
    if_true = "0",    # Value when condition is true
    if_false = "1",   # Value when condition is false
)

Note: Condition Resolution: If condition references a cache variable (e.g., condition="ac_cv_header_foo_h"), it's evaluated as a truthy check: - Condition is true if the check succeeded AND the value is truthy (non-empty, non-zero) - Condition is false if the check failed OR the value is falsy (empty, "0") - If condition includes a comparison (e.g., condition="ac_cv_header_foo_h==1"), it performs value comparison instead.

In GNU Autoconf, `AC_SUBST` is used to substitute shell variables into
Makefiles and template files (replacing `@VAR@` patterns). When used
without `condition`, it always sets the variable. When used with
`condition`, the value depends on the condition's result.

PARAMETERS

NameDescriptionDefault Value
variableVariable name (e.g., "LIBRARY_PATH")none
valueValue to assign when no condition is specified (defaults to "1")1
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
conditionSelects which value to use: if_true when condition is true, if_false when false. Does not affect whether the check runs.None
if_trueValue when condition is true (requires condition).1
if_falseValue when condition is false (requires condition).0

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AC_TRY_COMPILE

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_TRY_COMPILE(*, code, name, define, includes, language, compile_defines, copts, requires,
                      if_true, if_false, subst, error)

Try to compile custom code.

Original m4 example:

AC_TRY_COMPILE([#include <stdio.h>], [printf("test");], [AC_DEFINE([HAVE_PRINTF], [1])])

Example:

load("//autoconf:checks.bzl", "utils")
macros.AC_TRY_COMPILE(
    code = "#include <stdio.h>\nint main() { printf(\"test\"); return 0; }",
    define = "HAVE_PRINTF",
)
macros.AC_TRY_COMPILE(file = ":test.c", define = "CUSTOM_CHECK")
macros.AC_TRY_COMPILE(
    includes = ["pthread.h"],
    code = "int x = PTHREAD_CREATE_DETACHED; (void)x;",
    define = "HAVE_PTHREAD_CREATE_DETACHED",
)
macros.AC_TRY_COMPILE(
    code = utils.AC_LANG_PROGRAM(["#include <stdio.h>"], "printf("test");"),
    define = "HAVE_PRINTF",
)

Note: This is a rules_cc_autoconf extension. While GNU Autoconf has an obsolete AC_TRY_COMPILE macro (replaced by AC_COMPILE_IFELSE), this version adds support for file-based checks which is useful in Bazel.

When `includes` is provided along with `code`, the code is treated as
the body of main() and the includes are prepended. For the AC_LANG_PROGRAM
pattern (prologue + body), use `utils.AC_LANG_PROGRAM(prologue, body)` and
pass the result as `code`.

PARAMETERS

NameDescriptionDefault Value
codeCode to compile. If includes is also provided, this is treated as the body of main(). Otherwise, it should be complete C code. For AC_LANG_PROGRAM-style tests, use code = utils.AC_LANG_PROGRAM(prologue, body). May be a list of programs (or bodies, with includes): they are tried in order and the check succeeds when one compiles.None
nameCache variable name.None
defineDefine name to set if compilation succeedsNone
includesOptional list of headers to include. When provided, code is treated as the body of main() function.None
languageLanguage to use for check ("c" or "cpp")"c"
compile_definesOptional list of preprocessor define names from previous checks to add before includes (e.g., ["_GNU_SOURCE", "_DARWIN_C_SOURCE"]). Each string must match the define name of a previous check. The values from those checks will be used automatically.None
coptsOptional list of extra compiler flags for this check (e.g., ["-msse2", "-std=c11"]).None
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
if_trueValue to use when check succeeds (currently not used for this check type).None
if_falseValue to use when check fails (currently not used for this check type).None
substIf True, automatically create a substitution variable with the same name that will be set to "1" if the check succeeds, "0" if it fails.None
errorStop the build with this message when the check fails: AC_MSG_ERROR in the branch configure runs when the test does not pass, e.g. a mandatory language standard.None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AC_TRY_LINK(*, code, name, define, includes, language, compile_defines, copts, linkopts,
                   requires, if_true, if_false, subst, error)

Try to compile and link custom code.

This function mirrors GNU Autoconf's AC_LINK_IFELSE macro, which uses AC_LANG_PROGRAM to construct test programs. In Bazel, use code (optionally with includes). For the AC_LANG_PROGRAM pattern, pass code = utils.AC_LANG_PROGRAM(prologue, body).

Comparison with GNU Autoconf:

GNU Autoconf:

AC_LINK_IFELSE(
    [AC_LANG_PROGRAM(
        [[#include <stdio.h>]],     # Prologue (includes/declarations)
        [[printf("test");]]         # Body (code inside main())
    )],
    [AC_DEFINE([HAVE_PRINTF], [1])],
    []
)

Bazel (AC_LANG_PROGRAM pattern):

load("//autoconf:checks.bzl", "utils")
checks.AC_TRY_LINK(
    code = utils.AC_LANG_PROGRAM(["#include <stdio.h>"], "printf("test");"),
    define = "HAVE_PRINTF",
)

Bazel (includes pattern - backward compat):

checks.AC_TRY_LINK(
    includes = ["stdio.h"],
    code = "printf("test");",     # Treated as body of main()
    define = "HAVE_PRINTF",
)

Bazel (raw code pattern):

checks.AC_TRY_LINK(
    code = "#include <stdio.h>\nint main() { printf(\"test\"); return 0; }",
    define = "HAVE_PRINTF",
)

Note: This is similar to AC_TRY_COMPILE but also links the code, which is necessary to verify that functions are available at link time (not just declared).

PARAMETERS

NameDescriptionDefault Value
codeCode to compile and link. If includes is also provided, this is treated as the body of main(). Otherwise, it should be complete C code. For AC_LANG_PROGRAM-style tests use code = utils.AC_LANG_PROGRAM(prologue, body). May be a list of programs (or bodies, with includes): they are tried in order and the check succeeds when one links.None
nameCache variable name.None
defineDefine name to set if compilation and linking succeedsNone
includesOptional list of headers to include (backward compatibility). When provided, code is treated as the body of main() function.None
languageLanguage to use for check ("c" or "cpp")"c"
compile_definesOptional list of preprocessor define names from previous checks to add before includes (e.g., ["_GNU_SOURCE", "_DARWIN_C_SOURCE"]). Each string must match the define name of a previous check. The values from those checks will be used automatically.None
coptsOptional list of extra compiler flags for this check (e.g., ["-msse2", "-std=c11"]).None
linkoptsOptional list of extra linker flags for this check (e.g., ["-lpthread", "-Wl,--as-needed"]).None
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
if_trueValue to use when check succeeds (currently not used for this check type).1
if_falseValue to use when check fails (currently not used for this check type).0
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None
errorStop the build with this message when the check fails: AC_MSG_ERROR in the branch configure runs when the test does not pass, e.g. a mandatory language standard.None

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.AX_CXX_COMPILE_STDCXX

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.AX_CXX_COMPILE_STDCXX(version, ext, mandatory)

AX_CXX_COMPILE_STDCXX from the autoconf archive: is the C++ compiler in C++ mode?

Defines HAVE_CXX<version> when it is (or a later one), and in mandatory mode stops the build when it is not. The macro itself would add -std=c++<version> or -std=gnu++<version> to CXX; a Bazel build's standard comes from the root module (--cxxopt) or the toolchain, so only the test remains.

Original m4 example:

AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory])

Example:

checks.AX_CXX_COMPILE_STDCXX("17", "noext", mandatory = True)

PARAMETERS

NameDescriptionDefault Value
version11, 14, 17, 20, 23 or 26.none
extext or noext, as in the macro. Accepted for fidelity; both dialects report the same __cplusplus, so it does not change the test."ext"
mandatoryStop the build when the compiler is not in that mode (the macro's mandatory); False is its optional.True

RETURNS

A JSON-encoded check string for use with the autoconf rule.

checks.M4_VARIABLE

load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

checks.M4_VARIABLE(define, value, requires, condition, if_true, if_false)

Define a configuration M4 variable.

Original m4 example:

REPLACE_FOO=1

Conditional example (m4):

if test $HAVE_FOO = yes; then
  REPLACE_FOO=0
else
  REPLACE_FOO=1
fi

Example:

# Simple (always set)
macros.M4_VARIABLE("REPLACE_FOO", "1")

# Conditional (set based on another check's result)
macros.M4_VARIABLE(
    "REPLACE_FOO",
    condition = "HAVE_FOO",
    if_true = "0",    # Value when HAVE_FOO is true
    if_false = "1",   # Value when HAVE_FOO is false
)

Note: This is similar to AC_SUBST but is useful for tracking the difference between actual AC_DEFINE values and M4 shell variables used in macros. Unlike AC_SUBST, this does not set subst=true by default.

PARAMETERS

NameDescriptionDefault Value
defineDefine name (e.g., "REPLACE_FOO")none
valueValue to assign when no condition is specified (defaults to "1")1
requiresList of requirements that must be met before this check runs. Can be simple define names (e.g., "HAVE_FOO") or value-based requirements (e.g., "REPLACE_FSTAT=1" to require specific value)None
conditionSelects which value to use: if_true when condition is true, if_false when false. Does not affect whether the check runs.None
if_trueValue when condition is true (requires condition).None
if_falseValue when condition is false (requires condition).None

RETURNS

A JSON-encoded check string for use with the m4 rule.

macros.AC_CHECK_DECLS

load("@rules_cc_autoconf//autoconf:checks.bzl", "macros")

macros.AC_CHECK_DECLS(symbols, *, includes, compile_defines, language, requires, if_true, if_false,
                      subst)

Check multiple declarations, creating HAVE_DECL_ defines (AC_CHECK_DECLS).

GNU Autoconf's AC_CHECK_DECLS(symbol, ...) checks whether each symbol is declared (e.g. in a header). This macro runs AC_CHECK_DECL for each symbol and sets define names HAVE_DECL_ following autoconf conventions.

Upstream: https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.72/autoconf.html#index-AC_005fCHECK_002dDECL

M4 example (configure.ac): AC_CHECK_DECLS([NULL, stdout, stderr], [], [], [[#include <stdio.h>]]) AC_CHECK_DECL([NULL], [AC_DEFINE([HAVE_DECL_NULL], [1])], [], [[#include <stddef.h>]])

Bazel example (use #include <foo> in includes): load("//autoconf:checks.bzl", "macros") macros.AC_CHECK_DECLS(["NULL", "stdout", "stderr"], includes = ["#include <stdio.h>"])

Defines: HAVE_DECL_NULL, HAVE_DECL_STDOUT, HAVE_DECL_STDERR

PARAMETERS

NameDescriptionDefault Value
symbolsList of symbol names to check.none
includesOptional list of include directives (e.g. ["#include <stdio.h>"]).None
compile_definesOptional list of preprocessor define names (applies to all checks).None
languageLanguage to use for checks ("c" or "cpp")."c"
requiresRequirements that must be met (applies to all checks).None
if_trueValue to use when check succeeds (applies to all checks).None
if_falseValue to use when check fails (applies to all checks).None
substSubst behavior (applies to all checks).None

RETURNS

List of JSON-encoded check strings.

macros.AC_CHECK_FUNCS

load("@rules_cc_autoconf//autoconf:checks.bzl", "macros")

macros.AC_CHECK_FUNCS(functions, *, code, language, compile_defines, requires, subst)

Check multiple functions, creating HAVE_ defines (AC_CHECK_FUNCS).

GNU Autoconf's AC_CHECK_FUNCS(function, ...) checks whether each function is available (linkable). This macro runs AC_CHECK_FUNC for each function and sets HAVE_ following autoconf conventions.

Upstream: https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.72/autoconf.html#index-AC_005fCHECK_002dFUNC

M4 example (configure.ac): AC_CHECK_FUNCS([malloc, free, printf])

Bazel example: load("//autoconf:checks.bzl", "macros") macros.AC_CHECK_FUNCS(["malloc", "free", "printf"])

Defines: HAVE_MALLOC, HAVE_FREE, HAVE_PRINTF

PARAMETERS

NameDescriptionDefault Value
functionsList of function names to check.none
codeCustom program, or list of programs tried in order, to compile and link (applies to all checks); see AC_CHECK_FUNC.None
languageLanguage to use for checks ("c" or "cpp")."c"
compile_definesOptional list of preprocessor define names (applies to all checks).None
requiresRequirements that must be met (applies to all checks).None
substSubst behavior (applies to all checks).None

RETURNS

List of JSON-encoded check strings.

macros.AC_CHECK_HEADERS

load("@rules_cc_autoconf//autoconf:checks.bzl", "macros")

macros.AC_CHECK_HEADERS(headers, *, language, includes, compile_defines, requires)

Check multiple headers, creating HAVE_

defines (AC_CHECK_HEADERS).

GNU Autoconf's AC_CHECK_HEADERS(header, ...) checks whether each header can be compiled. This macro runs AC_CHECK_HEADER for each header and sets HAVE_<HEADER_UPPER> (e.g. HAVE_STDIO_H) following autoconf conventions. Header names are turned into include directives as #include

.

Upstream: https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.72/autoconf.html#index-AC_005fCHECK_002dHEADERS

Original m4 example:

AC_CHECK_HEADERS([stdio.h, stdlib.h, string.h])

Example:

load("//autoconf:checks.bzl", "macros")
macros.AC_CHECK_HEADERS(["stdio.h", "stdlib.h", "string.h"])
# Defines: HAVE_STDIO_H, HAVE_STDLIB_H, HAVE_STRING_H

PARAMETERS

NameDescriptionDefault Value
headersList of header names to check (e.g. "sys/stat.h").none
languageLanguage to use for checks ("c" or "cpp")."c"
includesOptional list of include directives (e.g. ["#include <stdio.h>"]).None
compile_definesOptional list of preprocessor define names (applies to all checks).None
requiresRequirements that must be met (applies to all checks).None

RETURNS

List of JSON-encoded check strings.

macros.AC_CHECK_MEMBERS

load("@rules_cc_autoconf//autoconf:checks.bzl", "macros")

macros.AC_CHECK_MEMBERS(aggregate_members, *, includes, language, compile_defines, requires,
                        if_true, if_false, subst)

Check multiple structure members, creating HAVE__ defines (AC_CHECK_MEMBERS).

GNU Autoconf's AC_CHECK_MEMBER(aggregate.member, ...) checks whether a structure member exists. This macro runs AC_CHECK_MEMBER for each "aggregate.member" and sets HAVE__ (e.g. HAVE_STRUCT_STAT_ST_RDEV). Use includes with #include <foo> for headers that declare the aggregate.

Upstream: https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.72/autoconf.html#index-AC_005fCHECK_002dMEMBER

M4 example (configure.ac): AC_CHECK_MEMBERS([struct stat.st_rdev, struct tm.tm_zone], [], [], [[#include <sys/stat.h>]]) AC_CHECK_MEMBER([struct stat.st_rdev], [], [], [[#include <sys/stat.h>]])

Bazel example (use #include <foo> in includes): load("//autoconf:checks.bzl", "macros") macros.AC_CHECK_MEMBERS( ["struct stat.st_rdev", "struct tm.tm_zone"], includes = ["#include <sys/stat.h>", "#include <time.h>"], )

PARAMETERS

NameDescriptionDefault Value
aggregate_membersList of member specs (e.g. ["struct stat.st_rdev"]).none
includesOptional list of include directives (e.g. ["#include <sys/stat.h>"]).None
languageLanguage to use for checks ("c" or "cpp")."c"
compile_definesOptional list of preprocessor define names (applies to all checks).None
requiresRequirements that must be met (applies to all checks).None
if_trueValue to use when check succeeds (applies to all checks).None
if_falseValue to use when check fails (applies to all checks).None
substSubst behavior (applies to all checks).None

RETURNS

List of JSON-encoded check strings.

macros.AC_CHECK_TYPES

load("@rules_cc_autoconf//autoconf:checks.bzl", "macros")

macros.AC_CHECK_TYPES(types, *, includes, language, compile_defines, requires, if_true, if_false,
                      subst)

Check multiple types, creating HAVE_ defines (AC_CHECK_TYPES).

GNU Autoconf's AC_CHECK_TYPE(type, ...) checks whether each type is defined. This macro runs AC_CHECK_TYPE for each type and sets HAVE_ (e.g. HAVE_SIZE_T) following autoconf conventions. Use includes with #include <foo> for the headers that declare the types.

Upstream: https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.72/autoconf.html#index-AC_005fCHECK_002dTYPE

M4 example (configure.ac): AC_CHECK_TYPES([size_t, pthread_t, pid_t], [], [], [[#include <stddef.h>]]) AC_CHECK_TYPE([pthread_t], [], [], [[#include <pthread.h>]])

Bazel example (use #include <foo> in includes): load("//autoconf:checks.bzl", "macros") macros.AC_CHECK_TYPES(["size_t", "pthread_t", "pid_t"], includes = ["#include <stddef.h>", "#include <pthread.h>"])

Defines: HAVE_SIZE_T, HAVE_PTHREAD_T, HAVE_PID_T

PARAMETERS

NameDescriptionDefault Value
typesList of type names to check.none
includesOptional list of include directives (e.g. ["#include <stddef.h>"]).None
languageLanguage to use for checks ("c" or "cpp")."c"
compile_definesOptional list of preprocessor define names (applies to all checks).None
requiresRequirements that must be met (applies to all checks).None
if_trueValue to use when check succeeds (applies to all checks).None
if_falseValue to use when check fails (applies to all checks).None
substSubst behavior (applies to all checks).None

RETURNS

List of JSON-encoded check strings.

load("@rules_cc_autoconf//autoconf:checks.bzl", "utils")

utils.AC_LANG_FUNC_LINK_TRY(function)

Return GNU autoconf's AC_LANG_FUNC_LINK_TRY(C) program for function.

The first program of AC_CHECK_FUNC's default code list. Pass it alone as code to get GNU's literal answer on every compiler; see AC_CHECK_FUNC for when that matters.

PARAMETERS

NameDescriptionDefault Value
functionName of the function (e.g., "snprintf")none

RETURNS

C source for the link probe.

utils.AC_LANG_PROGRAM

load("@rules_cc_autoconf//autoconf:checks.bzl", "utils")

utils.AC_LANG_PROGRAM(prologue, body)

Build program code from prologue and main body (AC_LANG_PROGRAM).

GNU Autoconf's AC_LANG_PROGRAM(prologue, body) expands to a complete test program: prologue (includes/declarations) followed by main() containing body. Use the result as the code argument to AC_TRY_COMPILE or AC_TRY_LINK.

Upstream: https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.72/autoconf.html#index-AC_005fLANG_005fPROGRAM

M4 example (configure.ac): AC_TRY_COMPILE([AC_LANG_PROGRAM([[#include <stdio.h>]], [printf("test");])], ...) AC_TRY_LINK([AC_LANG_PROGRAM([[#include <langinfo.h>]], [char* cs = nl_langinfo(CODESET); return !cs;])], ...)

Bazel example (use #include <foo> in the prologue list): load("//autoconf:checks.bzl", "utils", "checks") checks.AC_TRY_COMPILE( code = utils.AC_LANG_PROGRAM(["#include <stdio.h>"], "printf("test");"), define = "HAVE_PRINTF", ) checks.AC_TRY_LINK( code = utils.AC_LANG_PROGRAM( ["#include <langinfo.h>"], "char* cs = nl_langinfo(CODESET); return !cs;", ), define = "HAVE_LANGINFO_CODESET", )

PARAMETERS

NameDescriptionDefault Value
prologueList of strings (include directives or declarations), joined with newlines.none
bodyCode inside main() — a string, or a list of strings joined with newlines.none

RETURNS

A string suitable for the code parameter of AC_TRY_COMPILE or AC_TRY_LINK.

Gnulib

The @rules_cc_autoconf//gnulib module provides a collection of pre-built autoconf checks based on GNU Gnulib, a portability library for Unix-like systems. Instead of manually writing checks for common functions, headers, and types, you can reuse these well-tested, platform-aware implementations.

What is it?

Gnulib is a collection of M4 macros and C code that provides portability checks and replacements for common POSIX functions. The @rules_cc_autoconf//gnulib module translates many of these M4 macros into Bazel autoconf targets that you can use as dependencies.

Each target in @rules_cc_autoconf//gnulib/m4/ corresponds to a gnulib M4 macro and provides the same checks and defines that you would get from using that macro in a traditional configure.ac file.

Some modules expose linker-related subst variables via AC_SEARCH_LIBS (for example clock/time helpers that may need -lrt). Bazel does not run libtool like Autoconf; add an autoconf_linkopts target that lists those subst names and depend on it from your cc_library / cc_binary / cc_test targets. See Migrating M4 macros — Pattern 7b and the rule reference autoconf_linkopts.

Using Gnulib Targets

Instead of manually writing checks, you can add gnulib reusable targets as dependencies to your autoconf rule:

load("@rules_cc_autoconf//autoconf:autoconf.bzl", "autoconf")
load("@rules_cc_autoconf//autoconf:autoconf_hdr.bzl", "autoconf_hdr")
load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")
load("@rules_cc_autoconf//autoconf:autoconf_package_info.bzl", "autoconf_package_info")

autoconf_package_info(
    name = "package",
    package_name = "my_package",
    package_version = "1.0.0",
)

autoconf(
    name = "autoconf",
    checks = [
        # Only add custom checks that aren't available in gnulib
        checks.AC_DEFINE("CUSTOM_FEATURE", "1"),
    ],
    deps = [
        ":package",
        "@rules_cc_autoconf//gnulib/m4/lstat",      # Provides AC_CHECK_FUNC("lstat")
        "@rules_cc_autoconf//gnulib/m4/access",     # Provides AC_CHECK_FUNC("access")
        "@rules_cc_autoconf//gnulib/m4/unistd_h",   # Provides AC_CHECK_HEADER("unistd.h")
        "@rules_cc_autoconf//gnulib/m4/sys_stat_h", # Provides AC_CHECK_HEADER("sys/stat.h")
    ],
)

autoconf_hdr(
    name = "config",
    out = "config.h",
    template = "config.h.in",
    deps = [":autoconf"],
)

How the gnulib ports are tested

Every //gnulib/m4/<module> port has a suite under //gnulib/tests/compat/<module> created by gnu_gnulib_diff_test_suite. The oracle is GNU autoconf itself: the <module>_test_gnu_conformance test runs the pinned aclocal, autoconf and configure on the module's configure.ac using the m4 files from the pinned gnulib commit, with the same compiler, flags and environment the autoconf rule uses (both read them from the same Starlark helper), and requires the rendered config.h and subst.h to match the Bazel-generated headers byte for byte. There are no checked-in expected outputs for Linux and macOS; GNU autoconf produces them on the machine running the test.

The autotools are hermetic dev dependencies: autoconf and automake tarballs are fetched and installed by //autoconf/tests/gnu:deps.bzl, perl comes from rules_perl, m4 from the Bazel Central Registry m4 module, and the shell from rules_shell. Only the POSIX shell and userland come from the host. Run the bundled tools directly with, for example, bazel run //autoconf/tests/gnu:autoconf -- --version.

Every comparison leaves its inputs where a reviewer can read them. A conformance test's undeclared outputs directory (bazel-testlogs/.../test.outputs/) contains gnu/ (the configure work tree with config.h, subst.h, config.log, tool logs and the exact environment configure ran with), bazel/ (the Bazel headers), a config.h.diff / subst.h.diff when they differ, versions.json and fingerprint.json. A diff_test leaves expected/, actual/ and diff.patch.

On Windows the same conformance test runs under the MSYS2 bash from rules_shell, with Strawberry Perl from rules_perl and MSVC driven through gnulib's compile wrapper. There are no checked-in expected outputs on any platform.

When a port intentionally differs from upstream m4, list the variable in the suite's known_divergences with a reason; it is masked from the comparison and always printed in the test log. Every gnulib module has a conformance test; there is no opt-out.

M4 to Bazel Migration Guide

This guide teaches how to convert GNU Autoconf M4 macros (from configure.ac files) to the equivalent Bazel rules in this repository. It serves two readers:

  • Users migrating their own project. Read Quick Start through Platform Conditionals. Load autoconf from @rules_cc_autoconf//autoconf:autoconf.bzl as shown there.
  • Contributors porting gnulib .m4 modules into this repository. Also read Porting Strategy. Ports under //gnulib/m4 and //autoconf/macros load autoconf_cache instead (see autoconf vs autoconf_cache) and are verified by running GNU autoconf itself against the Bazel output.

Constraint for gnulib ports in this repository: When porting gnulib modules or fixing failing conformance tests, you may only modify BUILD.bazel files under //gnulib/m4. The following are not allowed:

  • Test fixtures under //gnulib/tests/compat/<module>: configure.ac, config.h.in, subst.h.in, test_*.c
  • .bzl files (autoconf rules, checks, macros, etc.)
  • Duplicates targets (e.g. //gnulib/tests/duplicates:gnulib and its dependency list)

Resolving duplicate check conflicts: If two modules define the same check and cause a conflict when aggregated (e.g. in the duplicates test), do not remove either module from the duplicates list. Instead, create an isolated autoconf target that contains only the conflicting check, then add that target as a dependency to both consumers.


Table of Contents

  1. Quick Start
  2. Architecture Overview
  3. Core Concepts
  4. Migration Patterns
  5. API Reference
  6. Platform Conditionals
  7. Dependencies and Reusable Modules
  8. Consuming Results Downstream
  9. Porting Strategy
  10. Best Practices
  11. Cross-Compilation Considerations
  12. Complete Examples
  13. Migration Checklist
  14. Common Pitfalls

Quick Start

Minimal Example

M4 (configure.ac):

AC_INIT([myproject], [1.0.0])
AC_CONFIG_HEADERS([config.h])
AC_CHECK_HEADERS([stdio.h stdlib.h])
AC_CHECK_FUNCS([malloc printf])
REPLACE_FOO=1
AC_SUBST([REPLACE_FOO])
AC_OUTPUT

Bazel (BUILD.bazel):

load("@rules_cc_autoconf//autoconf:autoconf.bzl", "autoconf")
load("@rules_cc_autoconf//autoconf:autoconf_hdr.bzl", "autoconf_hdr")
load("@rules_cc_autoconf//autoconf:checks.bzl", "checks", "macros")
load("@rules_cc_autoconf//autoconf:autoconf_package_info.bzl", "autoconf_package_info")

autoconf_package_info(
    name = "package",
    module_bazel = "MODULE.bazel"
)

autoconf(
    name = "autoconf",
    checks = macros.AC_CHECK_HEADERS(["stdio.h", "stdlib.h"]) + [
        checks.AC_SUBST("REPLACE_FOO", 1)
    ],
    deps = [":package"],
)

autoconf_hdr(
    name = "config",
    out = "config.h",
    template = "config.h.in",
    deps = [":autoconf"],
)

Architecture Overview

The migration system consists of these components:

1. autoconf Rule

Runs compilation checks against the configured cc_toolchain and produces check results. Each check creates a cache variable (e.g., ac_cv_header_stdio_h) and optionally a define (e.g., HAVE_STDIO_H) or subst value.

1b. autoconf_defaults Rule

Holds literal values that a check may override: gnulib's *_H_DEFAULTS (HAVE_FOO=1, REPLACE_FOO=0). Nothing is probed. Targets read a defaults target through default_deps, beneath every ordinary result; deps rejects it. See Defaults yield to your values.

2. autoconf_hdr Rule

Takes check results from autoconf targets and generates header files by processing templates. Supports two modes:

  • mode = "defines" — For config.h files (processes #undef directives)
  • mode = "subst" — For substitution files (processes @VAR@ placeholders)

3. autoconf_package_info Rule

Provides package metadata (PACKAGE_NAME, PACKAGE_VERSION, etc.) equivalent to AC_INIT.

4. autoconf_linkopts Rule (optional)

Turns subst values that hold linker flags (often from AC_SEARCH_LIBS) into CcInfo for cc_library / cc_binary / cc_test dependencies. See Pattern 7b.

autoconf vs autoconf_cache

autoconf_cache (from //autoconf:autoconf_toolchain.bzl) is identical to autoconf except that it does not resolve the autoconf_toolchain. Targets that the toolchain itself depends on (its cache_deps) must use it; using autoconf there would create a dependency cycle. Every port under //gnulib/m4 and //autoconf/macros therefore loads it under the name autoconf:

load("//autoconf:autoconf_toolchain.bzl", autoconf = "autoconf_cache")
load("//autoconf:checks.bzl", "checks")

Projects that consume the rules load autoconf from @rules_cc_autoconf//autoconf:autoconf.bzl as in the Quick Start. Everything else in this guide (checks, deps, select()) is the same for both.

Data Flow

configure.ac         →    BUILD.bazel
     ↓                         ↓
AC_INIT           →    autoconf_package_info
AC_CHECK_*        →    autoconf (checks = [...])
AC_CONFIG_HEADERS →    autoconf_hdr

Core Concepts

Cache Variables vs Defines vs Subst

Understanding the three types of outputs is crucial:

TypePurposeNaming ConventionOutput
Cache VariableInternal check resultac_cv_* (e.g., ac_cv_header_stdio_h)JSON result file
DefineC preprocessor defineHAVE_*, SIZEOF_*, etc.config.h via #define
SubstTemplate substitution@VAR@ patternssubst.h via placeholder replacement

Example:

# Creates ONLY a cache variable (no define, no subst)
checks.AC_CHECK_HEADER("stdio.h")
# → Cache: ac_cv_header_stdio_h

# Creates cache variable AND define
checks.AC_CHECK_HEADER("stdio.h", define = "HAVE_STDIO_H")
# → Cache: ac_cv_header_stdio_h
# → Define: HAVE_STDIO_H (in config.h)

# Creates cache variable, define, AND subst
checks.AC_CHECK_HEADER("stdio.h", define = "HAVE_STDIO_H", subst = True)
# → Cache: ac_cv_header_stdio_h
# → Define: HAVE_STDIO_H (in config.h)
# → Subst: HAVE_STDIO_H (replaces @HAVE_STDIO_H@ in subst.h)

The name Parameter

Every check has a name parameter (auto-generated if not specified) that becomes the cache variable name:

# Auto-generated name: "ac_cv_header_stdio_h"
checks.AC_CHECK_HEADER("stdio.h")

# Custom name
checks.AC_CHECK_HEADER("stdio.h", name = "my_custom_cache_var")

The define Parameter

Controls whether and how a define is created:

# No define (default) — only creates cache variable
checks.AC_CHECK_HEADER("stdio.h")

# Use cache variable name as define name
checks.AC_CHECK_HEADER("stdio.h", define = True)
# → Define name: ac_cv_header_stdio_h

# Explicit define name
checks.AC_CHECK_HEADER("stdio.h", define = "HAVE_STDIO_H")
# → Define name: HAVE_STDIO_H

Efficiency: define vs. name + separate AC_DEFINE

There are two ways to conditionally define a value based on a check result. Prefer define when possible — it's more efficient because it generates fewer internal operations.

Use define directly (preferred)

When you just need to set HAVE_X based on whether a check succeeds:

# EFFICIENT: One check, one define
checks.AC_CHECK_HEADER("argz.h", define = "HAVE_ARGZ_H")

This is equivalent to M4's AC_CHECK_HEADER([argz.h]) which automatically defines HAVE_ARGZ_H if the header is found.

Use name + AC_DEFINE(requires=...) only when necessary

Split the check and define only when one of these conditions applies:

  1. Other checks reference the cache variable via requires:
# REQUIRED: The cache variable is used by another check's `requires`
checks.AC_CHECK_HEADER("argz.h", name = "ac_cv_header_argz_h")
checks.AC_DEFINE("HAVE_ARGZ_H", requires = ["ac_cv_header_argz_h==1"])

# This check depends on the header being found
checks.AC_CHECK_TYPE(
    "error_t",
    includes = ["#include <argz.h>"],
    name = "ac_cv_type_error_t",
    requires = ["ac_cv_header_argz_h==1"],  # <-- Only run if header check passed
)
  1. Non-standard values (use condition for value selection):
# Use condition when you need different values based on a check
checks.AC_CHECK_FUNC("foo", name = "ac_cv_func_foo")
checks.AC_DEFINE("HAVE_FOO", condition = "ac_cv_func_foo", if_true = "yes", if_false = "no")
  1. Multiple outputs from one check (both AC_DEFINE and AC_SUBST):
# One check drives multiple outputs
checks.AC_CHECK_FUNC("lstat", name = "ac_cv_func_lstat")
checks.AC_DEFINE("HAVE_LSTAT", requires = ["ac_cv_func_lstat==1"])
checks.AC_SUBST("HAVE_LSTAT", condition = "ac_cv_func_lstat", if_true = "1", if_false = "0")

requires vs. condition

Use the correct parameter for the right purpose:

ParameterPurposeExample
requiresGate whether the check runs — if requirements aren't met, the define is not createdrequires = ["ac_cv_func_foo==1"]
conditionSelect between two values — the check always runs, but produces different valuescondition = "ac_cv_func_foo", if_true = "1", if_false = "0"

Expression syntax

requires entries and condition strings share one grammar, evaluated by the checker:

FormMeaning
NAMETrue if the named check passed / the value is truthy (an AC_SEARCH_LIBS result is true when the function was found, in libc or in a library)
!NAMENegation
NAME==1, NAME!=0, NAME<3, NAME>=2Compare the recorded value (==, !=, <, >, <=, >=)
A && B, A || B, ( ... )Boolean combination and grouping

NAME is looked up across three buckets: cache variable names (ac_cv_func_foo, gl_cv_...), define names (HAVE_FOO) and subst / M4 variable names (REPLACE_FOO). The cache name is the consistent form: it always means that one check, so prefer it in requires and condition. A bare define or subst name is also accepted and is convenient when you want whichever check (or toolchain default) publishes that name. If a bare name resolves to two different results across the dependency graph, the build fails and lists the candidates; switch to the cache name of the check you mean. See Name results by their cache variable for the naming table. Example from the strerror port:

checks.AC_SUBST(
    "REPLACE_STRERROR",
    condition = "!gl_cv_header_errno_h_complete || !_gl_cv_func_strerror_0_works",
)

Always write ==; a bare = is accepted only for backward compatibility.

Anti-pattern: Don't use condition with if_false = None to gate a define:

# BAD: Using condition to gate (if_false = None behavior may change)
checks.AC_DEFINE("HAVE_FOO", condition = "ac_cv_func_foo", if_true = 1, if_false = None)

# GOOD: Use requires to gate
checks.AC_DEFINE("HAVE_FOO", requires = ["ac_cv_func_foo==1"])

Anti-pattern: Unnecessary splitting

Don't do this — it's wasteful and harder to read:

# BAD: Unnecessary split when `define` would suffice
checks.AC_CHECK_HEADER("stdio.h", name = "ac_cv_header_stdio_h")
checks.AC_DEFINE("HAVE_STDIO_H", requires = ["ac_cv_header_stdio_h==1"])

# GOOD: Use `define` directly
checks.AC_CHECK_HEADER("stdio.h", define = "HAVE_STDIO_H")

Migration Patterns

Pattern 1: Simple Header Checks

M4:

AC_CHECK_HEADER([stdio.h])
AC_CHECK_HEADERS([stdlib.h string.h unistd.h])

Bazel:

# Singular AC_CHECK_HEADER → checks.AC_CHECK_HEADER
checks.AC_CHECK_HEADER("stdio.h", define = "HAVE_STDIO_H")

# Plural AC_CHECK_HEADERS → macros.AC_CHECK_HEADERS (preferred)
# Returns a list of checks with auto-generated HAVE_<HEADER> defines.
macros.AC_CHECK_HEADERS(["stdlib.h", "string.h", "unistd.h"])

Rules:

  • Remove square brackets, add quotes
  • For plural AC_CHECK_HEADERS, prefer macros.AC_CHECK_HEADERS([...]) over splitting into individual checks.AC_CHECK_HEADER calls — the macro returns a list of checks with auto-generated HAVE_<HEADER> defines, matching upstream behavior with less boilerplate
  • Splitting into individual checks.AC_CHECK_HEADER calls is only needed when you must customize per-header parameters (e.g., different requires, includes, or a non-standard define name)
  • Add define = "HAVE_<HEADER>" to singular calls to create defines in config.h

Pattern 2: Function Checks

M4:

AC_CHECK_FUNC([malloc])
AC_CHECK_FUNCS([printf scanf fopen])

Bazel:

# Singular AC_CHECK_FUNC → checks.AC_CHECK_FUNC
checks.AC_CHECK_FUNC("malloc", define = "HAVE_MALLOC")

# Plural AC_CHECK_FUNCS → macros.AC_CHECK_FUNCS (preferred)
macros.AC_CHECK_FUNCS(["printf", "scanf", "fopen"])

Rules:

  • For plural AC_CHECK_FUNCS, prefer macros.AC_CHECK_FUNCS([...]); it generates HAVE_<FUNCTION> defines for each entry
  • Use individual checks.AC_CHECK_FUNC calls only when per-function customization is required
  • Add define = "HAVE_<FUNCTION>" for singular calls
  • Do not replace an upstream AC_CHECK_FUNC with a hand-written AC_TRY_LINK / AC_TRY_COMPILE because the probe's answer looks wrong on some platform. That changes the cache name (ac_cv_func_<function>) that downstream conditions and published overlays reference, and it hides a checker bug. Rerun with --action_env=RULES_CC_AUTOCONF_DEBUG=debug and file an issue with the probe output instead; AC_CHECK_FUNC is meant to answer exactly as GNU autoconf does

Pattern 3: Type Checks

M4:

AC_CHECK_TYPE([size_t])
AC_CHECK_TYPES([int8_t, int64_t], [], [], [[#include <stdint.h>]])

Bazel:

# Singular: without explicit includes (uses AC_INCLUDES_DEFAULT)
checks.AC_CHECK_TYPE("size_t", define = "HAVE_SIZE_T")

# Plural AC_CHECK_TYPES → macros.AC_CHECK_TYPES (preferred)
# Shared includes apply to every type in the list.
macros.AC_CHECK_TYPES(["int8_t", "int64_t"], includes = ["#include <stdint.h>"])

Pattern 4: Declaration Checks

M4:

AC_CHECK_DECL([NULL], [], [], [[#include <stddef.h>]])
AC_CHECK_DECLS([execvpe, secure_getenv], [], [], [[#include <unistd.h>]])

Bazel:

# Singular AC_CHECK_DECL → checks.AC_CHECK_DECL
checks.AC_CHECK_DECL("NULL", define = "HAVE_DECL_NULL", includes = ["#include <stddef.h>"])

# Plural AC_CHECK_DECLS → macros.AC_CHECK_DECLS (preferred)
# Auto-generates HAVE_DECL_<SYMBOL> defines for each entry.
macros.AC_CHECK_DECLS(["execvpe", "secure_getenv"], includes = ["#include <unistd.h>"])

Note: AC_CHECK_DECL differs from AC_CHECK_FUNC — it checks if something is declared (not just defined as a macro).


Pattern 5: Member Checks

M4:

AC_CHECK_MEMBER([struct stat.st_rdev], [], [], [[#include <sys/stat.h>]])
AC_CHECK_MEMBERS([struct tm.tm_zone, struct tm.tm_gmtoff], [], [], [[#include <time.h>]])

Bazel:

# Singular AC_CHECK_MEMBER → checks.AC_CHECK_MEMBER
checks.AC_CHECK_MEMBER(
    "struct stat.st_rdev",
    define = "HAVE_STRUCT_STAT_ST_RDEV",
    includes = ["#include <sys/stat.h>"],
)

# Plural AC_CHECK_MEMBERS → macros.AC_CHECK_MEMBERS (preferred)
# Auto-generates HAVE_STRUCT_<AGGREGATE>_<MEMBER> defines for each entry.
macros.AC_CHECK_MEMBERS(
    ["struct tm.tm_zone", "struct tm.tm_gmtoff"],
    includes = ["#include <time.h>"],
)

Pattern 6: Size and Alignment Checks

M4:

AC_CHECK_SIZEOF([int])
AC_CHECK_SIZEOF([size_t], [], [[#include <stddef.h>]])
AC_CHECK_ALIGNOF([double])

Bazel:

checks.AC_CHECK_SIZEOF("int", define = "SIZEOF_INT")
checks.AC_CHECK_SIZEOF("size_t", define = "SIZEOF_SIZE_T", includes = ["#include <stddef.h>"])
checks.AC_CHECK_ALIGNOF("double", define = "ALIGNOF_DOUBLE")

Without includes, the probe compiles with autoconf's default includes (<sys/types.h> among them), as AC_CHECK_SIZEOF([off_t]) does in configure.

Note: In rules_cc_autoconf, AC_CHECK_SIZEOF / AC_CHECK_ALIGNOF are implemented with compile-time probing (not by executing a test binary). They still encode facts about the target toolchain, so interpret results carefully when cross-compiling (see Cross-Compilation).


Pattern 7: Library Checks

M4:

AC_CHECK_LIB([m], [cos])
AC_CHECK_LIB([pthread], [pthread_create])

Bazel:

checks.AC_CHECK_LIB("m", "cos", define = "HAVE_LIBM")
checks.AC_CHECK_LIB("pthread", "pthread_create", define = "HAVE_LIBPTHREAD")

Pattern 7b: AC_SEARCH_LIBS and autoconf_linkopts

M4:

AC_SEARCH_LIBS([clock_gettime], [rt posix4], [], [], [AC_MSG_ERROR([...])])
# LIBS now contains -lrt when needed

Bazel (check + propagate linker flags):

load("@rules_cc_autoconf//autoconf:autoconf.bzl", "autoconf")
load("@rules_cc_autoconf//autoconf:autoconf_linkopts.bzl", "autoconf_linkopts")
load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")

autoconf(
    name = "autoconf",
    checks = [
        checks.AC_SEARCH_LIBS(
            "clock_gettime",
            ["rt", "posix4"],
            subst = "CLOCK_TIME_LIB",
        ),
    ],
    deps = [":package"],
)

autoconf_linkopts(
    name = "linkopts",
    vars = ["CLOCK_TIME_LIB"],
    deps = [":autoconf"],
)

cc_library(
    name = "mylib",
    srcs = ["lib.c"],
    deps = [":linkopts"],
)

The checker records a subst value: empty string if the symbol links without extra libraries, otherwise a flag such as -lrt. The autoconf_linkopts rule reads those result files and exposes CcInfo so dependents get the right link flags. On GCC/Clang, flags are passed via a linker response file; on msvc-cl / clang-cl, the rule uses an MSVC-compatible path (see autoconf_linkopts).


Pattern 8: Compiler Flag Checks

M4:

AC_MSG_CHECKING([whether $CC accepts -Wall])
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS -Wall"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])],
  [AC_MSG_RESULT([yes]); AC_DEFINE([HAVE_FLAG_WALL], [1])],
  [AC_MSG_RESULT([no])])
CFLAGS="$save_CFLAGS"

Bazel:

checks.AC_CHECK_C_COMPILER_FLAG("-Wall", define = "HAVE_FLAG_WALL")
checks.AC_CHECK_CXX_COMPILER_FLAG("-std=c++17", define = "HAVE_FLAG_STD_C__17")

Pattern 9: Custom Compile Tests

M4:

AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <stdatomic.h>
]], [[
atomic_int x = 0;
(void)x;
]])], [AC_DEFINE([HAVE_STDATOMIC], [1])], [])

Bazel:

checks.AC_TRY_COMPILE(
    code = """
#include <stdatomic.h>
int main(void) {
    atomic_int x = 0;
    (void)x;
    return 0;
}
""",
    define = "HAVE_STDATOMIC",
)

Alternative using utils.AC_LANG_PROGRAM:

load("//autoconf:checks.bzl", "checks", "utils")

checks.AC_TRY_COMPILE(
    code = utils.AC_LANG_PROGRAM(
        ["#include <stdatomic.h>"],  # prologue
        "atomic_int x = 0; (void)x;",  # body of main()
    ),
    define = "HAVE_STDATOMIC",
)

M4:

AC_LINK_IFELSE([AC_LANG_PROGRAM([[
#include <langinfo.h>
]], [[
char* cs = nl_langinfo(CODESET);
return !cs;
]])], [AC_DEFINE([HAVE_LANGINFO_CODESET], [1])], [])

Bazel:

checks.AC_TRY_LINK(
    code = utils.AC_LANG_PROGRAM(
        ["#include <langinfo.h>"],
        "char* cs = nl_langinfo(CODESET); return !cs;",
    ),
    define = "HAVE_LANGINFO_CODESET",
)

Pattern 11: Unconditional Defines

M4:

AC_DEFINE([CUSTOM_VALUE], [42])
AC_DEFINE([ENABLE_FEATURE], [1])
AC_DEFINE([PROJECT_NAME], ["MyProject"])

Bazel:

checks.AC_DEFINE("CUSTOM_VALUE", "42")
checks.AC_DEFINE("ENABLE_FEATURE", "1")
checks.AC_DEFINE("PROJECT_NAME", '"MyProject"')  # Note: inner quotes for string literal

Pattern 12: Conditional Defines

M4:

if test "$ac_cv_func_lstat" = yes; then
  AC_DEFINE([HAVE_LSTAT], [1])
fi

Bazel:

# Gate the define on the check result
checks.AC_DEFINE(
    "HAVE_LSTAT",
    requires = ["ac_cv_func_lstat==1"],  # Only define if check passed
)

Pattern 13: Substitution Variables (AC_SUBST)

M4:

REPLACE_FSTAT=1
AC_SUBST([REPLACE_FSTAT])

Bazel:

checks.AC_SUBST("REPLACE_FSTAT", "1")

For conditional subst values:

checks.AC_SUBST(
    "REPLACE_STRERROR",
    condition = "_gl_cv_func_strerror_0_works",
    if_true = "0",
    if_false = "1",
)

Pattern 14: M4 Shell Variables

Many M4 macros use shell variables (not AC_DEFINE calls). Use M4_VARIABLE to track these:

M4:

REPLACE_FSTAT=1
HAVE_WORKING_MKTIME=0

Bazel:

checks.M4_VARIABLE("REPLACE_FSTAT", "1")
checks.M4_VARIABLE("HAVE_WORKING_MKTIME", "0")

When M4_VARIABLE is required (not just nice-to-have)

A value belongs in M4_VARIABLE whenever other M4 modules read it to make decisions, even if the producing module never calls AC_DEFINE or AC_SUBST on it.

Common signals that an M4 shell variable must be tracked with M4_VARIABLE:

  • The producing .m4 file sets it as a plain shell assignment (HAVE_FOO=1, GL_GENERATE_FOO_H=true) and other .m4 files branch on it (if test $HAVE_FOO = 1; then ...).
  • The variable controls whether a sibling module installs a replacement header (GL_GENERATE_<NAME>_H).
  • The variable feeds a consumer's requires = ["HAVE_FOO==1"] — without M4_VARIABLE the requirement can never be satisfied because no check produces the value.

Examples that are easy to miss:

# M4 sets `HAVE_OBSTACK=1` (or 0) and `GL_GENERATE_OBSTACK_H=true|false`
# as bare shell variables. They are NOT AC_SUBSTed, but other gnulib
# modules branch on them — so they must be tracked.
checks.M4_VARIABLE("HAVE_OBSTACK", 1),
checks.M4_VARIABLE("GL_GENERATE_OBSTACK_H", "false"),

Rule of thumb: if removing the variable from the original M4 would change the behavior of a different AC_DEFUN, it is a consumer-visible decision input and needs M4_VARIABLE. Use AC_SUBST instead only when the value is also meant to land in a generated template file (@VAR@).


Pattern 15: Language Selection

M4:

AC_LANG_PUSH([C++])
AC_CHECK_HEADER([iostream])
AC_LANG_POP([C++])

Bazel:

checks.AC_CHECK_HEADER("iostream", define = "HAVE_IOSTREAM", language = "cpp")

Pattern 16: Dependencies with requires

When a check depends on a previous check's result:

M4:

AC_CHECK_HEADER([stdio.h])
AC_CHECK_FUNC([fopen], [], [], [[#include <stdio.h>]])

Bazel:

checks.AC_CHECK_HEADER("stdio.h", define = "HAVE_STDIO_H")
checks.AC_CHECK_FUNC(
    "fopen",
    define = "HAVE_FOPEN",
    # Only check if stdio.h exists. The cache name is the consistent form;
    # the bare define name "HAVE_STDIO_H" also resolves while a single
    # check publishes it.
    requires = ["ac_cv_header_stdio_h"],
)

Value-based requirements:

checks.AC_CHECK_FUNC(
    "fstat64",
    define = "HAVE_FSTAT64",
    requires = ["REPLACE_FSTAT==1"],  # Only if REPLACE_FSTAT equals "1"
)

Pattern 17: Compile Defines

When test code needs defines from previous checks:

checks.AC_TRY_COMPILE(
    code = """
#include <sys/stat.h>
int main(void) {
    struct stat s;
    return s.st_rdev;
}
""",
    define = "HAVE_ST_RDEV",
    compile_defines = ["_GNU_SOURCE", "_DARWIN_C_SOURCE"],
)

API Reference

checks Struct — Singular Macros

MacroDescription
AC_CHECK_HEADER(header, ...)Check for a header file
AC_CHECK_FUNC(function, ...)Check for a function
AC_CHECK_TYPE(type_name, ...)Check for a type
AC_CHECK_DECL(symbol, ...)Check for a declaration
AC_CHECK_MEMBER(aggregate.member, ...)Check for a struct/union member
AC_CHECK_SIZEOF(type_name, ...)Check size of a type
AC_CHECK_ALIGNOF(type_name, ...)Check alignment of a type
AC_CHECK_LIB(library, function, ...)Check for a function in a library
AC_SEARCH_LIBS(function, libraries, ...)Find which library provides a symbol; set subst to "" or -lname (use with autoconf_linkopts)
AC_TRY_COMPILE(code=..., ...)Try to compile custom code
AC_TRY_LINK(code=..., ...)Try to compile and link custom code
AC_DEFINE(define, value=1, ...)Define a preprocessor macro
AC_DEFINE_UNQUOTED(define, ...)Define with unquoted value
AC_SUBST(variable, value=1, ...)Create a substitution variable
M4_VARIABLE(define, value=1, ...)Track M4 shell variables
AC_PROG_CC()Check for C compiler
AC_PROG_CXX()Check for C++ compiler
AC_PROG_CC_C_O()Check cc -c -o support
AC_FAIL(define, ...)Check that always fails; emit #undef (e.g. little-endian branch for WORDS_BIGENDIAN; GNU AC_C_BIGENDIAN is usually modeled with select() + AC_DEFINE / AC_FAIL)
AC_C_RESTRICT()Check for restrict keyword
AC_COMPUTE_INT(define, expression, ...)Compute integer at compile time (compile-time probe in this implementation)
AC_CHECK_C_COMPILER_FLAG(flag, ...)Check C compiler flag
AC_CHECK_CXX_COMPILER_FLAG(flag, ...)Check C++ compiler flag
AC_BUILD_SETTING(target=..., name=..., ...)Expose a Bazel build setting (string_flag, bool_flag, int_flag) as a define and/or subst without compiling anything. Passed through the build_settings attribute of autoconf, not checks

Composite GNU macros such as AC_C_INLINE, AC_C_BIGENDIAN, AC_SYS_LARGEFILE, AC_FUNC_ALLOCA or AC_TYPE_SIZE_T are not members of checks. They are ready-made targets under //autoconf/macros/ (e.g. //autoconf/macros/AC_C_INLINE); add them to deps. The generated checks reference lists every member of checks, macros and utils with its full signature.

macros Struct — Plural Macros

These return lists of checks with auto-generated define names:

MacroDescription
AC_CHECK_HEADERS(headers, ...)Check multiple headers
AC_CHECK_FUNCS(functions, ...)Check multiple functions
AC_CHECK_TYPES(types, ...)Check multiple types
AC_CHECK_DECLS(symbols, ...)Check multiple declarations
AC_CHECK_MEMBERS(members, ...)Check multiple struct members

utils Struct — Helper Functions

FunctionDescription
AC_LANG_PROGRAM(prologue, body)Build program code from prologue and body
AC_INCLUDES_DEFAULTDefault includes (stdio.h, stdlib.h, etc.)

gnulib macros Struct

Ports under //gnulib/m4 also have gnulib-specific helpers. They are loaded under a different name to avoid clashing with macros from checks.bzl:

load("//gnulib:macros.bzl", gl_macros = "macros")
MacroM4 equivalent
GL_CHECK_FUNCS_ANDROID(functions, includes = ...)gl_CHECK_FUNCS_ANDROID
GL_CHECK_FUNCS_MACOS(functions, includes = ...)gl_CHECK_FUNCS_MACOS
GL_CHECK_FUNCS_ANDROID_MACOS(functions, includes = ...)gl_CHECK_FUNCS_ANDROID_MACOS
GL_CHECK_NEXT_HEADERS(headers)gl_CHECK_NEXT_HEADERS
GL_NEXT_HEADERS(headers)gl_NEXT_HEADERS
AC_LIB_HAVE_LINKFLAGS(...)AC_LIB_HAVE_LINKFLAGS

Common Parameters

Most macros support these parameters:

ParameterTypeDescription
namestringCache variable name (auto-generated if omitted)
definestring/boolDefine name or True to use cache var name
includeslistInclude directives (e.g., ["#include <stdio.h>"])
languagestring"c" or "cpp"
requireslistDependencies that must be satisfied
compile_defineslistNames of results from earlier checks (e.g. ["_GNU_SOURCE"]); each is prepended to the probe as #define NAME value. Not raw macro definitions: _GNU_SOURCE and friends come from //gnulib/m4/extensions
coptslistExtra compiler flags for this probe only (e.g. ["-std=c11"]), appended after the toolchain's flags
conditionstringCondition for value selection
if_trueanyValue when condition is true
if_falseanyValue when condition is false
substbool/stringAlso create substitution variable

Platform Conditionals

Prefer actual checks over select()+AC_DEFINE

Do not use select() to gate AC_DEFINE or AC_SUBST for feature macros (e.g. HAVE_FOO) when the M4 performs a real check. Use the actual check so the result reflects the toolchain/platform.

When M4 uses a check macro such as:

  • gl_CHECK_FUNCS_ANDROID([func], [[#include <header.h>]])
  • AC_CHECK_FUNC([func])
  • AC_CHECK_HEADER([header.h])

Prefer:

  1. Bazel equivalent check — e.g. gl_macros.GL_CHECK_FUNCS_ANDROID(["func"], includes = ["#include <header.h>"]) (loaded from //gnulib:macros.bzl, see gnulib macros) or checks.AC_CHECK_FUNC("func", define = "HAVE_FUNC", subst = "HAVE_FUNC").
  2. Depend on the gnulib module that already implements the check — e.g. deps = ["//gnulib/m4/timespec_getres:gl_FUNC_TIMESPEC_GETRES"] instead of defining HAVE_TIMESPEC_GETRES via select().

Avoid:

  • select({ "@platforms//os:linux": [checks.AC_DEFINE("HAVE_FOO", "1")], "//conditions:default": [] }) to hardcode a feature per platform.
  • Duplicating the same check in multiple targets; depend on the canonical module that performs it.

This keeps config consistent with the actual build environment and avoids duplicate definitions when targets are aggregated (e.g. //gnulib/tests/duplicates:gnulib).

Using select() for Platform-Specific Checks

M4:

AC_REQUIRE([AC_CANONICAL_HOST])
case "$host_os" in
  mingw* | windows*)
    REPLACE_ACCESS=1
    ;;
  darwin*)
    REPLACE_FSTAT=1
    ;;
  *)
    ;;
esac

Bazel:

autoconf(
    name = "fstat",
    checks = select({
        "@platforms//os:windows": [
            checks.AC_SUBST("REPLACE_FSTAT", "1"),
        ],
        "@platforms//os:macos": [
            checks.AC_SUBST("REPLACE_FSTAT", "1"),
        ],
        "//conditions:default": [],
    }),
    visibility = ["//visibility:public"],
)

Common Platform Constraints

M4 PatternBazel Constraint
mingw*, windows*@platforms//os:windows
darwin*@platforms//os:macos
linux*@platforms//os:linux
freebsd*@platforms//os:freebsd
openbsd*@platforms//os:openbsd
Default (*)"//conditions:default"

Select on the OS or on the compiler?

Decide by what the M4 branch is actually about:

  • case $host_os branches describe the libc and OS (mingw* | windows* covers both MSVC and MinGW builds). Keep those on @platforms//os:* as in the table above.
  • Anything about the compiler driver selects on @rules_cc//cc/compiler:*: compiler flags (/w vs -w), defines spelled as options (/D vs -D), link libraries (advapi32.lib vs -ladvapi32), and probes that are really #ifdef _MSC_VER checks.

Clang targeting MinGW reports itself as plain clang on Windows, so a Windows arm that is really an MSVC arm must not be keyed on the OS. The pattern used by the integration overlays:

load("@bazel_skylib//lib:selects.bzl", "selects")

# clang driving the MinGW target (rules_cc reports it as plain `clang`).
config_setting(
    name = "windows_clang",
    constraint_values = ["@platforms//os:windows"],
    flag_values = {"@rules_cc//cc/compiler": "clang"},
)

# MSVC-style drivers.
selects.config_setting_group(
    name = "msvc_like",
    match_any = [
        "@rules_cc//cc/compiler:msvc-cl",
        "@rules_cc//cc/compiler:clang-cl",
    ],
)

cc_library(
    name = "mylib",
    linkopts = select({
        ":msvc_like": ["advapi32.lib", "bcrypt.lib"],
        ":windows_clang": ["-ladvapi32", "-lbcrypt"],
        "//conditions:default": [],
    }),
)

Most existing gnulib ports select Windows behaviour on @platforms//os:windows and have been validated against MSVC only. When touching one, ask which of the two kinds its branch is.

Combining Platform-Specific and Common Checks

autoconf(
    name = "lstat",
    checks = [
        # Common checks for all platforms
        checks.AC_CHECK_FUNC("lstat", define = "HAVE_LSTAT"),
    ] + select({
        "@platforms//os:macos": [
            checks.AC_SUBST("REPLACE_LSTAT", "1"),
        ],
        "//conditions:default": [
            checks.AC_SUBST("REPLACE_LSTAT", "0"),
        ],
    }),
    visibility = ["//visibility:public"],
)

Dependencies and Reusable Modules

Using Pre-built //gnulib/m4 Targets

Many common checks are already implemented in @rules_cc_autoconf//gnulib/m4/. Use these to avoid duplication:

Before (manual checks):

autoconf(
    name = "autoconf",
    checks = [
        checks.AC_CHECK_FUNC("lstat", define = "HAVE_LSTAT"),
        checks.AC_CHECK_HEADER("sys/stat.h", define = "HAVE_SYS_STAT_H"),
    ],
)

After (using gnulib modules):

autoconf(
    name = "autoconf",
    checks = [
        # Only add checks not provided by gnulib modules
    ],
    deps = [
        "//gnulib/m4/lstat",      # Provides lstat checks
        "//gnulib/m4/sys_stat_h", # Provides sys/stat.h checks
    ],
)

Common Patterns for gnulib Target Names

Check TypeTarget PatternExample
Function//gnulib/m4/<func>//gnulib/m4/lstat
Header//gnulib/m4/<header>_h//gnulib/m4/sys_stat_h
Type//gnulib/m4/<type>//gnulib/m4/off_t

Declaring Dependencies Between Modules

When creating reusable modules, use deps to express AC_REQUIRE relationships:

# gl_FUNC_LSTAT from lstat.m4
autoconf(
    name = "lstat",
    checks = [
        checks.AC_CHECK_FUNC("lstat", define = "HAVE_LSTAT"),
    ],
    deps = [
        ":gl_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK",  # AC_REQUIRE
        "//autoconf/macros/AC_CANONICAL_HOST",
    ],
)

Consuming Results Downstream

Everything above produces results. A downstream BUILD.bazel (your project, or a Bazel Central Registry overlay) consumes them through autoconf_hdr, autoconf_srcs and autoconf_linkopts. Four rules matter there.

Name results by their cache variable

Every check records one result under a unique cache name. Header templates use the define or subst name, but requires, condition and autoconf_srcs conditions look a name up across the cache, define and subst buckets:

CheckCache name
AC_CHECK_HEADER("foo.h")ac_cv_header_foo_h
AC_CHECK_FUNC("foo")ac_cv_func_foo
AC_CHECK_DECL("foo")ac_cv_have_decl_foo
AC_CHECK_TYPE("foo_t")ac_cv_type_foo_t
AC_DEFINE("HAVE_FOO")ac_cv_define_HAVE_FOO
AC_SUBST("REPLACE_FOO")ac_cv_subst_REPLACE_FOO
M4_VARIABLE("GL_GENERATE_FOO_H")GL_GENERATE_FOO_H
any check with name = "gl_cv_x"gl_cv_x

The cache name is the consistent form and always means that one check. A bare HAVE_FOO or REPLACE_FOO also resolves, and is convenient when you want whichever check, or toolchain default, publishes that name. If a bare name resolves to two distinct results across your deps, the build fails and prints the candidates (and, when nothing matches, the full list of available names); switch to the cache name of the check you mean. Cache names and check shapes are public API: published overlays reference the ports' probe names, including underscore-prefixed ones such as _gl_cv_func_opendir, so a port must not rename a probe or change which check kind produces a name.

Conditional sources with autoconf_srcs

load("@rules_cc_autoconf//autoconf:autoconf_srcs.bzl", "autoconf_srcs")

autoconf_srcs(
    name = "gnulib_conditional_srcs",
    srcs = {
        # gnulib compiles a replacement when HAVE_X = 0 || REPLACE_X = 1
        "lib/readlink.c": "!ac_cv_func_readlink || REPLACE_READLINK",
        # GL_GENERATE_ERRNO_H holds the words true/false, both truthy: compare it
        "lib/strerror-override.c": "GL_GENERATE_ERRNO_H==true || !_gl_cv_func_strerror_0_works",
    },
    deps = [
        "@rules_cc_autoconf//gnulib/m4/readlink",
        "@rules_cc_autoconf//gnulib/m4/strerror",
    ],
)

Each source is compiled only when its expression is true; the grammar is the one in Expression syntax. The gnulib rule is HAVE_X = 0 || REPLACE_X = 1; checking only REPLACE_X misses every libc that lacks the function (MSVC, often MinGW).

By default the generated file is a copy of the source, which breaks the source's own quoted includes of files next to it (lib/unictype/*.c include "bitmap.h"): the copy lives in the output tree, where those files are not. wrap = "include" makes the generated file a one-line #include "<path of the source>" instead, so such includes resolve where the source lives and diagnostics name the real file. The sources must then reach the compiler: list the autoconf_srcs target in the library's deps as well as its srcs (it provides the sources as headers of a CcInfo). The wrapper is named with the .ac infix (foo.c -> foo.ac.c) whatever naming says: a wrapper named like a root-level source would resolve its own #include "foo.c" to itself, since quoted includes search the including file's directory first. The converter emits this form.

A standard for every check

Checks compile with Bazel's --copt, --conlyopt and --cxxopt (the CFLAGS of a configure run), then the autoconf target's own copts, conlyopts and cxxopts (as on a cc_library), then a check's own copts. A target's flags are its own: they do not reach the ports of this repository or any other dependency, and results computed under them are not shared with targets that lack them.

A language standard is not something a port or a project sets for itself. The standard of a build belongs to the root module, as it does for every other C and C++ dependency: --cxxopt=-std=c++17 (and --host_cxxopt) in .bazelrc, or a feature of your toolchain enabled with --features, which the checks honour like cc_library does. Either way every check, port and compile sees one standard, in one configuration, with the toolchain's cached results intact. A module that put -std= in its own copts would override that choice: flags come in the order toolchain, --copt / --conlyopt / --cxxopt, target copts / conlyopts / cxxopts, then --per_file_copt (or a check's copts), and every compiler takes the last standard it sees. When configure.ac makes a standard mandatory (AX_CXX_COMPILE_STDCXX(17, noext, mandatory), AC_PROG_CC_C99), translate it the way configure behaves when the compiler cannot provide it: a check that probes __cplusplus or __STDC_VERSION__ and stops the build with a message naming the flag to add. That is checks.AC_LANG_STANDARD, which compares the standard the compiler runs in (__STDC_VERSION__, or __cplusplus / _MSVC_LANG) with the one asked for, at least that version unless compare says otherwise, and with error = True stops the build with a message that spells the flag for GCC, clang and MSVC. checks.AX_CXX_COMPILE_STDCXX("17", "noext") is the autoconf-archive macro on top of it, defining HAVE_CXX17:

checks.AC_LANG_STANDARD("c11", error = True)
checks.AC_LANG_STANDARD("c++17", define = "HAVE_CXX17")

error is also available on AC_TRY_COMPILE and AC_TRY_LINK, as the AC_MSG_ERROR of the branch configure takes when a test does not pass. Such a check needs no define: it is a validation action, run whenever the target is built, directly or through a dependency, even though nothing in config.h reads its result.

Libraries the checks need: cc_deps

A configure script probes with the project's dependencies on the include and link paths: CPPFLAGS and LIBS carry them, so AC_CHECK_LIB([gmp], ...) links against gmp and a probe may #include <gmp.h>. In Bazel those dependencies are cc_library targets, and the checks of an autoconf target see them through cc_deps:

autoconf(
    name = "autoconf",
    cc_deps = ["@gmp//:gmp"],
    checks = [
        checks.AC_CHECK_LIB("gmp", "__gmpz_init"),
        checks.AC_CHECK_HEADER("gmp.h"),
    ],
)

Every check of the target compiles with the dependencies' include directories and defines and links their archives after its own object, with each archive's directory on the library search path so the -l<name> of an AC_CHECK_LIB resolves to the dependency's lib<name>.a by name; the probe answers as it would with the library installed. The dependencies' link flags come along with the files they name (additional_linker_inputs, such as a version script), so the link succeeds in the sandbox. The dependencies' flags are part of each check's identity: the same check with and without them never shares a result. Prefer this over a static AC_DEFINE([HAVE_LIBGMP]), which asserts what the probe can establish.

Defaults yield to your values

The *_H_DEFAULTS values (HAVE_FOO=1, REPLACE_FOO=0, GNULIB_FOO=0) are not check results. Each header port keeps them in an autoconf_defaults target named :defaults (//gnulib/m4/stdlib_h:defaults), and targets read them through default_deps, never deps: the header port lists its own :defaults there, and so does every port whose m4 AC_REQUIREs that header's gl_*_H_DEFAULTS. An autoconf target passes the defaults it reads on to its dependents, still as defaults. A default has the lowest precedence: an ordinary result of the same name, from any check in the graph or from your own target, overrides it without a duplicate error, and two defaults of one name must agree. Two consequences:

  • Do not design around the defaults targets. Depend on the header port your templates need and define what your project computes itself.
  • When a later release's port starts publishing a value you set locally as an ordinary result, your copy becomes a duplicate-result error. Comment such overrides with the port they stand in for so they are easy to remove.

Your own defaults follow the same rule: put values that a probe may override into an autoconf_defaults target and list it in default_deps. defaults = True on autoconf_hdr, autoconf_srcs, autoconf_linkopts and cc_gnulib_conditional_hdrs is deprecated and ignored: the toolchain carries no defaults any more. Existing BUILD files keep loading; new ones should leave it unset and list default_deps where a template reads a header's defaults no dependency brings.

Refining platform selects

checks accepts select() keyed on any config_setting. Where a port had to choose a default for a fact only a runtime test could establish (see Cross-Compilation), your own targets can key on custom constraint_values that describe your environments more precisely and set the value accordingly.


Porting Strategy

When porting a gnulib M4 module to Bazel, follow this systematic approach:

Step 1: Fetch and Analyze the Original M4 File

First, read the original M4 file to understand its structure. The M4 file typically contains multiple AC_DEFUN macro definitions.

# Example: c32rtomb.m4
AC_DEFUN([gl_FUNC_C32RTOMB],           # Line 10 - Main function
[
  AC_REQUIRE([gl_UCHAR_H_DEFAULTS])    # Dependency (ignore _DEFAULTS)
  AC_REQUIRE([AC_CANONICAL_HOST])       # Dependency
  AC_REQUIRE([gl_MBRTOC32_SANITYCHECK]) # Dependency
  AC_REQUIRE([gl_C32RTOMB_SANITYCHECK]) # Dependency
  AC_REQUIRE([gl_CHECK_FUNC_C32RTOMB])  # Dependency
  ...
])

AC_DEFUN([gl_CHECK_FUNC_C32RTOMB],     # Line 59 - Helper function
[
  ...
])

AC_DEFUN([gl_C32RTOMB_SANITYCHECK],    # Line 94 - Sanity check
[
  AC_REQUIRE([gl_TYPE_CHAR32_T])
  AC_REQUIRE([gl_CHECK_FUNC_C32RTOMB])
  ...
])

Step 2: Build the Dependency Graph

Critical: Extract all AC_REQUIRE statements to build the dependency graph, with one important exception:

_DEFAULTS functions are a default_deps entry, not a deps entry. Functions like gl_UCHAR_H_DEFAULTS and gl_WCHAR_H_DEFAULTS set the initial shell variable values (HAVE_X=1, REPLACE_X=0) that the check functions may reassign. The header port keeps them in an autoconf_defaults target, :defaults, so AC_REQUIRE([gl_WCHAR_H_DEFAULTS]) becomes "//gnulib/m4/wchar_h:defaults" in default_deps; a check's own result overrides a default without a duplicate error. deps rejects defaults targets, so the two cannot be confused. Do not depend on the private gl_*_H_DEFAULTS or gl_*_H_REQUIRE_DEFAULTS targets directly.

Dependency extraction example:

gl_FUNC_C32RTOMB requires:
  - gl_UCHAR_H_DEFAULTS    ← IGNORE (ends in _DEFAULTS)
  - AC_CANONICAL_HOST      ← Include
  - gl_MBRTOC32_SANITYCHECK ← Include (from different module)
  - gl_C32RTOMB_SANITYCHECK ← Include (local target)
  - gl_CHECK_FUNC_C32RTOMB  ← Include (local target)

gl_C32RTOMB_SANITYCHECK requires:
  - gl_TYPE_CHAR32_T        ← Include (provided by uchar_h)
  - gl_CHECK_FUNC_C32RTOMB  ← Include (local target)

Step 3: Create Bazel Targets in M4 Order

Create one autoconf target for each AC_DEFUN, in the same order as they appear in the M4 file. This makes it easier to compare the Bazel code with the original M4.

Important convention: The target matching the package name (e.g., c32rtomb in the c32rtomb package) should:

  • Have no checks (checks = [] or omitted)
  • Have deps on all other targets in the same package
  • Exclude *_DEFAULTS targets from deps

This pattern separates the "what this module provides" (the package-named aggregator target) from "how it works" (the individual AC_DEFUN targets with checks).

"""https://github.com/coreutils/gnulib/blob/.../m4/c32rtomb.m4"""

load("//autoconf:autoconf_toolchain.bzl", autoconf = "autoconf_cache")
load("//autoconf:checks.bzl", "checks")

# gl_FUNC_C32RTOMB - lines 10-56 (FIRST AC_DEFUN in M4)
autoconf(
    name = "gl_FUNC_C32RTOMB",
    checks = [
        checks.AC_SUBST("HAVE_C32RTOMB", condition = "gl_cv_func_c32rtomb", ...),
        checks.AC_SUBST("REPLACE_C32RTOMB", ...),
    ],
    deps = [
        "//autoconf/macros/AC_CANONICAL_HOST",
        "//gnulib/m4/mbrtoc32:gl_MBRTOC32_SANITYCHECK",
        ":gl_C32RTOMB_SANITYCHECK",
        ":gl_CHECK_FUNC_C32RTOMB",
    ],
)

# gl_CHECK_FUNC_C32RTOMB - lines 59-92 (SECOND AC_DEFUN)
autoconf(
    name = "gl_CHECK_FUNC_C32RTOMB",
    checks = [
        checks.AC_CHECK_DECL("c32rtomb", ...),
        checks.AC_TRY_LINK(name = "gl_cv_func_c32rtomb", ...),
    ],
)

# gl_C32RTOMB_SANITYCHECK - lines 94-171 (THIRD AC_DEFUN)
autoconf(
    name = "gl_C32RTOMB_SANITYCHECK",
    checks = [
        checks.AC_DEFINE("HAVE_WORKING_C32RTOMB", ...),
        checks.AC_SUBST("HAVE_WORKING_C32RTOMB", ...),
    ],
    deps = [
        "//gnulib/m4/uchar_h",
        ":gl_CHECK_FUNC_C32RTOMB",
    ],
)

# Package-level aggregator target (matches package name)
# No checks - just deps on all other targets (excluding *_DEFAULTS)
autoconf(
    name = "c32rtomb",
    visibility = ["//visibility:public"],
    deps = [
        ":gl_FUNC_C32RTOMB",
        ":gl_CHECK_FUNC_C32RTOMB",
        ":gl_C32RTOMB_SANITYCHECK",
    ],
)

Step 4: Handle Shared Checks Without AC_REQUIRE

Multiple M4 files often perform the same check (e.g., AC_CHECK_HEADERS_ONCE([utmp.h])) without having an AC_REQUIRE relationship between them. In Bazel, this would cause duplicate check errors.

Solution: Create an isolated autoconf target that contains only the conflicting check, then add it as a dependency to both consumers. Do not remove modules from the duplicates test as a workaround.

Example: utmp.h header check

The utmp_h module checks for utmp.h:

# utmp_h.m4
AC_DEFUN([gl_UTMP_H], [
  AC_CHECK_HEADERS([utmp.h])
  ...
])

The readutmp module also checks for utmp.h:

# readutmp.m4
AC_DEFUN([gl_READUTMP], [
  AC_CHECK_HEADERS_ONCE([utmp.h utmpx.h])  # Same check, no AC_REQUIRE!
  ...
])

There's no AC_REQUIRE([gl_UTMP_H]) in readutmp, but both need the same header check. Create a separate target for just the header check:

# gnulib/m4/utmp_h/BUILD.bazel

# Isolated header check - can be shared by multiple modules
autoconf(
    name = "HAVE_UTMP_H",
    checks = [
        checks.AC_CHECK_HEADER("utmp.h", define = "HAVE_UTMP_H"),
    ],
    visibility = ["//visibility:public"],
)

# Main utmp_h module
autoconf(
    name = "utmp_h",
    checks = [
        # Other checks specific to utmp_h...
    ],
    deps = [
        ":HAVE_UTMP_H",  # Use the shared check
    ],
)
# gnulib/m4/readutmp/BUILD.bazel

autoconf(
    name = "readutmp",
    checks = [
        # Other checks specific to readutmp...
    ],
    deps = [
        "//gnulib/m4/utmp_h:HAVE_UTMP_H",  # Use the same shared check
    ],
)

Key principle: Keep the isolated check target in the most semantically relevant package (e.g., the header check for utmp.h lives in the utmp_h package), and have other modules depend on it.

Step 5: Verify and Fix Transitive Dependencies

The rules provide built-in duplication detection. When you build, you'll get clear error messages if a cache variable is defined in multiple places.

After building, you may find that transitive dependencies are incorrect. Common issues:

  1. Duplicate check errors: A define or subst is produced by two targets, or twice within one target

    • Within one target, or between a target and its deps, the build fails with Define variable `X` is duplicated on `//pkg:target` followed by LEFT: / RIGHT: lines naming both sources (likewise Subst variable ...)
    • Between two dependencies it fails with Define 'X' is defined in multiple dependencies with different result files
    • Solution: Remove the local definition and let the dependency provide it
    • Or: Create a shared target (see Step 4 above)
  2. Missing values: Expected substitution variables are not being set

    • Solution: Add the missing dependency or add the check locally
  3. Unexpected values: A transitive dependency is providing values you don't want

    • Solution: Remove the unnecessary dependency or restructure the dependency chain

Step 6: Understand Global Defaults vs Check Results

The same shape as autoconf, with precedence instead of ordering. In autoconf, gl_UCHAR_H_DEFAULTS sets HAVE_C32RTOMB=1 as a shell variable and gl_FUNC_C32RTOMB, if it runs later, may reassign it to 0; the last assignment wins. Bazel has no ordering, so the same two steps are two kinds of target: the header port's autoconf_defaults target :defaults, read through default_deps, provides HAVE_C32RTOMB=1 as a default, and gl_FUNC_C32RTOMB's probe provides an ordinary result that overrides it wherever both are in the graph. A configure.ac that never reaches gl_FUNC_C32RTOMB sees the default, exactly as GNU does.

Porting a _H_DEFAULTS macro. Its assignments become the substs of one autoconf_defaults target (M4_VARIABLE-style shell variables such as GL_GENERATE_FOO_H go in variables, AC_DEFINEs in defines); gl_FOO_H_REQUIRE_DEFAULTS, which adds the GNULIB_* indicators on top, is a second autoconf_defaults target with the first in its deps; the public :defaults aggregates both. A defaults target holds literal values only: nothing is probed, no toolchain is involved, and the files are written at analysis time. A probe that upstream keeps next to the defaults (gl_STDLIB_H's AC_CHECK_HEADERS_ONCE([random.h])) belongs in the header's autoconf target, where it is an ordinary result.

load("@rules_cc_autoconf//autoconf:autoconf_defaults.bzl", "autoconf_defaults")

# gl_UCHAR_H_DEFAULTS
autoconf_defaults(
    name = "gl_UCHAR_H_DEFAULTS",
    substs = {
        "HAVE_C32RTOMB": "1",
        "REPLACE_C32RTOMB": "0",
    },
)

# gl_UCHAR_H_REQUIRE_DEFAULTS: the GNULIB_* indicators, on top of the above
autoconf_defaults(
    name = "gl_UCHAR_H_REQUIRE_DEFAULTS",
    substs = {
        "GNULIB_C32RTOMB": "0",
    },
    deps = [":gl_UCHAR_H_DEFAULTS"],
)

autoconf_defaults(
    name = "defaults",
    visibility = ["//visibility:public"],
    deps = [":gl_UCHAR_H_REQUIRE_DEFAULTS"],
)

Key principle: a port lists in default_deps the :defaults of every header whose gl_*_H_DEFAULTS its m4 AC_REQUIREs (directly or through a macro it invokes), and nothing else of the defaults. The values in a :defaults target mirror the m4's initial assignments; change them only when upstream does.

Conformance disagreements: the GNU conformance test compares subst.h as well as config.h, with the defaults the Bazel target's dependencies bring and nothing else. A disagreement means one of:

  • A missing dependency: the port's m4 requires a header's defaults (or another macro) the BUILD does not list. Add it.
  • A missing override: upstream runs a check that reassigns a default and the port does not. Port the check.
  • An inherent difference (GNU probes the host PATH, runs a test binary whose answer is machine-dependent, or two macros legitimately compute one variable differently): list the variable in the suite's known_divergences with the reason. It is masked from the comparison and always printed in the test log.

Step 7: Run Tests and Iterate

# Build the module
bazel build //gnulib/m4/c32rtomb:c32rtomb

# Run the module's conformance suite
bazel test //gnulib/tests/compat/c32rtomb:all --test_output=errors

Every module has a suite under //gnulib/tests/compat/<module> declared with gnu_gnulib_diff_test_suite:

load("//gnulib/tests:gnu_gnulib_diff_test_suite.bzl", "gnu_gnulib_diff_test_suite")

gnu_gnulib_diff_test_suite(
    name = "c32rtomb_test",
    bazel_autoconf_target = "//gnulib/m4/c32rtomb",
    config_h_in = "config.h.in",
    configure_ac = "configure.ac",
    m4_files = ["@gnulib//:all_m4"],
    subst_h_in = "subst.h.in",
    test_c = "test_c32rtomb.c",
    # Only for inherent differences; each entry needs a reason.
    known_divergences = {
        "CLIX_PATH": "AC_PATH_PROG probes the host PATH; the Bazel port does not search the host",
    },
)

The <name>_gnu_conformance test is the oracle: it runs the pinned GNU aclocal/autoconf/configure with the Bazel C++ toolchain's compiler and flags and requires the Bazel-generated headers to match byte for byte. Fix the port until it passes; list only intentional differences from upstream m4 in known_divergences. No platform needs a checked-in expected output; the oracle produces it. See How the gnulib ports are tested.

Debugging a failing probe. The checker does not record compiler diagnostics. To see why a check failed, rerun with the debug variable passed to the actions:

bazel build //gnulib/m4/c32rtomb --action_env=RULES_CC_AUTOCONF_DEBUG=debug

The conformance test's undeclared outputs directory (bazel-testlogs/.../test.outputs/) contains GNU's config.log, the Bazel headers, and a config.h.diff / subst.h.diff when they differ.

Step 8: Test on Linux (if needed)

When porting modules that have platform-specific behavior from macOS or Windows, you may need to test on Linux to verify correctness. tools/docker_test/docker_test.sh runs Bazel tests inside a Docker container that mirrors the GitHub CI runner:

# Default target set
./tools/docker_test/docker_test.sh

# One module's conformance suite
./tools/docker_test/docker_test.sh //gnulib/tests/compat/c32rtomb/...

# Emulated x86_64 (on an arm64 host)
./tools/docker_test/docker_test.sh --amd64 //gnulib/tests/compat/c32rtomb/...

When to use Linux testing

  • Platform-specific checks: Modules with select() or Linux-only assumptions
  • Cross-platform verification: Confirm conditionals on a real Linux toolchain
  • Test failures: Debug Linux-only CI issues

Best Practices

1. Always Read the Original M4 File

Before migrating, understand what the M4 macro actually does:

  • What checks does it perform?
  • What defines/subst values does it create?
  • What are its dependencies (AC_REQUIRE)?
  • Are there platform-specific conditionals?

2. Use Cache Variable Names Consistently

Follow autoconf naming conventions:

  • Headers: ac_cv_header_<header>
  • Functions: ac_cv_func_<function>
  • Declarations: ac_cv_have_decl_<symbol>
  • Types: ac_cv_type_<type>

3. Prefer //gnulib/m4 Targets

Check if a gnulib module already exists before writing manual checks. This:

  • Avoids duplicate check errors
  • Ensures consistent behavior
  • Handles platform-specific logic

4. Use Meaningful Comments

Reference the original M4 file and line numbers:

"""https://github.com/coreutils/gnulib/blob/635dbdcf501d52d2e42daf6b44261af9ce2dfe38/m4/lstat.m4"""

autoconf(
    name = "lstat",
    checks = [
        # AC_CHECK_FUNCS_ONCE([lstat]) - line 17
        checks.AC_CHECK_FUNC("lstat", define = "HAVE_LSTAT"),
    ],
    deps = [
        # AC_REQUIRE([gl_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK]) - line 19
        ":gl_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK",
    ],
)

5. Test Your Migration

For gnulib ports, gnu_gnulib_diff_test_suite runs GNU autoconf itself against the Bazel output (see How the gnulib ports are tested). For your own checks, run diff tests against golden files to verify your migration produces correct output:

diff_test(
    name = "config_diff_test",
    file1 = "golden_config.h.in",
    file2 = ":config.h",
)

Cross-Compilation Considerations

Design Philosophy: Avoiding Runtime Checks

Bazel intentionally avoids runtime checks to ensure:

  1. Consistent behavior across builds
  2. Cross-compilation support without target system access
  3. Hermetic builds that don't depend on the build machine's locale, environment, etc.

The rules will never execute a compiled test program. When M4 uses AC_TRY_EVAL or AC_RUN_IFELSE, you have two options:

  1. If the fact can be established without running code, replace the probe with another check, typically AC_TRY_COMPILE with #if / #error on platform macros (see the strerror example). The result still has to match what GNU autoconf reports natively on the platforms the conformance test runs on.
  2. Otherwise, choose a sane default per target platform from your understanding of it, expressed with select(). The M4's own cross-compiling branch (the last argument of AC_RUN_IFELSE, often a case $host_os) is a good starting point; quote its lines in the comment. Downstream projects can refine the answer for their environments with custom constraints (see Refining platform selects).

Runtime vs compile-time checks

GNU Autoconf often uses AC_RUN_IFELSE and similar macros that execute test programs on the build machine. That model does not translate to cross-compilation: the binary may not run on the host when the target is different.

In rules_cc_autoconf, the checker runs compile and link actions for the target toolchain; it does not execute generated programs. Several macros that Autoconf sometimes implements with runtime probes are implemented here with compile-time logic instead:

  • AC_CHECK_SIZEOF, AC_CHECK_ALIGNOF, AC_COMPUTE_INT — Probed at compile time (e.g. via compile-time techniques in the checker), not by running a test executable on the host. They still record properties of the target toolchain; when cross-compiling, ensure the configured toolchain matches the intended target and that the probe is meaningful for that environment.

Still problematic or not modeled like GNU Autoconf:

  • AC_TRY_EVAL / AC_RUN_IFELSE (and M4 that assumes running the binary on the build machine) — Replace with select(), explicit AC_SUBST, or other target-appropriate defaults.
  • Locale and similar environment probes (e.g. gt_LOCALE_FR) — Usually replace with select() on OS/CPU, or fixed subst values, rather than executing locale code at configure time.
  • WORDS_BIGENDIAN / AC_C_BIGENDIAN — There is no single runtime probe; depend on //autoconf/macros/AC_C_BIGENDIAN, which models endianness with select() and AC_DEFINE, and uses AC_FAIL on branches where a define must stay absent (see AC_FAIL in the API table).

Generally “safe” in the sense of not requiring a runnable host binary (still subject to toolchain correctness when cross-compiling):

  • AC_CHECK_HEADER — compile-only
  • AC_CHECK_FUNC — link check
  • AC_CHECK_DECL — compile-only
  • AC_CHECK_TYPE — compile-only
  • AC_TRY_COMPILE — compile-only
  • AC_TRY_LINK — link check
  • AC_DEFINE / AC_SUBST — no compilation probe

Strategies for Cross-Compilation

  1. Use select() for runtime checks (preferred):

When M4 uses runtime checks to detect platform-specific behavior, replace with select():

M4 (uses runtime locale detection):

AC_DEFUN([gt_LOCALE_FR], [
  # Complex runtime locale testing with AC_TRY_EVAL
  # Tests various locale names by actually running setlocale()
  case "$host_os" in
    mingw* | windows*) gt_cv_locale_fr=French_France.1252 ;;
    *) gt_cv_locale_fr=fr_FR.ISO8859-1 ;;
  esac
  AC_SUBST([LOCALE_FR])
])

Bazel (uses select() for platform-specific defaults):

autoconf(
    name = "gt_LOCALE_FR",
    checks = select({
        "@platforms//os:windows": [
            checks.AC_SUBST("LOCALE_FR", "French_France.1252"),
        ],
        "//conditions:default": [
            checks.AC_SUBST("LOCALE_FR", "fr_FR.ISO8859-1"),
        ],
    }),
)
  1. Use compile-time detection for feature checks:
checks.AC_TRY_COMPILE(
    code = """
#if defined(__APPLE__) && defined(__MACH__)
  #error "macOS detected"
#endif
int main(void) { return 0; }
""",
    define = "_IS_MACOS",
)
  1. Use platform selects for known values:

WORDS_BIGENDIAN is a presence macro (code tests it with #ifdef), so defining it to 0 on little-endian targets would be wrong. Use AC_FAIL on the branch where the define must stay absent: it records a failed check so the header keeps /* #undef WORDS_BIGENDIAN */. This is how //autoconf/macros/AC_C_BIGENDIAN is implemented; depend on that target rather than re-creating it:

autoconf(
    name = "AC_C_BIGENDIAN",
    checks = select({
        "@platforms//cpu:ppc": [checks.AC_DEFINE("WORDS_BIGENDIAN", "1")],
        "@platforms//cpu:s390x": [checks.AC_DEFINE("WORDS_BIGENDIAN", "1")],
        "//conditions:default": [checks.AC_FAIL("WORDS_BIGENDIAN")],
    }),
)

Complete Examples

Example 1: Shared Probe + Conditional Subst (posix_memalign)

Original M4: gnulib/m4/posix_memalign.m4

Bazel (//gnulib/m4/posix_memalign/BUILD.bazel, abridged):

"""https://github.com/coreutils/gnulib/blob/1039a5f2cee3cda1c11f64a5eb3a15b2e87cd2f0/m4/posix_memalign.m4"""

load("//autoconf:autoconf_toolchain.bzl", autoconf = "autoconf_cache")
load("//autoconf:checks.bzl", "checks")
load("//gnulib:macros.bzl", gl_macros = "macros")

# gl_CHECK_FUNCS_ANDROID([posix_memalign], [[#include <stdlib.h>]]): probe and
# config.h define, shared with gl_ALIGNALLOC and gl_PREREQ_PAGEALIGN_ALLOC, which
# call it without gl_FUNC_POSIX_MEMALIGN (see Step 4).
autoconf(
    name = "gl_CHECK_FUNCS_ANDROID_posix_memalign",
    checks = gl_macros.GL_CHECK_FUNCS_ANDROID(
        ["posix_memalign"],
        includes = ["#include <stdlib.h>"],
    ),
    visibility = ["//visibility:public"],
)

autoconf(
    name = "gl_FUNC_POSIX_MEMALIGN",
    checks = [
        # if test $ac_cv_func_posix_memalign = yes; then ... else HAVE_POSIX_MEMALIGN=0; fi
        checks.AC_SUBST(
            "HAVE_POSIX_MEMALIGN",
            condition = "ac_cv_func_posix_memalign",
            if_false = 0,
            if_true = 1,
        ),
    ] + select({
        # On OpenBSD >= 6.2 the runtime test passes.
        "@platforms//os:openbsd": [
            checks.AC_SUBST("REPLACE_POSIX_MEMALIGN", 0),
        ],
        # glibc and macOS fail the AC_RUN_IFELSE test, so an existing
        # posix_memalign is replaced; where it is absent the default 0 stays.
        "//conditions:default": [
            checks.AC_SUBST(
                "REPLACE_POSIX_MEMALIGN",
                condition = "ac_cv_func_posix_memalign",
                if_false = 0,
                if_true = 1,
            ),
        ],
    }),
    visibility = ["//visibility:public"],
    deps = [
        ":gl_CHECK_FUNCS_ANDROID_posix_memalign",
        # gl_STDLIB_H_DEFAULTS is a _DEFAULTS macro: listed in default_deps, not here
        "//autoconf/macros/AC_CANONICAL_HOST",  # AC_REQUIRE([AC_CANONICAL_HOST])
        "//gnulib/m4/extensions",  # AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
    ],
)

# Package-level aggregator: no checks, deps on the AC_DEFUN targets.
autoconf(
    name = "posix_memalign",
    visibility = ["//visibility:public"],
    deps = [
        ":gl_FUNC_POSIX_MEMALIGN",
        "//autoconf/macros/AC_CHECK_INCLUDES_DEFAULT",
    ],
)

Example 2: Platform-Specific Module (fstat)

Original M4: gnulib/m4/fstat.m4

Bazel:

"""https://github.com/coreutils/gnulib/blob/1039a5f2cee3cda1c11f64a5eb3a15b2e87cd2f0/m4/fstat.m4"""

load("//autoconf:autoconf_toolchain.bzl", autoconf = "autoconf_cache")
load("//autoconf:checks.bzl", "checks")

# REPLACE_FSTAT=1 is set unconditionally on macOS (stat can return a negative
# tv_nsec) and Windows (MinGW's stat() timestamps depend on the time zone).
# Use the same check kind (AC_SUBST) on every branch of the select().
autoconf(
    name = "gl_FUNC_FSTAT",
    checks = select({
        "@platforms//os:macos": [
            checks.AC_SUBST("REPLACE_FSTAT", "1"),
        ],
        "@platforms//os:windows": [
            checks.AC_SUBST("REPLACE_FSTAT", "1"),
        ],
        "//conditions:default": [],
    }),
    visibility = ["//visibility:public"],
    deps = [
        "//gnulib/m4/fchdir:gl_FUNC_FCHDIR_FOR_CLOSE",  # gl_TEST_FCHDIR
        "//gnulib/m4/sys_stat_h",  # AC_REQUIRE([gl_SYS_STAT_H])
        "//gnulib/m4/sys_types_h",
    ],
)

# Prerequisites of lib/fstat.c and lib/stat-w32.c.
autoconf(
    name = "gl_PREREQ_FSTAT",
    visibility = ["//visibility:public"],
    deps = [
        "//gnulib/m4/stat:gl_PREREQ_STAT_W32",  # AC_REQUIRE([gl_PREREQ_STAT_W32])
        "//gnulib/m4/sys_stat_h:gl_SYS_STAT_H",
    ],
)

autoconf(
    name = "fstat",
    visibility = ["//visibility:public"],
    deps = [
        ":gl_FUNC_FSTAT",
        ":gl_PREREQ_FSTAT",
    ],
)

Example 3: Conditional Checks (strerror)

Original M4: gnulib/m4/strerror.m4

Bazel:

"""https://github.com/coreutils/gnulib/blob/1039a5f2cee3cda1c11f64a5eb3a15b2e87cd2f0/m4/strerror.m4"""

load("//autoconf:autoconf_toolchain.bzl", autoconf = "autoconf_cache")
load("//autoconf:checks.bzl", "checks", "utils")

autoconf(
    name = "gl_FUNC_STRERROR_0",
    checks = [
        # AC_CACHE_CHECK([whether strerror(0) succeeds], [gl_cv_func_strerror_0_works])
        # Upstream uses AC_RUN_IFELSE; the port fails this compile probe on exactly
        # the platforms the m4's runtime test reports as broken.
        checks.AC_TRY_COMPILE(
            name = "_gl_cv_func_strerror_0_works",
            code = utils.AC_LANG_PROGRAM(
                [
                    "#if defined(__APPLE__) && defined(__MACH__)",
                    "  #error \"macOS strerror needs REPLACE_STRERROR_0\"",
                    "#elif defined(__FreeBSD__) && __FreeBSD__ < 13",
                    "  #error \"FreeBSD < 13 strerror needs REPLACE_STRERROR_0\"",
                    "#elif defined(__sun) && defined(__SVR4)",
                    "  #error \"Solaris strerror needs REPLACE_STRERROR_0\"",
                    "#endif",
                ],
                "",
            ),
        ),
        # case "$gl_cv_func_strerror_0_works" in *yes) ;; *)
        #   REPLACE_STRERROR_0=1; AC_DEFINE([REPLACE_STRERROR_0], [1], ...) ;;
        # esac
        # A shell variable plus AC_DEFINE; the m4 never AC_SUBSTs it.
        # Gate with `requires`, not `condition` (see Core Concepts).
        checks.AC_DEFINE(
            "REPLACE_STRERROR_0",
            "1",
            requires = ["!_gl_cv_func_strerror_0_works"],
        ),
    ],
    visibility = ["//visibility:public"],
    deps = [
        "//autoconf/macros/AC_CANONICAL_HOST",
        "//autoconf/macros/AC_CHECK_INCLUDES_DEFAULT",
        "//gnulib/m4/errno_h",
        "//gnulib/m4/extensions",
        "//gnulib/m4/string_h",
    ],
)
load("//autoconf:checks.bzl", "checks", "utils")

autoconf(
    name = "autoconf",
    checks = [
        checks.AC_TRY_LINK(
            code = utils.AC_LANG_PROGRAM(
                [
                    "/* Prologue: includes and declarations */",
                    "#include <langinfo.h>",
                ],
                "/* Body: code inside main() */\n"
                "char* cs = nl_langinfo(CODESET);\n"
                "return !cs;",
            ),
            define = "HAVE_LANGINFO_CODESET",
        ),
    ],
)

Migration Checklist

When migrating an M4 file, follow this checklist:

  • Read and understand the original M4 file
  • Check for existing //gnulib/m4 targets that provide needed checks
  • Identify all AC_REQUIRE dependencies
  • Map each M4 macro to its Bazel equivalent
  • Convert argument syntax (brackets → quotes)
  • Use plural macros.AC_CHECK_* for grouped checks (split into individual checks.AC_CHECK_* calls only when per-entry customization is needed)
  • Add define = parameters for config.h defines
  • Add subst = parameters for @VAR@ substitutions
  • Add M4_VARIABLE entries for any shell-only variables that downstream M4 modules branch on (e.g. HAVE_<NAME>, GL_GENERATE_<NAME>_H)
  • Handle platform conditionals with select()
  • Add comments referencing original M4 file and line numbers
  • Add dependencies to deps list
  • For ports in this repository: load autoconf_cache and run bazel test //gnulib/tests/compat/<module>:all; for your own project, run a diff_test against an expected header
  • Verify no duplicate checks between direct checks and deps

Common Pitfalls

ProblemSolution
Translating plural macros one-by-oneUse macros.AC_CHECK_HEADERS([...]) / AC_CHECK_FUNCS / AC_CHECK_TYPES / AC_CHECK_DECLS / AC_CHECK_MEMBERS instead of expanding into individual checks.AC_CHECK_* calls (split only when per-entry params differ)
Missing M4_VARIABLE for consumer-visible decisionsM4 shell variables that other modules branch on (e.g. HAVE_OBSTACK, GL_GENERATE_OBSTACK_H) must be tracked with checks.M4_VARIABLE, or downstream requires = [...] clauses will never match
Missing define = parameterAdd define = "HAVE_FOO" to create defines in config.h
Wrong define namesFollow autoconf conventions: HAVE_<NAME>, SIZEOF_<TYPE>, etc.
Duplicate check errorsUse //gnulib/m4 targets instead of manual checks
Missing main() wrapperAC_TRY_COMPILE / AC_TRY_LINK code must be a complete program, unless includes is also given (then code is the body of main()); or build it with utils.AC_LANG_PROGRAM
Probe fails and you cannot see whyThe checker drops compiler output; rerun with --action_env=RULES_CC_AUTOCONF_DEBUG=debug
Probe differs between a Windows checkout and a Linux oneLine endings: a BUILD file checked out with CRLF puts CRLF into a check's program. Every check is encoded with LF (make_check), so the action and its result are the same from either checkout; a lone \r written as an escape is kept
autoconf used inside //gnulib/m4 or //autoconf/macrosLoad autoconf = "autoconf_cache" from //autoconf:autoconf_toolchain.bzl to avoid a toolchain dependency cycle
Different check kinds for one variable across select() armsUse the same kind (AC_SUBST vs M4_VARIABLE vs AC_DEFINE) on every branch, matching what the m4 does with it
MSVC flags, .lib libs or /D defines selected on @platforms//os:windowsClang targeting MinGW is also os:windows. Select compiler-driver things on @rules_cc//cc/compiler:*; keep only libc/OS behaviour on the OS constraint
Hand-written AC_TRY_LINK standing in for an upstream AC_CHECK_FUNCRenames the cache variable consumers reference and hides a checker bug; keep AC_CHECK_FUNC and report the wrong answer
Condition names only REPLACE_X for a gnulib replacement sourcegnulib compiles it when HAVE_X = 0 || REPLACE_X = 1; write !ac_cv_func_x || REPLACE_X
Bare HAVE_X resolves to the wrong result or is ambiguousName the probe's cache variable (ac_cv_func_x, _gl_cv_...) or ac_cv_define_X / ac_cv_subst_X for the exact check you mean
String literals in definesUse '"string"' (outer single, inner double quotes)
Platform conditionalsUse select() for case "$host_os" patterns
Cross-compilation failuresValidate sizeof/alignof/compute probes for the real target; replace true run-time Autoconf checks with select() or explicit substs
Conformance test disagrees with GNU autoconfAdd the missing dependency (a header's :defaults the m4 requires, or a check the port lacks); use known_divergences only for inherent differences (see Step 6)
Changing defaults to fix one moduleDon't change existing defaults; they may break other modules
A :defaults or *_DEFAULTS target in depsAnalysis fails: deps wants CcAutoconfInfo, defaults carry CcAutoconfDefaultsInfo. Move it to default_deps
Unnecessary name + separate AC_DEFINEUse define = directly unless the cache variable is needed in requires or you need custom values
Using condition with if_false = NoneUse requires = ["cache_var==1"] to gate defines; condition is for value selection, not gating
Hiding a fixable port behind known_divergencesFix the port so Bazel matches GNU; a divergence entry needs a reason that explains why no port can match

Converting a project

//tools/convert turns an autoconf project into a Bazel module built on these rules. It reads the project's configure.ac (expanded by the real autom4te) and the file lists of its build (Makefile.am, or a hand-written Makefile.in), and writes BUILD.bazel, MODULE.bazel and a report. What it cannot translate on its own it asks about in one file, decisions.bzl, which a person answers and keeps.

The result is a module in the shape of the hand-written BCR overlays: an autoconf target per configure body with checks.* calls, config.h and the other configured headers, the gnulib library with its conditional sources and wrapper headers, a cc_library per library and a cc_binary per program of the automake tree, bool_flags for the configure options, and select()s where configure branched on the platform.

Which projects

What sits next to configure.ac decides how much of a module comes out. The first note of the report names the shape it found.

ShapeIdentified byWhat comes out
1. autoconf + automake, usually gnulibconfigure.ac, Makefile.am, often m4/gnulib-comp.m4The whole module. coreutils, grep, sed, gzip, m4, bison, gawk, make, patch, mpfr, flex.
2. autoconf + hand-written Makefilesconfigure.ac, no Makefile.am; Makefile.in files, or plain Makefiles that include a file configure writes (tftp-hpa's config/MCONFIG), with literal source lists and $(CC) -o rulesThe whole module, read from the Makefile rules: archives are libraries, link rules programs, the compile rule's -D and -I the flags, an $(includedir)/<name> install directory an include prefix; an included configured file is read from its .in template. readline, libpcap, tcpdump, tftp-hpa. The reader follows literal lists and $(VAR) references, not make functions.
3. autoconf + make as a programconfigure.ac; Makefiles built from $(wildcard ...), $(patsubst ...), generated fragments, or rules written by awk at configure timeThe configuration only: the autoconf target, its headers and flags. glibc, gcc, binutils, ncurses. The libraries and programs are written by hand in BUILD.bazel.
4. hand-written configureno configure.acNothing; the converter stops. ffmpeg, musl, zlib, nginx, openssl. The migration guide applies by hand.

A Makefile.am that switches compilers (CC = $(TARGET_CC), grub's firmware side) builds nothing for the host: its programs and libraries are left out and reported, whatever the shape of the rest.

Running it

From a clone of this repository:

bazel run //tools/convert -- convert --src https://ftp.gnu.org/gnu/gzip/gzip-1.15.tar.xz --out /path/to/output --dev

--src takes a release archive URL, a local archive or a source tree. --out receives BUILD.bazel, MODULE.bazel, REPO.bazel (the module's defaults: license metadata, the standard as a future toolchain feature), CONVERSION_REPORT.md and decisions.bzl. The banner of each generated file repeats the command, which is what regenerates the conversion. Options:

OptionEffect
--devMODULE.bazel overrides rules_cc_autoconf with this checkout. Without it the module depends on the released version.
--decisions FILERead and extend this decisions file instead of decisions.bzl in the output directory.
--annotateComment every check, dep and rule with the m4 call or Makefile line it came from.
--subdir DIRConvert the configure.ac in a subdirectory of the tree.
--vendor-portsCopy the gnulib ports the conversion uses into the module, see Vendoring the ports.
--inline-checksDeclare the gnulib ports the conversion uses as targets of the project's own packages, in place of the dependencies on them, see Inlining the checks.
--only FILEConvert one m4 file in the layout of the hand ports, see Converting single m4 files.
--bcr-entry DIRWrite a registry entry in place and take the inputs from it, see A registry entry.
--http-archive-entry DIRWrite the one generated BUILD file into a consumer's 3rdparty/<name>/ for http_archive, see An http_archive.
--layout per-file|packageOne package per m4 file, or a single package. The default is per-file with --only and package otherwise.
--external, --in-repoEmit for an external project (@rules_cc_autoconf//... labels), or for this repository (//... labels and autoconf_cache). The default follows the layout: external for a package, in-repo per file.
--buildifier auto|none|PATHThe buildifier that formats the output. auto takes the one on PATH.
--strip-prefix PREFIXThe release archive's strip_prefix for source.json. By default <name>-<version>.
--keep-workKeep the scratch directory of the expansion; its path is printed.

--help lists the rest. --profile FILE belongs to the tool rather than to the convert subcommand, so it goes before the subcommand: bazel run //tools/convert -- --profile run.prof convert .... It writes a cProfile of the run to FILE and a text summary to FILE.txt.

What to do with the output depends on where the module is going, see Using the output.

Decisions

The first run usually exits with status 3 and lists the constructs it could not translate under "Decisions required" in the report. Each is an entry of decisions.bzl with decision.UNDECIDED as its value: replace the value and run the same command again. The outputs are written even while decisions are missing, so the project can be looked at, but a conversion is complete only when the file answers every question.

The file is read with a parser, never evaluated; its header names the rules_cc_autoconf that wrote it. New entries are added in key order with a comment block showing the construct: the upstream text in a fenced block tagged with its language, where it was found, and why it is asked. Entries the converter stops asking for are reported as unused. A checks.X(...) value reads as in a BUILD file: add load("@rules_cc_autoconf//autoconf:checks.bzl", "checks") at the top.

A DEFAULTS table answers every undecided key of a kind at once, for a construct that never matters for the module (a firmware compiler's probes, a toolchain's warning flags); the report lists what a default answered:

DEFAULTS = {
    "probe": decision.IGNORE,
}

The kinds the converter asks for:

KeyAsked whenAccepts
probe:<cache variable>A probe has no compile-time form: a runtime test, a cache value computed by shell, a check whose actions decide the variable, a program reading shell variables the rules cannot expand (#include "$srcdir/gmp-h.in"). Never requested for a header, function, type or library check, but applied when present: the value stands in for the probe ("no" for a library the registry does not have, which configure would probe the host for)IGNORE, ACCEPT (the converter's own guess), a literal, a select() of literals (a value-recording check such as AC_CHECK_SIZEOF defines its SIZEOF_* to it as well), or a replacement checks.AC_TRY_COMPILE(name = ...) / AC_TRY_LINK
variable:<NAME>A substituted or M4 variable is assigned in ways no one check expresses. For AC_DEFINE_UNQUOTED a string decision is the C literal's contents, without its quotesIGNORE, ACCEPT_PARTIAL, a literal, a select(), or checks.AC_SUBST(...) with condition / if_true / if_false
guard:<target>:<check>A check runs under a condition requires cannot spell: a variable no check produces, two platform dimensions, an option mixed with the platform. Also taken unasked for an unconditional check, to add a condition the script lacks: guard:autoconf:AC_CHECK_HEADER:crypt_h as ["ac_cv_lib_crypt_crypt"] keeps <crypt.h> off where libcrypt does not link; one header or function of a grouped check is split out under its singular keyALWAYS, NEVER, a requires list, or a select() of such lists
macro:<NAME>A macro has no port and no translationIGNORE, the label of a port, or the checks that stand in for it
actions:<macro>:<argument>A check's action arguments hold shell or calls the converter does not translateIGNORE or the checks that stand in for them
am_conditional:<NAME>Sources sit under an automake conditional that is neither an option nor a platformALWAYS, NEVER, or a config_setting label whose select() arm takes the sources
src_condition:<NAME>A gnulib source's gl_CONDITIONAL cannot be derived from the portsALWAYS, NEVER, or a condition expression
generated:<path>A source is not in the tree and no translatable recipe writes itIGNORE, the rule that writes it (inline, with its load() lines), or ":<target>" for a rule in BUILD.bazel
cppflag:<dir>:<NAME>A -D value only configure computesIGNORE or the C token
link:<dir>/<target>:<item>An LDADD item is neither a library of the tree nor a -l optionIGNORE, a dep label, or a linkopt

The kinds it never asks for, added by hand when wanted:

KeyEffectAccepts
flag:<option>Overrides the default of an option's bool_flag, which otherwise comes from the not-given arm's assignment, the variable the value is copied into, or the help string. Only the flag's build_setting_default changes: the select() arms keep reading configure's own default, so the option still gates what it gated (gawk's flag:mpfr: False turns MPFR off by default without making its deps unconditional). For a value option (--with-runtime-dir=DIR, a string_flag) a string is the defaultTrue, False, a string, IGNORE
program:<path>A program configure adds only when a probe holds is emitted with tags = ["manual"] and listed in the report with its key (program:src/hostid: the program's path from the project root, which is also the target's name); this names the platforms it builds on. The key applies to any program, of a Makefile.am or of a hand-written Makefile.in; a Makefile.in rule all does not reach (readline's examples/rlptytest, in OTHEREXE alone) gets no target until the key says soALWAYS, NEVER, MANUAL (defined, tags = ["manual"]), or a platform label list
subdir:<dir>configure enters an AC_CONFIG_SUBDIRS directory only sometimes, and neither the condition nor the Makefile's if around SUBDIRS += <dir> lowers to a platform or a config setting (gawk enters extension/ when its dlfcn.h probe succeeded); this names where the package's targets build, as their target_compatible_with. Until it is decided the package's C/C++ targets are defined with tags = ["manual"], as a probe-conditioned program is, so //... leaves them outALWAYS (built everywhere, the condition dropped), NEVER (not converted), MANUAL (the condition is not expressible: keep the targets out of //...), or a list of platform or config-setting labels; one "!<label>,<label>" entry for everywhere but those (["!@rules_cc//cc/compiler:msvc-cl,//:extensions_off"])
copts:<package>, conlyopts:<package>, cxxopts:<package>Compiler options every target of the package compiles with (. is the root), after NO_WARNINGS: a diagnostic a newer compiler turns into an error in code written for an older one, which no consumer can set on a module it depends on["-Wno-error=incompatible-pointer-types"]
standard:<package>:C, standard:<package>:CXXThe language standard every target and check of the package compiles in, when the script pins none and the project must (gawk's gnu11: its vendored regex makes implicit declarations clang 16 rejects in strict ISO modes). Rendered as a macro's pin is, C_STD_OPTS / CXX_STD_OPTS on the cc targets and the autoconf targets alike, so a hand-written BUILD.bazel loads the constant instead of spelling its own -std=: a pin the checks do not share breaks the build where the toolchain's default mode differs (gcc 15's C23 has bool built in, so config.h skips <stdbool.h>, which a -std=gnu11 compile then lacks)"gnu11", "c++17"
license:<file>The SPDX identifier of an exported license file (COPYING, LICENSE). The converter reads the family and version from the text and the -only / -or-later suffix from the source headers; a file that does not settle (a custom notice, headers that disagree) is requested with its opening lines. The identifier adds the registry's package metadata: a license() over the file, the package_metadata with the module's purl, and package(default_package_metadata = ...)"GPL-3.0-or-later", IGNORE (exported without metadata)
src_condition:<NAME>Overrides the condition the conversion derived for a gnulib sourceas above
library:<package>:<name>Leaves a registry library out of one package (. is the root): the project is built as on a host without it, the not-found path of its probes and pkg-config checks taken. <name> is the library as configure spells it, the -l name (lzma), the pkg-config package (sdl2) or the registry module; the report's notes name the key for each library foundIGNORE
subst:<NAME>, check:<cache variable>Drops the project's copy of a result a dependency already provides, when Bazel reports it as produced twiceIGNORE
define:<NAME>, define:<package>:<NAME>Drops the project's copy of a define a dependency already provides (IGNORE), or pins the define: a value rewrites the config header's #undef line, over the port's result, for a bundled gnulib older than the ports' whose sources want its own m4's answer (grep's NL_LANGINFO_MTSAFE on macOS). A bare name pins every config header of the project; with a package (. the root) only that package's, where the labels a select() names live (gawk's pc/config.h answers on a setting its root BUILD.bazel defines)IGNORE, a value, or a select() of values

The migration guide says how each autoconf construct maps to a check, a rule or a select, which is what a replacement is written in.

Bundled decisions

The questions every gnulib project raises the same way, the gettext probes and substitutions of a project that builds no message catalogs, gnulib's own witness variables, are answered once, in gnulib/m4/decisions.bzl beside the ports. The converter reads it beneath the project's decisions file: a key the project's file answers wins, a key it leaves out or UNDECIDED takes the bundled answer, and only then does DEFAULTS apply. The report lists every answer taken from it under "Decisions inherited", so nothing applies silently; the bundled file's own keys never count as unused. --bundled-decisions FILE reads another file, none reads none; by default the converter finds the checkout's, or the copy in its runfiles when it runs from elsewhere.

BUILD.bazel and autoconf.bzl

In project mode every package the conversion writes to has two files. autoconf.bzl is generated: the package's targets are the body of one macro, configure(), named as the script it stands for, the constants several rules share stand at the top level, and visibility("private") keeps the file loadable from its own package alone. A BUILD.bazel that still calls the macro by its former name, autoconf_targets(), keeps working: the generated file aliases it and the report asks for the switch. The converter writes it whole on every run, formatted twice, as a BUILD file first (where buildifier expands every rule call and sorts the lists it sorts) and then as the .bzl file the statements move into, so it reads like a BUILD file. BUILD.bazel is hand-written: the converter writes it once, as a stub that loads the macro and calls it, and never touches it again. In a registry entry both sit in overlay/, extension/BUILD.bazel and extension/autoconf.bzl beside a sub-configure package's. A package the conversion does not write (a tests/ package of hand-written tests) is a hand-written BUILD.bazel in the same place, which the converter leaves alone.

BUILD.bazel does three things:

  • Adds targets. Tests, tools, aliases, anything the conversion does not produce. A rule it loads from another module makes that module a bazel_dep of the generated MODULE.bazel. A constant autoconf.bzl defines at the top level (NO_WARNINGS, the option table) can be loaded from it; one defined inside the macro (a glob(), or a list built from one) cannot.
  • Replaces generated targets. A rule with the same name as a generated one replaces it: at the next conversion the generated rule is left out of autoconf.bzl, whose macro docstring names it with a "Targets replaced" section of the report, and consumers are unchanged.
  • Provides generated files. A generated:<path> decision of the form ":<target>" names a rule of BUILD.bazel that writes the file; the converter lists the file among the consumer's sources and emits no rule. This is the home for a tool shared by several outputs or a .bzl macro of its own; a one-line genrule fits inline in the decisions file.

For a shape 3 project BUILD.bazel holds the libraries and programs outright: there is no generated output to replace.

Visibility. Every generated target carries an explicit visibility, so a package(default_visibility = ...) in BUILD.bazel never publishes them. Public is deliberate and small: what make install ships, the libraries, programs and headers and their aliases, and the option flags, which are the module's configure options. Everything else, the autoconf targets, the config headers, autoconf_linkopts, the option settings, the gnulib and private header libraries and the generated-file rules, is private, or //:__subpackages__ when the module has other packages (a sub-configure, a hand-written tests/), which then see them. A tree rendered into a subdirectory of a larger workspace is confined to that directory instead, //third_party/foo:__subpackages__, and vendored ports are confined the same way.

A tree or entry converted before this layout, with a generated BUILD.bazel and a hand-written BUILD.additions.bazel beside it, is migrated by the next run: the additive file's contents become the new BUILD.bazel, its loads merged with the autoconf.bzl load (which names the top-level constants the text reads), and the additive file is removed; the report says so. A constant it read that autoconf.bzl now defines inside the macro has to be declared in BUILD.bazel by hand, and the report names those too.

How it translates

One bullet per construct; the table after them holds the details a reviewer of the output may want.

  • Expansion. autom4te expands configure.ac with a prelude that marks every AC_DEFUN body and check macro call. The converter never parses m4; an m4_fatal becomes a warning so the expansion completes.
  • Targets and deps. Each AC_DEFUN body is an autoconf target. AC_REQUIRE and calls of ported macros become deps on the ports under //gnulib/m4 and //autoconf/macros, the headers' :defaults targets default_deps. A local check a port already produces is left out and reported.
  • Checks. Check macros become checks.* and macros.* calls, their action arguments read as configure runs them. Runtime probes are never executed. The language standard configure switches the compiler into is pinned on the package, and recorded by a check.
  • Shell. The shell between calls is evaluated symbolically: the condition a call runs under becomes its requires, platform cases become select()s, options become flags with config settings, assignments to substituted variables become AC_SUBST checks. What the analysis cannot follow becomes a decision, never a guess.
  • Registry libraries. A library the registry provides, that configure probes for or pkg-config asks about, becomes a bazel_dep and a dep of what links it. The checks that compile or link against it move to an autoconf target of their own with the library as its one cc_deps entry, which the main target depends on; every other check stays toolchain-only, so its result does not depend on the dependency graph. A library configure only probes under one of its options (gawk's -lmpfr -lgmp under --enable-mpfr) is a dep in that option's select() arm, so turning the flag off drops it from the graph. configure's own probes stay probes; PKG_CHECK_MODULES has none to run and is decided at conversion time from the registry's versions.
  • The gnulib tree. gnulib-comp.m4 and gnulib.mk give the module deps, the wrapper headers, the recipe-written headers and the conditional sources of one gnulib library.
  • The automake tree. Every Makefile.am gives a cc_library per library and a cc_binary per program, with LDADD, LIBADD and the flag variables resolved into deps, defines, includes and copts.
  • Generated files. Recipes the converter can read become write_file, substitution or template rules; AC_CONFIG_FILES templates are rendered; everything else is a generated: decision.
  • Sub-configures. Each AC_CONFIG_SUBDIRS directory is a package of the same module, converted from its own tree, sharing the decisions file. One configure enters only sometimes builds where configure enters it: a host-OS condition or a decided automake conditional becomes the package's target_compatible_with; a probe-dependent one (gawk's extension/ needs dlfcn.h) is a subdir: decision, and its targets are tagged manual until it is made. An option the sub-configure's script declares under the same name as the root's (gawk's extension/ runs the root's MPFR macro) is the root's flag: the package keys on //:<name>_enabled and declares no flag of its own, so one --//:<name> switches the whole module.
  • Licensing. The license files the release ships are exported, with the registry's package metadata over them: the SPDX identifier is read from the text and the source headers, and asked for as a license: decision when they do not settle it.

Details by construct

ConstructBecomesWhy it is done that way
A probe without a cache variableA check named by its place in the source, autoconf_probe_configure_475A decision keyed by it holds across converter versions
AC_REPLACE_FUNCSA function check and a replacement compiled only when the function is missingAs the macro behaves
A check that refers to earlier definescompile_defines on the check; the body split into autoconf_1, autoconf_2, ... where a probe needs an earlier result of the same bodyProbes see confdefs.h as configure's do
AC_USE_SYSTEM_EXTENSIONSThe extension macros on every later declaration, type, member, compile and link checkconfigure defines them before those checks
AC_RUN_IFELSEThe m4's cross-compiling guess per platform, the program kept in a comment; a guess that is the same everywhere is a probe: decisionNothing is executed at analysis time
AC_PROG_CC_C99, AC_PROG_CC_STDC, AX_CXX_COMPILE_STDCXX, -std= in CFLAGS or a MakefileThe mode configure switches the compiler into is pinned on every target and check of the package (C_STD_OPTS / CXX_STD_OPTS: -std=gnu99, or /std:c11 on MSVC and clang-cl), and recorded by a checks.AC_LANG_STANDARD that does not stop the build; the script's own test "$ac_cv_prog_cc_c99" = no + AC_MSG_ERROR becomes the check's error. REPO.bazel carries the same standard as a commented-out toolchain feature, for when rules_cc names them (bazelbuild/rules_cc#936)Autoconf's macros switch the compiler and record whether they could; only the script stops. The root module's --conlyopt / --cxxopt come later on the command line and still win
test $x = yes on a probe's variableThe probe's successThe value is what configure tested
A variable copied from a probe's result (found="$pa_cv_func_x"), an alias of its name (AS_VAR_PUSHDEF([cache], ...) then AC_CACHE_VAL([cache], ...)), AS_VAR_SET / AS_VAR_IFThe probe, under the name the expansion spellsnasm's PA macros name every cache variable through an alias
A variable assigned inside a macro the body expands (PA_ARG_ENABLED([x], ..., [PDFOPT=...]))Seen by the calls after the macro and by the body's AC_SUBST of it, under the call's conditionconfigure runs the macro's shell in place
A probe over an empty program (AX_CHECK_COMPILE_FLAG, PA_ADD_CFLAGS, libpcap's AC_LBL_CHECK_COMPILER_OPT)Left out; its cache variable reads no, so the flag's defines and additions do not happenA Bazel toolchain owns the compiler options; the check tested the flag, not the code
A probe whose two arms substitute one name to constants (AC_SUBST([X], [1]) / [0])One AC_SUBST(X, condition = <probe>, if_true, if_false); a constant an enclosing branch substitutes merges into if_falseOne producer per name
AC_LIBOBJ / AC_REPLACE_FUNCS objects in a hand-written Makefile's @LIBOBJS@The source from AC_CONFIG_LIBOBJ_DIR, next to the Makefile, or where the Makefile's own rule for the object points (strlcpy.o: $(srcdir)/missing/strlcpy.c), compiled when the function is missingThe object name says nothing about the source's directory; the rule does
gnulib-tool's conditional dependencies (func_gl_gnulib_m4code_<module> () in gl_INIT)The function bodies read where they are defined, their gl_CONDITIONALs and AC_LIBOBJs under the tests inside themA static build enables every module, so each body runs once
A wrapper-header placeholder no port provides (@EMULTIHOP_HIDDEN@ in errno.in.h)AC_SUBST(name, 0) in the main targetAn unsubstituted @NAME@ does not compile where the wrapper is generated
gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STDIO_H_SIGPIPE], [1]) / gl_MODULE_INDICATOR_SET_VARIABLE in gl_INITAC_SUBST(GNULIB_X, 1) beside the module indicators, under the call's condition where it has one; the header ports' own _MODULE_INDICATOR_DEFAULTS calls and the 0s are left to the ports' defaultsstdio.h renames printf to the SIGPIPE-emulating replacement and signal.h defines SIGPIPE only when these read 1
Shell variables in a probe's program (#define GMP_NAIL_BITS $GMP_NAIL_BITS, #include "$srcdir/gmp-h.in")A literal the body assigns is expanded in place; $ac_includes_default is utils.AC_INCLUDES_DEFAULT; anything else makes the probe a probe: decisionconfigure expands them before compiling; the rules compile the text as written
A platform arm's constant define beside a probe's conditional define of the same name and value (make's HAVE_DOS_PATHS from AS_CASE([$host]) and from a _WIN32 compile test)The probe's define; the arm's constant is left outconfigure defines it once either way; the rules take one producer per define
A macro of a gnulib-tool file whose name no port carries (bison's m4/printf-posix-rpl.m4, since merged into printf-posix.m4)The port target of that namegnulib-comp.m4's gl_FILE_LIST says gnulib wrote the file, so the macro is the port's, not the project's
gnulib.mk's test $HAVE_X = 0 over a HAVE_* a port probe alone producesThe probe's cache variable (!ac_cv_func_wcwidth)The bare name resolves to the header port's default (HAVE_WCWIDTH=1) where the failed probe defined nothing, and ac_cv_subst_HAVE_X names that default outright; a name a port's own AC_SUBST produces keeps that spelling
An automake else branch, or if !COND (make's src/posixos.c under else of if WINDOWSENV)The conditional's negation: the other arms of the select the conditional lowers to ({"@platforms//os:windows": [], "//conditions:default": ["src/posixos.c"]}), a decision's ALWAYS and NEVER swappedAn else branch is compiled exactly where the if branch is not
AM_CPPFLAGS += -I<dir> under an automake conditionalThe directory in that arm of includesAs a -D under a conditional goes in that arm of local_defines
AC_SEARCH_LIBS with actionsThe actions as body shell under the search's result, their nested calls translated (an AC_LIBOBJ fallback compiled when the search fails)As for the other singular checks
AC_SEARCH_LIBS / AC_CHECK_LIB of a system library (-ldl, -lrt, -lm)The -l flag configure prepends to LIBS (the search's value; a sibling AC_SUBST for a library check with the default action) as a substitution the shared autoconf_linkopts carries to every program and libraryLIBS is on every link of the Makefile; on glibc before 2.34 dlopen needs -ldl
AC_LIB_HAVE_LINKFLAGS([ssl], [crypto], [includes], [code])A link probe against -lssl -lcrypto defining HAVE_LIBSSL, with HAVE_LIBSSL, LIBSSL and LTLIBSSL substituted; the registry's module as the dependency, as for AC_CHECK_LIBgettext's lib-link.m4 is that probe plus a prefix search
-lm, -lpthread, -ldl, -lrt, -lws2_32 and the other system librarieslinkopts, never a registry moduleThe toolchain provides them
AC_CANONICAL_HOST and other once-only library macros called inside a project macroTheir port, as when called at the top levelm4sugar hoists such a body before the caller's output; the call markers travel inside it
A call of an autoconf builtin in a shell comment, or an unbracketed one in a case arm the shell never ranNot a definition the expansion missed; noted and left outThe expansion attributes what it expanded
PKG_CHECK_MODULES under a configure optionThe dependency in the option's arm of the targets that link $(X_LIBS)The library is wanted only with the option
A variable another module provides with words (an ALLCAPS substitution, a port's M4 variable)Compared by valueThose values are the interface
AC_MSG_ERROREnds the path; a variable assigned on every continuing path counts as always assignedconfigure would not reach the rest
case $host_os / $host_cpu / $GCCselect()s on @platforms//os:*, @platforms//cpu:*, @rules_cc//cc/compiler:*, one dimension per selectOne axis per select keeps the arms readable
AC_ARG_ENABLE / AC_ARG_WITH compared with yes/noA BOOL_OPTIONS table of defaults with a bool_flag and a config_setting comprehension over itThirteen lines an option otherwise
AC_ARG_WITH([plugin-dir]) whose value a variable takes (PLUGIN_DIR="$with_plugin_dir", else a default)A string_flag named after the option, its default configure's at the install directories' defaults (${libdir}/plugins as /usr/local/lib/plugins); the main target reads the flag as a build_settings entry, which substitutes @VAR@ and is the make variable $(VAR); a Makefile -DPLUGIN_DIR reads $(PLUGIN_DIR) with toolchains = [":autoconf"]--//:plugin_dir=/opt/plugins reaches the substitution and the define alike; no variable: or cppflag: decision pins a constant
An option compared with other valuesA string_flag (an int_flag for numbers) with a config_setting per valueThe script branches on the value
PKG_CHECK_MODULES([X], [pkg >= 1.2])The found action when the registry provides every package at a satisfying version, with X_CFLAGS and X_LIBS holding what that action gives them (empty otherwise, the dependency carries headers and archive); the not-found action otherwise. PKG_CHECK_EXISTS likewise; PKG_CHECK_VAR stays a variable: decisionThere is no probe to run; the registry's version is the one compared and the report says an override is not checked
A registry library a target linksimplementation_deps, after the project's own include directories, unless a public header includes itIts headers are not part of the target's interface
A check that names a registry library (AC_CHECK_LIB, AC_SEARCH_LIBS, a header check or a probe including one of its headers)Moved to autoconf_<lib>, with every library it links as cc_deps (-lssl -lcrypto: both openssl targets); the bodies after it depend on it, a check of an earlier body gets autoconf_<lib>_<n>, a check under a select() arm (libssh2's openssl probe under --with-crypto=openssl) the same arm of the library target. A library only ever probed in one option's arm is a cc_deps entry, and a dep of the Makefile targets, in that arm alone (gawk's gmp and mpfr under --enable-mpfr)Only the checks that need the dependency see it; the rest share results across modules. An optional library leaves the graph with its option
gnulib's conditional sourcesautoconf_srcs(wrap = "include")A source's own #include "sibling.h" keeps working
gnulib's generated wrapper headersWritten under _gnulib_gen/ (the conditional ones under _gnulib_cond/), served ahead of the source directoryA maintainer's copy next to the .in.h template must not shadow the rendered one
A wrapper header assembled from two files (gnulib's assert.h: assert.in.h, then verify.h through a sed)A second autoconf_hdr rendering the appended file with the recipe's sed as exact substitutions (a deleted range fenced with #if 0), spliced in at the template's closing comment through GNULIB_COND_HDR_INLINES; a sed the converter cannot translate is reportedThe appended half is what gives compilers predating C23 (gcc < 9) a one-argument _Static_assert
An EXTRA_*_SOURCES objectFollows HAVE_X = 0 || REPLACE_X = 1 over the header's substitutions, or the port's case $host_os, or a src_condition: decision; REPLACE_X = 1 lowers to REPLACE_X != 0gnulib's REPLACE_* are zero or non-zero, and a newer gnulib answers 2 where an older gnulib.mk wrote 1
SUBDIRSEvery Makefile.am reached, po, doc, tests and the like exceptedThose build no code
LDADD orderAn archive depends on the archives every LDADD lists after itA static link keeps the Makefiles' order
AM_CPPFLAGS, _CPPFLAGS, _CFLAGSDefines, includes and -f code-generation options, expanded through the Makefile's own flag variables with automake's installation directories at their defaults; a variable only configure sets is reportedWhat make would pass, without configure's part
-I<dir> naming a source directory with headersA dependency on a shared library of those headers with the directory as its include path, in implementation_deps for a libraryThe headers are declared for the sandbox and the directory stays private to the targets whose flags named it
A library or program and the gnulib libraryA dependency when its flags put gnulib's directory on the include path or its LDADD links the archive; otherwise built without it, as upstream builds itgnulib's wrapper headers change what a compile sees
Several gnulib directories (libpaper's libgnu/ and src/libgnu/, each with its Makefile.gnulib)One gnulib library each, sharing the autoconf target; the second's top-level constants carry its name (GNUPAPER_SRC_CONDITIONS)One gl_INIT is one set of results
A library listed only under an automake conditionalBuilt when the conditional is a configure option or a decided config setting (target_compatible_with), left out when decided NEVER, an am_conditional: decision otherwiseAs for programs
Sources under both a case $host_os arm and a probeAn autoconf_srcs target per platform arm, in that arm's srcs and in depsOne select() dimension at a time
@top_builddir@/dir/file.c, $(top_srcdir)/dir/file.c in a Makefile.amThe file at that path of the treeWhat make would compile
Installed headersThe library's hdrs; the rest compile with the sourcesPrivate names never reach a consumer
-I$(top_srcdir) (a root header included as <config.h>)strip_include_prefix = "/" with include_prefix = "." on the root libraries with headers, the config headers library first in depsBazel 9 serves the virtual include directory for every header; Bazel 7 and 8 build it for a generated header only under an include_prefix, and refuse includes = ["."] at a main repository's root, which gcc would in any case drop from the quote chain it holds through -iquote (a dependency's config.h would be found first)
libfoo.aA rule named foo (lib/foo plus a libfoo alias when a program foo exists); an installed libtool library also a cc_shared_library (without -no-undefined in its LDFLAGS it links with -undefined dynamic_lookup on macOS and is not built on Windows, as libtool does); programs of subdirectories get top-level aliasesThe names consumers expect
The headers next to the programs of a directoryOne top-level glob constant named after the directories, LIB_SRC_HDRS, in each program's srcsOne glob a hundred programs share
WarningsEvery target's copts start with NO_WARNINGS, a select() giving /w to MSVC and clang-cl and -w to every other compiler; bison() runs with -Wnone, flex() with --nowarnA consumer cannot act on upstream's warnings and cannot change the flags of a module it depends on
A program listed only under an automake conditional decided as a config setting, or whose every source istarget_compatible_with on that settingA select() with an empty arm would be a binary without sources
bin_PROGRAMS = @bin_PROGRAMS@Read from EXTRA_PROGRAMS and configure's assignmentsThe list lives in configure
Yacc and lex sourcesThe registry's bison() and flex() rules, under bison/ and flex/The shipped generated files stay out of the way
A recipe of printf or echo linesA write_file, through autoconf_hdr(mode = "subst") when it carries configure's valuesThe content is known
A sed rewrite of a headerSubstitutionsThe content is known
A header assembled by echo lines under conditionalsOne template with #if variantsOne file, every arm
AC_CONFIG_FILES templates that are not MakefilesRendered by autoconf_hdr; a rendered header joins config_headers beside the AC_CONFIG_HEADERS outputSources include it as they do config.h
No AC_CONFIG_HEADERSautoheader's config.h.in written next to the BUILD file and rendered as config.h behind HAVE_CONFIG_Hconfigure would pass the defines as -D flags
A sub-configure's headers libraryWithout the root config.hBazel searches every dependency repository's root for quoted includes

Vendoring the ports

By default the module depends on the gnulib ports of this repository (@rules_cc_autoconf//gnulib/m4/...), which change as the repository tracks gnulib. --vendor-ports copies every port the conversion resolved, and every port those depend on, into third_party/rules_cc_autoconf/<package>/BUILD.bazel of the module, with labels rewritten and the in-repository rule replaced by the public autoconf rule. The module is then pinned to the snapshot it was converted against; regenerating against a newer checkout refreshes the copies and the diff shows what gnulib changed. The rules themselves, and the two ports that are rule implementations (AC_C_INLINE, AC_C_RESTRICT), still come from rules_cc_autoconf; the report names them. The converter needs the checkout the ports come from: run it there with bazel run, or pass --index <checkout>.

Inlining the checks

A dependency's checks run in the dependency's configuration. A project that pins a language standard (C_STD_OPTS, from a macro, a -std= in its flags or a standard: decision) pins its own checks and targets, not the ports it depends on: gnulib's gl_C_BOOL then answers for the toolchain's default mode, C23 on gcc 15 and later, and config.h leaves <stdbool.h> out of a compile that runs in gnu11. --inline-checks declares every gnulib port the conversion resolved, and every port those reach, as a target of the project's package: the same rule graph with the same names flattened (:gnulib/m4/c-bool/c-bool), each attribute the text of the port's BUILD file with the labels rewritten, the ports' :defaults and config settings mirrored alongside. GNULIB_M4_DEPS and GNULIB_M4_DEFAULT_DEPS then name the mirrors, which take the package's C_STD_OPTS like its own targets. The //autoconf/... ports stay dependencies. Each check's JSON is the port's, so the content cache still matches the toolchain's results wherever the flags agree; only a pinned project reruns.

The text is lifted from the port BUILD files with a parser at every run, from the checkout bazel run started in or the one --index <checkout> names; there is no snapshot, and an entry carries the ports of the tree its rules version names. //autoconf/tests/ports_mirror declares every port again through the same emitter (bazel run //tools/convert:update_index writes its BUILD file beside index.toml) and compares the evaluated attributes with the port's, so a port the lift cannot reproduce, or a mirror that fell behind its port, fails there before it reaches an entry.

Converting single m4 files

To port one m4 file the way the hand ports under //gnulib/m4 are written, name the file and give a configure.ac that invokes its macros:

bazel run //tools/convert -- convert --src /path/to/project --out /tmp/port --only foo.m4 --in-repo

Every other macro resolves to the existing ports. The output is one package per m4 file with an aggregator target named after the file, in the layout of //gnulib/m4/<name>.

Checking against GNU autoconf

The check subcommand runs the project's real configure with the pinned autotools and the compiler, flags and environment of the current Bazel C++ toolchain, as the conformance tests of this repository do, and diffs the resulting config header against one Bazel built from the converted BUILD.bazel:

bazel run //tools/convert -- check --src /path/to/project --out /tmp/check \
    --bazel-config-h lib/config.h=/path/to/output/bazel-bin/lib/config.h

The GNU results stay in /tmp/check/gnu/. This validates the host platform only; the conformance tests under //gnulib/tests are the reference for the rest.

Using the output

There are three ways to take a conversion into a build, from the most to the least packaged. In each, the files a person edits are decisions.bzl and the BUILD.bazel files; everything else is regenerated.

In all three the output directory holds hand-written files beside the generated ones, and the converter owns only its own. Every file it writes starts with a banner that says @generated and names rules_cc_autoconf; that mark is what it goes by:

  • A file in the way that lacks the mark and differs from what would be written stops the run, before anything is written, with the paths listed. Hand-written files are kept where they are: move the file aside, or delete it. A hand-written BUILD.bazel is never in the way, since the conversion writes autoconf.bzl beside it.
  • After writing, the converter's files that this run did not produce are removed, and nothing else: a package that left the automake tree leaves no stale autoconf.bzl behind, while a hand-written file is never touched. The report lists what was removed.

A project's own generated files (a parser that says @generated) are not the converter's, since the mark needs both words. MODULE.bazel is the one exception to the first rule: it is always regenerated, and the module() attributes of the file that was there, bazel_compatibility and compatibility_level among them, are kept in the new one.

A registry entry

--bcr-entry names the entry in a Bazel Central Registry checkout and takes everything else from it:

# the first time: the archive and its integrity
bazel run //tools/convert -- convert --bcr-entry bcr/modules/foo/1.2.bcr.1 \
    --src https://ftp.gnu.org/gnu/foo/foo-1.2.tar.gz --integrity sha256-...
# every time after
bazel run //tools/convert -- convert --bcr-entry bcr/modules/foo/1.2.bcr.1

The path gives the module name and version. In the entry directory:

FileRole
decisions.bzlThe decisions file: read if present, created if not.
source.jsonWritten on the first run from --src and --integrity; later runs take the archive URL, integrity and strip_prefix from it. Its overlay map lists every file under overlay/, as the registry's own integrity tool does.
overlay/The module's files, hand-written and generated side by side: the generated autoconf.bzl files (nested ones included, since a registry overlay may add files anywhere in the archive) and MODULE.bazel, the hand-written BUILD.bazel beside each, and whatever else the module needs (a .bzl a BUILD file loads, a tests/BUILD.bazel with its sources). Generated files are replaced, the rest is kept, under the rules above.
MODULE.bazel, presubmit.yml, ../metadata.jsonThe registry's own files. MODULE.bazel is regenerated, keeping the module() attributes an existing one had besides name and version (bazel_compatibility, compatibility_level); other statements it held are listed in the report. presubmit.yml is written when missing and otherwise left alone; the homepage comes from --homepage, AC_INIT or the existing metadata.json.

An entry converted before this layout, with a generated BUILD.bazel and a BUILD.additions.bazel beside it, is migrated by the next run, see BUILD.bazel and autoconf.bzl.

The report and the scratch output go to --out, by default a temporary directory whose path the run prints, so nothing but the entry lands in the checkout. The banner of the generated files repeats the --bcr-entry command. Without a checkout, --bcr with --out, --source-url, --integrity and optionally --bcr-version writes the same entry under <out>/modules/.

An http_archive

For a project whose conversion is one BUILD file, a consumer can keep the generated file in its own repository and inject it with http_archive. --http-archive-entry names the directory, conventionally 3rdparty/<name>/, and reads decisions.bzl and source.json from it as --bcr-entry does:

bazel run //tools/convert -- convert --http-archive-entry 3rdparty/foo \
    --src https://ftp.gnu.org/gnu/foo/foo-1.2.tar.gz --integrity sha256-...

It writes the generated BUILD file there as BUILD.foo.bazel, the name http_archive users expect for an injected build file (a hand-written file of that name is not overwritten), records the archive in source.json, and prints the lines the consumer's MODULE.bazel needs: the bazel_deps the generated module declared and the http_archive call with build_file = "//3rdparty/foo:BUILD.foo.bazel". The report carries the same lines. The consumer also depends on rules_cc_autoconf. The injected file is all generated: there is no hand-written BUILD.bazel beside it, so a project that needs targets of its own takes one of the other two ways.

http_archive injects one file. A conversion that writes more, a sub-configure package, a config.h.in the release lacks, vendored ports, stops with a message naming the extra files: that project is rendered into its tree instead.

Rendering into the project

For anything larger than one BUILD file, or when the sources are a checkout rather than a release archive, point --out at the source tree, or copy the output over it:

bazel run //tools/convert -- convert --src /path/to/foo --out /path/to/foo \
    --decisions /path/to/foo/decisions.bzl

The tree is then a Bazel module: MODULE.bazel, the autoconf.bzl and BUILD.bazel files and decisions.bzl live in it, and a consumer reaches it with local_path_override, git_override or archive_override, or by vendoring the tree. Packaging and redistribution are the project's own concern from there; the converter's part is the same regeneration command, with the tree as both input and output. A source tree that ships its own BUILD.bazel files keeps them: the converter writes autoconf.bzl beside each, and the report names the BUILD files that do not call configure() yet.

Keeping the converter current

  • tools/convert/index.toml is a snapshot of the hand ports: which macro lives in which target and what each provides. Regenerate it after adding or renaming ports with bazel run //tools/convert:update_index; CI fails when the committed snapshot differs from the ports.
  • tools/convert/libraries.toml maps -l names to registry modules, with the version an entry depends on. bazel run //tools/convert:update_libraries refreshes it from the public registry (--registry <clone> reads a clone's origin/main instead): a module keeps the upstream release the table holds and takes its newest .bcr.N; moving to another upstream release is per module, --allow-upstream <module>. A registry entry's regeneration pins its dependencies the same way, from the checkout, with convert --allow-upstream <module> for the exceptions; the newer releases left alone are listed as notes. A library the entry already pins, in its MODULE.bazel or its previous version's for a fresh .bcr.N, keeps that upstream release rather than the table's; the table serves the libraries the entry never pinned.
  • The golden tests under //tools/convert fix the output for a few gnulib modules and for gzip, each with its decisions file next to the expected output, and run buildifier with all warnings on it. After an intentional change, refresh a golden with bazel run //tools/convert:<name>_golden_test -- --update.
  • The Python is type-checked by mypy in strict mode (.mypy.ini) and linted and format-checked by ruff (.ruff.toml) on every build: the .bazelrc applies rules_venv's py_mypy_aspect and py_ruff_aspect, with the toolchains in tools/convert/3rdparty, so bazel build //... fails on a type error, a lint finding or an unformatted file. bazel run @rules_venv//python/ruff:format and :fix apply what ruff can fix; a target tagged no_mypy or no_ruff is skipped.
  • The prelude mirrors autoconf 2.73 internals. Updating the pinned autoconf means revisiting tools/convert/instrument.m4 and the shape regexes next to it.