Updated min tested Bazel version to 4.0.0 (#533)

* Updated min tested Bazel version to 4.0.0

* Assert that Bazel is at least 4.0.0 LTS

Since we only test against bazel 4.0 now, and will start using features that require it, this gives users
an obvious error message that they need to update.

* Add third_party bzl files to integration test rules_python distro

* remove helloworld test that relies on python2

Co-authored-by: Alex Eagle <eagle@post.harvard.edu>
Co-authored-by: Henry Fuller <hrofuller@gmail.com>
diff --git a/.bazelversion b/.bazelversion
index bea438e..fcdb2e1 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-3.3.1
+4.0.0
diff --git a/.gitignore b/.gitignore
index cc8decd..e3bb55e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,11 +32,11 @@
 *~
 
 # Bazel directories
-bazel-*
-bazel-bin
-bazel-genfiles
-bazel-out
-bazel-testlogs
+/bazel-*
+/bazel-bin
+/bazel-genfiles
+/bazel-out
+/bazel-testlogs
 
 # vim swap files
 *.swp
diff --git a/BUILD b/BUILD
index ad7569b..f548715 100644
--- a/BUILD
+++ b/BUILD
@@ -29,6 +29,7 @@
         "internal_setup.bzl",
         "//python:distribution",
         "//python/pip_install:distribution",
+        "//third_party/github.com/bazelbuild/bazel-skylib/lib:distribution",
         "//tools:distribution",
     ],
     visibility = ["//distro:__pkg__"],
diff --git a/docs/BUILD b/docs/BUILD
index 07877ab..0be577d 100644
--- a/docs/BUILD
+++ b/docs/BUILD
@@ -84,6 +84,7 @@
     deps = [
         ":bazel_repo_tools",
         ":pip_install_bzl",
+        "//third_party/github.com/bazelbuild/bazel-skylib/lib:versions",
     ],
 )
 
diff --git a/examples/legacy_pip_import/WORKSPACE b/examples/legacy_pip_import/WORKSPACE
index bd540c3..6777fb7 100644
--- a/examples/legacy_pip_import/WORKSPACE
+++ b/examples/legacy_pip_import/WORKSPACE
@@ -13,15 +13,6 @@
 pip_repositories()
 
 pip_import(
-    name = "helloworld_deps",
-    requirements = "//helloworld:requirements.txt",
-)
-
-load("@helloworld_deps//:requirements.bzl", _helloworld_install = "pip_install")
-
-_helloworld_install()
-
-pip_import(
     name = "boto_deps",
     requirements = "//boto:requirements.txt",
 )
diff --git a/examples/legacy_pip_import/helloworld/BUILD b/examples/legacy_pip_import/helloworld/BUILD
deleted file mode 100644
index e9f259c..0000000
--- a/examples/legacy_pip_import/helloworld/BUILD
+++ /dev/null
@@ -1,33 +0,0 @@
-# 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.
-
-load("@helloworld_deps//:requirements.bzl", "requirement")
-load("@rules_python//python:defs.bzl", "py_library", "py_test")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])  # Apache 2.0
-
-py_library(
-    name = "helloworld",
-    srcs = ["helloworld.py"],
-    deps = [requirement("futures")],
-)
-
-py_test(
-    name = "helloworld_test",
-    srcs = ["helloworld_test.py"],
-    python_version = "PY2",
-    deps = [":helloworld"],
-)
diff --git a/examples/legacy_pip_import/helloworld/helloworld.py b/examples/legacy_pip_import/helloworld/helloworld.py
deleted file mode 100644
index b629e80..0000000
--- a/examples/legacy_pip_import/helloworld/helloworld.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# 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.
-
-from concurrent import futures
-
-
-class HelloWorld(object):
-  def __init__(self):
-    self._threadpool = futures.ThreadPoolExecutor(max_workers=5)
-
-  def SayHello(self):
-    print("Hello World")
-
-  def SayHelloAsync(self):
-    self._threadpool.submit(self.SayHello)
-
-  def Stop(self):
-    self._threadpool.shutdown(wait = True)
diff --git a/examples/legacy_pip_import/helloworld/helloworld_test.py b/examples/legacy_pip_import/helloworld/helloworld_test.py
deleted file mode 100644
index da6ac77..0000000
--- a/examples/legacy_pip_import/helloworld/helloworld_test.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# 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.
-
-import helloworld
-import unittest
-
-
-class HelloWorldTest(unittest.TestCase):
-
-  def test_helloworld(self):
-    hw = helloworld.HelloWorld()
-    hw.SayHello()
-
-  def test_helloworld_async(self):
-    hw = helloworld.HelloWorld()
-    hw.SayHelloAsync()
-    hw.Stop()
-
-  def test_helloworld_multiple(self):
-    hw = helloworld.HelloWorld()
-    hw.SayHelloAsync()
-    hw.SayHelloAsync()
-    hw.SayHelloAsync()
-    hw.SayHelloAsync()
-    hw.Stop()
-
-
-if __name__ == '__main__':
-  unittest.main()
diff --git a/examples/legacy_pip_import/helloworld/requirements.txt b/examples/legacy_pip_import/helloworld/requirements.txt
deleted file mode 100644
index 372420d..0000000
--- a/examples/legacy_pip_import/helloworld/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-futures>=3.1
diff --git a/python/pip_install/repositories.bzl b/python/pip_install/repositories.bzl
index 5c66752..a2ed84d 100644
--- a/python/pip_install/repositories.bzl
+++ b/python/pip_install/repositories.bzl
@@ -3,6 +3,9 @@
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
 
+# Avoid a load from @bazel_skylib repository as users don't necessarily have it installed
+load("//third_party/github.com/bazelbuild/bazel-skylib/lib:versions.bzl", "versions")
+
 _RULE_DEPS = [
     (
         "pypi__click",
@@ -63,6 +66,13 @@
 
     (However we call it from pip_install, making it optional for users to do so.)
     """
+
+    # We only support Bazel LTS and rolling releases.
+    # Give the user an obvious error to upgrade rather than some obscure missing symbol later.
+    # It's not guaranteed that users call this function, but it's used by all the pip fetch
+    # repository rules so it's likely that most users get the right error.
+    versions.check("4.0.0")
+
     for (name, url, sha256) in _RULE_DEPS:
         maybe(
             http_archive,
diff --git a/third_party/github.com/bazelbuild/bazel-skylib/README.md b/third_party/github.com/bazelbuild/bazel-skylib/README.md
new file mode 100644
index 0000000..5ed93ff
--- /dev/null
+++ b/third_party/github.com/bazelbuild/bazel-skylib/README.md
@@ -0,0 +1,4 @@
+# vendored copy of skylib
+
+This exists so that users of rules_python don't have to install bazel-skylib
+copied from https://github.com/bazelbuild/bazel-skylib/blob/1.0.3
\ No newline at end of file
diff --git a/third_party/github.com/bazelbuild/bazel-skylib/lib/BUILD b/third_party/github.com/bazelbuild/bazel-skylib/lib/BUILD
new file mode 100644
index 0000000..9560aed
--- /dev/null
+++ b/third_party/github.com/bazelbuild/bazel-skylib/lib/BUILD
@@ -0,0 +1,22 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+licenses(["notice"])
+
+package(default_visibility = ["//visibility:public"])
+
+# export bzl files for the documentation
+exports_files(
+    glob(["*.bzl"]),
+    visibility = ["//:__subpackages__"],
+)
+
+filegroup(
+    name = "distribution",
+    srcs = glob(["**"]),
+    visibility = ["//:__pkg__"],
+)
+
+bzl_library(
+    name = "versions",
+    srcs = ["versions.bzl"],
+)
diff --git a/third_party/github.com/bazelbuild/bazel-skylib/lib/versions.bzl b/third_party/github.com/bazelbuild/bazel-skylib/lib/versions.bzl
new file mode 100644
index 0000000..0209a6f
--- /dev/null
+++ b/third_party/github.com/bazelbuild/bazel-skylib/lib/versions.bzl
@@ -0,0 +1,128 @@
+# Copyright 2018 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.
+
+"""Skylib module containing functions for checking Bazel versions."""
+
+def _get_bazel_version():
+    """Returns the current Bazel version"""
+
+    return native.bazel_version
+
+def _extract_version_number(bazel_version):
+    """Extracts the semantic version number from a version string
+
+    Args:
+      bazel_version: the version string that begins with the semantic version
+        e.g. "1.2.3rc1 abc1234" where "abc1234" is a commit hash.
+
+    Returns:
+      The semantic version string, like "1.2.3".
+    """
+    for i in range(len(bazel_version)):
+        c = bazel_version[i]
+        if not (c.isdigit() or c == "."):
+            return bazel_version[:i]
+    return bazel_version
+
+# Parse the bazel version string from `native.bazel_version`.
+# e.g.
+# "0.10.0rc1 abc123d" => (0, 10, 0)
+# "0.3.0" => (0, 3, 0)
+def _parse_bazel_version(bazel_version):
+    """Parses a version string into a 3-tuple of ints
+
+    int tuples can be compared directly using binary operators (<, >).
+
+    Args:
+      bazel_version: the Bazel version string
+
+    Returns:
+      An int 3-tuple of a (major, minor, patch) version.
+    """
+
+    version = _extract_version_number(bazel_version)
+    return tuple([int(n) for n in version.split(".")])
+
+def _is_at_most(threshold, version):
+    """Check that a version is lower or equals to a threshold.
+
+    Args:
+      threshold: the maximum version string
+      version: the version string to be compared to the threshold
+
+    Returns:
+      True if version <= threshold.
+    """
+    return _parse_bazel_version(version) <= _parse_bazel_version(threshold)
+
+def _is_at_least(threshold, version):
+    """Check that a version is higher or equals to a threshold.
+
+    Args:
+      threshold: the minimum version string
+      version: the version string to be compared to the threshold
+
+    Returns:
+      True if version >= threshold.
+    """
+
+    return _parse_bazel_version(version) >= _parse_bazel_version(threshold)
+
+def _check_bazel_version(minimum_bazel_version, maximum_bazel_version = None, bazel_version = None):
+    """Check that the version of Bazel is valid within the specified range.
+
+    Args:
+      minimum_bazel_version: minimum version of Bazel expected
+      maximum_bazel_version: maximum version of Bazel expected
+      bazel_version: the version of Bazel to check. Used for testing, defaults to native.bazel_version
+    """
+    if not bazel_version:
+        if "bazel_version" not in dir(native):
+            fail("Current Bazel version is lower than 0.2.1; expected at least {}".format(
+                minimum_bazel_version,
+            ))
+        elif not native.bazel_version:
+            # Using a non-release version, assume it is good.
+            return
+        else:
+            bazel_version = native.bazel_version
+
+    if not _is_at_least(
+        threshold = minimum_bazel_version,
+        version = bazel_version,
+    ):
+        fail("Current Bazel version is {}; expected at least {}".format(
+            bazel_version,
+            minimum_bazel_version,
+        ))
+
+    if maximum_bazel_version:
+        if not _is_at_most(
+            threshold = maximum_bazel_version,
+            version = bazel_version,
+        ):
+            fail("Current Bazel version is {}; expected at most {}".format(
+                bazel_version,
+                maximum_bazel_version,
+            ))
+
+    pass
+
+versions = struct(
+    get = _get_bazel_version,
+    parse = _parse_bazel_version,
+    check = _check_bazel_version,
+    is_at_most = _is_at_most,
+    is_at_least = _is_at_least,
+)
diff --git a/version.bzl b/version.bzl
index f63feab..37f458a 100644
--- a/version.bzl
+++ b/version.bzl
@@ -20,7 +20,7 @@
 # This version should be updated together with the version of the Bazel
 # in .bazelversion.
 # TODO(alexeagle): assert this is the case in a test
-BAZEL_VERSION = "3.3.1"
+BAZEL_VERSION = "4.0.0"
 
 # Versions of Bazel which users should be able to use.
 # Ensures we don't break backwards-compatibility,