Philipp Schrader | 9e1b9bd | 2021-12-28 00:15:12 -0800 | [diff] [blame] | 1 | """This script generates contents of the @pip repository. |
| 2 | |
| 3 | This repository is just a way to have simple-to-write targets to specify in |
| 4 | BUILD files. E.g. you can use @pip//numpy. |
| 5 | |
| 6 | The pip package names are normalized: |
| 7 | - Letters are lowercased. |
| 8 | - Periods are replaced by underscores. |
| 9 | - Dashes are replaced by underscores. |
| 10 | |
| 11 | We do this normalization because it produces predictable names. pip allows a |
| 12 | wide range of names to refer to the same package. That would be annoying to use |
| 13 | in BUILD files. |
| 14 | """ |
| 15 | |
Philipp Schrader | 42148d5 | 2023-01-03 00:55:37 -0800 | [diff] [blame] | 16 | import re |
Philipp Schrader | 9e1b9bd | 2021-12-28 00:15:12 -0800 | [diff] [blame] | 17 | import sys |
| 18 | import textwrap |
| 19 | from pathlib import Path |
| 20 | |
Philipp Schrader | 42148d5 | 2023-01-03 00:55:37 -0800 | [diff] [blame] | 21 | # 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". |
| 29 | REQUIREMENT_MATCH = re.compile(r"[-_.a-zA-Z0-9]+") |
| 30 | |
Philipp Schrader | 9e1b9bd | 2021-12-28 00:15:12 -0800 | [diff] [blame] | 31 | |
| 32 | def 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 Schrader | 42148d5 | 2023-01-03 00:55:37 -0800 | [diff] [blame] | 39 | 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 Schrader | 9e1b9bd | 2021-12-28 00:15:12 -0800 | [diff] [blame] | 42 | |
| 43 | |
| 44 | def 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"], |
Philipp Schrader | 9e1b9bd | 2021-12-28 00:15:12 -0800 | [diff] [blame] | 67 | ) |
| 68 | """)) |
| 69 | |
| 70 | |
| 71 | def main(argv): |
| 72 | requirements_path = Path(argv[1]) |
| 73 | requirements = parse_requirements(requirements_path) |
| 74 | |
| 75 | generate_build_files(requirements) |
| 76 | |
| 77 | |
| 78 | if __name__ == "__main__": |
| 79 | sys.exit(main(sys.argv)) |