Skip to content

Commit 098a1cb

Browse files
authored
Merge pull request #2352 from pygame-community/ankith26-readd-circleci
Re-add CircleCI for aarch64 builds
2 parents 0749a81 + 91c4171 commit 098a1cb

File tree

3 files changed

+186
-6
lines changed

3 files changed

+186
-6
lines changed

.circleci/config.yml

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,55 @@
11
# Use the latest 2.1 version of CircleCI pipeline process engine.
22
# See: https://circleci.com/docs/2.0/configuration-reference
33
version: 2.1
4+
parameters:
5+
GHA_Actor:
6+
type: string
7+
default: ""
8+
GHA_Action:
9+
type: string
10+
default: ""
11+
GHA_Event:
12+
type: string
13+
default: ""
14+
GHA_Meta:
15+
type: string
16+
default: ""
417

518
# Define a job to be invoked later in a workflow.
619
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
720
jobs:
821
linux-arm-wheels:
922
working_directory: ~/linux-wheels
1023
machine:
11-
image: ubuntu-2004:current
24+
image: ubuntu-2204:current
25+
docker_layer_caching: true
26+
1227
resource_class: arm.medium
1328

1429
environment:
1530
# these environment variables will be passed to the docker container
1631
- CIBW_ENVIRONMENT: PIP_CONFIG_FILE=buildconfig/pip_config.ini PORTMIDI_INC_PORTTIME=1 SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=disk
32+
- CIBW_PRERELEASE_PYTHONS: True # for 3.12 testing
33+
- CIBW_BUILD: "cp3{[7-9],10,11,12}-* pp3{[8-9],10}-*"
1734
- CIBW_ARCHS: aarch64
1835
- CIBW_SKIP: '*-musllinux_*'
19-
- CIBW_MANYLINUX_AARCH64_IMAGE: pygame/manylinux2014_base_aarch64
20-
- CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: pygame/manylinux2014_base_aarch64
36+
- CIBW_MANYLINUX_AARCH64_IMAGE: manylinux2014_base_aarch64
37+
- CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: manylinux2014_base_aarch64
2138
- CIBW_BEFORE_BUILD: pip install Sphinx && python setup.py docs
2239
- CIBW_TEST_COMMAND: python -m pygame.tests -v --exclude opengl,music,timing --time_out 300
2340
- CIBW_BUILD_VERBOSITY: 2
2441

2542
steps:
2643
- checkout
44+
- run:
45+
name: Build the aarch64 base image (and cache it)
46+
working_directory: buildconfig/manylinux-build/docker_base
47+
command: docker build -t manylinux2014_base_aarch64 -f Dockerfile-aarch64 .
48+
2749
- run:
2850
name: Build the Linux wheels.
2951
command: |
30-
pip3 install --user cibuildwheel==2.12.0
52+
pip3 install --user cibuildwheel==2.14.1
3153
PATH="$HOME/.local/bin:$PATH" cibuildwheel --output-dir wheelhouse
3254
3355
- store_artifacts:

.github/workflows/release-gh-draft.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ on:
55
branches: 'release/**'
66

77
jobs:
8+
manylinux-aarch64:
9+
runs-on: ubuntu-latest
10+
outputs:
11+
pipeline_id: ${{ steps.circleci.outputs.id }}
12+
13+
steps:
14+
- name: Trigger CircleCI builds on release
15+
id: circleci
16+
uses: CircleCI-Public/[email protected]
17+
env:
18+
CCI_TOKEN: ${{ secrets.CCI_TOKEN }}
19+
820
manylinux:
921
uses: ./.github/workflows/build-manylinux.yml
1022

@@ -18,14 +30,22 @@ jobs:
1830
uses: ./.github/workflows/build-ubuntu-sdist.yml
1931

2032
draft-release:
21-
needs: [manylinux, macos, windows, sdist]
33+
needs: [manylinux-aarch64, manylinux, macos, windows, sdist]
2234
runs-on: ubuntu-latest
2335
steps:
2436
- uses: actions/[email protected]
2537

2638
- name: Download all artifacts
2739
uses: actions/download-artifact@v3
28-
40+
41+
- name: Download manylinux-aarch64 artifacts from CircleCI
42+
continue-on-error: true # incase things don't work here, can manually handle it
43+
run: >
44+
python3 buildconfig/ci/circleci/pull_circleci_artifacts.py
45+
${{ secrets.CCI_TOKEN }}
46+
${{ needs.manylinux-aarch64.outputs.pipeline_id }}
47+
pygame-wheels-manylinux
48+
2949
# Strips 'release/' from the ref_name, this helps us access the version
3050
# name as 'steps.ver.outputs.VER'
3151
- name: Get version
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"""
2+
A script to automate downloading CircleCI artifacts.
3+
4+
Usage: python3 pull_circleci_artifacts.py <TOKEN> <PIPELINE_ID> <SAVE_DIR>
5+
TOKEN:
6+
CircleCI "personal access token" of a github (preferably machine) user.
7+
This is secret!
8+
9+
PIPELINE_ID:
10+
A unique string id that represents the CircleCI pipeline, whose artifacts this
11+
script pulls.
12+
This pipeline must have exactly one workflow and that workflow must have exactly
13+
one job. This script waits for the pipeline to finish, and pulls artifacts from
14+
this job. If the pipeline isn't successful on finish, this script exits with an
15+
error.
16+
17+
SAVE_DIR:
18+
The downloaded artifacts are saved to this directory
19+
20+
CircleCI API docs: https://circleci.com/docs/api/v2/index.html (useful for understanding
21+
this code)
22+
"""
23+
24+
# yup, all these are stdlib modules incase you are wondering
25+
import concurrent.futures
26+
import http.client
27+
import json
28+
import sys
29+
import time
30+
from pathlib import Path
31+
from urllib import request
32+
33+
_, token, pipeline_id, save_dir = sys.argv
34+
35+
headers = {"Circle-Token": token}
36+
save_dir = Path(save_dir)
37+
38+
print(
39+
f"Starting for {pipeline_id = } (and {save_dir = }), now establishing connection..."
40+
)
41+
42+
cci_api = http.client.HTTPSConnection("circleci.com")
43+
44+
45+
def paginate_get_items_and_next(url, next_page=""):
46+
"""
47+
Helper to get "items" and "next_page_token" from CircleCI API, used to handle
48+
pagination.
49+
"""
50+
51+
# page-token is used for pagination. Initially, it is unspecified.
52+
url_query = f"{url}?page-token={next_page}" if next_page else url
53+
cci_api.request("GET", f"/api/v2/{url_query}", headers=headers)
54+
response = cci_api.getresponse()
55+
if response.status != 200:
56+
raise RuntimeError(
57+
f"Request to '{url}' not successful: {response.status} ({response.reason})"
58+
)
59+
60+
response_dict = json.loads(response.read())
61+
if "message" in response_dict:
62+
raise RuntimeError(
63+
f"Request to '{url}' failed with message - {response_dict['message']}"
64+
)
65+
66+
return response_dict["items"], response_dict["next_page_token"]
67+
68+
69+
def paginate_get_single_item(url):
70+
"""
71+
Helper to get exactly one item from CircleCI paginated APIs
72+
"""
73+
items, _ = paginate_get_items_and_next(url)
74+
if len(items) != 1:
75+
raise RuntimeError(f"Expected one item, got {len(items)}")
76+
77+
return items[0]
78+
79+
80+
def paginate_get_all_items(url):
81+
"""
82+
Helper to get all "items" from CircleCI paginated APIs
83+
"""
84+
prev_page_tag = ""
85+
while True:
86+
items, prev_page_tag = paginate_get_items_and_next(url, prev_page_tag)
87+
if not items:
88+
# all artifacts are probably downloaded at this point
89+
break
90+
91+
yield from items
92+
if not prev_page_tag:
93+
# done with pagination, exit
94+
break
95+
96+
97+
def download_artifact(artifact):
98+
"""
99+
Helper to download an artifact given an "artifact dict". This can be concurrently
100+
called in multiple threads to speed up downloads.
101+
"""
102+
path = Path(artifact["path"])
103+
save_path = save_dir / path.name
104+
print(f"Downloading {path.name}")
105+
request.urlretrieve(artifact["url"], save_path)
106+
print(f"Done with saving {path.name}")
107+
108+
109+
cnt = 1
110+
while True:
111+
print(f"\nAttempt {cnt}")
112+
workflow = paginate_get_single_item(f"/pipeline/{pipeline_id}/workflow")
113+
if workflow["status"] != "running":
114+
if workflow["status"] != "success":
115+
# workflow failed
116+
raise RuntimeError(f"The workflow has status '{workflow['status']}'")
117+
118+
# successfully finished workflow at this point
119+
job = paginate_get_single_item(f"/workflow/{workflow['id']}/job")
120+
121+
# shouldn't really happen, but test anyways
122+
if job["status"] != "success":
123+
raise RuntimeError(f"The job has status '{workflow['status']}'")
124+
125+
print(f"Downloading artifacts (they will all be saved in {str(save_dir)})")
126+
with concurrent.futures.ThreadPoolExecutor() as pool:
127+
pool.map(
128+
download_artifact,
129+
paginate_get_all_items(
130+
f"/project/{job['project_slug']}/{job['job_number']}/artifacts"
131+
),
132+
)
133+
134+
break
135+
136+
cnt += 1
137+
print("Job is still running (now sleeping for 30s before retrying)")
138+
time.sleep(30)

0 commit comments

Comments
 (0)