diff --git a/source/dub/compilers/buildsettings.d b/source/dub/compilers/buildsettings.d index 3ba28e1..87a4f1a 100644 --- a/source/dub/compilers/buildsettings.d +++ b/source/dub/compilers/buildsettings.d @@ -73,7 +73,8 @@ addCopyFiles(bs.copyFiles); addVersions(bs.versions); addDebugVersions(bs.debugVersions); - mergeVersionFilters(bs); + addVersionFilters(bs.versionFilters); + addDebugVersionFilters(bs.debugVersionFilters); addImportPaths(bs.importPaths); addStringImportPaths(bs.stringImportPaths); addImportFiles(bs.importFiles); @@ -86,20 +87,6 @@ addPostRunCommands(bs.postRunCommands); } - void mergeVersionFilters(in ref BuildSettings bs) - { - // only filter version identifiers if they are explicitly listed in both - // build settings, so to avoid incorrect filtering - if (versionFilters.length && bs.versionFilters.length) - addVersionFilters(bs.versionFilters); - else - versionFilters = null; - if (debugVersionFilters.length && bs.debugVersionFilters.length) - addDebugVersionFilters(bs.debugVersionFilters); - else - debugVersionFilters = null; - } - void addDFlags(in string[] value...) { dflags ~= value; } void prependDFlags(in string[] value...) { prepend(dflags, value); } void removeDFlags(in string[] value...) { remove(dflags, value); } diff --git a/source/dub/dub.d b/source/dub/dub.d index 5f93227..634a3d4 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -780,6 +780,7 @@ if (existsFile(path ~ ".dub/build")) rmdirRecurse((path ~ ".dub/build").toNativeString()); if (existsFile(path ~ ".dub/obj")) rmdirRecurse((path ~ ".dub/obj").toNativeString()); + if (existsFile(path ~ ".dub/metadata_cache.json")) std.file.remove((path ~ ".dub/metadata_cache.json").toNativeString()); auto p = Package.load(path); if (p.getBuildSettings().targetType == TargetType.none) { diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d index 1d218c8..f158b75 100644 --- a/source/dub/generators/generator.d +++ b/source/dub/generators/generator.d @@ -238,6 +238,9 @@ if (bs.targetType == TargetType.executable) bs.addSourceFiles(mainSourceFiles); } + foreach (ref ti; targets.byValue) + inferVersionFilters(ti); + // mark packages as visited (only used during upwards propagation) void[0][Package] visited; @@ -365,18 +368,14 @@ import std.algorithm.sorting : partition; auto bs = &ti.buildSettings; - if (bs.versionFilters.length) - { - auto filtered = bs.versions.partition!(v => bs.versionFilters.canFind(v)); - logDebug("Filtering out unused versions for %s: %s", name, filtered); - bs.versions = bs.versions[0 .. $ - filtered.length]; - } - if (bs.debugVersionFilters.length) - { - auto filtered = bs.debugVersions.partition!(v => bs.debugVersionFilters.canFind(v)); - logDebug("Filtering out unused debug versions for %s: %s", name, filtered); - bs.debugVersions = bs.debugVersions[0 .. $ - filtered.length]; - } + + auto filtered = bs.versions.partition!(v => bs.versionFilters.canFind(v)); + logDebug("Filtering out unused versions for %s: %s", name, filtered); + bs.versions = bs.versions[0 .. $ - filtered.length]; + + filtered = bs.debugVersions.partition!(v => bs.debugVersionFilters.canFind(v)); + logDebug("Filtering out unused debug versions for %s: %s", name, filtered); + bs.debugVersions = bs.debugVersions[0 .. $ - filtered.length]; } // 5. override string import files in dependencies @@ -417,8 +416,92 @@ if (!hasOutput[name]) targets.remove(name); } + } - return mainSourceFiles; + // infer applicable version identifiers + private static void inferVersionFilters(ref TargetInfo ti) + { + import std.algorithm.searching : any; + import std.file : timeLastModified; + import std.range : chain; + import std.regex : ctRegex, matchAll; + import std.stdio : File; + import std.datetime : Clock, SysTime, UTC; + import dub.internal.vibecompat.data.json : Json, JSONException; + + auto bs = &ti.buildSettings; + + // only infer if neither version filters are specified explicitly + if (bs.versionFilters.length || bs.debugVersionFilters.length) + { + logDebug("Using specified versionFilters for %s: %s %s", ti.pack.name, + bs.versionFilters, bs.debugVersionFilters); + return; + } + + // check all sources for version identifiers + auto srcs = chain(bs.sourceFiles, bs.importFiles, bs.stringImportFiles); + // try to load cached filters first + auto cache = ti.pack.metadataCache; + try + { + auto cachedFilters = cache["versionFilters"]; + if (cachedFilters.type != Json.Type.undefined) + cachedFilters = cachedFilters[ti.config]; + if (cachedFilters.type != Json.Type.undefined) + { + immutable mtime = SysTime.fromISOExtString(cachedFilters["mtime"].get!string); + if (!srcs.any!(src => src.timeLastModified > mtime)) + { + auto versionFilters = cachedFilters["versions"][].map!(j => j.get!string).array; + auto debugVersionFilters = cachedFilters["debugVersions"][].map!(j => j.get!string).array; + logDebug("Using cached versionFilters for %s: %s %s", ti.pack.name, + versionFilters, debugVersionFilters); + bs.addVersionFilters(versionFilters); + bs.addDebugVersionFilters(debugVersionFilters); + return; + } + } + } + catch (JSONException e) + { + logWarn("Exception during loading invalid package cache %s.\n%s", + ti.pack.path ~ ".dub/metadata_cache.json", e); + } + + // use ctRegex for performance reasons, only small compile time increase + enum verRE = ctRegex!`(?:^|\s)version\s*\(\s*([^\s]*?)\s*\)`; + enum debVerRE = ctRegex!`(?:^|\s)debug\s*\(\s*([^\s]*?)\s*\)`; + + auto versionFilters = appender!(string[]); + auto debugVersionFilters = appender!(string[]); + + foreach (file; srcs) + { + foreach (line; File(file).byLine) + { + foreach (m; line.matchAll(verRE)) + if (!versionFilters.data.canFind(m[1])) + versionFilters.put(m[1].idup); + foreach (m; line.matchAll(debVerRE)) + if (!debugVersionFilters.data.canFind(m[1])) + debugVersionFilters.put(m[1].idup); + } + } + logDebug("Using inferred versionFilters for %s: %s %s", ti.pack.name, + versionFilters.data, debugVersionFilters.data); + bs.addVersionFilters(versionFilters.data); + bs.addDebugVersionFilters(debugVersionFilters.data); + + auto cachedFilters = cache["versionFilters"]; + if (cachedFilters.type == Json.Type.undefined) + cachedFilters = cache["versionFilters"] = [ti.config: Json.emptyObject]; + cachedFilters[ti.config] = [ + "mtime": Json(Clock.currTime(UTC()).toISOExtString), + "versions": Json(versionFilters.data.map!Json.array), + "debugVersions": Json(debugVersionFilters.data.map!Json.array), + ]; + ti.pack.metadataCache = cache; } private static void mergeFromDependent(in ref BuildSettings parent, ref BuildSettings child) @@ -435,7 +518,8 @@ parent.addDFlags(child.dflags); parent.addVersions(child.versions); parent.addDebugVersions(child.debugVersions); - parent.mergeVersionFilters(child); + parent.addVersionFilters(child.versionFilters); + parent.addDebugVersionFilters(child.debugVersionFilters); parent.addImportPaths(child.importPaths); parent.addStringImportPaths(child.stringImportPaths); // linking of static libraries is done by parent @@ -474,6 +558,7 @@ int targetExitStatus; bool combined; // compile all in one go instead of each dependency separately + bool filterVersions; // only used for generator "build" bool run, force, direct, rdmd, tempBuild, parallelBuild; diff --git a/source/dub/package_.d b/source/dub/package_.d index bac7020..5da9201 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -274,6 +274,22 @@ dstFile.writePrettyJsonString(m_info.toJson()); } + /// Get the metadata cache for this package + @property Json metadataCache() + { + enum silent_fail = true; + return jsonFromFile(m_path ~ ".dub/metadata_cache.json", silent_fail); + } + + /// Write metadata cache for this package + @property void metadataCache(Json json) + { + enum create_if_missing = true; + if (isWritableDir(m_path ~ ".dub", create_if_missing)) + writeJsonFile(m_path ~ ".dub/metadata_cache.json", json); + // TODO: store elsewhere + } + /** Returns the package recipe of a non-path-based sub package. For sub packages that are declared within the package recipe of the