1 module sbylib.engine.compiler.compiler;
2 
3 import std;
4 import core.thread : Thread;
5 import core.atomic : atomicOp;
6 import core.sync.semaphore;
7 
8 import sbylib.engine;
9 import sbylib.event;
10 import sbylib.engine.compiler.exception;
11 
12 class Compiler {
13 static:
14 
15     private __gshared int[string] seedList;
16     private __gshared Semaphore semaphore;
17 
18     struct CompileResult { string output, outputFileName; bool success; }
19 
20     void finalize() {
21         foreach (file, seed; seedList) {
22             foreach (i; 0..seed) {
23                 if (getFileName(file,i).exists) remove(getFileName(file, i));
24             }
25             if (getFileName(file, seed).exists) rename(getFileName(file, seed), getFileName(file, 0));
26         }
27     }
28 
29     auto compile(string inputFileName) {
30         try {
31             auto config = CompileConfig(inputFileName);
32 
33             return build(config).then((CompileResult r) {
34                 if (!r.success) throw new CompileErrorException(r.output);
35                 return new DLL(r.outputFileName); });
36         } catch (ResolveLocationException e) {
37             return promise!({
38                 if (e !is null) {
39                     throw new CompileErrorException(
40                         "Error in compiling " ~ inputFileName ~ ":\n" ~ e.msg);
41                 }
42                 return cast(DLL)null;
43             });
44         }
45     }
46 
47     private auto build(CompileConfig config) {
48         const base = config.mainFile.baseName(config.mainFile.extension);
49         auto seed = base in seedList ? seedList[base] : (seedList[base] = 0);
50         auto outputFileName = getFileName(base, seed);
51 
52         if (outputFileName.exists) {
53 
54             auto modifiedDependencies = config.dependencies
55                 .filter!(dep => dep.timeLastModified > outputFileName.timeLastModified);
56 
57             if (modifiedDependencies.empty) {
58                 return promise!(() => CompileResult("", outputFileName, true));
59             }
60 
61             seed = ++seedList[base];
62             outputFileName = getFileName(base, seed);
63         }
64 
65         const command = config.createCommand(outputFileName);
66 
67         if (semaphore is null) semaphore = new Semaphore(1);
68 
69         return promise!((void delegate(CompileResult) resolve) {
70             new Thread({
71                 semaphore.wait();
72                 scope (exit) semaphore.notify();
73 
74                 auto dmd = execute(command);
75 
76                 if (dmd.status != 0) {
77                     resolve(CompileResult(format!"Compilation failed\n%s"(dmd.output), outputFileName, false));
78                     return;
79                 }
80                 remove(format!"%s/%s%d.o"(cacheDir, base, seed));
81 
82                 resolve(CompileResult(dmd.output, outputFileName, true));
83             }).start();
84         });
85     }
86 
87     private string getFileName(string base, int seed) {
88         return format!"%s/%s%d.so"(cacheDir, base, seed);
89     }
90 }
91 
92 struct CompileConfig {
93     string   mainFile;
94     string[] inputFiles;
95     string[] importPath;
96     string[] libraryPath;
97     string[] librarySearchPath;
98 
99     this(string inputFileName) {
100         import sbylib.engine.util : getImportPath = importPath;
101         auto dependencies = memoize!dependentLibraries();
102 
103         this.mainFile = inputFileName;
104         this.inputFiles = DScanner.importListRecursive!((string f) => isProjectFile(f))(inputFileName)
105             .filter!((string f) => f.asAbsolutePath.asNormalizedPath.array != inputFileName.asAbsolutePath.asNormalizedPath.array)
106             .array;
107         this.importPath = memoize!getImportPath;
108         this.libraryPath = dependencies.libraryPathList;
109         this.librarySearchPath = dependencies.librarySearchPathList;
110     }
111 
112     string[] createCommand(string outputFileName) const {
113         version (DigitalMars) {
114             return ["dmd"]
115                 ~ "-L=-fuse-ld=gold"
116                 ~ "-g"
117                 ~ mainFile
118                 ~ inputFiles
119                 ~ ("-of="~ outputFileName)
120                 ~ "-shared"
121                 ~ importPath.map!(p => "-I" ~ p).array
122                 ~ librarySearchPath.map!(f => "-L-L" ~ f).array
123                 ~ libraryPath.map!(f => "-L-l" ~ f[3..$-2]).array;
124         } else version (LDC) {
125             return ["dmd"]
126                 ~ "-g"
127                 ~ mainFile
128                 ~ inputFiles
129                 ~ ("-of="~ outputFileName)
130                 ~ "-shared"
131                 ~ importPath.map!(p => "-I" ~ p).array
132                 ~ librarySearchPath.map!(f => "-L-L" ~ f).array
133                 ~ libraryPath.map!(f => "-L-l" ~ f[3..$-2]).array;
134         } else {
135             static assert("This compiler is not supported");
136         }
137     }
138 
139     auto lastModified() const {
140         return dependencies
141             .map!(p => p.timeLastModified)
142             .reduce!max;
143     }
144 
145     auto dependencies() const {
146         return ([mainFile]
147              ~ inputFiles
148              ~ importPath
149              ~ libraryPath.map!(p => search(p)).array)
150             .filter!(p => p.isFile)
151             .array;
152     }
153 
154     private auto search(string p) const {
155         auto tmp = librarySearchPath
156             .map!(d => d.buildPath(p))
157             .filter!(p => p.exists);
158         enforce(tmp.empty is false, "Library not found: " ~ p);
159         return tmp.front;
160     }
161 }
162 
163 private bool isProjectFile(string f) {
164     const projectRoot = MetaInfo().projectDirectory.absolutePath;
165     f = f.absolutePath.asNormalizedPath.array;
166 
167     while (f != f.dirName) {
168         if (f == projectRoot) return true;
169         f = f.dirName;
170     }
171     return false;
172 }