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