Newer
Older
dub_jkp / source / dub / compilers / compiler.d
  1. /**
  2. Compiler settings and abstraction.
  3.  
  4. Copyright: © 2013 rejectedsoftware e.K.
  5. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
  6. Authors: Sönke Ludwig
  7. */
  8. module dub.compilers.compiler;
  9.  
  10. import dub.compilers.dmd;
  11. import dub.compilers.gdc;
  12. import dub.compilers.ldc;
  13.  
  14. import std.algorithm;
  15. import std.array;
  16. import std.exception;
  17. import vibecompat.data.json;
  18. import vibecompat.inet.path;
  19.  
  20.  
  21. static this()
  22. {
  23. registerCompiler(new DmdCompiler);
  24. registerCompiler(new GdcCompiler);
  25. registerCompiler(new LdcCompiler);
  26. }
  27.  
  28.  
  29. Compiler getCompiler(string name)
  30. {
  31. foreach( c; s_compilers )
  32. if( c.name == name )
  33. return c;
  34.  
  35. // try to match names like gdmd or gdc-2.61
  36. if( name.canFind("dmd") ) return getCompiler("dmd");
  37. if( name.canFind("gdc") ) return getCompiler("gdc");
  38. if( name.canFind("ldc") ) return getCompiler("ldc");
  39. throw new Exception("Unknown compiler: "~name);
  40. }
  41.  
  42. void registerCompiler(Compiler c)
  43. {
  44. s_compilers ~= c;
  45. }
  46.  
  47. void warnOnSpecialCompilerFlags(string[] compiler_flags, string package_name)
  48. {
  49. import vibecompat.core.log;
  50. struct SpecialFlag {
  51. string[] flags;
  52. string alternative;
  53. }
  54. static immutable SpecialFlag[] s_specialFlags = [
  55. {["-c", "-o-", "-w", "-property"], "Managed by DUB, do not specify in package.json"},
  56. {["-of"], `Use "targetPath" and "targetName" to customize the output file`},
  57. {["-debug", "-fdebug", "-g"], "Call dub with --build=debug"},
  58. {["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"},
  59. {["-unittest", "-funittest"], "Call dub with --build=unittest"},
  60. {["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`},
  61. {["-D"], "Call dub with --build=docs or --build=ddox"},
  62. {["-X"], "Call dub with --build=ddox"},
  63. {["-cov"], "Call dub with --build=cov or --build=unittest-cox"},
  64. {["-profile"], "Call dub with --build=profile"},
  65. {["-version="], `Use "versions" to specify version constants in a compiler independent way`},
  66. //{["-debug=", `Use "debugVersions" to specify version constants in a compiler independent way`]},
  67. {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`},
  68. {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`},
  69. ];
  70.  
  71. bool got_preamble = false;
  72. void outputPreamble()
  73. {
  74. if (got_preamble) return;
  75. got_preamble = true;
  76. logWarn("");
  77. logWarn("Warning");
  78. logWarn("=======");
  79. logWarn("");
  80. logWarn("The following compiler flags have been specified in %s's", package_name);
  81. logWarn("package description file. They are handled by DUB and direct use in packages is");
  82. logWarn("discouraged.");
  83. logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags");
  84. logWarn("to the compiler, or use one of the suggestions below:");
  85. logWarn("");
  86. }
  87.  
  88. foreach (f; compiler_flags) {
  89. foreach (sf; s_specialFlags) {
  90. if (sf.flags.canFind!(sff => f.startsWith(sff))()) {
  91. outputPreamble();
  92. logWarn("%s: %s", f, sf.alternative);
  93. break;
  94. }
  95. }
  96. }
  97.  
  98. if (got_preamble) logWarn("");
  99. }
  100.  
  101.  
  102. interface Compiler {
  103. @property string name() const;
  104.  
  105. BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null);
  106.  
  107. /// Replaces high level fields with low level fields and converts
  108. /// dmd flags to compiler-specific flags
  109. void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all);
  110.  
  111. /// Adds the appropriate flag to set a target path
  112. void setTarget(ref BuildSettings settings, in BuildPlatform platform);
  113.  
  114. /// Invokes the underlying linker directly
  115. void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects);
  116. }
  117.  
  118.  
  119. /// BuildPlatform specific settings, like needed libraries or additional
  120. /// include paths.
  121. struct BuildSettings {
  122. TargetType targetType;
  123. string targetPath;
  124. string targetName;
  125. string[] dflags;
  126. string[] lflags;
  127. string[] libs;
  128. string[] sourceFiles;
  129. string[] copyFiles;
  130. string[] versions;
  131. string[] importPaths;
  132. string[] stringImportPaths;
  133. string[] preGenerateCommands;
  134. string[] postGenerateCommands;
  135. string[] preBuildCommands;
  136. string[] postBuildCommands;
  137.  
  138. void addDFlags(in string[] value...) { add(dflags, value); }
  139. void addLFlags(in string[] value...) { add(lflags, value); }
  140. void addLibs(in string[] value...) { add(libs, value); }
  141. void addSourceFiles(in string[] value...) { add(sourceFiles, value); }
  142. void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); }
  143. void addCopyFiles(in string[] value...) { add(copyFiles, value); }
  144. void addVersions(in string[] value...) { add(versions, value); }
  145. void addImportPaths(in string[] value...) { add(importPaths, value); }
  146. void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); }
  147. void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); }
  148. void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); }
  149. void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); }
  150. void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); }
  151.  
  152. // Adds vals to arr without adding duplicates.
  153. private void add(ref string[] arr, in string[] vals, bool no_duplicates = true)
  154. {
  155. if( !no_duplicates ){
  156. arr ~= vals;
  157. return;
  158. }
  159.  
  160. foreach( v; vals ){
  161. bool found = false;
  162. foreach( i; 0 .. arr.length )
  163. if( arr[i] == v ){
  164. found = true;
  165. break;
  166. }
  167. if( !found ) arr ~= v;
  168. }
  169. }
  170.  
  171. private void removePaths(ref string[] arr, in string[] vals)
  172. {
  173. bool matches(string s){
  174. foreach( p; vals )
  175. if( Path(s) == Path(p) )
  176. return true;
  177. return false;
  178. }
  179. arr = arr.filter!(s => !matches(s))().array();
  180. }
  181. }
  182.  
  183. /// Represents a platform a package can be build upon.
  184. struct BuildPlatform {
  185. /// e.g. ["posix", "windows"]
  186. string[] platform;
  187. /// e.g. ["x86", "x86_64"]
  188. string[] architecture;
  189. /// e.g. "dmd"
  190. string compiler;
  191.  
  192. /// Build platforms can be specified via a string specification.
  193. ///
  194. /// Specifications are build upon the following scheme, where each component
  195. /// is optional (indicated by []), but the order is obligatory.
  196. /// "[-platform][-architecture][-compiler]"
  197. ///
  198. /// So the following strings are valid specifications:
  199. /// "-windows-x86-dmd"
  200. /// "-dmd"
  201. /// "-arm"
  202. /// "-arm-dmd"
  203. /// "-windows-dmd"
  204. ///
  205. /// Params:
  206. /// specification = The specification being matched. It must be the empty string or start with a dash.
  207. ///
  208. /// Returns:
  209. /// true if the given specification matches this BuildPlatform, false otherwise. (The empty string matches)
  210. ///
  211. bool matchesSpecification(const(char)[] specification) const {
  212. if(specification.empty)
  213. return true;
  214. auto splitted=specification.splitter('-');
  215. assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!");
  216. splitted.popFront(); // Drop leading empty match.
  217. enforce(!splitted.empty, "Platform specification if present, must not be empty!");
  218. if(platform.canFind(splitted.front)) {
  219. splitted.popFront();
  220. if(splitted.empty)
  221. return true;
  222. }
  223. if(architecture.canFind(splitted.front)) {
  224. splitted.popFront();
  225. if(splitted.empty)
  226. return true;
  227. }
  228. if(compiler==splitted.front) {
  229. splitted.popFront();
  230. enforce(splitted.empty, "No valid specification! The compiler has to be the last element!");
  231. return true;
  232. }
  233. return false;
  234. }
  235. unittest {
  236. auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd");
  237. assert(platform.matchesSpecification("-posix"));
  238. assert(platform.matchesSpecification("-linux"));
  239. assert(platform.matchesSpecification("-linux-dmd"));
  240. assert(platform.matchesSpecification("-linux-x86_64-dmd"));
  241. assert(platform.matchesSpecification("-x86_64"));
  242. assert(!platform.matchesSpecification("-windows"));
  243. assert(!platform.matchesSpecification("-ldc"));
  244. assert(!platform.matchesSpecification("-windows-dmd"));
  245. }
  246. }
  247.  
  248. enum BuildSetting {
  249. dflags = 1<<0,
  250. lflags = 1<<1,
  251. libs = 1<<2,
  252. sourceFiles = 1<<3,
  253. copyFiles = 1<<4,
  254. versions = 1<<5,
  255. importPaths = 1<<6,
  256. stringImportPaths = 1<<7,
  257. none = 0,
  258. commandLine = dflags|copyFiles,
  259. commandLineSeparate = commandLine|lflags,
  260. all = dflags|lflags|libs|sourceFiles|copyFiles|versions|importPaths|stringImportPaths
  261. }
  262.  
  263. enum TargetType {
  264. autodetect,
  265. executable,
  266. library,
  267. sourceLibrary,
  268. dynamicLibrary,
  269. staticLibrary
  270. }
  271.  
  272. string getTargetFileName(in BuildSettings settings, in BuildPlatform platform)
  273. {
  274. assert(settings.targetName.length > 0, "No target name set.");
  275. final switch(settings.targetType){
  276. case TargetType.autodetect: assert(false);
  277. case TargetType.sourceLibrary: return null;
  278. case TargetType.executable:
  279. if( platform.platform.canFind("windows") )
  280. return settings.targetName ~ ".exe";
  281. else return settings.targetName;
  282. case TargetType.library:
  283. case TargetType.staticLibrary:
  284. if( platform.platform.canFind("windows") )
  285. return settings.targetName ~ ".lib";
  286. else return "lib" ~ settings.targetName ~ ".a";
  287. case TargetType.dynamicLibrary:
  288. if( platform.platform.canFind("windows") )
  289. return settings.targetName ~ ".dll";
  290. else return "lib" ~ settings.targetName ~ ".so";
  291. }
  292. }
  293.  
  294.  
  295.  
  296. private {
  297. Compiler[] s_compilers;
  298. }