diff --git a/changelog/injectsourcefile.dd b/changelog/injectsourcefile.dd new file mode 100644 index 0000000..5421ddc --- /dev/null +++ b/changelog/injectsourcefile.dd @@ -0,0 +1,9 @@ +Adds injection of source files from dependencies via injectSourceFiles command + +Each (sub)package now supports a source file that will be included in any executable or dynamic library that depends either directly or indirectly on it. + +This can be used to register and unregister elements of a package within the dependant package without requiring the dependant to acknowledge that the registration mechanism needs to take place. + +A comparable existing feature to this is the usage of sourceLibrary target type. +A sourceLibrary targetType defers compilation of source code until it is dependent upon by a static library, dynamic library or executable (sub)package. +Unlike sourceLibrary the injection of source code using this feature will inject it into every dynamic library and executable that depends on it, regardless of how deep it is in the dependency graph. diff --git a/examples/injected-from-dependency/dub.json b/examples/injected-from-dependency/dub.json new file mode 100644 index 0000000..bebef9a --- /dev/null +++ b/examples/injected-from-dependency/dub.json @@ -0,0 +1,19 @@ +{ + "description": "Some test code for Have_druntime version", + "name": "injected-from-dependency", + "targetType": "executable", + + "dependencies": { + ":toload": "*" + }, + + "subPackages": [ + { + "name": "toload", + "buildOptions": ["betterC"], + "sourcePaths": ["toload"], + "importPaths": ["toload"], + "injectSourceFiles": ["toload/ahook.d"] + } + ] +} diff --git a/examples/injected-from-dependency/source/entry.d b/examples/injected-from-dependency/source/entry.d new file mode 100644 index 0000000..6b4ed7c --- /dev/null +++ b/examples/injected-from-dependency/source/entry.d @@ -0,0 +1,4 @@ +void main() { + import something; + doSomething(); +} diff --git a/examples/injected-from-dependency/toload/ahook.d b/examples/injected-from-dependency/toload/ahook.d new file mode 100644 index 0000000..bd44019 --- /dev/null +++ b/examples/injected-from-dependency/toload/ahook.d @@ -0,0 +1,14 @@ +module toload.ahook; + +version(D_BetterC) { + pragma(crt_constructor) + extern(C) void someInitializer() { + import core.stdc.stdio; + printf("Hook ran!\n"); + } +} else { + shared static this() { + import std.stdio; + writeln("We have a runtime!!!!"); + } +} diff --git a/examples/injected-from-dependency/toload/something.d b/examples/injected-from-dependency/toload/something.d new file mode 100644 index 0000000..fca88f1 --- /dev/null +++ b/examples/injected-from-dependency/toload/something.d @@ -0,0 +1,12 @@ +module something; + +void doSomething() { + import core.stdc.stdio; + + version(D_BetterC) { + printf("druntime is not in the executable :(\n"); + + } else { + printf("druntime is in executable!\n"); + } +} diff --git a/source/dub/compilers/buildsettings.d b/source/dub/compilers/buildsettings.d index 594d38f..212c22a 100644 --- a/source/dub/compilers/buildsettings.d +++ b/source/dub/compilers/buildsettings.d @@ -31,6 +31,7 @@ string[] libs; string[] linkerFiles; string[] sourceFiles; + string[] injectSourceFiles; string[] copyFiles; string[] extraDependencyFiles; string[] versions; @@ -115,6 +116,7 @@ void addSourceFiles(in string[] value...) { add(sourceFiles, value); } void prependSourceFiles(in string[] value...) { prepend(sourceFiles, value); } void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); } + void addInjectSourceFiles(in string[] value...) { add(injectSourceFiles, value); } void addCopyFiles(in string[] value...) { add(copyFiles, value); } void addExtraDependencyFiles(in string[] value...) { add(extraDependencyFiles, value); } void addVersions(in string[] value...) { add(versions, value); } diff --git a/source/dub/description.d b/source/dub/description.d index 0ebdd5b..f26563b 100644 --- a/source/dub/description.d +++ b/source/dub/description.d @@ -84,6 +84,7 @@ string[] dflags; /// Flags passed to the D compiler string[] lflags; /// Flags passed to the linker string[] libs; /// Librariy names to link against (typically using "-l") + string[] injectSourceFiles; /// Files that should be injected when this package is dependend upon by a binary image. string[] copyFiles; /// Files to copy to the target directory string[] extraDependencyFiles; /// Files to check for rebuild dub project string[] versions; /// D version identifiers to set diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d index ebe4b86..38ea369 100644 --- a/source/dub/generators/generator.d +++ b/source/dub/generators/generator.d @@ -236,8 +236,12 @@ compatible. This also transports all Have_dependency_xyz version identifiers to `rootPackage`. - 4. Filter unused versions and debugVersions from all targets. The - filters have previously been upwards inherited (3.) so that versions + 4. Merge injectSourceFiles from dependencies into their dependents. + This is based upon binary images and will transcend direct relationships + including shared libraries. + + 5. Filter unused versions and debugVersions from all targets. The + filters have previously been upwards inherited (3. and 4.) so that versions used in a dependency are also applied to all dependents. Note: The upwards inheritance is done at last so that siblings do not @@ -420,6 +424,11 @@ } // 3. upwards inherit full build configurations (import paths, versions, debugVersions, versionFilters, importPaths, ...) + + // We do a check for if any dependency uses final binary injection source files, + // otherwise can ignore that bit of workload entirely + bool skipFinalBinaryMerging = true; + void configureDependents(ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0) { // use `visited` here as pkgs cannot depend on themselves @@ -431,19 +440,67 @@ // embedded non-binary dependencies foreach (deppack; ti.packages[1 .. $]) ti.buildSettings.add(targets[deppack.name].buildSettings); + // binary dependencies foreach (depname; ti.dependencies) { auto pdepti = &targets[depname]; + configureDependents(*pdepti, targets, level + 1); mergeFromDependency(pdepti.buildSettings, ti.buildSettings, genSettings.platform); + + if (!pdepti.buildSettings.injectSourceFiles.empty) + skipFinalBinaryMerging = false; } } configureDependents(*roottarget, targets); visited.clear(); - // 4. Filter applicable version and debug version identifiers + // 4. As an extension to configureDependents we need to copy any injectSourceFiles + // in our dependencies (ignoring targetType) + void configureDependentsFinalImages(ref TargetInfo ti, TargetInfo[string] targets, ref TargetInfo finalBinaryTarget, size_t level = 0) + { + // use `visited` here as pkgs cannot depend on themselves + if (ti.pack in visited) + return; + visited[ti.pack] = typeof(visited[ti.pack]).init; + + logDiagnostic("%sConfiguring dependent %s, deps:%(%s, %) for injectSourceFiles", ' '.repeat(2 * level), ti.pack.name, ti.dependencies); + + foreach (depname; ti.dependencies) + { + auto pdepti = &targets[depname]; + + if (!pdepti.buildSettings.injectSourceFiles.empty) + finalBinaryTarget.buildSettings.addSourceFiles(pdepti.buildSettings.injectSourceFiles); + + configureDependentsFinalImages(*pdepti, targets, finalBinaryTarget, level + 1); + } + } + + if (!skipFinalBinaryMerging) + { + foreach (ref target; targets.byValue) + { + switch (target.buildSettings.targetType) + { + case TargetType.executable: + case TargetType.dynamicLibrary: + configureDependentsFinalImages(target, targets, target); + + // We need to clear visited for each target that is executable dynamicLibrary + // due to this process needing to be recursive based upon the final binary targets. + visited.clear(); + break; + + default: + break; + } + } + } + + // 5. Filter applicable version and debug version identifiers if (genSettings.filterVersions) { foreach (name, ref ti; targets) @@ -462,7 +519,7 @@ } } - // 5. override string import files in dependencies + // 6. override string import files in dependencies static void overrideStringImports(ref TargetInfo target, ref TargetInfo parent, TargetInfo[string] targets, string[] overrides) { @@ -513,7 +570,7 @@ overrideStringImports(targets[depname], *roottarget, targets, roottarget.buildSettings.stringImportFiles); - // 6. downwards inherits dependency build settings + // 7. downwards inherits dependency build settings static void applyForcedSettings(const scope ref TargetInfo ti, TargetInfo[string] targets, BuildSettings[string] dependBS, size_t level = 0) { @@ -677,6 +734,7 @@ parent.addDebugVersionFilters(child.debugVersionFilters); parent.addImportPaths(child.importPaths); parent.addStringImportPaths(child.stringImportPaths); + parent.addInjectSourceFiles(child.injectSourceFiles); // linking of static libraries is done by parent if (child.targetType == TargetType.staticLibrary) { parent.addSourceFiles(child.sourceFiles.filter!(f => isLinkerFile(platform, f)).array); diff --git a/source/dub/package_.d b/source/dub/package_.d index cf49f92..98b463b 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -624,6 +624,7 @@ ret.dflags = bs.dflags; ret.lflags = bs.lflags; ret.libs = bs.libs; + ret.injectSourceFiles = bs.injectSourceFiles; ret.copyFiles = bs.copyFiles; ret.versions = bs.versions; ret.debugVersions = bs.debugVersions; diff --git a/source/dub/project.d b/source/dub/project.d index 0152cc0..aeabbbe 100644 --- a/source/dub/project.d +++ b/source/dub/project.d @@ -791,6 +791,7 @@ case "lflags": case "sourceFiles": + case "injectSourceFiles": case "versions": case "debugVersions": case "importPaths": @@ -838,6 +839,7 @@ { case "mainSourceFile": case "linkerFiles": + case "injectSourceFiles": case "copyFiles": case "importFiles": case "stringImportFiles": @@ -926,7 +928,8 @@ enum isRelativeFile = attributeName == "sourceFiles" || attributeName == "linkerFiles" || attributeName == "importFiles" || attributeName == "stringImportFiles" || - attributeName == "copyFiles" || attributeName == "mainSourceFile"; + attributeName == "copyFiles" || attributeName == "mainSourceFile" || + attributeName == "injectSourceFiles"; // For these, empty string means "main project directory", not "missing value" enum allowEmptyString = @@ -1022,6 +1025,7 @@ case "libs": return listBuildSetting!"libs"(args); case "linker-files": return listBuildSetting!"linkerFiles"(args); case "source-files": return listBuildSetting!"sourceFiles"(args); + case "injectSourceFiles": return listBuildSetting!"injectSourceFiles"(args); case "copy-files": return listBuildSetting!"copyFiles"(args); case "extra-dependency-files": return listBuildSetting!"extraDependencyFiles"(args); case "versions": return listBuildSetting!"versions"(args); @@ -1242,6 +1246,9 @@ dst.addPreRunCommands(settings.preRunCommands); dst.addPostRunCommands(settings.postRunCommands); + if (!settings.injectSourceFiles.empty) + dst.addInjectSourceFiles(processVars!true(project, pack, gsettings, settings.injectSourceFiles, true, buildEnvs)); + if (include_target_settings) { dst.targetType = settings.targetType; dst.targetPath = processVars(settings.targetPath, project, pack, gsettings, true, buildEnvs); diff --git a/source/dub/recipe/json.d b/source/dub/recipe/json.d index 0c5597b..c1d776d 100644 --- a/source/dub/recipe/json.d +++ b/source/dub/recipe/json.d @@ -221,6 +221,7 @@ case "sourcePaths": bs.sourcePaths[suffix] = deserializeJson!(string[])(value); break; case "sourcePath": bs.sourcePaths[suffix] ~= [value.get!string]; break; // deprecated case "excludedSourceFiles": bs.excludedSourceFiles[suffix] = deserializeJson!(string[])(value); break; + case "injectSourceFiles": bs.injectSourceFiles[suffix] = deserializeJson!(string[])(value); break; case "copyFiles": bs.copyFiles[suffix] = deserializeJson!(string[])(value); break; case "extraDependencyFiles": bs.extraDependencyFiles[suffix] = deserializeJson!(string[])(value); break; case "versions": bs.versions[suffix] = deserializeJson!(string[])(value); break; @@ -282,6 +283,7 @@ foreach (suffix, arr; bs.sourceFiles) ret["sourceFiles"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.sourcePaths) ret["sourcePaths"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.excludedSourceFiles) ret["excludedSourceFiles"~suffix] = serializeToJson(arr); + foreach (suffix, arr; bs.injectSourceFiles) ret["injectSourceFiles"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.copyFiles) ret["copyFiles"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.extraDependencyFiles) ret["extraDependencyFiles"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.versions) ret["versions"~suffix] = serializeToJson(arr); @@ -376,7 +378,7 @@ parseJson(rec1, jsonValue, null); PackageRecipe rec; parseJson(rec, rec1.toJson(), null); // verify that all fields are serialized properly - + assert(rec.name == "projectname"); assert(rec.buildSettings.environments == ["": ["Var1": "env"]]); assert(rec.buildSettings.buildEnvironments == ["": ["Var2": "buildEnv"]]); diff --git a/source/dub/recipe/packagerecipe.d b/source/dub/recipe/packagerecipe.d index f924c2a..79cecaf 100644 --- a/source/dub/recipe/packagerecipe.d +++ b/source/dub/recipe/packagerecipe.d @@ -190,6 +190,7 @@ string[][string] sourceFiles; string[][string] sourcePaths; string[][string] excludedSourceFiles; + string[][string] injectSourceFiles; string[][string] copyFiles; string[][string] extraDependencyFiles; string[][string] versions; @@ -281,6 +282,7 @@ // collect source files dst.addSourceFiles(collectFiles(sourcePaths, "*.d")); + dst.addInjectSourceFiles(collectFiles(injectSourceFiles, "*.d")); auto sourceFiles = dst.sourceFiles.sort(); // collect import files and remove sources diff --git a/source/dub/recipe/sdl.d b/source/dub/recipe/sdl.d index 177eeb9..e005e11 100644 --- a/source/dub/recipe/sdl.d +++ b/source/dub/recipe/sdl.d @@ -153,6 +153,7 @@ case "sourcePaths": setting.parsePlatformStringArray(bs.sourcePaths); break; case "excludedSourceFiles": setting.parsePlatformStringArray(bs.excludedSourceFiles); break; case "mainSourceFile": bs.mainSourceFile = setting.stringTagValue; break; + case "injectSourceFiles": setting.parsePlatformStringArray(bs.injectSourceFiles); break; case "copyFiles": setting.parsePlatformStringArray(bs.copyFiles); break; case "extraDependencyFiles": setting.parsePlatformStringArray(bs.extraDependencyFiles); break; case "versions": setting.parsePlatformStringArray(bs.versions); break; @@ -285,6 +286,7 @@ foreach (suffix, arr; bs.sourceFiles) adda("sourceFiles", suffix, arr); foreach (suffix, arr; bs.sourcePaths) adda("sourcePaths", suffix, arr); foreach (suffix, arr; bs.excludedSourceFiles) adda("excludedSourceFiles", suffix, arr); + foreach (suffix, arr; bs.injectSourceFiles) adda("injectSourceFiles", suffix, arr); foreach (suffix, arr; bs.copyFiles) adda("copyFiles", suffix, arr); foreach (suffix, arr; bs.extraDependencyFiles) adda("extraDependencyFiles", suffix, arr); foreach (suffix, arr; bs.versions) adda("versions", suffix, arr); @@ -464,6 +466,7 @@ excludedSourceFiles "excluded1" "excluded2" excludedSourceFiles "excluded3" mainSourceFile "main source" +injectSourceFiles "finalbinarysourcefile.d" "extrafile" copyFiles "copy1" "copy2" copyFiles "copy3" extraDependencyFiles "extradepfile1" "extradepfile2" @@ -566,7 +569,8 @@ assert(rec.buildSettings.sourcePaths == ["": ["sourcepath1", "sourcepath2", "sourcepath3"]]); assert(rec.buildSettings.excludedSourceFiles == ["": ["excluded1", "excluded2", "excluded3"]]); assert(rec.buildSettings.mainSourceFile == "main source"); - assert(rec.buildSettings.copyFiles == ["": ["copy1", "copy2", "copy3"]]); + assert(rec.buildSettings.sourceFiles == ["": ["source1", "source2", "source3"]]); + assert(rec.buildSettings.injectSourceFiles == ["": ["finalbinarysourcefile.d", "extrafile"]]); assert(rec.buildSettings.extraDependencyFiles == ["": ["extradepfile1", "extradepfile2", "extradepfile3"]]); assert(rec.buildSettings.versions == ["": ["version1", "version2", "version3"]]); assert(rec.buildSettings.debugVersions == ["": ["debug1", "debug2", "debug3"]]); diff --git a/test/injected-from-dependency/dub.json b/test/injected-from-dependency/dub.json new file mode 100644 index 0000000..95c30fb --- /dev/null +++ b/test/injected-from-dependency/dub.json @@ -0,0 +1,18 @@ +{ + "description": "Test if source file expected to be injected was into binary", + "name": "injected-from-dependency", + "targetType": "executable", + + "dependencies": { + ":toload": "*" + }, + + "subPackages": [ + { + "name": "toload", + "sourcePaths": ["toload"], + "importPaths": ["toload"], + "injectSourceFiles": ["toload/ahook.d"] + } + ] +} diff --git a/test/injected-from-dependency/source/entry.d b/test/injected-from-dependency/source/entry.d new file mode 100644 index 0000000..2c1a0b4 --- /dev/null +++ b/test/injected-from-dependency/source/entry.d @@ -0,0 +1,4 @@ +void main() { + import toload.vars; + assert(valueStoredHere == 1337); +} diff --git a/test/injected-from-dependency/toload/ahook.d b/test/injected-from-dependency/toload/ahook.d new file mode 100644 index 0000000..e98e765 --- /dev/null +++ b/test/injected-from-dependency/toload/ahook.d @@ -0,0 +1,6 @@ +module toload.ahook; + +shared static this() { + import toload.vars; + valueStoredHere = 1337; +} diff --git a/test/injected-from-dependency/toload/vars.d b/test/injected-from-dependency/toload/vars.d new file mode 100644 index 0000000..5ae1a78 --- /dev/null +++ b/test/injected-from-dependency/toload/vars.d @@ -0,0 +1,3 @@ +module toload.vars; + +int valueStoredHere;