Add option rename dll on Windows
@meteorcloudy
This PR will add an option rename dll on windows.
It won't change any name in the cc_binary and cc_library. It will only change the name when we copy the DLL to the binary directory and the name in the def file
https://github.com/bazelbuild/bazel/blob/1c03c8c4fac1e1028fcb5bb342da3a7fdfc88327/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java#L1054
It is just a draft and it can not handle correctly the file name extension ( only dll for now )
Closes #11524.
PiperOrigin-RevId: 316650491
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibrary.java
index e4ed503..0dc31a1 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibrary.java
@@ -314,6 +314,8 @@
if (ruleContext.getRule().getImplicitOutputsFunction() != ImplicitOutputsFunction.NONE
|| !ccCompilationOutputs.isEmpty()) {
if (featureConfiguration.isEnabled(CppRuleClasses.TARGETS_WINDOWS)) {
+ String dllNameSuffix = CppHelper.getDLLHashSuffix(ruleContext, featureConfiguration);
+ linkingHelper.setLinkedDLLNameSuffix(dllNameSuffix);
Artifact generatedDefFile = null;
Artifact defParser = common.getDefParser();
@@ -327,7 +329,8 @@
ccToolchain
.getFeatures()
.getArtifactNameForCategory(
- ArtifactCategory.DYNAMIC_LIBRARY, ruleContext.getLabel().getName()));
+ ArtifactCategory.DYNAMIC_LIBRARY,
+ ruleContext.getLabel().getName() + dllNameSuffix));
targetBuilder.addOutputGroup(DEF_FILE_OUTPUT_GROUP_NAME, generatedDefFile);
} catch (EvalException e) {
throw ruleContext.throwWithRuleError(e);
@@ -631,7 +634,7 @@
ccToolchain,
configuration,
Link.LinkTargetType.NODEPS_DYNAMIC_LIBRARY,
- /* linkedArtifactNameSuffix= */ ""));
+ CppHelper.getDLLHashSuffix(ruleContext, featureConfiguration)));
if (CppHelper.useInterfaceSharedLibraries(
cppConfiguration, ccToolchain, featureConfiguration)) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingHelper.java
index 91fdc9b..a742ef3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingHelper.java
@@ -117,6 +117,7 @@
private final CcToolchainProvider ccToolchain;
private final FdoContext fdoContext;
private String linkedArtifactNameSuffix = "";
+ private String linkedDLLNameSuffix = "";
private final ActionConstructionContext actionConstructionContext;
private final Label label;
@@ -312,6 +313,18 @@
return this;
}
+ /*
+ * Adds a suffix (_{hash}) for path of linked DLL. This will be enabled only when rename_dll is
+ * enabled and the linked artifact's LinkTargetType is NODEPS_DYNAMIC_LIBRARY
+ * (i.e DLL generated by cc_library). We have to add the suffix to make sure the CppLinkAction
+ * link against the renamed DLL. If not, CppLinkAction will link against the DLL whose name is the
+ * same as the name of cc_library.
+ */
+ public CcLinkingHelper setLinkedDLLNameSuffix(String suffix) {
+ this.linkedDLLNameSuffix = Preconditions.checkNotNull(suffix);
+ return this;
+ }
+
/**
* Enables the optional generation of interface dynamic libraries - this is only used when the
* linker generates a dynamic library, and only if the crosstool supports it. The default is not
@@ -862,9 +875,12 @@
CppHelper.getArtifactNameForCategory(
ruleErrorConsumer, ccToolchain, ArtifactCategory.PIC_FILE, maybePicName);
}
- String linkedName =
- CppHelper.getArtifactNameForCategory(
- ruleErrorConsumer, ccToolchain, linkTargetType.getLinkerOutput(), maybePicName);
+ String linkedName =
+ maybePicName
+ + (linkTargetType == LinkTargetType.NODEPS_DYNAMIC_LIBRARY ? linkedDLLNameSuffix : "");
+ linkedName =
+ CppHelper.getArtifactNameForCategory(
+ ruleErrorConsumer, ccToolchain, linkTargetType.getLinkerOutput(), linkedName);
PathFragment artifactFragment =
PathFragment.create(label.getName()).getParentDirectory().getRelative(linkedName);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
index 6b0c89d..246b78b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
@@ -63,12 +63,14 @@
import com.google.devtools.build.lib.rules.cpp.CcLinkingContext.Linkstamp;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.ExpansionException;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration.DynamicMode;
import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
import com.google.devtools.build.lib.shell.ShellUtils;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.StarlarkSemantics;
import com.google.devtools.build.lib.util.FileTypeSet;
+import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
@@ -930,6 +932,23 @@
}
}
+ /** Returns the suffix (_{hash}) for artifacts generated by cc_library on Windows. */
+ public static String getDLLHashSuffix(
+ RuleContext ruleContext, FeatureConfiguration featureConfiguration) {
+ CppOptions cppOptions =
+ Preconditions.checkNotNull(
+ ruleContext.getConfiguration().getOptions().get(CppOptions.class));
+ if (cppOptions.renameDLL
+ && cppOptions.dynamicMode != DynamicMode.OFF
+ && featureConfiguration.isEnabled(CppRuleClasses.TARGETS_WINDOWS)) {
+ Fingerprint digest = new Fingerprint();
+ digest.addString(ruleContext.getRepository().getName());
+ digest.addPath(ruleContext.getPackageDirectory());
+ return "_" + digest.hexDigestAndReset().substring(0, 10);
+ }
+ return "";
+ }
+
/**
* Returns true if the build implied by the given config and toolchain uses --start-lib/--end-lib
* ld options.
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
index 56ff203..16ef0fc 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
@@ -245,6 +245,23 @@
public DynamicMode dynamicMode;
@Option(
+ name = "incompatible_avoid_conflict_dlls",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS,
+ effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS, OptionEffectTag.AFFECTS_OUTPUTS},
+ metadataTags = {
+ OptionMetadataTag.INCOMPATIBLE_CHANGE,
+ OptionMetadataTag.TRIGGERED_BY_ALL_INCOMPATIBLE_CHANGES
+ },
+ help =
+ "If enabled, all C++ dynamic linked libraries (DLLs) generated by cc_library on Windows "
+ + "will be renamed to name_{hash}.dll where hash is calculated based on "
+ + "the RepositoryName and the DLL's package path. This option is useful "
+ + "when you have one package which depends on severals cc_library with the same name "
+ + "(e.g //foo/bar1:utils and //foo/bar2:utils).")
+ public boolean renameDLL;
+
+ @Option(
name = "force_pic",
defaultValue = "false",
documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS,
diff --git a/src/test/py/bazel/bazel_windows_cpp_test.py b/src/test/py/bazel/bazel_windows_cpp_test.py
index 4e8f17e..fabdc10 100644
--- a/src/test/py/bazel/bazel_windows_cpp_test.py
+++ b/src/test/py/bazel/bazel_windows_cpp_test.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import glob
import os
import unittest
from src.test.py.bazel import test_base
@@ -249,15 +250,25 @@
' linkstatic = 0,'
')',
])
+ bazel_bin = self.getBazelInfo('bazel-bin')
- # //main:main depends on both //lib:A and //:A,
- # their dlls are both called A.dll,
- # so there should be a conflict error
- exit_code, _, stderr = self.RunBazel(['build', '//main:main'])
- self.AssertExitCode(exit_code, 1, stderr)
- self.assertIn(
- 'ERROR: file \'main/A.dll\' is generated by these conflicting '
- 'actions:', ''.join(stderr))
+ # //main:main depends on both //lib:A and //:A
+ exit_code, _, stderr = self.RunBazel(
+ ['build', '//main:main', '--incompatible_avoid_conflict_dlls'])
+ self.AssertExitCode(exit_code, 0, stderr)
+
+ # Run the binary to see if it runs successfully
+ main_bin = os.path.join(bazel_bin, 'main/main.exe')
+ exit_code, stdout, stderr = self.RunProgram([main_bin])
+ self.AssertExitCode(exit_code, 0, stderr)
+ self.assertEqual(['Hello A, 1', 'Hello A, 2', 'Hello B', 'Hello C'], stdout)
+ # There are 2 A_{hash}.dll since //main:main depends on both //lib:A and
+ # //:A
+ self.assertEqual(
+ len(glob.glob(os.path.join(bazel_bin, 'main', 'A_*.dll'))), 2)
+ # There is only 1 B_{hash}.dll
+ self.assertEqual(
+ len(glob.glob(os.path.join(bazel_bin, 'main', 'B_*.dll'))), 1)
def testBuildDifferentCcBinariesDependOnConflictDLLs(self):
self.createProjectFiles()
@@ -278,19 +289,42 @@
' linkstatic = 0,'
')',
])
+ bazel_bin = self.getBazelInfo('bazel-bin')
self.ScratchFile('main/other_main.cc', ['int main() {return 0;}'])
# Building //main:main should succeed
- exit_code, _, stderr = self.RunBazel(['build', '//main:main'])
- self.AssertExitCode(exit_code, 0, stderr)
-
- # Building //main:other_main *and* //main:main should fail
exit_code, _, stderr = self.RunBazel(
- ['build', '//main:main', '//main:other_main'])
- self.AssertExitCode(exit_code, 1, stderr)
- self.assertIn(
- 'ERROR: file \'main/A.dll\' is generated by these conflicting '
- 'actions:', ''.join(stderr))
+ ['build', '//main:main', '--incompatible_avoid_conflict_dlls'])
+ self.AssertExitCode(exit_code, 0, stderr)
+ main_bin = os.path.join(bazel_bin, 'main/main.exe')
+
+ # Run the main_bin binary to see if it runs successfully
+ exit_code, stdout, stderr = self.RunProgram([main_bin])
+ self.AssertExitCode(exit_code, 0, stderr)
+ self.assertEqual(['Hello A, 1', 'Hello A, 2', 'Hello B', 'Hello C'], stdout)
+ # There is only 1 A_{hash}.dll since //main:main depends transitively on
+ # //:A
+ self.assertEqual(
+ len(glob.glob(os.path.join(bazel_bin, 'main', 'A_*.dll'))), 1)
+ # There is only 1 B_{hash}.dll
+ self.assertEqual(
+ len(glob.glob(os.path.join(bazel_bin, 'main', 'B_*.dll'))), 1)
+
+ # Building //main:other_main should succeed
+ exit_code, _, stderr = self.RunBazel([
+ 'build', '//main:main', '//main:other_main',
+ '--incompatible_avoid_conflict_dlls'
+ ])
+ self.AssertExitCode(exit_code, 0, stderr)
+ other_main_bin = os.path.join(bazel_bin, 'main/other_main.exe')
+
+ # Run the other_main_bin binary to see if it runs successfully
+ exit_code, stdout, stderr = self.RunProgram([other_main_bin])
+ self.AssertExitCode(exit_code, 0, stderr)
+ # There are 2 A_{hash}.dll since //main:main depends on //:A
+ # and //main:other_main depends on //lib:A
+ self.assertEqual(
+ len(glob.glob(os.path.join(bazel_bin, 'main', 'A_*.dll'))), 2)
def testDLLIsCopiedFromExternalRepo(self):
self.ScratchFile('ext_repo/WORKSPACE')