1 module reggae.rules.c_and_cpp;
2 
3 import reggae.build;
4 import reggae.path: buildPath;
5 import reggae.rules.common;
6 import reggae.types;
7 import std.range;
8 import std.traits;
9 import std.stdio;
10 import std.file;
11 
12 @safe:
13 
14 Target unityBuild(ExeName exeName,
15                   alias sourcesFunc,
16                   Flags flags = Flags(),
17                   IncludePaths includes = IncludePaths(),
18                   alias dependenciesFunc = emptyTargets,
19                   alias implicitsFunc = emptyTargets)() @trusted {
20 
21     import reggae.config: options;
22 
23     const srcFiles = sourcesToFileNames!(sourcesFunc);
24 
25     immutable dirName = buildPath(options.workingDir, objDirOf(Target(exeName.value)));
26     dirName.exists || mkdirRecurse(dirName);
27 
28     immutable fileName = buildPath(dirName, "unity.cpp");
29     auto unityFile = File(fileName, "w");
30 
31     unityFile.writeln(unityFileContents(options.projectPath, srcFiles));
32 
33     return unityTarget(exeName, options.projectPath, srcFiles, flags, includes,
34                        dependenciesFunc(), implicitsFunc());
35 }
36 
37 
38 
39 /**
40  Returns the contents of the unity build file for these source files.
41  The source files have to all be in the same language and the only
42  supported languages are C and C++
43  */
44 string unityFileContents(in string projectPath, in string[] files) pure {
45     import std.array;
46     import std.algorithm;
47     import std.path: dirSeparator;
48 
49     if(files.empty)
50         throw new Exception("Cannot perform a unity build with no files");
51 
52     immutable languages = files.map!getLanguage.array;
53 
54     if(!languages.all!(a => a == Language.C) && !languages.all!(a => a == Language.Cplusplus))
55         throw new Exception("Unity build can only be done if all files are C or C++");
56 
57 
58     return files.map!(a => `#include "` ~ buildPath(projectPath, a).replace(dirSeparator, "/") ~ `"`).join("\n");
59 }
60 
61 
62 /**
63  Returns the unity build target for these parameters.
64  */
65 Target unityTarget(ExeName exeName,
66                    string projectPath,
67                    string[] srcFiles,
68                    Flags flags = Flags(),
69                    IncludePaths includes = IncludePaths(),
70                    alias dependenciesFunc = emptyTargets,
71                    alias implicitsFunc = emptyTargets,
72     )() {
73     return unityTarget(exeName, projectPath, srcFiles, flags, includes, dependenciesFunc());
74 }
75 
76 Target unityTarget(R1, R2)(in ExeName exeName,
77                            in string projectPath,
78                            in string[] srcFiles,
79                            in Flags flags = Flags(),
80                            in IncludePaths includes = IncludePaths(),
81                            R1 dependencies = emptyTargets(),
82                            R2 implicits = emptyTargets(),
83 
84     )
85     pure if(isInputRange!R1 && is(ElementType!R1 == Target) && isInputRange!R2 && is(ElementType!R2 == Target)) {
86 
87     import std.algorithm;
88 
89     auto justFileName = srcFiles.map!getLanguage.front == Language.C ? "unity.c" : "unity.cpp";
90     auto unityFileName = buildPath(gBuilddir, objDirOf(Target(exeName.value)), justFileName);
91     auto command = compileCommand(unityFileName,
92                                    flags.value,
93                                    includes.value,
94                                    [],
95                                    projectPath,
96                                    No.justCompile);
97     auto unityFileTarget = Target.phony(unityFileName, "", [], srcFiles.map!(a => Target(a)).array);
98     return Target(exeName.value, command, unityFileTarget ~ dependencies.array);
99 }
100 
101 
102 private Target[] emptyTargets() {
103     return [];
104 }