88import subprocess
99import time
1010import typing
11+ import urllib
1112import zipfile
1213
1314import click
@@ -102,6 +103,108 @@ def fetch_github_actions_artifacts(
102103 return download_artifacts_github_actions (session , token , run_url )
103104
104105
106+ def wait_for_build_complete_circleci (
107+ session : requests .Session , token : str , pipeline_id : str
108+ ) -> None :
109+ while True :
110+ response = session .get (
111+ f"https://circleci.com/api/v2/pipeline/{ pipeline_id } /workflow" ,
112+ headers = {
113+ "Circle-Token" : token ,
114+ },
115+ )
116+ response .raise_for_status ()
117+ status = response .json ()["items" ][0 ]["status" ]
118+ if status == "success" :
119+ break
120+ elif status not in ("running" , "on_hold" , "not_run" ):
121+ raise ValueError (f"CircleCI build failed with status { status } " )
122+ time .sleep (3 )
123+
124+
125+ def download_artifacts_circleci (
126+ session : requests .Session , urls : typing .List [str ]
127+ ) -> typing .List [str ]:
128+ paths = []
129+ for url in urls :
130+ name = os .path .basename (urllib .parse .urlparse (url ).path )
131+ response = session .get (url )
132+ out_path = os .path .join (
133+ os .path .dirname (__file__ ),
134+ "dist" ,
135+ os .path .basename (name ),
136+ )
137+ with open (out_path , "wb" ) as f :
138+ f .write (response .content )
139+ paths .append (out_path )
140+ return paths
141+
142+
143+ def fetch_circleci_artifacts (token : str , version : str ) -> typing .List [str ]:
144+ session = requests .Session ()
145+
146+ response = session .get (
147+ "https://circleci.com/api/v2/pipeline?org-slug=gh/pyca" ,
148+ headers = {"Circle-Token" : token },
149+ )
150+ response .raise_for_status ()
151+ pipeline_id = None
152+ for item in response .json ()["items" ]:
153+ if item ["project_slug" ] == "gh/pyca/cryptography" :
154+ if item ["vcs" ].get ("tag" , None ) == version :
155+ pipeline_id = item ["id" ]
156+ break
157+
158+ if pipeline_id is None :
159+ raise ValueError (f"Could not find a pipeline for version { version } " )
160+
161+ wait_for_build_complete_circleci (session , token , pipeline_id )
162+ urls = fetch_circleci_artifact_urls (session , token , pipeline_id )
163+ return download_artifacts_circleci (session , urls )
164+
165+
166+ def fetch_circleci_artifact_urls (
167+ session : requests .Session , token : str , pipeline_id : str
168+ ) -> typing .List [str ]:
169+ response = session .get (
170+ f"https://circleci.com/api/v2/pipeline/{ pipeline_id } /workflow" ,
171+ headers = {"Circle-Token" : token },
172+ )
173+ response .raise_for_status ()
174+ workflow_id = response .json ()["items" ][0 ]["id" ]
175+ job_response = session .get (
176+ f"https://circleci.com/api/v2/workflow/{ workflow_id } /job" ,
177+ headers = {"Circle-Token" : token },
178+ )
179+ job_response .raise_for_status ()
180+ artifact_urls = []
181+ for job in job_response .json ()["items" ]:
182+ urls = fetch_circleci_artifact_url_from_job (
183+ session , token , job ["job_number" ]
184+ )
185+ artifact_urls .extend (urls )
186+
187+ return artifact_urls
188+
189+
190+ def fetch_circleci_artifact_url_from_job (
191+ session : requests .Session , token : str , job : str
192+ ) -> typing .List [str ]:
193+ response = session .get (
194+ f"https://circleci.com/api/v2/project/gh/pyca/cryptography/"
195+ f"{ job } /artifacts" ,
196+ headers = {"Circle-Token" : token },
197+ )
198+ response .raise_for_status ()
199+ urls = []
200+ for item in response .json ()["items" ]:
201+ url = item .get ("url" , None )
202+ if url is not None :
203+ urls .append (url )
204+
205+ return urls
206+
207+
105208@click .command ()
106209@click .argument ("version" )
107210def release (version : str ) -> None :
@@ -113,7 +216,12 @@ def release(version: str) -> None:
113216 f"https://github.com/settings/tokens/new?"
114217 f"description={ version } &scopes=repo"
115218 )
219+ print (
220+ "Get a CircleCI token at: "
221+ "https://app.circleci.com/settings/user/tokens"
222+ )
116223 github_token = getpass .getpass ("Github person access token: " )
224+ circle_token = getpass .getpass ("CircleCI token: " )
117225
118226 # Tag and push the tag (this will trigger the wheel builder in Actions)
119227 run ("git" , "tag" , "-s" , version , "-m" , "{0} release" .format (version ))
@@ -123,9 +231,13 @@ def release(version: str) -> None:
123231 github_actions_artifact_paths = fetch_github_actions_artifacts (
124232 github_token , version
125233 )
234+ # Download wheels from CircleCI
235+ circle_artifact_paths = fetch_circleci_artifacts (circle_token , version )
236+
237+ artifact_paths = github_actions_artifact_paths + circle_artifact_paths
126238
127239 # Upload wheels and sdist
128- run ("twine" , "upload" , * github_actions_artifact_paths )
240+ run ("twine" , "upload" , * artifact_paths )
129241
130242
131243if __name__ == "__main__" :
0 commit comments