Skip to content

Commit 9f956d6

Browse files
authored
CI: run check-urls: rework script to run on changed files and let it run in CI (#787)
Rework the `check-urls.py` script to make it work with the changed files file from GitHub Actions. Add a new job to run `check-urls.py` and check projects with changed `hunter.cmake` files and their URLs. Fixes: #598
1 parent ffd46c8 commit 9f956d6

2 files changed

Lines changed: 136 additions & 27 deletions

File tree

.github/workflows/ci.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,32 @@ on:
2222

2323
jobs:
2424

25+
check_urls:
26+
runs-on: ubuntu-latest
27+
steps:
28+
- name: Git checkout
29+
uses: actions/checkout@v4.1.1
30+
31+
- name: Manual dispatch, get project name from input
32+
if: github.event_name == 'workflow_dispatch'
33+
run: |
34+
echo '["cmake/projects/${{ github.event.inputs.project }}/hunter.cmake"]' > ${HOME}/files.json
35+
36+
- name: Get changed files and save them to ${HOME}/files.json
37+
if: github.event_name != 'workflow_dispatch'
38+
id: files
39+
uses: lots0logs/gh-action-get-changed-files@2.2.2
40+
with:
41+
token: ${{ secrets.GITHUB_TOKEN }}
42+
43+
- name: Install dependencies
44+
run: |
45+
sudo apt-get install -yq python3-pycurl
46+
47+
- name: Check changed projects for working URLs
48+
run: |
49+
python maintenance/check-urls.py
50+
2551
set_matrix:
2652
runs-on: ubuntu-latest
2753
outputs:

maintenance/check-urls.py

100644100755
Lines changed: 110 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,37 @@
1-
from os.path import dirname, abspath, join
1+
#!/usr/bin/env python3
2+
# # -*- coding: utf-8 -*-
3+
4+
import argparse
25
from glob import glob
6+
import json
7+
import pathlib
8+
import os
9+
from os.path import dirname, abspath, join
310
import re
11+
import signal
412
import sys
513
import pycurl
614

15+
# failed projects, global to print on early abort with Ctrl+C
16+
failed_projects: dict[str, list[str]] = dict()
17+
18+
19+
def print_failed_projects():
20+
if failed_projects:
21+
print("failed projects:")
22+
print(json.dumps(failed_projects, indent=2))
23+
else:
24+
print("all clear! No project with failing URL found")
25+
26+
27+
def signal_handler(signal, frame):
28+
# force exit as pycurl.error after KeyboardInterrupt is caught
29+
# and then the next url is checked
30+
print("You pressed Ctrl+C!")
31+
print_failed_projects()
32+
sys.exit(1)
33+
34+
735
def getResponseStausCode(url):
836
try:
937
c = pycurl.Curl()
@@ -18,36 +46,91 @@ def getResponseStausCode(url):
1846
except pycurl.error:
1947
return 999
2048

21-
hunterDir = dirname(dirname(abspath(__file__)))
22-
projectsDir = join(hunterDir, 'cmake', 'projects')
2349

24-
project = ''
25-
if len(sys.argv) > 1:
26-
project = sys.argv[1]
50+
def main():
51+
parser = argparse.ArgumentParser()
52+
parser.add_argument(
53+
"projects",
54+
help="project names to process, used for local debugging",
55+
nargs="*",
56+
)
57+
parser.add_argument(
58+
"-o",
59+
"--output",
60+
help="specify file to write failed projects and URLs to, default write to stdout",
61+
type=str,
62+
default="",
63+
)
64+
args = parser.parse_args()
2765

28-
projectsFiles = join(projectsDir, project, '**', '*.cmake')
66+
repo_root = pathlib.Path(__file__).parent.parent
67+
projects_dir = repo_root / "cmake" / "projects"
2968

30-
checkedFile = join(hunterDir, 'maintenance', 'checked.txt')
31-
try:
32-
checkedStream = open(checkedFile, "r+")
33-
checked = checkedStream.readlines()
34-
except FileNotFoundError:
35-
checkedStream = open(checkedFile, "w")
36-
checked = []
69+
projects = set()
70+
if args.projects:
71+
if "all" in args.projects:
72+
print("project 'all' specified, checking all projects")
73+
project_hunter_files = projects_dir / "*" / "hunter.cmake"
74+
for hunter_file in glob(project_hunter_files.as_posix(), recursive=False):
75+
project = pathlib.Path(hunter_file).parent.name
76+
projects.add(project)
77+
else:
78+
for project in args.projects:
79+
if (projects_dir / project).is_dir():
80+
projects.add(project)
81+
else:
82+
raise RuntimeError(
83+
f"provided project doesn't exist: {project}: expected dir: {projects_dir / project}"
84+
)
85+
else:
86+
try:
87+
with open(os.environ.get("HOME") + "/files.json") as json_files:
88+
files = json.load(json_files)
89+
except IOError:
90+
raise RuntimeError("Can't read changed files from files.json")
3791

38-
projects = dict()
92+
p = re.compile("cmake/projects/([^/]+)")
93+
for file in files:
94+
if p.match(file):
95+
project = p.match(file).group(1)
96+
if (projects_dir / project).is_dir():
97+
projects.add(project)
3998

40-
for projectFile in glob(projectsFiles, recursive=True):
41-
with open(projectFile, "r") as file:
42-
content = file.read()
99+
# override signal handler to make it possible to hard exit at Ctrl+C
100+
# and print a status if we've found a failing URL or not yet
101+
signal.signal(signal.SIGINT, signal_handler)
43102

44-
entries = re.findall(r'hunter_add_version\s*\(\s*PACKAGE_NAME\s+"*(.*?)"*\s+VERSION\s+"*(.*?)"*\s+URL\s+"*(.*?)"*\s+SHA1\s+"*(.*?)"*\s+.*?\)', content, re.MULTILINE | re.DOTALL)
45-
if len(entries):
103+
for project in sorted(projects):
104+
print()
105+
print(f"checking project: {project}")
106+
hunter_file = projects_dir / project / "hunter.cmake"
107+
if not hunter_file.is_file():
108+
raise RuntimeError(f"hunter.cmake file not found: {hunter_file}")
109+
with open(hunter_file, "r", encoding="utf-8") as file:
110+
content = file.read()
111+
112+
entries = re.findall(
113+
r'hunter_add_version\s*\(\s*PACKAGE_NAME\s+"*(.*?)"*\s+VERSION\s+"*(.*?)"*\s+URL\s+"*(.*?)"*\s+SHA1\s+"*(.*?)"*\s+.*?\)',
114+
content,
115+
re.MULTILINE | re.DOTALL,
116+
)
117+
if len(entries) == 0:
118+
raise RuntimeError(
119+
f"no URLs found for project '{project}' in file: {hunter_file}"
120+
)
46121
for name, version, url, _ in entries:
47-
if not any(url == x.rstrip('\n') for x in checked):
48-
statusCode = getResponseStausCode(url)
49-
print(str(statusCode) + ' ' + url)
50-
if statusCode > 200:
51-
checkedStream.write(str(statusCode) + ' ' + url + '\n')
52-
53-
checkedStream.close()
122+
status_code = getResponseStausCode(url)
123+
print(f"{status_code} {url}")
124+
if status_code > 200:
125+
if project not in failed_projects:
126+
failed_projects[project] = []
127+
failed_projects[project].append(f"{status_code} {url}")
128+
print_failed_projects()
129+
if len(failed_projects) > 0 and args.output:
130+
with open(args.output, "w", encoding="utf-8") as file:
131+
json.dump(failed_projects, file, indent=2)
132+
return 0 if len(failed_projects) == 0 else 1
133+
134+
135+
if __name__ == "__main__":
136+
sys.exit(main())

0 commit comments

Comments
 (0)