Enabled CI for Windows (#585)
Co-authored-by: Alex Eagle <eagle@post.harvard.edu>
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index 09af992..3e16b24 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -4,17 +4,33 @@
# keep this argument in sync with .pre-commit-config.yaml
warnings: "all"
all_targets: &all_targets
- build_targets:
+ build_targets:
- "..."
# As a regression test for #225, check that wheel targets still build when
# their package path is qualified with the repo name.
- "@rules_python//examples/wheel/..."
- # We control Bazel version in integration tests, so we don't need USE_BAZEL_VERSION for tests.
- skip_use_bazel_version_for_test: true
- test_targets:
+ # We control Bazel version in integration tests, so we don't need USE_BAZEL_VERSION for tests.
+ skip_use_bazel_version_for_test: true
+ test_targets:
- "..."
platforms:
ubuntu1804:
<<: *all_targets
macos:
<<: *all_targets
+ windows:
+ build_targets:
+ - "--" # Allows negative patterns; hack for https://github.com/bazelbuild/continuous-integration/pull/245
+ - "..."
+ # Gazelle is not fully Windows compatible: https://github.com/bazelbuild/bazel-gazelle/issues/1122
+ - "-//gazelle/..."
+ # As a regression test for #225, check that wheel targets still build when
+ # their package path is qualified with the repo name.
+ - "@rules_python//examples/wheel/..."
+ # We control Bazel version in integration tests, so we don't need USE_BAZEL_VERSION for tests.
+ skip_use_bazel_version_for_test: true
+ test_targets:
+ - "--" # Allows negative patterns; hack for https://github.com/bazelbuild/continuous-integration/pull/245
+ - "..."
+ # Gazelle is not fully Windows compatible: https://github.com/bazelbuild/bazel-gazelle/issues/1122
+ - "-//gazelle/..."
diff --git a/.bazelrc b/.bazelrc
index f06d343..e668a68 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -15,3 +15,6 @@
# creating (possibly empty) __init__.py files and adding them to the srcs of
# Python targets as required.
build --incompatible_default_to_explicit_init_py
+
+# Windows makes use of runfiles for some rules
+build --enable_runfiles
diff --git a/docs/BUILD b/docs/BUILD
index fe883d1..6ef0d6e 100644
--- a/docs/BUILD
+++ b/docs/BUILD
@@ -78,10 +78,19 @@
],
)
+# TODO: Stardoc does not guarantee consistent outputs accross platforms (Unix/Windows).
+# As a result we do not build or test docs on Windows.
+_NOT_WINDOWS = select({
+ "@platforms//os:linux": [],
+ "@platforms//os:macos": [],
+ "//conditions:default": ["@platforms//:incompatible"],
+})
+
stardoc(
name = "core-docs",
out = "python.md_",
input = "//python:defs.bzl",
+ target_compatible_with = _NOT_WINDOWS,
deps = [":defs"],
)
@@ -89,6 +98,7 @@
name = "pip-docs",
out = "pip.md_",
input = "//python:pip.bzl",
+ target_compatible_with = _NOT_WINDOWS,
deps = [
":bazel_repo_tools",
":pip_install_bzl",
@@ -100,6 +110,7 @@
name = "packaging-docs",
out = "packaging.md_",
input = "//python:packaging.bzl",
+ target_compatible_with = _NOT_WINDOWS,
deps = [":packaging_bzl"],
)
@@ -109,6 +120,7 @@
failure_message = "Please run: bazel run //docs:update",
file1 = k + ".md",
file2 = k + ".md_",
+ target_compatible_with = _NOT_WINDOWS,
)
for k in _DOCS.keys()
]
@@ -123,10 +135,12 @@
"cp -fv bazel-bin/docs/{0}.md_ docs/{0}.md".format(k)
for k in _DOCS.keys()
],
+ target_compatible_with = _NOT_WINDOWS,
)
sh_binary(
name = "update",
srcs = ["update.sh"],
data = _DOCS.values(),
+ target_compatible_with = _NOT_WINDOWS,
)
diff --git a/examples/wheel/wheel_test.py b/examples/wheel/wheel_test.py
index c28accd..ffdc640 100644
--- a/examples/wheel/wheel_test.py
+++ b/examples/wheel/wheel_test.py
@@ -13,6 +13,7 @@
# limitations under the License.
import os
+import platform
import unittest
import zipfile
@@ -89,9 +90,24 @@
"example_customized-0.0.1.dist-info/entry_points.txt"
)
# The entries are guaranteed to be sorted.
- self.assertEquals(
- record_contents,
- b"""\
+ if platform.system() == "Windows":
+ self.assertEquals(
+ record_contents,
+ b"""\
+example_customized-0.0.1.dist-info/METADATA,sha256=pzE96o3Sp63TDzxAZgl0F42EFevm8x15vpDLqDVp_EQ,378
+example_customized-0.0.1.dist-info/RECORD,,
+example_customized-0.0.1.dist-info/WHEEL,sha256=sobxWSyDDkdg_rinUth-jxhXHqoNqlmNMJY3aTZn2Us,91
+example_customized-0.0.1.dist-info/entry_points.txt,sha256=pqzpbQ8MMorrJ3Jp0ntmpZcuvfByyqzMXXi2UujuXD0,137
+examples/wheel/lib/data.txt,sha256=9vJKEdfLu8bZRArKLroPZJh1XKkK3qFMXiM79MBL2Sg,12
+examples/wheel/lib/module_with_data.py,sha256=8s0Khhcqz3yVsBKv2IB5u4l4TMKh7-c_V6p65WVHPms,637
+examples/wheel/lib/simple_module.py,sha256=z2hwciab_XPNIBNH8B1Q5fYgnJvQTeYf0ZQJpY8yLLY,637
+examples/wheel/main.py,sha256=sgg5iWN_9inYBjm6_Zw27hYdmo-l24fA-2rfphT-IlY,909
+""",
+ )
+ else:
+ self.assertEquals(
+ record_contents,
+ b"""\
example_customized-0.0.1.dist-info/METADATA,sha256=TeeEmokHE2NWjkaMcVJuSAq4_AXUoIad2-SLuquRmbg,372
example_customized-0.0.1.dist-info/RECORD,,
example_customized-0.0.1.dist-info/WHEEL,sha256=sobxWSyDDkdg_rinUth-jxhXHqoNqlmNMJY3aTZn2Us,91
@@ -101,7 +117,7 @@
examples/wheel/lib/simple_module.py,sha256=z2hwciab_XPNIBNH8B1Q5fYgnJvQTeYf0ZQJpY8yLLY,637
examples/wheel/main.py,sha256=sgg5iWN_9inYBjm6_Zw27hYdmo-l24fA-2rfphT-IlY,909
""",
- )
+ )
self.assertEquals(
wheel_contents,
b"""\
@@ -111,9 +127,28 @@
Tag: py3-none-any
""",
)
- self.assertEquals(
- metadata_contents,
- b"""\
+ if platform.system() == "Windows":
+ self.assertEquals(
+ metadata_contents,
+ b"""\
+Metadata-Version: 2.1
+Name: example_customized
+Version: 0.0.1
+Author: Example Author with non-ascii characters: \xc3\x85\xc2\xbc\xc3\x83\xc2\xb3\xc3\x85\xc2\x82w
+Author-email: example@example.com
+Home-page: www.example.com
+License: Apache 2.0
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Intended Audience :: Developers
+Requires-Dist: pytest
+
+This is a sample description of a wheel.
+""",
+ )
+ else:
+ self.assertEquals(
+ metadata_contents,
+ b"""\
Metadata-Version: 2.1
Name: example_customized
Version: 0.0.1
@@ -127,7 +162,7 @@
This is a sample description of a wheel.
""",
- )
+ )
self.assertEquals(
entry_point_contents,
b"""\
diff --git a/python/pip_install/extract_wheels/__init__.py b/python/pip_install/extract_wheels/__init__.py
index e8097e1..f1b7254 100644
--- a/python/pip_install/extract_wheels/__init__.py
+++ b/python/pip_install/extract_wheels/__init__.py
@@ -7,7 +7,6 @@
"""
import argparse
import glob
-import json
import os
import pathlib
import subprocess
diff --git a/python/pip_install/extract_wheels/lib/BUILD b/python/pip_install/extract_wheels/lib/BUILD
index 41fd3a6..2b0a91f 100644
--- a/python/pip_install/extract_wheels/lib/BUILD
+++ b/python/pip_install/extract_wheels/lib/BUILD
@@ -73,14 +73,11 @@
py_test(
name = "whl_filegroup_test",
size = "small",
- srcs = [
- "whl_filegroup_test.py",
- ],
+ srcs = ["whl_filegroup_test.py"],
data = ["//examples/wheel:minimal_with_py_package"],
+ main = "whl_filegroup_test.py",
tags = ["unit"],
- deps = [
- ":lib",
- ],
+ deps = [":lib"],
)
py_test(
diff --git a/python/pip_install/extract_wheels/lib/bazel.py b/python/pip_install/extract_wheels/lib/bazel.py
index 8e9519f..e880c20 100644
--- a/python/pip_install/extract_wheels/lib/bazel.py
+++ b/python/pip_install/extract_wheels/lib/bazel.py
@@ -296,6 +296,7 @@
enable_implicit_namespace_pkgs: bool,
repo_prefix: str,
incremental: bool = False,
+ incremental_dir: Path = Path("."),
) -> Optional[str]:
"""Extracts wheel into given directory and creates py_library and filegroup targets.
@@ -306,6 +307,7 @@
enable_implicit_namespace_pkgs: if true, disables conversion of implicit namespace packages and will unzip as-is
incremental: If true the extract the wheel in a format suitable for an external repository. This
effects the names of libraries and their dependencies, which point to other external repositories.
+ incremental_dir: An optional override for the working directory of incremental builds.
Returns:
The Bazel label for the extracted wheel, in the form '//path/to/wheel'.
@@ -313,7 +315,7 @@
whl = wheel.Wheel(wheel_file)
if incremental:
- directory = "."
+ directory = incremental_dir
else:
directory = sanitise_name(whl.name, prefix=repo_prefix)
diff --git a/python/pip_install/extract_wheels/lib/wheel.py b/python/pip_install/extract_wheels/lib/wheel.py
index 3803fba..85bc958 100644
--- a/python/pip_install/extract_wheels/lib/wheel.py
+++ b/python/pip_install/extract_wheels/lib/wheel.py
@@ -55,6 +55,7 @@
# Calculate the location of the entry_points.txt file
metadata = self.metadata
name = "{}-{}".format(metadata.name.replace("-", "_"), metadata.version)
+
# Note that the zipfile module always uses the forward slash as
# directory separator, even on Windows, so don't use os.path.join
# here. Reference for Python 3.10:
diff --git a/python/pip_install/extract_wheels/lib/whl_filegroup_test.py b/python/pip_install/extract_wheels/lib/whl_filegroup_test.py
index 5bf5f7a..f557713 100644
--- a/python/pip_install/extract_wheels/lib/whl_filegroup_test.py
+++ b/python/pip_install/extract_wheels/lib/whl_filegroup_test.py
@@ -2,6 +2,7 @@
import shutil
import tempfile
import unittest
+from pathlib import Path
from python.pip_install.extract_wheels.lib import bazel
@@ -12,12 +13,9 @@
self.wheel_dir = tempfile.mkdtemp()
self.wheel_path = os.path.join(self.wheel_dir, self.wheel_name)
shutil.copy(os.path.join("examples", "wheel", self.wheel_name), self.wheel_dir)
- self.original_dir = os.getcwd()
- os.chdir(self.wheel_dir)
def tearDown(self):
shutil.rmtree(self.wheel_dir)
- os.chdir(self.original_dir)
def _run(
self,
@@ -31,12 +29,14 @@
enable_implicit_namespace_pkgs=False,
incremental=incremental,
repo_prefix=repo_prefix,
+ incremental_dir=Path(self.wheel_dir),
)
# Take off the leading // from the returned label.
# Assert that the raw wheel ends up in the package.
generated_bazel_dir = (
generated_bazel_dir[2:] if not incremental else self.wheel_dir
)
+
self.assertIn(self.wheel_name, os.listdir(generated_bazel_dir))
with open("{}/BUILD.bazel".format(generated_bazel_dir)) as build_file:
build_file_content = build_file.read()
diff --git a/python/pip_install/parse_requirements_to_bzl/parse_requirements_to_bzl_test.py b/python/pip_install/parse_requirements_to_bzl/parse_requirements_to_bzl_test.py
index a5c76d3..9619af5 100644
--- a/python/pip_install/parse_requirements_to_bzl/parse_requirements_to_bzl_test.py
+++ b/python/pip_install/parse_requirements_to_bzl/parse_requirements_to_bzl_test.py
@@ -1,7 +1,8 @@
import argparse
import json
+import tempfile
import unittest
-from tempfile import NamedTemporaryFile
+from pathlib import Path
from python.pip_install.parse_requirements_to_bzl import (
generate_parsed_requirements_contents,
@@ -10,15 +11,15 @@
class TestParseRequirementsToBzl(unittest.TestCase):
def test_generated_requirements_bzl(self) -> None:
- with NamedTemporaryFile() as requirements_lock:
+ with tempfile.TemporaryDirectory() as temp_dir:
+ requirements_lock = Path(temp_dir) / "requirements.txt"
comments_and_flags = "#comment\n--require-hashes True\n"
requirement_string = "foo==0.0.0 --hash=sha256:hashofFoowhl"
- requirements_lock.write(
+ requirements_lock.write_bytes(
bytes(comments_and_flags + requirement_string, encoding="utf-8")
)
- requirements_lock.flush()
args = argparse.Namespace()
- args.requirements_lock = requirements_lock.name
+ args.requirements_lock = str(requirements_lock.resolve())
args.repo_prefix = "pip_parsed_deps_pypi__"
extra_pip_args = ["--index-url=pypi.org/simple"]
pip_data_exclude = ["**.foo"]
diff --git a/python/pip_install/pip_repository.bzl b/python/pip_install/pip_repository.bzl
index 8e69da7..1dc49c7 100644
--- a/python/pip_install/pip_repository.bzl
+++ b/python/pip_install/pip_repository.bzl
@@ -24,6 +24,23 @@
pypath = separator.join([str(p) for p in [rules_root] + thirdparty_roots])
return pypath
+def _get_python_interpreter_attr(rctx):
+ """A helper function for getting the `python_interpreter` attribute or it's default
+
+ Args:
+ rctx (repository_ctx): Handle to the rule repository context.
+
+ Returns:
+ str: The attribute value or it's default
+ """
+ if rctx.attr.python_interpreter:
+ return rctx.attr.python_interpreter
+
+ if "win" in rctx.os.name:
+ return "python.exe"
+ else:
+ return "python3"
+
def _resolve_python_interpreter(rctx):
"""Helper function to find the python interpreter from the common attributes
@@ -31,7 +48,8 @@
rctx: Handle to the rule repository context.
Returns: Python interpreter path.
"""
- python_interpreter = rctx.attr.python_interpreter
+ python_interpreter = _get_python_interpreter_attr(rctx)
+
if rctx.attr.python_interpreter_target != None:
target = rctx.attr.python_interpreter_target
python_interpreter = rctx.path(target)
@@ -39,7 +57,7 @@
if "/" not in python_interpreter:
python_interpreter = rctx.which(python_interpreter)
if not python_interpreter:
- fail("python interpreter not found")
+ fail("python interpreter `{}` not found in PATH".format(python_interpreter))
return python_interpreter
def _parse_optional_attrs(rctx, args):
@@ -125,10 +143,10 @@
str(rctx.attr.timeout),
]
- if rctx.attr.python_interpreter:
- args += ["--python_interpreter", rctx.attr.python_interpreter]
+ args += ["--python_interpreter", _get_python_interpreter_attr(rctx)]
if rctx.attr.python_interpreter_target:
args += ["--python_interpreter_target", str(rctx.attr.python_interpreter_target)]
+
else:
args = [
python_interpreter,
@@ -193,7 +211,15 @@
"pip_data_exclude": attr.string_list(
doc = "Additional data exclusion parameters to add to the pip packages BUILD file.",
),
- "python_interpreter": attr.string(default = "python3"),
+ "python_interpreter": attr.string(
+ doc = """\
+The python interpreter to use. This can either be an absolute path or the name
+of a binary found on the host's `PATH` environment variable. If no value is set
+`python3` is defaulted for Unix systems and `python.exe` for Windows.
+""",
+ # NOTE: This attribute should not have a default. See `_get_python_interpreter_attr`
+ # default = "python3"
+ ),
"python_interpreter_target": attr.label(
allow_single_file = True,
doc = """
diff --git a/tools/bazel_integration_test/test_runner.py b/tools/bazel_integration_test/test_runner.py
index eb5cabd..f10d86a 100644
--- a/tools/bazel_integration_test/test_runner.py
+++ b/tools/bazel_integration_test/test_runner.py
@@ -5,6 +5,7 @@
import shutil
import sys
import tempfile
+import textwrap
from pathlib import Path
from subprocess import Popen
@@ -12,6 +13,7 @@
r = runfiles.Create()
+
def main(conf_file):
with open(conf_file) as j:
config = json.load(j)
@@ -28,26 +30,50 @@
if workspacePath.startswith("external/"):
workspacePath = ".." + workspacePath[len("external") :]
- with tempfile.TemporaryDirectory(dir=os.environ["TEST_TMPDIR"]) as tmpdir:
- workdir = os.path.join(tmpdir, "wksp")
- print("copying workspace under test %s to %s" % (workspacePath, workdir))
- shutil.copytree(workspacePath, workdir)
+ with tempfile.TemporaryDirectory(dir=os.environ["TEST_TMPDIR"]) as tmp_homedir:
+ home_bazel_rc = Path(tmp_homedir) / ".bazelrc"
+ home_bazel_rc.write_text(
+ textwrap.dedent(
+ """\
+ startup --max_idle_secs=1
+ common --announce_rc
+ """
+ )
+ )
- for command in config['bazelCommands']:
- bazel_args = command.split(' ')
- bazel_args.append("--override_repository=rules_python=%s/rules_python" % os.environ['TEST_SRCDIR'])
+ with tempfile.TemporaryDirectory(dir=os.environ["TEST_TMPDIR"]) as tmpdir:
+ workdir = os.path.join(tmpdir, "wksp")
+ print("copying workspace under test %s to %s" % (workspacePath, workdir))
+ shutil.copytree(workspacePath, workdir)
- # Bazel's wrapper script needs this or you get
- # 2020/07/13 21:58:11 could not get the user's cache directory: $HOME is not defined
- os.environ["HOME"] = str(Path.home())
+ for command in config["bazelCommands"]:
+ bazel_args = command.split(" ")
+ bazel_args.append(
+ "--override_repository=rules_python=%s/rules_python"
+ % os.environ["TEST_SRCDIR"]
+ )
- bazel_args.insert(0, bazelBinary)
- bazel_process = Popen(bazel_args, cwd=workdir)
- bazel_process.wait()
- if bazel_process.returncode != 0:
- # Test failure in Bazel is exit 3
- # https://github.com/bazelbuild/bazel/blob/486206012a664ecb20bdb196a681efc9a9825049/src/main/java/com/google/devtools/build/lib/util/ExitCode.java#L44
- sys.exit(3)
+ # Bazel's wrapper script needs this or you get
+ # 2020/07/13 21:58:11 could not get the user's cache directory: $HOME is not defined
+ os.environ["HOME"] = str(tmp_homedir)
+
+ bazel_args.insert(0, bazelBinary)
+ bazel_process = Popen(bazel_args, cwd=workdir)
+ bazel_process.wait()
+
+ if platform.system() == "Windows":
+ # Cleanup any bazel files
+ bazel_process = Popen([bazelBinary, "clean"], cwd=workdir)
+ bazel_process.wait()
+
+ # Shutdown the bazel instance to avoid issues cleaning up the workspace
+ bazel_process = Popen([bazelBinary, "shutdown"], cwd=workdir)
+ bazel_process.wait()
+
+ if bazel_process.returncode != 0:
+ # Test failure in Bazel is exit 3
+ # https://github.com/bazelbuild/bazel/blob/486206012a664ecb20bdb196a681efc9a9825049/src/main/java/com/google/devtools/build/lib/util/ExitCode.java#L44
+ sys.exit(3)
if __name__ == "__main__":