Newer
Older
dub_jkp / source / dub / compilers / compiler.d
@Vladimir Panteleev Vladimir Panteleev on 22 Jul 2023 7 KB Don't wastefully execute everything via a shell
  1. /**
  2. Compiler settings and abstraction.
  3.  
  4. Copyright: © 2013-2016 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. deprecated("Please `import dub.dependency : Dependency` instead") public import dub.dependency : Dependency;
  12. public import dub.platform : BuildPlatform, matchesSpecification;
  13.  
  14. import dub.internal.vibecompat.inet.path;
  15. import dub.internal.vibecompat.core.file;
  16.  
  17. import dub.internal.logging;
  18.  
  19. import std.algorithm;
  20. import std.array;
  21. import std.exception;
  22. import std.process;
  23.  
  24. /// Exception thrown in Compiler.determinePlatform if the given architecture is
  25. /// not supported.
  26. class UnsupportedArchitectureException : Exception
  27. {
  28. this(string architecture, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) pure nothrow @safe
  29. {
  30. super("Unsupported architecture: "~architecture, file, line, nextInChain);
  31. }
  32. }
  33.  
  34. /// Exception thrown in getCompiler if no compiler matches the given name.
  35. class UnknownCompilerException : Exception
  36. {
  37. this(string compilerName, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) pure nothrow @safe
  38. {
  39. super("Unknown compiler: "~compilerName, file, line, nextInChain);
  40. }
  41. }
  42.  
  43. /// Exception thrown in invokeTool and probePlatform if running the compiler
  44. /// returned non-zero exit code.
  45. class CompilerInvocationException : Exception
  46. {
  47. this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) pure nothrow @safe
  48. {
  49. super(msg, file, line, nextInChain);
  50. }
  51. }
  52.  
  53. /** Returns a compiler handler for a given binary name.
  54.  
  55. The name will be compared against the canonical name of each registered
  56. compiler handler. If no match is found, the sub strings "dmd", "gdc" and
  57. "ldc", in this order, will be searched within the name. If this doesn't
  58. yield a match either, an $(LREF UnknownCompilerException) will be thrown.
  59. */
  60. Compiler getCompiler(string name)
  61. {
  62. foreach (c; s_compilers)
  63. if (c.name == name)
  64. return c;
  65.  
  66. // try to match names like gdmd or gdc-2.61
  67. if (name.canFind("dmd")) return getCompiler("dmd");
  68. if (name.canFind("gdc")) return getCompiler("gdc");
  69. if (name.canFind("ldc")) return getCompiler("ldc");
  70.  
  71. throw new UnknownCompilerException(name);
  72. }
  73.  
  74. /** Registers a new compiler handler.
  75.  
  76. Note that by default `DMDCompiler`, `GDCCompiler` and `LDCCompiler` are
  77. already registered at startup.
  78. */
  79. void registerCompiler(Compiler c)
  80. {
  81. s_compilers ~= c;
  82. }
  83.  
  84.  
  85. interface Compiler {
  86. /// Returns the canonical name of the compiler (e.g. "dmd").
  87. @property string name() const;
  88.  
  89. /** Determines the build platform properties given a set of build settings.
  90.  
  91. This will invoke the compiler to build a platform probe file, which
  92. determines the target build platform's properties during compile-time.
  93.  
  94. See_Also: `dub.compilers.utils.generatePlatformProbeFile`
  95. */
  96. BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null);
  97.  
  98. /// Replaces high level fields with low level fields and converts
  99. /// dmd flags to compiler-specific flags
  100. void prepareBuildSettings(ref BuildSettings settings, const scope ref BuildPlatform platform, BuildSetting supported_fields = BuildSetting.all) const;
  101.  
  102. /// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field.
  103. void extractBuildOptions(ref BuildSettings settings) const;
  104.  
  105. /// Computes the full file name of the generated binary.
  106. string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) const;
  107.  
  108. /// Adds the appropriate flag to set a target path
  109. void setTarget(ref BuildSettings settings, in BuildPlatform platform, string targetPath = null) const;
  110.  
  111. /// Invokes the compiler using the given flags
  112. deprecated("specify the working directory")
  113. final void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback)
  114. {
  115. invoke(settings, platform, output_callback, getWorkingDirectory());
  116. }
  117.  
  118. /// ditto
  119. void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback, NativePath cwd);
  120.  
  121. /// Invokes the underlying linker directly
  122. deprecated("specify the working directory")
  123. final void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback)
  124. {
  125. invokeLinker(settings, platform, objects, output_callback, getWorkingDirectory());
  126. }
  127.  
  128. /// ditto
  129. void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback, NativePath cwd);
  130.  
  131. /// Convert linker flags to compiler format
  132. string[] lflagsToDFlags(const string[] lflags) const;
  133.  
  134. /// Determines compiler version
  135. string determineVersion(string compiler_binary, string verboseOutput);
  136.  
  137. /** Runs a tool and provides common boilerplate code.
  138.  
  139. This method should be used by `Compiler` implementations to invoke the
  140. compiler or linker binary.
  141. */
  142. deprecated("specify the working directory")
  143. protected final void invokeTool(string[] args, void delegate(int, string) output_callback, string[string] env = null)
  144. {
  145. invokeTool(args, output_callback, getWorkingDirectory(), env);
  146. }
  147.  
  148. /// ditto
  149. protected final void invokeTool(string[] args, void delegate(int, string) output_callback, NativePath cwd, string[string] env = null)
  150. {
  151. import std.string;
  152.  
  153. int status;
  154. if (output_callback) {
  155. auto result = execute(args,
  156. env, Config.none, size_t.max, cwd.toNativeString());
  157. output_callback(result.status, result.output);
  158. status = result.status;
  159. } else {
  160. auto compiler_pid = spawnProcess(args,
  161. env, Config.none, cwd.toNativeString());
  162. status = compiler_pid.wait();
  163. }
  164.  
  165. version (Posix) if (status == -9) {
  166. throw new CompilerInvocationException(
  167. format("%s failed with exit code %s. This may indicate that the process has run out of memory.",
  168. args[0], status));
  169. }
  170. enforce!CompilerInvocationException(status == 0,
  171. format("%s failed with exit code %s.", args[0], status));
  172. }
  173.  
  174. /** Compiles platform probe file with the specified compiler and parses its output.
  175. Params:
  176. compiler_binary = binary to invoke compiler with
  177. args = arguments for the probe compilation
  178. arch_override = special handler for x86_mscoff
  179. */
  180. protected final BuildPlatform probePlatform(string compiler_binary, string[] args,
  181. string arch_override)
  182. {
  183. import dub.compilers.utils : generatePlatformProbeFile, readPlatformJsonProbe;
  184. import std.string : format, strip;
  185.  
  186. auto fil = generatePlatformProbeFile();
  187.  
  188. auto result = execute(compiler_binary ~ args ~ fil.toNativeString());
  189. enforce!CompilerInvocationException(result.status == 0,
  190. format("Failed to invoke the compiler %s to determine the build platform: %s",
  191. compiler_binary, result.output));
  192.  
  193. auto build_platform = readPlatformJsonProbe(result.output);
  194. build_platform.compilerBinary = compiler_binary;
  195.  
  196. auto ver = determineVersion(compiler_binary, result.output)
  197. .strip;
  198. if (ver.empty) {
  199. logWarn(`Could not probe the compiler version for "%s". ` ~
  200. `Toolchain requirements might be ineffective`, build_platform.compiler);
  201. }
  202. else {
  203. build_platform.compilerVersion = ver;
  204. }
  205.  
  206. // Skip the following check for LDC, emitting a warning if the specified `-arch`
  207. // cmdline option does not lead to the same string being found among
  208. // `build_platform.architecture`, as it's brittle and doesn't work with triples.
  209. if (build_platform.compiler != "ldc") {
  210. if (arch_override.length && !build_platform.architecture.canFind(arch_override) &&
  211. !(build_platform.compiler == "dmd" && arch_override.among("x86_omf", "x86_mscoff")) // Will be fixed in determinePlatform
  212. ) {
  213. logWarn(`Failed to apply the selected architecture %s. Got %s.`,
  214. arch_override, build_platform.architecture);
  215. }
  216. }
  217.  
  218. return build_platform;
  219. }
  220. }
  221.  
  222. private {
  223. Compiler[] s_compilers;
  224. }