blob: 31294cc3db3ecc927f0151b567aed6103562f9a6 [file]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF 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.
"""Verify legacy Spring Boot auto-configuration against current imports."""
import argparse
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
STARTER_ROOT = "shenyu-spring-boot-starter/"
FACTORIES_SUFFIX = "/src/main/resources/META-INF/spring.factories"
IMPORTS_SUFFIX = "/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"
AUTO_CONFIGURATION_KEY = "org.springframework.boot.autoconfigure.EnableAutoConfiguration"
def run_git(*args):
"""Run git and return its UTF-8 output."""
return subprocess.run(
["git", *args], cwd=ROOT, check=True, capture_output=True, text=True
).stdout
def read_properties(text):
"""Read the property lines needed from a Java properties file."""
logical_lines = []
pending = ""
for line in text.splitlines():
continuation = bool(pending)
pending += line.lstrip() if continuation else line.rstrip()
if pending.endswith("\\"):
pending = pending[:-1]
continue
logical_lines.append(pending)
pending = ""
if pending:
logical_lines.append(pending)
properties = {}
for line in logical_lines:
stripped = line.lstrip()
if not stripped or stripped.startswith(("#", "!")):
continue
separator = next((index for index, char in enumerate(line) if char in "=:"), -1)
if separator < 0:
continue
key = line[:separator].strip()
value = line[separator + 1:].strip()
properties[key] = value
return properties
def parse_args():
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"legacy_revision",
help="Git revision containing the legacy spring.factories files",
)
return parser.parse_args()
def main():
"""Compare every non-client legacy registration with modern imports."""
args = parse_args()
paths = run_git(
"ls-tree", "-r", "--name-only", args.legacy_revision, "--", STARTER_ROOT
).splitlines()
factories_paths = [
path for path in paths
if path.endswith(FACTORIES_SUFFIX)
and "/shenyu-spring-boot-starter-client/" not in f"/{path}"
]
errors = []
checked_entries = 0
for factories_path in factories_paths:
legacy_text = run_git("show", f"{args.legacy_revision}:{factories_path}")
properties = read_properties(legacy_text)
unexpected_keys = set(properties) - {AUTO_CONFIGURATION_KEY}
if unexpected_keys:
errors.append(f"{factories_path}: unexpected keys {sorted(unexpected_keys)}")
continue
legacy_classes = {
name.strip()
for name in properties.get(AUTO_CONFIGURATION_KEY, "").split(",")
if name.strip()
}
module_path = factories_path.removesuffix(FACTORIES_SUFFIX)
imports_path = ROOT / f"{module_path}{IMPORTS_SUFFIX}"
if not imports_path.is_file():
errors.append(f"{factories_path}: current imports file is missing")
continue
modern_classes = {
line.strip()
for line in imports_path.read_text(encoding="utf-8").splitlines()
if line.strip() and not line.lstrip().startswith("#")
}
if legacy_classes != modern_classes:
errors.append(
f"{factories_path}: legacy-only={sorted(legacy_classes - modern_classes)}, "
f"imports-only={sorted(modern_classes - legacy_classes)}"
)
checked_entries += len(legacy_classes)
if errors:
print("\n".join(errors), file=sys.stderr)
return 1
print(f"Verified {len(factories_paths)} legacy files and {checked_entries} entries.")
return 0
if __name__ == "__main__":
sys.exit(main())