Building Firefox under Eclipse/CDT, or I gotta have my identifier completion

Introduction

I've been using Eclipse as a C++ IDE for Mozilla development, which apparently puts me in a distinct minority among Mozilla developers.  Since I'm neither an Emacs nor a Vim user, I needed an alternative.  Frankly, Eclipse is not all that great as an editor, but having an IDE with halfway decent identifier completion and code browsing goes a long ways towards making up the difference.  It's not all that hard to set up an Eclipse project for mozilla-central (i.e. Firefox), but it's not exactly obvious either.  The purpose of this blog post is to explain one way to do so.

Disclaimer and Caveats

I'm not claiming that this is the optimal way to set up mozilla-central under Eclipse.  In fact, I'm not even claiming that it's right.  All I'm saying is that this approach is relatively simple and seems to actually work.  For expediency, these instructions make some assumptions.  First, they assume that you are developing under OS X.  I believe that they are easily adaptable to Linux and Windows+cygwin (or Windows+mozilla-build), but I don't know for sure.  Second, the instructions assume that you are already able to build mozilla-central from the command-line.  If you don't already know how to do that, then you don't want to start here.

Disclaimer #2

As a final disclaimer, this approach is relatively new to me.  I've mostly been using an older version of the CDT under Eclipse 3.0 and I'm not even going to mention what I had to do to get indexing to work properly.  This new approach is a lot simpler, and I'm reasonably confident that it will work well for sustained use.

Screenshots

I originally wrote these instructions in text-only form.  I've since added some marked-up screenshots for many of the steps.  These screenshots are attached at the end of the post.

Acknowledgements

And last, but not least, thanks to Mike "firetoad" Collins for trying out a slightly earlier version of these instructions.

Now the instructions.

Install Eclipse/CDT

  • Obviously you need Eclipse.  I will offer some advice, but I'm largely assuming that you can figure this part out for yourself.  These instructions assume that you are using Eclipse 3.5, better known as Galileo.  You need Eclipse with the CDT, the "C/C++ Development Tooling".  The easiest way to get this is to go to http://www.eclipse.org/downloads/ and download the pre-packaged Eclipse IDE for C/C++ Developers, which is what I've done.  I have an existing Eclipse installation, but rather than trying to upgrade, I've just got them side-by-side, which doesn't seem to be a problem as long as you set up separate workspaces for them.
  • By default Eclipse runs with a 256M heap, which is too small to reliably index the mozilla-central tree.  I've been running with 512M without any problem, although I've not really pushed it very hard yet.  There ought to be a way to configure the Eclipse heap size through the UI, but if there is, I haven't found it yet.  In the meantime, I've been launching it from the command-line, like so:
    • /Applications/eclipse-galileo/Eclipse-galileo.app/Contents/MacOS/eclipse -vmargs -Xmx512M &
  • Your path will differ, of course.  Also note that the path above is a little bit unusual because I've renamed the actual Eclipse application so it won't conflict with my other (older) Eclipse installation.
  • By default, Eclipse engages "Scalability Mode" for large files, which means it turns off most of the cool features.  I've increased the threshold to 15,000 lines (nsDocShell.cpp is about 11,000 lines of code, for example).  To change this setting, choose Eclipse >> Preferences, then type "scalability" into the search box.

Create a new project

  • Start Eclipse (but see the note above about making sure you're running it with enough memory).
  • Ctrl-click in the Project Explorer, choose New >> C++ Project
    • this will open the C++ Project dialog
  • In the C++ Project dialog,
    • In the Project name text field, enter the project name
      • I'm using firefox-eclipse
    • Uncheck Use default location
    • In the Location text field, enter the full path for the project source code.
      • I'm using /Users/cbartley/Dev/firefox-eclipse/src
    • Under Project type, choose Makefile project >> Empty Project
      • Note that other project options can cause Eclipse to create a default makefile, which you don't want.
    • Click Finish
  • You should now see the project firefox-eclipse in the Project Explorer.

Configure the project

  • Ctrl-click on firefox-eclipse in the Project Explorer, and choose Properties.
    • This will bring up the Properties for firefox-eclipse dialog.
  • In the Properties for firefox-eclipse dialog,
    • Select the Resource panel
      • Note the path after Location in the Resource panel.
        • This is the same path you entered a few steps above, but if you're like me, you've probably forgotten it already.
    • Select the C/C++ Build panel
      • Select Builder Settings inside the C/C++ Build panel
        • Uncheck Use default build command
        • In the Build command text field, enter:
          • bash -l -c 'make -f client.mk $0 $1' -b
            • this hairy looking command invokes a bash shell which in turn invokes the actual make command.  The dash-small-L option tells it to behave like a login shell.  This sidesteps some environment problems that I've run into when running make directly from Eclipse.  I won't elaborate on the $0, $1, or -b, except to tell you to be careful that you put the quotes in the right place!
        • In the Build directory text field, enter the path to the project source
          • I'm using /Users/cbartley/Dev/firefox-eclipse/src
            • Note that this the same path found in the Location text field on the Resource panel. I usually just Cmd-C copy it from there any more, to reduce the possibility of a typo
      • Select the Behavior tab inside the C/C++ Build panel
        • In the Build (Incremental build) text field, delete all and just leave the field empty.
    • Expand the C/C++ Build tab to show the tabs for its subpanels.
      • Select the Settings sub-panel
        • Select the Mach-O parser under Binary Parsers
    • Expand the C/C++ General tab to show the tabs for its sub-panels
      • Select the Index sub-panel
        • Check Enable project specific settings
        • Under Select indexer
          • Choose Full C/C++ Indexer (complete parse)
          • Check Index unused headers as C++ files
    • Click Apply
  • The project is now set up, but there's no source code yet.

Get the Source Code

  • Eclipse has created the directory for the project, but it's empty.  I've created my project at /Users/cbartley/Dev/firefox-eclipse/src.  Following standard convention, I want to check the mozilla-central project out to a directory named src, the same one as above, in fact.  The problem is that Mercurial complains if the directory already exists.  Checking the source out first doesn't help, because then Eclipse complains that the directory already exists.  I'm going to check mozilla-central out to a different location and then manually merge them.  Note that Eclipse has created some files inside the src directory that we want to preserve.
  • cd into the project directory
    • I do cd /Users/cbartley/Dev/firefox-eclipse/src
  • cd out one level
    • cd ..
      • I'm now in /Users/cbartley/Dev/firefox-eclipse
  • Now type the following commands
    • hg clone http://hg.mozilla.org/mozilla-central src-aside  # get the source code
    • mv src-aside/.h* src                                      # move mercurial files into src
    • mv src-aside/* src                                        # move regular files into src
    • rmdir src-aside                                           # remove the now superfluous src-aside directory

Set up the .mozconfig file

  • Create a basic .mozconfig file for the project; I'm assuming you've already got a usable one some place.
    • For example, I do cp ../mozilla/src/.mozconfig src
  • Make sure the "-s" option is NOT on for make
    • Normally make displays the complete command line for building each file.  When invoked with the "-s" option, however, it only displays the name of the file being built.  This is a problem for building under Eclipse, since Eclipse parses the build output to figure out where the header files are.
    • In my existing .mozconfig, I have the following line:
      • mk_add_options MOZ_MAKE_FLAGS="-s -j4"        # before
    •     I delete the "-s", leaving
      • mk_add_options MOZ_MAKE_FLAGS="-j4"           # after

    Build the project under Eclipse

    • Make sure that firefox-eclipse is selected in the Project Explorer.
    • Select the Console tab in the bottom pane so you can see the build output
    • Select Project >> Build Project from the menu
    • Wait

    Build the project under Eclipse, again

    • We want to do Clean rebuild under Eclipse; This extra step seems to be necessary for Eclipse to find the IDL-generated header files.  Don't ask me why.
    • Make sure that firefox-eclipse is selected in the Project Explorer
    • Select the Console tab in the bottom pane
    • Select Project >> Clean... from the menu
      • Make sure that Start a build immediately is checked
      • Click OK
    • Wait

    Make sure indexing is started

    • After the build completes Eclipse should start Indexing automatically.  A progress indicator should appear in the status bar on the lower right.
    • If indexing hasn't started, you can invoke it manually by:
      • Ctrl-click on firefox-eclipse in the Project Explorer, and choose Index >> Rebuild
    • Indexing takes something like four hours on my machine.  Eclipse remains usable in the meantime, you just won't have access to the more advanced code-completion and browsing features.

    After Indexing is completed

    • Cmd-Shift-R is kind of like the Awesome Bar for source files.
    • Cmd-Shift-T is kind of like the Awesome Bar for types, functions, etc.
    • Ctrl-Space invokes identifier completion.
    • Just typing identifier. or identifier-> will show a list of member variables and functions if you pause for a second.
    • F3 will take you to the declaration of an identifier
    • F3 over a #include will open the header file.
    • Hovering over a macro will show you its expansion.
    • Actually, many of the features will work in full (e.g. Cmd-Shift-R) or in part (e.g. identifier completion) before indexing is completed.

    10 responses
    I can't believe that anyone has been write a comment, this tutorial is amazing for two things, first compile such a big project in eclipse and second, it explains very well, how to compile with external sources and makefiles. btw, the command

    bash -l -c 'make -f GNUmakefile ' -b

    is really useful, Thank you.

    @David Jaime:

    I'm glad you found the post useful!

    Also, I should probably use this opportunity to explain what I'm doing with that build command. By default, Eclipse takes the build command and sticks either an "all" or a "clean" after it. So if your custom build command is "build_command", then Eclipse will actually run either:

    build_command all # for Project >> Build Project
    build_command clean # for the "clean" part of Project >> Clean...

    However, for Firefox it works better if instead of having Eclipse run the build command directly, we rather have Eclipse run a bash shell which in turn runs the build command. We not only run the bash shell, but we also tell it to run the shell like a login shell -- the "-l" (dash-little-L) parameter. The second parameter is the "-c" which tells bash to execute the command in the following string.

    So we end up with a command more like:

    bash -l -c 'build_command'

    Except that doesn't work quite right:

    bash -l -c 'build_command' all # Wrong! "all" is passed to bash, not build_command
    bash -l -c 'build_command' clean # Wrong! "clean" is passed to bash, not build_command

    So, instead, we want to do:

    bash -l -c 'build_command $0'

    which, after Eclipse builds the final command, looks like:

    bash -l -c 'build_command $0' all

    The bash shell passes everything after the command to the command-string as arguments, so it ends up executing the command

    'build_command all'

    which is what we want.

    Except, wait, there's a complication! The mozilla makefile doesn't have an "all" target! To do a build, you want to invoke make without any arguments at all. You might think this is no problem, the $0 in the command-string will get expanded to the empty string, but you'd be wrong!

    Instead,

    bash -l -c 'build_command $0' # that's right, no argument after the command-string

    causes bash to expand the command-string to

    build_command bash # D'oh! That's not what we want at all.

    This is where "-b" comes in. Basically, "-b" is a no-op. Its only function here is to sidestep unfortunate bash behavior when there's no arguments following the command-string.

    So we have a build command that looks like:

    bash -l -c 'build_command $0 $1' -b

    which Eclipse will expand into:

    bash -l -c 'build_command $0' -b # Project >> Build Project
    or
    bash -l -c 'build_command $0' -b clean # Project >> Clean... (1st step)

    These commands result in the following statements being executed by bash:

    build_command -b
    or
    build_command -b clean

    For Firefox, the actual build_command is "make -f client.mk", giving us the:

    bash -l -c 'make -f client.mk $0 $1' -b

    This is needless to say pretty ugly and contorted, and, obviously, hard to explain. But if there's a better way, I haven't figured it out yet.

    emacs does identifier completion out of the box (esc-forward slash) for any language, based on the content of all open buffers, and it is a GREAT editor:)

    (and you wouldn't have to clickity-click all of those menus, dialogs etc. Frankly, you have made me fear eclipse)

    Does debugging work?
    @Henri:

    I was never able to get debugging to work right with Firefox. The problem is that on the Mac at least, every time you launch Firefox it runs twice. I always surmised that this is to support connecting with an already running instance, but I never looked into it in detail. Anyway, the problem is that Eclipse would connect to Firefox properly on the first run, but had no way of re-connecting when the application re-launched.

    I think there's probably some work-around for this problem, but it will probably require help from someone who understands what's going on at startup.

    Set environment variables MOZ_NO_REMOTE and NO_EM_RESTART to 1.
    I set NO_EM_RESTART to 1 in XCode to work around the restart problem when debugging.
    Thanks for the great tutorial, it really helped me out!

    I still got an error though: when running the Firefox it complained libxul.so could not be found. So I had to set the environment variable LD_LIBRARY_PATH in the Run Configuration to the full path of the folder where libxul.so can be found (the folder where firefox-bin is too). If this is necessary in general, you could add it to the tutorial?

    And when debugging, there's also a warning .gdbinit could not be found. This can be solved by creating an empty file called .gdbinit, but since the debugging still seems to work, I'm leaving it this way :)

    Thanks again for the tutorial!

    Excellent stuff. Worked for me on x86_64 Linux with essentially no
    modifications. Differences:

    I had to set the heap size to 1G in order for indexing to complete.
    "-vmargs -Xmx1024M"

    I sidestepped all the hoop jumping listed at "Get the Source Code".
    Instead I told Eclipse to use as the source tree (via the C++ project
    dialogue, "Location" field), a pre-existing Fx tree that I had built.
    Eclipse didn't complain about this, and successfully indexed it all,
    which took about an hour on a 2.4 Ghz Core 2.

    This is with
    "Eclipse IDE for C/C++ Developers / Build id: 20100218-1602"

    Julian, what's your .gdbinit like?