diff --git a/docs/markdown/snippets/csc.md b/docs/markdown/snippets/csc.md new file mode 100644 index 000000000..90a6f7f94 --- /dev/null +++ b/docs/markdown/snippets/csc.md @@ -0,0 +1,4 @@ +## Visual Studio C# compiler support + +In addition to the Mono C# compiler we also support Visual Studio's C# +compiler. Currently this is only supported on the Ninja backend. diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index e8c8b39ba..8b616a60e 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -103,7 +103,8 @@ class NinjaBuildElement: # This is the only way I could find to make this work on all # platforms including Windows command shell. Slash is a dir separator # on Windows, too, so all characters are unambiguous and, more importantly, - # do not require quoting. + # do not require quoting, unless explicitely specified, which is necessary for + # the csc compiler. line = line.replace('\\', '/') outfile.write(line) @@ -988,7 +989,7 @@ int dummy; outname_rel = os.path.join(self.get_target_dir(target), fname) src_list = target.get_sources() compiler = target.compilers['cs'] - rel_srcs = [s.rel_to_builddir(self.build_to_src) for s in src_list] + rel_srcs = [os.path.normpath(s.rel_to_builddir(self.build_to_src)) for s in src_list] deps = [] commands = CompilerArgs(compiler, target.extra_args.get('cs', [])) commands += compiler.get_buildtype_args(buildtype) @@ -1014,8 +1015,8 @@ int dummy; for rel_src in generated_sources.keys(): dirpart, fnamepart = os.path.split(rel_src) if rel_src.lower().endswith('.cs'): - rel_srcs.append(rel_src) - deps.append(rel_src) + rel_srcs.append(os.path.normpath(rel_src)) + deps.append(os.path.normpath(rel_src)) for dep in target.get_external_deps(): commands.extend_direct(dep.get_link_args()) @@ -1588,7 +1589,15 @@ int dummy; def generate_cs_compile_rule(self, compiler, outfile): rule = 'rule %s_COMPILER\n' % compiler.get_language() invoc = ' '.join([ninja_quote(i) for i in compiler.get_exelist()]) - command = ' command = %s $ARGS $in\n' % invoc + + if mesonlib.is_windows(): + command = ''' command = {executable} @$out.rsp + rspfile = $out.rsp + rspfile_content = $ARGS $in +'''.format(executable=invoc) + else: + command = ' command = %s $ARGS $in\n' % invoc + description = ' description = Compiling C Sharp target $out.\n' outfile.write(rule) outfile.write(command) diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py index f09f25277..84c87fb09 100644 --- a/mesonbuild/compilers/__init__.py +++ b/mesonbuild/compilers/__init__.py @@ -67,6 +67,7 @@ __all__ = [ 'JavaCompiler', 'LLVMDCompiler', 'MonoCompiler', + 'VisualStudioCsCompiler', 'NAGFortranCompiler', 'ObjCCompiler', 'ObjCPPCompiler', @@ -127,7 +128,7 @@ from .cpp import ( IntelCPPCompiler, VisualStudioCPPCompiler, ) -from .cs import MonoCompiler +from .cs import MonoCompiler, VisualStudioCsCompiler from .d import ( DCompiler, DmdDCompiler, diff --git a/mesonbuild/compilers/cs.py b/mesonbuild/compilers/cs.py index dd7a433d9..f78e364b3 100644 --- a/mesonbuild/compilers/cs.py +++ b/mesonbuild/compilers/cs.py @@ -15,19 +15,26 @@ import os.path, subprocess from ..mesonlib import EnvironmentException +from ..mesonlib import is_windows from .compilers import Compiler, mono_buildtype_args -class MonoCompiler(Compiler): - def __init__(self, exelist, version, **kwargs): +class CsCompiler(Compiler): + def __init__(self, exelist, version, id, runner=None): self.language = 'cs' - super().__init__(exelist, version, **kwargs) - self.id = 'mono' - self.monorunner = 'mono' + super().__init__(exelist, version) + self.id = id + self.runner = runner def get_display_language(self): return 'C sharp' + def get_always_args(self): + return ['/nologo'] + + def get_linker_always_args(self): + return ['/nologo'] + def get_output_args(self, fname): return ['-out:' + fname] @@ -92,11 +99,14 @@ class MonoCompiler(Compiler): } } ''') - pc = subprocess.Popen(self.exelist + [src], cwd=work_dir) + pc = subprocess.Popen(self.exelist + self.get_always_args() + [src], cwd=work_dir) pc.wait() if pc.returncode != 0: raise EnvironmentException('Mono compiler %s can not compile programs.' % self.name_string()) - cmdlist = [self.monorunner, obj] + if self.runner: + cmdlist = [self.runner, obj] + else: + cmdlist = [os.path.join(work_dir, obj)] pe = subprocess.Popen(cmdlist, cwd=work_dir) pe.wait() if pe.returncode != 0: @@ -107,3 +117,25 @@ class MonoCompiler(Compiler): def get_buildtype_args(self, buildtype): return mono_buildtype_args[buildtype] + + +class MonoCompiler(CsCompiler): + def __init__(self, exelist, version): + super().__init__(exelist, version, 'mono', + 'mono') + + +class VisualStudioCsCompiler(CsCompiler): + def __init__(self, exelist, version): + super().__init__(exelist, version, 'csc') + + def get_buildtype_args(self, buildtype): + res = mono_buildtype_args[buildtype] + if not is_windows(): + tmp = [] + for flag in res: + if flag == '-debug': + flag = '-debug:portable' + tmp.append(flag) + res = tmp + return res diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index e553423c2..ccd85d53e 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -54,6 +54,7 @@ from .compilers import ( IntelFortranCompiler, JavaCompiler, MonoCompiler, + VisualStudioCsCompiler, NAGFortranCompiler, Open64FortranCompiler, PathScaleFortranCompiler, @@ -275,6 +276,10 @@ class Environment: else: self.default_c = ['cc', 'gcc', 'clang'] self.default_cpp = ['c++', 'g++', 'clang++'] + if mesonlib.is_windows(): + self.default_cs = ['csc', 'mcs'] + else: + self.default_cs = ['mcs', 'csc'] self.default_objc = ['cc'] self.default_objcpp = ['c++'] self.default_fortran = ['gfortran', 'g95', 'f95', 'f90', 'f77', 'ifort'] @@ -419,7 +424,7 @@ class Environment: def _get_compilers(self, lang, evar, want_cross): ''' The list of compilers is detected in the exact same way for - C, C++, ObjC, ObjC++, Fortran so consolidate it here. + C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here. ''' if self.is_cross_build() and want_cross: compilers = mesonlib.stringlistify(self.cross_info.config['binaries'][lang]) @@ -664,16 +669,24 @@ class Environment: raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') def detect_cs_compiler(self): - exelist = ['mcs'] - try: - p, out, err = Popen_safe(exelist + ['--version']) - except OSError: - raise EnvironmentException('Could not execute C# compiler "%s"' % ' '.join(exelist)) - version = search_version(out) - full_version = out.split('\n', 1)[0] - if 'Mono' in out: - return MonoCompiler(exelist, version, full_version=full_version) - raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + compilers, ccache, is_cross, exe_wrap = self._get_compilers('cs', 'CSC', False) + popen_exceptions = {} + for comp in compilers: + if not isinstance(comp, list): + comp = [comp] + try: + p, out, err = Popen_safe(comp + ['--version']) + except OSError as e: + popen_exceptions[' '.join(comp + ['--version'])] = e + continue + + version = search_version(out) + if 'Mono' in out: + return MonoCompiler(comp, version) + elif "Visual C#" in out: + return VisualStudioCsCompiler(comp, version) + + self._handle_exceptions(popen_exceptions, compilers) def detect_vala_compiler(self): if 'VALAC' in os.environ: diff --git a/run_project_tests.py b/run_project_tests.py index c2c3efe76..f9d4dbd04 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -465,6 +465,28 @@ def skippable(suite, test): # Other framework tests are allowed to be skipped on other platforms return True +def skip_csharp(backend): + if backend is not Backend.ninja: + return True + if not shutil.which('resgen'): + return True + if shutil.which('mcs'): + return False + if shutil.which('csc'): + # Only support VS2017 for now. Earlier versions fail + # under CI in mysterious ways. + try: + stdo = subprocess.check_output(['csc', '/version']) + except subprocess.CalledProcessError: + return True + # Having incrementing version numbers would be too easy. + # Microsoft reset the versioning back to 1.0 (from 4.x) + # when they got the Roslyn based compiler. Thus there + # is NO WAY to reliably do version number comparisons. + # Only support the version that ships with VS2017. + return not stdo.startswith(b'2.') + return True + def detect_tests_to_run(): # Name, subdirectory, skip condition. all_tests = [ @@ -478,7 +500,7 @@ def detect_tests_to_run(): ('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()), ('java', 'java', backend is not Backend.ninja or mesonlib.is_osx() or not have_java()), - ('C#', 'csharp', backend is not Backend.ninja or not shutil.which('mcs')), + ('C#', 'csharp', skip_csharp(backend)), ('vala', 'vala', backend is not Backend.ninja or not shutil.which('valac')), ('rust', 'rust', backend is not Backend.ninja or not shutil.which('rustc')), ('d', 'd', backend is not Backend.ninja or not have_d_compiler()), diff --git a/test cases/common/174 preserve gendir/base.inp b/test cases/common/178 preserve gendir/base.inp similarity index 100% rename from test cases/common/174 preserve gendir/base.inp rename to test cases/common/178 preserve gendir/base.inp diff --git a/test cases/common/174 preserve gendir/com/mesonbuild/subbie.inp b/test cases/common/178 preserve gendir/com/mesonbuild/subbie.inp similarity index 100% rename from test cases/common/174 preserve gendir/com/mesonbuild/subbie.inp rename to test cases/common/178 preserve gendir/com/mesonbuild/subbie.inp diff --git a/test cases/common/174 preserve gendir/genprog.py b/test cases/common/178 preserve gendir/genprog.py similarity index 100% rename from test cases/common/174 preserve gendir/genprog.py rename to test cases/common/178 preserve gendir/genprog.py diff --git a/test cases/common/174 preserve gendir/meson.build b/test cases/common/178 preserve gendir/meson.build similarity index 100% rename from test cases/common/174 preserve gendir/meson.build rename to test cases/common/178 preserve gendir/meson.build diff --git a/test cases/common/174 preserve gendir/testprog.c b/test cases/common/178 preserve gendir/testprog.c similarity index 100% rename from test cases/common/174 preserve gendir/testprog.c rename to test cases/common/178 preserve gendir/testprog.c diff --git a/test cases/csharp/1 basic/meson.build b/test cases/csharp/1 basic/meson.build index 2ee6a4a93..09e46c293 100644 --- a/test cases/csharp/1 basic/meson.build +++ b/test cases/csharp/1 basic/meson.build @@ -1,4 +1,4 @@ project('simple c#', 'cs') -e = executable('prog', 'prog.cs', install : true) +e = executable('prog', 'prog.cs', 'text.cs', install : true) test('basic', e) diff --git a/test cases/csharp/1 basic/prog.cs b/test cases/csharp/1 basic/prog.cs index dfb24002b..6ee47b074 100644 --- a/test cases/csharp/1 basic/prog.cs +++ b/test cases/csharp/1 basic/prog.cs @@ -1,7 +1,8 @@ using System; - + public class Prog { static public void Main () { - Console.WriteLine("C# is working."); + TextGetter tg = new TextGetter(); + Console.WriteLine(tg.getText()); } } diff --git a/test cases/csharp/1 basic/text.cs b/test cases/csharp/1 basic/text.cs new file mode 100644 index 000000000..c83c424c8 --- /dev/null +++ b/test cases/csharp/1 basic/text.cs @@ -0,0 +1,7 @@ +using System; + +public class TextGetter { + public String getText() { + return "C# is working."; + } +} diff --git a/test cases/csharp/4 external dep/meson.build b/test cases/csharp/4 external dep/meson.build index 004d25ffc..019d618b7 100644 --- a/test cases/csharp/4 external dep/meson.build +++ b/test cases/csharp/4 external dep/meson.build @@ -1,4 +1,9 @@ project('C# external library', 'cs') -glib_sharp_2 = dependency('glib-sharp-2.0') +glib_sharp_2 = dependency('glib-sharp-2.0', required : false) + +if not glib_sharp_2.found() + error('MESON_SKIP_TEST glib# not found.') +endif + e = executable('prog', 'prog.cs', dependencies: glib_sharp_2, install : true) test('libtest', e, args: [join_paths(meson.current_source_dir(), 'hello.txt')]) diff --git a/test cases/csharp/4 pkgconfig/meson.build b/test cases/csharp/4 pkgconfig/meson.build deleted file mode 100644 index e2ba03578..000000000 --- a/test cases/csharp/4 pkgconfig/meson.build +++ /dev/null @@ -1,7 +0,0 @@ -project('C# pkg-config', 'cs') - -nunit_dep = dependency('nunit') -nunit_runner = find_program('nunit-console') - -test_lib = library('test_lib', 'test-lib.cs', dependencies: nunit_dep) -test('nunit test', nunit_runner, args: test_lib) diff --git a/test cases/csharp/4 pkgconfig/test-lib.cs b/test cases/csharp/4 pkgconfig/test-lib.cs deleted file mode 100644 index 29f679505..000000000 --- a/test cases/csharp/4 pkgconfig/test-lib.cs +++ /dev/null @@ -1,11 +0,0 @@ -using NUnit.Framework; - -[TestFixture] -public class NUnitTest -{ - [Test] - public void Test() - { - Assert.AreEqual(1 + 1, 2); - } -}