The manual describes how do do simple integration with
gcc -M. This page describes how to go further, describing how to integrate with the Visual Studio alternative, and how to deal with generated imports.
The C pattern of
#include files is probably the most brutal, so we focus on it first.
This pattern also generalising to linking, for example with Haskell.
gcc has the
-MMD flag to generate a Makefile, the Visual Studio compiler
cl does not. However, it does have a flag
-showincludes which writes the include files on stdout as they are used. The initial example could be written using
Stdout stdout <- cmd "cl -showincludes -c" [input] ["-Fo" ++ output] need [ dropWhile isSpace x | x <- lines stdout , Just x <- [stripPrefix "Note: including file:" x]]
stripPrefix function is available in the
Data.List module. One warning: the "including file" message is localised, so if your developers are using non-English versions of Visual Studio the prefix string will be different
The initial example compiles the C file, then calls
need on all its source and header files. This works fine if the header files are all source code. However, if any of the header files are generated by the build system then when the compilation occurs they will not yet have been built. In general it is important to
need any generated files before they are used.
To detect the included headers without using the compiler we can define
usedHeaders as a top-level function:
usedHeaders src = [init x | x <- lines src, Just x <- [stripPrefix "#include \"" x]]
This function takes the source code of a C file (
src) and finds all lines that begin
#include ", then takes the filename afterwards. This function does not work for all C files, but for most projects it is usually easy to write such a function that covers everything allowed by your coding standards.
Assuming all interesting headers are only included directly by the C file (a restriction we remove in the next section), we can write the build rule as:
"_build//*.o" %> \out -> do let c = dropDirectory1 $ out -<.> "c" src <- readFile' c need $ usedHeaders src cmd "gcc -c" [c] "-o" [out]
This code calls
readFile' (which automatically calls
need on the source file), then uses calls
need on all headers used by the source file, then calls
gcc. All files have
need called on them before they are used, so if the C file or any of the header files have build system rules they will be run.
The previous section described how to deal with generated include files, but only coped with headers included directly by the C file. This section describes how to extend that to work with generated headers used either in C or header files, even when used by headers that were themselves generated. We can write:
["*.c.dep","*.h.dep"] |%> \out -> do src <- readFile' $ dropExtension out writeFileLines out $ usedHeaders src "*.deps" %> \out -> do dep <- readFileLines $ out -<.> "dep" deps <- mapM (readFileLines . (<.> "deps")) dep writeFileLines out $ nub $ dropExtension out : concat deps "*.o" %> \out -> do deps <- readFileLines $ out -<.> "c.deps" need deps cmd "gcc -c" [dropExtension out] "-o" out
For simplicity, this code assumes all files are in a single directory and all objects are generated files are placed in the same directory. We define three rules:
|%>, which defines a single action that matches multiple patterns. The file
foo.h.depcontains a list of headers directly included by
usedHeadersfrom the previous section.
*.depsrule takes the transitive closure of dependencies, so
foo.hand all headers that
foo.hpulls in. The rule takes the target file, and all the
.depsfor anything in the
.depfile, and combines them. More abstractly, the rule calculates the transitive closure of a, namely a*, by taking the dependencies of a (say b and c) and computing a* = union(a, b*, c*).
*.orule reads the associated
.depsfile (ensuring it is up to date) and then depends on its contents.
The pattern of
*.deps files occurs frequently, for example when linking Haskell files.