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.
Related rules
autoconf_hdr— Rendersconfig.h(or other templates) from check results.autoconf_srcs— Generates wrapped C/C++ sources and providesCcInfoso libraries and binaries compile against autoconf output.autoconf_linkopts— Resolves subst values (for example fromAC_SEARCH_LIBS) into linker inputs: on GCC/Clang, flags are passed via a response file; onmsvc-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
configurestep 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
- autoconf_defaults
- autoconf_hdr
- autoconf_srcs
- autoconf_linkopts
- autoconf_toolchain
- autoconf_package_info
- checks
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
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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | Other autoconf_defaults targets whose values this target carries along. | List of labels | optional | [] |
| defines | Default 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 -> String | optional | {} |
| substs | Default 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 -> String | optional | {} |
| variables | Default 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 -> String | optional | {} |
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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | List 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 labels | optional | [] |
| out | The output config file (typically config.h). | Label; nonconfigurable | required | |
| default_deps | autoconf_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 labels | optional | [] |
| defaults | Deprecated and ignored: defaults come through deps and default_deps; the autoconf toolchain no longer carries any. Accepted so existing BUILD files keep loading. | Boolean | optional | False |
| inlines | A 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 -> Label | optional | {} |
| mode | Processing mode that determines what should be replaced within the file. | String | optional | "defines" |
| substitutions | A 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: This would replace the exact string @MY_VERSION@ with 1.2.3, @BUILD_TYPE@ with release, and PLACEHOLDER_TEXT with actual_value. | Dictionary: String -> String | optional | {} |
| template | Template 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. | Label | required |
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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | List of autoconf targets which provide defines. Results from all deps will be merged together, and duplicate define names will produce an error. | List of labels | required | |
| srcs | A mapping of source file to define required to compile the file. | Dictionary: Label -> String | required | |
| default_deps | autoconf_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 labels | optional | [] |
| defaults | Deprecated and ignored: defaults come through deps and default_deps; the autoconf toolchain no longer carries any. Accepted so existing BUILD files keep loading. | Boolean | optional | False |
| naming | How 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. | String | optional | "package_relative" |
| wrap | How 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). | String | optional | "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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | List of autoconf targets providing check results. | List of labels | required | |
| default_deps | autoconf_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 labels | optional | [] |
| defaults | Deprecated and ignored: defaults come through deps and default_deps; the autoconf toolchain no longer carries any. Accepted so existing BUILD files keep loading. | Boolean | optional | False |
| vars | List 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 strings | required |
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:
- Transitive deps'
content_cache - Toolchain content cache (
cache_deps) - 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
defineorsubstsymbol names in the samecheckslist 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_cachetarget and depend on it from both consumers.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| cache_deps | Targets 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 labels | optional | [] |
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
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
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| aliases | Additional variables to define that are mirrored by the provided value variable. | Dictionary: String -> String | optional | {} |
| module_bazel | A MODULE.bazel file to parse module information from. Mutually exclusive with package_name and package_version. | Label | optional | None |
| package_bugreport | The package bug report email/URL. Used to populate PACKAGE_BUGREPORT define. | String | optional | "" |
| package_name | The package name. Must be provided together with package_version if module_bazel is not provided. | String | optional | "" |
| package_tarname | Exactly tarname, possibly generated from package. | String | optional | "" |
| package_url | The package home page URL. Used to populate PACKAGE_URL define. | String | optional | "" |
| package_version | The package version. Must be provided together with package_name if module_bazel is not provided. | String | optional | "" |
| strip_bcr_version | Whether or not to strip .bcr.* suffixes from module_bazel parsed versions. | Boolean | optional | False |
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
trueif:- The cache variable exists (check was run)
- The check succeeded (success = true)
- The value is truthy (non-empty string, not "0")
-
Condition is
falseif:- 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
- checks.AC_CHECK_ALIGNOF
- checks.AC_CHECK_CXX_COMPILER_FLAG
- checks.AC_CHECK_C_COMPILER_FLAG
- checks.AC_CHECK_DECL
- checks.AC_CHECK_FUNC
- checks.AC_CHECK_HEADER
- checks.AC_CHECK_LIB
- checks.AC_CHECK_MEMBER
- checks.AC_CHECK_SIZEOF
- checks.AC_CHECK_TYPE
- checks.AC_COMPUTE_INT
- checks.AC_C_RESTRICT
- checks.AC_DEFINE
- checks.AC_DEFINE_UNQUOTED
- checks.AC_FAIL
- checks.AC_LANG_STANDARD
- checks.AC_PROG_CC
- checks.AC_PROG_CC_C_O
- checks.AC_PROG_CXX
- checks.AC_SEARCH_LIBS
- checks.AC_SUBST
- checks.AC_TRY_COMPILE
- checks.AC_TRY_LINK
- checks.AX_CXX_COMPILE_STDCXX
- checks.M4_VARIABLE
- macros.AC_CHECK_DECLS
- macros.AC_CHECK_FUNCS
- macros.AC_CHECK_HEADERS
- macros.AC_CHECK_MEMBERS
- macros.AC_CHECK_TYPES
- utils.AC_LANG_FUNC_LINK_TRY
- utils.AC_LANG_PROGRAM
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
RETURNS
A JSON-encoded check string for use with the autoconf rule.
checks.AC_TRY_LINK
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
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++
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
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
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_
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_
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
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_
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_
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
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_
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
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_
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_#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
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_
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_#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
RETURNS
List of JSON-encoded check strings.
utils.AC_LANG_FUNC_LINK_TRY
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
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
| Name | Description | Default Value |
|---|---|---|
| prologue | List of strings (include directives or declarations), joined with newlines. | none |
| body | Code 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
autoconffrom@rules_cc_autoconf//autoconf:autoconf.bzlas shown there. - Contributors porting gnulib
.m4modules into this repository. Also read Porting Strategy. Ports under//gnulib/m4and//autoconf/macrosloadautoconf_cacheinstead (seeautoconfvsautoconf_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 .bzlfiles (autoconf rules, checks, macros, etc.)- Duplicates targets (e.g.
//gnulib/tests/duplicates:gnuliband 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
- Quick Start
- Architecture Overview
- Core Concepts
- Migration Patterns
- API Reference
- Platform Conditionals
- Dependencies and Reusable Modules
- Consuming Results Downstream
- Porting Strategy
- Best Practices
- Cross-Compilation Considerations
- Complete Examples
- Migration Checklist
- 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"— Forconfig.hfiles (processes#undefdirectives)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:
| Type | Purpose | Naming Convention | Output |
|---|---|---|---|
| Cache Variable | Internal check result | ac_cv_* (e.g., ac_cv_header_stdio_h) | JSON result file |
| Define | C preprocessor define | HAVE_*, SIZEOF_*, etc. | config.h via #define |
| Subst | Template substitution | @VAR@ patterns | subst.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:
- 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
)
- Non-standard values (use
conditionfor 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")
- Multiple outputs from one check (both
AC_DEFINEandAC_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:
| Parameter | Purpose | Example |
|---|---|---|
requires | Gate whether the check runs — if requirements aren't met, the define is not created | requires = ["ac_cv_func_foo==1"] |
condition | Select between two values — the check always runs, but produces different values | condition = "ac_cv_func_foo", if_true = "1", if_false = "0" |
Expression syntax
requires entries and condition strings share one grammar, evaluated by the checker:
| Form | Meaning |
|---|---|
NAME | True 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) |
!NAME | Negation |
NAME==1, NAME!=0, NAME<3, NAME>=2 | Compare 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, prefermacros.AC_CHECK_HEADERS([...])over splitting into individualchecks.AC_CHECK_HEADERcalls — the macro returns a list of checks with auto-generatedHAVE_<HEADER>defines, matching upstream behavior with less boilerplate - Splitting into individual
checks.AC_CHECK_HEADERcalls is only needed when you must customize per-header parameters (e.g., differentrequires,includes, or a non-standarddefinename) - Add
define = "HAVE_<HEADER>"to singular calls to create defines inconfig.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, prefermacros.AC_CHECK_FUNCS([...]); it generatesHAVE_<FUNCTION>defines for each entry - Use individual
checks.AC_CHECK_FUNCcalls only when per-function customization is required - Add
define = "HAVE_<FUNCTION>"for singular calls - Do not replace an upstream
AC_CHECK_FUNCwith a hand-writtenAC_TRY_LINK/AC_TRY_COMPILEbecause 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=debugand file an issue with the probe output instead;AC_CHECK_FUNCis 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",
)
Pattern 10: Link Tests
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
.m4file sets it as a plain shell assignment (HAVE_FOO=1,GL_GENERATE_FOO_H=true) and other.m4files 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"]— withoutM4_VARIABLEthe 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
| Macro | Description |
|---|---|
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:
| Macro | Description |
|---|---|
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
| Function | Description |
|---|---|
AC_LANG_PROGRAM(prologue, body) | Build program code from prologue and body |
AC_INCLUDES_DEFAULT | Default 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")
| Macro | M4 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:
| Parameter | Type | Description |
|---|---|---|
name | string | Cache variable name (auto-generated if omitted) |
define | string/bool | Define name or True to use cache var name |
includes | list | Include directives (e.g., ["#include <stdio.h>"]) |
language | string | "c" or "cpp" |
requires | list | Dependencies that must be satisfied |
compile_defines | list | Names 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 |
copts | list | Extra compiler flags for this probe only (e.g. ["-std=c11"]), appended after the toolchain's flags |
condition | string | Condition for value selection |
if_true | any | Value when condition is true |
if_false | any | Value when condition is false |
subst | bool/string | Also 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:
- Bazel equivalent check — e.g.
gl_macros.GL_CHECK_FUNCS_ANDROID(["func"], includes = ["#include <header.h>"])(loaded from//gnulib:macros.bzl, see gnulibmacros) orchecks.AC_CHECK_FUNC("func", define = "HAVE_FUNC", subst = "HAVE_FUNC"). - Depend on the gnulib module that already implements the check — e.g.
deps = ["//gnulib/m4/timespec_getres:gl_FUNC_TIMESPEC_GETRES"]instead of definingHAVE_TIMESPEC_GETRESviaselect().
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 Pattern | Bazel 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_osbranches 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 (/wvs-w), defines spelled as options (/Dvs-D), link libraries (advapi32.libvs-ladvapi32), and probes that are really#ifdef _MSC_VERchecks.
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 Type | Target Pattern | Example |
|---|---|---|
| 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:
| Check | Cache 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:
_DEFAULTSfunctions are adefault_depsentry, not adepsentry. Functions likegl_UCHAR_H_DEFAULTSandgl_WCHAR_H_DEFAULTSset the initial shell variable values (HAVE_X=1,REPLACE_X=0) that the check functions may reassign. The header port keeps them in anautoconf_defaultstarget,:defaults, soAC_REQUIRE([gl_WCHAR_H_DEFAULTS])becomes"//gnulib/m4/wchar_h:defaults"indefault_deps; a check's own result overrides a default without a duplicate error.depsrejects defaults targets, so the two cannot be confused. Do not depend on the privategl_*_H_DEFAULTSorgl_*_H_REQUIRE_DEFAULTStargets 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
*_DEFAULTStargets 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:
-
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 byLEFT:/RIGHT:lines naming both sources (likewiseSubst 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)
- Within one target, or between a target and its deps, the build fails with
-
Missing values: Expected substitution variables are not being set
- Solution: Add the missing dependency or add the check locally
-
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'sknown_divergenceswith 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:
- Consistent behavior across builds
- Cross-compilation support without target system access
- 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:
- If the fact can be established without running code, replace the probe with another check, typically
AC_TRY_COMPILEwith#if/#erroron platform macros (see thestrerrorexample). The result still has to match what GNU autoconf reports natively on the platforms the conformance test runs on. - 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 ofAC_RUN_IFELSE, often acase $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 withselect(), explicitAC_SUBST, or other target-appropriate defaults.- Locale and similar environment probes (e.g.
gt_LOCALE_FR) — Usually replace withselect()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 withselect()andAC_DEFINE, and usesAC_FAILon branches where a define must stay absent (seeAC_FAILin 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-onlyAC_CHECK_FUNC— link checkAC_CHECK_DECL— compile-onlyAC_CHECK_TYPE— compile-onlyAC_TRY_COMPILE— compile-onlyAC_TRY_LINK— link checkAC_DEFINE/AC_SUBST— no compilation probe
Strategies for Cross-Compilation
- 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"),
],
}),
)
- 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",
)
- 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",
],
)
Example 4: Link Test with AC_LANG_PROGRAM
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/m4targets that provide needed checks -
Identify all
AC_REQUIREdependencies - Map each M4 macro to its Bazel equivalent
- Convert argument syntax (brackets → quotes)
-
Use plural
macros.AC_CHECK_*for grouped checks (split into individualchecks.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_VARIABLEentries 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
depslist -
For ports in this repository: load
autoconf_cacheand runbazel test //gnulib/tests/compat/<module>:all; for your own project, run adiff_testagainst an expected header - Verify no duplicate checks between direct checks and deps
Common Pitfalls
| Problem | Solution |
|---|---|
| Translating plural macros one-by-one | Use 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 decisions | M4 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 = parameter | Add define = "HAVE_FOO" to create defines in config.h |
| Wrong define names | Follow autoconf conventions: HAVE_<NAME>, SIZEOF_<TYPE>, etc. |
| Duplicate check errors | Use //gnulib/m4 targets instead of manual checks |
| Missing main() wrapper | AC_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 why | The checker drops compiler output; rerun with --action_env=RULES_CC_AUTOCONF_DEBUG=debug |
| Probe differs between a Windows checkout and a Linux one | Line 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/macros | Load autoconf = "autoconf_cache" from //autoconf:autoconf_toolchain.bzl to avoid a toolchain dependency cycle |
Different check kinds for one variable across select() arms | Use 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:windows | Clang 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_FUNC | Renames 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 source | gnulib 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 ambiguous | Name 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 defines | Use '"string"' (outer single, inner double quotes) |
| Platform conditionals | Use select() for case "$host_os" patterns |
| Cross-compilation failures | Validate sizeof/alignof/compute probes for the real target; replace true run-time Autoconf checks with select() or explicit substs |
| Conformance test disagrees with GNU autoconf | Add 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 module | Don't change existing defaults; they may break other modules |
A :defaults or *_DEFAULTS target in deps | Analysis fails: deps wants CcAutoconfInfo, defaults carry CcAutoconfDefaultsInfo. Move it to default_deps |
Unnecessary name + separate AC_DEFINE | Use define = directly unless the cache variable is needed in requires or you need custom values |
Using condition with if_false = None | Use requires = ["cache_var==1"] to gate defines; condition is for value selection, not gating |
Hiding a fixable port behind known_divergences | Fix 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.
| Shape | Identified by | What comes out |
|---|---|---|
| 1. autoconf + automake, usually gnulib | configure.ac, Makefile.am, often m4/gnulib-comp.m4 | The whole module. coreutils, grep, sed, gzip, m4, bison, gawk, make, patch, mpfr, flex. |
| 2. autoconf + hand-written Makefiles | configure.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 rules | The 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 program | configure.ac; Makefiles built from $(wildcard ...), $(patsubst ...), generated fragments, or rules written by awk at configure time | The 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 configure | no configure.ac | Nothing; 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:
| Option | Effect |
|---|---|
--dev | MODULE.bazel overrides rules_cc_autoconf with this checkout. Without it the module depends on the released version. |
--decisions FILE | Read and extend this decisions file instead of decisions.bzl in the output directory. |
--annotate | Comment every check, dep and rule with the m4 call or Makefile line it came from. |
--subdir DIR | Convert the configure.ac in a subdirectory of the tree. |
--vendor-ports | Copy the gnulib ports the conversion uses into the module, see Vendoring the ports. |
--inline-checks | Declare 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 FILE | Convert one m4 file in the layout of the hand ports, see Converting single m4 files. |
--bcr-entry DIR | Write a registry entry in place and take the inputs from it, see A registry entry. |
--http-archive-entry DIR | Write the one generated BUILD file into a consumer's 3rdparty/<name>/ for http_archive, see An http_archive. |
--layout per-file|package | One package per m4 file, or a single package. The default is per-file with --only and package otherwise. |
--external, --in-repo | Emit 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|PATH | The buildifier that formats the output. auto takes the one on PATH. |
--strip-prefix PREFIX | The release archive's strip_prefix for source.json. By default <name>-<version>. |
--keep-work | Keep 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:
| Key | Asked when | Accepts |
|---|---|---|
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 quotes | IGNORE, 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 key | ALWAYS, NEVER, a requires list, or a select() of such lists |
macro:<NAME> | A macro has no port and no translation | IGNORE, 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 translate | IGNORE or the checks that stand in for them |
am_conditional:<NAME> | Sources sit under an automake conditional that is neither an option nor a platform | ALWAYS, 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 ports | ALWAYS, NEVER, or a condition expression |
generated:<path> | A source is not in the tree and no translatable recipe writes it | IGNORE, 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 computes | IGNORE or the C token |
link:<dir>/<target>:<item> | An LDADD item is neither a library of the tree nor a -l option | IGNORE, a dep label, or a linkopt |
The kinds it never asks for, added by hand when wanted:
| Key | Effect | Accepts |
|---|---|---|
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 default | True, 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 so | ALWAYS, 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 out | ALWAYS (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>:CXX | The 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 source | as 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 found | IGNORE |
subst:<NAME>, check:<cache variable> | Drops the project's copy of a result a dependency already provides, when Bazel reports it as produced twice | IGNORE |
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_depof the generated MODULE.bazel. A constantautoconf.bzldefines at the top level (NO_WARNINGS, the option table) can be loaded from it; one defined inside the macro (aglob(), or a list built from one) cannot. - Replaces generated targets. A rule with the same
nameas a generated one replaces it: at the next conversion the generated rule is left out ofautoconf.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 ofBUILD.bazelthat 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.bzlmacro of its own; a one-linegenrulefits 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.
autom4teexpandsconfigure.acwith a prelude that marks everyAC_DEFUNbody and check macro call. The converter never parses m4; anm4_fatalbecomes a warning so the expansion completes. - Targets and deps. Each
AC_DEFUNbody is anautoconftarget.AC_REQUIREand calls of ported macros becomedepson the ports under//gnulib/m4and//autoconf/macros, the headers':defaultstargetsdefault_deps. A local check a port already produces is left out and reported. - Checks. Check macros become
checks.*andmacros.*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 becomeselect()s, options become flags with config settings, assignments to substituted variables becomeAC_SUBSTchecks. 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_depand a dep of what links it. The checks that compile or link against it move to anautoconftarget of their own with the library as its onecc_depsentry, 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 -lgmpunder--enable-mpfr) is a dep in that option'sselect()arm, so turning the flag off drops it from the graph. configure's own probes stay probes;PKG_CHECK_MODULEShas none to run and is decided at conversion time from the registry's versions. - The gnulib tree.
gnulib-comp.m4andgnulib.mkgive the module deps, the wrapper headers, the recipe-written headers and the conditional sources of one gnulib library. - The automake tree. Every
Makefile.amgives acc_libraryper library and acc_binaryper program, withLDADD,LIBADDand 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_FILEStemplates are rendered; everything else is agenerated:decision. - Sub-configures. Each
AC_CONFIG_SUBDIRSdirectory 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'starget_compatible_with; a probe-dependent one (gawk's extension/ needs dlfcn.h) is asubdir:decision, and its targets are taggedmanualuntil 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>_enabledand 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
| Construct | Becomes | Why it is done that way |
|---|---|---|
| A probe without a cache variable | A check named by its place in the source, autoconf_probe_configure_475 | A decision keyed by it holds across converter versions |
AC_REPLACE_FUNCS | A function check and a replacement compiled only when the function is missing | As the macro behaves |
| A check that refers to earlier defines | compile_defines on the check; the body split into autoconf_1, autoconf_2, ... where a probe needs an earlier result of the same body | Probes see confdefs.h as configure's do |
AC_USE_SYSTEM_EXTENSIONS | The extension macros on every later declaration, type, member, compile and link check | configure defines them before those checks |
AC_RUN_IFELSE | The m4's cross-compiling guess per platform, the program kept in a comment; a guess that is the same everywhere is a probe: decision | Nothing is executed at analysis time |
AC_PROG_CC_C99, AC_PROG_CC_STDC, AX_CXX_COMPILE_STDCXX, -std= in CFLAGS or a Makefile | The 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 variable | The probe's success | The 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_IF | The probe, under the name the expansion spells | nasm'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 condition | configure 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 happen | A 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_false | One 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 missing | The 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 them | A 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 target | An 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_INIT | AC_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' defaults | stdio.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: decision | configure 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 out | configure 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 name | gnulib-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 produces | The 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 swapped | An else branch is compiled exactly where the if branch is not |
AM_CPPFLAGS += -I<dir> under an automake conditional | The directory in that arm of includes | As a -D under a conditional goes in that arm of local_defines |
AC_SEARCH_LIBS with actions | The 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 library | LIBS 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_LIB | gettext's lib-link.m4 is that probe plus a prefix search |
-lm, -lpthread, -ldl, -lrt, -lws2_32 and the other system libraries | linkopts, never a registry module | The toolchain provides them |
AC_CANONICAL_HOST and other once-only library macros called inside a project macro | Their port, as when called at the top level | m4sugar 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 ran | Not a definition the expansion missed; noted and left out | The expansion attributes what it expanded |
PKG_CHECK_MODULES under a configure option | The 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 value | Those values are the interface |
AC_MSG_ERROR | Ends the path; a variable assigned on every continuing path counts as always assigned | configure would not reach the rest |
case $host_os / $host_cpu / $GCC | select()s on @platforms//os:*, @platforms//cpu:*, @rules_cc//cc/compiler:*, one dimension per select | One axis per select keeps the arms readable |
AC_ARG_ENABLE / AC_ARG_WITH compared with yes/no | A BOOL_OPTIONS table of defaults with a bool_flag and a config_setting comprehension over it | Thirteen 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 values | A string_flag (an int_flag for numbers) with a config_setting per value | The 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: decision | There 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 links | implementation_deps, after the project's own include directories, unless a public header includes it | Its 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 sources | autoconf_srcs(wrap = "include") | A source's own #include "sibling.h" keeps working |
| gnulib's generated wrapper headers | Written under _gnulib_gen/ (the conditional ones under _gnulib_cond/), served ahead of the source directory | A 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 reported | The appended half is what gives compilers predating C23 (gcc < 9) a one-argument _Static_assert |
An EXTRA_*_SOURCES object | Follows 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 != 0 | gnulib's REPLACE_* are zero or non-zero, and a newer gnulib answers 2 where an older gnulib.mk wrote 1 |
SUBDIRS | Every Makefile.am reached, po, doc, tests and the like excepted | Those build no code |
LDADD order | An archive depends on the archives every LDADD lists after it | A static link keeps the Makefiles' order |
AM_CPPFLAGS, _CPPFLAGS, _CFLAGS | Defines, 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 reported | What make would pass, without configure's part |
-I<dir> naming a source directory with headers | A dependency on a shared library of those headers with the directory as its include path, in implementation_deps for a library | The 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 library | A 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 it | gnulib'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 conditional | Built when the conditional is a configure option or a decided config setting (target_compatible_with), left out when decided NEVER, an am_conditional: decision otherwise | As for programs |
Sources under both a case $host_os arm and a probe | An autoconf_srcs target per platform arm, in that arm's srcs and in deps | One select() dimension at a time |
@top_builddir@/dir/file.c, $(top_srcdir)/dir/file.c in a Makefile.am | The file at that path of the tree | What make would compile |
| Installed headers | The library's hdrs; the rest compile with the sources | Private 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 deps | Bazel 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.a | A 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 aliases | The names consumers expect |
| The headers next to the programs of a directory | One top-level glob constant named after the directories, LIB_SRC_HDRS, in each program's srcs | One glob a hundred programs share |
| Warnings | Every 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 --nowarn | A 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 is | target_compatible_with on that setting | A select() with an empty arm would be a binary without sources |
bin_PROGRAMS = @bin_PROGRAMS@ | Read from EXTRA_PROGRAMS and configure's assignments | The list lives in configure |
| Yacc and lex sources | The 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 lines | A write_file, through autoconf_hdr(mode = "subst") when it carries configure's values | The content is known |
| A sed rewrite of a header | Substitutions | The content is known |
A header assembled by echo lines under conditionals | One template with #if variants | One file, every arm |
AC_CONFIG_FILES templates that are not Makefiles | Rendered by autoconf_hdr; a rendered header joins config_headers beside the AC_CONFIG_HEADERS output | Sources include it as they do config.h |
No AC_CONFIG_HEADERS | autoheader's config.h.in written next to the BUILD file and rendered as config.h behind HAVE_CONFIG_H | configure would pass the defines as -D flags |
| A sub-configure's headers library | Without the root config.h | Bazel 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.bazelis never in the way, since the conversion writesautoconf.bzlbeside 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.bzlbehind, 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:
| File | Role |
|---|---|
decisions.bzl | The decisions file: read if present, created if not. |
source.json | Written 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.json | The 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.tomlis a snapshot of the hand ports: which macro lives in which target and what each provides. Regenerate it after adding or renaming ports withbazel run //tools/convert:update_index; CI fails when the committed snapshot differs from the ports.tools/convert/libraries.tomlmaps-lnames to registry modules, with the version an entry depends on.bazel run //tools/convert:update_librariesrefreshes it from the public registry (--registry <clone>reads a clone'sorigin/maininstead): 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, withconvert --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/convertfix 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 withbazel 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.bazelrcapplies rules_venv'spy_mypy_aspectandpy_ruff_aspect, with the toolchains intools/convert/3rdparty, sobazel build //...fails on a type error, a lint finding or an unformatted file.bazel run @rules_venv//python/ruff:formatand:fixapply what ruff can fix; a target taggedno_mypyorno_ruffis skipped. - The prelude mirrors autoconf 2.73 internals. Updating the pinned autoconf
means revisiting
tools/convert/instrument.m4and the shape regexes next to it.