From b5bf054cb1bde5c13a55dc048117be5b6c0fdad8 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Sat, 4 Jul 2026 07:47:51 +0000 Subject: [PATCH 1/3] Improve library update scripts - This checks the that Emscripten directory is clean (has no uncommitted changes) before copying its contents. You can force updating with `-f`/`--force`. - This automatically updates version numbers in `readme.txt`. Suggested in https://github.com/emscripten-core/emscripten/pull/27252#issuecomment-4879991836. --- system/lib/update_common.py | 74 ++++++++++++++++++++++++++++---- system/lib/update_compiler_rt.py | 4 +- system/lib/update_libcxx.py | 3 ++ system/lib/update_libcxxabi.py | 4 +- system/lib/update_libunwind.py | 4 +- system/lib/update_llvm_libc.py | 5 ++- system/lib/update_musl.py | 12 +----- 7 files changed, 83 insertions(+), 23 deletions(-) diff --git a/system/lib/update_common.py b/system/lib/update_common.py index 54f861f03fa55..6d77e2d8fbe5f 100644 --- a/system/lib/update_common.py +++ b/system/lib/update_common.py @@ -1,22 +1,28 @@ import os import sys import shutil +import subprocess +import re +import argparse script_dir = os.path.abspath(os.path.dirname(__file__)) emscripten_root = os.path.dirname(os.path.dirname(script_dir)) default_llvm_dir = os.path.join(os.path.dirname(emscripten_root), 'llvm-project') -def get_llvm_dir(): - if len(sys.argv) > 1: - llvm_dir = os.path.abspath(sys.argv[1]) - else: - llvm_dir = default_llvm_dir - if not os.path.isdir(llvm_dir): - print(f'LLVM directory not found: {llvm_dir}', file=sys.stderr) - print(f'Usage: {sys.argv[0]} [llvm_dir]', file=sys.stderr) +def parse_args(default_dir, dir_name='llvm_dir'): + parser = argparse.ArgumentParser(description=f'Update library from {dir_name}') + parser.add_argument('-f', '--force', action='store_true', help='Force update even if the Emscripten tree is not clean') + parser.add_argument('src_dir', nargs='?', default=default_dir, help=f'Path to {dir_name}') + args = parser.parse_args() + + src_dir = os.path.abspath(args.src_dir) + if not os.path.isdir(src_dir): + print(f'{dir_name} directory not found: {src_dir}', file=sys.stderr) + parser.print_help(sys.stderr) sys.exit(1) - return llvm_dir + + return src_dir, args.force def clean_dir(dirname, preserve_files=()): @@ -47,3 +53,53 @@ def copy_tree(upstream_dir, local_dir, excludes=()): if f in excludes: full = os.path.join(root, f) os.remove(full) + + +def check_clean(force=False): + if force: + return + try: + status = subprocess.check_output(['git', 'status', '--porcelain', '--untracked-files=no'], cwd=emscripten_root, stderr=subprocess.PIPE).decode('utf-8').strip() + if status: + print('Emscripten tree is not clean (has uncommitted changes).', file=sys.stderr) + sys.exit(1) + except subprocess.CalledProcessError as e: + print('Emscripten tree is not a valid git repository or git failed.', file=sys.stderr) + if e.stderr: + print(e.stderr.decode('utf-8').strip(), file=sys.stderr) + sys.exit(1) + + +def get_llvm_version(upstream_dir): + cmake_file = os.path.join(upstream_dir, 'cmake', 'Modules', 'LLVMVersion.cmake') + if not os.path.exists(cmake_file): + return None, None + with open(cmake_file, 'r') as f: + content = f.read() + major = re.search(r'set\(LLVM_VERSION_MAJOR\s+(\d+)\)', content) + minor = re.search(r'set\(LLVM_VERSION_MINOR\s+(\d+)\)', content) + patch = re.search(r'set\(LLVM_VERSION_PATCH\s+(\d+)\)', content) + if major and minor and patch: + return major.group(1), f'{major.group(1)}.{minor.group(1)}.{patch.group(1)}' + return None, None + + +def update_readme(local_dir, llvm_dir): + readme_path = os.path.join(local_dir, 'readme.txt') + if not os.path.exists(readme_path): + readme_path = os.path.join(local_dir, 'README.txt') + if not os.path.exists(readme_path): + return + + with open(readme_path, 'r') as f: + content = f.read() + + major, full_version = get_llvm_version(llvm_dir) + if major and full_version: + # Find full version numbers in the form of x.y.z and update them + content = re.sub(r'\b\d+\.\d+\.\d+\b', full_version, content) + # Find 'emscripten-libs-NN' strings and update them with the major version + content = re.sub(r'emscripten-libs-\d+', f'emscripten-libs-{major}', content) + + with open(readme_path, 'w') as f: + f.write(content) diff --git a/system/lib/update_compiler_rt.py b/system/lib/update_compiler_rt.py index b0057e86547fc..229304ab7ed54 100755 --- a/system/lib/update_compiler_rt.py +++ b/system/lib/update_compiler_rt.py @@ -30,7 +30,8 @@ def main(): - llvm_dir = get_llvm_dir() + llvm_dir, force = parse_args(default_llvm_dir, 'llvm_dir') + check_clean(force) upstream_dir = os.path.join(llvm_dir, 'compiler-rt') assert os.path.exists(upstream_dir) upstream_src = os.path.join(upstream_dir, 'lib', 'builtins') @@ -53,6 +54,7 @@ def main(): shutil.copy2(os.path.join(upstream_dir, 'CREDITS.TXT'), local_src) shutil.copy2(os.path.join(upstream_dir, 'LICENSE.TXT'), local_src) + update_readme(local_src, llvm_dir) if __name__ == '__main__': diff --git a/system/lib/update_libcxx.py b/system/lib/update_libcxx.py index 35d69c92bf266..bae486c9409b6 100755 --- a/system/lib/update_libcxx.py +++ b/system/lib/update_libcxx.py @@ -63,6 +63,8 @@ def generate_modules(cmake_version: str): shutil.copytree(dist, dst, dirs_exist_ok=True) def main(): + llvm_dir, force = parse_args(default_llvm_dir, 'llvm_dir') + check_clean(force) # Exit early if cmake is not found try: output= subprocess.check_output(['cmake', '--version'], text=True) @@ -112,6 +114,7 @@ def main(): copy_tree(upstream_dir, local_dir, excludes) shutil.copy2(os.path.join(libc_upstream_dir, 'LICENSE.TXT'), libc_local_dir) + update_readme(local_root, llvm_dir) if __name__ == '__main__': main() diff --git a/system/lib/update_libcxxabi.py b/system/lib/update_libcxxabi.py index dd8cfc1acd9ce..fbff2840a2d78 100755 --- a/system/lib/update_libcxxabi.py +++ b/system/lib/update_libcxxabi.py @@ -19,7 +19,8 @@ def main(): - llvm_dir = get_llvm_dir() + llvm_dir, force = parse_args(default_llvm_dir, 'llvm_dir') + check_clean(force) upstream_root = os.path.join(llvm_dir, 'libcxxabi') upstream_src = os.path.join(upstream_root, 'src') upstream_inc = os.path.join(upstream_root, 'include') @@ -34,6 +35,7 @@ def main(): copy_tree(upstream_inc, local_inc, excludes) shutil.copy2(os.path.join(upstream_root, 'CREDITS.TXT'), local_root) shutil.copy2(os.path.join(upstream_root, 'LICENSE.TXT'), local_root) + update_readme(local_root, llvm_dir) if __name__ == '__main__': diff --git a/system/lib/update_libunwind.py b/system/lib/update_libunwind.py index 7bd85738542aa..4490911f0b5e2 100755 --- a/system/lib/update_libunwind.py +++ b/system/lib/update_libunwind.py @@ -19,7 +19,8 @@ def main(): - llvm_dir = get_llvm_dir() + llvm_dir, force = parse_args(default_llvm_dir, 'llvm_dir') + check_clean(force) upstream_root = os.path.join(llvm_dir, 'libunwind') upstream_src = os.path.join(upstream_root, 'src') upstream_inc = os.path.join(upstream_root, 'include') @@ -33,6 +34,7 @@ def main(): copy_tree(upstream_src, local_src, excludes) copy_tree(upstream_inc, local_inc, excludes) shutil.copy2(os.path.join(upstream_root, 'LICENSE.TXT'), local_root) + update_readme(local_root, llvm_dir) if __name__ == '__main__': diff --git a/system/lib/update_llvm_libc.py b/system/lib/update_llvm_libc.py index 481eebc4d146c..d10d994ed44cf 100755 --- a/system/lib/update_llvm_libc.py +++ b/system/lib/update_llvm_libc.py @@ -46,7 +46,8 @@ def main(): - llvm_dir = get_llvm_dir() + llvm_dir, force = parse_args(default_llvm_dir, 'llvm_dir') + check_clean(force) libc_upstream_dir = os.path.join(llvm_dir, 'libc') assert os.path.exists(libc_upstream_dir) libc_local_dir = os.path.join(script_dir, 'llvm-libc') @@ -66,5 +67,7 @@ def main(): for file in files_to_exclude: os.remove(file) + update_readme(libc_local_dir, llvm_dir) + if __name__ == '__main__': main() diff --git a/system/lib/update_musl.py b/system/lib/update_musl.py index 927fff74c0e97..46be77e946aef 100755 --- a/system/lib/update_musl.py +++ b/system/lib/update_musl.py @@ -69,16 +69,6 @@ ) -if len(sys.argv) > 1: - musl_dir = os.path.abspath(sys.argv[1]) -else: - musl_dir = default_musl_dir -if not os.path.isdir(musl_dir): - print(f'musl directory not found: {musl_dir}', file=sys.stderr) - print(f'Usage: {sys.argv[0]} [musl_dir]', file=sys.stderr) - sys.exit(1) - - def make_ignore(root): root = Path(root).resolve() @@ -102,7 +92,9 @@ def ignore(directory, contents): def main(): + musl_dir, force = parse_args(default_musl_dir, 'musl_dir') assert os.path.exists(musl_dir) + check_clean(force) # Remove old version shutil.rmtree(local_src) From 884f6d49e3ca1ad7251b82d12b1a518d88a932dc Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 6 Jul 2026 21:55:18 +0000 Subject: [PATCH 2/3] Remove check_clean --- system/lib/update_common.py | 19 +------------------ system/lib/update_compiler_rt.py | 4 ++-- system/lib/update_libcxx.py | 3 +-- system/lib/update_libcxxabi.py | 3 +-- system/lib/update_libunwind.py | 3 +-- system/lib/update_llvm_libc.py | 3 +-- 6 files changed, 7 insertions(+), 28 deletions(-) diff --git a/system/lib/update_common.py b/system/lib/update_common.py index 6d77e2d8fbe5f..a71991ad1a1e4 100644 --- a/system/lib/update_common.py +++ b/system/lib/update_common.py @@ -1,7 +1,6 @@ import os import sys import shutil -import subprocess import re import argparse @@ -12,7 +11,6 @@ def parse_args(default_dir, dir_name='llvm_dir'): parser = argparse.ArgumentParser(description=f'Update library from {dir_name}') - parser.add_argument('-f', '--force', action='store_true', help='Force update even if the Emscripten tree is not clean') parser.add_argument('src_dir', nargs='?', default=default_dir, help=f'Path to {dir_name}') args = parser.parse_args() @@ -22,7 +20,7 @@ def parse_args(default_dir, dir_name='llvm_dir'): parser.print_help(sys.stderr) sys.exit(1) - return src_dir, args.force + return src_dir def clean_dir(dirname, preserve_files=()): @@ -55,21 +53,6 @@ def copy_tree(upstream_dir, local_dir, excludes=()): os.remove(full) -def check_clean(force=False): - if force: - return - try: - status = subprocess.check_output(['git', 'status', '--porcelain', '--untracked-files=no'], cwd=emscripten_root, stderr=subprocess.PIPE).decode('utf-8').strip() - if status: - print('Emscripten tree is not clean (has uncommitted changes).', file=sys.stderr) - sys.exit(1) - except subprocess.CalledProcessError as e: - print('Emscripten tree is not a valid git repository or git failed.', file=sys.stderr) - if e.stderr: - print(e.stderr.decode('utf-8').strip(), file=sys.stderr) - sys.exit(1) - - def get_llvm_version(upstream_dir): cmake_file = os.path.join(upstream_dir, 'cmake', 'Modules', 'LLVMVersion.cmake') if not os.path.exists(cmake_file): diff --git a/system/lib/update_compiler_rt.py b/system/lib/update_compiler_rt.py index 229304ab7ed54..854af2bdbab5f 100755 --- a/system/lib/update_compiler_rt.py +++ b/system/lib/update_compiler_rt.py @@ -30,8 +30,8 @@ def main(): - llvm_dir, force = parse_args(default_llvm_dir, 'llvm_dir') - check_clean(force) + llvm_dir = parse_args(default_llvm_dir, 'llvm_dir') + check_clean() upstream_dir = os.path.join(llvm_dir, 'compiler-rt') assert os.path.exists(upstream_dir) upstream_src = os.path.join(upstream_dir, 'lib', 'builtins') diff --git a/system/lib/update_libcxx.py b/system/lib/update_libcxx.py index bae486c9409b6..a3a99bc277a4d 100755 --- a/system/lib/update_libcxx.py +++ b/system/lib/update_libcxx.py @@ -63,8 +63,7 @@ def generate_modules(cmake_version: str): shutil.copytree(dist, dst, dirs_exist_ok=True) def main(): - llvm_dir, force = parse_args(default_llvm_dir, 'llvm_dir') - check_clean(force) + llvm_dir = parse_args(default_llvm_dir, 'llvm_dir') # Exit early if cmake is not found try: output= subprocess.check_output(['cmake', '--version'], text=True) diff --git a/system/lib/update_libcxxabi.py b/system/lib/update_libcxxabi.py index fbff2840a2d78..11719e5d0ea88 100755 --- a/system/lib/update_libcxxabi.py +++ b/system/lib/update_libcxxabi.py @@ -19,8 +19,7 @@ def main(): - llvm_dir, force = parse_args(default_llvm_dir, 'llvm_dir') - check_clean(force) + llvm_dir = parse_args(default_llvm_dir, 'llvm_dir') upstream_root = os.path.join(llvm_dir, 'libcxxabi') upstream_src = os.path.join(upstream_root, 'src') upstream_inc = os.path.join(upstream_root, 'include') diff --git a/system/lib/update_libunwind.py b/system/lib/update_libunwind.py index 4490911f0b5e2..bd32bbe951379 100755 --- a/system/lib/update_libunwind.py +++ b/system/lib/update_libunwind.py @@ -19,8 +19,7 @@ def main(): - llvm_dir, force = parse_args(default_llvm_dir, 'llvm_dir') - check_clean(force) + llvm_dir = parse_args(default_llvm_dir, 'llvm_dir') upstream_root = os.path.join(llvm_dir, 'libunwind') upstream_src = os.path.join(upstream_root, 'src') upstream_inc = os.path.join(upstream_root, 'include') diff --git a/system/lib/update_llvm_libc.py b/system/lib/update_llvm_libc.py index d10d994ed44cf..5227d2691bc8d 100755 --- a/system/lib/update_llvm_libc.py +++ b/system/lib/update_llvm_libc.py @@ -46,8 +46,7 @@ def main(): - llvm_dir, force = parse_args(default_llvm_dir, 'llvm_dir') - check_clean(force) + llvm_dir = parse_args(default_llvm_dir, 'llvm_dir') libc_upstream_dir = os.path.join(llvm_dir, 'libc') assert os.path.exists(libc_upstream_dir) libc_local_dir = os.path.join(script_dir, 'llvm-libc') From c2bdfe1c9a5cf5c1f8aa006606d6b26730b6e7e2 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 6 Jul 2026 22:00:10 +0000 Subject: [PATCH 3/3] More --- system/lib/update_compiler_rt.py | 1 - system/lib/update_musl.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/system/lib/update_compiler_rt.py b/system/lib/update_compiler_rt.py index 854af2bdbab5f..5036eb4b5e3d2 100755 --- a/system/lib/update_compiler_rt.py +++ b/system/lib/update_compiler_rt.py @@ -31,7 +31,6 @@ def main(): llvm_dir = parse_args(default_llvm_dir, 'llvm_dir') - check_clean() upstream_dir = os.path.join(llvm_dir, 'compiler-rt') assert os.path.exists(upstream_dir) upstream_src = os.path.join(upstream_dir, 'lib', 'builtins') diff --git a/system/lib/update_musl.py b/system/lib/update_musl.py index 46be77e946aef..a9f91cf49a944 100755 --- a/system/lib/update_musl.py +++ b/system/lib/update_musl.py @@ -92,9 +92,8 @@ def ignore(directory, contents): def main(): - musl_dir, force = parse_args(default_musl_dir, 'musl_dir') + musl_dir = parse_args(default_musl_dir, 'musl_dir') assert os.path.exists(musl_dir) - check_clean(force) # Remove old version shutil.rmtree(local_src)