blob: 4f9dff174ca2c0b12738db9602ee4ce960968032 [file] [log] [blame]
Philipp Schrader9e1b9bd2021-12-28 00:15:12 -08001"""This script generates contents of the @pip repository.
2
3This repository is just a way to have simple-to-write targets to specify in
4BUILD files. E.g. you can use @pip//numpy.
5
6The pip package names are normalized:
7- Letters are lowercased.
8- Periods are replaced by underscores.
9- Dashes are replaced by underscores.
10
11We do this normalization because it produces predictable names. pip allows a
12wide range of names to refer to the same package. That would be annoying to use
13in BUILD files.
14"""
15
16import sys
17import textwrap
18from pathlib import Path
19
20
21def parse_requirements(requirements_path: Path) -> list[str]:
22 """Parses tools/python/requirements.txt.
23
24 We don't want to parse the lock file since we really only want users to
25 depend on explicitly requested pip packages. We don't want users to depend
26 on transitive dependencies of our requested pip packages.
27 """
28 result = []
29 for line in requirements_path.read_text().splitlines():
30 # Ignore line comments.
31 if not line or line.startswith("#"):
32 continue
33
34 # Remove any inline comments that may or may not exist.
35 # E.g:
36 # numpy==1.2.3 # needed because we like it.
37 result.append(line.split()[0])
38
39 return result
40
41
42def generate_build_files(requirements: list[str]) -> None:
43 """Generate all the BUILD files in the "pip" external repository.
44
45 We create files like this:
46
47 external/pip/numpy/BUILD
48
49 and in that BUILD file we create a "numpy" target. That lets users depend
50 on "@pip//numpy".
51 """
52 for requirement in requirements:
53 requirement = requirement.lower().replace("-", "_").replace(".", "_")
54 requirement_dir = Path(requirement)
55 requirement_dir.mkdir()
56 # We could use an alias() here, but that behaves strangely with
57 # target_compatible_with pre-6.0.
58 (requirement_dir / "BUILD").write_text(
59 textwrap.dedent(f"""\
60 load("@pip_deps//:requirements.bzl", "requirement")
61 py_library(
62 name = "{requirement}",
63 deps = [requirement("{requirement}")],
64 visibility = ["//visibility:public"],
65 target_compatible_with = [
66 "@//tools/platforms/python:upstream_bundled_python",
67 ],
68 )
69 """))
70
71
72def main(argv):
73 requirements_path = Path(argv[1])
74 requirements = parse_requirements(requirements_path)
75
76 generate_build_files(requirements)
77
78
79if __name__ == "__main__":
80 sys.exit(main(sys.argv))