Compiling Multiple Projects from the Same Source Tree
by Demolishun · 04/25/2012 (11:42 pm) · 14 comments
Updated:
The resource has been updated to reflect the awesome feedback from the community!Introduction:
Inspired by Philip Gregory's resource: "A resource for musicians" I decided to make a simple tutorial for maintaining the engine code base. The idea behind this is that T3D in particular shares the code base between projects. So if you modify the source code from the engine you affect every project. This may not be what you want. In my case it was not. I found this out the hard way and had to reinstall the T3D source to get the original code.Note: GG, if you are listening, please provide an option for installing pieces of each release and/or selective repair of the install from the install program. That way when reinstalling only to get a portion of the engine one does not have to wait 15 to 20 minutes for the install to happen. I did try unzipping the file, but there is a binary wad of data I could not make heads or tails of. Thanks in advance.
Structure:
It is all about structure. The layout of the Torque 3D 1.2 directory is as follows for the relevant source directories:- Torque 3D -- Engine --- source -- My Projects --- "project name" ---- sourceNow the 2 directories we care about are the "source" directories. These contain the engine source (Torque 3D->Engine->source) and the project source (Torque 3D->My Projects->"project name"->source). Knowing the location of these 2 directories is important for what I will cover next. Use this is a reference.
Usage:
How does this help? Well, Microsoft Visual C++ (2008 for the example) has a great way to take advantage of overriding files from a project. This is a good thing. The way you take advantage of this feature is simple.Say you have to make a change to a file in the T3D source. Lets say that you need to make a SimObject have a virtual function so you can override it in a later SimObject based class. This will mean that if you make this change it will affect every project that uses this same source code base. Unless, you take advantage of overriding a source file from the project source directory. For this example to make this change we would have to modify the file SimObject.h.
So in MS Visual C++ we would open our project and look in the "Solution Explorer" on the left side. Now there is going to be a DLL project named <project name> DLL. Under this you can drill down to the SimObject.h file like this: Source Files->Engine->console->SimObject.h. Now, right click on the file and select Properties. This will pop up a dialog. Find the option "Exclude From Build". This won't actually affect .h (header files), but it will put a red mark on the file. This is a visual indicator that you intend to override this file and just helps you later when tracking down which source file is overridden somewhere else.
Now we need to find the SimObject.h file in Windows Explorer so we can make a COPY of that file from the Torque 3D->Engine->source location to the Torque 3D->My Projects->"project name"->source location. Do not move the file, COPY the file. The path in Explorer would be Torque 3D->Engine->source->console->SimObject.h. Then it would be copied to the Torque 3D->My Projects->"project name"->source->console directory. Make sure the directory structure under "source" matches the directory structure under "Engine". It would look like this:
- Torque 3D -- Engine --- source ---- console -- My Projects --- "project name" ---- source ----- console
It should now be compiling against the new SimObject.h. To make sure we know about the file in the project we should add it back to the project in the Solution Explorer. To do this go into the project explorer again and right click on the "source" directory and choose Add->Existing Item. This will just add a indicator that this is part of the project for .h files. Now the SimObject.h file is part of the project again. Any changes you make to this copy of the SimObject.h will not affect other projects you create later on unless you decide to merge this file back into the main source directories again.
Great, but I have a huge project and I don't know what I changed in the main T3D source!
That is where I was at. It was a big project (for me at least) and I did not know everything I had changed from the original source. The solution was to reinstall T3D in a different directory and copy the source back to the original location of the source from my current T3D installation. I backed up my original source directory first by changing the directory name. Then I downloaded a great tool: gnuwin32.sourceforge.net/packages/diffutils.htm It allowed me to do a diff on the original source file directory against my changed source file directory. I ran a command window in the Torque 3D->Engine directory (-r is for recursive):diff -r "source" "mysource" > changed.txtSo if "source" is the default source code for T3D and "mysource" is my backed up source files I have changed, then changed.txt will produce a diff that will show what changed from source to mysource. This could be applied by a program called "patch" (I think) to change the source to match your changes. Anyway, the changed.txt file can be used to show which files changed like for my project:
diff -r source orig 1.2/app/mainLoop.cpp source/app/mainLoop.cpp
373c373,383
< Platform::setCurrentDirectory( Platform::getMainDotCsDir() );
---
> //if(strnicmp(Platform::getExecutableName(),"python",6) == 0){
> if(dStrnicmp(Platform::getExecutableName(),"python",6) == 0){
> // if being launched from Python
> Platform::setMainDotCsDir( Platform::getCurrentDirectory() );
> //Platform::setCurrentDirectory( Platform::getCurrentDirectory() );
> Torque::FS::Unmount( "game" );
> Torque::FS::Mount( "game", Platform::FS::createNativeFS( ( const char* ) Platform::getCurrentDirectory() ) );
> }else{
> // if launched from Torque Toolbox or the executable wrapper for the DLL
> Platform::setCurrentDirectory( Platform::getMainDotCsDir() );
> }
diff -r source orig 1.2/console/compiledEval.cpp source/console/compiledEval.cpp
1297c1297
< if(curObject)
---
> if(curObject)
1726a1727,1737
> break;
> }
> // pyT3D
> case Namespace::Entry::extScriptCallbackType:
> {
> const char *ret = nsEntry->cb.mScriptStringCallbackFunc(gEvalState.thisObject, nsEntry->mNamespace, callArgc, callArgv);
> STR.popFrame();
> if(ret != STR.getStringValue())
> STR.setStringValue(ret);
> else
> STR.setLen(dStrlen(ret));
diff -r source orig 1.2/console/console.h source/console/console.h
17a18,21
> // pyT3D
> //#ifndef _CONSOLEINTERNAL_H_
> //#include "console/consoleInternal.h"
> //#endif
119a124,126
>
> // pyT3D
> typedef const char * (*ScriptStringCallback)(SimObject *obj, Namespace *nsObj, S32 argc, const char *argv[]);
diff -r source orig 1.2/console/consoleInternal.cpp source/console/consoleInternal.cpp
1227a1228,1243
> // pyT3D
> void Namespace::addScriptCommand( StringTableEntry name, ScriptStringCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header )
> {
> Entry *ent = createLocalEntry(name);
> trashCache();
>
> ent->mUsage = usage;
> ent->mHeader = header;
> ent->mMinArgs = minArgs;
> ent->mMaxArgs = maxArgs;
> ent->mToolOnly = isToolOnly;
>
> ent->mType = Entry::extScriptCallbackType;
> ent->cb.mScriptStringCallbackFunc = cb;
> }
>
1300a1317,1319
> // pyT3D
> case extScriptCallbackType:
> return cb.mScriptStringCallbackFunc(state->thisObject, this->mNamespace, argc, argv);
diff -r source orig 1.2/console/consoleInternal.h source/console/consoleInternal.h
77c77,79
< BoolCallbackType
---
> BoolCallbackType,
> // pyT3D
> extScriptCallbackType
126a129,130
> // pyT3D
> ScriptStringCallback mScriptStringCallbackFunc;
171a176,178
>
> // pyT3D
> void addScriptCommand( StringTableEntry name, ScriptStringCallback, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false, ConsoleFunctionHeader* header = NULL );
diff -r source orig 1.2/console/scriptObjects.h source/console/scriptObjects.h
23c23,24
< bool onAdd();
---
> // pyT3D : made virtual
> virtual bool onAdd();
diff -r source orig 1.2/console/simObject.h source/console/simObject.h
435c435,436
< const char *getDataField(StringTableEntry slotName, const char *array);
---
> // pyT3D added virtual
> virtual const char *getDataField(StringTableEntry slotName, const char *array);
445c446,447
< void setDataField(StringTableEntry slotName, const char *array, const char *value);
---
> // pyT3D added virtual
> virtual void setDataField(StringTableEntry slotName, const char *array, const char *value);
diff -r source orig 1.2/main/main.cpp source/main/main.cpp
25c25,26
< sprintf(gameLib, "%s.dll", filename);
---
> //sprintf(gameLib, "%s.dll", filename);
> sprintf(gameLib, "_pyT3D.pyd", filename);I was actually pleasantly surprised that I had changed relatively few original engine source files. I ended up having to move only 7 files to my project source directory. To help you understand how to determine which files changed you can see my list of changed files:compiledEval.cpp console.h consoleInternal.cpp consoleInternal.h mainLoop.cpp scriptObjects.h simObject.hNote: I did have to make some functions virtual in simObject.h. Can you spot them?
Conclusion:
I hope this all made sense for the people who would take advantage of this feature of MS Visual C++. I really like the ability to add and remove files at will. This makes it much simpler to keep code bases cleaner. This is probably an obvious feature to experienced coders, but for newer people blindly applying code resources it may save them some pain.Enjoy
Thanks for corrections and suggestions:
- Guy Allard for being the guinea pig and testing what I thought was working and wasn't. Guy is the guy who determined that .h files would not play nice unless in a compatible directory structure on the disk.
- Lukas Joergensen for suggesting using WinMerge. That should help those console challenged folks out there.
- Kerry Enfinger and Dan Keller for suggesting the use of version control software. That is definitely a good point that this method is not intended for that purpose. So make sure you use version control in addition! This is only for ease of compilation multiple projects from the same source.
About the author
I love programming, I love programming things that go click, whirr, boom. For organized T3D Links visit: http://demolishun.com/?page_id=67
#2
04/26/2012 (10:34 am)
Using VisualSVN Server and TortoiseSVN has been the only way to go for me. Both are free and you can easily diff, branch, merge, and revert the code base. I wouldn't do a project without them.
#3
I tried out your suggestion, but it seems that removing a header from the visual studio solution doesn't prevent it being used at compile time. I can remove simObject.h from the solution, then recompile without problem. The source files all #include "console/simObject.h", so I also don't see how placing a copy in the source folder would work.
It seems that the suggested method is fine for .cpp files, but not for headers. Can you check that this really does work for you? It would be great to be able to re-use the engine like this.
04/26/2012 (12:32 pm)
I've been looking for a way to use the engine like this, as I usually end up using a complete fresh T3D codebase for each project, as it's pretty much guaranteed that the engine source will need to be modified differently on a per project basis.I tried out your suggestion, but it seems that removing a header from the visual studio solution doesn't prevent it being used at compile time. I can remove simObject.h from the solution, then recompile without problem. The source files all #include "console/simObject.h", so I also don't see how placing a copy in the source folder would work.
It seems that the suggested method is fine for .cpp files, but not for headers. Can you check that this really does work for you? It would be great to be able to re-use the engine like this.
#4
I thought that was working fine. I will have to look for the .h files. I would have thought I would get compile errors including .h multiple times, but maybe it is silently failing.
I am trying "exclude from project" rather than remove. I have to rebuild the whole source to make sure it works.
Update:
I guess this only works with .cpp files and not .h. Dang it!
Update2:
It looks like you can alter which include file gets processed first by doing an explicit "Force Include" on the particular header file. I could not get it to do wild card "force include", but I can for individual files. I will see if this solves the issue.
Update3:
Okay, I think that worked. I will update the resource later. I am tired of looking at this. I am going to change the process a bit to make it more intuitive and easier to understand that files are not included. I am thinking the "don't build" flag might be a better way. I will have to test before I tweak the resource.
The resource has been corrected using Guy's methods below. Don't use Force Include unless you really need to. It is not necessary because the method Guy pointed out allows header files to override the original header files if placed in the proper directories in your project source directory. If you are confused reread the modified resource.
04/26/2012 (5:55 pm)
@Guy, I thought that was working fine. I will have to look for the .h files. I would have thought I would get compile errors including .h multiple times, but maybe it is silently failing.
I am trying "exclude from project" rather than remove. I have to rebuild the whole source to make sure it works.
Update:
I guess this only works with .cpp files and not .h. Dang it!
Update2:
It looks like you can alter which include file gets processed first by doing an explicit "Force Include" on the particular header file. I could not get it to do wild card "force include", but I can for individual files. I will see if this solves the issue.
Update3:
Okay, I think that worked. I will update the resource later. I am tired of looking at this. I am going to change the process a bit to make it more intuitive and easier to understand that files are not included. I am thinking the "don't build" flag might be a better way. I will have to test before I tweak the resource.
The resource has been corrected using Guy's methods below. Don't use Force Include unless you really need to. It is not necessary because the method Guy pointed out allows header files to override the original header files if placed in the proper directories in your project source directory. If you are confused reread the modified resource.
#5
Including or excluding headers from the visual studio project appears to not actually do anything when it comes to compilation time, as you don't need to remove the old one, or add the new one in order for it to be used. It seems to just make the header easier to find and edit from within VS.
04/27/2012 (3:49 am)
What I've found is that creating a source/console folder and putting the copy of simObject.h in there causes that file to be used in preference to the simObject.h located in engine/console. You can test that it works by purposefully adding a syntax error to it.Including or excluding headers from the visual studio project appears to not actually do anything when it comes to compilation time, as you don't need to remove the old one, or add the new one in order for it to be used. It seems to just make the header easier to find and edit from within VS.
#6
Good find! I will have to try that. I did not like the override thing at tall. I was hoping they had some sort of directory shadowing. I just could not find anything about it. What version of VC++ are you using?
04/27/2012 (8:44 am)
@Guy,Good find! I will have to try that. I did not like the override thing at tall. I was hoping they had some sort of directory shadowing. I just could not find anything about it. What version of VC++ are you using?
#7
04/27/2012 (8:46 am)
You can also use source control and put each project on a different branch. It's a good idea to use source control anyway.
#8
@Dan - indeed, always use source control. But, for example, if you're working on 3 projects, then you will need to have 3 full T3D codebases checked out, instead of just 1 main codebase with multiple project directories.
04/27/2012 (9:29 am)
@Frank - using VS2008@Dan - indeed, always use source control. But, for example, if you're working on 3 projects, then you will need to have 3 full T3D codebases checked out, instead of just 1 main codebase with multiple project directories.
#9
Thanks for the update. You are right if you create the same directory structure it seems to work. I will update the resource to reflect this. Also, it seems people are assuming this has something to do with source control. It doesn't. That is a separate issue from having multiple build sets on one machine.
04/27/2012 (10:36 am)
@Guy,Thanks for the update. You are right if you create the same directory structure it seems to work. I will update the resource to reflect this. Also, it seems people are assuming this has something to do with source control. It doesn't. That is a separate issue from having multiple build sets on one machine.
#10
05/01/2012 (5:32 am)
Thanks for this, it's useful.
#11
One thing is still annoying me though - the first compilation of each new project configuration results in a recompile of all of the libraries, which takes a significant amount of time. It's unnecessary, as I don't change the libs, only the game dll project.
So, why not make all of the projects put their intermediate files into a common location, so that they can be shared, then all new projects can just use the already generated intermediates.
The compiled libraries get placed in engine\lib\compiled, so why not put the intermediates in engine\lib\link
To automate this, you only have to edit a few lines of the project generator templates.
For VS2008:
edit: "MainTorqueDir"\tools\projectGenerator\templates\vc2k8_lib_proj.tpl
and change the three occurrences of:
IntermediateDirectory="{$projectOffset}../Link/Vc2k8.$(ConfigurationName).$(PlatformName)/$(ProjectName)"
to:
IntermediateDirectory="{$libDir}/Link/Vc2k8.$(ConfigurationName).$(PlatformName)/$(ProjectName)"
for VS2010:
edit:
"MainTorqueDir"\tools\projectGenerator\templates\vc2010_lib_proj.tpl
and change the three occurrences of:
{$projectOffset}../Link/VC2010.$(Configuration).$(PlatformName)/$(ProjectName)/</IntDir>
to:
{$libDir}/Link/VC2010.$(Configuration).$(PlatformName)/$(ProjectName)/</IntDir>
To make these changes take effect, run generateProjects.bat for your existing game projects. After compiling once, you won't have to continually recompile the libraries for new projects.
08/01/2012 (8:02 am)
I've been using this system now for a couple of months and it's almost perfect. I no longer have to install another copy of T3D just to start a new project that modifies the original source files. One thing is still annoying me though - the first compilation of each new project configuration results in a recompile of all of the libraries, which takes a significant amount of time. It's unnecessary, as I don't change the libs, only the game dll project.
So, why not make all of the projects put their intermediate files into a common location, so that they can be shared, then all new projects can just use the already generated intermediates.
The compiled libraries get placed in engine\lib\compiled, so why not put the intermediates in engine\lib\link
To automate this, you only have to edit a few lines of the project generator templates.
For VS2008:
edit: "MainTorqueDir"\tools\projectGenerator\templates\vc2k8_lib_proj.tpl
and change the three occurrences of:
IntermediateDirectory="{$projectOffset}../Link/Vc2k8.$(ConfigurationName).$(PlatformName)/$(ProjectName)"
to:
IntermediateDirectory="{$libDir}/Link/Vc2k8.$(ConfigurationName).$(PlatformName)/$(ProjectName)"
for VS2010:
edit:
"MainTorqueDir"\tools\projectGenerator\templates\vc2010_lib_proj.tpl
and change the three occurrences of:
{$projectOffset}../Link/VC2010.$(Configuration).$(PlatformName)/$(ProjectName)/</IntDir>
to:
{$libDir}/Link/VC2010.$(Configuration).$(PlatformName)/$(ProjectName)/</IntDir>
To make these changes take effect, run generateProjects.bat for your existing game projects. After compiling once, you won't have to continually recompile the libraries for new projects.
#12
thanks for that.it will save huge amount of space.minimum 300 mb per project.i will give it a try
[edit]
for those who do not know actually what goes behind this linking:
during building process in your project's build\Files\Link folder there creates 2 folder one for debug build and one for release build.total size around 600mb.it is the same files which are created seperatly for each project.
guy's
tweak will move these folder to Engine\lib\Link.
all project now will use same files.so if one project already have created those files then another project can use them without recompiling them.
which will save huge amount of space.
and
also compilation time.
but still the output of your project related dll will be stored into "buildFiles\Link" folder.
only library related file will be stored in Engine\lib\Link
08/01/2012 (11:05 am)
guy,thanks for that.it will save huge amount of space.minimum 300 mb per project.i will give it a try
[edit]
for those who do not know actually what goes behind this linking:
during building process in your project's build\Files\Link folder there creates 2 folder one for debug build and one for release build.total size around 600mb.it is the same files which are created seperatly for each project.
guy's
tweak will move these folder to Engine\lib\Link.
all project now will use same files.so if one project already have created those files then another project can use them without recompiling them.
which will save huge amount of space.
and
also compilation time.
but still the output of your project related dll will be stored into "buildFiles\Link" folder.
only library related file will be stored in Engine\lib\Link
#13
It makes sense I think to have the link files for the game .exe and .dll not shared, as these are the two things that are likely to change on a per project basis.
08/02/2012 (3:45 am)
Yeah, it's a big saving of space and time.It makes sense I think to have the link files for the game .exe and .dll not shared, as these are the two things that are likely to change on a per project basis.
#14
08/05/2012 (12:34 am)
Holy amazing Guy! You are a coding animal! I will have to try this as well. 
Torque Owner Lukas Joergensen
WinterLeaf Entertainment