# Licensed to the Software Freedom Conservancy (SFC) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The SFC licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. """Generate a Closure deps.js file by scanning JS sources for goog.provide/require/module calls. Replaces the Node.js closure-make-deps wrapper to avoid npm symlink issues on Windows. Usage: closure_make_deps.py files_list - path to a file containing one JS source path per line output - path to write the generated deps.js closure_path - directory containing Closure Library's base.js (for computing relative paths) """ import os import re import sys PROVIDE_RE = re.compile(r"goog\.provide\(\s*['\"]([^'\"]+)['\"]\s*\)") MODULE_RE = re.compile(r"goog\.module\(\s*['\"]([^'\"]+)['\"]\s*\)") REQUIRE_RE = re.compile(r"goog\.require\(\s*['\"]([^'\"]+)['\"]\s*\)") def strip_comments(content): """Remove JS comments so regexes don't match goog.require() in documentation.""" content = re.sub(r"/\*.*?\*/", "", content, flags=re.DOTALL) content = re.sub(r"//[^\n]*", "", content) return content def parse_js_file(path): """Extract goog.provide, goog.module, and goog.require namespaces from a JS file.""" with open(path, encoding="utf-8") as f: content = f.read() cleaned = strip_comments(content) provides = sorted(PROVIDE_RE.findall(cleaned)) modules = sorted(MODULE_RE.findall(cleaned)) requires = sorted(REQUIRE_RE.findall(cleaned)) is_module = len(modules) > 0 all_provides = sorted(set(provides + modules)) return all_provides, requires, is_module def main(): if len(sys.argv) < 4: print( "Usage: closure_make_deps.py ", file=sys.stderr, ) sys.exit(1) files_list_path = sys.argv[1] output_path = sys.argv[2] closure_path = sys.argv[3] with open(files_list_path, encoding="utf-8") as f: files = [line.strip() for line in f if line.strip()] lines = [] for filepath in files: provides, requires, is_module = parse_js_file(filepath) rel_path = os.path.relpath(filepath, closure_path) # deps.js is consumed in the browser, so always use forward slashes rel_path = rel_path.replace(os.sep, "/") provides_str = ", ".join(f"'{p}'" for p in provides) requires_str = ", ".join(f"'{r}'" for r in requires) if is_module: line = f"goog.addDependency('{rel_path}', [{provides_str}], [{requires_str}], {{'module': 'goog'}});" else: line = f"goog.addDependency('{rel_path}', [{provides_str}], [{requires_str}]);" lines.append((rel_path, line)) # Sort by relative path for deterministic output lines.sort(key=lambda x: x[0]) with open(output_path, "w", encoding="utf-8") as f: for _, line in lines: f.write(line + "\n") if __name__ == "__main__": main()