blob: ff418b3118396445a02edf22d813b528d4a631d2 [file]
#!/usr/bin/env python3
# Copyright 2026 The Emscripten Authors. All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License. Both these licenses can be
# found in the LICENSE file.
"""Synchronize emscripten dependency repositories (llvm-project and binaryen).
Discovers the revisions by parsing test/emsdk_version.txt in this repository
and fetching the corresponding DEPS file from the emscripten-releases repository.
Use this script if you want to build llvm and binaryn from source, and
want to keep them in sync with the versions implied by test/emsdk_version.txt.
"""
import argparse
import base64
import os
import re
import subprocess
import sys
import urllib.request
script_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(os.path.dirname(script_dir))
sys.path.insert(0, root_dir)
from tools import utils
VERBOSE = False
DRY_RUN = False
# Hardcode the URLs here rather than depend on specific named remotes
# (e.g. origin vs upstream)
LLVM_URL = 'https://github.com/llvm/llvm-project.git'
BINARYEN_URL = 'https://github.com/WebAssembly/binaryen.git'
def get_emsdk_version(emsdk_version_file=None):
if not emsdk_version_file:
emsdk_version_file = utils.path_from_root('test', 'emsdk_version.txt')
return utils.read_file(emsdk_version_file).strip()
def fetch_deps(emsdk_hash):
url = (
'https://chromium.googlesource.com/emscripten-releases/+/'
f'{emsdk_hash}/DEPS?format=TEXT'
)
if VERBOSE:
print(f'Fetching DEPS file from {url}...')
with urllib.request.urlopen(url) as response:
return base64.b64decode(response.read()).decode('utf-8')
def parse_deps(content):
revisions = {}
for key in ('llvm_project_revision', 'binaryen_revision'):
pattern = rf"['\"]{re.escape(key)}['\"]\s*:\s*['\"]([0-9a-fA-F]{{7,40}})['\"]"
m = re.search(pattern, content)
if not m:
utils.exit_with_error(f"Failed to parse '{key}' from DEPS file")
revisions[key] = m.group(1)
return revisions
def has_revision(repo_dir, revision):
res = subprocess.run(
['git', '-C', repo_dir, 'rev-parse', '--verify', f'{revision}^{{commit}}'],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
return res.returncode == 0
def get_current_revision(repo_dir):
return subprocess.check_output(
['git', '-C', repo_dir, 'rev-parse', 'HEAD'], text=True,
).strip()
def is_dirty(repo_dir):
return bool(subprocess.check_output(['git', '-C', repo_dir, 'status', '--porcelain'], text=True).strip())
def sync_repo(name, repo_dir, revision, url):
if not os.path.isdir(repo_dir):
utils.exit_with_error(
f"Directory for {name} does not exist: '{repo_dir}'. "
f"Use --{name}-dir to specify its location or --no-{name} to skip.",
)
print(f'Syncing {name} => {revision}')
cur_rev = get_current_revision(repo_dir)
if cur_rev.startswith(revision) or revision.startswith(cur_rev):
print(' already at the requested revision')
return
if is_dirty(repo_dir):
utils.exit_with_error("Directory for {name} is dirty: '{repo_dir}'")
if not has_revision(repo_dir, revision):
print(' Fetching revision')
if not DRY_RUN:
subprocess.check_call(['git', '-C', repo_dir, 'fetch', url, revision])
if not has_revision(repo_dir, revision):
utils.exit_with_error(
f"Revision {revision} not found in '{repo_dir}' even after fetching",
)
print(' Checking out revision')
if not DRY_RUN:
subprocess.check_call(['git', '-C', repo_dir, 'checkout', revision])
def main():
parent_dir = os.path.dirname(utils.path_from_root())
default_llvm_dir = os.path.join(parent_dir, 'llvm-project')
default_binaryen_dir = os.path.join(parent_dir, 'binaryen')
parser = argparse.ArgumentParser(
description='Sync emscripten dependency repos (llvm-project and binaryen) '
'to the revisions used in test/emsdk_version.txt.',
)
parser.add_argument('--llvm-dir', default=default_llvm_dir,
help='Path to the llvm-project repo (default: %(default)s)',
)
parser.add_argument('--binaryen-dir', default=default_binaryen_dir,
help='Path to the binaryen repo (default: %(default)s)',
)
parser.add_argument('-n', '--dry-run', action='store_true',
help='Print what would be done without modifying repositories',
)
parser.add_argument('-v', '--verbose',
action='store_true',
help='Print verbose diagnostic output',
)
args = parser.parse_args()
global VERBOSE, DRY_RUN
VERBOSE = args.verbose
DRY_RUN = args.dry_run
emsdk_hash = get_emsdk_version()
if VERBOSE or args.dry_run:
print(f'Using emscripten-releases revision: {emsdk_hash}')
deps_content = fetch_deps(emsdk_hash)
revisions = parse_deps(deps_content)
sync_repo('llvm-project', args.llvm_dir, revisions['llvm_project_revision'], LLVM_URL)
sync_repo('binaryen', args.binaryen_dir, revisions['binaryen_revision'], BINARYEN_URL)
print('Done.')
if __name__ == '__main__':
main()