blob: 53ffd894a17babddb96f4826caffd050b686a2e1 [file] [log] [blame]
Diana Picus3b2ef822016-10-13 16:53:18 +03001"""This is the main tool for handling llvm builds, bisects etc."""
2
3import os
Diana Picusadb07c42017-11-22 16:12:57 +01004from sys import argv
Diana Picus3b2ef822016-10-13 16:53:18 +03005from sys import exit
6
Diana Picus052b7d32017-11-24 16:19:41 +01007from modules.llvm import LLVMBuildConfig
Diana Picus95226d42017-11-01 13:16:54 +01008from modules.llvm import LLVMSubproject
9from modules.llvm import LLVMSourceConfig
Diana Picusb368cb62018-01-23 16:41:59 +010010from modules.llvm import run_test_suite
Diana Picusf73abbf2018-01-26 07:06:20 +010011from modules.llvm import setup_test_suite
Diana Picus052b7d32017-11-24 16:19:41 +010012from modules.utils import CommandPrinter
13from modules.utils import CommandRunner
Diana Picus5ad55422017-12-14 17:57:10 +010014from modules.utils import get_remote_branch
15from modules.utils import push_branch
Diana Picus95226d42017-11-01 13:16:54 +010016
Diana Picus052b7d32017-11-24 16:19:41 +010017from linaropy.cd import cd
Diana Picus3b2ef822016-10-13 16:53:18 +030018from linaropy.git.clone import Clone
19from linaropy.proj import Proj
20
21from argparse import Action, ArgumentParser, RawTextHelpFormatter
Diana Picusefc7bda2017-06-09 19:14:08 +020022from functools import partial
Diana Picus3b2ef822016-10-13 16:53:18 +030023
24
25def die(message, config_to_dump=None):
26 """Print an error message and exit."""
Diana Picusb4307602017-04-05 19:48:39 +020027 print(message)
Diana Picus3b2ef822016-10-13 16:53:18 +030028
29 if config_to_dump is not None:
30 dump_config(config_to_dump)
31
32 exit(1)
33
Diana Picus3d1a3012017-03-14 17:38:32 +010034
Diana Picus3b2ef822016-10-13 16:53:18 +030035def dump_config(config):
36 """Dump the list of projects that are enabled in the given config."""
37
Diana Picusb4307602017-04-05 19:48:39 +020038 print("Projects linked:")
Diana Picus3b2ef822016-10-13 16:53:18 +030039 enabled = config.get_enabled_subprojects()
40 if not enabled:
Diana Picusb4307602017-04-05 19:48:39 +020041 print("none")
Diana Picus3b2ef822016-10-13 16:53:18 +030042 else:
43 for subproj in sorted(enabled):
Diana Picusb4307602017-04-05 19:48:39 +020044 print(" + {}".format(subproj))
Diana Picus3b2ef822016-10-13 16:53:18 +030045
46
Diana Picus2c580832018-02-14 14:51:00 +010047def subproj_to_repo_map(subprojs, proj, reposRoot, dry=False):
48 """Get a dictionary mapping each subproject in subprojs to its repo."""
49 subprojsToRepos = {}
50 repo = None
51 for subproj in subprojs:
52 if not dry:
53 repo = Clone(proj, os.path.join(reposRoot, subproj))
54 subprojsToRepos[subproj] = repo
55
56 return subprojsToRepos
57
58
Diana Picus3b2ef822016-10-13 16:53:18 +030059def projects(args):
60 """Add/remove subprojects based on the values in args."""
61
62 proj = Proj()
Diana Picus3d1a3012017-03-14 17:38:32 +010063
Diana Picus9f756862017-12-20 10:35:08 +010064 llvm_worktree_root = args.sources
Diana Picus81089db2017-05-05 22:26:49 +020065 llvm_repos_root = args.repos
Diana Picusb1dbcba2018-02-07 01:33:17 +010066 config = LLVMSourceConfig(proj, llvm_worktree_root, dry=False)
Diana Picus3b2ef822016-10-13 16:53:18 +030067
68 if not args.add and not args.remove:
69 # Nothing to change, just print the current configuration
70 dump_config(config)
71 exit(0)
72
73 to_add = {}
74 if args.add:
Diana Picus2c580832018-02-14 14:51:00 +010075 to_add = subproj_to_repo_map(args.add, proj, llvm_repos_root)
Diana Picus3b2ef822016-10-13 16:53:18 +030076
77 try:
78 config.update(to_add, args.remove)
79 except (EnvironmentError, ValueError) as exc:
80 die("Failed to update subprojects because:\n{}".format(str(exc)))
81
82 dump_config(config)
83
Diana Picusefc7bda2017-06-09 19:14:08 +020084
Diana Picus95226d42017-11-01 13:16:54 +010085def push_current_branch(args):
Diana Picusefc7bda2017-06-09 19:14:08 +020086 """Push current branch to origin."""
87
88 proj = Proj()
89
Diana Picus9f756862017-12-20 10:35:08 +010090 llvm_worktree_root = args.sources
Diana Picusb1dbcba2018-02-07 01:33:17 +010091 config = LLVMSourceConfig(proj, llvm_worktree_root, dry=False)
Diana Picusefc7bda2017-06-09 19:14:08 +020092
Diana Picus95226d42017-11-01 13:16:54 +010093 llvm_worktree = Clone(proj, llvm_worktree_root)
94 local_branch = llvm_worktree.getbranch()
Diana Picusefc7bda2017-06-09 19:14:08 +020095
96 try:
Diana Picus95226d42017-11-01 13:16:54 +010097 remote_branch = get_remote_branch(llvm_worktree, local_branch)
98 config.for_each_enabled(partial(push_branch, proj, local_branch,
99 remote_branch))
100 print("Pushed to {}".format(remote_branch))
101 except (EnvironmentError, RuntimeError) as exc:
Diana Picusefc7bda2017-06-09 19:14:08 +0200102 die("Failed to push branch because: " + str(exc) + str(exc.__cause__))
103
Diana Picus95226d42017-11-01 13:16:54 +0100104
Diana Picus2c580832018-02-14 14:51:00 +0100105def cmake_flags_from_args(defs):
106 """
107 Get a list of valid CMake flags from the input VAR=VALUE list.
108 This boils down to just adding -D in front of each of them.
109 """
110 return ["-D{}".format(v) for v in defs]
111
112
Diana Picus052b7d32017-11-24 16:19:41 +0100113def configure_build(args):
114 """Configure a given build directory."""
115
116 proj = Proj()
117
Diana Picus9f756862017-12-20 10:35:08 +0100118 llvm_worktree_root = args.sources
Diana Picusb1dbcba2018-02-07 01:33:17 +0100119 sourceConfig = LLVMSourceConfig(proj, llvm_worktree_root, args.dry)
Diana Picus052b7d32017-11-24 16:19:41 +0100120
Diana Picus052b7d32017-11-24 16:19:41 +0100121 if args.dry:
122 consumer = CommandPrinter()
123 else:
124 if not os.path.exists(args.build):
125 os.makedirs(args.build)
126 consumer = CommandRunner()
127
Diana Picus6b1935f2018-02-07 16:44:11 +0100128 buildConfig = LLVMBuildConfig(sourceConfig, args.build, consumer)
129
130 if args.defs:
Diana Picus2c580832018-02-14 14:51:00 +0100131 args.defs = cmake_flags_from_args(args.defs)
Diana Picus6b1935f2018-02-07 16:44:11 +0100132
Diana Picus052b7d32017-11-24 16:19:41 +0100133 try:
Diana Picus6b1935f2018-02-07 16:44:11 +0100134 buildConfig.cmake(args.defs, args.generator)
Diana Picus052b7d32017-11-24 16:19:41 +0100135 except RuntimeError as exc:
136 die("Failed to configure {} because:\n{}".format(args.build, str(exc)))
137
138
Diana Picus37126b82018-01-19 16:14:26 +0100139def run_build(args):
140 """Run a build command in a given directory."""
141 build_dir = args.build
142
143 if args.dry:
144 consumer = CommandPrinter()
145 else:
146 consumer = CommandRunner()
147
148 try:
Diana Picus6b1935f2018-02-07 16:44:11 +0100149 LLVMBuildConfig(None, args.build, consumer).build(args.flags)
Diana Picus37126b82018-01-19 16:14:26 +0100150 except RuntimeError as exc:
151 die("Failed to build {} because:\n{}".format(args.build, str(exc)))
152
153
Diana Picusf73abbf2018-01-26 07:06:20 +0100154def setup_the_test_suite(args):
155 """Setup a sandbox for the test-suite."""
156 if args.dry:
157 consumer = CommandPrinter()
158 else:
159 consumer = CommandRunner()
160
161 try:
162 setup_test_suite(consumer, args.sandbox, args.lnt)
163 except RuntimeError as exc:
164 die("Failed to setup the test-suite because:\n{}".format(str(exc)))
165
166
Diana Picusb368cb62018-01-23 16:41:59 +0100167def run_the_test_suite(args):
168 """Run the test-suite in a given sandbox."""
169 if args.dry:
170 consumer = CommandPrinter()
171 else:
172 consumer = CommandRunner()
173
174 compilers = ["--cc={}".format(args.cc)]
175 if args.cxx:
176 compilers.append("--cxx={}".format(args.cxx))
177
178 try:
179 run_test_suite(consumer, args.sandbox, args.testsuite, args.lit,
180 compilers + args.flags)
181 except RuntimeError as exc:
182 die("Failed to run the test-suite because:\n{}".format(str(exc)))
183
Diana Picusb03e5082018-02-05 12:36:49 +0100184
185def build_and_test(args):
186 """
187 Build a set of LLVM subprojects, in one or two stages, with or without a
188 test-suite run.
189 """
190
191 proj = Proj()
192
193 dryRun = args.dry
Diana Picusfcfc6282018-02-14 18:50:24 +0100194 llvmRepos = args.repos
Diana Picusb03e5082018-02-05 12:36:49 +0100195 llvmWorktreeRoot = args.sources
196
197 stage1BuildDir = args.stage1
Diana Picus2c580832018-02-14 14:51:00 +0100198 stage1Subprojs = args.stage1Subprojs
199 stage1Defs = cmake_flags_from_args(args.stage1Defs)
200 stage1BuildFlags = args.stage1BuildFlags
201
Diana Picusb03e5082018-02-05 12:36:49 +0100202 stage2BuildDir = args.stage2
Diana Picusfcfc6282018-02-14 18:50:24 +0100203
204 enableTestSuite = args.enableTestSuite
Diana Picusb03e5082018-02-05 12:36:49 +0100205 sandboxDir = args.sandbox
Diana Picusb03e5082018-02-05 12:36:49 +0100206
207 if dryRun:
208 consumer = CommandPrinter()
209 else:
210 consumer = CommandRunner()
211
212 try:
213 sourceConfig = LLVMSourceConfig(proj, llvmWorktreeRoot, args.dry)
Diana Picus2c580832018-02-14 14:51:00 +0100214 if stage1Subprojs:
215 # FIXME: Decide whether or not we want to remove anything that isn't
216 # in stage1Subprojs (in case there are already some enabled
217 # subprojects in the source config).
218 sourceConfig.update(
219 subproj_to_repo_map(stage1Subprojs, proj, llvmRepos,
220 args.dry),
221 [])
Diana Picusb03e5082018-02-05 12:36:49 +0100222
223 if not dryRun and not os.path.exists(stage1BuildDir):
224 os.makedirs(stage1BuildDir)
225
226 buildConfig1 = LLVMBuildConfig(sourceConfig, stage1BuildDir, consumer)
Diana Picus2c580832018-02-14 14:51:00 +0100227 buildConfig1.cmake(stage1Defs, "Ninja")
228 buildConfig1.build(stage1BuildFlags)
Diana Picusb03e5082018-02-05 12:36:49 +0100229 testedBuildDir = stage1BuildDir
230
231 if stage2BuildDir is not None:
232 if not dryRun and not os.path.exists(stage2BuildDir):
233 os.makedirs(stage2BuildDir)
234
235 buildConfig2 = LLVMBuildConfig(sourceConfig, stage2BuildDir,
236 consumer)
237
238 # TODO: Make sure clang is actually built in this config (preferably
239 # before reaching this point)
240 buildConfig2.cmake(
241 [
242 "-DCMAKE_C_COMPILER={}/bin/clang".format(stage1BuildDir),
243 "-DCMAKE_CXX_COMPILER={}/bin/clang++".format(stage1BuildDir)],
244 "Ninja")
245 buildConfig2.build()
246 testedBuildDir = stage2BuildDir
247
Diana Picusfcfc6282018-02-14 18:50:24 +0100248 if enableTestSuite:
249 testSuiteDir = os.path.join(llvmRepos, "test-suite")
250 lntDir = os.path.join(llvmRepos, "lnt")
251
Diana Picusb03e5082018-02-05 12:36:49 +0100252 setup_test_suite(consumer, sandboxDir, lntDir)
253
254 # TODO: Make sure clang is actually built in this config (preferably
255 # before reaching this point)
256 lit = os.path.join(testedBuildDir, "bin", "llvm-lit")
257 clang = os.path.join(testedBuildDir, "bin", "clang")
258 run_test_suite(consumer, sandboxDir, testSuiteDir, lit,
259 ["--cc={}".format(clang)])
260
261 except RuntimeError as exc:
262 die("Failed because:\n{}".format(str(exc)))
263
264
Diana Picus3b2ef822016-10-13 16:53:18 +0300265##########################################################################
266# Command line parsing #
267##########################################################################
268
269# If we decide we want shorthands for the subprojects, we can append to this
270# list
Diana Picusb4307602017-04-05 19:48:39 +0200271valid_subprojects = list(LLVMSubproject.get_all_subprojects().keys())
Diana Picus3b2ef822016-10-13 16:53:18 +0300272
273options = ArgumentParser(formatter_class=RawTextHelpFormatter)
Diana Picusadb07c42017-11-22 16:12:57 +0100274subcommands = options.add_subparsers(dest="subcommand")
Diana Picus3b2ef822016-10-13 16:53:18 +0300275
276# Subcommand for adding / removing subprojects
Diana Picus36317e82017-10-31 15:35:24 +0100277projs = subcommands.add_parser(
278 "projects", help="Add/remove LLVM subprojects.\n"
279 "Adding a subproject will create a worktree for it "
280 "somewhere in the LLVM source tree, on the same git "
281 "branch as LLVM itself.\n"
282 "Removing a subproject will remove the worktree, but "
283 "not the underlying git branch.")
Diana Picus3b2ef822016-10-13 16:53:18 +0300284projs.set_defaults(run_command=projects)
285
286# TODO: Overwriting previous values is not necessarily what users expect (so for
287# instance --add S1 S2 --remove S3 --add S4 would lead to adding only S4). We
288# can do better by using action='append', which would create a list (of lists?
289# or of lists and scalars?) that we can flatten to obtain all the values passed
290# by the user.
291projs.add_argument(
292 '-a', '--add',
293 nargs='+',
294 choices=valid_subprojects,
295 metavar='subproject',
Diana Picus36317e82017-10-31 15:35:24 +0100296 help="Enable given subprojects. Valid values are:\n\t{}\n".format(
Diana Picus3b2ef822016-10-13 16:53:18 +0300297 "\n\t".join(valid_subprojects)))
298projs.add_argument(
299 '-r', '--remove',
300 nargs='+',
301 choices=valid_subprojects,
302 metavar='subproject',
Diana Picus36317e82017-10-31 15:35:24 +0100303 help="Disable given subprojects.")
Diana Picusadb07c42017-11-22 16:12:57 +0100304projs.add_argument(
305 '--repos',
306 help="Path to the directory containing the repositories for all LLVM "
307 "subprojects.")
Diana Picus9f756862017-12-20 10:35:08 +0100308projs.add_argument(
309 '--source-dir',
310 dest='sources',
311 required=True,
312 help="Path to the directory containing the LLVM worktree that we're adding "
313 "or removing subprojects from.")
Diana Picus3b2ef822016-10-13 16:53:18 +0300314
Diana Picusefc7bda2017-06-09 19:14:08 +0200315# Subcommand for pushing the current branch to origin
316push = subcommands.add_parser(
317 "push",
Diana Picus36317e82017-10-31 15:35:24 +0100318 help="Push current branch to origin linaro-local/<user>/<branch>, "
319 "for all enabled subprojects.")
Diana Picus95226d42017-11-01 13:16:54 +0100320push.set_defaults(run_command=push_current_branch)
Diana Picus9f756862017-12-20 10:35:08 +0100321push.add_argument(
322 '--source-dir',
323 dest='sources',
324 required=True,
325 help="Path to the directory containing the LLVM worktree.")
Diana Picusefc7bda2017-06-09 19:14:08 +0200326
Diana Picus052b7d32017-11-24 16:19:41 +0100327# Subcommand for configuring a build directory
328configure = subcommands.add_parser(
329 'configure',
330 help="Run CMake in the given build directory.")
331configure.add_argument(
Diana Picus9f756862017-12-20 10:35:08 +0100332 '--source-dir',
333 dest='sources',
334 required=True,
335 help="Path to the sources directory. It should contain an LLVM worktree.")
336configure.add_argument(
Diana Picus052b7d32017-11-24 16:19:41 +0100337 '--build-dir',
338 dest='build',
339 required=True,
340 help="Path to the build directory. It will be created if it does not exist")
341configure.add_argument(
342 '--cmake-generator',
343 dest='generator',
344 default='Ninja',
345 help="CMake generator to use (default is Ninja).")
346configure.add_argument(
347 '--cmake-def',
348 dest='defs',
349 metavar='VAR=VALUE',
350 default=[],
351 action='append',
352 # We add the -D in front of the variable ourselves because the argument
353 # parsing gets confused otherwise (and quoting doesn't help).
354 help="Additional CMake definitions, e.g. CMAKE_BUILD_TYPE=Release."
355 "May be passed several times. The -D is added automatically.")
356configure.add_argument(
357 '-n', '--dry-run',
358 dest='dry',
359 action='store_true',
360 default=False,
361 help="Print the CMake command instead of executing it.")
362configure.set_defaults(run_command=configure_build)
363
Diana Picus37126b82018-01-19 16:14:26 +0100364# Subcommand for building a target
365build = subcommands.add_parser(
366 'build',
367 help="Run a build command in the given directory."
368 "The build command can be either a 'ninja' or a 'make' command, depending "
369 "on what the build directory contains. First, we look for a 'build.ninja' "
370 "file. If that is not found, we look for a 'Makefile'. If that is not "
371 "found either, the script fails.")
372build.add_argument(
373 '--build-dir',
374 dest='build',
375 required=True,
376 help="Path to the build directory. It must have already been configured.")
377build.add_argument(
378 '-n', '--dry-run',
379 dest='dry',
380 action='store_true',
381 default=False,
382 help="Print the build command instead of executing it.")
383build.add_argument(
384 '--build-flag',
385 dest='flags',
386 metavar='FLAG',
387 default=[],
388 action='append',
389 help="Additional flags for the build command (e.g. targets to build). "
390 "May be passed several times. If your flag starts with a '-', use "
391 "'--build-flag=-FLAG' to pass it.")
392build.set_defaults(run_command=run_build)
393
Diana Picusf73abbf2018-01-26 07:06:20 +0100394# Subcommand for setting up the test-suite
395setupTestSuite = subcommands.add_parser(
396 'setup-test-suite',
397 help="Prepare a sandbox for running the test-suite.")
398setupTestSuite.add_argument(
399 '--sandbox',
400 required=True,
401 help="Path where we should setup the sandbox.")
402setupTestSuite.add_argument(
403 '--lnt',
404 required=True,
405 help="Path to the LNT sources.")
406setupTestSuite.add_argument(
407 '-n', '--dry-run',
408 dest='dry',
409 action='store_true',
410 default=False,
411 help="Print the commands instead of executing them.")
412setupTestSuite.set_defaults(run_command=setup_the_test_suite)
413
Diana Picusb368cb62018-01-23 16:41:59 +0100414# Subcommand for running the test-suite
415runTestSuite = subcommands.add_parser(
416 'run-test-suite',
417 help="Run the test-suite in the given sandbox.")
418runTestSuite.add_argument(
419 '--sandbox',
420 required=True,
421 help="Path to the sandbox. It must point to a virtualenv with a LNT setup.")
422runTestSuite.add_argument(
423 '--test-suite',
424 dest="testsuite",
425 required=True,
426 help="Path to the test-suite repo.")
427runTestSuite.add_argument(
428 '--use-lit',
429 dest="lit",
430 required=True,
431 help="Path to llvm-lit.")
432runTestSuite.add_argument(
433 '--lnt-flag',
434 dest='flags',
435 metavar='FLAG',
436 default=[],
437 action='append',
438 help="Additional flags to be passed to LNT when running the test-suite."
439 "May be passed several times. If your flag starts with a '-', use "
440 "'--lnt-flag=-FLAG' to pass it.")
441runTestSuite.add_argument(
442 # We can pass --cc through the --lnt-flag interface, but we generally won't
443 # want to test the system compiler, so force the user to be specific.
444 '--cc',
445 required=True,
446 help="The path to the C compiler that we're testing.")
447runTestSuite.add_argument(
448 # For symmetry, we also provide a --cxx argument, but this one isn't
449 # required since LNT tries to guess it based on the value of --cc.
450 '--cxx',
451 required=False,
452 help="The path to the C++ compiler that we're testing.")
453runTestSuite.add_argument(
454 '-n', '--dry-run',
455 dest='dry',
456 action='store_true',
457 default=False,
458 help="Print the commands instead of executing them.")
459runTestSuite.set_defaults(run_command=run_the_test_suite)
460
Diana Picusb03e5082018-02-05 12:36:49 +0100461buildAndTest = subcommands.add_parser(
462 'build-and-test', # TODO: This really needs a better name...
463 help="Run complex build scenarios with one or two stages of clang and "
464 "optionally a test-suite run. This should be flexible enough to allow "
465 "us to reproduce any buildbot configuration, but it can obviously be "
466 "used for other purposes as well.")
467buildAndTest.set_defaults(run_command=build_and_test)
468buildAndTest.add_argument(
Diana Picusfcfc6282018-02-14 18:50:24 +0100469 '--repos-dir',
470 dest='repos',
471 required=True,
472 help="Path to the root directory containing the repositories for LLVM and "
473 "the other subprojects.")
474buildAndTest.add_argument(
Diana Picusb03e5082018-02-05 12:36:49 +0100475 '--source-dir',
476 dest='sources',
477 required=True,
478 help="Path to the directory containing the LLVM worktree that we're going "
479 "to build from.")
480buildAndTest.add_argument(
481 '--stage1-build-dir',
482 dest='stage1',
483 required=True,
484 help="Path to the build directory for stage 1.")
485buildAndTest.add_argument(
Diana Picus2c580832018-02-14 14:51:00 +0100486 '--stage1-subproject',
487 dest='stage1Subprojs',
488 metavar='SUBPROJ',
489 choices=valid_subprojects,
490 default=[],
491 action='append',
492 help="Subprojects to enable for stage 1 of the build. Can be passed "
493 "multiple times. Valid values for the subproject are: {}. "
494 "If this is a 2-stage build, the same subprojects will be used for "
495 "both stages unless other subprojects are explicitly requested for "
496 "stage 2.".format(" ".join(valid_subprojects)))
497buildAndTest.add_argument(
498 '--stage1-cmake-def',
499 dest='stage1Defs',
500 metavar='VAR=VALUE',
501 default=[],
502 action='append',
503 help="Additional CMake definitions for stage 1, e.g. "
504 "CMAKE_BUILD_TYPE=Release. Can be passed multiple times. "
505 "The -D is added automatically. Does not affect stage 2.")
506buildAndTest.add_argument(
507 '--stage1-build-flag',
508 dest='stage1BuildFlags',
509 metavar='FLAG',
510 default=[],
511 action='append',
512 help="Additional flags for the stage 1 build command (e.g. targets to "
513 "build). Can be passed multiple times. If your flag starts with "
514 "a '-', use '--stage1-build-flag=-FLAG' to pass it. "
515 "Does not affect stage 2.")
516buildAndTest.add_argument(
Diana Picusb03e5082018-02-05 12:36:49 +0100517 '--stage2-build-dir',
518 dest='stage2',
519 help="Path to the build directory for stage 2.")
520buildAndTest.add_argument(
Diana Picusfcfc6282018-02-14 18:50:24 +0100521 "--enable-test-suite",
522 dest='enableTestSuite',
523 action='store_true',
524 default=False,
525 help="Whether or not to run the test-suite with the last compiler built.")
Diana Picusb03e5082018-02-05 12:36:49 +0100526buildAndTest.add_argument(
527 "--sandbox",
528 help="Path to the sandbox where the test-suite should be setup.")
529buildAndTest.add_argument(
Diana Picusb03e5082018-02-05 12:36:49 +0100530 '-n', '--dry-run',
531 dest='dry',
532 action='store_true',
533 default=False,
534 help="Print the commands instead of executing them.")
535
Diana Picus3b2ef822016-10-13 16:53:18 +0300536args = options.parse_args()
Diana Picusadb07c42017-11-22 16:12:57 +0100537if args.subcommand == "projects" and args.add and not args.repos:
538 projs.error(
539 "When adding a subproject you must also pass the --repos argument")
Diana Picus3b2ef822016-10-13 16:53:18 +0300540args.run_command(args)