bazel: make Starlark profiler work in Bazel

Fixes https://github.com/bazelbuild/bazel/issues/10890

Details:
- configure copybara so that Bazel sees JNI.java.oss as JNI.java.
- use the correct .so library name for Bazel (no "-jni").
- add the dynamic library to to Bazel's install-base using
  the platform's required name (libx.so, libx.dylib, x.dll).
- report errors during command startup, don't just store them. :)
- don't consider SIG_DFL as "signal already in use". The pthreads
  package used by Bazel sets SIGPROF to DFL whereas Blaze's uses IGN.

RELNOTES:
The --starlark_cpu_profile=<file> flag writes a profile in
pprof format containing a statistical summary of CPU usage
by all Starlark execution during the bazel command. Use it
to identify slow Starlark functions in loading and analysis.
PiperOrigin-RevId: 315678587
diff --git a/src/BUILD b/src/BUILD
index 1b03374..0cb189a 100644
--- a/src/BUILD
+++ b/src/BUILD
@@ -12,6 +12,9 @@
     name = "install_base_key-file" + suffix,
     srcs = [
         "//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar",
+        "//src/main/java/com/google/devtools/build/lib/syntax:cpu_profiler",
+        # TODO(brandjon): ensure we haven't forgotten any package-zip items,
+        # otherwise bazel won't correctly reextract modified files.
         "//src/main/cpp:client",
         "//src/main/tools:build-runfiles",
         "//src/main/tools:process-wrapper",
@@ -329,6 +332,8 @@
         "//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar",
         "install_base_key" + suffix,
         ":platforms_archive",
+        # Ordinary items follow:
+        "//src/main/java/com/google/devtools/build/lib/syntax:cpu_profiler",
         "//src/main/native:target-os-unix-native-lib",
         "//src/main/native/windows:target-os-native-lib",
         "//src/main/tools:build-runfiles",
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
index 2d4d6b1..0e67901 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
@@ -349,7 +349,7 @@
         out = new FileOutputStream(commonOptions.starlarkCpuProfile);
       } catch (IOException ex) {
         String message = "Starlark CPU profiler: " + ex.getMessage();
-        storedEventHandler.handle(Event.error(message));
+        outErr.printErrLn(message);
         return createDetailedCommandResult(
             message,
             ExitCode.LOCAL_ENVIRONMENTAL_ERROR,
@@ -359,7 +359,7 @@
         Starlark.startCpuProfile(out, Duration.ofMillis(10));
       } catch (IllegalStateException ex) { // e.g. SIGPROF in use
         String message = Strings.nullToEmpty(ex.getMessage());
-        storedEventHandler.handle(Event.error(message));
+        outErr.printErrLn(message);
         return createDetailedCommandResult(
             message,
             ExitCode.LOCAL_ENVIRONMENTAL_ERROR,
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BUILD b/src/main/java/com/google/devtools/build/lib/syntax/BUILD
index cc7c91b..0088f6b 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BUILD
@@ -122,7 +122,6 @@
         "StringModule.java",
         "Tuple.java",
     ],
-    data = [":libcpu_profiler.so"],  # will be loaded dynamically
     deps = [
         ":frontend",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
@@ -135,6 +134,35 @@
     ],
 )
 
+# Dynamic library for Starlark CPU profiler.
+# The application or test must arrange for the library to be added
+# to the java.library.path directory (e.g. by 'java -Djava.library.path=<dir>').
+# What a mess.
+filegroup(
+    name = "cpu_profiler",
+    srcs = select({
+        "//src/conditions:darwin": [":libcpu_profiler.dylib"],
+        "//src/conditions:windows": [":cpu_profiler.dll"],
+        "//conditions:default": [":libcpu_profiler.so"],  # POSIX
+    }),
+)
+
+genrule(
+    name = "cpu_profiler_darwin",
+    srcs = ["libcpu_profiler.so"],
+    outs = ["libcpu_profiler.dylib"],
+    cmd = "cp $< $@",
+    visibility = ["//visibility:private"],
+)
+
+genrule(
+    name = "cpu_profiler_windows",
+    srcs = ["libcpu_profiler.so"],
+    outs = ["cpu_profiler.dll"],
+    cmd = "cp $< $@",
+    visibility = ["//visibility:private"],
+)
+
 # The C++ portion of the Starlark CPU profiler.
 cc_binary(
     name = "libcpu_profiler.so",
@@ -150,5 +178,6 @@
         "//conditions:default": ["cpu_profiler_unimpl.cc"],
     }),
     linkshared = 1,
+    visibility = ["//visibility:private"],
     deps = ["@bazel_tools//tools/jdk:jni"],
 )
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/JNI.java b/src/main/java/com/google/devtools/build/lib/syntax/JNI.java
index b3d514f..b8de3009 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/JNI.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/JNI.java
@@ -18,8 +18,13 @@
   private JNI() {} // uninstantiable
 
   static void load() {
-    // Google's Java launcher statically links the JNI
-    // dependencies of a java_binary at build time,
-    // so there is nothing to do here.
+    try {
+      System.loadLibrary("cpu_profiler");
+    } catch (UnsatisfiedLinkError ex) {
+      // Ignore, deferring the error until a C function is called, if ever.
+      // Without this hack //src/test/shell/bazel:bazel_bootstrap_distfile_test
+      // fails with an utterly uninformative error.
+      // TODO(adonovan): remove try/catch once that test is fixed.
+    }
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/JNI.java.oss b/src/main/java/com/google/devtools/build/lib/syntax/JNI.java.oss
deleted file mode 100644
index 0e5bfee..0000000
--- a/src/main/java/com/google/devtools/build/lib/syntax/JNI.java.oss
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2020 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.build.lib.syntax;
-
-final class JNI {
-  private JNI() {} // uninstantiable
-
-  static void load() {
-    System.loadLibrary("cpu_profiler-jni");
-  }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/cpu_profiler_posix.cc b/src/main/java/com/google/devtools/build/lib/syntax/cpu_profiler_posix.cc
index 1d42662..c64b30d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/cpu_profiler_posix.cc
+++ b/src/main/java/com/google/devtools/build/lib/syntax/cpu_profiler_posix.cc
@@ -157,9 +157,11 @@
   // Is a handler already in effect?
   // Check for 3-arg and 1-arg forms.
   typedef void (*sighandler_t)(int);  // don't rely on this GNU extension
-  if ((oldact.sa_flags & SA_SIGINFO) != 0
-          ? (reinterpret_cast<sighandler_t>(oldact.sa_sigaction) != SIG_IGN)
-          : (oldact.sa_handler != SIG_IGN)) {
+  sighandler_t prev = (oldact.sa_flags & SA_SIGINFO) != 0
+                          ? reinterpret_cast<sighandler_t>(oldact.sa_sigaction)
+                          : oldact.sa_handler;
+  // The initial handler (DFL or IGN) may vary by thread package.
+  if (prev != SIG_DFL && prev != SIG_IGN) {
     // Someone else is profiling this JVM.
     // Restore their handler and fail.
     (void)sigaction(SIGPROF, &oldact, nullptr);
diff --git a/src/test/shell/integration/loading_phase_test.sh b/src/test/shell/integration/loading_phase_test.sh
index b4a9d1b..f9fd9e7 100755
--- a/src/test/shell/integration/loading_phase_test.sh
+++ b/src/test/shell/integration/loading_phase_test.sh
@@ -472,4 +472,30 @@
   expect_not_log "//foo_prefix"
 }
 
+function test_starlark_cpu_profile() {
+  if $is_windows; then
+    echo "Starlark profiler is not supported on Microsoft Windows."
+    return
+  fi
+
+  mkdir -p test
+  echo 'load("inc.bzl", "main"); main()' > test/BUILD
+  cat >> test/inc.bzl <<'EOF'
+def main():
+   for i in range(2000):
+      foo()
+def foo():
+   list(range(10000))
+   sorted(range(10000))
+main() # uses ~3 seconds of CPU
+EOF
+  bazel query --starlark_cpu_profile="${TEST_TMPDIR}/pprof.gz" test/BUILD
+  # We don't depend on pprof, so just look for some strings in the raw file.
+  gunzip "${TEST_TMPDIR}/pprof.gz"
+  for str in sorted list range foo test/BUILD test/inc.bzl main; do
+    grep -q sorted "${TEST_TMPDIR}/pprof" ||
+      fail "string '$str' not found in profiler output"
+  done
+}
+
 run_suite "Integration tests of ${PRODUCT_NAME} using loading/analysis phases."