#!/usr/bin/env python3 # Copyright 2025 the V8 project authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can # be found in the LICENSE file. import argparse import json import os from pathlib import Path import re import subprocess import sys TEST_ROOT = Path(__file__).absolute().parent.parent.parent / 'test' TEST_SUITES = [ 'mjsunit', 'debugger', 'inspector', 'message', 'js-perf-test', 'webkit' ] TEST_EXTENSIONS = ('.js', '.mjs') HAS_FLAGS_RE = re.compile("//\\s*Flags:.*\n") EXTRACT_FLAGS_RE = re.compile("//\\s*Flags:\\s+((--[A-z0-9-_=]+\\s*)+).*") FEATURE_FLAG_PREFIXES = { 'js': ('--js-', '--js_'), 'harmony': ('--harmony-', '--harmony_'), 'wasm': ('--experimental-wasm-', '--experimental_wasm_') } NEG_FEATURE_FLAG_PREFIXES = { 'js': ('--no-js-', '--no_js_'), 'harmony': ('--no-harmony-', '--no_harmony_'), 'wasm': ('--no-experimental-wasm-', '--no_experimental_wasm_') } STAGING_FLAGS = { 'js': '--js-staging', 'harmony': '--harmony', 'wasm': '--wasm-staging' } SHIPPING_FLAGS = { 'js': '--js-shipping', 'harmony': '--harmony-shipping', 'wasm': '' # Wasm doesn't have a shipping flag } parser = argparse.ArgumentParser() parser.add_argument( '--outdir', required=True, type=Path, help='build directory with d8') args = parser.parse_args() D8 = os.path.join(args.outdir, 'd8') def raw_flags_to_cli_flags(raw_flags_for_lang): return { kind: set([f'--{flag}' for flag in raw_flags] + [f"--{flag.replace('_', '-')}" for flag in raw_flags]) for kind, raw_flags in raw_flags_for_lang.items() } def get_feature_flags(): p = subprocess.run([D8, '--print-feature-flags-json'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) raw_flags = json.loads(p.stdout) return {key: raw_flags_to_cli_flags(val) for key, val in raw_flags.items()} def clean_up_feature_flags(contents, all_flags_by_lang): # Returns a (bool, string) pair, where the bool says whether the contents # changed due to a flag being cleaned up. if not re.search(HAS_FLAGS_RE, contents): return (False, contents) dirty = False cleaned_contents = [] emitted_staging_flags = set() for line in contents.splitlines(): match = re.search(EXTRACT_FLAGS_RE, line) if match: inprogress_flags = set(match.group(1).split()) original_flags = inprogress_flags.copy() for (lang, lang_flags) in all_flags_by_lang.items(): # Remove staged flags. inprogress_flags_no_staged = inprogress_flags - lang_flags['staged'] # If any staged flags were removed, emit the staging flag instead. Only # do so once per file. if (inprogress_flags_no_staged != inprogress_flags) and (lang not in emitted_staging_flags): inprogress_flags_no_staged.add(STAGING_FLAGS[lang]) emitted_staging_flags.add(lang) inprogress_flags = inprogress_flags_no_staged # Remove flags that are shipping. inprogress_flags -= lang_flags['shipping'] # Heuristically remove flags that look like feature flags (prefixed by # one of the known prefixes) that don't exist anymore. for flag in inprogress_flags.copy(): if flag == STAGING_FLAGS[lang] or flag == SHIPPING_FLAGS[lang]: continue if flag.startswith(FEATURE_FLAG_PREFIXES[lang]) and ( flag not in lang_flags['in-progress']): inprogress_flags.remove(flag) elif flag.startswith(NEG_FEATURE_FLAG_PREFIXES[lang]) and ( f"--{flag[5:]}" not in lang_flags['in-progress']): inprogress_flags.remove(flag) if inprogress_flags != original_flags: dirty = True if len(inprogress_flags) > 0: cleaned_contents.append(f"// Flags: {' '.join(inprogress_flags)}") else: cleaned_contents.append(line) else: cleaned_contents.append(line) if not dirty: return (False, contents) else: return (True, "\n".join(cleaned_contents) + "\n") if __name__ == '__main__': flags = get_feature_flags() # Walk test files. for suite_dir in TEST_SUITES: suite_path = TEST_ROOT / suite_dir for dirname, dirs, files in os.walk(suite_path, followlinks=True): for filename in files: if not filename.endswith(TEST_EXTENSIONS): continue abspath = Path(dirname) / filename encoding = 'utf-8' with open(abspath, 'rb') as fh: # We still have some test files that aren't valid UTF-8. But we also # can't unconditionally use ISO-8859-1, because we also have tests # that can only be encoded in UTF-8. try: contents = fh.read().decode(encoding) except: encoding = 'ISO-8859-1' contents = fh.read().decode(encoding) contents_changed, contents = clean_up_feature_flags(contents, flags) if contents_changed: with open(abspath, 'w', encoding=encoding) as fh: fh.write(contents) print(f"Changed flags in {abspath}") sys.exit(0)