"""FFmpeg component configuration rule. Provides a Bazel rule that generates config_components.h, config_extra.h, and *_list.c files based on per-component bool_flag build settings. The with_defaults/ transition (in component_flags.bzl) pre-computes the full resolved component state from static profiles in component_resolved.bzl, so this rule simply reads the flags as set -- no runtime dependency resolution. Usage in BUILD.bazel: ffmpeg_component_gen( name = "gen_components", outs = [ "config_components.h", "config_extra.h", "libavcodec/codec_list.c", ... ], ) Each output file becomes an individually addressable label (e.g. ":config_components.h", ":libavcodec/codec_list.c") so library targets can depend on exactly the files they need. Configure via build flags: bazel build //:ffmpeg --//:enable_aac_decoder=True --//:enable_h264_decoder=True bazel build //:ffmpeg --//:enable_vp9_decoder=False """ load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") load("@bazel_skylib//rules:expand_template.bzl", "expand_template") load( ":component_defs.bzl", "COMPONENT_REGISTRY", "CONFIG_EXTRA_REGISTRY", "FILTER_SYMBOL_MAP", "PROFILE_EVERYTHING", ) # --------------------------------------------------------------------------- # config.asm generation # --------------------------------------------------------------------------- def ffmpeg_config_asm(*, name, template, out): """Generates a NASM config.asm from a C config.h via two-pass text substitution. The original FFmpeg configure script generates config.asm independently from config.h -- it calls print_config() which writes %define lines directly to the .asm file in parallel with #define lines to the .h file. The result is a flat list of %define statements with no conditional logic. In Bazel, config.h is produced by autoconf_hdr and we derive config.asm from it via two expand_template passes: Pass 1 -- convert C preprocessor directives to NASM equivalents: #define ARCH_/CONFIG_/HAVE_ -> %define (value definitions) #ifndef -> %ifndef (fallback guards) #endif -> %endif Pass 2 -- comment out remaining C-only syntax: #define (non-ARCH/CONFIG/HAVE), #undef, #include, /* */, // The %ifndef/%endif conversion is critical: config.h.in has fallback blocks like "#ifndef ARCH_X86_64 / #define ARCH_X86_64 0 / #endif". Without proper conversion, these fallbacks unconditionally overwrite correct autoconf values. Args: name: Name of the final config.asm target. template: Label of the config.h target to convert. out: Output filename for the generated NASM config file. """ expand_template( name = name + "_step1", out = name + "_step1.h", substitutions = { "#define ARCH_": "%define ARCH_", "#define CONFIG_": "%define CONFIG_", "#define HAVE_": "%define HAVE_", "#endif": "%endif", "#ifndef ": "%ifndef ", }, template = template, ) expand_template( name = name, out = out, substitutions = { "#define ": "; #define", "#include ": "; #include", "#undef ": "; #undef", "/* ": "; /*", "// ": "; // ", }, template = ":" + name + "_step1", ) _EXTRA_KEYS = sorted(CONFIG_EXTRA_REGISTRY.keys()) def _read_state(ctx): """Read per-component and CONFIG_EXTRA bool_flags into a state dict.""" state = {} for i, target in enumerate(ctx.attr._components): state[PROFILE_EVERYTHING[i]] = target[BuildSettingInfo].value for i, target in enumerate(ctx.attr._extras): state[_EXTRA_KEYS[i]] = target[BuildSettingInfo].value return state # --------------------------------------------------------------------------- # Header generation # --------------------------------------------------------------------------- def _gen_config_components(state): """Generate config_components.h content.""" lines = [ "/* Automatically generated by Bazel - do not modify! */", "#ifndef FFMPEG_CONFIG_COMPONENTS_H", "#define FFMPEG_CONFIG_COMPONENTS_H", ] for comp in sorted(COMPONENT_REGISTRY.keys()): val = "1" if state.get(comp, False) else "0" lines.append("#define CONFIG_{} {}".format(comp.upper(), val)) lines.append("#endif /* FFMPEG_CONFIG_COMPONENTS_H */") return "\n".join(lines) + "\n" def _gen_config_extra(state): """Generate config_extra.h content.""" lines = [ "/* Automatically generated by Bazel - do not modify! */", "/* CONFIG_EXTRA subsystems resolved from component dependencies */", ] for comp in sorted(CONFIG_EXTRA_REGISTRY.keys()): val = "1" if state.get(comp, False) else "0" lines.append("#define CONFIG_{} {}".format(comp.upper(), val)) return "\n".join(lines) + "\n" # --------------------------------------------------------------------------- # List file generation # --------------------------------------------------------------------------- def _identity_symbol(comp, _entry): return comp def _filter_symbol(comp, _entry): return FILTER_SYMBOL_MAP.get(comp, comp) def _indev_symbol(comp, _entry): if comp.endswith("_indev"): return comp[:-len("_indev")] + "_demuxer" return comp def _outdev_symbol(comp, _entry): if comp.endswith("_outdev"): return comp[:-len("_outdev")] + "_muxer" return comp _FILTER_BUFFER_ENTRIES = [ "asrc_abuffer", "vsrc_buffer", "asink_abuffer", "vsink_buffer", ] def _gen_list(state, comp_types, struct_type, list_name, symbol_fn): """Generate a *_list.c file's content.""" lines = ["static const {} * const {} [] = {{".format(struct_type, list_name)] for comp in sorted(COMPONENT_REGISTRY.keys()): entry = COMPONENT_REGISTRY[comp] if entry.get("type") not in comp_types: continue if not state.get(comp, False): continue sym = symbol_fn(comp, entry) lines.append(" &ff_{},".format(sym)) if list_name == "filter_list": for buf in _FILTER_BUFFER_ENTRIES: lines.append(" &ff_{},".format(buf)) lines.append(" NULL };") return "\n".join(lines) + "\n" # --------------------------------------------------------------------------- # Dispatch table keyed by output basename # --------------------------------------------------------------------------- _LIST_DISPATCH = { "bsf_list.c": (["bsf"], "FFBitStreamFilter", "bitstream_filters", _identity_symbol), "codec_list.c": (["decoder", "encoder"], "FFCodec", "codec_list", _identity_symbol), "demuxer_list.c": (["demuxer"], "FFInputFormat", "demuxer_list", _identity_symbol), "filter_list.c": (["filter"], "AVFilter", "filter_list", _filter_symbol), "indev_list.c": (["indev"], "FFInputFormat", "indev_list", _indev_symbol), "muxer_list.c": (["muxer"], "FFOutputFormat", "muxer_list", _identity_symbol), "outdev_list.c": (["outdev"], "FFOutputFormat", "outdev_list", _outdev_symbol), "parser_list.c": (["parser"], "AVCodecParser", "parser_list", _identity_symbol), "protocol_list.c": (["protocol"], "URLProtocol", "url_protocols", _identity_symbol), } # --------------------------------------------------------------------------- # Rule implementation # --------------------------------------------------------------------------- def _ffmpeg_component_gen_impl(ctx): state = _read_state(ctx) for out in ctx.outputs.outs: name = out.basename if name == "config_components.h": content = _gen_config_components(state) elif name == "config_extra.h": content = _gen_config_extra(state) elif name in _LIST_DISPATCH: comp_types, struct_type, list_name, symbol_fn = _LIST_DISPATCH[name] content = _gen_list(state, comp_types, struct_type, list_name, symbol_fn) else: fail("ffmpeg_component_gen: unknown output file: " + name) ctx.actions.write(out, content) return [DefaultInfo(files = depset(ctx.outputs.outs))] _COMPONENT_LABELS = [Label("//:enable_" + comp) for comp in PROFILE_EVERYTHING] _EXTRA_LABELS = [Label("//:enable_" + comp) for comp in _EXTRA_KEYS] ffmpeg_component_gen = rule( doc = "Generates config_components.h, config_extra.h, and *_list.c files " + "from per-component bool_flag settings (pre-resolved by the transition). " + "Each output declared in outs becomes an individually addressable label.", implementation = _ffmpeg_component_gen_impl, attrs = { "outs": attr.output_list( doc = "Output files to generate. Each must be a known file: " + "config_components.h, config_extra.h, or one of the *_list.c files.", ), "_components": attr.label_list( doc = "Per-component bool_flag targets, one per entry in PROFILE_EVERYTHING.", default = _COMPONENT_LABELS, ), "_extras": attr.label_list( doc = "CONFIG_EXTRA bool_flag targets, one per entry in CONFIG_EXTRA_REGISTRY.", default = _EXTRA_LABELS, ), }, )