diff options
40 files changed, 573 insertions, 280 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index ac69dd4..60a6fd3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -49,6 +49,14 @@ branches: only: - master +init: + - ps: | + If($Env:compiler -like 'msvc2010') { + Set-WinSystemLocale de-DE + Start-Sleep -s 5 + Restart-Computer + } + install: - cmd: set "ORIG_PATH=%PATH%" # Boost 1.56.0: https://www.appveyor.com/docs/build-environment/#boost diff --git a/docs/markdown/Compiler-properties.md b/docs/markdown/Compiler-properties.md index 06efff3..4def628 100644 --- a/docs/markdown/Compiler-properties.md +++ b/docs/markdown/Compiler-properties.md @@ -103,12 +103,12 @@ The `result` variable encapsulates the state of the test, which can be extracted with the following methods. The `name` keyword argument works the same as with `compiles`. -| Method | Return value -| ------ | ------------ -| compiled | `True` if compilation succeeded. If `false` then all other methods return undefined values. -| returncode | The return code of the application as an integer -| stdout | Program's standard out as text. -| stderr | Program's standard error as text. +| Method | Return value | +| ------ | ------------ | +| compiled | `True` if compilation succeeded. If `false` then all other methods return undefined values. | +| returncode | The return code of the application as an integer | +| stdout | Program's standard out as text. | +| stderr | Program's standard error as text. | Here is an example usage: diff --git a/docs/markdown/D.md b/docs/markdown/D.md new file mode 100644 index 0000000..7b0d485 --- /dev/null +++ b/docs/markdown/D.md @@ -0,0 +1,89 @@ +--- +title: D +short-description: Compiling D sources +... + +# Compiling D applications + +Meson has support for compiling D programs. A minimal `meson.build` file for D looks like this: + +```meson +project('myapp', 'd') + +executable('myapp', 'app.d') +``` + +## Compiling different versions + +If you are using the [version()](https://dlang.org/spec/version.html) feature for conditional compilation, you can use it using the `d_module_versions` +target property: +```meson +project('myapp', 'd') +executable('myapp', 'app.d', d_module_versions: ['Demo', 'FeatureA']) +``` + +## Using embedded unittests + +If you are using embedded [unittest functions](https://dlang.org/spec/unittest.html), your source code needs to be compiled twice, once in regular +mode, and once with unittests active. This is done by setting the `d_unittest` target property to `true`. +Meson will only ever pass the respective compiler's `-unittest` flag, and never have the compiler generate an empty main function. +If you need that feature in a portable way, create an empty `main()` function for unittests yourself, since the GNU D compiler +does not have this feature. + +This is an example for using D unittests with Meson: +```meson +project('myapp_tested', 'd') + +myapp_src = ['app.d', 'alpha.d', 'beta.d'] +executable('myapp', myapp_src) + +test_exe = executable('myapp_test', myapp_src, d_unittest: true) +test('myapptest', test_exe) +``` + +# Compiling D libraries and installing them + +Building D libraries is a straightforward process, not different from how C libraries are built in Meson. You should generate a pkg-config file +and install it, in order to make other software on the system find the dependency once it is installed. + +This is an example on how to build a D shared library: +```meson +project('mylib', 'd', version: '1.2.0') + +project_soversion = 0 +glib_dep = dependency('glib-2.0') + +my_lib = library('mylib', + ['src/mylib/libfunctions.d'], + dependencies: [glib_dep], + install: true, + version: meson.project_version(), + soversion: project_soversion, + d_module_versions: ['FeatureA', 'featureB'] +) +pkgc.generate(name: 'mylib', + libraries: my_lib, + subdirs: 'd/mylib', + version: meson.project_version(), + description: 'A simple example D library.', + d_module_versions: ['FeatureA'] +) +install_subdir('src/mylib/', install_dir: 'include/d/mylib/') +``` + +It is important to make the D sources install in a subdirectory in the include path, in this case `/usr/include/d/mylib/mylib`. +All D compilers include the `/usr/include/d` directory by default, and if your library would be installed into `/usr/include/d/mylib`, there +is a high chance that, when you compile your project again on a machine where you installed it, the compiler will prefer the old installed include over +the new version in the source tree, leading to very confusing errors. + +This is an example of how to use the D library we just built and installed in an application: +```meson +project('myapp', 'd') + +mylib_dep = dependency('mylib', version: '>= 1.2.0') +myapp_src = ['app.d', 'alpha.d', 'beta.d'] +executable('myapp', myapp_src, dependencies: [mylib_dep]) +``` + +Please keep in mind that the library and executable would both need to be built with the exact same D compiler and D compiler version. The D ABI is not +stable across compilers and their versions, and mixing compilers will lead to problems. diff --git a/docs/markdown/Getting-meson.md b/docs/markdown/Getting-meson.md index d654ff3..8664d61 100644 --- a/docs/markdown/Getting-meson.md +++ b/docs/markdown/Getting-meson.md @@ -5,7 +5,7 @@ Meson releases can be downloaded from the [GitHub release page]. Meson is also available in the [Python Package Index] and can be -installed with <tt>pip3 install meson</tt>. +installed with `pip3 install meson`. The newest development code can be obtained directly from [Git] diff --git a/docs/markdown/Pkgconfig-module.md b/docs/markdown/Pkgconfig-module.md index 5a660fd..7f767f1 100644 --- a/docs/markdown/Pkgconfig-module.md +++ b/docs/markdown/Pkgconfig-module.md @@ -41,3 +41,5 @@ keyword arguments. e.g. `datadir=${prefix}/share`. The names `prefix`, `libdir` and `installdir` are reserved and may not be used. - `version` a string describing the version of this library +- `d_module_versions` a list of module version flags used when compiling + D sources referred to by this pkg-config file diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 1bed44d..de51479 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -201,10 +201,15 @@ following. to the target file. Note that your command argument list may not contain `@OUTPUT@` when capture mode is active. - `command` command to run to create outputs from inputs. The command - may be strings or the return of `find_program()` or `executable()` - (note: always specify commands in array form `['commandname', + may be strings or the return value of functions that return file-like + objects such as [`find_program()`](#find_program), + [`executable()`](#executable), [`configure_file()`](#configure_file), + [`files()`](#files), [`custom_target()`](#custom_target), etc. + Meson will automatically insert the appropriate dependencies on + targets and files listed in this keyword argument. + Note: always specify commands in array form `['commandname', '-arg1', '-arg2']` rather than as a string `'commandname -arg1 - -arg2'` as the latter will *not* work) + -arg2'` as the latter will *not* work. - `depend_files` files ([`string`](#string-object), [`files()`](#files), or [`configure_file()`](#configure_file)) that this target depends on but are not listed in the `command` keyword @@ -424,6 +429,10 @@ be passed to [shared and static libraries](#library). - `override_options` takes an array of strings in the same format as `project`'s `default_options` overriding the values of these options for this target only, since 0.40.0 +- `d_import_dirs` list of directories to look in for string imports used + in the D programmling language +- `d_unittest`, when set to true, the D modules are compiled in debug mode +- `d_module_versions` list of module versions set when compiling D sources The list of `sources`, `objects`, and `dependencies` is always flattened, which means you can freely nest and add lists while @@ -835,7 +844,7 @@ This function prints its argument to stdout. The first argument to this function must be a string defining the name of this project. It is followed by programming languages that the project uses. Supported values for languages are `c`, `cpp` (for -`C++`), `objc`, `objcpp`, `fortran`, `java`, `cs` (for `C#`) and +`C++`), `d`, `objc`, `objcpp`, `fortran`, `java`, `cs` (for `C#`) and `vala`. In versions before `0.40.0` you must have at least one language listed. @@ -1502,7 +1511,10 @@ A build target is either an [executable](#executable), files with custom flags. To use the object file(s) in another build target, use the `objects:` keyword argument. -- `full_path()` returns a full path pointing to the result target file +- `full_path()` returns a full path pointing to the result target file. + NOTE: In most cases using the object itself will do the same job as + this and will also allow Meson to setup inter-target dependencies + correctly. Please file a bug if that doesn't work for you. - `private_dir_include()` returns a opaque value that works like `include_directories` but points to the private directory of this @@ -1548,7 +1560,10 @@ cause a syntax error. This object is returned by [`custom_target`](#custom_target) and contains a target with the following methods: -- `full_path()` returns a full path pointing to the result target file +- `full_path()` returns a full path pointing to the result target file + NOTE: In most cases using the object itself will do the same job as + this and will also allow Meson to setup inter-target dependencies + correctly. Please file a bug if that doesn't work for you. ### `dependency` object diff --git a/docs/markdown/index.md b/docs/markdown/index.md index 15831d7..81c17ff 100644 --- a/docs/markdown/index.md +++ b/docs/markdown/index.md @@ -13,7 +13,7 @@ The main design point of Meson is that every moment a developer spends writing o ## Features * multiplatform support for Linux, OSX, Windows, GCC, Clang, Visual Studio and others -* supported languages include C, C++, Fortran, Java, Rust +* supported languages include C, C++, D, Fortran, Java, Rust * build definitions in a very readable and user friendly non-Turing complete DSL * cross compilation for many operating systems as well as bare metal * optimized for extremely fast full and incremental builds without sacrificing correctness diff --git a/docs/sitemap.txt b/docs/sitemap.txt index 755daac..89dfdbd 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -38,6 +38,7 @@ index.md Windows-module.md Java.md Vala.md + D.md IDE-integration.md Custom-build-targets.md Build-system-converters.md @@ -14,6 +14,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +# ghwt - GitHub WrapTool +# +# An emergency wraptool(1) replacement downloader that downloads +# directly from GitHub in case wrapdb.mesonbuild.com is down. + import urllib.request, json, sys, os, shutil, subprocess import configparser, hashlib @@ -24,7 +24,7 @@ def main(): # encoding, so we can just warn about it. e = locale.getpreferredencoding() if e.upper() != 'UTF-8' and not mesonlib.is_windows(): - print('Warning: You are using {!r} which is not a a Unicode-compatible ' + print('Warning: You are using {!r} which is not a Unicode-compatible ' 'locale.'.format(e), file=sys.stderr) print('You might see errors if you use UTF-8 strings as ' 'filenames, as strings, or as file contents.', file=sys.stderr) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 2188074..f67e412 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1048,7 +1048,7 @@ int dummy; if hasattr(i, 'fname'): i = i.fname if i.endswith('vala'): - vapiname = dep.name + '.vapi' + vapiname = dep.vala_vapi fullname = os.path.join(self.get_target_dir(dep), vapiname) result.add(fullname) break @@ -1818,7 +1818,7 @@ rule FORTRAN_DEP_HACK elem.add_item('DEPFILE', depfile) if len(extra_dependencies) > 0: elem.add_dep(extra_dependencies) - elem.add_item('DESC', 'Generating $out') + elem.add_item('DESC', 'Generating {!r}.'.format(sole_output)) if isinstance(exe, build.BuildTarget): elem.add_dep(self.get_target_filename(exe)) elem.add_item('COMMAND', cmdlist) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 39c900e..c54abbd 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -19,7 +19,7 @@ import itertools from . import environment from . import dependencies from . import mlog -from .mesonlib import File, MesonException +from .mesonlib import File, MesonException, listify, extract_as_list from .mesonlib import flatten, typeslistify, stringlistify, classify_unity_sources from .mesonlib import get_filenames_templates_dict, substitute_values from .environment import for_windows, for_darwin, for_cygwin @@ -36,6 +36,9 @@ known_basic_kwargs = {'install': True, 'vala_args': True, 'fortran_args': True, 'd_args': True, + 'd_import_dirs': True, + 'd_unittest': True, + 'd_module_versions': True, 'java_args': True, 'rust_args': True, 'link_args': True, @@ -351,11 +354,12 @@ class BuildTarget(Target): # 1. Pre-existing objects provided by the user with the `objects:` kwarg # 2. Compiled objects created by and extracted from another target self.process_objectlist(objects) + self.process_compilers() self.process_kwargs(kwargs, environment) self.check_unknown_kwargs(kwargs) if not self.sources and not self.generated and not self.objects: raise InvalidArguments('Build target %s has no sources.' % name) - self.process_compilers() + self.process_compilers_late() self.validate_sources() self.validate_cross_install(environment) @@ -411,8 +415,7 @@ class BuildTarget(Target): raise InvalidArguments(msg) def process_sourcelist(self, sources): - if not isinstance(sources, list): - sources = [sources] + sources = listify(sources) added_sources = {} # If the same source is defined multiple times, use it only once. for s in sources: # Holder unpacking. Ugly. @@ -437,6 +440,39 @@ class BuildTarget(Target): removed = True return removed + def process_compilers_late(self): + """Processes additional compilers after kwargs have been evaluated. + + This can add extra compilers that might be required by keyword + arguments, such as link_with or dependencies. It will also try to guess + which compiler to use if one hasn't been selected already. + """ + # Populate list of compilers + if self.is_cross: + compilers = self.environment.coredata.cross_compilers + else: + compilers = self.environment.coredata.compilers + + # If this library is linked against another library we need to consider + # the languages of those libraries as well. + if self.link_targets or self.link_whole_targets: + extra = set() + for t in itertools.chain(self.link_targets, self.link_whole_targets): + for name, compiler in t.compilers.items(): + if name in clike_langs: + extra.add((name, compiler)) + for name, compiler in sorted(extra, key=lambda p: sort_clike(p[0])): + self.compilers[name] = compiler + + if not self.compilers: + # No source files or parent targets, target consists of only object + # files of unknown origin. Just add the first clike compiler + # that we have and hope that it can link these objects + for lang in clike_langs: + if lang in compilers: + self.compilers[lang] = compilers[lang] + break + def process_compilers(self): ''' Populate self.compilers, which is the list of compilers that this @@ -485,14 +521,7 @@ class BuildTarget(Target): # Re-sort according to clike_langs self.compilers = OrderedDict(sorted(self.compilers.items(), key=lambda t: sort_clike(t[0]))) - else: - # No source files, target consists of only object files of unknown - # origin. Just add the first clike compiler that we have and hope - # that it can link these objects - for lang in clike_langs: - if lang in compilers: - self.compilers[lang] = compilers[lang] - break + # If all our sources are Vala, our target also needs the C compiler but # it won't get added above. if 'vala' in self.compilers and 'c' not in self.compilers: @@ -525,8 +554,7 @@ class BuildTarget(Target): generated twice, since the output needs to be passed to the ld_args and link_depends. """ - if not isinstance(sources, list): - sources = [sources] + sources = listify(sources) for s in sources: if hasattr(s, 'held_object'): s = s.held_object @@ -548,8 +576,7 @@ class BuildTarget(Target): return self.kwargs def unpack_holder(self, d): - if not isinstance(d, list): - d = [d] + d = listify(d) newd = [] for i in d: if isinstance(i, list): @@ -607,70 +634,54 @@ class BuildTarget(Target): self.copy_kwargs(kwargs) kwargs.get('modules', []) self.need_install = kwargs.get('install', self.need_install) - llist = kwargs.get('link_with', []) - if not isinstance(llist, list): - llist = [llist] + llist = extract_as_list(kwargs, 'link_with') for linktarget in llist: # Sorry for this hack. Keyword targets are kept in holders # in kwargs. Unpack here without looking at the exact type. if hasattr(linktarget, "held_object"): linktarget = linktarget.held_object self.link(linktarget) - lwhole = kwargs.get('link_whole', []) - if not isinstance(lwhole, list): - lwhole = [lwhole] + lwhole = extract_as_list(kwargs, 'link_whole') for linktarget in lwhole: # Sorry for this hack. Keyword targets are kept in holders # in kwargs. Unpack here without looking at the exact type. if hasattr(linktarget, "held_object"): linktarget = linktarget.held_object self.link_whole(linktarget) - c_pchlist = kwargs.get('c_pch', []) - if not isinstance(c_pchlist, list): - c_pchlist = [c_pchlist] + + c_pchlist, cpp_pchlist, clist, cpplist, cslist, valalist, objclist, objcpplist, fortranlist, rustlist \ + = extract_as_list(kwargs, 'c_pch', 'cpp_pch', 'c_args', 'cpp_args', 'cs_args', 'vala_args', 'objc_args', + 'objcpp_args', 'fortran_args', 'rust_args') + self.add_pch('c', c_pchlist) - cpp_pchlist = kwargs.get('cpp_pch', []) - if not isinstance(cpp_pchlist, list): - cpp_pchlist = [cpp_pchlist] self.add_pch('cpp', cpp_pchlist) - clist = kwargs.get('c_args', []) - if not isinstance(clist, list): - clist = [clist] - self.add_compiler_args('c', clist) - cpplist = kwargs.get('cpp_args', []) - if not isinstance(cpplist, list): - cpplist = [cpplist] - self.add_compiler_args('cpp', cpplist) - cslist = kwargs.get('cs_args', []) - if not isinstance(cslist, list): - cslist = [cslist] - self.add_compiler_args('cs', cslist) - valalist = kwargs.get('vala_args', []) - if not isinstance(valalist, list): - valalist = [valalist] - self.add_compiler_args('vala', valalist) - objclist = kwargs.get('objc_args', []) - if not isinstance(objclist, list): - objclist = [objclist] - self.add_compiler_args('objc', objclist) - objcpplist = kwargs.get('objcpp_args', []) - if not isinstance(objcpplist, list): - objcpplist = [objcpplist] - self.add_compiler_args('objcpp', objcpplist) - fortranlist = kwargs.get('fortran_args', []) - if not isinstance(fortranlist, list): - fortranlist = [fortranlist] - self.add_compiler_args('fortran', fortranlist) - rustlist = kwargs.get('rust_args', []) - if not isinstance(rustlist, list): - rustlist = [rustlist] - self.add_compiler_args('rust', rustlist) + compiler_args = {'c': clist, 'cpp': cpplist, 'cs': cslist, 'vala': valalist, 'objc': objclist, 'objcpp': objcpplist, + 'fortran': fortranlist, 'rust': rustlist + } + for key, value in compiler_args.items(): + self.add_compiler_args(key, value) + if not isinstance(self, Executable): self.vala_header = kwargs.get('vala_header', self.name + '.h') self.vala_vapi = kwargs.get('vala_vapi', self.name + '.vapi') self.vala_gir = kwargs.get('vala_gir', None) + dlist = stringlistify(kwargs.get('d_args', [])) self.add_compiler_args('d', dlist) + dfeatures = dict() + dfeature_unittest = kwargs.get('d_unittest', False) + if dfeature_unittest: + dfeatures['unittest'] = dfeature_unittest + dfeature_versions = kwargs.get('d_module_versions', None) + if dfeature_versions: + dfeatures['versions'] = dfeature_versions + dfeature_import_dirs = kwargs.get('d_import_dirs', None) + if dfeature_import_dirs: + dfeatures['import_dirs'] = dfeature_import_dirs + if dfeatures: + if 'd' in self.compilers: + self.add_compiler_args('d', self.compilers['d'].get_feature_args(dfeatures)) + self.link_args = flatten(kwargs.get('link_args', [])) for i in self.link_args: if not isinstance(i, str): @@ -682,14 +693,10 @@ This will become a hard error in a future Meson release.''') self.process_link_depends(kwargs.get('link_depends', []), environment) # Target-specific include dirs must be added BEFORE include dirs from # internal deps (added inside self.add_deps()) to override them. - inclist = kwargs.get('include_directories', []) - if not isinstance(inclist, list): - inclist = [inclist] + inclist = extract_as_list(kwargs, 'include_directories') self.add_include_dirs(inclist) # Add dependencies (which also have include_directories) - deplist = kwargs.get('dependencies', []) - if not isinstance(deplist, list): - deplist = [deplist] + deplist = extract_as_list(kwargs, 'dependencies') self.add_deps(deplist) # If an item in this list is False, the output corresponding to # the list index of that item will not be installed @@ -705,9 +712,7 @@ This will become a hard error in a future Meson release.''') raise InvalidArguments('Argument gui_app must be boolean.') elif 'gui_app' in kwargs: raise InvalidArguments('Argument gui_app can only be used on executables.') - extra_files = kwargs.get('extra_files', []) - if not isinstance(extra_files, list): - extra_files = [extra_files] + extra_files = extract_as_list(kwargs, 'extra_files') for i in extra_files: assert(isinstance(i, File)) trial = os.path.join(environment.get_source_dir(), i.subdir, i.fname) @@ -720,9 +725,7 @@ This will become a hard error in a future Meson release.''') self.build_rpath = kwargs.get('build_rpath', '') if not isinstance(self.build_rpath, str): raise InvalidArguments('Build_rpath is not a string.') - resources = kwargs.get('resources', []) - if not isinstance(resources, list): - resources = [resources] + resources = extract_as_list(kwargs, 'resources') for r in resources: if not isinstance(r, str): raise InvalidArguments('Resource argument is not a string.') @@ -811,8 +814,7 @@ This will become a hard error in a future Meson release.''') return self.include_dirs def add_deps(self, deps): - if not isinstance(deps, list): - deps = [deps] + deps = listify(deps) for dep in deps: if hasattr(dep, 'held_object'): dep = dep.held_object @@ -1040,9 +1042,7 @@ class Generator: self.arglist = args if 'output' not in kwargs: raise InvalidArguments('Generator must have "output" keyword argument.') - outputs = kwargs['output'] - if not isinstance(outputs, list): - outputs = [outputs] + outputs = listify(kwargs['output']) for rule in outputs: if not isinstance(rule, str): raise InvalidArguments('"output" may only contain strings.') @@ -1540,8 +1540,7 @@ class CustomTarget(Target): return deps def flatten_command(self, cmd): - if not isinstance(cmd, list): - cmd = [cmd] + cmd = listify(cmd) final_cmd = [] for c in cmd: if hasattr(c, 'held_object'): @@ -1576,9 +1575,7 @@ class CustomTarget(Target): self.sources.append(s) if 'output' not in kwargs: raise InvalidArguments('Missing keyword argument "output".') - self.outputs = kwargs['output'] - if not isinstance(self.outputs, list): - self.outputs = [self.outputs] + self.outputs = listify(kwargs['output']) # This will substitute values from the input into output and return it. inputs = get_sources_string_names(self.sources) values = get_filenames_templates_dict(inputs, []) @@ -1632,18 +1629,13 @@ class CustomTarget(Target): self.build_always = kwargs.get('build_always', False) if not isinstance(self.build_always, bool): raise InvalidArguments('Argument build_always must be a boolean.') - extra_deps = kwargs.get('depends', []) - if not isinstance(extra_deps, list): - extra_deps = [extra_deps] + extra_deps, depend_files = extract_as_list(kwargs, 'depends', 'depend_files', pop = False) for ed in extra_deps: while hasattr(ed, 'held_object'): ed = ed.held_object if not isinstance(ed, (CustomTarget, BuildTarget)): raise InvalidArguments('Can only depend on toplevel targets: custom_target or build_target (executable or a library)') self.extra_depends.append(ed) - depend_files = kwargs.get('depend_files', []) - if not isinstance(depend_files, list): - depend_files = [depend_files] for i in depend_files: if isinstance(i, (File, str)): self.depend_files.append(i) @@ -1793,8 +1785,7 @@ class Data: self.sources = sources self.install_dir = install_dir self.install_mode = install_mode - if not isinstance(self.sources, list): - self.sources = [self.sources] + self.sources = listify(self.sources) for s in self.sources: assert(isinstance(s, File)) diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index ec16134..255a506 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -16,7 +16,7 @@ import subprocess, os.path, tempfile from .. import mlog from .. import coredata -from ..mesonlib import EnvironmentException, version_compare, Popen_safe +from ..mesonlib import EnvironmentException, version_compare, Popen_safe, listify from .compilers import ( GCC_MINGW, @@ -1013,8 +1013,7 @@ class VisualStudioCCompiler(CCompiler): def get_link_whole_for(self, args): # Only since VS2015 - if not isinstance(args, list): - args = [args] + args = listify(args) return ['/WHOLEARCHIVE:' + x for x in args] def get_instruction_set_args(self, instruction_set): diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py index a989704..9739f28 100644 --- a/mesonbuild/compilers/d.py +++ b/mesonbuild/compilers/d.py @@ -27,6 +27,20 @@ from .compilers import ( CompilerArgs, ) +d_feature_args = {'gcc': {'unittest': '-funittest', + 'version': '-fversion', + 'import_dir': '-J' + }, + 'llvm': {'unittest': '-unittest', + 'version': '-d-version', + 'import_dir': '-J' + }, + 'dmd': {'unittest': '-unittest', + 'version': '-version', + 'import_dir': '-J' + } + } + class DCompiler(Compiler): def __init__(self, exelist, version, is_cross): self.language = 'd' @@ -79,8 +93,42 @@ class DCompiler(Compiler): # FIXME: Make this work for Windows, MacOS and cross-compiling return get_gcc_soname_args(GCC_STANDARD, prefix, shlib_name, suffix, path, soversion, is_shared_module) - def get_unittest_args(self): - return ['-unittest'] + def get_feature_args(self, kwargs): + res = [] + if 'unittest' in kwargs: + unittest = kwargs.pop('unittest') + unittest_arg = d_feature_args[self.id]['unittest'] + if not unittest_arg: + raise EnvironmentException('D compiler %s does not support the "unittest" feature.' % self.name_string()) + if unittest: + res.append(unittest_arg) + + if 'versions' in kwargs: + versions = kwargs.pop('versions') + if not isinstance(versions, list): + versions = [versions] + + version_arg = d_feature_args[self.id]['version'] + if not version_arg: + raise EnvironmentException('D compiler %s does not support the "feature versions" feature.' % self.name_string()) + for v in versions: + res.append('{0}={1}'.format(version_arg, v)) + + if 'import_dirs' in kwargs: + import_dirs = kwargs.pop('import_dirs') + if not isinstance(import_dirs, list): + import_dirs = [import_dirs] + + import_dir_arg = d_feature_args[self.id]['import_dir'] + if not import_dir_arg: + raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string()) + for d in import_dirs: + res.append('{0}{1}'.format(import_dir_arg, d)) + + if kwargs: + raise EnvironmentException('Unknown D compiler feature(s) selected: %s' % ', '.join(kwargs.keys())) + + return res def get_buildtype_linker_args(self, buildtype): return [] @@ -217,9 +265,6 @@ class GnuDCompiler(DCompiler): def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, build_rpath, install_rpath) - def get_unittest_args(self): - return ['-funittest'] - class LLVMDCompiler(DCompiler): def __init__(self, exelist, version, is_cross): diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index cc4837a..7c7f986 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -23,7 +23,7 @@ from enum import Enum from .. import mlog from .. import mesonlib -from ..mesonlib import MesonException, Popen_safe, flatten, version_compare_many +from ..mesonlib import MesonException, Popen_safe, flatten, version_compare_many, listify # These must be defined in this file to avoid cyclical references. @@ -374,10 +374,7 @@ class ExternalProgram: def __init__(self, name, command=None, silent=False, search_dir=None): self.name = name if command is not None: - if not isinstance(command, list): - self.command = [command] - else: - self.command = command + self.command = listify(command) else: self.command = self._search(name, search_dir) if not silent: diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 12e0239..c0ac5a8 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -24,7 +24,7 @@ import sysconfig from .. import mlog from .. import mesonlib -from ..mesonlib import Popen_safe +from ..mesonlib import Popen_safe, extract_as_list from ..environment import detect_cpu_family from .base import DependencyException, DependencyMethods @@ -132,9 +132,7 @@ class BoostDependency(ExternalDependency): return args def get_requested(self, kwargs): - candidates = kwargs.get('modules', []) - if not isinstance(candidates, list): - candidates = [candidates] + candidates = extract_as_list(kwargs, 'modules') for c in candidates: if not isinstance(c, str): raise DependencyException('Boost module argument is not a string.') diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index 99e017b..8f183e5 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -23,7 +23,7 @@ from collections import OrderedDict from .. import mlog from .. import mesonlib -from ..mesonlib import MesonException, Popen_safe, version_compare +from ..mesonlib import MesonException, Popen_safe, version_compare, extract_as_list from ..environment import for_windows, detect_cpu from .base import DependencyException, DependencyMethods @@ -468,12 +468,9 @@ class WxDependency(ExternalDependency): self.link_args = out.split() def get_requested(self, kwargs): - modules = 'modules' - if modules not in kwargs: + if 'modules' not in kwargs: return [] - candidates = kwargs[modules] - if not isinstance(candidates, list): - candidates = [candidates] + candidates = extract_as_list(kwargs, 'modules') for c in candidates: if not isinstance(c, str): raise DependencyException('wxwidgets module argument is not a string') diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 2bcf198..6658c26 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -21,7 +21,7 @@ from . import optinterpreter from . import compilers from .wrap import wrap, WrapMode from . import mesonlib -from .mesonlib import FileMode, Popen_safe +from .mesonlib import FileMode, Popen_safe, listify, extract_as_list from .dependencies import ExternalProgram from .dependencies import InternalDependency, Dependency, DependencyException from .interpreterbase import InterpreterBase @@ -264,8 +264,7 @@ class DependencyHolder(InterpreterObject): return self.held_object.get_version() def pkgconfig_method(self, args, kwargs): - if not isinstance(args, list): - args = [args] + args = listify(args) if len(args) != 1: raise InterpreterException('get_pkgconfig_variable takes exactly one argument.') varname = args[0] @@ -669,9 +668,7 @@ class CompilerHolder(InterpreterObject): if not isinstance(nobuiltins, bool): raise InterpreterException('Type of no_builtin_args not a boolean.') args = [] - incdirs = kwargs.get('include_directories', []) - if not isinstance(incdirs, list): - incdirs = [incdirs] + incdirs = extract_as_list(kwargs, 'include_directories') for i in incdirs: if not isinstance(i, IncludeDirsHolder): raise InterpreterException('Include directories argument must be an include_directories object.') @@ -688,8 +685,7 @@ class CompilerHolder(InterpreterObject): def determine_dependencies(self, kwargs): deps = kwargs.get('dependencies', None) if deps is not None: - if not isinstance(deps, list): - deps = [deps] + deps = listify(deps) final_deps = [] for d in deps: try: @@ -752,10 +748,13 @@ class CompilerHolder(InterpreterObject): return self.compiler.symbols_have_underscore_prefix(self.environment) def unittest_args_method(self, args, kwargs): - # At time, only D compilers have this feature. - if not hasattr(self.compiler, 'get_unittest_args'): - raise InterpreterException('This {} compiler has no unittest arguments.'.format(self.compiler.get_display_language())) - return self.compiler.get_unittest_args() + ''' + This function is deprecated and should not be used. + It can be removed in a future version of Meson. + ''' + if not hasattr(self.compiler, 'get_feature_args'): + raise InterpreterException('This {} compiler has no feature arguments.'.format(self.compiler.get_display_language())) + return self.compiler.get_feature_args({'unittest': 'true'}) def has_member_method(self, args, kwargs): if len(args) != 2: @@ -1249,6 +1248,9 @@ pch_kwargs = set(['c_pch', 'cpp_pch']) lang_arg_kwargs = set(['c_args', 'cpp_args', 'd_args', + 'd_import_dirs', + 'd_unittest', + 'd_module_versions', 'fortran_args', 'java_args', 'objc_args', @@ -1260,7 +1262,7 @@ lang_arg_kwargs = set(['c_args', vala_kwargs = set(['vala_header', 'vala_gir', 'vala_vapi']) rust_kwargs = set(['rust_crate_type']) -cs_kwargs = set(['resources']) +cs_kwargs = set(['resources', 'cs_args']) buildtarget_kwargs = set(['build_by_default', 'build_rpath', @@ -1454,8 +1456,7 @@ class Interpreter(InterpreterBase): raise InterpreterException('Module returned a value of unknown type.') def process_new_values(self, invalues): - if not isinstance(invalues, list): - invalues = [invalues] + invalues = listify(invalues) for v in invalues: if isinstance(v, (build.BuildTarget, build.CustomTarget, build.RunTarget)): self.add_target(v.name, v) @@ -1535,19 +1536,12 @@ class Interpreter(InterpreterBase): version = kwargs.get('version', self.project_version) if not isinstance(version, str): raise InterpreterException('Version must be a string.') - incs = kwargs.get('include_directories', []) - if not isinstance(incs, list): - incs = [incs] - libs = kwargs.get('link_with', []) - if not isinstance(libs, list): - libs = [libs] - sources = kwargs.get('sources', []) - if not isinstance(sources, list): - sources = [sources] + incs = extract_as_list(kwargs, 'include_directories') + libs = extract_as_list(kwargs, 'link_with') + sources = extract_as_list(kwargs, 'sources') sources = self.source_strings_to_files(self.flatten(sources)) deps = self.flatten(kwargs.get('dependencies', [])) - if not isinstance(deps, list): - deps = [deps] + deps = listify(deps) compile_args = mesonlib.stringlistify(kwargs.get('compile_args', [])) link_args = mesonlib.stringlistify(kwargs.get('link_args', [])) final_deps = [] @@ -1692,11 +1686,11 @@ class Interpreter(InterpreterBase): raise InterpreterException('Argument required for get_option.') optname = args[0] try: - return compilers.base_options[optname].value + return self.environment.get_coredata().base_options[optname].value except KeyError: pass try: - return self.environment.get_coredata().get_builtin_option(optname) + return self.environment.coredata.get_builtin_option(optname) except RuntimeError: pass try: @@ -1721,6 +1715,11 @@ class Interpreter(InterpreterBase): return self.coredata.external_args[lang] except KeyError: pass + # Some base options are not defined in some environments, return the default value. + try: + return compilers.base_options[optname].value + except KeyError: + pass raise InterpreterException('Tried to access unknown option "%s".' % optname) @noKwargs @@ -1730,8 +1729,7 @@ class Interpreter(InterpreterBase): return ConfigurationDataHolder() def parse_default_options(self, default_options): - if not isinstance(default_options, list): - default_options = [default_options] + default_options = listify(default_options) for option in default_options: if not isinstance(option, str): mlog.debug(option) @@ -2019,7 +2017,7 @@ class Interpreter(InterpreterBase): if progobj is None: progobj = self.program_from_system(args) if required and (progobj is None or not progobj.found()): - raise InvalidArguments('Program "%s" not found or not executable' % exename) + raise InvalidArguments('Program "%s" not found or not executable' % args[0]) if progobj is None: return ExternalProgramHolder(dependencies.ExternalProgram('nonexistingprogram')) return progobj @@ -2277,12 +2275,8 @@ class Interpreter(InterpreterBase): elif len(args) == 1: if 'command' not in kwargs: raise InterpreterException('Missing "command" keyword argument') - all_args = kwargs['command'] - if not isinstance(all_args, list): - all_args = [all_args] - deps = kwargs.get('depends', []) - if not isinstance(deps, list): - deps = [deps] + all_args = extract_as_list(kwargs, 'command') + deps = extract_as_list(kwargs, 'depends') else: raise InterpreterException('Run_target needs at least one positional argument.') @@ -2333,8 +2327,7 @@ class Interpreter(InterpreterBase): if isinstance(envlist, EnvironmentVariablesHolder): env = envlist.held_object else: - if not isinstance(envlist, list): - envlist = [envlist] + envlist = listify(envlist) # Convert from array to environment object env = EnvironmentVariablesHolder() for e in envlist: @@ -2363,9 +2356,7 @@ class Interpreter(InterpreterBase): par = kwargs.get('is_parallel', True) if not isinstance(par, bool): raise InterpreterException('Keyword argument is_parallel must be a boolean.') - cmd_args = kwargs.get('args', []) - if not isinstance(cmd_args, list): - cmd_args = [cmd_args] + cmd_args = extract_as_list(kwargs, 'args') for i in cmd_args: if not isinstance(i, (str, mesonlib.File, TargetHolder)): raise InterpreterException('Command line arguments must be strings, files or targets.') @@ -2504,9 +2495,7 @@ class Interpreter(InterpreterBase): if not isinstance(install_dir, str): raise InvalidArguments('Keyword argument install_dir not a string.') if 'exclude_files' in kwargs: - exclude = kwargs['exclude_files'] - if not isinstance(exclude, list): - exclude = [exclude] + exclude = extract_as_list(kwargs, 'exclude_files') for f in exclude: if not isinstance(f, str): raise InvalidArguments('Exclude argument not a string.') @@ -2516,9 +2505,7 @@ class Interpreter(InterpreterBase): else: exclude_files = set() if 'exclude_directories' in kwargs: - exclude = kwargs['exclude_directories'] - if not isinstance(exclude, list): - exclude = [exclude] + exclude = extract_as_list(kwargs, 'exclude_directories') for d in exclude: if not isinstance(d, str): raise InvalidArguments('Exclude argument not a string.') @@ -2681,9 +2668,7 @@ different subdirectory. if re.fullmatch('[_a-zA-Z][_0-9a-zA-Z]*', setup_name) is None: raise InterpreterException('Setup name may only contain alphanumeric characters.') try: - inp = kwargs.get('exe_wrapper', []) - if not isinstance(inp, list): - inp = [inp] + inp = extract_as_list(kwargs, 'exe_wrapper') exe_wrapper = [] for i in inp: if hasattr(i, 'held_object'): @@ -2825,8 +2810,7 @@ different subdirectory. is_cross = False try: kw_src = self.flatten(kwargs['sources']) - if not isinstance(kw_src, list): - kw_src = [kw_src] + kw_src = listify(kw_src) except KeyError: kw_src = [] sources += kw_src @@ -2834,12 +2818,9 @@ different subdirectory. objs = self.flatten(kwargs.get('objects', [])) kwargs['dependencies'] = self.flatten(kwargs.get('dependencies', [])) if 'extra_files' in kwargs: - ef = kwargs['extra_files'] - if not isinstance(ef, list): - ef = [ef] + ef = extract_as_list(kwargs, 'extra_files') kwargs['extra_files'] = self.source_strings_to_files(ef) - if not isinstance(objs, list): - objs = [objs] + objs = listify(objs) self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources) if targetholder is ExecutableHolder: targetclass = build.Executable diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index d03e5a2..71134a8 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -14,6 +14,7 @@ """A library of random helper functionality.""" +import sys import stat import time import platform, subprocess, operator, os, shutil, re @@ -473,6 +474,26 @@ def replace_if_different(dst, dst_tmp): else: os.unlink(dst_tmp) + +def listify(*args): + ''' + Returns a list with all args embedded in a list if they are not of type list. + This function preserves order. + ''' + if len(args) == 1: # Special case with one single arg + return args[0] if type(args[0]) is list else [args[0]] + return [item if type(item) is list else [item] for item in args] + + +def extract_as_list(dict_object, *keys, pop = False): + ''' + Extracts all values from given dict_object and listifies them. + ''' + if pop: + return listify(*[dict_object.pop(key, []) for key in keys]) + return listify(*[dict_object.get(key, []) for key in keys]) + + def typeslistify(item, types): ''' Ensure that type(@item) is one of @types or a @@ -509,6 +530,8 @@ def expand_arguments(args): return expended_args def Popen_safe(args, write=None, stderr=subprocess.PIPE, **kwargs): + if sys.version_info < (3, 6) or not sys.stdout.encoding: + return Popen_safe_legacy(args, write=write, stderr=stderr, **kwargs) p = subprocess.Popen(args, universal_newlines=True, close_fds=False, stdout=subprocess.PIPE, @@ -516,6 +539,25 @@ def Popen_safe(args, write=None, stderr=subprocess.PIPE, **kwargs): o, e = p.communicate(write) return p, o, e +def Popen_safe_legacy(args, write=None, stderr=subprocess.PIPE, **kwargs): + p = subprocess.Popen(args, universal_newlines=False, + stdout=subprocess.PIPE, + stderr=stderr, **kwargs) + if write is not None: + write = write.encode('utf-8') + o, e = p.communicate(write) + if o is not None: + if sys.stdout.encoding: + o = o.decode(encoding=sys.stdout.encoding, errors='replace').replace('\r\n', '\n') + else: + o = o.decode(errors='replace').replace('\r\n', '\n') + if e is not None: + if sys.stderr.encoding: + e = e.decode(encoding=sys.stderr.encoding, errors='replace').replace('\r\n', '\n') + else: + e = e.decode(errors='replace').replace('\r\n', '\n') + return p, o, e + def commonpath(paths): ''' For use on Python 3.4 where os.path.commonpath is not available. diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index fcdd193..137d380 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -98,17 +98,12 @@ class GnomeModule(ExtensionModule): cmd = ['glib-compile-resources', '@INPUT@'] - source_dirs = kwargs.pop('source_dir', []) - if not isinstance(source_dirs, list): - source_dirs = [source_dirs] + source_dirs, dependencies = mesonlib.extract_as_list(kwargs, 'source_dir', 'dependencies', pop=True) if len(args) < 2: raise MesonException('Not enough arguments; the name of the resource ' 'and the path to the XML file are required') - dependencies = kwargs.pop('dependencies', []) - if not isinstance(dependencies, list): - dependencies = [dependencies] # Validate dependencies for (ii, dep) in enumerate(dependencies): if hasattr(dep, 'held_object'): @@ -328,8 +323,7 @@ class GnomeModule(ExtensionModule): cflags = OrderedSet() ldflags = OrderedSet() gi_includes = OrderedSet() - if not isinstance(deps, list): - deps = [deps] + deps = mesonlib.listify(deps) for dep in deps: if hasattr(dep, 'held_object'): @@ -464,17 +458,14 @@ class GnomeModule(ExtensionModule): scan_command += ['--filelist=' + gir_filelist_filename] if 'link_with' in kwargs: - link_with = kwargs.pop('link_with') - if not isinstance(link_with, list): - link_with = [link_with] + link_with = mesonlib.extract_as_list(kwargs, 'link_with', pop = True) + for link in link_with: scan_command += self._get_link_args(state, link.held_object, depends, use_gir_args=True) if 'includes' in kwargs: - includes = kwargs.pop('includes') - if not isinstance(includes, list): - includes = [includes] + includes = mesonlib.extract_as_list(kwargs, 'includes', pop = True) for inc in includes: if hasattr(inc, 'held_object'): inc = inc.held_object @@ -515,17 +506,17 @@ class GnomeModule(ExtensionModule): # FIXME: Linking directly to libasan is not recommended but g-ir-scanner # does not understand -f LDFLAGS. https://bugzilla.gnome.org/show_bug.cgi?id=783892 # ldflags += compilers.sanitizer_link_args(sanitize) - if kwargs.get('symbol_prefix'): + if 'symbol_prefix' in kwargs: sym_prefix = kwargs.pop('symbol_prefix') if not isinstance(sym_prefix, str): raise MesonException('Gir symbol prefix must be str') scan_command += ['--symbol-prefix=%s' % sym_prefix] - if kwargs.get('identifier_prefix'): + if 'identifier_prefix' in kwargs: identifier_prefix = kwargs.pop('identifier_prefix') if not isinstance(identifier_prefix, str): raise MesonException('Gir identifier prefix must be str') scan_command += ['--identifier-prefix=%s' % identifier_prefix] - if kwargs.get('export_packages'): + if 'export_packages' in kwargs: pkgs = kwargs.pop('export_packages') if isinstance(pkgs, str): scan_command += ['--pkg-export=%s' % pkgs] @@ -534,9 +525,7 @@ class GnomeModule(ExtensionModule): else: raise MesonException('Gir export packages must be str or list') - deps = kwargs.pop('dependencies', []) - if not isinstance(deps, list): - deps = [deps] + deps = mesonlib.extract_as_list(kwargs, 'dependencies', pop = True) deps = (girtarget.get_all_link_deps() + girtarget.get_external_deps() + deps) # Need to recursively add deps on GirTarget sources from our @@ -593,9 +582,7 @@ class GnomeModule(ExtensionModule): for i in gi_includes: scan_command += ['--add-include-path=%s' % i] - inc_dirs = kwargs.pop('include_directories', []) - if not isinstance(inc_dirs, list): - inc_dirs = [inc_dirs] + inc_dirs = mesonlib.extract_as_list(kwargs, 'include_directories', pop = True) for incd in inc_dirs: if not isinstance(incd.held_object, (str, build.IncludeDirs)): raise MesonException( @@ -618,7 +605,7 @@ class GnomeModule(ExtensionModule): scankwargs = {'output': girfile, 'command': scan_command, 'depends': depends} - if kwargs.get('install'): + if 'install' in kwargs: scankwargs['install'] = kwargs['install'] scankwargs['install_dir'] = kwargs.get('install_dir_gir', os.path.join(state.environment.get_datadir(), 'gir-1.0')) @@ -636,7 +623,7 @@ class GnomeModule(ExtensionModule): 'output': typelib_output, 'command': typelib_cmd, } - if kwargs.get('install'): + if 'install' in kwargs: typelib_kwargs['install'] = kwargs['install'] typelib_kwargs['install_dir'] = kwargs.get('install_dir_typelib', os.path.join(state.environment.get_libdir(), 'girepository-1.0')) @@ -759,9 +746,7 @@ This will become a hard error in the future.''') if mode not in VALID_MODES: raise MesonException('gtkdoc: Mode {} is not a valid mode: {}'.format(mode, VALID_MODES)) - src_dirs = kwargs['src_dir'] - if not isinstance(src_dirs, list): - src_dirs = [src_dirs] + src_dirs = mesonlib.extract_as_list(kwargs, 'src_dir') header_dirs = [] for src_dir in src_dirs: if hasattr(src_dir, 'held_object'): @@ -806,9 +791,7 @@ This will become a hard error in the future.''') def _get_build_args(self, kwargs, state): args = [] cflags, ldflags, gi_includes = self._get_dependencies_flags(kwargs.get('dependencies', []), state, include_rpath=True) - inc_dirs = kwargs.get('include_directories', []) - if not isinstance(inc_dirs, list): - inc_dirs = [inc_dirs] + inc_dirs = mesonlib.extract_as_list(kwargs, 'include_directories') for incd in inc_dirs: if not isinstance(incd.held_object, (str, build.IncludeDirs)): raise MesonException( @@ -839,9 +822,7 @@ This will become a hard error in the future.''') if kwarg_name not in kwargs: return [] - new_args = kwargs[kwarg_name] - if not isinstance(new_args, list): - new_args = [new_args] + new_args = mesonlib.extract_as_list(kwargs, kwarg_name) args = [] for i in new_args: if expend_file_state and isinstance(i, mesonlib.File): @@ -1200,12 +1181,8 @@ G_END_DECLS''' @staticmethod def _vapi_args_to_command(prefix, variable, kwargs, accept_vapi=False): - arg_list = kwargs.get(variable) - if not arg_list: - return [] + arg_list = mesonlib.extract_as_list(kwargs, variable) ret = [] - if not isinstance(arg_list, list): - arg_list = [arg_list] for arg in arg_list: if not isinstance(arg, str): types = 'strings' + ' or InternalDependencys' if accept_vapi else '' @@ -1224,9 +1201,7 @@ G_END_DECLS''' arg_list = kwargs.get('packages') if not arg_list: return [], [], [], [] - if not isinstance(arg_list, list): - arg_list = [arg_list] - + arg_list = mesonlib.listify(arg_list) vapi_depends = [] vapi_packages = [] vapi_includes = [] @@ -1300,12 +1275,10 @@ G_END_DECLS''' cmd += pkg_cmd cmd += ['--metadatadir=' + source_dir] - inputs = kwargs.get('sources') - if not inputs: + if 'sources' not in kwargs: raise MesonException('sources are required to generate the vapi file') - if not isinstance(inputs, list): - inputs = [inputs] + inputs = mesonlib.extract_as_list(kwargs, 'sources') link_with = [] for i in inputs: diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 0a0498c..824ba78 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -108,8 +108,7 @@ class PkgConfigModule(ExtensionModule): ofile.write('\n') def process_libs(self, libs): - if not isinstance(libs, list): - libs = [libs] + libs = mesonlib.listify(libs) processed_libs = [] for l in libs: if hasattr(l, 'held_object'): @@ -121,7 +120,7 @@ class PkgConfigModule(ExtensionModule): @permittedKwargs({'libraries', 'version', 'name', 'description', 'filebase', 'subdirs', 'requires', 'requires_private', 'libraries_private', - 'install_dir', 'extra_cflags', 'variables', 'url'}) + 'install_dir', 'extra_cflags', 'variables', 'url', 'd_module_versions'}) def generate(self, state, args, kwargs): if len(args) > 0: raise mesonlib.MesonException('Pkgconfig_gen takes no positional arguments.') @@ -148,6 +147,12 @@ class PkgConfigModule(ExtensionModule): conflicts = mesonlib.stringlistify(kwargs.get('conflicts', [])) extra_cflags = mesonlib.stringlistify(kwargs.get('extra_cflags', [])) + dversions = kwargs.get('d_module_versions', None) + if dversions: + compiler = state.environment.coredata.compilers.get('d') + if compiler: + extra_cflags.extend(compiler.get_feature_args({'versions': dversions})) + def parse_variable_list(stringlist): reserved = ['prefix', 'libdir', 'includedir'] variables = [] diff --git a/mesonbuild/modules/qt4.py b/mesonbuild/modules/qt4.py index 4056b6d..37e630b 100644 --- a/mesonbuild/modules/qt4.py +++ b/mesonbuild/modules/qt4.py @@ -15,7 +15,7 @@ import os from .. import mlog from .. import build -from ..mesonlib import MesonException, Popen_safe +from ..mesonlib import MesonException, Popen_safe, extract_as_list from ..dependencies import Qt4Dependency from . import ExtensionModule import xml.etree.ElementTree as ET @@ -99,21 +99,8 @@ class Qt4Module(ExtensionModule): @permittedKwargs({'moc_headers', 'moc_sources', 'ui_files', 'qresources', 'method'}) def preprocess(self, state, args, kwargs): - rcc_files = kwargs.pop('qresources', []) - if not isinstance(rcc_files, list): - rcc_files = [rcc_files] - ui_files = kwargs.pop('ui_files', []) - if not isinstance(ui_files, list): - ui_files = [ui_files] - moc_headers = kwargs.pop('moc_headers', []) - if not isinstance(moc_headers, list): - moc_headers = [moc_headers] - moc_sources = kwargs.pop('moc_sources', []) - if not isinstance(moc_sources, list): - moc_sources = [moc_sources] - sources = kwargs.pop('sources', []) - if not isinstance(sources, list): - sources = [sources] + rcc_files, ui_files, moc_headers, moc_sources, sources \ + = extract_as_list(kwargs, 'qresources', 'ui_files', 'moc_headers', 'moc_sources', 'sources', pop = True) sources += args[1:] method = kwargs.get('method', 'auto') self._detect_tools(state.environment, method) diff --git a/mesonbuild/modules/qt5.py b/mesonbuild/modules/qt5.py index 6194a23..ef3d52f 100644 --- a/mesonbuild/modules/qt5.py +++ b/mesonbuild/modules/qt5.py @@ -15,7 +15,7 @@ import os from .. import mlog from .. import build -from ..mesonlib import MesonException, Popen_safe +from ..mesonlib import MesonException, Popen_safe, extract_as_list from ..dependencies import Qt5Dependency from . import ExtensionModule import xml.etree.ElementTree as ET @@ -105,21 +105,8 @@ class Qt5Module(ExtensionModule): @permittedKwargs({'moc_headers', 'moc_sources', 'ui_files', 'qresources', 'method'}) def preprocess(self, state, args, kwargs): - rcc_files = kwargs.pop('qresources', []) - if not isinstance(rcc_files, list): - rcc_files = [rcc_files] - ui_files = kwargs.pop('ui_files', []) - if not isinstance(ui_files, list): - ui_files = [ui_files] - moc_headers = kwargs.pop('moc_headers', []) - if not isinstance(moc_headers, list): - moc_headers = [moc_headers] - moc_sources = kwargs.pop('moc_sources', []) - if not isinstance(moc_sources, list): - moc_sources = [moc_sources] - sources = kwargs.pop('sources', []) - if not isinstance(sources, list): - sources = [sources] + rcc_files, ui_files, moc_headers, moc_sources, sources \ + = extract_as_list(kwargs, 'qresources', 'ui_files', 'moc_headers', 'moc_sources', 'sources', pop = True) sources += args[1:] method = kwargs.get('method', 'auto') self._detect_tools(state.environment, method) diff --git a/mesonbuild/modules/unstable_simd.py b/mesonbuild/modules/unstable_simd.py index 828afec..b774cff 100644 --- a/mesonbuild/modules/unstable_simd.py +++ b/mesonbuild/modules/unstable_simd.py @@ -73,9 +73,7 @@ class SimdModule(ExtensionModule): } lib_kwargs.update(basic_kwargs) langarg_key = compiler.get_language() + '_args' - old_lang_args = lib_kwargs.get(langarg_key, []) - if not isinstance(old_lang_args, list): - old_lang_args = [old_lang_args] + old_lang_args = mesonlib.extract_as_list(lib_kwargs, langarg_key) all_lang_args = old_lang_args + args lib_kwargs[langarg_key] = all_lang_args result.append(interpreter.func_static_lib(None, [libname], lib_kwargs)) diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index 6fef5bb..ab215dc 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -16,7 +16,7 @@ import os from .. import mlog from .. import mesonlib, dependencies, build -from ..mesonlib import MesonException +from ..mesonlib import MesonException, extract_as_list from . import get_include_args from . import ModuleReturnValue from . import ExtensionModule @@ -35,9 +35,7 @@ class WindowsModule(ExtensionModule): comp = self.detect_compiler(state.compilers) extra_args = mesonlib.stringlistify(kwargs.get('args', [])) - inc_dirs = kwargs.pop('include_directories', []) - if not isinstance(inc_dirs, list): - inc_dirs = [inc_dirs] + inc_dirs = extract_as_list(kwargs, 'include_directories', pop = True) for incd in inc_dirs: if not isinstance(incd.held_object, (str, build.IncludeDirs)): raise MesonException('Resource include dirs should be include_directories().') diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index b88c5ef..b88c5ef 100755..100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py diff --git a/msi/License.rtf b/msi/License.rtf index 9b58df9..9b58df9 100755..100644 --- a/msi/License.rtf +++ b/msi/License.rtf diff --git a/run_unittests.py b/run_unittests.py index 6487496..2626931 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1948,6 +1948,11 @@ endian = 'little' crossfile.flush() self.init(testdir, ['--cross-file='+crossfile.name]) + def test_reconfigure(self): + testdir = os.path.join(self.unit_test_dir, '13 reconfigure') + self.init(testdir, ['-Db_lto=true'], default_args=False) + self.build('reconfigure') + class LinuxArmCrossCompileTests(BasePlatformTests): ''' diff --git a/test cases/common/146 C and CPP link/dummy.c b/test cases/common/146 C and CPP link/dummy.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/common/146 C and CPP link/dummy.c diff --git a/test cases/common/146 C and CPP link/meson.build b/test cases/common/146 C and CPP link/meson.build index db84445..2dd3364 100644 --- a/test cases/common/146 C and CPP link/meson.build +++ b/test cases/common/146 C and CPP link/meson.build @@ -65,10 +65,44 @@ libfoo = shared_library( # # VS2010 lacks the /WHOLEARCHIVE option that later versions of MSVC support, so # don't run this tests on that backend. -if meson.backend() != 'vs2010' +if not (cxx.get_id() == 'msvc' and cxx.version().version_compare('<19')) libfoowhole = shared_library( 'foowhole', ['foobar.c', 'foobar.h'], link_whole : [libc, libcpp], ) endif + +# Test sublinking (linking C and C++, then linking that to C) +libfoo_static = static_library( + 'foo_static', + ['foobar.c', 'foobar.h'], + link_with : [libc, libcpp], +) + +libsub = shared_library( + 'sub', + ['sub.c', 'sub.h'], + link_with : libfoo_static, +) + +if not (cxx.get_id() == 'msvc' and cxx.version().version_compare('<19')) + libsubwhole = shared_library( + 'subwhole', + ['sub.c', 'sub.h'], + link_whole : libfoo_static, + ) +endif + +# Test that it really is recursive +libsub_static = static_library( + 'sub_static', + ['sub.c', 'sub.h'], + link_with : libfoo_static, +) + +libsubsub = shared_library( + 'subsub', + ['dummy.c'], + link_with : libsub_static, +) diff --git a/test cases/common/146 C and CPP link/sub.c b/test cases/common/146 C and CPP link/sub.c new file mode 100644 index 0000000..7c078f8 --- /dev/null +++ b/test cases/common/146 C and CPP link/sub.c @@ -0,0 +1,19 @@ +/* Copyright © 2017 Dylan Baker + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "sub.h" + +float a_half(void) { + return .5; +} diff --git a/test cases/common/146 C and CPP link/sub.h b/test cases/common/146 C and CPP link/sub.h new file mode 100644 index 0000000..5b02e17 --- /dev/null +++ b/test cases/common/146 C and CPP link/sub.h @@ -0,0 +1,16 @@ +/* Copyright © 2017 Dylan Baker + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +float a_half(void); diff --git a/test cases/common/47 options/meson.build b/test cases/common/47 options/meson.build index 4058748..2a764f0 100644 --- a/test cases/common/47 options/meson.build +++ b/test cases/common/47 options/meson.build @@ -12,6 +12,7 @@ if get_option('combo_opt') != 'combo' error('Incorrect value to combo option.') endif +# If the default changes, update test cases/unit/13 reconfigure if get_option('b_lto') != false error('Incorrect value in base option.') endif diff --git a/test cases/d/9 features/app.d b/test cases/d/9 features/app.d new file mode 100644 index 0000000..37cc1dd --- /dev/null +++ b/test cases/d/9 features/app.d @@ -0,0 +1,51 @@ + +import std.stdio; +import std.array : split; +import std.string : strip; + +auto getMenu () +{ + auto foods = import ("food.txt").strip.split ("\n"); + return foods; +} + +auto getPeople () +{ + return import ("people.txt").strip.split ("\n"); +} + +void main (string[] args) +{ + import std.array : join; + import core.stdc.stdlib : exit; + + immutable request = args[1]; + if (request == "menu") { + version (No_Menu) { + } else { + writeln ("On the menu: ", getMenu.join (", ")); + exit (0); + } + } + + version (With_People) { + if (request == "people") { + writeln ("People: ", getPeople.join (", ")); + exit (0); + } + } + + // we fail here + exit (1); +} + +unittest +{ + writeln ("TEST"); + import core.stdc.stdlib : exit; + + writeln(getMenu); + assert (getMenu () == ["Spam", "Eggs", "Spam", "Baked Beans", "Spam", "Spam"]); + + exit (0); +} diff --git a/test cases/d/9 features/data/food.txt b/test cases/d/9 features/data/food.txt new file mode 100644 index 0000000..8275dd0 --- /dev/null +++ b/test cases/d/9 features/data/food.txt @@ -0,0 +1,6 @@ +Spam +Eggs +Spam +Baked Beans +Spam +Spam diff --git a/test cases/d/9 features/data/people.txt b/test cases/d/9 features/data/people.txt new file mode 100644 index 0000000..abbae06 --- /dev/null +++ b/test cases/d/9 features/data/people.txt @@ -0,0 +1,5 @@ +Rick +Morty +Summer +Beth +Jerry diff --git a/test cases/d/9 features/meson.build b/test cases/d/9 features/meson.build new file mode 100644 index 0000000..9e63710 --- /dev/null +++ b/test cases/d/9 features/meson.build @@ -0,0 +1,29 @@ +project('D Features', 'd') + +# directory for data +data_dir = join_paths(meson.current_source_dir(), 'data') + +e_plain = executable('dapp_menu', + 'app.d', + d_import_dirs: [data_dir] +) +test('dapp_menu_t_fail', e_plain, should_fail: true) +test('dapp_menu_t', e_plain, args: ['menu']) + +# test feature versions and string imports +e_versions = executable('dapp_versions', + 'app.d', + d_import_dirs: [data_dir], + d_module_versions: ['No_Menu', 'With_People'] +) +test('dapp_versions_t_fail', e_versions, args: ['menu'], should_fail: true) +test('dapp_versions_t', e_versions, args: ['people']) + +# test everything and unittests +e_test = executable('dapp_test', + 'app.d', + d_import_dirs: [data_dir], + d_module_versions: ['No_Menu', 'With_People'], + d_unittest: true +) +test('dapp_test', e_test) diff --git a/test cases/unit/13 reconfigure/meson.build b/test cases/unit/13 reconfigure/meson.build new file mode 100644 index 0000000..102180e --- /dev/null +++ b/test cases/unit/13 reconfigure/meson.build @@ -0,0 +1,5 @@ +project('reconfigure test', ['c']) + +if get_option('b_lto') != true + error('b_lto not set') +endif diff --git a/test cases/vala/12 custom output/bar.vala b/test cases/vala/12 custom output/bar.vala new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/vala/12 custom output/bar.vala diff --git a/test cases/vala/12 custom output/meson.build b/test cases/vala/12 custom output/meson.build index ef6dbb5..c328959 100644 --- a/test cases/vala/12 custom output/meson.build +++ b/test cases/vala/12 custom output/meson.build @@ -3,7 +3,11 @@ project('valatest', 'c', 'vala') glib = dependency('glib-2.0') gobject = dependency('gobject-2.0') -library('foo-1.0', 'foo.vala', - vala_header: 'foo.h', - vala_vapi: 'foo.vapi', +foo_lib = library('foo-1.0', 'foo.vala', + vala_header: 'foo.h', + vala_vapi: 'foo.vapi', + dependencies: [glib, gobject]) + +library('bar', 'bar.vala', + link_with: [foo_lib], dependencies: [glib, gobject]) |