88UPLOAD_URL_PREFIX = os .getenv ("UPLOAD_URL_PREFIX" , "https://www.python.org/ftp/" )
99UPLOAD_PATH_PREFIX = os .getenv ("UPLOAD_PATH_PREFIX" , "/srv/www.python.org/ftp/" )
1010UPLOAD_URL = os .getenv ("UPLOAD_URL" )
11- UPLOAD_DIR = os .getenv ("UPLOAD_DIR" )
11+ UPLOAD_DIR = os .getenv ("UPLOAD_DIR" , "dist" )
1212UPLOAD_HOST = os .getenv ("UPLOAD_HOST" , "" )
1313UPLOAD_HOST_KEY = os .getenv ("UPLOAD_HOST_KEY" , "" )
1414UPLOAD_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+
129148def 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 )))
187206else :
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+
193228print ("Planned uploads:" )
194229for f , u , p in UPLOADS :
195230 print (f"{ f } -> { p } " )
196231 print (f" Final URL: { u } " )
197232print ()
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
203237for f , u , p in UPLOADS :
204238 print ("Upload" , f , "to" , p )
0 commit comments