diff options
103 files changed, 1405 insertions, 454 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index 6df696f..234c773 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,7 +8,7 @@ graft tools include authors.txt include contributing.txt include COPYING -include readme.txt +include README.md include run_cross_test.py include run_tests.py include ghwt.py diff --git a/man/meson.1 b/man/meson.1 index 8432321..83ac397 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -1,4 +1,4 @@ -.TH MESON "1" "June 2016" "meson 0.32.0" "User Commands" +.TH MESON "1" "July 2016" "meson 0.33.0" "User Commands" .SH NAME meson - a high productivity build system .SH DESCRIPTION diff --git a/man/mesonconf.1 b/man/mesonconf.1 index e1cef24..35f8486 100644 --- a/man/mesonconf.1 +++ b/man/mesonconf.1 @@ -1,4 +1,4 @@ -.TH MESONCONF "1" "June 2016" "mesonconf 0.32.0" "User Commands" +.TH MESONCONF "1" "July 2016" "mesonconf 0.33.0" "User Commands" .SH NAME mesonconf - a tool to configure Meson builds .SH DESCRIPTION diff --git a/man/mesonintrospect.1 b/man/mesonintrospect.1 index 39392e3..307b99a 100644 --- a/man/mesonintrospect.1 +++ b/man/mesonintrospect.1 @@ -1,4 +1,4 @@ -.TH MESONCONF "1" "June 2016" "mesonintrospect 0.32.0" "User Commands" +.TH MESONCONF "1" "July 2016" "mesonintrospect 0.33.0" "User Commands" .SH NAME mesonintrospect - a tool to extract information about a Meson build .SH DESCRIPTION diff --git a/man/wraptool.1 b/man/wraptool.1 index 780b8e2..26d942f 100644 --- a/man/wraptool.1 +++ b/man/wraptool.1 @@ -1,4 +1,4 @@ -.TH WRAPTOOL "1" "June 2016" "meson 0.32.0" "User Commands" +.TH WRAPTOOL "1" "July 2016" "meson 0.33.0" "User Commands" .SH NAME wraptool - source dependency downloader .SH DESCRIPTION diff --git a/manual tests/1 wrap/meson.build b/manual tests/1 wrap/meson.build index 99ea260..aee358d 100644 --- a/manual tests/1 wrap/meson.build +++ b/manual tests/1 wrap/meson.build @@ -1,14 +1,13 @@ project('downloader', 'c') -s = subproject('sqlite') +cc = meson.get_compiler('c') -libdl = find_library('dl', required : false) +s = subproject('sqlite').get_variable('sqlite_dep') +th = dependency('threads') + +libdl = cc.find_library('dl', required : false) e = executable('dtest', 'main.c', - include_directories : s.get_variable('sqinc'), - link_args : ['-pthread'], - c_args : '-pthread', - dependencies : libdl, - link_with : s.get_variable('sqlib')) + dependencies : [th, libdl, s]) test('dltest', e) diff --git a/manual tests/1 wrap/subprojects/sqlite.wrap b/manual tests/1 wrap/subprojects/sqlite.wrap index cf6ff7a..6d14949 100644 --- a/manual tests/1 wrap/subprojects/sqlite.wrap +++ b/manual tests/1 wrap/subprojects/sqlite.wrap @@ -5,6 +5,6 @@ source_url = http://sqlite.com/2015/sqlite-amalgamation-3080802.zip source_filename = sqlite-amalgamation-3080802.zip source_hash = 5ebeea0dfb75d090ea0e7ff84799b2a7a1550db3fe61eb5f6f61c2e971e57663 -patch_url = http://wrapdb.mesonbuild.com/v1/projects/sqlite/3080802/2/get_zip -patch_filename = sqlite-3080802-2-wrap.zip -patch_hash = 300eb0e16cf7d32e873f16617de959c0a3e914614c5fddcff858fc010db10688 +patch_url = https://wrapdb.mesonbuild.com/v1/projects/sqlite/3080802/5/get_zip +patch_filename = sqlite-3080802-5-wrap.zip +patch_hash = d66469a73fa1344562d56a1d7627d5d0ee4044a77b32d16cf4bbb85741d4c9fd diff --git a/manual tests/4 standalone binaries/readme.txt b/manual tests/4 standalone binaries/readme.txt index 5a3604d..991b5c6 100644 --- a/manual tests/4 standalone binaries/readme.txt +++ b/manual tests/4 standalone binaries/readme.txt @@ -1,6 +1,6 @@ This directory shows how you can build redistributable binaries. On OSX this menans building an app bundle and a .dmg installer. On Linux -it means building a binary that bundles its dependencies. On Windows +it means building an archive that bundles its dependencies. On Windows it means building an .exe installer. To build each package you run the corresponding build_ARCH.sh build diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index fcc3d3b..1f1c3ca 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -110,9 +110,13 @@ class Backend(): # On some platforms (msvc for instance), the file that is used for # dynamic linking is not the same as the dynamic library itself. This # file is called an import library, and we want to link against that. - # On platforms where this distinction is not important, the import - # library is the same as the dynamic library itself. - return os.path.join(self.get_target_dir(target), target.get_import_filename()) + # On all other platforms, we link to the library directly. + if isinstance(target, build.SharedLibrary): + link_lib = target.get_import_filename() or target.get_filename() + return os.path.join(self.get_target_dir(target), link_lib) + elif isinstance(target, build.StaticLibrary): + return os.path.join(self.get_target_dir(target), target.get_filename()) + raise AssertionError('BUG: Tried to link to something that\'s not a library') def get_target_dir(self, target): if self.environment.coredata.get_builtin_option('layout') == 'mirror': @@ -292,6 +296,33 @@ class Backend(): args = includeargs + args return args + @staticmethod + def escape_extra_args(compiler, args): + # No extra escaping/quoting needed when not running on Windows + if not mesonlib.is_windows(): + return args + extra_args = [] + # Compiler-specific escaping is needed for -D args but not for any others + if compiler.get_id() == 'msvc': + # MSVC needs escaping when a -D argument ends in \ or \" + for arg in args: + if arg.startswith('-D') or arg.startswith('/D'): + # Without extra escaping for these two, the next character + # gets eaten + if arg.endswith('\\'): + arg += '\\' + elif arg.endswith('\\"'): + arg = arg[:-2] + '\\\\"' + extra_args.append(arg) + else: + # MinGW GCC needs all backslashes in defines to be doubly-escaped + # FIXME: Not sure about Cygwin or Clang + for arg in args: + if arg.startswith('-D') or arg.startswith('/D'): + arg = arg.replace('\\', '\\\\') + extra_args.append(arg) + return extra_args + def generate_basic_compiler_args(self, target, compiler): commands = [] commands += self.get_cross_stdlib_args(target, compiler) @@ -300,7 +331,7 @@ class Backend(): commands += compiler.get_option_compile_args(self.environment.coredata.compiler_options) commands += self.build.get_global_args(compiler) commands += self.environment.coredata.external_args[compiler.get_language()] - commands += target.get_extra_args(compiler.get_language()) + commands += self.escape_extra_args(compiler, target.get_extra_args(compiler.get_language())) commands += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) if self.environment.coredata.get_builtin_option('werror'): commands += compiler.get_werror_args() @@ -496,11 +527,19 @@ class Backend(): if isinstance(i, build.Executable): cmd += self.exe_object_to_cmd_array(i) continue - if isinstance(i, build.CustomTarget): + elif isinstance(i, build.CustomTarget): # GIR scanner will attempt to execute this binary but # it assumes that it is in path, so always give it a full path. tmp = i.get_filename()[0] i = os.path.join(self.get_target_dir(i), tmp) + elif isinstance(i, mesonlib.File): + i = os.path.join(i.subdir, i.fname) + if absolute_paths: + i = os.path.join(self.environment.get_build_dir(), i) + # FIXME: str types are blindly added and ignore the 'absolute_paths' argument + elif not isinstance(i, str): + err_msg = 'Argument {0} is of unknown type {1}' + raise RuntimeError(err_msg.format(str(i), str(type(i)))) for (j, src) in enumerate(srcs): i = i.replace('@INPUT%d@' % j, src) for (j, res) in enumerate(ofilenames): diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index a1bccc2..15f298b 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -496,18 +496,33 @@ int dummy; pickle.dump(d, ofile) def generate_target_install(self, d): - libdir = self.environment.get_libdir() - bindir = self.environment.get_bindir() - should_strip = self.environment.coredata.get_builtin_option('strip') for t in self.build.get_targets().values(): if t.should_install(): + # Find the installation directory outdir = t.get_custom_install_dir() - if outdir is None: - if isinstance(t, build.Executable): - outdir = bindir - else: - outdir = libdir + if outdir is not None: + pass + elif isinstance(t, build.SharedLibrary): + # For toolchains/platforms that need an import library for + # linking (separate from the shared library with all the + # code), we need to install the import library (dll.a/.lib) + if t.get_import_filename(): + # Install the import library. + i = [self.get_target_filename_for_linking(t), + self.environment.get_import_lib_dir(), + # It has no aliases, should not be stripped, and + # doesn't have an install_rpath + [], False, ''] + d.targets.append(i) + outdir = self.environment.get_shared_lib_dir() + elif isinstance(t, build.StaticLibrary): + outdir = self.environment.get_static_lib_dir() + elif isinstance(t, build.Executable): + outdir = self.environment.get_bindir() + else: + # XXX: Add BuildTarget-specific install dir cases here + outdir = self.environment.get_libdir() i = [self.get_target_filename(t), outdir, t.get_aliaslist(),\ should_strip, t.install_rpath] d.targets.append(i) @@ -1665,8 +1680,12 @@ rule FORTRAN_DEP_HACK else: soversion = None commands += linker.get_soname_args(target.name, abspath, soversion) + # This is only visited when using the Visual Studio toolchain if target.vs_module_defs and hasattr(linker, 'gen_vs_module_defs_args'): commands += linker.gen_vs_module_defs_args(target.vs_module_defs.rel_to_builddir(self.build_to_src)) + # This is only visited when building for Windows using either MinGW/GCC or Visual Studio + if target.import_filename: + commands += linker.gen_import_library_args(os.path.join(target.subdir, target.import_filename)) elif isinstance(target, build.StaticLibrary): commands += linker.get_std_link_args() else: @@ -1684,8 +1703,7 @@ rule FORTRAN_DEP_HACK commands += linker.thread_link_flags() if not isinstance(target, build.StaticLibrary): commands += target.link_args - # External deps must be last because target link libraries may depend on them. - if not(isinstance(target, build.StaticLibrary)): + # External deps must be last because target link libraries may depend on them. for dep in target.get_external_deps(): commands += dep.get_link_args() for d in target.get_dependencies(): @@ -1803,7 +1821,7 @@ rule FORTRAN_DEP_HACK ninja_command = environment.detect_ninja() if ninja_command is None: - raise MesonException('Could not detect ninja command') + raise MesonException('Could not detect Ninja v1.6 or newer)') elem = NinjaBuildElement(self.all_outputs, 'clean', 'CUSTOM_COMMAND', 'PHONY') elem.add_item('COMMAND', [ninja_command, '-t', 'clean']) elem.add_item('description', 'Cleaning') diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index d262e6b..669bcf8 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -28,6 +28,27 @@ import xml.dom.minidom from ..mesonlib import MesonException from ..environment import Environment +def split_o_flags_args(args): + """ + Splits any /O args and returns them. Does not take care of flags overriding + previous ones. Skips non-O flag arguments. + + ['/Ox', '/Ob1'] returns ['/Ox', '/Ob1'] + ['/Oxj', '/MP'] returns ['/Ox', '/Oj'] + """ + o_flags = [] + for arg in args: + if not arg.startswith('/O'): + continue + flags = list(arg[2:]) + # Assume that this one can't be clumped with the others since it takes + # an argument itself + if 'b' in flags: + o_flags.append(arg) + else: + o_flags += ['/O' + f for f in flags] + return o_flags + class RegenInfo(): def __init__(self, source_dir, build_dir, depfiles): self.source_dir = source_dir @@ -40,6 +61,7 @@ class Vs2010Backend(backends.Backend): self.project_file_version = '10.0.30319.1' self.sources_conflicts = {} self.platform_toolset = None + self.vs_version = '2010' def object_filename_from_source(self, target, source): basename = os.path.basename(source.fname) @@ -76,9 +98,14 @@ class Vs2010Backend(backends.Backend): outputs = [] custom_target_include_dirs = [] custom_target_output_files = [] + target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target)) + down = self.target_to_build_root(target) for genlist in target.get_generated_sources(): if isinstance(genlist, build.CustomTarget): - custom_target_output_files += [os.path.join(self.get_target_dir(genlist), i) for i in genlist.output] + for i in genlist.output: + # Path to the generated source from the current vcxproj dir via the build root + ipath = os.path.join(down, self.get_target_dir(genlist), i) + custom_target_output_files.append(ipath) 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) @@ -89,7 +116,6 @@ class Vs2010Backend(backends.Backend): outfilelist = genlist.get_outfilelist() exe_arr = self.exe_object_to_cmd_array(exe) base_args = generator.get_arglist() - target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target)) for i in range(len(infilelist)): if len(infilelist) == len(outfilelist): sole_output = os.path.join(target_private_dir, outfilelist[i]) @@ -123,7 +149,17 @@ class Vs2010Backend(backends.Backend): def generate(self, interp): self.resolve_source_conflicts() self.interpreter = interp - self.platform = 'Win32' + target_machine = self.interpreter.builtin['target_machine'].cpu_family_method(None, None) + if target_machine.endswith('64'): + # amd64 or x86_64 + self.platform = 'x64' + elif target_machine == 'x86': + # x86 + self.platform = 'Win32' + elif 'arm' in target_machine.lower(): + self.platform = 'ARM' + else: + raise MesonException('Unsupported Visual Studio platform: ' + target_machine) self.buildtype = self.environment.coredata.get_builtin_option('buildtype') sln_filename = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.sln') projlist = self.generate_projects() @@ -183,7 +219,7 @@ class Vs2010Backend(backends.Backend): def generate_solution(self, sln_filename, projlist): ofile = open(sln_filename, 'w') ofile.write('Microsoft Visual Studio Solution File, Format Version 11.00\n') - ofile.write('# Visual Studio 2010\n') + ofile.write('# Visual Studio ' + self.vs_version + '\n') prj_templ = prj_line = 'Project("{%s}") = "%s", "%s", "{%s}"\n' for p in projlist: prj_line = prj_templ % (self.environment.coredata.guid, p[0], p[1], p[2]) @@ -400,9 +436,61 @@ class Vs2010Backend(backends.Backend): # they are part of the CustomBuildStep Outputs. return - @classmethod - def quote_define_cmdline(cls, arg): - return re.sub(r'^([-/])D(.*?)="(.*)"$', r'\1D\2=\"\3\"', arg) + @staticmethod + def escape_preprocessor_define(define): + # See: https://msdn.microsoft.com/en-us/library/bb383819.aspx + table = str.maketrans({'%': '%25', '$': '%24', '@': '%40', + "'": '%27', ';': '%3B', '?': '%3F', '*': '%2A', + # We need to escape backslash because it'll be un-escaped by + # Windows during process creation when it parses the arguments + # Basically, this converts `\` to `\\`. + '\\': '\\\\'}) + return define.translate(table) + + @staticmethod + def escape_additional_option(option): + # See: https://msdn.microsoft.com/en-us/library/bb383819.aspx + table = str.maketrans({'%': '%25', '$': '%24', '@': '%40', + "'": '%27', ';': '%3B', '?': '%3F', '*': '%2A', ' ': '%20',}) + option = option.translate(table) + # Since we're surrounding the option with ", if it ends in \ that will + # escape the " when the process arguments are parsed and the starting + # " will not terminate. So we escape it if that's the case. I'm not + # kidding, this is how escaping works for process args on Windows. + if option.endswith('\\'): + option += '\\' + return '"{}"'.format(option) + + @staticmethod + def split_link_args(args): + """ + Split a list of link arguments into three lists: + * library search paths + * library filenames (or paths) + * other link arguments + """ + lpaths = [] + libs = [] + other = [] + for arg in args: + if arg.startswith('/LIBPATH:'): + lpath = arg[9:] + # De-dup library search paths by removing older entries when + # a new one is found. This is necessary because unlike other + # search paths such as the include path, the library is + # searched for in the newest (right-most) search path first. + if lpath in lpaths: + lpaths.remove(lpath) + lpaths.append(lpath) + # It's ok if we miss libraries with non-standard extensions here. + # They will go into the general link arguments. + elif arg.endswith('.lib') or arg.endswith('.a'): + # De-dup + if arg not in libs: + libs.append(arg) + else: + other.append(arg) + return (lpaths, libs, other) def gen_vcxproj(self, target, ofname, guid, compiler): mlog.debug('Generating vcxproj %s.' % target.name) @@ -424,11 +512,15 @@ class Vs2010Backend(backends.Backend): return self.gen_run_target_vcxproj(target, ofname, guid) else: raise MesonException('Unknown target type for %s' % target.get_basename()) + # Prefix to use to access the build root from the vcxproj dir down = self.target_to_build_root(target) + # Prefix to use to access the source tree's root from the vcxproj dir proj_to_src_root = os.path.join(down, self.build_to_src) + # Prefix to use to access the source tree's subdir from the vcxproj dir proj_to_src_dir = os.path.join(proj_to_src_root, target.subdir) (sources, headers, objects, languages) = self.split_sources(target.sources) - buildtype = self.buildtype + buildtype_args = compiler.get_buildtype_args(self.buildtype) + buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype) project_name = target.name target_name = target.name root = ET.Element('Project', {'DefaultTargets' : "Build", @@ -438,9 +530,10 @@ class Vs2010Backend(backends.Backend): prjconf = ET.SubElement(confitems, 'ProjectConfiguration', {'Include' : self.buildtype + '|' + self.platform}) p = ET.SubElement(prjconf, 'Configuration') - p.text= buildtype + p.text= self.buildtype pl = ET.SubElement(prjconf, 'Platform') pl.text = self.platform + # Globals globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals') guidelem = ET.SubElement(globalgroup, 'ProjectGuid') guidelem.text = guid @@ -453,13 +546,68 @@ class Vs2010Backend(backends.Backend): pname= ET.SubElement(globalgroup, 'ProjectName') pname.text = project_name ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.Default.props') + # Start configuration type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration') ET.SubElement(type_config, 'ConfigurationType').text = conftype ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte' if self.platform_toolset: ET.SubElement(type_config, 'PlatformToolset').text = self.platform_toolset + # FIXME: Meson's LTO support needs to be integrated here ET.SubElement(type_config, 'WholeProgramOptimization').text = 'false' - ET.SubElement(type_config, 'UseDebugLibraries').text = 'true' + # Let VS auto-set the RTC level + ET.SubElement(type_config, 'BasicRuntimeChecks').text = 'Default' + o_flags = split_o_flags_args(buildtype_args) + if '/Oi' in o_flags: + ET.SubElement(type_config, 'IntrinsicFunctions').text = 'true' + if '/Ob1' in o_flags: + ET.SubElement(type_config, 'InlineFunctionExpansion').text = 'OnlyExplicitInline' + elif '/Ob2' in o_flags: + ET.SubElement(type_config, 'InlineFunctionExpansion').text = 'AnySuitable' + # Size-preserving flags + if '/Os' in o_flags: + ET.SubElement(type_config, 'FavorSizeOrSpeed').text = 'Size' + else: + ET.SubElement(type_config, 'FavorSizeOrSpeed').text = 'Speed' + # Incremental linking increases code size + if '/INCREMENTAL:NO' in buildtype_link_args: + ET.SubElement(type_config, 'LinkIncremental').text = 'false' + # CRT type; debug or release + if '/MDd' in buildtype_args: + ET.SubElement(type_config, 'UseDebugLibraries').text = 'true' + ET.SubElement(type_config, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL' + else: + ET.SubElement(type_config, 'UseDebugLibraries').text = 'false' + ET.SubElement(type_config, 'RuntimeLibrary').text = 'MultiThreadedDLL' + # Debug format + if '/ZI' in buildtype_args: + ET.SubElement(type_config, 'DebugInformationFormat').text = 'EditAndContinue' + elif '/Zi' in buildtype_args: + ET.SubElement(type_config, 'DebugInformationFormat').text = 'ProgramDatabase' + elif '/Z7' in buildtype_args: + ET.SubElement(type_config, 'DebugInformationFormat').text = 'OldStyle' + # Generate Debug info + if '/DEBUG' in buildtype_link_args: + ET.SubElement(type_config, 'GenerateDebugInformation').text = 'true' + # Runtime checks + if '/RTC1' in buildtype_args: + ET.SubElement(type_config, 'BasicRuntimeChecks').text = 'EnableFastChecks' + elif '/RTCu' in buildtype_args: + ET.SubElement(type_config, 'BasicRuntimeChecks').text = 'UninitializedLocalUsageCheck' + elif '/RTCs' in buildtype_args: + ET.SubElement(type_config, 'BasicRuntimeChecks').text = 'StackFrameRuntimeCheck' + # Optimization flags + if '/Ox' in o_flags: + ET.SubElement(type_config, 'Optimization').text = 'Full' + elif '/O2' in o_flags: + ET.SubElement(type_config, 'Optimization').text = 'MaxSpeed' + elif '/O1' in o_flags: + ET.SubElement(type_config, 'Optimization').text = 'MinSpace' + elif '/Od' in o_flags: + ET.SubElement(type_config, 'Optimization').text = 'Disabled' + # Warning level + warning_level = self.environment.coredata.get_builtin_option('warning_level') + ET.SubElement(type_config, 'WarningLevel').text = 'Level' + warning_level + # End configuration ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.props') 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) @@ -467,6 +615,7 @@ class Vs2010Backend(backends.Backend): gen_src += custom_src gen_hdrs += custom_hdrs gen_langs += custom_langs + # Project information direlem = ET.SubElement(root, 'PropertyGroup') fver = ET.SubElement(direlem, '_ProjectFileVersion') fver.text = self.project_file_version @@ -474,15 +623,13 @@ class Vs2010Backend(backends.Backend): outdir.text = '.\\' intdir = ET.SubElement(direlem, 'IntDir') intdir.text = target.get_id() + '\\' - tname = ET.SubElement(direlem, 'TargetName') - tname.text = target_name - inclinc = ET.SubElement(direlem, 'LinkIncremental') - inclinc.text = 'true' + tfilename = os.path.splitext(target.get_filename()) + ET.SubElement(direlem, 'TargetName').text = tfilename[0] + ET.SubElement(direlem, 'TargetExt').text = tfilename[1] + # Build information compiles = ET.SubElement(root, 'ItemDefinitionGroup') clconf = ET.SubElement(compiles, 'ClCompile') - 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] + generated_files_include_dirs @@ -496,21 +643,43 @@ class Vs2010Backend(backends.Backend): for l, args in target.extra_args.items(): if l in extra_args: extra_args[l] += compiler.unix_compile_flags_to_native(args) - general_args = compiler.get_buildtype_args(self.buildtype).copy() # FIXME all the internal flags of VS (optimization etc) are represented # by their own XML elements. In theory we should split all flags to those # that have an XML element and those that don't and serialise them # properly. This is a crapton of work for no real gain, so just dump them # here. - general_args += compiler.get_option_compile_args(self.environment.coredata.compiler_options) + general_args = compiler.get_option_compile_args(self.environment.coredata.compiler_options) for d in target.get_external_deps(): - try: - general_args += d.compile_args - except AttributeError: - pass - + # Cflags required by external deps might have UNIX-specific flags, + # so filter them out if needed + d_compile_args = compiler.unix_compile_flags_to_native(d.get_compile_args()) + for arg in d_compile_args: + if arg.startswith('-I') or arg.startswith('/I'): + inc_dir = arg[2:] + # De-dup + if inc_dir not in inc_dirs: + inc_dirs.append(inc_dir) + else: + general_args.append(arg) + + defines = [] + # Split preprocessor defines and include directories out of the list of + # all extra arguments. The rest go into %(AdditionalOptions). for l, args in extra_args.items(): - extra_args[l] = [Vs2010Backend.quote_define_cmdline(x) for x in args] + extra_args[l] = [] + for arg in args: + if arg.startswith('-D') or arg.startswith('/D'): + define = self.escape_preprocessor_define(arg[2:]) + # De-dup + if define not in defines: + defines.append(define) + elif arg.startswith('-I') or arg.startswith('/I'): + inc_dir = arg[2:] + # De-dup + if inc_dir not in inc_dirs: + inc_dirs.append(inc_dir) + else: + extra_args[l].append(self.escape_additional_option(arg)) languages += gen_langs has_language_specific_args = any(l != extra_args['c'] for l in extra_args.values()) @@ -537,11 +706,9 @@ class Vs2010Backend(backends.Backend): inc_dirs.append('%(AdditionalIncludeDirectories)') ET.SubElement(clconf, 'AdditionalIncludeDirectories').text = ';'.join(inc_dirs) - preproc = ET.SubElement(clconf, 'PreprocessorDefinitions') + ET.SubElement(clconf, 'PreprocessorDefinitions').text = ';'.join(defines) rebuild = ET.SubElement(clconf, 'MinimalRebuild') rebuild.text = 'true' - rtlib = ET.SubElement(clconf, 'RuntimeLibrary') - rtlib.text = 'MultiThreadedDebugDLL' funclink = ET.SubElement(clconf, 'FunctionLevelLinking') funclink.text = 'true' pch_node = ET.SubElement(clconf, 'PrecompiledHeader') @@ -564,28 +731,39 @@ class Vs2010Backend(backends.Backend): pch_out = ET.SubElement(clconf, 'PrecompiledHeaderOutputFile') pch_out.text = '$(IntDir)$(TargetName)-%s.pch' % pch_source[2] - warnings = ET.SubElement(clconf, 'WarningLevel') - warnings.text = 'Level3' - debinfo = ET.SubElement(clconf, 'DebugInformationFormat') - debinfo.text = 'EditAndContinue' resourcecompile = ET.SubElement(compiles, 'ResourceCompile') ET.SubElement(resourcecompile, 'PreprocessorDefinitions') link = ET.SubElement(compiles, 'Link') # Put all language args here, too. extra_link_args = compiler.get_option_link_args(self.environment.coredata.compiler_options) + # FIXME: Can these buildtype linker args be added as tags in the + # vcxproj file (similar to buildtype compiler args) instead of in + # AdditionalOptions? extra_link_args += compiler.get_buildtype_linker_args(self.buildtype) for l in self.environment.coredata.external_link_args.values(): - extra_link_args += compiler.unix_link_flags_to_native(l) - extra_link_args += compiler.unix_link_flags_to_native(target.link_args) + extra_link_args += l + if not isinstance(target, build.StaticLibrary): + extra_link_args += target.link_args + # External deps must be last because target link libraries may depend on them. + for dep in target.get_external_deps(): + extra_link_args += dep.get_link_args() + for d in target.get_dependencies(): + if isinstance(d, build.StaticLibrary): + for dep in d.get_external_deps(): + extra_link_args += dep.get_link_args() + extra_link_args = compiler.unix_link_flags_to_native(extra_link_args) + (additional_libpaths, additional_links, extra_link_args) = self.split_link_args(extra_link_args) if len(extra_link_args) > 0: extra_link_args.append('%(AdditionalOptions)') ET.SubElement(link, "AdditionalOptions").text = ' '.join(extra_link_args) + if len(additional_libpaths) > 0: + additional_libpaths.insert(0, '%(AdditionalLibraryDirectories)') + ET.SubElement(link, 'AdditionalLibraryDirectories').text = ';'.join(additional_libpaths) - additional_links = [] + # Add more libraries to be linked if needed for t in target.get_dependencies(): lobj = self.build.targets[t.get_id()] - rel_path = self.relpath(lobj.subdir, target.subdir) - linkname = os.path.join(rel_path, lobj.get_import_filename()) + linkname = os.path.join(down, self.get_target_filename_for_linking(lobj)) 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))) @@ -600,26 +778,37 @@ class Vs2010Backend(backends.Backend): ET.SubElement(link, 'AdditionalDependencies').text = ';'.join(additional_links) ofile = ET.SubElement(link, 'OutputFile') ofile.text = '$(OutDir)%s' % target.get_filename() - addlibdir = ET.SubElement(link, 'AdditionalLibraryDirectories') - addlibdir.text = '%(AdditionalLibraryDirectories)' subsys = ET.SubElement(link, 'SubSystem') subsys.text = subsystem - gendeb = ET.SubElement(link, 'GenerateDebugInformation') - gendeb.text = 'true' if isinstance(target, build.SharedLibrary): + # DLLs built with MSVC always have an import library except when + # they're data-only DLLs, but we don't support those yet. ET.SubElement(link, 'ImportLibrary').text = target.get_import_filename() - pdb = ET.SubElement(link, 'ProgramDataBaseFileName') - pdb.text = '$(OutDir}%s.pdb' % target_name + # Add module definitions file, if provided + if target.vs_module_defs: + relpath = os.path.join(down, target.vs_module_defs.rel_to_builddir(self.build_to_src)) + ET.SubElement(link, 'ModuleDefinitionFile').text = relpath + if '/ZI' in buildtype_args or '/Zi' in buildtype_args: + pdb = ET.SubElement(link, 'ProgramDataBaseFileName') + pdb.text = '$(OutDir}%s.pdb' % target_name if isinstance(target, build.Executable): ET.SubElement(link, 'EntryPointSymbol').text = entrypoint targetmachine = ET.SubElement(link, 'TargetMachine') - targetmachine.text = 'MachineX86' + targetplatform = self.platform.lower() + if targetplatform == 'win32': + targetmachine.text = 'MachineX86' + elif targetplatform == 'x64': + targetmachine.text = 'MachineX64' + elif targetplatform == 'arm': + targetmachine.text = 'MachineARM' + else: + raise MesonException('Unsupported Visual Studio target machine: ' + targetmachine) extra_files = target.extra_files if len(headers) + len(gen_hdrs) + len(extra_files) > 0: inc_hdrs = ET.SubElement(root, 'ItemGroup') for h in headers: - relpath = h.rel_to_builddir(proj_to_src_root) + relpath = os.path.join(down, h.rel_to_builddir(self.build_to_src)) ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath) for h in gen_hdrs: ET.SubElement(inc_hdrs, 'CLInclude', Include=h) @@ -630,7 +819,7 @@ class Vs2010Backend(backends.Backend): if len(sources) + len(gen_src) + len(pch_sources) > 0: inc_src = ET.SubElement(root, 'ItemGroup') for s in sources: - relpath = s.rel_to_builddir(proj_to_src_root) + relpath = os.path.join(down, s.rel_to_builddir(self.build_to_src)) inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=relpath) self.add_pch(inc_cl, proj_to_src_dir, pch_sources, s) self.add_additional_options(s, inc_cl, extra_args, additional_options_set) @@ -658,7 +847,7 @@ class Vs2010Backend(backends.Backend): if self.has_objects(objects, additional_objects, gen_objs): inc_objs = ET.SubElement(root, 'ItemGroup') for s in objects: - relpath = s.rel_to_builddir(proj_to_src_root) + relpath = os.path.join(down, s.rel_to_builddir(self.build_to_src)) ET.SubElement(inc_objs, 'Object', Include=relpath) for s in additional_objects: ET.SubElement(inc_objs, 'Object', Include=s) diff --git a/mesonbuild/backend/vs2015backend.py b/mesonbuild/backend/vs2015backend.py index c138cb5..8d4ff45 100644 --- a/mesonbuild/backend/vs2015backend.py +++ b/mesonbuild/backend/vs2015backend.py @@ -20,6 +20,7 @@ class Vs2015Backend(Vs2010Backend): def __init__(self, build): super().__init__(build) self.platform_toolset = 'v140' + self.vs_version = '2015' @staticmethod def has_objects(objects, additional_objects, generated_objects): diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index 0ce90ce..e64866d 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -292,12 +292,10 @@ class XCodeBackend(backends.Backend): reftype = 0 if isinstance(t, build.Executable): typestr = 'compiled.mach-o.executable' - path = t.get_filename() + path = fname elif isinstance(t, build.SharedLibrary): - # OSX has a completely different shared library - # naming scheme so do this manually. typestr = self.get_xcodetype('dummy.dylib') - path = t.get_osx_filename() + path = fname else: typestr = self.get_xcodetype(fname) path = '"%s"' % t.get_filename() @@ -626,7 +624,7 @@ class XCodeBackend(backends.Backend): headerdirs.append(os.path.join(self.environment.get_build_dir(), cd)) for l in target.link_targets: abs_path = os.path.join(self.environment.get_build_dir(), - l.subdir, buildtype, l.get_osx_filename()) + l.subdir, buildtype, l.get_filename()) dep_libs.append("'%s'" % abs_path) if isinstance(l, build.SharedLibrary): links_dylib = True diff --git a/mesonbuild/build.py b/mesonbuild/build.py index c60f37f..6a4f375 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -16,8 +16,9 @@ from . import coredata from . import environment from . import dependencies from . import mlog -import copy, os +import copy, os, re from .mesonlib import File, flatten, MesonException +from .environment import for_windows, for_darwin known_basic_kwargs = {'install' : True, 'c_pch' : True, @@ -49,27 +50,37 @@ known_shlib_kwargs.update({'version' : True, 'name_suffix' : True, 'vs_module_defs' : True}) -backslash_explanation = \ -'''Compiler arguments have a backslash "\\" character. This is unfortunately not -permitted. The reason for this is that backslash is a shell quoting character -that behaves differently across different systems. Because of this is it not -possible to make it work reliably across all the platforms Meson needs to -support. +def sources_are_suffix(sources, suffix): + for source in sources: + if source.endswith('.' + suffix): + return True + return False -There are several different ways of working around this issue. Most of the time -you are using this to provide a -D define to your compiler. Try instead to -create a config.h file and put all of your definitions in it using -configure_file(). - -Another approach is to move the backslashes into the source and have the other -bits in the def. So you would have an arg -DPLAIN_TEXT="foo" and then in your -C sources something like this: +def compiler_is_msvc(sources, is_cross, env): + """ + Since each target does not currently have the compiler information attached + to it, we must do this detection manually here. -const char *fulltext = "\\\\" PLAIN_TEXT; + This detection is purposely incomplete and will cause bugs if other code is + extended and this piece of code is forgotten. + """ + compiler = None + if sources_are_suffix(sources, 'c'): + try: + compiler = env.detect_c_compiler(is_cross) + except MesonException: + return False + elif sources_are_suffix(sources, 'cxx') or \ + sources_are_suffix(sources, 'cpp') or \ + sources_are_suffix(sources, 'cc'): + try: + compiler = env.detect_cpp_compiler(is_cross) + except MesonException: + return False + if compiler and compiler.get_id() == 'msvc': + return True + return False -We are fully aware that these are not really usable or pleasant ways to do -this but it's the best we can do given the way shell quoting works. -''' class InvalidArguments(MesonException): pass @@ -209,6 +220,10 @@ class BuildTarget(): raise InvalidArguments('Build target %s has no sources.' % name) self.validate_sources() + def __repr__(self): + repr_str = "<{0} {1}: {2}>" + return repr_str.format(self.__class__.__name__, self.get_id(), self.filename) + def get_id(self): # This ID must also be a valid file name on all OSs. # It should also avoid shell metacharacters for obvious @@ -569,8 +584,6 @@ class BuildTarget(): for a in args: if not isinstance(a, (str, File)): raise InvalidArguments('A non-string passed to compiler args.') - if isinstance(a, str) and '\\' in a: - raise InvalidArguments(backslash_explanation) if language in self.extra_args: self.extra_args[language] += args else: @@ -593,6 +606,10 @@ class Generator(): self.exe = exe self.process_kwargs(kwargs) + def __repr__(self): + repr_str = "<{0}: {1}>" + return repr_str.format(self.__class__.__name__, self.exe) + def get_exe(self): return self.exe @@ -670,15 +687,19 @@ class GeneratedList(): class Executable(BuildTarget): def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs) - self.prefix = '' - self.suffix = environment.get_exe_suffix() - suffix = environment.get_exe_suffix() - if len(self.sources) > 0 and self.sources[0].endswith('.cs'): - suffix = 'exe' - if suffix != '': - self.filename = self.name + '.' + suffix - else: - self.filename = self.name + # Unless overriden, executables have no suffix or prefix. Except on + # Windows and with C#/Mono executables where the suffix is 'exe' + if not hasattr(self, 'prefix'): + self.prefix = '' + if not hasattr(self, 'suffix'): + # Executable for Windows or C#/Mono + if for_windows(is_cross, environment) or sources_are_suffix(self.sources, 'cs'): + self.suffix = 'exe' + else: + self.suffix = '' + self.filename = self.name + if self.suffix: + self.filename += '.' + self.suffix def type_suffix(self): return "@exe" @@ -686,52 +707,161 @@ class Executable(BuildTarget): class StaticLibrary(BuildTarget): def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs) - if len(self.sources) > 0 and self.sources[0].endswith('.cs'): + if sources_are_suffix(self.sources, 'cs'): raise InvalidArguments('Static libraries not supported for C#.') + # By default a static library is named libfoo.a even on Windows because + # MSVC does not have a consistent convention for what static libraries + # are called. The MSVC CRT uses libfoo.lib syntax but nothing else uses + # it and GCC only looks for static libraries called foo.lib and + # libfoo.a. However, we cannot use foo.lib because that's the same as + # the import library. Using libfoo.a is ok because people using MSVC + # always pass the library filename while linking anyway. if not hasattr(self, 'prefix'): - self.prefix = environment.get_static_lib_prefix() - self.suffix = environment.get_static_lib_suffix() - if len(self.sources) > 0 and self.sources[0].endswith('.rs'): - self.suffix = 'rlib' + self.prefix = 'lib' + if not hasattr(self, 'suffix'): + # Rust static library crates have .rlib suffix + if sources_are_suffix(self.sources, 'rs'): + self.suffix = 'rlib' + else: + self.suffix = 'a' self.filename = self.prefix + self.name + '.' + self.suffix - def get_import_filename(self): - return self.filename - - def get_osx_filename(self): - return self.get_filename() - def type_suffix(self): return "@sta" class SharedLibrary(BuildTarget): def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): - self.version = None self.soversion = None + self.ltversion = None self.vs_module_defs = None - super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs); - if len(self.sources) > 0 and self.sources[0].endswith('.cs'): + # The import library this target will generate + self.import_filename = None + # The import library that Visual Studio would generate (and accept) + self.vs_import_filename = None + # The import library that GCC would generate (and prefer) + self.gcc_import_filename = None + super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs) + if not hasattr(self, 'prefix'): + self.prefix = None + if not hasattr(self, 'suffix'): + self.suffix = None + self.basic_filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + self.determine_filenames(is_cross, environment) + + def determine_filenames(self, is_cross, env): + """ + See https://github.com/mesonbuild/meson/pull/417 for details. + + First we determine the filename template (self.filename_tpl), then we + set the output filename (self.filename). + + The template is needed while creating aliases (self.get_aliaslist), + which are needed while generating .so shared libraries for Linux. + + Besides this, there's also the import library name, which is only used + on Windows since on that platform the linker uses a separate library + called the "import library" during linking instead of the shared + library (DLL). The toolchain will output an import library in one of + two formats: GCC or Visual Studio. + + When we're building with Visual Studio, the import library that will be + generated by the toolchain is self.vs_import_filename, and with + MinGW/GCC, it's self.gcc_import_filename. self.import_filename will + always contain the import library name this target will generate. + """ + prefix = '' + suffix = '' + self.filename_tpl = self.basic_filename_tpl + # If the user already provided the prefix and suffix to us, we don't + # need to do any filename suffix/prefix detection. + # NOTE: manual prefix/suffix override is currently only tested for C/C++ + if self.prefix != None and self.suffix != None: + pass + # C# and Mono + elif sources_are_suffix(self.sources, 'cs'): + prefix = '' + suffix = 'dll' + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + # Rust + elif sources_are_suffix(self.sources, 'rs'): + # Currently, we always build --crate-type=rlib prefix = 'lib' + suffix = 'rlib' + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + # C, C++, Swift, Vala + # Only Windows uses a separate import library for linking + # For all other targets/platforms import_filename stays None + elif for_windows(is_cross, env): suffix = 'dll' + self.vs_import_filename = '{0}.lib'.format(self.name) + self.gcc_import_filename = 'lib{0}.dll.a'.format(self.name) + if compiler_is_msvc(self.sources, is_cross, env): + # Shared library is of the form foo.dll + prefix = '' + # Import library is called foo.lib + self.import_filename = self.vs_import_filename + # Assume GCC-compatible naming + else: + # Shared library is of the form libfoo.dll + prefix = 'lib' + # Import library is called libfoo.dll.a + self.import_filename = self.gcc_import_filename + # Shared library has the soversion if it is defined + if self.soversion: + self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' + else: + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + elif for_darwin(is_cross, env): + prefix = 'lib' + suffix = 'dylib' + if self.soversion: + # libfoo.X.dylib + self.filename_tpl = '{0.prefix}{0.name}.{0.soversion}.{0.suffix}' + else: + # libfoo.dylib + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' else: - prefix = environment.get_shared_lib_prefix() - suffix = environment.get_shared_lib_suffix() - if not hasattr(self, 'prefix'): - self.prefix = prefix - if not hasattr(self, 'suffix'): - if len(self.sources) > 0 and self.sources[0].endswith('.rs'): - self.suffix = 'rlib' + prefix = 'lib' + suffix = 'so' + if self.ltversion: + # libfoo.so.X[.Y[.Z]] (.Y and .Z are optional) + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.ltversion}' + elif self.soversion: + # libfoo.so.X + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.soversion}' else: - self.suffix = suffix - self.importsuffix = environment.get_import_lib_suffix() - self.filename = self.prefix + self.name + '.' + self.suffix + # No versioning, libfoo.so + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + if self.prefix == None: + self.prefix = prefix + if self.suffix == None: + self.suffix = suffix + self.filename = self.filename_tpl.format(self) def process_kwargs(self, kwargs, environment): super().process_kwargs(kwargs, environment) + # Shared library version if 'version' in kwargs: - self.set_version(kwargs['version']) + self.ltversion = kwargs['version'] + if not isinstance(self.ltversion, str): + raise InvalidArguments('Shared library version needs to be a string, not ' + type(self.ltversion).__name__) + if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', self.ltversion): + raise InvalidArguments('Invalid Shared library version "{0}". Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.'.format(self.ltversion)) + # Try to extract/deduce the soversion if 'soversion' in kwargs: - self.set_soversion(kwargs['soversion']) + self.soversion = kwargs['soversion'] + if isinstance(self.soversion, int): + self.soversion = str(self.soversion) + if not isinstance(self.soversion, str): + raise InvalidArguments('Shared library soversion is not a string or integer.') + try: + int(self.soversion) + except ValueError: + raise InvalidArguments('Shared library soversion must be a valid integer') + elif self.ltversion: + # library version is defined, get the soversion from that + self.soversion = self.ltversion.split('.')[0] + # Visual Studio module-definitions file if 'vs_module_defs' in kwargs: path = kwargs['vs_module_defs'] if (os.path.isabs(path)): @@ -742,46 +872,41 @@ class SharedLibrary(BuildTarget): def check_unknown_kwargs(self, kwargs): self.check_unknown_kwargs_int(kwargs, known_shlib_kwargs) - def get_shbase(self): - return self.prefix + self.name + '.' + self.suffix - def get_import_filename(self): - return self.prefix + self.name + '.' + self.importsuffix - - def get_all_link_deps(self): - return [self] + self.get_transitive_link_deps() - - def get_filename(self): - '''Works on all platforms except OSX, which does its own thing.''' - fname = self.get_shbase() - if self.version is None: - return fname - else: - return fname + '.' + self.version + """ + The name of the import library that will be outputted by the compiler - def get_osx_filename(self): - if self.version is None: - return self.get_shbase() - return self.prefix + self.name + '.' + self.version + '.' + self.suffix + Returns None if there is no import library required for this platform + """ + return self.import_filename - def set_version(self, version): - if not isinstance(version, str): - raise InvalidArguments('Shared library version is not a string.') - self.version = version + def get_import_filenameslist(self): + if self.import_filename: + return [self.vs_import_filename, self.gcc_import_filename] + return [] - def set_soversion(self, version): - if isinstance(version, int): - version = str(version) - if not isinstance(version, str): - raise InvalidArguments('Shared library soversion is not a string or integer.') - self.soversion = version + def get_all_link_deps(self): + return [self] + self.get_transitive_link_deps() def get_aliaslist(self): - aliases = [] - if self.soversion is not None: - aliases.append(self.get_shbase() + '.' + self.soversion) - if self.version is not None: - aliases.append(self.get_shbase()) + """ + If the versioned library name is libfoo.so.0.100.0, aliases are: + * libfoo.so.0 (soversion) + * libfoo.so (unversioned; for linking) + """ + # Aliases are only useful with .so libraries. Also if the .so library + # ends with .so (no versioning), we don't need aliases. + if self.suffix != 'so' or self.filename.endswith('.so'): + return [] + # Unversioned alias: libfoo.so + aliases = [self.basic_filename_tpl.format(self)] + # If ltversion != soversion we create an soversion alias: libfoo.so.X + if self.ltversion and self.ltversion != self.soversion: + if not self.soversion: + # This is done in self.process_kwargs() + raise AssertionError('BUG: If library version is defined, soversion must have been defined') + alias_tpl = self.filename_tpl.replace('ltversion', 'soversion') + aliases.append(alias_tpl.format(self)) return aliases def type_suffix(self): @@ -815,6 +940,10 @@ class CustomTarget: mlog.log(mlog.bold('Warning:'), 'Unknown keyword arguments in target %s: %s' % (self.name, ', '.join(unknowns))) + def __repr__(self): + repr_str = "<{0} {1}: {2}>" + return repr_str.format(self.__class__.__name__, self.get_id(), self.command) + def get_id(self): return self.name + self.type_suffix() @@ -851,7 +980,7 @@ class CustomTarget: for i, c in enumerate(cmd): if hasattr(c, 'held_object'): c = c.held_object - if isinstance(c, str): + if isinstance(c, str) or isinstance(c, File): final_cmd.append(c) elif isinstance(c, dependencies.ExternalProgram): if not c.found(): @@ -867,8 +996,6 @@ class CustomTarget: if not isinstance(s, str): raise InvalidArguments('Array as argument %d contains a non-string.' % i) final_cmd.append(s) - elif isinstance(c, File): - final_cmd.append(os.path.join(c.subdir, c.fname)) else: raise InvalidArguments('Argument %s in "command" is invalid.' % i) self.command = final_cmd @@ -944,6 +1071,10 @@ class RunTarget: self.dependencies = dependencies self.subdir = subdir + def __repr__(self): + repr_str = "<{0} {1}: {2}>" + return repr_str.format(self.__class__.__name__, self.get_id(), self.command) + def get_id(self): return self.name + self.type_suffix() @@ -994,6 +1125,12 @@ class ConfigureFile(): self.targetname = targetname self.configuration_data = configuration_data + def __repr__(self): + repr_str = "<{0}: {1} -> {2}>" + src = os.path.join(self.subdir, self.sourcename) + dst = os.path.join(self.subdir, self.targetname) + return repr_str.format(self.__class__.__name__, src, dst) + def get_configuration_data(self): return self.configuration_data @@ -1011,6 +1148,9 @@ class ConfigurationData(): super().__init__() self.values = {} + def __repr__(self): + return repr(self.values) + def get(self, name): return self.values[name] diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 8ee86bc..30eecec 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -142,6 +142,7 @@ base_options = { ['none', 'address', 'thread', 'undefined', 'memory'], 'none'), 'b_lundef': coredata.UserBooleanOption('b_lundef', 'Use -Wl,--no-undefined when linking', True), + 'b_asneeded': coredata.UserBooleanOption('b_asneeded', 'Use -Wl,--as-needed when linking', True), 'b_pgo': coredata.UserComboOption('b_pgo', 'Use profile guide optimization', ['off', 'generate', 'use'], 'off'), @@ -224,6 +225,11 @@ def get_base_link_args(options, linker): except KeyError: pass try: + if options['b_asneeded'].value: + args.append('-Wl,--as-needed') + except KeyError: + pass + try: if options['b_coverage'].value: args += linker.get_coverage_link_args() except KeyError: @@ -274,6 +280,13 @@ class Compiler(): def get_linker_always_args(self): return [] + def gen_import_library_args(self, implibname): + """ + Used only on Windows for libraries that need an import library. + This currently means C, C++, Fortran. + """ + return [] + def get_options(self): return {} # build afresh every time @@ -473,6 +486,14 @@ class CCompiler(Compiler): def get_linker_search_args(self, dirname): return ['-L'+dirname] + def gen_import_library_args(self, implibname): + """ + The name of the outputted import library + + This implementation is used only on Windows by compilers that use GNU ld + """ + return ['-Wl,--out-implib=' + implibname] + def sanity_check_impl(self, work_dir, environment, sname, code): mlog.debug('Sanity testing ' + self.language + ' compiler:', ' '.join(self.exelist)) mlog.debug('Is cross compiler: %s.' % str(self.is_cross)) @@ -1499,6 +1520,10 @@ class VisualStudioCCompiler(CCompiler): objname = os.path.splitext(pchname)[0] + '.obj' return (objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname ]) + def gen_import_library_args(self, implibname): + "The name of the outputted import library" + return ['/IMPLIB:' + implibname] + def build_rpath_args(self, build_dir, rpath_paths, install_rpath): return [] @@ -1642,6 +1667,7 @@ class GnuCCompiler(CCompiler): 'b_colorout'] if self.gcc_type != GCC_OSX: self.base_options.append('b_lundef') + self.base_options.append('b_asneeded') def get_colorout_args(self, colortype): if mesonlib.version_compare(self.version, '>=4.9.0'): @@ -1712,6 +1738,7 @@ class GnuObjCCompiler(ObjCCompiler): self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage'] if self.gcc_type != GCC_OSX: self.base_options.append('b_lundef') + self.base_options.append('b_asneeded') def get_buildtype_args(self, buildtype): return gnulike_buildtype_args[buildtype] @@ -1740,6 +1767,7 @@ class GnuObjCPPCompiler(ObjCPPCompiler): self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage'] if self.gcc_type != GCC_OSX: self.base_options.append('b_lundef') + self.base_options.append('b_asneeded') def get_buildtype_args(self, buildtype): return gnulike_buildtype_args[buildtype] @@ -1761,6 +1789,7 @@ class ClangObjCCompiler(GnuObjCCompiler): self.clang_type = cltype if self.clang_type != CLANG_OSX: self.base_options.append('b_lundef') + self.base_options.append('b_asneeded') class ClangObjCPPCompiler(GnuObjCPPCompiler): def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None): @@ -1770,6 +1799,7 @@ class ClangObjCPPCompiler(GnuObjCPPCompiler): self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage'] if self.clang_type != CLANG_OSX: self.base_options.append('b_lundef') + self.base_options.append('b_asneeded') class ClangCCompiler(CCompiler): def __init__(self, exelist, version, clang_type, is_cross, exe_wrapper=None): @@ -1782,6 +1812,7 @@ class ClangCCompiler(CCompiler): self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage'] if self.clang_type != CLANG_OSX: self.base_options.append('b_lundef') + self.base_options.append('b_asneeded') def get_buildtype_args(self, buildtype): return gnulike_buildtype_args[buildtype] @@ -1831,6 +1862,7 @@ class GnuCPPCompiler(CPPCompiler): 'b_colorout'] if self.gcc_type != GCC_OSX: self.base_options.append('b_lundef') + self.base_options.append('b_asneeded') def get_colorout_args(self, colortype): if mesonlib.version_compare(self.version, '>=4.9.0'): @@ -1892,6 +1924,7 @@ class ClangCPPCompiler(CPPCompiler): self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage'] if self.clang_type != CLANG_OSX: self.base_options.append('b_lundef') + self.base_options.append('b_asneeded') def get_buildtype_args(self, buildtype): return gnulike_buildtype_args[buildtype] @@ -2057,6 +2090,14 @@ class GnuFortranCompiler(FortranCompiler): def get_always_args(self): return ['-pipe'] + def gen_import_library_args(self, implibname): + """ + The name of the outputted import library + + Used only on Windows + """ + return ['-Wl,--out-implib=' + implibname] + class G95FortranCompiler(FortranCompiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None): super().__init__(exelist, version, is_cross, exe_wrapper=None) @@ -2068,6 +2109,14 @@ class G95FortranCompiler(FortranCompiler): def get_always_args(self): return ['-pipe'] + def gen_import_library_args(self, implibname): + """ + The name of the outputted import library + + Used only on Windows + """ + return ['-Wl,--out-implib=' + implibname] + class SunFortranCompiler(FortranCompiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None): super().__init__(exelist, version, is_cross, exe_wrapper=None) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 21b907e..7c8e458 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -15,7 +15,7 @@ import pickle, os, uuid from .mesonlib import MesonException, default_libdir, default_libexecdir, default_prefix -version = '0.33.0.dev1' +version = '0.34.0.dev1' backendlist = ['ninja', 'vs2010', 'vs2015', 'xcode'] class UserOption: @@ -100,10 +100,10 @@ class UserStringArrayOption(UserOption): raise MesonException('Valuestring does not define an array: ' + newvalue) newvalue = eval(newvalue, {}, {}) # Yes, it is unsafe. if not isinstance(newvalue, list): - raise MesonException('String array value is not an array.') + raise MesonException('"{0}" should be a string array, but it is not'.format(str(newvalue))) for i in newvalue: if not isinstance(i, str): - raise MesonException('String array element not a string.') + raise MesonException('String array element "{0}" is not a string.'.format(str(newvalue))) self.value = newvalue # This class contains all data that must persist over multiple @@ -133,6 +133,8 @@ class CoreData(): self.cross_compilers = {} self.deps = {} self.modules = {} + # Only to print a warning if it changes between Meson invocations. + self.pkgconf_envvar = os.environ.get('PKG_CONFIG_PATH', '') def init_builtins(self, options): self.builtins = {} diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index 544291b..b4f825b 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -122,56 +122,66 @@ class PkgConfigDependency(Dependency): if self.required: raise DependencyException('%s dependency %s not found.' % (self.type_string, name)) self.modversion = 'none' + return + self.modversion = out.decode().strip() + found_msg = ['%s dependency' % self.type_string, mlog.bold(name), 'found:'] + self.version_requirement = kwargs.get('version', None) + if self.version_requirement is None: + self.is_found = True else: - self.modversion = out.decode().strip() - mlog.log('%s dependency' % self.type_string, mlog.bold(name), 'found:', - mlog.green('YES'), self.modversion) - self.version_requirement = kwargs.get('version', None) - if self.version_requirement is None: - self.is_found = True - else: - if not isinstance(self.version_requirement, str): - raise DependencyException('Version argument must be string.') - self.is_found = mesonlib.version_compare(self.modversion, self.version_requirement) - if not self.is_found and self.required: + if not isinstance(self.version_requirement, str): + raise DependencyException('Version argument must be string.') + self.is_found = mesonlib.version_compare(self.modversion, self.version_requirement) + if not self.is_found: + found_msg += [mlog.red('NO'), 'found {!r}'.format(self.modversion), + 'but need {!r}'.format(self.version_requirement)] + mlog.log(*found_msg) + if self.required: raise DependencyException( 'Invalid version of a dependency, needed %s %s found %s.' % (name, self.version_requirement, self.modversion)) - if not self.is_found: return - p = subprocess.Popen([pkgbin, '--cflags', name], stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out = p.communicate()[0] - if p.returncode != 0: - raise DependencyException('Could not generate cargs for %s:\n\n%s' % \ - (name, out.decode(errors='ignore'))) - self.cargs = out.decode().split() - - libcmd = [pkgbin, '--libs'] - if self.static: - libcmd.append('--static') - p = subprocess.Popen(libcmd + [name], stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out = p.communicate()[0] - if p.returncode != 0: - raise DependencyException('Could not generate libs for %s:\n\n%s' % \ - (name, out.decode(errors='ignore'))) - self.libs = [] - for lib in out.decode().split(): - if lib.endswith(".la"): - shared_libname = self.extract_libtool_shlib(lib) - shared_lib = os.path.join(os.path.dirname(lib), shared_libname) - if not os.path.exists(shared_lib): - shared_lib = os.path.join(os.path.dirname(lib), ".libs", shared_libname) - - if not os.path.exists(shared_lib): - raise DependencyException('Got a libtools specific "%s" dependencies' - 'but we could not compute the actual shared' - 'library path' % lib) - lib = shared_lib - self.is_libtool = True - - self.libs.append(lib) + found_msg += [mlog.green('YES'), self.modversion] + mlog.log(*found_msg) + # Fetch cargs to be used while using this dependency + self._set_cargs() + # Fetch the libraries and library paths needed for using this + self._set_libs() + + def _set_cargs(self): + p = subprocess.Popen([self.pkgbin, '--cflags', self.name], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out = p.communicate()[0] + if p.returncode != 0: + raise DependencyException('Could not generate cargs for %s:\n\n%s' % \ + (name, out.decode(errors='ignore'))) + self.cargs = out.decode().split() + + def _set_libs(self): + libcmd = [self.pkgbin, '--libs'] + if self.static: + libcmd.append('--static') + p = subprocess.Popen(libcmd + [self.name], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out = p.communicate()[0] + if p.returncode != 0: + raise DependencyException('Could not generate libs for %s:\n\n%s' % \ + (name, out.decode(errors='ignore'))) + self.libs = [] + for lib in out.decode().split(): + if lib.endswith(".la"): + shared_libname = self.extract_libtool_shlib(lib) + shared_lib = os.path.join(os.path.dirname(lib), shared_libname) + if not os.path.exists(shared_lib): + shared_lib = os.path.join(os.path.dirname(lib), ".libs", shared_libname) + + if not os.path.exists(shared_lib): + raise DependencyException('Got a libtools specific "%s" dependencies' + 'but we could not compute the actual shared' + 'library path' % lib) + lib = shared_lib + self.is_libtool = True + self.libs.append(lib) def get_variable(self, variable_name): p = subprocess.Popen([self.pkgbin, '--variable=%s' % variable_name, self.name], @@ -343,37 +353,13 @@ class WxDependency(Dependency): class ExternalProgram(): def __init__(self, name, fullpath=None, silent=False, search_dir=None): self.name = name - self.fullpath = None if fullpath is not None: if not isinstance(fullpath, list): self.fullpath = [fullpath] else: self.fullpath = fullpath else: - self.fullpath = [shutil.which(name)] - if self.fullpath[0] is None and search_dir is not None: - trial = os.path.join(search_dir, name) - suffix = os.path.splitext(trial)[-1].lower()[1:] - if mesonlib.is_windows() and (suffix == 'exe' or suffix == 'com'\ - or suffix == 'bat'): - self.fullpath = [trial] - elif not mesonlib.is_windows() and os.access(trial, os.X_OK): - self.fullpath = [trial] - else: - # Now getting desperate. Maybe it is a script file that is a) not chmodded - # executable or b) we are on windows so they can't be directly executed. - try: - first_line = open(trial).readline().strip() - if first_line.startswith('#!'): - commands = first_line[2:].split('#')[0].strip().split() - if mesonlib.is_windows(): - # Windows does not have /usr/bin. - commands[0] = commands[0].split('/')[-1] - if commands[0] == 'env': - commands = commands[1:] - self.fullpath = commands + [trial] - except Exception: - pass + self.fullpath = self._search(name, search_dir) if not silent: if self.found(): mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'), @@ -381,6 +367,67 @@ class ExternalProgram(): else: mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO')) + @staticmethod + def _shebang_to_cmd(script): + """ + Windows does not understand shebangs, so we check if the file has a + shebang and manually parse it to figure out the interpreter to use + """ + try: + first_line = open(script).readline().strip() + if first_line.startswith('#!'): + commands = first_line[2:].split('#')[0].strip().split() + if mesonlib.is_windows(): + # Windows does not have /usr/bin. + commands[0] = commands[0].split('/')[-1] + if commands[0] == 'env': + commands = commands[1:] + return commands + [script] + except Exception: + pass + return False + + @staticmethod + def _is_executable(path): + suffix = os.path.splitext(path)[-1].lower()[1:] + if mesonlib.is_windows(): + if suffix == 'exe' or suffix == 'com' or suffix == 'bat': + return True + elif os.access(path, os.X_OK): + return True + return False + + def _search_dir(self, name, search_dir): + if search_dir is None: + return False + trial = os.path.join(search_dir, name) + if not os.path.exists(trial): + return False + if self._is_executable(trial): + return [trial] + # Now getting desperate. Maybe it is a script file that is a) not chmodded + # executable or b) we are on windows so they can't be directly executed. + return self._shebang_to_cmd(trial) + + def _search(self, name, search_dir): + commands = self._search_dir(name, search_dir) + if commands: + return commands + # Do a standard search in PATH + fullpath = shutil.which(name) + if fullpath or not mesonlib.is_windows(): + # On UNIX-like platforms, the standard PATH search is enough + return [fullpath] + # On Windows, interpreted scripts must have an extension otherwise they + # cannot be found by a standard PATH search. So we do a custom search + # where we manually search for a script with a shebang in PATH. + search_dirs = os.environ.get('PATH', '').split(';') + for search_dir in search_dirs: + commands = self._search_dir(name, search_dir) + if commands: + return commands + return [None] + def found(self): return self.fullpath[0] is not None @@ -1105,11 +1152,13 @@ class Python3Dependency(Dependency): super().__init__() self.name = 'python3' self.is_found = False + self.version = "3.something_maybe" try: pkgdep = PkgConfigDependency('python3', environment, kwargs) if pkgdep.found(): self.cargs = pkgdep.cargs self.libs = pkgdep.libs + self.version = pkgdep.get_version() self.is_found = True return except Exception: @@ -1127,6 +1176,7 @@ class Python3Dependency(Dependency): self.libs = ['-L{}/libs'.format(basedir), '-lpython{}'.format(vernum)] self.is_found = True + self.version = sysconfig.get_config_var('py_version_short') elif mesonlib.is_osx(): # In OSX the Python 3 framework does not have a version # number in its name. @@ -1146,6 +1196,9 @@ class Python3Dependency(Dependency): def get_link_args(self): return self.libs + def get_version(self): + return self.version + def get_dep_identifier(name, kwargs): elements = [name] modlist = kwargs.get('modules', []) diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 3868301..404ed3e 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -45,11 +45,13 @@ def find_valgrind(): def detect_ninja(): for n in ['ninja', 'ninja-build']: try: - p = subprocess.Popen([n, '--version'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + p = subprocess.Popen([n, '--version'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) except FileNotFoundError: continue - p.communicate() - if p.returncode == 0: + version = p.communicate()[0].decode(errors='ignore') + # Perhaps we should add a way for the caller to know the failure mode + # (not found or too old) + if p.returncode == 0 and mesonlib.version_compare(version, ">=1.6"): return n def detect_cpu_family(): @@ -80,11 +82,37 @@ def detect_system(): return platform.system().lower() +def for_windows(is_cross, env): + """ + Host machine is windows? + + Note: 'host' is the machine on which compiled binaries will run + """ + if not is_cross: + return mesonlib.is_windows() + elif env.cross_info.has_host(): + return env.cross_info.config['host_machine']['system'] == 'windows' + return False + +def for_darwin(is_cross, env): + """ + Host machine is Darwin (iOS/OS X)? + + Note: 'host' is the machine on which compiled binaries will run + """ + if not is_cross: + return mesonlib.is_osx() + elif env.cross_info.has_host(): + return env.cross_info.config['host_machine']['system'] == 'darwin' + return False + + class Environment(): private_dir = 'meson-private' log_dir = 'meson-logs' coredata_file = os.path.join(private_dir, 'coredata.dat') version_regex = '\d+(\.\d+)+(-[a-zA-Z0-9]+)?' + def __init__(self, source_dir, build_dir, main_script_file, options, original_cmd_line_args): assert(os.path.isabs(main_script_file)) assert(not os.path.islink(main_script_file)) @@ -100,7 +128,11 @@ class Environment(): self.coredata = coredata.load(cdf) self.first_invocation = False except FileNotFoundError: + # WARNING: Don't use any values from coredata in __init__. It gets + # re-initialized with project options by the interpreter during + # build file parsing. self.coredata = coredata.CoreData(options) + self.coredata.meson_script_file = self.meson_script_file self.first_invocation = True if self.coredata.cross_file: self.cross_info = CrossBuildInfo(self.coredata.cross_file) @@ -122,40 +154,27 @@ class Environment(): self.default_static_linker = 'ar' self.vs_static_linker = 'lib' + # Various prefixes and suffixes for import libraries, shared libraries, + # static libraries, and executables. + # Versioning is added to these names in the backends as-needed. cross = self.is_cross_build() if (not cross and mesonlib.is_windows()) \ or (cross and self.cross_info.has_host() and self.cross_info.config['host_machine']['system'] == 'windows'): self.exe_suffix = 'exe' - if self.detect_c_compiler(cross).get_id() == 'msvc': - self.import_lib_suffix = 'lib' - else: - # MinGW-GCC doesn't generate and can't link with a .lib - # It uses the DLL file as the import library - self.import_lib_suffix = 'dll' - self.shared_lib_suffix = 'dll' - self.shared_lib_prefix = '' - self.static_lib_suffix = 'lib' - self.static_lib_prefix = '' self.object_suffix = 'obj' + self.win_libdir_layout = True else: self.exe_suffix = '' - if (not cross and mesonlib.is_osx()) or \ - (cross and self.cross_info.has_host() and self.cross_info.config['host_machine']['system'] == 'darwin'): - self.shared_lib_suffix = 'dylib' - else: - self.shared_lib_suffix = 'so' - self.shared_lib_prefix = 'lib' - self.static_lib_suffix = 'a' - self.static_lib_prefix = 'lib' self.object_suffix = 'o' - self.import_lib_suffix = self.shared_lib_suffix + self.win_libdir_layout = False def is_cross_build(self): return self.cross_info is not None - def dump_coredata(self): + def dump_coredata(self, mtime): cdf = os.path.join(self.get_build_dir(), Environment.coredata_file) coredata.save(self.coredata, cdf) + os.utime(cdf, times=(mtime, mtime)) def get_script_dir(self): import mesonbuild.scripts @@ -641,22 +660,19 @@ class Environment(): def get_exe_suffix(self): return self.exe_suffix - # On Windows (MSVC) the library has suffix dll - # but you link against a file that has suffix lib. - def get_import_lib_suffix(self): - return self.import_lib_suffix - - def get_shared_lib_prefix(self): - return self.shared_lib_prefix + def get_import_lib_dir(self): + "Install dir for the import library (library used for linking)" + return self.get_libdir() - def get_shared_lib_suffix(self): - return self.shared_lib_suffix + def get_shared_lib_dir(self): + "Install dir for the shared library" + if self.win_libdir_layout: + return self.get_bindir() + return self.get_libdir() - def get_static_lib_prefix(self): - return self.static_lib_prefix - - def get_static_lib_suffix(self): - return self.static_lib_suffix + def get_static_lib_dir(self): + "Install dir for the static library" + return self.get_libdir() def get_object_suffix(self): return self.object_suffix @@ -682,17 +698,6 @@ class Environment(): def get_datadir(self): return self.coredata.get_builtin_option('datadir') - def find_library(self, libname, dirs): - if dirs is None: - dirs = mesonlib.get_library_dirs() - suffixes = [self.get_shared_lib_suffix(), self.get_static_lib_suffix()] - prefix = self.get_shared_lib_prefix() - for d in dirs: - for suffix in suffixes: - trial = os.path.join(d, prefix + libname + '.' + suffix) - if os.path.isfile(trial): - return trial - def get_args_from_envvars(lang): if lang == 'c': @@ -721,14 +726,12 @@ def get_args_from_envvars(lang): class CrossBuildInfo(): def __init__(self, filename): - self.config = {} + self.config = {'properties': {}} self.parse_datafile(filename) if 'target_machine' in self.config: return if not 'host_machine' in self.config: raise mesonlib.MesonException('Cross info file must have either host or a target machine.') - if not 'properties' in self.config: - raise mesonlib.MesonException('Cross file is missing "properties".') if not 'binaries' in self.config: raise mesonlib.MesonException('Cross file is missing "binaries".') diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index df4cb0d..9c2a74c 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -120,8 +120,14 @@ class RunProcess(InterpreterObject): pc = self.run_command(command_array, source_dir, build_dir, subdir, in_builddir) (stdout, stderr) = pc.communicate() self.returncode = pc.returncode - self.stdout = stdout.decode().replace('\r\n', '\n') - self.stderr = stderr.decode().replace('\r\n', '\n') + if sys.stdout.encoding: + self.stdout = stdout.decode(encoding=sys.stdout.encoding, errors='ignore').replace('\r\n', '\n') + else: + self.stdout = stdout.decode(errors='ignore').replace('\r\n', '\n') + if sys.stderr.encoding: + self.stderr = stderr.decode(encoding=sys.stderr.encoding, errors='ignore').replace('\r\n', '\n') + else: + self.stderr = stderr.decode(errors='ignore').replace('\r\n', '\n') self.methods.update({'returncode' : self.returncode_method, 'stdout' : self.stdout_method, 'stderr' : self.stderr_method, @@ -1317,6 +1323,8 @@ class Interpreter(): return self.evaluate_plusassign(cur) elif isinstance(cur, mparser.IndexNode): return self.evaluate_indexing(cur) + elif isinstance(cur, mparser.TernaryNode): + return self.evaluate_ternary(cur) elif self.is_elementary_type(cur): return cur else: @@ -1381,7 +1389,8 @@ class Interpreter(): r = wrap.Resolver(os.path.join(self.build.environment.get_source_dir(), self.subproject_dir)) resolved = r.resolve(dirname) if resolved is None: - raise InterpreterException('Subproject directory does not exist and can not be downloaded.') + msg = 'Subproject directory {!r} does not exist and can not be downloaded.' + raise InterpreterException(msg.format(os.path.join(self.subproject_dir, dirname))) subdir = os.path.join(self.subproject_dir, resolved) os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) self.global_args_frozen = True @@ -1677,21 +1686,29 @@ class Interpreter(): dep = dependencies.find_external_dependency(name, self.environment, kwargs) except dependencies.DependencyException: if 'fallback' in kwargs: - dep = self.dependency_fallback(kwargs) + dep = self.dependency_fallback(name, kwargs) self.coredata.deps[identifier] = dep.held_object return dep raise self.coredata.deps[identifier] = dep return DependencyHolder(dep) - def dependency_fallback(self, kwargs): + def dependency_fallback(self, name, kwargs): fbinfo = kwargs['fallback'] check_stringlist(fbinfo) if len(fbinfo) != 2: raise InterpreterException('Fallback info must have exactly two items.') dirname, varname = fbinfo - self.do_subproject(dirname, {}) + try: + self.do_subproject(dirname, {}) + except: + mlog.log('Also couldn\'t find a fallback subproject in', + mlog.bold(os.path.join(self.subproject_dir, dirname)), + 'for the dependency', mlog.bold(name)) + raise dep = self.subprojects[dirname].get_variable_method([varname], {}) + if not isinstance(dep, (DependencyHolder, InternalDependencyHolder)): + raise InterpreterException('Fallback variable is not a dependency object.') # Check if the version of the declared dependency matches what we want if 'version' in kwargs: wanted = kwargs['version'] @@ -1699,6 +1716,9 @@ class Interpreter(): if found == 'undefined' or not mesonlib.version_compare(found, wanted): m = 'Subproject "{0}" dependency "{1}" version is "{2}" but "{3}" is required.' raise InterpreterException(m.format(dirname, varname, found, wanted)) + mlog.log('Found a', mlog.green('fallback'), 'subproject', + mlog.bold(os.path.join(self.subproject_dir, dirname)), 'for', + mlog.bold(name)) return dep def func_executable(self, node, args, kwargs): @@ -1987,7 +2007,9 @@ class Interpreter(): raise InterpreterException('Argument "configuration" is not of type configuration_data') ofile_abs = os.path.join(self.environment.build_dir, self.subdir, output) if inputfile is not None: - conffile = os.path.join(self.subdir, inputfile) + # Normalize the path of the conffile to avoid duplicates + # This is especially important to convert '/' to '\' on Windows + conffile = os.path.normpath(os.path.join(self.subdir, inputfile)) if conffile not in self.build_def_files: self.build_def_files.append(conffile) os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) @@ -2382,6 +2404,16 @@ class Interpreter(): if not isinstance(node.elseblock, mparser.EmptyNode): self.evaluate_codeblock(node.elseblock) + def evaluate_ternary(self, node): + assert(isinstance(node, mparser.TernaryNode)) + result = self.evaluate_statement(node.condition) + if not isinstance(result, bool): + raise InterpreterException('Ternary condition is not boolean.') + if result: + return self.evaluate_statement(node.trueblock) + else: + return self.evaluate_statement(node.falseblock) + def evaluate_foreach(self, node): assert(isinstance(node, mparser.ForeachClauseNode)) varname = node.varname.value diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 837f78a..1672d95 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -28,6 +28,16 @@ class File: self.subdir = subdir self.fname = fname + def __str__(self): + return os.path.join(self.subdir, self.fname) + + def __repr__(self): + ret = '<File: {0}' + if not self.is_built: + ret += ' (not built)' + ret += '>' + return ret.format(os.path.join(self.subdir, self.fname)) + @staticmethod def from_source_file(source_root, subdir, fname): if not os.path.isfile(os.path.join(source_root, subdir, fname)): diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 3f5612b..e002d9a 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -15,7 +15,7 @@ # limitations under the License. import sys, stat, traceback, pickle, argparse -import datetime +import time, datetime import os.path from . import environment, interpreter, mesonlib from . import build @@ -117,6 +117,13 @@ itself as required.''' raise RuntimeError('Something went terribly wrong. Please file a bug.') return (src_dir, build_dir) + def check_pkgconfig_envvar(self, env): + curvar = os.environ.get('PKG_CONFIG_PATH', '') + if curvar != env.coredata.pkgconf_envvar: + mlog.log(mlog.red("WARNING:"), 'PKG_CONFIG_PATH has changed between invocations from "%s" to "%s".' % + (env.coredata.pkgconf_envvar, curvar)) + env.coredata.pkgconf_envvar = curvar + def generate(self): env = environment.Environment(self.source_dir, self.build_dir, self.meson_script_file, self.options, self.original_cmd_line_args) mlog.initialize(env.get_log_dir()) @@ -124,6 +131,7 @@ itself as required.''' mlog.debug('Python binary:', sys.executable) mlog.debug('Python system:', platform.system()) mlog.log(mlog.bold('The Meson build system')) + self.check_pkgconfig_envvar(env) mlog.log('Version:', coredata.version) mlog.log('Source dir:', mlog.bold(self.source_dir)) mlog.log('Build dir:', mlog.bold(self.build_dir)) @@ -156,11 +164,21 @@ itself as required.''' mlog.log('Build machine cpu family:', mlog.bold(intr.builtin['build_machine'].cpu_family_method([], {}))) mlog.log('Build machine cpu:', mlog.bold(intr.builtin['build_machine'].cpu_method([], {}))) intr.run() - env.dump_coredata() + coredata_mtime = time.time() g.generate(intr) g.run_postconf_scripts() dumpfile = os.path.join(env.get_scratch_dir(), 'build.dat') pickle.dump(b, open(dumpfile, 'wb')) + # Write this last since we use the existence of this file to check if + # we generated the build file successfully, so we don't want an error + # that pops up during generation, post-conf scripts, etc to cause us to + # incorrectly signal a successful meson run which will cause an error + # about an already-configured build directory when the user tries again. + # + # However, we set the mtime to an earlier value to ensure that doing an + # mtime comparison between the coredata dump and other build files + # shows the build files to be newer, not older. + env.dump_coredata(coredata_mtime) def run_script_command(args): cmdname = args[0] diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 037b1f5..be3c3ac 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -288,7 +288,7 @@ class GnomeModule: args += self.unpack_args('--htmlargs=', 'html_args', kwargs) args += self.unpack_args('--scanargs=', 'scan_args', kwargs) args += self.unpack_args('--fixxrefargs=', 'fixxref_args', kwargs) - res = [build.RunTarget(targetname, command[0], command[1:] + args, state.subdir)] + res = [build.RunTarget(targetname, command[0], command[1:] + args, [], state.subdir)] if kwargs.get('install', True): res.append(build.InstallScript(command + args)) return res diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index 29d6236..cd4e343 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -32,9 +32,14 @@ class WindowsModule: res_args = extra_args + ['/nologo', '/fo@OUTPUT@', '@INPUT@'] suffix = 'res' else: - rescomp = dependencies.ExternalProgram('windres', silent=True) + # Pick-up env var WINDRES if set. This is often used for specifying + # an arch-specific windres. + rescomp_name = os.environ.get('WINDRES', 'windres') + rescomp = dependencies.ExternalProgram(rescomp_name, silent=True) res_args = extra_args + ['@INPUT@', '@OUTPUT@'] suffix = 'o' + if not rescomp.found(): + raise MesonException('Could not find Windows resource compiler %s.' % ' '.join(rescomp.get_command())) res_files = mesonlib.stringlistify(args) res_kwargs = {'output' : '@BASENAME@.' + suffix, 'arguments': res_args} diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index fd720fb..2f0eb2d 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -67,6 +67,7 @@ class Lexer: ('lt', re.compile(r'<')), ('ge', re.compile(r'>=')), ('gt', re.compile(r'>')), + ('questionmark', re.compile(r'\?')), ] def lex(self, code): @@ -282,6 +283,14 @@ class IfNode(): self.condition = condition self.block = block +class TernaryNode(): + def __init__(self, lineno, colno, condition, trueblock, falseblock): + self.lineno = lineno + self.colno = colno + self.condition = condition + self.trueblock = trueblock + self.falseblock = falseblock + class ArgumentNode(): def __init__(self, token): self.lineno = token.lineno @@ -344,6 +353,7 @@ class Parser: def __init__(self, code): self.stream = Lexer().lex(code) self.getsym() + self.in_ternary = False def getsym(self): try: @@ -383,6 +393,16 @@ class Parser: raise ParseException('Assignment target must be an id.', left.lineno, left.colno) return AssignmentNode(left.lineno, left.colno, left.value, value) + elif self.accept('questionmark'): + if self.in_ternary: + raise ParseException('Nested ternary operators are not allowed.', + left.lineno, left.colno) + self.in_ternary = True + trueblock = self.e1() + self.expect('colon') + falseblock = self.e1() + self.in_ternary = False + return TernaryNode(left.lineno, left.colno, left, trueblock, falseblock) return left def e2(self): diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py index 8ff0dd1..cb136f4 100644 --- a/mesonbuild/scripts/depfixer.py +++ b/mesonbuild/scripts/depfixer.py @@ -20,6 +20,7 @@ import sys, struct SHT_STRTAB = 3 DT_NEEDED = 1 DT_RPATH = 15 +DT_RUNPATH = 29 DT_STRTAB = 5 DT_SONAME = 14 @@ -211,21 +212,29 @@ class Elf(DataSizes): self.bf.seek(strtab.val + soname.val) print(self.read_str()) - def get_rpath_offset(self): + def get_entry_offset(self, entrynum): sec = self.find_section(b'.dynstr') for i in self.dynamic: - if i.d_tag == DT_RPATH: + if i.d_tag == entrynum: return sec.sh_offset + i.val return None def print_rpath(self): - offset = self.get_rpath_offset() + offset = self.get_entry_offset(DT_RPATH) if offset is None: print("This file does not have an rpath.") else: self.bf.seek(offset) print(self.read_str()) + def print_runpath(self): + offset = self.get_entry_offset(DT_RUNPATH) + if offset is None: + print("This file does not have a runpath.") + else: + self.bf.seek(offset) + print(self.read_str()) + def print_deps(self): sec = self.find_section(b'.dynstr') deps = [] @@ -257,9 +266,15 @@ class Elf(DataSizes): self.bf.write(newname) def fix_rpath(self, new_rpath): + # The path to search for can be either rpath or runpath. + # Fix both of them to be sure. + self.fix_rpathtype_entry(new_rpath, DT_RPATH) + self.fix_rpathtype_entry(new_rpath, DT_RUNPATH) + + def fix_rpathtype_entry(self, new_rpath, entrynum): if isinstance(new_rpath, str): new_rpath = new_rpath.encode('utf8') - rp_off = self.get_rpath_offset() + rp_off = self.get_entry_offset(entrynum) if rp_off is None: if self.verbose: print('File does not have rpath. It should be a fully static executable.') @@ -272,12 +287,12 @@ class Elf(DataSizes): self.bf.write(new_rpath) self.bf.write(b'\0'*(len(old_rpath) - len(new_rpath) + 1)) if len(new_rpath) == 0: - self.remove_rpath_entry() + self.remove_rpath_entry(entrynum) - def remove_rpath_entry(self): + def remove_rpath_entry(self, entrynum): sec = self.find_section(b'.dynamic') for (i, entry) in enumerate(self.dynamic): - if entry.d_tag == DT_RPATH: + if entry.d_tag == entrynum: rpentry = self.dynamic[i] rpentry.d_tag = 0 self.dynamic = self.dynamic[:i] + self.dynamic[i+1:] + [rpentry] @@ -296,6 +311,7 @@ def run(args): e = Elf(args[0]) if len(args) == 1: e.print_rpath() + e.print_runpath() else: new_rpath = args[1] e.fix_rpath(new_rpath) diff --git a/mesonbuild/scripts/gtkdochelper.py b/mesonbuild/scripts/gtkdochelper.py index cc06f7b..0c87717 100644 --- a/mesonbuild/scripts/gtkdochelper.py +++ b/mesonbuild/scripts/gtkdochelper.py @@ -17,6 +17,7 @@ import sys, os import subprocess import shutil import argparse +from mesonbuild.mesonlib import MesonException from mesonbuild.scripts import destdir_join parser = argparse.ArgumentParser() @@ -31,6 +32,18 @@ parser.add_argument('--htmlargs', dest='htmlargs', default='') parser.add_argument('--scanargs', dest='scanargs', default='') parser.add_argument('--fixxrefargs', dest='fixxrefargs', default='') +def gtkdoc_run_check(cmd, cwd): + p = subprocess.Popen(cmd, cwd=cwd, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stde, stdo) = p.communicate() + if p.returncode != 0: + err_msg = ["{!r} failed with status {:d}".format(cmd[0], p.returncode)] + if stde: + err_msg.append(stde.decode(errors='ignore')) + if stdo: + err_msg.append(stdo.decode(errors='ignore')) + raise MesonException('\n'.join(err_msg)) + def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir, main_file, module, html_args, scan_args, fixxref_args): abs_src = os.path.join(source_root, src_subdir) @@ -39,10 +52,9 @@ def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir, scan_cmd = ['gtkdoc-scan', '--module=' + module, '--source-dir=' + abs_src] + scan_args -# print(scan_cmd) -# sys.exit(1) - subprocess.check_call(scan_cmd, - cwd=abs_out) + gtkdoc_run_check(scan_cmd, abs_out) + + # Make docbook files if main_file.endswith('sgml'): modeflag = '--sgml-mode' else: @@ -56,9 +68,9 @@ def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir, if len(main_file) > 0: # Yes, this is the flag even if the file is in xml. mkdb_cmd.append('--main-sgml-file=' + main_file) -# print(mkdb_cmd) -# sys.exit(1) - subprocess.check_call(mkdb_cmd, cwd=abs_out) + gtkdoc_run_check(mkdb_cmd, abs_out) + + # Make HTML documentation shutil.rmtree(htmldir, ignore_errors=True) try: os.mkdir(htmldir) @@ -73,15 +85,13 @@ def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir, else: mkhtml_cmd.append('%s-docs.xml' % module) # html gen must be run in the HTML dir -# print(mkhtml_cmd) -# sys.exit(1) - subprocess.check_call(mkhtml_cmd, cwd=os.path.join(abs_out, 'html'), shell=False) + gtkdoc_run_check(mkhtml_cmd, os.path.join(abs_out, 'html')) + + # Fix cross-references in HTML files fixref_cmd = ['gtkdoc-fixxref', '--module=' + module, '--module-dir=html'] + fixxref_args -# print(fixref_cmd) -# sys.exit(1) - subprocess.check_call(fixref_cmd, cwd=abs_out) + gtkdoc_run_check(fixref_cmd, abs_out) def install_gtkdoc(build_root, doc_subdir, install_prefix, datadir, module): source = os.path.join(build_root, doc_subdir, 'html') diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py index 3a87f2d..1924b95 100644 --- a/mesonbuild/scripts/meson_install.py +++ b/mesonbuild/scripts/meson_install.py @@ -19,6 +19,14 @@ from glob import glob from mesonbuild.scripts import depfixer from mesonbuild.scripts import destdir_join +install_log_file = None + +def append_to_log(line): + install_log_file.write(line) + if not line.endswith('\n'): + install_log_file.write('\n') + install_log_file.flush() + def do_copy(from_file, to_file): try: # Python's copyfile fails if the target file already exists. @@ -27,6 +35,7 @@ def do_copy(from_file, to_file): pass shutil.copyfile(from_file, to_file) shutil.copystat(from_file, to_file) + append_to_log(to_file) def do_install(datafilename): ifile = open(datafilename, 'rb') @@ -43,7 +52,7 @@ def do_install(datafilename): def install_subdirs(data): for (src_dir, inst_dir, dst_dir) in data.install_subdirs: - if src_dir.endswith('/'): + if src_dir.endswith('/') or src_dir.endswith('\\'): src_dir = src_dir[:-1] src_prefix = os.path.join(src_dir, inst_dir) print('Installing subdir %s to %s.' % (src_prefix, dst_dir)) @@ -78,6 +87,7 @@ def install_subdirs(data): os.mkdir(parent_dir) shutil.copystat(os.path.split(abs_src)[0], parent_dir) shutil.copy2(abs_src, abs_dst, follow_symlinks=False) + append_to_log(abs_dst) def install_data(d): for i in d.data: @@ -104,6 +114,7 @@ def install_man(d): if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'): open(outfilename, 'wb').write(gzip.compress(open(full_source_filename, 'rb').read())) shutil.copystat(full_source_filename, outfilename) + append_to_log(outfilename) else: do_copy(full_source_filename, outfilename) @@ -209,6 +220,7 @@ def install_targets(d): except FileNotFoundError: pass os.symlink(os.path.split(fname)[-1], symlinkfilename) + append_to_log(symlinkfilename) except (NotImplementedError, OSError): if not printed_symlink_error: print("Symlink creation does not work on this platform.") @@ -224,11 +236,19 @@ def install_targets(d): raise def run(args): + global install_log_file if len(args) != 1: print('Installer script for Meson. Do not run on your own, mmm\'kay?') print('meson_install.py [install info file]') datafilename = args[0] - do_install(datafilename) + private_dir = os.path.split(datafilename)[0] + log_dir = os.path.join(private_dir, '../meson-logs') + with open(os.path.join(log_dir, 'install-log.txt'), 'w') as lf: + install_log_file = lf + append_to_log('# List of files installed by Meson') + append_to_log('# Does not contain files installed by custom scripts.') + do_install(datafilename) + install_log_file = None return 0 if __name__ == '__main__': diff --git a/mesonbuild/scripts/regen_checker.py b/mesonbuild/scripts/regen_checker.py index 2e974d8..f65f3bd 100644 --- a/mesonbuild/scripts/regen_checker.py +++ b/mesonbuild/scripts/regen_checker.py @@ -33,25 +33,28 @@ def need_regen(regeninfo, regen_timestamp): Vs2010Backend.touch_regen_timestamp(regeninfo.build_dir) return False -def regen(regeninfo): +def regen(regeninfo, mesonscript, backend): scriptdir = os.path.split(__file__)[0] - mesonscript = os.path.join(scriptdir, '../../', 'meson') cmd = [sys.executable, mesonscript, '--internal', 'regenerate', regeninfo.build_dir, regeninfo.source_dir, - '--backend=vs2010'] + '--backend=' + backend] subprocess.check_call(cmd) def run(args): private_dir = args[0] dumpfile = os.path.join(private_dir, 'regeninfo.dump') + coredata = os.path.join(private_dir, 'coredata.dat') regeninfo = pickle.load(open(dumpfile, 'rb')) + coredata = pickle.load(open(coredata, 'rb')) + mesonscript = coredata.meson_script_file + backend = coredata.get_builtin_option('backend') regen_timestamp = os.stat(dumpfile).st_mtime if need_regen(regeninfo, regen_timestamp): - regen(regeninfo) + regen(regeninfo, mesonscript, backend) sys.exit(0) if __name__ == '__main__': diff --git a/run_cross_test.py b/run_cross_test.py index a788850..3abd288 100755 --- a/run_cross_test.py +++ b/run_cross_test.py @@ -34,7 +34,7 @@ meson_command = './meson.py' extra_flags = ['--cross-file', sys.argv[1]] ninja_command = environment.detect_ninja() if ninja_command is None: - raise RuntimeError('Could not find Ninja executable.') + raise RuntimeError('Could not find Ninja v1.6 or newer') compile_commands = [ninja_command] test_commands = [ninja_command, 'test'] install_commands = [ninja_command, 'install'] diff --git a/run_tests.py b/run_tests.py index 6f51a19..34258d8 100755 --- a/run_tests.py +++ b/run_tests.py @@ -116,52 +116,61 @@ def setup_commands(backend): backend_flags = [] ninja_command = environment.detect_ninja() if ninja_command is None: - raise RuntimeError('Could not find Ninja executable.') + raise RuntimeError('Could not find Ninja v1.6 or newer') if print_debug: compile_commands = [ninja_command, '-v'] else: compile_commands = [ninja_command] + compile_commands += ['-w', 'dupbuild=err'] test_commands = [ninja_command, 'test', 'benchmark'] install_commands = [ninja_command, 'install'] -def platform_fix_filename(fname): - if mesonlib.is_osx(): - if fname.endswith('.so'): - return fname[:-2] + 'dylib' - return fname.replace('.so.', '.dylib.') - elif mesonlib.is_windows(): - if fname.endswith('.so'): - (p, f) = os.path.split(fname) - f = f[3:-2] + 'dll' - return os.path.join(p, f) - if fname.endswith('.a'): - return fname[:-1] + 'lib' +def get_relative_files_list_from_dir(fromdir): + paths = [] + for (root, _, files) in os.walk(fromdir): + reldir = os.path.relpath(root, start=fromdir) + for f in files: + path = os.path.join(reldir, f).replace('\\', '/') + if path.startswith('./'): + path = path[2:] + paths.append(path) + return paths + +def platform_fix_exe_name(fname): + if not fname.endswith('?exe'): + return fname + fname = fname[:-4] + if mesonlib.is_windows(): + return fname + '.exe' return fname def validate_install(srcdir, installdir): - if mesonlib.is_windows(): - # Don't really know how Windows installs should work - # so skip. - return '' + # List of installed files info_file = os.path.join(srcdir, 'installed_files.txt') + # If this exists, the test does not install any other files + noinst_file = 'usr/no-installed-files' expected = {} found = {} - if os.path.exists(info_file): + ret_msg = '' + # Generate list of expected files + if os.path.exists(os.path.join(installdir, noinst_file)): + expected[noinst_file] = False + elif os.path.exists(info_file): for line in open(info_file): - expected[platform_fix_filename(line.strip())] = True - for root, _, files in os.walk(installdir): - for fname in files: - found_name = os.path.join(root, fname)[len(installdir)+1:] - found[found_name] = True - expected = set(expected) - found = set(found) - missing = expected - found - for fname in missing: - return 'Expected file %s missing.' % fname - extra = found - expected - for fname in extra: - return 'Found extra file %s.' % fname - return '' + expected[platform_fix_exe_name(line.strip())] = False + # Check if expected files were found + for fname in expected: + if os.path.exists(os.path.join(installdir, fname)): + expected[fname] = True + for (fname, found) in expected.items(): + if not found: + ret_msg += 'Expected file {0} missing.\n'.format(fname) + # Check if there are any unexpected files + found = get_relative_files_list_from_dir(installdir) + for fname in found: + if fname not in expected: + ret_msg += 'Extra file {0} found.\n'.format(fname) + return ret_msg def log_text_file(logfile, testdir, stdo, stde): global stop, executor, futures @@ -299,7 +308,7 @@ def detect_tests_to_run(): all_tests = [] all_tests.append(('common', gather_tests('test cases/common'), False)) all_tests.append(('failing', gather_tests('test cases/failing'), False)) - all_tests.append(('prebuilt object', gather_tests('test cases/prebuilt object'), False)) + all_tests.append(('prebuilt', gather_tests('test cases/prebuilt'), False)) all_tests.append(('platform-osx', gather_tests('test cases/osx'), False if mesonlib.is_osx() else True)) all_tests.append(('platform-windows', gather_tests('test cases/windows'), False if mesonlib.is_windows() else True)) @@ -396,27 +405,51 @@ def check_format(): fullname = os.path.join(root, file) check_file(fullname) -def generate_prebuilt_object(): - source = 'test cases/prebuilt object/1 basic/source.c' - objectbase = 'test cases/prebuilt object/1 basic/prebuilt.' - if shutil.which('cl'): - objectfile = objectbase + 'obj' - cmd = ['cl', '/nologo', '/Fo'+objectfile, '/c', source] +def pbcompile(compiler, source, objectfile): + if compiler == 'cl': + cmd = [compiler, '/nologo', '/Fo'+objectfile, '/c', source] else: - if mesonlib.is_windows(): - objectfile = objectbase + 'obj' - else: - objectfile = objectbase + 'o' - if shutil.which('cc'): - cmd = 'cc' - elif shutil.which('gcc'): - cmd = 'gcc' - else: - raise RuntimeError("Could not find C compiler.") - cmd = [cmd, '-c', source, '-o', objectfile] + cmd = [compiler, '-c', source, '-o', objectfile] subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + +def generate_pb_object(compiler, object_suffix): + source = 'test cases/prebuilt/1 object/source.c' + objectfile = 'test cases/prebuilt/1 object/prebuilt.' + object_suffix + pbcompile(compiler, source, objectfile) return objectfile +def generate_pb_static(compiler, object_suffix, static_suffix): + source = 'test cases/prebuilt/2 static/libdir/best.c' + objectfile = 'test cases/prebuilt/2 static/libdir/best.' + object_suffix + stlibfile = 'test cases/prebuilt/2 static/libdir/libbest.' + static_suffix + pbcompile(compiler, source, objectfile) + if compiler == 'cl': + linker = ['lib', '/NOLOGO', '/OUT:' + stlibfile, objectfile] + else: + linker = ['ar', 'csr', stlibfile, objectfile] + subprocess.check_call(linker) + os.unlink(objectfile) + return stlibfile + +def generate_prebuilt(): + static_suffix = 'a' + if shutil.which('cl'): + compiler = 'cl' + static_suffix = 'lib' + elif shutil.which('cc'): + compiler = 'cc' + elif shutil.which('gcc'): + compiler = 'gcc' + else: + raise RuntimeError("Could not find C compiler.") + if mesonlib.is_windows(): + object_suffix = 'obj' + else: + object_suffix = 'o' + objectfile = generate_pb_object(compiler, object_suffix) + stlibfile = generate_pb_static(compiler, object_suffix, static_suffix) + return (objectfile, stlibfile) + if __name__ == '__main__': parser = argparse.ArgumentParser(description="Run the test suite of Meson.") parser.add_argument('extra_args', nargs='*', @@ -430,12 +463,13 @@ if __name__ == '__main__': if script_dir != '': os.chdir(script_dir) check_format() - pbfile = generate_prebuilt_object() + pbfiles = generate_prebuilt() try: run_tests(options.extra_args) except StopException: pass - os.unlink(pbfile) + for f in pbfiles: + os.unlink(f) print('\nTotal passed tests:', passing_tests) print('Total failed tests:', failing_tests) print('Total skipped tests:', skipped_tests) diff --git a/test cases/common/114 multiple dir configure file/meson.build b/test cases/common/114 multiple dir configure file/meson.build new file mode 100644 index 0000000..180227c --- /dev/null +++ b/test cases/common/114 multiple dir configure file/meson.build @@ -0,0 +1,7 @@ +project('multiple dir configure file', 'c') + +subdir('subdir') + +configure_file(input : 'subdir/someinput.in', + output : 'outputhere', + configuration : configuration_data()) diff --git a/test cases/common/114 multiple dir configure file/subdir/meson.build b/test cases/common/114 multiple dir configure file/subdir/meson.build new file mode 100644 index 0000000..a8f731d --- /dev/null +++ b/test cases/common/114 multiple dir configure file/subdir/meson.build @@ -0,0 +1,4 @@ +configure_file(input : 'someinput.in', + output : 'outputsubdir', + install : false, + configuration : configuration_data()) diff --git a/test cases/common/114 multiple dir configure file/subdir/someinput.in b/test cases/common/114 multiple dir configure file/subdir/someinput.in new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/common/114 multiple dir configure file/subdir/someinput.in diff --git a/test cases/common/115 spaces backslash/asm output/meson.build b/test cases/common/115 spaces backslash/asm output/meson.build new file mode 100644 index 0000000..b5f13f5 --- /dev/null +++ b/test cases/common/115 spaces backslash/asm output/meson.build @@ -0,0 +1,2 @@ +configure_file(output : 'blank.txt', configuration : configuration_data()) + diff --git a/test cases/common/115 spaces backslash/comparer-end-notstring.c b/test cases/common/115 spaces backslash/comparer-end-notstring.c new file mode 100644 index 0000000..65bf8bc --- /dev/null +++ b/test cases/common/115 spaces backslash/comparer-end-notstring.c @@ -0,0 +1,20 @@ +#include "comparer.h" + +#ifndef COMPARER_INCLUDED +#error "comparer.h not included" +#endif + +/* This converts foo\\\\bar\\\\ to "foo\\bar\\" (string literal) */ +#define Q(x) #x +#define QUOTE(x) Q(x) + +#define COMPARE_WITH "foo\\bar\\" /* This is the literal `foo\bar\` */ + +int main(int argc, char **argv) { + if(strcmp(QUOTE(DEF_WITH_BACKSLASH), COMPARE_WITH)) { + printf("Arg string is quoted incorrectly: %s instead of %s\n", + QUOTE(DEF_WITH_BACKSLASH), COMPARE_WITH); + return 1; + } + return 0; +} diff --git a/test cases/common/115 spaces backslash/comparer-end.c b/test cases/common/115 spaces backslash/comparer-end.c new file mode 100644 index 0000000..fef25a5 --- /dev/null +++ b/test cases/common/115 spaces backslash/comparer-end.c @@ -0,0 +1,16 @@ +#include "comparer.h" + +#ifndef COMPARER_INCLUDED +#error "comparer.h not included" +#endif + +#define COMPARE_WITH "foo\\bar\\" /* This is `foo\bar\` */ + +int main (int argc, char **argv) { + if (strcmp (DEF_WITH_BACKSLASH, COMPARE_WITH)) { + printf ("Arg string is quoted incorrectly: %s vs %s\n", + DEF_WITH_BACKSLASH, COMPARE_WITH); + return 1; + } + return 0; +} diff --git a/test cases/common/115 spaces backslash/comparer.c b/test cases/common/115 spaces backslash/comparer.c new file mode 100644 index 0000000..937cb47 --- /dev/null +++ b/test cases/common/115 spaces backslash/comparer.c @@ -0,0 +1,16 @@ +#include "comparer.h" + +#ifndef COMPARER_INCLUDED +#error "comparer.h not included" +#endif + +#define COMPARE_WITH "foo\\bar" /* This is the literal `foo\bar` */ + +int main (int argc, char **argv) { + if (strcmp (DEF_WITH_BACKSLASH, COMPARE_WITH)) { + printf ("Arg string is quoted incorrectly: %s instead of %s\n", + DEF_WITH_BACKSLASH, COMPARE_WITH); + return 1; + } + return 0; +} diff --git a/test cases/common/115 spaces backslash/include/comparer.h b/test cases/common/115 spaces backslash/include/comparer.h new file mode 100644 index 0000000..624d96c --- /dev/null +++ b/test cases/common/115 spaces backslash/include/comparer.h @@ -0,0 +1,4 @@ +#include <string.h> +#include <stdio.h> + +#define COMPARER_INCLUDED diff --git a/test cases/common/115 spaces backslash/meson.build b/test cases/common/115 spaces backslash/meson.build new file mode 100644 index 0000000..bf614e8 --- /dev/null +++ b/test cases/common/115 spaces backslash/meson.build @@ -0,0 +1,28 @@ +project('comparer', 'c') + +# Added manually as a c_arg to test handling of include paths with backslashes +# and spaces. This is especially useful on Windows in vcxproj files since it +# stores include directories in a separate element that has its own +# context-specific escaping/quoting. +include_dir = meson.current_source_dir() + '/include' +default_c_args = ['-I' + include_dir] + +if meson.get_compiler('c').get_id() == 'msvc' + default_c_args += ['/Faasm output\\'] + # Hack to create the 'asm output' directory in the builddir + subdir('asm output') +endif + +# Path can contain \. Here we're sending `"foo\bar"`. +test('backslash quoting', + executable('comparer', 'comparer.c', + c_args : default_c_args + ['-DDEF_WITH_BACKSLASH="foo\\bar"'])) +# Path can end in \ without any special quoting. Here we send `"foo\bar\"`. +test('backslash end quoting', + executable('comparer-end', 'comparer-end.c', + c_args : default_c_args + ['-DDEF_WITH_BACKSLASH="foo\\bar\\"'])) +# Path can (really) end in \ if we're not passing a string literal without any +# special quoting. Here we're sending `foo\bar\`. +test('backslash end quoting when not a string literal', + executable('comparer-end-notstring', 'comparer-end-notstring.c', + c_args : default_c_args + ['-DDEF_WITH_BACKSLASH=foo\\bar\\'])) diff --git a/test cases/common/116 ternary/meson.build b/test cases/common/116 ternary/meson.build new file mode 100644 index 0000000..3e65046 --- /dev/null +++ b/test cases/common/116 ternary/meson.build @@ -0,0 +1,7 @@ +project('ternary operator', 'c') + +one = true ? 1 : error('False branch should not be evaluated') +two = false ? error('True branch should not be evaluated.') : 2 + +assert(one == 1, 'Return value from ternary true is wrong.') +assert(two == 2, 'Return value from ternary false is wrong.') diff --git a/test cases/common/27 library versions/installed_files.txt b/test cases/common/27 library versions/installed_files.txt index a218d29..e10d1dd 100644 --- a/test cases/common/27 library versions/installed_files.txt +++ b/test cases/common/27 library versions/installed_files.txt @@ -1,3 +1 @@ -usr/lib/libsomelib.so -usr/lib/libsomelib.so.0 -usr/lib/libsomelib.so.1.2.3 +usr/lib/prefixsomelib.suffix diff --git a/test cases/common/27 library versions/meson.build b/test cases/common/27 library versions/meson.build index cebcd64..2e2bef7 100644 --- a/test cases/common/27 library versions/meson.build +++ b/test cases/common/27 library versions/meson.build @@ -1,6 +1,9 @@ project('library versions', 'c') -lib = shared_library('somelib', 'lib.c', \ -version : '1.2.3', \ -soversion : '0', \ -install : true) +shared_library('somelib', 'lib.c', + name_prefix : 'prefix', + name_suffix : 'suffix', + install_dir : 'lib', + install : true) + +subdir('subdir') diff --git a/test cases/common/27 library versions/subdir/meson.build b/test cases/common/27 library versions/subdir/meson.build new file mode 100644 index 0000000..b51033d --- /dev/null +++ b/test cases/common/27 library versions/subdir/meson.build @@ -0,0 +1,8 @@ +# Test that using files generated with configure_file as sources works. +# We do this inside a subdir so that the path isn't accidentally correct +# because there is no structure in the build dir. +genlib = configure_file(input : '../lib.c', + output : 'genlib.c', + configuration : configuration_data()) +shared_library('genlib', genlib, + install : false) diff --git a/test cases/common/46 library chain/installed_files.txt b/test cases/common/46 library chain/installed_files.txt index 8d38fd9..c7dab9f 100644 --- a/test cases/common/46 library chain/installed_files.txt +++ b/test cases/common/46 library chain/installed_files.txt @@ -1,4 +1 @@ -usr/bin/prog -usr/lib/liblib1.so -usr/lib/liblib2.so -usr/lib/liblib3.so +usr/bin/prog?exe diff --git a/test cases/common/46 library chain/subdir/meson.build b/test cases/common/46 library chain/subdir/meson.build index c165abe..ab71bda 100644 --- a/test cases/common/46 library chain/subdir/meson.build +++ b/test cases/common/46 library chain/subdir/meson.build @@ -1,4 +1,4 @@ subdir('subdir2') subdir('subdir3') -lib1 = shared_library('lib1', 'lib1.c', install : true, link_with : [lib2, lib3]) +lib1 = shared_library('lib1', 'lib1.c', install : false, link_with : [lib2, lib3]) diff --git a/test cases/common/46 library chain/subdir/subdir2/meson.build b/test cases/common/46 library chain/subdir/subdir2/meson.build index 2281e4b..befd94d 100644 --- a/test cases/common/46 library chain/subdir/subdir2/meson.build +++ b/test cases/common/46 library chain/subdir/subdir2/meson.build @@ -1 +1 @@ -lib2 = shared_library('lib2', 'lib2.c', install : true) +lib2 = shared_library('lib2', 'lib2.c', install : false) diff --git a/test cases/common/46 library chain/subdir/subdir3/meson.build b/test cases/common/46 library chain/subdir/subdir3/meson.build index f049c79..7bd249a 100644 --- a/test cases/common/46 library chain/subdir/subdir3/meson.build +++ b/test cases/common/46 library chain/subdir/subdir3/meson.build @@ -1 +1 @@ -lib3 = shared_library('lib3', 'lib3.c', install : true) +lib3 = shared_library('lib3', 'lib3.c', install : false) diff --git a/test cases/common/49 subproject/installed_files.txt b/test cases/common/49 subproject/installed_files.txt index f2e1e27..dc09fb7 100644 --- a/test cases/common/49 subproject/installed_files.txt +++ b/test cases/common/49 subproject/installed_files.txt @@ -1,3 +1,2 @@ -usr/bin/user -usr/lib/libsublib.so +usr/bin/user?exe usr/share/sublib/sublib.depmf diff --git a/test cases/common/49 subproject/subprojects/sublib/meson.build b/test cases/common/49 subproject/subprojects/sublib/meson.build index 269f815..acaf1bf 100644 --- a/test cases/common/49 subproject/subprojects/sublib/meson.build +++ b/test cases/common/49 subproject/subprojects/sublib/meson.build @@ -13,7 +13,7 @@ if meson.project_version() != '1.0.0' endif i = include_directories('include') -l = shared_library('sublib', 'sublib.c', include_directories : i, install : true, +l = shared_library('sublib', 'sublib.c', include_directories : i, install : false, c_args : '-DBUILDING_SUB=2') t = executable('simpletest', 'simpletest.c', include_directories : i, link_with : l) test('plain', t) diff --git a/test cases/common/51 pkgconfig-gen/installed_files.txt b/test cases/common/51 pkgconfig-gen/installed_files.txt index cc5d34b..d6a23d7 100644 --- a/test cases/common/51 pkgconfig-gen/installed_files.txt +++ b/test cases/common/51 pkgconfig-gen/installed_files.txt @@ -1,3 +1,2 @@ usr/include/simple.h -usr/lib/libsimple.so usr/lib/pkgconfig/simple.pc diff --git a/test cases/common/51 pkgconfig-gen/meson.build b/test cases/common/51 pkgconfig-gen/meson.build index 4044b3d..b5f6921 100644 --- a/test cases/common/51 pkgconfig-gen/meson.build +++ b/test cases/common/51 pkgconfig-gen/meson.build @@ -2,7 +2,7 @@ project('pkgconfig-gen', 'c') pkgg = import('pkgconfig') -lib = shared_library('simple', 'simple.c', install : true) +lib = shared_library('simple', 'simple.c') libver = '1.0' h = install_headers('simple.h') diff --git a/test cases/common/52 custom install dirs/installed_files.txt b/test cases/common/52 custom install dirs/installed_files.txt index dc495e2..1b8b561 100644 --- a/test cases/common/52 custom install dirs/installed_files.txt +++ b/test cases/common/52 custom install dirs/installed_files.txt @@ -1,4 +1,4 @@ -usr/dib/dab/dub/prog +usr/dib/dab/dub/prog?exe usr/some/dir/sample.h usr/woman/prog.1.gz usr/meow/datafile.cat diff --git a/test cases/common/6 linkshared/installed_files.txt b/test cases/common/6 linkshared/installed_files.txt index 2a4c330..c7dab9f 100644 --- a/test cases/common/6 linkshared/installed_files.txt +++ b/test cases/common/6 linkshared/installed_files.txt @@ -1,2 +1 @@ -usr/bin/prog -usr/lib/libmylib.so +usr/bin/prog?exe diff --git a/test cases/common/6 linkshared/meson.build b/test cases/common/6 linkshared/meson.build index 1ecb052..846b4a0 100644 --- a/test cases/common/6 linkshared/meson.build +++ b/test cases/common/6 linkshared/meson.build @@ -2,7 +2,7 @@ project('shared library linking test', 'c', 'cpp') lib = shared_library('mylib', 'libfile.c' # Split to different lines before and after the comma to test parser. - , install : true) + , install : false) # Don't install libraries in common tests; the path is platform-specific exe = executable('prog', 'main.c', link_with : lib, install : true) test('runtest', exe) diff --git a/test cases/common/60 install script/installed_files.txt b/test cases/common/60 install script/installed_files.txt index 58315f9..94e3164 100644 --- a/test cases/common/60 install script/installed_files.txt +++ b/test cases/common/60 install script/installed_files.txt @@ -1,2 +1,2 @@ -usr/bin/prog +usr/bin/prog?exe usr/diiba/daaba/file.dat diff --git a/test cases/common/60 install script/meson.build b/test cases/common/60 install script/meson.build index 66db0a7..6cab840 100644 --- a/test cases/common/60 install script/meson.build +++ b/test cases/common/60 install script/meson.build @@ -1,8 +1,8 @@ project('custom install script', 'c') if meson.get_compiler('c').get_id() == 'msvc' - meson.add_install_script('myinstall.bat') + install_data('no-installed-files', install_dir : '') else meson.add_install_script('myinstall.sh') + executable('prog', 'prog.c', install : true) endif -executable('prog', 'prog.c', install : true) diff --git a/test cases/common/60 install script/myinstall.bat b/test cases/common/60 install script/myinstall.bat deleted file mode 100644 index 7036077..0000000 --- a/test cases/common/60 install script/myinstall.bat +++ /dev/null @@ -1,3 +0,0 @@ -@ECHO OFF - -echo At this point we could do something. diff --git a/test cases/common/60 install script/myinstall.sh b/test cases/common/60 install script/myinstall.sh index 42a415e..79512c9 100755 --- a/test cases/common/60 install script/myinstall.sh +++ b/test cases/common/60 install script/myinstall.sh @@ -4,9 +4,7 @@ set -eu echo Starting custom installation step -# These commands fail on Windows, but we don't really care. - mkdir -p "${DESTDIR}${MESON_INSTALL_PREFIX}/diiba/daaba" touch "${DESTDIR}${MESON_INSTALL_PREFIX}/diiba/daaba/file.dat" -echo Finishing custom install step +echo Finished custom install step diff --git a/test cases/common/60 install script/no-installed-files b/test cases/common/60 install script/no-installed-files new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/common/60 install script/no-installed-files diff --git a/test cases/common/67 foreach/installed_files.txt b/test cases/common/67 foreach/installed_files.txt index 36c6386..2930ff0 100644 --- a/test cases/common/67 foreach/installed_files.txt +++ b/test cases/common/67 foreach/installed_files.txt @@ -1,3 +1,3 @@ -usr/bin/prog1 -usr/bin/prog2 -usr/bin/prog3 +usr/bin/prog1?exe +usr/bin/prog2?exe +usr/bin/prog3?exe diff --git a/test cases/common/76 configure file in custom target/src/meson.build b/test cases/common/76 configure file in custom target/src/meson.build index b049e95..cbbce5c 100644 --- a/test cases/common/76 configure file in custom target/src/meson.build +++ b/test cases/common/76 configure file in custom target/src/meson.build @@ -2,3 +2,19 @@ custom_target('thing', output : 'final.dat', input : cfile, command : [find_program('mycompiler.py'), '@INPUT@', '@OUTPUT@']) + +# Test usage of a `configure_file` as part of the command list +py3 = find_program('python3', required : false) +if not py3.found() + # Maybe 'python' is Python 3 + py3 = find_program('python') +endif + +compiler = configure_file(input : 'mycompiler.py', + output : 'mycompiler2.py', + configuration : configuration_data()) + +custom_target('thing2', +output : 'final2.dat', +input : cfile, +command : [py3, compiler, '@INPUT@', '@OUTPUT@']) diff --git a/test cases/common/8 install/installed_files.txt b/test cases/common/8 install/installed_files.txt index 1e9166b..cbbdc03 100644 --- a/test cases/common/8 install/installed_files.txt +++ b/test cases/common/8 install/installed_files.txt @@ -1,3 +1,2 @@ -usr/bin/prog -usr/lib/libshar.so -usr/lib/libstat.a +usr/bin/prog?exe +usr/libtest/libstat.a diff --git a/test cases/common/8 install/meson.build b/test cases/common/8 install/meson.build index 677a2ee..12ad389 100644 --- a/test cases/common/8 install/meson.build +++ b/test cases/common/8 install/meson.build @@ -1,5 +1,4 @@ -project('install test', 'c') +project('install test', 'c', default_options : ['libdir=libtest']) stlib = static_library('stat', 'stat.c', install : true) -shlib = shared_library('shar', 'shar.c', install : true) exe = executable('prog', 'prog.c', install : true) diff --git a/test cases/common/8 install/shar.c b/test cases/common/8 install/shar.c deleted file mode 100644 index 9c816a6..0000000 --- a/test cases/common/8 install/shar.c +++ /dev/null @@ -1 +0,0 @@ -int func() { return 15; } diff --git a/test cases/common/86 same basename/meson.build b/test cases/common/86 same basename/meson.build index e320f95..3dc384e 100644 --- a/test cases/common/86 same basename/meson.build +++ b/test cases/common/86 same basename/meson.build @@ -5,10 +5,9 @@ project('same basename', 'c') # then the .o files will clobber each other. shlib = shared_library('name', 'lib.c', c_args : '-DSHAR') -# On Windows a static lib is a foo.lib but a share library -# is both a foo.dll and a foo.lib. Put static in subdir to avoid -# name clashes. -subdir('sub') +# On Windows a static lib is now libfoo.a, so it does not conflict with foo.lib +# from the shared library above +stlib = static_library('name', 'lib.c', c_args : '-DSTAT') exe1 = executable('name', 'exe1.c', link_with : stlib) exe2 = executable('name2', 'exe2.c', link_with : shlib) diff --git a/test cases/common/86 same basename/sub/meson.build b/test cases/common/86 same basename/sub/meson.build deleted file mode 100644 index 07250a5..0000000 --- a/test cases/common/86 same basename/sub/meson.build +++ /dev/null @@ -1 +0,0 @@ -stlib = static_library('name', '../lib.c', c_args : '-DSTAT') diff --git a/test cases/csharp/2 library/installed_files.txt b/test cases/csharp/2 library/installed_files.txt index b830634..4ebea55 100644 --- a/test cases/csharp/2 library/installed_files.txt +++ b/test cases/csharp/2 library/installed_files.txt @@ -1,2 +1,2 @@ usr/bin/prog.exe -usr/lib/libhelper.dll +usr/lib/helper.dll diff --git a/test cases/failing/24 backslash/comparer.c b/test cases/failing/24 backslash/comparer.c deleted file mode 100644 index f562f5e..0000000 --- a/test cases/failing/24 backslash/comparer.c +++ /dev/null @@ -1,10 +0,0 @@ -#include<string.h> -#include<stdio.h> - -int main(int argc, char **argv) { - if(strcmp(DEF_WITH_BACKSLASH, "foo\\bar")) { - printf("Arg string is quoted incorrectly: %s\n", DEF_WITH_BACKSLASH); - return 1; - } - return 0; -} diff --git a/test cases/failing/24 backslash/meson.build b/test cases/failing/24 backslash/meson.build deleted file mode 100644 index dba891e..0000000 --- a/test cases/failing/24 backslash/meson.build +++ /dev/null @@ -1,3 +0,0 @@ -project('comparer', 'c') - -test('backslash quoting', executable('comparer', 'comparer.c', c_args : '-DDEF_WITH_BACKSLASH="foo\\bar"')) diff --git a/test cases/failing/30 nested ternary/meson.build b/test cases/failing/30 nested ternary/meson.build new file mode 100644 index 0000000..f9c2e5f --- /dev/null +++ b/test cases/failing/30 nested ternary/meson.build @@ -0,0 +1,3 @@ +project('nested ternary', 'c') + +x = true ? (false ? 1 : 0) : 2 diff --git a/test cases/linuxlike/7 library versions/installed_files.txt b/test cases/linuxlike/7 library versions/installed_files.txt new file mode 100644 index 0000000..b997e53 --- /dev/null +++ b/test cases/linuxlike/7 library versions/installed_files.txt @@ -0,0 +1,9 @@ +usr/lib/libsome.so +usr/lib/libsome.so.0 +usr/lib/libsome.so.1.2.3 +usr/lib/libnoversion.so +usr/lib/libonlyversion.so +usr/lib/libonlyversion.so.1 +usr/lib/libonlyversion.so.1.4.5 +usr/lib/libonlysoversion.so +usr/lib/libonlysoversion.so.5 diff --git a/test cases/linuxlike/7 library versions/lib.c b/test cases/linuxlike/7 library versions/lib.c new file mode 100644 index 0000000..67b6f4d --- /dev/null +++ b/test cases/linuxlike/7 library versions/lib.c @@ -0,0 +1,3 @@ +int myFunc() { + return 55; +} diff --git a/test cases/linuxlike/7 library versions/meson.build b/test cases/linuxlike/7 library versions/meson.build new file mode 100644 index 0000000..504aa4e --- /dev/null +++ b/test cases/linuxlike/7 library versions/meson.build @@ -0,0 +1,18 @@ +project('library versions', 'c') + +shared_library('some', 'lib.c', + version : '1.2.3', + soversion : '0', + install : true) + +shared_library('noversion', 'lib.c', + install : true) + +shared_library('onlyversion', 'lib.c', + version : '1.4.5', + install : true) + +shared_library('onlysoversion', 'lib.c', + # Also test that int soversion is acceptable + soversion : 5, + install : true) diff --git a/test cases/linuxlike/8 subproject library install/installed_files.txt b/test cases/linuxlike/8 subproject library install/installed_files.txt new file mode 100644 index 0000000..5c4a301 --- /dev/null +++ b/test cases/linuxlike/8 subproject library install/installed_files.txt @@ -0,0 +1,3 @@ +usr/lib/libsublib.so +usr/lib/libsublib.so.5 +usr/lib/libsublib.so.2.1.0 diff --git a/test cases/linuxlike/8 subproject library install/meson.build b/test cases/linuxlike/8 subproject library install/meson.build new file mode 100644 index 0000000..63e57cf --- /dev/null +++ b/test cases/linuxlike/8 subproject library install/meson.build @@ -0,0 +1,6 @@ +project('subproj lib install', 'c', + version : '2.3.4', + license : 'mylicense') + +# Test that the subproject library gets installed +subproject('sublib', version : '1.0.0') diff --git a/test cases/linuxlike/8 subproject library install/subprojects/sublib/include/subdefs.h b/test cases/linuxlike/8 subproject library install/subprojects/sublib/include/subdefs.h new file mode 100644 index 0000000..681c7b8 --- /dev/null +++ b/test cases/linuxlike/8 subproject library install/subprojects/sublib/include/subdefs.h @@ -0,0 +1,21 @@ +#ifndef SUBDEFS_H_ +#define SUBDEFS_H_ + +#if defined _WIN32 || defined __CYGWIN__ +#if defined BUILDING_SUB + #define DLL_PUBLIC __declspec(dllexport) +#else + #define DLL_PUBLIC __declspec(dllimport) +#endif +#else + #if defined __GNUC__ + #define DLL_PUBLIC __attribute__ ((visibility("default"))) + #else + #pragma message ("Compiler does not support symbol visibility.") + #define DLL_PUBLIC + #endif +#endif + +int DLL_PUBLIC subfunc(); + +#endif diff --git a/test cases/linuxlike/8 subproject library install/subprojects/sublib/meson.build b/test cases/linuxlike/8 subproject library install/subprojects/sublib/meson.build new file mode 100644 index 0000000..97658fa --- /dev/null +++ b/test cases/linuxlike/8 subproject library install/subprojects/sublib/meson.build @@ -0,0 +1,10 @@ +project('subproject', 'c', + version : '1.0.0', + license : ['sublicense1', 'sublicense2']) + +i = include_directories('include') +shared_library('sublib', 'sublib.c', + version : '2.1.0', + soversion : 5, + include_directories : i, install : true, + c_args : '-DBUILDING_SUB=2') diff --git a/test cases/linuxlike/8 subproject library install/subprojects/sublib/sublib.c b/test cases/linuxlike/8 subproject library install/subprojects/sublib/sublib.c new file mode 100644 index 0000000..c13326b --- /dev/null +++ b/test cases/linuxlike/8 subproject library install/subprojects/sublib/sublib.c @@ -0,0 +1,5 @@ +#include<subdefs.h> + +int DLL_PUBLIC subfunc() { + return 42; +} diff --git a/test cases/osx/2 library versions/installed_files.txt b/test cases/osx/2 library versions/installed_files.txt new file mode 100644 index 0000000..66470ab --- /dev/null +++ b/test cases/osx/2 library versions/installed_files.txt @@ -0,0 +1,4 @@ +usr/lib/libsome.0.dylib +usr/lib/libnoversion.dylib +usr/lib/libonlyversion.1.dylib +usr/lib/libonlysoversion.5.dylib diff --git a/test cases/osx/2 library versions/lib.c b/test cases/osx/2 library versions/lib.c new file mode 100644 index 0000000..67b6f4d --- /dev/null +++ b/test cases/osx/2 library versions/lib.c @@ -0,0 +1,3 @@ +int myFunc() { + return 55; +} diff --git a/test cases/osx/2 library versions/meson.build b/test cases/osx/2 library versions/meson.build new file mode 100644 index 0000000..504aa4e --- /dev/null +++ b/test cases/osx/2 library versions/meson.build @@ -0,0 +1,18 @@ +project('library versions', 'c') + +shared_library('some', 'lib.c', + version : '1.2.3', + soversion : '0', + install : true) + +shared_library('noversion', 'lib.c', + install : true) + +shared_library('onlyversion', 'lib.c', + version : '1.4.5', + install : true) + +shared_library('onlysoversion', 'lib.c', + # Also test that int soversion is acceptable + soversion : 5, + install : true) diff --git a/test cases/prebuilt object/1 basic/main.c b/test cases/prebuilt/1 object/main.c index 480bda5..480bda5 100644 --- a/test cases/prebuilt object/1 basic/main.c +++ b/test cases/prebuilt/1 object/main.c diff --git a/test cases/prebuilt object/1 basic/meson.build b/test cases/prebuilt/1 object/meson.build index 92f966b..92f966b 100644 --- a/test cases/prebuilt object/1 basic/meson.build +++ b/test cases/prebuilt/1 object/meson.build diff --git a/test cases/prebuilt object/1 basic/source.c b/test cases/prebuilt/1 object/source.c index f39b4f3..f39b4f3 100644 --- a/test cases/prebuilt object/1 basic/source.c +++ b/test cases/prebuilt/1 object/source.c diff --git a/test cases/prebuilt/2 static/libdir/best.c b/test cases/prebuilt/2 static/libdir/best.c new file mode 100644 index 0000000..ab774e1 --- /dev/null +++ b/test cases/prebuilt/2 static/libdir/best.c @@ -0,0 +1,3 @@ +const char *msg() { + return "I am the best."; +} diff --git a/test cases/prebuilt/2 static/libdir/best.h b/test cases/prebuilt/2 static/libdir/best.h new file mode 100644 index 0000000..063017f --- /dev/null +++ b/test cases/prebuilt/2 static/libdir/best.h @@ -0,0 +1,3 @@ +#pragma once + +const char *msg(); diff --git a/test cases/prebuilt/2 static/libdir/meson.build b/test cases/prebuilt/2 static/libdir/meson.build new file mode 100644 index 0000000..8d74ccf --- /dev/null +++ b/test cases/prebuilt/2 static/libdir/meson.build @@ -0,0 +1,5 @@ +cc = meson.get_compiler('c') +stlib = cc.find_library('best', dirs : meson.current_source_dir()) + +best_dep = declare_dependency(dependencies : stlib, + include_directories : include_directories('.')) diff --git a/test cases/prebuilt/2 static/main.c b/test cases/prebuilt/2 static/main.c new file mode 100644 index 0000000..d172625 --- /dev/null +++ b/test cases/prebuilt/2 static/main.c @@ -0,0 +1,7 @@ +#include<stdio.h> +#include<best.h> + +int main(int argc, char **argv) { + printf("%s\n", msg()); + return 0; +} diff --git a/test cases/prebuilt/2 static/meson.build b/test cases/prebuilt/2 static/meson.build new file mode 100644 index 0000000..9ea1d0d --- /dev/null +++ b/test cases/prebuilt/2 static/meson.build @@ -0,0 +1,5 @@ +project('prebuilt static lib', 'c') + +subdir('libdir') + +test('static', executable('mainprog', 'main.c', dependencies : best_dep)) diff --git a/test cases/rust/1 basic/installed_files.txt b/test cases/rust/1 basic/installed_files.txt index 61eda27..c7dab9f 100644 --- a/test cases/rust/1 basic/installed_files.txt +++ b/test cases/rust/1 basic/installed_files.txt @@ -1 +1 @@ -usr/bin/prog +usr/bin/prog?exe diff --git a/test cases/rust/2 sharedlib/installed_files.txt b/test cases/rust/2 sharedlib/installed_files.txt index 5a7c7d6..85acff2 100644 --- a/test cases/rust/2 sharedlib/installed_files.txt +++ b/test cases/rust/2 sharedlib/installed_files.txt @@ -1,2 +1,2 @@ -usr/bin/prog +usr/bin/prog?exe usr/lib/libstuff.rlib diff --git a/test cases/rust/3 staticlib/installed_files.txt b/test cases/rust/3 staticlib/installed_files.txt index 5a7c7d6..85acff2 100644 --- a/test cases/rust/3 staticlib/installed_files.txt +++ b/test cases/rust/3 staticlib/installed_files.txt @@ -1,2 +1,2 @@ -usr/bin/prog +usr/bin/prog?exe usr/lib/libstuff.rlib diff --git a/test cases/windows/7 mingw dll versioning/installed_files.txt b/test cases/windows/7 mingw dll versioning/installed_files.txt new file mode 100644 index 0000000..8c2a8f2 --- /dev/null +++ b/test cases/windows/7 mingw dll versioning/installed_files.txt @@ -0,0 +1,4 @@ +usr/bin/libsome-0.dll +usr/lib/libsome.dll.a +usr/bin/libnoversion.dll +usr/lib/libnoversion.dll.a diff --git a/test cases/windows/7 mingw dll versioning/lib.c b/test cases/windows/7 mingw dll versioning/lib.c new file mode 100644 index 0000000..cf7dfdd --- /dev/null +++ b/test cases/windows/7 mingw dll versioning/lib.c @@ -0,0 +1,6 @@ +#ifdef _WIN32 +__declspec(dllexport) +#endif +int myFunc() { + return 55; +} diff --git a/test cases/windows/7 mingw dll versioning/meson.build b/test cases/windows/7 mingw dll versioning/meson.build new file mode 100644 index 0000000..23a3343 --- /dev/null +++ b/test cases/windows/7 mingw dll versioning/meson.build @@ -0,0 +1,17 @@ +project('mingw dll versioning', 'c') + +cc = meson.get_compiler('c') + +# Test that MinGW/GCC creates correctly-named dll files and dll.a files, +# and also installs them in the right place +if cc.get_id() != 'msvc' + shared_library('some', 'lib.c', + version : '1.2.3', + soversion : '0', + install : true) + + shared_library('noversion', 'lib.c', + install : true) +else + install_data('no-installed-files', install_dir : '') +endif diff --git a/test cases/windows/7 mingw dll versioning/no-installed-files b/test cases/windows/7 mingw dll versioning/no-installed-files new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/windows/7 mingw dll versioning/no-installed-files diff --git a/test cases/windows/8 msvc dll versioning/installed_files.txt b/test cases/windows/8 msvc dll versioning/installed_files.txt new file mode 100644 index 0000000..5f6e26a --- /dev/null +++ b/test cases/windows/8 msvc dll versioning/installed_files.txt @@ -0,0 +1,4 @@ +usr/bin/some-0.dll +usr/lib/some.lib +usr/bin/noversion.dll +usr/lib/noversion.lib diff --git a/test cases/windows/8 msvc dll versioning/lib.c b/test cases/windows/8 msvc dll versioning/lib.c new file mode 100644 index 0000000..cf7dfdd --- /dev/null +++ b/test cases/windows/8 msvc dll versioning/lib.c @@ -0,0 +1,6 @@ +#ifdef _WIN32 +__declspec(dllexport) +#endif +int myFunc() { + return 55; +} diff --git a/test cases/windows/8 msvc dll versioning/meson.build b/test cases/windows/8 msvc dll versioning/meson.build new file mode 100644 index 0000000..0c36173 --- /dev/null +++ b/test cases/windows/8 msvc dll versioning/meson.build @@ -0,0 +1,16 @@ +project('msvc dll versioning', 'c') + +cc = meson.get_compiler('c') + +# Test that MSVC creates foo-0.dll and bar.dll +if cc.get_id() == 'msvc' + shared_library('some', 'lib.c', + version : '1.2.3', + soversion : '0', + install : true) + + shared_library('noversion', 'lib.c', + install : true) +else + install_data('no-installed-files', install_dir : '') +endif diff --git a/test cases/windows/8 msvc dll versioning/no-installed-files b/test cases/windows/8 msvc dll versioning/no-installed-files new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/windows/8 msvc dll versioning/no-installed-files diff --git a/test cases/windows/9 find program/meson.build b/test cases/windows/9 find program/meson.build new file mode 100644 index 0000000..ef34586 --- /dev/null +++ b/test cases/windows/9 find program/meson.build @@ -0,0 +1,4 @@ +project('find program', 'c') + +prog = find_program('test-script') +test('script', prog) diff --git a/test cases/windows/9 find program/test-script b/test cases/windows/9 find program/test-script new file mode 100644 index 0000000..d105a81 --- /dev/null +++ b/test cases/windows/9 find program/test-script @@ -0,0 +1,3 @@ +#!/usr/bin/env python + +print('1') diff --git a/tools/ac_converter.py b/tools/ac_converter.py index c7dbd37..571481e 100755 --- a/tools/ac_converter.py +++ b/tools/ac_converter.py @@ -116,6 +116,7 @@ function_data = \ 'HAVE_READLINK': ('readlink', 'unistd.h'), 'HAVE_RES_INIT': ('res_init', 'resolv.h'), 'HAVE_SENDMMSG': ('sendmmsg', 'sys/socket.h'), + 'HAVE_SOCKET' : ('socket',' sys/socket.h'), 'HAVE_GETENV': ('getenv', 'stdlib.h'), 'HAVE_SETENV': ('setenv', 'stdlib.h'), 'HAVE_PUTENV': ('putenv', 'stdlib.h'), @@ -161,6 +162,11 @@ function_data = \ 'HAVE_GETPWUID_R': ('getpwuid_r', 'pwd.h'), 'HAVE_GETUID': ('getuid', 'unistd.h'), 'HAVE_LRINTF': ('lrintf', 'math.h'), + 'HAVE_DECL_ISNAN': ('isnan', 'math.h'), + 'HAVE_DECL_ISINF': ('isinf', 'math.h'), + 'HAVE_ROUND': ('round', 'math.h'), + 'HAVE_NEARBYINT': ('nearbyint', 'math.h'), + 'HAVE_RINT': ('rint', 'math.h'), 'HAVE_MKFIFO': ('mkfifo', 'sys/stat.h'), 'HAVE_MLOCK': ('mlock', 'sys/mman.h'), 'HAVE_NANOSLEEP': ('nanosleep', 'time.h'), @@ -269,6 +275,6 @@ for line in open(sys.argv[1]): print("cdata.set('%s', cc.sizeof('%s'))" % (elem, typename)) print(''' -configure_file(input : 'config.h.in', +configure_file(input : 'config.h.meson', output : 'config.h', configuration : cdata)''') |