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}")
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 (via the macros module) and run them 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.
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
Rules
autoconf
load("@rules_cc_autoconf//autoconf:autoconf.bzl", "autoconf")
autoconf(name, deps, checks)
Run autoconf-like checks and produce results.
This rule runs compilation checks against the configured cc_toolchain and produces
a JSON results file containing the check outcomes. Unlike autoconf_hdr, this rule
does not generate any header files - it only performs checks.
Example:
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.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | Additional autoconf or package_info dependencies. | List of labels | optional | [] |
| checks | List of JSON-encoded checks from checks (e.g., checks.AC_CHECK_HEADER('stdio.h')). | List of strings | optional | [] |
autoconf_hdr
Rules
autoconf_hdr
load("@rules_cc_autoconf//autoconf:autoconf_hdr.bzl", "autoconf_hdr")
autoconf_hdr(name, deps, out, defaults, defaults_exclude, defaults_include, 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 | |
| defaults | Whether to include toolchain defaults. When False (the default), no toolchain defaults are included and only the explicit deps provide check results. When True, defaults from the autoconf toolchain are included, subject to filtering by defaults_include or defaults_exclude. | Boolean | optional | False |
| defaults_exclude | Labels to exclude from toolchain defaults. Only effective when defaults=True. If specified, defaults from these labels are excluded. Labels not found in the toolchain are silently ignored. Mutually exclusive with defaults_include. | List of labels | optional | [] |
| defaults_include | Labels to include from toolchain defaults. Only effective when defaults=True. If specified, only defaults from these labels are included. An error is raised if a specified label is not found in the toolchain. Mutually exclusive with defaults_exclude. | List of labels | optional | [] |
| 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, defaults, defaults_exclude, defaults_include, naming)
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 | |
| defaults | Whether to include toolchain defaults. When False (the default), no toolchain defaults are included and only the explicit deps provide check results. When True, defaults from the autoconf toolchain are included, subject to filtering by defaults_include or defaults_exclude. | Boolean | optional | False |
| defaults_exclude | Labels to exclude from toolchain defaults. Only effective when defaults=True. If specified, defaults from these labels are excluded. Labels not found in the toolchain are silently ignored. Mutually exclusive with defaults_include. | List of labels | optional | [] |
| defaults_include | Labels to include from toolchain defaults. Only effective when defaults=True. If specified, only defaults from these labels are included. An error is raised if a specified label is not found in the toolchain. Mutually exclusive with defaults_exclude. | List of labels | optional | [] |
| 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. | String | optional | "package_relative" |
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.
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_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_INLINE
- checks.AC_C_RESTRICT
- checks.AC_DEFINE
- checks.AC_DEFINE_UNQUOTED
- checks.AC_PROG_CC
- checks.AC_PROG_CC_C_O
- checks.AC_PROG_CXX
- checks.AC_SUBST
- checks.AC_TRY_COMPILE
- checks.AC_TRY_LINK
- 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_PROGRAM
checks.AC_CHECK_ALIGNOF
load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")
checks.AC_CHECK_ALIGNOF(type_name, *, define, includes, language, 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, 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, requires, subst)
Check for a function.
Original m4 example:
AC_CHECK_FUNC([malloc])
AC_CHECK_FUNC([mmap], [AC_DEFINE([HAVE_MMAP_FEATURE], [1])])
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, 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, 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.
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,
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, 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, 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, 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_INLINE
load("@rules_cc_autoconf//autoconf:checks.bzl", "checks")
checks.AC_C_INLINE(define, language, requires, subst)
Check what inline keyword the compiler supports.
Original m4 example:
AC_C_INLINE
Example:
macros.AC_C_INLINE()
Tests inline keyword and defines it to the appropriate value.
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_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_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, requires, if_true,
if_false, subst)
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, requires, if_true,
if_false, subst)
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.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_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.
package_info
Rules
package_info
load("@rules_cc_autoconf//autoconf:package_info.bzl", "package_info")
package_info(name, aliases, module_bazel, package_bugreport, package_name, package_tarname,
package_url, package_version, strip_bcr_version)
A rule for parsing module info from MODULE.bazel files.
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:package_info.bzl", "package_info")
package_info(
name = "package_info",
module_bazel = "//:MODULE.bazel",
)
autoconf(
name = "config_h",
out = "config.h",
checks = [
# ...
],
deps = [
":package_info",
],
)
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 |
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.
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:package_info.bzl", "package_info")
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"],
)
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.
Important Constraint: When porting gnulib modules or fixing failing tests, you may only modify BUILD.bazel files. The following are not allowed:
- Golden files (e.g.
golden_config*.h.in,golden_subst*.h.in) configure.acfiles.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
- Porting Strategy
- Best Practices
- Cross-Compilation Considerations
- Complete Examples
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])
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")
load("@rules_cc_autoconf//autoconf:package_info.bzl", "package_info")
package_info(
name = "package",
package_name = "myproject",
package_version = "1.0.0",
)
autoconf(
name = "autoconf",
checks = [
checks.AC_CHECK_HEADER("stdio.h", define = "HAVE_STDIO_H"),
checks.AC_CHECK_HEADER("stdlib.h", define = "HAVE_STDLIB_H"),
checks.AC_CHECK_FUNC("malloc", define = "HAVE_MALLOC"),
checks.AC_CHECK_FUNC("printf", define = "HAVE_PRINTF"),
],
deps = [":package"],
)
autoconf_hdr(
name = "config",
out = "config.h",
template = "config.h.in",
deps = [":autoconf"],
)
Architecture Overview
The migration system consists of three main 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.
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. package_info Rule
Provides package metadata (PACKAGE_NAME, PACKAGE_VERSION, etc.) equivalent to AC_INIT.
Data Flow
configure.ac → BUILD.bazel
↓ ↓
AC_INIT → 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" |
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:
checks.AC_CHECK_HEADER("stdio.h", define = "HAVE_STDIO_H")
checks.AC_CHECK_HEADER("stdlib.h", define = "HAVE_STDLIB_H")
checks.AC_CHECK_HEADER("string.h", define = "HAVE_STRING_H")
checks.AC_CHECK_HEADER("unistd.h", define = "HAVE_UNISTD_H")
Rules:
- Remove square brackets, add quotes
- Split
AC_CHECK_HEADERS(plural) into individualAC_CHECK_HEADERcalls - Add
define = "HAVE_<HEADER>"to create defines inconfig.h
Pattern 2: Function Checks
M4:
AC_CHECK_FUNC([malloc])
AC_CHECK_FUNCS([printf scanf fopen])
Bazel:
checks.AC_CHECK_FUNC("malloc", define = "HAVE_MALLOC")
checks.AC_CHECK_FUNC("printf", define = "HAVE_PRINTF")
checks.AC_CHECK_FUNC("scanf", define = "HAVE_SCANF")
checks.AC_CHECK_FUNC("fopen", define = "HAVE_FOPEN")
Rules:
- Use the GNU Autoconf extern declaration pattern automatically
- Add
define = "HAVE_<FUNCTION>"for config.h defines
Pattern 3: Type Checks
M4:
AC_CHECK_TYPE([size_t])
AC_CHECK_TYPES([int8_t, int64_t], [], [], [[#include <stdint.h>]])
Bazel:
# Without explicit includes (uses AC_INCLUDES_DEFAULT)
checks.AC_CHECK_TYPE("size_t", define = "HAVE_SIZE_T")
# With explicit includes
checks.AC_CHECK_TYPE("int8_t", define = "HAVE_INT8_T", includes = ["#include <stdint.h>"])
checks.AC_CHECK_TYPE("int64_t", define = "HAVE_INT64_T", includes = ["#include <stdint.h>"])
Pattern 4: Declaration Checks
M4:
AC_CHECK_DECL([NULL], [], [], [[#include <stddef.h>]])
AC_CHECK_DECLS([execvpe], [], [], [[#include <unistd.h>]])
Bazel:
checks.AC_CHECK_DECL("NULL", define = "HAVE_DECL_NULL", includes = ["#include <stddef.h>"])
checks.AC_CHECK_DECL("execvpe", define = "HAVE_DECL_EXECVPE", 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 stat.st_blocks])
Bazel:
checks.AC_CHECK_MEMBER(
"struct stat.st_rdev",
define = "HAVE_STRUCT_STAT_ST_RDEV",
includes = ["#include <sys/stat.h>"],
)
checks.AC_CHECK_MEMBER(
"struct tm.tm_zone",
define = "HAVE_STRUCT_TM_TM_ZONE",
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")
Warning: These macros are NOT cross-compile friendly (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 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")
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",
requires = ["HAVE_STDIO_H"], # Only check if stdio.h exists
)
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_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_C_BIGENDIAN() | Check byte order |
AC_C_INLINE() | Check for inline keyword |
AC_C_RESTRICT() | Check for restrict keyword |
AC_COMPUTE_INT(define, expression, ...) | Compute integer at compile time |
AC_CHECK_C_COMPILER_FLAG(flag, ...) | Check C compiler flag |
AC_CHECK_CXX_COMPILER_FLAG(flag, ...) | Check C++ compiler flag |
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.) |
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 | Defines to add before compilation |
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>"])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" |
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",
],
)
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:
Ignore
_DEFAULTSfunctions. Functions likegl_UCHAR_H_DEFAULTS,gl_WCHAR_H_DEFAULTS, etc. set initial shell variable values that get overridden by the actual check functions. In Bazel, these defaults cause duplicate check errors if both the defaults and the actual check try to set the same variable.
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.bzl", "autoconf")
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 variable is defined both locally and in a dependency
- The build will fail with an error like:
Cache variable 'X' is defined both locally and in dependencies - Solution: Remove the local definition and let the dependency provide it
- Or: Create a shared target (see Step 4 above)
- The build will fail with an error like:
-
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
Important architectural difference: Bazel uses a shared dependency graph with global defaults, while autoconf runs each configure.ac independently.
In autoconf:
gl_UCHAR_H_DEFAULTSsetsHAVE_C32RTOMB=1as a shell variable- If
gl_FUNC_C32RTOMBis later called, it may override this to0 - Different
configure.acfiles may or may not callgl_FUNC_C32RTOMB
In Bazel:
- Global defaults (like
HAVE_C32RTOMB="1"inuchar_h) are shared across all modules - There's only ONE value for each variable in the dependency graph
- Changing a default to make one module's test pass may break other modules
Key principle: Existing defaults should NOT be changed to make a new module pass. Defaults are fine as long as nothing in the dependency graph explicitly depends on those targets with conflicting values.
Subst test disagreements: Because of this architectural difference, subst tests may sometimes disagree between Bazel and autoconf+configure. This happens when:
- Autoconf runs a specific check (e.g.,
gl_FUNC_C32RTOMB→HAVE_C32RTOMB=0on macOS) - Bazel uses the global default (e.g.,
uchar_h→HAVE_C32RTOMB=1)
These cases should be evaluated on a case-by-case basis:
- If the golden file was generated from an autoconf run that included specific checks not in the Bazel dependency graph, the golden may need updating
- If the Bazel dependency graph should include those checks, add the appropriate dependency
- Sometimes the disagreement is acceptable - document it and move on
Step 7: Run Tests and Iterate
# Build the module
bazel build //gnulib/m4/c32rtomb:c32rtomb
# Run compatibility tests
bazel test //gnulib/tests/compatibility/c32rtomb:all
Compare test output against golden files and fix any discrepancies.
Step 8: Test on Linux (if needed)
When porting modules that have platform-specific behavior, you may need to test on Linux to verify correctness. The repository provides several scripts for running tests in Linux Docker containers:
Available Linux Test Scripts
1. test_linux_docker.sh — Comprehensive testing on multiple Linux distributions
- Tests on Ubuntu 22.04 and Rocky Linux 9
- Usage:
./test_linux_docker.sh [--ubuntu-only | --rocky-only] - Runs full test suite:
bazel test //autoconf/... //gnulib/... - Saves results to
docker_test_results/directory - Generates summary files with test statistics
2. test_modules.sh — Test specific modules
- Usage:
./test_modules.sh [--docker] module1 module2 ... - Without
--docker: runs tests locally (macOS) - With
--docker: runs tests in Docker (Linux) - Example:
./test_modules.sh --docker fsusage renameat
3. docker_bazel.sh — Generic Bazel command runner
- Usage:
./docker_bazel.sh [--amd64] <bazel_command> - Builds/reuses Docker image and runs any Bazel command
- Supports
--amd64flag for x86_64 emulation - Example:
./docker_bazel.sh "test //gnulib/tests/compatibility/c32rtomb:all --test_output=errors"
4. update_linux_golden.sh — Update Linux golden files
- Usage:
./update_linux_golden.sh module1 module2 ... - Builds targets in Docker, extracts
config.handsubst.houtputs - Updates
golden_config_linux.h.inandgolden_subst_linux.h.infiles - Use when Linux-specific golden files need updating
5. scripts/run_linux_tests.sh — Run tests and update golden files
- Builds Docker image from
Dockerfile - Runs tests and executes
update.pyto extract results - Mounts
gnulibdirectory so updates write directly to host
When to Use Linux Testing
- Platform-specific checks: Modules with
select()statements for Linux-specific behavior - Golden file updates: When Linux golden files (
golden_config_linux.h.in,golden_subst_linux.h.in) need updating - Cross-platform verification: Verify that platform conditionals work correctly
- Test failures: Debug Linux-specific test failures
Example Workflow
# Test a specific module on Linux
./test_modules.sh --docker c32rtomb
# Update Linux golden files after making changes
./update_linux_golden.sh c32rtomb
# Run full test suite on Linux
./test_linux_docker.sh --ubuntu-only
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
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",
)
6. Only Split Golden Files When Necessary
Golden files should only be split into platform-specific versions (golden_config_linux.h.in, golden_config_macos.h.in) when there are genuine differences in the expected output between platforms.
Use a single golden file when:
- The output is identical across all platforms
- Any platform differences are handled in the Bazel targets (not the expected output)
# GOOD: Single golden file when content is the same
gnu_gnulib_diff_test_suite(
name = "sig_atomic_t_test",
golden_config_h = "golden_config.h.in",
golden_subst_h = "golden_subst.h.in",
...
)
Split golden files only when:
- The
gnu_autoconftest produces genuinely different outputs on different platforms - Platform-specific defines have different values (e.g.,
REPLACE_FSTATis1on macOS,0on Linux)
# ONLY when content genuinely differs between platforms
gnu_gnulib_diff_test_suite(
name = "fstat_test",
golden_config_h = {
"linux": "golden_config_linux.h.in",
"macos": "golden_config_macos.h.in",
},
...
)
Anti-pattern: Don't split golden files just because you're unsure — first verify the content differs by running gnu_autoconf tests on both platforms.
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.
When M4 macros use AC_TRY_EVAL or AC_RUN_IFELSE (running compiled code at configure time), these should be replaced with select() statements that provide platform-specific defaults.
Runtime vs Compile-Time Checks
Some macros require running compiled code, which doesn't work when cross-compiling:
NOT cross-compile safe (use select() instead):
AC_CHECK_SIZEOF— Computessizeof()by running codeAC_CHECK_ALIGNOF— Computes alignment by running codeAC_COMPUTE_INT— Evaluates expressions by running codeAC_C_BIGENDIAN— Detects endianness at runtimeAC_TRY_EVAL/AC_RUN_IFELSE— Runs compiled test programs- Locale detection macros (e.g.,
gt_LOCALE_FR) — Tests locale availability at runtime
Cross-compile safe:
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— No check, just defines
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:
autoconf(
name = "endian",
checks = select({
"@platforms//cpu:x86_64": [
checks.AC_DEFINE("WORDS_BIGENDIAN", "0"),
],
"@platforms//cpu:arm64": [
checks.AC_DEFINE("WORDS_BIGENDIAN", "0"), # ARM64 is little-endian
],
"//conditions:default": [],
}),
)
Complete Examples
Example 1: Simple Module (posix_memalign)
Original M4: gnulib/m4/posix_memalign.m4
Bazel:
"""https://github.com/coreutils/gnulib/blob/635dbdcf501d52d2e42daf6b44261af9ce2dfe38/m4/posix_memalign.m4"""
load("//autoconf:autoconf.bzl", "autoconf")
load("//autoconf:checks.bzl", "checks")
autoconf(
name = "posix_memalign",
checks = [
checks.AC_CHECK_FUNC("posix_memalign", define = "HAVE_POSIX_MEMALIGN"),
checks.AC_SUBST("REPLACE_POSIX_MEMALIGN", "1"),
],
visibility = ["//visibility:public"],
deps = [
"//autoconf/macros/AC_CANONICAL_HOST",
"//gnulib/m4/extensions",
"//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/635dbdcf501d52d2e42daf6b44261af9ce2dfe38/m4/fstat.m4"""
load("//autoconf:autoconf.bzl", "autoconf")
load("//autoconf:checks.bzl", "checks")
autoconf(
name = "fstat",
checks = select({
"@platforms//os:macos": [
# macOS: stat can return negative tv_nsec
checks.AC_SUBST("REPLACE_FSTAT", "1"),
],
"@platforms//os:windows": [
# Windows: stat returns timezone-affected timestamps
checks.M4_VARIABLE("REPLACE_FSTAT", "1"),
],
"//conditions:default": [],
}),
visibility = ["//visibility:public"],
deps = [
"//gnulib/m4/sys_stat_h",
"//gnulib/m4/sys_types_h",
],
)
Example 3: Conditional Checks (strerror)
Original M4: gnulib/m4/strerror.m4
Bazel:
"""https://github.com/coreutils/gnulib/blob/635dbdcf501d52d2e42daf6b44261af9ce2dfe38/m4/strerror.m4"""
load("//autoconf:autoconf.bzl", "autoconf")
load("//autoconf:checks.bzl", "checks", "utils")
autoconf(
name = "gl_FUNC_STRERROR_0",
checks = [
# Compile-time platform detection for strerror(0) behavior
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 replacement\"",
"#endif",
],
"",
),
),
# Set REPLACE_STRERROR_0 if check fails
checks.AC_DEFINE(
"REPLACE_STRERROR_0",
condition = "_gl_cv_func_strerror_0_works",
if_false = 1,
),
] + select({
"@platforms//os:macos": [
checks.AC_SUBST("REPLACE_STRERROR_0", "1"),
],
"//conditions:default": [
checks.AC_SUBST("REPLACE_STRERROR_0", "0"),
],
}),
visibility = ["//visibility:public"],
deps = [
"//autoconf/macros/AC_CANONICAL_HOST",
"//gnulib/m4/errno_h",
"//gnulib/m4/extensions",
],
)
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)
- Split plural macros into individual calls
-
Add
define =parameters for config.h defines -
Add
subst =parameters for @VAR@ substitutions -
Handle platform conditionals with
select() - Add comments referencing original M4 file and line numbers
-
Add dependencies to
depslist - Test with diff tests against golden files
- Verify no duplicate checks between direct checks and deps
Common Pitfalls
| Problem | Solution |
|---|---|
| Forgetting to split plural macros | AC_CHECK_HEADERS([a b c]) needs 3 separate AC_CHECK_HEADER calls |
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 code must include int main(void) { ... } or use utils.AC_LANG_PROGRAM |
| String literals in defines | Use '"string"' (outer single, inner double quotes) |
| Platform conditionals | Use select() for case "$host_os" patterns |
| Cross-compilation failures | Avoid AC_CHECK_SIZEOF and similar runtime checks |
| Subst test disagrees with autoconf | Evaluate case-by-case: may be due to global defaults vs specific checks (see Step 6) |
| Changing defaults to fix one module | Don't change existing defaults; they may break other modules |
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 |
| Unnecessary golden file split | Only split into _linux.h.in / _macos.h.in when content genuinely differs between platforms |