User:Solar/Makefile: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
Content added Content deleted
(Some comments.)
(Some better explanations.)
Line 5: Line 5:
----
----


Sometimes, your project consists of more than one binary: You have to build several libraries and executables from a single source tree, but with different compiler / linker settings. This, too, can be comfortably covered with a single Makefile, but requires some more involved syntax. One example is presented here; there are, of course, many other ways to achieve this.
As an extension to [[Makefile]], this should outline how one could build several binaries with different settings using a single Makefile.


The idea is to create a subdirectory for each module (executable or library). In each these module subdirectories, module-specific settings are stored in a file <tt>build.mk</tt>.
The basic idea is to create a subdirectory for each module (executable or library). In each these module subdirectories, the overall structure is identical:


* module-specific settings are stored in a file <tt>build.mk</tt>;
Each module subdirectory contains a directory <tt>includes</tt>, where all headers with "global" visibility are stored (i.e., headers visible to other modules). All other headers and source files are located in <tt>src</tt>, with object files being stored in <tt>obj</tt> by the compiler.
* headers with "global" project visibility (i.e., visible to other modules) are stored in <tt>includes</tt>;
* all other headers and source files are located in <tt>src</tt>;
* a directory <tt>obj</tt> exists for the compiler to put its object files.

At the top level, there is a directory <tt>bin</tt> and a directory <tt>lib</tt> for binaries and libraries, respectively. A directory <tt>includes</tt> will eventually contain links to all the "global" headers. And, of course, there is our <tt>Makefile</tt>.


<pre>
<pre>
Line 19: Line 24:
│   ├── obj
│   ├── obj
│   └── src
│   └── src
│   ├── bar_local.h
│   ├── interface.c
│   └── bar_test.c
│   ├── interface.h
└── main.c
├── baz
├── build.mk
├── includes
└── global.h
├── obj
└── src
├── hashmap.c
├── hashmap.h
│ └── algorithm.c
├── bin
├── bin
├── foo
   ├── build.mk
   ├── includes
     └── global.h
   ├── obj
   └── src
   ├── foo_local.h
   ├── foo_test.c
   └── main.c
├── includes
├── includes
├── lib
├── lib
Line 36: Line 42:
</pre>
</pre>


The contents of the several <tt>build.mk</tt> files is similar to what is done in <tt>Makefile.am</tt> for Automake:
The contents of the individual modules' <tt>build.mk</tt> files is similar to what is done in <tt>Makefile.am</tt> for Automake.

Example for a binary module:

<pre>
# CFLAGS for module 'bar'
CFLAGS_bar :=

# Executable to build in module 'bar'
bar_PROGRAM := bar

# Libraries that the executable depends on:
bar_LIBRARIES := libbaz.a

# Sources for the executable 'bar' (without headers)
bar_SOURCES := interface.c main.c
</pre>

Example for a library module:


<pre>
<pre>
# CFLAGS for module 'foo'
# CFLAGS for module 'baz'
CFLAGS_baz :=
CFLAGS_foo := -Wall


# Executables to build in module 'foo'
# Archive / Lib to build in module 'baz'
baz_ARCHIVE := libbaz.a
foo_PROGRAMS := foo
# Archives / Libs to build in module 'foo'
foo_ARCHIVES := foolib.a


# Sources for the executable 'foo' (without headers)
# Sources for the archive / lib 'libbaz.a' (without headers)
libbaz.a_SOURCES := hashmap.c algorithm.c
foo_SOURCES := main.c
# Sources for the archive / lib 'foolib.a' (without headers)
foolib.a_SOURCES := foo_test.c
</pre>
</pre>


The main Makefile makes heavy use of GNU make's template functionality. It might look daunting at first, but if you look at it carefully, you will see it is pretty straightforward, and easily adaptable or expandable.
The main Makefile looks like this:


<!-- No, this isn't bash. No, the syntax highlighter doesn't know "make". Let's have at least a LITTLE bit of color. -->
<!-- No, this isn't bash. No, the syntax highlighter doesn't know "make". Let's have at least a LITTLE bit of color. -->
<source lang="bash">
<source lang="bash">
# Modules in the project (you coul 'find' these, but stating
# Modules in the project (you could 'find' these, but stating
# them explicitly allows for subdirectories like 'tmp' or 'doc'
# them explicitly allows for subdirectories like 'tmp' or 'doc'
# without upsetting the build process.
# without upsetting the build process.
MODULES := foo bar
MODULES := bar baz


# Global CFLAGS. Add to them if you must, but don't remove '-MMD -I includes'.
# Global CFLAGS. Add to them if you must, but don't remove '-MMD -I includes',
# which is used for header dependency tracking.
CFLAGS := -MMD -I includes
CFLAGS_global := -MMD -I includes
# Global ARFLAGS.
# Global ARFLAGS.
ARFLAGS := rc
ARFLAGS := rc
Line 75: Line 96:
###################################################################
###################################################################
# What follows are several templates (think "functions"), which are
# What follows are several templates (think "functions"), which are
# later instantiated for each registered module.
# later instantiated for each registered module ($(1) being the
# module name).
###################################################################
###################################################################


Line 83: Line 105:
endef
endef


# Setting a module's build rules for object files
# Setting a module's build rules for object files in <module>/obj.
define RULES_template
define RULES_template
$(1)/obj/%.o: $(1)/src/%.c
$(1)/obj/%.o: $(1)/src/%.c
$$(CC) $$(CFLAGS) $$(CFLAGS_$(1)) -c $$< -o $$@
$$(CC) $$(CFLAGS) $$(CFLAGS_global) $$(CFLAGS_$(1)) -c $$< -o $$@
endef
endef


# Setting a module's build rules for executable targets.
# Setting a module's build rules for executable targets.
# (Depending on its sources' object files and any libraries.)
# Also adds a module's dependency files to the global list.
# Also adds a module's dependency files to the global list.
define PROGRAM_template
define PROGRAM_template
Line 97: Line 120:
endef
endef


# Setting a module's build rules for archive targets
# Setting a module's build rules for archive targets.
# (Depending on its sources' object files.)
define ARCHIVE_template
define ARCHIVE_template
DEPENDENCIES := $(DEPENDENCIES) $(patsubst %,$(2)/obj/%.d,$(basename $($(1)_SOURCES)))
DEPENDENCIES := $(DEPENDENCIES) $(patsubst %,$(2)/obj/%.d,$(basename $($(1)_SOURCES)))
Line 105: Line 129:


# Linking a module's global includes into the global include directory
# Linking a module's global includes into the global include directory
# (where they will be available as <module>/filename.h)
# (where they will be available as <module>/filename.h).
define INCLUDE_template
define INCLUDE_template
ifeq ($(wildcard includes/$(1)),)
ifeq ($(wildcard includes/$(1)),)
Line 112: Line 136:
endef
endef


# Now, instantiating the templates for each module.
$(foreach module,$(MODULES),$(eval include $(module)/build.mk))
$(foreach module,$(MODULES),$(eval include $(module)/build.mk))
$(foreach module,$(MODULES),$(eval $(call RULES_template,$(module))))
$(foreach module,$(MODULES),$(eval $(call RULES_template,$(module))))
$(foreach module,$(MODULES),$(eval $(foreach binary,$($(module)_PROGRAMS),$(call PROGRAM_template,$(binary),$(module)))))
$(foreach module,$(MODULES),$(eval $(foreach binary,$($(module)_PROGRAM),$(call PROGRAM_template,$(binary),$(module)))))
$(foreach module,$(MODULES),$(eval $(foreach library,$($(module)_ARCHIVES),$(call ARCHIVE_template,$(library),$(module)))))
$(foreach module,$(MODULES),$(eval $(foreach library,$($(module)_ARCHIVE),$(call ARCHIVE_template,$(library),$(module)))))
$(foreach module,$(MODULES),$(eval $(call INCLUDE_template,$(module))))
$(foreach module,$(MODULES),$(eval $(call INCLUDE_template,$(module))))



Revision as of 07:38, 10 March 2012

Note: This isn't finished, just a rough draft. I didn't test this exhaustively, and will probably not do so anytime soon. But what's there already might be helpful.




Sometimes, your project consists of more than one binary: You have to build several libraries and executables from a single source tree, but with different compiler / linker settings. This, too, can be comfortably covered with a single Makefile, but requires some more involved syntax. One example is presented here; there are, of course, many other ways to achieve this.

The basic idea is to create a subdirectory for each module (executable or library). In each these module subdirectories, the overall structure is identical:

  • module-specific settings are stored in a file build.mk;
  • headers with "global" project visibility (i.e., visible to other modules) are stored in includes;
  • all other headers and source files are located in src;
  • a directory obj exists for the compiler to put its object files.

At the top level, there is a directory bin and a directory lib for binaries and libraries, respectively. A directory includes will eventually contain links to all the "global" headers. And, of course, there is our Makefile.

.
├── bar
│   ├── build.mk
│   ├── includes
│   │   └── global.h
│   ├── obj
│   └── src
│       ├── interface.c
│       ├── interface.h
│       └── main.c
├── baz
│   ├── build.mk
│   ├── includes
│   │   └── global.h
│   ├── obj
│   └── src
│       ├── hashmap.c
│       ├── hashmap.h
│       └── algorithm.c
├── bin
├── includes
├── lib
└── Makefile

The contents of the individual modules' build.mk files is similar to what is done in Makefile.am for Automake.

Example for a binary module:

# CFLAGS for module 'bar'
CFLAGS_bar := 

# Executable to build in module 'bar'
bar_PROGRAM := bar

# Libraries that the executable depends on:
bar_LIBRARIES := libbaz.a

# Sources for the executable 'bar' (without headers)
bar_SOURCES := interface.c main.c

Example for a library module:

# CFLAGS for module 'baz'
CFLAGS_baz :=

# Archive / Lib to build in module 'baz'
baz_ARCHIVE := libbaz.a

# Sources for the archive / lib 'libbaz.a' (without headers)
libbaz.a_SOURCES := hashmap.c algorithm.c

The main Makefile makes heavy use of GNU make's template functionality. It might look daunting at first, but if you look at it carefully, you will see it is pretty straightforward, and easily adaptable or expandable.

# Modules in the project (you could 'find' these, but stating
# them explicitly allows for subdirectories like 'tmp' or 'doc'
# without upsetting the build process.
MODULES := bar baz

# Global CFLAGS. Add to them if you must, but don't remove '-MMD -I includes',
# which is used for header dependency tracking.
CFLAGS_global := -MMD -I includes
# Global ARFLAGS.
ARFLAGS := rc

.PHONY: clean mrproper

# Add whatever should be your default / global target.
all:
	@echo "Default target."

###################################################################
# What follows are several templates (think "functions"), which are
# later instantiated for each registered module ($(1) being the
# module name).
###################################################################

# Including a module's build.mk
define MK_template
include $(1)/build.mk
endef

# Setting a module's build rules for object files in <module>/obj.
define RULES_template
$(1)/obj/%.o: $(1)/src/%.c
	$$(CC) $$(CFLAGS) $$(CFLAGS_global) $$(CFLAGS_$(1)) -c $$< -o $$@
endef

# Setting a module's build rules for executable targets.
# (Depending on its sources' object files and any libraries.)
# Also adds a module's dependency files to the global list.
define PROGRAM_template
DEPENDENCIES := $(DEPENDENCIES) $(patsubst %,$(2)/obj/%.d,$(basename $($(1)_SOURCES)))
bin/$(1): $(patsubst %,$(2)/obj/%.o,$(basename $($(1)_SOURCES))) $(foreach library,$($(1)_LIBRARIES),lib/$(library))
	$$(LD) $$(LDFLAGS) $$(LDFLAGS_$(2)) $$^ -o $$@
endef

# Setting a module's build rules for archive targets.
# (Depending on its sources' object files.)
define ARCHIVE_template
DEPENDENCIES := $(DEPENDENCIES) $(patsubst %,$(2)/obj/%.d,$(basename $($(1)_SOURCES)))
lib/$(1): $(patsubst %,$(2)/obj/%.o,$(basename $($(1)_SOURCES)))
	$$(AR) $$(ARFLAGS) $$@ $$?
endef

# Linking a module's global includes into the global include directory
# (where they will be available as <module>/filename.h).
define INCLUDE_template
ifeq ($(wildcard includes/$(1)),)
    $$(shell ln -s ../$(1)/includes includes/$(1))
endif
endef

# Now, instantiating the templates for each module.
$(foreach module,$(MODULES),$(eval include $(module)/build.mk))
$(foreach module,$(MODULES),$(eval $(call RULES_template,$(module))))
$(foreach module,$(MODULES),$(eval $(foreach binary,$($(module)_PROGRAM),$(call PROGRAM_template,$(binary),$(module)))))
$(foreach module,$(MODULES),$(eval $(foreach library,$($(module)_ARCHIVE),$(call ARCHIVE_template,$(library),$(module)))))
$(foreach module,$(MODULES),$(eval $(call INCLUDE_template,$(module))))

# Include the dependency files (generated by GCC's -MMD option)
-include $(sort $(DEPENDENCIES))

clean:
	$(RM) $(foreach mod,$(MODULES),$(mod)/obj/*.o)

mrproper: clean
	$(RM) $(foreach mod,$(MODULES),$(mod)/obj/*.d) includes/* bin/* lib/*