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