From 085650a1e3769366ac47f7d8a59386ed6d5a1ef5 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Fri, 11 Nov 2016 06:12:34 +0530 Subject: [PATCH] vala: Implement valac.find_library Move CCompiler.compile to Compiler.compile so that ValaCompiler can use it. Also rewrite ValaCompiler.sanity_check to use it since it does a simple compile check. At the same time, it enhances ExternalLibrary to support arguments for languages other than C-like. Includes a test for this that links against zlib through Vala. Closes #983 --- mesonbuild/backend/ninjabackend.py | 2 + mesonbuild/compilers.py | 133 ++++++++++-------- mesonbuild/dependencies.py | 32 +++-- mesonbuild/interpreter.py | 14 +- test cases/vala/13 find library/meson.build | 9 ++ test cases/vala/13 find library/test.vala | 6 + .../vala/4 config/meson-something-else.vapi | 1 + test cases/vala/4 config/meson.build | 8 +- test cases/vala/4 config/prog.vala | 1 + 9 files changed, 133 insertions(+), 73 deletions(-) create mode 100644 test cases/vala/13 find library/meson.build create mode 100644 test cases/vala/13 find library/test.vala create mode 100644 test cases/vala/4 config/meson-something-else.vapi diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 76f47ab2c..bb681d73a 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1045,6 +1045,8 @@ int dummy; and d.version_requirement.startswith(('>=', '==')): args += ['--target-glib', d.version_requirement[2:]] args += ['--pkg', d.name] + elif isinstance(d, dependencies.ExternalLibrary): + args += d.get_lang_args('vala') extra_args = [] for a in target.extra_args.get('vala', []): diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 9b76f2984..1505807f1 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -430,6 +430,51 @@ class Compiler(): extra_flags += environment.cross_info.config['properties'].get(lang_link_args_key, []) return extra_flags + @contextlib.contextmanager + def compile(self, code, extra_args=None): + if extra_args is None: + extra_args = [] + + try: + with tempfile.TemporaryDirectory() as tmpdirname: + if isinstance(code, str): + srcname = os.path.join(tmpdirname, + 'testfile.' + self.default_suffix) + with open(srcname, 'w') as ofile: + ofile.write(code) + elif isinstance(code, mesonlib.File): + srcname = code.fname + + # Extension only matters if running results; '.exe' is + # guaranteed to be executable on every platform. + output = os.path.join(tmpdirname, 'output.exe') + + commands = self.get_exelist() + commands.append(srcname) + commands += extra_args + commands += self.get_output_args(output) + mlog.debug('Running compile:') + mlog.debug('Working directory: ', tmpdirname) + mlog.debug('Command line: ', ' '.join(commands), '\n') + mlog.debug('Code:\n', code) + p = subprocess.Popen(commands, cwd=tmpdirname, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stde, stdo) = p.communicate() + stde = stde.decode() + stdo = stdo.decode() + mlog.debug('Compiler stdout:\n', stdo) + mlog.debug('Compiler stderr:\n', stde) + + p.input_name = srcname + p.output_name = output + yield p + except (PermissionError, OSError): + # On Windows antivirus programs and the like hold on to files so + # they can't be deleted. There's not much to do in this case. Also, + # catch OSError because the directory is then no longer empty. + pass + def get_colorout_args(self, colortype): return [] @@ -659,51 +704,6 @@ int main () {{ args = extra_args + self.get_compiler_check_args() return self.compiles(templ.format(hname, symbol, prefix), env, args, dependencies) - @contextlib.contextmanager - def compile(self, code, extra_args=None): - if extra_args is None: - extra_args = [] - - try: - with tempfile.TemporaryDirectory() as tmpdirname: - if isinstance(code, str): - srcname = os.path.join(tmpdirname, - 'testfile.' + self.default_suffix) - with open(srcname, 'w') as ofile: - ofile.write(code) - elif isinstance(code, mesonlib.File): - srcname = code.fname - - # Extension only matters if running results; '.exe' is - # guaranteed to be executable on every platform. - output = os.path.join(tmpdirname, 'output.exe') - - commands = self.get_exelist() - commands.append(srcname) - commands += extra_args - commands += self.get_output_args(output) - mlog.debug('Running compile:') - mlog.debug('Working directory: ', tmpdirname) - mlog.debug('Command line: ', ' '.join(commands), '\n') - mlog.debug('Code:\n', code) - p = subprocess.Popen(commands, cwd=tmpdirname, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - (stde, stdo) = p.communicate() - stde = stde.decode() - stdo = stdo.decode() - mlog.debug('Compiler stdout:\n', stdo) - mlog.debug('Compiler stderr:\n', stde) - - p.input_name = srcname - p.output_name = output - yield p - except (PermissionError, OSError): - # On Windows antivirus programs and the like hold on to files so - # they can't be deleted. There's not much to do in this case. Also, - # catch OSError because the directory is then no longer empty. - pass - def compiles(self, code, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] @@ -1330,27 +1330,48 @@ class ValaCompiler(Compiler): def needs_static_linker(self): return False # Because compiles into C. + def get_output_args(self, target): + return ['-o', target] + def get_werror_args(self): return ['--fatal-warnings'] def sanity_check(self, work_dir, environment): - src = 'valatest.vala' - source_name = os.path.join(work_dir, src) - with open(source_name, 'w') as ofile: - ofile.write('''class SanityCheck : Object { -} -''') - extra_flags = self.get_cross_extra_flags(environment, compile=True, link=False) - pc = subprocess.Popen(self.exelist + extra_flags + ['-C', '-c', src], cwd=work_dir) - pc.wait() - if pc.returncode != 0: - raise EnvironmentException('Vala compiler %s can not compile programs.' % self.name_string()) + code = 'class MesonSanityCheck : Object { }' + args = self.get_cross_extra_flags(environment, compile=True, link=False) + args += ['-C'] + with self.compile(code, args) as p: + if p.returncode != 0: + msg = 'Vala compiler {!r} can not compile programs' \ + ''.format(self.name_string()) + raise EnvironmentException(msg) def get_buildtype_args(self, buildtype): if buildtype == 'debug' or buildtype == 'debugoptimized' or buildtype == 'minsize': return ['--debug'] return [] + def find_library(self, libname, env, extra_dirs): + if extra_dirs and isinstance(extra_dirs, str): + extra_dirs = [extra_dirs] + # Valac always looks in the default vapi dir, so only search there if + # no extra dirs are specified. + if len(extra_dirs) == 0: + code = 'class MesonFindLibrary : Object { }' + vapi_args = ['--pkg', libname] + args = self.get_cross_extra_flags(env, compile=True, link=False) + args += ['-C'] + vapi_args + with self.compile(code, args) as p: + if p.returncode == 0: + return vapi_args + # Not found? Try to find the vapi file itself. + for d in extra_dirs: + vapi = os.path.join(d, libname + '.vapi') + if os.path.isfile(vapi): + return vapi + mlog.debug('Searched {!r} and {!r} wasn\'t found'.format(extra_dirs, libname)) + return None + class RustCompiler(Compiler): def __init__(self, exelist, version): self.language = 'rust' diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index 8a1ff16dd..d336c2117 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -455,29 +455,37 @@ class ExternalProgram(): return self.name class ExternalLibrary(Dependency): - def __init__(self, name, link_args=None, silent=False): + # TODO: Add `lang` to the parent Dependency object so that dependencies can + # be expressed for languages other than C-like + def __init__(self, name, link_args=None, language=None, silent=False): super().__init__('external') self.name = name - # Rename fullpath to link_args once standalone find_library() gets removed. - if link_args is not None: - if isinstance(link_args, list): - self.link_args = link_args + self.is_found = False + self.link_args = [] + self.lang_args = [] + if link_args: + self.is_found = True + if not isinstance(link_args, list): + link_args = [link_args] + if language: + self.lang_args = {language: link_args} else: - self.link_args = [link_args] - else: - self.link_args = link_args + self.link_args = link_args if not silent: - if self.found(): + if self.is_found: mlog.log('Library', mlog.bold(name), 'found:', mlog.green('YES')) else: mlog.log('Library', mlog.bold(name), 'found:', mlog.red('NO')) def found(self): - return self.link_args is not None + return self.is_found def get_link_args(self): - if self.found(): - return self.link_args + return self.link_args + + def get_lang_args(self, lang): + if lang in self.lang_args: + return self.lang_args[lang] return [] class BoostDependency(Dependency): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 33587a46a..f8af82efc 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -944,9 +944,17 @@ class CompilerHolder(InterpreterObject): if not os.path.isabs(i): raise InvalidCode('Search directory %s is not an absolute path.' % i) linkargs = self.compiler.find_library(libname, self.environment, search_dirs) - if required and linkargs is None: - raise InterpreterException('Library {} not found'.format(libname)) - lib = dependencies.ExternalLibrary(libname, linkargs) + if required and not linkargs: + l = self.compiler.language.capitalize() + raise InterpreterException('{} library {!r} not found'.format(l, libname)) + # If this is set to None, the library and link arguments are for + # a C-like compiler. Otherwise, it's for some other language that has + # a find_library implementation. We do this because it's easier than + # maintaining a list of languages that can consume C libraries. + lang = None + if self.compiler.language == 'vala': + lang = 'vala' + lib = dependencies.ExternalLibrary(libname, linkargs, language=lang) return ExternalLibraryHolder(lib) def has_argument_method(self, args, kwargs): diff --git a/test cases/vala/13 find library/meson.build b/test cases/vala/13 find library/meson.build new file mode 100644 index 000000000..03054d29b --- /dev/null +++ b/test cases/vala/13 find library/meson.build @@ -0,0 +1,9 @@ +project('find vala library', 'vala', 'c') + +valac = meson.get_compiler('vala') + +gobject = dependency('gobject-2.0') +zlib = valac.find_library('zlib') + +e = executable('zlibtest', 'test.vala', dependencies : [gobject, zlib]) +test('testzlib', e) diff --git a/test cases/vala/13 find library/test.vala b/test cases/vala/13 find library/test.vala new file mode 100644 index 000000000..b087cfb6d --- /dev/null +++ b/test cases/vala/13 find library/test.vala @@ -0,0 +1,6 @@ +using ZLib; + +public static int main(string[] args) { + stdout.printf("ZLIB_VERSION is: %s\n", ZLib.VERSION.STRING); + return 0; +} diff --git a/test cases/vala/4 config/meson-something-else.vapi b/test cases/vala/4 config/meson-something-else.vapi new file mode 100644 index 000000000..ce2b83a73 --- /dev/null +++ b/test cases/vala/4 config/meson-something-else.vapi @@ -0,0 +1 @@ +public const string SOMETHING_ELSE; diff --git a/test cases/vala/4 config/meson.build b/test cases/vala/4 config/meson.build index 9436e41cf..7cb93cf73 100644 --- a/test cases/vala/4 config/meson.build +++ b/test cases/vala/4 config/meson.build @@ -1,11 +1,15 @@ project('valatest', 'vala', 'c') -valadeps = [dependency('glib-2.0'), dependency('gobject-2.0')] +valac = meson.get_compiler('vala') +# Try to find our library +valadeps = [valac.find_library('meson-something-else', dirs : meson.current_source_dir())] +valadeps += [dependency('glib-2.0'), dependency('gobject-2.0')] e = executable( 'valaprog', sources : ['config.vapi', 'prog.vala'], dependencies : valadeps, -c_args : '-DDATA_DIRECTORY="@0@"'.format(meson.current_source_dir()) +c_args : ['-DDATA_DIRECTORY="@0@"'.format(meson.current_source_dir()), + '-DSOMETHING_ELSE="Out of this world!"'] ) test('valatest', e) diff --git a/test cases/vala/4 config/prog.vala b/test cases/vala/4 config/prog.vala index 7ab600cc5..2b08dadb6 100644 --- a/test cases/vala/4 config/prog.vala +++ b/test cases/vala/4 config/prog.vala @@ -2,6 +2,7 @@ class MainProg : GLib.Object { public static int main(string[] args) { stdout.printf("DATA_DIRECTORY is: %s.\n", DATA_DIRECTORY); + stdout.printf("SOMETHING_ELSE is: %s.\n", SOMETHING_ELSE); return 0; } }