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