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 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.  
  142. /**
  143. * Specifies a list of command line flags usable for controlling
  144. * filter behavior for `--build=ddox` [experimental]
  145. */
  146. @Optional @Name("-ddoxFilterArgs") string[] ddoxFilterArgs;
  147.  
  148. /// Specify which tool to use with `--build=ddox` (experimental)
  149. @Optional @Name("-ddoxTool") string ddoxTool;
  150.  
  151. /**
  152. * Sub-packages path or definitions
  153. *
  154. * Sub-packages allow to break component of a large framework into smaller
  155. * packages. In the recipe file, subpackages entry can take one of two forms:
  156. * either the path to a sub-folder where a recipe file exists,
  157. * or an object of the same format as a recipe file (or `PackageRecipe`).
  158. */
  159. @Optional SubPackage[] subPackages;
  160.  
  161. /// Usually unused by users, this is set by dub automatically
  162. @Optional @Name("version") string version_;
  163.  
  164. inout(ConfigurationInfo) getConfiguration(string name)
  165. inout {
  166. foreach (c; configurations)
  167. if (c.name == name)
  168. return c;
  169. throw new Exception("Unknown configuration: "~name);
  170. }
  171.  
  172. /** Clones the package recipe recursively.
  173. */
  174. PackageRecipe clone() const { return .clone(this); }
  175. }
  176.  
  177. struct SubPackage
  178. {
  179. string path;
  180. PackageRecipe recipe;
  181. }
  182.  
  183. /// Describes minimal toolchain requirements
  184. struct ToolchainRequirements
  185. {
  186. import std.typecons : Tuple, tuple;
  187.  
  188. /// DUB version requirement
  189. Dependency dub = Dependency.any;
  190. /// D front-end version requirement
  191. Dependency frontend = Dependency.any;
  192. /// DMD version requirement
  193. Dependency dmd = Dependency.any;
  194. /// LDC version requirement
  195. Dependency ldc = Dependency.any;
  196. /// GDC version requirement
  197. Dependency gdc = Dependency.any;
  198.  
  199. /** Get the list of supported compilers.
  200.  
  201. Returns:
  202. An array of couples of compiler name and compiler requirement
  203. */
  204. @property Tuple!(string, Dependency)[] supportedCompilers() const
  205. {
  206. Tuple!(string, Dependency)[] res;
  207. if (dmd != Dependency.invalid) res ~= Tuple!(string, Dependency)("dmd", dmd);
  208. if (ldc != Dependency.invalid) res ~= Tuple!(string, Dependency)("ldc", ldc);
  209. if (gdc != Dependency.invalid) res ~= Tuple!(string, Dependency)("gdc", gdc);
  210. return res;
  211. }
  212.  
  213. bool empty()
  214. const {
  215. import std.algorithm.searching : all;
  216. return only(dub, frontend, dmd, ldc, gdc)
  217. .all!(r => r == Dependency.any);
  218. }
  219. }
  220.  
  221.  
  222. /// Bundles information about a build configuration.
  223. struct ConfigurationInfo {
  224. string name;
  225. string[] platforms;
  226. BuildSettingsTemplate buildSettings;
  227.  
  228. this(string name, BuildSettingsTemplate build_settings)
  229. {
  230. enforce(!name.empty, "Configuration name is empty.");
  231. this.name = name;
  232. this.buildSettings = build_settings;
  233. }
  234.  
  235. bool matchesPlatform(in BuildPlatform platform)
  236. const {
  237. if( platforms.empty ) return true;
  238. foreach(p; platforms)
  239. if (platform.matchesSpecification(p))
  240. return true;
  241. return false;
  242. }
  243. }
  244.  
  245. /**
  246. * A dependency with possible `BuildSettingsTemplate`
  247. *
  248. * Currently only `dflags` is taken into account, but the parser accepts any
  249. * value that is in `BuildSettingsTemplate`.
  250. * This feature was originally introduced to support `-preview`, as setting
  251. * a `-preview` in `dflags` does not propagate down to dependencies.
  252. */
  253. public struct RecipeDependency
  254. {
  255. /// The dependency itself
  256. public Dependency dependency;
  257.  
  258. /// Additional dflags, if any
  259. public BuildSettingsTemplate settings;
  260.  
  261. /// Convenience alias as most uses just want to deal with the `Dependency`
  262. public alias dependency this;
  263. }
  264.  
  265. /// Type used to avoid a breaking change when `Dependency[string]`
  266. /// was changed to `RecipeDependency[string]`
  267. private struct RecipeDependencyAA
  268. {
  269. /// The underlying data, `public` as `alias this` to `private` field doesn't
  270. /// always work.
  271. public RecipeDependency[string] data;
  272.  
  273. /// Expose base function, e.g. `clear`
  274. alias data this;
  275.  
  276. /// Supports assignment from a `RecipeDependency` (used in the parser)
  277. public void opIndexAssign(RecipeDependency dep, string key)
  278. pure nothrow
  279. {
  280. this.data[key] = dep;
  281. }
  282.  
  283. /// Supports assignment from a `Dependency`, used in user code mostly
  284. public void opIndexAssign(Dependency dep, string key)
  285. pure nothrow
  286. {
  287. this.data[key] = RecipeDependency(dep);
  288. }
  289. }
  290.  
  291. /// This keeps general information about how to build a package.
  292. /// It contains functions to create a specific BuildSetting, targeted at
  293. /// a certain BuildPlatform.
  294. struct BuildSettingsTemplate {
  295. RecipeDependencyAA dependencies;
  296. string systemDependencies;
  297. TargetType targetType = TargetType.autodetect;
  298. string targetPath;
  299. string targetName;
  300. string workingDirectory;
  301. string mainSourceFile;
  302. string[string] subConfigurations;
  303. string[][string] dflags;
  304. string[][string] lflags;
  305. string[][string] libs;
  306. string[][string] sourceFiles;
  307. string[][string] sourcePaths;
  308. string[][string] excludedSourceFiles;
  309. string[][string] injectSourceFiles;
  310. string[][string] copyFiles;
  311. string[][string] extraDependencyFiles;
  312. string[][string] versions;
  313. string[][string] debugVersions;
  314. string[][string] versionFilters;
  315. string[][string] debugVersionFilters;
  316. string[][string] importPaths;
  317. string[][string] stringImportPaths;
  318. string[][string] preGenerateCommands;
  319. string[][string] postGenerateCommands;
  320. string[][string] preBuildCommands;
  321. string[][string] postBuildCommands;
  322. string[][string] preRunCommands;
  323. string[][string] postRunCommands;
  324. string[string][string] environments;
  325. string[string][string] buildEnvironments;
  326. string[string][string] runEnvironments;
  327. string[string][string] preGenerateEnvironments;
  328. string[string][string] postGenerateEnvironments;
  329. string[string][string] preBuildEnvironments;
  330. string[string][string] postBuildEnvironments;
  331. string[string][string] preRunEnvironments;
  332. string[string][string] postRunEnvironments;
  333. Flags!BuildRequirement[string] buildRequirements;
  334. Flags!BuildOption[string] buildOptions;
  335.  
  336.  
  337. BuildSettingsTemplate dup() const {
  338. return clone(this);
  339. }
  340.  
  341. /// Constructs a BuildSettings object from this template.
  342. void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, NativePath base_path)
  343. const {
  344. dst.targetType = this.targetType;
  345. if (!this.targetPath.empty) dst.targetPath = this.targetPath;
  346. if (!this.targetName.empty) dst.targetName = this.targetName;
  347. if (!this.workingDirectory.empty) dst.workingDirectory = this.workingDirectory;
  348. if (!this.mainSourceFile.empty) {
  349. auto p = NativePath(this.mainSourceFile);
  350. p.normalize();
  351. dst.mainSourceFile = p.toNativeString();
  352. dst.addSourceFiles(dst.mainSourceFile);
  353. }
  354.  
  355. string[] collectFiles(in string[][string] paths_map, string pattern)
  356. {
  357. auto files = appender!(string[]);
  358.  
  359. import dub.project : buildSettingsVars;
  360. import std.typecons : Nullable;
  361.  
  362. static Nullable!(string[string]) envVarCache;
  363.  
  364. if (envVarCache.isNull) envVarCache = environment.toAA();
  365.  
  366. foreach (suffix, paths; paths_map) {
  367. if (!platform.matchesSpecification(suffix))
  368. continue;
  369.  
  370. foreach (spath; paths) {
  371. enforce(!spath.empty, "Paths must not be empty strings.");
  372. auto path = NativePath(spath);
  373. if (!path.absolute) path = base_path ~ path;
  374. if (!existsFile(path) || !isDir(path.toNativeString())) {
  375. import std.algorithm : any, find;
  376. const hasVar = chain(buildSettingsVars, envVarCache.get.byKey).any!((string var) {
  377. return spath.find("$"~var).length > 0 || spath.find("${"~var~"}").length > 0;
  378. });
  379. if (!hasVar)
  380. logWarn("Invalid source/import path: %s", path.toNativeString());
  381. continue;
  382. }
  383.  
  384. auto pstr = path.toNativeString();
  385. foreach (d; dirEntries(pstr, pattern, SpanMode.depth)) {
  386. import std.path : baseName, pathSplitter;
  387. import std.algorithm.searching : canFind;
  388. // eliminate any hidden files, or files in hidden directories. But always include
  389. // files that are listed inside hidden directories that are specifically added to
  390. // the project.
  391. if (d.isDir || pathSplitter(d.name[pstr.length .. $])
  392. .canFind!(name => name.length && name[0] == '.'))
  393. continue;
  394. auto src = NativePath(d.name).relativeTo(base_path);
  395. files ~= src.toNativeString();
  396. }
  397. }
  398. }
  399.  
  400. return files.data;
  401. }
  402.  
  403. // collect source files
  404. dst.addSourceFiles(collectFiles(sourcePaths, "*.d"));
  405. auto sourceFiles = dst.sourceFiles.sort();
  406.  
  407. // collect import files and remove sources
  408. import std.algorithm : copy, setDifference;
  409.  
  410. auto importFiles = collectFiles(importPaths, "*.{d,di}").sort();
  411. immutable nremoved = importFiles.setDifference(sourceFiles).copy(importFiles.release).length;
  412. importFiles = importFiles[0 .. $ - nremoved];
  413. dst.addImportFiles(importFiles.release);
  414.  
  415. dst.addStringImportFiles(collectFiles(stringImportPaths, "*"));
  416.  
  417. getPlatformSetting!("dflags", "addDFlags")(dst, platform);
  418. getPlatformSetting!("lflags", "addLFlags")(dst, platform);
  419. getPlatformSetting!("libs", "addLibs")(dst, platform);
  420. getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform);
  421. getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform);
  422. getPlatformSetting!("injectSourceFiles", "addInjectSourceFiles")(dst, platform);
  423. getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform);
  424. getPlatformSetting!("extraDependencyFiles", "addExtraDependencyFiles")(dst, platform);
  425. getPlatformSetting!("versions", "addVersions")(dst, platform);
  426. getPlatformSetting!("debugVersions", "addDebugVersions")(dst, platform);
  427. getPlatformSetting!("versionFilters", "addVersionFilters")(dst, platform);
  428. getPlatformSetting!("debugVersionFilters", "addDebugVersionFilters")(dst, platform);
  429. getPlatformSetting!("importPaths", "addImportPaths")(dst, platform);
  430. getPlatformSetting!("stringImportPaths", "addStringImportPaths")(dst, platform);
  431. getPlatformSetting!("preGenerateCommands", "addPreGenerateCommands")(dst, platform);
  432. getPlatformSetting!("postGenerateCommands", "addPostGenerateCommands")(dst, platform);
  433. getPlatformSetting!("preBuildCommands", "addPreBuildCommands")(dst, platform);
  434. getPlatformSetting!("postBuildCommands", "addPostBuildCommands")(dst, platform);
  435. getPlatformSetting!("preRunCommands", "addPreRunCommands")(dst, platform);
  436. getPlatformSetting!("postRunCommands", "addPostRunCommands")(dst, platform);
  437. getPlatformSetting!("environments", "addEnvironments")(dst, platform);
  438. getPlatformSetting!("buildEnvironments", "addBuildEnvironments")(dst, platform);
  439. getPlatformSetting!("runEnvironments", "addRunEnvironments")(dst, platform);
  440. getPlatformSetting!("preGenerateEnvironments", "addPreGenerateEnvironments")(dst, platform);
  441. getPlatformSetting!("postGenerateEnvironments", "addPostGenerateEnvironments")(dst, platform);
  442. getPlatformSetting!("preBuildEnvironments", "addPreBuildEnvironments")(dst, platform);
  443. getPlatformSetting!("postBuildEnvironments", "addPostBuildEnvironments")(dst, platform);
  444. getPlatformSetting!("preRunEnvironments", "addPreRunEnvironments")(dst, platform);
  445. getPlatformSetting!("postRunEnvironments", "addPostRunEnvironments")(dst, platform);
  446. getPlatformSetting!("buildRequirements", "addRequirements")(dst, platform);
  447. getPlatformSetting!("buildOptions", "addOptions")(dst, platform);
  448. }
  449.  
  450. void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform)
  451. const {
  452. foreach(suffix, values; __traits(getMember, this, name)){
  453. if( platform.matchesSpecification(suffix) )
  454. __traits(getMember, dst, addname)(values);
  455. }
  456. }
  457.  
  458. void warnOnSpecialCompilerFlags(string package_name, string config_name)
  459. {
  460. auto nodef = false;
  461. auto noprop = false;
  462. foreach (req; this.buildRequirements) {
  463. if (req & BuildRequirement.noDefaultFlags) nodef = true;
  464. if (req & BuildRequirement.relaxProperties) noprop = true;
  465. }
  466.  
  467. if (noprop) {
  468. 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.`);
  469. logWarn("");
  470. }
  471.  
  472. if (nodef) {
  473. logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages.");
  474. logWarn("");
  475. } else {
  476. string[] all_dflags;
  477. Flags!BuildOption all_options;
  478. foreach (flags; this.dflags) all_dflags ~= flags;
  479. foreach (options; this.buildOptions) all_options |= options;
  480. .warnOnSpecialCompilerFlags(all_dflags, all_options, package_name, config_name);
  481. }
  482. }
  483. }
  484.  
  485. package(dub) void checkPlatform(const scope ref ToolchainRequirements tr, BuildPlatform platform, string package_name)
  486. {
  487. import dub.compilers.utils : dmdLikeVersionToSemverLike;
  488. import std.algorithm.iteration : map;
  489. import std.format : format;
  490.  
  491. string compilerver;
  492. Dependency compilerspec;
  493.  
  494. switch (platform.compiler) {
  495. default:
  496. compilerspec = Dependency.any;
  497. compilerver = "0.0.0";
  498. break;
  499. case "dmd":
  500. compilerspec = tr.dmd;
  501. compilerver = platform.compilerVersion.length
  502. ? dmdLikeVersionToSemverLike(platform.compilerVersion)
  503. : "0.0.0";
  504. break;
  505. case "ldc":
  506. compilerspec = tr.ldc;
  507. compilerver = platform.compilerVersion;
  508. if (!compilerver.length) compilerver = "0.0.0";
  509. break;
  510. case "gdc":
  511. compilerspec = tr.gdc;
  512. compilerver = platform.compilerVersion;
  513. if (!compilerver.length) compilerver = "0.0.0";
  514. break;
  515. }
  516.  
  517. enforce(compilerspec != Dependency.invalid,
  518. format(
  519. "Installed %s %s is not supported by %s. Supported compiler(s):\n%s",
  520. platform.compiler, platform.compilerVersion, package_name,
  521. tr.supportedCompilers.map!((cs) {
  522. auto str = " - " ~ cs[0];
  523. if (cs[1] != Dependency.any) str ~= ": " ~ cs[1].toString();
  524. return str;
  525. }).join("\n")
  526. )
  527. );
  528.  
  529. enforce(compilerspec.matches(compilerver),
  530. format(
  531. "Installed %s-%s does not comply with %s compiler requirement: %s %s\n" ~
  532. "Please consider upgrading your installation.",
  533. platform.compiler, platform.compilerVersion,
  534. package_name, platform.compiler, compilerspec
  535. )
  536. );
  537.  
  538. enforce(tr.frontend.matches(dmdLikeVersionToSemverLike(platform.frontendVersionString)),
  539. format(
  540. "Installed %s-%s with frontend %s does not comply with %s frontend requirement: %s\n" ~
  541. "Please consider upgrading your installation.",
  542. platform.compiler, platform.compilerVersion,
  543. platform.frontendVersionString, package_name, tr.frontend
  544. )
  545. );
  546. }
  547.  
  548. package bool addRequirement(ref ToolchainRequirements req, string name, string value)
  549. {
  550. switch (name) {
  551. default: return false;
  552. case "dub": req.dub = parseDependency(value); break;
  553. case "frontend": req.frontend = parseDMDDependency(value); break;
  554. case "ldc": req.ldc = parseDependency(value); break;
  555. case "gdc": req.gdc = parseDependency(value); break;
  556. case "dmd": req.dmd = parseDMDDependency(value); break;
  557. }
  558. return true;
  559. }
  560.  
  561. private static Dependency parseDependency(string dep)
  562. {
  563. if (dep == "no") return Dependency.invalid;
  564. return Dependency(dep);
  565. }
  566.  
  567. private static Dependency parseDMDDependency(string dep)
  568. {
  569. import dub.compilers.utils : dmdLikeVersionToSemverLike;
  570. import dub.dependency : Dependency;
  571. import std.algorithm : map, splitter;
  572. import std.array : join;
  573.  
  574. if (dep == "no") return Dependency.invalid;
  575. return dep
  576. .splitter(' ')
  577. .map!(r => dmdLikeVersionToSemverLike(r))
  578. .join(' ')
  579. .Dependency;
  580. }
  581.  
  582. private T clone(T)(ref const(T) val)
  583. {
  584. import std.sumtype;
  585. import std.traits : isSomeString, isDynamicArray, isAssociativeArray, isBasicType, ValueType;
  586.  
  587. static if (is(T == immutable)) return val;
  588. else static if (isBasicType!T) return val;
  589. else static if (isDynamicArray!T) {
  590. alias V = typeof(T.init[0]);
  591. static if (is(V == immutable)) return val;
  592. else {
  593. T ret = new V[val.length];
  594. foreach (i, ref f; val)
  595. ret[i] = clone!V(f);
  596. return ret;
  597. }
  598. } else static if (isAssociativeArray!T) {
  599. alias V = ValueType!T;
  600. T ret;
  601. foreach (k, ref f; val)
  602. ret[k] = clone!V(f);
  603. return ret;
  604. } else static if (is(T == SumType!A, A...)) {
  605. return val.match!((any) => T(clone(any)));
  606. } else static if (is(T == struct)) {
  607. T ret;
  608. foreach (i, M; typeof(T.tupleof))
  609. ret.tupleof[i] = clone!M(val.tupleof[i]);
  610. return ret;
  611. } else static assert(false, "Unsupported type: "~T.stringof);
  612. }
  613.  
  614. unittest { // issue #1407 - duplicate main source file
  615. {
  616. BuildSettingsTemplate t;
  617. t.mainSourceFile = "./foo.d";
  618. t.sourceFiles[""] = ["foo.d"];
  619. BuildSettings bs;
  620. t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/"));
  621. assert(bs.sourceFiles == ["foo.d"]);
  622. }
  623.  
  624. version (Windows) {{
  625. BuildSettingsTemplate t;
  626. t.mainSourceFile = "src/foo.d";
  627. t.sourceFiles[""] = ["src\\foo.d"];
  628. BuildSettings bs;
  629. t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/"));
  630. assert(bs.sourceFiles == ["src\\foo.d"]);
  631. }}
  632. }