main.py 4.49 KB
Newer Older
1 2 3 4 5
import logging.config
import os
import re
import subprocess
import sys
6
import json
7 8 9

from github import Github

10 11 12 13
REBUILD_ALL_PATTERNS = [
    r'^\.circleci/\.*',
    r'^\.github/\.*',
    r'^package\.json',
14
    r'ops/check-changed/.*',
15
]
16 17 18
with open("../../nx.json") as file:
    nx_json_data = json.load(file)
REBUILD_ALL_PATTERNS += nx_json_data["implicitDependencies"].keys()
19

20 21 22 23 24
GO_PATTERNS = [
    r'^go\.mod',
    r'^go\.sum',
]

25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
WHITELISTED_BRANCHES = {
    'master',
    'develop'
}

LOGGING_CONFIG = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(levelname)s]: %(message)s'
        },
    },
    'handlers': {
        'default': {
            'level': 'INFO',
            'formatter': 'standard',
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stderr'
        },
    },
    'loggers': {
        '': {
            'handlers': ['default'],
            'level': 'INFO',
            'propagate': False
        },
    }
}

logging.config.dictConfig(LOGGING_CONFIG)
log = logging.getLogger(__name__)


def main():
60 61 62 63
    patterns = sys.argv[1].split(',') + REBUILD_ALL_PATTERNS
    no_go_deps = os.getenv('CHECK_CHANGED_NO_GO_DEPS')
    if no_go_deps is None:
        patterns = patterns + GO_PATTERNS
64 65 66 67 68 69 70 71 72 73 74 75 76 77

    fp = os.path.realpath(__file__)
    monorepo_path = os.path.realpath(os.path.join(fp, '..', '..'))

    log.info('Discovered monorepo path: %s', monorepo_path)
    current_branch = git_cmd('rev-parse --abbrev-ref HEAD', monorepo_path)
    log.info('Current branch: %s', current_branch)

    if current_branch in WHITELISTED_BRANCHES:
        log.info('Current branch %s is whitelisted, triggering build', current_branch)
        exit_build()

    pr_urls = os.getenv('CIRCLE_PULL_REQUESTS', None)
    pr_urls = pr_urls.split(',') if pr_urls else []
78 79 80 81 82 83 84 85 86

    # If we successfully extracted a PR number and did not find PRs from CIRCLE_PULL_REQUESTS,
    # we are on a merge queue branch and can reconstruct the original PR URL from the PR number.
    pr_number = extract_pr_number(current_branch)
    if not pr_urls and pr_number is not None:
        log.info('No PR URLs found but extracted branch number, constructing PR URL')
        base_url = "https://github.com/ethereum-optimism/optimism/pull/"
        pr_urls = [base_url + pr_number]

87
    if len(pr_urls) == 0:
88 89
        log.info('Not a PR build, triggering build')
        exit_build()
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
    if len(pr_urls) > 1:
        log.warning('Multiple PR URLs found, choosing the first one. PRs found:')
        for url in pr_urls:
            log.warning(url)

    gh_token = os.getenv('GITHUB_ACCESS_TOKEN')
    if gh_token is None:
        log.info('No GitHub access token found - likely a fork. Triggering build')
        exit_build()

    g = Github(gh_token)
    try:
        g.get_user()
        repo = g.get_repo(os.getenv('CIRCLE_PROJECT_USERNAME') + '/' + os.getenv('CIRCLE_PROJECT_REPONAME'))
    except Exception:
        log.exception('Failed to get repo from GitHub')
        exit_build()

    pr = repo.get_pull(int(pr_urls[0].split('/')[-1]))
    log.info('Found PR: %s', pr.url)

    base_sha = pr.base.sha
    head_sha = pr.head.sha

    diffs = git_cmd('diff --name-only {}...{}'.format(base_sha, head_sha), monorepo_path).split('\n')
    log.info('Found diff. Checking for matches...')
    for diff in diffs:
        if match_path(diff, patterns):
            log.info('Match found, triggering build')
            exit_build()
        else:
            log.info('❌ no match found on %s', diff)

    log.info('No matches found, skipping build')
    exit_nobuild()


def git_cmd(cmd, cwd):
    return subprocess.check_output(['git'] + cmd.split(' '), cwd=cwd).decode('utf-8').strip()


def match_path(path, patterns):
    for pattern in patterns:
        if re.search(pattern, path):
            log.info('✅ match found on %s: %s', path, pattern)
            return True
    return False

138 139 140 141 142 143 144 145 146
def extract_pr_number(branch_name):
    # Merge queue branches are named: gh-readonly-queue/{base_branch}/pr-{number}-{sha}
    match = re.search(r'/pr-(\d+)-', branch_name)
    if match:
        pr_number = match.group(1)
        log.info('Extracted PR number: %s', pr_number)
        return pr_number
    else:
        return None
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162

def exit_build():
    sys.exit(0)


def exit_nobuild():
    subprocess.check_call(['circleci', 'step', 'halt'])
    sys.exit(0)


if __name__ == '__main__':
    try:
        main()
    except Exception:
        log.exception('Unhandled exception, triggering build')
        exit_build()