From 56823272ab96892e4f6c8dd86842d8c930281209 Mon Sep 17 00:00:00 2001 From: Matthias Klumpp Date: Thu, 18 Aug 2016 10:42:12 +0200 Subject: Implement D support This patch adds support for the D programming language[1] to Meson. The following compilers are supported: * LDC * GDC * DMD [1]: http://dlang.org/ --- authors.txt | 1 + mesonbuild/backend/backends.py | 9 +- mesonbuild/build.py | 5 + mesonbuild/compilers.py | 281 ++++++++++++++++++++++++++++- mesonbuild/environment.py | 49 ++++- mesonbuild/interpreter.py | 4 + mesonbuild/optinterpreter.py | 1 + run_tests.py | 12 ++ test cases/d/1 simple/app.d | 8 + test cases/d/1 simple/installed_files.txt | 1 + test cases/d/1 simple/meson.build | 4 + test cases/d/1 simple/utils.d | 8 + test cases/d/2 library/app.d | 8 + test cases/d/2 library/installed_files.txt | 4 + test cases/d/2 library/libstuff.d | 9 + test cases/d/2 library/meson.build | 14 ++ 16 files changed, 414 insertions(+), 4 deletions(-) create mode 100644 test cases/d/1 simple/app.d create mode 100644 test cases/d/1 simple/installed_files.txt create mode 100644 test cases/d/1 simple/meson.build create mode 100644 test cases/d/1 simple/utils.d create mode 100644 test cases/d/2 library/app.d create mode 100644 test cases/d/2 library/installed_files.txt create mode 100644 test cases/d/2 library/libstuff.d create mode 100644 test cases/d/2 library/meson.build diff --git a/authors.txt b/authors.txt index 4244917..e9fceb0 100644 --- a/authors.txt +++ b/authors.txt @@ -40,3 +40,4 @@ Noam Meltzer Vincent Szolnoky Zhe Wang Wim Taymans +Matthias Klumpp diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 1f1c3ca..806a6f3 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -16,6 +16,7 @@ import os, pickle, re from .. import build from .. import dependencies from .. import mesonlib +from .. import compilers import json import subprocess from ..mesonlib import MesonException @@ -233,6 +234,9 @@ class Backend(): def has_swift(self, target): return self.has_source_suffix(target, '.swift') + def has_d(self, target): + return self.has_source_suffix(target, '.d') + def determine_linker(self, target, src): if isinstance(target, build.StaticLibrary): if self.build.static_cross_linker is not None: @@ -358,7 +362,10 @@ class Backend(): if not isinstance(d, build.StaticLibrary) and\ not isinstance(d, build.SharedLibrary): raise RuntimeError('Tried to link with a non-library target "%s".' % d.get_basename()) - args.append(self.get_target_filename_for_linking(d)) + if isinstance(compiler, compilers.LLVMDCompiler): + args.extend(['-L', self.get_target_filename_for_linking(d)]) + else: + args.append(self.get_target_filename_for_linking(d)) # If you have executable e that links to shared lib s1 that links to shared library s2 # you have to specify s2 as well as s1 when linking e even if e does not directly use # s2. Gcc handles this case fine but Clang does not for some reason. Thus we need to diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 61dace7..143cba9 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -27,6 +27,7 @@ known_basic_kwargs = {'install' : True, 'cpp_args' : True, 'cs_args' : True, 'vala_args' : True, + 'd_args' : True, 'link_args' : True, 'link_depends': True, 'link_with' : True, @@ -385,6 +386,10 @@ class BuildTarget(): if not isinstance(valalist, list): valalist = [valalist] self.add_compiler_args('vala', valalist) + dlist = kwargs.get('d_args', []) + if not isinstance(dlist, list): + dlist = [dlist] + self.add_compiler_args('d', dlist) self.link_args = kwargs.get('link_args', []) if not isinstance(self.link_args, list): self.link_args = [self.link_args] diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index d020033..88184cb 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -23,7 +23,7 @@ from . import coredata about. To support a new compiler, add its information below. Also add corresponding autodetection code in environment.py.""" -header_suffixes = ['h', 'hh', 'hpp', 'hxx', 'H', 'ipp', 'moc', 'vapi'] +header_suffixes = ['h', 'hh', 'hpp', 'hxx', 'H', 'ipp', 'moc', 'vapi', 'di'] cpp_suffixes = ['cc', 'cpp', 'cxx', 'h', 'hh', 'hpp', 'ipp', 'hxx', 'c++'] c_suffixes = ['c'] clike_suffixes = c_suffixes + cpp_suffixes @@ -108,6 +108,27 @@ rust_buildtype_args = {'plain' : [], 'minsize' : [], } +d_gdc_buildtype_args = {'plain' : [], + 'debug' : ['-g', '-O0'], + 'debugoptimized' : ['-g', '-O'], + 'release' : ['-O3', '-frelease'], + 'minsize' : [], + } + +d_ldc_buildtype_args = {'plain' : [], + 'debug' : ['-g', '-O0'], + 'debugoptimized' : ['-g', '-O'], + 'release' : ['-O3', '-release'], + 'minsize' : [], + } + +d_dmd_buildtype_args = {'plain' : [], + 'debug' : ['-g'], + 'debugoptimized' : ['-g', '-O'], + 'release' : ['-O', '-release'], + 'minsize' : [], + } + mono_buildtype_args = {'plain' : [], 'debug' : ['-debug'], 'debugoptimized': ['-debug', '-optimize+'], @@ -1425,6 +1446,264 @@ class SwiftCompiler(Compiler): suffix = filename.split('.')[-1] return suffix in ('swift') +class DCompiler(Compiler): + def __init__(self, exelist, version, is_cross): + super().__init__(exelist, version) + self.id = 'unknown' + self.language = 'd' + self.is_cross = is_cross + + def sanity_check(self, work_dir, environment): + source_name = os.path.join(work_dir, 'sanity.d') + output_name = os.path.join(work_dir, 'dtest') + ofile = open(source_name, 'w') + ofile.write('''void main() { +} +''') + ofile.close() + pc = subprocess.Popen(self.exelist + self.get_output_args(output_name) + [source_name], cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('D compiler %s can not compile programs.' % self.name_string()) + if subprocess.call(output_name) != 0: + raise EnvironmentException('Executables created by D compiler %s are not runnable.' % self.name_string()) + + def needs_static_linker(self): + return True + + def name_string(self): + return ' '.join(self.exelist) + + def get_exelist(self): + return self.exelist + + def get_id(self): + return self.id + + def get_language(self): + return self.language + + def can_compile(self, fname): + suffix = fname.split('.')[-1] + return suffix in ('d', 'di') + + def get_linker_exelist(self): + return self.exelist[:] + + def depfile_for_object(self, objfile): + return objfile + '.' + self.get_depfile_suffix() + + def get_depfile_suffix(self): + return 'dep' + + def get_pic_args(self): + return ['-fPIC'] + + def get_std_shared_lib_link_args(self): + return ['-shared'] + + def get_soname_args(self, shlib_name, path, soversion): + return [] + + def build_rpath_args(self, build_dir, rpath_paths, install_rpath): + # This method is to be used by LDC and DMD. + # GDC can deal with the verbatim flags. + if len(rpath_paths) == 0 and len(install_rpath) == 0: + return [] + paths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths]) + if len(paths) < len(install_rpath): + padding = 'X'*(len(install_rpath) - len(paths)) + if len(paths) == 0: + paths = padding + else: + paths = paths + ':' + padding + return ['-L-rpath={}'.format(paths)] + +class GnuDCompiler(DCompiler): + def __init__(self, exelist, version, is_cross): + DCompiler.__init__(self, exelist, version, is_cross) + self.id = 'gcc' + self.warn_args = {'1': ['-Wall', '-Wdeprecated'], + '2': ['-Wall', '-Wextra', '-Wdeprecated'], + '3': ['-Wall', '-Wextra', '-Wdeprecated', '-Wpedantic']} + + def get_dependency_gen_args(self, outtarget, outfile): + # FIXME: Passing -fmake-deps results in a file-not-found message. + # Investigate why. + return [] + + def get_output_args(self, target): + return ['-o', target] + + def get_compile_only_args(self): + return ['-c'] + + def get_linker_output_args(self, target): + return ['-o', target] + + def get_include_args(self, path, is_system): + return ['-I' + path] + + def get_warn_args(self, level): + return self.warn_args[level] + + def get_werror_args(self): + return ['-Werror'] + + def get_buildtype_linker_args(self, buildtype): + return [] + + def get_std_exe_link_args(self): + return [] + + def get_buildtype_args(self, buildtype): + return d_gdc_buildtype_args[buildtype] + + def build_rpath_args(self, build_dir, rpath_paths, install_rpath): + return build_unix_rpath_args(build_dir, rpath_paths, install_rpath) + +class LLVMDCompiler(DCompiler): + def __init__(self, exelist, version, is_cross): + DCompiler.__init__(self, exelist, version, is_cross) + self.id = 'llvm' + + def get_dependency_gen_args(self, outtarget, outfile): + # LDC using the -deps flag returns a non-Makefile dependency-info file, which + # the backends can not use. So we disable this feature for now. + return [] + + def get_output_args(self, target): + return ['-of', target] + + def get_compile_only_args(self): + return ['-c'] + + def get_linker_output_args(self, target): + return ['-of', target] + + def get_include_args(self, path, is_system): + return ['-I' + path] + + def get_warn_args(self, level): + if level == '2': + return ['-wi'] + else: + return ['-w'] + + def get_coverage_args(self): + return ['-cov'] + + def get_buildtype_linker_args(self, buildtype): + return [] + + def get_std_exe_link_args(self): + return [] + + def get_buildtype_args(self, buildtype): + return d_ldc_buildtype_args[buildtype] + + def get_pic_args(self): + return ['-relocation-model=pic'] + + def _translate_args(self, args): + ldcargs = [] + # Translate common arguments to flags this compiler can + # understand. + # The flags might have been added by pkg-config files, + # and are therefore out of the user's control. + for arg in args: + if arg == '-pthread': + continue + if arg.startswith('-Wl,'): + linkargs = arg[arg.index(',')+1:].split(',') + for la in linkargs: + ldcargs.append('-L' + la.strip()) + continue + elif arg.startswith('-l'): + # translate library link flag + ldcargs.append('-L' + arg) + continue + ldcargs.append(arg) + + return ldcargs + + def unix_link_flags_to_native(self, args): + return self._translate_args(args) + + def unix_compile_flags_to_native(self, args): + return self._translate_args(args) + +class DmdDCompiler(DCompiler): + def __init__(self, exelist, version, is_cross): + DCompiler.__init__(self, exelist, version, is_cross) + self.id = 'dmd' + + def get_dependency_gen_args(self, outtarget, outfile): + # LDC using the -deps flag returns a non-Makefile dependency-info file, which + # the backends can not use. So we disable this feature for now. + return [] + + def get_output_args(self, target): + return ['-of' + target] + + def get_werror_args(self): + return ['-w'] + + def get_compile_only_args(self): + return ['-c'] + + def get_linker_output_args(self, target): + return ['-of' + target] + + def get_include_args(self, path, is_system): + return ['-I' + path] + + def get_warn_args(self, level): + return [] + + def get_coverage_args(self): + return ['-cov'] + + def get_buildtype_linker_args(self, buildtype): + return [] + + def get_std_exe_link_args(self): + return [] + + def get_buildtype_args(self, buildtype): + return d_dmd_buildtype_args[buildtype] + + def get_std_shared_lib_link_args(self): + return ['-shared', '-defaultlib=libphobos2.so'] + + def _translate_args(self, args): + dmdargs = [] + # Translate common arguments to flags this compiler can + # understand. + # The flags might have been added by pkg-config files, + # and are therefore out of the user's control. + for arg in args: + if arg == '-pthread': + continue + if arg.startswith('-Wl,'): + linkargs = arg[arg.index(',')+1:].split(',') + for la in linkargs: + dmdargs.append('-L' + la.strip()) + continue + elif arg.startswith('-l'): + # translate library link flag + dmdargs.append('-L' + arg) + continue + dmdargs.append(arg) + + return dmdargs + + def unix_link_flags_to_native(self, args): + return self._translate_args(args) + + def unix_compile_flags_to_native(self, args): + return self._translate_args(args) + class VisualStudioCCompiler(CCompiler): std_warn_args = ['/W3'] std_opt_args= ['/O2'] diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 16af0d1..a4d053e 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -18,6 +18,7 @@ from . import mesonlib from . import mlog from .compilers import * import configparser +import shutil build_filename = 'meson.build' @@ -580,6 +581,49 @@ class Environment(): return RustCompiler(exelist, version) raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + def detect_d_compiler(self): + exelist = None + is_cross = False + # Search for a D compiler. + # We prefer LDC over GDC unless overridden with the DC + # environment variable because LDC has a much more + # up to date language version at time (2016). + if 'DC' in os.environ: + exelist = os.environ['DC'].split() + elif self.is_cross_build() and want_cross: + exelist = [self.cross_info.config['binaries']['d']] + ccache = [] + is_cross = True + elif shutil.which("ldc2"): + exelist = ['ldc2'] + elif shutil.which("ldc"): + exelist = ['ldc'] + elif shutil.which("gdc"): + exelist = ['gdc'] + elif shutil.which("dmd"): + exelist = ['dmd'] + else: + raise EnvironmentException('Could not find any supported D compiler.') + + try: + p = subprocess.Popen(exelist + ['--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + raise EnvironmentException('Could not execute D compiler "%s"' % ' '.join(exelist)) + (out, _) = p.communicate() + out = out.decode(errors='ignore') + vmatch = re.search(Environment.version_regex, out) + if vmatch: + version = vmatch.group(0) + else: + version = 'unknown version' + if 'LLVM D compiler' in out: + return LLVMDCompiler(exelist, version, is_cross) + elif 'gdc' in out: + return GnuDCompiler(exelist, version, is_cross) + elif 'Digital Mars' in out: + return DmdDCompiler(exelist, version, is_cross) + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + def detect_swift_compiler(self): exelist = ['swiftc'] try: @@ -712,13 +756,14 @@ def get_args_from_envvars(lang, compiler_is_linker): if val: mlog.log('Appending {} from environment: {!r}'.format(var, val)) - if lang not in ('c', 'cpp', 'objc', 'objcpp', 'fortran'): + if lang not in ('c', 'cpp', 'objc', 'objcpp', 'fortran', 'd'): return ([], []) # Compile flags cflags_mapping = {'c': 'CFLAGS', 'cpp': 'CXXFLAGS', 'objc': 'OBJCFLAGS', 'objcpp': 'OBJCXXFLAGS', - 'fortran': 'FFLAGS'} + 'fortran': 'FFLAGS', + 'd': 'DFLAGS'} compile_flags = os.environ.get(cflags_mapping[lang], '') log_var(cflags_mapping[lang], compile_flags) compile_flags = compile_flags.split() diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 8645b68..5a6d702 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1566,6 +1566,10 @@ class Interpreter(): comp = self.environment.detect_vala_compiler() if need_cross_compiler: cross_comp = comp # Vala is too (I think). + elif lang == 'd': + comp = self.environment.detect_d_compiler() + if need_cross_compiler: + cross_comp = comp # D as well (AFAIK). elif lang == 'rust': comp = self.environment.detect_rust_compiler() if need_cross_compiler: diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 409f9dc..e38bdb9 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -20,6 +20,7 @@ import os, re forbidden_option_names = coredata.get_builtin_options() forbidden_prefixes = {'c_': True, 'cpp_': True, + 'd_': True, 'rust_': True, 'fortran_': True, 'objc_': True, diff --git a/run_tests.py b/run_tests.py index 34258d8..3e49662 100755 --- a/run_tests.py +++ b/run_tests.py @@ -304,6 +304,17 @@ def gather_tests(testdir): tests = [os.path.join(testdir, t[1]) for t in testlist] return tests +def have_d_compiler(): + if shutil.which("ldc2"): + return True + elif shutil.which("ldc"): + return True + elif shutil.which("gdc"): + return True + elif shutil.which("dmd"): + return True + return False + def detect_tests_to_run(): all_tests = [] all_tests.append(('common', gather_tests('test cases/common'), False)) @@ -318,6 +329,7 @@ def detect_tests_to_run(): all_tests.append(('C#', gather_tests('test cases/csharp'), False if shutil.which('mcs') else True)) all_tests.append(('vala', gather_tests('test cases/vala'), False if shutil.which('valac') else True)) all_tests.append(('rust', gather_tests('test cases/rust'), False if shutil.which('rustc') else True)) + all_tests.append(('d', gather_tests('test cases/d'), False if have_d_compiler() else True)) all_tests.append(('objective c', gather_tests('test cases/objc'), False if not mesonlib.is_windows() else True)) all_tests.append(('fortran', gather_tests('test cases/fortran'), False if shutil.which('gfortran') else True)) all_tests.append(('swift', gather_tests('test cases/swift'), False if shutil.which('swiftc') else True)) diff --git a/test cases/d/1 simple/app.d b/test cases/d/1 simple/app.d new file mode 100644 index 0000000..0be1d2c --- /dev/null +++ b/test cases/d/1 simple/app.d @@ -0,0 +1,8 @@ + +import std.stdio; +import utils; + +void main () +{ + printGreeting ("a Meson D test"); +} diff --git a/test cases/d/1 simple/installed_files.txt b/test cases/d/1 simple/installed_files.txt new file mode 100644 index 0000000..9374c54 --- /dev/null +++ b/test cases/d/1 simple/installed_files.txt @@ -0,0 +1 @@ +usr/bin/dsimpleapp?exe diff --git a/test cases/d/1 simple/meson.build b/test cases/d/1 simple/meson.build new file mode 100644 index 0000000..a10b67b --- /dev/null +++ b/test cases/d/1 simple/meson.build @@ -0,0 +1,4 @@ +project('D Simple Test', 'd') + +e = executable('dsimpleapp', ['app.d', 'utils.d'], install : true) +test('apptest', e) diff --git a/test cases/d/1 simple/utils.d b/test cases/d/1 simple/utils.d new file mode 100644 index 0000000..8645548 --- /dev/null +++ b/test cases/d/1 simple/utils.d @@ -0,0 +1,8 @@ + +import std.stdio; +import std.string : format; + +void printGreeting (string name) +{ + writeln ("Hello, I am %s.".format (name)); +} diff --git a/test cases/d/2 library/app.d b/test cases/d/2 library/app.d new file mode 100644 index 0000000..5d84a69 --- /dev/null +++ b/test cases/d/2 library/app.d @@ -0,0 +1,8 @@ + +import libstuff; + +void main () +{ + immutable ret = printLibraryString ("foo"); + assert (ret == 4); +} diff --git a/test cases/d/2 library/installed_files.txt b/test cases/d/2 library/installed_files.txt new file mode 100644 index 0000000..f062075 --- /dev/null +++ b/test cases/d/2 library/installed_files.txt @@ -0,0 +1,4 @@ +usr/bin/app_d?exe +?usr/lib/libstuff.so +usr/bin/app_s?exe +usr/lib/libstuff.a diff --git a/test cases/d/2 library/libstuff.d b/test cases/d/2 library/libstuff.d new file mode 100644 index 0000000..676a643 --- /dev/null +++ b/test cases/d/2 library/libstuff.d @@ -0,0 +1,9 @@ + +import std.stdio; +import std.string : format; + +int printLibraryString (string str) +{ + writeln ("Library says: %s".format (str)); + return 4; +} diff --git a/test cases/d/2 library/meson.build b/test cases/d/2 library/meson.build new file mode 100644 index 0000000..811131c --- /dev/null +++ b/test cases/d/2 library/meson.build @@ -0,0 +1,14 @@ +project('D Library', 'd') + +if meson.get_compiler('d').get_id() != 'gcc' + ldyn = shared_library('stuff', 'libstuff.d', install : true) + ed = executable('app_d', 'app.d', link_with : ldyn, install : true) + test('linktest_dyn', ed) + +else + message('GDC can not build shared libraries. Build skipped.') +endif + +lstatic = static_library('stuff', 'libstuff.d', install : true) +es = executable('app_s', 'app.d', link_with : lstatic, install : true) +test('linktest_static', es) -- cgit v1.1