Skip to content

Commit 75618c5

Browse files
committed
add concept of build cache
1 parent 7caf910 commit 75618c5

File tree

3 files changed

+73
-56
lines changed

3 files changed

+73
-56
lines changed

idom/__main__.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ def main(*args: str) -> None:
2424
type=str,
2525
default=None,
2626
)
27-
cli.add_argument("--force", action="store_true")
2827
cli.add_argument("--debug", action="store_true")
2928

3029
parsed = cli.parse_args(args or sys.argv[1:])
@@ -39,7 +38,7 @@ def main(*args: str) -> None:
3938

4039

4140
ARG_REQUIREMENTS: Dict[str, Dict[str, Any]] = {
42-
"installed restore uninstall": {"exports": None, "force": False},
41+
"installed restore uninstall": {"exports": None},
4342
"installed restore": {"dependencies": []},
4443
}
4544

@@ -57,7 +56,7 @@ def run(args: argparse.Namespace) -> None:
5756
)
5857

5958
if args.command == "install":
60-
install(args.dependencies or [], args.exports or [], args.force)
59+
install(args.dependencies or [], args.exports or [])
6160
elif args.command == "uninstall":
6261
delete_web_modules(args.dependencies)
6362
elif args.command == "restore":

idom/client/manage.py

+70-53
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from loguru import logger
55
from pathlib import Path
66
from tempfile import TemporaryDirectory
7-
from typing import Optional, List, Union, Dict, Sequence, Any, cast
7+
from typing import Optional, List, Union, Dict, Sequence
88

99
from .utils import Spinner
1010

@@ -56,6 +56,7 @@ def register_web_module(name: str, source: Union[str, Path]) -> str:
5656

5757
def delete_web_modules(names: Sequence[str], skip_missing: bool = False) -> None:
5858
paths = []
59+
cache = Cache(BUILD_DIR)
5960
for name in _to_list_of_str(names):
6061
exists = False
6162

@@ -78,9 +79,13 @@ def delete_web_modules(names: Sequence[str], skip_missing: bool = False) -> None
7879
if not exists and not skip_missing:
7980
raise ValueError(f"Module '{name}' does not exist.")
8081

82+
cache.delete_package(name)
83+
8184
for p in paths:
8285
_delete_os_paths(p)
8386

87+
cache.save()
88+
8489

8590
def installed() -> List[str]:
8691
names: List[str] = []
@@ -91,56 +96,33 @@ def installed() -> List[str]:
9196
return list(sorted(names))
9297

9398

94-
def install(
95-
packages: Sequence[str], exports: Sequence[str] = (), force: bool = False
96-
) -> None:
97-
package_list = _to_list_of_str(packages)
98-
export_list = _to_list_of_str(exports)
99-
100-
for pkg in package_list:
101-
at_count = pkg.count("@")
102-
if pkg.startswith("@") and at_count == 1:
103-
export_list.append(pkg)
104-
else:
105-
# this works even if there are no @ symbols
106-
export_list.append(pkg.rsplit("@", 1)[0])
107-
108-
if force:
109-
for exp in export_list:
110-
delete_web_modules(exp, skip_missing=True)
111-
99+
def install(packages: Sequence[str], exports: Sequence[str] = ()) -> None:
112100
with TemporaryDirectory() as tempdir:
113101
tempdir_path = Path(tempdir)
114102
temp_static_dir = tempdir_path / "static"
103+
temp_build_dir = temp_static_dir / "build"
115104

105+
# copy over the whole ./static directory into the temp one
116106
shutil.copytree(STATIC_DIR, temp_static_dir, symlinks=True)
117-
assert (temp_static_dir / "package.json").exists()
107+
108+
cache = Cache(temp_build_dir)
109+
cache.add_packages(packages, exports)
118110

119111
with open(temp_static_dir / "package.json") as f:
120112
package_json = json.load(f)
121113

122-
temp_build_dir = temp_static_dir / "build"
123-
124-
cache = _read_idom_build_cache(temp_build_dir)
125-
126-
export_list = list(set(cache["export_list"] + export_list))
127-
package_list = list(set(cache["package_list"] + package_list))
128-
129114
pkg_snowpack = package_json.setdefault("snowpack", {})
130-
pkg_snowpack.setdefault("install", []).extend(export_list)
115+
pkg_snowpack.setdefault("install", []).extend(cache.export_list)
131116

132117
with (temp_static_dir / "package.json").open("w+") as f:
133118
json.dump(package_json, f)
134119

135-
with Spinner(f"Installing: {', '.join(package_list)}"):
120+
with Spinner(f"Installing: {', '.join(packages)}"):
136121
_run_subprocess(["npm", "install"], temp_static_dir)
137-
_run_subprocess(["npm", "install"] + package_list, temp_static_dir)
122+
_run_subprocess(["npm", "install"] + cache.package_list, temp_static_dir)
138123
_run_subprocess(["npm", "run", "build"], temp_static_dir)
139124

140-
cache["export_list"] = export_list
141-
cache["package_list"] = package_list
142-
143-
_write_idom_build_cache(temp_build_dir, cache)
125+
cache.save()
144126

145127
if BUILD_DIR.exists():
146128
shutil.rmtree(BUILD_DIR)
@@ -150,12 +132,56 @@ def install(
150132

151133
def restore() -> None:
152134
with Spinner("Restoring"):
153-
_delete_os_paths(WEB_MODULES, NODE_MODULES)
135+
_delete_os_paths(BUILD_DIR)
154136
_run_subprocess(["npm", "install"], STATIC_DIR)
155137
_run_subprocess(["npm", "run", "build"], STATIC_DIR)
156138
STATIC_SHIMS.clear()
157139

158140

141+
class Cache:
142+
"""Manages a cache file stored at ``build/.idom-cache.json``"""
143+
144+
__slots__ = "_file", "package_list", "export_list"
145+
146+
def __init__(self, path: Path) -> None:
147+
self._file = path / ".idom-cache.json"
148+
if not self._file.exists():
149+
self.package_list: List[str] = []
150+
self.export_list: List[str] = []
151+
else:
152+
self._load()
153+
154+
def add_packages(self, packages: Sequence[str], exports: Sequence[str]) -> None:
155+
package_list = _to_list_of_str(packages)
156+
export_list = _to_list_of_str(exports)
157+
export_list.extend(map(_export_name_from_package, package_list))
158+
self.package_list = list(set(self.package_list + package_list))
159+
self.export_list = list(set(self.export_list + export_list))
160+
161+
def delete_package(self, export_name: str) -> None:
162+
self.export_list.remove(export_name)
163+
for i, pkg in enumerate(self.package_list):
164+
if _export_name_from_package(pkg) == export_name:
165+
del self.package_list[i]
166+
break
167+
168+
def save(self) -> None:
169+
cache = {
170+
name: getattr(self, name)
171+
for name in self.__slots__
172+
if not name.startswith("_")
173+
}
174+
with self._file.open("w+") as f:
175+
json.dump(cache, f)
176+
177+
def _load(self) -> None:
178+
with self._file.open() as f:
179+
cache = json.load(f)
180+
for name in self.__slots__:
181+
if not name.startswith("_"):
182+
setattr(self, name, cache[name])
183+
184+
159185
def _run_subprocess(args: List[str], cwd: Union[str, Path]) -> None:
160186
try:
161187
subprocess.run(
@@ -176,23 +202,14 @@ def _delete_os_paths(*paths: Path) -> None:
176202
shutil.rmtree(p)
177203

178204

179-
def _read_idom_build_cache(path: Path) -> Dict[str, Any]:
180-
cache_file = path / ".idom-cache.json"
181-
if not cache_file.exists():
182-
return {
183-
"package_list": [],
184-
"export_list": [],
185-
}
186-
else:
187-
with cache_file.open() as f:
188-
return cast(Dict[str, Any], json.load(f))
189-
190-
191-
def _write_idom_build_cache(path: Path, cache: Dict[str, Any]) -> None:
192-
cache_file = path / ".idom-cache.json"
193-
with cache_file.open("w+") as f:
194-
json.dump(cache, f)
195-
196-
197205
def _to_list_of_str(value: Sequence[str]) -> List[str]:
198206
return [value] if isinstance(value, str) else list(value)
207+
208+
209+
def _export_name_from_package(pkg: str) -> str:
210+
at_count = pkg.count("@")
211+
if pkg.startswith("@") and at_count == 1:
212+
return pkg
213+
else:
214+
# this works even if there are no @ symbols
215+
return pkg.rsplit("@", 1)[0]

idom/client/static/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules
22
web_modules
3+
build

0 commit comments

Comments
 (0)