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. import dub.internal.logging;
  14.  
  15. import dub.internal.vibecompat.core.file;
  16. import dub.internal.vibecompat.inet.path;
  17.  
  18. import dub.internal.configy.Attributes;
  19.  
  20. import std.algorithm : findSplit, sort;
  21. import std.array : join, split;
  22. import std.exception : enforce;
  23. import std.file;
  24. import std.range;
  25. import std.process : environment;
  26.  
  27.  
  28. /**
  29. Returns the individual parts of a qualified package name.
  30.  
  31. Sub qualified package names are lists of package names separated by ":". For
  32. example, "packa:packb:packc" references a package named "packc" that is a
  33. sub package of "packb", which in turn is a sub package of "packa".
  34. */
  35. deprecated("This function is not supported as subpackages cannot be nested")
  36. string[] getSubPackagePath(string package_name) @safe pure
  37. {
  38. return package_name.split(":");
  39. }
  40.  
  41. deprecated @safe unittest
  42. {
  43. assert(getSubPackagePath("packa:packb:packc") == ["packa", "packb", "packc"]);
  44. assert(getSubPackagePath("pack") == ["pack"]);
  45. }
  46.  
  47. /**
  48. Returns the name of the top level package for a given (sub) package name of
  49. format `"basePackageName"` or `"basePackageName:subPackageName"`.
  50.  
  51. In case of a top level package, the qualified name is returned unmodified.
  52. */
  53. deprecated("Use `dub.dependency : PackageName(arg).main` instead")
  54. string getBasePackageName(string package_name) @safe pure
  55. {
  56. return package_name.findSplit(":")[0];
  57. }
  58.  
  59. /**
  60. Returns the qualified sub package part of the given package name of format
  61. `"basePackageName:subPackageName"`, or empty string if none.
  62.  
  63. This is the part of the package name excluding the base package
  64. name. See also $(D getBasePackageName).
  65. */
  66. deprecated("Use `dub.dependency : PackageName(arg).sub` instead")
  67. string getSubPackageName(string package_name) @safe pure
  68. {
  69. return package_name.findSplit(":")[2];
  70. }
  71.  
  72. deprecated @safe unittest
  73. {
  74. assert(getBasePackageName("packa:packb:packc") == "packa");
  75. assert(getBasePackageName("pack") == "pack");
  76. assert(getSubPackageName("packa:packb:packc") == "packb:packc");
  77. assert(getSubPackageName("pack") == "");
  78. }
  79.  
  80. /**
  81. Represents the contents of a package recipe file (dub.json/dub.sdl) in an abstract way.
  82.  
  83. This structure is used to reason about package descriptions in isolation.
  84. For higher level package handling, see the $(D Package) class.
  85. */
  86. struct PackageRecipe {
  87. /**
  88. * Name of the package, used to uniquely identify the package.
  89. *
  90. * This field is the only mandatory one.
  91. * Must be comprised of only lower case ASCII alpha-numeric characters,
  92. * "-" or "_".
  93. */
  94. string name;
  95.  
  96. /// Brief description of the package.
  97. @Optional string description;
  98.  
  99. /// URL of the project website
  100. @Optional string homepage;
  101.  
  102. /**
  103. * List of project authors
  104. *
  105. * the suggested format is either:
  106. * "Peter Parker"
  107. * or
  108. * "Peter Parker <pparker@example.com>"
  109. */
  110. @Optional string[] authors;
  111.  
  112. /// Copyright declaration string
  113. @Optional string copyright;
  114.  
  115. /// License(s) under which the project can be used
  116. @Optional string license;
  117.  
  118. /// Set of version requirements for DUB, compilers and/or language frontend.
  119. @Optional ToolchainRequirements toolchainRequirements;
  120.  
  121. /**
  122. * Specifies an optional list of build configurations
  123. *
  124. * By default, the first configuration present in the package recipe
  125. * will be used, except for special configurations (e.g. "unittest").
  126. * A specific configuration can be chosen from the command line using
  127. * `--config=name` or `-c name`. A package can select a specific
  128. * configuration in one of its dependency by using the `subConfigurations`
  129. * build setting.
  130. * Build settings defined at the top level affect all configurations.
  131. */
  132. @Optional @Key("name") ConfigurationInfo[] configurations;
  133.  
  134. /**
  135. * Defines additional custom build types or overrides the default ones
  136. *
  137. * Build types can be selected from the command line using `--build=name`
  138. * or `-b name`. The default build type is `debug`.
  139. */
  140. @Optional BuildSettingsTemplate[string] buildTypes;
  141.  
  142. /**
  143. * Build settings influence the command line arguments and options passed
  144. * to the compiler and linker.
  145. *
  146. * All build settings can be present at the top level, and are optional.
  147. * Build settings can also be found in `configurations`.
  148. */
  149. @Optional BuildSettingsTemplate buildSettings;
  150. alias buildSettings this;
  151.  
  152. /**
  153. * Specifies a list of command line flags usable for controlling
  154. * filter behavior for `--build=ddox` [experimental]
  155. */
  156. @Optional @Name("-ddoxFilterArgs") string[] ddoxFilterArgs;
  157.  
  158. /// Specify which tool to use with `--build=ddox` (experimental)
  159. @Optional @Name("-ddoxTool") string ddoxTool;
  160.  
  161. /**
  162. * Sub-packages path or definitions
  163. *
  164. * Sub-packages allow to break component of a large framework into smaller
  165. * packages. In the recipe file, sub-packages entry can take one of two forms:
  166. * either the path to a sub-folder where a recipe file exists,
  167. * or an object of the same format as a recipe file (or `PackageRecipe`).
  168. */
  169. @Optional SubPackage[] subPackages;
  170.  
  171. /// Usually unused by users, this is set by dub automatically
  172. @Optional @Name("version") string version_;
  173.  
  174. inout(ConfigurationInfo) getConfiguration(string name)
  175. inout {
  176. foreach (c; configurations)
  177. if (c.name == name)
  178. return c;
  179. throw new Exception("Unknown configuration: "~name);
  180. }
  181.  
  182. /** Clones the package recipe recursively.
  183. */
  184. PackageRecipe clone() const { return .clone(this); }
  185. }
  186.  
  187. struct SubPackage
  188. {
  189. string path;
  190. PackageRecipe recipe;
  191.  
  192. /**
  193. * Given a YAML parser, recurses into `recipe` or use `path`
  194. * depending on the node type.
  195. *
  196. * Two formats are supported for sub-packages: a string format,
  197. * which is just the path to the sub-package, and embedding the
  198. * full sub-package recipe into the parent package recipe.
  199. *
  200. * To support such a dual syntax, Configy requires the use
  201. * of a `fromYAML` method, as it exposes the underlying format.
  202. */
  203. static SubPackage fromYAML (scope ConfigParser!SubPackage p)
  204. {
  205. import dub.internal.dyaml.node;
  206.  
  207. if (p.node.nodeID == NodeID.mapping)
  208. return SubPackage(null, p.parseAs!PackageRecipe);
  209. else
  210. return SubPackage(p.parseAs!string);
  211. }
  212. }
  213.  
  214. /// Describes minimal toolchain requirements
  215. struct ToolchainRequirements
  216. {
  217. import std.typecons : Tuple, tuple;
  218.  
  219. // TODO: We can remove `@Optional` once bosagora/configy#30 is resolved,
  220. // currently it fails because `Dependency.opCmp` is not CTFE-able.
  221.  
  222. /// DUB version requirement
  223. @Optional @converter((scope ConfigParser!VersionRange p) => p.node.as!string.parseVersionRange)
  224. VersionRange dub = VersionRange.Any;
  225. /// D front-end version requirement
  226. @Optional @converter((scope ConfigParser!VersionRange p) => p.node.as!string.parseDMDDependency)
  227. VersionRange frontend = VersionRange.Any;
  228. /// DMD version requirement
  229. @Optional @converter((scope ConfigParser!VersionRange p) => p.node.as!string.parseDMDDependency)
  230. VersionRange dmd = VersionRange.Any;
  231. /// LDC version requirement
  232. @Optional @converter((scope ConfigParser!VersionRange p) => p.node.as!string.parseVersionRange)
  233. VersionRange ldc = VersionRange.Any;
  234. /// GDC version requirement
  235. @Optional @converter((scope ConfigParser!VersionRange p) => p.node.as!string.parseVersionRange)
  236. VersionRange gdc = VersionRange.Any;
  237.  
  238. /** Get the list of supported compilers.
  239.  
  240. Returns:
  241. An array of couples of compiler name and compiler requirement
  242. */
  243. @property Tuple!(string, VersionRange)[] supportedCompilers() const
  244. {
  245. Tuple!(string, VersionRange)[] res;
  246. if (dmd != VersionRange.Invalid) res ~= Tuple!(string, VersionRange)("dmd", dmd);
  247. if (ldc != VersionRange.Invalid) res ~= Tuple!(string, VersionRange)("ldc", ldc);
  248. if (gdc != VersionRange.Invalid) res ~= Tuple!(string, VersionRange)("gdc", gdc);
  249. return res;
  250. }
  251.  
  252. bool empty()
  253. const {
  254. import std.algorithm.searching : all;
  255. return only(dub, frontend, dmd, ldc, gdc)
  256. .all!(r => r == VersionRange.Any);
  257. }
  258. }
  259.  
  260.  
  261. /// Bundles information about a build configuration.
  262. struct ConfigurationInfo {
  263. string name;
  264. @Optional string[] platforms;
  265. @Optional BuildSettingsTemplate buildSettings;
  266. alias buildSettings this;
  267.  
  268. /**
  269. * Equivalent to the default constructor, used by Configy
  270. */
  271. this(string name, string[] p, BuildSettingsTemplate build_settings)
  272. @safe pure nothrow @nogc
  273. {
  274. this.name = name;
  275. this.platforms = p;
  276. this.buildSettings = build_settings;
  277. }
  278.  
  279. this(string name, BuildSettingsTemplate build_settings)
  280. {
  281. enforce(!name.empty, "Configuration name is empty.");
  282. this.name = name;
  283. this.buildSettings = build_settings;
  284. }
  285.  
  286. bool matchesPlatform(in BuildPlatform platform)
  287. const {
  288. if( platforms.empty ) return true;
  289. foreach(p; platforms)
  290. if (platform.matchesSpecification(p))
  291. return true;
  292. return false;
  293. }
  294. }
  295.  
  296. /**
  297. * A dependency with possible `BuildSettingsTemplate`
  298. *
  299. * Currently only `dflags` is taken into account, but the parser accepts any
  300. * value that is in `BuildSettingsTemplate`.
  301. * This feature was originally introduced to support `-preview`, as setting
  302. * a `-preview` in `dflags` does not propagate down to dependencies.
  303. */
  304. public struct RecipeDependency
  305. {
  306. /// The dependency itself
  307. public Dependency dependency;
  308.  
  309. /// Additional dflags, if any
  310. public BuildSettingsTemplate settings;
  311.  
  312. /// Convenience alias as most uses just want to deal with the `Dependency`
  313. public alias dependency this;
  314.  
  315. /**
  316. * Read a `Dependency` and `BuildSettingsTemplate` from the config file
  317. *
  318. * Required to support both short and long form
  319. */
  320. static RecipeDependency fromYAML (scope ConfigParser!RecipeDependency p)
  321. {
  322. import dub.internal.dyaml.node;
  323.  
  324. if (p.node.nodeID == NodeID.scalar) {
  325. auto d = YAMLFormat(p.node.as!string);
  326. return RecipeDependency(d.toDependency());
  327. }
  328. auto d = p.parseAs!YAMLFormat;
  329. return RecipeDependency(d.toDependency(), d.settings);
  330. }
  331.  
  332. /// In-file representation of a dependency as specified by the user
  333. private struct YAMLFormat
  334. {
  335. @Name("version") @Optional string version_;
  336. @Optional string path;
  337. @Optional string repository;
  338. bool optional;
  339. @Name("default") bool default_;
  340.  
  341. @Optional BuildSettingsTemplate settings;
  342. alias settings this;
  343.  
  344. /**
  345. * Used by Configy to provide rich error message when parsing.
  346. *
  347. * Exceptions thrown from `validate` methods will be wrapped with field/file
  348. * information and rethrown from Configy, providing the user
  349. * with the location of the configuration that triggered the error.
  350. */
  351. public void validate () const
  352. {
  353. enforce(this.optional || !this.default_,
  354. "Setting default to 'true' has no effect if 'optional' is not set");
  355. enforce(this.version_.length || this.path.length || this.repository.length,
  356. "Need to provide one of the following fields: 'version', 'path', or 'repository'");
  357.  
  358. enforce(!this.path.length || !this.repository.length,
  359. "Cannot provide a 'path' dependency if a repository dependency is used");
  360. enforce(!this.repository.length || this.version_.length,
  361. "Need to provide a commit hash in 'version' field with 'repository' dependency");
  362.  
  363. // Need to deprecate this as it's fairly common
  364. version (none) {
  365. enforce(!this.path.length || !this.version_.length,
  366. "Cannot provide a 'path' dependency if a 'version' dependency is used");
  367. }
  368. }
  369.  
  370. /// Turns this struct into a `Dependency`
  371. public Dependency toDependency () const
  372. {
  373. auto result = () {
  374. if (this.path.length)
  375. return Dependency(NativePath(this.path));
  376. if (this.repository.length)
  377. return Dependency(Repository(this.repository, this.version_));
  378. return Dependency(VersionRange.fromString(this.version_));
  379. }();
  380. result.optional = this.optional;
  381. result.default_ = this.default_;
  382. return result;
  383. }
  384. }
  385. }
  386.  
  387. /// Type used to avoid a breaking change when `Dependency[string]`
  388. /// was changed to `RecipeDependency[string]`
  389. package struct RecipeDependencyAA
  390. {
  391. /// The underlying data, `public` as `alias this` to `private` field doesn't
  392. /// always work.
  393. public RecipeDependency[string] data;
  394.  
  395. /// Expose base function, e.g. `clear`
  396. alias data this;
  397.  
  398. /// Supports assignment from a `RecipeDependency` (used in the parser)
  399. public void opIndexAssign(RecipeDependency dep, string key)
  400. pure nothrow
  401. {
  402. this.data[key] = dep;
  403. }
  404.  
  405. /// Supports assignment from a `Dependency`, used in user code mostly
  406. public void opIndexAssign(Dependency dep, string key)
  407. pure nothrow
  408. {
  409. this.data[key] = RecipeDependency(dep);
  410. }
  411.  
  412. /// Configy doesn't like `alias this` to an AA
  413. static RecipeDependencyAA fromYAML (scope ConfigParser!RecipeDependencyAA p)
  414. {
  415. return RecipeDependencyAA(p.parseAs!(typeof(this.data)));
  416. }
  417. }
  418.  
  419. /// This keeps general information about how to build a package.
  420. /// It contains functions to create a specific BuildSetting, targeted at
  421. /// a certain BuildPlatform.
  422. struct BuildSettingsTemplate {
  423. @Optional RecipeDependencyAA dependencies;
  424. @Optional string systemDependencies;
  425. @Optional TargetType targetType = TargetType.autodetect;
  426. @Optional string targetPath;
  427. @Optional string targetName;
  428. @Optional string workingDirectory;
  429. @Optional string mainSourceFile;
  430. @Optional string[string] subConfigurations;
  431. @StartsWith("dflags") string[][string] dflags;
  432. @StartsWith("lflags") string[][string] lflags;
  433. @StartsWith("libs") string[][string] libs;
  434. @StartsWith("sourceFiles") string[][string] sourceFiles;
  435. @StartsWith("sourcePaths") string[][string] sourcePaths;
  436. @StartsWith("cSourcePaths") string[][string] cSourcePaths;
  437. @StartsWith("excludedSourceFiles") string[][string] excludedSourceFiles;
  438. @StartsWith("injectSourceFiles") string[][string] injectSourceFiles;
  439. @StartsWith("copyFiles") string[][string] copyFiles;
  440. @StartsWith("extraDependencyFiles") string[][string] extraDependencyFiles;
  441. @StartsWith("versions") string[][string] versions;
  442. @StartsWith("debugVersions") string[][string] debugVersions;
  443. @StartsWith("versionFilters") string[][string] versionFilters;
  444. @StartsWith("debugVersionFilters") string[][string] debugVersionFilters;
  445. @StartsWith("importPaths") string[][string] importPaths;
  446. @StartsWith("cImportPaths") string[][string] cImportPaths;
  447. @StartsWith("stringImportPaths") string[][string] stringImportPaths;
  448. @StartsWith("preGenerateCommands") string[][string] preGenerateCommands;
  449. @StartsWith("postGenerateCommands") string[][string] postGenerateCommands;
  450. @StartsWith("preBuildCommands") string[][string] preBuildCommands;
  451. @StartsWith("postBuildCommands") string[][string] postBuildCommands;
  452. @StartsWith("preRunCommands") string[][string] preRunCommands;
  453. @StartsWith("postRunCommands") string[][string] postRunCommands;
  454. @StartsWith("environments") string[string][string] environments;
  455. @StartsWith("buildEnvironments")string[string][string] buildEnvironments;
  456. @StartsWith("runEnvironments") string[string][string] runEnvironments;
  457. @StartsWith("preGenerateEnvironments") string[string][string] preGenerateEnvironments;
  458. @StartsWith("postGenerateEnvironments") string[string][string] postGenerateEnvironments;
  459. @StartsWith("preBuildEnvironments") string[string][string] preBuildEnvironments;
  460. @StartsWith("postBuildEnvironments") string[string][string] postBuildEnvironments;
  461. @StartsWith("preRunEnvironments") string[string][string] preRunEnvironments;
  462. @StartsWith("postRunEnvironments") string[string][string] postRunEnvironments;
  463.  
  464. @StartsWith("buildRequirements") @Optional
  465. Flags!BuildRequirement[string] buildRequirements;
  466. @StartsWith("buildOptions") @Optional
  467. Flags!BuildOption[string] buildOptions;
  468.  
  469.  
  470. BuildSettingsTemplate dup() const {
  471. return clone(this);
  472. }
  473.  
  474. /// Constructs a BuildSettings object from this template.
  475. void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, NativePath base_path)
  476. const {
  477. dst.targetType = this.targetType;
  478. if (!this.targetPath.empty) dst.targetPath = this.targetPath;
  479. if (!this.targetName.empty) dst.targetName = this.targetName;
  480. if (!this.workingDirectory.empty) dst.workingDirectory = this.workingDirectory;
  481. if (!this.mainSourceFile.empty) {
  482. auto p = NativePath(this.mainSourceFile);
  483. p.normalize();
  484. dst.mainSourceFile = p.toNativeString();
  485. dst.addSourceFiles(dst.mainSourceFile);
  486. }
  487.  
  488. string[] collectFiles(in string[][string] paths_map, string pattern)
  489. {
  490. auto files = appender!(string[]);
  491.  
  492. import dub.project : buildSettingsVars;
  493. import std.typecons : Nullable;
  494.  
  495. static Nullable!(string[string]) envVarCache;
  496.  
  497. if (envVarCache.isNull) envVarCache = environment.toAA();
  498.  
  499. foreach (suffix, paths; paths_map) {
  500. if (!platform.matchesSpecification(suffix))
  501. continue;
  502.  
  503. foreach (spath; paths) {
  504. enforce(!spath.empty, "Paths must not be empty strings.");
  505. auto path = NativePath(spath);
  506. if (!path.absolute) path = base_path ~ path;
  507. if (!existsDirectory(path)) {
  508. import std.algorithm : any, find;
  509. const hasVar = chain(buildSettingsVars, envVarCache.get.byKey).any!((string var) {
  510. return spath.find("$"~var).length > 0 || spath.find("${"~var~"}").length > 0;
  511. });
  512. if (!hasVar)
  513. logWarn("Invalid source/import path: %s", path.toNativeString());
  514. continue;
  515. }
  516.  
  517. auto pstr = path.toNativeString();
  518. foreach (d; dirEntries(pstr, pattern, SpanMode.depth)) {
  519. import std.path : baseName, pathSplitter;
  520. import std.algorithm.searching : canFind;
  521. // eliminate any hidden files, or files in hidden directories. But always include
  522. // files that are listed inside hidden directories that are specifically added to
  523. // the project.
  524. if (d.isDir || pathSplitter(d.name[pstr.length .. $])
  525. .canFind!(name => name.length && name[0] == '.'))
  526. continue;
  527. auto src = NativePath(d.name).relativeTo(base_path);
  528. files ~= src.toNativeString();
  529. }
  530. }
  531. }
  532.  
  533. return files.data;
  534. }
  535.  
  536. // collect source files. Note: D source from 'sourcePaths' and C sources from 'cSourcePaths' are joint into 'sourceFiles'
  537. dst.addSourceFiles(collectFiles(sourcePaths, "*.d"));
  538. dst.addSourceFiles(collectFiles(cSourcePaths, "*.{c,i}"));
  539. auto sourceFiles = dst.sourceFiles.sort();
  540.  
  541. // collect import files and remove sources
  542. import std.algorithm : copy, setDifference;
  543.  
  544. auto importFiles =
  545. chain(collectFiles(importPaths, "*.{d,di}"), collectFiles(cImportPaths, "*.h"))
  546. .array()
  547. .sort();
  548. immutable nremoved = importFiles.setDifference(sourceFiles).copy(importFiles.release).length;
  549. importFiles = importFiles[0 .. $ - nremoved];
  550. dst.addImportFiles(importFiles.release);
  551.  
  552. dst.addStringImportFiles(collectFiles(stringImportPaths, "*"));
  553.  
  554. getPlatformSetting!("dflags", "addDFlags")(dst, platform);
  555. getPlatformSetting!("lflags", "addLFlags")(dst, platform);
  556. getPlatformSetting!("libs", "addLibs")(dst, platform);
  557. getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform);
  558. getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform);
  559. getPlatformSetting!("injectSourceFiles", "addInjectSourceFiles")(dst, platform);
  560. getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform);
  561. getPlatformSetting!("extraDependencyFiles", "addExtraDependencyFiles")(dst, platform);
  562. getPlatformSetting!("versions", "addVersions")(dst, platform);
  563. getPlatformSetting!("debugVersions", "addDebugVersions")(dst, platform);
  564. getPlatformSetting!("versionFilters", "addVersionFilters")(dst, platform);
  565. getPlatformSetting!("debugVersionFilters", "addDebugVersionFilters")(dst, platform);
  566. getPlatformSetting!("importPaths", "addImportPaths")(dst, platform);
  567. getPlatformSetting!("cImportPaths", "addCImportPaths")(dst, platform);
  568. getPlatformSetting!("stringImportPaths", "addStringImportPaths")(dst, platform);
  569. getPlatformSetting!("preGenerateCommands", "addPreGenerateCommands")(dst, platform);
  570. getPlatformSetting!("postGenerateCommands", "addPostGenerateCommands")(dst, platform);
  571. getPlatformSetting!("preBuildCommands", "addPreBuildCommands")(dst, platform);
  572. getPlatformSetting!("postBuildCommands", "addPostBuildCommands")(dst, platform);
  573. getPlatformSetting!("preRunCommands", "addPreRunCommands")(dst, platform);
  574. getPlatformSetting!("postRunCommands", "addPostRunCommands")(dst, platform);
  575. getPlatformSetting!("environments", "addEnvironments")(dst, platform);
  576. getPlatformSetting!("buildEnvironments", "addBuildEnvironments")(dst, platform);
  577. getPlatformSetting!("runEnvironments", "addRunEnvironments")(dst, platform);
  578. getPlatformSetting!("preGenerateEnvironments", "addPreGenerateEnvironments")(dst, platform);
  579. getPlatformSetting!("postGenerateEnvironments", "addPostGenerateEnvironments")(dst, platform);
  580. getPlatformSetting!("preBuildEnvironments", "addPreBuildEnvironments")(dst, platform);
  581. getPlatformSetting!("postBuildEnvironments", "addPostBuildEnvironments")(dst, platform);
  582. getPlatformSetting!("preRunEnvironments", "addPreRunEnvironments")(dst, platform);
  583. getPlatformSetting!("postRunEnvironments", "addPostRunEnvironments")(dst, platform);
  584. getPlatformSetting!("buildRequirements", "addRequirements")(dst, platform);
  585. getPlatformSetting!("buildOptions", "addOptions")(dst, platform);
  586. }
  587.  
  588. void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform)
  589. const {
  590. foreach(suffix, values; __traits(getMember, this, name)){
  591. if( platform.matchesSpecification(suffix) )
  592. __traits(getMember, dst, addname)(values);
  593. }
  594. }
  595.  
  596. void warnOnSpecialCompilerFlags(string package_name, string config_name)
  597. {
  598. auto nodef = false;
  599. auto noprop = false;
  600. foreach (req; this.buildRequirements) {
  601. if (req & BuildRequirement.noDefaultFlags) nodef = true;
  602. if (req & BuildRequirement.relaxProperties) noprop = true;
  603. }
  604.  
  605. if (noprop) {
  606. 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.`);
  607. logWarn("");
  608. }
  609.  
  610. if (nodef) {
  611. logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages.");
  612. logWarn("");
  613. } else {
  614. string[] all_dflags;
  615. Flags!BuildOption all_options;
  616. foreach (flags; this.dflags) all_dflags ~= flags;
  617. foreach (options; this.buildOptions) all_options |= options;
  618. .warnOnSpecialCompilerFlags(all_dflags, all_options, package_name, config_name);
  619. }
  620. }
  621. }
  622.  
  623. package(dub) void checkPlatform(const scope ref ToolchainRequirements tr, BuildPlatform platform, string package_name)
  624. {
  625. import std.algorithm.iteration : map;
  626. import std.format : format;
  627.  
  628. Version compilerver;
  629. VersionRange compilerspec;
  630.  
  631. switch (platform.compiler) {
  632. default:
  633. compilerspec = VersionRange.Any;
  634. compilerver = Version.minRelease;
  635. break;
  636. case "dmd":
  637. compilerspec = tr.dmd;
  638. compilerver = platform.compilerVersion.length
  639. ? Version(dmdLikeVersionToSemverLike(platform.compilerVersion))
  640. : Version.minRelease;
  641. break;
  642. case "ldc":
  643. compilerspec = tr.ldc;
  644. compilerver = platform.compilerVersion.length
  645. ? Version(platform.compilerVersion)
  646. : Version.minRelease;
  647. break;
  648. case "gdc":
  649. compilerspec = tr.gdc;
  650. compilerver = platform.compilerVersion.length
  651. ? Version(platform.compilerVersion)
  652. : Version.minRelease;
  653. break;
  654. }
  655.  
  656. enforce(compilerspec != VersionRange.Invalid,
  657. format(
  658. "Installed %s %s is not supported by %s. Supported compiler(s):\n%s",
  659. platform.compiler, platform.compilerVersion, package_name,
  660. tr.supportedCompilers.map!((cs) {
  661. auto str = " - " ~ cs[0];
  662. if (cs[1] != VersionRange.Any) str ~= ": " ~ cs[1].toString();
  663. return str;
  664. }).join("\n")
  665. )
  666. );
  667.  
  668. enforce(compilerspec.matches(compilerver),
  669. format(
  670. "Installed %s-%s does not comply with %s compiler requirement: %s %s\n" ~
  671. "Please consider upgrading your installation.",
  672. platform.compiler, platform.compilerVersion,
  673. package_name, platform.compiler, compilerspec
  674. )
  675. );
  676.  
  677. enforce(tr.frontend.matches(Version(dmdLikeVersionToSemverLike(platform.frontendVersionString))),
  678. format(
  679. "Installed %s-%s with frontend %s does not comply with %s frontend requirement: %s\n" ~
  680. "Please consider upgrading your installation.",
  681. platform.compiler, platform.compilerVersion,
  682. platform.frontendVersionString, package_name, tr.frontend
  683. )
  684. );
  685. }
  686.  
  687. package bool addRequirement(ref ToolchainRequirements req, string name, string value)
  688. {
  689. switch (name) {
  690. default: return false;
  691. case "dub": req.dub = parseVersionRange(value); break;
  692. case "frontend": req.frontend = parseDMDDependency(value); break;
  693. case "ldc": req.ldc = parseVersionRange(value); break;
  694. case "gdc": req.gdc = parseVersionRange(value); break;
  695. case "dmd": req.dmd = parseDMDDependency(value); break;
  696. }
  697. return true;
  698. }
  699.  
  700. private VersionRange parseVersionRange(string dep)
  701. {
  702. if (dep == "no") return VersionRange.Invalid;
  703. return VersionRange.fromString(dep);
  704. }
  705.  
  706. private VersionRange parseDMDDependency(string dep)
  707. {
  708. import std.algorithm : map, splitter;
  709. import std.array : join;
  710.  
  711. if (dep == "no") return VersionRange.Invalid;
  712. return VersionRange.fromString(dep
  713. .splitter(' ')
  714. .map!(r => dmdLikeVersionToSemverLike(r))
  715. .join(' '));
  716. }
  717.  
  718. private T clone(T)(ref const(T) val)
  719. {
  720. import dub.internal.dyaml.stdsumtype;
  721. import std.traits : isSomeString, isDynamicArray, isAssociativeArray, isBasicType, ValueType;
  722.  
  723. static if (is(T == immutable)) return val;
  724. else static if (isBasicType!T || is(T Base == enum) && isBasicType!Base) {
  725. return val;
  726. } else static if (isDynamicArray!T) {
  727. alias V = typeof(T.init[0]);
  728. static if (is(V == immutable)) return val;
  729. else {
  730. T ret = new V[val.length];
  731. foreach (i, ref f; val)
  732. ret[i] = clone!V(f);
  733. return ret;
  734. }
  735. } else static if (isAssociativeArray!T) {
  736. alias V = ValueType!T;
  737. T ret;
  738. foreach (k, ref f; val)
  739. ret[k] = clone!V(f);
  740. return ret;
  741. } else static if (is(T == SumType!A, A...)) {
  742. return val.match!((any) => T(clone(any)));
  743. } else static if (is(T == struct)) {
  744. T ret;
  745. foreach (i, M; typeof(T.tupleof))
  746. ret.tupleof[i] = clone!M(val.tupleof[i]);
  747. return ret;
  748. } else static assert(false, "Unsupported type: "~T.stringof);
  749. }
  750.  
  751. unittest { // issue #1407 - duplicate main source file
  752. {
  753. BuildSettingsTemplate t;
  754. t.mainSourceFile = "./foo.d";
  755. t.sourceFiles[""] = ["foo.d"];
  756. BuildSettings bs;
  757. t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/"));
  758. assert(bs.sourceFiles == ["foo.d"]);
  759. }
  760.  
  761. version (Windows) {{
  762. BuildSettingsTemplate t;
  763. t.mainSourceFile = "src/foo.d";
  764. t.sourceFiles[""] = ["src\\foo.d"];
  765. BuildSettings bs;
  766. t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/"));
  767. assert(bs.sourceFiles == ["src\\foo.d"]);
  768. }}
  769. }
  770.  
  771. /**
  772. * Edit all dependency names from `:foo` to `name:foo`.
  773. *
  774. * TODO: Remove the special case in the parser and remove this hack.
  775. */
  776. package void fixDependenciesNames (T) (string root, ref T aggr) nothrow
  777. {
  778. static foreach (idx, FieldRef; T.tupleof) {
  779. static if (is(immutable typeof(FieldRef) == immutable RecipeDependencyAA)) {
  780. string[] toReplace;
  781. foreach (key; aggr.tupleof[idx].byKey)
  782. if (key.length && key[0] == ':')
  783. toReplace ~= key;
  784. foreach (k; toReplace) {
  785. aggr.tupleof[idx][root ~ k] = aggr.tupleof[idx][k];
  786. aggr.tupleof[idx].data.remove(k);
  787. }
  788. }
  789. else static if (is(typeof(FieldRef) == struct))
  790. fixDependenciesNames(root, aggr.tupleof[idx]);
  791. }
  792. }
  793.  
  794. /**
  795. Turn a DMD-like version (e.g. 2.082.1) into a SemVer-like version (e.g. 2.82.1).
  796. The function accepts a dependency operator prefix and some text postfix.
  797. Prefix and postfix are returned verbatim.
  798. Params:
  799. ver = version string, possibly with a dependency operator prefix and some
  800. test postfix.
  801. Returns:
  802. A Semver compliant string
  803. */
  804. private string dmdLikeVersionToSemverLike(string ver)
  805. {
  806. import std.algorithm : countUntil, joiner, map, skipOver, splitter;
  807. import std.array : join, split;
  808. import std.ascii : isDigit;
  809. import std.conv : text;
  810. import std.exception : enforce;
  811. import std.functional : not;
  812. import std.range : padRight;
  813.  
  814. const start = ver.countUntil!isDigit;
  815. enforce(start != -1, "Invalid semver: "~ver);
  816. const prefix = ver[0 .. start];
  817. ver = ver[start .. $];
  818.  
  819. const end = ver.countUntil!(c => !c.isDigit && c != '.');
  820. const postfix = end == -1 ? null : ver[end .. $];
  821. auto verStr = ver[0 .. $-postfix.length];
  822.  
  823. auto comps = verStr
  824. .splitter(".")
  825. .map!((a) { if (a.length > 1) a.skipOver("0"); return a;})
  826. .padRight("0", 3);
  827.  
  828. return text(prefix, comps.joiner("."), postfix);
  829. }
  830.  
  831. ///
  832. unittest {
  833. assert(dmdLikeVersionToSemverLike("2.082.1") == "2.82.1");
  834. assert(dmdLikeVersionToSemverLike("2.082.0") == "2.82.0");
  835. assert(dmdLikeVersionToSemverLike("2.082") == "2.82.0");
  836. assert(dmdLikeVersionToSemverLike("~>2.082") == "~>2.82.0");
  837. assert(dmdLikeVersionToSemverLike("~>2.082-beta1") == "~>2.82.0-beta1");
  838. assert(dmdLikeVersionToSemverLike("2.4.6") == "2.4.6");
  839. assert(dmdLikeVersionToSemverLike("2.4.6-alpha12") == "2.4.6-alpha12");
  840. }