Include Files
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.
Include files with Visual Studio
While 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 cl as:
Stdout stdout <- cmd "cl -showincludes -c" [input] ["-Fo" ++ output] need [ dropWhile isSpace x | x <- lines stdout , Just x <- [stripPrefix "Note: including file:" x]]
The 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
Generated imports
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 feasible 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.
Generated transitive imports
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:
- The
*.c.depand*.h.deprule uses|%>, which defines a single action that matches multiple patterns. The filefoo.h.depcontains a list of headers directly included byfoo.h, usingusedHeadersfrom the previous section. - The
*.depsrule takes the transitive closure of dependencies, sofoo.h.depscontainsfoo.hand all headers thatfoo.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*). - The
*.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.