Constructing a Medium-sized Project with Jam

Introduction

Jam is a replacement for make(1). See here for more details.

I'm attempting to use jam to build our Windows code, but in order to keep the scale of this discussion down, I'm just going to explain the aspects of our codebase that caused difficulty, and then I'm going to fake them up in a mock build tree. This will enable me to explain things in isolation.

Introduction

Tutorial

I've written this article in the form of a tutorial, because I think better when trying to formulate reproducible instructions for this kind of thing.

Other Stuff

Miscellaneous

This is some stuff I wrote earlier. I'm going to try to factor it into the main discussion, but for now, you can find it here:

Building an MFC Application with Jam: Introduction

Introduction

Since the majority of our Windows applications are written using MFC, it's a useful experiment to get jam to build a freshly-generated MFC application. Once we've got this working, we can turn our attention to the things that make our build process different.

I've attempted to break down the process of getting an MFC application to build into discrete chunks. They're not particularly self-contained at the moment, but they attempt to describe one problem (and hopefully its solution) each:
You can find the resulting source code from this example here.


Using AppWizard to generate the application

Run up Visual C++, and generate a new "MFC AppWizard (exe)" project. I'm going to be replicating parts of our build system around it, so I called it mfc_exe and put it in s:\jam-test\apps\mfc_exe. The default settings for the application are fine, so just keep clicking "Next".

When you've got your application generated, get Visual C++ to build it, just for sanity's sake.

Creating the Jamfile

The obvious thing to do at this point is to put the names of the .cpp files into a Jamfile, like this:

Main mfc_exe : ChildFrm.cpp MainFrm.cpp mfc_exe.cpp mfc_exeDoc.cpp mfc_exeView.cpp StdAfx.cpp ;

...and then to try building it, using -d2 to see what's going on. Not surprisingly, it doesn't work:

Link mfc_exe.exe
nafxcw.lib(afxmem.obj) : error LNK2005: "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z) already def
ined in libc.lib(new.obj)
nafxcw.lib(afxmem.obj) : error LNK2005: "void __cdecl operator delete(void *)" (??3@YAXPAX@Z) already defined
in libc.lib(delete.obj)
libc.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
nafxcw.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __endthreadex
nafxcw.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __beginthreadex
mfc_exe.exe : fatal error LNK1120: 3 unresolved externals

link /nologo    /out:mfc_exe.exe   ChildFrm.obj MainFrm.obj mfc_exe.obj mfc_exeDoc.obj mfc_exeView.obj StdAfx.
obj   P:\VStudio\VC98\lib\advapi32.lib P:\VStudio\VC98\lib\libc.lib P:\VStudio\VC98\lib\oldnames.lib P:\VStudi
o\VC98\lib\kernel32.lib

...failed Link mfc_exe.exe ...

Building an MFC Application with Jam: Compiler Flags


Essentially, jam's invocation of Visual C++ isn't using the multithreaded libraries (the __beginthreadex unresolved external), and there's something else wrong with the implicit link instructions.

Let's take a look at the compiler settings and see what's different. Jam is invoking the compiler like this:

cl /nologo /c /FoChildFrm.obj /IP:\VStudio\VC98\include /TpChildFrm.cpp

Developer Studio is invoking the compiler like this (taken from the .plg file):

cl /nologo /MDd /W3 /Gm /GX /ZI /Od
    /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_AFXDLL" /D "_MBCS"
    /Fp"Debug/mfc_exe.pch" /Yu"stdafx.h" /Fo"Debug/" /Fd"Debug/"
    /FD /GZ   /c ChildFrm.cpp

Looking at the results of cl /? tells us the following:

Switch

Description

 

Switch

Description

/nologo

Don't output a version banner

 

/MDd

Link with the MSVCRTD.lib file.

/W3

Set the warning level

 

/Gm

Enable minimal rebuild

/GX

Enable exceptions

 

/ZI

Enable Edit and Continue debug info

/Od

Disable optimisations (debug)

 

/D

Define some stuff

/Fp

Name precompiled header file

 

/Yu

Use .PCH file

/Fo

Name object file

 

/Fd

Name .PDB file

/FD

Generate file dependencies

 

/GZ

Enable runtime debug checks

/c

Don't link; just compile

 

/I

Name include directory

/Tp

Treat the file as C++

 

 

 

Obviously, we'd like the warnings and debug information. We'll probably need the /D switches, as well. Diagnosing the error messages above suggests that we'll need /MDd. For now we can ignore the precompiled header stuff, and we'll come back to the file naming things.

That leaves us with a file looking like this:

C++FLAGS += /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_AFXDLL" /D "_MBCS" ;

Main mfc_exe : ChildFrm.cpp MainFrm.cpp mfc_exe.cpp mfc_exeDoc.cpp mfc_exeView.cpp StdAfx.cpp ;

...and the following results:

LINK : warning LNK4098: defaultlib "msvcrtd.lib" conflicts with use of other libs; use /NODEFAULTLIB:library
libc.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
mfc_exe.exe : fatal error LNK1120: 1 unresolved externals

...which looks like it's caused by Jambase adding libraries we don't want.

Building an MFC Application with Jam: Link Libraries


We'll copy Jambase from the distribution directory and put it in into S:\jam-test, which is where it'll end up in our final build system. Looking through the file reveals the following:

    else if $(MSVCNT) {
    ECHO "Compiler is Microsoft Visual C++" ;

    AR      ?= lib ;
    AS      ?= masm386 ;
    CC      ?= cl /nologo ;
    CCFLAGS     ?= "" ;
    C++     ?= $(CC) ;
    C++FLAGS    ?= $(CCFLAGS) ;
    LINK        ?= link /nologo ;
    LINKFLAGS   ?= "" ;
    LINKLIBS    ?= $(MSVCNT)\\lib\\advapi32.lib
                $(MSVCNT)\\lib\\libc.lib
                $(MSVCNT)\\lib\\oldnames.lib
                $(MSVCNT)\\lib\\kernel32.lib ;
    OPTIM       ?= "" ;
    STDHDRS     ?= $(MSVCNT)\\include ;
    UNDEFFLAG   ?= "/u _" ;
    }

We'll take out the LINKLIBS line, leaving it looking like this:

    LINKLIBS    ?= "" ;

Remembering to invoke jam as: jam -f /jam-test/Jambase leaves us with this:

LINK : fatal error LNK1561: entry point must be defined

Building an MFC Application with Jam: Entry Point


Jam is invoking link like this:

link /nologo /out:mfc_exe.exe ChildFrm.obj MainFrm.obj mfc_exe.obj mfc_exeDoc.obj mfc_exeView.obj
 StdAfx.obj

Developer Studio is invoking link with a response file containing the following:

/nologo /subsystem:windows /incremental:yes /pdb:"Debug/mfc_exe.pdb"
/debug /machine:I386 /out:"Debug/mfc_exe.exe" /pdbtype:sept
.\Debug\mfc_exe.obj
.\Debug\StdAfx.obj
.\Debug\MainFrm.obj
.\Debug\ChildFrm.obj
.\Debug\mfc_exeDoc.obj
.\Debug\mfc_exeView.obj
.\Debug\mfc_exe.res

We'll add some of the more interesting switches to our Jamfile, and see what happens:

C++FLAGS += /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_AFXDLL" /D "_MBCS" ;
LINKFLAGS += /subsystem:windows /incremental:yes /debug /machine:I386 ;

Main mfc_exe : ChildFrm.cpp MainFrm.cpp mfc_exe.cpp mfc_exeDoc.cpp mfc_exeView.cpp StdAfx.cpp ;

It builds! Does it run? It does. Unfortunately, it bails out immediately. It should have brought up a window of some kind. Perhaps if we run it in the debugger?

Building an MFC Application with Jam: Resource Files


Running our newly-built MFC application in the debugger reveals the following smoking gun in the output window:

Warning: no document names in string for template #129.
Warning: no document names in string for template #129.
Warning: no shared menu for document template #129.
Warning: no document names in string for template #129.
Warning: no shared menu for document template #129.
Warning: failed to load menu for CFrameWnd.

Looks to me like it's not linking in the resource files. We'd better sort that out now. What we'd like to do is simply add the .rc file to the list of source files in the Jamfile, and have it magically work. However, when we try that, we get:

Unknown suffix on mfc_exe.rc - see UserObject rule in Jamfile(5)

Looking in the Jambase.html file included in the distribution, we find a section that suggests overriding the UserObject rule in order to tell jam about .rc files. It says to put it in Jamrules, but since we don't have one, we'll put it in our Jamfile for the time being:

RC ?= rc ;

C++FLAGS += /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_AFXDLL" /D "_MBCS" ;
LINKFLAGS += /subsystem:windows /incremental:yes /debug /machine:I386 ;
RCFLAGS += /d "_DEBUG" /d "_AFXDLL" ;

rule UserObject {
    switch $(>) {
    case *.rc   : ResourceCompiler $(<) : $(>) ;
    case *      : EXIT "Unknown suffix on" $(>) "- see UserObject rule in Jamfile(5)." ;
    }
}

rule ResourceCompiler {
    DEPENDS $(<) : $(>) ;
    Clean clean : $(<) ;
}

actions ResourceCompiler {
    $(RC) /l 0x809 /fo $(<) $(RCFLAGS) $(>)
}

Main mfc_exe : ChildFrm.cpp MainFrm.cpp mfc_exe.cpp mfc_exeDoc.cpp mfc_exeView.cpp StdAfx.cpp mfc_exe.rc ;

Unfortunately, this approach is flawed: The generated file is called .obj, rather than .res. This causes a major problem in AppWizard-generated MFC applications, because the .rc file has the same base name as the main application source file, and they're both configured to generate a file with the .obj suffix.

Thus, one of the build steps will overwrite the output from the other. This is a bad thing.

It looks like we'll have to give up on our ideal of simply adding the filename to the list of .cpp files -- at least until we can figure out the magic in the Main rule in Jambase. Since the odds of that happening are slim, we'll cast our net a little further afield.

Building an MFC Application with Jam: Resource Files (2)


One of the mailing list participants, Chris Antos, forwarded me a copy of his Jambase file a little while ago. It contains all sorts of useful rules, but I'm not entirely sure what some of it does yet.

Lifting the relevant sections, and simplifying them results in the following:

# Resource prog : resources.rc ;
rule Resource {
    # _s is the source (.rc) file.
    # _r is the target (.res) file.
    # _e is the executable (.exe) file.

    local _s = [ FGristFiles $(>) ] ;
    local _r = $(_s:S=.res) ;       # Chris Antos has some stuff here to set grist ...:G=)
    local _e = [ FAppendSuffix $(<) : $(SUFEXE) ] ;

    # Make the executable depend on the .res file, and
    # make the .res file depend on the .rc file.
    DEPENDS $(_e) : $(_r) ;
    DEPENDS $(_r) : $(_s) ;

    LOCATE on $(_r) = $(LOCATE_TARGET) ;
    SEARCH on $(_s) = $(SEARCH_SOURCE) ;
    NEEDLIBS on $(_e) += $(_r) ;

    # TODO: Header file scanning.

    Rc $(_r) : $(_s) ;

    Clean clean : $(<) ;
}

actions Rc {
    $(RC) $(RCFLAGS) /I$(HDRS) /I$(RCHDRS) /Fo $(<) $(>)
}

This works fine. If we build the program, we get a working executable!

Building an MFC Application with Jam: What Next?


We've just successfully built an MFC application using jam. There are a couple of things that we still need to consider:

I'll come back to this later and deal with some of the above points.

Building Jam on Windows NT

2004/01/09 - 10:28am | Jam

First, you'll need to download jam version 2.3.2 from ftp.perforce.com in zip format or as a tar.gz

Unpack the contents into a new directory. Edit the Makefile, and uncomment the relevant lines under the comment:

# NT (with Microsoft compiler)

Tell it where your VC++ libraries live. This might not work if you've installed VC++ in the default location (spaces in the path names, see). I haven't, so I just:

set MSVCNT=P:\VStudio\VC98

Build it:

nmake -f Makefile

It's a bootstrap build process. It uses make (or nmake) to build jam, and then runs jam to build itself again. You should have a jam.exe file in the bin.ntx86 directory. Copy it somewhere sensible.

Linker Command Line Length

2004/01/09 - 10:37am | Jam

Jam imposes a hard limit of 996 characters on command lines when built on NT. This limit is higher for other operating systems, and can actually be raised to around 10Kb on Windows 2000. However, it's still not high enough for some link actions.

We'd like, therefore, to place the linker actions in a response file, and invoke the linker with that instead. Replace the "actions Link..." clause in the NT-specific section of Jambase with this:

	rule Link 	{
		MODE on $(<) = $(EXEMODE) ;
		LINKFLAGS on $(<) += $(LINKFLAGS) $(SUBDIRLINKFLAGS) ;
		Chmod $(<) ;

		local _i ;
		StartLink $(<) : $(>) ;
		for _i in $(>) {
			LinkItems $(<) : $(_i) ;
		}
		FinishLink $(<) : $(>) ;
	}

	rule StartLink	{
		Clean clean : $(<:S=.rsp) ;
	}

	actions quietly Link 	{}

	# We have to touch the file first,
	# or the delete will fail, stopping the build.
	actions quietly StartLink	{
		$(TOUCH) $(<:S=.rsp)
		$(RM) $(<:S=.rsp)
	}

	actions together piecemeal quietly LinkItems	{
		ECHO $(>) >> $(<:S=.rsp)
	}

	actions FinishLink bind NEEDLIBS	{
		$(LINK) $(LINKFLAGS) /out:$(<) $(UNDEFS) @$(<:S=.rsp) $(NEEDLIBS) $(LINKLIBS)
	}

Remember to set TOUCH to something sensible.

Conflicting 'lib' target

2004/01/09 - 10:39am | Jam

empeg's source tree has a directory called lib, in which the core libraries used by all of our products live. Unfortunately, this conflicts with one of the included pseudo-targets that jam uses.

The fix is relatively simple. You need to edit the included Jambase file, and rename every mention of lib to something else, e.g. libs.

Lines 552-554:

DEPENDS all : shell files lib exe obj ;
DEPENDS all shell files lib exe obj : first ;
NOTFILE all first shell files lib exe obj dirs clean uninstall ;

...change this to...

DEPENDS all : shell files libs exe obj ;
DEPENDS all shell files libs exe obj : first ;
NOTFILE all first shell files libs exe obj dirs clean uninstall ;

Lines 827-830:

	else {
	    DEPENDS lib : $(_l) ;
	}

...change this to...

	else {
	    DEPENDS libs : $(_l) ;
	}

You should probably also change the comments at lines 41-52 that refer to 'lib'.

This then requires that you use your new Jambase instead of the included one. You have two choices:

  1. Use the -f switch to jam to tell jam where to find an alternate Jambase file. This is the simplest, but requires more typing.
  2. Recompile jam, including the new file. This is relatively simple. Copy the edited Jambase into the source directory for jam, and rebuild it.

Jam - Separate Release/Debug Target Directories

2002/02/01 - 12:05pm | Jam

By default, AppWizard-generated applications use separate directories for the output of the different debug and release builds. We'd like to replicate that functionality.

This is controlled by the LOCATE_TARGET variable. It's initialised by the SubDir rule to be the same as the SEARCH_SOURCE variable, unless ALL_LOCATE_TARGET is set.

However, if we set ALL_LOCATE_TARGET to, e.g. Debug in our Jamrules file, or using the -s switch to jam, the object files and targets are all built in exactly the same directory. It's not relative to the subdirectory. This could cause confusion if directories generate object files with the same names.

The comments in Jambase state that the LOCATE_TARGET variable should be set after invoking the SubDir rule, if required. This is a pain. We much prefer the following change to the SubDir rule:

       # directory should not hold object files, LOCATE_TARGET can
       # subsequently be redefined.

       local path = [ FDirName $(SUBDIR) $(TARGET_PREFIX) ] ;

       SEARCH_SOURCE = $(SUBDIR) ;
       LOCATE_SOURCE = $(ALL_LOCATE_TARGET) $(path) ;
       LOCATE_TARGET = $(ALL_LOCATE_TARGET) $(path) ;
       SOURCE_GRIST = $(path) ;

       # Reset per-directory ccflags, hdrs

This allows us to place a rule like the following in our Jamrules file:

if ! $(DEBUG) {
	ECHO Assuming DEBUG=1 ;
	DEBUG = 1 ;
}

if $(DEBUG) = 0 {
	TARGET_PREFIX = Release ;
} else {
	TARGET_PREFIX = Debug ;
}

Jam - Resource File Dependencies

2004/01/16 - 10:14am | Jam

Introduction

Our MFC application has a resource script. This resource script suffers from a minor problem: It's not dependency-scanned. If we edit any file included by it -- for example the .rc2 file, it's not rebuilt properly. We need to add the following to our Resource rule:

	NEEDLIBS on $(_e) += $(_r) ;

	# .rc files have #includes, but this limits the dependency search to
	# the .rc's directory and the SubDirHdrs for this directory.

	HDRS on $(_r) = $(HDRS) $(SEARCH_SOURCE) $(SUBDIRHDRS) ;

	HDRRULE on $(_s) = HdrRule ;
	HDRSCAN on $(_s) = $(HDRPATTERN) ;
	HDRSEARCH on $(_s) = $(SEARCH_SOURCE) $(SUBDIRHDRS) ;
	HDRGRIST on $(_s) = $(HDRGRIST) ;

	Rc $(_r) : $(_s) ;

Source is here.

Building a DLL with Jam: Introduction

Introduction

empeg's code base contains several Windows DLL projects. I'm going to investigate getting these built with jam the same way as I did for the MFC applications -- by building a simple project with the AppWizard, and getting the project built.

You can find the resulting source code from this example here.


Using AppWizard to generate the DLL

As I've already stated, our DLLs don't use MFC, so we'll use the AppWizard to generate a "Win32 Dynamic-Link Library". To mirror the structure used at empeg, we'll create the DLL in the S:\jam-test\lib\ directory, and we'll call it win32_dll.

The AppWizard wants to know what type of DLL we're creating, so we'll create one that exports some symbols. We'll build it with Developer Studio, just to check.

Creating the Jamfile

The obvious thing to start with is a Jamfile that looks like this:

Main win32_dll : StdAfx.cpp win32_dll.cpp ;

The Main rule section in the Jambase file is geared up to create a .EXE file, so we'll have to hack on it a little. If we just copy it to a new rule called SharedLibrary, we can make our changes there. It turns out that the only change we need to make is the suffix of the generated file:

	_t = [ FAppendSuffix $(<) : $(SUFSHR) ] ;

We have to go and define SUFSHR somewhere, though. The definition of the SharedLibrary rule is here.

Once we've added the new rules to Jambase, and changed "Main" to "SharedLibrary" in our Jamfile, we get the following:

win32_dll.cpp(25) : error C2491: 'nWin32_dll' : definition of dllimport data not allowed
win32_dll.cpp(29) : error C2491: 'fnWin32_dll' : definition of dllimport function not allowed
win32_dll.cpp(36) : warning C4273: 'CWin32_dll::CWin32_dll' : inconsistent dll linkage.  dllexport assumed.

This doesn't work, so we'll take a look at the compiler command lines.

Building a static library with Jam: Introduction

Introduction

Once again, we're going to do what we did with the MFC application and DLL examples: build the project with AppWizard, and then get it built with jam.

Using AppWizard to generate the library

Run up Visual C++ and generate a new "Win32 Static Library" project. Call it win32_lib, and put it in the S:\jam-test\lib\win32_lib directory. For now we want neither "Pre-Compiled header" nor "MFC support".

This gives us a project with no files in it. We'll create a C++ file, and a header file (something.cpp and something.h), and we'll create a simple function:

/* something.cpp */

#include "something.h"
#include <string.h>

int something(const char *p) {
    return strlen(p) + 42;
}
/* something.h */

int something(const char *p);

We'll add these to the Visual C++ project, and check that it builds, as we did with the other examples: for sanity's sake.

Creating the Jamfile

As we did with the other examples, we'll create a simple Jamfile. We'll also add all of the SubDir stuff necessary to integrate it into our overall build system:

SubDir TOP lib win32_lib ;

Library win32_lib : something.cpp ;

This builds, except that Jam says "warning: lib depends on itself". This is down to the fact that one of jam's pseudotargets is called 'lib'. The Jamfile.html documentation gives three different options to resolve this conflict:

  1. Change the name of the conflicting file.
  2. Modify Jambase and change the name of the pseudotarget.
  3. Use grist on the target name.

Now, I can't change the name of the conflicting file (or, in this case, directory) -- I'm looking to use jam to build empeg's source code. We've had a directory called lib for over two years. It's in CVS with that name, and there's over 180KLOC in there. That rules out option one.

I haven't figured out the implications of using grist on the target name, and a brief look at the example suggests that this isn't viable anyway, so that kinda leaves us with option 2 -- changing Jambase. See here.

Linking with a Shared Library

Introduction

Now we've got our application and DLL building as part of the same project, we really ought to persuade them to link together.

The first thing we ought to do is establish some kind of dependency in the code. It's test-first, but for Jamfiles. We'll do this by attempting to use one of the symbols exported from the DLL in our MFC application. We'll add this piece of code to InitInstance:

    int j = fnWin32_dll();

This requires that we include the relevant header file:

#include "win32_dll/win32_dll.h"

It now compiles correctly, but fails to link. We need to establish a dependency on the import library generated as part of the DLL build process. We can add this to the mfc_exe\Jamfile:

LinkLibraries mfc_exe : win32_lib win32_dll ;

This fails because jam doesn't know how to build win32_dll.lib. We need to add some extra stuff to the SharedLibaryFromObjects rule:

	MakeLocate $(_t) : $(LOCATE_TARGET) ;

	# Tell jam where it can find the import library
	MakeLocate $(_t:S=$(SUFLIB)) : $(LOCATE_TARGET) ;

	Clean clean : $(_t) ;

This compiles and links. It even runs -- if we make sure that the DLL can be found when loading the EXE.

If we use shared libraries on Unix, we'll have to invent a SharedLinkLibraries rule, because gcc wants the name of the .so file in order to establish the runtime dependency, and LinkLibraries assumes .lib.

Source code is here.


Bringing it together with the SubDir rule

Introduction

Now we've built an application and a DLL, we'd like to include them in the same build process. This is what Jam's SubDir rule does.

In the top-level directory, i.e. jam-test, we place a Jamfile looking like this:

SubDir TOP ;

SubInclude TOP lib ;
SubInclude TOP apps ;

We also need to create an empty Jamrules file, in order to supress a warning. This is not a problem: we'll probably be putting project-specific rules in there in a moment.

We also have to create corresponding Jamfile files for the other directories. The one in the apps directory looks like this:

SubDir TOP apps ;

SubInclude TOP apps mfc_exe ;

And we have to add SubDir invocations to the top of the original Jamfiles, so that they know where they are.

We run the build from the top-level directory:

S:\jam-test>jam -d2 -f /jam-test/Jambase
Compiler is Microsoft Visual C++
...found 116 target(s)...
...updating 8 target(s)...
C++ apps\mfc_exe\ChildFrm.obj

        cl /nologo  /c  /MTd /W3 /Gm /GX /ZI /Od /D WIN32 /D _DEBUG /D _WINDOWS /D _MBCS /D _USRDLL /D WIN32_D
LL_EXPORTS /MDd /W3 /Gm /GX /ZI /Od /D WIN32 /D _DEBUG /D _WINDOWS /D _AFXDLL /D _MBCS    /Foapps\mfc_exe\Chil
dFrm.obj  /Iapps\mfc_exe  /IP:\VStudio\VC98\include  /Tpapps\mfc_exe\ChildFrm.cpp

Command line warning D4025 : overriding '/MTd' with '/MDd'
ChildFrm.cpp

... etc. ...

Rc apps\mfc_exe\mfc_exe.res

        rc  /d _DEBUG /d _AFXDLL /l 0x809    /Fo apps\mfc_exe\mfc_exe.res  apps\mfc_exe\mfc_exe.rc

Link apps\mfc_exe\mfc_exe.exe

        link /nologo   /dll /incremental:yes /debug /machine:I386 /subsystem:windows /incremental:yes /debug /
machine:I386  /out:apps\mfc_exe\mfc_exe.exe   apps\mfc_exe\ChildFrm.obj apps\mfc_exe\MainFrm.obj apps\mfc_exe\
mfc_exe.obj apps\mfc_exe\mfc_exeDoc.obj apps\mfc_exe\mfc_exeView.obj apps\mfc_exe\StdAfx.obj  apps\mfc_exe\mfc
_exe.res

...updated 8 target(s)...

S:\jam-test>

If we attempt to run the mfc_exe executable, we find that it's "not a valid Win32 executable". This would appear to be down to the /dll switch being passed to the linker: we've managed to build a DLL and give it a .exe extension.

This behaviour is down to the way that Jam runs. Unlike recursive make, jam loads all of the named and included Jamfiles into the same "namespace". Thus, C++FLAGS and LINKFLAGS are persistant from one Jamfile to the next.

At this point, we do something we should have done before: Since we're invoking our SharedLibrary rule to build the DLL, we should add the /dll switch to the linker command line at that point.

We also don't like the "overriding '/MTd' with '/MDd'" warning. At this point, we introduce the SUBDIRC++FLAGS rule. It's like C++FLAGS, but the flags only stay in effect until the next SubDir rule. We should change the DLL and executable Jamfiles to use this. For example, mfc_exe\Jamfile looks like this:

SubDir TOP apps mfc_exe ;

SUBDIRC++FLAGS += /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_AFXDLL" /D "_MBCS" ;
LINKFLAGS += /subsystem:windows /incremental:yes /debug /machine:I386 ;
RCFLAGS += /d "_DEBUG" /d "_AFXDLL" /l 0x809 ;

Main mfc_exe : ChildFrm.cpp MainFrm.cpp mfc_exe.cpp mfc_exeDoc.cpp mfc_exeView.cpp StdAfx.cpp ;
Resource mfc_exe : mfc_exe.rc ;

We also note that the LINKFLAGS are accumulating. There's (strangely) no SUBDIRLINKFLAGS corresponding to SUBDIRC++FLAGS, but it's not a major problem. The flags only differ slightly, and that's according to the exe/dll nature of the target. We can move them into Jambase:

rule MainFromObjects {
	local _s _t ;

	# Add grist to file names
	# Add suffix to exe

	_s = [ FGristFiles $(>) ] ;
	_t = [ FAppendSuffix $(<) : $(SUFEXE) ] ;

	if $(_t) != $(<) {
	    DEPENDS $(<) : $(_t) ;
	    NOTFILE $(<) ;
	}

	# make compiled sources a dependency of target

	DEPENDS exe : $(_t) ;
	DEPENDS $(_t) : $(_s) ;
	MakeLocate $(_t) : $(LOCATE_TARGET) ;

	Clean clean : $(_t) ;

	LINKFLAGS on $(_t) += /subsystem:windows /incremental:yes /debug /machine:I386 ;
	Link $(_t) : $(_s) ;
}
rule SharedLibraryFromObjects {
	local _s _t ;

	# Add grist to file names
	# Add suffix to dll

	_s = [ FGristFiles $(>) ] ;
	_t = [ FAppendSuffix $(<) : $(SUFSHR) ] ;

	if $(_t) != $(<) {
	    DEPENDS $(<) : $(_t) ;
	    NOTFILE $(<) ;
	}

	# make compiled sources a dependency of target

	DEPENDS exe : $(_t) ;
	DEPENDS $(_t) : $(_s) ;
	MakeLocate $(_t) : $(LOCATE_TARGET) ;

	Clean clean : $(_t) ;

	LINKFLAGS on $(_t) += /dll /incremental:yes /debug /machine:I386 ;
	Link $(_t) : $(_s) ;
}

Conclusions

That was easy enough. We've still got a couple of things to work out:

You can find the source resulting from this here.

System Libraries

Obviously, your code doesn't just link with your libraries. It also has to link with some of the system libraries. Jam manages this by using the LINKLIBS variable. The simplest way to make this work is something like the following:

LINKLIBS on emplode.exe += ws2_32.lib ;

Here you can see that we're telling jam to pass ws2_32.lib on to the linker when it tries to link emplode.exe.

The main problem with this approach is that it's a bit Windows-specific. If we put aside the fact that we know we've got to link with ws2_32.lib for a moment, we still can't ignore the fact that the Jamfile needs to know that the target is called emplode.exe. It hasn't yet had to know.

[Say something about the SystemLibraries rule, including the fact that it doesn't work with DLLs]