Newer
Older
dub_jkp / source / dub / compilers / utils.d
@Mathias Lang Mathias Lang on 20 Dec 2023 13 KB Move dmd version parser to packagerecipe
  1. /**
  2. Utility functionality for compiler class implementations.
  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.utils;
  9.  
  10. import dub.compilers.buildsettings;
  11. import dub.platform : BuildPlatform, archCheck, compilerCheck, platformCheck;
  12. import dub.internal.vibecompat.inet.path;
  13. import dub.internal.logging;
  14.  
  15. import std.algorithm : canFind, endsWith, filter;
  16.  
  17. /**
  18. Alters the build options to comply with the specified build requirements.
  19.  
  20. And enabled options that do not comply will get disabled.
  21. */
  22. void enforceBuildRequirements(ref BuildSettings settings)
  23. {
  24. settings.addOptions(BuildOption.warningsAsErrors);
  25. if (settings.requirements & BuildRequirement.allowWarnings) { settings.options &= ~BuildOption.warningsAsErrors; settings.options |= BuildOption.warnings; }
  26. if (settings.requirements & BuildRequirement.silenceWarnings) settings.options &= ~(BuildOption.warningsAsErrors|BuildOption.warnings);
  27. if (settings.requirements & BuildRequirement.disallowDeprecations) { settings.options &= ~(BuildOption.ignoreDeprecations|BuildOption.deprecationWarnings); settings.options |= BuildOption.deprecationErrors; }
  28. if (settings.requirements & BuildRequirement.silenceDeprecations) { settings.options &= ~(BuildOption.deprecationErrors|BuildOption.deprecationWarnings); settings.options |= BuildOption.ignoreDeprecations; }
  29. if (settings.requirements & BuildRequirement.disallowInlining) settings.options &= ~BuildOption.inline;
  30. if (settings.requirements & BuildRequirement.disallowOptimization) settings.options &= ~BuildOption.optimize;
  31. if (settings.requirements & BuildRequirement.requireBoundsCheck) settings.options &= ~BuildOption.noBoundsCheck;
  32. if (settings.requirements & BuildRequirement.requireContracts) settings.options &= ~BuildOption.releaseMode;
  33. if (settings.requirements & BuildRequirement.relaxProperties) settings.options &= ~BuildOption.property;
  34. }
  35.  
  36.  
  37. /**
  38. Determines if a specific file name has the extension of a linker file.
  39.  
  40. Linker files include static/dynamic libraries, resource files, object files
  41. and DLL definition files.
  42. */
  43. bool isLinkerFile(const scope ref BuildPlatform platform, string f)
  44. {
  45. import std.path;
  46. switch (extension(f)) {
  47. default:
  48. return false;
  49. case ".lib", ".obj", ".res", ".def":
  50. return platform.isWindows();
  51. case ".a", ".o", ".so", ".dylib":
  52. return !platform.isWindows();
  53. }
  54. }
  55.  
  56. unittest {
  57. BuildPlatform p;
  58.  
  59. p.platform = ["windows"];
  60. assert(isLinkerFile(p, "test.obj"));
  61. assert(isLinkerFile(p, "test.lib"));
  62. assert(isLinkerFile(p, "test.res"));
  63. assert(!isLinkerFile(p, "test.o"));
  64. assert(!isLinkerFile(p, "test.d"));
  65.  
  66. p.platform = ["something else"];
  67. assert(isLinkerFile(p, "test.o"));
  68. assert(isLinkerFile(p, "test.a"));
  69. assert(isLinkerFile(p, "test.so"));
  70. assert(isLinkerFile(p, "test.dylib"));
  71. assert(!isLinkerFile(p, "test.obj"));
  72. assert(!isLinkerFile(p, "test.d"));
  73. }
  74.  
  75.  
  76. /**
  77. Adds a default DT_SONAME (ELF) / 'install name' (Mach-O) when linking a dynamic library.
  78. This makes dependees reference their dynamic-lib deps by filename only (DT_NEEDED etc.)
  79. instead of by the path used in the dependee linker cmdline, and enables loading the
  80. deps from the dependee's output directory - either by setting the LD_LIBRARY_PATH
  81. environment variable, or baking an rpath into the executable.
  82. */
  83. package void addDynamicLibName(ref BuildSettings settings, in BuildPlatform platform, string fileName)
  84. {
  85. if (!platform.isWindows()) {
  86. // *pre*pend to allow the user to override it
  87. if (platform.platform.canFind("darwin"))
  88. settings.prependLFlags("-install_name", "@rpath/" ~ fileName);
  89. else
  90. settings.prependLFlags("-soname", fileName);
  91. }
  92. }
  93.  
  94.  
  95. /**
  96. Replaces each referenced import library by the appropriate linker flags.
  97.  
  98. This function tries to invoke "pkg-config" if possible and falls back to
  99. direct flag translation if that fails.
  100. */
  101. void resolveLibs(ref BuildSettings settings, const scope ref BuildPlatform platform)
  102. {
  103. import std.string : format;
  104. import std.array : array;
  105.  
  106. if (settings.libs.length == 0) return;
  107.  
  108. if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) {
  109. logDiagnostic("Ignoring all import libraries for static library build.");
  110. settings.libs = null;
  111. if (platform.isWindows())
  112. settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array;
  113. }
  114.  
  115. version (Posix) {
  116. import std.algorithm : any, map, partition, startsWith;
  117. import std.array : array, join, split;
  118. import std.exception : enforce;
  119. import std.process : execute;
  120.  
  121. try {
  122. enum pkgconfig_bin = "pkg-config";
  123.  
  124. bool exists(string lib) {
  125. return execute([pkgconfig_bin, "--exists", lib]).status == 0;
  126. }
  127.  
  128. auto pkgconfig_libs = settings.libs.partition!(l => !exists(l));
  129. pkgconfig_libs ~= settings.libs[0 .. $ - pkgconfig_libs.length]
  130. .partition!(l => !exists("lib"~l)).map!(l => "lib"~l).array;
  131. settings.libs = settings.libs[0 .. $ - pkgconfig_libs.length];
  132.  
  133. if (pkgconfig_libs.length) {
  134. logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.join(", "));
  135. auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs);
  136. enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output));
  137. foreach (f; libflags.output.split()) {
  138. if (f.startsWith("-L-L")) {
  139. settings.addLFlags(f[2 .. $]);
  140. } else if (f.startsWith("-defaultlib")) {
  141. settings.addDFlags(f);
  142. } else if (f.startsWith("-L-defaultlib")) {
  143. settings.addDFlags(f[2 .. $]);
  144. } else if (f.startsWith("-pthread")) {
  145. settings.addLFlags("-lpthread");
  146. } else if (f.startsWith("-L-l")) {
  147. settings.addLFlags(f[2 .. $].split(","));
  148. } else if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(","));
  149. else settings.addLFlags(f);
  150. }
  151. }
  152. if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", "));
  153. } catch (Exception e) {
  154. logDiagnostic("pkg-config failed: %s", e.msg);
  155. logDiagnostic("Falling back to direct -l... flags.");
  156. }
  157. }
  158. }
  159.  
  160.  
  161. /** Searches the given list of compiler flags for ones that have a generic
  162. equivalent.
  163.  
  164. Certain compiler flags should, instead of using compiler-specific syntax,
  165. be specified as build options (`BuildOption`) or built requirements
  166. (`BuildRequirements`). This function will output warning messages to
  167. assist the user in making the best choice.
  168. */
  169. void warnOnSpecialCompilerFlags(string[] compiler_flags, Flags!BuildOption options, string package_name, string config_name)
  170. {
  171. import std.algorithm : any, endsWith, startsWith;
  172. import std.range : empty;
  173.  
  174. struct SpecialFlag {
  175. string[] flags;
  176. string alternative;
  177. }
  178. static immutable SpecialFlag[] s_specialFlags = [
  179. {["-c", "-o-"], "Automatically issued by DUB, do not specify in dub.json"},
  180. {["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`},
  181. {["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"},
  182. {["-wi"], `Use the "buildRequirements" field to control warning behavior`},
  183. {["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`},
  184. {["-of"], `Use "targetPath" and "targetName" to customize the output file`},
  185. {["-debug", "-fdebug", "-g"], "Call dub with --build=debug"},
  186. {["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"},
  187. {["-unittest", "-funittest"], "Call dub with --build=unittest"},
  188. {["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`},
  189. {["-D"], "Call dub with --build=docs or --build=ddox"},
  190. {["-X"], "Call dub with --build=ddox"},
  191. {["-cov"], "Call dub with --build=cov or --build=unittest-cov"},
  192. {["-cov=ctfe"], "Call dub with --build=cov-ctfe or --build=unittest-cov-ctfe"},
  193. {["-profile"], "Call dub with --build=profile"},
  194. {["-version="], `Use "versions" to specify version constants in a compiler independent way`},
  195. {["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`},
  196. {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`},
  197. {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`},
  198. {["-m32", "-m64", "-m32mscoff"], `Use --arch=x86/--arch=x86_64/--arch=x86_mscoff to specify the target architecture, e.g. 'dub build --arch=x86_64'`}
  199. ];
  200.  
  201. struct SpecialOption {
  202. BuildOption[] flags;
  203. string alternative;
  204. }
  205. static immutable SpecialOption[] s_specialOptions = [
  206. {[BuildOption.debugMode], "Call DUB with --build=debug"},
  207. {[BuildOption.releaseMode], "Call DUB with --build=release"},
  208. {[BuildOption.coverage], "Call DUB with --build=cov or --build=unittest-cov"},
  209. {[BuildOption.coverageCTFE], "Call DUB with --build=cov-ctfe or --build=unittest-cov-ctfe"},
  210. {[BuildOption.debugInfo], "Call DUB with --build=debug"},
  211. {[BuildOption.inline], "Call DUB with --build=release"},
  212. {[BuildOption.noBoundsCheck], "Call DUB with --build=release-nobounds"},
  213. {[BuildOption.optimize], "Call DUB with --build=release"},
  214. {[BuildOption.profile], "Call DUB with --build=profile"},
  215. {[BuildOption.unittests], "Call DUB with --build=unittest"},
  216. {[BuildOption.syntaxOnly], "Call DUB with --build=syntax"},
  217. {[BuildOption.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"},
  218. {[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"},
  219. {[BuildOption.property], "This flag is deprecated and has no effect"}
  220. ];
  221.  
  222. bool got_preamble = false;
  223. void outputPreamble()
  224. {
  225. if (got_preamble) return;
  226. got_preamble = true;
  227. logWarn("");
  228. if (config_name.empty) logWarn("## Warning for package %s ##", package_name);
  229. else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name);
  230. logWarn("");
  231. logWarn("The following compiler flags have been specified in the package description");
  232. logWarn("file. They are handled by DUB and direct use in packages is discouraged.");
  233. logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags");
  234. logWarn("to the compiler, or use one of the suggestions below:");
  235. logWarn("");
  236. }
  237.  
  238. foreach (f; compiler_flags) {
  239. foreach (sf; s_specialFlags) {
  240. if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) {
  241. outputPreamble();
  242. logWarn("%s: %s", f, sf.alternative);
  243. break;
  244. }
  245. }
  246. }
  247.  
  248. foreach (sf; s_specialOptions) {
  249. foreach (f; sf.flags) {
  250. if (options & f) {
  251. outputPreamble();
  252. logWarn("%s: %s", f, sf.alternative);
  253. break;
  254. }
  255. }
  256. }
  257.  
  258. if (got_preamble) logWarn("");
  259. }
  260.  
  261. private enum probeBeginMark = "__dub_probe_begin__";
  262. private enum probeEndMark = "__dub_probe_end__";
  263.  
  264. /**
  265. Generate a file that will give, at compile time, information about the compiler (architecture, frontend version...)
  266.  
  267. See_Also: `readPlatformProbe`
  268. */
  269. NativePath generatePlatformProbeFile()
  270. {
  271. import dub.internal.vibecompat.core.file;
  272. import dub.internal.vibecompat.data.json;
  273. import dub.internal.utils;
  274. import std.string : format;
  275.  
  276. // try to not use phobos in the probe to avoid long import times
  277. enum probe = q{
  278. module dub_platform_probe;
  279.  
  280. template toString(int v) { enum toString = v.stringof; }
  281. string stringArray(string[] ary) {
  282. string res;
  283. foreach (i, e; ary) {
  284. if (i)
  285. res ~= ", ";
  286. res ~= '"' ~ e ~ '"';
  287. }
  288. return res;
  289. }
  290.  
  291. pragma(msg, `%1$s`
  292. ~ '\n' ~ `{`
  293. ~ '\n' ~ ` "compiler": "`~ determineCompiler() ~ `",`
  294. ~ '\n' ~ ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,`
  295. ~ '\n' ~ ` "compilerVendor": "` ~ __VENDOR__ ~ `",`
  296. ~ '\n' ~ ` "platform": [`
  297. ~ '\n' ~ ` ` ~ determinePlatform().stringArray
  298. ~ '\n' ~ ` ],`
  299. ~ '\n' ~ ` "architecture": [`
  300. ~ '\n' ~ ` ` ~ determineArchitecture().stringArray
  301. ~ '\n' ~ ` ],`
  302. ~ '\n' ~ `}`
  303. ~ '\n' ~ `%2$s`);
  304.  
  305. string[] determinePlatform() { %3$s }
  306. string[] determineArchitecture() { %4$s }
  307. string determineCompiler() { %5$s }
  308.  
  309. }.format(probeBeginMark, probeEndMark, platformCheck, archCheck, compilerCheck);
  310.  
  311. auto path = getTempFile("dub_platform_probe", ".d");
  312. writeFile(path, probe);
  313.  
  314. return path;
  315. }
  316.  
  317. /**
  318. Processes the JSON output generated by compiling the platform probe file.
  319.  
  320. See_Also: `generatePlatformProbeFile`.
  321. */
  322. BuildPlatform readPlatformJsonProbe(string output)
  323. {
  324. import std.algorithm : map;
  325. import std.array : array;
  326. import std.exception : enforce;
  327. import std.string;
  328.  
  329. // work around possible additional output of the compiler
  330. auto idx1 = output.indexOf(probeBeginMark);
  331. auto idx2 = output.lastIndexOf(probeEndMark);
  332. enforce(idx1 >= 0 && idx1 < idx2,
  333. "Unexpected platform information output - does not contain a JSON object.");
  334. output = output[idx1+probeBeginMark.length .. idx2];
  335.  
  336. import dub.internal.vibecompat.data.json;
  337. auto json = parseJsonString(output);
  338.  
  339. BuildPlatform build_platform;
  340. build_platform.platform = json["platform"].get!(Json[]).map!(e => e.get!string()).array();
  341. build_platform.architecture = json["architecture"].get!(Json[]).map!(e => e.get!string()).array();
  342. build_platform.compiler = json["compiler"].get!string;
  343. build_platform.frontendVersion = json["frontendVersion"].get!int;
  344. return build_platform;
  345. }