blob: 132cc2447ddfb33496ec5da96ee43e7a4749d853 [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
Philipp Schrader42148d52023-01-03 00:55:37 -080016import re
Philipp Schrader9e1b9bd2021-12-28 00:15:12 -080017import sys
18import textwrap
19from pathlib import Path
20
Philipp Schrader42148d52023-01-03 00:55:37 -080021# Regex to parse the lines in a requirements file.
22# - Ignore line comments.
23# - Remove any inline comments that may or may not exist.
24# - Also remove any version specifiers. We don't use it.
25#
26# E.g:
27# numpy==1.2.3 # needed because we like it.
28# turns into "numpy".
29REQUIREMENT_MATCH = re.compile(r"[-_.a-zA-Z0-9]+")
30
Philipp Schrader9e1b9bd2021-12-28 00:15:12 -080031
32def parse_requirements(requirements_path: Path) -> list[str]:
33 """Parses tools/python/requirements.txt.
34
35 We don't want to parse the lock file since we really only want users to
36 depend on explicitly requested pip packages. We don't want users to depend
37 on transitive dependencies of our requested pip packages.
38 """
Philipp Schrader42148d52023-01-03 00:55:37 -080039 lines = requirements_path.read_text().splitlines()
40 matches = map(REQUIREMENT_MATCH.match, lines)
41 return [match.group(0) for match in matches if match]
Philipp Schrader9e1b9bd2021-12-28 00:15:12 -080042
43
44def generate_build_files(requirements: list[str]) -> None:
45 """Generate all the BUILD files in the "pip" external repository.
46
47 We create files like this:
48
49 external/pip/numpy/BUILD
50
51 and in that BUILD file we create a "numpy" target. That lets users depend
52 on "@pip//numpy".
53 """
54 for requirement in requirements:
55 requirement = requirement.lower().replace("-", "_").replace(".", "_")
56 requirement_dir = Path(requirement)
57 requirement_dir.mkdir()
58 # We could use an alias() here, but that behaves strangely with
59 # target_compatible_with pre-6.0.
60 (requirement_dir / "BUILD").write_text(
61 textwrap.dedent(f"""\
62 load("@pip_deps//:requirements.bzl", "requirement")
63 py_library(
64 name = "{requirement}",
65 deps = [requirement("{requirement}")],
66 visibility = ["//visibility:public"],
67 target_compatible_with = [
68 "@//tools/platforms/python:upstream_bundled_python",
69 ],
70 )
71 """))
72
73
74def main(argv):
75 requirements_path = Path(argv[1])
76 requirements = parse_requirements(requirements_path)
77
78 generate_build_files(requirements)
79
80
81if __name__ == "__main__":
82 sys.exit(main(sys.argv))