Skip to content

Commit 7423b99

Browse files
add release automation script (#65)
1 parent 8ff148f commit 7423b99

File tree

6 files changed

+501
-7
lines changed

6 files changed

+501
-7
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# *******************************************************************************
2+
# Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
#
4+
# See the NOTICE file(s) distributed with this work for additional
5+
# information regarding copyright ownership.
6+
#
7+
# This program and the accompanying materials are made available under the
8+
# terms of the Apache License Version 2.0 which is available at
9+
# https://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# SPDX-License-Identifier: Apache-2.0
12+
# *******************************************************************************
13+
14+
name: Check for Module Updates
15+
16+
on:
17+
schedule:
18+
- cron: '0 0,12 * * *' # Twice a day
19+
workflow_dispatch:
20+
pull_request_target:
21+
types: [opened, reopened, synchronize]
22+
merge_group:
23+
types: [checks_requested]
24+
25+
jobs:
26+
check-updates:
27+
runs-on: ubuntu-latest
28+
29+
permissions:
30+
contents: write
31+
pull-requests: write
32+
33+
steps:
34+
- name: Checkout the repository
35+
uses: actions/[email protected]
36+
37+
38+
- name: Run version checker script
39+
id: check_versions
40+
env:
41+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
42+
run: |
43+
pip install -r tools/requirements.txt
44+
python scripts/check_and_update_modules.py
45+
46+
- name: Create Pull Request
47+
uses: peter-evans/create-pull-request@v7
48+
with:
49+
title: Update modules

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@ bazel run //tools:license.check.license_check
1919
# Verify copyright:
2020
bazel run //tools:copyright.check
2121
```
22+
## Release automation
23+
24+
The release automation workflow will run 2 times a day once in 12 AM and once in 12 PM.
25+
26+
In case an urgent release of a module is needed, run the ```check_new_releases``` workflow manually.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# *******************************************************************************
2+
# Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
#
4+
# See the NOTICE file(s) distributed with this work for additional
5+
# information regarding copyright ownership.
6+
#
7+
# This program and the accompanying materials are made available under the
8+
# terms of the Apache License Version 2.0 which is available at
9+
# https://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# SPDX-License-Identifier: Apache-2.0
12+
# *******************************************************************************
13+
14+
import json
15+
import os
16+
from urllib.parse import urlparse
17+
18+
import requests
19+
from generate_module_files import generate_needed_files
20+
from github import Github
21+
22+
23+
def get_actual_versions_from_metadata(modules_path="modules"):
24+
actual_modules_versions = {}
25+
for module_name in os.listdir(modules_path):
26+
module_dir = os.path.join(modules_path, module_name)
27+
metadata_path = os.path.join(module_dir, "metadata.json")
28+
if os.path.isdir(module_dir) and os.path.exists(metadata_path):
29+
try:
30+
with open(metadata_path, "r") as f:
31+
metadata = json.load(f)
32+
versions = metadata.get("versions", [])
33+
if versions:
34+
actual_modules_versions[module_name] = versions[-1]
35+
else:
36+
actual_modules_versions[module_name] = None
37+
except Exception as e:
38+
print(f"Error reading {metadata_path}: {e}")
39+
actual_modules_versions[module_name] = None
40+
return actual_modules_versions
41+
42+
def get_latest_release_info(repo_url: str, github_token: str = ""):
43+
try:
44+
path_parts = urlparse(repo_url).path.strip('/').split('/')
45+
if len(path_parts) != 2:
46+
raise ValueError("Invalid GitHub repo URL format")
47+
owner, repo_name = path_parts
48+
49+
gh = Github(github_token) if github_token else Github()
50+
repo = gh.get_repo(f"{owner}/{repo_name}")
51+
52+
release = repo.get_latest_release()
53+
version = release.tag_name
54+
tarball = release.tarball_url
55+
56+
return repo_name, version, tarball
57+
58+
except Exception as e:
59+
print(f"Error fetching release info for {repo_url}: {e}")
60+
return None, None, None
61+
62+
def enrich_modules(modules_list, actual_versions_dict, github_token=""):
63+
enriched = []
64+
for module in modules_list:
65+
module_name = module["module_name"]
66+
module_url = module["module_url"]
67+
68+
repo_name, version, tarball = get_latest_release_info(module_url, github_token)
69+
if version is None:
70+
print(f"Skipping module {module_name} due to missing release info.")
71+
continue
72+
73+
clean_version = version.lstrip("v")
74+
actual_version = actual_versions_dict.get(module_name)
75+
76+
if clean_version != actual_version:
77+
enriched.append({
78+
"module_name": module_name,
79+
"module_url": module_url,
80+
"repo_name": repo_name,
81+
"module_version": clean_version,
82+
"tarball": tarball,
83+
"module_file_url": f"{module_url}/blob/{version}/MODULE.bazel"
84+
})
85+
else:
86+
print(f"Module {module_name} is up to date (version {clean_version})")
87+
88+
return enriched
89+
90+
def process_module(module):
91+
bazel_file_url = module["module_file_url"].replace("https://github.com", "https://raw.githubusercontent.com").replace("blob", "refs/tags")
92+
r = requests.get(bazel_file_url)
93+
if not r.ok:
94+
print(f"Failed to fetch MODULE.bazel for {module['module_name']}")
95+
return
96+
97+
bazel_content = r.text
98+
generate_needed_files(
99+
module_name=module["module_name"],
100+
module_version=module["module_version"],
101+
bazel_module_file_content=bazel_content,
102+
tarball=module["tarball"],
103+
repo_name=module["repo_name"]
104+
)
105+
print(f"✅ Successfully processed {module['module_name']}")
106+
107+
if __name__ == "__main__":
108+
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "")
109+
110+
modules = [
111+
{
112+
"module_name": "score_docs_as_code",
113+
"module_url": "https://github.com/eclipse-score/docs-as-code",
114+
},
115+
{
116+
"module_name": "score_process",
117+
"module_url": "https://github.com/eclipse-score/process_description",
118+
},
119+
{
120+
"module_name": "score_platform",
121+
"module_url": "https://github.com/eclipse-score/score",
122+
},
123+
{
124+
"module_name": "score_toolchains_gcc",
125+
"module_url": "https://github.com/eclipse-score/toolchains_gcc",
126+
},
127+
]
128+
129+
actual_versions = get_actual_versions_from_metadata("modules")
130+
modules_to_update = enrich_modules(modules, actual_versions, GITHUB_TOKEN)
131+
132+
if not modules_to_update:
133+
print("No modules need update.")
134+
print("[]")
135+
else:
136+
module_list = "\n".join(
137+
[f"- **{m['module_name']}**: {actual_versions.get(m['module_name'], 'unknown')}{m['module_version']}" for m in modules_to_update]
138+
)
139+
print("### Modules needing update (markdown list):")
140+
print(module_list)
141+
142+
for module in modules_to_update:
143+
process_module(module)
144+
145+
print(json.dumps(modules_to_update))

scripts/generate_module_files.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# *******************************************************************************
2+
# Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
#
4+
# See the NOTICE file(s) distributed with this work for additional
5+
# information regarding copyright ownership.
6+
#
7+
# This program and the accompanying materials are made available under the
8+
# terms of the Apache License Version 2.0 which is available at
9+
# https://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# SPDX-License-Identifier: Apache-2.0
12+
# *******************************************************************************
13+
14+
import argparse
15+
import json
16+
import subprocess
17+
from pathlib import Path
18+
19+
import requests
20+
21+
22+
def generate_source_json(new_version_dir: Path, tarball: str, repo_name: str, module_version: str):
23+
source_dict = {}
24+
process = subprocess.run(f"curl -Ls {tarball}" + " | sha256sum | awk '{ print $1 }' | xxd -r -p | base64", capture_output=True, shell=True, text=True)
25+
integrity = "sha256-" + process.stdout.strip()
26+
source_dict["integrity"] = str(integrity)
27+
source_dict["strip_prefix"] = f"{repo_name}-{module_version}"
28+
source_dict["url"] = tarball
29+
with open(new_version_dir / "source.json", "w") as f:
30+
f.write(json.dumps(source_dict, indent=4))
31+
32+
def change_metadata(module_path: Path, module_name: str, module_version: str):
33+
metadata_path = module_path / "metadata.json"
34+
35+
with metadata_path.open("r") as f:
36+
metadata = json.load(f)
37+
if module_version not in metadata["versions"]:
38+
metadata["versions"].append(module_version)
39+
with metadata_path.open("w") as f:
40+
json.dump(metadata, f, indent=4)
41+
42+
def generate_needed_files(module_name: str, module_version: str, bazel_module_file_content: str, tarball: str, repo_name: str):
43+
module_path = Path("modules") / module_name
44+
change_metadata(module_path, module_name, module_version)
45+
new_version_dir = module_path / module_version
46+
new_version_dir.mkdir(exist_ok=True)
47+
with open(new_version_dir / "MODULE.bazel", "w") as f:
48+
f.write(bazel_module_file_content)
49+
generate_source_json(new_version_dir, tarball,repo_name, module_version)
50+
51+
52+
53+
if __name__ == "__main__":
54+
parser = argparse.ArgumentParser(description='Download GitHub release files')
55+
parser.add_argument("--repo_name", help="Name of the repository")
56+
parser.add_argument('--module_file_url', help='Repository in format owner/repo')
57+
parser.add_argument('--module_name', help='Name of the folder of the to be release module (in bazel_registry)')
58+
parser.add_argument('--module_version', help='Module version')
59+
parser.add_argument('--tarball', help='Download source tarball')
60+
61+
# 1. get the module.bazel file
62+
args = parser.parse_args()
63+
bazel_file_url = args.module_file_url.replace("https://github.com","https://raw.githubusercontent.com").replace("blob", "refs/tags")
64+
r = requests.get(bazel_file_url)
65+
assert r.ok
66+
module_text = r.text
67+
generate_needed_files(args.module_name, args.module_version, module_text, args.tarball, args.repo_name)

tools/requirements.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ packaging
77

88
# needed by test_verify_semver_compatibility_level.py
99
pyfakefs
10+
11+
#need to fetch github API
12+
PyGithub

0 commit comments

Comments
 (0)