Servo's documentation is good, but it doesnt cover much how to start working on Servo. You know, all these tiny things that sound obvious to everybody but not to you. I hope this document will help.
Add a comment below if there's anything that I got wrong or I should add.
- http://doc.servo.org/servo/index.html
- https://github.com/servo/servo/wiki
- http://rustbyexample.com
- https://doc.rust-lang.org
- mach help:
./mach --help
(see below) - servo options:
./mach run -- --help
(see below)
IRC channels (irc.mozilla.org):
- #servo
- #rust
- #cargo
https://lists.mozilla.org/listinfo/dev-servo
Building Servo is quite easy. Install the prerequisites described in the README file. Then type:
./mach build -d
Note: on Mac, you might run into a SSL issue while compiling. You'll find a solution to this problem here.
The -d
option means "debug build". You can also build with the -r
option which means "release build". Buidling with -d
will allow you to use a debugger (lldb). A -r
build is more performant. I personnaly always use the -d
.
You can use and build a release build and a debug build in parallel. I prefer to stick to one version. It's too easy to build version -r
and run -d
by mistake (leading to a lot of confusion).
The servo binary is located in target/debug/servo
(or target/release/servo
). You can directly run this binary. I recommend using ./mach
instead:
./mach run -d -- http://github.com
… is equivalent to:
./target/debug/servo http://github.com
mach
is a python utility that does plenty of things to make our life easier (build, run, run tests, udpate dependencies… see ./mach --help
). Beside editing files and git commands, everything else is done via mach
.
./mach run -d [mach options] -- [servo options]
The --
separates mach
options from servo
options. This is not required, but I recommend it. mach
and servo
have some options with the same name (--help
, --debug
), the --
makes it clear where options apply.
Don't skip this. Do it.
./mach --help # mach options
./mach run -- --help # servo options
Even if you have never seen any Rust code, it's not too hard to read Servo's code. But there are some basics things one must know:
- Match and Patterns
- Options
- Expression
- Traits
- That doesn't sound important, but be sure to understand how
println!()
works, especially the formatting traits
This won't be enough to do any serious work, but if you want to navigate the code and fix basic bugs, that should do it. It's a good starting point, and as you dig into Servo source code, you'll learn more.
For a more exhaustive documentation:
- doc.rust-lang.org
- rust by example (I really liked this one)
A Rust library is called a crate. Servo uses plenty of crates. These crates are dependencies. They are listed in files called Cargo.toml
. Servo is split in components and ports (see components
and ports
directories). Each has its own dependencies, each has its own Cargo.toml
file.
Cargo.toml
files list the dependencies. You can edit this file.
For example, components/net_traits/Cargo.toml
includes:
[dependencies.stb_image]
git = "https://github.com/servo/rust-stb-image"
But because rust-stb-image
API might change over time, it's not safe to compile against the HEAD
of rust-stb-image
. A Cargo.lock
file is a snapshot of a Cargo.toml
file which include a reference to an exact revision, ensuring everybody is alway compiling with the same configuration:
[[package]]
name = "stb_image"
source = "git+https://github.com/servo/rust-stb-image#f4c5380cd586bfe16326e05e2518aa044397894b"
This file should not be edited by hand. In a normal Rust project, to update the git revision, you would use cargo update -p stb_image
, but in Servo, use ./mach update-cargo -p stb_image
.
As explained above, Servo depends on a lot of libraries, which makes it very modular. While working on a bug in Servo, you'll often end up in one of its dependency. You will then want to compile your own version of the dependency (hey, maybe compiling against the HEAD of the library will fix the issue!).
For example, I'm trying to bring some cocoa events to Servo. The Servo window on Desktop is constructed with a library named Glutin. Glutin itself depends on a cocoa library named cocoa-rs. When building Servo, magically, all these dependencies are downloaded and built for you. But because I want to work on this cocoa event feature, I want Servo to use my own version of glutin and cocoa-rs.
This is how my projects are laid out:
~/servo-all/servo/
~/servo-all/cocoa-rs/
~/servo-all/glutin/
These are all git repositories.
To make it so that servo uses ~/servo-all/cocoa-rs/
and ~/servo-all/glutin/
, create a ~servo-all/.cargo/config
file:
$ cat ~/servo-all/.cargo/config
paths = ['glutin', 'cocoa-rs']
This will tell any cargo project to not use the online version of the dependency, but your local clone.
Alright. Time for the fun part.
Before starting the debugger right away, you might want get some information about what's happening, how, and when. Luckily, Servo comes with plenty of logs that will help us. Type these 2 commands:
./mach run -d -- --help
./mach run -d -- --debug help
I won't describe all the available options here, but this is what I usually use:
./mach run -d -- -i -y 1 -t 1 --debug dump-layer-tree /tmp/a.html
… to avoid using too many threads and make things easier to understand.
To that, I'll add:
./mach run -d -- /tmp/a.html -- -NSShowAllViews YES
…to outline Cocoa's views.
You can also enable some extra logging (warning: verbose!):
RUST_LOG="debug" ./mach run -d -- -i -y 1 -t 1 /tmp/a.html
Using RUST_LOG="debug"
is usually the very first thing I do when I have no idea where to look. Because this is very verbose, I usually combine these with ts
(moreutils
package (apt-get, brew)) to add timestamps and tee
to save the logs (while keeping them in the console):
RUST_LOG="debug" ./mach run -d -- -i -y 1 -t 1 /tmp/a.html 2>&1 | ts -s "%.S: " | tee /tmp/log.txt
I think there's a better and more granular way to enable these logs. Not sure how though.
Use RUST_BACKTRACE=1
to dump the backtrace when Servo panics.
You will want to add your own logs. Luckily, many structures implement the fmt::Debug
trait, so adding:
println!("foobar: {:?}", foobar)
usually just works. If it doesn't, maybe foobar's properties implement the right trait.
To run the debugger:
./mach -d --debug -- -y 1 -t 1 /tmp/a.html
From here, use:
(lldb) b a_servo_function # add a breakpoint
(lldb) run # run until breakpoint is reached
(lldb) bt # see backtrace
(lldb) thread list # see the mess
(lldb) next / step / …
See this lldb turorial.
To inspect variables, I like to use the gui
mode (use left/right to expand variables):
(lldb) gui
┌──<Variables>───────────────────────────────────────────────────────────────────────────┐
│ ◆─(&mut gfx::paint_task::PaintTask<Box<CompositorProxy>>) self = 0x000070000163a5b0 │
│ ├─◆─(msg::constellation_msg::PipelineId) id │
│ ├─◆─(url::Url) _url │
│ │ ├─◆─(collections::string::String) scheme │
│ │ │ └─◆─(collections::vec::Vec<u8>) vec │
│ │ ├─◆─(url::SchemeData) scheme_data │
│ │ ├─◆─(core::option::Option<collections::string::String>) query │
│ │ └─◆─(core::option::Option<collections::string::String>) fragment │
│ ├─◆─(std::sync::mpsc::Receiver<gfx::paint_task::LayoutToPaintMsg>) layout_to_paint_port│
│ ├─◆─(std::sync::mpsc::Receiver<gfx::paint_task::ChromeToPaintMsg>) chrome_to_paint_port│
└────────────────────────────────────────────────────────────────────────────────────────┘
If lldb crashes on certain lines involving the profile()
function, it's not just you. Comment out the profiling code, and only keep the inner function, and that should do it.
This is boring. But your PR won't get accepted without a test. Tests are located in the tests
directory. You'll see that there are a loooot of files in there (~100.000), so finding the proper location for your test is not always obvious.
First, look at the "Testing" section in ./mach --help
to understand the different test categories. You'll also find some update-*
commands. It's used to update the list of expected results.
I don't know much about tests. I usually grep for a similar feature in the test directory to see if there's a related test file I can use as a reference (or maybe add an extra test in it).
You should write the test in tests/wpt/mozilla/tests
or in tests/wpt/web-platform-tests
if it's something that doesn't depend on servo-only features.
Once you added your test, you want to update the list of tests and the list of expected results:
./mach test-wpt --manifest-update
Ask your questions on IRC irc://irc.mozilla.org#servo or to the mailing list.