• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pybuilder / pybuilder / 23886302068

02 Apr 2026 05:56AM UTC coverage: 82.885% (-0.08%) from 82.968%
23886302068

push

github

web-flow
Add --project-info (-i) CLI option for JSON project configuration dump [release] (#946)

## Summary

- Adds `pyb -i` / `pyb --project-info` that outputs the full project
configuration as pretty-printed JSON to stdout without running a build
- Runs plugin initializers to populate all properties but does not
execute any tasks or create build/test venvs
- Log messages go to stderr (via new `StdErrLogger` /
`ColoredStdErrLogger` classes) so stdout is always clean, parseable JSON
- Mutually exclusive with `-t`, `-T`, `--start-project`,
`--update-project`

### JSON output includes:
- Project metadata (name, version, authors, license, URLs, etc.)
- All build properties (built-in + plugin-defined, after initializers
run)
- Loaded plugins
- Runtime, build, plugin, and extras dependencies
- Available tasks with descriptions and dependency graphs
- Manifest files, package data, files to install

### Usage:
```bash
pyb -i 2>/dev/null | jq .project.name
pyb -i -E ci -P verbose=true 2>/dev/null | jq .properties
```

## Test plan

- [x] 678 unit tests pass (including new tests for option parsing,
stderr logging, serialization, JSON output)
- [x] 3 cram tests pass (help output updated, new project-info cram
test, existing no-build test)
- [x] `pyb -i | python -m json.tool` produces valid JSON
- [x] `pyb -i -X 2>log.txt` sends debug logs to stderr, JSON to stdout
- [x] `pyb -i -t` rejected as mutually exclusive

1426 of 1888 branches covered (75.53%)

Branch coverage included in aggregate %.

54 of 72 new or added lines in 1 file covered. (75.0%)

1 existing line in 1 file now uncovered.

5606 of 6596 relevant lines covered (84.99%)

33.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.74
/src/main/python/pybuilder/cli.py
1
#   -*- coding: utf-8 -*-
2
#
3
#   This file is part of PyBuilder
4
#
5
#   Copyright 2011-2020 PyBuilder Team
6
#
7
#   Licensed under the Apache License, Version 2.0 (the "License");
8
#   you may not use this file except in compliance with the License.
9
#   You may obtain a copy of the License at
10
#
11
#       http://www.apache.org/licenses/LICENSE-2.0
12
#
13
#   Unless required by applicable law or agreed to in writing, software
14
#   distributed under the License is distributed on an "AS IS" BASIS,
15
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
#   See the License for the specific language governing permissions and
17
#   limitations under the License.
18

19
"""
20
    The PyBuilder cli module.
21
    Contains the PyBuilder command-line entrypoint.
22
"""
23

24
import datetime
40✔
25
import json
40✔
26
import optparse
40✔
27
import re
40✔
28
import sys
40✔
29
import traceback
40✔
30
from os.path import sep, normcase as nc
40✔
31

32
from pybuilder import __version__
40✔
33
from pybuilder import extern
40✔
34
from pybuilder.core import Logger
40✔
35
from pybuilder.errors import PyBuilderException
40✔
36
from pybuilder.execution import ExecutionManager
40✔
37
from pybuilder.python_utils import IS_WIN
40✔
38
from pybuilder.reactor import Reactor
40✔
39
from pybuilder.scaffolding import start_project, update_project
40✔
40
from pybuilder.terminal import (BOLD, BROWN, RED, GREEN, bold, styled_text,
40✔
41
                                fg, italic, print_text, print_text_line,
42
                                print_error, print_error_line, draw_line)
43
from pybuilder.utils import format_timestamp, get_dist_version_string
40✔
44

45
PROPERTY_OVERRIDE_PATTERN = re.compile(r'^[a-zA-Z0-9_]+=.*')
40✔
46
DEFAULT_LOG_TIME_FORMAT = '[%Y-%m-%d %H:%M:%S]'
40✔
47
_extern = extern
40✔
48

49

50
class CommandLineUsageException(PyBuilderException):
40✔
51
    def __init__(self, usage, message):
40✔
52
        super(CommandLineUsageException, self).__init__(message)
40✔
53
        self.usage = usage
40✔
54

55

56
class StdOutLogger(Logger):
40✔
57
    def _level_to_string(self, level):
40✔
58
        if Logger.DEBUG == level:
40✔
59
            return "[DEBUG]"
40✔
60
        if Logger.INFO == level:
40✔
61
            return "[INFO] "
40✔
62
        if Logger.WARN == level:
40✔
63
            return "[WARN] "
40✔
64
        return "[ERROR]"
40✔
65

66
    def _do_log(self, level, message, *arguments):
40✔
67
        formatted_message = self._format_message(message, *arguments)
40✔
68
        log_level = self._level_to_string(level)
40✔
69
        if self.log_time_format is not None:
40✔
70
            timestamp = datetime.datetime.now().strftime(self.log_time_format) + ' '
40✔
71
        else:
72
            timestamp = ''
40✔
73
        print_text_line("{0}{1} {2}".format(timestamp, log_level, formatted_message))
40✔
74

75

76
class ColoredStdOutLogger(StdOutLogger):
40✔
77
    def _level_to_string(self, level):
40✔
78
        if Logger.DEBUG == level:
40✔
79
            return italic("[DEBUG]")
40✔
80
        if Logger.INFO == level:
40✔
81
            return bold("[INFO] ")
40✔
82
        if Logger.WARN == level:
40✔
83
            return styled_text("[WARN] ", BOLD, fg(BROWN))
40✔
84
        return styled_text("[ERROR]", BOLD, fg(RED))
40✔
85

86

87
class StdErrLogger(StdOutLogger):
40✔
88
    def _do_log(self, level, message, *arguments):
40✔
89
        formatted_message = self._format_message(message, *arguments)
40✔
90
        log_level = self._level_to_string(level)
40✔
91
        if self.log_time_format is not None:
40!
NEW
92
            timestamp = datetime.datetime.now().strftime(self.log_time_format) + ' '
×
93
        else:
94
            timestamp = ''
40✔
95
        print_error_line("{0}{1} {2}".format(timestamp, log_level, formatted_message))
40✔
96

97

98
class ColoredStdErrLogger(ColoredStdOutLogger, StdErrLogger):
40✔
99
    pass
40✔
100

101

102
def _log_time_format_argument__check_if_timestamp_passed(option, opt_str, value, parser):
40✔
103
    assert value is None
40✔
104
    value = DEFAULT_LOG_TIME_FORMAT
40✔
105

106
    for arg in parser.rargs:
40✔
107
        # stop on --foo like options
108
        if arg[:2] == "--" and len(arg) > 2:
40✔
109
            break
40✔
110
        # stop on -foo like options
111
        elif arg[:1] == "-" and len(arg) > 1:
40✔
112
            break
40✔
113
        else:
114
            value = arg.rstrip()
40✔
115
            del parser.rargs[0]
40✔
116
            break  # Only consume 1 argument
40✔
117

118
    setattr(parser.values, option.dest, value)
40✔
119

120

121
def parse_options(args):
40✔
122
    parser = optparse.OptionParser(usage="%prog [options] [+|^]task1 [[[+|^]task2] ...]",
40✔
123
                                   version="%prog " + __version__)
124

125
    def error(msg):
40✔
126
        raise CommandLineUsageException(
40✔
127
            parser.get_usage() + parser.format_option_help(), msg)
128

129
    parser.error = error
40✔
130

131
    list_tasks_option = parser.add_option("-t", "--list-tasks",
40✔
132
                                          action="store_true",
133
                                          dest="list_tasks",
134
                                          default=False,
135
                                          help="List all tasks that can be run in the current build configuration")
136

137
    list_plan_tasks_option = parser.add_option("-T", "--list-plan-tasks",
40✔
138
                                               action="store_true",
139
                                               dest="list_plan_tasks",
140
                                               default=False,
141
                                               help="List tasks that will be run with current execution plan")
142

143
    project_info_option = parser.add_option("-i", "--project-info",
40✔
144
                                            action="store_true",
145
                                            dest="project_info",
146
                                            default=False,
147
                                            help="Output project configuration as JSON")
148

149
    start_project_option = parser.add_option("--start-project",
40✔
150
                                             action="store_true",
151
                                             dest="start_project",
152
                                             default=False,
153
                                             help="Initialize build descriptors and Python project structure")
154

155
    update_project_option = parser.add_option("--update-project",
40✔
156
                                              action="store_true",
157
                                              dest="update_project",
158
                                              default=False,
159
                                              help="Update build descriptors and Python project structure")
160

161
    project_group = optparse.OptionGroup(
40✔
162
        parser, "Project Options", "Customizes the project to build.")
163

164
    project_group.add_option("-D", "--project-directory",
40✔
165
                             dest="project_directory",
166
                             help="Root directory to execute in",
167
                             metavar="<project directory>",
168
                             default=".")
169

170
    project_group.add_option("-O", "--offline",
40✔
171
                             dest="offline",
172
                             help="Attempt to execute the build without network connectivity (may cause build failure)",
173
                             default=False,
174
                             action="store_true")
175

176
    project_group.add_option("-E", "--environment",
40✔
177
                             dest="environments",
178
                             help="Activate the given environment for this build. Can be used multiple times",
179
                             metavar="<environment>",
180
                             action="append",
181
                             default=[])
182

183
    project_group.add_option("-P",
40✔
184
                             action="append",
185
                             dest="property_overrides",
186
                             default=[],
187
                             metavar="<property>=<value>",
188
                             help="Set/ override a property value")
189

190
    project_group.add_option("-x", "--exclude",
40✔
191
                             action="append",
192
                             dest="exclude_optional_tasks",
193
                             default=[],
194
                             metavar="<task>",
195
                             help="Exclude optional task dependencies")
196

197
    project_group.add_option("-o", "--exclude-all-optional",
40✔
198
                             action="store_true",
199
                             dest="exclude_all_optional",
200
                             default=False,
201
                             help="Exclude all optional task dependencies")
202

203
    project_group.add_option("--force-exclude",
40✔
204
                             action="append",
205
                             dest="exclude_tasks",
206
                             default=[],
207
                             metavar="<task>",
208
                             help="Exclude any task dependencies "
209
                                  "(dangerous, may break the build in unexpected ways)")
210

211
    project_group.add_option("--reset-plugins",
40✔
212
                             action="store_true",
213
                             dest="reset_plugins",
214
                             default=False,
215
                             help="Reset plugins directory prior to running the build")
216

217
    project_group.add_option("--no-venvs",
40✔
218
                             action="store_true",
219
                             dest="no_venvs",
220
                             default=False,
221
                             help="Disables the use of Python Virtual Environments")
222

223
    parser.add_option_group(project_group)
40✔
224

225
    output_group = optparse.OptionGroup(
40✔
226
        parser, "Output Options", "Modifies the messages printed during a build.")
227

228
    output_group.add_option("-X", "--debug",
40✔
229
                            action="store_true",
230
                            dest="debug",
231
                            default=False,
232
                            help="Print debug messages")
233

234
    output_group.add_option("-v", "--verbose",
40✔
235
                            action="store_true",
236
                            dest="verbose",
237
                            default=False,
238
                            help="Enable verbose output")
239

240
    output_group.add_option("-q", "--quiet",
40✔
241
                            action="store_true",
242
                            dest="quiet",
243
                            default=False,
244
                            help="Quiet mode; print only warnings and errors")
245

246
    output_group.add_option("-Q", "--very-quiet",
40✔
247
                            action="store_true",
248
                            dest="very_quiet",
249
                            default=False,
250
                            help="Very quiet mode; print only errors")
251

252
    output_group.add_option("-c", "--color",
40✔
253
                            action="store_true",
254
                            dest="force_color",
255
                            default=False,
256
                            help="Force colored output")
257

258
    output_group.add_option("-C", "--no-color",
40✔
259
                            action="store_true",
260
                            dest="no_color",
261
                            default=False,
262
                            help="Disable colored output")
263

264
    output_group.add_option("-f", "--log-time-format",
40✔
265
                            action='callback',
266
                            callback=_log_time_format_argument__check_if_timestamp_passed,
267
                            dest="log_time_format",
268
                            help="Define the format of timestamp in the log (default: no timestamps)",
269
                            default=None)
270

271
    parser.add_option_group(output_group)
40✔
272

273
    options, arguments = parser.parse_args(args=list(args))
40✔
274

275
    if options.list_tasks and options.list_plan_tasks:
40!
276
        parser.error("%s and %s are mutually exclusive" % (list_tasks_option, list_plan_tasks_option))
×
277
    if options.project_info and (options.list_tasks or options.list_plan_tasks):
40✔
278
        parser.error("%s is mutually exclusive with %s and %s" % (
40✔
279
            project_info_option, list_tasks_option, list_plan_tasks_option))
280
    if options.project_info and (options.start_project or options.update_project):
40✔
281
        parser.error("%s is mutually exclusive with %s and %s" % (
40✔
282
            project_info_option, start_project_option, update_project_option))
283
    if options.start_project and options.update_project:
40!
284
        parser.error("%s and %s are mutually exclusive" % (start_project_option, update_project_option))
×
285

286
    property_overrides = {}
40✔
287
    for pair in options.property_overrides:
40✔
288
        if not PROPERTY_OVERRIDE_PATTERN.match(pair):
40✔
289
            parser.error("%s is not a property definition." % pair)
40✔
290
        key, val = pair.split("=", 1)
40✔
291
        property_overrides[key] = val
40✔
292

293
    options.property_overrides = property_overrides
40✔
294

295
    if options.very_quiet:
40!
296
        options.quiet = True
×
297

298
    return options, arguments
40✔
299

300

301
def init_reactor(logger):
40✔
302
    execution_manager = ExecutionManager(logger)
22✔
303
    reactor = Reactor(logger, execution_manager)
22✔
304
    return reactor
22✔
305

306

307
def should_colorize(options):
40✔
308
    return options.force_color or (sys.stdout.isatty() and not options.no_color)
22✔
309

310

311
def init_logger(options, use_stderr=False):
40✔
312
    threshold = Logger.INFO
22✔
313
    if options.debug:
22!
314
        threshold = Logger.DEBUG
22✔
315
    elif options.quiet:
×
316
        threshold = Logger.WARN
×
317

318
    if not should_colorize(options):
22!
319
        if use_stderr:
22!
NEW
320
            logger = StdErrLogger(threshold, options.log_time_format)
×
321
        else:
322
            logger = StdOutLogger(threshold, options.log_time_format)
22✔
323
    else:
324
        if IS_WIN:
×
325
            import colorama
×
326
            colorama.init()
×
NEW
327
        if use_stderr:
×
NEW
328
            logger = ColoredStdErrLogger(threshold, options.log_time_format)
×
329
        else:
NEW
330
            logger = ColoredStdOutLogger(threshold, options.log_time_format)
×
331

332
    return logger
22✔
333

334

335
def print_build_summary(options, summary):
40✔
336
    print_text_line("Build Summary")
22✔
337
    print_text_line("%20s: %s" % ("Project", summary.project.name))
22✔
338
    print_text_line("%20s: %s%s" % ("Version", summary.project.version, get_dist_version_string(summary.project)))
22✔
339
    print_text_line("%20s: %s" % ("Base directory", summary.project.basedir))
22✔
340
    print_text_line("%20s: %s" %
22✔
341
                    ("Environments", ", ".join(options.environments)))
342

343
    task_summary = ""
22✔
344
    for task in summary.task_summaries:
22✔
345
        task_summary += " %s [%d ms]" % (task.task, task.execution_time)
22✔
346

347
    print_text_line("%20s:%s" % ("Tasks", task_summary))
22✔
348

349

350
def print_styled_text(text, options, *style_attributes):
40✔
351
    if should_colorize(options):
22!
352
        add_trailing_nl = False
×
353
        if text[-1] == '\n':
×
354
            text = text[:-1]
×
355
            add_trailing_nl = True
×
356
        text = styled_text(text, *style_attributes)
×
357
        if add_trailing_nl:
×
358
            text += '\n'
×
359
    print_text(text)
22✔
360

361

362
def print_styled_text_line(text, options, *style_attributes):
40✔
363
    print_styled_text(text + "\n", options, *style_attributes)
22✔
364

365

366
def print_build_status(failure_message, options, successful):
40✔
367
    draw_line()
22✔
368
    if successful:
22!
369
        print_styled_text_line("BUILD SUCCESSFUL", options, BOLD, fg(GREEN))
22✔
370
    else:
371
        print_styled_text_line(
×
372
            "BUILD FAILED - {0}".format(failure_message), options, BOLD, fg(RED))
373
    draw_line()
22✔
374

375

376
def print_elapsed_time_summary(start, end):
40✔
377
    time_needed = end - start
22✔
378
    millis = ((time_needed.days * 24 * 60 * 60) + time_needed.seconds) * 1000 + time_needed.microseconds / 1000
22✔
379
    print_text_line("Build finished at %s" % format_timestamp(end))
22✔
380
    print_text_line("Build took %d seconds (%d ms)" %
22✔
381
                    (time_needed.seconds, millis))
382

383

384
def print_summary(successful, summary, start, end, options, failure_message):
40✔
385
    print_build_status(failure_message, options, successful)
22✔
386

387
    if successful and summary:
22!
388
        print_build_summary(options, summary)
22✔
389

390
    print_elapsed_time_summary(start, end)
22✔
391

392

393
def length_of_longest_string(list_of_strings):
40✔
394
    if len(list_of_strings) == 0:
40✔
395
        return 0
40✔
396

397
    result = 0
40✔
398
    for string in list_of_strings:
40✔
399
        length_of_string = len(string)
40✔
400
        if length_of_string > result:
40✔
401
            result = length_of_string
40✔
402

403
    return result
40✔
404

405

406
def task_description(task):
40✔
407
    return " ".join(task.description) or "<no description available>"
40✔
408

409

410
def print_task_list(tasks, quiet=False):
40✔
411
    if quiet:
40✔
412
        print_text_line("\n".join([task.name + ":" + task_description(task)
40✔
413
                                   for task in tasks]))
414
        return
40✔
415

416
    column_length = length_of_longest_string(
40✔
417
        list(map(lambda task: task.name, tasks)))
418
    column_length += 4
40✔
419

420
    for task in tasks:
40✔
421
        task_name = task.name.rjust(column_length)
40✔
422
        print_text_line("{0} - {1}".format(task_name, task_description(task)))
40✔
423

424
        if task.dependencies:
40✔
425
            whitespace = (column_length + 3) * " "
40✔
426
            depends_on_message = "depends on tasks: %s" % " ".join(
40✔
427
                [str(dependency) for dependency in task.dependencies])
428
            print_text_line(whitespace + depends_on_message)
40✔
429

430

431
def print_list_of_tasks(reactor, quiet=False):
40✔
432
    tasks = reactor.get_tasks()
40✔
433
    sorted_tasks = sorted(tasks)
40✔
434
    if not quiet:
40✔
435
        print_text_line('Tasks found for project "%s":' % reactor.project.name)
40✔
436
    print_task_list(sorted_tasks, quiet)
40✔
437

438

439
def print_plan_list_of_tasks(options, arguments, reactor, quiet=False):
40✔
440
    execution_plan = reactor.create_execution_plan(arguments, options.environments)
×
441
    if not quiet:
×
442
        print_text_line('Tasks that will be executed for project "%s":' % reactor.project.name)
×
443
    print_task_list(execution_plan, quiet)
×
444

445

446
def _serialize_dependency(dep):
40✔
447
    from pybuilder.core import RequirementsFile
40✔
448
    if isinstance(dep, RequirementsFile):
40✔
449
        return {
40✔
450
            "name": dep.name,
451
            "version": None,
452
            "declaration_only": dep.declaration_only,
453
            "type": "requirements_file"
454
        }
455
    return {
40✔
456
        "name": dep.name,
457
        "version": dep.version,
458
        "url": dep.url,
459
        "extras": dep.extras,
460
        "markers": dep.markers,
461
        "declaration_only": dep.declaration_only,
462
        "type": "dependency"
463
    }
464

465

466
def _safe_property_value(value):
40✔
467
    if value is None or isinstance(value, (bool, int, float, str)):
40✔
468
        return value
40✔
469
    if isinstance(value, (list, tuple)):
40✔
470
        return [_safe_property_value(v) for v in value]
40✔
471
    if isinstance(value, dict):
40✔
472
        return {str(k): _safe_property_value(v) for k, v in value.items()}
40✔
473
    if isinstance(value, set):
40✔
474
        return sorted([_safe_property_value(v) for v in value], key=str)
40✔
475
    return repr(value)
40✔
476

477

478
def _build_project_info(reactor):
40✔
479
    project = reactor.project
40✔
480

481
    def serialize_person(p):
40✔
482
        if isinstance(p, (dict, str)):
40!
483
            return p
40✔
NEW
484
        result = {"name": p.name}
×
NEW
485
        if p.email:
×
NEW
486
            result["email"] = p.email
×
NEW
487
        if p.roles:
×
NEW
488
            result["roles"] = list(p.roles)
×
NEW
489
        return result
×
490

491
    def serialize_person_list(persons):
40✔
492
        return [serialize_person(p) for p in persons]
40✔
493

494
    tasks_info = []
40✔
495
    for task in sorted(reactor.get_tasks()):
40✔
496
        task_deps = []
40✔
497
        for dep in task.dependencies:
40✔
498
            task_deps.append({
40✔
499
                "name": str(dep.name),
500
                "optional": dep.optional
501
            })
502
        tasks_info.append({
40✔
503
            "name": task.name,
504
            "description": task_description(task),
505
            "dependencies": task_deps
506
        })
507

508
    return {
40✔
509
        "pybuilder_version": __version__,
510
        "project": {
511
            "name": project.name,
512
            "version": project.version,
513
            "dist_version": project.dist_version,
514
            "basedir": project.basedir,
515
            "summary": project.summary,
516
            "description": project.description,
517
            "author": project.author,
518
            "authors": serialize_person_list(project.authors),
519
            "maintainer": project.maintainer,
520
            "maintainers": serialize_person_list(project.maintainers),
521
            "license": project.license,
522
            "url": project.url,
523
            "urls": project.urls,
524
            "requires_python": project.requires_python,
525
            "default_task": project.default_task,
526
            "obsoletes": project.obsoletes,
527
            "explicit_namespaces": project.explicit_namespaces,
528
        },
529
        "environments": list(project.environments),
530
        "properties": {str(k): _safe_property_value(v) for k, v in sorted(project.properties.items())},
531
        "plugins": list(reactor.get_plugins()),
532
        "dependencies": {
533
            "runtime": [_serialize_dependency(d) for d in project.dependencies],
534
            "build": [_serialize_dependency(d) for d in project.build_dependencies],
535
            "plugin": [_serialize_dependency(d) for d in project.plugin_dependencies],
536
            "extras": {name: [_serialize_dependency(d) for d in deps]
537
                       for name, deps in project.extras_dependencies.items()}
538
        },
539
        "tasks": tasks_info,
540
        "manifest_included_files": project.manifest_included_files,
541
        "package_data": dict(project.package_data),
542
        "files_to_install": [[dest, files] for dest, files in project.files_to_install],
543
    }
544

545

546
def print_project_info(reactor, environments):
40✔
547
    reactor.project._environments = tuple(environments)
40✔
548
    reactor.execution_manager.execute_initializers(
40✔
549
        environments, logger=reactor.logger, project=reactor.project, reactor=reactor)
550

551
    info = _build_project_info(reactor)
40✔
552
    print_text_line(json.dumps(info, indent=2, default=str))
40✔
553

554

555
def get_failure_message():
40✔
556
    exc_type, exc_obj, exc_tb = sys.exc_info()
40✔
557

558
    filename = None
40✔
559
    lineno = None
40✔
560

561
    while exc_tb.tb_next:
40!
562
        exc_tb = exc_tb.tb_next
×
563

564
    frame = exc_tb.tb_frame
40✔
565
    if hasattr(frame, "f_code"):
40!
566
        code = frame.f_code
40✔
567
        filename = code.co_filename
40✔
568
        lineno = exc_tb.tb_lineno
40✔
569

570
        filename = nc(filename)
40✔
571
        for path in sys.path:
40!
572
            path = nc(path)
40✔
573
            if filename.startswith(path) and len(filename) > len(path) and filename[len(path)] == sep:
40✔
574
                filename = filename[len(path) + 1:]
40✔
575
                break
40✔
576

577
    return "%s%s%s" % ("%s: " % exc_type.__name__ if not isinstance(exc_obj, PyBuilderException) else "",
40✔
578
                       exc_obj,
579
                       " (%s:%d)" % (filename, lineno) if filename else "")
580

581

582
def main(*args):
40✔
583
    if not args:
22!
584
        args = sys.argv[1:]
×
585
    try:
22✔
586
        options, arguments = parse_options(args)
22✔
587
    except CommandLineUsageException as e:
×
588
        print_error_line("Usage error: %s\n" % e)
×
589
        print_error(e.usage)
×
590
        return 1
×
591

592
    start = datetime.datetime.now()
22✔
593

594
    logger = init_logger(options, use_stderr=options.project_info)
22✔
595
    reactor = init_reactor(logger)
22✔
596

597
    if options.start_project:
22!
598
        return start_project()
×
599

600
    if options.update_project:
22!
601
        return update_project()
×
602

603
    if options.project_info:
22!
NEW
604
        try:
×
NEW
605
            reactor.prepare_build(property_overrides=options.property_overrides,
×
606
                                  project_directory=options.project_directory,
607
                                  exclude_optional_tasks=options.exclude_optional_tasks,
608
                                  exclude_tasks=options.exclude_tasks,
609
                                  exclude_all_optional=options.exclude_all_optional,
610
                                  offline=options.offline,
611
                                  no_venvs=options.no_venvs
612
                                  )
NEW
613
            print_project_info(reactor, options.environments)
×
NEW
614
            return 0
×
NEW
615
        except PyBuilderException:
×
NEW
616
            print_build_status(get_failure_message(), options, successful=False)
×
NEW
617
            return 1
×
618

619
    if options.list_tasks or options.list_plan_tasks:
22!
620
        try:
×
621
            reactor.prepare_build(property_overrides=options.property_overrides,
×
622
                                  project_directory=options.project_directory,
623
                                  exclude_optional_tasks=options.exclude_optional_tasks,
624
                                  exclude_tasks=options.exclude_tasks,
625
                                  exclude_all_optional=options.exclude_all_optional,
626
                                  offline=options.offline,
627
                                  no_venvs=options.no_venvs
628
                                  )
629
            if options.list_tasks:
×
630
                print_list_of_tasks(reactor, quiet=options.very_quiet)
×
631

632
            if options.list_plan_tasks:
×
633
                print_plan_list_of_tasks(options, arguments, reactor, quiet=options.very_quiet)
×
634
            return 0
×
635
        except PyBuilderException:
×
636
            print_build_status(get_failure_message(), options, successful=False)
×
637
            return 1
×
638

639
    if not options.very_quiet:
22!
640
        print_styled_text_line(
22✔
641
            "PyBuilder version {0}".format(__version__), options, BOLD)
642
        print_text_line("Build started at %s" % format_timestamp(start))
22✔
643
        draw_line()
22✔
644

645
    successful = True
22✔
646
    failure_message = None
22✔
647
    summary = None
22✔
648

649
    try:
22✔
650
        try:
22✔
651
            reactor.prepare_build(property_overrides=options.property_overrides,
22✔
652
                                  project_directory=options.project_directory,
653
                                  exclude_optional_tasks=options.exclude_optional_tasks,
654
                                  exclude_tasks=options.exclude_tasks,
655
                                  exclude_all_optional=options.exclude_all_optional,
656
                                  reset_plugins=options.reset_plugins,
657
                                  offline=options.offline,
658
                                  no_venvs=options.no_venvs
659
                                  )
660

661
            if options.verbose or options.debug:
22!
662
                logger.debug("Verbose output enabled.\n")
22✔
663
                reactor.project.set_property("verbose", True)
22✔
664

665
            summary = reactor.build(
22✔
666
                environments=options.environments, tasks=arguments)
667

668
        except KeyboardInterrupt:
×
669
            raise PyBuilderException("Build aborted")
×
670

671
    except (Exception, SystemExit):
×
672
        successful = False
×
673
        failure_message = get_failure_message()
×
674
        if options.debug:
×
675
            traceback.print_exc(file=sys.stderr)
×
676

677
    finally:
678
        end = datetime.datetime.now()
22✔
679
        if not options.very_quiet:
22!
680
            print_summary(
22✔
681
                successful, summary, start, end, options, failure_message)
682

683
    return 0 if successful else 1
22✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc