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 configure step can do dozens of unrelated checks at once, making cache behaviour and debugging harder.
  • Environment-sensitive: running shell scripts and toolchain discovery inside a Bazel action is fragile across platforms, remote execution, and containerized builds.

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

Rules

autoconf

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

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
depsAdditional autoconf or package_info dependencies.List of labelsoptional[]
checksList of JSON-encoded checks from checks (e.g., checks.AC_CHECK_HEADER('stdio.h')).List of stringsoptional[]

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

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
depsList of autoconf targets which provide check results. Results from all deps will be merged together, and duplicate define names will produce an error. If not provided, an empty results file will be created.List of labelsoptional[]
outThe output config file (typically config.h).Label; nonconfigurablerequired
defaultsWhether 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.
BooleanoptionalFalse
defaults_excludeLabels 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 labelsoptional[]
defaults_includeLabels 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 labelsoptional[]
inlinesA mapping of strings to files for replace within the content of the given template attribute.

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

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

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


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

autoconf_srcs

Rules

autoconf_srcs

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

autoconf_srcs(name, deps, srcs, 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

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
depsList of autoconf targets which provide defines. Results from all deps will be merged together, and duplicate define names will produce an error.List of labelsrequired
srcsA mapping of source file to define required to compile the file.Dictionary: Label -> Stringrequired
defaultsWhether 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.
BooleanoptionalFalse
defaults_excludeLabels 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 labelsoptional[]
defaults_includeLabels 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 labelsoptional[]
namingHow to name generated sources: package_relative keeps the original package-relative path, ac_suffix inserts .ac. before the final suffix (e.g. foo.cc -> foo.ac.cc), and per_target prefixes outputs with {ctx.label.name} to namespace them per rule.Stringoptional"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 true if:

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

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

If the condition includes a comparison operator (e.g., condition="ac_cv_header_foo_h==1"), it performs value comparison instead of truthy check.

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

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

NameDescriptionDefault Value
type_nameName of the typenone
defineCustom define name (defaults to ALIGNOF_<TYPE>)None
includesOptional list of header names to include (e.g. ["stddef.h"])None
languageLanguage to use for check ("c" or "cpp")"c"
requiresList of requirements that must be met before this check runs. Can be simple define names (e.g., "HAVE_FOO") or value-based requirements (e.g., "REPLACE_FSTAT=1" to require specific value)None
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

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

checks.AC_CHECK_CXX_COMPILER_FLAG

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

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

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

Original m4 example:

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

Example:

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

PARAMETERS

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

RETURNS

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

checks.AC_CHECK_C_COMPILER_FLAG

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

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

Check if the C compiler supports a specific flag.

Original m4 example:

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

Example:

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

PARAMETERS

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

RETURNS

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

checks.AC_CHECK_DECL

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

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

Check if a symbol is declared.

Original m4 example:

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

Example:

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

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

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

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

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

PARAMETERS

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

RETURNS

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

checks.AC_CHECK_FUNC

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

checks.AC_CHECK_FUNC(function, name, *, define, code, language, compile_defines, 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

NameDescriptionDefault Value
functionName of the function (e.g., "printf")none
nameCache variable name (defaults to ac_cv_func_<FUNCTION> following autoconf convention)None
defineDefine behavior: - None (default): No define is created, only cache variable - True: Create define using the cache variable name (name) - "HAVE_FOO": Create define with explicit name HAVE_FOO (implies define=True)None
codeCustom code to compile (optional).None
languageLanguage to use for check ("c" or "cpp")"c"
compile_definesOptional list of preprocessor define names from previous checks to add before includes (e.g., ["_GNU_SOURCE", "_DARWIN_C_SOURCE"]). Each string must match the define name of a previous check. The values from those checks will be used automatically.None
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
substSubst behavior: - None (default): No subst is created, only cache variable - True: Create subst using the cache variable name (name) - "HAVE_FOO": Create subst with explicit name HAVE_FOO (implies subst=True)None

RETURNS

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

checks.AC_CHECK_HEADER

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

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

Check for a header file.

Original m4 example:

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

Example:

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

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

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

PARAMETERS

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

RETURNS

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

checks.AC_CHECK_LIB

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

checks.AC_CHECK_LIB(library, function, *, name, define, code, language, 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

NameDescriptionDefault Value
libraryLibrary name without the -l prefix (e.g., "m", "pthread")none
functionFunction name to check for in the library (e.g., "cos", "pthread_create")none
nameThe name of the cache variable. Defaults to ac_cv_lib_library_functionNone
defineCustom define name (defaults to HAVE_LIB<LIBRARY>)None
codeCustom code to compile and link (optional)None
languageLanguage to use for check ("c" or "cpp")"c"
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
if_trueValue to use when check succeeds (currently not used for this check type).None
if_falseValue to use when check fails (currently not used for this check type).None
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

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

checks.AC_CHECK_MEMBER

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

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

Check if a struct or union has a member.

Original m4 example:

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

Example:

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

PARAMETERS

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

RETURNS

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

checks.AC_CHECK_SIZEOF

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

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

Check the size of a type.

Original m4 example:

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

Example:

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

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

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

PARAMETERS

NameDescriptionDefault Value
type_nameName of the type (e.g., int, size_t, void*)none
nameCache variable name (defaults to ac_cv_sizeof_<TYPE> following autoconf convention)None
defineDefine behavior: - None (default): No define is created, only cache variable - True: Create define using the cache variable name (name) - "SIZEOF_FOO": Create define with explicit name SIZEOF_FOO (implies define=True)None
includesOptional list of header names to include (e.g. ["stddef.h"])None
languageLanguage to use for check ("c" or "cpp")"c"
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
if_trueValue to use when check succeeds (currently not used for this check type).None
if_falseValue to use when check fails (currently not used for this check type).None
substSubst behavior: - None (default): No subst is created, only cache variable - True: Create subst using the cache variable name (name) - "SIZEOF_FOO": Create subst with explicit name SIZEOF_FOO (implies subst=True)None

RETURNS

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

checks.AC_CHECK_TYPE

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

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

Check for a type.

Original m4 example:

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

Example:

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

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

PARAMETERS

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

RETURNS

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

checks.AC_COMPUTE_INT

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

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

Compute an integer value at compile time.

Original m4 example:

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

Example:

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

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

PARAMETERS

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

RETURNS

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

checks.AC_C_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

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

RETURNS

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

checks.AC_C_RESTRICT

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

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

Check if the compiler supports restrict keyword.

Original m4 example:

AC_C_RESTRICT

Example:

checks.AC_C_RESTRICT()

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

PARAMETERS

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

RETURNS

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

checks.AC_DEFINE

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

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

Define a configuration macro.

Original m4 example:

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

Conditional example (m4):

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

Example:

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

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

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

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

PARAMETERS

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

RETURNS

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

checks.AC_DEFINE_UNQUOTED

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

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

Define a configuration macro without quoting (AC_DEFINE_UNQUOTED).

Original m4 example:

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

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

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

Example:

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

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

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

PARAMETERS

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

RETURNS

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

checks.AC_PROG_CC

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

checks.AC_PROG_CC(requires)

Check that a C compiler is available.

Original m4 example:

AC_PROG_CC
AC_PROG_CC([gcc clang])

Example:

macros.AC_PROG_CC()

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

PARAMETERS

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

RETURNS

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

checks.AC_PROG_CC_C_O

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

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

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

Original m4 example:

AC_PROG_CC_C_O

Example:

macros.AC_PROG_CC_C_O()

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

PARAMETERS

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

RETURNS

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

checks.AC_PROG_CXX

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

checks.AC_PROG_CXX(requires)

Check that a C++ compiler is available.

Original m4 example:

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

Example:

macros.AC_PROG_CXX()

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

PARAMETERS

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

RETURNS

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

checks.AC_SUBST

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

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

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

Original m4 example:

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

Conditional example (m4):

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

Example:

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

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

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

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

PARAMETERS

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

RETURNS

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

checks.AC_TRY_COMPILE

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

checks.AC_TRY_COMPILE(*, code, name, define, includes, language, compile_defines, 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

NameDescriptionDefault Value
codeCode to compile. If includes is also provided, this is treated as the body of main(). Otherwise, it should be complete C code. For AC_LANG_PROGRAM-style tests, use code = utils.AC_LANG_PROGRAM(prologue, body).None
nameCache variable name.None
defineDefine name to set if compilation succeedsNone
includesOptional list of headers to include. When provided, code is treated as the body of main() function.None
languageLanguage to use for check ("c" or "cpp")"c"
compile_definesOptional list of preprocessor define names from previous checks to add before includes (e.g., ["_GNU_SOURCE", "_DARWIN_C_SOURCE"]). Each string must match the define name of a previous check. The values from those checks will be used automatically.None
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
if_trueValue to use when check succeeds (currently not used for this check type).None
if_falseValue to use when check fails (currently not used for this check type).None
substIf True, automatically create a substitution variable with the same name that will be set to "1" if the check succeeds, "0" if it fails.None

RETURNS

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

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

NameDescriptionDefault Value
codeCode to compile and link. If includes is also provided, this is treated as the body of main(). Otherwise, it should be complete C code. For AC_LANG_PROGRAM-style tests use code = utils.AC_LANG_PROGRAM(prologue, body).None
nameCache variable name.None
defineDefine name to set if compilation and linking succeedsNone
includesOptional list of headers to include (backward compatibility). When provided, code is treated as the body of main() function.None
languageLanguage to use for check ("c" or "cpp")"c"
compile_definesOptional list of preprocessor define names from previous checks to add before includes (e.g., ["_GNU_SOURCE", "_DARWIN_C_SOURCE"]). Each string must match the define name of a previous check. The values from those checks will be used automatically.None
requiresRequirements that must be met for this check to run. Can be define names (e.g., "HAVE_FOO"), negated (e.g., "!HAVE_FOO"), or value-based (e.g., "REPLACE_FSTAT==1", "REPLACE_FSTAT!=0").None
if_trueValue to use when check succeeds (currently not used for this check type).1
if_falseValue to use when check fails (currently not used for this check type).0
substIf True, mark as a substitution variable (for @VAR@ replacement in subst.h).None

RETURNS

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

checks.M4_VARIABLE

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

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

Define a configuration M4 variable.

Original m4 example:

REPLACE_FOO=1

Conditional example (m4):

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

Example:

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

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

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

PARAMETERS

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

RETURNS

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

macros.AC_CHECK_DECLS

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

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

Check multiple declarations, creating HAVE_DECL_ defines (AC_CHECK_DECLS).

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

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

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

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

Defines: HAVE_DECL_NULL, HAVE_DECL_STDOUT, HAVE_DECL_STDERR

PARAMETERS

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

RETURNS

List of JSON-encoded check strings.

macros.AC_CHECK_FUNCS

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

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

Check multiple functions, creating HAVE_ defines (AC_CHECK_FUNCS).

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

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

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

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

Defines: HAVE_MALLOC, HAVE_FREE, HAVE_PRINTF

PARAMETERS

NameDescriptionDefault Value
functionsList of function names to check.none
codeCustom code to compile (applies to all checks).None
languageLanguage to use for checks ("c" or "cpp")."c"
compile_definesOptional list of preprocessor define names (applies to all checks).None
requiresRequirements that must be met (applies to all checks).None
substSubst behavior (applies to all checks).None

RETURNS

List of JSON-encoded check strings.

macros.AC_CHECK_HEADERS

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

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

Check multiple headers, creating HAVE_

defines (AC_CHECK_HEADERS).

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

.

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

Original m4 example:

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

Example:

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

PARAMETERS

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

RETURNS

List of JSON-encoded check strings.

macros.AC_CHECK_MEMBERS

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

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

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

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

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

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

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

PARAMETERS

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

RETURNS

List of JSON-encoded check strings.

macros.AC_CHECK_TYPES

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

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

Check multiple types, creating HAVE_ defines (AC_CHECK_TYPES).

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

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

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

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

Defines: HAVE_SIZE_T, HAVE_PTHREAD_T, HAVE_PID_T

PARAMETERS

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

RETURNS

List of JSON-encoded check strings.

utils.AC_LANG_PROGRAM

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

utils.AC_LANG_PROGRAM(prologue, body)

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

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

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

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

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

PARAMETERS

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

RETURNS

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

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

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

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.ac files
  • .bzl files (autoconf rules, checks, macros, etc.)
  • Duplicates targets (e.g. //gnulib/tests/duplicates:gnulib and its dependency list)

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


Table of Contents

  1. Quick Start
  2. Architecture Overview
  3. Core Concepts
  4. Migration Patterns
  5. API Reference
  6. Platform Conditionals
  7. Dependencies and Reusable Modules
  8. Porting Strategy
  9. Best Practices
  10. Cross-Compilation Considerations
  11. 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" — For config.h files (processes #undef directives)
  • 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:

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

Example:

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

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

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

The name Parameter

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

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

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

The define Parameter

Controls whether and how a define is created:

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

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

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

Efficiency: define vs. name + separate AC_DEFINE

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

Use define directly (preferred)

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

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

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

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

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

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

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

requires vs. condition

Use the correct parameter for the right purpose:

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

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 individual AC_CHECK_HEADER calls
  • Add define = "HAVE_<HEADER>" to create defines in config.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",
)

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

MacroDescription
AC_CHECK_HEADER(header, ...)Check for a header file
AC_CHECK_FUNC(function, ...)Check for a function
AC_CHECK_TYPE(type_name, ...)Check for a type
AC_CHECK_DECL(symbol, ...)Check for a declaration
AC_CHECK_MEMBER(aggregate.member, ...)Check for a struct/union member
AC_CHECK_SIZEOF(type_name, ...)Check size of a type
AC_CHECK_ALIGNOF(type_name, ...)Check alignment of a type
AC_CHECK_LIB(library, function, ...)Check for a function in a library
AC_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:

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

utils Struct — Helper Functions

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

Common Parameters

Most macros support these parameters:

ParameterTypeDescription
namestringCache variable name (auto-generated if omitted)
definestring/boolDefine name or True to use cache var name
includeslistInclude directives (e.g., ["#include <stdio.h>"])
languagestring"c" or "cpp"
requireslistDependencies that must be satisfied
compile_defineslistDefines to add before compilation
conditionstringCondition for value selection
if_trueanyValue when condition is true
if_falseanyValue when condition is false
substbool/stringAlso create substitution variable

Platform Conditionals

Prefer actual checks over select()+AC_DEFINE

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

When M4 uses a check macro such as:

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

Prefer:

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

Avoid:

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

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

Using select() for Platform-Specific Checks

M4:

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

Bazel:

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

Common Platform Constraints

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

Combining Platform-Specific and Common Checks

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

Dependencies and Reusable Modules

Using Pre-built //gnulib/m4 Targets

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

Before (manual checks):

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

After (using gnulib modules):

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

Common Patterns for gnulib Target Names

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

Declaring Dependencies Between Modules

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

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

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 _DEFAULTS functions. Functions like gl_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 *_DEFAULTS targets from deps

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

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

load("//autoconf:autoconf.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:

  1. 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)
  2. Missing values: Expected substitution variables are not being set

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

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

Step 6: Understand Global Defaults vs Check Results

Important architectural difference: Bazel uses a shared dependency graph with global defaults, while autoconf runs each configure.ac independently.

In autoconf:

  • gl_UCHAR_H_DEFAULTS sets HAVE_C32RTOMB=1 as a shell variable
  • If gl_FUNC_C32RTOMB is later called, it may override this to 0
  • Different configure.ac files may or may not call gl_FUNC_C32RTOMB

In Bazel:

  • Global defaults (like HAVE_C32RTOMB="1" in uchar_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_C32RTOMBHAVE_C32RTOMB=0 on macOS)
  • Bazel uses the global default (e.g., uchar_hHAVE_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 --amd64 flag 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.h and subst.h outputs
  • Updates golden_config_linux.h.in and golden_subst_linux.h.in files
  • 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.py to extract results
  • Mounts gnulib directory 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_autoconf test produces genuinely different outputs on different platforms
  • Platform-specific defines have different values (e.g., REPLACE_FSTAT is 1 on macOS, 0 on 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:

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

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 — Computes sizeof() by running code
  • AC_CHECK_ALIGNOF — Computes alignment by running code
  • AC_COMPUTE_INT — Evaluates expressions by running code
  • AC_C_BIGENDIAN — Detects endianness at runtime
  • AC_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-only
  • AC_CHECK_FUNC — Link check
  • AC_CHECK_DECL — Compile-only
  • AC_CHECK_TYPE — Compile-only
  • AC_TRY_COMPILE — Compile-only
  • AC_TRY_LINK — Link check
  • AC_DEFINE — No check, just defines

Strategies for Cross-Compilation

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

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

M4 (uses runtime locale detection):

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

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

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

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

Migration Checklist

When migrating an M4 file, follow this checklist:

  • Read and understand the original M4 file
  • Check for existing //gnulib/m4 targets that provide needed checks
  • Identify all AC_REQUIRE dependencies
  • Map each M4 macro to its Bazel equivalent
  • Convert argument syntax (brackets → quotes)
  • 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 deps list
  • Test with diff tests against golden files
  • Verify no duplicate checks between direct checks and deps

Common Pitfalls

ProblemSolution
Forgetting to split plural macrosAC_CHECK_HEADERS([a b c]) needs 3 separate AC_CHECK_HEADER calls
Missing define = parameterAdd define = "HAVE_FOO" to create defines in config.h
Wrong define namesFollow autoconf conventions: HAVE_<NAME>, SIZEOF_<TYPE>, etc.
Duplicate check errorsUse //gnulib/m4 targets instead of manual checks
Missing main() wrapperAC_TRY_COMPILE code must include int main(void) { ... } or use utils.AC_LANG_PROGRAM
String literals in definesUse '"string"' (outer single, inner double quotes)
Platform conditionalsUse select() for case "$host_os" patterns
Cross-compilation failuresAvoid AC_CHECK_SIZEOF and similar runtime checks
Subst test disagrees with autoconfEvaluate case-by-case: may be due to global defaults vs specific checks (see Step 6)
Changing defaults to fix one moduleDon't change existing defaults; they may break other modules
Unnecessary name + separate AC_DEFINEUse define = directly unless the cache variable is needed in requires or you need custom values
Using condition with if_false = NoneUse requires = ["cache_var==1"] to gate defines; condition is for value selection, not gating
Unnecessary golden file splitOnly split into _linux.h.in / _macos.h.in when content genuinely differs between platforms