Source code

Revision control

Other Tools

1
# This Source Code Form is subject to the terms of the Mozilla Public
2
# License, v. 2.0. If a copy of the MPL was not distributed with this
3
# file, # You can obtain one at http://mozilla.org/MPL/2.0/.
4
5
from __future__ import absolute_import, print_function, unicode_literals
6
7
import argparse
8
import itertools
9
import json
10
import logging
11
import operator
12
import os
13
import re
14
import subprocess
15
import sys
16
import tempfile
17
18
import mozpack.path as mozpath
19
20
from mach.decorators import (
21
CommandArgument,
22
CommandArgumentGroup,
23
CommandProvider,
24
Command,
25
SettingsProvider,
26
SubCommand,
27
)
28
29
from mozbuild.base import (
30
BuildEnvironmentNotFoundException,
31
MachCommandBase,
32
MachCommandConditions as conditions,
33
MozbuildObject,
34
)
35
36
EXCESSIVE_SWAP_MESSAGE = '''
37
===================
38
PERFORMANCE WARNING
39
40
Your machine experienced a lot of swap activity during the build. This is
41
possibly a sign that your machine doesn't have enough physical memory or
42
not enough available memory to perform the build. It's also possible some
43
other system activity during the build is to blame.
44
45
If you feel this message is not appropriate for your machine configuration,
46
please file a Firefox Build System :: General bug at
48
and tell us about your machine and build configuration so we can adjust the
49
warning heuristic.
50
===================
51
'''
52
53
54
class StoreDebugParamsAndWarnAction(argparse.Action):
55
def __call__(self, parser, namespace, values, option_string=None):
56
sys.stderr.write('The --debugparams argument is deprecated. Please ' +
57
'use --debugger-args instead.\n\n')
58
setattr(namespace, self.dest, values)
59
60
61
@CommandProvider
62
class Watch(MachCommandBase):
63
"""Interface to watch and re-build the tree."""
64
65
@Command('watch', category='post-build', description='Watch and re-build the tree.',
66
conditions=[conditions.is_firefox])
67
@CommandArgument('-v', '--verbose', action='store_true',
68
help='Verbose output for what commands the watcher is running.')
69
def watch(self, verbose=False):
70
"""Watch and re-build the source tree."""
71
72
if not conditions.is_artifact_build(self):
73
print('mach watch requires an artifact build. See '
75
return 1
76
77
if not self.substs.get('WATCHMAN', None):
78
print('mach watch requires watchman to be installed. See '
80
return 1
81
82
self._activate_virtualenv()
83
try:
84
self.virtualenv_manager.install_pip_package('pywatchman==1.3.0')
85
except Exception:
86
print('Could not install pywatchman from pip. See '
88
return 1
89
90
from mozbuild.faster_daemon import Daemon
91
daemon = Daemon(self.config_environment)
92
93
try:
94
return daemon.watch()
95
except KeyboardInterrupt:
96
# Suppress ugly stack trace when user hits Ctrl-C.
97
sys.exit(3)
98
99
100
@CommandProvider
101
class CargoProvider(MachCommandBase):
102
"""Invoke cargo in useful ways."""
103
104
@Command('cargo', category='build',
105
description='Invoke cargo in useful ways.')
106
def cargo(self):
107
self.parser.print_usage()
108
return 1
109
110
@SubCommand('cargo', 'check',
111
description='Run `cargo check` on a given crate. Defaults to gkrust.')
112
@CommandArgument('--all-crates', default=None, action='store_true',
113
help='Check all of the crates in the tree.')
114
@CommandArgument('crates', default=None, nargs='*', help='The crate name(s) to check.')
115
def check(self, all_crates=None, crates=None):
116
# XXX duplication with `mach vendor rust`
117
crates_and_roots = {
118
'gkrust': 'toolkit/library/rust',
119
'gkrust-gtest': 'toolkit/library/gtest/rust',
120
'js': 'js/rust',
121
'mozjs_sys': 'js/src',
122
'baldrdash': 'js/src/wasm/cranelift',
123
'geckodriver': 'testing/geckodriver',
124
}
125
126
if all_crates:
127
crates = crates_and_roots.keys()
128
elif crates is None or crates == []:
129
crates = ['gkrust']
130
131
for crate in crates:
132
root = crates_and_roots.get(crate, None)
133
if not root:
134
print('Cannot locate crate %s. Please check your spelling or '
135
'add the crate information to the list.' % crate)
136
return 1
137
138
check_targets = [
139
'force-cargo-library-check',
140
'force-cargo-host-library-check',
141
'force-cargo-program-check',
142
'force-cargo-host-program-check',
143
]
144
145
ret = self._run_make(srcdir=False, directory=root,
146
ensure_exit_code=0, silent=True,
147
print_directory=False, target=check_targets)
148
if ret != 0:
149
return ret
150
151
return 0
152
153
154
@CommandProvider
155
class Doctor(MachCommandBase):
156
"""Provide commands for diagnosing common build environment problems"""
157
@Command('doctor', category='devenv',
158
description='')
159
@CommandArgument('--fix', default=None, action='store_true',
160
help='Attempt to fix found problems.')
161
def doctor(self, fix=None):
162
self._activate_virtualenv()
163
from mozbuild.doctor import Doctor
164
doctor = Doctor(self.topsrcdir, self.topobjdir, fix)
165
return doctor.check_all()
166
167
168
@CommandProvider
169
class Clobber(MachCommandBase):
170
NO_AUTO_LOG = True
171
CLOBBER_CHOICES = ['objdir', 'python']
172
173
@Command('clobber', category='build',
174
description='Clobber the tree (delete the object directory).')
175
@CommandArgument('what', default=['objdir'], nargs='*',
176
help='Target to clobber, must be one of {{{}}} (default objdir).'.format(
177
', '.join(CLOBBER_CHOICES)))
178
@CommandArgument('--full', action='store_true',
179
help='Perform a full clobber')
180
def clobber(self, what, full=False):
181
"""Clean up the source and object directories.
182
183
Performing builds and running various commands generate various files.
184
185
Sometimes it is necessary to clean up these files in order to make
186
things work again. This command can be used to perform that cleanup.
187
188
By default, this command removes most files in the current object
189
directory (where build output is stored). Some files (like Visual
190
Studio project files) are not removed by default. If you would like
191
to remove the object directory in its entirety, run with `--full`.
192
193
The `python` target will clean up various generated Python files from
194
the source directory and will remove untracked files from well-known
195
directories containing Python packages. Run this to remove .pyc files,
196
compiled C extensions, etc. Note: all files not tracked or ignored by
197
version control in well-known Python package directories will be
198
deleted. Run the `status` command of your VCS to see if any untracked
199
files you haven't committed yet will be deleted.
200
"""
201
invalid = set(what) - set(self.CLOBBER_CHOICES)
202
if invalid:
203
print('Unknown clobber target(s): {}'.format(', '.join(invalid)))
204
return 1
205
206
ret = 0
207
if 'objdir' in what:
208
from mozbuild.controller.clobber import Clobberer
209
try:
210
Clobberer(self.topsrcdir, self.topobjdir, self.substs).remove_objdir(full)
211
except OSError as e:
212
if sys.platform.startswith('win'):
213
if isinstance(e, WindowsError) and e.winerror in (5, 32):
214
self.log(logging.ERROR, 'file_access_error', {'error': e},
215
"Could not clobber because a file was in use. If the "
216
"application is running, try closing it. {error}")
217
return 1
218
raise
219
220
if 'python' in what:
221
if conditions.is_hg(self):
222
cmd = ['hg', 'purge', '--all', '-I', 'glob:**.py[cdo]',
223
'-I', 'path:python/', '-I', 'path:third_party/python/']
224
elif conditions.is_git(self):
225
cmd = ['git', 'clean', '-f', '-x', '*.py[cdo]', 'python/',
226
'third_party/python/']
227
else:
228
# We don't know what is tracked/untracked if we don't have VCS.
229
# So we can't clean python/ and third_party/python/.
230
cmd = ['find', '.', '-type', 'f', '-name', '*.py[cdo]',
231
'-delete']
232
ret = subprocess.call(cmd, cwd=self.topsrcdir)
233
return ret
234
235
@property
236
def substs(self):
237
try:
238
return super(Clobber, self).substs
239
except BuildEnvironmentNotFoundException:
240
return {}
241
242
243
@CommandProvider
244
class Logs(MachCommandBase):
245
"""Provide commands to read mach logs."""
246
NO_AUTO_LOG = True
247
248
@Command('show-log', category='post-build',
249
description='Display mach logs')
250
@CommandArgument('log_file', nargs='?', type=argparse.FileType('rb'),
251
help='Filename to read log data from. Defaults to the log of the last '
252
'mach command.')
253
def show_log(self, log_file=None):
254
if not log_file:
255
path = self._get_state_filename('last_log.json')
256
log_file = open(path, 'rb')
257
258
if os.isatty(sys.stdout.fileno()):
259
env = dict(os.environ)
260
if 'LESS' not in env:
261
# Sensible default flags if none have been set in the user
262
# environment.
263
env[b'LESS'] = b'FRX'
264
less = subprocess.Popen(['less'], stdin=subprocess.PIPE, env=env)
265
# Various objects already have a reference to sys.stdout, so we
266
# can't just change it, we need to change the file descriptor under
267
# it to redirect to less's input.
268
# First keep a copy of the sys.stdout file descriptor.
269
output_fd = os.dup(sys.stdout.fileno())
270
os.dup2(less.stdin.fileno(), sys.stdout.fileno())
271
272
startTime = 0
273
for line in log_file:
274
created, action, params = json.loads(line)
275
if not startTime:
276
startTime = created
277
self.log_manager.terminal_handler.formatter.start_time = \
278
created
279
if 'line' in params:
280
record = logging.makeLogRecord({
281
'created': created,
282
'name': self._logger.name,
283
'levelno': logging.INFO,
284
'msg': '{line}',
285
'params': params,
286
'action': action,
287
})
288
self._logger.handle(record)
289
290
if self.log_manager.terminal:
291
# Close less's input so that it knows that we're done sending data.
292
less.stdin.close()
293
# Since the less's input file descriptor is now also the stdout
294
# file descriptor, we still actually have a non-closed system file
295
# descriptor for less's input. Replacing sys.stdout's file
296
# descriptor with what it was before we replaced it will properly
297
# close less's input.
298
os.dup2(output_fd, sys.stdout.fileno())
299
less.wait()
300
301
302
@CommandProvider
303
class Warnings(MachCommandBase):
304
"""Provide commands for inspecting warnings."""
305
306
@property
307
def database_path(self):
308
return self._get_state_filename('warnings.json')
309
310
@property
311
def database(self):
312
from mozbuild.compilation.warnings import WarningsDatabase
313
314
path = self.database_path
315
316
database = WarningsDatabase()
317
318
if os.path.exists(path):
319
database.load_from_file(path)
320
321
return database
322
323
@Command('warnings-summary', category='post-build',
324
description='Show a summary of compiler warnings.')
325
@CommandArgument('-C', '--directory', default=None,
326
help='Change to a subdirectory of the build directory first.')
327
@CommandArgument('report', default=None, nargs='?',
328
help='Warnings report to display. If not defined, show the most '
329
'recent report.')
330
def summary(self, directory=None, report=None):
331
database = self.database
332
333
if directory:
334
dirpath = self.join_ensure_dir(self.topsrcdir, directory)
335
if not dirpath:
336
return 1
337
else:
338
dirpath = None
339
340
type_counts = database.type_counts(dirpath)
341
sorted_counts = sorted(type_counts.iteritems(),
342
key=operator.itemgetter(1))
343
344
total = 0
345
for k, v in sorted_counts:
346
print('%d\t%s' % (v, k))
347
total += v
348
349
print('%d\tTotal' % total)
350
351
@Command('warnings-list', category='post-build',
352
description='Show a list of compiler warnings.')
353
@CommandArgument('-C', '--directory', default=None,
354
help='Change to a subdirectory of the build directory first.')
355
@CommandArgument('--flags', default=None, nargs='+',
356
help='Which warnings flags to match.')
357
@CommandArgument('report', default=None, nargs='?',
358
help='Warnings report to display. If not defined, show the most '
359
'recent report.')
360
def list(self, directory=None, flags=None, report=None):
361
database = self.database
362
363
by_name = sorted(database.warnings)
364
365
topsrcdir = mozpath.normpath(self.topsrcdir)
366
367
if directory:
368
directory = mozpath.normsep(directory)
369
dirpath = self.join_ensure_dir(topsrcdir, directory)
370
if not dirpath:
371
return 1
372
373
if flags:
374
# Flatten lists of flags.
375
flags = set(itertools.chain(*[flaglist.split(',') for flaglist in flags]))
376
377
for warning in by_name:
378
filename = mozpath.normsep(warning['filename'])
379
380
if filename.startswith(topsrcdir):
381
filename = filename[len(topsrcdir) + 1:]
382
383
if directory and not filename.startswith(directory):
384
continue
385
386
if flags and warning['flag'] not in flags:
387
continue
388
389
if warning['column'] is not None:
390
print('%s:%d:%d [%s] %s' % (
391
filename, warning['line'], warning['column'],
392
warning['flag'], warning['message']))
393
else:
394
print('%s:%d [%s] %s' % (filename, warning['line'],
395
warning['flag'], warning['message']))
396
397
def join_ensure_dir(self, dir1, dir2):
398
dir1 = mozpath.normpath(dir1)
399
dir2 = mozpath.normsep(dir2)
400
joined_path = mozpath.join(dir1, dir2)
401
if os.path.isdir(joined_path):
402
return joined_path
403
print('Specified directory not found.')
404
return None
405
406
407
@CommandProvider
408
class GTestCommands(MachCommandBase):
409
@Command('gtest', category='testing',
410
description='Run GTest unit tests (C++ tests).')
411
@CommandArgument('gtest_filter', default=b"*", nargs='?', metavar='gtest_filter',
412
help="test_filter is a ':'-separated list of wildcard patterns "
413
"(called the positive patterns), optionally followed by a '-' "
414
"and another ':'-separated pattern list (called the negative patterns).")
415
@CommandArgument('--jobs', '-j', default='1', nargs='?', metavar='jobs', type=int,
416
help='Run the tests in parallel using multiple processes.')
417
@CommandArgument('--tbpl-parser', '-t', action='store_true',
418
help='Output test results in a format that can be parsed by TBPL.')
419
@CommandArgument('--shuffle', '-s', action='store_true',
420
help='Randomize the execution order of tests.')
421
@CommandArgument('--enable-webrender', action='store_true',
422
default=False, dest='enable_webrender',
423
help='Enable the WebRender compositor in Gecko.')
424
@CommandArgumentGroup('Android')
425
@CommandArgument('--package',
426
default='org.mozilla.geckoview.test',
427
group='Android',
428
help='Package name of test app.')
429
@CommandArgument('--adbpath',
430
dest='adb_path',
431
group='Android',
432
help='Path to adb binary.')
433
@CommandArgument('--deviceSerial',
434
dest='device_serial',
435
group='Android',
436
help="adb serial number of remote device. "
437
"Required when more than one device is connected to the host. "
438
"Use 'adb devices' to see connected devices.")
439
@CommandArgument('--remoteTestRoot',
440
dest='remote_test_root',
441
group='Android',
442
help='Remote directory to use as test root '
443
'(eg. /mnt/sdcard/tests or /data/local/tests).')
444
@CommandArgument('--libxul',
445
dest='libxul_path',
446
group='Android',
447
help='Path to gtest libxul.so.')
448
@CommandArgument('--no-install', action='store_true',
449
default=False,
450
group='Android',
451
help='Skip the installation of the APK.')
452
@CommandArgumentGroup('debugging')
453
@CommandArgument('--debug', action='store_true', group='debugging',
454
help='Enable the debugger. Not specifying a --debugger option will result in '
455
'the default debugger being used.')
456
@CommandArgument('--debugger', default=None, type=str, group='debugging',
457
help='Name of debugger to use.')
458
@CommandArgument('--debugger-args', default=None, metavar='params', type=str,
459
group='debugging',
460
help='Command-line arguments to pass to the debugger itself; '
461
'split as the Bourne shell would.')
462
def gtest(self, shuffle, jobs, gtest_filter, tbpl_parser, enable_webrender,
463
package, adb_path, device_serial, remote_test_root, libxul_path, no_install,
464
debug, debugger, debugger_args):
465
466
# We lazy build gtest because it's slow to link
467
try:
468
config = self.config_environment
469
except Exception:
470
print("Please run |./mach build| before |./mach gtest|.")
471
return 1
472
473
active_backend = config.substs.get('BUILD_BACKENDS', [None])[0]
474
if 'Tup' in active_backend:
475
gtest_build_target = mozpath.join(self.topobjdir, '<gtest>')
476
else:
477
gtest_build_target = 'recurse_gtest'
478
479
res = self._mach_context.commands.dispatch('build', self._mach_context,
480
what=[gtest_build_target])
481
if res:
482
print("Could not build xul-gtest")
483
return res
484
485
if self.substs.get('MOZ_WIDGET_TOOLKIT') == 'cocoa':
486
self._run_make(directory='browser/app', target='repackage',
487
ensure_exit_code=True)
488
489
cwd = os.path.join(self.topobjdir, '_tests', 'gtest')
490
491
if not os.path.isdir(cwd):
492
os.makedirs(cwd)
493
494
if conditions.is_android(self):
495
if jobs != 1:
496
print("--jobs is not supported on Android and will be ignored")
497
if debug or debugger or debugger_args:
498
print("--debug options are not supported on Android and will be ignored")
499
return self.android_gtest(cwd, shuffle, gtest_filter,
500
package, adb_path, device_serial,
501
remote_test_root, libxul_path,
502
enable_webrender, not no_install)
503
504
if package or adb_path or device_serial or remote_test_root or libxul_path or no_install:
505
print("One or more Android-only options will be ignored")
506
507
app_path = self.get_binary_path('app')
508
args = [app_path, '-unittest', '--gtest_death_test_style=threadsafe']
509
510
if sys.platform.startswith('win') and \
511
'MOZ_LAUNCHER_PROCESS' in self.defines:
512
args.append('--wait-for-browser')
513
514
if debug or debugger or debugger_args:
515
args = self.prepend_debugger_args(args, debugger, debugger_args)
516
517
# Use GTest environment variable to control test execution
518
# For details see:
520
gtest_env = {b'GTEST_FILTER': gtest_filter}
521
522
# Note: we must normalize the path here so that gtest on Windows sees
523
# a MOZ_GMP_PATH which has only Windows dir seperators, because
524
# nsIFile cannot open the paths with non-Windows dir seperators.
525
xre_path = os.path.join(os.path.normpath(self.topobjdir), "dist", "bin")
526
gtest_env["MOZ_XRE_DIR"] = xre_path
527
gtest_env["MOZ_GMP_PATH"] = os.pathsep.join(
528
os.path.join(xre_path, p, "1.0")
529
for p in ('gmp-fake', 'gmp-fakeopenh264')
530
)
531
532
gtest_env[b"MOZ_RUN_GTEST"] = b"True"
533
534
if shuffle:
535
gtest_env[b"GTEST_SHUFFLE"] = b"True"
536
537
if tbpl_parser:
538
gtest_env[b"MOZ_TBPL_PARSER"] = b"True"
539
540
if enable_webrender:
541
gtest_env[b"MOZ_WEBRENDER"] = b"1"
542
gtest_env[b"MOZ_ACCELERATED"] = b"1"
543
else:
544
gtest_env[b"MOZ_WEBRENDER"] = b"0"
545
546
if jobs == 1:
547
return self.run_process(args=args,
548
append_env=gtest_env,
549
cwd=cwd,
550
ensure_exit_code=False,
551
pass_thru=True)
552
553
from mozprocess import ProcessHandlerMixin
554
import functools
555
556
def handle_line(job_id, line):
557
# Prepend the jobId
558
line = '[%d] %s' % (job_id + 1, line.strip())
559
self.log(logging.INFO, "GTest", {'line': line}, '{line}')
560
561
gtest_env["GTEST_TOTAL_SHARDS"] = str(jobs)
562
processes = {}
563
for i in range(0, jobs):
564
gtest_env["GTEST_SHARD_INDEX"] = str(i)
565
processes[i] = ProcessHandlerMixin([app_path, "-unittest"],
566
cwd=cwd,
567
env=gtest_env,
568
processOutputLine=[
569
functools.partial(handle_line, i)],
570
universal_newlines=True)
571
processes[i].run()
572
573
exit_code = 0
574
for process in processes.values():
575
status = process.wait()
576
if status:
577
exit_code = status
578
579
# Clamp error code to 255 to prevent overflowing multiple of
580
# 256 into 0
581
if exit_code > 255:
582
exit_code = 255
583
584
return exit_code
585
586
def android_gtest(self, test_dir, shuffle, gtest_filter,
587
package, adb_path, device_serial, remote_test_root, libxul_path,
588
enable_webrender, install):
589
# setup logging for mozrunner
590
from mozlog.commandline import setup_logging
591
format_args = {'level': self._mach_context.settings['test']['level']}
592
default_format = self._mach_context.settings['test']['format']
593
setup_logging('mach-gtest', {}, {default_format: sys.stdout}, format_args)
594
595
# ensure that a device is available and test app is installed
596
from mozrunner.devices.android_device import (verify_android_device, get_adb_path)
597
verify_android_device(self, install=install, app=package, device_serial=device_serial)
598
599
if not adb_path:
600
adb_path = get_adb_path(self)
601
if not libxul_path:
602
libxul_path = os.path.join(self.topobjdir, "dist", "bin", "gtest", "libxul.so")
603
604
# run gtest via remotegtests.py
605
import imp
606
path = os.path.join('testing', 'gtest', 'remotegtests.py')
607
with open(path, 'r') as fh:
608
imp.load_module('remotegtests', fh, path,
609
('.py', 'r', imp.PY_SOURCE))
610
import remotegtests
611
tester = remotegtests.RemoteGTests()
612
tester.run_gtest(test_dir, shuffle, gtest_filter, package, adb_path, device_serial,
613
remote_test_root, libxul_path, None, enable_webrender)
614
tester.cleanup()
615
616
return 0
617
618
def prepend_debugger_args(self, args, debugger, debugger_args):
619
'''
620
Given an array with program arguments, prepend arguments to run it under a
621
debugger.
622
623
:param args: The executable and arguments used to run the process normally.
624
:param debugger: The debugger to use, or empty to use the default debugger.
625
:param debugger_args: Any additional parameters to pass to the debugger.
626
'''
627
628
import mozdebug
629
630
if not debugger:
631
# No debugger name was provided. Look for the default ones on
632
# current OS.
633
debugger = mozdebug.get_default_debugger_name(mozdebug.DebuggerSearch.KeepLooking)
634
635
if debugger:
636
debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args)
637
if not debuggerInfo:
638
print("Could not find a suitable debugger in your PATH.")
639
return 1
640
641
# Parameters come from the CLI. We need to convert them before
642
# their use.
643
if debugger_args:
644
from mozbuild import shellutil
645
try:
646
debugger_args = shellutil.split(debugger_args)
647
except shellutil.MetaCharacterException as e:
648
print("The --debugger_args you passed require a real shell to parse them.")
649
print("(We can't handle the %r character.)" % e.char)
650
return 1
651
652
# Prepend the debugger args.
653
args = [debuggerInfo.path] + debuggerInfo.args + args
654
return args
655
656
657
@CommandProvider
658
class Package(MachCommandBase):
659
"""Package the built product for distribution."""
660
661
@Command('package', category='post-build',
662
description='Package the built product for distribution as an APK, DMG, etc.')
663
@CommandArgument('-v', '--verbose', action='store_true',
664
help='Verbose output for what commands the packaging process is running.')
665
def package(self, verbose=False):
666
ret = self._run_make(directory=".", target='package',
667
silent=not verbose, ensure_exit_code=False)
668
if ret == 0:
669
self.notify('Packaging complete')
670
return ret
671
672
673
@CommandProvider
674
class Install(MachCommandBase):
675
"""Install a package."""
676
677
@Command('install-desktop', category='post-build',
678
conditional_name='install',
679
conditions=[conditions.is_not_android],
680
description='Install the package on the machine.')
681
@CommandArgument('--verbose', '-v', action='store_true',
682
help='Print verbose output when installing to an Android emulator.')
683
def install(self, verbose=False):
684
ret = self._run_make(directory=".", target='install', ensure_exit_code=False)
685
if ret == 0:
686
self.notify('Install complete')
687
return ret
688
689
690
@SettingsProvider
691
class RunSettings():
692
config_settings = [
693
('runprefs.*', 'string', """
694
Pass a pref into Firefox when using `mach run`, of the form `foo.bar=value`.
695
Prefs will automatically be cast into the appropriate type. Integers can be
696
single quoted to force them to be strings.
697
""".strip()),
698
]
699
700
701
@CommandProvider
702
class RunProgram(MachCommandBase):
703
"""Run the compiled program."""
704
705
prog_group = 'the compiled program'
706
707
@Command('run-desktop', category='post-build',
708
conditional_name='run',
709
conditions=[conditions.is_not_android],
710
description='Run the compiled program, possibly under a debugger or DMD.')
711
@CommandArgument('params', nargs='...', group=prog_group,
712
help='Command-line arguments to be passed through to the program. Not '
713
'specifying a --profile or -P option will result in a temporary profile '
714
'being used.')
715
@CommandArgumentGroup(prog_group)
716
@CommandArgument('--remote', '-r', action='store_true', group=prog_group,
717
help='Do not pass the --no-remote argument by default.')
718
@CommandArgument('--background', '-b', action='store_true', group=prog_group,
719
help='Do not pass the --foreground argument by default on Mac.')
720
@CommandArgument('--noprofile', '-n', action='store_true', group=prog_group,
721
help='Do not pass the --profile argument by default.')
722
@CommandArgument('--disable-e10s', action='store_true', group=prog_group,
723
help='Run the program with electrolysis disabled.')
724
@CommandArgument('--enable-crash-reporter', action='store_true', group=prog_group,
725
help='Run the program with the crash reporter enabled.')
726
@CommandArgument('--setpref', action='append', default=[], group=prog_group,
727
help='Set the specified pref before starting the program. Can be set '
728
'multiple times. Prefs can also be set in ~/.mozbuild/machrc in the '
729
'[runprefs] section - see `./mach settings` for more information.')
730
@CommandArgument('--temp-profile', action='store_true', group=prog_group,
731
help='Run the program using a new temporary profile created inside '
732
'the objdir.')
733
@CommandArgument('--macos-open', action='store_true', group=prog_group,
734
help="On macOS, run the program using the open(1) command. Per open(1), "
735
"the browser is launched \"just as if you had double-clicked the file's "
736
"icon\". The browser can not be launched under a debugger with this option.")
737
@CommandArgumentGroup('debugging')
738
@CommandArgument('--debug', action='store_true', group='debugging',
739
help='Enable the debugger. Not specifying a --debugger option will result '
740
'in the default debugger being used.')
741
@CommandArgument('--debugger', default=None, type=str, group='debugging',
742
help='Name of debugger to use.')
743
@CommandArgument('--debugger-args', default=None, metavar='params', type=str,
744
group='debugging',
745
help='Command-line arguments to pass to the debugger itself; '
746
'split as the Bourne shell would.')
747
@CommandArgument('--debugparams', action=StoreDebugParamsAndWarnAction,
748
default=None, type=str, dest='debugger_args', group='debugging',
749
help=argparse.SUPPRESS)
750
@CommandArgumentGroup('DMD')
751
@CommandArgument('--dmd', action='store_true', group='DMD',
752
help='Enable DMD. The following arguments have no effect without this.')
753
@CommandArgument('--mode', choices=['live', 'dark-matter', 'cumulative', 'scan'], group='DMD',
754
help='Profiling mode. The default is \'dark-matter\'.')
755
@CommandArgument('--stacks', choices=['partial', 'full'], group='DMD',
756
help='Allocation stack trace coverage. The default is \'partial\'.')
757
@CommandArgument('--show-dump-stats', action='store_true', group='DMD',
758
help='Show stats when doing dumps.')
759
def run(self, params, remote, background, noprofile, disable_e10s,
760
enable_crash_reporter, setpref, temp_profile, macos_open, debug,
761
debugger, debugger_args, dmd, mode, stacks, show_dump_stats):
762
763
from mozprofile import Profile, Preferences
764
765
try:
766
binpath = self.get_binary_path('app')
767
except Exception as e:
768
print("It looks like your program isn't built.",
769
"You can run |mach build| to build it.")
770
print(e)
771
return 1
772
773
args = []
774
if macos_open:
775
if debug:
776
print("The browser can not be launched in the debugger "
777
"when using the macOS open command.")
778
return 1
779
try:
780
m = re.search(r'^.+\.app', binpath)
781
apppath = m.group(0)
782
args = ['open', apppath, '--args']
783
except Exception as e:
784
print("Couldn't get the .app path from the binary path. "
785
"The macOS open option can only be used on macOS")
786
print(e)
787
return 1
788
else:
789
args = [binpath]
790
791
if params:
792
args.extend(params)
793
794
if not remote:
795
args.append('-no-remote')
796
797
if not background and sys.platform == 'darwin':
798
args.append('-foreground')
799
800
if sys.platform.startswith('win') and \
801
'MOZ_LAUNCHER_PROCESS' in self.defines:
802
args.append('-wait-for-browser')
803
804
no_profile_option_given = \
805
all(p not in params for p in ['-profile', '--profile', '-P'])
806
if no_profile_option_given and not noprofile:
807
prefs = {
808
'browser.aboutConfig.showWarning': False,
809
'browser.shell.checkDefaultBrowser': False,
810
'general.warnOnAboutConfig': False,
811
}
812
prefs.update(self._mach_context.settings.runprefs)
813
prefs.update([p.split('=', 1) for p in setpref])
814
for pref in prefs:
815
prefs[pref] = Preferences.cast(prefs[pref])
816
817
tmpdir = os.path.join(self.topobjdir, 'tmp')
818
if not os.path.exists(tmpdir):
819
os.makedirs(tmpdir)
820
821
if (temp_profile):
822
path = tempfile.mkdtemp(dir=tmpdir, prefix='profile-')
823
else:
824
path = os.path.join(tmpdir, 'profile-default')
825
826
profile = Profile(path, preferences=prefs)
827
args.append('-profile')
828
args.append(profile.profile)
829
830
if not no_profile_option_given and setpref:
831
print("setpref is only supported if a profile is not specified")
832
return 1
833
834
if not no_profile_option_given:
835
# The profile name may be non-ascii, but come from the
836
# commandline as str, so convert here with a better guess at
837
# an encoding than the default.
838
encoding = (sys.getfilesystemencoding() or
839
sys.getdefaultencoding())
840
args = [unicode(a, encoding) if not isinstance(a, unicode) else a
841
for a in args]
842
843
extra_env = {
844
'MOZ_DEVELOPER_REPO_DIR': self.topsrcdir,
845
'MOZ_DEVELOPER_OBJ_DIR': self.topobjdir,
846
'RUST_BACKTRACE': 'full',
847
}
848
849
if not enable_crash_reporter:
850
extra_env['MOZ_CRASHREPORTER_DISABLE'] = '1'
851
else:
852
extra_env['MOZ_CRASHREPORTER'] = '1'
853
854
if disable_e10s:
855
extra_env['MOZ_FORCE_DISABLE_E10S'] = '1'
856
857
if debug or debugger or debugger_args:
858
if 'INSIDE_EMACS' in os.environ:
859
self.log_manager.terminal_handler.setLevel(logging.WARNING)
860
861
import mozdebug
862
if not debugger:
863
# No debugger name was provided. Look for the default ones on
864
# current OS.
865
debugger = mozdebug.get_default_debugger_name(mozdebug.DebuggerSearch.KeepLooking)
866
867
if debugger:
868
self.debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args)
869
870
if not debugger or not self.debuggerInfo:
871
print("Could not find a suitable debugger in your PATH.")
872
return 1
873
874
# Parameters come from the CLI. We need to convert them before
875
# their use.
876
if debugger_args:
877
from mozbuild import shellutil
878
try:
879
debugger_args = shellutil.split(debugger_args)
880
except shellutil.MetaCharacterException as e:
881
print("The --debugger-args you passed require a real shell to parse them.")
882
print("(We can't handle the %r character.)" % e.char)
883
return 1
884
885
# Prepend the debugger args.
886
args = [self.debuggerInfo.path] + self.debuggerInfo.args + args
887
888
if dmd:
889
dmd_params = []
890
891
if mode:
892
dmd_params.append('--mode=' + mode)
893
if stacks:
894
dmd_params.append('--stacks=' + stacks)
895
if show_dump_stats:
896
dmd_params.append('--show-dump-stats=yes')
897
898
if dmd_params:
899
extra_env['DMD'] = ' '.join(dmd_params)
900
else:
901
extra_env['DMD'] = '1'
902
903
return self.run_process(args=args, ensure_exit_code=False,
904
pass_thru=True, append_env=extra_env)
905
906
907
@CommandProvider
908
class Buildsymbols(MachCommandBase):
909
"""Produce a package of debug symbols suitable for use with Breakpad."""
910
911
@Command('buildsymbols', category='post-build',
912
description='Produce a package of Breakpad-format symbols.')
913
def buildsymbols(self):
914
return self._run_make(directory=".", target='buildsymbols', ensure_exit_code=False)
915
916
917
@CommandProvider
918
class Makefiles(MachCommandBase):
919
@Command('empty-makefiles', category='build-dev',
920
description='Find empty Makefile.in in the tree.')
921
def empty(self):
922
import pymake.parser
923
import pymake.parserdata
924
925
IGNORE_VARIABLES = {
926
'DEPTH': ('@DEPTH@',),
927
'topsrcdir': ('@top_srcdir@',),
928
'srcdir': ('@srcdir@',),
929
'relativesrcdir': ('@relativesrcdir@',),
930
'VPATH': ('@srcdir@',),
931
}
932
933
IGNORE_INCLUDES = [
934
'include $(DEPTH)/config/autoconf.mk',
935
'include $(topsrcdir)/config/config.mk',
936
'include $(topsrcdir)/config/rules.mk',
937
]
938
939
def is_statement_relevant(s):
940
if isinstance(s, pymake.parserdata.SetVariable):
941
exp = s.vnameexp
942
if not exp.is_static_string:
943
return True
944
945
if exp.s not in IGNORE_VARIABLES:
946
return True
947
948
return s.value not in IGNORE_VARIABLES[exp.s]
949
950
if isinstance(s, pymake.parserdata.Include):
951
if s.to_source() in IGNORE_INCLUDES:
952
return False
953
954
return True
955
956
for path in self._makefile_ins():
957
relpath = os.path.relpath(path, self.topsrcdir)
958
try:
959
statements = [s for s in pymake.parser.parsefile(path)
960
if is_statement_relevant(s)]
961
962
if not statements:
963
print(relpath)
964
except pymake.parser.SyntaxError:
965
print('Warning: Could not parse %s' % relpath, file=sys.stderr)
966
967
def _makefile_ins(self):
968
for root, dirs, files in os.walk(self.topsrcdir):
969
for f in files:
970
if f == 'Makefile.in':
971
yield os.path.join(root, f)
972
973
974
@CommandProvider
975
class MachDebug(MachCommandBase):
976
@Command('environment', category='build-dev',
977
description='Show info about the mach and build environment.')
978
@CommandArgument('--format', default='pretty',
979
choices=['pretty', 'configure', 'json'],
980
help='Print data in the given format.')
981
@CommandArgument('--output', '-o', type=str,
982
help='Output to the given file.')
983
@CommandArgument('--verbose', '-v', action='store_true',
984
help='Print verbose output.')
985
def environment(self, format, output=None, verbose=False):
986
func = getattr(self, '_environment_%s' % format.replace('.', '_'))
987
988
if output:
989
# We want to preserve mtimes if the output file already exists
990
# and the content hasn't changed.
991
from mozbuild.util import FileAvoidWrite
992
with FileAvoidWrite(output) as out:
993
return func(out, verbose)
994
return func(sys.stdout, verbose)
995
996
def _environment_pretty(self, out, verbose):
997
state_dir = self._mach_context.state_dir
998
import platform
999
print('platform:\n\t%s' % platform.platform(), file=out)
1000
print('python version:\n\t%s' % sys.version, file=out)
1001
print('python prefix:\n\t%s' % sys.prefix, file=out)
1002
print('mach cwd:\n\t%s' % self._mach_context.cwd, file=out)
1003
print('os cwd:\n\t%s' % os.getcwd(), file=out)
1004
print('mach directory:\n\t%s' % self._mach_context.topdir, file=out)
1005
print('state directory:\n\t%s' % state_dir, file=out)
1006
1007
print('object directory:\n\t%s' % self.topobjdir, file=out)
1008
1009
if self.mozconfig['path']:
1010
print('mozconfig path:\n\t%s' % self.mozconfig['path'], file=out)
1011
if self.mozconfig['configure_args']:
1012
print('mozconfig configure args:', file=out)
1013
for arg in self.mozconfig['configure_args']:
1014
print('\t%s' % arg, file=out)
1015
1016
if self.mozconfig['make_extra']:
1017
print('mozconfig extra make args:', file=out)
1018
for arg in self.mozconfig['make_extra']:
1019
print('\t%s' % arg, file=out)
1020
1021
if self.mozconfig['make_flags']:
1022
print('mozconfig make flags:', file=out)
1023
for arg in self.mozconfig['make_flags']:
1024
print('\t%s' % arg, file=out)
1025
1026
config = None
1027
1028
try:
1029
config = self.config_environment
1030
1031
except Exception:
1032
pass
1033
1034
if config:
1035
print('config topsrcdir:\n\t%s' % config.topsrcdir, file=out)
1036
print('config topobjdir:\n\t%s' % config.topobjdir, file=out)
1037
1038
if verbose:
1039
print('config substitutions:', file=out)
1040
for k in sorted(config.substs):
1041
print('\t%s: %s' % (k, config.substs[k]), file=out)
1042
1043
print('config defines:', file=out)
1044
for k in sorted(config.defines):
1045
print('\t%s' % k, file=out)
1046
1047
def _environment_json(self, out, verbose):
1048
import json
1049
1050
class EnvironmentEncoder(json.JSONEncoder):
1051
def default(self, obj):
1052
if isinstance(obj, MozbuildObject):
1053
result = {
1054
'topsrcdir': obj.topsrcdir,
1055
'topobjdir': obj.topobjdir,
1056
'mozconfig': obj.mozconfig,
1057
}
1058
if verbose:
1059
result['substs'] = obj.substs
1060
result['defines'] = obj.defines
1061
return result
1062
elif isinstance(obj, set):
1063
return list(obj)
1064
return json.JSONEncoder.default(self, obj)
1065
json.dump(self, cls=EnvironmentEncoder, sort_keys=True, fp=out)
1066
1067
1068
@CommandProvider
1069
class Vendor(MachCommandBase):
1070
"""Vendor third-party dependencies into the source repository."""
1071
1072
@Command('vendor', category='misc',
1073
description='Vendor third-party dependencies into the source repository.')
1074
def vendor(self):
1075
self.parser.print_usage()
1076
sys.exit(1)
1077
1078
@SubCommand('vendor', 'rust',
1079
description='Vendor rust crates from crates.io into third_party/rust')
1080
@CommandArgument('--ignore-modified', action='store_true',
1081
help='Ignore modified files in current checkout',
1082
default=False)
1083
@CommandArgument('--build-peers-said-large-imports-were-ok', action='store_true',
1084
help='Permit overly-large files to be added to the repository',
1085
default=False)
1086
def vendor_rust(self, **kwargs):
1087
from mozbuild.vendor_rust import VendorRust
1088
vendor_command = self._spawn(VendorRust)
1089
vendor_command.vendor(**kwargs)
1090
1091
@SubCommand('vendor', 'aom',
1092
description='Vendor av1 video codec reference implementation into the '
1093
'source repository.')
1094
@CommandArgument('-r', '--revision',
1095
help='Repository tag or commit to update to.')
1096
@CommandArgument('--repo',
1097
help='Repository url to pull a snapshot from. '
1098
'Supports github and googlesource.')
1099
@CommandArgument('--ignore-modified', action='store_true',
1100
help='Ignore modified files in current checkout',
1101
default=False)
1102
def vendor_aom(self, **kwargs):
1103
from mozbuild.vendor_aom import VendorAOM
1104
vendor_command = self._spawn(VendorAOM)
1105
vendor_command.vendor(**kwargs)
1106
1107
@SubCommand('vendor', 'dav1d',
1108
description='Vendor dav1d implementation of AV1 into the source repository.')
1109
@CommandArgument('-r', '--revision',
1110
help='Repository tag or commit to update to.')
1111
@CommandArgument('--repo',
1112
help='Repository url to pull a snapshot from. Supports gitlab.')
1113
@CommandArgument('--ignore-modified', action='store_true',
1114
help='Ignore modified files in current checkout',
1115
default=False)
1116
def vendor_dav1d(self, **kwargs):
1117
from mozbuild.vendor_dav1d import VendorDav1d
1118
vendor_command = self._spawn(VendorDav1d)
1119
vendor_command.vendor(**kwargs)
1120
1121
@SubCommand('vendor', 'python',
1122
description='Vendor Python packages from pypi.org into third_party/python')
1123
@CommandArgument('--with-windows-wheel', action='store_true',
1124
help='Vendor a wheel for Windows along with the source package',
1125
default=False)
1126
@CommandArgument('packages', default=None, nargs='*',
1127
help='Packages to vendor. If omitted, packages and their dependencies '
1128
'defined in Pipfile.lock will be vendored. If Pipfile has been modified, '
1129
'then Pipfile.lock will be regenerated. Note that transient dependencies '
1130
'may be updated when running this command.')
1131
def vendor_python(self, **kwargs):
1132
from mozbuild.vendor_python import VendorPython
1133
vendor_command = self._spawn(VendorPython)
1134
vendor_command.vendor(**kwargs)
1135
1136
@SubCommand('vendor', 'manifest',
1137
description='Vendor externally hosted repositories into this '
1138
'repository.')
1139
@CommandArgument('files', nargs='+',
1140
help='Manifest files to work on')
1141
@CommandArgumentGroup('verify')
1142
@CommandArgument('--verify', '-v', action='store_true', group='verify',
1143
required=True, help='Verify manifest')
1144
def vendor_manifest(self, files, verify):
1145
from mozbuild.vendor_manifest import verify_manifests
1146
verify_manifests(files)
1147
1148
1149
@CommandProvider
1150
class WebRTCGTestCommands(GTestCommands):
1151
@Command('webrtc-gtest', category='testing',
1152
description='Run WebRTC.org GTest unit tests.')
1153
@CommandArgument('gtest_filter', default=b"*", nargs='?', metavar='gtest_filter',
1154
help="test_filter is a ':'-separated list of wildcard patterns "
1155
"(called the positive patterns), optionally followed by a '-' and "
1156
"another ':'-separated pattern list (called the negative patterns).")
1157
@CommandArgumentGroup('debugging')
1158
@CommandArgument('--debug', action='store_true', group='debugging',
1159
help='Enable the debugger. Not specifying a --debugger option will '
1160
'result in the default debugger being used.')
1161
@CommandArgument('--debugger', default=None, type=str, group='debugging',
1162
help='Name of debugger to use.')
1163
@CommandArgument('--debugger-args', default=None, metavar='params', type=str,
1164
group='debugging',
1165
help='Command-line arguments to pass to the debugger itself; '
1166
'split as the Bourne shell would.')
1167
def gtest(self, gtest_filter, debug, debugger,
1168
debugger_args):
1169
app_path = self.get_binary_path('webrtc-gtest')
1170
args = [app_path]
1171
1172
if debug or debugger or debugger_args:
1173
args = self.prepend_debugger_args(args, debugger, debugger_args)
1174
1175
# Used to locate resources used by tests
1176
cwd = os.path.join(self.topsrcdir, 'media', 'webrtc', 'trunk')
1177
1178
if not os.path.isdir(cwd):
1179
print('Unable to find working directory for tests: %s' % cwd)
1180
return 1
1181
1182
gtest_env = {
1183
# These tests are not run under ASAN upstream, so we need to
1184
# disable some checks.
1185
b'ASAN_OPTIONS': 'alloc_dealloc_mismatch=0',
1186
# Use GTest environment variable to control test execution
1187
# For details see:
1189
b'GTEST_FILTER': gtest_filter
1190
}
1191
1192
return self.run_process(args=args,
1193
append_env=gtest_env,
1194
cwd=cwd,
1195
ensure_exit_code=False,
1196
pass_thru=True)
1197
1198
1199
@CommandProvider
1200
class Repackage(MachCommandBase):
1201
'''Repackages artifacts into different formats.
1202
1203
This is generally used after packages are signed by the signing
1204
scriptworkers in order to bundle things up into shippable formats, such as a
1205
.dmg on OSX or an installer exe on Windows.
1206
'''
1207
@Command('repackage', category='misc',
1208
description='Repackage artifacts into different formats.')
1209
def repackage(self):
1210
print("Usage: ./mach repackage [dmg|installer|mar] [args...]")
1211
1212
@SubCommand('repackage', 'dmg',
1213
description='Repackage a tar file into a .dmg for OSX')
1214
@CommandArgument('--input', '-i', type=str, required=True,
1215
help='Input filename')
1216
@CommandArgument('--output', '-o', type=str, required=True,
1217
help='Output filename')
1218
def repackage_dmg(self, input, output):
1219
if not os.path.exists(input):
1220
print('Input file does not exist: %s' % input)
1221
return 1
1222
1223
if not os.path.exists(os.path.join(self.topobjdir, 'config.status')):
1224
print('config.status not found. Please run |mach configure| '
1225
'prior to |mach repackage|.')
1226
return 1
1227
1228
from mozbuild.repackaging.dmg import repackage_dmg
1229
repackage_dmg(input, output)
1230
1231
@SubCommand('repackage', 'installer',
1232
description='Repackage into a Windows installer exe')
1233
@CommandArgument('--tag', type=str, required=True,
1234
help='The .tag file used to build the installer')
1235
@CommandArgument('--setupexe', type=str, required=True,
1236
help='setup.exe file inside the installer')
1237
@CommandArgument('--package', type=str, required=False,
1238
help='Optional package .zip for building a full installer')
1239
@CommandArgument('--output', '-o', type=str, required=True,
1240
help='Output filename')
1241
@CommandArgument('--package-name', type=str, required=False,
1242
help='Name of the package being rebuilt')
1243
@CommandArgument('--sfx-stub', type=str, required=True,
1244
help='Path to the self-extraction stub.')
1245
@CommandArgument('--use-upx', required=False, action='store_true',
1246
help='Run UPX on the self-extraction stub.')
1247
def repackage_installer(self, tag, setupexe, package, output, package_name, sfx_stub, use_upx):
1248
from mozbuild.repackaging.installer import repackage_installer
1249
repackage_installer(
1250
topsrcdir=self.topsrcdir,
1251
tag=tag,
1252
setupexe=setupexe,
1253
package=package,
1254
output=output,
1255
package_name=package_name,
1256
sfx_stub=sfx_stub,
1257
use_upx=use_upx,
1258
)
1259
1260
@SubCommand('repackage', 'msi',
1261
description='Repackage into a MSI')
1262
@CommandArgument('--wsx', type=str, required=True,
1263
help='The wsx file used to build the installer')
1264
@CommandArgument('--version', type=str, required=True,
1265
help='The Firefox version used to create the installer')
1266
@CommandArgument('--locale', type=str, required=True,
1267
help='The locale of the installer')
1268
@CommandArgument('--arch', type=str, required=True,
1269
help='The archtecture you are building.')
1270
@CommandArgument('--setupexe', type=str, required=True,
1271
help='setup.exe installer')
1272
@CommandArgument('--candle', type=str, required=False,
1273
help='location of candle binary')
1274
@CommandArgument('--light', type=str, required=False,
1275
help='location of light binary')
1276
@CommandArgument('--output', '-o', type=str, required=True,
1277
help='Output filename')
1278
def repackage_msi(self, wsx, version, locale, arch, setupexe, candle, light, output):
1279
from mozbuild.repackaging.msi import repackage_msi
1280
repackage_msi(
1281
topsrcdir=self.topsrcdir,
1282
wsx=wsx,
1283
version=version,
1284
locale=locale,
1285
arch=arch,
1286
setupexe=setupexe,
1287
candle=candle,
1288
light=light,
1289
output=output,
1290
)
1291
1292
@SubCommand('repackage', 'mar',
1293
description='Repackage into complete MAR file')
1294
@CommandArgument('--input', '-i', type=str, required=True,
1295
help='Input filename')
1296
@CommandArgument('--mar', type=str, required=True,
1297
help='Mar binary path')
1298
@CommandArgument('--output', '-o', type=str, required=True,
1299
help='Output filename')
1300
@CommandArgument('--format', type=str, default='lzma',
1301
choices=('lzma', 'bz2'),
1302
help='Mar format')
1303
@CommandArgument('--arch', type=str, required=True,
1304
help='The archtecture you are building.')
1305
@CommandArgument('--mar-channel-id', type=str,
1306
help='Mar channel id')
1307
def repackage_mar(self, input, mar, output, format, arch, mar_channel_id):
1308
from mozbuild.repackaging.mar import repackage_mar
1309
repackage_mar(
1310
self.topsrcdir,
1311
input,
1312
mar,
1313
output,
1314
format,
1315
arch=arch,
1316
mar_channel_id=mar_channel_id,
1317
)
1318
1319
1320
@CommandProvider
1321
class Analyze(MachCommandBase):
1322
""" Get information about a file in the build graph """
1323
@Command('analyze', category='misc',
1324
description='Analyze the build graph.')
1325
def analyze(self):
1326
print("Usage: ./mach analyze [files|report] [args...]")
1327
1328
@SubCommand('analyze', 'files',
1329
description='Get incremental build cost for file(s) from the tup database.')
1330
@CommandArgument('--path', help='Path to tup db',
1331
default=None)
1332
@CommandArgument('files', nargs='*', help='Files to analyze')
1333
def analyze_files(self, path, files):
1334
from mozbuild.analyze.graph import Graph
1335
if path is None:
1336
path = mozpath.join(self.topsrcdir, '.tup', 'db')
1337
if os.path.isfile(path):
1338
g = Graph(path)
1339
g.file_summaries(files)
1340
g.close()
1341
else:
1342
res = 'Please make sure you have a local tup db *or* specify the location with --path.'
1343
print('Could not find a valid tup db in ' + path, res, sep='\n')
1344
return 1
1345
1346
@SubCommand('analyze', 'all',
1347
description='Get a report of files changed within the last n days and '
1348
'their corresponding build cost.')
1349
@CommandArgument('--days', '-d', type=int, default=14,
1350
help='Number of days to include in the report.')
1351
@CommandArgument('--format', default='pretty',
1352
choices=['pretty', 'csv', 'json', 'html'],
1353
help='Print or export data in the given format.')
1354
@CommandArgument('--limit', type=int, default=None,
1355
help='Get the top n most expensive files from the report.')
1356
@CommandArgument('--path', help='Path to cost_dict.gz',
1357
default=None)
1358
def analyze_report(self, days, format, limit, path):
1359
from mozbuild.analyze.hg import Report
1360
self._activate_virtualenv()
1361
try:
1362
self.virtualenv_manager.install_pip_package('tablib==0.12.1')
1363
except Exception:
1364
print('Could not install tablib via pip.')
1365
return 1
1366
if path is None:
1367
# go find tup db and make a cost_dict
1368
from mozbuild.analyze.graph import Graph
1369
db_path = mozpath.join(self.topsrcdir, '.tup', 'db')
1370
if os.path.isfile(db_path):
1371
g = Graph(db_path)
1372
r = Report(days, cost_dict=g.get_cost_dict())
1373
g.close()
1374
r.generate_output(format, limit, self.topobjdir)
1375
else:
1376
res = 'Please specify the location of cost_dict.gz with --path.'
1377
print('Could not find %s to make a cost dictionary.' % db_path, res, sep='\n')
1378
return 1
1379
else:
1380
# path to cost_dict.gz was specified
1381
if os.path.isfile(path):
1382
r = Report(days, path)
1383
r.generate_output(format, limit, self.topobjdir)
1384
else:
1385
res = 'Please specify the location of cost_dict.gz with --path.'
1386
print('Could not find cost_dict.gz at %s' % path, res, sep='\n')
1387
return 1
1388
1389
1390
@SettingsProvider
1391
class TelemetrySettings():
1392
config_settings = [
1393
('build.telemetry', 'boolean', """
1394
Enable submission of build system telemetry.
1395
""".strip(), False),
1396
]
1397
1398
1399
@CommandProvider
1400
class L10NCommands(MachCommandBase):
1401
@Command('package-multi-locale', category='post-build',
1402
description='Package a multi-locale version of the built product '
1403
'for distribution as an APK, DMG, etc.')
1404
@CommandArgument('--locales', metavar='LOCALES', nargs='+',
1405
required=True,
1406
help='List of locales to package, including "en-US"')
1407
@CommandArgument('--verbose', action='store_true',
1408
help='Log informative status messages.')
1409
def package_l10n(self, verbose=False, locales=[]):
1410
backends = self.substs['BUILD_BACKENDS']
1411
if 'RecursiveMake' not in backends:
1412
self.log(logging.ERROR, 'package-multi-locale', {'backends': backends},
1413
"Multi-locale packaging requires the full (non-artifact) "
1414
"'RecursiveMake' build backend; got {backends}.")
1415
return 1
1416
1417
if 'en-US' not in locales:
1418
self.log(logging.WARN, 'package-multi-locale', {'locales': locales},
1419
'List of locales does not include default locale "en-US": '
1420
'{locales}; adding "en-US"')
1421
locales.append('en-US')
1422
locales = list(sorted(locales))
1423
1424
append_env = {
1425
'MOZ_CHROME_MULTILOCALE': ' '.join(locales),
1426
}
1427
1428
for locale in locales:
1429
if locale == 'en-US':
1430
self.log(logging.INFO, 'package-multi-locale', {'locale': locale},
1431
'Skipping default locale {locale}')
1432
continue
1433
1434
self.log(logging.INFO, 'package-multi-locale', {'locale': locale},
1435
'Processing chrome Gecko resources for locale {locale}')
1436
self.run_process(
1437
[mozpath.join(self.topsrcdir, 'mach'), 'build', 'chrome-{}'.format(locale)],
1438
append_env=append_env,
1439
pass_thru=True,
1440
ensure_exit_code=True,
1441
cwd=mozpath.join(self.topsrcdir))
1442
1443
if self.substs['MOZ_BUILD_APP'] == 'mobile/android':
1444
self.log(logging.INFO, 'package-multi-locale', {},
1445
'Invoking `mach android assemble-app`')
1446
self.run_process(
1447
[mozpath.join(self.topsrcdir, 'mach'), 'android', 'assemble-app'],
1448
append_env=append_env,
1449
pass_thru=True,
1450
ensure_exit_code=True,
1451
cwd=mozpath.join(self.topsrcdir))
1452
1453
self.log(logging.INFO, 'package-multi-locale', {},
1454
'Invoking multi-locale `mach package`')
1455
self._run_make(
1456
directory=self.topobjdir,
1457
target=['package', 'AB_CD=multi'],
1458
append_env=append_env,
1459
pass_thru=True,
1460
ensure_exit_code=True)
1461
1462
if self.substs['MOZ_BUILD_APP'] == 'mobile/android':
1463
self.log(logging.INFO, 'package-multi-locale', {},
1464
'Invoking `mach android archive-geckoview`')
1465
self.run_process(
1466
[mozpath.join(self.topsrcdir, 'mach'), 'android',
1467
'archive-geckoview'.format(locale)],
1468
append_env=append_env,
1469
pass_thru=True,
1470
ensure_exit_code=True,
1471
cwd=mozpath.join(self.topsrcdir))
1472
1473
return 0