mirror of
https://github.com/mesonbuild/meson.git
synced 2026-06-30 19:57:45 +00:00
Added ability to specify target in meson compile
This commit is contained in:
committed by
Jussi Pakkanen
parent
4d0233540f
commit
5696a5abba
@@ -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)*
|
||||
|
||||
19
docs/markdown/snippets/add_meson_compile_target.md
Normal file
19
docs/markdown/snippets/add_meson_compile_target.md
Normal file
@@ -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`.
|
||||
@@ -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`
|
||||
if options.jobs > 0:
|
||||
cmd.append('-m{}'.format(options.jobs))
|
||||
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.append('-m')
|
||||
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('-maxCpuCount:{}'.format(options.jobs))
|
||||
else:
|
||||
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',
|
||||
@@ -114,6 +248,20 @@ def add_arguments(parser: 'argparse.ArgumentParser') -> None:
|
||||
default='.',
|
||||
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',
|
||||
@@ -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(
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user