diff --git a/source/dub/dub.d b/source/dub/dub.d index 38afbad..1f408d1 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -808,7 +808,7 @@ // detect dependencies to the root package (or sub packages thereof) if (dbasename == basepack.name) { auto absdeppath = dspec.mapToPath(pack.path).path; - auto subpack = basepack.getSubPackage(getSubPackageName(dname), true); + auto subpack = m_dub.m_packageManager.getSubPackage(basepack, getSubPackageName(dname), true); if (subpack) { auto desireddeppath = dname == dbasename ? basepack.path : subpack.path; enforce(dspec.path.empty || absdeppath == desireddeppath, @@ -842,9 +842,10 @@ if (basename != name) { auto subname = getSubPackageName(name); auto basepack = getPackage(basename, dep); - if (auto sp = basepack.getSubPackage(subname, true)) { + if (auto sp = m_dub.m_packageManager.getSubPackage(basepack, subname, true)) { return sp; - } else if (!basepack.exportedPackageCount) { + } else if (!basepack.subPackages.canFind!(p => p.path.length)) { + // note: external sub packages are handled further below logDiagnostic("Sub package %s doesn't exist in %s %s.", name, basename, dep.version_); return null; } diff --git a/source/dub/package_.d b/source/dub/package_.d index ab3d668..7c3237e 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -30,7 +30,7 @@ struct FilenameAndFormat { string filename; - PackageFormat format; + PackageFormat format; } struct PathAndFormat { @@ -66,7 +66,7 @@ PackageInfo m_info; Package m_parentPackage; } - + static PathAndFormat findPackageFile(Path path) { foreach(file; packageInfoFiles) { @@ -80,7 +80,7 @@ { RawPackage raw_package; m_infoFile = infoFile; - + try { if(m_infoFile.empty) { m_infoFile = findPackageFile(root); @@ -88,17 +88,48 @@ } raw_package = rawPackageFromFile(m_infoFile); } catch (Exception ex) throw ex;//throw new Exception(format("Failed to load package %s: %s", m_infoFile.toNativeString(), ex.msg)); - + enforce(raw_package !is null, format("Missing package description for package at %s", root.toNativeString())); this(raw_package, root, parent, versionOverride); } - + this(Json package_info, Path root = Path(), Package parent = null, string versionOverride = "") { this(new JsonPackage(package_info), root, parent, versionOverride); } + this(const RawPackage raw_package, Path root = Path(), Package parent = null, string versionOverride = "") { + PackageInfo recipe; + + // parse the Package description + if(raw_package !is null) + { + scope(failure) logError("Failed to parse package description in %s", root.toNativeString()); + raw_package.parseInto(recipe, parent ? parent.name : null); + + if (!versionOverride.empty) + recipe.version_ = versionOverride; + + // try to run git to determine the version of the package if no explicit version was given + if (recipe.version_.length == 0 && !parent) { + try recipe.version_ = determineVersionFromSCM(root); + catch (Exception e) logDebug("Failed to determine version by SCM: %s", e.msg); + + if (recipe.version_.length == 0) { + logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", recipe.name, this.path.toNativeString()); + // TODO: Assume unknown version here? + // recipe.version_ = Version.UNKNOWN.toString(); + recipe.version_ = Version.MASTER.toString(); + } else logDiagnostic("Determined package version using GIT: %s %s", recipe.name, recipe.version_); + } + } + + this(recipe, root, parent); + } + + this(PackageInfo recipe, Path root = Path(), Package parent = null) + { m_parentPackage = parent; m_path = root; m_path.endsWithSlash = true; @@ -110,10 +141,16 @@ m_info.buildSettings.stringImportPaths[""] ~= defvf; } - string app_main_file; - auto pkg_name = (raw_package is null) ? "unknown" : raw_package.package_name; + // use the given recipe as the basis + m_info = recipe; + + // WARNING: changed semantics here. Previously, "sourcePaths" etc. + // could overwrite what was determined here. Now the default paths + // are always added. This must be fixed somehow! // check for default source folders + string app_main_file; + auto pkg_name = recipe.name.length ? recipe.name : "unknown"; foreach(defsf; ["source/", "src/"]){ auto p = m_path ~ defsf; if( existsFile(p) ){ @@ -127,29 +164,6 @@ } } - // parse the Package description - if(raw_package !is null) - { - scope(failure) logError("Failed to parse package description in %s", root.toNativeString()); - raw_package.parseInto(this, parent ? parent.name : null); - - if (!versionOverride.empty) - m_info.version_ = versionOverride; - - // try to run git to determine the version of the package if no explicit version was given - if (m_info.version_.length == 0 && !parent) { - try m_info.version_ = determineVersionFromSCM(root); - catch (Exception e) logDebug("Failed to determine version by SCM: %s", e.msg); - - if (m_info.version_.length == 0) { - logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", m_info.name, this.path.toNativeString()); - // TODO: Assume unknown version here? - // m_info.version_ = Version.UNKNOWN.toString(); - m_info.version_ = Version.MASTER.toString(); - } else logDiagnostic("Determined package version using GIT: %s %s", m_info.name, m_info.version_); - } - } - // generate default configurations if none are defined if (m_info.configurations.length == 0) { if (m_info.buildSettings.targetType == TargetType.executable) { @@ -192,9 +206,7 @@ @property const(Dependency[string]) dependencies() const { return m_info.dependencies; } @property inout(Package) basePackage() inout { return m_parentPackage ? m_parentPackage.basePackage : this; } @property inout(Package) parentPackage() inout { return m_parentPackage; } - @property inout(SubPackage)[] allSubPackages() inout { return m_info.subPackages; } - @property auto exportedPackages() { return m_info.subPackages.filter!(p => p.referenceName !is null); } - @property auto exportedPackageCount() { return m_info.exportedPackageCount; } + @property inout(SubPackage)[] subPackages() inout { return m_info.subPackages; } @property string[] configurations() const { @@ -204,15 +216,6 @@ return ret.data; } - Package parseImportedPackage(string importedPackage) - { - auto packageFilename = Path(importedPackage); - packageFilename.normalize(); - enforce(!packageFilename.absolute, "Sub package paths must not be absolute: " ~ importedPackage); - enforce(!packageFilename.startsWith(Path("..")), "Sub packages must be in a sub directory, not " ~ importedPackage); - return path.empty ? null : new Package(path ~ packageFilename, PathAndFormat(), this, this.vers); - } - const(Dependency[string]) getDependencies(string config) const { Dependency[string] ret; @@ -239,14 +242,14 @@ m_infoFile = PathAndFormat(filename); } - inout(Package) getSubPackage(string name, bool silent_fail = false) + /*inout(Package) getSubPackage(string name, bool silent_fail = false) inout { foreach (p; m_info.subPackages) if (p.package_ !is null && p.package_.name == this.name ~ ":" ~ name) return p.package_; enforce(silent_fail, format("Unknown sub package: %s:%s", this.name, name)); return null; - } + }*/ void warnOnSpecialCompilerFlags() { @@ -475,12 +478,12 @@ if(silent_fail) return null; throw new Exception("SDL not implemented"); } } - + static abstract class RawPackage { string package_name; // Should already be lower case - string version_; - abstract void parseInto(Package package_, string parent_name) const; + string version_; + abstract void parseInto(ref PackageInfo package_, string parent_name) const; } private static class JsonPackage : RawPackage { @@ -503,14 +506,14 @@ this.package_name = nameLower; } - override void parseInto(Package package_, string parent_name) const + override void parseInto(ref PackageInfo recipe, string parent_name) const { - package_.info.parseJson(package_, json, parent_name); + recipe.parseJson(json, parent_name); } } private static class SdlPackage : RawPackage { - override void parseInto(Package package_, string parent_name) const + override void parseInto(ref PackageInfo package_, string parent_name) const { throw new Exception("SDL packages not implemented yet"); } @@ -520,8 +523,8 @@ struct SubPackage { - string referenceName; - Package package_; + string path; + PackageInfo recipe; } /// Specifying package information without any connection to a certain @@ -538,7 +541,7 @@ BuildSettingsTemplate buildSettings; ConfigurationInfo[] configurations; BuildSettingsTemplate[string] buildTypes; - + size_t exportedPackageCount; SubPackage[] subPackages; @@ -559,28 +562,8 @@ if (c.name == name) return c; throw new Exception("Unknown configuration: "~name); - } - void parseSubPackages(Package containingPackage, Json[] subPackagesJson) - { - enforce(!containingPackage.parentPackage, format("'subPackages' found in '%s'. This is only supported in the main package file for '%s'.", - containingPackage.name, containingPackage.parentPackage.name)); - - this.exportedPackageCount = 0; - this.subPackages = new SubPackage[subPackagesJson.length]; - foreach(i, subPackageJson; subPackagesJson) { - // Handle referenced Packages - if(subPackageJson.type == Json.Type.string) { - string packageNameReference = subPackageJson.get!string; - this.subPackages[i] = SubPackage(packageNameReference, - containingPackage.parseImportedPackage(packageNameReference)); - this.exportedPackageCount++; - } else { - this.subPackages[i] = SubPackage(null, - new Package(subPackageJson, containingPackage.m_path, containingPackage)); - } - } } - void parseJson(Package containingPackage, Json json, string parent_name) + void parseJson(Json json, string parent_name) { foreach( string field, value; json ){ switch(field){ @@ -592,7 +575,6 @@ case "authors": this.authors = deserializeJson!(string[])(value); break; case "copyright": this.copyright = value.get!string; break; case "license": this.license = value.get!string; break; - case "subPackages": parseSubPackages(containingPackage, value.opt!(Json[])); break; case "configurations": break; // handled below, after the global settings have been parsed case "buildTypes": foreach (string name, settings; value) { @@ -607,8 +589,10 @@ enforce(this.name.length > 0, "The package \"name\" field is missing or empty."); + auto fullname = parent_name.length ? parent_name ~ ":" ~ this.name : this.name; + // parse build settings - this.buildSettings.parseJson(json, parent_name.length ? parent_name ~ ":" ~ this.name : this.name); + this.buildSettings.parseJson(json, fullname); if (auto pv = "configurations" in json) { TargetType deftargettp = TargetType.library; @@ -621,6 +605,10 @@ this.configurations ~= ci; } } + + // parse any sub packages after the main package has been fully parsed + if (auto ps = "subPackages" in json) + parseSubPackages(fullname, ps.opt!(Json[])); } Json toJson() @@ -636,10 +624,10 @@ if( !this.subPackages.empty ) { Json[] jsonSubPackages = new Json[this.subPackages.length]; foreach(i, subPackage; subPackages) { - if(subPackage.referenceName !is null) { - jsonSubPackages[i] = Json(subPackage.referenceName); + if(subPackage.path !is null) { + jsonSubPackages[i] = Json(subPackage.path); } else { - jsonSubPackages[i] = subPackage.package_.m_info.toJson(); + jsonSubPackages[i] = subPackage.recipe.toJson(); } } ret.subPackages = jsonSubPackages; @@ -658,6 +646,27 @@ if( !this.ddoxFilterArgs.empty ) ret["-ddoxFilterArgs"] = this.ddoxFilterArgs.serializeToJson(); return ret; } + + private void parseSubPackages(string parent_package_name, Json[] subPackagesJson) + { + enforce(!parent_package_name.canFind(":"), format("'subPackages' found in '%s'. This is only supported in the main package file for '%s'.", + parent_package_name, getBasePackageName(parent_package_name))); + + this.exportedPackageCount = 0; + this.subPackages = new SubPackage[subPackagesJson.length]; + foreach(i, subPackageJson; subPackagesJson) { + // Handle referenced Packages + if(subPackageJson.type == Json.Type.string) { + string subpath = subPackageJson.get!string; + this.subPackages[i] = SubPackage(subpath, PackageInfo.init); + this.exportedPackageCount++; + } else { + PackageInfo subinfo; + subinfo.parseJson(subPackageJson, parent_package_name); + this.subPackages[i] = SubPackage(null, subinfo); + } + } + } } /// Bundles information about a build configuration. diff --git a/source/dub/packagemanager.d b/source/dub/packagemanager.d index 05a72b7..8db4046 100644 --- a/source/dub/packagemanager.d +++ b/source/dub/packagemanager.d @@ -186,6 +186,15 @@ return getBestPackage(name, Dependency(version_spec)); } + Package getSubPackage(Package base_package, string sub_name, bool silent_fail) + { + foreach (p; getPackageIterator(base_package.name~":"~sub_name)) + if (p.parentPackage is base_package) + return p; + enforce(silent_fail, "Sub package "~base_package.name~":"~sub_name~" doesn't exist."); + return null; + } + /** Determines if a package is managed by DUB. @@ -206,26 +215,17 @@ { int iterator(int delegate(ref Package) del) { - int handlePackage(Package p) { - if (auto ret = del(p)) return ret; - foreach (sp; p.allSubPackages) - if(sp.package_ !is null) - if (auto ret = del(sp.package_)) - return ret; - return 0; - } - foreach (tp; m_temporaryPackages) - if (auto ret = handlePackage(tp)) return ret; + if (auto ret = del(tp)) return ret; // first search local packages foreach (tp; LocalPackageType.min .. LocalPackageType.max+1) foreach (p; m_repositories[cast(LocalPackageType)tp].localPackages) - if (auto ret = handlePackage(p)) return ret; + if (auto ret = del(p)) return ret; // and then all packages gathered from the search path foreach( p; m_packages ) - if( auto ret = handlePackage(p) ) + if( auto ret = del(p) ) return ret; return 0; } @@ -719,17 +719,27 @@ // Additionally to the internally defined subpackages, whose metadata // is loaded with the main dub.json, load all externally defined // packages after the package is available with all the data. - foreach (package_; pack.exportedPackages) { - auto path = pack.path ~ package_.referenceName; - if (!existsFile(path)) { - logError("Package %s declared a sub-package, definition file is missing: %s", pack.name, path.toNativeString()); - continue; - } + foreach (spr; pack.subPackages) { + Package sp; + + if (spr.path.length) { + auto p = Path(spr.path); + p.normalize(); + enforce(!p.absolute, "Sub package paths must be sub paths of the parent package."); + auto path = pack.path ~ p; + if (!existsFile(path)) { + logError("Package %s declared a sub-package, definition file is missing: %s", pack.name, path.toNativeString()); + continue; + } + sp = new Package(path, PathAndFormat(), pack); + } else sp = new Package(spr.recipe, pack.path, pack); + // Add the subpackage. try { - dst_repos ~= new Package(path, PathAndFormat(), pack); + dst_repos ~= sp; } catch (Exception e) { - logError("Package '%s': Failed to load sub-package in %s, error: %s", pack.name, path.toNativeString(), e.msg); + logError("Package '%s': Failed to load sub-package %s: %s", pack.name, + spr.path.length ? spr.path : spr.recipe.name, e.msg); logDiagnostic("Full error: %s", e.toString().sanitize()); } } diff --git a/source/dub/project.d b/source/dub/project.d index f505bd0..a979e91 100644 --- a/source/dub/project.d +++ b/source/dub/project.d @@ -224,7 +224,7 @@ if (!path.absolute) path = pack.path ~ path; logDiagnostic("Adding local %s", path); p = m_packageManager.getOrLoadPackage(path); - if (name.canFind(':')) p = p.getSubPackage(getSubPackageName(name)); + if (name.canFind(':')) p = m_packageManager.getSubPackage(p, getSubPackageName(name), false); enforce(p.name == name, format("Path based dependency %s is referenced with a wrong name: %s vs. %s", path.toNativeString(), name, p.name)); @@ -237,7 +237,7 @@ p = m_rootPackage.basePackage; } else if (basename == m_rootPackage.basePackage.name) { vspec = Dependency(m_rootPackage.ver); - try p = m_rootPackage.basePackage.getSubPackage(getSubPackageName(name)); + try p = m_packageManager.getSubPackage(m_rootPackage.basePackage, getSubPackageName(name), false); catch (Exception e) { logDiagnostic("Error getting sub package %s: %s", name, e.msg); continue;