Newer
Older
dub_jkp / source / dub / recipe / packagerecipe.d
@Remi Thebault Remi Thebault on 18 Aug 2018 10 KB implement minDubVersion check
  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.url;
  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.  
  24.  
  25. /**
  26. Returns the individual parts of a qualified package name.
  27.  
  28. Sub qualified package names are lists of package names separated by ":". For
  29. example, "packa:packb:packc" references a package named "packc" that is a
  30. sub package of "packb", which in turn is a sub package of "packa".
  31. */
  32. string[] getSubPackagePath(string package_name) @safe pure
  33. {
  34. return package_name.split(":");
  35. }
  36.  
  37. /**
  38. Returns the name of the top level package for a given (sub) package name.
  39.  
  40. In case of a top level package, the qualified name is returned unmodified.
  41. */
  42. string getBasePackageName(string package_name) @safe pure
  43. {
  44. return package_name.findSplit(":")[0];
  45. }
  46.  
  47. /**
  48. Returns the qualified sub package part of the given package name.
  49.  
  50. This is the part of the package name excluding the base package
  51. name. See also $(D getBasePackageName).
  52. */
  53. string getSubPackageName(string package_name) @safe pure
  54. {
  55. return package_name.findSplit(":")[2];
  56. }
  57.  
  58. @safe unittest
  59. {
  60. assert(getSubPackagePath("packa:packb:packc") == ["packa", "packb", "packc"]);
  61. assert(getSubPackagePath("pack") == ["pack"]);
  62. assert(getBasePackageName("packa:packb:packc") == "packa");
  63. assert(getBasePackageName("pack") == "pack");
  64. assert(getSubPackageName("packa:packb:packc") == "packb:packc");
  65. assert(getSubPackageName("pack") == "");
  66. }
  67.  
  68. /**
  69. Represents the contents of a package recipe file (dub.json/dub.sdl) in an abstract way.
  70.  
  71. This structure is used to reason about package descriptions in isolation.
  72. For higher level package handling, see the $(D Package) class.
  73. */
  74. struct PackageRecipe {
  75. string name;
  76. string version_;
  77. string description;
  78. string homepage;
  79. string[] authors;
  80. string copyright;
  81. string license;
  82. string minDubVersion;
  83. string[] ddoxFilterArgs;
  84. string ddoxTool;
  85. BuildSettingsTemplate buildSettings;
  86. ConfigurationInfo[] configurations;
  87. BuildSettingsTemplate[string] buildTypes;
  88.  
  89. SubPackage[] subPackages;
  90.  
  91. inout(ConfigurationInfo) getConfiguration(string name)
  92. inout {
  93. foreach (c; configurations)
  94. if (c.name == name)
  95. return c;
  96. throw new Exception("Unknown configuration: "~name);
  97. }
  98.  
  99. /** Clones the package recipe recursively.
  100. */
  101. PackageRecipe clone() const { return .clone(this); }
  102. }
  103.  
  104. struct SubPackage
  105. {
  106. string path;
  107. PackageRecipe recipe;
  108. }
  109.  
  110.  
  111. /// Bundles information about a build configuration.
  112. struct ConfigurationInfo {
  113. string name;
  114. string[] platforms;
  115. BuildSettingsTemplate buildSettings;
  116.  
  117. this(string name, BuildSettingsTemplate build_settings)
  118. {
  119. enforce(!name.empty, "Configuration name is empty.");
  120. this.name = name;
  121. this.buildSettings = build_settings;
  122. }
  123.  
  124. bool matchesPlatform(in BuildPlatform platform)
  125. const {
  126. if( platforms.empty ) return true;
  127. foreach(p; platforms)
  128. if( platform.matchesSpecification("-"~p) )
  129. return true;
  130. return false;
  131. }
  132. }
  133.  
  134. /// This keeps general information about how to build a package.
  135. /// It contains functions to create a specific BuildSetting, targeted at
  136. /// a certain BuildPlatform.
  137. struct BuildSettingsTemplate {
  138. Dependency[string] dependencies;
  139. string systemDependencies;
  140. TargetType targetType = TargetType.autodetect;
  141. string targetPath;
  142. string targetName;
  143. string workingDirectory;
  144. string mainSourceFile;
  145. string[string] subConfigurations;
  146. string[][string] dflags;
  147. string[][string] lflags;
  148. string[][string] libs;
  149. string[][string] sourceFiles;
  150. string[][string] sourcePaths;
  151. string[][string] excludedSourceFiles;
  152. string[][string] copyFiles;
  153. string[][string] versions;
  154. string[][string] debugVersions;
  155. string[][string] importPaths;
  156. string[][string] stringImportPaths;
  157. string[][string] preGenerateCommands;
  158. string[][string] postGenerateCommands;
  159. string[][string] preBuildCommands;
  160. string[][string] postBuildCommands;
  161. BuildRequirements[string] buildRequirements;
  162. BuildOptions[string] buildOptions;
  163.  
  164.  
  165. /// Constructs a BuildSettings object from this template.
  166. void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, NativePath base_path)
  167. const {
  168. dst.targetType = this.targetType;
  169. if (!this.targetPath.empty) dst.targetPath = this.targetPath;
  170. if (!this.targetName.empty) dst.targetName = this.targetName;
  171. if (!this.workingDirectory.empty) dst.workingDirectory = this.workingDirectory;
  172. if (!this.mainSourceFile.empty) {
  173. auto p = NativePath(this.mainSourceFile);
  174. p.normalize();
  175. dst.mainSourceFile = p.toNativeString();
  176. dst.addSourceFiles(dst.mainSourceFile);
  177. }
  178.  
  179. string[] collectFiles(in string[][string] paths_map, string pattern)
  180. {
  181. auto files = appender!(string[]);
  182.  
  183. foreach (suffix, paths; paths_map) {
  184. if (!platform.matchesSpecification(suffix))
  185. continue;
  186.  
  187. foreach (spath; paths) {
  188. enforce(!spath.empty, "Paths must not be empty strings.");
  189. auto path = NativePath(spath);
  190. if (!path.absolute) path = base_path ~ path;
  191. if (!existsFile(path) || !isDir(path.toNativeString())) {
  192. import dub.project : buildSettingsVars;
  193. import std.algorithm : any, find;
  194. const hasVar = buildSettingsVars.any!((string var) {
  195. return spath.find("$"~var).length > 0 || spath.find("${"~var~"}").length > 0;
  196. });
  197. if (!hasVar)
  198. logWarn("Invalid source/import path: %s", path.toNativeString());
  199. continue;
  200. }
  201.  
  202. foreach (d; dirEntries(path.toNativeString(), pattern, SpanMode.depth)) {
  203. import std.path : baseName;
  204. if (baseName(d.name)[0] == '.' || d.isDir) continue;
  205. auto src = NativePath(d.name).relativeTo(base_path);
  206. files ~= src.toNativeString();
  207. }
  208. }
  209. }
  210.  
  211. return files.data;
  212. }
  213.  
  214. // collect source files
  215. dst.addSourceFiles(collectFiles(sourcePaths, "*.d"));
  216. auto sourceFiles = dst.sourceFiles.sort();
  217.  
  218. // collect import files and remove sources
  219. import std.algorithm : copy, setDifference;
  220.  
  221. auto importFiles = collectFiles(importPaths, "*.{d,di}").sort();
  222. immutable nremoved = importFiles.setDifference(sourceFiles).copy(importFiles.release).length;
  223. importFiles = importFiles[0 .. $ - nremoved];
  224. dst.addImportFiles(importFiles.release);
  225.  
  226. dst.addStringImportFiles(collectFiles(stringImportPaths, "*"));
  227.  
  228. getPlatformSetting!("dflags", "addDFlags")(dst, platform);
  229. getPlatformSetting!("lflags", "addLFlags")(dst, platform);
  230. getPlatformSetting!("libs", "addLibs")(dst, platform);
  231. getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform);
  232. getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform);
  233. getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform);
  234. getPlatformSetting!("versions", "addVersions")(dst, platform);
  235. getPlatformSetting!("debugVersions", "addDebugVersions")(dst, platform);
  236. getPlatformSetting!("importPaths", "addImportPaths")(dst, platform);
  237. getPlatformSetting!("stringImportPaths", "addStringImportPaths")(dst, platform);
  238. getPlatformSetting!("preGenerateCommands", "addPreGenerateCommands")(dst, platform);
  239. getPlatformSetting!("postGenerateCommands", "addPostGenerateCommands")(dst, platform);
  240. getPlatformSetting!("preBuildCommands", "addPreBuildCommands")(dst, platform);
  241. getPlatformSetting!("postBuildCommands", "addPostBuildCommands")(dst, platform);
  242. getPlatformSetting!("buildRequirements", "addRequirements")(dst, platform);
  243. getPlatformSetting!("buildOptions", "addOptions")(dst, platform);
  244. }
  245.  
  246. void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform)
  247. const {
  248. foreach(suffix, values; __traits(getMember, this, name)){
  249. if( platform.matchesSpecification(suffix) )
  250. __traits(getMember, dst, addname)(values);
  251. }
  252. }
  253.  
  254. void warnOnSpecialCompilerFlags(string package_name, string config_name)
  255. {
  256. auto nodef = false;
  257. auto noprop = false;
  258. foreach (req; this.buildRequirements) {
  259. if (req & BuildRequirement.noDefaultFlags) nodef = true;
  260. if (req & BuildRequirement.relaxProperties) noprop = true;
  261. }
  262.  
  263. if (noprop) {
  264. 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.`);
  265. logWarn("");
  266. }
  267.  
  268. if (nodef) {
  269. logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages.");
  270. logWarn("");
  271. } else {
  272. string[] all_dflags;
  273. BuildOptions all_options;
  274. foreach (flags; this.dflags) all_dflags ~= flags;
  275. foreach (options; this.buildOptions) all_options |= options;
  276. .warnOnSpecialCompilerFlags(all_dflags, all_options, package_name, config_name);
  277. }
  278. }
  279. }
  280.  
  281. private T clone(T)(ref const(T) val)
  282. {
  283. import std.traits : isSomeString, isDynamicArray, isAssociativeArray, isBasicType, ValueType;
  284.  
  285. static if (is(T == immutable)) return val;
  286. else static if (isBasicType!T) return val;
  287. else static if (isDynamicArray!T) {
  288. alias V = typeof(T.init[0]);
  289. static if (is(V == immutable)) return val;
  290. else {
  291. T ret = new V[val.length];
  292. foreach (i, ref f; val)
  293. ret[i] = clone!V(f);
  294. return ret;
  295. }
  296. } else static if (isAssociativeArray!T) {
  297. alias V = ValueType!T;
  298. T ret;
  299. foreach (k, ref f; val)
  300. ret[k] = clone!V(f);
  301. return ret;
  302. } else static if (is(T == struct)) {
  303. T ret;
  304. foreach (i, M; typeof(T.tupleof))
  305. ret.tupleof[i] = clone!M(val.tupleof[i]);
  306. return ret;
  307. } else static assert(false, "Unsupported type: "~T.stringof);
  308. }
  309.  
  310. unittest { // issue #1407 - duplicate main source file
  311. {
  312. BuildSettingsTemplate t;
  313. t.mainSourceFile = "./foo.d";
  314. t.sourceFiles[""] = ["foo.d"];
  315. BuildSettings bs;
  316. t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/"));
  317. assert(bs.sourceFiles == ["foo.d"]);
  318. }
  319.  
  320. version (Windows) {{
  321. BuildSettingsTemplate t;
  322. t.mainSourceFile = "src/foo.d";
  323. t.sourceFiles[""] = ["src\\foo.d"];
  324. BuildSettings bs;
  325. t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/"));
  326. assert(bs.sourceFiles == ["src\\foo.d"]);
  327. }}
  328. }