diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f86f2f74df..c33b57367c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -102,7 +102,7 @@ jobs: # FIXME: DMD fails a few tests on Windows; remove them for now if [[ '${{ matrix.dc }}' = dmd* ]]; then # DLL support is lacking - rm -rf test/{1-dynLib-simple,2-dynLib-dep,2-dynLib-with-staticLib-dep} + rm -rf test/{1-dynLib-simple,2-dynLib-dep,2-dynLib-with-staticLib-dep,dynLib-monolith} # Unicode in paths too rm -rf test/issue130-unicode-СНА* # ImportC probably requires set-up MSVC environment variables diff --git a/changelog/ldc_visibility.dd b/changelog/ldc_visibility.dd new file mode 100644 index 0000000000..f172d70c21 --- /dev/null +++ b/changelog/ldc_visibility.dd @@ -0,0 +1,20 @@ +LDC: Apply dflags affecting symbol visibility to all deps + +The `-fvisibility` (and, on Windows, `-dllimport` and `-link-defaultlib-shared`) +flags in effect for some root project are now passed down to all (direct and indirect) +dependencies of that root project - when using LDC. This is mainly important on +Windows, for executables linked against shared druntime/Phobos, as well as DLLs. + +For Windows executables to be linked against the druntime/Phobos DLLs via +`-link-defaultlib-shared`, this means that all its static-library dub dependencies +are now automatically compiled with `-link-defaultlib-shared` too, thus defaulting to +`-dllimport=defaultLibsOnly`, which is a (Windows-specific) requirement for this +scenario. + +With LDC, code ending up in a Windows DLL is compiled with `-fvisibility=public +-dllimport=all` by default (implicit flags added by dub). This can now be overridden +via e.g. `-fvisibility=hidden -dllimport=defaultLibsOnly` to generate a DLL exporting +only select symbols with explicit `export` visibility and linking all dub deps +statically (but druntime/Phobos dynamically). If you want to additionally link druntime +and Phobos statically into the DLL, use `-link-defaultlib-shared=false +-fvisibility=hidden -dllimport=none` instead. diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d index 88d719ef6e..b6d1b05dac 100644 --- a/source/dub/generators/generator.d +++ b/source/dub/generators/generator.d @@ -459,6 +459,39 @@ class ProjectGenerator configureDependents(*roottarget, targets); visited.clear(); + // LDC: need to pass down dflags affecting symbol visibility, especially on Windows + if (genSettings.platform.compiler == "ldc") + { + const isWindows = genSettings.platform.isWindows(); + bool passDownDFlag(string flag) + { + if (flag.startsWith("--")) + flag = flag[1 .. $]; + return flag.startsWith("-fvisibility=") || (isWindows && + (flag.startsWith("-link-defaultlib-shared") || + flag.startsWith("-dllimport="))); + } + + // all dflags from dependencies have already been added to the root project + auto rootDFlagsToPassDown = roottarget.buildSettings.dflags.filter!passDownDFlag.array; + + if (rootDFlagsToPassDown.length) + { + foreach (name, ref ti; targets) + { + if (&ti != roottarget && ti.buildSettings.targetType != TargetType.dynamicLibrary) + { + import std.range : chain; + ti.buildSettings.dflags = ti.buildSettings.dflags + // remove all existing visibility flags first to reduce duplicates + .filter!(f => !passDownDFlag(f)) + .chain(rootDFlagsToPassDown) + .array; + } + } + } + } + // 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) diff --git a/test/dynLib-monolith.script.d b/test/dynLib-monolith.script.d new file mode 100644 index 0000000000..f31b643801 --- /dev/null +++ b/test/dynLib-monolith.script.d @@ -0,0 +1,39 @@ +/+ dub.json: { + "name": "dynlib-monolith-script" +} +/ +module dynlib_monolith_script; + +import std.algorithm, std.path, std.process, std.stdio; + +int main() +{ + const dc = environment.get("DC", "dmd"); + if (!dc.startsWith("ldc")) + { + writeln("Skipping test, needs LDC"); + return 0; + } + + // enforce a full build (2 static libs, 1 dynamic one) and collect -v output + enum projDir = dirName(__FILE_FULL_PATH__).buildPath("dynLib-monolith"); + const res = execute([environment.get("DUB", "dub"), "build", "-f", "-v", "--root", projDir]); + + int errorOut(string msg) + { + writeln("Error: " ~ msg); + writeln("==========================================================="); + writeln(res.output); + writeln("==========================================================="); + return 1; + } + + if (res.status != 0) + return errorOut("The dub invocation failed:"); + + version (Windows) enum needle = " -fvisibility=hidden -dllimport=defaultLibsOnly"; + else enum needle = " -fvisibility=hidden"; + if (res.output.count(needle) != 3) + return errorOut("Cannot find exactly 3 occurrences of '" ~ needle ~ "' in the verbose dub output:"); + + return 0; +} diff --git a/test/dynLib-monolith/.no_run b/test/dynLib-monolith/.no_run new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/dynLib-monolith/.no_test b/test/dynLib-monolith/.no_test new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/dynLib-monolith/dub.sdl b/test/dynLib-monolith/dub.sdl new file mode 100644 index 0000000000..4c68861ee1 --- /dev/null +++ b/test/dynLib-monolith/dub.sdl @@ -0,0 +1,17 @@ +name "dynlib-monolith" +description "A 'monolithic' dynamic library, linking all dependencies statically (except for shared druntime/Phobos) and exporting only select symbols explicitly via 'export'" +targetType "dynamicLibrary" +dependency "inner_dep" path="inner_dep" + +# With LDC, `-fvisibility=hidden` is passed down for compiling all dependencies. +dflags "-fvisibility=hidden" platform="ldc" +dflags "-fvisibility=hidden" platform="gdc" +# not supported by DMD + +# With LDC on Windows, code ending up in a DLL is compiled with +# `-fvisibility=public -dllimport=all` by default. So we additionally need to +# override `-dllimport` to dllimport from the shared druntime/Phobos DLLs only +# (used by default for dynamic libraries; with static druntime/Phobos via +# `-link-defaultlib-shared=false`, use `-dllimport=none`). +# This flag is also passed down to all deps. +dflags "-dllimport=defaultLibsOnly" platform="windows-ldc" diff --git a/test/dynLib-monolith/inner_dep/dub.sdl b/test/dynLib-monolith/inner_dep/dub.sdl new file mode 100644 index 0000000000..4535eaaf14 --- /dev/null +++ b/test/dynLib-monolith/inner_dep/dub.sdl @@ -0,0 +1,4 @@ +name "inner_dep" +description "A static library depending on another static library" +targetType "staticLibrary" +dependency "staticlib-simple" path="../../1-staticLib-simple" diff --git a/test/dynLib-monolith/inner_dep/source/inner_dep/mod.d b/test/dynLib-monolith/inner_dep/source/inner_dep/mod.d new file mode 100644 index 0000000000..123866e7f3 --- /dev/null +++ b/test/dynLib-monolith/inner_dep/source/inner_dep/mod.d @@ -0,0 +1,7 @@ +module inner_dep.mod; + +void innerDepFunction() +{ + import staticlib.app; + entry(); +} diff --git a/test/dynLib-monolith/source/library.d b/test/dynLib-monolith/source/library.d new file mode 100644 index 0000000000..80bef37022 --- /dev/null +++ b/test/dynLib-monolith/source/library.d @@ -0,0 +1,7 @@ +module library; + +export void foo() +{ + import inner_dep.mod; + innerDepFunction(); +} diff --git a/test/exe-shared-defaultlib/.no_test b/test/exe-shared-defaultlib/.no_test new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/exe-shared-defaultlib/dub.sdl b/test/exe-shared-defaultlib/dub.sdl new file mode 100644 index 0000000000..5068e0996e --- /dev/null +++ b/test/exe-shared-defaultlib/dub.sdl @@ -0,0 +1,11 @@ +name "exe-shared-defaultlib" +description "An executable with a statically linked dep, linked against *shared* druntime/Phobos" +targetType "executable" +dependency "staticlib-simple" path="../1-staticLib-simple/" + +# With LDC on Windows, `-link-defaultlib-shared` is passed down for compiling +# all dependencies (for an implicit `-dllimport=defaultLibsOnly`). +dflags "-link-defaultlib-shared" platform="ldc" + +# no libphobos2.dll/dylib with DMD on Windows/Mac +dflags "-defaultlib=libphobos2.so" platform="linux-dmd" diff --git a/test/exe-shared-defaultlib/source/app.d b/test/exe-shared-defaultlib/source/app.d new file mode 100644 index 0000000000..36aa99b121 --- /dev/null +++ b/test/exe-shared-defaultlib/source/app.d @@ -0,0 +1,7 @@ +module app; +import staticlib.app; + +void main() +{ + entry(); +}