Newer
Older
dub_jkp / source / dub / package_.d
@Dicebot Dicebot on 1 Nov 2013 27 KB Remove 'installation' references.
  1. /**
  2. Stuff with dependencies.
  3.  
  4. Copyright: © 2012-2013 Matthias Dondorff
  5. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
  6. Authors: Matthias Dondorff
  7. */
  8. module dub.package_;
  9.  
  10. import dub.compilers.compiler;
  11. import dub.dependency;
  12. import dub.internal.utils;
  13. import dub.internal.vibecompat.core.log;
  14. import dub.internal.vibecompat.core.file;
  15. import dub.internal.vibecompat.data.json;
  16. import dub.internal.vibecompat.inet.url;
  17.  
  18. import std.algorithm;
  19. import std.array;
  20. import std.conv;
  21. import std.exception;
  22. import std.file;
  23. import std.range;
  24. import std.string;
  25. import std.traits : EnumMembers;
  26.  
  27. enum PackageJsonFilename = "package.json";
  28.  
  29.  
  30. /// Indicates where a package has been or should be placed to.
  31. enum PlacementLocation {
  32. /// Packages retrived with 'local' will be placed in the current folder
  33. /// using the package name as destination.
  34. local,
  35. /// Packages with 'userWide' will be placed in a folder accessible by
  36. /// all of the applications from the current user.
  37. userWide,
  38. /// Packages retrieved with 'systemWide' will be placed in a shared folder,
  39. /// which can be accessed by all users of the system.
  40. systemWide
  41. }
  42.  
  43. /// Representing a downloded / cached package, usually constructed from a json object.
  44. /// Documentation of the package.json can be found at
  45. /// http://registry.vibed.org/package-format
  46. class Package {
  47. static struct LocalPackageDef { string name; Version version_; Path path; }
  48.  
  49. private {
  50. Path m_path;
  51. PackageInfo m_info;
  52. Package m_parentPackage;
  53. Package[] m_subPackages;
  54. }
  55.  
  56. this(Path root, Package parent = null)
  57. {
  58. this(jsonFromFile(root ~ PackageJsonFilename), root, parent);
  59. }
  60.  
  61. this(Json packageInfo, Path root = Path(), Package parent = null)
  62. {
  63. m_parentPackage = parent;
  64. m_path = root;
  65.  
  66. // check for default string import folders
  67. foreach(defvf; ["views"]){
  68. auto p = m_path ~ defvf;
  69. if( existsFile(p) )
  70. m_info.buildSettings.stringImportPaths[""] ~= defvf;
  71. }
  72.  
  73. string[] app_files;
  74. auto pkg_name = packageInfo.name.get!string();
  75.  
  76. // check for default source folders
  77. foreach(defsf; ["source", "src"]){
  78. auto p = m_path ~ defsf;
  79. if( existsFile(p) ){
  80. m_info.buildSettings.sourcePaths[""] ~= defsf;
  81. m_info.buildSettings.importPaths[""] ~= defsf;
  82. if( existsFile(p ~ "app.d") ) app_files ~= Path(defsf ~ "/app.d").toNativeString();
  83. else if( existsFile(p ~ (pkg_name~".d")) ) app_files ~= Path(defsf ~ "/"~pkg_name~".d").toNativeString();
  84. }
  85. }
  86.  
  87. // parse the JSON description
  88. {
  89. scope(failure) logError("Failed to parse package description in %s", root.toNativeString());
  90. m_info.parseJson(packageInfo);
  91.  
  92. // try to run git to determine the version of the package if no explicit version was given
  93. if (m_info.version_.length == 0 && !parent) {
  94. import dub.internal.std.process;
  95. try {
  96. auto branch = execute(["git", "--git-dir="~(root~".git").toNativeString(), "rev-parse", "--abbrev-ref", "HEAD"]);
  97. enforce(branch.status == 0, "git rev-parse failed: " ~ branch.output);
  98. if (branch.output.strip() == "HEAD") {
  99. //auto ver = execute("git",)
  100. enforce(false, "oops");
  101. } else {
  102. m_info.version_ = "~" ~ branch.output.strip();
  103. }
  104. } catch (Exception e) {
  105. logDebug("Failed to run git: %s", e.msg);
  106. }
  107.  
  108. if (m_info.version_.length == 0) {
  109. logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", m_info.name, this.path.toNativeString());
  110. m_info.version_ = "~master";
  111. } else logDiagnostic("Determined package version using GIT: %s %s", m_info.name, m_info.version_);
  112. }
  113. }
  114.  
  115. // generate default configurations if none are defined
  116. if (m_info.configurations.length == 0) {
  117. if (m_info.buildSettings.targetType == TargetType.executable) {
  118. BuildSettingsTemplate app_settings;
  119. app_settings.targetType = TargetType.executable;
  120. m_info.configurations ~= ConfigurationInfo("application", app_settings);
  121. } else if (m_info.buildSettings.targetType != TargetType.none) {
  122. BuildSettingsTemplate lib_settings;
  123. lib_settings.targetType = m_info.buildSettings.targetType == TargetType.autodetect ? TargetType.library : m_info.buildSettings.targetType;
  124.  
  125. if (m_info.buildSettings.targetType == TargetType.autodetect) {
  126. if (app_files.length) {
  127. lib_settings.excludedSourceFiles[""] = app_files;
  128.  
  129. BuildSettingsTemplate app_settings;
  130. app_settings.targetType = TargetType.executable;
  131. m_info.configurations ~= ConfigurationInfo("application", app_settings);
  132. }
  133. }
  134.  
  135. m_info.configurations ~= ConfigurationInfo("library", lib_settings);
  136. }
  137. }
  138.  
  139. // load all sub packages defined in the package description
  140. foreach (p; packageInfo.subPackages.opt!(Json[]))
  141. m_subPackages ~= new Package(p, root, this);
  142. }
  143. @property string name()
  144. const {
  145. if (m_parentPackage) return m_parentPackage.name ~ ":" ~ m_info.name;
  146. else return m_info.name;
  147. }
  148. @property string vers() const { return m_parentPackage ? m_parentPackage.vers : m_info.version_; }
  149. @property Version ver() const { return Version(this.vers); }
  150. @property const(PackageInfo) info() const { return m_info; }
  151. @property Path path() const { return m_path; }
  152. @property Path packageInfoFile() const { return m_path ~ "package.json"; }
  153. @property const(Dependency[string]) dependencies() const { return m_info.dependencies; }
  154. @property inout(Package) basePackage() inout { return m_parentPackage ? m_parentPackage.basePackage : this; }
  155. @property inout(Package) parentPackage() inout { return m_parentPackage; }
  156. @property inout(Package)[] subPackages() inout { return m_subPackages; }
  157.  
  158. @property string[] configurations()
  159. const {
  160. auto ret = appender!(string[])();
  161. foreach( ref config; m_info.configurations )
  162. ret.put(config.name);
  163. return ret.data;
  164. }
  165.  
  166. inout(Package) getSubPackage(string name) inout {
  167. foreach (p; m_subPackages)
  168. if (p.name == this.name ~ ":" ~ name)
  169. return p;
  170. throw new Exception(format("Unknown sub package: %s:%s", this.name, name));
  171. }
  172.  
  173. void warnOnSpecialCompilerFlags()
  174. {
  175. // warn about use of special flags
  176. m_info.buildSettings.warnOnSpecialCompilerFlags(m_info.name, null);
  177. foreach (ref config; m_info.configurations)
  178. config.buildSettings.warnOnSpecialCompilerFlags(m_info.name, config.name);
  179. }
  180.  
  181. /// Returns all BuildSettings for the given platform and config.
  182. BuildSettings getBuildSettings(in BuildPlatform platform, string config)
  183. const {
  184. BuildSettings ret;
  185. foreach(ref conf; m_info.configurations){
  186. if( conf.name != config ) continue;
  187. m_info.buildSettings.getPlatformSettings(ret, platform, this.path);
  188. conf.buildSettings.getPlatformSettings(ret, platform, this.path);
  189. // construct default target name based on package name
  190. if( ret.targetName.empty ) ret.targetName = this.name.replace(":", "_");
  191.  
  192. // special support for DMD style flags
  193. getCompiler("dmd").extractBuildOptions(ret);
  194.  
  195. return ret;
  196. }
  197. assert(config is null, "Unknown configuration for "~m_info.name~": "~config);
  198. m_info.buildSettings.getPlatformSettings(ret, platform, this.path);
  199. return ret;
  200. }
  201.  
  202. void addBuildTypeSettings(ref BuildSettings settings, in BuildPlatform platform, string build_type)
  203. const {
  204. if (build_type == "$DFLAGS") {
  205. import dub.internal.std.process;
  206. string dflags = environment.get("DFLAGS");
  207. settings.addDFlags(dflags.split());
  208. return;
  209. }
  210.  
  211. if (auto pbt = build_type in m_info.buildTypes) {
  212. logDiagnostic("Using custom build type '%s'.", build_type);
  213. pbt.getPlatformSettings(settings, platform, this.path);
  214. } else {
  215. with(BuildOptions) switch (build_type) {
  216. default: throw new Exception(format("Unknown build type for %s: '%s'", this.name, build_type));
  217. case "plain": break;
  218. case "debug": settings.addOptions(debug_, debugInfo); break;
  219. case "release": settings.addOptions(release, optimize, inline); break;
  220. case "unittest": settings.addOptions(unittests, debug_, debugInfo); break;
  221. case "docs": settings.addOptions(syntaxOnly); settings.addDFlags("-c", "-Dddocs"); break;
  222. case "ddox": settings.addOptions(syntaxOnly); settings.addDFlags("-c", "-Df__dummy.html", "-Xfdocs.json"); break;
  223. case "profile": settings.addOptions(profile, optimize, inline, debugInfo); break;
  224. case "cov": settings.addOptions(coverage, debugInfo); break;
  225. case "unittest-cov": settings.addOptions(unittests, coverage, debug_, debugInfo); break;
  226. }
  227. }
  228. }
  229.  
  230. string getSubConfiguration(string config, in Package dependency, in BuildPlatform platform)
  231. const {
  232. bool found = false;
  233. foreach(ref c; m_info.configurations){
  234. if( c.name == config ){
  235. if( auto pv = dependency.name in c.buildSettings.subConfigurations ) return *pv;
  236. found = true;
  237. break;
  238. }
  239. }
  240. assert(found || config is null, "Invalid configuration \""~config~"\" for "~this.name);
  241. if( auto pv = dependency.name in m_info.buildSettings.subConfigurations ) return *pv;
  242. return null;
  243. }
  244.  
  245. /// Returns the default configuration to build for the given platform
  246. string getDefaultConfiguration(in BuildPlatform platform, bool is_main_package = false)
  247. const {
  248. foreach(ref conf; m_info.configurations){
  249. if( !conf.matchesPlatform(platform) ) continue;
  250. if( !is_main_package && conf.buildSettings.targetType == TargetType.executable ) continue;
  251. return conf.name;
  252. }
  253. return null;
  254. }
  255.  
  256. /// Returns a list of configurations suitable for the given platform
  257. string[] getPlatformConfigurations(in BuildPlatform platform, bool is_main_package = false)
  258. const {
  259. auto ret = appender!(string[]);
  260. foreach(ref conf; m_info.configurations){
  261. if (!conf.matchesPlatform(platform)) continue;
  262. if (!is_main_package && conf.buildSettings.targetType == TargetType.executable) continue;
  263. ret ~= conf.name;
  264. }
  265. if (ret.data.length == 0) ret.put(null);
  266. return ret.data;
  267. }
  268.  
  269. /// Human readable information of this package and its dependencies.
  270. string generateInfoString() const {
  271. string s;
  272. s ~= m_info.name ~ ", version '" ~ m_info.version_ ~ "'";
  273. s ~= "\n Dependencies:";
  274. foreach(string p, ref const Dependency v; m_info.dependencies)
  275. s ~= "\n " ~ p ~ ", version '" ~ to!string(v) ~ "'";
  276. return s;
  277. }
  278. /// Writes the json file back to the filesystem
  279. void writeJson(Path path) {
  280. auto dstFile = openFile((path~PackageJsonFilename).toString(), FileMode.CreateTrunc);
  281. scope(exit) dstFile.close();
  282. dstFile.writePrettyJsonString(m_info.toJson());
  283. assert(false);
  284. }
  285.  
  286. bool hasDependency(string depname, string config)
  287. const {
  288. if (depname in m_info.buildSettings.dependencies) return true;
  289. foreach (ref c; m_info.configurations)
  290. if ((config.empty || c.name == config) && depname in c.buildSettings.dependencies)
  291. return true;
  292. return false;
  293. }
  294.  
  295. void describe(ref Json dst, BuildPlatform platform, string config)
  296. {
  297. dst.path = m_path.toNativeString();
  298. dst.name = this.name;
  299. dst["version"] = this.vers;
  300. dst.description = m_info.description;
  301. dst.homepage = m_info.homepage;
  302. dst.authors = m_info.authors.serializeToJson();
  303. dst.copyright = m_info.copyright;
  304. dst.license = m_info.license;
  305. dst.dependencies = m_info.dependencies.keys.serializeToJson();
  306.  
  307. // save build settings
  308. BuildSettings bs = getBuildSettings(platform, config);
  309.  
  310. foreach (string k, v; bs.serializeToJson()) dst[k] = v;
  311. dst.remove("requirements");
  312. dst.remove("sourceFiles");
  313. dst.targetType = bs.targetType.to!string();
  314. dst.targetFileName = getTargetFileName(bs, platform);
  315.  
  316. // prettify build requirements output
  317. Json[] breqs;
  318. for (int i = 1; i <= BuildRequirements.max; i <<= 1)
  319. if (bs.requirements & i)
  320. breqs ~= Json(to!string(cast(BuildRequirements)i));
  321. dst.buildRequirements = breqs;
  322.  
  323. // prettify files output
  324. Json[] files;
  325. foreach (f; bs.sourceFiles) {
  326. auto jf = Json.EmptyObject;
  327. jf.path = f;
  328. jf["type"] = "source";
  329. files ~= jf;
  330. }
  331. dst.files = Json(files);
  332. }
  333. }
  334.  
  335. /// Specifying package information without any connection to a certain
  336. /// retrived package, like Package class is doing.
  337. struct PackageInfo {
  338. string name;
  339. string version_;
  340. string description;
  341. string homepage;
  342. string[] authors;
  343. string copyright;
  344. string license;
  345. string[] ddoxFilterArgs;
  346. BuildSettingsTemplate buildSettings;
  347. ConfigurationInfo[] configurations;
  348. BuildSettingsTemplate[string] buildTypes;
  349.  
  350. @property const(Dependency)[string] dependencies()
  351. const {
  352. const(Dependency)[string] ret;
  353. foreach (n, d; this.buildSettings.dependencies)
  354. ret[n] = d;
  355. foreach (ref c; configurations)
  356. foreach (n, d; c.buildSettings.dependencies)
  357. ret[n] = d;
  358. return ret;
  359. }
  360.  
  361. void parseJson(Json json)
  362. {
  363. foreach( string field, value; json ){
  364. switch(field){
  365. default: break;
  366. case "name": this.name = value.get!string; break;
  367. case "version": this.version_ = value.get!string; break;
  368. case "description": this.description = value.get!string; break;
  369. case "homepage": this.homepage = value.get!string; break;
  370. case "authors": this.authors = deserializeJson!(string[])(value); break;
  371. case "copyright": this.copyright = value.get!string; break;
  372. case "license": this.license = value.get!string; break;
  373. case "-ddoxFilterArgs": this.ddoxFilterArgs = deserializeJson!(string[])(value); break;
  374. case "configurations": break; // handled below, after the global settings have been parsed
  375. case "buildTypes":
  376. foreach (string name, settings; value) {
  377. BuildSettingsTemplate bs;
  378. bs.parseJson(settings);
  379. buildTypes[name] = bs;
  380. }
  381. break;
  382. }
  383. }
  384.  
  385. // parse build settings
  386. this.buildSettings.parseJson(json);
  387.  
  388. if (auto pv = "configurations" in json) {
  389. TargetType deftargettp = TargetType.library;
  390. if (this.buildSettings.targetType != TargetType.autodetect)
  391. deftargettp = this.buildSettings.targetType;
  392.  
  393. foreach (settings; *pv) {
  394. ConfigurationInfo ci;
  395. ci.parseJson(settings, deftargettp);
  396. this.configurations ~= ci;
  397. }
  398. }
  399.  
  400. enforce(this.name.length > 0, "The package \"name\" field is missing or empty.");
  401. }
  402.  
  403. Json toJson()
  404. const {
  405. auto ret = buildSettings.toJson();
  406. ret.name = this.name;
  407. if( !this.version_.empty ) ret["version"] = this.version_;
  408. if( !this.description.empty ) ret.description = this.description;
  409. if( !this.homepage.empty ) ret.homepage = this.homepage;
  410. if( !this.authors.empty ) ret.authors = serializeToJson(this.authors);
  411. if( !this.copyright.empty ) ret.copyright = this.copyright;
  412. if( !this.license.empty ) ret.license = this.license;
  413. if( !this.ddoxFilterArgs.empty ) ret["-ddoxFilterArgs"] = this.ddoxFilterArgs.serializeToJson();
  414. if( this.configurations ){
  415. Json[] configs;
  416. foreach(config; this.configurations)
  417. configs ~= config.toJson();
  418. ret.configurations = configs;
  419. }
  420. return ret;
  421. }
  422. }
  423.  
  424. /// Bundles information about a build configuration.
  425. struct ConfigurationInfo {
  426. string name;
  427. string[] platforms;
  428. BuildSettingsTemplate buildSettings;
  429.  
  430. this(string name, BuildSettingsTemplate build_settings)
  431. {
  432. enforce(!name.empty, "Configuration name is empty.");
  433. this.name = name;
  434. this.buildSettings = build_settings;
  435. }
  436.  
  437. void parseJson(Json json, TargetType default_target_type = TargetType.library)
  438. {
  439. this.buildSettings.targetType = default_target_type;
  440.  
  441. foreach(string name, value; json){
  442. switch(name){
  443. default: break;
  444. case "name":
  445. this.name = value.get!string();
  446. enforce(!this.name.empty, "Configurations must have a non-empty name.");
  447. break;
  448. case "platforms": this.platforms = deserializeJson!(string[])(value); break;
  449. }
  450. }
  451.  
  452. enforce(!this.name.empty, "Configuration is missing a name.");
  453.  
  454. BuildSettingsTemplate bs;
  455. this.buildSettings.parseJson(json);
  456. }
  457.  
  458. Json toJson()
  459. const {
  460. auto ret = buildSettings.toJson();
  461. ret.name = name;
  462. if( this.platforms.length ) ret.platforms = serializeToJson(platforms);
  463. return ret;
  464. }
  465.  
  466. bool matchesPlatform(in BuildPlatform platform)
  467. const {
  468. if( platforms.empty ) return true;
  469. foreach(p; platforms)
  470. if( platform.matchesSpecification("-"~p) )
  471. return true;
  472. return false;
  473. }
  474. }
  475.  
  476. /// This keeps general information about how to build a package.
  477. /// It contains functions to create a specific BuildSetting, targeted at
  478. /// a certain BuildPlatform.
  479. struct BuildSettingsTemplate {
  480. Dependency[string] dependencies;
  481. TargetType targetType = TargetType.autodetect;
  482. string targetPath;
  483. string targetName;
  484. string workingDirectory;
  485. string[string] subConfigurations;
  486. string[][string] dflags;
  487. string[][string] lflags;
  488. string[][string] libs;
  489. string[][string] sourceFiles;
  490. string[][string] sourcePaths;
  491. string[][string] excludedSourceFiles;
  492. string[][string] copyFiles;
  493. string[][string] versions;
  494. string[][string] debugVersions;
  495. string[][string] importPaths;
  496. string[][string] stringImportPaths;
  497. string[][string] preGenerateCommands;
  498. string[][string] postGenerateCommands;
  499. string[][string] preBuildCommands;
  500. string[][string] postBuildCommands;
  501. BuildRequirements[string] buildRequirements;
  502. BuildOptions[string] buildOptions;
  503.  
  504. void parseJson(Json json)
  505. {
  506. foreach(string name, value; json)
  507. {
  508. auto idx = std.string.indexOf(name, "-");
  509. string basename, suffix;
  510. if( idx >= 0 ) basename = name[0 .. idx], suffix = name[idx .. $];
  511. else basename = name;
  512. switch(basename){
  513. default: break;
  514. case "dependencies":
  515. foreach( string pkg, verspec; value ) {
  516. enforce(pkg !in this.dependencies, "The dependency '"~pkg~"' is specified more than once." );
  517. Dependency dep;
  518. if( verspec.type == Json.Type.Object ){
  519. enforce("version" in verspec, "Package information provided for package " ~ pkg ~ " is missing a version field.");
  520. auto ver = verspec["version"].get!string;
  521. if( auto pp = "path" in verspec ) {
  522. // This enforces the "version" specifier to be a simple version,
  523. // without additional range specifiers.
  524. dep = Dependency(Version(ver));
  525. dep.path = Path(verspec.path.get!string());
  526. } else {
  527. // Using the string to be able to specifiy a range of versions.
  528. dep = Dependency(ver);
  529. }
  530. if( auto po = "optional" in verspec ) {
  531. dep.optional = verspec.optional.get!bool();
  532. }
  533. } else {
  534. // canonical "package-id": "version"
  535. dep = Dependency(verspec.get!string());
  536. }
  537. this.dependencies[pkg] = dep;
  538. }
  539. break;
  540. case "targetType":
  541. enforce(suffix.empty, "targetType does not support platform customization.");
  542. targetType = value.get!string().to!TargetType();
  543. break;
  544. case "targetPath":
  545. enforce(suffix.empty, "targetPath does not support platform customization.");
  546. this.targetPath = value.get!string;
  547. if (this.workingDirectory is null) this.workingDirectory = this.targetPath;
  548. break;
  549. case "targetName":
  550. enforce(suffix.empty, "targetName does not support platform customization.");
  551. this.targetName = value.get!string;
  552. break;
  553. case "workingDirectory":
  554. enforce(suffix.empty, "workingDirectory does not support platform customization.");
  555. this.workingDirectory = value.get!string;
  556. break;
  557. case "subConfigurations":
  558. enforce(suffix.empty, "subConfigurations does not support platform customization.");
  559. this.subConfigurations = deserializeJson!(string[string])(value);
  560. break;
  561. case "dflags": this.dflags[suffix] = deserializeJson!(string[])(value); break;
  562. case "lflags": this.lflags[suffix] = deserializeJson!(string[])(value); break;
  563. case "libs": this.libs[suffix] = deserializeJson!(string[])(value); break;
  564. case "files":
  565. case "sourceFiles": this.sourceFiles[suffix] = deserializeJson!(string[])(value); break;
  566. case "sourcePaths": this.sourcePaths[suffix] = deserializeJson!(string[])(value); break;
  567. case "sourcePath": this.sourcePaths[suffix] ~= [value.get!string()]; break; // deprecated
  568. case "excludedSourceFiles": this.excludedSourceFiles[suffix] = deserializeJson!(string[])(value); break;
  569. case "copyFiles": this.copyFiles[suffix] = deserializeJson!(string[])(value); break;
  570. case "versions": this.versions[suffix] = deserializeJson!(string[])(value); break;
  571. case "debugVersions": this.debugVersions[suffix] = deserializeJson!(string[])(value); break;
  572. case "importPaths": this.importPaths[suffix] = deserializeJson!(string[])(value); break;
  573. case "stringImportPaths": this.stringImportPaths[suffix] = deserializeJson!(string[])(value); break;
  574. case "preGenerateCommands": this.preGenerateCommands[suffix] = deserializeJson!(string[])(value); break;
  575. case "postGenerateCommands": this.postGenerateCommands[suffix] = deserializeJson!(string[])(value); break;
  576. case "preBuildCommands": this.preBuildCommands[suffix] = deserializeJson!(string[])(value); break;
  577. case "postBuildCommands": this.postBuildCommands[suffix] = deserializeJson!(string[])(value); break;
  578. case "buildRequirements":
  579. BuildRequirements reqs;
  580. foreach (req; deserializeJson!(string[])(value))
  581. reqs |= to!BuildRequirements(req);
  582. this.buildRequirements[suffix] = reqs;
  583. break;
  584. case "buildOptions":
  585. BuildOptions options;
  586. foreach (opt; deserializeJson!(string[])(value))
  587. options |= to!BuildOptions(opt);
  588. this.buildOptions[suffix] = options;
  589. break;
  590. }
  591. }
  592. }
  593.  
  594. Json toJson()
  595. const {
  596. auto ret = Json.EmptyObject;
  597. if( this.dependencies !is null ){
  598. auto deps = Json.EmptyObject;
  599. foreach( pack, d; this.dependencies ){
  600. if( d.path.empty && !d.optional ){
  601. deps[pack] = d.toString();
  602. } else {
  603. auto vjson = Json.EmptyObject;
  604. vjson["version"] = d.version_.toString();
  605. if (!d.path.empty) vjson["path"] = d.path.toString();
  606. if (d.optional) vjson["optional"] = true;
  607. deps[pack] = vjson;
  608. }
  609. }
  610. ret.dependencies = deps;
  611. }
  612. if (targetType != TargetType.autodetect) ret["targetType"] = targetType.to!string();
  613. if (!targetPath.empty) ret["targetPath"] = targetPath;
  614. if (!targetName.empty) ret["targetName"] = targetName;
  615. if (!workingDirectory.empty) ret["workingDirectory"] = workingDirectory;
  616. foreach (suffix, arr; dflags) ret["dflags"~suffix] = serializeToJson(arr);
  617. foreach (suffix, arr; lflags) ret["lflags"~suffix] = serializeToJson(arr);
  618. foreach (suffix, arr; libs) ret["libs"~suffix] = serializeToJson(arr);
  619. foreach (suffix, arr; sourceFiles) ret["sourceFiles"~suffix] = serializeToJson(arr);
  620. foreach (suffix, arr; sourcePaths) ret["sourcePaths"~suffix] = serializeToJson(arr);
  621. foreach (suffix, arr; excludedSourceFiles) ret["excludedSourceFiles"~suffix] = serializeToJson(arr);
  622. foreach (suffix, arr; copyFiles) ret["copyFiles"~suffix] = serializeToJson(arr);
  623. foreach (suffix, arr; versions) ret["versions"~suffix] = serializeToJson(arr);
  624. foreach (suffix, arr; debugVersions) ret["debugVersions"~suffix] = serializeToJson(arr);
  625. foreach (suffix, arr; importPaths) ret["importPaths"~suffix] = serializeToJson(arr);
  626. foreach (suffix, arr; stringImportPaths) ret["stringImportPaths"~suffix] = serializeToJson(arr);
  627. foreach (suffix, arr; preGenerateCommands) ret["preGenerateCommands"~suffix] = serializeToJson(arr);
  628. foreach (suffix, arr; postGenerateCommands) ret["postGenerateCommands"~suffix] = serializeToJson(arr);
  629. foreach (suffix, arr; preBuildCommands) ret["preBuildCommands"~suffix] = serializeToJson(arr);
  630. foreach (suffix, arr; postBuildCommands) ret["postBuildCommands"~suffix] = serializeToJson(arr);
  631. foreach (suffix, arr; buildRequirements) {
  632. string[] val;
  633. foreach (i; [EnumMembers!BuildRequirements])
  634. if (arr & i) val ~= to!string(i);
  635. ret["buildRequirements"~suffix] = serializeToJson(val);
  636. }
  637. foreach (suffix, arr; buildOptions) {
  638. string[] val;
  639. foreach (i; [EnumMembers!BuildOptions])
  640. if (arr & i) val ~= to!string(i);
  641. ret["buildOptions"~suffix] = serializeToJson(val);
  642. }
  643. return ret;
  644. }
  645.  
  646. /// Constructs a BuildSettings object from this template.
  647. void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, Path base_path)
  648. const {
  649. dst.targetType = this.targetType;
  650. if (!this.targetPath.empty) dst.targetPath = this.targetPath;
  651. if (!this.targetName.empty) dst.targetName = this.targetName;
  652. if (!this.workingDirectory.empty) dst.workingDirectory = this.workingDirectory;
  653.  
  654. // collect source files from all source folders
  655. foreach(suffix, paths; sourcePaths){
  656. if( !platform.matchesSpecification(suffix) )
  657. continue;
  658.  
  659. foreach(spath; paths){
  660. auto path = base_path ~ spath;
  661. if( !existsFile(path) || !isDir(path.toNativeString()) ){
  662. logWarn("Invalid source path: %s", path.toNativeString());
  663. continue;
  664. }
  665.  
  666. foreach(d; dirEntries(path.toNativeString(), "*.d", SpanMode.depth)){
  667. if (isDir(d.name)) continue;
  668. auto src = Path(d.name).relativeTo(base_path);
  669. dst.addSourceFiles(src.toNativeString());
  670. }
  671. }
  672. }
  673.  
  674. getPlatformSetting!("dflags", "addDFlags")(dst, platform);
  675. getPlatformSetting!("lflags", "addLFlags")(dst, platform);
  676. getPlatformSetting!("libs", "addLibs")(dst, platform);
  677. getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform);
  678. getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform);
  679. getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform);
  680. getPlatformSetting!("versions", "addVersions")(dst, platform);
  681. getPlatformSetting!("debugVersions", "addDebugVersions")(dst, platform);
  682. getPlatformSetting!("importPaths", "addImportPaths")(dst, platform);
  683. getPlatformSetting!("stringImportPaths", "addStringImportPaths")(dst, platform);
  684. getPlatformSetting!("preGenerateCommands", "addPreGenerateCommands")(dst, platform);
  685. getPlatformSetting!("postGenerateCommands", "addPostGenerateCommands")(dst, platform);
  686. getPlatformSetting!("preBuildCommands", "addPreBuildCommands")(dst, platform);
  687. getPlatformSetting!("postBuildCommands", "addPostBuildCommands")(dst, platform);
  688. getPlatformSetting!("buildRequirements", "addRequirements")(dst, platform);
  689. getPlatformSetting!("buildOptions", "addOptions")(dst, platform);
  690. }
  691.  
  692. void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform)
  693. const {
  694. foreach(suffix, values; __traits(getMember, this, name)){
  695. if( platform.matchesSpecification(suffix) )
  696. __traits(getMember, dst, addname)(values);
  697. }
  698. }
  699.  
  700. void warnOnSpecialCompilerFlags(string package_name, string config_name)
  701. {
  702. auto nodef = false;
  703. auto noprop = false;
  704. foreach (req; this.buildRequirements) {
  705. if (req & BuildRequirements.noDefaultFlags) nodef = true;
  706. if (req & BuildRequirements.relaxProperties) noprop = true;
  707. }
  708.  
  709. if (noprop) {
  710. 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.`);
  711. logWarn("");
  712. }
  713.  
  714. if (nodef) {
  715. logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages.");
  716. logWarn("");
  717. } else {
  718. string[] all_dflags;
  719. foreach (flags; this.dflags)
  720. all_dflags ~= flags;
  721. .warnOnSpecialCompilerFlags(all_dflags, package_name, config_name);
  722. }
  723. }
  724. }
  725.  
  726. /// Returns all package names, starting with the root package in [0].
  727. string[] getSubPackagePath(string package_name)
  728. {
  729. return package_name.split(":");
  730. }
  731.  
  732. /// Returns the name of the base package in the case of some sub package or the
  733. /// package itself, if it is already a full package.
  734. string getBasePackage(string package_name)
  735. {
  736. return package_name.getSubPackagePath()[0];
  737. }