Newer
Older
dub_jkp / source / dub / recipe / packagerecipe.d
  1. /**
  2. Abstract representation of a package description file.
  3.  
  4. Copyright: © 2012-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, Matthias Dondorff
  7. */
  8. module dub.recipe.packagerecipe;
  9.  
  10. import dub.compilers.compiler;
  11. import dub.compilers.utils : warnOnSpecialCompilerFlags;
  12. import dub.dependency;
  13.  
  14. import dub.internal.vibecompat.core.file;
  15. import dub.internal.vibecompat.core.log;
  16. import dub.internal.vibecompat.inet.path;
  17.  
  18. import std.algorithm : findSplit, sort;
  19. import std.array : join, split;
  20. import std.exception : enforce;
  21. import std.file;
  22. import std.range;
  23. import std.process : environment;
  24.  
  25.  
  26. /**
  27. Returns the individual parts of a qualified package name.
  28.  
  29. Sub qualified package names are lists of package names separated by ":". For
  30. example, "packa:packb:packc" references a package named "packc" that is a
  31. sub package of "packb", which in turn is a sub package of "packa".
  32. */
  33. string[] getSubPackagePath(string package_name) @safe pure
  34. {
  35. return package_name.split(":");
  36. }
  37.  
  38. /**
  39. Returns the name of the top level package for a given (sub) package name.
  40.  
  41. In case of a top level package, the qualified name is returned unmodified.
  42. */
  43. string getBasePackageName(string package_name) @safe pure
  44. {
  45. return package_name.findSplit(":")[0];
  46. }
  47.  
  48. /**
  49. Returns the qualified sub package part of the given package name.
  50.  
  51. This is the part of the package name excluding the base package
  52. name. See also $(D getBasePackageName).
  53. */
  54. string getSubPackageName(string package_name) @safe pure
  55. {
  56. return package_name.findSplit(":")[2];
  57. }
  58.  
  59. @safe unittest
  60. {
  61. assert(getSubPackagePath("packa:packb:packc") == ["packa", "packb", "packc"]);
  62. assert(getSubPackagePath("pack") == ["pack"]);
  63. assert(getBasePackageName("packa:packb:packc") == "packa");
  64. assert(getBasePackageName("pack") == "pack");
  65. assert(getSubPackageName("packa:packb:packc") == "packb:packc");
  66. assert(getSubPackageName("pack") == "");
  67. }
  68.  
  69. /**
  70. Represents the contents of a package recipe file (dub.json/dub.sdl) in an abstract way.
  71.  
  72. This structure is used to reason about package descriptions in isolation.
  73. For higher level package handling, see the $(D Package) class.
  74. */
  75. struct PackageRecipe {
  76. string name;
  77. string version_;
  78. string description;
  79. string homepage;
  80. string[] authors;
  81. string copyright;
  82. string license;
  83. string[] ddoxFilterArgs;
  84. string ddoxTool;
  85. BuildSettingsTemplate buildSettings;
  86. ConfigurationInfo[] configurations;
  87. BuildSettingsTemplate[string] buildTypes;
  88.  
  89. ToolchainRequirements toolchainRequirements;
  90.  
  91. SubPackage[] subPackages;
  92.  
  93. inout(ConfigurationInfo) getConfiguration(string name)
  94. inout {
  95. foreach (c; configurations)
  96. if (c.name == name)
  97. return c;
  98. throw new Exception("Unknown configuration: "~name);
  99. }
  100.  
  101. /** Clones the package recipe recursively.
  102. */
  103. PackageRecipe clone() const { return .clone(this); }
  104. }
  105.  
  106. struct SubPackage
  107. {
  108. string path;
  109. PackageRecipe recipe;
  110. }
  111.  
  112. /// Describes minimal toolchain requirements
  113. struct ToolchainRequirements
  114. {
  115. import std.typecons : Tuple, tuple;
  116.  
  117. /// DUB version requirement
  118. Dependency dub = Dependency.any;
  119. /// D front-end version requirement
  120. Dependency frontend = Dependency.any;
  121. /// DMD version requirement
  122. Dependency dmd = Dependency.any;
  123. /// LDC version requirement
  124. Dependency ldc = Dependency.any;
  125. /// GDC version requirement
  126. Dependency gdc = Dependency.any;
  127.  
  128. /** Get the list of supported compilers.
  129.  
  130. Returns:
  131. An array of couples of compiler name and compiler requirement
  132. */
  133. @property Tuple!(string, Dependency)[] supportedCompilers() const
  134. {
  135. Tuple!(string, Dependency)[] res;
  136. if (dmd != Dependency.invalid) res ~= Tuple!(string, Dependency)("dmd", dmd);
  137. if (ldc != Dependency.invalid) res ~= Tuple!(string, Dependency)("ldc", ldc);
  138. if (gdc != Dependency.invalid) res ~= Tuple!(string, Dependency)("gdc", gdc);
  139. return res;
  140. }
  141.  
  142. bool empty()
  143. const {
  144. import std.algorithm.searching : all;
  145. return only(dub, frontend, dmd, ldc, gdc)
  146. .all!(r => r == Dependency.any);
  147. }
  148. }
  149.  
  150.  
  151. /// Bundles information about a build configuration.
  152. struct ConfigurationInfo {
  153. string name;
  154. string[] platforms;
  155. BuildSettingsTemplate buildSettings;
  156.  
  157. this(string name, BuildSettingsTemplate build_settings)
  158. {
  159. enforce(!name.empty, "Configuration name is empty.");
  160. this.name = name;
  161. this.buildSettings = build_settings;
  162. }
  163.  
  164. bool matchesPlatform(in BuildPlatform platform)
  165. const {
  166. if( platforms.empty ) return true;
  167. foreach(p; platforms)
  168. if( platform.matchesSpecification("-"~p) )
  169. return true;
  170. return false;
  171. }
  172. }
  173.  
  174. /// This keeps general information about how to build a package.
  175. /// It contains functions to create a specific BuildSetting, targeted at
  176. /// a certain BuildPlatform.
  177. struct BuildSettingsTemplate {
  178. Dependency[string] dependencies;
  179. BuildSettingsTemplate[string] dependencyBuildSettings;
  180. string systemDependencies;
  181. TargetType targetType = TargetType.autodetect;
  182. string targetPath;
  183. string targetName;
  184. string workingDirectory;
  185. string mainSourceFile;
  186. string[string] subConfigurations;
  187. string[][string] dflags;
  188. string[][string] lflags;
  189. string[][string] libs;
  190. string[][string] sourceFiles;
  191. string[][string] sourcePaths;
  192. string[][string] excludedSourceFiles;
  193. string[][string] injectSourceFiles;
  194. string[][string] copyFiles;
  195. string[][string] extraDependencyFiles;
  196. string[][string] versions;
  197. string[][string] debugVersions;
  198. string[][string] versionFilters;
  199. string[][string] debugVersionFilters;
  200. string[][string] importPaths;
  201. string[][string] stringImportPaths;
  202. string[][string] preGenerateCommands;
  203. string[][string] postGenerateCommands;
  204. string[][string] preBuildCommands;
  205. string[][string] postBuildCommands;
  206. string[][string] preRunCommands;
  207. string[][string] postRunCommands;
  208. string[string][string] environments;
  209. string[string][string] buildEnvironments;
  210. string[string][string] runEnvironments;
  211. string[string][string] preGenerateEnvironments;
  212. string[string][string] postGenerateEnvironments;
  213. string[string][string] preBuildEnvironments;
  214. string[string][string] postBuildEnvironments;
  215. string[string][string] preRunEnvironments;
  216. string[string][string] postRunEnvironments;
  217. Flags!BuildRequirement[string] buildRequirements;
  218. Flags!BuildOption[string] buildOptions;
  219.  
  220.  
  221. /// Constructs a BuildSettings object from this template.
  222. void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, NativePath base_path)
  223. const {
  224. dst.targetType = this.targetType;
  225. if (!this.targetPath.empty) dst.targetPath = this.targetPath;
  226. if (!this.targetName.empty) dst.targetName = this.targetName;
  227. if (!this.workingDirectory.empty) dst.workingDirectory = this.workingDirectory;
  228. if (!this.mainSourceFile.empty) {
  229. auto p = NativePath(this.mainSourceFile);
  230. p.normalize();
  231. dst.mainSourceFile = p.toNativeString();
  232. dst.addSourceFiles(dst.mainSourceFile);
  233. }
  234.  
  235. string[] collectFiles(in string[][string] paths_map, string pattern)
  236. {
  237. auto files = appender!(string[]);
  238.  
  239. import dub.project : buildSettingsVars;
  240. import std.typecons : Nullable;
  241.  
  242. static Nullable!(string[string]) envVarCache;
  243.  
  244. if (envVarCache.isNull) envVarCache = environment.toAA();
  245.  
  246. foreach (suffix, paths; paths_map) {
  247. if (!platform.matchesSpecification(suffix))
  248. continue;
  249.  
  250. foreach (spath; paths) {
  251. enforce(!spath.empty, "Paths must not be empty strings.");
  252. auto path = NativePath(spath);
  253. if (!path.absolute) path = base_path ~ path;
  254. if (!existsFile(path) || !isDir(path.toNativeString())) {
  255. import std.algorithm : any, find;
  256. const hasVar = chain(buildSettingsVars, envVarCache.get.byKey).any!((string var) {
  257. return spath.find("$"~var).length > 0 || spath.find("${"~var~"}").length > 0;
  258. });
  259. if (!hasVar)
  260. logWarn("Invalid source/import path: %s", path.toNativeString());
  261. continue;
  262. }
  263.  
  264. auto pstr = path.toNativeString();
  265. foreach (d; dirEntries(pstr, pattern, SpanMode.depth)) {
  266. import std.path : baseName, pathSplitter;
  267. import std.algorithm.searching : canFind;
  268. // eliminate any hidden files, or files in hidden directories. But always include
  269. // files that are listed inside hidden directories that are specifically added to
  270. // the project.
  271. if (d.isDir || pathSplitter(d.name[pstr.length .. $])
  272. .canFind!(name => name.length && name[0] == '.'))
  273. continue;
  274. auto src = NativePath(d.name).relativeTo(base_path);
  275. files ~= src.toNativeString();
  276. }
  277. }
  278. }
  279.  
  280. return files.data;
  281. }
  282.  
  283. // collect source files
  284. dst.addSourceFiles(collectFiles(sourcePaths, "*.d"));
  285. auto sourceFiles = dst.sourceFiles.sort();
  286.  
  287. // collect import files and remove sources
  288. import std.algorithm : copy, setDifference;
  289.  
  290. auto importFiles = collectFiles(importPaths, "*.{d,di}").sort();
  291. immutable nremoved = importFiles.setDifference(sourceFiles).copy(importFiles.release).length;
  292. importFiles = importFiles[0 .. $ - nremoved];
  293. dst.addImportFiles(importFiles.release);
  294.  
  295. dst.addStringImportFiles(collectFiles(stringImportPaths, "*"));
  296.  
  297. getPlatformSetting!("dflags", "addDFlags")(dst, platform);
  298. getPlatformSetting!("lflags", "addLFlags")(dst, platform);
  299. getPlatformSetting!("libs", "addLibs")(dst, platform);
  300. getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform);
  301. getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform);
  302. getPlatformSetting!("injectSourceFiles", "addInjectSourceFiles")(dst, platform);
  303. getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform);
  304. getPlatformSetting!("extraDependencyFiles", "addExtraDependencyFiles")(dst, platform);
  305. getPlatformSetting!("versions", "addVersions")(dst, platform);
  306. getPlatformSetting!("debugVersions", "addDebugVersions")(dst, platform);
  307. getPlatformSetting!("versionFilters", "addVersionFilters")(dst, platform);
  308. getPlatformSetting!("debugVersionFilters", "addDebugVersionFilters")(dst, platform);
  309. getPlatformSetting!("importPaths", "addImportPaths")(dst, platform);
  310. getPlatformSetting!("stringImportPaths", "addStringImportPaths")(dst, platform);
  311. getPlatformSetting!("preGenerateCommands", "addPreGenerateCommands")(dst, platform);
  312. getPlatformSetting!("postGenerateCommands", "addPostGenerateCommands")(dst, platform);
  313. getPlatformSetting!("preBuildCommands", "addPreBuildCommands")(dst, platform);
  314. getPlatformSetting!("postBuildCommands", "addPostBuildCommands")(dst, platform);
  315. getPlatformSetting!("preRunCommands", "addPreRunCommands")(dst, platform);
  316. getPlatformSetting!("postRunCommands", "addPostRunCommands")(dst, platform);
  317. getPlatformSetting!("environments", "addEnvironments")(dst, platform);
  318. getPlatformSetting!("buildEnvironments", "addBuildEnvironments")(dst, platform);
  319. getPlatformSetting!("runEnvironments", "addRunEnvironments")(dst, platform);
  320. getPlatformSetting!("preGenerateEnvironments", "addPreGenerateEnvironments")(dst, platform);
  321. getPlatformSetting!("postGenerateEnvironments", "addPostGenerateEnvironments")(dst, platform);
  322. getPlatformSetting!("preBuildEnvironments", "addPreBuildEnvironments")(dst, platform);
  323. getPlatformSetting!("postBuildEnvironments", "addPostBuildEnvironments")(dst, platform);
  324. getPlatformSetting!("preRunEnvironments", "addPreRunEnvironments")(dst, platform);
  325. getPlatformSetting!("postRunEnvironments", "addPostRunEnvironments")(dst, platform);
  326. getPlatformSetting!("buildRequirements", "addRequirements")(dst, platform);
  327. getPlatformSetting!("buildOptions", "addOptions")(dst, platform);
  328. }
  329.  
  330. void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform)
  331. const {
  332. foreach(suffix, values; __traits(getMember, this, name)){
  333. if( platform.matchesSpecification(suffix) )
  334. __traits(getMember, dst, addname)(values);
  335. }
  336. }
  337.  
  338. void warnOnSpecialCompilerFlags(string package_name, string config_name)
  339. {
  340. auto nodef = false;
  341. auto noprop = false;
  342. foreach (req; this.buildRequirements) {
  343. if (req & BuildRequirement.noDefaultFlags) nodef = true;
  344. if (req & BuildRequirement.relaxProperties) noprop = true;
  345. }
  346.  
  347. if (noprop) {
  348. logWarn(`Warning: "buildRequirements": ["relaxProperties"] is deprecated and is now the default behavior. Note that the -property switch will probably be removed in future versions of DMD.`);
  349. logWarn("");
  350. }
  351.  
  352. if (nodef) {
  353. logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages.");
  354. logWarn("");
  355. } else {
  356. string[] all_dflags;
  357. Flags!BuildOption all_options;
  358. foreach (flags; this.dflags) all_dflags ~= flags;
  359. foreach (options; this.buildOptions) all_options |= options;
  360. .warnOnSpecialCompilerFlags(all_dflags, all_options, package_name, config_name);
  361. }
  362. }
  363. }
  364.  
  365. package(dub) void checkPlatform(const scope ref ToolchainRequirements tr, BuildPlatform platform, string package_name)
  366. {
  367. import dub.compilers.utils : dmdLikeVersionToSemverLike;
  368. import std.algorithm.iteration : map;
  369. import std.format : format;
  370.  
  371. string compilerver;
  372. Dependency compilerspec;
  373.  
  374. switch (platform.compiler) {
  375. default:
  376. compilerspec = Dependency.any;
  377. compilerver = "0.0.0";
  378. break;
  379. case "dmd":
  380. compilerspec = tr.dmd;
  381. compilerver = platform.compilerVersion.length
  382. ? dmdLikeVersionToSemverLike(platform.compilerVersion)
  383. : "0.0.0";
  384. break;
  385. case "ldc":
  386. compilerspec = tr.ldc;
  387. compilerver = platform.compilerVersion;
  388. if (!compilerver.length) compilerver = "0.0.0";
  389. break;
  390. case "gdc":
  391. compilerspec = tr.gdc;
  392. compilerver = platform.compilerVersion;
  393. if (!compilerver.length) compilerver = "0.0.0";
  394. break;
  395. }
  396.  
  397. enforce(compilerspec != Dependency.invalid,
  398. format(
  399. "Installed %s %s is not supported by %s. Supported compiler(s):\n%s",
  400. platform.compiler, platform.compilerVersion, package_name,
  401. tr.supportedCompilers.map!((cs) {
  402. auto str = " - " ~ cs[0];
  403. if (cs[1] != Dependency.any) str ~= ": " ~ cs[1].toString();
  404. return str;
  405. }).join("\n")
  406. )
  407. );
  408.  
  409. enforce(compilerspec.matches(compilerver),
  410. format(
  411. "Installed %s-%s does not comply with %s compiler requirement: %s %s\n" ~
  412. "Please consider upgrading your installation.",
  413. platform.compiler, platform.compilerVersion,
  414. package_name, platform.compiler, compilerspec
  415. )
  416. );
  417.  
  418. enforce(tr.frontend.matches(dmdLikeVersionToSemverLike(platform.frontendVersionString)),
  419. format(
  420. "Installed %s-%s with frontend %s does not comply with %s frontend requirement: %s\n" ~
  421. "Please consider upgrading your installation.",
  422. platform.compiler, platform.compilerVersion,
  423. platform.frontendVersionString, package_name, tr.frontend
  424. )
  425. );
  426. }
  427.  
  428. package bool addRequirement(ref ToolchainRequirements req, string name, string value)
  429. {
  430. switch (name) {
  431. default: return false;
  432. case "dub": req.dub = parseDependency(value); break;
  433. case "frontend": req.frontend = parseDMDDependency(value); break;
  434. case "ldc": req.ldc = parseDependency(value); break;
  435. case "gdc": req.gdc = parseDependency(value); break;
  436. case "dmd": req.dmd = parseDMDDependency(value); break;
  437. }
  438. return true;
  439. }
  440.  
  441. private static Dependency parseDependency(string dep)
  442. {
  443. if (dep == "no") return Dependency.invalid;
  444. return Dependency(dep);
  445. }
  446.  
  447. private static Dependency parseDMDDependency(string dep)
  448. {
  449. import dub.compilers.utils : dmdLikeVersionToSemverLike;
  450. import dub.dependency : Dependency;
  451. import std.algorithm : map, splitter;
  452. import std.array : join;
  453.  
  454. if (dep == "no") return Dependency.invalid;
  455. return dep
  456. .splitter(' ')
  457. .map!(r => dmdLikeVersionToSemverLike(r))
  458. .join(' ')
  459. .Dependency;
  460. }
  461.  
  462. private T clone(T)(ref const(T) val)
  463. {
  464. import std.traits : isSomeString, isDynamicArray, isAssociativeArray, isBasicType, ValueType;
  465.  
  466. static if (is(T == immutable)) return val;
  467. else static if (isBasicType!T) return val;
  468. else static if (isDynamicArray!T) {
  469. alias V = typeof(T.init[0]);
  470. static if (is(V == immutable)) return val;
  471. else {
  472. T ret = new V[val.length];
  473. foreach (i, ref f; val)
  474. ret[i] = clone!V(f);
  475. return ret;
  476. }
  477. } else static if (isAssociativeArray!T) {
  478. alias V = ValueType!T;
  479. T ret;
  480. foreach (k, ref f; val)
  481. ret[k] = clone!V(f);
  482. return ret;
  483. } else static if (is(T == struct)) {
  484. T ret;
  485. foreach (i, M; typeof(T.tupleof))
  486. ret.tupleof[i] = clone!M(val.tupleof[i]);
  487. return ret;
  488. } else static assert(false, "Unsupported type: "~T.stringof);
  489. }
  490.  
  491. unittest { // issue #1407 - duplicate main source file
  492. {
  493. BuildSettingsTemplate t;
  494. t.mainSourceFile = "./foo.d";
  495. t.sourceFiles[""] = ["foo.d"];
  496. BuildSettings bs;
  497. t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/"));
  498. assert(bs.sourceFiles == ["foo.d"]);
  499. }
  500.  
  501. version (Windows) {{
  502. BuildSettingsTemplate t;
  503. t.mainSourceFile = "src/foo.d";
  504. t.sourceFiles[""] = ["src\\foo.d"];
  505. BuildSettings bs;
  506. t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/"));
  507. assert(bs.sourceFiles == ["src\\foo.d"]);
  508. }}
  509. }