mirror of https://github.com/mpv-player/mpv
new build system
Further changes by the following people: James Ross-Gowan <rossy@jrg.systems>: win32 fixes
This commit is contained in:
parent
f386463840
commit
6551ea5bd3
|
@ -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 ``.``.
|
|
@ -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))
|
|
@ -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)
|
|
@ -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)
|
Loading…
Reference in New Issue