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.dependency;
  12.  
  13. import dub.internal.vibecompat.core.file;
  14. import dub.internal.vibecompat.core.log;
  15. import dub.internal.vibecompat.inet.url;
  16.  
  17. import std.algorithm : findSplit, sort;
  18. import std.array : join, split;
  19. import std.exception : enforce;
  20. import std.file;
  21. import std.range;
  22.  
  23.  
  24. /**
  25. Returns the individual parts of a qualified package name.
  26.  
  27. Sub qualified package names are lists of package names separated by ":". For
  28. example, "packa:packb:packc" references a package named "packc" that is a
  29. sub package of "packb", wich in turn is a sub package of "packa".
  30. */
  31. string[] getSubPackagePath(string package_name)
  32. {
  33. return package_name.split(":");
  34. }
  35.  
  36. /**
  37. Returns the name of the top level package for a given (sub) package name.
  38.  
  39. In case of a top level package, the qualified name is returned unmodified.
  40. */
  41. string getBasePackageName(string package_name)
  42. {
  43. return package_name.findSplit(":")[0];
  44. }
  45.  
  46. /**
  47. Returns the qualified sub package part of the given package name.
  48.  
  49. This is the part of the package name excluding the base package
  50. name. See also $(D getBasePackageName).
  51. */
  52. string getSubPackageName(string package_name)
  53. {
  54. return package_name.findSplit(":")[2];
  55. }
  56.  
  57. unittest
  58. {
  59. assert(getSubPackagePath("packa:packb:packc") == ["packa", "packb", "packc"]);
  60. assert(getSubPackagePath("pack") == ["pack"]);
  61. assert(getBasePackageName("packa:packb:packc") == "packa");
  62. assert(getBasePackageName("pack") == "pack");
  63. assert(getSubPackageName("packa:packb:packc") == "packb:packc");
  64. assert(getSubPackageName("pack") == "");
  65. }
  66.  
  67. /**
  68. Represents the contents of a package recipe file (dub.json/dub.sdl) in an abstract way.
  69.  
  70. This structure is used to reason about package descriptions in isolation.
  71. For higher level package handling, see the $(D Package) class.
  72. */
  73. struct PackageRecipe {
  74. string name;
  75. string version_;
  76. string description;
  77. string homepage;
  78. string[] authors;
  79. string copyright;
  80. string license;
  81. string[] ddoxFilterArgs;
  82. string ddoxTool;
  83. BuildSettingsTemplate buildSettings;
  84. ConfigurationInfo[] configurations;
  85. BuildSettingsTemplate[string] buildTypes;
  86.  
  87. SubPackage[] subPackages;
  88.  
  89. deprecated("Use Package.dependencies or the dependencies of the individual BuildSettingsTemplates instead.")
  90. @property const(Dependency)[string] dependencies()
  91. const {
  92. Dependency[string] ret;
  93. foreach (n, d; this.buildSettings.dependencies)
  94. ret[n] = d;
  95. foreach (ref c; configurations)
  96. foreach (n, d; c.buildSettings.dependencies)
  97. ret[n] = d;
  98. return ret;
  99. }
  100.  
  101. inout(ConfigurationInfo) getConfiguration(string name)
  102. inout {
  103. foreach (c; configurations)
  104. if (c.name == name)
  105. return c;
  106. throw new Exception("Unknown configuration: "~name);
  107. }
  108. }
  109.  
  110. struct SubPackage
  111. {
  112. string path;
  113. PackageRecipe recipe;
  114. }
  115.  
  116.  
  117. /// Bundles information about a build configuration.
  118. struct ConfigurationInfo {
  119. string name;
  120. string[] platforms;
  121. BuildSettingsTemplate buildSettings;
  122.  
  123. this(string name, BuildSettingsTemplate build_settings)
  124. {
  125. enforce(!name.empty, "Configuration name is empty.");
  126. this.name = name;
  127. this.buildSettings = build_settings;
  128. }
  129.  
  130. bool matchesPlatform(in BuildPlatform platform)
  131. const {
  132. if( platforms.empty ) return true;
  133. foreach(p; platforms)
  134. if( platform.matchesSpecification("-"~p) )
  135. return true;
  136. return false;
  137. }
  138. }
  139.  
  140. /// This keeps general information about how to build a package.
  141. /// It contains functions to create a specific BuildSetting, targeted at
  142. /// a certain BuildPlatform.
  143. struct BuildSettingsTemplate {
  144. Dependency[string] dependencies;
  145. string systemDependencies;
  146. TargetType targetType = TargetType.autodetect;
  147. string targetPath;
  148. string targetName;
  149. string workingDirectory;
  150. string mainSourceFile;
  151. string[string] subConfigurations;
  152. string[][string] dflags;
  153. string[][string] lflags;
  154. string[][string] libs;
  155. string[][string] sourceFiles;
  156. string[][string] sourcePaths;
  157. string[][string] excludedSourceFiles;
  158. string[][string] copyFiles;
  159. string[][string] versions;
  160. string[][string] debugVersions;
  161. string[][string] importPaths;
  162. string[][string] stringImportPaths;
  163. string[][string] preGenerateCommands;
  164. string[][string] postGenerateCommands;
  165. string[][string] preBuildCommands;
  166. string[][string] postBuildCommands;
  167. BuildRequirements[string] buildRequirements;
  168. BuildOptions[string] buildOptions;
  169.  
  170.  
  171. /// Constructs a BuildSettings object from this template.
  172. void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, Path base_path)
  173. const {
  174. dst.targetType = this.targetType;
  175. if (!this.targetPath.empty) dst.targetPath = this.targetPath;
  176. if (!this.targetName.empty) dst.targetName = this.targetName;
  177. if (!this.workingDirectory.empty) dst.workingDirectory = this.workingDirectory;
  178. if (!this.mainSourceFile.empty) {
  179. dst.mainSourceFile = this.mainSourceFile;
  180. dst.addSourceFiles(this.mainSourceFile);
  181. }
  182.  
  183. void collectFiles(string method)(in string[][string] paths_map, string pattern)
  184. {
  185. foreach (suffix, paths; paths_map) {
  186. if (!platform.matchesSpecification(suffix))
  187. continue;
  188.  
  189. foreach (spath; paths) {
  190. enforce(!spath.empty, "Paths must not be empty strings.");
  191. auto path = Path(spath);
  192. if (!path.absolute) path = base_path ~ path;
  193. if (!existsFile(path) || !isDir(path.toNativeString())) {
  194. logWarn("Invalid source/import path: %s", path.toNativeString());
  195. continue;
  196. }
  197.  
  198. foreach (d; dirEntries(path.toNativeString(), pattern, SpanMode.depth)) {
  199. import std.path : baseName;
  200. if (baseName(d.name)[0] == '.' || isDir(d.name)) continue;
  201. auto src = Path(d.name).relativeTo(base_path);
  202. __traits(getMember, dst, method)(src.toNativeString());
  203. }
  204. }
  205. }
  206. }
  207.  
  208. // collect files from all source/import folders
  209. collectFiles!"addSourceFiles"(sourcePaths, "*.d");
  210. collectFiles!"addImportFiles"(importPaths, "*.{d,di}");
  211. dst.removeImportFiles(dst.sourceFiles);
  212. collectFiles!"addStringImportFiles"(stringImportPaths, "*");
  213.  
  214. // ensure a deterministic order of files as passed to the compiler
  215. dst.sourceFiles.sort();
  216.  
  217. getPlatformSetting!("dflags", "addDFlags")(dst, platform);
  218. getPlatformSetting!("lflags", "addLFlags")(dst, platform);
  219. getPlatformSetting!("libs", "addLibs")(dst, platform);
  220. getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform);
  221. getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform);
  222. getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform);
  223. getPlatformSetting!("versions", "addVersions")(dst, platform);
  224. getPlatformSetting!("debugVersions", "addDebugVersions")(dst, platform);
  225. getPlatformSetting!("importPaths", "addImportPaths")(dst, platform);
  226. getPlatformSetting!("stringImportPaths", "addStringImportPaths")(dst, platform);
  227. getPlatformSetting!("preGenerateCommands", "addPreGenerateCommands")(dst, platform);
  228. getPlatformSetting!("postGenerateCommands", "addPostGenerateCommands")(dst, platform);
  229. getPlatformSetting!("preBuildCommands", "addPreBuildCommands")(dst, platform);
  230. getPlatformSetting!("postBuildCommands", "addPostBuildCommands")(dst, platform);
  231. getPlatformSetting!("buildRequirements", "addRequirements")(dst, platform);
  232. getPlatformSetting!("buildOptions", "addOptions")(dst, platform);
  233. }
  234.  
  235. void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform)
  236. const {
  237. foreach(suffix, values; __traits(getMember, this, name)){
  238. if( platform.matchesSpecification(suffix) )
  239. __traits(getMember, dst, addname)(values);
  240. }
  241. }
  242.  
  243. void warnOnSpecialCompilerFlags(string package_name, string config_name)
  244. {
  245. auto nodef = false;
  246. auto noprop = false;
  247. foreach (req; this.buildRequirements) {
  248. if (req & BuildRequirement.noDefaultFlags) nodef = true;
  249. if (req & BuildRequirement.relaxProperties) noprop = true;
  250. }
  251.  
  252. if (noprop) {
  253. 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.`);
  254. logWarn("");
  255. }
  256.  
  257. if (nodef) {
  258. logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages.");
  259. logWarn("");
  260. } else {
  261. string[] all_dflags;
  262. BuildOptions all_options;
  263. foreach (flags; this.dflags) all_dflags ~= flags;
  264. foreach (options; this.buildOptions) all_options |= options;
  265. .warnOnSpecialCompilerFlags(all_dflags, all_options, package_name, config_name);
  266. }
  267. }
  268. }