diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d index 78dc4f1..a96871f 100644 --- a/source/dub/generators/generator.d +++ b/source/dub/generators/generator.d @@ -505,3 +505,27 @@ storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames); runCommands(commands, env); } + +private bool isRecursiveInvocation(string pack) +{ + import std.algorithm : canFind, splitter; + import std.process : environment; + + return environment + .get("DUB_PACKAGES_USED", "") + .splitter(",") + .canFind(pack); +} + +private void storeRecursiveInvokations(string[string] env, string[] packs) +{ + import std.algorithm : canFind, splitter; + import std.range : chain; + import std.process : environment; + + env["DUB_PACKAGES_USED"] = environment + .get("DUB_PACKAGES_USED", "") + .splitter(",") + .chain(packs) + .join(","); +} diff --git a/source/dub/init.d b/source/dub/init.d index a73657a..ec09a9a 100644 --- a/source/dub/init.d +++ b/source/dub/init.d @@ -83,7 +83,7 @@ private void initVibeDPackage(Path root_path, ref PackageRecipe p) { - if("vibe-d" !in p.dependencies) + if ("vibe-d" !in p.buildSettings.dependencies) p.buildSettings.dependencies["vibe-d"] = Dependency("~>0.7.26"); p.description = "A simple vibe.d server application."; p.buildSettings.versions[""] ~= "VibeDefaultMain"; diff --git a/source/dub/package_.d b/source/dub/package_.d index cd55983..2ac1787 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -1,9 +1,9 @@ /** - Stuff with dependencies. + Contains high-level functionality for working with packages. - Copyright: © 2012-2013 Matthias Dondorff, © 2012-2015 Sönke Ludwig + Copyright: © 2012-2013 Matthias Dondorff, © 2012-2016 Sönke Ludwig License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. - Authors: Matthias Dondorff + Authors: Matthias Dondorff, Sönke Ludwig, Martin Nowak, Nick Sabalausky */ module dub.package_; @@ -31,9 +31,10 @@ import std.typecons : Nullable; +/// Lists the supported package recipe formats. enum PackageFormat { - json, - sdl + json, /// JSON based, using the ".json" file extension + sdl /// SDLang based, using the ".sdl" file extension } struct FilenameAndFormat { @@ -41,23 +42,21 @@ PackageFormat format; } -// Supported package descriptions in decreasing order of preference. +/// Supported package descriptions in decreasing order of preference. static immutable FilenameAndFormat[] packageInfoFiles = [ {"dub.json", PackageFormat.json}, {"dub.sdl", PackageFormat.sdl}, {"package.json", PackageFormat.json} ]; +/// Returns a list of all recognized package recipe file names in descending order of precedence. @property string[] packageInfoFilenames() { return packageInfoFiles.map!(f => cast(string)f.filename).array; } +/// Returns the default package recile file name. @property string defaultPackageFilename() { return packageInfoFiles[0].filename; } -/** - Represents a package, including its sub packages - - Documentation of the dub.json can be found at - http://registry.vibed.org/package-format +/** Represents a package, including its sub packages. */ class Package { private { @@ -67,16 +66,19 @@ Package m_parentPackage; } - static Path findPackageFile(Path path) - { - foreach(file; packageInfoFiles) { - auto filename = path ~ file.filename; - if(existsFile(filename)) return filename; - } - return Path.init; - } + /** Constructs a `Package` using a package that is physically present on the local file system. - this(Path root, Path recipe_file = Path.init, Package parent = null, string versionOverride = "") + Params: + root = The directory in which the package resides. + recipe_file = Optional path to the package recipe file. If left + empty, the `root` directory will be searched for a recipe file. + parent = Reference to the parent package, if the new package is a + sub package. + version_override = Optional version to associate to the package + instead of the one declared in the package recipe, or the one + determined by invoking the VCS (GIT currently). + */ + this(Path root, Path recipe_file = Path.init, Package parent = null, string version_override = "") { import dub.recipe.io; @@ -91,22 +93,34 @@ auto recipe = readPackageRecipe(m_infoFile, parent ? parent.name : null); - this(recipe, root, parent, versionOverride); + this(recipe, root, parent, version_override); } - this(Json package_info, Path root = Path(), Package parent = null, string versionOverride = "") + /** Constructs a `Package` using an in-memory package recipe. + + Params: + json_recipe = The package recipe in JSON format + recipe = The package recipe in generic format + root = The directory in which the package resides (if any). + parent = Reference to the parent package, if the new package is a + sub package. + version_override = Optional version to associate to the package + instead of the one declared in the package recipe, or the one + determined by invoking the VCS (GIT currently). + */ + this(Json json_recipe, Path root = Path(), Package parent = null, string version_override = "") { import dub.recipe.json; PackageRecipe recipe; - parseJson(recipe, package_info, parent ? parent.name : null); - this(recipe, root, parent, versionOverride); + parseJson(recipe, json_recipe, parent ? parent.name : null); + this(recipe, root, parent, version_override); } - - this(PackageRecipe recipe, Path root = Path(), Package parent = null, string versionOverride = "") + /// ditto + this(PackageRecipe recipe, Path root = Path(), Package parent = null, string version_override = "") { - if (!versionOverride.empty) - recipe.version_ = versionOverride; + if (!version_override.empty) + recipe.version_ = version_override; // try to run git to determine the version of the package if no explicit version was given if (recipe.version_.length == 0 && !parent) { @@ -132,45 +146,131 @@ simpleLint(); } + /** Searches the given directory for package recipe files. + + Params: + directory = The directory to search + + Returns: + Returns the full path to the package file, if any was found. + Otherwise returns an empty path. + */ + static Path findPackageFile(Path directory) + { + foreach (file; packageInfoFiles) { + auto filename = directory ~ file.filename; + if (existsFile(filename)) return filename; + } + return Path.init; + } + + /** Returns the qualified name of the package. + + The qualified name includes any possible parent package if this package + is a sub package. + */ @property string name() const { if (m_parentPackage) return m_parentPackage.name ~ ":" ~ m_info.name; else return m_info.name; } - @property string vers() const { return m_parentPackage ? m_parentPackage.vers : m_info.version_; } - @property Version ver() const { return Version(this.vers); } - @property void ver(Version ver) { assert(m_parentPackage is null); m_info.version_ = ver.toString(); } - @property ref inout(PackageRecipe) info() inout { return m_info; } + + /** Returns the directory in which the package resides. + + Note that this can be empty for packages that are not stored in the + local file system. + */ @property Path path() const { return m_path; } + + + /** Accesses the version associated with this package. + + Note that this is a shortcut to `this.recipe.version_`. + */ + @property Version version_() const { return m_parentPackage ? m_parentPackage.version_ : Version(m_info.version_); } + /// ditto + @property void version_(Version value) { assert(m_parentPackage is null); m_info.version_ = value.toString(); } + + /** Accesses the recipe contents of this package. + */ + @property ref inout(PackageRecipe) recipe() inout { return m_info; } + + /** Returns the path to the package recipe file. + + Note that this can be empty for packages that are not stored in the + local file system. + */ + @property Path recipePath() const { return m_infoFile; } + + + deprecated("Use .version_.toString() instead.") + @property string vers() const { return m_parentPackage ? m_parentPackage.vers : m_info.version_; } + + deprecated("Use .version instead") + @property Version ver() const { return Version(this.vers); } + deprecated("Use .version instead") + @property void ver(Version ver) { assert(m_parentPackage is null); m_info.version_ = ver.toString(); } + + deprecated("Use .recipe instead") + @property ref inout(PackageRecipe) info() inout { return m_info; } + + deprecated("Use .recipePath instead") @property Path packageInfoFilename() const { return m_infoFile; } - @property const(Dependency[string]) dependencies() const { return m_info.dependencies; } + + /** Returns a list of all possible dependencies of the package. + + This list includes all dependencies of all configurations. If different + configurations have a dependency to the same package, but with differing + version specifications, only one of them will be returned. + */ + @property const(Dependency[string]) dependencies() + const { + Dependency[string] ret; + foreach (n, d; this.recipe.buildSettings.dependencies) + ret[n] = d; + foreach (ref c; this.recipe.configurations) + foreach (n, d; c.buildSettings.dependencies) + ret[n] = d; + return ret; + } + + /** Returns the base package of this package. + + The base package is the root of the sub package hierarchy (i.e. the + topmost parent). This will be `null` for packages that are not sub + packages. + */ @property inout(Package) basePackage() inout { return m_parentPackage ? m_parentPackage.basePackage : this; } + + /** Returns the parent of this package. + + The parent package is the package that contains a sub package. This will + be `null` for packages that are not sub packages. + */ @property inout(Package) parentPackage() inout { return m_parentPackage; } + + /** Returns the list of all sub packages. + + Note that this is a shortcut for `this.recipe.subPackages`. + */ @property inout(SubPackage)[] subPackages() inout { return m_info.subPackages; } + /** Returns the list of all build configuration names. + + Configuration contents can be accessed using `this.recipe.configurations`. + */ @property string[] configurations() const { auto ret = appender!(string[])(); - foreach( ref config; m_info.configurations ) + foreach (ref config; m_info.configurations) ret.put(config.name); return ret.data; } - const(Dependency[string]) getDependencies(string config) - const { - Dependency[string] ret; - foreach (k, v; m_info.buildSettings.dependencies) - ret[k] = v; - foreach (ref conf; m_info.configurations) - if (conf.name == config) { - foreach (k, v; conf.buildSettings.dependencies) - ret[k] = v; - break; - } - return ret; - } + /** Writes the current recipe contents to a recipe file. - /** Overwrites the packge description file using the default filename with the current information. + The parameter-less overload writes to `this.path`, which must not be + empty. The default recipe file name will be used in this case. */ void storeInfo() { @@ -187,6 +287,13 @@ dstFile.writePrettyJsonString(m_info.toJson()); } + /** Returns the package recipe of a non-path-based sub package. + + For sub packages that are declared within the package recipe of the + parent package, this function will return the corresponding recipe. Sub + packages declared using a path must be loaded manually (or using the + `PackageManager`). + */ Nullable!PackageRecipe getInternalSubPackage(string name) { foreach (ref p; m_info.subPackages) @@ -195,6 +302,11 @@ return Nullable!PackageRecipe(); } + /** Searches for use of compiler-specific flags that have generic + alternatives. + + This will output a warning message for each such flag to the console. + */ void warnOnSpecialCompilerFlags() { // warn about use of special flags @@ -203,6 +315,16 @@ config.buildSettings.warnOnSpecialCompilerFlags(m_info.name, config.name); } + /** Retrieves a build settings template. + + If no `config` is given, this returns the build settings declared at the + root level of the package recipe. Otherwise returns the settings + declared within the given configuration (excluding those at the root + level). + + Note that this is a shortcut to accessing `this.recipe.buildSettings` or + `this.recipe.configurations[].buildSettings`. + */ const(BuildSettingsTemplate) getBuildSettings(string config = null) const { if (config.length) { @@ -215,7 +337,13 @@ } } - /// Returns all BuildSettings for the given platform and config. + /** Returns all BuildSettings for the given platform and configuration. + + This will gather the effective build settings declared in tha package + recipe for when building on a particular platform and configuration. + Root build settings and configuration specific settings will be + merged. + */ BuildSettings getBuildSettings(in BuildPlatform platform, string config) const { BuildSettings ret; @@ -238,7 +366,12 @@ return ret; } - /// Returns the combination of all build settings for all configurations and platforms + /** Returns the combination of all build settings for all configurations + and platforms. + + This can be useful for IDEs to gather a list of all potentially used + files or settings. + */ BuildSettings getCombinedBuildSettings() const { BuildSettings ret; @@ -255,6 +388,13 @@ return ret; } + /** Adds build type specific settings to an existing set of build settings. + + This function searches the package recipe for overridden build types. If + none is found, the default build settings will be applied, if + `build_type` matches a default build type name. An exception is thrown + otherwise. + */ void addBuildTypeSettings(ref BuildSettings settings, in BuildPlatform platform, string build_type) const { if (build_type == "$DFLAGS") { @@ -286,6 +426,14 @@ } } + /** Returns the selected configuration for a certain dependency. + + If no configuration is specified in the package recipe, null will be + returned instead. + + FIXME: The `platform` parameter is currently ignored, as the + `"subConfigurations"` field doesn't support platform suffixes. + */ string getSubConfiguration(string config, in Package dependency, in BuildPlatform platform) const { bool found = false; @@ -301,7 +449,15 @@ return null; } - /// Returns the default configuration to build for the given platform + /** Returns the default configuration to build for the given platform. + + This will return the first configuration that is applicable to the given + platform, or `null` if none is applicable. By default, only library + configurations will be returned. Setting `allow_non_library` to `true` + will also return executable configurations. + + See_Also: `getPlatformConfigurations` + */ string getDefaultConfiguration(in BuildPlatform platform, bool allow_non_library = false) const { foreach (ref conf; m_info.configurations) { @@ -312,38 +468,84 @@ return null; } - /// Returns a list of configurations suitable for the given platform - string[] getPlatformConfigurations(in BuildPlatform platform, bool is_main_package = false) + /** Returns a list of configurations suitable for the given platform. + + Params: + platform = The platform against which to match configurations + allow_non_library = If set to true, executable configurations will + also be included. + + See_Also: `getDefaultConfiguration` + */ + string[] getPlatformConfigurations(in BuildPlatform platform, bool allow_non_library = false) const { auto ret = appender!(string[]); foreach(ref conf; m_info.configurations){ if (!conf.matchesPlatform(platform)) continue; - if (!is_main_package && conf.buildSettings.targetType == TargetType.executable) continue; + if (!allow_non_library && conf.buildSettings.targetType == TargetType.executable) continue; ret ~= conf.name; } if (ret.data.length == 0) ret.put(null); return ret.data; } - /// Human readable information of this package and its dependencies. - string generateInfoString() const { + + /** Human readable information of this package and its dependencies. + + This will return the name and version of this package, as well as of all + root dependencies. + */ + string generateInfoString() + const { string s; s ~= m_info.name ~ ", version '" ~ m_info.version_ ~ "'"; s ~= "\n Dependencies:"; - foreach(string p, ref const Dependency v; m_info.dependencies) + foreach(string p, ref const Dependency v; this.dependencies) s ~= "\n " ~ p ~ ", version '" ~ v.toString() ~ "'"; return s; } - bool hasDependency(string depname, string config) + /** Determines if the package has a dependency to a certain package. + + Params: + dependency_name = The name of the package to search for + config = Name of the configuration to use when searching + for dependencies + + See_Also: `getDependencies` + */ + bool hasDependency(string dependency_name, string config) const { - if (depname in m_info.buildSettings.dependencies) return true; + if (dependency_name in m_info.buildSettings.dependencies) return true; foreach (ref c; m_info.configurations) - if ((config.empty || c.name == config) && depname in c.buildSettings.dependencies) + if ((config.empty || c.name == config) && dependency_name in c.buildSettings.dependencies) return true; return false; } + /** Retrieves all dependencies for a particular configuration. + + This includes dependencies that are declared at the root level of the + package recipe, as well as those declared within the specified + configuration. If no configuration with the given name exists, only + dependencies declared at the root level will be retunred. + + See_Also: `hasDependency` + */ + const(Dependency[string]) getDependencies(string config) + const { + Dependency[string] ret; + foreach (k, v; m_info.buildSettings.dependencies) + ret[k] = v; + foreach (ref conf; m_info.configurations) + if (conf.name == config) { + foreach (k, v; conf.buildSettings.dependencies) + ret[k] = v; + break; + } + return ret; + } + /** Returns a description of the package for use in IDEs or build tools. */ PackageDescription describe(BuildPlatform platform, string config) @@ -579,24 +781,3 @@ return null; } - -bool isRecursiveInvocation(string pack) -{ - import std.process : environment; - - return environment - .get("DUB_PACKAGES_USED", "") - .splitter(",") - .canFind(pack); -} - -void storeRecursiveInvokations(string[string] env, string[] packs) -{ - import std.process : environment; - - env["DUB_PACKAGES_USED"] = environment - .get("DUB_PACKAGES_USED", "") - .splitter(",") - .chain(packs) - .join(","); -} diff --git a/source/dub/recipe/packagerecipe.d b/source/dub/recipe/packagerecipe.d index 85f48d5..65d407e 100644 --- a/source/dub/recipe/packagerecipe.d +++ b/source/dub/recipe/packagerecipe.d @@ -86,6 +86,7 @@ SubPackage[] subPackages; + deprecated("Use Package.dependencies or the dependencies of the individual BuildSettingsTemplates instead.") @property const(Dependency)[string] dependencies() const { Dependency[string] ret; diff --git a/source/dub/recipe/sdl.d b/source/dub/recipe/sdl.d index c29e374..c282ac1 100644 --- a/source/dub/recipe/sdl.d +++ b/source/dub/recipe/sdl.d @@ -423,8 +423,8 @@ assert(rec.subPackages[0].recipe.name == "subpackage1"); assert(rec.subPackages[1].path == ""); assert(rec.subPackages[1].recipe.name == "subpackage2"); - assert(rec.subPackages[1].recipe.dependencies.length == 1); - assert("projectname:subpackage1" in rec.subPackages[1].recipe.dependencies); + assert(rec.subPackages[1].recipe.buildSettings.dependencies.length == 1); + assert("projectname:subpackage1" in rec.subPackages[1].recipe.buildSettings.dependencies); assert(rec.subPackages[2].path == "pathsp3"); assert(rec.configurations.length == 2); assert(rec.configurations[0].name == "config1");