Files
ROCm/tools/submodules/setup_submodules.py
2025-04-16 13:41:06 -04:00

332 lines
10 KiB
Python

'''
This script sets up git submodules based on a manifest file. It performs the following tasks:
1. Finds the root directory of the project.
2. Parses command line arguments to get the manifest file and output folder paths.
3. Parses the manifest file to get a list of projects.
4. Gets a list of existing submodules.
5. Removes submodules that are not in the manifest file.
6. Adds new submodules from the manifest file.
7. Updates existing submodules to the specified revision.
'''
import os
import subprocess
import sys
import argparse
import pathlib
import logging
import xml.etree.ElementTree as ET
class Project:
"""
Project class to store project information.
Attributes:
name (str): Name of the project.
revision (str): Revision of the project.
path (str): Path to the project.
Methods:
__str__: Returns a string representation of the project.
__repr__: Returns a string representation of the project.
"""
def __init__(self, **kwargs):
'''
Project class to store project information
:param name: Name of the project
:param revision: Revision of the project
:param path: Path to the project
'''
self.name = kwargs.get('name')
self.revision = kwargs.get('revision')
self.path = kwargs.get('path')
def __str__(self):
return f"Project {self.name} at {self.path} with revision {self.revision}"
def __repr__(self):
return self.__str__()
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
DARK_RED='\033[0;31m'
NC='\033[0m' # No Color
def find_root() -> str:
'''
Find the root directory of the project
:return: Root directory of the project
'''
current = os.getcwd()
while current != "/":
if os.path.exists(os.path.join(current, ".git")):
return current
current = os.path.dirname(current)
print("Could not find root directory", file=sys.stderr)
sys.exit(1)
def run_command(command) -> list:
'''
Run a command in the shell
:param command: Command to run
:return: List of errors
'''
error_log = []
print(f"Running command: {command}")
# subprocess.run(command, shell=True, check=True)
result = subprocess.run(command, shell=True, check=False,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
print(f"{RED}Error running command: {result.stderr.decode('utf-8')}{NC}",
file=sys.stderr)
logging.error("Error running command: %s", command)
logging.error("\t%s", result.stderr.decode('utf-8'))
error_log.append(f"Error running command: {command}")
error_log.append(f"\t{result.stderr.decode('utf-8')}\n")
else:
if result.stdout:
print(result.stdout.decode('utf-8'))
return error_log
def parse_arguments(project_root: str) -> argparse.Namespace:
'''
Parse command line arguments
:param project_root: Root directory of the project
:return: Parsed arguments
'''
parser = argparse.ArgumentParser(description="Setup submodules based on a manifest file.")
parser.add_argument("-m", "--manifest", required=False,
default=f'{project_root}/default.xml', help="Path to the manifest file")
parser.add_argument("-o", "--output", required=False,
default='libs', help="Path to the submodule folder")
return parser.parse_args()
def parse_manifest(manifest_path: str, output_path: str) -> list:
'''
Parse the manifest file and return a list of projects
:param manifest_path: Path to the manifest file
:param output_path: Path to the output folder
:return: List of projects
'''
tree = ET.parse(manifest_path)
root = tree.getroot()
defaults = root.find('default')
default_revision = None
if defaults is not None:
default_revision = defaults.get('revision')
# print(f"Default revision: {default_revision}")
projects = []
for project in root.findall('project'):
name = project.get('name')
revision = project.get('revision', default_revision)
path = os.path.join(output_path, project.get('path', name))
projects.append(Project(name = name,
revision = revision,
path = path))
return projects
def get_existing_submodules(project_root: str) -> list:
'''
Get a list of existing submodules
:param project_root: Root directory of the project
:return: List of existing submodules
'''
gitmodules_path = os.path.join(project_root, ".gitmodules")
existing_submodules = []
if os.path.exists(gitmodules_path):
with open(gitmodules_path, "r", encoding="utf-8") as gitmodules_file:
for line in gitmodules_file:
line = line.strip()
if line.startswith("[submodule"):
submodule_name = pathlib.Path(line.split('"')[1]).as_posix()
existing_submodules.append(submodule_name)
return existing_submodules
def remove_submodule(project_root: str, module_path: str) -> list:
'''
Remove a submodule
:param project_root: Root directory of the project
:param module_path: Path to the submodule
:return: List of errors
'''
error_log = []
gitmodules_path = os.path.join(project_root, ".gitmodules")
error_log.extend(run_command(f"git submodule deinit -f {module_path}"))
error_log.extend(run_command(f"rm -rf {module_path}"))
error_log.extend(run_command(f"rm -rf .git/modules/{module_path}"))
# error_log.extend(run_command(f"git rm -r --cached {module_path}"))
# # Remove the submodule from the .gitmodules file
with open(gitmodules_path, "r", encoding="utf-8") as gitmodules_file:
lines = gitmodules_file.readlines()
with open(gitmodules_path, "w", encoding="utf-8") as gitmodules_file:
skip = False
for line in lines:
if line.strip().startswith(f"[submodule \"{module_path}\"]"):
skip = True
elif skip and line.strip().startswith("[submodule"):
skip = False
if not skip:
gitmodules_file.write(line)
return error_log
def main():
'''
Main function
'''
# Set up logging
log_file = os.path.join(os.path.expanduser("~"), "submodule_setup.log")
logging.basicConfig(level=logging.DEBUG,
filename=log_file,
filemode='w',
format='%(asctime)s - %(levelname)s - %(message)s')
# Save errors to a list to output at the end
error_log = []
project_root = find_root()
os.chdir(project_root)
print(f"Found project root at {project_root}")
args = parse_arguments(project_root)
print(f"Using manifest file at {args.manifest}")
# Check if manifest file exists
if not os.path.exists(args.manifest):
print(f"Manifest file {args.manifest} does not exist", file=sys.stderr)
sys.exit(1)
# Check if output folder exists
print(f"Using output folder at {args.output}\n")
if not os.path.exists(args.output):
print(f"Output folder {args.output} does not exist. Creating it.")
os.makedirs(args.output)
# Create a list of existing submodules
existing_submodules = get_existing_submodules(project_root)
for module in existing_submodules:
logging.debug("Existing submodule %s", module)
logging.debug("=====\n")
# Create a list of projects from the manifest file
projects = parse_manifest(args.manifest, args.output)
for project in projects:
logging.debug("Manifest Project %s to %s at %s",
project.name,
project.path,
project.revision)
logging.debug("=====\n")
print('\n*** Initializing submodules ***\n')
run_command("git submodule update --init")
print(f'\n{DARK_RED}*** Removing old projects ***{NC}\n')
# If project is in existing submodules and not in manifest, remove it
removed_modules = []
project_paths = {project.path for project in projects}
for module in existing_submodules:
if module not in project_paths:
print(f"{DARK_RED}Removing submodule {module}{NC}")
logging.info("Removing submodule %s", module)
removed_modules.append(module)
error_log.extend(remove_submodule(project_root, module))
print()
# Remove the submodules from the list of existing submodules
existing_submodules = [module for module in existing_submodules
if module not in removed_modules]
print(f'\n{GREEN}*** Adding new projects ***{NC}\n')
# Add new submodules
for project in projects:
if project.path not in existing_submodules:
print(f"{GREEN}Adding submodule ../{project.name}{NC}")
logging.info("Adding submodule ../%s to %s", project.name, project.path)
error_log.extend(
run_command(f"git submodule add ../{project.name} {project.path}"))
error_log.extend(
run_command(f"cd {project.path} "
f"&& git checkout {project.revision} "
f"&& cd {project_root}"))
print()
print(f'\n{BLUE}*** Updating existing projects ***{NC}\n')
# Update existing submodules
for project in projects:
if project.path in existing_submodules and project.path not in removed_modules:
print(f"{BLUE}Updating submodule {project.path}{NC}")
logging.info("Updating submodule %s", project.path)
error_log.extend(
run_command(f"cd {project.path} && "
"git fetch --tag && "
f"git checkout {project.revision} "
f"&& cd {project_root}"))
print()
# Print errors at the end
if error_log:
print(f"\n{RED}Errors occurred. Please check the logs: {log_file}{NC}\n"
"Error Summary:\n",
file=sys.stderr)
logging.error("Errors occurred. Please check the logs: %s", log_file)
logging.error("Error Summary:")
for error in error_log:
print(error, file=sys.stderr)
logging.error(error)
else:
print(f"{GREEN}All submodules updated successfully{NC}")
if __name__ == "__main__":
main()