new build system

Further changes by the following people:

James Ross-Gowan <rossy@jrg.systems>: win32 fixes
This commit is contained in:
wm4 2019-06-21 02:13:48 +02:00
parent f386463840
commit 6551ea5bd3
5 changed files with 2106 additions and 0 deletions

199
DOCS/build-system.rst Normal file
View File

@ -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 ``.``.

93
Makefile.new Normal file
View File

@ -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))

740
TOOLS/configure_common.py Normal file
View File

@ -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-<feature>
# "no": force disable, like: --disable-<feature>
# "auto": force auto detection, like --with-<feature>=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=<yes|no|auto|default>
# 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 <desc>..." 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)

55
TOOLS/makefile_common.mak Normal file
View File

@ -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)

1019
configure vendored Executable file

File diff suppressed because it is too large Load Diff