Skip to content

Commit e24bcf0

Browse files
committed
Updater script
1 parent e402f0d commit e24bcf0

File tree

3 files changed

+203
-0
lines changed

3 files changed

+203
-0
lines changed

builder.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,12 @@ def getRuntimeDlls():
258258
if file.endswith(".dll"):
259259
shutil.copy(os.path.join(datafolder, file), ".")
260260

261+
def installUpdater():
262+
shutil.copy(originalFolder + "\\standalone\\pyunity_updater.py", "..")
263+
py_compile.compile("..\\pyunity_updater.py", "..\\pyunity_updater.pyc")
264+
with zipfile.ZipFile("Lib\\python.zip", "a") as zf:
265+
zf.write("..\\pyunity_updater.pyc")
266+
261267
def copyExeInfoFiles():
262268
shutil.copy(originalFolder + "\\standalone\\pyunity-editor.c", "..")
263269
shutil.copy(originalFolder + "\\standalone\\icons.ico", "..")
@@ -346,6 +352,7 @@ def main():
346352
if MSVC_RUNTIME:
347353
getRuntimeDlls()
348354

355+
installUpdater()
349356
copyExeInfoFiles()
350357
if "GITHUB_ACTIONS" in os.environ:
351358
compileMsvc()

standalone/pyunity-editor.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ int main(int argc, char **argv) {
6666
int retcode = Py_Main(argc, program);
6767
exit(retcode);
6868
}
69+
PyObject *right3 = Py_BuildValue("s", "-U");
70+
PyObject *right4 = Py_BuildValue("s", "--update");
71+
if (PyUnicode_Compare(left, right3) == 0 ||
72+
PyUnicode_Compare(left, right4) == 0) {
73+
PyObject *updater = PyImport_ImportModule("pyunity_updater");
74+
CHECK_ERROR();
75+
PyObject *func = PyObject_GetAttrString(updater, "main");
76+
CHECK_ERROR();
77+
PyObject_CallFunction(func, NULL);
78+
CHECK_ERROR();
79+
}
6980

7081
PyObject *editor = PyImport_ImportModule("pyunity_editor.cli");
7182
CHECK_ERROR();

standalone/pyunity_updater.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
from pathlib import Path
2+
import subprocess
3+
import py_compile
4+
import tempfile
5+
import zipfile
6+
import urllib.request
7+
import ctypes
8+
import shutil
9+
import glob
10+
import sys
11+
import os
12+
13+
ZIP_OPTIONS = {"compression": zipfile.ZIP_DEFLATED, "compresslevel": 9}
14+
15+
originalFolder = os.getcwd()
16+
17+
class ZipFile(zipfile.ZipFile):
18+
def remove(self, zinfo_or_arcname):
19+
"""Remove a member from the archive."""
20+
21+
if self.mode not in ('w', 'x', 'a'):
22+
raise ValueError("remove() requires mode 'w', 'x', or 'a'")
23+
if not self.fp:
24+
raise ValueError(
25+
"Attempt to write to ZIP archive that was already closed")
26+
if self._writing:
27+
raise ValueError(
28+
"Can't write to ZIP archive while an open writing handle exists"
29+
)
30+
31+
# Make sure we have an existing info object
32+
if isinstance(zinfo_or_arcname, ZipInfo):
33+
zinfo = zinfo_or_arcname
34+
# make sure zinfo exists
35+
if zinfo not in self.filelist:
36+
raise KeyError(
37+
'There is no item %r in the archive' % zinfo_or_arcname)
38+
else:
39+
# get the info object
40+
zinfo = self.getinfo(zinfo_or_arcname)
41+
42+
return self._remove_members({zinfo})
43+
44+
def _remove_members(self, members, *, remove_physical=True, chunk_size=2**20):
45+
"""Remove members in a zip file.
46+
47+
All members (as zinfo) should exist in the zip; otherwise the zip file
48+
will erroneously end in an inconsistent state.
49+
"""
50+
fp = self.fp
51+
entry_offset = 0
52+
member_seen = False
53+
54+
# get a sorted filelist by header offset, in case the dir order
55+
# doesn't match the actual entry order
56+
filelist = sorted(self.filelist, key=lambda x: x.header_offset)
57+
for i in range(len(filelist)):
58+
info = filelist[i]
59+
is_member = info in members
60+
61+
if not (member_seen or is_member):
62+
continue
63+
64+
# get the total size of the entry
65+
try:
66+
offset = filelist[i + 1].header_offset
67+
except IndexError:
68+
offset = self.start_dir
69+
entry_size = offset - info.header_offset
70+
71+
if is_member:
72+
member_seen = True
73+
entry_offset += entry_size
74+
75+
# update caches
76+
self.filelist.remove(info)
77+
try:
78+
del self.NameToInfo[info.filename]
79+
except KeyError:
80+
pass
81+
continue
82+
83+
# update the header and move entry data to the new position
84+
if remove_physical:
85+
old_header_offset = info.header_offset
86+
info.header_offset -= entry_offset
87+
read_size = 0
88+
while read_size < entry_size:
89+
fp.seek(old_header_offset + read_size)
90+
data = fp.read(min(entry_size - read_size, chunk_size))
91+
fp.seek(info.header_offset + read_size)
92+
fp.write(data)
93+
fp.flush()
94+
read_size += len(data)
95+
96+
# Avoid missing entry if entries have a duplicated name.
97+
# Reverse the order as NameToInfo normally stores the last added one.
98+
for info in reversed(self.filelist):
99+
self.NameToInfo.setdefault(info.filename, info)
100+
101+
# update state
102+
if remove_physical:
103+
self.start_dir -= entry_offset
104+
self._didModify = True
105+
106+
# seek to the start of the central dir
107+
fp.seek(self.start_dir)
108+
109+
def errorMessage(msg):
110+
if sys.stderr is not None:
111+
sys.stderr.write(msg)
112+
else:
113+
ctypes.windll.user32.MessageBoxW(None, msg, "PyUnity Updater error", 0x10)
114+
exit(1)
115+
116+
def getPyUnity():
117+
url = "https://nightly.link/pyunity/pyunity/workflows/windows/develop/purepython.zip"
118+
print("GET", url, "-> pyunity-artifact.zip", flush=True)
119+
urllib.request.urlretrieve(url, "pyunity-artifact.zip")
120+
with zipfile.ZipFile("pyunity-artifact.zip") as zf:
121+
print("EXTRACT pyunity-artifact.zip", flush=True)
122+
zf.extractall("pyunity-artifact")
123+
file = glob.glob("pyunity-artifact/*.whl")[0]
124+
with zipfile.ZipFile(file) as zf:
125+
print("EXTRACT", os.path.basename(file), flush=True)
126+
zf.extractall("pyunity-package")
127+
128+
def getPyUnityEditor():
129+
url = "https://nightly.link/pyunity/pyunity-gui/workflows/windows/develop/purepython.zip"
130+
print("GET", url, "-> editor-artifact.zip", flush=True)
131+
urllib.request.urlretrieve(url, "editor-artifact.zip")
132+
with zipfile.ZipFile("editor-artifact.zip") as zf:
133+
print("EXTRACT editor-artifact.zip", flush=True)
134+
zf.extractall("editor-artifact")
135+
file = glob.glob("editor-artifact/*.whl")[0]
136+
with zipfile.ZipFile(file) as zf:
137+
print("EXTRACT", os.path.basename(file), flush=True)
138+
zf.extractall("editor-package")
139+
140+
# copied from builder.py
141+
def addPackage(zf, name, path, orig, distInfo=True):
142+
print("COMPILE", name, flush=True)
143+
os.chdir("..\\" + name)
144+
paths = glob.glob(path, recursive=True)
145+
if distInfo:
146+
paths.extend(glob.glob("*.dist-info\\**\\*", recursive=True))
147+
for file in paths:
148+
if file.endswith(".py"):
149+
py_compile.compile(file, file + "c", file, doraise=True)
150+
zf.write(file + "c")
151+
elif not file.endswith(".pyc"):
152+
zf.write(file)
153+
os.chdir(orig)
154+
155+
def updatePackages():
156+
getPyUnity()
157+
getPyUnityEditor()
158+
with ZipFile(__file__, "a", **ZIP_OPTIONS) as zf:
159+
removed = set()
160+
for file in zf.filelist:
161+
for folder in ["pyunity/", "pyunity-", "pyunity_editor/", "pyunity_editor-"]:
162+
if file.filename.startswith(folder):
163+
removed.add(file)
164+
break
165+
zf._remove_members(removed)
166+
167+
os.chdir(os.path.join(workdir, "pyunity-package"))
168+
addPackage(zf, "pyunity-package", "pyunity\\**\\*", workdir)
169+
os.chdir(os.path.join(workdir, "editor-package"))
170+
addPackage(zf, "editor-package", "pyunity_editor\\**\\*", workdir)
171+
172+
def main():
173+
source = os.path.abspath(__file__)
174+
if not os.path.dirname(source).endswith(".zip"):
175+
errorMessage("Updater source not found in zip\nZip file not locatable")
176+
177+
workdir = tempfile.mkdtemp()
178+
os.chdir(workdir)
179+
try:
180+
updatePackages()
181+
except Exception as e:
182+
errorMessage(traceback.format_exception(type(e), e, e.__traceback__))
183+
finally:
184+
os.chdir(originalFolder)
185+
shutil.rmtree(workdir)

0 commit comments

Comments
 (0)