Fix python zip file name when target name has dot (.)

To fix https://buildkite.com/bazel/bazel-at-head-plus-disabled/builds/351#26e616ad-56b1-4d78-b64c-fcb3c5c66f41/436-470

When building python zip file on linux and mac, we accidentally remove the last segment when a python target name has dot (.).
For example, `create_tensorflow.python_api_2_keras_python_api_gen_compat_v2 -> create_tensorflow`

This commit replace the usage of `ruleContext.getDerivedArtifact` with a custom implementation to fix this problem.

Closes #10119.

PiperOrigin-RevId: 277685509
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
index edda16d..8817271 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
@@ -131,14 +131,6 @@
     return result;
   }
 
-  /**
-   * Returns an artifact next to the executable file with ".temp" suffix. Used only if we're
-   * building a zip.
-   */
-  public Artifact getPythonIntermediateStubArtifact(RuleContext ruleContext, Artifact executable) {
-    return ruleContext.getRelatedArtifact(executable.getRootRelativePath(), ".temp");
-  }
-
   private static String boolToLiteral(boolean value) {
     return value ? "True" : "False";
   }
@@ -308,7 +300,7 @@
 
     if (!ruleContext.hasErrors()) {
       // Create the stub file that's needed by the python zip file.
-      Artifact stubFileForZipFile = getPythonIntermediateStubArtifact(ruleContext, executable);
+      Artifact stubFileForZipFile = common.getPythonIntermediateStubArtifact(executable);
       createStubFile(ruleContext, stubFileForZipFile, common, /* isForZipFile= */ true);
 
       createPythonZipAction(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java
index ede7f3a..161cc8b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java
@@ -858,9 +858,34 @@
     addPyExtraActionPseudoAction();
   }
 
-  /** @return An artifact next to the executable file with ".zip" suffix */
+  /** @return an artifact next to the executable file with a given suffix. */
+  private Artifact getArtifactWithExtension(Artifact executable, String extension) {
+    // On Windows, the Python executable has .exe extension on Windows,
+    // On Linux, the Python executable has no extension.
+    // We can't use ruleContext#getRelatedArtifact because it would mangle files with dots in the
+    // name on non-Windows platforms.
+    PathFragment pathFragment = executable.getRootRelativePath();
+    String fileName = executable.getFilename();
+    if (OS.getCurrent() == OS.WINDOWS) {
+      Preconditions.checkArgument(fileName.endsWith(".exe"));
+      fileName = fileName.substring(0, fileName.length() - 4) + extension;
+    } else {
+      fileName = fileName + extension;
+    }
+    return ruleContext.getDerivedArtifact(pathFragment.replaceName(fileName), executable.getRoot());
+  }
+
+  /** Returns an artifact next to the executable file with ".zip" suffix. */
   public Artifact getPythonZipArtifact(Artifact executable) {
-    return ruleContext.getRelatedArtifact(executable.getRootRelativePath(), ".zip");
+    return getArtifactWithExtension(executable, ".zip");
+  }
+
+  /**
+   * Returns an artifact next to the executable file with ".temp" suffix. Used only if we're
+   * building a zip.
+   */
+  public Artifact getPythonIntermediateStubArtifact(Artifact executable) {
+    return getArtifactWithExtension(executable, ".temp");
   }
 
   /** Returns an artifact next to the executable file with no suffix. Only called for Windows. */
diff --git a/src/test/py/bazel/py_test.py b/src/test/py/bazel/py_test.py
index 085ec2b..b770e10 100644
--- a/src/test/py/bazel/py_test.py
+++ b/src/test/py/bazel/py_test.py
@@ -114,6 +114,23 @@
     self.assertFalse(
         os.path.exists('bazel-bin/src/a/a.runfiles/__main__/src/a/__init__.py'))
 
+  # Regression test for https://github.com/bazelbuild/bazel/pull/10119
+  def testBuildingZipFileWithTargetNameWithDot(self):
+    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
+    self.ScratchFile('BUILD', [
+        'py_binary(',
+        '  name = "bin.v1",  # .v1 should not be treated as extension and removed accidentally',
+        '  srcs = ["bin.py"],',
+        '  main = "bin.py",',
+        ')',
+    ])
+    self.ScratchFile('bin.py', 'print("Hello, world")')
+    exit_code, _, stderr = self.RunBazel(
+        ['build', '--build_python_zip', '//:bin.v1'])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertTrue(os.path.exists('bazel-bin/bin.v1.temp'))
+    self.assertTrue(os.path.exists('bazel-bin/bin.v1.zip'))
+
 
 if __name__ == '__main__':
   unittest.main()