Skip to content

Commit

Permalink
gnome: Distinguish between internal and external linker flags
Browse files Browse the repository at this point in the history
When an older version of the library being built is installed in the
same prefix as external dependencies, we have to be careful to construct
the linker or compiler command line. If a -L flag from external
dependencoes comes before a -L flag pointing to builddir, it is possible
for the linker to load older libraries from the installation prefix
instead of the newly built ones, which is likely to cause undefined
reference error.

Since the order of dependencies is not significant, we cannot expect
internal dependencies to appear before external dependencies when
recursively iterating the list of dependencies. To make it harder to
make mistakes, linker flags come from internal and external
dependencies are now stored in different order sets. Code using
_get_dependencies_flags are expected to follow the order when
constructing linker command line:

  1. Internal linker flags
  2. LDFLAGS set by users
  3. External linker flags

It is similar to what automake and libtool do for autotools projects.
  • Loading branch information
lantw44 authored and nirbheek committed Jul 1, 2018
1 parent cb2e487 commit cb36add
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 24 deletions.
61 changes: 37 additions & 24 deletions mesonbuild/modules/gnome.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,8 @@ def _get_link_args(self, state, lib, depends, include_rpath=False,
def _get_dependencies_flags(self, deps, state, depends, include_rpath=False,
use_gir_args=False):
cflags = OrderedSet()
ldflags = OrderedSet()
internal_ldflags = OrderedSet()
external_ldflags = OrderedSet()
gi_includes = OrderedSet()
deps = mesonlib.listify(deps, unholder=True)

Expand All @@ -326,17 +327,19 @@ def _get_dependencies_flags(self, deps, state, depends, include_rpath=False,
for lib in dep.libraries:
if hasattr(lib, 'held_object'):
lib = lib.held_object
ldflags.update(self._get_link_args(state, lib, depends, include_rpath))
internal_ldflags.update(self._get_link_args(state, lib, depends, include_rpath))
libdepflags = self._get_dependencies_flags(lib.get_external_deps(), state, depends, include_rpath,
use_gir_args)
cflags.update(libdepflags[0])
ldflags.update(libdepflags[1])
gi_includes.update(libdepflags[2])
internal_ldflags.update(libdepflags[1])
external_ldflags.update(libdepflags[2])
gi_includes.update(libdepflags[3])
extdepflags = self._get_dependencies_flags(dep.ext_deps, state, depends, include_rpath,
use_gir_args)
cflags.update(extdepflags[0])
ldflags.update(extdepflags[1])
gi_includes.update(extdepflags[2])
internal_ldflags.update(extdepflags[1])
external_ldflags.update(extdepflags[2])
gi_includes.update(extdepflags[3])
for source in dep.sources:
if hasattr(source, 'held_object'):
source = source.held_object
Expand All @@ -351,9 +354,9 @@ def _get_dependencies_flags(self, deps, state, depends, include_rpath=False,
# For PkgConfigDependency only:
getattr(dep, 'is_libtool', False)):
lib_dir = os.path.dirname(lib)
ldflags.update(["-L%s" % lib_dir])
external_ldflags.update(["-L%s" % lib_dir])
if include_rpath:
ldflags.update(['-Wl,-rpath {}'.format(lib_dir)])
external_ldflags.update(['-Wl,-rpath {}'.format(lib_dir)])
libname = os.path.basename(lib)
if libname.startswith("lib"):
libname = libname[3:]
Expand All @@ -362,7 +365,7 @@ def _get_dependencies_flags(self, deps, state, depends, include_rpath=False,
# Hack to avoid passing some compiler options in
if lib.startswith("-W"):
continue
ldflags.update([lib])
external_ldflags.update([lib])

if isinstance(dep, PkgConfigDependency):
girdir = dep.get_pkgconfig_variable("girdir", {'default': ''})
Expand All @@ -376,14 +379,17 @@ def _get_dependencies_flags(self, deps, state, depends, include_rpath=False,
continue

if gir_has_extra_lib_arg(self.interpreter) and use_gir_args:
fixed_ldflags = OrderedSet()
for ldflag in ldflags:
if ldflag.startswith("-l"):
fixed_ldflags.add(ldflag.replace('-l', '--extra-library=', 1))
else:
fixed_ldflags.add(ldflag)
ldflags = fixed_ldflags
return cflags, ldflags, gi_includes
def fix_ldflags(ldflags):
fixed_ldflags = OrderedSet()
for ldflag in ldflags:
if ldflag.startswith("-l"):
fixed_ldflags.add(ldflag.replace('-l', '--extra-library=', 1))
else:
fixed_ldflags.add(ldflag)
return fixed_ldflags
internal_ldflags = fix_ldflags(internal_ldflags)
external_ldflags = fix_ldflags(external_ldflags)
return cflags, internal_ldflags, external_ldflags, gi_includes

@FeatureNewKwargs('build target', '0.40.0', ['build_by_default'])
@permittedKwargs({'sources', 'nsversion', 'namespace', 'symbol_prefix', 'identifier_prefix',
Expand Down Expand Up @@ -487,7 +493,8 @@ def generate_gir(self, state, args, kwargs):
'Gir includes must be str, GirTarget, or list of them')

cflags = []
ldflags = []
internal_ldflags = []
external_ldflags = []
for lang, compiler in girtarget.compilers.items():
# XXX: Can you use g-i with any other language?
if lang in ('c', 'cpp', 'objc', 'objcpp', 'd'):
Expand All @@ -504,7 +511,7 @@ def generate_gir(self, state, args, kwargs):
sanitize = state.environment.coredata.base_options['b_sanitize'].value
cflags += compilers.sanitizer_compile_args(sanitize)
if 'address' in sanitize.split(','):
ldflags += ['-lasan']
external_ldflags += ['-lasan']
# FIXME: Linking directly to libasan is not recommended but g-ir-scanner
# does not understand -f LDFLAGS. https://bugzilla.gnome.org/show_bug.cgi?id=783892
# ldflags += compilers.sanitizer_link_args(sanitize)
Expand Down Expand Up @@ -565,10 +572,11 @@ def generate_gir(self, state, args, kwargs):
# ldflags will be misinterpreted by gir scanner (showing
# spurious dependencies) but building GStreamer fails if they
# are not used here.
dep_cflags, dep_ldflags, gi_includes = self._get_dependencies_flags(deps, state, depends,
use_gir_args=True)
dep_cflags, dep_internal_ldflags, dep_external_ldflags, gi_includes = \
self._get_dependencies_flags(deps, state, depends, use_gir_args=True)
cflags += list(dep_cflags)
ldflags += list(dep_ldflags)
internal_ldflags += list(dep_internal_ldflags)
external_ldflags += list(dep_external_ldflags)
scan_command += ['--cflags-begin']
scan_command += cflags
scan_command += state.environment.coredata.get_external_args(lang)
Expand All @@ -578,7 +586,7 @@ def generate_gir(self, state, args, kwargs):
# ones.
if isinstance(girtarget, build.SharedLibrary):
scan_command += ["-L@PRIVATE_OUTDIR_ABS_%s@" % girtarget.get_id()]
scan_command += list(ldflags)
scan_command += list(internal_ldflags)
for i in gi_includes:
scan_command += ['--add-include-path=%s' % i]

Expand Down Expand Up @@ -606,6 +614,7 @@ def generate_gir(self, state, args, kwargs):
for link_arg in state.environment.coredata.get_external_link_args(lang):
if link_arg.startswith('-L'):
scan_command.append(link_arg)
scan_command += list(external_ldflags)

scankwargs = {'output': girfile,
'command': scan_command,
Expand Down Expand Up @@ -832,15 +841,19 @@ def gtkdoc(self, state, args, kwargs):
def _get_build_args(self, kwargs, state, depends):
args = []
deps = extract_as_list(kwargs, 'dependencies', unholder=True)
cflags, ldflags, gi_includes = self._get_dependencies_flags(deps, state, depends, include_rpath=True)
cflags, internal_ldflags, external_ldflags, gi_includes = \
self._get_dependencies_flags(deps, state, depends, include_rpath=True)
inc_dirs = mesonlib.extract_as_list(kwargs, 'include_directories')
for incd in inc_dirs:
if not isinstance(incd.held_object, (str, build.IncludeDirs)):
raise MesonException(
'Gir include dirs should be include_directories().')
cflags.update(get_include_args(inc_dirs))
cflags.update(state.environment.coredata.get_external_args('c'))
ldflags = OrderedSet()
ldflags.update(internal_ldflags)
ldflags.update(state.environment.coredata.get_external_link_args('c'))
ldflags.update(external_ldflags)
if cflags:
args += ['--cflags=%s' % ' '.join(cflags)]
if ldflags:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "fake-gthread.h"

int fake_gthread_fake_function (void)
{
return 7;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef FAKE_GTHREAD_H
#define FAKE_GTHREAD_H

int fake_gthread_fake_function (void);

#endif /* FAKE_GTHREAD_H */
12 changes: 12 additions & 0 deletions test cases/frameworks/22 gir link order/fake-gthread/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
fake_gthread_sources = ['fake-gthread.c', 'fake-gthread.h']
fake_gthread_lib = shared_library(
'gthread-2.0',
sources : fake_gthread_sources,
install : false,
)

fake_gthread_includes = include_directories('.')
fake_gthread = declare_dependency(
include_directories : fake_gthread_includes,
link_with : fake_gthread_lib,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include "get-prgname.h"

#include <glib.h>

const char *get_prgname_get_name (void)
{
return g_get_prgname ();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef GET_PRGNAME_H
#define GET_PRGNAME_H

const char *get_prgname_get_name (void);

#endif /* GET_PRGNAME_H */
13 changes: 13 additions & 0 deletions test cases/frameworks/22 gir link order/get-prgname/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
get_prgname_sources = ['get-prgname.c', 'get-prgname.h']
get_prgname_lib = shared_library(
'get-prgname',
sources : get_prgname_sources,
dependencies : [glib],
install : false,
)

get_prgname_includes = include_directories('.')
get_prgname = declare_dependency(
include_directories : get_prgname_includes,
link_with : get_prgname_lib,
)
48 changes: 48 additions & 0 deletions test cases/frameworks/22 gir link order/meson-sample.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "meson-sample.h"

#include "get-prgname.h"
#include "fake-gthread.h"

struct _MesonSample {
GObject parent_instance;
};

G_DEFINE_TYPE (MesonSample, meson_sample, G_TYPE_OBJECT)

/**
* meson_sample_new:
*
* Allocates a new #MesonSample.
*
* Returns: (transfer full): a #MesonSample.
*/
MesonSample *
meson_sample_new (void)
{
return g_object_new (MESON_TYPE_SAMPLE, NULL);
}

static void
meson_sample_class_init (MesonSampleClass *klass)
{
}

static void
meson_sample_init (MesonSample *self)
{
}

/**
* meson_sample_print_message:
* @self: a #MesonSample.
*
* Prints a message.
*/
void
meson_sample_print_message (MesonSample *self)
{
g_return_if_fail (MESON_IS_SAMPLE (self));

g_print ("Message: %s\n", get_prgname_get_name ());
g_print ("Message: %d\n", fake_gthread_fake_function ());
}
17 changes: 17 additions & 0 deletions test cases/frameworks/22 gir link order/meson-sample.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef MESON_SAMPLE_H
#define MESON_SAMPLE_H

#include <glib-object.h>

G_BEGIN_DECLS

#define MESON_TYPE_SAMPLE (meson_sample_get_type())

G_DECLARE_FINAL_TYPE (MesonSample, meson_sample, MESON, SAMPLE, GObject)

MesonSample *meson_sample_new (void);
void meson_sample_print_message (MesonSample *self);

G_END_DECLS

#endif /* MESON_SAMPLE_H */
41 changes: 41 additions & 0 deletions test cases/frameworks/22 gir link order/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
project('gir link order', 'c')

if not dependency('glib-2.0', required : false).found()
error('MESON_SKIP_TEST glib not found.')
endif

gnome = import('gnome')
glib = dependency('glib-2.0')
gobject = dependency('gobject-2.0')

# get-prgname is a shared library which uses a function from glib-2.0. It is
# used to introduce external -L flags which may cause -L order problems.
subdir('get-prgname')

# fake-gthread is a shared library which has the same name as gthread-2.0 from
# GLib. This is used to simulate the case where an older or unrelated version
# of a library is already installed on the system. Our meson sample library
# defined below uses a function from fake-gthread. If meson messes up -L order,
# the linker will find libgthread-2.0.so installed on the system and fail to
# find the symbol our meson sample library uses.
subdir('fake-gthread')

meson_sample_sources = ['meson-sample.c', 'meson-sample.h']
meson_sample_lib = shared_library(
'sample',
sources : meson_sample_sources,
dependencies : [gobject, get_prgname, fake_gthread],
install : false,
)

gnome.generate_gir(
meson_sample_lib,
sources : meson_sample_sources,
nsversion : '1.0',
namespace : 'Meson',
symbol_prefix : 'meson',
identifier_prefix : 'Meson',
includes : ['GObject-2.0'],
install : false,
build_by_default: true,
)

0 comments on commit cb36add

Please sign in to comment.