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