diff options
-rw-r--r-- | .travis.yml | 12 | ||||
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | cross/ownstdlib.txt | 13 | ||||
-rw-r--r-- | manual tests/9 nostdlib/meson.build | 10 | ||||
-rw-r--r-- | manual tests/9 nostdlib/prog.c | 7 | ||||
-rw-r--r-- | manual tests/9 nostdlib/subprojects/mylibc/libc.c | 35 | ||||
-rw-r--r-- | manual tests/9 nostdlib/subprojects/mylibc/meson.build | 11 | ||||
-rw-r--r-- | manual tests/9 nostdlib/subprojects/mylibc/stdio.h | 5 | ||||
-rw-r--r-- | manual tests/9 nostdlib/subprojects/mylibc/stubstart.s | 8 | ||||
-rw-r--r-- | mesonbuild/backend/backends.py | 1 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 15 | ||||
-rw-r--r-- | mesonbuild/build.py | 1 | ||||
-rw-r--r-- | mesonbuild/compilers.py | 133 | ||||
-rw-r--r-- | mesonbuild/environment.py | 6 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 37 | ||||
-rwxr-xr-x | run_tests.py | 8 | ||||
-rw-r--r-- | test cases/common/43 has function/meson.build | 8 |
17 files changed, 218 insertions, 97 deletions
diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..01d8eb5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +sudo: required + +language: c + +services: + - docker + +before_install: + - docker pull jpakkane/mesonci:xenial + +script: + - docker run jpakkane/mesonci:xenial /bin/sh -c "cd /root && git clone https://github.com/mesonbuild/meson.git && cd meson && ./run_tests.py" @@ -4,6 +4,11 @@ MesonĀ® is a project to create the best possible next-generation build system. +####Build status + +<a href="https://travis-ci.org/mesonbuild/meson"><img +src="https://travis-ci.org/mesonbuild/meson.svg?branch=master"></a> + ####Dependencies - [Python](http://python.org) (version 3.4 or newer) diff --git a/cross/ownstdlib.txt b/cross/ownstdlib.txt new file mode 100644 index 0000000..46e99f7 --- /dev/null +++ b/cross/ownstdlib.txt @@ -0,0 +1,13 @@ +# This is a setup for compiling a program that runs natively +# but uses a custom std lib. This test will only work on +# x86_64. + +[target_machine] +system = 'linux' +cpu_family = 'x86_64' +cpu = 'x86_64' +endian = 'little' + +[properties] + +c_stdlib = ['mylibc', 'mylibc_dep'] # Subproject name, dependency name diff --git a/manual tests/9 nostdlib/meson.build b/manual tests/9 nostdlib/meson.build new file mode 100644 index 0000000..3ef743e --- /dev/null +++ b/manual tests/9 nostdlib/meson.build @@ -0,0 +1,10 @@ +project('own libc', 'c') + +# A simple project that uses its own libc. + +# Note that we don't need to specify anything, the flags to use +# stdlib come from the cross file. + +exe = executable('selfcontained', 'prog.c') + +test('standalone test', exe) diff --git a/manual tests/9 nostdlib/prog.c b/manual tests/9 nostdlib/prog.c new file mode 100644 index 0000000..9414bce --- /dev/null +++ b/manual tests/9 nostdlib/prog.c @@ -0,0 +1,7 @@ + +#include<stdio.h> + +int main() { + const char *message = "Hello without stdlib.\n"; + return simple_print(message, simple_strlen(message)); +} diff --git a/manual tests/9 nostdlib/subprojects/mylibc/libc.c b/manual tests/9 nostdlib/subprojects/mylibc/libc.c new file mode 100644 index 0000000..67261cb --- /dev/null +++ b/manual tests/9 nostdlib/subprojects/mylibc/libc.c @@ -0,0 +1,35 @@ +/* Do not use this as the basis of your own libc. + * The code is probably unoptimal or wonky, as I + * had no prior experience with this, but instead + * just fiddled with the code until it worked. + */ + +#include<stdio.h> + +#define STDOUT 1 +#define SYS_WRITE 4 + +int simple_print(const char *msg, const long bufsize) { + int count; + long total_written = 0; + while(total_written < bufsize) { + asm( + "int $0x80\n\t" + : "=a"(count) + : "0"(SYS_WRITE), "b"(STDOUT), "c"(msg+total_written), "d"(bufsize-total_written) + :); + if(count == 0) { + return 1; + } + total_written += count; + } + return 0; +} + +int simple_strlen(const char *str) { + int len = 0; + while(str[len] != '\0') { + len++; + } + return len; +} diff --git a/manual tests/9 nostdlib/subprojects/mylibc/meson.build b/manual tests/9 nostdlib/subprojects/mylibc/meson.build new file mode 100644 index 0000000..aa0184e --- /dev/null +++ b/manual tests/9 nostdlib/subprojects/mylibc/meson.build @@ -0,0 +1,11 @@ +project('own libc', 'c') + +# A very simple libc implementation + +# Do not specify -nostdlib & co. They come from cross specifications. + +libc = static_library('c', 'libc.c', 'stubstart.s') + +mylibc_dep = declare_dependency(link_with : libc, + include_directories : include_directories('.') +) diff --git a/manual tests/9 nostdlib/subprojects/mylibc/stdio.h b/manual tests/9 nostdlib/subprojects/mylibc/stdio.h new file mode 100644 index 0000000..c3f8f56 --- /dev/null +++ b/manual tests/9 nostdlib/subprojects/mylibc/stdio.h @@ -0,0 +1,5 @@ +#pragma once + +int simple_print(const char *msg, const long bufsize); + +int simple_strlen(const char *str); diff --git a/manual tests/9 nostdlib/subprojects/mylibc/stubstart.s b/manual tests/9 nostdlib/subprojects/mylibc/stubstart.s new file mode 100644 index 0000000..0a6d972 --- /dev/null +++ b/manual tests/9 nostdlib/subprojects/mylibc/stubstart.s @@ -0,0 +1,8 @@ +.globl _start + +_start: + + call main + movl %eax, %ebx + movl $1, %eax + int $0x80 diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 7c6caa6..d4a0f99 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -286,6 +286,7 @@ class Backend(): def generate_basic_compiler_args(self, target, compiler): commands = [] + commands += self.get_cross_stdlib_args(target, compiler) commands += compiler.get_always_args() commands += compiler.get_warn_args(self.environment.coredata.get_builtin_option('warning_level')) commands += compiler.get_option_compile_args(self.environment.coredata.compiler_options) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index bd6f4db..4b85565 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1394,6 +1394,13 @@ rule FORTRAN_DEP_HACK mod_files.append(os.path.join(dirname, mod_name)) return mod_files + def get_cross_stdlib_args(self, target, compiler): + if not target.is_cross: + return [] + if self.environment.cross_info.has_stdlib(compiler.language): + return [] + return compiler.get_no_stdinc_args() + def generate_single_compile(self, target, outfile, src, is_generated=False, header_deps=[], order_deps=[]): if(isinstance(src, str) and src.endswith('.h')): raise RuntimeError('Fug') @@ -1599,6 +1606,13 @@ rule FORTRAN_DEP_HACK elem.add_item('CROSS', '--cross-host=' + self.environment.cross_info.config['host_machine']['system']) elem.write(outfile) + def get_cross_stdlib_link_args(self, target, linker): + if isinstance(target, build.StaticLibrary) or not target.is_cross: + return [] + if not self.environment.cross_info.has_stdlib(linker.language): + return [] + return linker.get_no_stdlib_link_args() + def generate_link(self, target, outfile, outname, obj_list, linker, extra_args=[]): if isinstance(target, build.StaticLibrary): linker_base = 'STATIC' @@ -1612,6 +1626,7 @@ rule FORTRAN_DEP_HACK linker_rule = linker_base + crstr + '_LINKER' abspath = os.path.join(self.environment.get_build_dir(), target.subdir) commands = [] + commands += self.get_cross_stdlib_link_args(target, linker) commands += linker.get_linker_always_args() if not isinstance(target, build.StaticLibrary): commands += compilers.get_base_link_args(self.environment.coredata.base_options, diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 1e9a1bb..9de556c 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -101,6 +101,7 @@ class Build: self.install_dirs = [] self.dep_manifest_name = None self.dep_manifest = {} + self.cross_stdlibs = {} def has_language(self, language): for i in self.compilers: diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 25eefc6..1fc936a 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -311,6 +311,12 @@ class CCompiler(Compiler): def get_always_args(self): return [] + def get_no_stdinc_args(self): + return ['-nostdinc'] + + def get_no_stdlib_link_args(self): + return ['-nostdlib'] + def get_warn_args(self, level): return self.warn_args[level] @@ -414,30 +420,32 @@ class CCompiler(Compiler): def get_linker_search_args(self, dirname): return ['-L'+dirname] - def sanity_check(self, work_dir): - mlog.debug('Sanity testing C compiler:', ' '.join(self.exelist)) + def sanity_check_impl(self, work_dir, sname, code): + mlog.debug('Sanity testing ' + self.language + ' compiler:', ' '.join(self.exelist)) mlog.debug('Is cross compiler: %s.' % str(self.is_cross)) - source_name = os.path.join(work_dir, 'sanitycheckc.c') + extra_flags = [] + source_name = os.path.join(work_dir, sname) + binname = sname.rsplit('.', 1)[0] if self.is_cross: - binname = 'sanitycheckc_cross' - else: - binname = 'sanitycheckc' + binname += '_cross' + if self.exe_wrapper is None: + # Linking cross built apps is painful. You can't really + # tell if you should use -nostdlib or not and for example + # on OSX the compiler binary is the same but you need + # a ton of compiler flags to differentiate between + # arm and x86_64. So just compile. + extra_flags = self.get_compile_only_args() + # Is a valid executable output for all toolchains and platforms + binname += '.exe' + # Write binary check source binary_name = os.path.join(work_dir, binname) ofile = open(source_name, 'w') - ofile.write('int main(int argc, char **argv) { int class=0; return class; }\n') + ofile.write(code) ofile.close() - if self.is_cross and self.exe_wrapper is None: - # Linking cross built apps is painful. You can't really - # tell if you should use -nostdlib or not and for example - # on OSX the compiler binary is the same but you need - # a ton of compiler flags to differentiate between - # arm and x86_64. So just compile. - extra_flags = ['-c'] - else: - extra_flags = [] - cmdlist = self.exelist + extra_flags + [source_name, '-o', binary_name] - pc = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # Compile sanity check + cmdlist = self.exelist + extra_flags + [source_name] + self.get_output_args(binary_name) + pc = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=work_dir) (stdo, stde) = pc.communicate() stdo = stdo.decode() stde = stde.decode() @@ -448,7 +456,8 @@ class CCompiler(Compiler): mlog.debug(stde) mlog.debug('-----') if pc.returncode != 0: - raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) + raise EnvironmentException('Compiler {0} can not compile programs.'.format(self.name_string())) + # Run sanity check if self.is_cross: if self.exe_wrapper is None: # Can't check if the binaries run so we have to assume they do @@ -460,7 +469,11 @@ class CCompiler(Compiler): pe = subprocess.Popen(cmdlist) pe.wait() if pe.returncode != 0: - raise EnvironmentException('Executables created by C compiler %s are not runnable.' % self.name_string()) + raise EnvironmentException('Executables created by {0} compiler {1} are not runnable.'.format(self.language, self.name_string())) + + def sanity_check(self, work_dir): + code = 'int main(int argc, char **argv) { int class=0; return class; }\n' + return self.sanity_check_impl(work_dir, 'sanitycheckc.c', code) def has_header(self, hname, extra_args=[]): templ = '''#include<%s> @@ -692,6 +705,7 @@ int main(int argc, char **argv) { # Then, undef the symbol to get rid of it completely. templ = ''' #define {1} meson_disable_define_of_{1} + #include <limits.h> {0} #undef {1} ''' @@ -709,6 +723,8 @@ int main(int argc, char **argv) { # glibc defines functions that are not available on Linux as stubs that # fail with ENOSYS (such as e.g. lchmod). In this case we want to fail # instead of detecting the stub as a valid symbol. + # We always include limits.h above to ensure that these are defined for + # stub functions. stubs_fail = ''' #if defined __stub_{1} || defined __stub___{1} fail fail fail this function is not going to work @@ -739,7 +755,8 @@ int main(int argc, char **argv) { # redefines the symbol to be something else. In that case, we want to # still detect the function. We still want to fail if __stub_foo or # _stub_foo are defined, of course. - if self.links('{0}\n' + stubs_fail + '\nint main() {{ {1}; }}'.format(prefix, funcname), extra_args): + header_templ = '#include <limits.h>\n{0}\n' + stubs_fail + '\nint main() {{ {1}; }}' + if self.links(header_templ.format(prefix, funcname), extra_args): return True # Some functions like alloca() are defined as compiler built-ins which # are inlined by the compiler, so test for that instead. Built-ins are @@ -808,42 +825,8 @@ class CPPCompiler(CCompiler): return False def sanity_check(self, work_dir): - source_name = os.path.join(work_dir, 'sanitycheckcpp.cc') - binary_name = os.path.join(work_dir, 'sanitycheckcpp') - ofile = open(source_name, 'w') - ofile.write('class breakCCompiler;int main(int argc, char **argv) { return 0; }\n') - ofile.close() - if self.is_cross and self.exe_wrapper is None: - # Skipping link because of the same reason as for C. - # The comment in CCompiler explains why this is done. - extra_flags = ['-c'] - else: - extra_flags = [] - cmdlist = self.exelist + extra_flags + [source_name, '-o', binary_name] - pc = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdo, stde) = pc.communicate() - stdo = stdo.decode() - stde = stde.decode() - mlog.debug('Sanity check compiler command line:', ' '.join(cmdlist)) - mlog.debug('Sanity check compile stdout:') - mlog.debug(stdo) - mlog.debug('-----\nSanity check compile stderr:') - mlog.debug(stde) - mlog.debug('-----') - pc.wait() - if pc.returncode != 0: - raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) - if self.is_cross: - if self.exe_wrapper is None: - # Can't check if the binaries run so we have to assume they do - return - cmdlist = self.exe_wrapper + [binary_name] - else: - cmdlist = [binary_name] - pe = subprocess.Popen(cmdlist) - pe.wait() - if pe.returncode != 0: - raise EnvironmentException('Executables created by C++ compiler %s are not runnable.' % self.name_string()) + code = 'class breakCCompiler;int main(int argc, char **argv) { return 0; }\n' + return self.sanity_check_impl(work_dir, 'sanitycheckcpp.cc', code) class ObjCCompiler(CCompiler): def __init__(self, exelist, version, is_cross, exe_wrap): @@ -1386,24 +1369,6 @@ class VisualStudioCCompiler(CCompiler): objname = os.path.splitext(pchname)[0] + '.obj' return (objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname ]) - def sanity_check(self, work_dir): - source_name = 'sanitycheckc.c' - binary_name = 'sanitycheckc' - ofile = open(os.path.join(work_dir, source_name), 'w') - ofile.write('int main(int argc, char **argv) { return 0; }\n') - ofile.close() - pc = subprocess.Popen(self.exelist + [source_name, '/Fe' + binary_name], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - cwd=work_dir) - pc.wait() - if pc.returncode != 0: - raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) - pe = subprocess.Popen(os.path.join(work_dir, binary_name)) - pe.wait() - if pe.returncode != 0: - raise EnvironmentException('Executables created by C++ compiler %s are not runnable.' % self.name_string()) - def build_rpath_args(self, build_dir, rpath_paths, install_rpath): return [] @@ -1468,24 +1433,6 @@ class VisualStudioCPPCompiler(VisualStudioCCompiler): return True return False - def sanity_check(self, work_dir): - source_name = 'sanitycheckcpp.cpp' - binary_name = 'sanitycheckcpp' - ofile = open(os.path.join(work_dir, source_name), 'w') - ofile.write('class BreakPlainC;int main(int argc, char **argv) { return 0; }\n') - ofile.close() - pc = subprocess.Popen(self.exelist + [source_name, '/Fe' + binary_name], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - cwd=work_dir) - pc.wait() - if pc.returncode != 0: - raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) - pe = subprocess.Popen(os.path.join(work_dir, binary_name)) - pe.wait() - if pe.returncode != 0: - raise EnvironmentException('Executables created by C++ compiler %s are not runnable.' % self.name_string()) - def get_options(self): return {'cpp_eh' : coredata.UserComboOption('cpp_eh', 'C++ exception handling type.', diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 41e8531..5096320 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -759,6 +759,12 @@ class CrossBuildInfo(): def has_target(self): return 'target_machine' in self.config + def has_stdlib(self, language): + return language + '_stdlib' in self.config['properties'] + + def get_stdlib(self, language): + return self.config['properties'][language + '_stdlib'] + # Wehn compiling a cross compiler we use the native compiler for everything. # But not when cross compiling a cross compiler. def need_cross_compiler(self): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index adcdd7a..bf11439 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -876,7 +876,7 @@ class MesonMain(InterpreterObject): if self.is_cross_build_method(None, None) and \ 'binaries' in self.build.environment.cross_info.config and \ self.build.environment.cross_info.need_exe_wrapper(): - exe_wrap = self.build.environment.cross_info.config['binaries'].get('exe_wrap', None) + exe_wrap = self.build.environment.cross_info.config['binaries'].get('exe_wrapper', None) if exe_wrap is None: return False # We return True when exe_wrap is defined, when it's not needed, and @@ -1077,6 +1077,22 @@ class Interpreter(): if not isinstance(first, mparser.FunctionNode) or first.func_name != 'project': raise InvalidCode('First statement must be a call to project') + def check_cross_stdlibs(self): + if self.build.environment.is_cross_build(): + cross_info = self.build.environment.cross_info + for c in self.build.cross_compilers: + l = c.language + try: + di = mesonlib.stringlistify(cross_info.get_stdlib(l)) + if len(di) != 2: + raise InterpreterException('Stdlib definition for %s should have exactly two elements.' \ + % l) + projname, depname = di + subproj = self.do_subproject(projname, {}) + self.build.cross_stdlibs[l] = subproj.get_variable_method([depname], {}) + except KeyError as e: + pass + def run(self): self.evaluate_codeblock(self.ast) mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets)))) @@ -1411,6 +1427,8 @@ class Interpreter(): if 'vala' in langs: if not 'c' in langs: raise InterpreterException('Compiling Vala requires C. Add C to your project languages and rerun Meson.') + if not self.is_subproject(): + self.check_cross_stdlibs() @stringArgs def func_add_languages(self, node, args, kwargs): @@ -1982,11 +2000,28 @@ class Interpreter(): mlog.debug('Unknown target type:', str(targetholder)) raise RuntimeError('Unreachable code') target = targetclass(name, self.subdir, self.subproject, is_cross, sources, objs, self.environment, kwargs) + if is_cross: + self.add_cross_stdlib_info(target) l = targetholder(target, self) self.add_target(name, l.held_object) self.global_args_frozen = True return l + def get_used_languages(self, target): + result = {} + for i in target.sources: + for c in self.build.compilers: + if c.can_compile(i): + result[c.language] = True + break + return result + + def add_cross_stdlib_info(self, target): + for l in self.get_used_languages(target): + if self.environment.cross_info.has_stdlib(l) and \ + self.subproject != self.environment.cross_info.get_stdlib(l)[0]: + target.add_external_deps(self.build.cross_stdlibs[l]) + def check_sources_exist(self, subdir, sources): for s in sources: if not isinstance(s, str): diff --git a/run_tests.py b/run_tests.py index ad2450e..1317380 100755 --- a/run_tests.py +++ b/run_tests.py @@ -157,7 +157,7 @@ def validate_install(srcdir, installdir): return '' def log_text_file(logfile, testdir, stdo, stde): - global stop + global stop, executor, futures logfile.write('%s\nstdout\n\n---\n' % testdir) logfile.write(stdo) logfile.write('\n\n---\n\nstderr\n\n---\n') @@ -167,6 +167,10 @@ def log_text_file(logfile, testdir, stdo, stde): print(stdo) print(stde, file=sys.stderr) if stop: + print("Aborting..") + for f in futures: + f[2].cancel() + executor.shutdown() raise StopException() def run_configure_inprocess(commandlist): @@ -300,7 +304,7 @@ def detect_tests_to_run(): return all_tests def run_tests(extra_args): - global passing_tests, failing_tests, stop + global passing_tests, failing_tests, stop, executor, futures all_tests = detect_tests_to_run() logfile = open('meson-test-run.txt', 'w', encoding="utf_8") junit_root = ET.Element('testsuites') diff --git a/test cases/common/43 has function/meson.build b/test cases/common/43 has function/meson.build index c7fe353..00ca640 100644 --- a/test cases/common/43 has function/meson.build +++ b/test cases/common/43 has function/meson.build @@ -9,7 +9,13 @@ endif # Should also be able to detect it without specifying the header # We check for a different function here to make sure the result is # not taken from a cache (ie. the check above) -assert(cc.has_function('fprintf'), '"fprintf" function not found without include (should always exist).') +# On MSVC fprintf is defined as an inline function in the header, so it cannot +# be found without the include. +if cc.get_id() != 'msvc' + assert(cc.has_function('fprintf'), '"fprintf" function not found without include (on !msvc).') +else + assert(cc.has_function('fprintf', prefix : '#include <stdio.h>'), '"fprintf" function not found with include (on msvc).') +endif if cc.has_function('hfkerhisadf', prefix : '#include<stdio.h>') error('Found non-existent function "hfkerhisadf".') |