Newer
Older
dub_jkp / source / dub / compilers / compiler.d
  1. /**
  2. Compiler settings and abstraction.
  3.  
  4. Copyright: © 2013-2014 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. public import dub.compilers.buildsettings;
  11.  
  12. import dub.compilers.dmd;
  13. import dub.compilers.gdc;
  14. import dub.compilers.ldc;
  15. import dub.internal.vibecompat.core.file;
  16. import dub.internal.vibecompat.core.log;
  17. import dub.internal.vibecompat.data.json;
  18. import dub.internal.vibecompat.inet.path;
  19.  
  20. import std.algorithm;
  21. import std.array;
  22. import std.conv;
  23. import std.exception;
  24. import std.process;
  25.  
  26.  
  27. static this()
  28. {
  29. registerCompiler(new DmdCompiler);
  30. registerCompiler(new GdcCompiler);
  31. registerCompiler(new LdcCompiler);
  32. }
  33.  
  34. Compiler getCompiler(string name)
  35. {
  36. foreach (c; s_compilers)
  37. if (c.name == name)
  38. return c;
  39.  
  40. // try to match names like gdmd or gdc-2.61
  41. if (name.canFind("dmd")) return getCompiler("dmd");
  42. if (name.canFind("gdc")) return getCompiler("gdc");
  43. if (name.canFind("ldc")) return getCompiler("ldc");
  44.  
  45. throw new Exception("Unknown compiler: "~name);
  46. }
  47.  
  48. string defaultCompiler()
  49. {
  50. static string name;
  51. if (!name.length) name = findCompiler();
  52. return name;
  53. }
  54.  
  55. private string findCompiler()
  56. {
  57. import std.process : env=environment;
  58. import dub.version_ : initialCompilerBinary;
  59. version (Windows) enum sep = ";", exe = ".exe";
  60. version (Posix) enum sep = ":", exe = "";
  61.  
  62. auto def = Path(initialCompilerBinary);
  63. if (def.absolute && existsFile(def))
  64. return initialCompilerBinary;
  65.  
  66. auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"];
  67. if (!def.absolute)
  68. compilers = initialCompilerBinary ~ compilers;
  69.  
  70. auto paths = env.get("PATH", "").splitter(sep).map!Path;
  71. auto res = compilers.find!(bin => paths.canFind!(p => existsFile(p ~ (bin~exe))));
  72. return res.empty ? initialCompilerBinary : res.front;
  73. }
  74.  
  75. void registerCompiler(Compiler c)
  76. {
  77. s_compilers ~= c;
  78. }
  79.  
  80. void warnOnSpecialCompilerFlags(string[] compiler_flags, BuildOptions options, string package_name, string config_name)
  81. {
  82. struct SpecialFlag {
  83. string[] flags;
  84. string alternative;
  85. }
  86. static immutable SpecialFlag[] s_specialFlags = [
  87. {["-c", "-o-"], "Automatically issued by DUB, do not specify in dub.json"},
  88. {["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`},
  89. {["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"},
  90. {["-wi"], `Use the "buildRequirements" field to control warning behavior`},
  91. {["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`},
  92. {["-of"], `Use "targetPath" and "targetName" to customize the output file`},
  93. {["-debug", "-fdebug", "-g"], "Call dub with --build=debug"},
  94. {["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"},
  95. {["-unittest", "-funittest"], "Call dub with --build=unittest"},
  96. {["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`},
  97. {["-D"], "Call dub with --build=docs or --build=ddox"},
  98. {["-X"], "Call dub with --build=ddox"},
  99. {["-cov"], "Call dub with --build=cov or --build=unittest-cox"},
  100. {["-profile"], "Call dub with --build=profile"},
  101. {["-version="], `Use "versions" to specify version constants in a compiler independent way`},
  102. {["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`},
  103. {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`},
  104. {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`},
  105. {["-m32", "-m64"], `Use --arch=x86/--arch=x86_64 to specify the target architecture`}
  106. ];
  107.  
  108. struct SpecialOption {
  109. BuildOptions[] flags;
  110. string alternative;
  111. }
  112. static immutable SpecialOption[] s_specialOptions = [
  113. {[BuildOptions.debugMode], "Call DUB with --build=debug"},
  114. {[BuildOptions.releaseMode], "Call DUB with --build=release"},
  115. {[BuildOptions.coverage], "Call DUB with --build=cov or --build=unittest-cov"},
  116. {[BuildOptions.debugInfo], "Call DUB with --build=debug"},
  117. {[BuildOptions.inline], "Call DUB with --build=release"},
  118. {[BuildOptions.noBoundsCheck], "Call DUB with --build=release-nobounds"},
  119. {[BuildOptions.optimize], "Call DUB with --build=release"},
  120. {[BuildOptions.profile], "Call DUB with --build=profile"},
  121. {[BuildOptions.unittests], "Call DUB with --build=unittest"},
  122. {[BuildOptions.warnings, BuildOptions.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"},
  123. {[BuildOptions.ignoreDeprecations, BuildOptions.deprecationWarnings, BuildOptions.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"},
  124. {[BuildOptions.property], "This flag is deprecated and has no effect"}
  125. ];
  126.  
  127. bool got_preamble = false;
  128. void outputPreamble()
  129. {
  130. if (got_preamble) return;
  131. got_preamble = true;
  132. logWarn("");
  133. if (config_name.empty) logWarn("## Warning for package %s ##", package_name);
  134. else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name);
  135. logWarn("");
  136. logWarn("The following compiler flags have been specified in the package description");
  137. logWarn("file. They are handled by DUB and direct use in packages is discouraged.");
  138. logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags");
  139. logWarn("to the compiler, or use one of the suggestions below:");
  140. logWarn("");
  141. }
  142.  
  143. foreach (f; compiler_flags) {
  144. foreach (sf; s_specialFlags) {
  145. if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) {
  146. outputPreamble();
  147. logWarn("%s: %s", f, sf.alternative);
  148. break;
  149. }
  150. }
  151. }
  152.  
  153. foreach (sf; s_specialOptions) {
  154. foreach (f; sf.flags) {
  155. if (options & f) {
  156. outputPreamble();
  157. logWarn("%s: %s", f, sf.alternative);
  158. break;
  159. }
  160. }
  161. }
  162.  
  163. if (got_preamble) logWarn("");
  164. }
  165.  
  166.  
  167. /**
  168. Alters the build options to comply with the specified build requirements.
  169. */
  170. void enforceBuildRequirements(ref BuildSettings settings)
  171. {
  172. settings.addOptions(BuildOptions.warningsAsErrors);
  173. if (settings.requirements & BuildRequirements.allowWarnings) { settings.options &= ~BuildOptions.warningsAsErrors; settings.options |= BuildOptions.warnings; }
  174. if (settings.requirements & BuildRequirements.silenceWarnings) settings.options &= ~(BuildOptions.warningsAsErrors|BuildOptions.warnings);
  175. if (settings.requirements & BuildRequirements.disallowDeprecations) { settings.options &= ~(BuildOptions.ignoreDeprecations|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.deprecationErrors; }
  176. if (settings.requirements & BuildRequirements.silenceDeprecations) { settings.options &= ~(BuildOptions.deprecationErrors|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.ignoreDeprecations; }
  177. if (settings.requirements & BuildRequirements.disallowInlining) settings.options &= ~BuildOptions.inline;
  178. if (settings.requirements & BuildRequirements.disallowOptimization) settings.options &= ~BuildOptions.optimize;
  179. if (settings.requirements & BuildRequirements.requireBoundsCheck) settings.options &= ~BuildOptions.noBoundsCheck;
  180. if (settings.requirements & BuildRequirements.requireContracts) settings.options &= ~BuildOptions.releaseMode;
  181. if (settings.requirements & BuildRequirements.relaxProperties) settings.options &= ~BuildOptions.property;
  182. }
  183.  
  184.  
  185. /**
  186. Replaces each referenced import library by the appropriate linker flags.
  187.  
  188. This function tries to invoke "pkg-config" if possible and falls back to
  189. direct flag translation if that fails.
  190. */
  191. void resolveLibs(ref BuildSettings settings)
  192. {
  193. import std.string : format;
  194.  
  195. if (settings.libs.length == 0) return;
  196.  
  197. if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) {
  198. logDiagnostic("Ignoring all import libraries for static library build.");
  199. settings.libs = null;
  200. version(Windows) settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array;
  201. }
  202.  
  203. version (Posix) {
  204. try {
  205. enum pkgconfig_bin = "pkg-config";
  206. string[] pkgconfig_libs;
  207. foreach (lib; settings.libs)
  208. if (execute([pkgconfig_bin, "--exists", "lib"~lib]).status == 0)
  209. pkgconfig_libs ~= lib;
  210.  
  211. logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.map!(l => "lib"~l).array.join(", "));
  212.  
  213. if (pkgconfig_libs.length) {
  214. auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs.map!(l => "lib"~l)().array());
  215. enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output));
  216. foreach (f; libflags.output.split()) {
  217. if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(","));
  218. else settings.addLFlags(f);
  219. }
  220. settings.libs = settings.libs.filter!(l => !pkgconfig_libs.canFind(l)).array;
  221. }
  222. if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", "));
  223. } catch (Exception e) {
  224. logDiagnostic("pkg-config failed: %s", e.msg);
  225. logDiagnostic("Falling back to direct -l... flags.");
  226. }
  227. }
  228. }
  229.  
  230.  
  231. interface Compiler {
  232. @property string name() const;
  233.  
  234. BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null);
  235.  
  236. /// Replaces high level fields with low level fields and converts
  237. /// dmd flags to compiler-specific flags
  238. void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all) const;
  239.  
  240. /// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field.
  241. void extractBuildOptions(ref BuildSettings settings) const;
  242.  
  243. /// Adds the appropriate flag to set a target path
  244. void setTarget(ref BuildSettings settings, in BuildPlatform platform, string targetPath = null) const;
  245.  
  246. /// Invokes the compiler using the given flags
  247. void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback);
  248.  
  249. /// Invokes the underlying linker directly
  250. void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback);
  251.  
  252. protected final void invokeTool(string[] args, void delegate(int, string) output_callback)
  253. {
  254. import std.string;
  255.  
  256. int status;
  257. if (output_callback) {
  258. auto result = executeShell(escapeShellCommand(args));
  259. output_callback(result.status, result.output);
  260. status = result.status;
  261. } else {
  262. auto compiler_pid = spawnShell(escapeShellCommand(args));
  263. status = compiler_pid.wait();
  264. }
  265.  
  266. version (Posix) if (status == -9) {
  267. throw new Exception(format("%s failed with exit code %s. This may indicate that the process has run out of memory."),
  268. args[0], status);
  269. }
  270. enforce(status == 0, format("%s failed with exit code %s.", args[0], status));
  271. }
  272. }
  273.  
  274.  
  275. /// Represents a platform a package can be build upon.
  276. struct BuildPlatform {
  277. /// e.g. ["posix", "windows"]
  278. string[] platform;
  279. /// e.g. ["x86", "x86_64"]
  280. string[] architecture;
  281. /// Canonical compiler name e.g. "dmd"
  282. string compiler;
  283. /// Compiler binary name e.g. "ldmd2"
  284. string compilerBinary;
  285. /// Compiled frontend version (e.g. 2065)
  286. int frontendVersion;
  287.  
  288. enum any = BuildPlatform(null, null, null, null, -1);
  289.  
  290. /// Build platforms can be specified via a string specification.
  291. ///
  292. /// Specifications are build upon the following scheme, where each component
  293. /// is optional (indicated by []), but the order is obligatory.
  294. /// "[-platform][-architecture][-compiler]"
  295. ///
  296. /// So the following strings are valid specifications:
  297. /// "-windows-x86-dmd"
  298. /// "-dmd"
  299. /// "-arm"
  300. /// "-arm-dmd"
  301. /// "-windows-dmd"
  302. ///
  303. /// Params:
  304. /// specification = The specification being matched. It must be the empty string or start with a dash.
  305. ///
  306. /// Returns:
  307. /// true if the given specification matches this BuildPlatform, false otherwise. (The empty string matches)
  308. ///
  309. bool matchesSpecification(const(char)[] specification)
  310. const {
  311. if (specification.empty) return true;
  312. if (this == any) return true;
  313.  
  314. auto splitted=specification.splitter('-');
  315. assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!");
  316. splitted.popFront(); // Drop leading empty match.
  317. enforce(!splitted.empty, "Platform specification if present, must not be empty!");
  318. if (platform.canFind(splitted.front)) {
  319. splitted.popFront();
  320. if(splitted.empty)
  321. return true;
  322. }
  323. if (architecture.canFind(splitted.front)) {
  324. splitted.popFront();
  325. if(splitted.empty)
  326. return true;
  327. }
  328. if (compiler == splitted.front) {
  329. splitted.popFront();
  330. enforce(splitted.empty, "No valid specification! The compiler has to be the last element!");
  331. return true;
  332. }
  333. return false;
  334. }
  335. unittest {
  336. auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd");
  337. assert(platform.matchesSpecification("-posix"));
  338. assert(platform.matchesSpecification("-linux"));
  339. assert(platform.matchesSpecification("-linux-dmd"));
  340. assert(platform.matchesSpecification("-linux-x86_64-dmd"));
  341. assert(platform.matchesSpecification("-x86_64"));
  342. assert(!platform.matchesSpecification("-windows"));
  343. assert(!platform.matchesSpecification("-ldc"));
  344. assert(!platform.matchesSpecification("-windows-dmd"));
  345. }
  346. }
  347.  
  348.  
  349. string getTargetFileName(in BuildSettings settings, in BuildPlatform platform)
  350. {
  351. assert(settings.targetName.length > 0, "No target name set.");
  352. final switch (settings.targetType) {
  353. case TargetType.autodetect: assert(false, "Configurations must have a concrete target type.");
  354. case TargetType.none: return null;
  355. case TargetType.sourceLibrary: return null;
  356. case TargetType.executable:
  357. if( platform.platform.canFind("windows") )
  358. return settings.targetName ~ ".exe";
  359. else return settings.targetName;
  360. case TargetType.library:
  361. case TargetType.staticLibrary:
  362. if (platform.platform.canFind("windows") && platform.compiler == "dmd")
  363. return settings.targetName ~ ".lib";
  364. else return "lib" ~ settings.targetName ~ ".a";
  365. case TargetType.dynamicLibrary:
  366. if( platform.platform.canFind("windows") )
  367. return settings.targetName ~ ".dll";
  368. else return "lib" ~ settings.targetName ~ ".so";
  369. case TargetType.object:
  370. if (platform.platform.canFind("windows"))
  371. return settings.targetName ~ ".obj";
  372. else return settings.targetName ~ ".o";
  373. }
  374. }
  375.  
  376.  
  377. bool isLinkerFile(string f)
  378. {
  379. import std.path;
  380. switch (extension(f)) {
  381. default:
  382. return false;
  383. version (Windows) {
  384. case ".lib", ".obj", ".res", ".def":
  385. return true;
  386. } else {
  387. case ".a", ".o", ".so", ".dylib":
  388. return true;
  389. }
  390. }
  391. }
  392.  
  393. /// Generate a file that will give, at compile time, informations about the compiler (architecture, frontend version...)
  394. Path generatePlatformProbeFile()
  395. {
  396. import dub.internal.vibecompat.core.file;
  397. import dub.internal.vibecompat.data.json;
  398. import dub.internal.utils;
  399.  
  400. auto path = getTempFile("dub_platform_probe", ".d");
  401.  
  402. auto fil = openFile(path, FileMode.CreateTrunc);
  403. scope (failure) {
  404. fil.close();
  405. }
  406.  
  407. fil.write(q{
  408. module dub_platform_probe;
  409.  
  410. template toString(int v) { enum toString = v.stringof; }
  411.  
  412. pragma(msg, `{`);
  413. pragma(msg,` "compiler": "`~ determineCompiler() ~ `",`);
  414. pragma(msg, ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,`);
  415. pragma(msg, ` "compilerVendor": "` ~ __VENDOR__ ~ `",`);
  416. pragma(msg, ` "platform": [`);
  417. pragma(msg, ` ` ~ determinePlatform());
  418. pragma(msg, ` ],`);
  419. pragma(msg, ` "architecture": [`);
  420. pragma(msg, ` ` ~ determineArchitecture());
  421. pragma(msg, ` ],`);
  422. pragma(msg, `}`);
  423.  
  424. string determinePlatform()
  425. {
  426. string ret;
  427. version(Windows) ret ~= `"windows", `;
  428. version(linux) ret ~= `"linux", `;
  429. version(Posix) ret ~= `"posix", `;
  430. version(OSX) ret ~= `"osx", `;
  431. version(FreeBSD) ret ~= `"freebsd", `;
  432. version(OpenBSD) ret ~= `"openbsd", `;
  433. version(NetBSD) ret ~= `"netbsd", `;
  434. version(DragonFlyBSD) ret ~= `"dragonflybsd", `;
  435. version(BSD) ret ~= `"bsd", `;
  436. version(Solaris) ret ~= `"solaris", `;
  437. version(AIX) ret ~= `"aix", `;
  438. version(Haiku) ret ~= `"haiku", `;
  439. version(SkyOS) ret ~= `"skyos", `;
  440. version(SysV3) ret ~= `"sysv3", `;
  441. version(SysV4) ret ~= `"sysv4", `;
  442. version(Hurd) ret ~= `"hurd", `;
  443. version(Android) ret ~= `"android", `;
  444. version(Cygwin) ret ~= `"cygwin", `;
  445. version(MinGW) ret ~= `"mingw", `;
  446. return ret;
  447. }
  448.  
  449. string determineArchitecture()
  450. {
  451. string ret;
  452. version(X86) ret ~= `"x86", `;
  453. version(X86_64) ret ~= `"x86_64", `;
  454. version(ARM) ret ~= `"arm", `;
  455. version(ARM_Thumb) ret ~= `"arm_thumb", `;
  456. version(ARM_SoftFloat) ret ~= `"arm_softfloat", `;
  457. version(ARM_HardFloat) ret ~= `"arm_hardfloat", `;
  458. version(ARM64) ret ~= `"arm64", `;
  459. version(PPC) ret ~= `"ppc", `;
  460. version(PPC_SoftFP) ret ~= `"ppc_softfp", `;
  461. version(PPC_HardFP) ret ~= `"ppc_hardfp", `;
  462. version(PPC64) ret ~= `"ppc64", `;
  463. version(IA64) ret ~= `"ia64", `;
  464. version(MIPS) ret ~= `"mips", `;
  465. version(MIPS32) ret ~= `"mips32", `;
  466. version(MIPS64) ret ~= `"mips64", `;
  467. version(MIPS_O32) ret ~= `"mips_o32", `;
  468. version(MIPS_N32) ret ~= `"mips_n32", `;
  469. version(MIPS_O64) ret ~= `"mips_o64", `;
  470. version(MIPS_N64) ret ~= `"mips_n64", `;
  471. version(MIPS_EABI) ret ~= `"mips_eabi", `;
  472. version(MIPS_NoFloat) ret ~= `"mips_nofloat", `;
  473. version(MIPS_SoftFloat) ret ~= `"mips_softfloat", `;
  474. version(MIPS_HardFloat) ret ~= `"mips_hardfloat", `;
  475. version(SPARC) ret ~= `"sparc", `;
  476. version(SPARC_V8Plus) ret ~= `"sparc_v8plus", `;
  477. version(SPARC_SoftFP) ret ~= `"sparc_softfp", `;
  478. version(SPARC_HardFP) ret ~= `"sparc_hardfp", `;
  479. version(SPARC64) ret ~= `"sparc64", `;
  480. version(S390) ret ~= `"s390", `;
  481. version(S390X) ret ~= `"s390x", `;
  482. version(HPPA) ret ~= `"hppa", `;
  483. version(HPPA64) ret ~= `"hppa64", `;
  484. version(SH) ret ~= `"sh", `;
  485. version(SH64) ret ~= `"sh64", `;
  486. version(Alpha) ret ~= `"alpha", `;
  487. version(Alpha_SoftFP) ret ~= `"alpha_softfp", `;
  488. version(Alpha_HardFP) ret ~= `"alpha_hardfp", `;
  489. return ret;
  490. }
  491.  
  492. string determineCompiler()
  493. {
  494. version(DigitalMars) return "dmd";
  495. else version(GNU) return "gdc";
  496. else version(LDC) return "ldc";
  497. else version(SDC) return "sdc";
  498. else return null;
  499. }
  500. });
  501.  
  502. fil.close();
  503.  
  504. return path;
  505. }
  506.  
  507. BuildPlatform readPlatformProbe(string output)
  508. {
  509. import std.string;
  510.  
  511. // work around possible additional output of the compiler
  512. auto idx1 = output.indexOf("{");
  513. auto idx2 = output.lastIndexOf("}");
  514. enforce(idx1 >= 0 && idx1 < idx2,
  515. "Unexpected platform information output - does not contain a JSON object.");
  516. output = output[idx1 .. idx2+1];
  517.  
  518. import dub.internal.vibecompat.data.json;
  519. auto json = parseJsonString(output);
  520.  
  521. BuildPlatform build_platform;
  522. build_platform.platform = json.platform.get!(Json[]).map!(e => e.get!string()).array();
  523. build_platform.architecture = json.architecture.get!(Json[]).map!(e => e.get!string()).array();
  524. build_platform.compiler = json.compiler.get!string;
  525. build_platform.frontendVersion = json.frontendVersion.get!int;
  526. return build_platform;
  527. }
  528.  
  529. private {
  530. Compiler[] s_compilers;
  531. }