aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Subprojects.md9
-rw-r--r--docs/markdown/snippets/subproject-foreach.md7
-rwxr-xr-x[-rw-r--r--]mesonbuild/msubprojects.py37
3 files changed, 46 insertions, 7 deletions
diff --git a/docs/markdown/Subprojects.md b/docs/markdown/Subprojects.md
index 24b8af6..61b3eca 100644
--- a/docs/markdown/Subprojects.md
+++ b/docs/markdown/Subprojects.md
@@ -241,6 +241,15 @@ changes.
To come back to the revision set in wrap file (i.e. master), just run
`meson subprojects checkout` with no branch name.
+## Execute a command on all subprojects
+
+*Since 0.51.0*
+
+The command-line `meson subprojects foreach <command> [...]` will
+execute a command in each subproject directory. For example this can be useful
+to check the status of subprojects (e.g. with `git status` or `git diff`) before
+performing other actions on them.
+
## Why must all subprojects be inside a single directory?
There are several reasons.
diff --git a/docs/markdown/snippets/subproject-foreach.md b/docs/markdown/snippets/subproject-foreach.md
new file mode 100644
index 0000000..3a8ffc4
--- /dev/null
+++ b/docs/markdown/snippets/subproject-foreach.md
@@ -0,0 +1,7 @@
+## Add new `meson subprojects foreach` command
+
+`meson subprojects` has learned a new `foreach` command which accepts a command
+with arguments and executes it in each subproject directory.
+
+For example this can be useful to check the status of subprojects (e.g. with
+`git status` or `git diff`) before performing other actions on them.
diff --git a/mesonbuild/msubprojects.py b/mesonbuild/msubprojects.py
index 2c1bf8b..c31b07d 100644..100755
--- a/mesonbuild/msubprojects.py
+++ b/mesonbuild/msubprojects.py
@@ -1,4 +1,5 @@
import os, subprocess
+import argparse
from . import mlog
from .mesonlib import Popen_safe
@@ -167,6 +168,18 @@ def download(wrap, repo_dir, options):
except WrapException as e:
mlog.log(' ->', mlog.red(str(e)))
+def foreach(wrap, repo_dir, options):
+ mlog.log('Executing command in %s' % repo_dir)
+ if not os.path.isdir(repo_dir):
+ mlog.log(' -> Not downloaded yet')
+ return
+ try:
+ subprocess.check_call([options.command] + options.args, cwd=repo_dir)
+ mlog.log('')
+ except subprocess.CalledProcessError as e:
+ out = e.output.decode().strip()
+ mlog.log(' -> ', mlog.red(out))
+
def add_common_arguments(p):
p.add_argument('--sourcedir', default='.',
help='Path to source directory')
@@ -197,6 +210,15 @@ def add_arguments(parser):
add_common_arguments(p)
p.set_defaults(subprojects_func=download)
+ p = subparsers.add_parser('foreach', help='Execute a command in each subproject directory.')
+ p.add_argument('command', metavar='command ...',
+ help='Command to execute in each subproject directory')
+ p.add_argument('args', nargs=argparse.REMAINDER,
+ help=argparse.SUPPRESS)
+ p.add_argument('--sourcedir', default='.',
+ help='Path to source directory')
+ p.set_defaults(subprojects_func=foreach)
+
def run(options):
src_dir = os.path.relpath(os.path.realpath(options.sourcedir))
if not os.path.isfile(os.path.join(src_dir, 'meson.build')):
@@ -207,13 +229,14 @@ def run(options):
mlog.log('Directory', mlog.bold(src_dir), 'does not seem to have subprojects.')
return 0
files = []
- for name in options.subprojects:
- f = os.path.join(subprojects_dir, name + '.wrap')
- if not os.path.isfile(f):
- mlog.error('Subproject', mlog.bold(name), 'not found.')
- return 1
- else:
- files.append(f)
+ if hasattr(options, 'subprojects'):
+ for name in options.subprojects:
+ f = os.path.join(subprojects_dir, name + '.wrap')
+ if not os.path.isfile(f):
+ mlog.error('Subproject', mlog.bold(name), 'not found.')
+ return 1
+ else:
+ files.append(f)
if not files:
for f in os.listdir(subprojects_dir):
if f.endswith('.wrap'):