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 | |
| 16 | import sys |
| 17 | import textwrap |
| 18 | from pathlib import Path |
| 19 | |
| 20 | |
| 21 | def 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 | |
| 42 | def 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 | |
| 72 | def main(argv): |
| 73 | requirements_path = Path(argv[1]) |
| 74 | requirements = parse_requirements(requirements_path) |
| 75 | |
| 76 | generate_build_files(requirements) |
| 77 | |
| 78 | |
| 79 | if __name__ == "__main__": |
| 80 | sys.exit(main(sys.argv)) |