Skip to content

Commit 608c3f0

Browse files
committed
npm/jedi: Further trim down build logic.
We used to separately build jedi and then use another Python step to package it. We can just do it from the same environment to avoid packaging issues.
1 parent b65443f commit 608c3f0

6 files changed

Lines changed: 168 additions & 16 deletions

File tree

.github/workflows/publish-jedi.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
with:
3939
node-version: '24'
4040
registry-url: 'https://registry.npmjs.org'
41-
- run: ./build.py "$NPM_VERSION"
42-
working-directory: npm/jedi
41+
- run: poetry run python build.py "$NPM_VERSION"
42+
working-directory: jedi
4343
- run: npm publish --tag "$NPM_TAG"
44-
working-directory: npm/jedi/build
44+
working-directory: jedi/npm-build

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
build/
2626
build-*/
2727
_build/
28+
npm-build/
2829

2930
# Tests
3031
######################

build-all.sh

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,15 @@ cd "$REPO_ROOT"
1919
flake8
2020
doc8
2121

22-
# pybricks-jedi wheel (from local source)
22+
# pybricks-jedi tests
2323
echo "==> Testing pybricks-jedi"
2424
cd "$REPO_ROOT/jedi"
2525
poetry run pytest -vv
2626

27-
echo "==> Building pybricks-jedi wheel"
28-
cd "$REPO_ROOT/jedi"
29-
rm -rf dist/
30-
poetry build --format=wheel
31-
3227
# @pybricks/jedi npm package
3328
echo "==> Building @pybricks/jedi"
34-
cd "$REPO_ROOT"
35-
python3 npm/jedi/build.py "$NPM_VERSION"
29+
cd "$REPO_ROOT/jedi"
30+
poetry run python build.py "$NPM_VERSION"
3631

3732
# @pybricks/ide-docs npm package
3833
echo "==> Building @pybricks/ide-docs"
@@ -43,5 +38,5 @@ yarn build
4338

4439
echo ""
4540
echo "Build complete."
46-
echo " jedi npm package : npm/jedi/build/"
41+
echo " jedi npm package : jedi/npm-build/"
4742
echo " ide-docs : npm/ide-docs/html/"
File renamed without changes.

jedi/build.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/usr/bin/env python3
2+
# Run with: poetry run python build.py <npm-version>
3+
# Builds the @pybricks/jedi npm package into npm-build/.
4+
5+
import email.parser
6+
import importlib.metadata
7+
import json
8+
import pathlib
9+
import shutil
10+
import subprocess
11+
import sys
12+
import zipfile
13+
14+
if len(sys.argv) != 2:
15+
print(f"Usage: {sys.argv[0]} <version>", file=sys.stderr)
16+
sys.exit(1)
17+
18+
VERSION = sys.argv[1]
19+
20+
ROOT_DIR = pathlib.Path(__file__).parent.resolve()
21+
REPO_ROOT_DIR = (ROOT_DIR / "..").resolve()
22+
BUILD_DIR = ROOT_DIR / "npm-build"
23+
24+
package_json = {
25+
"name": "@pybricks/jedi",
26+
"version": VERSION,
27+
"description": "Binary distribution of pybricks-jedi Python package and dependencies for use with Pyodide.",
28+
"repository": {
29+
"type": "git",
30+
"url": "git+https://github.com/pybricks/pybricks-api.git",
31+
"directory": "jedi",
32+
},
33+
"publishConfig": {"registry": "https://registry.npmjs.org", "access": "public"},
34+
}
35+
36+
license_identifiers: set[str] = set()
37+
license_text: dict[str, str] = {}
38+
whl_map: dict[str, str] = {}
39+
40+
# ensure empty build directory so we don't end up with stale files
41+
shutil.rmtree(BUILD_DIR, True)
42+
BUILD_DIR.mkdir()
43+
44+
# build pybricks api wheel from local source so pip uses it instead of fetching from PyPI
45+
subprocess.check_call(["poetry", "build", "--format=wheel"], cwd=REPO_ROOT_DIR)
46+
47+
# copy locally built pybricks wheel to build dir
48+
for whl in (REPO_ROOT_DIR / "dist").glob("pybricks-*.whl"):
49+
shutil.copy(whl, BUILD_DIR)
50+
51+
# build pybricks-jedi wheel from local source
52+
subprocess.check_call(["poetry", "build", "--format=wheel"], cwd=ROOT_DIR)
53+
54+
# copy locally built wheel to build dir
55+
for whl in (ROOT_DIR / "dist").glob("pybricks_jedi-*.whl"):
56+
shutil.copy(whl, BUILD_DIR)
57+
58+
# download transitive dependencies using the versions already installed in the venv
59+
# (installed by poetry from the lockfile, so versions are pinned correctly)
60+
transitive_packages = ["jedi", "parso", "docstring-parser", "typing-extensions"]
61+
transitive = [
62+
f"{pkg}=={importlib.metadata.version(pkg)}" for pkg in transitive_packages
63+
]
64+
subprocess.check_call(
65+
[sys.executable, "-m", "pip", "download", "--only-binary=any"] + transitive,
66+
cwd=BUILD_DIR,
67+
)
68+
69+
# extract info from wheel files to generate javascript package files
70+
for whl in BUILD_DIR.glob("*.whl"):
71+
with zipfile.ZipFile(whl) as f:
72+
73+
def is_dist_info_metadata(info: zipfile.ZipInfo):
74+
return info.filename.endswith(".dist-info/METADATA")
75+
76+
def is_dist_info_license(info: zipfile.ZipInfo):
77+
return ".dist-info" in info.filename and "LICENSE" in info.filename
78+
79+
metadata = next(filter(is_dist_info_metadata, f.filelist))
80+
81+
with f.open(metadata.filename) as mf:
82+
meta = email.parser.BytesParser().parse(mf)
83+
84+
# extract package name from metadata
85+
name = meta["Name"]
86+
87+
if not name:
88+
raise RuntimeError(f"missing 'Name' in {whl.name} METADATA")
89+
90+
whl_map[name] = whl.name
91+
92+
# extract license identifier from metadata
93+
94+
license = meta["License"]
95+
96+
if not license:
97+
# some packages are missing license in metadata
98+
if whl.name.startswith("typing_extensions-"):
99+
license = "Python-2.0"
100+
else:
101+
raise RuntimeError(f"missing 'License' in {whl.name} METADATA")
102+
103+
license_identifiers.add(license)
104+
105+
if whl.name.startswith("pybricks_jedi-"):
106+
with open(ROOT_DIR / "LICENSE") as lf:
107+
license_text[whl.name] = lf.read()
108+
else:
109+
try:
110+
license = next(filter(is_dist_info_license, f.filelist))
111+
except StopIteration:
112+
raise RuntimeError(f"missing license for {whl.name}")
113+
114+
with f.open(license) as lf:
115+
license_text[whl.name] = lf.read().decode()
116+
117+
118+
# generate package.json file
119+
120+
# create "license" item from collected license identifiers
121+
package_json["license"] = (
122+
license_identifiers[0]
123+
if len(license_identifiers) < 2
124+
else f"({' AND '.join(license_identifiers)})"
125+
)
126+
127+
# create "exports" item from collect whl map
128+
package_json["exports"] = {f"./{k}.whl": f"./{v}" for k, v in whl_map.items()}
129+
130+
with open(BUILD_DIR / "package.json", "w") as f:
131+
json.dump(package_json, f, indent=2)
132+
133+
# generate LICENSE file
134+
135+
NEWLINE = "\n"
136+
DIVIDER = "=" * 80 + NEWLINE
137+
with open(BUILD_DIR / "LICENSE", "w") as f:
138+
for whl, text in license_text.items():
139+
f.write(DIVIDER)
140+
f.write(whl)
141+
f.writelines([NEWLINE, DIVIDER, NEWLINE])
142+
f.write(text)
143+
f.write(NEWLINE)
144+
145+
# copy additional files
146+
147+
for file in ("README.md",):
148+
shutil.copy(ROOT_DIR / file, BUILD_DIR / file)

npm/jedi/build.py

100755100644
Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#!/usr/bin/env python3
22

33
import email.parser
4-
import importlib.metadata
54
import json
65
import pathlib
76
import shutil
@@ -57,9 +56,18 @@
5756
# download transitive dependencies using the versions already installed in the venv
5857
# (installed by poetry from the lockfile, so versions are pinned correctly)
5958
transitive_packages = ["jedi", "parso", "docstring-parser", "typing-extensions"]
60-
transitive = [
61-
f"{pkg}=={importlib.metadata.version(pkg)}" for pkg in transitive_packages
62-
]
59+
# use the jedi venv's Python to query metadata since packages are installed there
60+
jedi_venv_python = JEDI_SRC_DIR / ".venv" / "bin" / "python"
61+
pkg_versions = json.loads(
62+
subprocess.check_output(
63+
[
64+
jedi_venv_python,
65+
"-c",
66+
f"import importlib.metadata, json; print(json.dumps({{p: importlib.metadata.version(p) for p in {transitive_packages!r}}}))",
67+
]
68+
)
69+
)
70+
transitive = [f"{pkg}=={pkg_versions[pkg]}" for pkg in transitive_packages]
6371
subprocess.check_call(
6472
[sys.executable, "-m", "pip", "download", "--only-binary=any"] + transitive,
6573
cwd=BUILD_DIR,

0 commit comments

Comments
 (0)