import subprocess import bs4 import argparse import logging from pathlib import Path logger = logging.getLogger(__name__) def parse_arguments(): parser = argparse.ArgumentParser() parser.add_argument('project', type=Path) return parser.parse_args() def can_compile(path: Path) -> bool: process = subprocess.run(['mvn', 'clean', 'compile'], cwd=path, check=False,capture_output=True) return process.returncode == 0 def can_test_compile(path: Path) -> bool: process = subprocess.run(['mvn', 'clean', 'compile', 'test-compile'], cwd=path, check=False,capture_output=True) return process.returncode == 0 def write_pom(project_path: Path, pom: bs4.BeautifulSoup | str) -> None: with open(project_path / 'pom.xml', 'w') as f: f.write(str(pom)) def check_for_dependencies_that_need_only_be_used_in_test_scope(pom, errors, project_path): soup_deps = pom.select_one('dependencies') if soup_deps is None: logger.info('No dependencies... nothing to do.') return main_dependencies = [] for dep in soup_deps.select('dependency'): if dep.scope is None: main_dependencies.append(dep) for dep in main_dependencies: name = '{}:{}'.format(dep.groupId.get_text(), dep.artifactId.get_text()) logger.info('Checking %s for move to test scope', name) dep.insert(-1, bs4.BeautifulSoup('test', 'lxml-xml')) write_pom(project_path, pom) if can_compile(project_path): errors.append(f'Could move {name} to test scope') logger.error('%s', errors[-1]) else: dep.scope.extract() del dep, name def check_for_unneeded_dependencies(pom, errors, project_path): """Assumes that `check_for_dependencies_that_need_only_be_used_in_test_scope` have already been run, such that we can skip main dependencies. """ soup_deps = pom.select_one('dependencies') if soup_deps is None: logger.info('No dependencies... nothing to do.') return test_dependencies = [] for dep in soup_deps.select('dependency'): if dep.scope and dep.scope.get_text().strip() == 'test': test_dependencies.append(dep) for dep in test_dependencies: name = '{}:{}'.format(dep.groupId.get_text(), dep.artifactId.get_text()) logger.info('Checking %s for removal', name) dep.extract() write_pom(project_path, pom) if can_test_compile(project_path): errors.append(f'Could remove {name} from dependencies') logger.error('%s', errors[-1]) else: soup_deps.insert(-1, dep) del dep, name def main(): logging.basicConfig() logger.setLevel('INFO') args = parse_arguments() assert can_compile(args.project), 'Project did not compile before modification!' with open(args.project / 'pom.xml') as f: original_pom = f.read() pom = bs4.BeautifulSoup(original_pom, 'lxml-xml') errors = [] try: check_for_dependencies_that_need_only_be_used_in_test_scope(pom, errors, args.project) check_for_unneeded_dependencies(pom, errors, args.project) except Exception: logger.exception('Error occured while checking dependencies') finally: logger.info('Resetting POM') write_pom(args.project, original_pom) logger.info('Finished checks') if errors: logger.error('Encountered %d errors', len(errors)) for error in errors: logger.error('%s', error) exit(1) if __name__ == '__main__': main()