Add support for extracting objects in unity builds

Not only does extract_all_objects() now work properly again,
extract_objects() also works if you specify a subset of sources all of
which have been compiled into a single unified object.

So, for instance, this allows you to extract all the objects
corresponding to the C sources compiled into a target consisting of
C and C++ sources.
pull/938/head
Nirbheek Chauhan 8 years ago
parent 9c9c5ab2a8
commit 57ce7d4618
  1. 79
      mesonbuild/backend/backends.py
  2. 6
      mesonbuild/backend/ninjabackend.py
  3. 42
      mesonbuild/build.py
  4. 2
      mesonbuild/interpreter.py
  5. 16
      mesonbuild/mesonlib.py
  6. 3
      test cases/common/110 extract same name/meson.build
  7. 4
      test cases/common/84 extract from nested subdir/meson.build
  8. 2
      test cases/frameworks/7 gnome/mkenums/meson-sample.h
  9. 6
      test cases/vala/5 target glib/meson.build

@ -19,7 +19,7 @@ from .. import mesonlib
from .. import compilers from .. import compilers
import json import json
import subprocess import subprocess
from ..mesonlib import MesonException from ..mesonlib import MesonException, get_compiler_for_source, classify_unity_sources
class InstallData(): class InstallData():
def __init__(self, source_dir, build_dir, prefix): def __init__(self, source_dir, build_dir, prefix):
@ -78,21 +78,6 @@ class Backend():
priv_dirname = self.get_target_private_dir_abs(t) priv_dirname = self.get_target_private_dir_abs(t)
os.makedirs(priv_dirname, exist_ok=True) os.makedirs(priv_dirname, exist_ok=True)
def get_compiler_for_lang(self, lang):
for i in self.build.compilers:
if i.language == lang:
return i
raise RuntimeError('No compiler for language ' + lang)
def get_compiler_for_source(self, src, is_cross):
comp = self.build.cross_compilers if is_cross else self.build.compilers
for i in comp:
if i.can_compile(src):
return i
if isinstance(src, mesonlib.File):
src = src.fname
raise RuntimeError('No specified compiler can handle file ' + src)
def get_target_filename(self, t): def get_target_filename(self, t):
if isinstance(t, build.CustomTarget): if isinstance(t, build.CustomTarget):
if len(t.get_outputs()) != 1: if len(t.get_outputs()) != 1:
@ -153,14 +138,17 @@ class Backend():
# target that the GeneratedList is used in # target that the GeneratedList is used in
return os.path.join(self.get_target_private_dir(target), src) return os.path.join(self.get_target_private_dir(target), src)
def get_unity_source_filename(self, target, suffix):
return target.name + '-unity.' + suffix
def generate_unity_files(self, target, unity_src): def generate_unity_files(self, target, unity_src):
langlist = {}
abs_files = [] abs_files = []
result = [] result = []
compsrcs = classify_unity_sources(target.compilers.values(), unity_src)
def init_language_file(language, suffix): def init_language_file(suffix):
outfilename = os.path.join(self.get_target_private_dir_abs(target), outfilename = os.path.join(self.get_target_private_dir_abs(target),
target.name + '-unity' + suffix) self.get_unity_source_filename(target, suffix))
outfileabs = os.path.join(self.environment.get_build_dir(), outfileabs = os.path.join(self.environment.get_build_dir(),
outfilename) outfilename)
outfileabs_tmp = outfileabs + '.tmp' outfileabs_tmp = outfileabs + '.tmp'
@ -171,20 +159,12 @@ class Backend():
result.append(outfilename) result.append(outfilename)
return open(outfileabs_tmp, 'w') return open(outfileabs_tmp, 'w')
try: # For each language, generate a unity source file and return the list
for src in unity_src: for comp, srcs in compsrcs.items():
comp = self.get_compiler_for_source(src, target.is_cross) lang = comp.get_language()
language = comp.get_language() with init_language_file(comp.get_default_suffix()) as ofile:
try: for src in srcs:
ofile = langlist[language]
except KeyError:
suffix = '.' + comp.get_default_suffix()
ofile = langlist[language] = init_language_file(language,
suffix)
ofile.write('#include<%s>\n' % src) ofile.write('#include<%s>\n' % src)
finally:
for x in langlist.values():
x.close()
[mesonlib.replace_if_different(x, x + '.tmp') for x in abs_files] [mesonlib.replace_if_different(x, x + '.tmp') for x in abs_files]
return result return result
@ -281,21 +261,34 @@ class Backend():
raise AssertionError("BUG: Couldn't determine linker for sources {!r}".format(src)) raise AssertionError("BUG: Couldn't determine linker for sources {!r}".format(src))
def object_filename_from_source(self, target, source): def object_filename_from_source(self, target, source):
return source.fname.replace('/', '_').replace('\\', '_') + '.' + self.environment.get_object_suffix() if isinstance(source, mesonlib.File):
source = source.fname
return source.replace('/', '_').replace('\\', '_') + '.' + self.environment.get_object_suffix()
def determine_ext_objs(self, extobj, proj_dir_to_build_root=''): def determine_ext_objs(self, extobj, proj_dir_to_build_root):
result = [] result = []
targetdir = self.get_target_private_dir(extobj.target) targetdir = self.get_target_private_dir(extobj.target)
# With unity builds, there's just one object that contains all the
# sources, so if we want all the objects, just return that.
if self.environment.coredata.get_builtin_option('unity'):
if not extobj.unity_compatible:
# This should never happen
msg = 'BUG: Meson must not allow extracting single objects ' \
'in Unity builds'
raise AssertionError(msg)
comp = get_compiler_for_source(extobj.target.compilers.values(),
extobj.srclist[0])
# The unity object name uses the full absolute path of the source file
osrc = os.path.join(self.get_target_private_dir_abs(extobj.target),
self.get_unity_source_filename(extobj.target,
comp.get_default_suffix()))
objname = self.object_filename_from_source(extobj.target, osrc)
objpath = os.path.join(proj_dir_to_build_root, targetdir, objname)
return [objpath]
for osrc in extobj.srclist: for osrc in extobj.srclist:
# If extracting in a subproject, the subproject objname = self.object_filename_from_source(extobj.target, osrc)
# name gets duplicated in the file name. objpath = os.path.join(proj_dir_to_build_root, targetdir, objname)
pathsegs = osrc.subdir.split(os.sep) result.append(objpath)
if pathsegs[0] == 'subprojects':
pathsegs = pathsegs[2:]
fixedpath = os.sep.join(pathsegs)
objname = os.path.join(proj_dir_to_build_root, targetdir,
self.object_filename_from_source(extobj.target, osrc))
result.append(objname)
return result return result
def get_pch_include_args(self, compiler, target): def get_pch_include_args(self, compiler, target):

@ -18,7 +18,7 @@ from .. import build
from .. import mlog from .. import mlog
from .. import dependencies from .. import dependencies
from .. import compilers from .. import compilers
from ..mesonlib import File, MesonException from ..mesonlib import File, MesonException, get_compiler_for_source
from .backends import InstallData from .backends import InstallData
from ..build import InvalidArguments from ..build import InvalidArguments
import os, sys, pickle, re import os, sys, pickle, re
@ -1684,7 +1684,7 @@ rule FORTRAN_DEP_HACK
if isinstance(src, RawFilename) and src.fname.endswith('.h'): if isinstance(src, RawFilename) and src.fname.endswith('.h'):
raise AssertionError('BUG: sources should not contain headers') raise AssertionError('BUG: sources should not contain headers')
extra_orderdeps = [] extra_orderdeps = []
compiler = self.get_compiler_for_source(src, target.is_cross) compiler = get_compiler_for_source(target.compilers.values(), src)
commands = [] commands = []
# The first thing is implicit include directories: source, build and private. # The first thing is implicit include directories: source, build and private.
commands += compiler.get_include_args(self.get_target_private_dir(target), False) commands += compiler.get_include_args(self.get_target_private_dir(target), False)
@ -1867,7 +1867,7 @@ rule FORTRAN_DEP_HACK
'directory as source, please put it in a subdirectory.' \ 'directory as source, please put it in a subdirectory.' \
''.format(target.get_basename()) ''.format(target.get_basename())
raise InvalidArguments(msg) raise InvalidArguments(msg)
compiler = self.get_compiler_for_lang(lang) compiler = target.compilers[lang]
if compiler.id == 'msvc': if compiler.id == 'msvc':
src = os.path.join(self.build_to_src, target.get_source_subdir(), pch[-1]) src = os.path.join(self.build_to_src, target.get_source_subdir(), pch[-1])
(commands, dep, dst, objs) = self.generate_msvc_pch_command(target, compiler, pch) (commands, dep, dst, objs) = self.generate_msvc_pch_command(target, compiler, pch)

@ -17,7 +17,7 @@ from . import environment
from . import dependencies from . import dependencies
from . import mlog from . import mlog
import copy, os, re import copy, os, re
from .mesonlib import File, flatten, MesonException, stringlistify from .mesonlib import File, flatten, MesonException, stringlistify, classify_unity_sources
from .environment import for_windows, for_darwin from .environment import for_windows, for_darwin
known_basic_kwargs = {'install' : True, known_basic_kwargs = {'install' : True,
@ -176,9 +176,42 @@ class IncludeDirs():
return self.extra_build_dirs return self.extra_build_dirs
class ExtractedObjects(): class ExtractedObjects():
'''
Holds a list of sources for which the objects must be extracted
'''
def __init__(self, target, srclist): def __init__(self, target, srclist):
self.target = target self.target = target
self.srclist = srclist self.srclist = srclist
self.check_unity_compatible()
def check_unity_compatible(self):
# Figure out if the extracted object list is compatible with a Unity
# build. When we're doing a Unified build, we go through the sources,
# and create a single source file from each subset of the sources that
# can be compiled with a specific compiler. Then we create one object
# from each unified source file.
# If the list of sources for which we want objects is the same as the
# list of sources that go into each unified build, we're good.
self.unity_compatible = False
srclist_set = set(self.srclist)
# Objects for all the sources are required, so we're compatible
if srclist_set == set(self.target.sources):
self.unity_compatible = True
return
# Check if the srclist is a subset (of the target's sources) that is
# going to form a unified source file and a single object
compsrcs = classify_unity_sources(self.target.compilers.values(),
self.target.sources)
for srcs in compsrcs.values():
if srclist_set == set(srcs):
self.unity_compatible = True
return
msg = 'Single object files can not be extracted in Unity builds. ' \
'You can only extract all the object files at once.'
raise MesonException(msg)
def get_want_all_objects(self):
return self.want_all_objects
class EnvironmentVariables(): class EnvironmentVariables():
def __init__(self): def __init__(self):
@ -397,14 +430,11 @@ class BuildTarget():
if 'link_with' in self.kwargs: if 'link_with' in self.kwargs:
self.kwargs['link_with'] = self.unpack_holder(self.kwargs['link_with']) self.kwargs['link_with'] = self.unpack_holder(self.kwargs['link_with'])
def extract_objects(self, srcargs): def extract_objects(self, srclist):
obj_src = [] obj_src = []
for srclist in srcargs:
if not isinstance(srclist, list):
srclist = [srclist]
for src in srclist: for src in srclist:
if not isinstance(src, str): if not isinstance(src, str):
raise MesonException('Extraction arguments must be strings.') raise MesonException('Object extraction arguments must be strings.')
src = File(False, self.subdir, src) src = File(False, self.subdir, src)
if src not in self.sources: if src not in self.sources:
raise MesonException('Tried to extract unknown source %s.' % src) raise MesonException('Tried to extract unknown source %s.' % src)

@ -2571,8 +2571,6 @@ requirements use the version keyword argument instead.''')
else: else:
obj = self.evaluate_statement(invokable) obj = self.evaluate_statement(invokable)
method_name = node.name method_name = node.name
if method_name == 'extract_objects' and self.environment.coredata.get_builtin_option('unity'):
raise InterpreterException('Single object files can not be extracted in Unity builds.')
args = node.args args = node.args
if isinstance(obj, mparser.StringNode): if isinstance(obj, mparser.StringNode):
obj = obj.get_value() obj = obj.get_value()

@ -70,6 +70,22 @@ class File:
def __hash__(self): def __hash__(self):
return hash((self.fname, self.subdir, self.is_built)) return hash((self.fname, self.subdir, self.is_built))
def get_compiler_for_source(compilers, src):
for comp in compilers:
if comp.can_compile(src):
return comp
raise RuntimeError('No specified compiler can handle file {!s}'.format(src))
def classify_unity_sources(compilers, sources):
compsrclist = {}
for src in sources:
comp = get_compiler_for_source(compilers, src)
if comp not in compsrclist:
compsrclist[comp] = [src]
else:
compsrclist[comp].append(src)
return compsrclist
def flatten(item): def flatten(item):
if not isinstance(item, list): if not isinstance(item, list):
return item return item

@ -1,6 +1,7 @@
project('object extraction', 'c') project('object extraction', 'c')
lib = shared_library('somelib', ['lib.c', 'src/lib.c']) lib = shared_library('somelib', ['lib.c', 'src/lib.c'])
obj = lib.extract_objects(['lib.c', 'src/lib.c']) # Also tests that the object list is flattened properly
obj = lib.extract_objects(['lib.c', ['src/lib.c']])
exe = executable('main', 'main.c', objects: obj) exe = executable('main', 'main.c', objects: obj)
test('extraction', exe) test('extraction', exe)

@ -1,4 +1,8 @@
project('Extract objects from subdirs.', 'c') project('Extract objects from subdirs.', 'c')
if meson.is_unity()
message('Unity build: skipping incompatible test')
else
subdir('src') subdir('src')
subdir('tst') subdir('tst')
endif

@ -1,3 +1,5 @@
#pragma once
typedef enum typedef enum
{ {
MESON_THE_XVALUE, MESON_THE_XVALUE,

@ -1,4 +1,8 @@
project('valatest', 'vala', 'c', default_options : ['werror=true']) project('valatest', 'vala', 'c')
if not meson.is_unity()
add_global_arguments('-Werror', language : 'c')
endif
valadeps = [dependency('glib-2.0', version : '>=2.32'), dependency('gobject-2.0')] valadeps = [dependency('glib-2.0', version : '>=2.32'), dependency('gobject-2.0')]

Loading…
Cancel
Save