From 6551ea5bd3e9781e3740e99a00c56a49917d5fc9 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 21 Jun 2019 02:13:48 +0200 Subject: new build system Further changes by the following people: James Ross-Gowan : win32 fixes --- DOCS/build-system.rst | 199 +++++++++ Makefile.new | 93 +++++ TOOLS/configure_common.py | 740 ++++++++++++++++++++++++++++++++ TOOLS/makefile_common.mak | 55 +++ configure | 1019 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 2106 insertions(+) create mode 100644 DOCS/build-system.rst create mode 100644 Makefile.new create mode 100644 TOOLS/configure_common.py create mode 100644 TOOLS/makefile_common.mak create mode 100755 configure diff --git a/DOCS/build-system.rst b/DOCS/build-system.rst new file mode 100644 index 0000000000..8b8f122e6d --- /dev/null +++ b/DOCS/build-system.rst @@ -0,0 +1,199 @@ +Build system overview +===================== + +mpv's new build system is based on Python and completely replaces the previous +./waf build system. + +This file describes internals. See the README in the top level directory for +user help. + +User help (to be moved to README.md) +==================================== + +Compiling with full features requires development files for several +external libraries. Below is a list of some important requirements. + +For a list of the available build options use `./configure --help`. If +you think you have support for some feature installed but configure fails to +detect it, the file `build/config.log` may contain information about the +reasons for the failure. + +NOTE: To avoid cluttering the output with unreadable spam, `--help` only shows +one of the many switches for each option. If the option is autodetected by +default, the `--disable-***` switch is printed; if the option is disabled by +default, the `--enable-***` switch is printed. Either way, you can use +`--enable-***` or `--disable-***` regardless of what is printed by `--help`. +By default, most features are auto-detected. You can use ``--with-***=option`` +to get finer control over whether auto-detection is used for a feature. + +Example: + + ./configure && make -j20 + +If everything goes well, the mpv binary is created in the ``build`` directory. + +`make` alone can be used to rebuild parts of the player. On update, it's +recommended to run `make dist-clean` and to rerun configure. + +See `./configure --help` for advanced usage. + +Motivation & Requirements +========================= + +It's unclear what the fuck the author of the new build system was thinking. + +Big picture +=========== + +The configure script is written in Python. It generates config.h and config.mak +files (and possibly more). By default these are written to a newly created +"build" directory. It also writes a build.log. + +The "actual" build system is based on GNU make (other make variants probably +won't work). The Makefile in the project root is manually created by the build +system "user" (i.e. the mpv developers), and is fixed and not changed by +configure. It includes the configured-generated build/config.mak file for the +variable parts. It also includes Header file dependencies are handled +automatically with the ``-MD`` option (which the compiler must support). + +For out-of-tree builds, a small Makefile is generated that includes the one +from the source directory. Simply call configure from another directory. +(Note: this is broken, fails at generated files, and is also ugly.) + +By default, it attempts not to write any build output to the source tree, except +to the "build" directory. + +Comparison to previous waf build system +======================================= + +The new configure uses the same concept as our custom layer above waf, which +made the checks generally declarative. In fact, most checks were ported +straight, changing only to the new syntax. + +Some of the internal and user-visible conventions are extremely similar. For +example, the new system creates a build dir and writes to it by default. + +The choice of Python as implementation language is unfortunate. Shell was +considered, but discarded for being too fragile, error prone, and PITA-ish. +Lua would be reasonable, but is too fragmented, and requires external +dependencies to do meaningful UNIX scripting. There is nothing else left that +is widely supported enough, does not require external dependencies, and which +isn't something that I would not touch without gloves. Bootstrapping a system +implemented in C was considered, but deemed too problematic. + +mpv's custom configure +====================== + +All of the configuration process is handled with a mostly-declarative approach. +Each configure check is a call to a "check" function, which takes various named +arguments. The check function is obviously always called, even if the +corresponding feature is disabled. + +A simple example using pkg-config would be:: + +check("-vdpau*", + desc = "VDPAU acceleration", + deps = "x11", + fn = lambda: check_pkg_config("vdpau >= 0.2"), + sources = ["video/filter/vf_vdpaupp.c", + "video/out/vo_vdpau.c", + "video/vdpau.c", + "video/vdpau_mixer.c"]) + +This defines a feature called ``vdpau`` which can be enabled or disabled by +the users with configure flags (that's the meaning of ``-``). This feature +depends on another feature whose name is ``x11``, and the autodetection check +consists of running ``pkg-config`` and looking for ``vdpau`` with version +``>= 0.2``. If the check succeeds a ``#define HAVE_VDPAU 1`` will be added to +``config.h``, if not ``#define HAVE_VDPAU 0`` will be added (the ``*`` on the +feature name triggers emitting of such defines). + +The defines names are automatically prepended with ``HAVE_``, capitalized, and +some special characters are replaced with underscores. + +If the test succeeds, the listed source files are added to the build. + +Read the inline-documentation on the check function in configure_common.py for +details. The following text only gives a crude overview. + +Configure tests +--------------- + +The check function has a ``fn`` parameter. This function is called when it's +time to perform actual configure checks. Most check calls in configure make +this a lambda, so the actual code to run can be passed inline as a function +argument. (This is similar to the old waf based system, just that functions +like check_pkg_config returned a function as result, which hid the indirection.) + +One central function is ``check_cc``. It's quite similar to the waf-native +function with the same name. One difference is that there is no ``mandatory`` +option - instead it always returns a bool for success. On success, the passed +build flags are appended to the check's build flags. This makes it easier to +compose checks. For example:: + +check(desc = "C11/C99", + fn = lambda: check_cc(flags = "-std=c11") or + check_cc(flags = "-std=c99"), + required = "No C11 or C99 support.") + +This tries to use -std=c11, but allows a fallback to -std=c99. + +If the entire check fails, none of the added build flags are added. For example, +you could chain multiple tests like this:: + +check("-vapoursynth*", + fn = lambda: check_pkg_config("vapoursynth >= 24") and + check_pkg_config("vapoursynth-script >= 23")) + +If the second check fails, the final executable won't link to ``vapoursynth``. +(Note that this test could just make a single check_pkg_config call, and pass +each dependency as separate argument.) + +Source files +------------ + +configure generates the list of source files and writes it to config.mak. You +can add source files at any point in configure, but normally they're added with +the ``sources`` parameter in each feature check. This is done because a larger +number of source files depend on configure options, so having it all in the same +place as the check is slightly nicer than having a separate conditional mess in +the fixed Makefile. + +Configure phases, non-declarative actions +----------------------------------------- + +configure was written to be as single-pass as possible. It doesn't even put the +checks in any lists or so (except for the outcome). Handling of ``--enable-...`` +etc. options is done while running configure. If you pass e.g. +``--enable-doesntexist``, configure will complain about an unknown +``doesntexist`` feature only once all checks have been actually run. + +Although this is slightly weird, it is done so that the ``configure`` file +itself can be a flat file with simple top-down execution. It enables you to add +arbitrary non-declarative checks and such between the ``check`` calls. + +One thing you need to be aware of is that if ``--help`` was passed to configure, +it will run in "help mode". You may have to use ``is_running()`` to check +whether it's in a mode where checks are actually executed. Outside of this mode, +``dep_enabled()`` will fail. + +Makefile +-------- + +Although most source files are added from configure, this build system still +may require you to write some make. In particular, generated files are not +handled by configure. + +make is bad. It's hard to use, hard to debug, and extremely fragile. It may be +replaced by something else in the future, including the possibility of turning +configure into waf-light. + +Variables: + + ``BUILD`` + The directory for build output. Can be a relative path, usually set to + ``build``. + ``ROOT`` + The directory that contains ``configure``. Usually the root directory + of the repository. Source files need to be addressed relative to this + path. Can be a relative path, usually set to ``.``. diff --git a/Makefile.new b/Makefile.new new file mode 100644 index 0000000000..58ce1a0319 --- /dev/null +++ b/Makefile.new @@ -0,0 +1,93 @@ +BUILDDIR = build + +include $(BUILDDIR)/config.mak +include $(ROOT)/TOOLS/makefile_common.mak + +PROJNAME = mpv + +.PHONY: .force + +$(BUILD)/generated/version.h: $(ROOT)/version.sh .force + $(LOG) "VERSION" $@ + $(Q) mkdir -p $(@D) + $(Q) $(ROOT)/version.sh --versionh=$@ + +$(BUILD)/generated/ebml_types.h $(BUILD)/generated/ebml_defs.c: $(ROOT)/TOOLS/matroska.py + $(LOG) "EBML" "$(BUILD)/generated/ebml_types.h $(BUILD)/generated/ebml_defs.c" + $(Q) mkdir -p $(@D) + $(Q) $< --generate-header > $(BUILD)/generated/ebml_types.h + $(Q) $< --generate-definitions > $(BUILD)/generated/ebml_defs.c + +$(BUILD)/generated/%.inc: $(ROOT)/TOOLS/file2string.py $(ROOT)/% + $(LOG) "INC" $@ + $(Q) mkdir -p $(@D) + $(Q) $^ > $@ + +# Dependencies for generated files unfortunately need to be declared manually. +# This is because dependency scanning is a gross shitty hack by concept, and +# requires that the compiler successfully compiles a file to get its +# dependencies. This results in a chicken-and-egg problem, and in conclusion +# it works for static header files only. +# If any headers include generated headers, you need to manually set +# dependencies on all source files that include these headers! +# And because make is fucking shit, you actually need to set these on all files +# that are generated from these sources, instead of the source files. Make rules +# specify recipes, not dependencies. +# (Possible counter measures: always generate them with an order dependency, or +# introduce separate dependency scanner step for creating .d files.) + +$(BUILD)/common/version.o: $(BUILD)/generated/version.h + +$(BUILD)/osdep/mpv.o: $(BUILD)/generated/version.h + +$(BUILD)/demux/demux_mkv.o $(BUILD)/demux/ebml.o: \ + $(BUILD)/generated/ebml_types.h $(BUILD)/generated/ebml_defs.c + +$(BUILD)/video/out/x11_common.o: $(BUILD)/generated/etc/mpv-icon-8bit-16x16.png.inc \ + $(BUILD)/generated/etc/mpv-icon-8bit-32x32.png.inc \ + $(BUILD)/generated/etc/mpv-icon-8bit-64x64.png.inc \ + $(BUILD)/generated/etc/mpv-icon-8bit-128x128.png.inc + +$(BUILD)/input/input.o: $(BUILD)/generated/etc/input.conf.inc + +$(BUILD)/player/main.o: $(BUILD)/generated/etc/builtin.conf.inc + +$(BUILD)/sub/osd_libass.o: $(BUILD)/generated/sub/osd_font.otf.inc + +$(BUILD)/player/lua.o: $(BUILD)/generated/player/lua/defaults.lua.inc \ + $(BUILD)/generated/player/lua/assdraw.lua.inc \ + $(BUILD)/generated/player/lua/options.lua.inc \ + $(BUILD)/generated/player/lua/osc.lua.inc \ + $(BUILD)/generated/player/lua/ytdl_hook.lua.inc \ + $(BUILD)/generated/player/lua/stats.lua.inc \ + $(BUILD)/generated/player/lua/console.lua.inc \ + +$(BUILD)/player/javascript.o: $(BUILD)/generated/player/javascript/defaults.js.inc + +$(BUILD)/osdep/macosx_application.m $(BUILD)/video/out/cocoa_common.m: \ + $(BUILD)/generated/TOOLS/osxbundle/mpv.app/Contents/Resources/icon.icns.inc + +# Why doesn't wayland just provide fucking libraries like anyone else, instead +# of overly complex XML generation bullshit? +# And fuck make too. + +# $(1): path prefix to the protocol, $(1)/$(2).xml is the full path. +# $(2): the name of the protocol, without path or extension +define generate_trash = +$$(BUILD)/video/out/wayland_common.o \ +$$(BUILD)/video/out/opengl/context_wayland.o \ +: $$(BUILD)/generated/wayland/$(2).c $$(BUILD)/generated/wayland/$(2).h +$$(BUILD)/generated/wayland/$(2).c: $(1)/$(2).xml + $$(LOG) "WAYSHC" $$@ + $$(Q) mkdir -p $$(@D) + $$(Q) $$(WAYSCAN) private-code $$< $$@ +$$(BUILD)/generated/wayland/$(2).h: $(1)/$(2).xml + $$(LOG) "WAYSHH" $$@ + $$(Q) mkdir -p $$(@D) + $$(Q) $$(WAYSCAN) client-header $$< $$@ +endef + +$(eval $(call generate_trash,$(WL_PROTO_DIR)/unstable/idle-inhibit/,idle-inhibit-unstable-v1)) +$(eval $(call generate_trash,$(WL_PROTO_DIR)/stable/presentation-time/,presentation-time)) +$(eval $(call generate_trash,$(WL_PROTO_DIR)/stable/xdg-shell/,xdg-shell)) +$(eval $(call generate_trash,$(WL_PROTO_DIR)/unstable/xdg-decoration/,xdg-decoration-unstable-v1)) diff --git a/TOOLS/configure_common.py b/TOOLS/configure_common.py new file mode 100644 index 0000000000..ea2f32ea1a --- /dev/null +++ b/TOOLS/configure_common.py @@ -0,0 +1,740 @@ +import atexit +import os +import shutil +import subprocess +import sys +import tempfile + +# ...the fuck? +NoneType = type(None) +function = type(lambda: 0) + +programs_info = [ + # env. name default + ("CC", "cc"), + ("PKG_CONFIG", "pkg-config"), + ("WINDRES", "windres"), + ("WAYSCAN", "wayland-scanner"), +] + +install_paths_info = [ + # env/opt default + ("PREFIX", "/usr/local"), + ("BINDIR", "$(PREFIX)/bin"), + ("LIBDIR", "$(PREFIX)/lib"), + ("CONFDIR", "$(PREFIX)/etc/$(PROJNAME)"), + ("INCDIR", "$(PREFIX)/include"), + ("DATADIR", "$(PREFIX)/share"), + ("MANDIR", "$(DATADIR)/man"), + ("DOCDIR", "$(DATADIR)/doc/$(PROJNAME)"), + ("HTMLDIR", "$(DOCDIR)"), + ("ZSHDIR", "$(DATADIR)/zsh"), + ("CONFLOADDIR", "$(CONFDIR)"), +] + +# for help output only; code grabs them manually +other_env_vars = [ + # env # help text + ("CFLAGS", "User C compiler flags to append."), + ("CPPFLAGS", "Also treated as C compiler flags."), + ("LDFLAGS", "C compiler flags for link command."), + ("TARGET", "Prefix for default build tools (for cross compilation)"), + ("CROSS_COMPILE", "Same as TARGET."), +] + +class _G: + help_mode = False # set if --help is specified on the command line + + log_file = None # opened log file + + temp_path = None # set to a private, writable temporary directory + build_dir = None + root_dir = None + out_of_tree = False + + install_paths = {} # var name to path, see install_paths_info + + programs = {} # key is symbolic name, like CC, value is string of + # executable name - only set if check_program was called + + exe_format = "elf" + + cflags = [] + ldflags = [] + + config_h = "" # new contents of config.h (written at the end) + config_mak = "" # new contents of config.mak (written at the end) + + sources = [] + + state_stack = [] + + feature_opts = {} # keyed by option name, values are: + # "yes": force enable, like --enable- + # "no": force disable, like: --disable- + # "auto": force auto detection, like --with-=auto + # "default": default (same as option not given) + + dep_enabled = {} # keyed by dependency identifier; value is a bool + # missing key means the check was not run yet + + +# Convert a string to a C string literal. Adds the required "". +def _c_quote_string(s): + s = s.replace("\\", "\\\\") + s = s.replace("\"", "\\\"") + return "\"%s\"" % s + +# Convert a string to a make variable. Escaping is annoying: sometimes, you add +# e..g arbitrary paths (=> everything escaped), but sometimes you want to keep +# make variable use like $(...) unescaped. +def _c_quote_makefile_var(s): + s = s.replace("\\", "\\\\") + s = s.replace("\"", "\\\"") + s = s.replace(" ", "\ ") # probably + return s + +def die(msg): + sys.stderr.write("Fatal error: %s\n" % msg) + sys.stderr.write("Not updating build files.\n") + if _G.log_file: + _G.log_file.write("--- Stopping due to error: %s\n" % msg) + sys.exit(1) + +# To be called before any user checks are performed. +def begin(): + _G.root_dir = "." + _G.build_dir = "build" + + for var, val in install_paths_info: + _G.install_paths[var] = val + + for arg in sys.argv[1:]: + if arg.startswith("-"): + name = arg[1:] + if name.startswith("-"): + name = name[1:] + opt = name.split("=", 1) + name = opt[0] + val = opt[1] if len(opt) > 1 else "" + def noval(): + if val: + die("Option --%s does not take a value." % name) + if name == "help": + noval() + _G.help_mode = True + continue + elif name.startswith("enable-"): + noval() + _G.feature_opts[name[7:]] = "yes" + continue + elif name.startswith("disable-"): + noval() + _G.feature_opts[name[8:]] = "no" + continue + elif name.startswith("with-"): + if val not in ["yes", "no", "auto", "default"]: + die("Option --%s requires 'yes', 'no', 'auto', or 'default'." + % name) + _G.feature_opts[name[5:]] = val + continue + uname = name.upper() + setval = None + if uname in _G.install_paths: + def set_install_path(name, val): + _G.install_paths[name] = val + setval = set_install_path + elif uname == "BUILDDIR": + def set_build_path(name, val): + _G.build_dir = val + setval = set_build_path + if not setval: + die("Unknown option: %s" % arg) + if not val: + die("Option --%s requires a value." % name) + setval(uname, val) + continue + + if _G.help_mode: + print("Environment variables controlling choice of build tools:") + for name, default in programs_info: + print(" %-30s %s" % (name, default)) + + print("") + print("Environment variables/options controlling install paths:") + for name, default in install_paths_info: + print(" %-30s '%s' (also --%s)" % (name, default, name.lower())) + + print("") + print("Other environment variables:") + for name, help in other_env_vars: + print(" %-30s %s" % (name, help)) + print("In addition, pkg-config queries PKG_CONFIG_PATH.") + print("") + print("General build options:") + print(" %-30s %s" % ("--builddir=PATH", "Build directory (default: build)")) + print(" %-30s %s" % ("", "(Requires using 'make BUILDDIR=PATH')")) + print("") + print("Specific build configuration:") + # check() invocations will print the options they understand. + return + + _G.temp_path = tempfile.mkdtemp(prefix = "mpv-configure-") + def _cleanup(): + shutil.rmtree(_G.temp_path) + atexit.register(_cleanup) + + # (os.path.samefile() is "UNIX only") + if os.path.realpath(sys.path[0]) != os.path.realpath(os.getcwd()): + print("This looks like an out of tree build.") + print("This doesn't actually work.") + # Keep the build dir; this makes it less likely to accidentally trash + # an existing dir, especially if dist-clean (wipes build dir) is used. + # Also, this will work even if the same-directory check above was wrong. + _G.build_dir = os.path.join(os.getcwd(), _G.build_dir) + _G.root_dir = sys.path[0] + _G.out_of_tree = True + + os.makedirs(_G.build_dir, exist_ok = True) + _G.log_file = open(os.path.join(_G.build_dir, "config.log"), "w") + + _G.config_h += "// Generated by configure.\n" + \ + "#pragma once\n\n" + + +# Check whether the first argument is the same type of any in the following +# arguments. This _always_ returns val, but throws an exception if type checking +# fails. +# This is not very pythonic, but I'm trying to prevent bugs, so bugger off. +def typecheck(val, *types): + vt = type(val) + for t in types: + if vt == t: + return val + raise Exception("Value '%s' of type %s not any of %s" % (val, type(val), types)) + +# If val is None, return [] +# If val is a list, return val. +# Otherwise, return [val] +def normalize_list_arg(val): + if val is None: + return [] + if type(val) == list: + return val + return [val] + +def push_build_flags(): + _G.state_stack.append( + (_G.cflags[:], _G.ldflags[:], _G.config_h, _G.config_mak, + _G.programs.copy())) + +def pop_build_flags_discard(): + top = _G.state_stack[-1] + _G.state_stack = _G.state_stack[:-1] + + (_G.cflags[:], _G.ldflags[:], _G.config_h, _G.config_mak, + _G.programs) = top + +def pop_build_flags_merge(): + top = _G.state_stack[-1] + _G.state_stack = _G.state_stack[:-1] + +# Return build dir. +def get_build_dir(): + assert _G.build_dir is not None # too early? + return _G.build_dir + +# Root directory, i.e. top level source directory, or where configure/Makefile +# are located. +def get_root_dir(): + assert _G.root_dir is not None # too early? + return _G.root_dir + +# Set which type of executable format the target uses. +# Used for conventions which refuse to abstract properly. +def set_exe_format(fmt): + assert fmt in ["elf", "pe", "macho"] + _G.exe_format = fmt + +# A check is a check, dependency, or anything else that adds source files, +# preprocessor symbols, libraries, include paths, or simply serves as +# dependency check for other checks. +# Always call this function with named arguments. +# Arguments: +# name: String or None. Symbolic name of the check. The name can be used as +# dependency identifier by other checks. This is the first argument, and +# usually passed directly, instead of as named argument. +# If this starts with a "-" flag, options with names derived from this +# are generated: +# --enable-$option +# --disable-$option +# --with-$option= +# Where "$option" is the name without flag characters, and occurrences +# of "_" are replaced with "-". +# If this ends with a "*" flag, the result of this check is emitted as +# preprocessor symbol to config.h. It will have the name "HAVE_$DEF", +# and will be either set to 0 (check failed) or 1 (check succeeded), +# and $DEF is the name without flag characters and all uppercase. +# desc: String or None. If specified, "Checking for ..." is printed +# while running configure. If not specified, desc is auto-generated from +# the name. +# default: Boolean or None. If True or None, the check is soft-enabled (that +# means it can still be disabled by options, dependency checks, or +# the check function). If False, the check is disabled by default, +# but can be enabled by an option. +# deps, deps_any, deps_neg: String, array of strings, or None. If a check is +# enabled by default/command line options, these checks are performed in +# the following order: deps_neg, deps_any, deps +# deps requires all dependencies in the list to be enabled. +# deps_any requires 1 or more dependencies to be enabled. +# deps_neg requires that all dependencies are disabled. +# fn: Function or None. The function is run after dependency checks. If it +# returns True, the check is enabled, if it's False, it will be disabled. +# Typically, your function for example check for the existence of +# libraries, and add them to the final list of CFLAGS/LDFLAGS. +# None behaves like "lambda: True". +# Note that this needs to be a function. If not, it'd be run before the +# check() function is even called. That would mean the function runs even +# if the check was disabled, and could add unneeded things to CFLAGS. +# If this function returns False, all added build flags are removed again, +# which makes it easy to compose checks. +# You're not supposed to call check() itself from fn. +# sources: String, Array of Strings, or None. +# If the check is enabled, add these sources to the build. +# Duplicate sources are removed at end of configuration. +# required: String or None. If this is a string, the check is required, and +# if it's not enabled, the string is printed as error message. +def check(name = None, option = None, desc = None, deps = None, deps_any = None, + deps_neg = None, sources = None, fn = None, required = None, + default = None): + + deps = normalize_list_arg(deps) + deps_any = normalize_list_arg(deps_any) + deps_neg = normalize_list_arg(deps_neg) + sources = normalize_list_arg(sources) + + typecheck(name, str, NoneType) + typecheck(option, str, NoneType) + typecheck(desc, str, NoneType) + typecheck(deps, NoneType, list) + typecheck(deps_any, NoneType, list) + typecheck(deps_neg, NoneType, list) + typecheck(sources, NoneType, list) + typecheck(fn, NoneType, function) + typecheck(required, str, NoneType) + typecheck(default, bool, NoneType) + + option_name = None + define_name = None + if name is not None: + opt_flag = name.startswith("-") + if opt_flag: + name = name[1:] + def_flag = name.endswith("*") + if def_flag: + name = name[:-1] + if opt_flag: + option_name = name.replace("_", "-") + if def_flag: + define_name = "HAVE_" + name.replace("-", "_").upper() + + if desc is None and name is not None: + desc = name + + if _G.help_mode: + if not option_name: + return + + defaction = "enable" + if required is not None: + # If they are required, but also have option set, these are just + # "strongly required" options. + defaction = "enable" + elif default == False: + defaction = "disable" + elif deps or deps_any or deps_neg or fn: + defaction = "autodetect" + act = "enable" if defaction == "disable" else "disable" + opt = "--%s-%s" % (act, option_name) + print(" %-30s %s %s [%s]" % (opt, act, desc, defaction)) + return + + _G.log_file.write("\n--- Test: %s\n" % (name if name else "(unnnamed)")) + + if desc: + sys.stdout.write("Checking for %s... " % desc) + outcome = "yes" + + force_opt = required is not None + use_dep = True if default is None else default + + # Option handling. + if option_name: + # (The option gets removed, so we can determine whether all options were + # applied in the end.) + val = _G.feature_opts.pop(option_name, "default") + if val == "yes": + use_dep = True + force_opt = True + elif val == "no": + use_dep = False + force_opt = False + elif val == "auto": + use_dep = True + elif val == "default": + pass + else: + assert False + + if not use_dep: + outcome = "disabled" + + # Dependency resolution. + # But first, check whether all dependency identifiers really exist. + for d in deps_neg + deps_any + deps: + dep_enabled(d) # discard result + if use_dep: + for d in deps_neg: + if dep_enabled(d): + use_dep = False + outcome = "conflicts with %s" % d + break + if use_dep: + any_found = False + for d in deps_any: + if dep_enabled(d): + any_found = True + break + if len(deps_any) > 0 and not any_found: + use_dep = False + outcome = "not any of %s found" % (", ".join(deps_any)) + if use_dep: + for d in deps: + if not dep_enabled(d): + use_dep = False + outcome = "%s not found" % d + break + + # Running actual checks. + if use_dep and fn: + push_build_flags() + if fn(): + pop_build_flags_merge() + else: + pop_build_flags_discard() + use_dep = False + outcome = "no" + + # Outcome reporting and terminating if dependency not found. + if name: + _G.dep_enabled[name] = use_dep + if define_name: + add_config_h_define(define_name, 1 if use_dep else 0) + if use_dep: + _G.sources += sources + if desc: + sys.stdout.write("%s\n" % outcome) + _G.log_file.write("--- Outcome: %s (%s=%d)\n" % + (outcome, name if name else "(unnnamed)", use_dep)) + + if required is not None and not use_dep: + print("Warning: %s" % required) + + if force_opt and not use_dep: + die("This feature is required.") + + +# Runs the process like with execv() (just that args[0] is used for both command +# and first arg. passed to the process). +# Returns the process stdout output on success, or None on non-0 exit status. +# In particular, this logs the command and its output/exit status to the log +# file. +def _run_process(args): + p = subprocess.Popen(args, stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + stdin = -1) + (p_out, p_err) = p.communicate() + # We don't really want this. But Python 3 in particular makes it too much of + # a PITA (think power drill in anus) to consistently use byte strings, so + # we need to use "unicode" strings. Yes, a bad program could just blow up + # our butt here by outputting invalid UTF-8. + # Weakly support Python 2 too (gcc outputs UTF-8, which crashes Python 2). + if type(b"") != str: + p_out = p_out.decode("utf-8") + p_err = p_err.decode("utf-8") + status = p.wait() + _G.log_file.write("--- Command: %s\n" % " ".join(args)) + if p_out: + _G.log_file.write("--- stdout:\n%s" % p_out) + if p_err: + _G.log_file.write("--- stderr:\n%s" % p_err) + _G.log_file.write("--- Exit status: %s\n" % status) + return p_out if status == 0 else None + +# Run the C compiler, possibly including linking. Return whether the compiler +# exited with success status (0 exit code) as boolean. What exactly it does +# depends on the arguments. Generally, it constructs a source file and tries +# to compile it. With no arguments, it compiles, but doesn't link, a source +# file that contains a dummy main function. +# Note: these tests are cumulative. +# Arguments: +# include: String, array of strings, or None. For each string +# "#include <$value>" is added to the top of the source file. +# decl: String, array of strings, or None. Added to the top of the source +# file, global scope, separated by newlines. +# expr: String or None. Added to the body of the main function. Despite the +# name, needs to be a full statement, needs to end with ";". +# defined: String or None. Adds code that fails if "#ifdef $value" fails. +# flags: String, array of strings, or None. Each string is added to the +# compiler command line. +# Also, if the test succeeds, all arguments are added to the CFLAGS +# (if language==c) written to config.mak. +# link: String, array of strings, or None. Each string is added to the +# compiler command line, and the compiler is made to link (not passing +# "-c"). +# A value of [] triggers linking without further libraries. +# A value of None disables the linking step. +# Also, if the test succeeds, all link strings are added to the LDFLAGS +# written to config.mak. +# language: "c" for C, "m" for Objective-C. +def check_cc(include = None, decl = None, expr = None, defined = None, + flags = None, link = None, language = "c"): + assert language in ["c", "m"] + + use_linking = link is not None + + contents = "" + for inc in normalize_list_arg(include): + contents += "#include <%s>\n" % inc + for dec in normalize_list_arg(decl): + contents += "%s\n" % dec + for define in normalize_list_arg(defined): + contents += ("#ifndef %s\n" % define) + \ + "#error failed\n" + \ + "#endif\n" + if expr or use_linking: + contents += "int main(int argc, char **argv) {\n"; + if expr: + contents += expr + "\n" + contents += "return 0; }\n" + source = os.path.join(_G.temp_path, "test." + language) + _G.log_file.write("--- Test file %s:\n%s" % (source, contents)) + with open(source, "w") as f: + f.write(contents) + + flags = normalize_list_arg(flags) + link = normalize_list_arg(link) + + outfile = os.path.join(_G.temp_path, "test") + args = [get_program("CC"), source] + args += _G.cflags + flags + if use_linking: + args += _G.ldflags + link + args += ["-o%s" % outfile] + else: + args += ["-c", "-o%s.o" % outfile] + if _run_process(args) is None: + return False + + _G.cflags += flags + _G.ldflags += link + return True + +# Run pkg-config with function arguments passed as command arguments. Typically, +# you specify pkg-config version expressions, like "libass >= 0.14". Returns +# success as boolean. +# If this succeeds, the --cflags and --libs are added to CFLAGS and LDFLAGS. +def check_pkg_config(*args): + args = list(args) + pkg_config_cmd = [get_program("PKG_CONFIG")] + + cflags = _run_process(pkg_config_cmd + ["--cflags"] + args) + if cflags is None: + return False + ldflags = _run_process(pkg_config_cmd + ["--libs"] + args) + if ldflags is None: + return False + + _G.cflags += cflags.split() + _G.ldflags += ldflags.split() + return True + +def get_pkg_config_variable(arg, varname): + typecheck(arg, str) + pkg_config_cmd = [get_program("PKG_CONFIG")] + + res = _run_process(pkg_config_cmd + ["--variable=" + varname] + [arg]) + if res is not None: + res = res.strip() + return res + +# Check for a specific build tool. You pass in a symbolic name (e.g. "CC"), +# which is then resolved to a full name and added as variable to config.mak. +# The function returns a bool for success. You're not supposed to use the +# program from configure; instead you're supposed to have rules in the makefile +# using the generated variables. +# (Some configure checks use the program directly anyway with get_program().) +def check_program(env_name): + for name, default in programs_info: + if name == env_name: + val = os.environ.get(env_name, None) + if val is None: + prefix = os.environ.get("TARGET", None) + if prefix is None: + prefix = os.environ.get("CROSS_COMPILE", "") + # Shitty hack: default to gcc if a prefix is given, as binutils + # toolchains generally provide only a -gcc wrapper. + if prefix and default == "cc": + default = "gcc" + val = prefix + default + # Interleave with output. Sort of unkosher, but dare to stop me. + sys.stdout.write("(%s) " % val) + _G.log_file.write("--- Trying '%s' for '%s'...\n" % (val, env_name)) + try: + _run_process([val]) + except OSError as err: + _G.log_file.write("%s\n" % err) + return False + _G.programs[env_name] = val + add_config_mak_var(env_name, val) + return True + assert False, "Unknown program name '%s'" % env_name + +# Get the resolved value for a program. Explodes in your face if there wasn't +# a successful and merged check_program() call before. +def get_program(env_name): + val = _G.programs.get(env_name, None) + assert val is not None, "Called get_program(%s) without successful check." % env_name + return val + +# Return whether all passed dependency identifiers are fulfilled. +def dep_enabled(*deps): + for d in deps: + val = _G.dep_enabled.get(d, None) + assert val is not None, "Internal error: unknown dependency %s" % d + if not val: + return False + return True + +# Add all of the passed strings to CFLAGS. +def add_cflags(*fl): + _G.cflags += list(fl) + +# Add a preprocessor symbol of the given name to config.h. +# If val is a string, it's quoted as string literal. +# If val is None, it's defined without value. +def add_config_h_define(name, val): + if type(val) == type("") or type(val) == type(b""): + val = _c_quote_string(val) + if val is None: + val = "" + _G.config_h += "#define %s %s\n" % (name, val) + +# Add a makefile variable of the given name to config.mak. +# If val is a string, it's quoted as string literal. +def add_config_mak_var(name, val): + if type(val) == type("") or type(val) == type(b""): + val = _c_quote_makefile_var(val) + _G.config_mak += "%s = %s\n" % (name, val) + +# Add these source files to the build. +def add_sources(*sources): + _G.sources += list(sources) + +# Get an environment variable and parse it as flags array. +def _get_env_flags(name): + res = os.environ.get(name, "").split() + if len(res) == 1 and len(res[0]) == 0: + res = [] + return res + +# To be called at the end of user checks. +def finish(): + if not is_running(): + return + + is_fatal = False + for key, val in _G.feature_opts.items(): + print("Unknown feature set on command line: %s" % key) + if val == "yes": + is_fatal = True + if is_fatal: + die("Unknown feature was force-enabled.") + + _G.config_h += "\n" + add_config_h_define("CONFIGURATION", " ".join(sys.argv)) + add_config_h_define("MPV_CONFDIR", "$(CONFLOADDIR)") + enabled_features = [x[0] for x in filter(lambda x: x[1], _G.dep_enabled.items())] + add_config_h_define("FULLCONFIG", " ".join(sorted(enabled_features))) + + with open(os.path.join(_G.build_dir, "config.h"), "w") as f: + f.write(_G.config_h) + + add_config_mak_var("BUILD", _G.build_dir) + add_config_mak_var("ROOT", _G.root_dir) + _G.config_mak += "\n" + + add_config_mak_var("EXESUF", ".exe" if _G.exe_format == "pe" else "") + + for name, _ in install_paths_info: + add_config_mak_var(name, _G.install_paths[name]) + _G.config_mak += "\n" + + _G.config_mak += "CFLAGS = %s %s %s\n" % (" ".join(_G.cflags), + os.environ.get("CPPFLAGS", ""), + os.environ.get("CFLAGS", "")) + _G.config_mak += "\n" + _G.config_mak += "LDFLAGS = %s %s\n" % (" ".join(_G.ldflags), + os.environ.get("LDFLAGS", "")) + _G.config_mak += "\n" + + sources = [] + for s in _G.sources: + # Prefix all source files with "$(ROOT)/". This is important for out of + # tree builds, where configure/make is run from "somewhere else", and + # not the source directory. + # Generated sources need to be prefixed with "$(BUILD)/" (for the same + # reason). Since we do not know whether a source file is generated, the + # convention is that the user takes care of prefixing it. + if not s.startswith("$(BUILD)"): + assert not s.startswith("$") # no other variables which make sense + assert not s.startswith("generated/") # requires $(BUILD) prefix + s = "$(ROOT)/%s" % s + sources.append(s) + + _G.config_mak += "SOURCES = \\\n" + for s in sorted(list(set(sources))): + _G.config_mak += " %s \\\n" % s + + _G.config_mak += "\n" + + with open(os.path.join(_G.build_dir, "config.mak"), "w") as f: + f.write("# Generated by configure.\n\n" + _G.config_mak) + + if _G.out_of_tree: + try: + os.symlink(os.path.join(_G.root_dir, "Makefile.new"), "Makefile") + except FileExistsError: + print("Not overwriting existing Makefile.") + + _G.log_file.write("--- Finishing successfully.\n") + print("Done. You can run 'make' now.") + +# Return whether to actually run configure tests, and whether results of those +# tests are available. +def is_running(): + return not _G.help_mode + +# Each argument is an array or tuple, with the first element giving the +# dependency identifier, or "_" to match always fulfilled. The elements after +# this are added as source files if the dependency matches. This stops after +# the first matching argument. +def pick_first_matching_dep(*deps): + winner = None + for e in deps: + if (e[0] == "_" or dep_enabled(e[0])) and (winner is None): + # (the odd indirection though winner is so that all dependency + # identifiers are checked for existence) + winner = e[1:] + if winner is not None: + add_sources(*winner) diff --git a/TOOLS/makefile_common.mak b/TOOLS/makefile_common.mak new file mode 100644 index 0000000000..6d5c462cb2 --- /dev/null +++ b/TOOLS/makefile_common.mak @@ -0,0 +1,55 @@ +ifdef V +Q = +else +Q = @ +endif + +CFLAGS := -I$(ROOT) -I$(BUILD) $(CFLAGS) + +OBJECTS = $(SOURCES:.c=.o) +OBJECTS := $(OBJECTS:.rc=.o) + +TARGET = mpv + +# The /./ -> / is for cosmetic reasons. +BUILD_OBJECTS = $(subst /./,/,$(addprefix $(BUILD)/, $(OBJECTS))) + +BUILD_TARGET = $(addprefix $(BUILD)/, $(TARGET))$(EXESUF) +BUILD_DEPS = $(BUILD_OBJECTS:.o=.d) +CLEAN_FILES += $(BUILD_OBJECTS) $(BUILD_DEPS) $(BUILD_TARGET) + +LOG = $(Q) printf "%s\t%s\n" + +# Special rules. + +all: $(BUILD_TARGET) + +clean: + $(LOG) "CLEAN" + $(Q) rm -f $(CLEAN_FILES) + $(Q) rm -rf $(BUILD)/generated/ + $(Q) (rmdir $(BUILD)/*/*/* $(BUILD)/*/* $(BUILD)/*) 2> /dev/null || true + +dist-clean: + $(LOG) "DIST-CLEAN" + $(Q) rm -rf $(BUILD) + +# Generic pattern rules (used for most source files). + +$(BUILD)/%.o: %.c + $(LOG) "CC" "$@" + $(Q) mkdir -p $(@D) + $(Q) $(CC) $(CFLAGS) $< -c -o $@ + +$(BUILD)/%.o: %.rc + $(LOG) "WINRC" "$@" + $(Q) mkdir -p $(@D) + $(Q) $(WINDRES) -I$(ROOT) -I$(BUILD) $< $@ + +$(BUILD_TARGET): $(BUILD_OBJECTS) + $(LOG) "LINK" "$@" + $(Q) $(CC) $(BUILD_OBJECTS) $(CFLAGS) $(LDFLAGS) -o $@ + +.PHONY: all clean .pregen + +-include $(BUILD_DEPS) diff --git a/configure b/configure new file mode 100755 index 0000000000..456cc8c6d3 --- /dev/null +++ b/configure @@ -0,0 +1,1019 @@ +#!/usr/bin/env python3 + +#missing: +#- actually support out of tree builds +#- libmpv +#- doc generation +#- windows console wrapper thing (?) +#- osx testing +#- swift stuff (impossible, crapple wants you to stick a dagger up your ass?) +#- vaapi interops (?) +#- RPI stuff +#- newer BSD changes +#- it's weird how the wayland trash is compiled to BUILDDIR/BUILDDIR/ + +import os +from TOOLS.configure_common import * + +begin() + +# (workaround for insufficient Python lambda syntax) +def chain(*a): + return a[-1] + +check("-lgpl", + desc = "LGPL (version 2.1 or later) build", + default = False) +check("gpl*", + desc = "GPL (version 2 or later) build", + deps_neg = "lgpl") +check("-build-date*", + desc = "whether to include binary compile time", + fn = lambda: chain(add_cflags("-DNO_BUILD_TIMESTAMPS"), True)) +check(desc = "whether compiler works", + required = "C compiler missing or broken", + fn = lambda: check_program("CC") and check_cc(link = [])) +check(desc = "pkg-config", + required = "pkg-config missing or broken", + fn = lambda: check_program("PKG_CONFIG")) + +check("-cplayer", + desc = "mpv CLI player binary") +check("-libmpv-shared", + desc = "libmpv shared library", + fn = lambda: check_cc(flags = "-fPIC"), + #'-Wl,-version-script', '-Wl,mpv.def + default = False) +check("-libmpv-static", + desc = "libmpv static library", + default = False, + deps_neg = "libmpv-shared") + +add_cflags("-MD", "-MP", "-D_ISOC99_SOURCE", "-D_GNU_SOURCE", + "-D_LARGEFILE_SOURCE", "-D_FILE_OFFSET_BITS=64", + "-D_LARGEFILE64_SOURCE", + "-Wall") +check(desc = "C11/C99", + fn = lambda: check_cc(flags = "-std=c11") or + check_cc(flags = "-std=c99"), + required = "No C11 or C99 support.") +check("-optimize", + fn = lambda: chain(add_cflags("-O2"), True), + desc = "whether to optimize") +check("-debug-build", + desc = "whether to compile-in debugging information", + fn = lambda: chain(add_cflags("-g"), + check_cc(flags = ["-g3", "-ggdb"]), + True)) +check(desc = "warning cflags", + fn = lambda: check_cc(flags = [ + "-Werror=implicit-function-declaration", + "-Wno-error=deprecated-declarations", + "-Wno-error=unused-function", + "-Wempty-body", + "-Wdisabled-optimization", + "-Wstrict-prototypes", + "-Wno-format-zero-length", + "-Werror=format-security", + "-Wno-redundant-decls", + "-Wvla", + "-Wno-format-truncation"])) +check(desc = "-fno-math-errno", + fn = lambda: check_cc(flags = "-fno-math-errno")) + +check("gnuc", + desc = "GNU C", + fn = lambda: check_cc(defined = "__GNUC__")) +check("clang", + fn = lambda: check_cc(defined = "__clang__")) + +# Note that an important reason to try different set of warning flags is the +# fact that both compilers may have different bogus behavior wrt. certain +# warning options. What is needed on one compiler may be annoying or dangerous +# on the other. +check(desc = "extra gcc warnings", + deps = "gnuc", + deps_neg = "clang", + fn = lambda: check_cc(flags = [ + "-Wall", "-Wundef", "-Wmissing-prototypes", "-Wshadow", + "-Wno-switch", "-Wparentheses", "-Wpointer-arith", + "-Wno-pointer-sign", + # GCC bug 66425 + "-Wno-unused-result"])) +check(desc = "extra clang warnings", + deps = "clang", + fn = lambda: check_cc(flags = [ + "-Wno-logical-op-parentheses", "-fcolor-diagnostics", + "-Wno-tautological-compare", + "-Wno-tautological-constant-out-of-range-compare"])) + +check("-usan", + desc = "undefined sanitizer", + fn = lambda: check_cc(flags = "-fsanitize=undefined", link = [])) + +# Reminder: normally always built, but enabled by MPV_LEAK_REPORT. +# Building it can be disabled only by defining NDEBUG through CFLAGS. +check("-ta-leak-report*", + desc = "enable ta leak report by default (development only)", + default = False) + +check("libdl*", + fn = lambda: check_cc(link = "-ldl", include = "dlfcn.h", + expr = 'dlopen("", 0);')) +check("libm", + fn = lambda: check_cc(link = "-lm")) +check("win32", + fn = lambda: check_cc(defined = "_WIN32", + flags = ["-D_WIN32_WINNT=0x0602", "-DUNICODE", "-DCOBJMACROS", + "-DINITGUID", "-U__STRICT_ANSI__", + "-D__USE_MINGW_ANSI_STDIO=1"], + include = "windows.h", + link = ["-Wl,--major-os-version=6,--minor-os-version=0", + "-Wl,--major-subsystem-version=6,--minor-subsystem-version=0", + "-mwindows"]) and + check_program("WINDRES") and + chain(set_exe_format("pe"), True), + sources = ["osdep/mpv.rc", + "osdep/w32_keyboard.c", + "osdep/windows_utils.c"]) + +check("osx", + fn = lambda: check_cc(defined = "__APPLE__") and + chain(set_exe_format("macho"), True)) + +check("mingw", + fn = lambda: check_cc(include = "stdlib.h", + defined = ["__MINGW32__", "__MINGW64_VERSION_MAJOR"])) +check("posix*", + fn = lambda: check_cc(include = "unistd.h", + defined = "_POSIX_VERSION"), + sources = ["osdep/polldev.c", + "sub/filter_regex.c"]) +check("development environment", + deps_any = ["posix", "mingw"], + required = "Unable to find either POSIX or MinGW-w64 environment.") + +check("-cplugins*", + desc = "C plugins", + deps = "libdl", + deps_neg = "win32", + fn = lambda: check_cc(link = "-rdynamic")) + +check("noexecstack", + fn = lambda: check_cc(link = "-Wl,-z,noexecstack")) + +check("win-dep", + deps = "win32", + fn = lambda: check_cc(link = ["-Wl,--nxcompat", "-Wl,--no-seh", "-Wl,--dynamicbase"])) + +check("-android*", + fn = lambda: check_cc(include = "android/api-level.h", + expr = "(void)__ANDROID__;", + link = ["-landroid", "-lEGL"]), + sources = ["osdep/android/strnlen.c", + "video/out/opengl/context_android.c", + "video/out/vo_mediacodec_embed.c"]) + +check("-uwp*", + desc = "Universal Windows Platform", + deps = "mingw", + default = False, + fn = lambda: check_cc(link = "-lwindowsapp"), + sources = "osdep/path-uwp.c") +check("win32-desktop*", + desc = "win32 desktop APIs", + deps = "win32", + deps_neg = "uwp", + fn = lambda: check_cc(link = ["-lwinmm", "-lgdi32", "-lole32", + "-luuid", "-lavrt", "-ldwmapi", + "-lversion"]), + sources = ["osdep/path-win.c", + "video/out/w32_common.c", + "video/out/win32/displayconfig.c", + "video/out/win32/droptarget.c"]) +def check_vista_pthreads(): + path = os.path.abspath(os.path.join(get_root_dir(), "osdep/win32/include")) + add_cflags("-I%s" % path) + add_cflags("-isystem%s" % path) + # define IN_WINPTHREAD to workaround mingw stupidity (we never want it + # to define features specific to its own pthread stuff) + add_cflags("-DIN_WINPTHREAD") + return True +check("-win32-internal-pthreads*", + deps = "win32", + deps_neg = "posix", + fn = lambda: check_vista_pthreads(), + sources = "osdep/win32/pthread.c") +check("pthreads", + deps_neg = "win32-internal-pthreads", + fn = lambda: check_cc(link = "-pthread", flags = "-pthread", + include = "pthread.h", + expr = "pthread_self();")) +check(desc = "any pthread support", + deps_any = ["pthreads", "win32-internal-pthreads"], + required = "Unable to find pthreads support.") +check("stdatomic*", + fn = lambda: check_cc(include = "stdatomic.h", + expr = + "atomic_int_least64_t test = ATOMIC_VAR_INIT(123);" + "atomic_fetch_add(&test, 1);")) +check("atomics", + desc = "stdatomic.h support or slow emulation", + deps_any = ["stdatomic", "gnuc"], + required = "Required.") +check("librt", + fn = lambda: check_cc(link = "-lrt")) +check("iconv*", + fn = lambda: check_cc(include = "iconv.h", link = [], + expr = "iconv_open(0, 0);") or + check_cc(include = "iconv.h", link = "-liconv", + expr = "iconv_open(0, 0);"), + required = "Unable to find iconv which should be part of a standard \ +compilation environment. Aborting. If you really mean to compile without \ +iconv support use --disable-iconv.") +check("dos-paths*", + deps = "win32") +check("glob-posix*", + desc = "glob() POSIX support", + deps = "posix", + deps_neg = "win32", + fn = lambda: check_cc(include = "glob.h", + expr = 'glob("filename", 0, 0, 0);')) +check("glob-win32", + desc = "glob() win32 replacement", + deps_neg = "glob-posix", + deps = "win32", + sources = "osdep/glob-win.c"), +check("glob*", + desc = "any glob() support", + deps_any = ["glob-posix", "glob-win32"]) +check("fchmod*", + fn = lambda: check_cc(include = "sys/stat.h", expr = "fchmod(0, 0);")) +check("glibc-thread-name*", + deps = "pthreads", + fn = lambda: check_cc(include = "pthread.h", + expr = 'pthread_setname_np(pthread_self(), "ducks");')) +check("osx-thread-name*", + deps = "pthreads", + fn = lambda: check_cc(include = "pthread.h", + expr = 'pthread_setname_np("ducks");')) +check("bsd-thread-name*", + deps = "pthreads", + fn = lambda: check_cc(include = ["pthread.h", "pthread_np.h"], + expr = 'pthread_set_name_np(pthread_self(), "ducks");')) +check("bsd-fstatfs*", + fn = lambda: check_cc(include = ["sys/param.h", "sys/mount.h"], + expr = "struct statfs fs; fstatfs(0, &fs); fs.f_fstypename;")) +check("linux-fstatfs*", + fn = lambda: check_cc(include = "sys/vfs.h", + expr = "struct statfs fs; fstatfs(0, &fs); fs.f_namelen;")) + +check("-lua*", + fn = lambda: + check_pkg_config("lua >= 5.1.0 lua < 5.2.0") or + check_pkg_config("lua51 >= 5.1.0") or # OpenBSD + check_pkg_config("lua5.1 >= 5.1.0") or # debian + check_pkg_config("lua-5.1 >= 5.1.0") or # FreeBSD + check_pkg_config("lua >= 5.2.0 lua < 5.3.0" ) or + check_pkg_config("lua52 >= 5.2.0") or # Arch + check_pkg_config("lua5.2 >= 5.2.0") or # debian + check_pkg_config("lua-5.2 >= 5.2.0") or # FreeBSD + check_pkg_config("luajit >= 2.0.0"), + sources = "player/lua.c") +check("-javascript*", + fn = lambda: check_pkg_config("mujs", ">= 1.0.0"), + sources = "player/javascript.c") +check("-libass*", + desc = "libass subtitle/OSD renderer", + fn = lambda: check_pkg_config("libass >= 0.12.1"), + required = "Unable to find development files for libass, or the version " + + "found is too old. Aborting. You can use --disable-libass " + + "to ignore this warning.", + sources = ["sub/ass_mp.c", + "sub/osd_libass.c", + "sub/sd_ass.c"]) +check(deps_neg = "libass", + sources = "sub/osd_dummy.c") +check("-zlib*", + fn = lambda: check_cc(link = "-lz", include = "zlib.h", + expr = "inflate(0, Z_NO_FLUSH);"), + required = "Unable to find development files for zlib.") +check("-uchardet*", + fn = lambda: check_pkg_config("uchardet")) +check("-cocoa*", + deps = "osx", + fn = lambda: check_cc(decl = "#import ", + language = "m"), + sources = ["osdep/macosx_application.m", + "osdep/macosx_events.m", + "osdep/macosx_menubar.m", + "osdep/path-macosx.m", + "video/out/cocoa_common.m", + "video/out/cocoa/events_view.m", + "video/out/cocoa/video_view.m", + "video/out/cocoa/window.m"]) +check("-rubberband*", + fn = lambda: check_pkg_config("rubberband >= 1.8.0"), + sources = "audio/filter/af_rubberband.c") +check("-lcms2*", + fn = lambda: check_pkg_config("lcms2 >= 2.6")) +check("-vapoursynth*", + fn = lambda: check_pkg_config("vapoursynth >= 24") and + check_pkg_config("vapoursynth-script >= 23")) +check("-vapoursynth-lazy*", + desc = "VapourSynth filter bridge (Lazy Lua)", + deps = "lua", + fn = lambda: check_pkg_config("vapoursynth >= 24")) +check("vapoursynth-core*", + deps = ["vapoursynth", "vapoursynth-lazy"], + sources = "video/filter/vf_vapoursynth.c") +check("-libarchive*", + desc = "libarchive wrapper for reading zip files and more", + fn = lambda: check_pkg_config("libarchive >= 3.0.0"), + sources = ["demux/demux_libarchive.c", + "stream/stream_libarchive.c"]) + +check(desc = "FFmpeg", + fn = lambda: check_pkg_config( + "libavutil >= 56.12.100", + "libavcodec >= 58.16.100", + "libavformat >= 58.9.100", + "libswscale >= 5.0.101", + "libavfilter >= 7.14.100", + "libswresample >= 3.0.100"), + required = "Unable to find development files for some of the required \ +FFmpeg libraries.") +check("-ffmpeg-strict-abi*", + desc = "Disable all known FFmpeg ABI violations'", + default = False) + +check("-zimg*", + desc = "libzimg support (high quality software scaler)", + fn = lambda: check_pkg_config("zimg >= 2.9"), + sources = ["video/filter/vf_fingerprint.c", + "video/zimg.c"]), + +check("-libavdevice*", + fn = lambda: check_pkg_config("libavdevice >= 57.0.0")) + +check("-sdl2", + default = False, + fn = lambda: check_pkg_config('sdl2')) +check("-sdl2-audio*", + deps = "sdl2", + sources = "audio/out/ao_sdl.c") +check("-sdl2-video*", + deps = "sdl2", + sources = "video/out/vo_sdl.c") +check("-sdl2-gamepad*", + desc = "SDL2 gamepad input", + deps = "sdl2", + default = False, + sources = "input/sdl_gamepad.c") + +check("-pulse*", + fn = lambda: check_pkg_config("libpulse >= 1.0"), + sources = "audio/out/ao_pulse.c") +check("-jack*", + deps = "gpl", + fn = lambda: check_pkg_config("jack"), + sources = "audio/out/ao_jack.c") +check("-openal*", + default = False, + fn = lambda: check_pkg_config("openal >= 1.13"), + sources = "audio/out/ao_openal.c") +check("-opensles*", + fn = lambda: check_cc(include = "SLES/OpenSLES.h", + link = "-lOpenSLES", + expr = "slCreateEngine;"), + sources = "audio/out/ao_opensles.c") +check("-alsa*", + fn = lambda: check_pkg_config("alsa >= 1.0.18"), + sources = "audio/out/ao_alsa.c") +check("-coreaudio*", + # TODO: missing frameworks: "CoreFoundation", "CoreAudio", "AudioUnit", "AudioToolbox" + deps = "osx", + sources = ["audio/out/ao_coreaudio.c", + "audio/out/ao_coreaudio_chmap.c", + "audio/out/ao_coreaudio_exclusive.c", + "audio/out/ao_coreaudio_properties.c", + "audio/out/ao_coreaudio_utils.c"]) +check("-audiounit*", + desc = "AudioUnit output for iOS", + # TODO: missing frameworks: "Foundation", "AudioToolbox" + deps = "osx", + sources = ["audio/out/ao_audiounit.m", + "audio/out/ao_coreaudio_chmap.c", + "audio/out/ao_coreaudio_utils.c"]) +check("-wasapi*", + deps = "win32", + sources = ["audio/out/ao_wasapi.c", + "audio/out/ao_wasapi_changenotify.c", + "audio/out/ao_wasapi_utils.c"]) + +check("vt_h*", + fn = lambda: check_cc(include = ["sys/vt.h", "sys/ioctl.h"], + expr = "int m; ioctl(0, VT_GETMODE, &m);")) +check("consio_h*", + deps_neg = "vt_h", + fn = lambda: check_cc(include = ["sys/consio.h", "sys/ioctl.h"], + expr = "int m; ioctl(0, VT_GETMODE, &m);")) +check("-drm*", + deps_any = ["vt_h", "consio_h"], + fn = lambda: check_pkg_config("libdrm"), + sources = ["video/out/drm_atomic.c", + "video/out/drm_common.c", + "video/out/vo_drm.c"]) +check("-drmprime*", + fn = lambda: check_cc(include = "libavutil/pixfmt.h", + expr = "int i = AV_PIX_FMT_DRM_PRIME;")) +check(deps = ["drm", "drmprime"], + sources = ["video/out/drm_prime.c", + "video/out/opengl/hwdec_drmprime_drm.c"]) + +check("gbm", + fn = lambda: check_pkg_config("gbm")) + +def check_wayland_protos(): + data = get_pkg_config_variable("wayland-protocols", "pkgdatadir") + if data is None: + return False + add_config_mak_var("WL_PROTO_DIR", data) + return True + +check("-wayland*", + # TODO: where does this check whether the protocol files are available? + fn = lambda: check_wayland_protos() and + check_program("WAYSCAN") and + check_pkg_config("wayland-client >= 1.6.0", + "wayland-cursor >= 1.6.0", + "xkbcommon >= 0.3.0"), + sources = ["video/out/wayland_common.c", + "$(BUILD)/generated/wayland/idle-inhibit-unstable-v1.c", + "$(BUILD)/generated/wayland/presentation-time.c", + "$(BUILD)/generated/wayland/xdg-shell.c", + "$(BUILD)/generated/wayland/xdg-decoration-unstable-v1.c"]) +check("memfd_create*", + desc = "Linux's memfd_create()", + deps = "wayland", + fn = lambda: check_cc(include = "sys/mman.h", link = [], + expr = "memfd_create(0, MFD_CLOEXEC | MFD_ALLOW_SEALING);"), + sources = "video/out/vo_wlshm.c") + +check("-x11*", + deps = "gpl", + fn = lambda: check_pkg_config("x11 >= 1.0.0", + "xscrnsaver >= 1.0.0", + "xext >= 1.0.0", + "xinerama >= 1.0.0", + "xrandr >= 1.2.0"), + sources = ["video/out/vo_x11.c", + "video/out/x11_common.c"]) +check("-xv*", + deps = "x11", + fn = lambda: check_pkg_config("xv"), + sources = "video/out/vo_xv.c") + +check("-libplacebo*", + desc = "libpl