226 lines
6.5 KiB
Python
226 lines
6.5 KiB
Python
# mypy: allow-untyped-defs
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
import requests
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
here = os.path.abspath(os.path.dirname(__file__))
|
|
wpt_root = os.path.abspath(os.path.join(here, os.pardir, os.pardir))
|
|
|
|
if wpt_root not in sys.path:
|
|
sys.path.append(wpt_root)
|
|
|
|
from tools.wpt.testfiles import get_git_cmd
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Status:
|
|
SUCCESS = 0
|
|
FAIL = 1
|
|
|
|
|
|
def run(cmd, return_stdout=False, **kwargs):
|
|
logger.info(" ".join(cmd))
|
|
if return_stdout:
|
|
f = subprocess.check_output
|
|
else:
|
|
f = subprocess.check_call
|
|
return f(cmd, **kwargs)
|
|
|
|
|
|
def create_manifest(path):
|
|
run(["./wpt", "manifest", "-p", path])
|
|
|
|
|
|
def create_web_feature_manifest(path):
|
|
run(["./wpt", "web-features-manifest", "-p", path])
|
|
|
|
|
|
def compress_manifest(path):
|
|
for args in [["gzip", "-k", "-f", "--best"],
|
|
["bzip2", "-k", "-f", "--best"],
|
|
["zstd", "-k", "-f", "--ultra", "-22", "-q"]]:
|
|
run(args + [path])
|
|
|
|
|
|
def request(url, desc, method=None, data=None, json_data=None, params=None, headers=None):
|
|
github_token = os.environ.get("GITHUB_TOKEN")
|
|
default_headers = {
|
|
"Authorization": "token %s" % github_token,
|
|
"Accept": "application/vnd.github.machine-man-preview+json"
|
|
}
|
|
|
|
_headers = default_headers
|
|
if headers is not None:
|
|
_headers.update(headers)
|
|
|
|
kwargs = {"params": params,
|
|
"headers": _headers}
|
|
try:
|
|
logger.info("Requesting URL %s" % url)
|
|
if json_data is not None or data is not None:
|
|
if method is None:
|
|
method = requests.post
|
|
kwargs["json"] = json_data
|
|
kwargs["data"] = data
|
|
elif method is None:
|
|
method = requests.get
|
|
|
|
resp = method(url, **kwargs)
|
|
|
|
except Exception as e:
|
|
logger.error(f"{desc} failed:\n{e}")
|
|
return None
|
|
|
|
try:
|
|
resp.raise_for_status()
|
|
except requests.HTTPError:
|
|
logger.error("%s failed: Got HTTP status %s. Response:" %
|
|
(desc, resp.status_code))
|
|
logger.error(resp.text)
|
|
return None
|
|
|
|
try:
|
|
return resp.json()
|
|
except ValueError:
|
|
logger.error("%s failed: Returned data was not JSON Response:" % desc)
|
|
logger.error(resp.text)
|
|
|
|
|
|
def get_pr(owner, repo, sha):
|
|
data = request("https://api.github.com/search/issues?q=type:pr+is:merged+repo:%s/%s+sha:%s" %
|
|
(owner, repo, sha), "Getting PR")
|
|
if data is None:
|
|
return None
|
|
|
|
items = data["items"]
|
|
if len(items) == 0:
|
|
logger.error("No PR found for %s" % sha)
|
|
return None
|
|
if len(items) > 1:
|
|
logger.warning("Found multiple PRs for %s" % sha)
|
|
|
|
pr = items[0]
|
|
|
|
return pr["number"]
|
|
|
|
|
|
def get_file_upload_details(manifest_path, sha):
|
|
"""
|
|
For a given file, generate details used to upload to GitHub.
|
|
"""
|
|
path = Path(manifest_path)
|
|
stem = path.stem
|
|
extension = path.suffix
|
|
upload_filename_prefix = f"{stem}-{sha}{extension}"
|
|
upload_label_prefix = path.name
|
|
upload_desc = f"{stem.title()} upload"
|
|
return upload_filename_prefix, upload_label_prefix, upload_desc
|
|
|
|
|
|
def create_release(manifest_file_paths, owner, repo, sha, tag, body):
|
|
logger.info(f"Creating a release for tag='{tag}', target_commitish='{sha}'")
|
|
create_url = f"https://api.github.com/repos/{owner}/{repo}/releases"
|
|
create_data = {"tag_name": tag,
|
|
"target_commitish": sha,
|
|
"name": tag,
|
|
"body": body,
|
|
"draft": True}
|
|
create_resp = request(create_url, "Release creation", json_data=create_data)
|
|
if not create_resp:
|
|
return False
|
|
|
|
# Upload URL contains '{?name,label}' at the end which we want to remove
|
|
upload_url = create_resp["upload_url"].split("{", 1)[0]
|
|
|
|
upload_exts = [".gz", ".bz2", ".zst"]
|
|
for manifest_path in manifest_file_paths:
|
|
upload_filename_prefix, upload_label_prefix, upload_desc = get_file_upload_details(manifest_path, sha)
|
|
for upload_ext in upload_exts:
|
|
upload_filename = f"{upload_filename_prefix}{upload_ext}"
|
|
params = {"name": upload_filename,
|
|
"label": f"{upload_label_prefix}{upload_ext}"}
|
|
|
|
with open(f"{manifest_path}{upload_ext}", "rb") as f:
|
|
upload_data = f.read()
|
|
|
|
logger.info("Uploading %s bytes" % len(upload_data))
|
|
|
|
upload_resp = request(upload_url, upload_desc, data=upload_data, params=params,
|
|
headers={'Content-Type': 'application/octet-stream'})
|
|
if not upload_resp:
|
|
return False
|
|
|
|
release_id = create_resp["id"]
|
|
edit_url = f"https://api.github.com/repos/{owner}/{repo}/releases/{release_id}"
|
|
edit_data = create_data.copy()
|
|
edit_data["draft"] = False
|
|
edit_resp = request(edit_url, "Release publishing", method=requests.patch, json_data=edit_data)
|
|
if not edit_resp:
|
|
return False
|
|
|
|
logger.info("Released %s" % edit_resp["html_url"])
|
|
return True
|
|
|
|
|
|
def should_dry_run():
|
|
with open(os.environ["GITHUB_EVENT_PATH"]) as f:
|
|
event = json.load(f)
|
|
logger.info(json.dumps(event, indent=2))
|
|
|
|
if "pull_request" in event:
|
|
logger.info("Dry run for PR")
|
|
return True
|
|
if event.get("ref") != "refs/heads/master":
|
|
logger.info("Dry run for ref %s" % event.get("ref"))
|
|
return True
|
|
return False
|
|
|
|
|
|
def main():
|
|
dry_run = should_dry_run()
|
|
|
|
manifest_path = os.path.join(tempfile.mkdtemp(), "MANIFEST.json")
|
|
web_features_manifest_path = os.path.join(tempfile.mkdtemp(), "WEB_FEATURES_MANIFEST.json")
|
|
|
|
create_manifest(manifest_path)
|
|
create_web_feature_manifest(web_features_manifest_path)
|
|
|
|
compress_manifest(manifest_path)
|
|
compress_manifest(web_features_manifest_path)
|
|
|
|
owner, repo = os.environ["GITHUB_REPOSITORY"].split("/", 1)
|
|
|
|
git = get_git_cmd(wpt_root)
|
|
head_rev = git("rev-parse", "HEAD").strip()
|
|
body = git("show", "--no-patch", "--format=%B", "HEAD")
|
|
|
|
if dry_run:
|
|
return Status.SUCCESS
|
|
|
|
pr = get_pr(owner, repo, head_rev)
|
|
if pr is None:
|
|
return Status.FAIL
|
|
tag_name = "merge_pr_%s" % pr
|
|
|
|
manifest_paths = [manifest_path, web_features_manifest_path]
|
|
if not create_release(manifest_paths, owner, repo, head_rev, tag_name, body):
|
|
return Status.FAIL
|
|
|
|
return Status.SUCCESS
|
|
|
|
|
|
if __name__ == "__main__":
|
|
code = main() # type: ignore
|
|
assert isinstance(code, int)
|
|
sys.exit(code)
|