-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
Copy pathbuild_process.py
189 lines (157 loc) · 6.75 KB
/
build_process.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import os
import sys
import json
import string
import shutil
import logging
import coloredlogs
import fire
import requests
from .._utils import run_command_with_process, compute_hash, job
logger = logging.getLogger(__name__)
coloredlogs.install(
fmt="%(asctime)s,%(msecs)03d %(levelname)s - %(message)s", datefmt="%H:%M:%S"
)
class BuildProcess:
def __init__(self, main, deps_info):
self.logger = logger
self.main = main
self.build_folder = self._concat(self.main, "build")
self.deps_info = deps_info
self.npm_modules = self._concat(self.main, "node_modules")
self.package_lock = self._concat(self.main, "package-lock.json")
self.package = self._concat(self.main, "package.json")
self._parse_package(path=self.package)
self.asset_paths = (self.deps_folder, self.npm_modules)
def _parse_package(self, path):
with open(path, "r", encoding="utf-8") as fp:
package = json.load(fp)
self.version = package["version"]
self.name = package["name"]
self.deps_folder = self._concat(self.main, os.pardir, "deps")
self.deps = package["dependencies"]
@staticmethod
def _concat(*paths):
return os.path.realpath(os.path.sep.join((path for path in paths if path)))
@staticmethod
def _clean_path(path):
if os.path.exists(path):
logger.warning("🚨 %s already exists, remove it!", path)
try:
if os.path.isfile(path):
os.remove(path)
if os.path.isdir(path):
shutil.rmtree(path)
except OSError:
sys.exit(1)
else:
logger.warning("🚨 %s doesn't exist, no action taken", path)
@job("clean all the previous assets generated by build tool")
def clean(self):
for path in self.asset_paths:
self._clean_path(path)
@job("run `npm ci`")
def npm(self):
"""Job to install npm packages."""
os.chdir(self.main)
run_command_with_process("npm ci")
@job("build the renderer in dev mode")
def watch(self):
os.chdir(self.main)
os.system("npm run build:dev")
@job("run the whole building process in sequence")
def build(self, build=None):
self.clean()
self.npm()
self.bundles(build)
self.digest()
@job("compute the hash digest for assets")
def digest(self):
if not os.path.exists(self.deps_folder):
try:
os.makedirs(self.deps_folder)
except OSError:
logger.exception("🚨 having issues manipulating %s", self.deps_folder)
sys.exit(1)
payload = {self.name: self.version}
for folder in (self.deps_folder, self.build_folder):
copies = tuple(
_
for _ in os.listdir(folder)
if os.path.splitext(_)[-1] in {".js", ".map"}
)
logger.info("bundles in %s %s", folder, copies)
for copy in copies:
payload[f"SHA256 ({copy})"] = compute_hash(self._concat(folder, copy))
with open(self._concat(self.main, "digest.json"), "w", encoding="utf-8") as fp:
json.dump(payload, fp, sort_keys=True, indent=4, separators=(",", ":"))
logger.info(
"bundle digest in digest.json:\n%s",
json.dumps(payload, sort_keys=True, indent=4),
)
@job("copy and generate the bundles")
def bundles(self, build=None): # pylint:disable=too-many-locals
if not os.path.exists(self.deps_folder):
try:
os.makedirs(self.deps_folder)
except OSError:
logger.exception("🚨 having issues manipulating %s", self.deps_folder)
sys.exit(1)
self._parse_package(self.package_lock)
getattr(self, "_bundles_extra", lambda: None)()
versions = {
"version": self.version,
"package": self.name.replace(" ", "_").replace("-", "_"),
}
for scope, name, subfolder, filename, extras in self.deps_info:
version = self.deps["/".join(filter(None, [scope, name]))]["version"]
name_squashed = name.replace("-", "").replace(".", "")
versions[name_squashed] = version
logger.info("copy npm dependency => %s", filename)
ext = "min.js" if "min" in filename.split(".") else "js"
target = f"{name}@{version}.{ext}"
shutil.copyfile(
self._concat(self.npm_modules, scope, name, subfolder, filename),
self._concat(self.deps_folder, target),
)
if extras:
extras_str = '", "'.join(extras)
versions[f"extra_{name_squashed}_versions"] = f'"{extras_str}"'
for extra_version in extras:
url = f"https://cdn.jsdelivr.net/npm/{name}@{extra_version}/umd/{filename}"
res = requests.get(url)
extra_target = f"{name}@{extra_version}.{ext}"
extra_path = self._concat(self.deps_folder, extra_target)
with open(extra_path, "wb") as fp:
fp.write(res.content)
_script = "build:dev" if build == "local" else "build:js"
logger.info("run `npm run %s`", _script)
os.chdir(self.main)
run_command_with_process(f"npm run {_script}")
logger.info("generate the `__init__.py` from template and versions")
with open(self._concat(self.main, "init.template"), encoding="utf-8") as fp:
t = string.Template(fp.read())
renderer_init = self._concat(self.deps_folder, os.pardir, "_dash_renderer.py")
with open(renderer_init, "w", encoding="utf-8") as fp:
fp.write(t.safe_substitute(versions))
class Renderer(BuildProcess):
def __init__(self):
"""dash-renderer's path is binding with the dash folder hierarchy."""
extras = [
"18.2.0",
"18.3.1",
] # versions to include beyond what's in package.json
super().__init__(
self._concat(os.path.dirname(__file__), os.pardir, "dash-renderer"),
(
("@babel", "polyfill", "dist", "polyfill.min.js", None),
(None, "react", "umd", "react.production.min.js", extras),
(None, "react", "umd", "react.development.js", extras),
(None, "react-dom", "umd", "react-dom.production.min.js", extras),
(None, "react-dom", "umd", "react-dom.development.js", extras),
(None, "prop-types", None, "prop-types.min.js", None),
(None, "prop-types", None, "prop-types.js", None),
),
)
def renderer():
fire.Fire(Renderer)