Newer
Older
dub_jkp / source / dub / compilers / utils.d
@سليمان السهمي  (Suleyman Sahmi) سليمان السهمي (Suleyman Sahmi) on 10 Nov 2019 13 KB reduce visual complexity of concatenation with std.string.fromat
  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.core.log;
  13. import dub.internal.vibecompat.inet.path;
  14. import std.algorithm : canFind, endsWith, filter;
  15.  
  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(in 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.platform.canFind("windows");
  51. case ".a", ".o", ".so", ".dylib":
  52. return !platform.platform.canFind("windows");
  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. Replaces each referenced import library by the appropriate linker flags.
  78.  
  79. This function tries to invoke "pkg-config" if possible and falls back to
  80. direct flag translation if that fails.
  81. */
  82. void resolveLibs(ref BuildSettings settings, in ref BuildPlatform platform)
  83. {
  84. import std.string : format;
  85. import std.array : array;
  86.  
  87. if (settings.libs.length == 0) return;
  88.  
  89. if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) {
  90. logDiagnostic("Ignoring all import libraries for static library build.");
  91. settings.libs = null;
  92. if (platform.platform.canFind("windows"))
  93. settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array;
  94. }
  95.  
  96. version (Posix) {
  97. import std.algorithm : any, map, partition, startsWith;
  98. import std.array : array, join, split;
  99. import std.exception : enforce;
  100. import std.process : execute;
  101.  
  102. try {
  103. enum pkgconfig_bin = "pkg-config";
  104.  
  105. bool exists(string lib) {
  106. return execute([pkgconfig_bin, "--exists", lib]).status == 0;
  107. }
  108.  
  109. auto pkgconfig_libs = settings.libs.partition!(l => !exists(l));
  110. pkgconfig_libs ~= settings.libs[0 .. $ - pkgconfig_libs.length]
  111. .partition!(l => !exists("lib"~l)).map!(l => "lib"~l).array;
  112. settings.libs = settings.libs[0 .. $ - pkgconfig_libs.length];
  113.  
  114. if (pkgconfig_libs.length) {
  115. logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.join(", "));
  116. auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs);
  117. enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output));
  118. foreach (f; libflags.output.split()) {
  119. if (f.startsWith("-L-L")) {
  120. settings.addLFlags(f[2 .. $]);
  121. } else if (f.startsWith("-defaultlib")) {
  122. settings.addDFlags(f);
  123. } else if (f.startsWith("-L-defaultlib")) {
  124. settings.addDFlags(f[2 .. $]);
  125. } else if (f.startsWith("-pthread")) {
  126. settings.addLFlags("-lpthread");
  127. } else if (f.startsWith("-L-l")) {
  128. settings.addLFlags(f[2 .. $].split(","));
  129. } else if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(","));
  130. else settings.addLFlags(f);
  131. }
  132. }
  133. if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", "));
  134. } catch (Exception e) {
  135. logDiagnostic("pkg-config failed: %s", e.msg);
  136. logDiagnostic("Falling back to direct -l... flags.");
  137. }
  138. }
  139. }
  140.  
  141.  
  142. /** Searches the given list of compiler flags for ones that have a generic
  143. equivalent.
  144.  
  145. Certain compiler flags should, instead of using compiler-specific syntax,
  146. be specified as build options (`BuildOptions`) or built requirements
  147. (`BuildRequirements`). This function will output warning messages to
  148. assist the user in making the best choice.
  149. */
  150. void warnOnSpecialCompilerFlags(string[] compiler_flags, BuildOptions options, string package_name, string config_name)
  151. {
  152. import std.algorithm : any, endsWith, startsWith;
  153. import std.range : empty;
  154.  
  155. struct SpecialFlag {
  156. string[] flags;
  157. string alternative;
  158. }
  159. static immutable SpecialFlag[] s_specialFlags = [
  160. {["-c", "-o-"], "Automatically issued by DUB, do not specify in dub.json"},
  161. {["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`},
  162. {["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"},
  163. {["-wi"], `Use the "buildRequirements" field to control warning behavior`},
  164. {["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`},
  165. {["-of"], `Use "targetPath" and "targetName" to customize the output file`},
  166. {["-debug", "-fdebug", "-g"], "Call dub with --build=debug"},
  167. {["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"},
  168. {["-unittest", "-funittest"], "Call dub with --build=unittest"},
  169. {["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`},
  170. {["-D"], "Call dub with --build=docs or --build=ddox"},
  171. {["-X"], "Call dub with --build=ddox"},
  172. {["-cov"], "Call dub with --build=cov or --build=unittest-cov"},
  173. {["-profile"], "Call dub with --build=profile"},
  174. {["-version="], `Use "versions" to specify version constants in a compiler independent way`},
  175. {["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`},
  176. {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`},
  177. {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`},
  178. {["-m32", "-m64", "-m32mscoff"], `Use --arch=x86/--arch=x86_64/--arch=x86_mscoff to specify the target architecture, e.g. 'dub build --arch=x86_64'`}
  179. ];
  180.  
  181. struct SpecialOption {
  182. BuildOption[] flags;
  183. string alternative;
  184. }
  185. static immutable SpecialOption[] s_specialOptions = [
  186. {[BuildOption.debugMode], "Call DUB with --build=debug"},
  187. {[BuildOption.releaseMode], "Call DUB with --build=release"},
  188. {[BuildOption.coverage], "Call DUB with --build=cov or --build=unittest-cov"},
  189. {[BuildOption.debugInfo], "Call DUB with --build=debug"},
  190. {[BuildOption.inline], "Call DUB with --build=release"},
  191. {[BuildOption.noBoundsCheck], "Call DUB with --build=release-nobounds"},
  192. {[BuildOption.optimize], "Call DUB with --build=release"},
  193. {[BuildOption.profile], "Call DUB with --build=profile"},
  194. {[BuildOption.unittests], "Call DUB with --build=unittest"},
  195. {[BuildOption.syntaxOnly], "Call DUB with --build=syntax"},
  196. {[BuildOption.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"},
  197. {[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"},
  198. {[BuildOption.property], "This flag is deprecated and has no effect"}
  199. ];
  200.  
  201. bool got_preamble = false;
  202. void outputPreamble()
  203. {
  204. if (got_preamble) return;
  205. got_preamble = true;
  206. logWarn("");
  207. if (config_name.empty) logWarn("## Warning for package %s ##", package_name);
  208. else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name);
  209. logWarn("");
  210. logWarn("The following compiler flags have been specified in the package description");
  211. logWarn("file. They are handled by DUB and direct use in packages is discouraged.");
  212. logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags");
  213. logWarn("to the compiler, or use one of the suggestions below:");
  214. logWarn("");
  215. }
  216.  
  217. foreach (f; compiler_flags) {
  218. foreach (sf; s_specialFlags) {
  219. if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) {
  220. outputPreamble();
  221. logWarn("%s: %s", f, sf.alternative);
  222. break;
  223. }
  224. }
  225. }
  226.  
  227. foreach (sf; s_specialOptions) {
  228. foreach (f; sf.flags) {
  229. if (options & f) {
  230. outputPreamble();
  231. logWarn("%s: %s", f, sf.alternative);
  232. break;
  233. }
  234. }
  235. }
  236.  
  237. if (got_preamble) logWarn("");
  238. }
  239.  
  240. /**
  241. Turn a DMD-like version (e.g. 2.082.1) into a SemVer-like version (e.g. 2.82.1).
  242. The function accepts a dependency operator prefix and some text postfix.
  243. Prefix and postfix are returned verbatim.
  244. Params:
  245. ver = version string, possibly with a dependency operator prefix and some
  246. test postfix.
  247. Returns:
  248. A Semver compliant string
  249. */
  250. package(dub) string dmdLikeVersionToSemverLike(string ver)
  251. {
  252. import std.algorithm : countUntil, joiner, map, skipOver, splitter;
  253. import std.array : join, split;
  254. import std.ascii : isDigit;
  255. import std.conv : text;
  256. import std.exception : enforce;
  257. import std.functional : not;
  258. import std.range : padRight;
  259.  
  260. const start = ver.countUntil!isDigit;
  261. enforce(start != -1, "Invalid semver: "~ver);
  262. const prefix = ver[0 .. start];
  263. ver = ver[start .. $];
  264.  
  265. const end = ver.countUntil!(c => !c.isDigit && c != '.');
  266. const postfix = end == -1 ? null : ver[end .. $];
  267. auto verStr = ver[0 .. $-postfix.length];
  268.  
  269. auto comps = verStr
  270. .splitter(".")
  271. .map!((a) { if (a.length > 1) a.skipOver("0"); return a;})
  272. .padRight("0", 3);
  273.  
  274. return text(prefix, comps.joiner("."), postfix);
  275. }
  276.  
  277. ///
  278. unittest {
  279. assert(dmdLikeVersionToSemverLike("2.082.1") == "2.82.1");
  280. assert(dmdLikeVersionToSemverLike("2.082.0") == "2.82.0");
  281. assert(dmdLikeVersionToSemverLike("2.082") == "2.82.0");
  282. assert(dmdLikeVersionToSemverLike("~>2.082") == "~>2.82.0");
  283. assert(dmdLikeVersionToSemverLike("~>2.082-beta1") == "~>2.82.0-beta1");
  284. assert(dmdLikeVersionToSemverLike("2.4.6") == "2.4.6");
  285. assert(dmdLikeVersionToSemverLike("2.4.6-alpha12") == "2.4.6-alpha12");
  286. }
  287.  
  288. private enum probeBeginMark = "__dub_probe_begin__";
  289. private enum probeEndMark = "__dub_probe_end__";
  290.  
  291. /**
  292. Generate a file that will give, at compile time, information about the compiler (architecture, frontend version...)
  293.  
  294. See_Also: `readPlatformProbe`
  295. */
  296. NativePath generatePlatformProbeFile()
  297. {
  298. import dub.internal.vibecompat.core.file;
  299. import dub.internal.vibecompat.data.json;
  300. import dub.internal.utils;
  301. import std.string : format;
  302.  
  303. // try to not use phobos in the probe to avoid long import times
  304. enum probe = q{
  305. module dub_platform_probe;
  306.  
  307. template toString(int v) { enum toString = v.stringof; }
  308. string stringArray(string[] ary) {
  309. string res;
  310. foreach (i, e; ary) {
  311. if (i)
  312. res ~= ", ";
  313. res ~= '"' ~ e ~ '"';
  314. }
  315. return res;
  316. }
  317.  
  318. pragma(msg, `%1$s`
  319. ~ '\n' ~ `{`
  320. ~ '\n' ~ ` "compiler": "`~ determineCompiler() ~ `",`
  321. ~ '\n' ~ ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,`
  322. ~ '\n' ~ ` "compilerVendor": "` ~ __VENDOR__ ~ `",`
  323. ~ '\n' ~ ` "platform": [`
  324. ~ '\n' ~ ` ` ~ determinePlatform().stringArray
  325. ~ '\n' ~ ` ],`
  326. ~ '\n' ~ ` "architecture": [`
  327. ~ '\n' ~ ` ` ~ determineArchitecture().stringArray
  328. ~ '\n' ~ ` ],`
  329. ~ '\n' ~ `}`
  330. ~ '\n' ~ `%2$s`);
  331.  
  332. string[] determinePlatform() { %3$s }
  333. string[] determineArchitecture() { %4$s }
  334. string determineCompiler() { %5$s }
  335.  
  336. }.format(probeBeginMark, probeEndMark, platformCheck, archCheck, compilerCheck);
  337.  
  338. auto path = getTempFile("dub_platform_probe", ".d");
  339. auto fil = openFile(path, FileMode.createTrunc);
  340. fil.write(probe);
  341.  
  342. return path;
  343. }
  344.  
  345. /**
  346. Processes the JSON output generated by compiling the platform probe file.
  347.  
  348. See_Also: `generatePlatformProbeFile`.
  349. */
  350. BuildPlatform readPlatformJsonProbe(string output)
  351. {
  352. import std.algorithm : map;
  353. import std.array : array;
  354. import std.exception : enforce;
  355. import std.string;
  356.  
  357. // work around possible additional output of the compiler
  358. auto idx1 = output.indexOf(probeBeginMark);
  359. auto idx2 = output.lastIndexOf(probeEndMark);
  360. enforce(idx1 >= 0 && idx1 < idx2,
  361. "Unexpected platform information output - does not contain a JSON object.");
  362. output = output[idx1+probeBeginMark.length .. idx2];
  363.  
  364. import dub.internal.vibecompat.data.json;
  365. auto json = parseJsonString(output);
  366.  
  367. BuildPlatform build_platform;
  368. build_platform.platform = json["platform"].get!(Json[]).map!(e => e.get!string()).array();
  369. build_platform.architecture = json["architecture"].get!(Json[]).map!(e => e.get!string()).array();
  370. build_platform.compiler = json["compiler"].get!string;
  371. build_platform.frontendVersion = json["frontendVersion"].get!int;
  372. return build_platform;
  373. }