Monday, October 1, 2007

Making Good on My MPC Promise

Had to create a new project today - the first time since my promise. And yes, I did make it using MPC. It was really good to get this started.

It wasn't easy, for sure. There are a few decisions to be made and a few gotchas to avoid. With a little blogging, I should be able to help anybody who cares adopt this tool.

The Files.
MPC has four file types - I've used three of them. The file types are:

  • Project files *.mpc: The project files are used to represent build targets, including executables, static libraries, and dynamic libraries. When targeting a Visual Studio build environment, a .vcproj file will be created for each .mpc file.
  • Workspace files *.mwc: The workspace files have two purposes. First, it allows you to define where the tool should be looking for files. Second, it stands as a placeholder for a workspace for your project.
  • Project base files *.mpb: The design of MPC splits out project descriptions into two parts: how to build and to consume. The above .mpc file describes how to build a project, while the base files describe how to consume the project. This is a key distinction from Visual Studio, where the consumer of a.lib defines how to consume a.lib. These files are said to be base files and projects which use them are said to "inherit" from the base files. While this can truly be an inheritance relationship, I more often see the producer/consumer model.
  • Workspace base files *.mwb: Like project files, workspace files can use inheritance as well. The .mwb files provide this capability. This is the file type which I haven't used yet, but if you had multiple workspace files to create, this could be useful.
Decisions to be Made
When building MPC infrastructure, my philosophy is to set up MPC in order to (1) make consistency across projects easy to achieve, (2) make creating new projects as simple as possible. The first decision is where to keep your .mpb and .mwb files. Whatever the directory, indicate to the MPC scripts that it must be searched always. This is accomplished through the cmdline option in a .mwc file.

example.mwc:

workspace (example) {
cmdline += -include C:\mpc_base
}

This is a simple workspace, but shows how a simple tool with a rational set of defaults can be a powerful time saver. In this case a workspace named example will be created and include projects for all .mpc files that MPC encounters while running. Alternatively, individual .mpc files can be named. When MPC runs, if it cannot find a base file, it will also look in C:\mpc_base. Note the += operator - this is appending to the value, not overwriting, which can also be expressed.

Second, it is useful to determine how and where you will set defaults for projects. For example, do you have a common include point? Perhaps C:\svn\libs\include? Should all projects add this directory to their include path? To set defaults like this, define a project that all projects should inherit from. If your company is named xyz, I would name this project xyz_base:

xyz_base.mpb:

project (xyz_base) {
includes += C:\svn\libs\include
}
If you are groaning at the use of absolute paths, you can also indicate macros:

project (xyz_base) {
includes += $(LIB_ROOT)\include
}
This expression would not ask MPC to interpret the environment variable LIB_ROOT, but pass it on in the project files which inherit from it.

Creating Projects.
Now suppose my project consisted of a static library named calc and a executable named gui, which links in the calc library. I'll need to be able to build calc:

calc.mpc:

project (calc) : xyz_base {
staticname = calc
sharedname =
}
Calc derives from xyz, so it inherits the includes directive from it. No files are mentioned, so all source files in the .mpc file's directory will be included in the project build. Calc is defined as a static library, with output name calc. Extensions to the file name will be automatically added to indicate the debugginess and threaddedness of the library.

To consume calc, you will need a .mpb file:

calc.mpb:

project (calc) : xyz_base {
libs += calc
after += calc
libpaths += $(XYZ_ROOT)/calc
}
Here you see that consumers of calc need to link in the calc library if they are not already, and must be built after calc. Also, the consumers will know where to find the cal libraries, which would be the default, as they are not overridden in calc.mpc.

Finally, the consuming application:

gui.mpc:

project (gui) : calc, xyz_base {
exename = gui
}
When looking at this, it is easy to see how MPC can lead to productivity gains. Three lines and you have a project, working for both debug and release builds.

To build the projects, go to the project's root directory and run the mwc.pl script. This is the workspace creator, which traverses your directory structure and invokes mpc.pl for you.

perl \mpc\mwc.pl xyz.mwc -type vc8

assuming you have mpc installed in the \mpc directory. I invoked mwc, which invokes mpc.

Gotcha.
If you have not yet created your source files, MPC bails out. Older versions do this quietly, and newer versions give an error message about their being no targets. In any case, if project and workspace files are not created, this is the reason.

Its not trivial, but its well worth your time. More to come.

No comments: