Each makefile follows the same pattern: it does some setup, includes
the standard make rules, and provides rules for eight standard targets:
all, install, test, run-test, clean, dist, and doc-update.
"Some setup" is defining two variables: $(thisdir) and
$(SUBDIRS). $(thisdir) is the directory that the makefile lives in,
relative to the top directory (ie, class/corlib) and $(SUBDIRS)
defines the subdirectories that should be built in.
The eight targets do the following:
* all-local builds whatever someone would expect to be built
when they just type 'make'. Most likely Foo.dll or Foo.exe
* install-local installs whatever got built by all-local.
* test-local _builds_ the test programs or libraries but does
_not_ run them.
* run-test-local actually runs the tests. It shouldn't
necessarily exit in an error if the test fails, but should make that
situation obvious. It should only run tests that take care of
themselves automatically; interactive tests should have an individual
target. The idea is that 'make run-test' from the toplevel should be
able to proceed unsupervised and test everything that can be tested in
such a manner.
* run-test-ondotnet-local is a variant of run-test-local. It is used only to validate if our tests themselves works fine under Microsoft runtime (on Windows). Basically, in this target, we should not use $(TEST_RUNTIME) to test our libraries.
* clean-local removes built files; 'make clean' should leave
only files that go into a distribution tarball. (But it is not necessarily
true that all files that go into a tarball need to be left after a make clean.)
* dist-local copies files into the distribution tree, which is
given by the variable $(distdir). dist-local always depends on the
target 'dist-default'. See ** 'make dist' below.
* doc-update-local should generate or update monodoc documentation,
if appropriate. This is usually only appropriate for libraries. It's
defined as a standard target so that it can easily be run recursively
across all libraries within the module.
** Build configuration
In general, MCS needs to be able to build relying only on the
existence of a runtime and core libraries (corlib, System,
System.Xml). So there shouldn't be any checking for libraries or
whatnot; MCS should be able to build out of the box. We try to keep
platform detection and feature testing (ie, for HP/UX echo) inside
the makefiles; right now, there's no configuration script, and it'd
be nice to keep it that way. (I am told that some people build on
both Windows and Linux in the same tree, which would be impossible to
do if we cached platform-related configury values.)
That being said, it's very convenient for developers to be able to
customize their builds to suit their needs. To allow this, the
Makefile rules are set up to allow people to override pretty much any
important variable.
Configuration variables are given defaults in `config-default.make';
`rules.make' optionally includes `$(topdir)/build/config.make', so you
can customize your build without CVS trying to commit your modified
`config-default.make' all the time. Platform-specific variables are
defined in `$(topdir)/build/platforms/$(PLATFORM).make', where
$(PLATFORM) is detected in config-default.make. (Currently, the only
choices are linux.make and win32.make.)
The best way to learn what the configuration variables are is to read
`config.make' and `platform.make'. There aren't too many and hopefully
they should be self-explanatory; see the numerous examples below for
more information if you're confused.
** Recommendations for platform specifics
If you find yourself needing a platform-specific customization, try
and express it in terms of a feature, rather than a platform test. In
other words, this is good:
========================================
run-test-local: my-test.exe
ifdef PLATFORM_NEEDS_CRAZY_CRAP
crazy-crap
endif
$(RUNTIME) my-test.exe
========================================
and this is bad:
========================================
run-test-local: my-test.exe
ifdef WINDOWS
crazy-crap
else
ifdef AMIGA
crazy-crap
endif
endif
$(RUNTIME) my-test.exe
========================================
The latter accumulates and gets unpleasant and it sucks. Granted,
right now we only have two platforms, so it's not a big deal, but it's
good form to get used to and practice. Anyway, take a look at how we
do the various corlib building hacks for examples of how we've done
platform-specificity. It certainly isn't pretty, but at least it's a
little structured.
** Saving effort
The point of the build system is to abstract things and take
care of all the easy stuff. So if you find yourself writing a
Makefile, know that there's probably already infrastructure to do what
you want. Here are all the common cases I can think of ...
* Compiling C# code? use:
========================================
my-program.exe: my-source.cs
$(CSCOMPILE) /target:exe /out:$@ $^
========================================
or
========================================
my-lib.dll: my-source.cs
$(CSCOMPILE) /target:library /out:$@ $^
========================================
Note the '$@' and '$^' variables. The former means "the name of the
file that I am trying to make" and the latter means "all the
dependencies of the file I am trying to make." USE THESE VARIABLES
AGGRESSIVELY. Say that you add a new source to your program:
========================================
my-program.exe: my-source.cs my-new-source.cs
$(CSCOMPILE) /target:exe /out:$@ $^
========================================
Because of the $^ variable, you don't need to remember to add another
file to the command line. Similarly, if you rename your program, you
won't need to remember to change the rule:
========================================
MonoVaporizer.exe: my-source.cs my-new-source.cs
$(CSCOMPILE) /target:exe /out:$@ $^
========================================
will still work. Another useful variable is $<, which means "the first
dependency of whatever I'm building." If you order your dependencies
carefully it can be extremely useful.
* Just building an executable? use:
========================================
PROGRAM = myprogram.exe
LOCAL_MCS_FLAGS = /r:System.Xml.dll
include ../build/executable.make
========================================
executable.make builds a program in the current directory. Its name is
held in $(PROGRAM), and its sources are listed in the file
$(PROGRAM).sources. It might seem to make more sense to just list the
program's sources in the Makefile, but when we build on Windows we
need to change slashes around, which is much easier to do if the
sources are listed in a file. The variable $(LOCAL_MCS_FLAGS) changes
the flags given to the compiler; it is included in $(CSCOMPILE) so you
don't need to worry about it.
executable.make does a lot for you: it builds the program in 'make
all-local', installs the program in $(prefix)/bin, distributes the
sources, and defines empty test targets. Now, if your program has a
test, set the variable HAS_TEST:
========================================
PROGRAM = myprogram.exe
LOCAL_MCS_FLAGS = /r:System.Xml.dll
HAS_TEST = yes
include ../build/executable.make
test-local: mytester.exe
run-test-local: mytester.exe
$(RUNTIME) $<
mytester.exe: mytester.cs
$(CSCOMPILE) /target:exe /out:$@ mytester.cs
========================================
If your program has NUnit tests, set the variable HAS_NUNIT_TEST:
========================================
PROGRAM = myprogram.exe
LOCAL_MCS_FLAGS = /r:System.Xml.dll
HAS_NUNIT_TEST = yes
include ../build/executable.make
========================================
HAS_NUNIT_TEST tests follow library.make NUnit test conventions:
the files should be in a subdirectory called Test/, and if
your program is called myprogram.exe, they should be listed in
myprogram_test.dll.sources. The names in that files should *not* have
the Test/ prefix. 'make test' will build myprogram_test_$(PROFILE).dll
in the current directory, automatically supplying the flags to
reference the original program and NUnit.Framework.dll.
If your program has 'built sources', that is, source files generated
from other files (say, generated by jay), define a variable called
BUILT_SOURCES and do *not* list the sources in $(PROGRAM).sources:
========================================
PROGRAM = myprogram.exe
LOCAL_MCS_FLAGS = /r:System.Xml.dll
BUILT_SOURCES = parser.cs
CLEAN_FILES = y.output
include ../build/executable.make
parser.cs: parser.jay
$(topdir)/jay/jay $< > $@
========================================
executable.make will automatically delete the $(BUILT_SOURCES) files
on 'make clean'. Since this situation is a common occurrence and jay
happens to leave behind y.output files, you can also define a variable
called $(CLEAN_FILES) that lists extra files to be deleted when 'make clean' is
called. (That's in addition to your executable and the built sources).
* Buildling a library? Use
========================================
LIBRARY = Mono.MyLib.dll
LIB_MCS_FLAGS = /unsafe
TEST_MCS_FLAGS = /r:System.Xml.dll
include ../../build/library.make
========================================
Where you library is called $(LIBRARY); it will be put into
$(topdir)/class/lib. LIB_MCS_FLAGS is the set of MCS flags to use when
compiling the library; in addition, a global set of flags called
$(LIBRARY_FLAGS) is added (that variable is defined in
config-defaults.make), as well as the usual $(LOCAL_MCS_FLAGS).
As in executable.make, the sources for your library are listed in
$(LIBRARY).sources. Note: these source lists should have Unix forward
slashes and Unix newlines (\n, not \r\n.) If you get an error about
"touch: need a filename", that means your .sources file doesn't end in
a newline. It should.
Now library.make also assumes that your library has an NUnit2 test
harness. The files should be in a subdirectory called Test/, and if
your library is called Mono.Foo.dll, they should be listed in
Mono.Foo_test.dll.sources. The names in that files should *not* have
the Test/ prefix. 'make test' will build Mono.Foo_test.dll in the
current directory, automatically supplying the flags to reference the
original library and NUnit.Framework.dll.
If you don't have a test, just do this:
========================================
LIBRARY = Mono.MyLib.dll
LIB_MCS_FLAGS = /unsafe
NO_TEST = yes
include ../../build/library.make
========================================
and feel ashamed. Every good library has a test suite!
Extra flags needed to compile the test library should be listed in
$(TEST_MCS_FLAGS); often you will have a line like this:
========================================
TEST_MCS_FLAGS = $(LIB_MCS_FLAGS)
========================================
Again, library.make does a lot for you: it builds the dll, it
generates makefile fragments to track the dependencies, it installs
the library, it builds the test dll on 'make test', it runs
$(TEST_HARNESS) on it on 'make run-test', it removes the appropriate
files on 'make clean', and it distributes all the source files on
'make dist'. (TEST_HARNESS defaults to be nunit-console.exe but it may
be overridden to, say, nunit-gtk). If you have extra files to
distribute when using either library.make or executable.make, use the
variable $(EXTRA_DISTFILES):
========================================
EXTRA_DISTFILES = \
Test/testcase1.in \
Test/testcase1.out \
README
========================================
Again, library.make and executable.make do the right things so that we
can build on Windows, doing some trickery to invert slashes and
overcome command-line length limitations. Use them unless you have a
really good reason not to. If you're building a bunch of small
executables, check out tools/Makefile or tools/security/Makefile; if
all the files are in the current directory, changing slashes isn't a
big deal, and command-line lengths won't be a problem, so
executable.make isn't necessary (and indeed it won't work, since it
can only build one .exe in a directory).
If you're building a library, library.make is highly recommended; the
only DLL that doesn't use it is corlib, because building corlib is a
fair bit more complicated than it should be. Oh well.
library.make also automatically supports generating and updating
monodoc documentation. Documentation is stored within the
Documentation directory (a sibling to the Test directory), and is
generated/updated whenever the doc-update target is executed.
Assembling of the documentation so that the monodoc browser can
display the documentation is handled separately within the mcs/docs
all-local target; see mcs/docs/Makefile for details.
* Running a C# program? Use $(RUNTIME)
========================================
run-test-local: myprog.exe
$(RUNTIME) myprog.exe
========================================
$(RUNTIME) might be empty (if you're on windows), so don't expect to
be able to give it any arguments. If you're on a platform which has an
interpreter or jitter, $(RUNTIME_FLAGS) is included in $(RUNTIME), so
set that variable.
$(TEST_RUNTIME) is the runtime to use when running tests. Right now it's
just "mono --debug".
* Calling the compiler directly? Use $(MCS).
Really, you should use $(CSCOMPILE) whenever possible, but $(MCS) is
out there. $(BOOTSTRAP_MCS) is the C# compiler that we use to build
mcs.exe; on Linux, we then use mcs.exe to build everything else, but
on Windows, we use csc.exe to build everything. Only use
$(BOOTSTRAP_MCS) if you know what you're doing.
* Compiling C code? Use $(CCOMPILE)
To give it flags, set $(LOCAL_CFLAGS). As with compiling C#, the
variable $(CFLAGS) will automatically be included on the command line.