My knowledge of make and autotools (which I'm not yet using for this project) is rudimentary at best despite plenty of googling and experimenting over a long period of time. I have a source hierarchy like below that I'm trying to find way to build has seamlessly as possible.
The application is made up of a main application with source in various subfolders under app/src. These are built with the respective Makefile in the root of that folder.
Then I have multiple other utilities that reside different folders under app/tools that each have their own Makefile.
app/src/module1/file1.cpp
app/src/module1/file1.hpp
app/src/module2/file2.cpp
app/src/module2/file2.hpp
app/src/module3/file3.cpp
app/src/module3/file3.hpp
app/src/main.cpp
app/src/main.hpp
app/src/Makefile
app/tools/util1/file1.cpp
app/tools/util1/file1.hpp
app/tools/util1/Makefile
app/tools/util2/file2.cpp
app/tools/util2/file2.hpp
app/tools/util2/Makefile
The problem for me is that some of these tools depend on source files inside the app/src source folder, but with a preprocess macro EXTERNAL_TOOL enabled. So the object files generated from compiling the main app and the varous utilities are not compatible.
Currently to build each portion of the project I'm having to clean the source tree in between. This is painful and certainly not what I want in the end. What would be the best way to go about solving this? Ideas I've had that I've no been able to put into practice are:
I'm not too sure I have the time and patience needed to master make / autotools. Might one of the other build tools (scons? cmake?) make this kind of task easier to accomplish? If so which one?
UPDATE: This is what I've got now
SOURCES := util1.cpp util2.cpp util3.cpp \
../../src/module1/file1.cpp \
../../src/module1/file2.cpp \
../../src/module1/file3.cpp \
../../src/module2/file4.cpp \
../../src/module3/file5.cpp \
../../src/module3/file6.cpp \
../../src/module4/file7.cpp \
../../src/module4/file8.cpp \
../../src/module3/file9.cpp \
../../src/module4/file10.cpp \
../../src/module5/file11.cpp \
../../src/module3/file12.cpp \
../../src/module1/file13.cpp \
../../src/module3/file14.cpp \
../../src/module3/file15.cpp
OBJECTS = $(join $(addsuffix .util/, $(dir $(SOURCES))), $(notdir $(SOURCES:.cpp=.o)))
.PHONY: all mkdir
all: util
util: $(OBJECTS)
$(CXX) $(CXXFLAGS) $(OBJECTS) $(LIBS) -o util
$(OBJECTS): | mkdir
$(CXX) -c $(CXXFLAGS) -o $@ $(patsubst %.o,%.cpp,$(subst .util/,,$@))
mkdir:
@mkdir -p $(sort $(dir $(OBJECTS)))
clean:
-@rm -f $(OBJECTS) util
-@rmdir $(sort $(dir $(OBJECTS))) 2>/dev/null
I came about this after extensive googling SO browsing. This seems to work, but this part doesn't really seem particular nice (feels like a bit of a hack):
$(OBJECTS): | mkdir
$(CXX) -c $(CXXFLAGS) -o $@ $(patsubst %.o,%.cpp,$(subst .util/,,$@))
In particular I'm not too keen on the fact I'm creating the list of objects from sources earlier on and adding the suffix, only to do the reverse down here. I couldn't seem to get it working any other way.
CMake has add_definitions and remove_definitions commands. You can use them to define macros for different parts of your project:
# building tools #
add_definitions(-DEXTERNAL_TOOL)
add_subdirectory($TOOL1$ $BUILD_DIR$)
add_subdirectory($TOOL2$ $BUILD_DIR$)
...
# building main app #
remove_definitions(-DEXTERNAL_TOOL)
add_executable(...)
This can be done with SCons rather painlessly. You will definitely need a build directory hierarchy for the objects built with different preprocessor macros. In SCons terms, creating build directories like this is called variant_dir. I would recomend the following SCons Hierarchical build structure:
app/SConstruct
app/src/module1/file1.cpp
app/src/module1/file1.hpp
app/src/module2/file2.cpp
app/src/module2/file2.hpp
app/src/module3/file3.cpp
app/src/module3/file3.hpp
app/src/main.cpp
app/src/main.hpp
app/src/SConscript_modules
app/src/SConscript_main
app/tools/util1/file1.cpp
app/tools/util1/file1.hpp
app/tools/util2/file2.cpp
app/tools/util2/file2.hpp
app/tools/SConscript
app/build/main/
app/build/target1/modules/
app/build/target2/modules/
app/build/tools/utils/
To be able to build the same source files with different preprocessor macros, you will need to build the same file with several different Environments. These env's could be setup in the src/module SConscript scripts, or from the root SConstruct and passed down. I prefer the second option, since it will make the src/module SCons scripts modular, and unaware (agnostic) of the preprocessor macros.
Here is the root build script, which creates the different env's and orchestrates the sub-directory build scripts:
app/SConstruct
defines1 = ['MACRO1']
defines2 = ['MACRO2']
env1 = Environment(CPPDEFINES = defines1)
env2 = Environment(CPPDEFINES = defines2)
includePaths = [
'src/module1',
'src/module2',
'src/module3',
]
env1.Append(CPPPATH = includePaths)
env2.Append(CPPPATH = includePaths)
# Build different versions of the module libs
SConscript('src/SConscript_modules',
variant_dir = '#build/target1/modules',
exports = {'env':env1},
duplicate=0)
SConscript('src/SConscript_modules',
variant_dir = '#build/target2/modules',
exports = {'env':env2},
duplicate=0)
# Build main with env1
SConscript('src/SConscript_main',
variant_dir = '#build/main',
exports = {'env':env2},
duplicate=0)
# Build tools with env2
SConscript('tools/SConscript',
variant_dir = '#build/utils',
exports = {'env':env2},
duplicate=0)
This is the build script for main app/src/SConscript_main
Import('env')
sourceFiles = ['main.cpp']
# If you want to modify the env here, Clone() it first, otherwise
# the changes will be visible to all other SConscripts
env.Program(target = 'main', source = sourceFiles)
This is the build script for the module libs, it will be called twice, each time with a different env app/src/SConscript_modules
Import('env')
module1SourceFiles = ['file1.cpp']
module2SourceFiles = ['file2.cpp']
module3SourceFiles = ['file3.cpp']
# If you want to modify the env here, Clone() it first, otherwise
# the changes will be visible to all other SConscripts
env.Library(target = 'module1', source = module1SourceFiles)
env.Library(target = 'module2', source = module2SourceFiles)
env.Library(target = 'module3', source = module3SourceFiles)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With