Firefox Build System: Present and Future
Gregory Szorc
gps@mozilla.com
November 29, 2012
Agenda
Overview of existing build system
Performance evaluation of existing build system
Reinventing build configs
Better tools and automation through mach
Other build efforts
Q&A
What is the Build System
Build System is overloaded
Different meanings to different people
A subset of the tasks between deciding you want to build Firefox and running it.
Me
The Spectrum Perspective
Realization
I want to fix a bug.
I want to code a new feature.
I want to build it for myself.
Prepare Yourselves
Install build dependencies
Grab the Source
hg clone https://hg.mozilla.org/mozilla-central
git clone git://github.com/mozilla/mozilla-central.git
Configure the Build
Create a mozconfig file
Configure Mercurial and/or Git
Start a Build
./mach build
make -f client.mk
Wait
Relief
It's over!
Testing
mochitest
reftest
xpcshell
Marionette
...
Packaging
Windows installer
OS X dmg
Archives (for binaries and even test files)
Development Tasks
Create, submit, apply patches
Pull new tree revisions
Repeat
What I Consider the Build System
System bootstrapping
Build configuration (mozconfigs)
Actual building
Running tests
Packaging
Coordinated by a Driver
Dawn of time - 2012: client.mk
What Happens When You Type Build ?
1. mozconfig evaluated
a mozconfig
file is any shell script
mk_add_options
registers make statements to be executed by client.mk
ac_add_options
registers arguments to configure
Everything else sourced as part of configure
2. Verification and Configuration
autoconf2.13: configure.in
-> configure
configure
-> config.status
config.status: Computed Tree Config
3. Build Preparation
Input Output
mozilla-config.h.in mozilla-config.h
autoconf.mk.in autoconf.mk
Makefile.in 'sMakefile 's
*.gyp Makefile 's
4. Invoke the Build Backend
Example Makefile.in
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
DIRS = tests
CPPSRCS = nsFoo.cpp nsBar.cpp
LOCAL_INCLUDES += -I$(srcdir)/../build
EXTRA_COMPONENTS = toolkitplaces.manifest nsLivemarkService.js
ifdef MOZ_XUL
EXTRA_COMPONENTS += nsPlacesAutoComplete.js nsPlacesAutoComplete.manifest
endif
include $(topsrcdir)/config/rules.mk
The Header
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
The Body
DIRS = tests
CPPSRCS = nsFoo.cpp nsBar.cpp
LOCAL_INCLUDES += -I$(srcdir)/../build
EXTRA_COMPONENTS = toolkitplaces.manifest nsLivemarkService.js
ifdef MOZ_XUL
EXTRA_COMPONENTS += nsPlacesAutoComplete.js nsPlacesAutoComplete.manifest
endif
In theory you just define a bunch of variables
In practice lots of other things
The Footer
include $(topsrcdir)/config/rules.mk
Tier Traversal
Makefile Map (click me)
Makefile Tree Map
None
Tree Map: Makefile Count
Tree Map: C++ File Count
Slider: Makefile Count
Slider: C++ File Count
Slider: IDL File Count
On Recursive Make
~1160 Makefile for a browser build on OS X
Generally only 1 Makefile active at a time
Parallel execution (-j) occurs within a single Makefile
If Makefile has N < -j tasks, CPU starved
Can't build dependencies not defined in current Makefile
Benchmarks
*
Resource Usage - Effects of -j on Clobber Builds
Ubuntu 12.10 x64 on i7-2600K 12GB RAM, 7200 RPM HD, empty page cache
Build
Wall Time
CPU Time
Speedup
Overall CPU %
CPU Efficiency
Read (MB)
Write (MB)
I/O Wait Read (s?)
I/O Wait Write (s?)
-j1
66:10
63:52
1.00x
12.6
96.5%
382
4,898
247
3,321
-j2
38:16
68:44
1.73x
23.2
89.8%
372
4,856
296
2,614
-j3
28:10
70:17
2.35x
32.6
83.2%
370
4,869
317
3,124
-j4
23:39
73:14
2.80x
40.5
77.4%
401
4,861
339
2,920
-j5
23:02
85:38
2.87x
48.6
74.4%
372
4,862
331
3,464
-j6
22:19
97:27
2.96x
56.8
72.8%
372
4,839
342
3,329
-j7
21:49
108:51
3.03x
64.4
71.3%
355
4,828
287
4,000
-j8
21:40
120:35
3.05x
71.8
69.6%
356
4,865
300
4,413
-j9
21:44
121:46
3.04x
71.9
70.0%
355
4,859
306
4,090
-j10
21:25
120:39
3.09x
72.2
70.4%
366
4,832
377
3,781
Resource Usage - Effects of Caches on Clobber Builds
Ubuntu 12.10 x64 on i7-2600K 12GB RAM, 7200 RPM HD. All builds -j8.
Page Cache State
ccache State
Wall Time
Read Bytes (MB)
Write Bytes (MB)
I/O Wait Read (s)
I/O Wait Write (s)
Empty
Disabled
21:38
356
4,865
300
4,413
Populated
Disabled
20:45
0
4,842
0
4,200
Empty
Empty
23:59
361
7,982
425
8,097
Empty
Populated
9:34
3,185
4,904
3,401
7,591
Populated
Populated
4:04
1
4,317
2
11,921
Resource Usage - Linux Other
Build
Wall Time
CPU %
Read Bytes
Write Bytes
I/O Wait Read
I/O Wait Write
-j8
21:40
71.8
356
4,865
300
4,413
-j12 RAM disk
21:12
75.2
N/A
N/A
N/A
N/A
GCC 4.7 -j8
24:11
72.2
353
5,242
359
3,904
2GB RAM -j12
26:35
59.82
4,681MB
5,252MB
1,495
4,837
-j12 (Flush Page Cache Every 30s)
62:39
51.71
15,455MB
5,392MB
19,375
3,767
Resource Usage - Try Servers
All builds performed using -j12 without ccache enabled
Type
Host
Wall Time
CPU %
Read MB
Write MB
Read Wait
Write Wait
Linux64 #1
bld-centos6-hp-032
19:32
82.5
50
4,351
12
6,641
Linux64 #2
bld-centos6-hp-029
19:36
82.5
45
4,452
17
8,722
Linux64 #3
try-linux64-ec2-316
26:17
61.3
111
4,421
43
10,830
Linux64 #4
try-linux64-ec2-607
26:26
61.2
47
4,423
34
16,902
OS X 64 #1
bld-lion-r5-036
21:42
75.9
374
3,669
91
265
OS X 64 #2
bld-lion-r5-026
21:55
75.8
366
3,385
99
321
OS X 64 #3
bld-lion-r5-022
21:30
74.8
368
3,373
129
339
OS X 64 #4
bld-lion-r5-020
21:34
75.5
378
3,380
109
309
Resource Usage - 2011 MacBook Pro
2011 MacBook Pro, 8GB RAM, factory magnetic hard drive
Build
Wall Time
CPU %
Read Bytes
Write Bytes
I/O Wait Read
I/O Wait Write
-j4
26:33
46.48
738MB
3,674MB
223
2,023
-j12 #1
23:19
73.85
1000MB
3,710MB
270
1,998
-j12 #2
22:44
74.03
705MB
3,582MB
271
2,106
Pretty Graphs
Linux -j1
Linux -j2
Linux -j3
Linux -j4
Linux -j5
Linux -j6
Linux -j7
Linux -j8
Linux -j9
Linux -j10
Linux -j24 (Separate Machine)
Linux -j8 GCC 4.7
Linux -j8 Page Cache Populated
Linux -j8 ccache Empty, Page Cache Empty
Linux -j8 ccache Populated, Page Cache Empty
Linux -j8 ccache Populated, Page Cache Populated
Linux Periodic Page Cache Flushing
Try Linux #1
Try Linux #2
Try Linux #3 (EC2)
Try Linux #4 (EC2)
Try OS X #1
Try OS X #2
Try OS X #3
Try OS X #4
MBP -j12 Page Cache Populated
MBP -j4 Unknown
Try Linux ccache ec2 #1
Try Linux ccache ec2 #2
Try OS X ccache #1
Try OS X ccache #2
CPU
Disk Read Bytes
Disk Write Bytes
Disk Read Wait (ms)
Disk Write Wait (ms)
Disk Read Count
Disk Write Count
Build Observations
Builds use a lot of CPU!
Mostly CPU heavy except in known areas
... but we don't scale with # of cores in use!
Page cache is important
ccache adds significant I/O
GCC uses ~10% more CPU, per-process memory, and ~400MB objdir output
Hyperthreading: meh
Where Do the Resources Go?
63:52 i7-2600K core time to clobber
~400MB read I/O (srcdir + toolchain)
~5GB write I/O (objdir output)
~4.2GB in objdir (Clang), 4.4GB (GCC)
~4.6GB in page cache (Clang), 4.8GB (GCC)
~3.4GB in page cache just for ccache files!
~7.3GB in page cache when ccache enabled
1-2GB memory linking libXUL
30-300MB per compiler process
Why We Build Slow: Things in Your Control
Your mozconfig. Don't do debug builds unless you need them.
Your CPU is slow
Limited memory causes excessive disk I/O
Slow disk stalls work
GCC slower and larger memory consumer than Clang
ccache adds significant I/O load
Build System Recommendations
Modern CPU with 4+ physical cores (Core i5 Sandy Bridge or newer)
8+GB RAM. 12+ if using ccache or not a headless builder
A real SSD
Why We Build Slow: Things (Likely) Not in Your Control
C++ takes a lot of CPU to compile. We have a ton of C++
Build system not invoking enough commands in parallel
configure is single process (20-40s)
NSS -j1 (70s)
linking not performed in parallel (10-120s)
sqlite.c not compiled in parallel (20s)
Excessive rebuilding
New process overhead on Windows
pymake slower than GNU make
Dependency Hell
Thoughts on ccache
In the ideal build system, ccache isn't needed
... if you've compiled already, you don't invoke the compiler!
Fixing excessive rebuilding should marginalize ccache
... unless you build multiple trees (RelEng)
... unless you toggle between significantly differing trees (some devs)
Until then, watch out for I/O overhead of ccache
... consider disabling on magnetic hard drives
What Should We Optimize?
Eliminate the troughs (single core bottlenecks)
Minimize total CPU requirements
Precompiled headers
Smarter C++ template usage?
Other compiler hacks?
Prevent excessive rebuilding
Takeaways / Reflection Point
Overall build system structure and process
Building Firefox takes a lot of resources
... and we don't even use everything available!
More Make Non-Awesomeness
Hard to maintain
Simple things are hard. Hard things are really hard
... in all fairness, simple dependencies are easy!
mtime is is only out of date detection
Lots of foot guns
Difficult to bulk refactor build config
Recursive make won't scale into the future
Goals for a Better Build System
Build faster
Easier to maintain
Support multiple build backends (make, Tup, Visual Studio, Eclipse, Ninja, etc)
Foster new awesomeness
Curb the foot guns
All problems in computer science can be solved by another level of indirection.
Goodbye Makefile.in, Hello moz.build
DIRS += [
'external',
'component',
'bug656331_component',
'component_no_aslr',
]
if CONFIG.OS_ARCH == 'WINNT':
DIRS += ['windows']
if CONFIG.DEHYDRA_PATH:
DIRS += ['static-checker']
CPP_SOURCES = ['nsFoo.cpp', 'nsBar.cpp']
add_xpcom_manifest('MyXPCOMService.manifest', test_only=True)
Python With a Twist
moz.build files are actually Python files
But running in a highly specialized execution sandbox
Can't import Python modules
Can't do I/O
Python globals/built-ins must be whitelisted to be exposed
moz.build Example: Locals and Globals
local_variable = 'dir_a'
DIRS = [local_variable]
DIRS
is available after execution because it is UPPERCASE
local_variable
is local to the execution environment
DIRS
is defined in a central manifest, with all the other UPPERCASE variables
moz.build Example: Non-Available Built-ins
# Runtime error because import is not available
import os
# Runtime error because OrderedDict is not exposed to the sandbox.
d = OrderedDict()
# Runtime error because open() is not exposed to the sandbox.
fh = open('xpcshell.ini')
moz.build Example: Bad Variable Access
# Runtime error because DIRS expects a list, not a string.
DIRS = 'dir_a'
# Runtime error because FOO is not a known UPPERCASE variable.
FOO = 'bar'
# Runtime error because BAR is not a known UPPERCASE variable.
local_variable = BAR
ZOMG Useful Error Messages!
==============================
ERROR PROCESSING MOZBUILD FILE
==============================
The error occurred while processing the following file:
/home/gps/src/mozilla-central/services/moz.build
The error was triggered on line 6 of that file.
The underlying problem is an attempt to write a reserved UPPERCASE
variable that does not exist.
The variable write causing the error is:
FOO
Please change the file to not use this variable.
You can view the list of UPPERCASE variables by running:
./mach mozbuild-reference
So What Can We Do?
Not that much (at least if you expect a real Python environment)
And that's the point
moz.build Traversal
Traversal is like Makefile
s. Start at root file, recurse into children.
Execute each moz.build
file in isolation.
Output of individual moz.build
is dictionary of UPPERCASE variable values:
{
DIRS: ['dir_a', 'dir_b'],
CPP_SOURCES: ['nsFoo.cpp', 'nsBar.cpp'],
XPCOM_MANIFEST_FILES: {
MyXPCOMService.manifest: {test_only: True}
},
}
Processing the Output
Build config is a stream of dictionaries. 1 dict for each moz.build
file.
... massaged into Python classes
... class instances define specific aspect of the build config or tree. e.g. XPCOMComponent
, DirectoryTraversal
, CPPCompilation
Additional validation performed when converting to classes. Missing file detection, enforcement of higher-level rules and conventions, etc.
Ultimate output is a stream of class instances
Enter Consumers
from mozbuild.backend.base import BuildDataConsumer
class MyBuildDataConsumer(BuildDataConsumer):
"""Writes the set of all XPCOM component files to a text file."""
def __init__(self):
self.component_files = set()
def handle_xpcom_component(self, component):
"""Called for every XPCOMComponent instance in the output stream."""
self.component_files.add(component.path)
def finished(self):
"""Called when all emitted classes have been consumed."""
with open('all_xpcom_manifest_files.txt', 'w') as fh:
for path in self.component_files:
fh.write(path + '\n')
Build Backends
Just a consumer that facilitates building the tree!
A make build backend writes out Makefile
s.
A Visual Studio backend writes out Visual Studio .vsproj
files
A Tup backend writes out Tupfile
s
moz.build
files allow us to more easily create and experiment with build backends
New build backends will enable faster builds
Other Consumer Possibilities
Generate Clang compilation databases
Generate code autocomplete databases
One-off build backends (static analysis, address sanitizer)
Capture Valgrind or GDB macros, settings
Analyze C/C++ with Clang Python bindings
Build system dependency analysis
More Radical Ideas for moz.build Files
Replace xpcshell.ini
?
Replace packaging files like removed-files.in
, package-manifest.in
?
Record module owners, peers and/or reviewers for directories?
Define static analysis and auditing policies?
Define Bugzilla components?
Any metadata could be captured by moz.build
files!
moz.build Transition Plan
Identify Makefile functionality/variables to be ported
Remove variables from Makefile.in
, add to moz.build
Implement consumer that outputs a .mk
file
Recursive make build backend works as it does today. Data just comes from moz.build
files instead of Makefile.in
Repeat until everything defined in moz.build
files
Eventually implement alternate build backends (like non-recursive make or Tup)
Implications of Transition
For recursive make, not much
As we roll out new backends, make
will no longer build the tree
... you'll have to do everything through a more intelligent build driver (mach
)
moz.build When?
Patches written and preliminary r+
DIRS conversion first
In holding pattern because B2G backport concerns
Rinse and repeat with many flag days until Makefile.in
eliminated
Bug 784841 tracks
Concluding with Build Config
Essentially we're creating an API for the build/tree config
... and that API will enable awesomeness
Python Versions Today
Python is our tooling language
Currently require Python 2.6 to build the tree
Some test suites may still run on 2.5 (Talos is even 2.4!)
Version chaos makes authoring difficult, prevents progress
Difficult to write Python 3 compatible code
Lack of handy newer language features
Numerous bugs in older Pythons
Python Versions Tomorrow
Days to weeks: Python 2.7 to build, preferably 2.7.3
Weeks to months: Python 2.7 for ALL OF THE THINGS
Many months: Python 2.7/3.x dual compatibility (no rush)
A few years: Require Python 3
mozboot
Bootstrap your machine to build the tree
Execute a Python script and it just works on OS X and popular Linux distros, some BSD
May eventually replace MozillaBuild (Windows development environment)
Please test this and file bugs!
$ wget https://hg.mozilla.org/.../bootstrap.py && python bootstrap.py
Or if you have the tree:
$ ./mach bootstrap
[I] just started using mach today. I approve heartily. Way less voodoo.
mconley
Mach
... is not a build system!
is a generic driver to common developer tasks
All your make targets in one place
Goal: better user experience
Goal: enable better, faster, stronger (but not harder) tools and automation
Current state: young, but improving all the time
Advantages of mach
Python >> make and shell
Increased tools cohesion
Friendlier tools = happier developers = increased community engagement
Usability Comparison: Run Single xpcshell Test
What
make
mach
Command
$ SOLO_FILE=test_load_modules.js make -C services/sync/tests check-one
$ ./mach xpcshell-test services/common/tests/unit/test_load_modules.js
Required Knowledge
services/common/tests/unit/test_load_modules.js
is an xpcshell test file
make target to run a single xpcshell test is check-one
Must define SOLO_FILE
variable to run an individual test
Must execute make within the directory containing the XPCSHELL_TESTS
variable
services/common/tests/unit/test_load_modules.js
is an xpcshell test file
mach command to run an xpcshell test is xpcshell-test
Misc
No make help
Realistically requires docs on MDN
mach help
can educate!
Shell tab-complete test paths!
You can do everything with just the tree
Even Better Usability
$ mach test test_load_modules.js
mach
is in $PATH
and you can run it from anywhere
mach
can deduce file is an xpcshell test and it does the right thing
Requires some improvements to mach (I'm working on them)
Requires richer and more consistent metadata (moz.build
files!)
Ever Betterer Usability
$ mach test_load_modules.js
Argument is a file!
That file is an xpcshell test - let me invoke the xpcshell test runner!
...And I'll be sure the tree is built before I try it and will either build it for you or tell you what to do in order to build it
mach Opens Doors
Much more flexible than make
Deep integration with test runners
Unified logging bus
Forcing tools to be written as libraries, not standalone scripts
Conveniently brokers all interaction and can make intelligent decisions
mach Ideas
Configure Mercurial and/or Git with sane defaults
Easily submit [proper] patches to Bugzilla
Find reviewers for patch
Incorporate all those great tools that are floating around
Organize mach commands into categories
Shell completion support
Record and report metrics from building
... add stuff to moz.build
files, consume with mach
Adding a mach Command
from __future__ import print_function, unicode_literals
from mach.decorators import (
CommandArgument,
CommandProvider,
Command,
)
@CommandProvider
class MyCommandClass(object):
@Command('doit', help='Run it!')
@CommandArgument('--debug', '-d', action='store_true',
help='Do it in debug mode.')
def doit(self, debug=False):
print('I did it!')
See MDN for more.
Other In-Progress Improvements
Custom Python importer so tree isn't littered with .pyc files
Port driver logic from client.mk to Python
Eliminate client.mk
Potential Improvements and Ideas
Rewrite parts of configure in Python and/or parallelize
Replace mozconfigs with moz.build -like files?
mach config file?
Streaming logs from TBPL (possibly with metlog )
Structured logging from mach eliminates log parsing
Parallel test runners and/or unify test harness code
Invent MFBT acronym for moz.build files
Vision: Bootstrap
Linux, OS X, BSDs, etc
wget https://www.mozilla.org/build_firefox.py && python build_firefox.py
Windows
Download and run https://www.mozilla.org/build_firefox.vbs
All required system dependencies are installed.
Source tree is cloned automatically
Bootstrap info in tree and always in sync with the tree you are building (unlike a wiki)
Vision: Development Workflow
We activate a shell tied to a source tree and an output directory:
./mach activate obj-firefox
First time wizard guides you through
Automatically configure Git/Mercurial if needed
Help you create a tree configuration file (mozconfig?)
mach added to $PATH
mach build
# wait through faster build, complete with progress indicator
mach run
# Firefox starts!
Future Crazy Ideas
What about running a background service with an HTML interface?
web UI easier to target than terminal
background building of the tree
push-button interface
yo dawg, I hear you like to use Firefox to build Firefox
How Can I Help?
Learn Python
squash mach bugs
Write mach commands - incorporate tools into tree
Give test runners some API love
#build , #mach on irc.mozilla.org
It's Over!