diff options
-rw-r--r-- | mesonbuild/backend/backends.py | 52 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 47 | ||||
-rw-r--r-- | mesonbuild/backend/vs2010backend.py | 32 | ||||
-rw-r--r-- | mesonbuild/backend/xcodebackend.py | 7 | ||||
-rw-r--r-- | mesonbuild/build.py | 11 | ||||
-rw-r--r-- | mesonbuild/compilers.py | 46 | ||||
-rw-r--r-- | mesonbuild/environment.py | 41 | ||||
-rw-r--r-- | mesonbuild/mesonlib.py | 4 | ||||
-rw-r--r-- | mesonbuild/mesonmain.py | 5 | ||||
-rw-r--r-- | mesonbuild/scripts/delwithsuffix.py | 2 | ||||
-rw-r--r-- | mesonbuild/scripts/meson_exe.py | 74 |
11 files changed, 251 insertions, 70 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index b7a2c30..7c6caa6 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -34,6 +34,18 @@ class InstallData(): self.install_scripts = [] self.install_subdirs = [] +class ExecutableSerialisation(): + def __init__(self, name, fname, cmd_args, env, is_cross, exe_wrapper, + workdir, extra_paths): + self.name = name + self.fname = fname + self.cmd_args = cmd_args + self.env = env + self.is_cross = is_cross + self.exe_runner = exe_wrapper + self.workdir = workdir + self.extra_paths = extra_paths + class TestSerialisation: def __init__(self, name, suite, fname, is_cross, exe_wrapper, is_parallel, cmd_args, env, should_fail, valgrind_args, timeout, workdir, extra_paths): @@ -154,6 +166,35 @@ class Backend(): raise MesonException('Unknown data type in object list.') return obj_list + def serialise_executable(self, exe, cmd_args, workdir, env={}): + import uuid + # Can't just use exe.name here; it will likely be run more than once + scratch_file = 'meson_exe_{0}_{1}.dat'.format(exe.name, + str(uuid.uuid4())[:8]) + exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file) + with open(exe_data, 'wb') as f: + if isinstance(exe, dependencies.ExternalProgram): + exe_fullpath = exe.fullpath + else: + exe_fullpath = [os.path.join(self.environment.get_build_dir(), + self.get_target_filename(exe))] + is_cross = self.environment.is_cross_build() and \ + self.environment.cross_info.need_cross_compiler() and \ + self.environment.cross_info.need_exe_wrapper() + if is_cross: + exe_wrapper = self.environment.cross_info.config['binaries'].get('exe_wrapper', None) + else: + exe_wrapper = None + if mesonlib.is_windows(): + extra_paths = self.determine_windows_extra_paths(exe) + else: + extra_paths = [] + es = ExecutableSerialisation(exe.name, exe_fullpath, cmd_args, env, + is_cross, exe_wrapper, workdir, + extra_paths) + pickle.dump(es, f) + return exe_data + def serialise_tests(self): test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') datafile = open(test_data, 'wb') @@ -163,6 +204,7 @@ class Backend(): datafile = open(benchmark_data, 'wb') self.write_benchmark_file(datafile) datafile.close() + return (test_data, benchmark_data) def has_source_suffix(self, target, suffix): for s in target.get_sources(): @@ -390,6 +432,16 @@ class Backend(): final_args.append(a) return final_args + def get_custom_target_provided_libraries(self, target): + libs = [] + for t in target.get_generated_sources(): + if not isinstance(t, build.CustomTarget): + continue + for f in t.output: + if self.environment.is_library(f): + libs.append(os.path.join(self.get_target_dir(t), f)) + return libs + def eval_custom_target_command(self, target, absolute_paths=False): if not absolute_paths: ofilenames = [os.path.join(self.get_target_dir(target), i) for i in target.output] diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 4415a68..ce8968c 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -341,6 +341,7 @@ int dummy; def generate_custom_target(self, target, outfile): (srcs, ofilenames, cmd) = self.eval_custom_target_command(target) deps = [] + desc = 'Generating {0} with a {1} command.' for i in target.get_dependencies(): # FIXME, should not grab element at zero but rather expand all. if isinstance(i, list): @@ -364,9 +365,23 @@ int dummy; tmp = [tmp] for fname in tmp: elem.add_dep(os.path.join(self.get_target_dir(d), fname)) + # Windows doesn't have -rpath, so for EXEs that need DLLs built within + # the project, we need to set PATH so the DLLs are found. We use + # a serialized executable wrapper for that and check if the + # CustomTarget command needs extra paths first. + if mesonlib.is_windows() and \ + self.determine_windows_extra_paths(target.command[0]): + exe_data = self.serialise_executable(target.command[0], cmd[1:], + # All targets are built from the build dir + self.environment.get_build_dir()) + cmd = [sys.executable, self.environment.get_build_command(), + '--internal', 'exe', exe_data] + cmd_type = 'meson_exe.py custom' + else: + cmd_type = 'custom' elem.add_item('COMMAND', cmd) - elem.add_item('description', 'Generating %s with a custom command.' % target.name) + elem.add_item('description', desc.format(target.name, cmd_type)) elem.write(outfile) self.processed_targets[target.name + target.type_suffix()] = True @@ -433,27 +448,27 @@ int dummy; if gcovr_exe: added_rule = True elem = NinjaBuildElement(self.all_outputs, 'coverage-xml', 'CUSTOM_COMMAND', '') - elem.add_item('COMMAND', [gcovr_exe, '-x', '-r', self.environment.get_build_dir(),\ + elem.add_item('COMMAND', [gcovr_exe, '-x', '-r', self.environment.get_source_dir(),\ '-o', os.path.join(self.environment.get_log_dir(), 'coverage.xml')]) elem.add_item('DESC', 'Generating XML coverage report.') elem.write(outfile) elem = NinjaBuildElement(self.all_outputs, 'coverage-text', 'CUSTOM_COMMAND', '') - elem.add_item('COMMAND', [gcovr_exe, '-r', self.environment.get_build_dir(),\ + elem.add_item('COMMAND', [gcovr_exe, '-r', self.environment.get_source_dir(),\ '-o', os.path.join(self.environment.get_log_dir(), 'coverage.txt')]) elem.add_item('DESC', 'Generating text coverage report.') elem.write(outfile) if lcov_exe and genhtml_exe: added_rule = True - phony_elem = NinjaBuildElement(self.all_outputs, 'coverage-html', 'phony', 'coveragereport/index.html') - phony_elem.write(outfile) - - elem = NinjaBuildElement(self.all_outputs, 'coveragereport/index.html', 'CUSTOM_COMMAND', '') htmloutdir = os.path.join(self.environment.get_log_dir(), 'coveragereport') + covinfo = os.path.join(self.environment.get_log_dir(), 'coverage.info') + phony_elem = NinjaBuildElement(self.all_outputs, 'coverage-html', 'phony', os.path.join(htmloutdir, 'index.html')) + phony_elem.write(outfile) + elem = NinjaBuildElement(self.all_outputs, os.path.join(htmloutdir, 'index.html'), 'CUSTOM_COMMAND', '') command = [lcov_exe, '--directory', self.environment.get_build_dir(),\ - '--capture', '--output-file', 'coverage.info', '--no-checksum',\ + '--capture', '--output-file', covinfo, '--no-checksum',\ '&&', genhtml_exe, '--prefix', self.environment.get_build_dir(),\ '--output-directory', htmloutdir, '--title', 'Code coverage',\ - '--legend', '--show-details', 'coverage.info'] + '--legend', '--show-details', covinfo] elem.add_item('COMMAND', command) elem.add_item('DESC', 'Generating HTML coverage report.') elem.write(outfile) @@ -584,10 +599,9 @@ int dummy; elem.write(outfile) def generate_tests(self, outfile): - self.serialise_tests() + (test_data, benchmark_data) = self.serialise_tests() valgrind = environment.find_valgrind() script_root = self.environment.get_script_dir() - test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') cmd = [ sys.executable, self.environment.get_build_command(), '--internal', 'test' ] if not self.environment.coredata.get_builtin_option('stdsplit'): cmd += ['--no-stdsplit'] @@ -610,7 +624,6 @@ int dummy; # And then benchmarks. benchmark_script = os.path.join(script_root, 'meson_benchmark.py') - benchmark_data = os.path.join(self.environment.get_scratch_dir(), 'meson_benchmark_setup.dat') cmd = [sys.executable, self.environment.get_build_command(), '--internal', 'benchmark', benchmark_data] elem = NinjaBuildElement(self.all_outputs, 'benchmark', 'CUSTOM_COMMAND', ['all', 'PHONY']) elem.add_item('COMMAND', cmd) @@ -1706,16 +1719,6 @@ rule FORTRAN_DEP_HACK elem.add_item('LINK_ARGS', commands) return elem - def get_custom_target_provided_libraries(self, target): - libs = [] - for t in target.get_generated_sources(): - if not isinstance(t, build.CustomTarget): - continue - for f in t.output: - if self.environment.is_library(f): - libs.append(os.path.join(self.get_target_dir(t), f)) - return libs - def determine_rpath_dirs(self, target): link_deps = target.get_all_link_deps() result = [] diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 725b8ed..82d0dc9 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -69,13 +69,18 @@ class Vs2010Backend(backends.Backend): if len(src_conflicts) > 1} def generate_custom_generator_commands(self, target, parent_node): - all_output_files = [] + generator_output_files = [] commands = [] inputs = [] outputs = [] + custom_target_include_dirs = [] + custom_target_output_files = [] for genlist in target.get_generated_sources(): if isinstance(genlist, build.CustomTarget): - all_output_files += [os.path.join(self.get_target_dir(genlist), i) for i in genlist.output] + custom_target_output_files += [os.path.join(self.get_target_dir(genlist), i) for i in genlist.output] + idir = self.relpath(self.get_target_dir(genlist), self.get_target_dir(target)) + if idir not in custom_target_include_dirs: + custom_target_include_dirs.append(idir) else: generator = genlist.get_generator() exe = generator.get_exe() @@ -93,7 +98,7 @@ class Vs2010Backend(backends.Backend): infilename = os.path.join(self.environment.get_source_dir(), curfile) outfiles = genlist.get_outputs_for(curfile) outfiles = [os.path.join(target_private_dir, of) for of in outfiles] - all_output_files += outfiles + generator_output_files += outfiles args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output)\ for x in base_args] args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir()).replace("@BUILD_DIR@", target_private_dir) @@ -111,7 +116,7 @@ class Vs2010Backend(backends.Backend): ET.SubElement(cbs, 'Message').text = 'Generating custom sources.' pg = ET.SubElement(parent_node, 'PropertyGroup') ET.SubElement(pg, 'CustomBuildBeforeTargets').text = 'ClCompile' - return all_output_files + return generator_output_files, custom_target_output_files, custom_target_include_dirs def generate(self, interp): self.resolve_source_conflicts() @@ -260,6 +265,8 @@ class Vs2010Backend(backends.Backend): lang = self.lang_from_source_file(i) if lang not in languages: languages.append(lang) + elif self.environment.is_library(i): + pass else: # Everything that is not an object or source file is considered a header. headers.append(i) @@ -436,8 +443,12 @@ class Vs2010Backend(backends.Backend): ET.SubElement(type_config, 'WholeProgramOptimization').text = 'false' ET.SubElement(type_config, 'UseDebugLibraries').text = 'true' ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.props') - generated_files = self.generate_custom_generator_commands(target, root) + generated_files, custom_target_output_files, generated_files_include_dirs = self.generate_custom_generator_commands(target, root) (gen_src, gen_hdrs, gen_objs, gen_langs) = self.split_sources(generated_files) + (custom_src, custom_hdrs, custom_objs, custom_langs) = self.split_sources(custom_target_output_files) + gen_src += custom_src + gen_hdrs += custom_hdrs + gen_langs += custom_langs direlem = ET.SubElement(root, 'PropertyGroup') fver = ET.SubElement(direlem, '_ProjectFileVersion') fver.text = self.project_file_version @@ -455,7 +466,7 @@ class Vs2010Backend(backends.Backend): opt = ET.SubElement(clconf, 'Optimization') opt.text = 'disabled' inc_dirs = ['.', self.relpath(self.get_target_private_dir(target), self.get_target_dir(target)), - proj_to_src_dir] + proj_to_src_dir] + generated_files_include_dirs extra_args = {'c': [], 'cpp': []} for l, args in self.environment.coredata.external_args.items(): @@ -561,10 +572,14 @@ class Vs2010Backend(backends.Backend): rel_path = self.relpath(lobj.subdir, target.subdir) linkname = os.path.join(rel_path, lobj.get_import_filename()) additional_links.append(linkname) + for lib in self.get_custom_target_provided_libraries(target): + additional_links.append(self.relpath(lib, self.get_target_dir(target))) additional_objects = [] for o in self.flatten_object_list(target, down): assert(isinstance(o, str)) additional_objects.append(o) + for o in custom_objs: + additional_objects.append(self.relpath(o, self.get_target_dir(target))) if len(additional_links) > 0: additional_links.append('%(AdditionalDependencies)') ET.SubElement(link, 'AdditionalDependencies').text = ';'.join(additional_links) @@ -773,7 +788,6 @@ if %%errorlevel%% neq 0 goto :VCEnd''' ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c' postbuild = ET.SubElement(action, 'PostBuildEvent') ET.SubElement(postbuild, 'Message') - test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') test_command = [sys.executable, self.environment.get_build_command(), '--internal', @@ -787,14 +801,12 @@ endlocal & call :cmErrorLevel %%errorlevel%% & goto :cmDone exit /b %%1 :cmDone if %%errorlevel%% neq 0 goto :VCEnd''' + test_data = self.serialise_tests()[0] ET.SubElement(postbuild, 'Command').text =\ cmd_templ % ('" "'.join(test_command), test_data) ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') tree = ET.ElementTree(root) tree.write(ofname, encoding='utf-8', xml_declaration=True) - datafile = open(test_data, 'wb') - self.serialise_tests() - datafile.close() # ElementTree can not do prettyprinting so do it manually #doc = xml.dom.minidom.parse(ofname) #open(ofname, 'w').write(doc.toprettyxml()) diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index 6bda826..0ce90ce 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -64,7 +64,7 @@ class XCodeBackend(backends.Backend): def generate(self, interp): self.interpreter = interp - self.serialise_tests() + test_data = self.serialise_tests()[0] self.generate_filemap() self.generate_buildmap() self.generate_buildstylemap() @@ -92,7 +92,7 @@ class XCodeBackend(backends.Backend): self.generate_pbx_group() self.generate_pbx_native_target() self.generate_pbx_project() - self.generate_pbx_shell_build_phase() + self.generate_pbx_shell_build_phase(test_data) self.generate_pbx_sources_build_phase() self.generate_pbx_target_dependency() self.generate_xc_build_configuration() @@ -480,7 +480,7 @@ class XCodeBackend(backends.Backend): self.write_line('};') self.ofile.write('/* End PBXProject section */\n') - def generate_pbx_shell_build_phase(self): + def generate_pbx_shell_build_phase(self, test_data): self.ofile.write('\n/* Begin PBXShellScriptBuildPhase section */\n') self.write_line('%s = {' % self.test_command_id) self.indent_level += 1 @@ -496,7 +496,6 @@ class XCodeBackend(backends.Backend): self.write_line('shellPath = /bin/sh;') script_root = self.environment.get_script_dir() test_script = os.path.join(script_root, 'meson_test.py') - test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') cmd = [sys.executable, test_script, test_data, '--wd', self.environment.get_build_dir()] cmdstr = ' '.join(["'%s'" % i for i in cmd]) self.write_line('shellScript = "%s";' % cmdstr) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 63cdcf3..105fb0b 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -871,11 +871,12 @@ class CustomTarget: self.install = kwargs['install'] if not isinstance(self.install, bool): raise InvalidArguments('"install" must be boolean.') - if 'install_dir' not in kwargs: - raise InvalidArguments('"install_dir" not specified.') - self.install_dir = kwargs['install_dir'] - if not(isinstance(self.install_dir, str)): - raise InvalidArguments('"install_dir" must be a string.') + if self.install: + if 'install_dir' not in kwargs: + raise InvalidArguments('"install_dir" not specified.') + self.install_dir = kwargs['install_dir'] + if not(isinstance(self.install_dir, str)): + raise InvalidArguments('"install_dir" must be a string.') else: self.install = False self.build_always = kwargs.get('build_always', False) diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 0535653..a320e7d 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -520,7 +520,8 @@ int main () {{ {1}; }}''' ofile = open(srcname, 'w') ofile.write(code) ofile.close() - extra_args = extra_args + self.get_output_args(dstname) + extra_args = self.unix_link_flags_to_native(extra_args) + \ + self.get_output_args(dstname) p = self.compile(code, srcname, extra_args) try: os.remove(dstname) @@ -539,7 +540,7 @@ int main () {{ {1}; }}''' ofile.close() exename = srcname + '.exe' # Is guaranteed to be executable on every platform. commands = self.get_exelist() - commands += extra_args + commands += self.unix_link_flags_to_native(extra_args) commands.append(srcname) commands += self.get_output_args(exename) p = subprocess.Popen(commands, cwd=os.path.split(srcname)[0], stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -576,16 +577,30 @@ int main () {{ {1}; }}''' return RunResult(True, pe.returncode, so, se) def cross_sizeof(self, element, prefix, env, extra_args=[]): - templ = '''%s + element_exists_templ = '''#include <stdio.h> +{0} +int main(int argc, char **argv) {{ + {1} something; +}} +''' + templ = '''#include <stdio.h> +%s int temparray[%d-sizeof(%s)]; ''' try: extra_args += env.cross_info.config['properties'][self.language + '_args'] except KeyError: pass + extra_args += self.get_no_optimization_args() + if not self.compiles(element_exists_templ.format(prefix, element)): + return -1 for i in range(1, 1024): code = templ % (prefix, i, element) if self.compiles(code, extra_args): + if self.id == 'msvc': + # MSVC refuses to construct an array of zero size, so + # the test only succeeds when i is sizeof(element) + 1 + return i - 1 return i raise EnvironmentException('Cross checking sizeof overflowed.') @@ -608,6 +623,11 @@ int main(int argc, char **argv) { return int(res.stdout) def cross_alignment(self, typename, env, extra_args=[]): + type_exists_templ = '''#include <stdio.h> +int main(int argc, char **argv) {{ + {0} something; +}} +''' templ = '''#include<stddef.h> struct tmp { char c; @@ -620,9 +640,16 @@ int testarray[%d-offsetof(struct tmp, target)]; extra_args += env.cross_info.config['properties'][self.language + '_args'] except KeyError: pass + extra_args += self.get_no_optimization_args() + if not self.compiles(type_exists_templ.format(typename)): + return -1 for i in range(1, 1024): code = templ % (typename, i) if self.compiles(code, extra_args): + if self.id == 'msvc': + # MSVC refuses to construct an array of zero size, so + # the test only succeeds when i is sizeof(element) + 1 + return i - 1 return i raise EnvironmentException('Cross checking offsetof overflowed.') @@ -1366,19 +1393,6 @@ class VisualStudioCCompiler(CCompiler): def build_rpath_args(self, build_dir, rpath_paths, install_rpath): return [] - def find_library(self, libname, extra_dirs): - code = '''int main(int argc, char **argv) { - return 0; -} - ''' - args = [] - for i in extra_dirs: - args += self.get_linker_search_args(i) - args.append(libname + '.lib') - if self.links(code, extra_args=args): - return args - return None - # FIXME, no idea what these should be. def thread_flags(self): return [] diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index a7d6d97..41e8531 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -158,7 +158,8 @@ class Environment(): coredata.save(self.coredata, cdf) def get_script_dir(self): - return os.path.join(os.path.dirname(self.meson_script_file), '../scripts') + import mesonbuild.scripts + return os.path.dirname(mesonbuild.scripts.__file__) def get_log_dir(self): return self.log_dir @@ -222,6 +223,7 @@ class Environment(): ccache = self.detect_ccache() is_cross = False exe_wrap = None + popen_exceptions = {} for compiler in compilers: try: basename = os.path.basename(compiler).lower() @@ -229,9 +231,10 @@ class Environment(): arg = '/?' else: arg = '--version' - p = subprocess.Popen([compiler] + [arg], stdout=subprocess.PIPE, + p = subprocess.Popen([compiler, arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except OSError: + except OSError as e: + popen_exceptions[' '.join([compiler, arg])] = e continue (out, err) = p.communicate() out = out.decode(errors='ignore') @@ -262,7 +265,12 @@ class Environment(): # everything else to stdout. Why? Lord only knows. version = re.search(Environment.version_regex, err).group() return VisualStudioCCompiler([compiler], version, is_cross, exe_wrap) - raise EnvironmentException('Unknown compiler(s): "' + ', '.join(compilers) + '"') + errmsg = 'Unknown compiler(s): "' + ', '.join(compilers) + '"' + if popen_exceptions: + errmsg += '\nThe follow exceptions were encountered:' + for (c, e) in popen_exceptions.items(): + errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e) + raise EnvironmentException(errmsg) def detect_fortran_compiler(self, want_cross): evar = 'FC' @@ -281,13 +289,15 @@ class Environment(): compilers = self.default_fortran is_cross = False exe_wrap = None + popen_exceptions = {} for compiler in compilers: for arg in ['--version', '-V']: try: - p = subprocess.Popen([compiler] + [arg], + p = subprocess.Popen([compiler, arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except OSError: + except OSError as e: + popen_exceptions[' '.join([compiler, arg])] = e continue (out, err) = p.communicate() out = out.decode(errors='ignore') @@ -325,8 +335,12 @@ class Environment(): if 'NAG Fortran' in err: return NAGFortranCompiler([compiler], version, is_cross, exe_wrap) - - raise EnvironmentException('Unknown compiler(s): "' + ', '.join(compilers) + '"') + errmsg = 'Unknown compiler(s): "' + ', '.join(compilers) + '"' + if popen_exceptions: + errmsg += '\nThe follow exceptions were encountered:' + for (c, e) in popen_exceptions.items(): + errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e) + raise EnvironmentException(errmsg) def get_scratch_dir(self): return self.scratch_dir @@ -355,6 +369,7 @@ class Environment(): ccache = self.detect_ccache() is_cross = False exe_wrap = None + popen_exceptions = {} for compiler in compilers: basename = os.path.basename(compiler).lower() if basename == 'cl' or basename == 'cl.exe': @@ -365,7 +380,8 @@ class Environment(): p = subprocess.Popen([compiler, arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except OSError: + except OSError as e: + popen_exceptions[' '.join([compiler, arg])] = e continue (out, err) = p.communicate() out = out.decode(errors='ignore') @@ -394,7 +410,12 @@ class Environment(): if 'Microsoft' in out or 'Microsoft' in err: version = re.search(Environment.version_regex, err).group() return VisualStudioCPPCompiler([compiler], version, is_cross, exe_wrap) - raise EnvironmentException('Unknown compiler(s) "' + ', '.join(compilers) + '"') + errmsg = 'Unknown compiler(s): "' + ', '.join(compilers) + '"' + if popen_exceptions: + errmsg += '\nThe follow exceptions were encountered:' + for (c, e) in popen_exceptions.items(): + errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e) + raise EnvironmentException(errmsg) def detect_objc_compiler(self, want_cross): if self.is_cross_build() and want_cross: diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 2ac0932..fe831bd 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -252,7 +252,9 @@ def do_mesondefine(line, confdata): def do_conf_file(src, dst, confdata): data = open(src).readlines() - regex = re.compile('@(.*?)@') + # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define + # Also allow escaping '@' with '\@' + regex = re.compile(r'[^\\]?@([-a-zA-Z0-9_]+)@') result = [] for line in data: if line.startswith('#mesondefine'): diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 3b05afb..052c178 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -163,7 +163,10 @@ itself as required.''' def run_script_command(args): cmdname = args[0] cmdargs = args[1:] - if cmdname == 'test': + if cmdname == 'exe': + import mesonbuild.scripts.meson_exe as abc + cmdfunc = abc.run + elif cmdname == 'test': import mesonbuild.scripts.meson_test as abc cmdfunc = abc.run elif cmdname == 'benchmark': diff --git a/mesonbuild/scripts/delwithsuffix.py b/mesonbuild/scripts/delwithsuffix.py index 38ab406..e112101 100644 --- a/mesonbuild/scripts/delwithsuffix.py +++ b/mesonbuild/scripts/delwithsuffix.py @@ -17,7 +17,7 @@ import os, sys def run(args): - if len(sys.argv) != 2: + if len(sys.argv) != 3: print('delwithsuffix.py <root of subdir to process> <suffix to delete>') sys.exit(1) diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py new file mode 100644 index 0000000..f075fa0 --- /dev/null +++ b/mesonbuild/scripts/meson_exe.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +# Copyright 2013-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import argparse +import pickle +import platform +import subprocess + +import mesonbuild + +options = None + +parser = argparse.ArgumentParser() +parser.add_argument('args', nargs='+') + +def is_windows(): + platname = platform.system().lower() + return platname == 'windows' or 'mingw' in platname + +def run_with_mono(fname): + if fname.endswith('.exe') and not is_windows(): + return True + return False + +def run_exe(exe): + if exe.fname[0].endswith('.jar'): + cmd = ['java', '-jar'] + exe.fname + elif not exe.is_cross and run_with_mono(exe.fname[0]): + cmd = ['mono'] + exe.fname + else: + if exe.is_cross: + if exe.exe_runner is None: + raise Exception('BUG: Trying to run cross-compiled exes with no wrapper') + else: + cmd = [exe.exe_runner] + exe.fname + else: + cmd = exe.fname + child_env = os.environ.copy() + child_env.update(exe.env) + if len(exe.extra_paths) > 0: + child_env['PATH'] = ';'.join(exe.extra_paths + ['']) + child_env['PATH'] + p = subprocess.Popen(cmd + exe.cmd_args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=child_env, + cwd=exe.workdir) + +def run(args): + global options + options = parser.parse_args(args) + if len(options.args) != 1: + print('Test runner for Meson. Do not run on your own, mmm\'kay?') + print(sys.argv[0] + ' [data file]') + exe_data_file = options.args[0] + exe = pickle.load(open(exe_data_file, 'rb')) + run_exe(exe) + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) |