Newer
Older
dub_jkp / source / dub / package_.d
  1. /**
  2. Contains high-level functionality for working with packages.
  3.  
  4. Copyright: © 2012-2013 Matthias Dondorff, © 2012-2016 Sönke Ludwig
  5. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
  6. Authors: Matthias Dondorff, Sönke Ludwig, Martin Nowak, Nick Sabalausky
  7. */
  8. module dub.package_;
  9.  
  10. public import dub.recipe.packagerecipe;
  11.  
  12. import dub.compilers.compiler;
  13. import dub.dependency;
  14. import dub.description;
  15. import dub.recipe.json;
  16. import dub.recipe.sdl;
  17.  
  18. import dub.internal.utils;
  19. import dub.internal.vibecompat.core.log;
  20. import dub.internal.vibecompat.core.file;
  21. import dub.internal.vibecompat.data.json;
  22. import dub.internal.vibecompat.inet.url;
  23.  
  24. import std.algorithm;
  25. import std.array;
  26. import std.conv;
  27. import std.exception;
  28. import std.file;
  29. import std.range;
  30. import std.string;
  31. import std.typecons : Nullable;
  32.  
  33.  
  34. /// Lists the supported package recipe formats.
  35. enum PackageFormat {
  36. json, /// JSON based, using the ".json" file extension
  37. sdl /// SDLang based, using the ".sdl" file extension
  38. }
  39.  
  40. struct FilenameAndFormat {
  41. string filename;
  42. PackageFormat format;
  43. }
  44.  
  45. /// Supported package descriptions in decreasing order of preference.
  46. static immutable FilenameAndFormat[] packageInfoFiles = [
  47. {"dub.json", PackageFormat.json},
  48. {"dub.sdl", PackageFormat.sdl},
  49. {"package.json", PackageFormat.json}
  50. ];
  51.  
  52. /// Returns a list of all recognized package recipe file names in descending order of precedence.
  53. @property string[] packageInfoFilenames() { return packageInfoFiles.map!(f => cast(string)f.filename).array; }
  54.  
  55. /// Returns the default package recile file name.
  56. @property string defaultPackageFilename() { return packageInfoFiles[0].filename; }
  57.  
  58.  
  59. /** Represents a package, including its sub packages.
  60. */
  61. class Package {
  62. private {
  63. NativePath m_path;
  64. NativePath m_infoFile;
  65. PackageRecipe m_info;
  66. PackageRecipe m_rawRecipe;
  67. Package m_parentPackage;
  68. }
  69.  
  70. /** Constructs a `Package` using an in-memory package recipe.
  71.  
  72. Params:
  73. json_recipe = The package recipe in JSON format
  74. recipe = The package recipe in generic format
  75. root = The directory in which the package resides (if any).
  76. parent = Reference to the parent package, if the new package is a
  77. sub package.
  78. scm_path = The directory in which the VCS (Git) stores its state.
  79. Different than root/.git for submodules.
  80. version_override = Optional version to associate to the package
  81. instead of the one declared in the package recipe, or the one
  82. determined by invoking the VCS (Git currently).
  83. */
  84. this(Json json_recipe, NativePath root = NativePath(), Package parent = null,
  85. NativePath scm_path = NativePath(), string version_override = "")
  86. {
  87. import dub.recipe.json;
  88.  
  89. PackageRecipe recipe;
  90. parseJson(recipe, json_recipe, parent ? parent.name : null);
  91. this(recipe, root, parent, version_override, scm_path);
  92. }
  93. /// ditto
  94. this(PackageRecipe recipe, NativePath root = NativePath(), Package parent = null,
  95. string version_override = "", NativePath scm_path = NativePath())
  96. {
  97. // save the original recipe
  98. m_rawRecipe = recipe.clone;
  99.  
  100. if (!version_override.empty)
  101. recipe.version_ = version_override;
  102.  
  103. // try to run git to determine the version of the package if no explicit version was given
  104. if (recipe.version_.length == 0 && !parent) {
  105. try recipe.version_ = determineVersionFromSCM(root, scm_path);
  106. catch (Exception e) logDebug("Failed to determine version by SCM: %s", e.msg);
  107.  
  108. if (recipe.version_.length == 0) {
  109. logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", recipe.name, root.toNativeString());
  110. // TODO: Assume unknown version here?
  111. // recipe.version_ = Version.unknown.toString();
  112. recipe.version_ = Version.masterBranch.toString();
  113. } else logDiagnostic("Determined package version using Git: %s %s", recipe.name, recipe.version_);
  114. }
  115.  
  116. m_parentPackage = parent;
  117. m_path = root;
  118. m_path.endsWithSlash = true;
  119.  
  120. // use the given recipe as the basis
  121. m_info = recipe;
  122.  
  123. checkDubRequirements();
  124. fillWithDefaults();
  125. }
  126.  
  127. /** Searches the given directory for package recipe files.
  128.  
  129. Params:
  130. directory = The directory to search
  131.  
  132. Returns:
  133. Returns the full path to the package file, if any was found.
  134. Otherwise returns an empty path.
  135. */
  136. static NativePath findPackageFile(NativePath directory)
  137. {
  138. foreach (file; packageInfoFiles) {
  139. auto filename = directory ~ file.filename;
  140. if (existsFile(filename)) return filename;
  141. }
  142. return NativePath.init;
  143. }
  144.  
  145. /** Constructs a `Package` using a package that is physically present on the local file system.
  146.  
  147. Params:
  148. root = The directory in which the package resides.
  149. recipe_file = Optional path to the package recipe file. If left
  150. empty, the `root` directory will be searched for a recipe file.
  151. parent = Reference to the parent package, if the new package is a
  152. sub package.
  153. scm_path = The directory in which the VCS (Git) stores its state.
  154. Different than root/.git for submodules!
  155. version_override = Optional version to associate to the package
  156. instead of the one declared in the package recipe, or the one
  157. determined by invoking the VCS (Git currently).
  158. */
  159. static Package load(NativePath root,
  160. NativePath recipe_file = NativePath.init, Package parent = null,
  161. string version_override = "", NativePath scm_path = NativePath.init)
  162. {
  163. import dub.recipe.io;
  164.  
  165. if (recipe_file.empty) recipe_file = findPackageFile(root);
  166.  
  167. enforce(!recipe_file.empty,
  168. "No package file found in %s, expected one of %s"
  169. .format(root.toNativeString(),
  170. packageInfoFiles.map!(f => cast(string)f.filename).join("/")));
  171.  
  172. auto recipe = readPackageRecipe(recipe_file, parent ? parent.name : null);
  173.  
  174. auto ret = new Package(recipe, root, parent, version_override, scm_path);
  175. ret.m_infoFile = recipe_file;
  176. return ret;
  177. }
  178.  
  179. /** Returns the qualified name of the package.
  180.  
  181. The qualified name includes any possible parent package if this package
  182. is a sub package.
  183. */
  184. @property string name()
  185. const {
  186. if (m_parentPackage) return m_parentPackage.name ~ ":" ~ m_info.name;
  187. else return m_info.name;
  188. }
  189.  
  190. /** Returns the directory in which the package resides.
  191.  
  192. Note that this can be empty for packages that are not stored in the
  193. local file system.
  194. */
  195. @property NativePath path() const { return m_path; }
  196.  
  197.  
  198. /** Accesses the version associated with this package.
  199.  
  200. Note that this is a shortcut to `this.recipe.version_`.
  201. */
  202. @property Version version_() const { return m_parentPackage ? m_parentPackage.version_ : Version(m_info.version_); }
  203. /// ditto
  204. @property void version_(Version value) { assert(m_parentPackage is null); m_info.version_ = value.toString(); }
  205.  
  206. /** Accesses the recipe contents of this package.
  207.  
  208. The recipe contains any default values and configurations added by DUB.
  209. To access the raw user recipe, use the `rawRecipe` property.
  210.  
  211. See_Also: `rawRecipe`
  212. */
  213. @property ref inout(PackageRecipe) recipe() inout { return m_info; }
  214.  
  215. /** Accesses the original package recipe.
  216.  
  217. The returned recipe matches exactly the contents of the original package
  218. recipe. For the effective package recipe, augmented with DUB generated
  219. default settings and configurations, use the `recipe` property.
  220.  
  221. See_Also: `recipe`
  222. */
  223. @property ref const(PackageRecipe) rawRecipe() const { return m_rawRecipe; }
  224.  
  225. /** Returns the path to the package recipe file.
  226.  
  227. Note that this can be empty for packages that are not stored in the
  228. local file system.
  229. */
  230. @property NativePath recipePath() const { return m_infoFile; }
  231.  
  232.  
  233. /** Returns the base package of this package.
  234.  
  235. The base package is the root of the sub package hierarchy (i.e. the
  236. topmost parent). This will be `null` for packages that are not sub
  237. packages.
  238. */
  239. @property inout(Package) basePackage() inout { return m_parentPackage ? m_parentPackage.basePackage : this; }
  240.  
  241. /** Returns the parent of this package.
  242.  
  243. The parent package is the package that contains a sub package. This will
  244. be `null` for packages that are not sub packages.
  245. */
  246. @property inout(Package) parentPackage() inout { return m_parentPackage; }
  247.  
  248. /** Returns the list of all sub packages.
  249.  
  250. Note that this is a shortcut for `this.recipe.subPackages`.
  251. */
  252. @property inout(SubPackage)[] subPackages() inout { return m_info.subPackages; }
  253.  
  254. /** Returns the list of all build configuration names.
  255.  
  256. Configuration contents can be accessed using `this.recipe.configurations`.
  257. */
  258. @property string[] configurations()
  259. const {
  260. auto ret = appender!(string[])();
  261. foreach (ref config; m_info.configurations)
  262. ret.put(config.name);
  263. return ret.data;
  264. }
  265.  
  266. /** Writes the current recipe contents to a recipe file.
  267.  
  268. The parameter-less overload writes to `this.path`, which must not be
  269. empty. The default recipe file name will be used in this case.
  270. */
  271. void storeInfo()
  272. {
  273. storeInfo(m_path);
  274. m_infoFile = m_path ~ defaultPackageFilename;
  275. }
  276. /// ditto
  277. void storeInfo(NativePath path)
  278. const {
  279. enforce(!version_.isUnknown, "Trying to store a package with an 'unknown' version, this is not supported.");
  280. auto filename = path ~ defaultPackageFilename;
  281. auto dstFile = openFile(filename.toNativeString(), FileMode.createTrunc);
  282. scope(exit) dstFile.close();
  283. dstFile.writePrettyJsonString(m_info.toJson());
  284. }
  285.  
  286. /// Get the metadata cache for this package
  287. @property Json metadataCache()
  288. {
  289. enum silent_fail = true;
  290. return jsonFromFile(m_path ~ ".dub/metadata_cache.json", silent_fail);
  291. }
  292.  
  293. /// Write metadata cache for this package
  294. @property void metadataCache(Json json)
  295. {
  296. enum create_if_missing = true;
  297. if (isWritableDir(m_path ~ ".dub", create_if_missing))
  298. writeJsonFile(m_path ~ ".dub/metadata_cache.json", json);
  299. // TODO: store elsewhere
  300. }
  301.  
  302. /** Returns the package recipe of a non-path-based sub package.
  303.  
  304. For sub packages that are declared within the package recipe of the
  305. parent package, this function will return the corresponding recipe. Sub
  306. packages declared using a path must be loaded manually (or using the
  307. `PackageManager`).
  308. */
  309. Nullable!PackageRecipe getInternalSubPackage(string name)
  310. {
  311. foreach (ref p; m_info.subPackages)
  312. if (p.path.empty && p.recipe.name == name)
  313. return Nullable!PackageRecipe(p.recipe);
  314. return Nullable!PackageRecipe();
  315. }
  316.  
  317. /** Searches for use of compiler-specific flags that have generic
  318. alternatives.
  319.  
  320. This will output a warning message for each such flag to the console.
  321. */
  322. void warnOnSpecialCompilerFlags()
  323. {
  324. // warn about use of special flags
  325. m_info.buildSettings.warnOnSpecialCompilerFlags(m_info.name, null);
  326. foreach (ref config; m_info.configurations)
  327. config.buildSettings.warnOnSpecialCompilerFlags(m_info.name, config.name);
  328. }
  329.  
  330. /** Retrieves a build settings template.
  331.  
  332. If no `config` is given, this returns the build settings declared at the
  333. root level of the package recipe. Otherwise returns the settings
  334. declared within the given configuration (excluding those at the root
  335. level).
  336.  
  337. Note that this is a shortcut to accessing `this.recipe.buildSettings` or
  338. `this.recipe.configurations[].buildSettings`.
  339. */
  340. const(BuildSettingsTemplate) getBuildSettings(string config = null)
  341. const {
  342. if (config.length) {
  343. foreach (ref conf; m_info.configurations)
  344. if (conf.name == config)
  345. return conf.buildSettings;
  346. assert(false, "Unknown configuration: "~config);
  347. } else {
  348. return m_info.buildSettings;
  349. }
  350. }
  351.  
  352. /** Returns all BuildSettings for the given platform and configuration.
  353.  
  354. This will gather the effective build settings declared in tha package
  355. recipe for when building on a particular platform and configuration.
  356. Root build settings and configuration specific settings will be
  357. merged.
  358. */
  359. BuildSettings getBuildSettings(in BuildPlatform platform, string config)
  360. const {
  361. BuildSettings ret;
  362. m_info.buildSettings.getPlatformSettings(ret, platform, this.path);
  363. bool found = false;
  364. foreach(ref conf; m_info.configurations){
  365. if( conf.name != config ) continue;
  366. conf.buildSettings.getPlatformSettings(ret, platform, this.path);
  367. found = true;
  368. break;
  369. }
  370. assert(found || config is null, "Unknown configuration for "~m_info.name~": "~config);
  371.  
  372. // construct default target name based on package name
  373. if( ret.targetName.empty ) ret.targetName = this.name.replace(":", "_");
  374.  
  375. // special support for DMD style flags
  376. getCompiler("dmd").extractBuildOptions(ret);
  377.  
  378. return ret;
  379. }
  380.  
  381. /** Returns the combination of all build settings for all configurations
  382. and platforms.
  383.  
  384. This can be useful for IDEs to gather a list of all potentially used
  385. files or settings.
  386. */
  387. BuildSettings getCombinedBuildSettings()
  388. const {
  389. BuildSettings ret;
  390. m_info.buildSettings.getPlatformSettings(ret, BuildPlatform.any, this.path);
  391. foreach(ref conf; m_info.configurations)
  392. conf.buildSettings.getPlatformSettings(ret, BuildPlatform.any, this.path);
  393.  
  394. // construct default target name based on package name
  395. if (ret.targetName.empty) ret.targetName = this.name.replace(":", "_");
  396.  
  397. // special support for DMD style flags
  398. getCompiler("dmd").extractBuildOptions(ret);
  399.  
  400. return ret;
  401. }
  402.  
  403. /** Adds build type specific settings to an existing set of build settings.
  404.  
  405. This function searches the package recipe for overridden build types. If
  406. none is found, the default build settings will be applied, if
  407. `build_type` matches a default build type name. An exception is thrown
  408. otherwise.
  409. */
  410. void addBuildTypeSettings(ref BuildSettings settings, in BuildPlatform platform, string build_type)
  411. const {
  412. if (build_type == "$DFLAGS") {
  413. import std.process;
  414. string dflags = environment.get("DFLAGS");
  415. settings.addDFlags(dflags.split());
  416. return;
  417. }
  418.  
  419. if (auto pbt = build_type in m_info.buildTypes) {
  420. logDiagnostic("Using custom build type '%s'.", build_type);
  421. pbt.getPlatformSettings(settings, platform, this.path);
  422. } else {
  423. with(BuildOption) switch (build_type) {
  424. default: throw new Exception(format("Unknown build type for %s: '%s'", this.name, build_type));
  425. case "plain": break;
  426. case "debug": settings.addOptions(debugMode, debugInfo); break;
  427. case "release": settings.addOptions(releaseMode, optimize, inline); break;
  428. case "release-debug": settings.addOptions(releaseMode, optimize, inline, debugInfo); break;
  429. case "release-nobounds": settings.addOptions(releaseMode, optimize, inline, noBoundsCheck); break;
  430. case "unittest": settings.addOptions(unittests, debugMode, debugInfo); break;
  431. case "docs": settings.addOptions(syntaxOnly, _docs); break;
  432. case "ddox": settings.addOptions(syntaxOnly, _ddox); break;
  433. case "profile": settings.addOptions(profile, optimize, inline, debugInfo); break;
  434. case "profile-gc": settings.addOptions(profileGC, debugInfo); break;
  435. case "cov": settings.addOptions(coverage, debugInfo); break;
  436. case "unittest-cov": settings.addOptions(unittests, coverage, debugMode, debugInfo); break;
  437. case "syntax": settings.addOptions(syntaxOnly); break;
  438. }
  439. }
  440. }
  441.  
  442. /** Returns the selected configuration for a certain dependency.
  443.  
  444. If no configuration is specified in the package recipe, null will be
  445. returned instead.
  446.  
  447. FIXME: The `platform` parameter is currently ignored, as the
  448. `"subConfigurations"` field doesn't support platform suffixes.
  449. */
  450. string getSubConfiguration(string config, in Package dependency, in BuildPlatform platform)
  451. const {
  452. bool found = false;
  453. foreach(ref c; m_info.configurations){
  454. if( c.name == config ){
  455. if( auto pv = dependency.name in c.buildSettings.subConfigurations ) return *pv;
  456. found = true;
  457. break;
  458. }
  459. }
  460. assert(found || config is null, "Invalid configuration \""~config~"\" for "~this.name);
  461. if( auto pv = dependency.name in m_info.buildSettings.subConfigurations ) return *pv;
  462. return null;
  463. }
  464.  
  465. /** Returns the default configuration to build for the given platform.
  466.  
  467. This will return the first configuration that is applicable to the given
  468. platform, or `null` if none is applicable. By default, only library
  469. configurations will be returned. Setting `allow_non_library` to `true`
  470. will also return executable configurations.
  471.  
  472. See_Also: `getPlatformConfigurations`
  473. */
  474. string getDefaultConfiguration(in BuildPlatform platform, bool allow_non_library = false)
  475. const {
  476. foreach (ref conf; m_info.configurations) {
  477. if (!conf.matchesPlatform(platform)) continue;
  478. if (!allow_non_library && conf.buildSettings.targetType == TargetType.executable) continue;
  479. return conf.name;
  480. }
  481. return null;
  482. }
  483.  
  484. /** Returns a list of configurations suitable for the given platform.
  485.  
  486. Params:
  487. platform = The platform against which to match configurations
  488. allow_non_library = If set to true, executable configurations will
  489. also be included.
  490.  
  491. See_Also: `getDefaultConfiguration`
  492. */
  493. string[] getPlatformConfigurations(in BuildPlatform platform, bool allow_non_library = false)
  494. const {
  495. auto ret = appender!(string[]);
  496. foreach(ref conf; m_info.configurations){
  497. if (!conf.matchesPlatform(platform)) continue;
  498. if (!allow_non_library && conf.buildSettings.targetType == TargetType.executable) continue;
  499. ret ~= conf.name;
  500. }
  501. if (ret.data.length == 0) ret.put(null);
  502. return ret.data;
  503. }
  504.  
  505. /** Determines if the package has a dependency to a certain package.
  506.  
  507. Params:
  508. dependency_name = The name of the package to search for
  509. config = Name of the configuration to use when searching
  510. for dependencies
  511.  
  512. See_Also: `getDependencies`
  513. */
  514. bool hasDependency(string dependency_name, string config)
  515. const {
  516. if (dependency_name in m_info.buildSettings.dependencies) return true;
  517. foreach (ref c; m_info.configurations)
  518. if ((config.empty || c.name == config) && dependency_name in c.buildSettings.dependencies)
  519. return true;
  520. return false;
  521. }
  522.  
  523. /** Retrieves all dependencies for a particular configuration.
  524.  
  525. This includes dependencies that are declared at the root level of the
  526. package recipe, as well as those declared within the specified
  527. configuration. If no configuration with the given name exists, only
  528. dependencies declared at the root level will be returned.
  529.  
  530. See_Also: `hasDependency`
  531. */
  532. const(Dependency[string]) getDependencies(string config)
  533. const {
  534. Dependency[string] ret;
  535. foreach (k, v; m_info.buildSettings.dependencies)
  536. ret[k] = v;
  537. foreach (ref conf; m_info.configurations)
  538. if (conf.name == config) {
  539. foreach (k, v; conf.buildSettings.dependencies)
  540. ret[k] = v;
  541. break;
  542. }
  543. return ret;
  544. }
  545.  
  546. /** Returns a list of all possible dependencies of the package.
  547.  
  548. This list includes all dependencies of all configurations. The same
  549. package may occur multiple times with possibly different `Dependency`
  550. values.
  551. */
  552. PackageDependency[] getAllDependencies()
  553. const {
  554. auto ret = appender!(PackageDependency[]);
  555. getAllDependenciesRange().copy(ret);
  556. return ret.data;
  557. }
  558.  
  559. // Left as package until the final API for this has been found
  560. package auto getAllDependenciesRange()
  561. const {
  562. return
  563. chain(
  564. only(this.recipe.buildSettings.dependencies.byKeyValue),
  565. this.recipe.configurations.map!(c => c.buildSettings.dependencies.byKeyValue)
  566. )
  567. .joiner()
  568. .map!(d => PackageDependency(d.key, d.value));
  569. }
  570.  
  571.  
  572. /** Returns a description of the package for use in IDEs or build tools.
  573. */
  574. PackageDescription describe(BuildPlatform platform, string config)
  575. const {
  576. return describe(platform, getCompiler(platform.compilerBinary), config);
  577. }
  578. /// ditto
  579. PackageDescription describe(BuildPlatform platform, Compiler compiler, string config)
  580. const {
  581. PackageDescription ret;
  582. ret.configuration = config;
  583. ret.path = m_path.toNativeString();
  584. ret.name = this.name;
  585. ret.version_ = this.version_;
  586. ret.description = m_info.description;
  587. ret.homepage = m_info.homepage;
  588. ret.authors = m_info.authors.dup;
  589. ret.copyright = m_info.copyright;
  590. ret.license = m_info.license;
  591. ret.dependencies = getDependencies(config).keys;
  592.  
  593. // save build settings
  594. BuildSettings bs = getBuildSettings(platform, config);
  595. BuildSettings allbs = getCombinedBuildSettings();
  596.  
  597. ret.targetType = bs.targetType;
  598. ret.targetPath = bs.targetPath;
  599. ret.targetName = bs.targetName;
  600. if (ret.targetType != TargetType.none && compiler)
  601. ret.targetFileName = compiler.getTargetFileName(bs, platform);
  602. ret.workingDirectory = bs.workingDirectory;
  603. ret.mainSourceFile = bs.mainSourceFile;
  604. ret.dflags = bs.dflags;
  605. ret.lflags = bs.lflags;
  606. ret.libs = bs.libs;
  607. ret.copyFiles = bs.copyFiles;
  608. ret.versions = bs.versions;
  609. ret.debugVersions = bs.debugVersions;
  610. ret.importPaths = bs.importPaths;
  611. ret.stringImportPaths = bs.stringImportPaths;
  612. ret.preGenerateCommands = bs.preGenerateCommands;
  613. ret.postGenerateCommands = bs.postGenerateCommands;
  614. ret.preBuildCommands = bs.preBuildCommands;
  615. ret.postBuildCommands = bs.postBuildCommands;
  616.  
  617. // prettify build requirements output
  618. for (int i = 1; i <= BuildRequirement.max; i <<= 1)
  619. if (bs.requirements & cast(BuildRequirement)i)
  620. ret.buildRequirements ~= cast(BuildRequirement)i;
  621.  
  622. // prettify options output
  623. for (int i = 1; i <= BuildOption.max; i <<= 1)
  624. if (bs.options & cast(BuildOption)i)
  625. ret.options ~= cast(BuildOption)i;
  626.  
  627. // collect all possible source files and determine their types
  628. SourceFileRole[string] sourceFileTypes;
  629. foreach (f; allbs.stringImportFiles) sourceFileTypes[f] = SourceFileRole.unusedStringImport;
  630. foreach (f; allbs.importFiles) sourceFileTypes[f] = SourceFileRole.unusedImport;
  631. foreach (f; allbs.sourceFiles) sourceFileTypes[f] = SourceFileRole.unusedSource;
  632. foreach (f; bs.stringImportFiles) sourceFileTypes[f] = SourceFileRole.stringImport;
  633. foreach (f; bs.importFiles) sourceFileTypes[f] = SourceFileRole.import_;
  634. foreach (f; bs.sourceFiles) sourceFileTypes[f] = SourceFileRole.source;
  635. foreach (f; sourceFileTypes.byKey.array.sort()) {
  636. SourceFileDescription sf;
  637. sf.path = f;
  638. sf.role = sourceFileTypes[f];
  639. ret.files ~= sf;
  640. }
  641.  
  642. return ret;
  643. }
  644.  
  645. private void checkDubRequirements()
  646. {
  647. import dub.dependency : Dependency;
  648. import dub.semver : isValidVersion;
  649. import dub.version_ : dubVersion;
  650. import std.exception : enforce;
  651.  
  652. const dep = m_info.toolchainRequirements.dub;
  653.  
  654. static assert(dubVersion.length);
  655. static if (dubVersion[0] == 'v') {
  656. enum dv = dubVersion[1 .. $];
  657. }
  658. else {
  659. enum dv = dubVersion;
  660. }
  661. static assert(isValidVersion(dv));
  662.  
  663. enforce(dep.matches(dv),
  664. "dub-" ~ dv ~ " does not comply with toolchainRequirements.dub "
  665. ~ "specification: " ~ m_info.toolchainRequirements.dub.toString()
  666. ~ "\nPlease consider upgrading your DUB installation");
  667. }
  668.  
  669. private void fillWithDefaults()
  670. {
  671. auto bs = &m_info.buildSettings;
  672.  
  673. // check for default string import folders
  674. if ("" !in bs.stringImportPaths) {
  675. foreach(defvf; ["views"]){
  676. if( existsFile(m_path ~ defvf) )
  677. bs.stringImportPaths[""] ~= defvf;
  678. }
  679. }
  680.  
  681. // check for default source folders
  682. immutable hasSP = ("" in bs.sourcePaths) !is null;
  683. immutable hasIP = ("" in bs.importPaths) !is null;
  684. if (!hasSP || !hasIP) {
  685. foreach (defsf; ["source/", "src/"]) {
  686. if (existsFile(m_path ~ defsf)) {
  687. if (!hasSP) bs.sourcePaths[""] ~= defsf;
  688. if (!hasIP) bs.importPaths[""] ~= defsf;
  689. }
  690. }
  691. }
  692.  
  693. // check for default app_main
  694. string app_main_file;
  695. auto pkg_name = m_info.name.length ? m_info.name : "unknown";
  696. foreach(sf; bs.sourcePaths.get("", null)){
  697. auto p = m_path ~ sf;
  698. if( !existsFile(p) ) continue;
  699. foreach(fil; ["app.d", "main.d", pkg_name ~ "/main.d", pkg_name ~ "/" ~ "app.d"]){
  700. if( existsFile(p ~ fil) ) {
  701. app_main_file = (NativePath(sf) ~ fil).toNativeString();
  702. break;
  703. }
  704. }
  705. }
  706.  
  707. // generate default configurations if none are defined
  708. if (m_info.configurations.length == 0) {
  709. if (bs.targetType == TargetType.executable) {
  710. BuildSettingsTemplate app_settings;
  711. app_settings.targetType = TargetType.executable;
  712. if (bs.mainSourceFile.empty) app_settings.mainSourceFile = app_main_file;
  713. m_info.configurations ~= ConfigurationInfo("application", app_settings);
  714. } else if (bs.targetType != TargetType.none) {
  715. BuildSettingsTemplate lib_settings;
  716. lib_settings.targetType = bs.targetType == TargetType.autodetect ? TargetType.library : bs.targetType;
  717.  
  718. if (bs.targetType == TargetType.autodetect) {
  719. if (app_main_file.length) {
  720. lib_settings.excludedSourceFiles[""] ~= app_main_file;
  721.  
  722. BuildSettingsTemplate app_settings;
  723. app_settings.targetType = TargetType.executable;
  724. app_settings.mainSourceFile = app_main_file;
  725. m_info.configurations ~= ConfigurationInfo("application", app_settings);
  726. }
  727. }
  728.  
  729. m_info.configurations ~= ConfigurationInfo("library", lib_settings);
  730. }
  731. }
  732. }
  733.  
  734. package void simpleLint()
  735. const {
  736. if (m_parentPackage) {
  737. if (m_parentPackage.path != path) {
  738. if (this.recipe.license.length && this.recipe.license != m_parentPackage.recipe.license)
  739. logWarn("Warning: License in subpackage %s is different than it's parent package, this is discouraged.", name);
  740. }
  741. }
  742. if (name.empty) logWarn("Warning: The package in %s has no name.", path);
  743. bool[string] cnames;
  744. foreach (ref c; this.recipe.configurations) {
  745. if (c.name in cnames)
  746. logWarn("Warning: Multiple configurations with the name \"%s\" are defined in package \"%s\". This will most likely cause configuration resolution issues.",
  747. c.name, this.name);
  748. cnames[c.name] = true;
  749. }
  750. }
  751. }
  752.  
  753. private string determineVersionFromSCM(NativePath path, NativePath scm_path)
  754. {
  755. if (scm_path.empty) {
  756. scm_path = path ~ ".git";
  757. }
  758. // On Windows, which is slow at running external processes,
  759. // cache the version numbers that are determined using
  760. // Git to speed up the initialization phase.
  761. version (Windows) {
  762. import std.file : exists, readText;
  763.  
  764. // quickly determine head commit without invoking Git
  765. string head_commit;
  766. auto hpath = (scm_path ~ "HEAD").toNativeString();
  767. if (exists(hpath)) {
  768. auto head_ref = readText(hpath).strip();
  769. if (head_ref.startsWith("ref: ")) {
  770. auto rpath = (scm_path ~ head_ref[5 .. $]).toNativeString();
  771. if (exists(rpath))
  772. head_commit = readText(rpath).strip();
  773. }
  774. }
  775.  
  776. // return the last determined version for that commit
  777. // not that this is not always correct, most notably when
  778. // a tag gets added/removed/changed and changes the outcome
  779. // of the full version detection computation
  780. auto vcachepath = path ~ ".dub/version.json";
  781. if (existsFile(vcachepath)) {
  782. auto ver = jsonFromFile(vcachepath);
  783. if (head_commit == ver["commit"].opt!string)
  784. return ver["version"].get!string;
  785. }
  786. }
  787.  
  788. // if no cache file or the HEAD commit changed, perform full detection
  789. auto ret = determineVersionWithGit(path, scm_path);
  790.  
  791. version (Windows) {
  792. // update version cache file
  793. if (head_commit.length) {
  794. if (!existsFile(path ~".dub")) createDirectory(path ~ ".dub");
  795. atomicWriteJsonFile(vcachepath, Json(["commit": Json(head_commit), "version": Json(ret)]));
  796. }
  797. }
  798.  
  799. return ret;
  800. }
  801.  
  802. // determines the version of a package that is stored in a Git working copy
  803. // by invoking the "git" executable
  804. private string determineVersionWithGit(NativePath path, NativePath git_dir)
  805. {
  806. import std.process;
  807. import dub.semver;
  808.  
  809. if (!existsFile(git_dir) || !isDir(git_dir.toNativeString)) return null;
  810. auto git_dir_param = "--git-dir=" ~ git_dir.toNativeString();
  811.  
  812. static string exec(scope string[] params...) {
  813. auto ret = executeShell(escapeShellCommand(params));
  814. if (ret.status == 0) return ret.output.strip;
  815. logDebug("'%s' failed with exit code %s: %s", params.join(" "), ret.status, ret.output.strip);
  816. return null;
  817. }
  818.  
  819. auto tag = exec("git", git_dir_param, "describe", "--long", "--tags");
  820. if (tag !is null) {
  821. auto parts = tag.split("-");
  822. auto commit = parts[$-1];
  823. auto num = parts[$-2].to!int;
  824. tag = parts[0 .. $-2].join("-");
  825. if (tag.startsWith("v") && isValidVersion(tag[1 .. $])) {
  826. if (num == 0) return tag[1 .. $];
  827. else if (tag.canFind("+")) return format("%s.commit.%s.%s", tag[1 .. $], num, commit);
  828. else return format("%s+commit.%s.%s", tag[1 .. $], num, commit);
  829. }
  830. }
  831.  
  832. auto branch = exec("git", git_dir_param, "rev-parse", "--abbrev-ref", "HEAD");
  833. if (branch !is null) {
  834. if (branch != "HEAD") return "~" ~ branch;
  835. }
  836.  
  837. return null;
  838. }