docs: enforce that api docs are up-to-date

Previously we just suggested you run a shell script to update, but there was no clue when that was needed in a PR
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 74afc7e..676b60f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -43,15 +43,14 @@
 
 ### Documentation
 
-To regenerate the content under the `docs/` directory, run this script in the
-repository root:
+To regenerate the content under the `docs/` directory, run this command:
 
 ```shell
-./update_docs.sh
+bazel run //docs:update
 ```
 
 This needs to be done whenever the docstrings in the corresponding .bzl files
-are changed; see `docs/BUILD`.
+are changed; a test failure will remind you to run this command when needed.
 
 ### Precompiled tools
 
diff --git a/docs/BUILD b/docs/BUILD
index 0be577d..1cc82d2 100644
--- a/docs/BUILD
+++ b/docs/BUILD
@@ -12,17 +12,22 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-###############################################################################
-# To regenerate documentation, run the top-level update_docs.sh script.       #
-###############################################################################
-
 load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
 load("@io_bazel_stardoc//stardoc:stardoc.bzl", "stardoc")
 
 package(default_visibility = ["//visibility:public"])
 
 licenses(["notice"])  # Apache 2.0
 
+_DOCS = {
+    "packaging": "//docs:packaging-docs",
+    "pip": "//docs:pip-docs",
+    "python": "//docs:core-docs",
+    "whl": "//docs:whl-docs",
+}
+
 # We define these bzl_library targets here rather than in the //python package
 # because they're only used for doc generation. This way, we avoid requiring
 # our users to depend on Skylib.
@@ -68,7 +73,7 @@
 
 stardoc(
     name = "core-docs",
-    out = "python.md",
+    out = "python.md_",
     input = "//python:defs.bzl",
     deps = [":defs"],
 )
@@ -79,7 +84,7 @@
 
 stardoc(
     name = "pip-docs",
-    out = "pip.md",
+    out = "pip.md_",
     input = "//python:pip.bzl",
     deps = [
         ":bazel_repo_tools",
@@ -90,12 +95,40 @@
 
 stardoc(
     name = "whl-docs",
-    out = "whl.md",
+    out = "whl.md_",
     input = "//python:whl.bzl",
 )
 
 stardoc(
     name = "packaging-docs",
-    out = "packaging.md",
+    out = "packaging.md_",
     input = "//python:packaging.bzl",
 )
+
+[
+    diff_test(
+        name = "check_" + k,
+        failure_message = "Please run:   bazel run //docs:update",
+        file1 = k + ".md",
+        file2 = k + ".md_",
+    )
+    for k in _DOCS.keys()
+]
+
+write_file(
+    name = "gen_update",
+    out = "update.sh",
+    content = [
+        "#!/usr/bin/env bash",
+        "cd $BUILD_WORKSPACE_DIRECTORY",
+    ] + [
+        "cp -fv bazel-bin/docs/{0}.md_ docs/{0}.md".format(k)
+        for k in _DOCS.keys()
+    ],
+)
+
+sh_binary(
+    name = "update",
+    srcs = ["update.sh"],
+    data = _DOCS.values(),
+)
diff --git a/internal_deps.bzl b/internal_deps.bzl
index f8b880a..de2226c 100644
--- a/internal_deps.bzl
+++ b/internal_deps.bzl
@@ -11,10 +11,11 @@
     maybe(
         http_archive,
         name = "bazel_skylib",
-        strip_prefix = "bazel-skylib-1.0.2",
-        url = "https://github.com/bazelbuild/bazel-skylib/archive/1.0.2.zip",
-        type = "zip",
-        sha256 = "64ad2728ccdd2044216e4cec7815918b7bb3bb28c95b7e9d951f9d4eccb07625",
+        sha256 = "c6966ec828da198c5d9adbaa94c05e3a1c7f21bd012a0b29ba8ddbccb2c93b0d",
+        urls = [
+            "https://github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
+            "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
+        ],
     )
 
     maybe(
diff --git a/update_docs.sh b/update_docs.sh
deleted file mode 100755
index 764530c..0000000
--- a/update_docs.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-
-# Copyright 2017 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.
-
-set -euo pipefail
-
-bazel build //docs/...
-
-cp bazel-bin/docs/python.md docs/
-cp bazel-bin/docs/pip.md docs/
-cp bazel-bin/docs/whl.md docs/
-cp bazel-bin/docs/packaging.md docs/