metaMake is a GNU Make makefile fragment that allows for building multiple architectures in parallel with human friendly dependency tracking between target objects.
High-level steps for writing a Makefile with metaMake rules:
- Add in the "all" and "clean" targets. Optionally configure the verbosity of the build
- Define the architectures
- Build artifacts
- Provide a set of makefile meta rules for building multiple architectures simultaneously. (i.e. Bare-metal target, and host for unit tests)
- Provide a set of makefile meta rules for importing and exporting build dependency information between fragments within a build. This allows for many small reusable libraries to be handled in a sane manner during a build
- Build code for any architecture known in the build system (universal build)
- This does not replace autotools or cmake. Those build systems are designed for detecting the build environment and adapting the build system accordingly. This build system is for when the target environment is known (bare-metal). (Also, neither of those build systems build for multiple architectures at the same time without some hackery)
- This does not hide the details of using or writing a Makefile. This project only provides meta rules that hopefully make it easier to write and maintain a Makefile
- These makefile meta rules where designed with bare-metal targets in mind, where simultaneous architecture builds are desirable for building target code and unit test code at the same time
- These makefile meta rules where designed to build C, C++, and assembly.
- These makefile meta rules where designed for GCC compilers
All of the meta rules for metaMake are stored in Meta.mk
. This file can be copied into other projects and modified as needed. See the examples for how to use it.
Also, be careful mixing tabs and spaces inside the makefiles. Only recipes can use tabs, all other forms of indentation must use spaces.
For the majority of projects, everything can be put in a single Makefile
.
If the project is large, or a build hierarchy is needed, then it is recommended to use a non-recursive make fragment system. In this setup, there is a top level Makefile
that includes rule fragments. Each library or executable in the build system should be nested in a directory with a Rules.mk
file. The Rules.mk
file specifies the rules and recipes required to build that library or executable.
See the Example project for a look at how the top level Makefile
and Rules.mk
can be setup. The fragment system is inspired by this non-recursive makefile solution.
The meta rules append to two variables, ALL_TARGETS
and CLEAN_TARGETS
. The ALL_TARGETS
variable contains a list of real targets (physical files) that should be build by the all
rule. The CLEAN_TARGETS
variable contains a list of phony targets (target that are not files) that should be used as a prerequisite for a clean
target. The following fragment is useful to have in the top level makefile.
# ---------------------------------------------------------------- DEFAULT GOAL
# Set the default goal, in case none is specified
.DEFAULT_GOAL := all
# ------------------------------------------------------------------ META RULES
# This must be included before any meta rules are used
include Meta.mk
# ........ other stuff - Architecture definition, build rules, etc
# ---------------------------------------------------------------- GLOBAL RULES
.PHONY: all
all: $(ALL_TARGETS)
@echo "===== All build finished ====="
.PHONY: clean
clean: $(CLEAN_TARGETS)
@echo "===== Clean finished ====="
.PHONY: help
help:
@echo "Available targets:"
@echo " all - Build all top level targets"
@echo " clean - Clean intermediate build files"
Be careful with mixing spaces and tabs. Tabs must be used for a recipe, spaces must be used for indentation everywhere else in a makefile.
Also, note that the clean
and all
recipe lines are at the bottom of the file. That is because the variables ALL_TARGETS
and CLEAN_TARGETS
will be expanded immediately for the recipe line, and will therefore not contain anything if they come before the meta rules. A workaround to that is to use secondary expansion on the recipe lines, or better yet, just depend on the METAMAKE_ALL
and METAMAKE_CLEAN
phony targets.
# ---------------------------------------------------------------- GLOBAL RULES
# Since the `all` rule appears as the first recipe, it will be the default
# one to be ran
.PHONY: all
all: METAMAKE_ALL
@echo "===== All build finished ====="
.PHONY: clean
clean: METAMAKE_CLEAN
@echo "===== Clean finished ====="
.PHONY: help
help:
@echo "Available targets:"
@echo " all - Build all top level targets"
@echo " clean - Clean intermediate build files"
# ------------------------------------------------------------------ META RULES
# This must be included before any meta rules are used
include Meta.mk
# ........ other stuff - Architecture definition, build rules, etc
Defining Q = @
in the top level Makefile
will set the output to quiet. Defining Q =
will set the output to verbose.
The following fragment is useful to have in the top level makefile if you want the optionally turn verbose mode on or off.
ifeq ("$(origin V)", "command line")
ENABLE_VERBOSE = $(V)
endif
ifndef ENABLE_VERBOSE
ENABLE_VERBOSE = 0
endif
ifeq ($(ENABLE_VERBOSE),1)
Q =
else
Q = @
endif
Calling make V=0
or make V=1
with the above fragment will enable or disable verbose output.
This meta rules defines the compiler prefix and flags that must be used when building for a specified architecture. This section must appear before any meta recipe for the architecture!
The first parameter of the BEGIN_DEFINE_ARCH
is the architecture name; in this example it is MY_ARCH
. The second parameter is the build output directory, where all objects, libraries, and binaries are created; in this example it is build/my_arch
.
$(call BEGIN_DEFINE_ARCH, MY_ARCH, build/my_arch)
PREFIX := # Compiler prefix
ASF := # Assembler flags
CF := # C Flags
CXXF := # C++ Flags
LF := # Linker flags
LL := # Linker last flags
$(call END_DEFINE_ARCH)
- The ASF, CF, CXXF, LF, and LL flags are global flags applied to all meta recipes for the given architecture. These may be appended later with
BEGIN_APPEND_ARCH_FLAGS
andEND_APPEND_ARCH_FLAGS
Below is an example for defining a host (X86) build and ARM build architecture:
$(call BEGIN_DEFINE_ARCH, ARM_CM4, build/arm_cm4)
PREFIX := arm-none-eabi-
ASF := -mcpu=cortex-m4 -mthumb -DCORE_M4
CF := -Os -g3 -Wall -mcpu=cortex-m4 -mthumb -DCORE_M4 \
-std=c99
CXXF := -Os -g3 -Wall -mcpu=cortex-m4 -mthumb -DCORE_M4 \
-std=c++14 -fno-exceptions -fno-rtti
$(call END_DEFINE_ARCH)
$(call BEGIN_DEFINE_ARCH, HOST, build/host)
CF := -O0 -g3 -Wall -std=c99
CXXF := -O0 -g3 -Wall -std=c++14
$(call END_DEFINE_ARCH)
Using this section will append flags for all recipes in the given architecture. This section may appear at any time during the build, since architecture flags are not applied until the build steps are executed.
$(call BEGIN_APPEND_ARCH_FLAGS, MY_ARCH)
ASF := # Assembler flags
CF := # C Flags
CXXF := # C++ Flags
LF := # Linker flags
LL := # Linker last flags
$(call END_APPEND_ARCH_FLAGS)
A build for a specific architecture can be created using the BEGIN_ARCH_BUILD
and END_ARCH_BUILD
calls. The BEGIN_ARCH_BUILD
call takes one parameter, the architecture that is being built for.
$(call BEGIN_ARCH_BUILD, ARM_CM4)
# ... Meta build constructs
$(call END_ARCH_BUILD)
The architecture must be defined before using this construct.
A universal build will build for all available architectures. This is intended for building libraries that are platform independent. Also, the universal build can be used for building executables with a few caveats. See the Usage Tips section for information on that. All the same constructs that can be used in the architecture specific build can be used in the universal build.
$(call BEGIN_UNIVERSAL_BUILD)
# ... Meta build constructs
$(call END_UNIVERSAL_BUILD)
All architectures must be defined before using this construct
Include paths can be added inside of the meta build constructs with ADD_AS_INCLUDE
, ADD_C_INCLUDE
, and ADD_CXX_INCLUDE
ADD_AS_INCLUDE
will append a list of include paths onto the assembler build flagsADD_C_INCLUDE
will append a list of include paths onto the C build flagsADD_CXX_INCLUDE
will append a list of include paths onto the C++ build flags
All of the above constructs take a list of paths as their only argument. The include paths added with these constructs are exportable, see the Exporting Build Dependencies and Importing Build Dependencies sections for more info on that.
Currently, C include paths are treated as a subset of C++ include paths. If a path is added using the ADD_C_INCLUDE
, it will be visible to C++ code as well. However, include paths added with ADD_CXX_INCLUDE
will not be visible to C code, for obvious reasons.
$(call BEGIN_UNIVERSAL_BUILD)
$(call ADD_C_INCLUDE, include/path other/include/path)
$(call ADD_CXX_INCLUDE, include/cxx/path)
# ... #
$(call END_UNIVERSAL_BUILD)
Source files can be built with a single call to BUILD_SOURCE
. This construct will take a list of source files as its only parameter. Currently, the following types of files are compatible:
- .c for C source
- .cpp or .cc for C++ source
- .S for Assembly files
MY_SOURCE_PATH := my/src/path
MY_SOURCE_FILES := $(find $(MY_SOURCE_PATH) -name "*.c") \
$(find $(MY_SOURCE_PATH) -name "*.S")
$(call BEGIN_UNIVERSAL_BUILD)
$(call ADD_C_INCLUDE, my/include/path)
$(call BUILD_SOURCE, $(MY_SOURCE_FILES))
# ... #
$(call END_UNIVERSAL_BUILD)
There are several constructs for adding build flags the the meta build constructs.
ADD_ASF_FLAG
will append to the assembler build flagsADD_CF_FLAG
will append to the C build flagsADD_CXXF_FLAG
will append to the C++ build flagsADD_LF_FLAG
will append the first linker flagsADD_LL_FLAG
will append the last linker flags
All of the above constructs take a list of flags as their first parameter. The LL and LF flags are only used for linking steps. The CF and CXXF flags are used both for linking and source build steps.
Flags added with the above constructs are exportable flags. Hidden flags are supported as well by defining the following variables within the build construct.
- ASF for hidden assembler build flags
- CF for hidden C build flags
- CXXF for hidden C++ build flags
- LF for hidden first linker flags
- LL for hidden last linker flags
The above variables must be defined as a simply expanded variable, i.e. use the :=
notation instead of the =
notation.
$(call BEGIN_ARCH_BUILD, ARM_CM0)
$(call ADD_C_INCLUDE, my/include/path)
$(call ADD_CF_FLAG, -DARM_CM0)
$(call BUILD_SOURCE, $(MY_SOURCE_FILES))
# Hidden flags for changing the linking behavior
CF := -Ildscripts
LF := -nostdlib -T myexec.ld
LL := -lgcc
$(call CXX_LINK, myexec.elf)
$(call END_ARCH_BUILD)
Static libraries are created with the MAKE_LIBRARY
call. This call takes a single parameter for the library base-name to build. Internally, every BUILD_SOURCE
call appends a list of generated objects for the build construct. When then MAKE_LIBRARY
call is made, it will create a static library out of all those objects.
$(call BEGIN_UNIVERSAL_BUILD)
$(call ADD_CXX_INCLUDE, include)
$(call BUILD_SOURCE, foo.cpp bar.cpp)
$(call BUILD_SOURCE, newFeature.cpp)
$(call MAKE_LIBRARY, myCode)
$(call EXPORT_SHALLOW_DEPS, myCode)
$(call END_UNIVERSAL_BUILD)
The above example will take all of take the object files generated in both of BUILD_SOURCE
calls, and create a static library name libmyCode.a
. Note that the MAKE_LIBRARY
call used the libraries base-name, not its full name. That is due to the way that linking rules need to be generated.
Note that if a BUILD_SOURCE
call is made after the MAKE_LIBRARY
call, the objects generated in that step will not make it into the proceeding library call.
Making a static library and using the EXPORT_SHALLOW_DEPS
call is the key to the automatic dependency system. See Importing and Exporting Build Dependencies section for details on how this works.
Linking an executable is done with the CXX_LINK
and CC_LINK
calls.
- If an executable is only being built with objects generated from C code, then the
CC_LINK
call can be used - If an executable is being built from C++ generated objects, or contains a mix of C and C++ objects, the the
CXX_LINK
call should be used
Both calls take the output executable name as their first parameter.
MY_SRC := foo.cpp bar.cpp main.cpp
$(call BEGIN_ARCH_BUILD, ARM_CM4)
$(call ADD_CXX_INCLUDE, $(d)/include)
$(call BUILD_SOURCE, $(MY_SRC))
$(call CXX_LINK, myprogram.elf)
$(call APPEND_ALL_TARGET_VAR)
$(call END_ARCH_BUILD)
The APPEND_ALL_TARGET_VAR
call will add the output executable to the ALL_TARGETS
variable, causing it to be a high-level build target. See Adding Artifacts to the All Target List.
This was the initial raison d'etre for the metaMake project. These constructs allow for small pieces of code to be built into small libraries and pulled into other build constructs with minimal effort.
$(call BEGIN_UNIVERSAL_BUILD)
$(call ADD_CXX_INCLUDE, libfoo/include)
$(call BUILD_SOURCE, foo_1.cpp foo_2.cpp)
$(call MAKE_LIBRARY, foo)
$(call EXPORT_SHALLOW_DEPS, libfoo)
$(call END_UNIVERSAL_BUILD)
$(call BEGIN_UNIVERSAL_BUILD)
$(call ADD_CXX_INCLUDE, libbar/include)
$(call BUILD_SOURCE, bar_1.cpp bar_2.cpp)
$(call MAKE_LIBRARY, bar)
$(call EXPORT_SHALLOW_DEPS, libbar)
$(call END_UNIVERSAL_BUILD)
$(call BEGIN_ARCH_BUILD, ARM_CM4)
$(call IMPORT_DEPS, libbar libfoo)
$(call ADD_CXX_INCLUDE, application/include)
$(call BUILD_SOURCE, main.cpp)
$(call CXX_LINK, application.elf)
$(call APPEND_ALL_TARGET_VAR)
$(call END_ARCH_BUILD)
In the above example, there are two libraries that are being built; libfoo.a
and libbar.a
. There is an executable named application.elf
being built for the ARM_CM4 architecture that depends on libfoo.a
and libbar.a
. With the IMPORT_DEPS
call, the application build construct gains the appropriate include paths and linking steps required to build against the libraries.
- The
EXPORT_SHALLOW_DEPS
call take a human friendly dependency name as its first parameter. This exports only the dependencies for the immediate build construct. This will not export a recursive list of dependencies, see the Shallow Dependency Limitations section for details. - This
IMPORT_DEPS
call takes a list of human friendly dependency names as its first parameter. It is not required that a dependency is exported before it is imported, only that it is present after all build constructs are parsed.
The ALL_TARGETS
variable can be appended with the output of the last build construct rule by using the APPEND_ALL_TARGET_VAR
call. This call does not take any parameters.
- If this call is made after a
CC_LINK
orCXX_LINK
call, it will append the output executable name to theALL_TARGETS
variable. - If this call is made after a
MAKE_LIBRARY
call, then that output library will be appended to theALL_TARGETS
variable - If this call is made after a
BUILD_SOURCE
call, then all the objects generated by that call will be appended to theALL_TARGETS
variable.
It is normally a good idea to only have "top-level" targets added to the ALL_TARGETS
variable, and let make
figure out the required objects to build from there.
The CLEAN_TARGETS
variable contains a list of phony targets that should be called to clean all build artifacts. A clean rule is automatically generated per architecture that is defined, and appended to the CLEAN_TARGETS
variable.
These calls are normally not needed in a build system, but are available for tweaking build construct behavior.
The APPEND_TARGET_TO_VAR
call has the same behavior as the APPEND_ALL_TARGET_VAR
, but takes a variable name as its first parameter. This variable is appended with the name of the output target in the previous build construct command.
OUTPUT_ELF_NAME :=
$(call BEGIN_ARCH_BUILD, ARM_CM4)
$(call ADD_CXX_INCLUDE, application/include)
$(call BUILD_SOURCE, main.cpp foo.cpp bar.cpp)
$(call CXX_LINK, application.elf)
$(call APPEND_TARGET_TO_VAR, OUTPUT_ELF_NAME)
$(call END_ARCH_BUILD)
$(OUTPUT_ELF_NAME:%.elf=%.hex): $(OUTPUT_ELF_NAME)
$(call GET_ARCH_PREFIX, ARM_CM4)objcopy -O ihex $< $@
This example gets the complete filename of the linking step output, and uses it to generate a hex file using the objcopy tool.
Custom build dependencies can be added with the ADD_CUSTOM_BUILD_DEP
call. The first parameter of this call takes a list of real or phony target names (not to be confused with the human friendly dependency names used in the other build constructs). Build dependencies will block any BUILD_SOURCE
calls from running until the build dependency has been satisfied. A build dependency is special in that it is the ONLY dependency information that is exported recursively. Most of the time this is used to block the building of source until headers are moved into pace. Use this with caution.
Similar to the ADD_CUSTOM_BUILD_DEP
call, ADD_CUSTOM_LINK_DEP
will add custom dependencies to CC_LINK
and CXX_LINK
output targets. Use this with caution, but it allows object files to be custom inserted into the linking steps.
The FIND_SOURCE_IN_DIR
call can be used to recursively find source in a directory. This call takes a directory as its first parameter. It will search for the following types of files:
- .c for C source
- .cpp and .cc for C++ source
- .s or .S for Assembly source
It is recommended to use a simply expanded variable for this call, else it will cause additional overhead (use :=
instead of =
)
MY_SOURCE := $(call FIND_SOURCE_IN_DIR, my/source)
It can sometimes be useful to get at the compiler prefix used by the meta build constructs when writing custom recipes. The GET_ARCH_PREFIX
call takes an architecture as its first parameter, and returns the compiler prefix.
application.hex: application.elf
$(call GET_ARCH_PREFIX, ARM_CM4)objcopy -O ihex $< $@
Universal builds may be mixed with architecture specific builds, with a few caveats. Under the hood, the meta make fragment tracks universal builds using the exported dependency names. When the end of the universal build step is reached, the build rule is expanded for all known architectures. To mix universal builds and architecture specific builds, it is recommended that the architecture specific build be defined first.
MY_SRC = src/foo.cpp src/bar.cpp
$(call BEGIN_ARCH_BUILD, ARM_CM4)
$(call ADD_CXX_INCLUDE, $(d)/include)
$(call ADD_CXXF_FLAG, -DSPECIAL_ARM_CM4_BUILD_FLAG)
$(call BUILD_SOURCE, $(MY_SRC))
$(call MAKE_LIBRARY, myLib)
$(call EXPORT_SHALLOW_DEPS, myLib)
$(call END_ARCH_BUILD)
$(call BEGIN_UNIVERSAL_BUILD)
$(call ADD_CXX_INCLUDE, $(d)/include)
$(call BUILD_SOURCE, $(MY_SRC))
$(call MAKE_LIBRARY, myLib)
$(call EXPORT_SHALLOW_DEPS, myLib)
$(call END_UNIVERSAL_BUILD)
This can be useful as well for unit testing code which expects to build against platform specific code. In most cases, it is fine to expose just the headers to the code under test to get it to compile.
$(call BEGIN_ARCH_BUILD, ARM_CM4)
$(call ADD_C_INCLUDE, $(d)/include)
$(call BUILD_SOURCE, $(STM32F4_DRIVERS_SRC))
$(call MAKE_LIBRARY, stm32f4_drivers)
$(call EXPORT_SHALLOW_DEPS, stm32f4_drivers)
$(call END_ARCH_BUILD)
$(call BEGIN_UNIVERSAL_BUILD)
$(call ADD_C_INCLUDE, $(d)/include)
$(call EXPORT_SHALLOW_DEPS, stm32f4_drivers)
$(call END_UNIVERSAL_BUILD)
The above fragment only builds for ARM_CM4
, and only exposes its headers for any other architecture builds.
Under the hood, the universal build system is generating a build step string. Once the universal build step is ended, the build string is evaluated for all known architectures. By appending the output targets to the ALL_TARGETS variable every time the build rule is evaluated for an architecture, a target can be build for all architectures defined in the build system.
$(call BEGIN_UNIVERSAL_BUILD)
$(call ADD_C_INCLUDE, $(d)/include)
$(call BUILD_SOURCE, $(SOURCE_$(d)))
$(call CC_LINK, myExecutable.elf)
$(call APPEND_ALL_TARGET_VAR)
# See the list of bugs, all universal build rules MUST export a dependency
$(call EXPORT_SHALLOW_DEPS, myExecutable.elf)
$(call END_UNIVERSAL_BUILD)
Release and debug builds can be generated by defining multiple architectures.
$(call BEGIN_DEFINE_ARCH, HOST_RELEASE, build/host/release)
PREFIX :=
CF := -O3
$(call END_DEFINE_ARCH)
$(call BEGIN_DEFINE_ARCH, HOST_DEBUG, build/host/debug)
PREFIX :=
CF := -O0 -g3
$(call END_DEFINE_ARCH)
Sometimes it is useful to build a code fragment as both a library and an executable. In this example the library only source is built first, then a library created. After the library is created, the executable only source is built and an executable is linked (containing both the library objects and executable objects).
INCLUDE_DIR := include
LIB_SOURCE := src/foo.cpp src/bar.cpp
EXEC_SOURCE := src/main.cpp
LIB_NAME := mylib
EXEC_NAME := myexec.elf
$(call BEGIN_ARCH_BUILD, ARM_CM4)
$(call ADD_CXX_INCLUDE, $(INCLUDE_DIR))
$(call BUILD_SOURCE, $(LIB_SOURCE))
$(call MAKE_LIBRARY, $(LIB_NAME))
$(call EXPORT_SHALLOW_DEPS, $(LIB_NAME))
LF := -T ldscripts/myexec.ld
$(call BUILD_SOURCE, $(EXEC_SOURCE))
$(call CXX_LINK, $(EXEC_NAME))
$(call END_ARCH_BUILD)
There is no built-in meta rule to do this, but thats what you get for wanting to use some other build system. However, it is not difficult to do.
EXTERNAL_LIB_FOLDER := plebeian_code/extlib
EXTERNAL_LIB_HEADERS := $(EXTERNAL_LIB_FOLDER)/include
EXTERNAL_LIB_FILE := $(EXTERNAL_LIB_FOLDER)/build/libextlib.a
$(EXTERNAL_LIB_FILE):
cd $(EXTERNAL_LIB_FOLDER) && $(MAKE)
$(call BEGIN_ARCH_BUILD, HOST)
$(call ADD_CXX_INCLUDE, $(EXTERNAL_LIB_HEADERS))
$(call ADD_CUSTOM_LINK_DEP, $(EXTERNAL_LIB_FILE))
$(call ADD_LF_FLAG, -L$(strip $(dir $(EXTERNAL_LIB_FILE))))
$(call ADD_LL_FLAG, -l$(strip $(EXTERNAL_LIB_FILE)))
$(call EXPORT_SHALLOW_DEPS, extlib)
$(call END_ARCH_BUILD)
The ADD_CUSTOM_LINK_DEP
will cause recipes that import ````extlib``` to depend on the external library build recipe completing before trying to link against it. The rest is manually adding in the linker flags.
- TODO: Write docs for internal variables
- TODO: Write docs for internal rules
- TODO: Write docs for describing how the build constructs work
Version information for this project can be found in the variables METAMAKE_MAJOR_VERSION
and METAMAKE_MINOR_VERSION
. The major version will increment every time backwards compatibility in the metaMake API is broken. The minor number should increment between behavior changes within the API.
Things that are still in development
- Examples
- Documentation
- Cleanup the variable names in the meta-rules
- Add debug information to the meta rules
- Parameter sanity checks on all call and eval meta rules
- Add settings to disable pretty messages in the meta rules
Currently, the universal build rules must NOT be used for anything that does not export dependencies, else the build rule will not get expanded. This is currently a limitation that allows for universal builds to be mixed with architecture specific builds. This behavior may change in the future.
Currently, there is only shallow dependency exporting, which is mostly due to recursive dependency resolution being a very complex problem to solve using Makefile meta rules.
$(call BEGIN_UNIVERSAL_BUILD)
$(call ADD_CXX_INCLUDE, mylib_1/include)
$(call BUILD_SOURCE, $(MY_LIB_1_SRC))
$(call MAKE_LIBRARY, mylib_1)
$(call EXPORT_SHALLOW_DEPS, mylib_1)
$(call END_UNIVERSAL_BUILD)
$(call BEGIN_UNIVERSAL_BUILD)
$(call IMPORT_DEPS, mylib_1)
$(call ADD_CXX_INCLUDE, mylib_2/include)
$(call BUILD_SOURCE, $(MY_LIB_2_SRC))
$(call MAKE_LIBRARY, mylib_2)
$(call EXPORT_SHALLOW_DEPS, mylib_2)
$(call END_UNIVERSAL_BUILD)
$(call BEGIN_ARCH_BUILD, HOST)
$(call IMPORT_DEPS, mylib_2)
$(call ADD_CXX_INCLUDE, application/include)
$(call BUILD_SOURCE, main.cpp)
$(call CXX_LINK, application)
$(call END_ARCH_BUILD)
In the above example, application
will fail to build, since application is only importing dependencies for mylib_2
, when it really depends on mylib_2
and then on mylib_1
.
The correct work around to the above problem is for application to have the following import line instead:
# ... #
$(call IMPORT_DEPS, mylib_2 mylib_1)
# ... #
Note that since mylib_2
and mylib_1
are both static libraries, their linking order actually matters. If you are not familiar with this problem, there are many great resources on the net that describe linking order issues involved with static libraries.
This gets even more difficult to solve if two libraries end up depending on each other (directly or indirectly). My recommendation is to design your build constructs in layers, where each layer can only depend on constructs in layers below it, not on constructs in the same layer or above. Better yet, try to keep libraries self contained, then this problem goes away completely.
If you do run into a problem with circular dependencies, and reorganizing the libraries is not an option, then a quick-fix solution would be to just import the dependencies multiple times until the issue goes away.
# Last resort fix, don't do this!!!
# ... #
$(call IMPORT_DEPS, mylib_2 mylib_1 mylib_2 mylib_1)
# ... #
Currently, it is not recommended to export dependencies more than once from a build rule. It will work, but probably not the way you want. For example:
MINIMAL_SRC := foo.cpp bar.cpp
FEATURE_Y_SRC := newFeature.cpp
$(call BEGIN_UNIVERSAL_BUILD)
# Build minimal library features
$(call ADD_CXX_INCLUDE, include/minimal)
$(call BUILD_SOURCE, $(MINIMAL_SRC))
$(call MAKE_LIBRARY, mylib_minimal)
$(call EXPORT_SHALLOW_DEPS, mylib_minimal)
# Build in extra features, inheriting all objects generated in
# the previous build step
# Don't do this!
$(call ADD_CXX_INCLUDE, include/newFeature)
$(call BUILD_SOURCE, $(FEATURE_Y_SRC))
$(call MAKE_LIBRARY, mylib_full)
$(call EXPORT_SHALLOW_DEPS, mylib_full)
$(call END_UNIVERSAL_BUILD)
The mylib_full
will contain the same objects as mylib_minimal
, plus the new feature objects. However, this will create a very inefficient linking step, where anyone importing mylib_full
as a dependency will also get the linking step for mylib_minimal
. This behavior is not sane, and will probably change in the future.
There is currently nothing in place to stop a build rule being generated multiple times for the same object file. If you do this, you're going to have a bad time.
MY_SRC := foo.cpp bar.cpp
$(call BEGIN_UNIVERSAL_BUILD)
$(call ADD_CXX_INCLUDE, include)
$(call BUILD_SOURCE, $(MY_SRC))
$(call MAKE_LIBRARY, mylib_1)
$(call END_UNIVERSAL_BUILD)
# Don't do this!
$(call BEGIN_UNIVERSAL_BUILD)
$(call ADD_CXX_INCLUDE, include)
$(call ADD_CXXF_FLAG, -DENABLE_COOL_FEATURE)
$(call BUILD_SOURCE, $(MY_SRC))
$(call MAKE_LIBRARY, mylib_2)
$(call END_UNIVERSAL_BUILD)
MIT license for all files.