blob: 37c81b3b594d44654c993b7ae5b886fc51da22d8 [file] [log] [blame]
# 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> <output> <closure_path>
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 <files_list> <output> <closure_path>",
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()