- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
- <HTML>
-
- <HEAD>
-
- <META NAME="VPSiteProject" CONTENT="file:///D|/rld/Docs/Project.vpp"><META HTTP-EQUIV="Content-Type" CONTENT="text/html;CHARSET=iso-8859-1">
- <META NAME="GENERATOR" Content="Visual Page 2.0 for Windows">
- <TITLE>Building an MFC Application with Jam: Introduction</TITLE>
- <LINK REL="stylesheet" TYPE="text/css" HREF="roger.css">
-
- <STYLE TYPE="text/css">
- <!--
- P {
- margin : 0 0 .5em 0
- }
- P.links {
- margin : 1em 0 3em 0 ;
- Text-Align : Left ;
- Font-Size : 0.900000em
- }
- H1.node {
- Font-Size : 1.300000em
- }
- PRE {
- padding : 1em ;
- Font-Size : 0.900000em ;
- Background-Color : #EEEEEE
- }
- EM.node {
- margin : 0 0 1em 0 ;
- display : block ;
- Font-Size : 0.800000em ;
- Color : gray ;
- Font-Style : Normal
- }
- .node {
-
- }
- -->
- </STYLE>
-
- </HEAD>
-
- <BODY>
-
- <H1>Constructing a Medium-sized Project with Jam</H1>
- <H2>Introduction</H2>
- <P>Jam is a replacement for <CODE>make(1)</CODE>. See <A HREF="Jam.html">here</A> for more details.</P>
- <P>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.</P>
- <H2>Introduction</H2>
-
- <UL>
- <LI><A HREF="#BuildJam">Building jam on Windows NT</A>.
- </UL>
-
- <P>
- <H2>Tutorial</H2>
- <P>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.</P>
-
- <UL>
- <LI><A HREF="#mfc">Building an MFC program</A>.
- <LI><A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/shared_lib/">Building a shared library (DLL)</A>.
- <LI><A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/sub_dir/">Bringing it together with the SubDir
- rule</A>.
- <LI><A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/link_dll/">Linking with a shared library</A>.
- <LI><A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/static_lib/">Building (and linking with) static
- libraries</A>.
- <LI><A HREF="http://www.differentpla.net/node/view/96">Resource script dependencies</A>.
- <LI><A HREF="http://www.differentpla.net/node/view/95">Separate Debug/Release directories</A>.
- </UL>
-
- <P>
- <H2>Other Stuff</H2>
-
- <UL>
- <LI><A HREF="http://www.differentpla.net/node/view/37">Conflicting 'lib' target</A>.
- <LI><A HREF="http://www.differentpla.net/node/view/36">Linker command line length</A>.
- </UL>
-
- <P>
- <H2>Miscellaneous</H2>
- <P>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:</P>
-
- <UL>
- <LI><A HREF="http://www.differentpla.net/%7Eroger/devel/jam/misc/system_libs.html">Linking with system libraries</A>.
- </UL>
-
- <P>
- <H1><A NAME="%23mfc"></A>Building an MFC Application with Jam: Introduction</H1>
- <H2>Introduction</H2>
- <P>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.</P>
- <P>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:<BR>
- You can find the resulting source code from this example <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/src/jam-test-20010711a.tar.gz">here</A>.</P>
- <P>
- <HR ALIGN="CENTER">
-
- <H2>Using AppWizard to generate the application</H2>
- <P>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 <CODE>mfc_exe</CODE> and put it in <CODE>s:\jam-test\apps\mfc_exe</CODE>.
- The default settings for the application are fine, so just keep clicking "Next".</P>
- <P>When you've got your application generated, get Visual C++ to build it, just for sanity's sake.</P>
- <H2>Creating the Jamfile</H2>
- <P>The obvious thing to do at this point is to put the names of the .cpp files into a Jamfile, like this:</P>
-
- <DIV CLASS="snippet">
- <PRE>Main mfc_exe : ChildFrm.cpp MainFrm.cpp mfc_exe.cpp mfc_exeDoc.cpp mfc_exeView.cpp StdAfx.cpp ;
- </PRE>
- </DIV>
-
- <P>...and then to try building it, using -d2 to see what's going on. Not surprisingly, it doesn't work:
-
- <DIV CLASS="results">
- <PRE>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 ...
- </PRE>
- </DIV>
-
- <P>
- <H1>Building an MFC Application with Jam: Compiler Flags</H1>
- <P>
- <HR ALIGN="CENTER">
- </P>
- <P>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.</P>
- <P>Let's take a look at the compiler settings and see what's different. Jam is invoking the compiler like this:</P>
-
- <DIV CLASS="snippet">
- <PRE>cl /nologo /c /FoChildFrm.obj /IP:\VStudio\VC98\include /TpChildFrm.cpp
- </PRE>
- </DIV>
-
- <P>Developer Studio is invoking the compiler like this (taken from the .plg file):
-
- <DIV CLASS="snippet">
- <PRE>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
- </PRE>
- </DIV>
-
- <P>Looking at the results of <CODE>cl /?</CODE> tells us the following:
- <TABLE BORDER="0">
- <tbody> <TR>
- <TH>
- <P>Switch
- </TH>
- <TH>
- <P>Description
- </TH>
- <TH WIDTH="25">
- <P>
- </TH>
- <TH>
- <P>Switch
- </TH>
- <TH>
- <P>Description
- </TH>
- </TR>
- <TR>
- <TD>
- <P>/nologo
- </TD>
- <TD>
- <P>Don't output a version banner
- </TD>
- <TD WIDTH="25">
- <P>
- </TD>
- <TD>
- <P>/MDd
- </TD>
- <TD>
- <P>Link with the MSVCRTD.lib file.
- </TD>
- </TR>
- <TR>
- <TD>
- <P>/W3
- </TD>
- <TD>
- <P>Set the warning level
- </TD>
- <TD WIDTH="25">
- <P>
- </TD>
- <TD>
- <P>/Gm
- </TD>
- <TD>
- <P>Enable minimal rebuild
- </TD>
- </TR>
- <TR>
- <TD>
- <P>/GX
- </TD>
- <TD>
- <P>Enable exceptions
- </TD>
- <TD WIDTH="25">
- <P>
- </TD>
- <TD>
- <P>/ZI
- </TD>
- <TD>
- <P>Enable Edit and Continue debug info
- </TD>
- </TR>
- <TR>
- <TD>
- <P>/Od
- </TD>
- <TD>
- <P>Disable optimisations (debug)
- </TD>
- <TD WIDTH="25">
- <P>
- </TD>
- <TD>
- <P>/D
- </TD>
- <TD>
- <P>Define some stuff
- </TD>
- </TR>
- <TR>
- <TD>
- <P>/Fp
- </TD>
- <TD>
- <P>Name precompiled header file
- </TD>
- <TD WIDTH="25">
- <P>
- </TD>
- <TD>
- <P>/Yu
- </TD>
- <TD>
- <P>Use .PCH file
- </TD>
- </TR>
- <TR>
- <TD>
- <P>/Fo
- </TD>
- <TD>
- <P>Name object file
- </TD>
- <TD WIDTH="25">
- <P>
- </TD>
- <TD>
- <P>/Fd
- </TD>
- <TD>
- <P>Name .PDB file
- </TD>
- </TR>
- <TR>
- <TD>
- <P>/FD
- </TD>
- <TD>
- <P>Generate file dependencies
- </TD>
- <TD WIDTH="25">
- <P>
- </TD>
- <TD>
- <P>/GZ
- </TD>
- <TD>
- <P>Enable runtime debug checks
- </TD>
- </TR>
- <TR>
- <TD>
- <P>/c
- </TD>
- <TD>
- <P>Don't link; just compile
- </TD>
- <TD WIDTH="25">
- <P>
- </TD>
- <TD>
- <P>/I
- </TD>
- <TD>
- <P>Name include directory
- </TD>
- </TR>
- <TR>
- <TD>
- <P>/Tp
- </TD>
- <TD>
- <P>Treat the file as C++
- </TD>
- <TD WIDTH="25">
- <P>
- </TD>
- <TD>
- <P>
- </TD>
- <TD>
- <P>
- </TD>
- </TR>
- </tbody></TABLE>
- </P>
- <P>Obviously, we'd like the warnings and debug information. We'll probably need the <CODE>/D</CODE> switches, as
- well. Diagnosing the error messages above suggests that we'll need <CODE>/MDd</CODE>. For now we can ignore the
- precompiled header stuff, and we'll come back to the file naming things.</P>
- <P>That leaves us with a file looking like this:</P>
-
- <DIV CLASS="snippet">
- <PRE>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 ;
- </PRE>
- </DIV>
-
- <P>...and the following results:
-
- <DIV CLASS="snippet">
- <PRE>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
- </PRE>
- </DIV>
-
- <P>...which looks like it's caused by <CODE>Jambase</CODE> adding libraries we don't want.</P>
- <H1>Building an MFC Application with Jam: Link Libraries</H1>
- <P>
- <HR ALIGN="CENTER">
- </P>
- <P>We'll copy <CODE>Jambase</CODE> from the distribution directory and put it in into <CODE>S:\jam-test</CODE>,
- which is where it'll end up in our final build system. Looking through the file reveals the following:</P>
-
- <DIV CLASS="snippet">
- <PRE> 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 _" ;
- }
- </PRE>
- </DIV>
-
- <P>We'll take out the LINKLIBS line, leaving it looking like this:
-
- <DIV CLASS="snippet">
- <PRE> LINKLIBS ?= "" ;
- </PRE>
- </DIV>
-
- <P>Remembering to invoke jam as: <CODE>jam -f /jam-test/Jambase</CODE> leaves us with this:</P>
-
- <DIV CLASS="snippet">
- <PRE>LINK : fatal error LNK1561: entry point must be defined</PRE>
- </DIV>
-
- <H1>Building an MFC Application with Jam: Entry Point</H1>
- <P>
- <HR ALIGN="CENTER">
- </P>
- <P>Jam is invoking link like this:</P>
-
- <DIV CLASS="snippet">
- <PRE>link /nologo /out:mfc_exe.exe ChildFrm.obj MainFrm.obj mfc_exe.obj mfc_exeDoc.obj mfc_exeView.obj
- StdAfx.obj
- </PRE>
- </DIV>
-
- <P>Developer Studio is invoking link with a response file containing the following:
-
- <DIV CLASS="snippet">
- <PRE>/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
- </PRE>
- </DIV>
-
- <P>We'll add some of the more interesting switches to our Jamfile, and see what happens:</P>
-
- <DIV CLASS="snippet">
- <PRE>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 ;
- </PRE>
- </DIV>
-
- <P>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?</P>
- <H1>Building an MFC Application with Jam: Resource Files</H1>
- <P>
- <HR ALIGN="CENTER">
- </P>
- <P>Running our newly-built MFC application in the debugger reveals the following smoking gun in the output window:</P>
-
- <DIV CLASS="snippet">
- <PRE>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.
- </PRE>
- </DIV>
-
- <P>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 <CODE>.rc</CODE> file to the list of source files in the Jamfile, and have it magically work.
- However, when we try that, we get:</P>
-
- <DIV CLASS="snippet">
- <PRE>Unknown suffix on mfc_exe.rc - see UserObject rule in Jamfile(5)
- </PRE>
- </DIV>
-
- <P>Looking in the <CODE>Jambase.html</CODE> file included in the distribution, we find a section that suggests
- overriding the <CODE>UserObject</CODE> rule in order to tell jam about .rc files. It says to put it in <CODE>Jamrules</CODE>,
- but since we don't have one, we'll put it in our <CODE>Jamfile</CODE> for the time being:</P>
-
- <DIV CLASS="snippet">
- <PRE>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 ;
- </PRE>
- </DIV>
-
- <P>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.</P>
- <P>Thus, one of the build steps will overwrite the output from the other. This is a bad thing.</P>
- <P>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 <CODE>Main</CODE> rule in <CODE>Jambase</CODE>. Since the odds
- of that happening are slim, we'll cast our net a little further afield.</P>
- <H1>Building an MFC Application with Jam: Resource Files (2)</H1>
- <P>
- <HR ALIGN="CENTER">
- </P>
- <P>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.</P>
- <P>Lifting the relevant sections, and simplifying them results in the following:</P>
-
- <DIV CLASS="snippet">
- <PRE># 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 $(<) $(>)
- }
- </PRE>
- </DIV>
-
- <P>
- <P>This works fine. If we build the program, we get a working executable!</P>
- <H1>Building an MFC Application with Jam: What Next?</H1>
- <P>
- <HR ALIGN="CENTER">
- </P>
- <P>We've just successfully built an MFC application using jam. There are a couple of things that we still need
- to consider:</P>
-
- <UL>
- <LI>Resource Scripts can have include files. We've not done anything about dependency checking on them.
- <LI>Separate Debug/Release configurations.
- <LI>Visual C++ puts the output in Debug or Release. Currently, we just dump everything in the current directory.
- <LI>The AppWizard-generated project used precompiled headers. We ought to, as well. They dramatically improve compilation
- speed.
- </UL>
-
- <P>I'll come back to this later and deal with some of the above points.</P>
- <H1 CLASS="node"><A NAME="%23BuildJam"></A>Building Jam on Windows NT</H1>
- <P><EM><SPAN CLASS="node">2004/01/09 - 10:28am | <A HREF="http://www.differentpla.net/taxonomy/page/or/16">Jam</A></SPAN></EM></P>
- <P>First, you'll need to download jam version 2.3.2 from ftp.perforce.com in <A HREF="ftp://ftp.perforce.com/pub/jam/jam-2.3.2.zip">zip
- format</A> or <A HREF="ftp://ftp.perforce.com/pub/jam/jam-2.3.2.tar.gz">as a tar.gz</A></P>
- <P>Unpack the contents into a new directory. Edit the <CODE>Makefile</CODE>, and uncomment the relevant lines under
- the comment:</P>
- <PRE># NT (with Microsoft compiler)</PRE>
- <P>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:</P>
- <PRE><FONT FACE="Courier New, Courier">set MSVCNT=P:\VStudio\VC98</FONT></PRE>
- <P>Build it:</P>
- <PRE><FONT FACE="Courier New, Courier">nmake -f Makefile</FONT></PRE>
- <P>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 <CODE>jam.exe</CODE> file in the <CODE>bin.ntx86</CODE> directory. Copy it somewhere sensible.</P>
- <P><!-- Linker Command Line Length -->
- <H1 CLASS="node">Linker Command Line Length</H1>
- <P><EM CLASS="node">2004/01/09 - 10:37am | <A HREF="http://www.differentpla.net/taxonomy/page/or/16">Jam</A></EM></P>
- <P>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.</P>
- <P>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 <CODE>Jambase</CODE> with this:</P>
-
- <DIV CLASS="snippet">
- <PRE> 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)
- }
- </PRE>
- </DIV>
-
- <P>Remember to set <CODE>TOUCH</CODE> to something sensible.</P>
- <P><!-- Conflicting 'lib' target -->
- <H1 CLASS="node">Conflicting 'lib' target</H1>
- <P><EM CLASS="node">2004/01/09 - 10:39am | <A HREF="http://www.differentpla.net/taxonomy/page/or/16">Jam</A></EM></P>
- <P>empeg's source tree has a directory called <CODE>lib</CODE>, 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.</P>
- <P>The fix is relatively simple. You need to edit the included <CODE>Jambase</CODE> file, and rename every mention
- of <CODE>lib</CODE> to something else, e.g. <CODE>libs</CODE>.</P>
- <P>Lines 552-554:</P>
-
- <DIV CLASS="before">
- <PRE>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 ;
- </PRE>
- </DIV>
-
- <P>...change this to...
-
- <DIV CLASS="after">
- <PRE>DEPENDS all : shell files <B>libs</B> exe obj ;
- DEPENDS all shell files <B>libs</B> exe obj : first ;
- NOTFILE all first shell files <B>libs</B> exe obj dirs clean uninstall ;
- </PRE>
- </DIV>
-
- <P>Lines 827-830:
-
- <DIV CLASS="before">
- <PRE> else {
- DEPENDS lib : $(_l) ;
- }
- </PRE>
- </DIV>
-
- <P>...change this to...
-
- <DIV CLASS="after">
- <PRE> else {
- DEPENDS <B>libs</B> : $(_l) ;
- }
- </PRE>
- </DIV>
-
- <P>You should probably also change the comments at lines 41-52 that refer to 'lib'.</P>
- <P>This then requires that you use your new <CODE>Jambase</CODE> instead of the included one. You have two choices:</P>
-
- <OL>
- <LI>Use the <CODE>-f</CODE> switch to <CODE>jam</CODE> to tell jam where to find an alternate Jambase file. This
- is the simplest, but requires more typing.
- <LI>Recompile jam, including the new file. This is relatively simple. Copy the edited <CODE>Jambase</CODE> into
- the source directory for jam, and rebuild it.
- </OL>
-
- <P><!-- Jam - Separate Release/Debug Target Directories -->
- <H1 CLASS="node">Jam - Separate Release/Debug Target Directories</H1>
- <P><EM CLASS="node">2002/02/01 - 12:05pm | <A HREF="http://www.differentpla.net/taxonomy/page/or/16">Jam</A></EM></P>
- <P>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.</P>
- <P>This is controlled by the <CODE>LOCATE_TARGET</CODE> variable. It's initialised by the <CODE>SubDir</CODE> rule
- to be the same as the <CODE>SEARCH_SOURCE</CODE> variable, unless <CODE>ALL_LOCATE_TARGET</CODE> is set.</P>
- <P>However, if we set <CODE>ALL_LOCATE_TARGET</CODE> to, e.g. <CODE>Debug</CODE> in our <CODE>Jamrules</CODE> file,
- or using the <CODE>-s</CODE> 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.</P>
- <P>The comments in <CODE>Jambase</CODE> state that the <CODE>LOCATE_TARGET</CODE> variable should be set after
- invoking the <CODE>SubDir</CODE> rule, if required. This is a pain. We much prefer the following change to the
- <CODE>SubDir</CODE> rule:</P>
-
- <DIV CLASS="snippet">
- <PRE> # directory should not hold object files, LOCATE_TARGET can
- # subsequently be redefined.
-
- <B>local path = [ FDirName $(SUBDIR) $(TARGET_PREFIX) ] ;
-
- SEARCH_SOURCE = $(SUBDIR) ;
- LOCATE_SOURCE = $(ALL_LOCATE_TARGET) $(path) ;
- LOCATE_TARGET = $(ALL_LOCATE_TARGET) $(path) ;
- SOURCE_GRIST = $(path) ;</B>
-
- # Reset per-directory ccflags, hdrs
- </PRE>
- </DIV>
-
- <P>This allows us to place a rule like the following in our <CODE>Jamrules</CODE> file:</P>
-
- <DIV CLASS="snippet">
- <PRE>if ! $(DEBUG) {
- ECHO Assuming DEBUG=1 ;
- DEBUG = 1 ;
- }
-
- if $(DEBUG) = 0 {
- TARGET_PREFIX = Release ;
- } else {
- TARGET_PREFIX = Debug ;
- }</PRE>
- </DIV>
-
- <P><!-- Jam - Resource File Dependencies -->
- <H1 CLASS="node">Jam - Resource File Dependencies</H1>
- <P><EM CLASS="node">2004/01/16 - 10:14am | <A HREF="http://www.differentpla.net/taxonomy/page/or/16">Jam</A></EM></P>
- <P>
- <H2>Introduction</H2>
- <P>Our <A HREF="http://www.differentpla.net/mfc_app/">MFC application</A> 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 <CODE>.rc2</CODE> file, it's not rebuilt properly. We need to add the following to our <CODE>Resource</CODE>
- rule:</P>
-
- <DIV CLASS="snippet">
- <PRE> NEEDLIBS on $(_e) += $(_r) ;
-
- <B># .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) ;</B>
-
- Rc $(_r) : $(_s) ;
- </PRE>
- </DIV>
-
- <P>Source is <A HREF="http://www.differentpla.net/src/jam-test-20010717a.tar.gz">here</A>.</P>
- <H1>Building a DLL with Jam: Introduction</H1>
- <H2>Introduction</H2>
- <P>empeg's code base contains several Windows DLL projects. I'm going to investigate getting these built with jam
- the <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/mfc_app/">same way</A> as I did for the MFC
- applications -- by building a simple project with the AppWizard, and getting the project built.</P>
-
- <UL>
- <LI>Introduction / Creating the Jamfile.
- <LI><A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/shared_lib/cpp_flags.html">Compiler Flags</A>.
- <LI><A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/shared_lib/entry_point.html">Entry Point</A>.
- </UL>
-
- <P>You can find the resulting source code from this example <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/src/jam-test-20010712a.tar.gz">here</A>.</P>
- <P>
- <HR ALIGN="CENTER">
-
- <H2>Using AppWizard to generate the DLL</H2>
- <P>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 <CODE>S:\jam-test\lib\</CODE>
- directory, and we'll call it <CODE>win32_dll</CODE>.</P>
- <P>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.</P>
- <H2>Creating the Jamfile</H2>
- <P>The obvious thing to start with is a Jamfile that looks like this:</P>
-
- <DIV CLASS="snippet">
- <PRE>Main win32_dll : StdAfx.cpp win32_dll.cpp ;
- </PRE>
- </DIV>
-
- <P>The <CODE>Main</CODE> rule section in the <CODE>Jambase</CODE> 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 <CODE>SharedLibrary</CODE>, we can make our
- changes there. It turns out that the only change we need to make is the suffix of the generated file:
-
- <DIV CLASS="snippet">
- <PRE> _t = [ FAppendSuffix $(<) : $(SUFSHR) ] ;
- </PRE>
- </DIV>
-
- <P>We have to go and define SUFSHR somewhere, though. The definition of the <CODE>SharedLibrary</CODE> rule is
- <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/shared_lib/shared_lib_rule.html">here</A>.</P>
- <P>Once we've added the new rules to Jambase, and changed "Main" to "SharedLibrary" in our
- Jamfile, we get the following:</P>
-
- <DIV CLASS="snippet">
- <PRE>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.
- </PRE>
- </DIV>
-
- <P>This doesn't work, so we'll take a look at the compiler command lines.</P>
- <H1>Building a static library with Jam: Introduction</H1>
- <H2>Introduction</H2>
- <P>Once again, we're going to do what we did with the <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/mfc_app/">MFC
- application</A> and <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/shared_lib/">DLL</A> examples:
- build the project with AppWizard, and then get it built with jam.</P>
- <H2>Using AppWizard to generate the library</H2>
- <P>Run up Visual C++ and generate a new "Win32 Static Library" project. Call it <CODE>win32_lib</CODE>,
- and put it in the <CODE>S:\jam-test\lib\win32_lib</CODE> directory. For now we want neither "Pre-Compiled
- header" nor "MFC support".</P>
- <P>This gives us a project with no files in it. We'll create a C++ file, and a header file (<CODE>something.cpp</CODE>
- and <CODE>something.h</CODE>), and we'll create a simple function:</P>
-
- <DIV CLASS="snippet">
- <PRE>/* something.cpp */
-
- #include "something.h"
- #include <string.h>
-
- int something(const char *p) {
- return strlen(p) + 42;
- }
- </PRE>
- </DIV>
-
-
- <DIV CLASS="snippet">
- <PRE>/* something.h */
-
- int something(const char *p);
- </PRE>
- </DIV>
-
- <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.</P>
- <P>
- <H2>Creating the Jamfile</H2>
- <P>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:</P>
-
- <DIV CLASS="snippet">
- <PRE>SubDir TOP lib win32_lib ;
-
- Library win32_lib : something.cpp ;
- </PRE>
- </DIV>
-
- <P>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 <CODE>Jamfile.html</CODE> documentation gives three different options
- to resolve this conflict:
-
- <OL>
- <LI>Change the name of the conflicting file.
- <LI>Modify Jambase and change the name of the pseudotarget.
- <LI>Use grist on the target name.
- </OL>
-
- <P>
- <P>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.</P>
- <P>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 <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/misc/conflict_lib.html">here</A>.</P>
- <H1>Linking with a Shared Library</H1>
- <H2>Introduction</H2>
- <P>Now we've got our <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/mfc_app/">application</A>
- and <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/shared_lib/">DLL</A> building as part of the
- same project, we really ought to persuade them to link together.</P>
- <P>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 <CODE>InitInstance</CODE>:</P>
-
- <DIV CLASS="snippet">
- <PRE> int j = fnWin32_dll();
- </PRE>
- </DIV>
-
- <P>This requires that we include the relevant header file:
-
- <DIV CLASS="snippet">
- <PRE>#include "win32_dll/win32_dll.h"
- </PRE>
- </DIV>
-
- <P>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 <CODE>mfc_exe\Jamfile</CODE>:
-
- <DIV CLASS="snippet">
- <PRE>LinkLibraries mfc_exe : win32_lib win32_dll ;
- </PRE>
- </DIV>
-
- <P>This fails because jam doesn't know how to build win32_dll.lib. We need to add some extra stuff to the <CODE>SharedLibaryFromObjects</CODE>
- rule:
-
- <DIV CLASS="snippet">
- <PRE> MakeLocate $(_t) : $(LOCATE_TARGET) ;
-
- <B># Tell jam where it can find the import library
- MakeLocate $(_t:S=$(SUFLIB)) : $(LOCATE_TARGET) ;</B>
-
- Clean clean : $(_t) ;
- </PRE>
- </DIV>
-
- <P>This compiles and links. It even runs -- if we make sure that the DLL can be found when loading the EXE.</P>
- <P>
- <P>If we use shared libraries on Unix, we'll have to invent a <CODE>SharedLinkLibraries</CODE> rule, because <CODE>gcc</CODE>
- wants the name of the <CODE>.so</CODE> file in order to establish the runtime dependency, and <CODE>LinkLibraries</CODE>
- assumes <CODE>.lib</CODE>.</P>
- <P>Source code is <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/src/jam-test-20010716a.tar.gz">here</A>.</P>
- <P>
- <HR ALIGN="CENTER">
- </P>
- <H1>Bringing it together with the SubDir rule</H1>
- <H2>Introduction</H2>
- <P>Now we've built <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/mfc_app/">an application</A>
- and <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/shared_lib/">a DLL</A>, we'd like to include
- them in the same build process. This is what Jam's <CODE>SubDir</CODE> rule does.</P>
- <P>In the top-level directory, i.e. <CODE>jam-test</CODE>, we place a <CODE>Jamfile</CODE> looking like this:</P>
-
- <DIV CLASS="snippet">
- <PRE>SubDir TOP ;
-
- SubInclude TOP lib ;
- SubInclude TOP apps ;
- </PRE>
- </DIV>
-
- <P>We also need to create an empty <CODE>Jamrules</CODE> 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.</P>
- <P>We also have to create corresponding <CODE>Jamfile</CODE> files for the other directories. The one in the <CODE>apps</CODE>
- directory looks like this:</P>
-
- <DIV CLASS="snippet">
- <PRE>SubDir TOP apps ;
-
- SubInclude TOP apps mfc_exe ;
- </PRE>
- </DIV>
-
- <P>And we have to add <CODE>SubDir</CODE> invocations to the top of the original Jamfiles, so that they know where
- they are.</P>
- <P>We run the build from the top-level directory:</P>
-
- <DIV CLASS="snippet">
- <PRE>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
-
- <B>... etc. ...</B>
-
- 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>
- </PRE>
- </DIV>
-
- <P>If we attempt to run the <CODE>mfc_exe</CODE> executable, we find that it's "not a valid Win32 executable".
- This would appear to be down to the <CODE>/dll</CODE> switch being passed to the linker: we've managed to build
- a DLL and give it a .exe extension.</P>
- <P>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.</P>
- <P>At this point, we do something we should have done before: Since we're invoking our <CODE>SharedLibrary</CODE>
- rule to build the DLL, we should add the <CODE>/dll</CODE> switch to the linker command line at that point.</P>
- <P>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:</P>
-
- <DIV CLASS="snippet">
- <PRE>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 ;
- </PRE>
- </DIV>
-
- <P>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 <CODE>Jambase</CODE>:</P>
-
- <DIV CLASS="snippet">
- <PRE>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) ;
-
- <B>LINKFLAGS on $(_t) += /subsystem:windows /incremental:yes /debug /machine:I386 ;</B>
- Link $(_t) : $(_s) ;
- }
- </PRE>
- <PRE>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) ;
-
- <B>LINKFLAGS on $(_t) += /dll /incremental:yes /debug /machine:I386 ;</B>
- Link $(_t) : $(_s) ;
- }
- </PRE>
- </DIV>
-
- <P>
- <H2>Conclusions</H2>
- <P>That was easy enough. We've still got a couple of things to work out:</P>
-
- <UL>
- <LI>We're not actually attempting to link the EXE with the DLL, so there's no dependency established.
- <LI>The resource compiler flags aren't being reset. We've only got one resource script so far, so it's not an issue
- yet.
- </UL>
-
- <P>You can find the source resulting from this <A HREF="http://www.differentpla.net/%7Eroger/devel/jam/tutorial/src/jam-test-20010713a.tar.gz">here</A>.</P>
- <P>
- <H1>System Libraries</H1>
- <P>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 <CODE>LINKLIBS</CODE> variable. The simplest way to make this work is something like
- the following:</P>
-
- <DIV CLASS="before">
- <PRE>LINKLIBS on emplode.exe += ws2_32.lib ;
- </PRE>
- </DIV>
-
- <P>Here you can see that we're telling jam to pass <CODE>ws2_32.lib</CODE> on to the linker when it tries to link
- <CODE>emplode.exe</CODE>.</P>
- <P>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 <CODE>ws2_32.lib</CODE> for a moment, we still can't ignore the fact that the Jamfile needs
- to know that the target is called <CODE>emplode.exe</CODE>. It hasn't yet had to know.</P>
-
- <P>[Say something about the SystemLibraries rule, including the fact that it doesn't work with DLLs]
-
- </BODY>
-
- </HTML>