Skip to content

Commit 3d2f535

Browse files
committed
remove the requirement of npm to build pythonmonkey
Merge branch 'Xmader/feat/npm-py' into ci-ubuntu20-deprecation
2 parents 6a5401a + 9a58363 commit 3d2f535

File tree

4 files changed

+76
-35
lines changed

4 files changed

+76
-35
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ Read this if you want to build a local version.
7474
- rust
7575
- python3.8 or later with header files (python3-dev)
7676
- spidermonkey latest from mozilla-central
77-
- npm (nodejs)
7877
- [Poetry](https://python-poetry.org/docs/#installation)
7978
- [poetry-dynamic-versioning](https://github.com/mtkennerly/poetry-dynamic-versioning)
8079

python/pminit/pmpm.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# @file pmpm.py
2+
# A minimum copy of npm written in pure Python.
3+
# Currently, this can only install dependencies specified by package-lock.json into node_modules.
4+
# @author Tom Tang <[email protected]>
5+
# @date July 2023
6+
7+
import json
8+
import io
9+
import os, shutil
10+
import tempfile
11+
import tarfile
12+
from dataclasses import dataclass
13+
import urllib.request
14+
from typing import List, Union
15+
16+
@dataclass
17+
class PackageItem:
18+
installation_path: str
19+
tarball_url: str
20+
has_install_script: bool
21+
22+
def parse_package_lock_json(json_data: Union[str, bytes]) -> List[PackageItem]:
23+
# See https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json#packages
24+
packages: dict = json.loads(json_data)["packages"]
25+
items: List[PackageItem] = []
26+
for key, entry in packages.items():
27+
if key == "":
28+
# Skip the root project (listed with a key of "")
29+
continue
30+
items.append(
31+
PackageItem(
32+
installation_path=key, # relative path from the root project folder
33+
# The path is flattened for nested node_modules, e.g., "node_modules/create-ecdh/node_modules/bn.js"
34+
tarball_url=entry["resolved"], # TODO: handle git dependencies
35+
has_install_script=entry.get("hasInstallScript", False) # the package has a preinstall, install, or postinstall script
36+
)
37+
)
38+
return items
39+
40+
def download_package(tarball_url: str) -> bytes:
41+
with urllib.request.urlopen(tarball_url) as response:
42+
tarball_data: bytes = response.read()
43+
return tarball_data
44+
45+
def unpack_package(work_dir:str, installation_path: str, tarball_data: bytes):
46+
installation_path = os.path.join(work_dir, installation_path)
47+
shutil.rmtree(installation_path, ignore_errors=True)
48+
49+
with tempfile.TemporaryDirectory(prefix="pmpm_cache-") as tmpdir:
50+
with io.BytesIO(tarball_data) as tar_file:
51+
with tarfile.open(fileobj=tar_file) as tar:
52+
tar.extractall(tmpdir)
53+
shutil.move(
54+
os.path.join(tmpdir, "package"), # Strip the root folder
55+
installation_path
56+
)
57+
58+
def main(work_dir: str):
59+
with open(os.path.join(work_dir, "package-lock.json"), encoding="utf-8") as f:
60+
items = parse_package_lock_json(f.read())
61+
for i in items:
62+
print("Installing " + i.installation_path)
63+
tarball_data = download_package(i.tarball_url)
64+
unpack_package(work_dir, i.installation_path, tarball_data)
65+
66+
if __name__ == "__main__":
67+
main(os.getcwd())

python/pminit/post-install-hook.py

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,13 @@
1-
import subprocess
2-
import sys
3-
import shutil
1+
import os
2+
import pmpm
43

5-
def execute(cmd: str):
6-
popen = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.STDOUT,
7-
shell = True, text = True )
8-
for stdout_line in iter(popen.stdout.readline, ""):
9-
sys.stdout.write(stdout_line)
10-
sys.stdout.flush()
11-
12-
popen.stdout.close()
13-
return_code = popen.wait()
14-
if return_code:
15-
raise subprocess.CalledProcessError(return_code, cmd)
4+
WORK_DIR = os.path.join(
5+
os.path.realpath(os.path.dirname(__file__)),
6+
"pythonmonkey"
7+
)
168

179
def main():
18-
node_package_manager = 'npm'
19-
# check if npm is installed on the system
20-
if (shutil.which(node_package_manager) is None):
21-
print("""
22-
23-
PythonMonkey Build Error:
24-
25-
26-
* It appears npm is not installed on this system.
27-
* npm is required for PythonMonkey to build.
28-
* Please install NPM and Node.js before installing PythonMonkey.
29-
* Refer to the documentation for installing NPM and Node.js here: https://nodejs.org/en/download
30-
31-
32-
""")
33-
raise Exception("PythonMonkey build error: Unable to find npm on the system.")
34-
else:
35-
execute(f"cd pythonmonkey && {node_package_manager} i --no-package-lock") # do not update package-lock.json
10+
pmpm.main(WORK_DIR) # cd pythonmonkey && npm i
3611

3712
if __name__ == "__main__":
38-
main()
39-
13+
main()

python/pminit/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ documentation = "https://docs.pythonmonkey.io/"
1111
repository = "https://github.com/Distributive-Network/PythonMonkey"
1212

1313
include = [
14+
"pmpm.py",
1415
# Install extra files into the pythonmonkey package
1516
"pythonmonkey/package*.json",
1617
{ path = "pythonmonkey/node_modules/**/*", format = "wheel" },

0 commit comments

Comments
 (0)