Skip to content

Commit e157656

Browse files
authored
Add parallel update path for prereleases. (#206)
This allows us to make prerelease releases that are available from python.org and will automatically update through subsequent prereleases until the next real release. Prereleases are not added to the Store.
1 parent 780374b commit e157656

File tree

4 files changed

+60
-16
lines changed

4 files changed

+60
-16
lines changed

_msbuild.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def main_exe(name):
102102
ItemDefinition('Link',
103103
SubSystem='CONSOLE',
104104
DelayLoadDLLs=f'{DLL_NAME}.dll;ole32.dll;shell32.dll;advapi32.dll',
105-
DisableSpecificWarnings=Prepend('4199;'),
105+
AdditionalOptions=Prepend('/IGNORE:4199 '),
106106
),
107107
INCLUDE_TMPDIR,
108108
Manifest('default.manifest'),
@@ -124,7 +124,7 @@ def mainw_exe(name):
124124
ItemDefinition('Link',
125125
SubSystem='WINDOWS',
126126
DelayLoadDLLs=f'{DLL_NAME}.dll;ole32.dll;shell32.dll;advapi32.dll',
127-
DisableSpecificWarnings=Prepend('4199;'),
127+
AdditionalOptions=Prepend('/IGNORE:4199 '),
128128
),
129129
INCLUDE_TMPDIR,
130130
ItemDefinition('ClCompile', PreprocessorDefinitions=Prepend(f'EXE_NAME=L"{name}";')),
@@ -296,6 +296,12 @@ def _make_xyzw_version(v, sep="."):
296296
return sep.join(map(str, (v.major, v.minor, micro, 0)))
297297

298298

299+
def _is_prerelease(v):
300+
from packaging.version import parse
301+
v = parse(v)
302+
return bool(v.pre)
303+
304+
299305
def _patch_appx_identity(source, dest, **new):
300306
from xml.etree import ElementTree as ET
301307
NS = {}
@@ -335,7 +341,7 @@ def update_file(file, content):
335341
def init_METADATA():
336342
import os, re
337343
_, sep, version = os.getenv("BUILD_SOURCEBRANCH", os.getenv("GITHUB_REF", "")).rpartition("/")
338-
if sep and "." in version:
344+
if "." in version:
339345
from packaging.version import parse
340346
try:
341347
# Looks like a version tag
@@ -359,6 +365,8 @@ def init_PACKAGE(tag=None):
359365
tmpdir = get_current_build_state().temp_dir
360366
INCLUDE_TMPDIR.options["AdditionalIncludeDirectories"] = Prepend(f"{tmpdir};")
361367

368+
pre = _is_prerelease(METADATA["Version"])
369+
362370
# GENERATE _version MODULE
363371
ver_py = tmpdir / "_version.py"
364372
update_file(ver_py, f"__version__ = {METADATA['Version']!r}")
@@ -384,6 +392,9 @@ def init_PACKAGE(tag=None):
384392

385393
appinstaller = tmpdir / "pymanager.appinstaller"
386394
_patch_appinstaller(PACKAGE.find("pymanager.appinstaller").source, appinstaller,
395+
# Prerelease builds use a separate online appinstaller file
396+
# Release builds upload to both files, to move beta users onto final
397+
AppInstallerName="pymanager-preview" if pre else "pymanager",
387398
Version=appx_version,
388399
Publisher=appx_publisher,
389400
Url=appx_url,

ci/upload.py

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
UPLOAD_URL_PREFIX = os.getenv("UPLOAD_URL_PREFIX", "https://www.python.org/ftp/")
99
UPLOAD_PATH_PREFIX = os.getenv("UPLOAD_PATH_PREFIX", "/srv/www.python.org/ftp/")
1010
UPLOAD_URL = os.getenv("UPLOAD_URL")
11-
UPLOAD_DIR = os.getenv("UPLOAD_DIR")
11+
UPLOAD_DIR = os.getenv("UPLOAD_DIR", "dist")
1212
UPLOAD_HOST = os.getenv("UPLOAD_HOST", "")
1313
UPLOAD_HOST_KEY = os.getenv("UPLOAD_HOST_KEY", "")
1414
UPLOAD_KEYFILE = os.getenv("UPLOAD_KEYFILE", "")
@@ -126,6 +126,25 @@ def url2path(url):
126126
return UPLOAD_PATH_PREFIX + url[len(UPLOAD_URL_PREFIX) :]
127127

128128

129+
def appinstaller_uri_matches(file, name):
130+
NS = {}
131+
with open(file, "r", encoding="utf-8") as f:
132+
NS = dict(e for _, e in ET.iterparse(f, events=("start-ns",)))
133+
for k, v in NS.items():
134+
ET.register_namespace(k, v)
135+
NS["x"] = NS[""]
136+
137+
with open(file, "r", encoding="utf-8") as f:
138+
xml = ET.parse(f)
139+
140+
self_uri = xml.find(".[@Uri]", NS).get("Uri")
141+
if not self_uri:
142+
print("##[error]Empty Uri attribute in appinstaller file")
143+
sys.exit(2)
144+
145+
return self_uri.rpartition("/")[2].casefold() == name.casefold()
146+
147+
129148
def validate_appinstaller(file, uploads):
130149
NS = {}
131150
with open(file, "r", encoding="utf-8") as f:
@@ -141,10 +160,8 @@ def validate_appinstaller(file, uploads):
141160
if not self_uri:
142161
print("##[error]Empty Uri attribute in appinstaller file")
143162
sys.exit(2)
144-
if not any(
145-
u.casefold() == self_uri.casefold() and f == file
146-
for f, u, _ in uploads
147-
):
163+
upload_targets = [u for f, u, _ in uploads if f == file]
164+
if not any(u.casefold() == self_uri.casefold() for u in upload_targets):
148165
print("##[error]Uri", self_uri, "in appinstaller file is not where "
149166
"the appinstaller file is being uploaded.")
150167
sys.exit(2)
@@ -164,6 +181,8 @@ def validate_appinstaller(file, uploads):
164181
print(file, "checked:")
165182
print("-", package_uri, "is part of this upload")
166183
print("-", self_uri, "is the destination of this file")
184+
if len(upload_targets) > 1:
185+
print(" - other destinations:", *(set(upload_targets) - set([self_uri])))
167186
print()
168187

169188

@@ -185,20 +204,35 @@ def purge(url):
185204
u = UPLOAD_URL + f.name
186205
UPLOADS.append((f, u, url2path(u)))
187206
else:
188-
for pat in ("python-manager-*.msix", "python-manager-*.msi", "pymanager.appinstaller"):
207+
for pat in ("python-manager-*.msix", "python-manager-*.msi"):
189208
for f in UPLOAD_DIR.glob(pat):
190209
u = UPLOAD_URL + f.name
191210
UPLOADS.append((f, u, url2path(u)))
192211

212+
# pymanager.appinstaller is always uploaded to the pymanager-preview URL,
213+
# and where the file specifies a different location, is also updated as its
214+
# own filename. Later validation checks that the URL listed in the file is
215+
# one of the planned uploads. If we ever need to release an update for the
216+
# "main" line but not prereleases, this code would have to be modified
217+
# (but more likely we'd just immediately modify or replace
218+
# 'pymanager.appinstaller' on the download server).
219+
f = UPLOAD_DIR / "pymanager.appinstaller"
220+
if f.is_file():
221+
u = UPLOAD_URL + "pymanager-preview.appinstaller"
222+
UPLOADS.append((f, u, url2path(u)))
223+
224+
if not appinstaller_uri_matches(f, "pymanager-preview.appinstaller"):
225+
u = UPLOAD_URL + f.name
226+
UPLOADS.append((f, u, url2path(u)))
227+
193228
print("Planned uploads:")
194229
for f, u, p in UPLOADS:
195230
print(f"{f} -> {p}")
196231
print(f" Final URL: {u}")
197232
print()
198233

199-
for f, *_ in UPLOADS:
200-
if f.match("*.appinstaller"):
201-
validate_appinstaller(f, UPLOADS)
234+
for f in {f for f, *_ in UPLOADS if f.match("*.appinstaller")}:
235+
validate_appinstaller(f, UPLOADS)
202236

203237
for f, u, p in UPLOADS:
204238
print("Upload", f, "to", p)

make.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@
2121

2222
ref = "none"
2323
try:
24-
if os.getenv("BUILD_SOURCEBRANCH"):
25-
ref = os.getenv("BUILD_SOURCEBRANCH")
26-
else:
24+
ref = os.getenv("BUILD_SOURCEBRANCH", os.getenv("GITHUB_REF", ""))
25+
if not ref:
2726
with subprocess.Popen(
2827
["git", "describe", "HEAD", "--tags"],
2928
stdout=subprocess.PIPE,

src/pymanager/pymanager.appinstaller

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<AppInstaller
33
xmlns="http://schemas.microsoft.com/appx/appinstaller/2018"
44
Version="${Version}"
5-
Uri="${Url}/pymanager.appinstaller">
5+
Uri="${Url}/${AppInstallerName}.appinstaller">
66

77
<MainPackage
88
Name="PythonSoftwareFoundation.PythonManager"

0 commit comments

Comments
 (0)