diff --git a/ci/ciimage/opensuse/install.sh b/ci/ciimage/opensuse/install.sh index b0097172a8a9..9e1f1c530425 100755 --- a/ci/ciimage/opensuse/install.sh +++ b/ci/ciimage/opensuse/install.sh @@ -38,11 +38,13 @@ chmod +x /ci/env_vars.sh source /ci/env_vars.sh -dub_fetch urld -dub build urld --compiler=dmd -dub_fetch dubtestproject +dub_fetch dubtestproject@1.2.0 dub build dubtestproject:test1 --compiler=dmd dub build dubtestproject:test2 --compiler=dmd +dub build dubtestproject:test3 --compiler=dmd +dub_fetch urld@3.0.0 +dub build urld --compiler=dmd + # Cleanup zypper --non-interactive clean --all diff --git a/ci/ciimage/ubuntu-rolling/install.sh b/ci/ciimage/ubuntu-rolling/install.sh index 63e497073ad9..9f7551f2b7b8 100755 --- a/ci/ciimage/ubuntu-rolling/install.sh +++ b/ci/ciimage/ubuntu-rolling/install.sh @@ -45,11 +45,12 @@ eatmydata apt-get -y install --no-install-recommends wine-stable # Wine is spec install_python_packages hotdoc # dub stuff -dub_fetch urld -dub build urld --compiler=gdc -dub_fetch dubtestproject +dub_fetch dubtestproject@1.2.0 dub build dubtestproject:test1 --compiler=ldc2 dub build dubtestproject:test2 --compiler=ldc2 +dub build dubtestproject:test3 --compiler=gdc +dub_fetch urld@3.0.0 +dub build urld --compiler=gdc # Remove debian version of Rust and install latest with rustup. # This is needed to get the cross toolchain as well. diff --git a/mesonbuild/dependencies/dub.py b/mesonbuild/dependencies/dub.py index 1c904ab2a5af..491703b186fc 100644 --- a/mesonbuild/dependencies/dub.py +++ b/mesonbuild/dependencies/dub.py @@ -5,10 +5,11 @@ from .base import ExternalDependency, DependencyException, DependencyTypeName from .pkgconfig import PkgConfigDependency -from ..mesonlib import (Popen_safe, join_args, version_compare) +from ..mesonlib import (Popen_safe, join_args, version_compare, version_compare_many) from ..options import OptionKey from ..programs import ExternalProgram from .. import mlog +from enum import Enum import re import os import json @@ -56,6 +57,10 @@ class FindTargetEntry(TypedDict): search: str artifactPath: str +class DubDescriptionSource(Enum): + Local = 'local' + External = 'external' + class DubDependency(ExternalDependency): # dub program and version class_dubbin: T.Optional[T.Tuple[ExternalProgram, str]] = None @@ -87,7 +92,6 @@ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T. if DubDependency.class_dubbin is None: if self.required: raise DependencyException('DUB not found.') - self.is_found = False return (self.dubbin, dubver) = DubDependency.class_dubbin # pylint: disable=unpacking-non-sequence @@ -108,20 +112,11 @@ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T. mlog.warning(f'DUB dependency {name} not found because Dub {dubver} ' "is not compatible with Meson. (Can't locate artifacts in DUB's cache)." ' Upgrade to Dub >= 1.35') - self.is_found = False return mlog.debug('Determining dependency {!r} with DUB executable ' '{!r}'.format(name, self.dubbin.get_path())) - # if an explicit version spec was stated, use this when querying Dub - main_pack_spec = name - if 'version' in kwargs: - version_spec = kwargs['version'] - if isinstance(version_spec, list): - version_spec = " ".join(version_spec) - main_pack_spec = f'{name}@{version_spec}' - # we need to know the target architecture dub_arch = self.compiler.arch @@ -135,37 +130,11 @@ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T. elif dub_buildtype == 'minsize': dub_buildtype = 'release' - # A command that might be useful in case of missing DUB package - def dub_build_deep_command() -> str: - if self._dub_has_build_deep: - cmd = ['dub', 'build', '--deep'] - else: - cmd = ['dub', 'run', '--yes', 'dub-build-deep', '--'] - - return join_args(cmd + [ - main_pack_spec, - '--arch=' + dub_arch, - '--compiler=' + self.compiler.get_exelist()[-1], - '--build=' + dub_buildtype - ]) - - # Ask dub for the package - describe_cmd = [ - 'describe', main_pack_spec, '--arch=' + dub_arch, - '--build=' + dub_buildtype, '--compiler=' + self.compiler.get_exelist()[-1] - ] - ret, res, err = self._call_dubbin(describe_cmd) - - if ret != 0: - mlog.debug('DUB describe failed: ' + err) - if 'locally' in err: - mlog.error(mlog.bold(main_pack_spec), 'is not present locally. You may try the following command:') - mlog.log(mlog.bold(dub_build_deep_command())) - self.is_found = False + result = self._get_dub_description(dub_arch, dub_buildtype) + if result is None: return - + description, build_cmd, description_source = result dub_comp_id = self._ID_MAP[self.compiler.get_id()] - description: DubDescription = json.loads(res) self.compile_args = [] self.link_args = self.raw_link_args = [] @@ -204,7 +173,7 @@ def find_package_target(pkg: DubPackDesc) -> bool: mlog.error(mlog.bold(pack_id), 'not found') mlog.log('You may try the following command to install the necessary DUB libraries:') - mlog.log(mlog.bold(dub_build_deep_command())) + mlog.log(mlog.bold(build_cmd)) return False @@ -223,33 +192,45 @@ def find_package_target(pkg: DubPackDesc) -> bool: # 4. Add other build settings (imports, versions etc.) # 1 - self.is_found = False packages: T.Dict[str, DubPackDesc] = {} + found_it = False for pkg in description['packages']: packages[pkg['name']] = pkg if not pkg['active']: continue - if pkg['targetType'] == 'dynamicLibrary': - mlog.error('DUB dynamic library dependencies are not supported.') - self.is_found = False - return - # check that the main dependency is indeed a library if pkg['name'] == name: - self.is_found = True - if pkg['targetType'] not in ['library', 'sourceLibrary', 'staticLibrary']: - mlog.error(mlog.bold(name), "found but it isn't a library") - self.is_found = False + mlog.error(mlog.bold(name), "found but it isn't a static library, it is:", + pkg['targetType']) return + if self.version_reqs is not None: + ver = pkg['version'] + if not version_compare_many(ver, self.version_reqs)[0]: + mlog.error(mlog.bold(f'{name}@{ver}'), + 'does not satisfy all version requirements of:', + ' '.join(self.version_reqs)) + return + + found_it = True self.version = pkg['version'] self.pkg = pkg + if not found_it: + mlog.error(f'Could not find {name} in DUB description.') + if description_source is DubDescriptionSource.Local: + mlog.log('Make sure that the dependency is registered for your dub project by running:') + mlog.log(mlog.bold(f'dub add {name}')) + elif description_source is DubDescriptionSource.External: + # It shouldn't be possible to get here + mlog.log('Make sure that the dependency is built:') + mlog.log(mlog.bold(build_cmd)) + return + if name not in targets: - self.is_found = False if self.pkg['targetType'] == 'sourceLibrary': # source libraries have no associated targets, # but some build settings like import folders must be found from the package object. @@ -258,10 +239,7 @@ def find_package_target(pkg: DubPackDesc) -> bool: # (See openssl DUB package for example of sourceLibrary) mlog.error('DUB targets of type', mlog.bold('sourceLibrary'), 'are not supported.') else: - mlog.error('Could not find target description for', mlog.bold(main_pack_spec)) - - if not self.is_found: - mlog.error(f'Could not find {name} in DUB description') + mlog.error('Could not find target description for', mlog.bold(self.name)) return # Current impl only supports static libraries @@ -269,19 +247,17 @@ def find_package_target(pkg: DubPackDesc) -> bool: # 2 if not find_package_target(self.pkg): - self.is_found = False return # 3 for link_dep in targets[name]['linkDependencies']: pkg = packages[link_dep] if not find_package_target(pkg): - self.is_found = False return if show_buildtype_warning: mlog.log('If it is not suitable, try the following command and reconfigure Meson with', mlog.bold('--clearcache')) - mlog.log(mlog.bold(dub_build_deep_command())) + mlog.log(mlog.bold(build_cmd)) # 4 bs = targets[name]['buildSettings'] @@ -345,6 +321,60 @@ def find_package_target(pkg: DubPackDesc) -> bool: # fallback self.link_args.append('-l'+lib) + self.is_found = True + + # Get the dub description needed to resolve the dependency and a + # build command that can be used to build the dependency in case it is + # not present. + def _get_dub_description(self, dub_arch: str, dub_buildtype: str) -> T.Optional[T.Tuple[DubDescription, str, DubDescriptionSource]]: + def get_build_command() -> T.List[str]: + if self._dub_has_build_deep: + cmd = ['dub', 'build', '--deep'] + else: + cmd = ['dub', 'run', '--yes', 'dub-build-deep', '--'] + + return cmd + [ + '--arch=' + dub_arch, + '--compiler=' + self.compiler.get_exelist()[-1], + '--build=' + dub_buildtype, + ] + + # Ask dub for the package + describe_cmd = [ + 'describe', '--arch=' + dub_arch, + '--build=' + dub_buildtype, '--compiler=' + self.compiler.get_exelist()[-1] + ] + helper_build = join_args(get_build_command()) + source = DubDescriptionSource.Local + ret, res, err = self._call_dubbin(describe_cmd) + if ret == 0: + return (json.loads(res), helper_build, source) + + pack_spec = self.name + if self.version_reqs is not None: + if len(self.version_reqs) > 1: + mlog.error('Multiple version requirements are not supported for raw dub dependencies.') + mlog.error("Please specify only an exact version like '1.2.3'") + raise DependencyException('Multiple version requirements are not solvable for raw dub depencies') + elif len(self.version_reqs) == 1: + pack_spec += '@' + self.version_reqs[0] + + describe_cmd = [ + 'describe', pack_spec, '--arch=' + dub_arch, + '--build=' + dub_buildtype, '--compiler=' + self.compiler.get_exelist()[-1] + ] + helper_build = join_args(get_build_command() + [pack_spec]) + source = DubDescriptionSource.External + ret, res, err = self._call_dubbin(describe_cmd) + if ret == 0: + return (json.loads(res), helper_build, source) + + mlog.debug('DUB describe failed: ' + err) + if 'locally' in err: + mlog.error(mlog.bold(pack_spec), 'is not present locally. You may try the following command:') + mlog.log(mlog.bold(helper_build)) + return None + # This function finds the target of the provided JSON package, built for the right # compiler, architecture, configuration... # It returns (target|None, {compatibilities}) @@ -469,7 +499,7 @@ def _get_comp_versions_to_find(self, dub_comp_id: str) -> T.List[str]: def _call_dubbin(self, args: T.List[str], env: T.Optional[T.Dict[str, str]] = None) -> T.Tuple[int, str, str]: assert isinstance(self.dubbin, ExternalProgram) - p, out, err = Popen_safe(self.dubbin.get_command() + args, env=env) + p, out, err = Popen_safe(self.dubbin.get_command() + args, env=env, cwd=self.env.get_source_dir()) return p.returncode, out.strip(), err.strip() def _call_compbin(self, args: T.List[str], env: T.Optional[T.Dict[str, str]] = None) -> T.Tuple[int, str, str]: diff --git a/test cases/d/11 dub/meson.build b/test cases/d/11 dub/meson.build index 91955710e709..d4c6fa440191 100644 --- a/test cases/d/11 dub/meson.build +++ b/test cases/d/11 dub/meson.build @@ -17,7 +17,7 @@ test('test urld', test_exe) # If you want meson to generate/update a dub.json file dlang = import('dlang') -dlang.generate_dub_file(meson.project_name().to_lower(), meson.source_root(), +dlang.generate_dub_file(meson.project_name().to_lower(), meson.build_root(), authors: 'Meson Team', description: 'Test executable', copyright: 'Copyright © 2018, Meson Team', @@ -25,4 +25,4 @@ dlang.generate_dub_file(meson.project_name().to_lower(), meson.source_root(), sourceFiles: 'test.d', targetType: 'executable', dependencies: urld_dep -) \ No newline at end of file +) diff --git a/test cases/d/17 dub and meson project/.gitignore b/test cases/d/17 dub and meson project/.gitignore new file mode 100644 index 000000000000..0036ff3946de --- /dev/null +++ b/test cases/d/17 dub and meson project/.gitignore @@ -0,0 +1,2 @@ +17-dub-meson-project* +lib17-dub-meson-project* diff --git a/test cases/d/17 dub and meson project/dub.json b/test cases/d/17 dub and meson project/dub.json new file mode 100644 index 000000000000..f04e51b551c4 --- /dev/null +++ b/test cases/d/17 dub and meson project/dub.json @@ -0,0 +1,11 @@ +{ + "name": "17-dub-meson-project", + "dependencies": { + "urld": ">=3.0.0 <3.0.1", + "dubtestproject:test3": "1.2.0", + ":multi-configuration": "*" + }, + "subPackages": [ + "multi-configuration" + ] +} diff --git a/test cases/d/17 dub and meson project/dub.selections.json b/test cases/d/17 dub and meson project/dub.selections.json new file mode 100644 index 000000000000..ad72f31683c5 --- /dev/null +++ b/test cases/d/17 dub and meson project/dub.selections.json @@ -0,0 +1,7 @@ +{ + "fileVersion": 1, + "versions": { + "dubtestproject": "1.2.0", + "urld": "3.0.0" + } +} diff --git a/test cases/d/17 dub and meson project/meson.build b/test cases/d/17 dub and meson project/meson.build new file mode 100644 index 000000000000..8bc7d83e74a4 --- /dev/null +++ b/test cases/d/17 dub and meson project/meson.build @@ -0,0 +1,32 @@ +project('Dub dependency respects dub.selections.json', 'd') + +dub_exe = find_program('dub', required : false) +if not dub_exe.found() + error('MESON_SKIP_TEST: Dub not found') +endif + +dub_ver = dub_exe.version() +if not dub_ver.version_compare('>=1.35.0') + error('MESON_SKIP_TEST: test requires dub >=1.35.0') +endif + +# Multiple versions supported +urld = dependency('urld', method: 'dub', version: [ '>=3.0.0', '<3.0.1' ]) + +# The version we got is the one in dub.selections.json +version = urld.version() +if version != '3.0.0' + error(f'Expected urld version to be the one selected in dub.selections.json but got @version@') +endif + +# dependency calls from subdirectories respect meson.source_root()/dub.selections.json +subdir('x/y/z') + +# dependencies respect their configuration selected in dub.json +run_command(dub_exe, 'build', '--deep', ':multi-configuration', + '--compiler', meson.get_compiler('d').cmd_array()[0], + '--arch', host_machine.cpu_family(), + '--root', meson.source_root(), + '--config', 'lib', + check: true) +found = dependency('17-dub-meson-project:multi-configuration', method: 'dub') diff --git a/test cases/d/17 dub and meson project/multi-configuration/.gitignore b/test cases/d/17 dub and meson project/multi-configuration/.gitignore new file mode 100644 index 000000000000..61763ee20b1b --- /dev/null +++ b/test cases/d/17 dub and meson project/multi-configuration/.gitignore @@ -0,0 +1,2 @@ +libmulti-configuration* +multi-configuration* diff --git a/test cases/d/17 dub and meson project/multi-configuration/dub.json b/test cases/d/17 dub and meson project/multi-configuration/dub.json new file mode 100644 index 000000000000..373176d9ecc8 --- /dev/null +++ b/test cases/d/17 dub and meson project/multi-configuration/dub.json @@ -0,0 +1,14 @@ +{ + "name": "multi-configuration", + "configurations": { + "app": { + "targetType": "executable" + }, + "lib": { + "targetType": "library", + "excludedSourceFiles": [ + "source/app.d" + ] + } + } +} diff --git a/test cases/d/17 dub and meson project/multi-configuration/dub.selections.json b/test cases/d/17 dub and meson project/multi-configuration/dub.selections.json new file mode 100644 index 000000000000..322586b10676 --- /dev/null +++ b/test cases/d/17 dub and meson project/multi-configuration/dub.selections.json @@ -0,0 +1,5 @@ +{ + "fileVersion": 1, + "versions": { + } +} diff --git a/test cases/d/17 dub and meson project/multi-configuration/source/app.d b/test cases/d/17 dub and meson project/multi-configuration/source/app.d new file mode 100644 index 000000000000..d66321b3c581 --- /dev/null +++ b/test cases/d/17 dub and meson project/multi-configuration/source/app.d @@ -0,0 +1 @@ +void main () {} diff --git a/test cases/d/17 dub and meson project/multi-configuration/source/foo.d b/test cases/d/17 dub and meson project/multi-configuration/source/foo.d new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test cases/d/17 dub and meson project/source/app.d b/test cases/d/17 dub and meson project/source/app.d new file mode 100644 index 000000000000..d66321b3c581 --- /dev/null +++ b/test cases/d/17 dub and meson project/source/app.d @@ -0,0 +1 @@ +void main () {} diff --git a/test cases/d/17 dub and meson project/x/y/z/dub.json b/test cases/d/17 dub and meson project/x/y/z/dub.json new file mode 100644 index 000000000000..dc0ef515964a --- /dev/null +++ b/test cases/d/17 dub and meson project/x/y/z/dub.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "dubtestproject:test3": "*" + }, + "name": "dub-respects-project-root-subdir" +} diff --git a/test cases/d/17 dub and meson project/x/y/z/dub.selections.json b/test cases/d/17 dub and meson project/x/y/z/dub.selections.json new file mode 100644 index 000000000000..daf7a2de9823 --- /dev/null +++ b/test cases/d/17 dub and meson project/x/y/z/dub.selections.json @@ -0,0 +1,6 @@ +{ + "fileVersion": 1, + "versions": { + "dubtestproject": "1.3.0" + } +} diff --git a/test cases/d/17 dub and meson project/x/y/z/meson.build b/test cases/d/17 dub and meson project/x/y/z/meson.build new file mode 100644 index 000000000000..84430de07715 --- /dev/null +++ b/test cases/d/17 dub and meson project/x/y/z/meson.build @@ -0,0 +1,6 @@ +root = meson.source_root() +dep = dependency('dubtestproject:test3', method: 'dub') +version = dep.version() +if version != '1.2.0' + error(f'Expected urld version to be the one selected in "@root@/dub.selections.json" but got @version@') +endif diff --git a/test cases/failing/132 dub missing dependency/dub.json b/test cases/failing/132 dub missing dependency/dub.json new file mode 100644 index 000000000000..1ad9ddcc1f19 --- /dev/null +++ b/test cases/failing/132 dub missing dependency/dub.json @@ -0,0 +1,3 @@ +{ + "name": "132-missing-dep" +} diff --git a/test cases/failing/132 dub missing dependency/dub.selections.json b/test cases/failing/132 dub missing dependency/dub.selections.json new file mode 100644 index 000000000000..322586b10676 --- /dev/null +++ b/test cases/failing/132 dub missing dependency/dub.selections.json @@ -0,0 +1,5 @@ +{ + "fileVersion": 1, + "versions": { + } +} diff --git a/test cases/failing/132 dub missing dependency/meson.build b/test cases/failing/132 dub missing dependency/meson.build new file mode 100644 index 000000000000..fcccb3b415d4 --- /dev/null +++ b/test cases/failing/132 dub missing dependency/meson.build @@ -0,0 +1,17 @@ +project('Dub dependency not in dub.json') + +if not add_languages('d', required: false) + error('MESON_SKIP_TEST test requires D compiler') +endif + +dub_exe = find_program('dub', required : false) +if not dub_exe.found() + error('MESON_SKIP_TEST: Dub not found') +endif + +dub_ver = dub_exe.version() +if not dub_ver.version_compare('>=1.35.0') + error('MESON_SKIP_TEST: test requires dub >=1.35.0') +endif + +dep = dependency('urld', method: 'dub') # not in dub.json diff --git a/test cases/failing/132 dub missing dependency/source/app.d b/test cases/failing/132 dub missing dependency/source/app.d new file mode 100644 index 000000000000..d66321b3c581 --- /dev/null +++ b/test cases/failing/132 dub missing dependency/source/app.d @@ -0,0 +1 @@ +void main () {} diff --git a/test cases/failing/132 dub missing dependency/test.json b/test cases/failing/132 dub missing dependency/test.json new file mode 100644 index 000000000000..e58dbc7aabee --- /dev/null +++ b/test cases/failing/132 dub missing dependency/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "line": "test cases/failing/132 dub missing dependency/meson.build:17:6: ERROR: Dependency \"urld\" not found", + "line": "dub add urld" + } + ] +}