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 }