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.
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.
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:
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.
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.
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 ...
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.
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
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?
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.
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!
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.
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.
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.
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:
-f
switch to jam
to tell jam where to find an alternate Jambase file. This
is the simplest, but requires more typing.
Jambase
into
the source directory for jam, and rebuild it.
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 ; }
2004/01/16 - 10:14am | Jam
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.
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.
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.
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.
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.
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.
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:
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.
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.
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) ; }
That was easy enough. We've still got a couple of things to work out:
You can find the source resulting from this here.
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]