aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTheQwertiest <qwertiest@mail.ru>2020-05-21 17:05:04 +0300
committerJussi Pakkanen <jpakkane@gmail.com>2020-06-29 19:54:38 +0300
commit5696a5abbaaff75279d9c50d431de47f35dc6228 (patch)
treee5b4dadb1d7bf676ebd46b8bde9c13190bb02269
parent4d0233540f15c686c199d8f464fc7499a094645e (diff)
downloadmeson-5696a5abbaaff75279d9c50d431de47f35dc6228.zip
meson-5696a5abbaaff75279d9c50d431de47f35dc6228.tar.gz
meson-5696a5abbaaff75279d9c50d431de47f35dc6228.tar.bz2
Added ability to specify target in `meson compile`
-rw-r--r--docs/markdown/Commands.md39
-rw-r--r--docs/markdown/snippets/add_meson_compile_target.md19
-rw-r--r--mesonbuild/mcompile.py209
-rwxr-xr-xrun_unittests.py68
4 files changed, 291 insertions, 44 deletions
diff --git a/docs/markdown/Commands.md b/docs/markdown/Commands.md
index 4d3de55..e2a352a 100644
--- a/docs/markdown/Commands.md
+++ b/docs/markdown/Commands.md
@@ -136,24 +136,30 @@ meson configure builddir -Doption=new_value
*(since 0.54.0)*
```
-$ meson compile [-h] [-j JOBS] [-l LOAD_AVERAGE] [--clean] [-C BUILDDIR]
+$ meson compile [-h] [--clean] [-C BUILDDIR] [-j JOBS] [-l LOAD_AVERAGE]
[--verbose] [--ninja-args NINJA_ARGS] [--vs-args VS_ARGS]
+ [TARGET [TARGET ...]]
```
Builds a default or a specified target of a configured meson project.
```
+positional arguments:
+ TARGET Targets to build. Target has the
+ following format: [PATH_TO_TARGET/]TARGE
+ T_NAME[:TARGET_TYPE].
+
optional arguments:
-h, --help show this help message and exit
+ --clean Clean the build directory.
+ -C BUILDDIR The directory containing build files to
+ be built.
-j JOBS, --jobs JOBS The number of worker jobs to run (if
supported). If the value is less than 1
the build program will guess.
-l LOAD_AVERAGE, --load-average LOAD_AVERAGE
The system load average to try to
- maintain (if supported)
- --clean Clean the build directory.
- -C BUILDDIR The directory containing build files to
- be built.
+ maintain (if supported).
--verbose Show more verbose output.
--ninja-args NINJA_ARGS Arguments to pass to `ninja` (applied
only on `ninja` backend).
@@ -161,6 +167,19 @@ optional arguments:
only on `vs` backend).
```
+`--verbose` argument is available since 0.55.0.
+
+#### Targets
+
+*(since 0.55.0)*
+
+`TARGET` has the following syntax `[PATH/]NAME[:TYPE]`, where:
+- `NAME`: name of the target from `meson.build` (e.g. `foo` from `executable('foo', ...)`).
+- `PATH`: path to the target relative to the root `meson.build` file. Note: relative path for a target specified in the root `meson.build` is `./`.
+- `TYPE`: type of the target. Can be one of the following: 'executable', 'static_library', 'shared_library', 'shared_module', 'custom', 'run', 'jar'.
+
+`PATH` and/or `TYPE` can be ommited if the resulting `TARGET` can be used to uniquely identify the target in `meson.build`.
+
#### Backend specific arguments
*(since 0.55.0)*
@@ -193,6 +212,16 @@ Execute a dry run on ninja backend with additional debug info:
meson compile --ninja-args=-n,-d,explain
```
+Build three targets: two targets that have the same `foo` name, but different type, and a `bar` target:
+```
+meson compile foo:shared_library foo:static_library bar
+```
+
+Produce a coverage html report (if available):
+```
+meson compile coverage-html
+```
+
### dist
*(since 0.52.0)*
diff --git a/docs/markdown/snippets/add_meson_compile_target.md b/docs/markdown/snippets/add_meson_compile_target.md
new file mode 100644
index 0000000..d75862f
--- /dev/null
+++ b/docs/markdown/snippets/add_meson_compile_target.md
@@ -0,0 +1,19 @@
+## Added ability to specify targets in `meson compile`
+
+It's now possible to specify targets in `meson compile`, which will result in building only the requested targets.
+
+Usage: `meson compile [TARGET [TARGET...]]`
+`TARGET` has the following syntax: `[PATH/]NAME[:TYPE]`.
+`NAME`: name of the target from `meson.build` (e.g. `foo` from `executable('foo', ...)`).
+`PATH`: path to the target relative to the root `meson.build` file. Note: relative path for a target specified in the root `meson.build` is `./`.
+`TYPE`: type of the target (e.g. `shared_library`, `executable` and etc)
+
+`PATH` and/or `TYPE` can be ommited if the resulting `TARGET` can be used to uniquely identify the target in `meson.build`.
+
+For example targets from the following code:
+```meson
+shared_library('foo', ...)
+static_library('foo', ...)
+executable('bar', ...)
+```
+can be invoked with `meson compile foo:shared_library foo:static_library bar`.
diff --git a/mesonbuild/mcompile.py b/mesonbuild/mcompile.py
index 3799ce3..9fe3a65 100644
--- a/mesonbuild/mcompile.py
+++ b/mesonbuild/mcompile.py
@@ -14,9 +14,11 @@
"""Entrypoint script for backend agnostic compile."""
-import argparse
+import json
+import re
import sys
import typing as T
+from collections import defaultdict
from pathlib import Path
from . import mlog
@@ -26,10 +28,13 @@ from .mesonlib import MesonException
from mesonbuild.environment import detect_ninja
from mesonbuild.coredata import UserArrayOption
+if T.TYPE_CHECKING:
+ import argparse
+
def array_arg(value: str) -> T.List[str]:
return UserArrayOption(None, value, allow_dups=True, user_input=True).value
-def validate_builddir(builddir: Path):
+def validate_builddir(builddir: Path) -> None:
if not (builddir / 'meson-private' / 'coredata.dat' ).is_file():
raise MesonException('Current directory is not a meson build directory: `{}`.\n'
'Please specify a valid build dir or change the working directory to it.\n'
@@ -42,7 +47,93 @@ def get_backend_from_coredata(builddir: Path) -> str:
"""
return coredata.load(str(builddir)).get_builtin_option('backend')
-def get_parsed_args_ninja(options: 'argparse.Namespace', builddir: Path):
+def parse_introspect_data(builddir: Path) -> T.Dict[str, T.List[dict]]:
+ """
+ Converts a List of name-to-dict to a dict of name-to-dicts (since names are not unique)
+ """
+ path_to_intro = builddir / 'meson-info' / 'intro-targets.json'
+ if not path_to_intro.exists():
+ raise MesonException('`{}` is missing! Directory is not configured yet?'.format(path_to_intro.name))
+ with path_to_intro.open() as f:
+ schema = json.load(f)
+
+ parsed_data = defaultdict(list) # type: T.Dict[str, T.List[dict]]
+ for target in schema:
+ parsed_data[target['name']] += [target]
+ return parsed_data
+
+class ParsedTargetName:
+ full_name = ''
+ name = ''
+ type = ''
+ path = ''
+
+ def __init__(self, target: str):
+ self.full_name = target
+ split = target.rsplit(':', 1)
+ if len(split) > 1:
+ self.type = split[1]
+ if not self._is_valid_type(self.type):
+ raise MesonException('Can\'t invoke target `{}`: unknown target type: `{}`'.format(target, self.type))
+
+ split = split[0].rsplit('/', 1)
+ if len(split) > 1:
+ self.path = split[0]
+ self.name = split[1]
+ else:
+ self.name = split[0]
+
+ @staticmethod
+ def _is_valid_type(type: str) -> bool:
+ # Ammend docs in Commands.md when editing this list
+ allowed_types = {
+ 'executable',
+ 'static_library',
+ 'shared_library',
+ 'shared_module',
+ 'custom',
+ 'run',
+ 'jar',
+ }
+ return type in allowed_types
+
+def get_target_from_intro_data(target: ParsedTargetName, builddir: Path, introspect_data: dict) -> dict:
+ if target.name not in introspect_data:
+ raise MesonException('Can\'t invoke target `{}`: target not found'.format(target.full_name))
+
+ intro_targets = introspect_data[target.name]
+ found_targets = []
+
+ resolved_bdir = builddir.resolve()
+
+ if not target.type and not target.path:
+ found_targets = intro_targets
+ else:
+ for intro_target in intro_targets:
+ if (intro_target['subproject'] or
+ (target.type and target.type != intro_target['type'].replace(' ', '_')) or
+ (target.path
+ and intro_target['filename'] != 'no_name'
+ and Path(target.path) != Path(intro_target['filename'][0]).relative_to(resolved_bdir).parent)):
+ continue
+ found_targets += [intro_target]
+
+ if not found_targets:
+ raise MesonException('Can\'t invoke target `{}`: target not found'.format(target.full_name))
+ elif len(found_targets) > 1:
+ raise MesonException('Can\'t invoke target `{}`: ambigious name. Add target type and/or path: `PATH/NAME:TYPE`'.format(target.full_name))
+
+ return found_targets[0]
+
+def generate_target_names_ninja(target: ParsedTargetName, builddir: Path, introspect_data: dict) -> T.List[str]:
+ intro_target = get_target_from_intro_data(target, builddir, introspect_data)
+
+ if intro_target['type'] == 'run':
+ return [target.name]
+ else:
+ return [str(Path(out_file).relative_to(builddir.resolve())) for out_file in intro_target['filename']]
+
+def get_parsed_args_ninja(options: 'argparse.Namespace', builddir: Path) -> T.List[str]:
runner = detect_ninja()
if runner is None:
raise MesonException('Cannot find ninja.')
@@ -50,57 +141,100 @@ def get_parsed_args_ninja(options: 'argparse.Namespace', builddir: Path):
cmd = [runner, '-C', builddir.as_posix()]
+ if options.targets:
+ intro_data = parse_introspect_data(builddir)
+ for t in options.targets:
+ cmd.extend(generate_target_names_ninja(ParsedTargetName(t), builddir, intro_data))
+ if options.clean:
+ cmd.append('clean')
+
# If the value is set to < 1 then don't set anything, which let's
# ninja/samu decide what to do.
if options.jobs > 0:
cmd.extend(['-j', str(options.jobs)])
if options.load_average > 0:
cmd.extend(['-l', str(options.load_average)])
+
if options.verbose:
- cmd.append('-v')
- if options.clean:
- cmd.append('clean')
+ cmd.append('--verbose')
+
+ cmd += options.ninja_args
return cmd
-def get_parsed_args_vs(options: 'argparse.Namespace', builddir: Path):
+def generate_target_name_vs(target: ParsedTargetName, builddir: Path, introspect_data: dict) -> str:
+ intro_target = get_target_from_intro_data(target, builddir, introspect_data)
+
+ assert intro_target['type'] != 'run', 'Should not reach here: `run` targets must be handle above'
+
+ # Normalize project name
+ # Source: https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-build-specific-targets-in-solutions-by-using-msbuild-exe
+ target_name = re.sub('[\%\$\@\;\.\(\)\']', '_', intro_target['id'])
+ rel_path = Path(intro_target['filename'][0]).relative_to(builddir.resolve()).parent
+ if rel_path != '.':
+ target_name = str(rel_path / target_name)
+ return target_name
+
+def get_parsed_args_vs(options: 'argparse.Namespace', builddir: Path) -> T.List[str]:
slns = list(builddir.glob('*.sln'))
assert len(slns) == 1, 'More than one solution in a project?'
-
sln = slns[0]
- cmd = ['msbuild', str(sln.resolve())]
- # In msbuild `-m` with no number means "detect cpus", the default is `-m1`
+ cmd = ['msbuild']
+
+ if options.targets:
+ intro_data = parse_introspect_data(builddir)
+ has_run_target = any(map(
+ lambda t:
+ get_target_from_intro_data(ParsedTargetName(t), builddir, intro_data)['type'] == 'run',
+ options.targets
+ ))
+
+ if has_run_target:
+ # `run` target can't be used the same way as other targets on `vs` backend.
+ # They are defined as disabled projects, which can't be invoked as `.sln`
+ # target and have to be invoked directly as project instead.
+ # Issue: https://github.com/microsoft/msbuild/issues/4772
+
+ if len(options.targets) > 1:
+ raise MesonException('Only one target may be specified when `run` target type is used on this backend.')
+ intro_target = get_target_from_intro_data(ParsedTargetName(options.targets[0]), builddir, intro_data)
+ proj_dir = Path(intro_target['filename'][0]).parent
+ proj = proj_dir/'{}.vcxproj'.format(intro_target['id'])
+ cmd += [str(proj.resolve())]
+ else:
+ cmd += [str(sln.resolve())]
+ cmd.extend(['-target:{}'.format(generate_target_name_vs(ParsedTargetName(t), builddir, intro_data)) for t in options.targets])
+ else:
+ cmd += [str(sln.resolve())]
+
+ if options.clean:
+ cmd.extend(['-target:Clean'])
+
+ # In msbuild `-maxCpuCount` with no number means "detect cpus", the default is `-maxCpuCount:1`
if options.jobs > 0:
- cmd.append('-m{}'.format(options.jobs))
+ cmd.append('-maxCpuCount:{}'.format(options.jobs))
else:
- cmd.append('-m')
+ cmd.append('-maxCpuCount')
if options.load_average:
mlog.warning('Msbuild does not have a load-average switch, ignoring.')
+
if not options.verbose:
- cmd.append('/v:minimal')
- if options.clean:
- cmd.append('/t:Clean')
+ cmd.append('-verbosity:minimal')
+
+ cmd += options.vs_args
return cmd
def add_arguments(parser: 'argparse.ArgumentParser') -> None:
"""Add compile specific arguments."""
parser.add_argument(
- '-j', '--jobs',
- action='store',
- default=0,
- type=int,
- help='The number of worker jobs to run (if supported). If the value is less than 1 the build program will guess.'
- )
- parser.add_argument(
- '-l', '--load-average',
- action='store',
- default=0,
- type=int,
- help='The system load average to try to maintain (if supported)'
- )
+ 'targets',
+ metavar='TARGET',
+ nargs='*',
+ default=None,
+ help='Targets to build. Target has the following format: [PATH_TO_TARGET/]TARGET_NAME[:TARGET_TYPE].')
parser.add_argument(
'--clean',
action='store_true',
@@ -115,6 +249,20 @@ def add_arguments(parser: 'argparse.ArgumentParser') -> None:
help='The directory containing build files to be built.'
)
parser.add_argument(
+ '-j', '--jobs',
+ action='store',
+ default=0,
+ type=int,
+ help='The number of worker jobs to run (if supported). If the value is less than 1 the build program will guess.'
+ )
+ parser.add_argument(
+ '-l', '--load-average',
+ action='store',
+ default=0,
+ type=int,
+ help='The system load average to try to maintain (if supported).'
+ )
+ parser.add_argument(
'--verbose',
action='store_true',
help='Show more verbose output.'
@@ -138,13 +286,14 @@ def run(options: 'argparse.Namespace') -> int:
cmd = [] # type: T.List[str]
+ if options.targets and options.clean:
+ raise MesonException('`TARGET` and `--clean` can\'t be used simultaneously')
+
backend = get_backend_from_coredata(bdir)
if backend == 'ninja':
cmd = get_parsed_args_ninja(options, bdir)
- cmd += options.ninja_args
elif backend.startswith('vs'):
cmd = get_parsed_args_vs(options, bdir)
- cmd += options.vs_args
else:
# TODO: xcode?
raise MesonException(
diff --git a/run_unittests.py b/run_unittests.py
index 1af0d8b..8a12180 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -4630,33 +4630,83 @@ recommended as it is not supported on some platforms''')
def test_meson_compile(self):
"""Test the meson compile command."""
- prog = 'trivialprog'
- if is_windows():
- prog = '{}.exe'.format(prog)
+
+ def get_exe_name(basename: str) -> str:
+ if is_windows():
+ return '{}.exe'.format(basename)
+ else:
+ return basename
+
+ def get_shared_lib_name(basename: str) -> str:
+ if mesonbuild.environment.detect_msys2_arch():
+ return 'lib{}.dll'.format(basename)
+ elif is_windows():
+ return '{}.dll'.format(basename)
+ elif is_cygwin():
+ return 'cyg{}.dll'.format(basename)
+ elif is_osx():
+ return 'lib{}.dylib'.format(basename)
+ else:
+ return 'lib{}.so'.format(basename)
+
+ def get_static_lib_name(basename: str) -> str:
+ return 'lib{}.a'.format(basename)
+
+ # Base case (no targets or additional arguments)
testdir = os.path.join(self.common_test_dir, '1 trivial')
self.init(testdir)
self._run([*self.meson_command, 'compile', '-C', self.builddir])
- # If compile worked then we should get a program
- self.assertPathExists(os.path.join(self.builddir, prog))
+ self.assertPathExists(os.path.join(self.builddir, get_exe_name('trivialprog')))
+
+ # `--clean`
self._run([*self.meson_command, 'compile', '-C', self.builddir, '--clean'])
- self.assertPathDoesNotExist(os.path.join(self.builddir, prog))
+ self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog')))
+
+ # Target specified in a project with unique names
+
+ testdir = os.path.join(self.common_test_dir, '6 linkshared')
+ self.init(testdir, extra_args=['--wipe'])
+ # Multiple targets and target type specified
+ self._run([*self.meson_command, 'compile', '-C', self.builddir, 'mylib', 'mycpplib:shared_library'])
+ # Check that we have a shared lib, but not an executable, i.e. check that target actually worked
+ self.assertPathExists(os.path.join(self.builddir, get_shared_lib_name('mylib')))
+ self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('prog')))
+ self.assertPathExists(os.path.join(self.builddir, get_shared_lib_name('mycpplib')))
+ self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('cppprog')))
+
+ # Target specified in a project with non unique names
+
+ testdir = os.path.join(self.common_test_dir, '190 same target name')
+ self.init(testdir, extra_args=['--wipe'])
+ self._run([*self.meson_command, 'compile', '-C', self.builddir, './foo'])
+ self.assertPathExists(os.path.join(self.builddir, get_static_lib_name('foo')))
+ self._run([*self.meson_command, 'compile', '-C', self.builddir, 'sub/foo'])
+ self.assertPathExists(os.path.join(self.builddir, 'sub', get_static_lib_name('foo')))
+
+ # run_target
+
+ testdir = os.path.join(self.common_test_dir, '54 run target')
+ self.init(testdir, extra_args=['--wipe'])
+ out = self._run([*self.meson_command, 'compile', '-C', self.builddir, 'py3hi'])
+ self.assertIn('I am Python3.', out)
# `--$BACKEND-args`
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
if self.backend is Backend.ninja:
self.init(testdir, extra_args=['--wipe'])
# Dry run - should not create a program
self._run([*self.meson_command, 'compile', '-C', self.builddir, '--ninja-args=-n'])
- self.assertPathDoesNotExist(os.path.join(self.builddir, prog))
+ self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog')))
elif self.backend is Backend.vs:
self.init(testdir, extra_args=['--wipe'])
self._run([*self.meson_command, 'compile', '-C', self.builddir])
# Explicitly clean the target through msbuild interface
- self._run([*self.meson_command, 'compile', '-C', self.builddir, '--vs-args=-t:{}:Clean'.format(re.sub(r'[\%\$\@\;\.\(\)\']', '_', prog))])
- self.assertPathDoesNotExist(os.path.join(self.builddir, prog))
+ self._run([*self.meson_command, 'compile', '-C', self.builddir, '--vs-args=-t:{}:Clean'.format(re.sub(r'[\%\$\@\;\.\(\)\']', '_', get_exe_name('trivialprog')))])
+ self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog')))
def test_spurious_reconfigure_built_dep_file(self):
testdir = os.path.join(self.unit_test_dir, '75 dep files')