diff --git a/.gitignore b/.gitignore index 275a4b6..250273f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ /bin/dub-* # Ignore files or directories created by the test suite. +/test/test.log /test/custom-source-main-bug487/custom-source-main-bug487 /test/3-copyFiles/bin/ /test/ignore-hidden-1/ignore-hidden-1 diff --git a/build-files.txt b/build-files.txt index 3d48908..fbda121 100644 --- a/build-files.txt +++ b/build-files.txt @@ -7,6 +7,12 @@ source/dub/init.d source/dub/packagemanager.d source/dub/packagesupplier.d +source/dub/packagesuppliers/package.d +source/dub/packagesuppliers/fallback.d +source/dub/packagesuppliers/filesystem.d +source/dub/packagesuppliers/packagesupplier.d +source/dub/packagesuppliers/maven.d +source/dub/packagesuppliers/registry.d source/dub/package_.d source/dub/platform.d source/dub/project.d diff --git a/changelog/customCachePaths.dd b/changelog/customCachePaths.dd new file mode 100644 index 0000000..d9030d2 --- /dev/null +++ b/changelog/customCachePaths.dd @@ -0,0 +1,6 @@ +DUB supports "customCachePaths" for providing read-only package paths + +With this release DUB allows defining additional paths that contain packages in subfolders with the pattern "(name)-(version)/(name)/" by defining a "customCachePaths" field in `/etc/dub/settings.json` or `~/.dub/settings.json.` + +"customCachePaths" can be used to provide prebuilt DUB libraries (e.g. for distribution package maintainers). + diff --git a/dub.sdl b/dub.sdl index 85fcaba..2607c34 100644 --- a/dub.sdl +++ b/dub.sdl @@ -1,6 +1,7 @@ name "dub" description "Package manager for D packages" -authors "Matthias Dondorff" "Sönke Ludwig" +authors "Sönke Ludwig" "Martin Nowak" "Matthias Dondorff" "Sebastian Wilzbach" \ + "more than 80 contributors total" copyright "Copyright © 2012-2016 rejectedsoftware e.K., Copyright © 2012-2014 Matthias Dondorff" license "MIT" diff --git a/source/dub/commandline.d b/source/dub/commandline.d index f3825c8..6f545b0 100644 --- a/source/dub/commandline.d +++ b/source/dub/commandline.d @@ -17,7 +17,7 @@ import dub.internal.vibecompat.inet.url; import dub.package_; import dub.packagemanager; -import dub.packagesupplier; +import dub.packagesuppliers; import dub.project; import dub.internal.utils : getDUBVersion, getClosestMatch; @@ -230,11 +230,11 @@ // should simply retry over all registries instead of using a special // FallbackPackageSupplier. auto urls = url.splitter(' '); - PackageSupplier ps = new RegistryPackageSupplier(URL(urls.front)); + PackageSupplier ps = getRegistryPackageSupplier(urls.front); urls.popFront; if (!urls.empty) ps = new FallbackPackageSupplier(ps, - urls.map!(u => cast(PackageSupplier) new RegistryPackageSupplier(URL(u))).array); + urls.map!(u => getRegistryPackageSupplier(u)).array); return ps; }) .array; diff --git a/source/dub/compilers/utils.d b/source/dub/compilers/utils.d index 2d05d37..999a69e 100644 --- a/source/dub/compilers/utils.d +++ b/source/dub/compilers/utils.d @@ -253,10 +253,13 @@ module dub_platform_probe; template toString(int v) { enum toString = v.stringof; } - string join(string[] ary, string sep) { - string res = ary[0]; - foreach (e; ary[1 .. $]) - res ~= sep ~ e; + string stringArray(string[] ary) { + string res; + foreach (i, e; ary) { + if (i) + res ~= ", "; + res ~= '"' ~ e ~ '"'; + } return res; } @@ -265,10 +268,10 @@ pragma(msg, ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,`); pragma(msg, ` "compilerVendor": "` ~ __VENDOR__ ~ `",`); pragma(msg, ` "platform": [`); - pragma(msg, ` "` ~ determinePlatform().join(`", "`) ~ '"'); + pragma(msg, ` ` ~ determinePlatform().stringArray); pragma(msg, ` ],`); pragma(msg, ` "architecture": [`); - pragma(msg, ` "` ~ determineArchitecture().join(`", "`) ~ '"'); + pragma(msg, ` ` ~ determineArchitecture().stringArray); pragma(msg, ` ],`); pragma(msg, `}`); diff --git a/source/dub/dub.d b/source/dub/dub.d index 47bf48b..c6a79a5 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -17,7 +17,7 @@ import dub.internal.vibecompat.inet.url; import dub.package_; import dub.packagemanager; -import dub.packagesupplier; +import dub.packagesuppliers; import dub.project; import dub.generators.generator; import dub.init; @@ -87,6 +87,34 @@ ]; } +/** Returns a registry package supplier according to protocol. + + Allowed protocols are dub+http(s):// and maven+http(s)://. +*/ +PackageSupplier getRegistryPackageSupplier(string url) +{ + switch (url.startsWith("dub+", "mvn+")) + { + case 1: + return new RegistryPackageSupplier(URL(url[4..$])); + case 2: + return new MavenRegistryPackageSupplier(URL(url[4..$])); + default: + return new RegistryPackageSupplier(URL(url)); + } +} + +unittest +{ + auto dubRegistryPackageSupplier = getRegistryPackageSupplier("dub+https://code.dlang.org"); + assert(dubRegistryPackageSupplier.description.canFind(" https://code.dlang.org")); + + dubRegistryPackageSupplier = getRegistryPackageSupplier("https://code.dlang.org"); + assert(dubRegistryPackageSupplier.description.canFind(" https://code.dlang.org")); + + auto mavenRegistryPackageSupplier = getRegistryPackageSupplier("mvn+http://localhost:8040/maven/libs-release/dubpackages"); + assert(mavenRegistryPackageSupplier.description.canFind(" http://localhost:8040/maven/libs-release/dubpackages")); +} /** Provides a high-level entry point for DUB's functionality. @@ -145,14 +173,14 @@ { ps ~= environment.get("DUB_REGISTRY", null) .splitter(";") - .map!(url => cast(PackageSupplier)new RegistryPackageSupplier(URL(url))) + .map!(url => getRegistryPackageSupplier(url)) .array; } if (skip_registry < SkipPackageSuppliers.configured) { ps ~= m_config.registryURLs - .map!(url => cast(PackageSupplier)new RegistryPackageSupplier(URL(url))) + .map!(url => getRegistryPackageSupplier(url)) .array; } @@ -161,6 +189,11 @@ m_packageSuppliers = ps; m_packageManager = new PackageManager(m_dirs.localRepository, m_dirs.systemSettings); + + auto ccps = m_config.customCachePaths; + if (ccps.length) + m_packageManager.customCachePaths = ccps; + updatePackageSearchPath(); } @@ -224,12 +257,12 @@ m_defaultArchitecture = m_config.defaultArchitecture; } - version(Windows) + version(Windows) private void migrateRepositoryFromRoaming(NativePath roamingDir, NativePath localDir) { immutable roamingDirPath = roamingDir.toNativeString(); if (!existsDirectory(roamingDir)) return; - + immutable localDirPath = localDir.toNativeString(); logInfo("Detected a package cache in " ~ roamingDirPath ~ ". This will be migrated to " ~ localDirPath ~ ". Please wait..."); if (!existsDirectory(localDir)) @@ -237,7 +270,7 @@ mkdirRecurse(localDirPath); } - runCommand("xcopy /s /e /y " ~ roamingDirPath ~ " " ~ localDirPath ~ " > NUL"); + runCommand("xcopy /s /e /y " ~ roamingDirPath ~ " " ~ localDirPath ~ " > NUL"); rmdirRecurse(roamingDirPath); } @@ -1234,16 +1267,15 @@ import std.path : buildPath, dirName, expandTilde, isAbsolute, isDirSeparator; import std.process : environment; import std.range : front; - import std.regex : ctRegex, matchFirst; m_defaultCompiler = m_config.defaultCompiler.expandTilde; if (m_defaultCompiler.length && m_defaultCompiler.isAbsolute) return; - auto dubPrefix = m_defaultCompiler.matchFirst(ctRegex!(`^\$DUB_BINARY_PATH`)); - if(!dubPrefix.empty) + static immutable BinaryPrefix = `$DUB_BINARY_PATH`; + if(m_defaultCompiler.startsWith(BinaryPrefix)) { - m_defaultCompiler = thisExePath().dirName() ~ dubPrefix.post; + m_defaultCompiler = thisExePath().dirName() ~ m_defaultCompiler[BinaryPrefix.length .. $]; return; } @@ -1626,6 +1658,21 @@ return ret; } + @property NativePath[] customCachePaths() + { + import std.algorithm.iteration : map; + import std.array : array; + + NativePath[] ret; + if (auto pv = "customCachePaths" in m_data) + ret = (*pv).deserializeJson!(string[]) + .map!(s => NativePath(s)) + .array; + if (m_parentConfig) + ret ~= m_parentConfig.customCachePaths; + return ret; + } + @property string defaultCompiler() const { if (auto pv = "defaultCompiler" in m_data) diff --git a/source/dub/internal/utils.d b/source/dub/internal/utils.d index 22e6244..4eb455a 100644 --- a/source/dub/internal/utils.d +++ b/source/dub/internal/utils.d @@ -198,7 +198,8 @@ Pid pid; pid = spawnShell(cmd, stdin, childStdout, childStderr, env, config); auto exitcode = pid.wait(); - enforce(exitcode == 0, "Command failed with exit code "~to!string(exitcode)); + enforce(exitcode == 0, "Command failed with exit code " + ~ to!string(exitcode) ~ ": " ~ cmd); } } diff --git a/source/dub/internal/vibecompat/data/json.d b/source/dub/internal/vibecompat/data/json.d index c352bf3..9d2a7b6 100644 --- a/source/dub/internal/vibecompat/data/json.d +++ b/source/dub/internal/vibecompat/data/json.d @@ -2102,13 +2102,19 @@ private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message = "JSON exception") { - enforceEx!JSONException(cond, message, file, line); + static if (__VERSION__ >= 2079) + enforce!JSONException(cond, message, file, line); + else + enforceEx!JSONException(cond, message, file, line); } private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message, string err_file, int err_line) { auto errmsg() { return format("%s(%s): Error: %s", err_file, err_line+1, message); } - enforceEx!JSONException(cond, errmsg, file, line); + static if (__VERSION__ >= 2079) + enforce!JSONException(cond, errmsg, file, line); + else + enforceEx!JSONException(cond, errmsg, file, line); } private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message, string err_file, int* err_line) diff --git a/source/dub/packagemanager.d b/source/dub/packagemanager.d index c210b2b..13134b5 100644 --- a/source/dub/packagemanager.d +++ b/source/dub/packagemanager.d @@ -30,7 +30,7 @@ /// packages. class PackageManager { private { - Repository[LocalPackageType] m_repositories; + Repository[] m_repositories; NativePath[] m_searchPath; Package[] m_packages; Package[] m_temporaryPackages; @@ -39,8 +39,9 @@ this(NativePath user_path, NativePath system_path, bool refresh_packages = true) { - m_repositories[LocalPackageType.user] = Repository(user_path); - m_repositories[LocalPackageType.system] = Repository(system_path); + m_repositories.length = LocalPackageType.max+1; + m_repositories[LocalPackageType.user] = Repository(user_path ~ "packages/"); + m_repositories[LocalPackageType.system] = Repository(system_path ~ "packages/"); if (refresh_packages) refresh(true); } @@ -71,14 +72,33 @@ auto ret = appender!(NativePath[])(); ret.put(cast(NativePath[])m_searchPath); // work around Phobos 17251 if (!m_disableDefaultSearchPaths) { - ret.put(cast(NativePath[])m_repositories[LocalPackageType.user].searchPath); - ret.put(cast(NativePath)m_repositories[LocalPackageType.user].packagePath); - ret.put(cast(NativePath[])m_repositories[LocalPackageType.system].searchPath); - ret.put(cast(NativePath)m_repositories[LocalPackageType.system].packagePath); + foreach (ref repo; m_repositories) { + ret.put(cast(NativePath[])repo.searchPath); + ret.put(cast(NativePath)repo.packagePath); + } } return ret.data; } + /** Sets additional (read-only) package cache paths to search for packages. + + Cache paths have the same structure as the default cache paths, such as + ".dub/packages/". + + Note that previously set custom paths will be removed when setting this + property. + */ + @property void customCachePaths(NativePath[] custom_cache_paths) + { + import std.algorithm.iteration : map; + import std.array : array; + + m_repositories.length = LocalPackageType.max+1; + m_repositories ~= custom_cache_paths.map!(p => Repository(p)).array; + + refresh(false); + } + /** Looks up a specific package. @@ -100,8 +120,8 @@ Package getPackage(string name, Version ver, bool enable_overrides = true) { if (enable_overrides) { - foreach (tp; [LocalPackageType.user, LocalPackageType.system]) - foreach (ovr; m_repositories[tp].overrides) + foreach (ref repo; m_repositories) + foreach (ovr; repo.overrides) if (ovr.package_ == name && ovr.version_.matches(ver)) { Package pack; if (!ovr.targetPath.empty) pack = getOrLoadPackage(ovr.targetPath); @@ -266,8 +286,8 @@ 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) + foreach (ref repo; m_repositories) + foreach (p; repo.localPackages) if (auto ret = del(p)) return ret; // and then all packages gathered from the search path @@ -803,7 +823,6 @@ private struct Repository { - NativePath path; NativePath packagePath; NativePath[] searchPath; Package[] localPackages; @@ -811,7 +830,6 @@ this(NativePath path) { - this.path = path; - this.packagePath = path ~"packages/"; + this.packagePath = path; } } diff --git a/source/dub/packagesupplier.d b/source/dub/packagesupplier.d index 44ddbcf..244779e 100644 --- a/source/dub/packagesupplier.d +++ b/source/dub/packagesupplier.d @@ -1,344 +1,10 @@ -/** - Contains (remote) package supplier interface and implementations. +/** +deprecated("Please use dub.packagesuppliers") + Contains (remote) package supplier interface and implementations. +public import dub.packagesuppliers; 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 */ +deprecated("Please use dub.packagesuppliers") module dub.packagesupplier; - -import dub.dependency; -import dub.internal.utils; -import dub.internal.vibecompat.core.log; -import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.data.json; -import dub.internal.vibecompat.inet.url; - -import std.algorithm : filter, sort; -import std.array : array; -import std.conv; -import std.datetime; -import std.exception; -import std.file; -import std.string : format; -import std.typecons : AutoImplement; -import std.zip; - -// TODO: Could drop the "best package" behavior and let retrievePackage/ -// getPackageDescription take a Version instead of Dependency. But note -// this means that two requests to the registry are necessary to retrieve -// a package recipe instead of one (first get version list, then the -// package recipe) - -/** - Base interface for remote package suppliers. - - Provides functionality necessary to query package versions, recipes and - contents. -*/ -interface PackageSupplier { - /// Represents a single package search result. - static struct SearchResult { string name, description, version_; } - - /// Returns a human-readable representation of the package supplier. - @property string description(); - - /** Retrieves a list of all available versions(/branches) of a package. - - Throws: Throws an exception if the package name is not known, or if - an error occurred while retrieving the version list. - */ - Version[] getVersions(string package_id); - - /** Downloads a package and stores it as a ZIP file. - - Params: - path = Absolute path of the target ZIP file - package_id = Name of the package to retrieve - dep: Version constraint to match against - pre_release: If true, matches the latest pre-release version. - Otherwise prefers stable versions. - */ - void fetchPackage(NativePath path, string package_id, Dependency dep, bool pre_release); - - /** Retrieves only the recipe of a particular package. - - Params: - package_id = Name of the package of which to retrieve the recipe - dep: Version constraint to match against - pre_release: If true, matches the latest pre-release version. - Otherwise prefers stable versions. - */ - Json fetchPackageRecipe(string package_id, Dependency dep, bool pre_release); - - /** Searches for packages matching the given search query term. - - Search queries are currently a simple list of words separated by - white space. Results will get ordered from best match to worst. - */ - SearchResult[] searchPackages(string query); -} - - -/** - File system based package supplier. - - This package supplier searches a certain directory for files with names of - the form "[package name]-[version].zip". -*/ -class FileSystemPackageSupplier : PackageSupplier { - private { - NativePath m_path; - } - - this(NativePath root) { m_path = root; } - - override @property string description() { return "file repository at "~m_path.toNativeString(); } - - Version[] getVersions(string package_id) - { - Version[] ret; - foreach (DirEntry d; dirEntries(m_path.toNativeString(), package_id~"*", SpanMode.shallow)) { - NativePath p = NativePath(d.name); - logDebug("Entry: %s", p); - enforce(to!string(p.head)[$-4..$] == ".zip"); - auto vers = p.head.toString()[package_id.length+1..$-4]; - logDebug("Version: %s", vers); - ret ~= Version(vers); - } - ret.sort(); - return ret; - } - - void fetchPackage(NativePath path, string packageId, Dependency dep, bool pre_release) - { - enforce(path.absolute); - logInfo("Storing package '"~packageId~"', version requirements: %s", dep); - auto filename = bestPackageFile(packageId, dep, pre_release); - enforce(existsFile(filename)); - copyFile(filename, path); - } - - Json fetchPackageRecipe(string packageId, Dependency dep, bool pre_release) - { - auto filename = bestPackageFile(packageId, dep, pre_release); - return jsonFromZip(filename, "dub.json"); - } - - SearchResult[] searchPackages(string query) - { - // TODO! - return null; - } - - private NativePath bestPackageFile(string packageId, Dependency dep, bool pre_release) - { - NativePath toPath(Version ver) { - return m_path ~ (packageId ~ "-" ~ ver.toString() ~ ".zip"); - } - auto versions = getVersions(packageId).filter!(v => dep.matches(v)).array; - enforce(versions.length > 0, format("No package %s found matching %s", packageId, dep)); - foreach_reverse (ver; versions) { - if (pre_release || !ver.isPreRelease) - return toPath(ver); - } - return toPath(versions[$-1]); - } -} - - -/** - Online registry based package supplier. - - This package supplier connects to an online registry (e.g. - $(LINK https://code.dlang.org/)) to search for available packages. -*/ -class RegistryPackageSupplier : PackageSupplier { - private { - URL m_registryUrl; - struct CacheEntry { Json data; SysTime cacheTime; } - CacheEntry[string] m_metadataCache; - Duration m_maxCacheTime; - } - - this(URL registry) - { - m_registryUrl = registry; - m_maxCacheTime = 24.hours(); - } - - override @property string description() { return "registry at "~m_registryUrl.toString(); } - - Version[] getVersions(string package_id) - { - auto md = getMetadata(package_id); - if (md.type == Json.Type.null_) - return null; - Version[] ret; - foreach (json; md["versions"]) { - auto cur = Version(cast(string)json["version"]); - ret ~= cur; - } - ret.sort(); - return ret; - } - - void fetchPackage(NativePath path, string packageId, Dependency dep, bool pre_release) - { - import std.array : replace; - Json best = getBestPackage(packageId, dep, pre_release); - if (best.type == Json.Type.null_) - return; - auto vers = best["version"].get!string; - auto url = m_registryUrl ~ NativePath(PackagesPath~"/"~packageId~"/"~vers~".zip"); - logDiagnostic("Downloading from '%s'", url); - foreach(i; 0..3) { - try{ - download(url, path); - return; - } - catch(HTTPStatusException e) { - if (e.status == 404) throw e; - else { - logDebug("Failed to download package %s from %s (Attempt %s of 3)", packageId, url, i + 1); - continue; - } - } - } - throw new Exception("Failed to download package %s from %s".format(packageId, url)); - } - - Json fetchPackageRecipe(string packageId, Dependency dep, bool pre_release) - { - return getBestPackage(packageId, dep, pre_release); - } - - private Json getMetadata(string packageId) - { - auto now = Clock.currTime(UTC()); - if (auto pentry = packageId in m_metadataCache) { - if (pentry.cacheTime + m_maxCacheTime > now) - return pentry.data; - m_metadataCache.remove(packageId); - } - - auto url = m_registryUrl ~ NativePath(PackagesPath ~ "/" ~ packageId ~ ".json"); - - logDebug("Downloading metadata for %s", packageId); - logDebug("Getting from %s", url); - - string jsonData; - foreach(i; 0..3) { - try { - jsonData = cast(string)download(url); - break; - } - catch (HTTPStatusException e) - { - if (e.status == 404) { - logDebug("Package %s not found at %s (404): %s", packageId, description, e.msg); - return Json(null); - } - else { - logDebug("Error getting metadata for package %s at %s (attempt %s of 3): %s", packageId, description, i + 1, e.msg); - if (i == 2) - throw e; - continue; - } - } - } - Json json = parseJsonString(jsonData, url.toString()); - // strip readme data (to save size and time) - foreach (ref v; json["versions"]) - v.remove("readme"); - m_metadataCache[packageId] = CacheEntry(json, now); - return json; - } - - SearchResult[] searchPackages(string query) { - import std.uri : encodeComponent; - auto url = m_registryUrl; - url.localURI = "/api/packages/search?q="~encodeComponent(query); - string data; - data = cast(string)download(url); - import std.algorithm : map; - return data.parseJson.opt!(Json[]) - .map!(j => SearchResult(j["name"].opt!string, j["description"].opt!string, j["version"].opt!string)) - .array; - } - - private Json getBestPackage(string packageId, Dependency dep, bool pre_release) - { - Json md = getMetadata(packageId); - if (md.type == Json.Type.null_) - return md; - Json best = null; - Version bestver; - foreach (json; md["versions"]) { - auto cur = Version(cast(string)json["version"]); - if (!dep.matches(cur)) continue; - if (best == null) best = json; - else if (pre_release) { - if (cur > bestver) best = json; - } else if (bestver.isPreRelease) { - if (!cur.isPreRelease || cur > bestver) best = json; - } else if (!cur.isPreRelease && cur > bestver) best = json; - bestver = Version(cast(string)best["version"]); - } - enforce(best != null, "No package candidate found for "~packageId~" "~dep.toString()); - return best; - } -} - -package abstract class AbstractFallbackPackageSupplier : PackageSupplier -{ - protected PackageSupplier m_default; - protected PackageSupplier[] m_fallbacks; - - this(PackageSupplier default_, PackageSupplier[] fallbacks) - { - m_default = default_; - m_fallbacks = fallbacks; - } - - override @property string description() - { - import std.algorithm : map; - return format("%s (fallback %s)", m_default.description, m_fallbacks.map!(x => x.description)); - } - - // Workaround https://issues.dlang.org/show_bug.cgi?id=2525 - abstract override Version[] getVersions(string package_id); - abstract override void fetchPackage(NativePath path, string package_id, Dependency dep, bool pre_release); - abstract override Json fetchPackageRecipe(string package_id, Dependency dep, bool pre_release); - abstract override SearchResult[] searchPackages(string query); -} - -/** - Combines two package suppliers and uses the second as fallback to handle failures. - - Assumes that both registries serve the same packages (--mirror). -*/ -package alias FallbackPackageSupplier = AutoImplement!(AbstractFallbackPackageSupplier, fallback); - -private template fallback(T, alias func) -{ - enum fallback = q{ - import std.range : back, dropBackOne; - import dub.internal.vibecompat.core.log : logDebug; - scope (failure) - { - foreach (m_fallback; m_fallbacks.dropBackOne) - { - try - return m_fallback.%1$s(args); - catch(Exception) - logDebug("Package supplier %s failed. Trying next fallback.", m_fallback); - } - return m_fallbacks.back.%1$s(args); - } - return m_default.%1$s(args); - }.format(__traits(identifier, func)); -} - -private enum PackagesPath = "packages"; +public import dub.packagesuppliers; diff --git a/source/dub/packagesuppliers/fallback.d b/source/dub/packagesuppliers/fallback.d new file mode 100644 index 0000000..7a691a9 --- /dev/null +++ b/source/dub/packagesuppliers/fallback.d @@ -0,0 +1,58 @@ +module dub.packagesuppliers.fallback; + +import dub.packagesuppliers.packagesupplier; +import std.typecons : AutoImplement; + +package abstract class AbstractFallbackPackageSupplier : PackageSupplier +{ + protected PackageSupplier m_default; + protected PackageSupplier[] m_fallbacks; + + this(PackageSupplier default_, PackageSupplier[] fallbacks) + { + m_default = default_; + m_fallbacks = fallbacks; + } + + override @property string description() + { + import std.algorithm.iteration : map; + import std.format : format; + return format("%s (fallback %s)", m_default.description, m_fallbacks.map!(x => x.description)); + } + + // Workaround https://issues.dlang.org/show_bug.cgi?id=2525 + abstract override Version[] getVersions(string package_id); + abstract override void fetchPackage(NativePath path, string package_id, Dependency dep, bool pre_release); + abstract override Json fetchPackageRecipe(string package_id, Dependency dep, bool pre_release); + abstract override SearchResult[] searchPackages(string query); +} + + +/** + Combines two package suppliers and uses the second as fallback to handle failures. + + Assumes that both registries serve the same packages (--mirror). +*/ +package(dub) alias FallbackPackageSupplier = AutoImplement!(AbstractFallbackPackageSupplier, fallback); + +private template fallback(T, alias func) +{ + import std.format : format; + enum fallback = q{ + import std.range : back, dropBackOne; + import dub.internal.vibecompat.core.log : logDebug; + scope (failure) + { + foreach (m_fallback; m_fallbacks.dropBackOne) + { + try + return m_fallback.%1$s(args); + catch(Exception) + logDebug("Package supplier %s failed. Trying next fallback.", m_fallback); + } + return m_fallbacks.back.%1$s(args); + } + return m_default.%1$s(args); + }.format(__traits(identifier, func)); +} diff --git a/source/dub/packagesuppliers/filesystem.d b/source/dub/packagesuppliers/filesystem.d new file mode 100644 index 0000000..323e47b --- /dev/null +++ b/source/dub/packagesuppliers/filesystem.d @@ -0,0 +1,80 @@ +module dub.packagesuppliers.filesystem; + +import dub.packagesuppliers.packagesupplier; + +/** + File system based package supplier. + + This package supplier searches a certain directory for files with names of + the form "[package name]-[version].zip". +*/ +class FileSystemPackageSupplier : PackageSupplier { + import dub.internal.vibecompat.core.log; + version (Have_vibe_core) import dub.internal.vibecompat.inet.path : toNativeString; + import std.exception : enforce; + private { + NativePath m_path; + } + + this(NativePath root) { m_path = root; } + + override @property string description() { return "file repository at "~m_path.toNativeString(); } + + Version[] getVersions(string package_id) + { + import std.algorithm.sorting : sort; + import std.file : dirEntries, DirEntry, SpanMode; + import std.conv : to; + Version[] ret; + foreach (DirEntry d; dirEntries(m_path.toNativeString(), package_id~"*", SpanMode.shallow)) { + NativePath p = NativePath(d.name); + logDebug("Entry: %s", p); + enforce(to!string(p.head)[$-4..$] == ".zip"); + auto vers = p.head.toString()[package_id.length+1..$-4]; + logDebug("Version: %s", vers); + ret ~= Version(vers); + } + ret.sort(); + return ret; + } + + void fetchPackage(NativePath path, string packageId, Dependency dep, bool pre_release) + { + import dub.internal.vibecompat.core.file : copyFile, existsFile; + enforce(path.absolute); + logInfo("Storing package '"~packageId~"', version requirements: %s", dep); + auto filename = bestPackageFile(packageId, dep, pre_release); + enforce(existsFile(filename)); + copyFile(filename, path); + } + + Json fetchPackageRecipe(string packageId, Dependency dep, bool pre_release) + { + import dub.internal.utils : jsonFromZip; + auto filename = bestPackageFile(packageId, dep, pre_release); + return jsonFromZip(filename, "dub.json"); + } + + SearchResult[] searchPackages(string query) + { + // TODO! + return null; + } + + private NativePath bestPackageFile(string packageId, Dependency dep, bool pre_release) + { + import std.algorithm.iteration : filter; + import std.array : array; + import std.format : format; + NativePath toPath(Version ver) { + return m_path ~ (packageId ~ "-" ~ ver.toString() ~ ".zip"); + } + auto versions = getVersions(packageId).filter!(v => dep.matches(v)).array; + enforce(versions.length > 0, format("No package %s found matching %s", packageId, dep)); + foreach_reverse (ver; versions) { + if (pre_release || !ver.isPreRelease) + return toPath(ver); + } + return toPath(versions[$-1]); + } +} diff --git a/source/dub/packagesuppliers/maven.d b/source/dub/packagesuppliers/maven.d new file mode 100644 index 0000000..235aa32 --- /dev/null +++ b/source/dub/packagesuppliers/maven.d @@ -0,0 +1,137 @@ +module dub.packagesuppliers.maven; + +import dub.packagesuppliers.packagesupplier; + +/** + Maven repository based package supplier. + + This package supplier connects to a maven repository + to search for available packages. +*/ +class MavenRegistryPackageSupplier : PackageSupplier { + import dub.internal.utils : download, HTTPStatusException; + import dub.internal.vibecompat.data.json : serializeToJson; + import dub.internal.vibecompat.core.log; + import dub.internal.vibecompat.inet.url : URL; + + import std.datetime : Clock, Duration, hours, SysTime, UTC; + + private { + URL m_mavenUrl; + struct CacheEntry { Json data; SysTime cacheTime; } + CacheEntry[string] m_metadataCache; + Duration m_maxCacheTime; + } + + this(URL mavenUrl) + { + m_mavenUrl = mavenUrl; + m_maxCacheTime = 24.hours(); + } + + override @property string description() { return "maven repository at "~m_mavenUrl.toString(); } + + Version[] getVersions(string package_id) + { + import std.algorithm.sorting : sort; + auto md = getMetadata(package_id); + if (md.type == Json.Type.null_) + return null; + Version[] ret; + foreach (json; md["versions"]) { + auto cur = Version(json["version"].get!string); + ret ~= cur; + } + ret.sort(); + return ret; + } + + void fetchPackage(NativePath path, string packageId, Dependency dep, bool pre_release) + { + import std.format : format; + auto md = getMetadata(packageId); + Json best = getBestPackage(md, packageId, dep, pre_release); + if (best.type == Json.Type.null_) + return; + auto vers = best["version"].get!string; + auto url = m_mavenUrl~NativePath("%s/%s/%s-%s.zip".format(packageId, vers, packageId, vers)); + logDiagnostic("Downloading from '%s'", url); + foreach(i; 0..3) { + try{ + download(url, path); + return; + } + catch(HTTPStatusException e) { + if (e.status == 404) throw e; + else { + logDebug("Failed to download package %s from %s (Attempt %s of 3)", packageId, url, i + 1); + continue; + } + } + } + throw new Exception("Failed to download package %s from %s".format(packageId, url)); + } + + Json fetchPackageRecipe(string packageId, Dependency dep, bool pre_release) + { + auto md = getMetadata(packageId); + return getBestPackage(md, packageId, dep, pre_release); + } + + private Json getMetadata(string packageId) + { + import std.xml; + + auto now = Clock.currTime(UTC()); + if (auto pentry = packageId in m_metadataCache) { + if (pentry.cacheTime + m_maxCacheTime > now) + return pentry.data; + m_metadataCache.remove(packageId); + } + + auto url = m_mavenUrl~NativePath(packageId~"/maven-metadata.xml"); + + logDebug("Downloading maven metadata for %s", packageId); + logDebug("Getting from %s", url); + + string xmlData; + foreach(i; 0..3) { + try { + xmlData = cast(string)download(url); + break; + } + catch (HTTPStatusException e) + { + if (e.status == 404) { + logDebug("Maven metadata %s not found at %s (404): %s", packageId, description, e.msg); + return Json(null); + } + else { + logDebug("Error getting maven metadata for %s at %s (attempt %s of 3): %s", packageId, description, i + 1, e.msg); + if (i == 2) + throw e; + continue; + } + } + } + + auto json = Json(["name": Json(packageId), "versions": Json.emptyArray]); + auto xml = new DocumentParser(xmlData); + + xml.onStartTag["versions"] = (ElementParser xml) { + xml.onEndTag["version"] = (in Element e) { + json["versions"] ~= serializeToJson(["name": packageId, "version": e.text]); + }; + xml.parse(); + }; + xml.parse(); + m_metadataCache[packageId] = CacheEntry(json, now); + return json; + } + + SearchResult[] searchPackages(string query) + { + return []; + } +} + diff --git a/source/dub/packagesuppliers/package.d b/source/dub/packagesuppliers/package.d new file mode 100644 index 0000000..5bb8057 --- /dev/null +++ b/source/dub/packagesuppliers/package.d @@ -0,0 +1,10 @@ +module dub.packagesuppliers; + +/** + Contains (remote) package supplier interface and implementations. +*/ +public import dub.packagesuppliers.fallback; +public import dub.packagesuppliers.filesystem; +public import dub.packagesuppliers.packagesupplier; +public import dub.packagesuppliers.maven; +public import dub.packagesuppliers.registry; diff --git a/source/dub/packagesuppliers/packagesupplier.d b/source/dub/packagesuppliers/packagesupplier.d new file mode 100644 index 0000000..4018977 --- /dev/null +++ b/source/dub/packagesuppliers/packagesupplier.d @@ -0,0 +1,82 @@ +module dub.packagesuppliers.packagesupplier; + +public import dub.dependency : Dependency, Version; +public import dub.internal.vibecompat.core.file : NativePath; +public import dub.internal.vibecompat.data.json : Json; + +/** + Base interface for remote package suppliers. + + Provides functionality necessary to query package versions, recipes and + contents. +*/ +interface PackageSupplier { + /// Represents a single package search result. + static struct SearchResult { string name, description, version_; } + + /// Returns a human-readable representation of the package supplier. + @property string description(); + + /** Retrieves a list of all available versions(/branches) of a package. + + Throws: Throws an exception if the package name is not known, or if + an error occurred while retrieving the version list. + */ + Version[] getVersions(string package_id); + + /** Downloads a package and stores it as a ZIP file. + + Params: + path = Absolute path of the target ZIP file + package_id = Name of the package to retrieve + dep: Version constraint to match against + pre_release: If true, matches the latest pre-release version. + Otherwise prefers stable versions. + */ + void fetchPackage(NativePath path, string package_id, Dependency dep, bool pre_release); + + /** Retrieves only the recipe of a particular package. + + Params: + package_id = Name of the package of which to retrieve the recipe + dep: Version constraint to match against + pre_release: If true, matches the latest pre-release version. + Otherwise prefers stable versions. + */ + Json fetchPackageRecipe(string package_id, Dependency dep, bool pre_release); + + /** Searches for packages matching the given search query term. + + Search queries are currently a simple list of words separated by + white space. Results will get ordered from best match to worst. + */ + SearchResult[] searchPackages(string query); +} + +// TODO: Could drop the "best package" behavior and let retrievePackage/ +// getPackageDescription take a Version instead of Dependency. But note +// this means that two requests to the registry are necessary to retrieve +// a package recipe instead of one (first get version list, then the +// package recipe) + +package Json getBestPackage(Json metadata, string packageId, Dependency dep, bool pre_release) +{ + import std.exception : enforce; + if (metadata.type == Json.Type.null_) + return metadata; + Json best = null; + Version bestver; + foreach (json; metadata["versions"]) { + auto cur = Version(json["version"].get!string); + if (!dep.matches(cur)) continue; + if (best == null) best = json; + else if (pre_release) { + if (cur > bestver) best = json; + } else if (bestver.isPreRelease) { + if (!cur.isPreRelease || cur > bestver) best = json; + } else if (!cur.isPreRelease && cur > bestver) best = json; + bestver = Version(cast(string)best["version"]); + } + enforce(best != null, "No package candidate found for "~packageId~" "~dep.toString()); + return best; +} diff --git a/source/dub/packagesuppliers/registry.d b/source/dub/packagesuppliers/registry.d new file mode 100644 index 0000000..6706b47 --- /dev/null +++ b/source/dub/packagesuppliers/registry.d @@ -0,0 +1,138 @@ +module dub.packagesuppliers.registry; + +import dub.packagesuppliers.packagesupplier; + +package enum PackagesPath = "packages"; + +/** + Online registry based package supplier. + + This package supplier connects to an online registry (e.g. + $(LINK https://code.dlang.org/)) to search for available packages. +*/ +class RegistryPackageSupplier : PackageSupplier { + import dub.internal.utils : download, HTTPStatusException; + import dub.internal.vibecompat.core.log; + import dub.internal.vibecompat.data.json : parseJson, parseJsonString, serializeToJson; + import dub.internal.vibecompat.inet.url : URL; + + import std.datetime : Clock, Duration, hours, SysTime, UTC; + private { + URL m_registryUrl; + struct CacheEntry { Json data; SysTime cacheTime; } + CacheEntry[string] m_metadataCache; + Duration m_maxCacheTime; + } + + this(URL registry) + { + m_registryUrl = registry; + m_maxCacheTime = 24.hours(); + } + + override @property string description() { return "registry at "~m_registryUrl.toString(); } + + Version[] getVersions(string package_id) + { + import std.algorithm.sorting : sort; + auto md = getMetadata(package_id); + if (md.type == Json.Type.null_) + return null; + Version[] ret; + foreach (json; md["versions"]) { + auto cur = Version(cast(string)json["version"]); + ret ~= cur; + } + ret.sort(); + return ret; + } + + void fetchPackage(NativePath path, string packageId, Dependency dep, bool pre_release) + { + import std.array : replace; + import std.format : format; + auto md = getMetadata(packageId); + Json best = getBestPackage(md, packageId, dep, pre_release); + if (best.type == Json.Type.null_) + return; + auto vers = best["version"].get!string; + auto url = m_registryUrl ~ NativePath(PackagesPath~"/"~packageId~"/"~vers~".zip"); + logDiagnostic("Downloading from '%s'", url); + foreach(i; 0..3) { + try{ + download(url, path); + return; + } + catch(HTTPStatusException e) { + if (e.status == 404) throw e; + else { + logDebug("Failed to download package %s from %s (Attempt %s of 3)", packageId, url, i + 1); + continue; + } + } + } + throw new Exception("Failed to download package %s from %s".format(packageId, url)); + } + + Json fetchPackageRecipe(string packageId, Dependency dep, bool pre_release) + { + auto md = getMetadata(packageId); + return getBestPackage(md, packageId, dep, pre_release); + } + + private Json getMetadata(string packageId) + { + auto now = Clock.currTime(UTC()); + if (auto pentry = packageId in m_metadataCache) { + if (pentry.cacheTime + m_maxCacheTime > now) + return pentry.data; + m_metadataCache.remove(packageId); + } + + auto url = m_registryUrl ~ NativePath(PackagesPath ~ "/" ~ packageId ~ ".json"); + + logDebug("Downloading metadata for %s", packageId); + logDebug("Getting from %s", url); + + string jsonData; + foreach(i; 0..3) { + try { + jsonData = cast(string)download(url); + break; + } + catch (HTTPStatusException e) + { + if (e.status == 404) { + logDebug("Package %s not found at %s (404): %s", packageId, description, e.msg); + return Json(null); + } + else { + logDebug("Error getting metadata for package %s at %s (attempt %s of 3): %s", packageId, description, i + 1, e.msg); + if (i == 2) + throw e; + continue; + } + } + } + Json json = parseJsonString(jsonData, url.toString()); + // strip readme data (to save size and time) + foreach (ref v; json["versions"]) + v.remove("readme"); + m_metadataCache[packageId] = CacheEntry(json, now); + return json; + } + + SearchResult[] searchPackages(string query) { + import std.array : array; + import std.algorithm.iteration : map; + import std.uri : encodeComponent; + auto url = m_registryUrl; + url.localURI = "/api/packages/search?q="~encodeComponent(query); + string data; + data = cast(string)download(url); + return data.parseJson.opt!(Json[]) + .map!(j => SearchResult(j["name"].opt!string, j["description"].opt!string, j["version"].opt!string)) + .array; + } +} + diff --git a/source/dub/project.d b/source/dub/project.d index 8e23c84..277b92b 100644 --- a/source/dub/project.d +++ b/source/dub/project.d @@ -17,7 +17,6 @@ import dub.internal.vibecompat.inet.url; import dub.package_; import dub.packagemanager; -import dub.packagesupplier; import dub.generators.generator; import std.algorithm; diff --git a/test/common.sh b/test/common.sh index 7bd2a27..eba424d 100644 --- a/test/common.sh +++ b/test/common.sh @@ -2,12 +2,17 @@ set -ueEo pipefail +function log() { + echo -e "\033[0;33m[INFO] $@\033[0m" + echo "[INFO] $@" >> $(dirname "${BASH_SOURCE[0]}")/test.log +} + # lineno[, msg] function die() { local line=$1 local msg=${2:-command failed} local supplemental=${3:-} - >&2 echo "[ERROR] $SOURCE_FILE:$1 $msg" + echo "[ERROR] $SOURCE_FILE:$1 $msg" | tee -a $(dirname "${BASH_SOURCE[0]}")/test.log | cat 1>&2 if [ ! -z "$supplemental" ]; then echo "$supplemental" | >&2 sed 's|^| |g' fi diff --git a/test/issue1416-maven-repo-pkg-supplier.sh b/test/issue1416-maven-repo-pkg-supplier.sh new file mode 100755 index 0000000..4f106b4 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +DIR=$(dirname "${BASH_SOURCE[0]}") + +. "$DIR"/common.sh + +PORT=$(($$ + 1024)) # PID + 1024 + +dub remove maven-dubpackage --non-interactive --version=* 2>/dev/null || true + +"$DUB" build --single "$DIR"/test_registry.d +"$DIR"/test_registry --folder="$DIR/issue1416-maven-repo-pkg-supplier" --port=$PORT & +PID=$! +sleep 1 +trap 'kill $PID 2>/dev/null || true' exit + +echo "Trying to download maven-dubpackage (1.0.5)" +"$DUB" fetch maven-dubpackage --version=1.0.5 --skip-registry=all --registry=mvn+http://localhost:$PORT/maven/release/dubpackages + +if ! dub remove maven-dubpackage --non-interactive --version=1.0.5 2>/dev/null; then + die 'DUB did not install package from maven registry.' +fi + +echo "Trying to download maven-dubpackage (latest)" +"$DUB" fetch maven-dubpackage --skip-registry=all --registry=mvn+http://localhost:$PORT/maven/release/dubpackages + +if ! dub remove maven-dubpackage --non-interactive --version=1.0.6 2>/dev/null; then + die 'DUB did not install latest package from maven registry.' +fi \ No newline at end of file diff --git a/test/issue1416-maven-repo-pkg-supplier.sh.min_frontend b/test/issue1416-maven-repo-pkg-supplier.sh.min_frontend new file mode 100644 index 0000000..bb0a2e1 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier.sh.min_frontend @@ -0,0 +1 @@ +2.076 diff --git a/test/issue1416-maven-repo-pkg-supplier/.gitignore b/test/issue1416-maven-repo-pkg-supplier/.gitignore new file mode 100644 index 0000000..304e955 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier/.gitignore @@ -0,0 +1,8 @@ +.dub +docs.json +__dummy.html +docs/ +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1416-maven-repo-pkg-supplier/.no_build b/test/issue1416-maven-repo-pkg-supplier/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier/.no_build diff --git a/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/1.0.5/maven-dubpackage-1.0.5.zip b/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/1.0.5/maven-dubpackage-1.0.5.zip new file mode 100644 index 0000000..c3e7d58 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/1.0.5/maven-dubpackage-1.0.5.zip Binary files differ diff --git a/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/1.0.6/maven-dubpackage-1.0.6.zip b/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/1.0.6/maven-dubpackage-1.0.6.zip new file mode 100644 index 0000000..333d920 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/1.0.6/maven-dubpackage-1.0.6.zip Binary files differ diff --git a/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/maven-metadata.xml b/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/maven-metadata.xml new file mode 100644 index 0000000..7b171d5 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/maven-metadata.xml @@ -0,0 +1,14 @@ + + + dubpackages + maven-dubpackage + + 1.0.6 + 1.0.6 + + 1.0.5 + 1.0.6 + + 20180317184845 + + \ No newline at end of file diff --git a/test/issue838-custom-cache-paths.sh b/test/issue838-custom-cache-paths.sh new file mode 100755 index 0000000..da8e84a --- /dev/null +++ b/test/issue838-custom-cache-paths.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +CONFIG_FILE=$CURR_DIR/../etc/dub/settings.json + +mkdir $CURR_DIR/../etc && mkdir $CURR_DIR/../etc/dub || true +echo "{\"customCachePaths\": [\"$CURR_DIR/issue838-custom-cache-paths/cache\"]}" > $CONFIG_FILE + +trap "rm $CONFIG_FILE" EXIT + +if ! { $DUB build --root "$CURR_DIR/issue838-custom-cache-paths" --skip-registry=all; }; then + die $LINENO 'Failed to build package with custom cache path for dependencies.' +fi diff --git a/test/issue838-custom-cache-paths/.no_build b/test/issue838-custom-cache-paths/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue838-custom-cache-paths/.no_build diff --git a/test/issue838-custom-cache-paths/cache/foo-1.0.0/foo/dub.sdl b/test/issue838-custom-cache-paths/cache/foo-1.0.0/foo/dub.sdl new file mode 100644 index 0000000..650050d --- /dev/null +++ b/test/issue838-custom-cache-paths/cache/foo-1.0.0/foo/dub.sdl @@ -0,0 +1,3 @@ +name "foo" +version "1.0.0" +targetType "sourceLibrary" diff --git a/test/issue838-custom-cache-paths/dub.sdl b/test/issue838-custom-cache-paths/dub.sdl new file mode 100644 index 0000000..f4135fc --- /dev/null +++ b/test/issue838-custom-cache-paths/dub.sdl @@ -0,0 +1,2 @@ +name "test" +dependency "foo" version="1.0.0" diff --git a/test/issue838-custom-cache-paths/source/app.d b/test/issue838-custom-cache-paths/source/app.d new file mode 100644 index 0000000..9198103 --- /dev/null +++ b/test/issue838-custom-cache-paths/source/app.d @@ -0,0 +1,3 @@ +void main() +{ +} diff --git a/test/run-unittest.sh b/test/run-unittest.sh index 8d14e33..5f27b93 100755 --- a/test/run-unittest.sh +++ b/test/run-unittest.sh @@ -3,12 +3,16 @@ . $(dirname "${BASH_SOURCE[0]}")/common.sh +> $(dirname "${BASH_SOURCE[0]}")/test.log + function log() { echo -e "\033[0;33m[INFO] $@\033[0m" + echo "[INFO] $@" >> $(dirname "${BASH_SOURCE[0]}")/test.log } function logError() { echo -e 1>&2 "\033[0;31m[ERROR] $@\033[0m" + echo "[ERROR] $@" >> $(dirname "${BASH_SOURCE[0]}")/test.log any_errors=1 } @@ -33,7 +37,10 @@ CURR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) FRONTEND="${FRONTEND:-}" +if [ "$#" -gt 0 ]; then FILTER=$1; else FILTER=".*"; fi + for script in $(ls $CURR_DIR/*.sh); do + if [[ ! "$script" =~ $FILTER ]]; then continue; fi if [ "$script" = "$(readlink -f ${BASH_SOURCE[0]})" ] || [ "$(basename $script)" = "common.sh" ]; then continue; fi if [ -e $script.min_frontend ] && [ ! -z "$FRONTEND" ] && [ ${FRONTEND} \< $(cat $script.min_frontend) ]; then continue; fi log "Running $script..." @@ -41,6 +48,7 @@ done for pack in $(ls -d $CURR_DIR/*/); do + if [[ ! "$pack" =~ $FILTER ]]; then continue; fi if [ -e $pack/.min_frontend ] && [ ! -z "$FRONTEND" -a "$FRONTEND" \< $(cat $pack/.min_frontend) ]; then continue; fi # First we build the packages @@ -70,4 +78,8 @@ fi done +echo +echo 'Testing summary:' +cat $(dirname "${BASH_SOURCE[0]}")/test.log + exit ${any_errors:-0}