blob: dd36c5f93ca7df3b7b91413f3bb207f5e851d21f [file] [log] [blame]
Philipp Schrader272e07b2024-03-29 17:26:02 -07001import os
2import subprocess
3import sys
4import tempfile
5from pathlib import Path
6
7import yaml
8from python.runfiles import runfiles
9
10RUNFILES = runfiles.Create()
11
12FAKE_NPM_BIN = RUNFILES.Rlocation(
13 "org_frc971/tools/foxglove/creation_wrapper_npm")
14BUILDOZER_BIN = RUNFILES.Rlocation(
15 "com_github_bazelbuild_buildtools/buildozer/buildozer_/buildozer")
16
17WORKSPACE_DIR = Path(os.environ["BUILD_WORKSPACE_DIRECTORY"])
18WORKING_DIR = Path(os.environ["BUILD_WORKING_DIRECTORY"])
19
20
21def create_npm_link(temp_dir: Path, env: dict[str, str]):
22 """Set up the creation_wrapper_npm.py script as the "npm" binary."""
23 bin_dir = temp_dir / "bin"
24 bin_dir.mkdir()
25 npm = bin_dir / "npm"
26 npm.symlink_to(FAKE_NPM_BIN)
27 env["PATH"] = f"{temp_dir / 'bin'}:{env['PATH']}"
28
29
30def run_create_foxglove_extension(argv: list[str], name: str):
31 """Runs the create-foxglove-extension binary.
32
33 Args:
34 argv: The list of command line arguments passed to this wrapper.
35 name: The (directory) name of the new extension to be created.
36 """
37 with tempfile.TemporaryDirectory() as temp_dir:
38 temp_dir = Path(temp_dir)
39 env = os.environ.copy()
40 create_npm_link(temp_dir, env)
41
42 env["BAZEL_BINDIR"] = WORKING_DIR
43 env.pop("RUNFILES_DIR", None)
44 env.pop("RUNFILES_MANIFEST_FILE", None)
45
46 subprocess.run(argv[1:], check=True, env=env, cwd=WORKING_DIR)
47 # For some reason, the `foxglove-extension` binary doesn't set up the
48 # ts-loader dependency. Do it manually here.
49 subprocess.run(["npm", "install", "ts-loader@^9"],
50 check=True,
51 env=env,
52 cwd=WORKING_DIR / name)
53
54
55def add_new_js_project(name: str):
56 """Tell Bazel about the new project."""
57 # The name of the Bazel package for the new extension.
58 package_name = WORKING_DIR.relative_to(WORKSPACE_DIR) / name
59
60 # Add the new "node_modules" directory to the ignore list.
61 bazelignore_file = WORKSPACE_DIR / ".bazelignore"
62 bazelignore = bazelignore_file.read_text()
63 bazelignore_entry = str(package_name / "node_modules")
64 if bazelignore_entry not in bazelignore.splitlines():
65 bazelignore = bazelignore.rstrip("\n") + "\n"
66 bazelignore_file.write_text(bazelignore + bazelignore_entry + "\n")
67
68 # Add the new project to the workspace list. This ensures the lock file
69 # gets updated properly.
70 pnpm_workspace_file = WORKSPACE_DIR / "pnpm-workspace.yaml"
71 pnpm_workspace = yaml.load(pnpm_workspace_file.read_text(),
72 Loader=yaml.CLoader)
73 if str(package_name) not in pnpm_workspace["packages"]:
74 pnpm_workspace["packages"].append(str(package_name))
75 pnpm_workspace_file.write_text(yaml.dump(pnpm_workspace))
76
77 # Add the new project to the workspace. This ensures that all of its
78 # dependencies get downloaded by Bazel.
79 subprocess.check_call([
80 BUILDOZER_BIN,
81 f"add data @//{package_name}:package.json",
82 "WORKSPACE:npm",
83 ],
84 cwd=WORKSPACE_DIR)
85
86 # Regenerate the lock file with the new project's dependencies included.
87 subprocess.check_call([
88 "bazel",
89 "run",
90 "--",
91 "@pnpm//:pnpm",
92 "--dir",
93 WORKSPACE_DIR,
94 "install",
95 "--lockfile-only",
96 ],
97 cwd=WORKSPACE_DIR)
98
99
100def main(argv):
101 """Runs the main logic."""
102
103 # Assume that the only argument the user passed in is the name of the
104 # extension. We can probably do better here, but oh well.
105 create_foxglove_extension_args = argv[2:]
106 name = create_foxglove_extension_args[0]
107
108 run_create_foxglove_extension(argv, name)
109 add_new_js_project(name)
110
111 # Generate a BUILD file.
112 build_file_template = WORKSPACE_DIR / "tools/foxglove/BUILD.bazel.tmpl"
113 build_file = WORKING_DIR / name / "BUILD.bazel"
114 build_file.write_text(build_file_template.read_text())
115
116 # Fix up the tsconfig.json. For some reason the inheritance for the `lib`
117 # field doesn't work out of the box. We're using string manipulation since
118 # we don't have a readily-available "JSON with comments" parser.
119 tsconfig_file = WORKING_DIR / name / "tsconfig.json"
120 tsconfig = tsconfig_file.read_text()
121 tsconfig = tsconfig.replace('"lib": ["dom"]', '"lib": ["dom", "es2022"]')
122 tsconfig_file.write_text(tsconfig)
123
124
125if __name__ == "__main__":
126 sys.exit(main(sys.argv))