Refactor code structure and optimize performance across multiple modules
This commit is contained in:
380
generate_solutions.py
Normal file
380
generate_solutions.py
Normal file
@@ -0,0 +1,380 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate Visual Studio solution files for StellaOps
|
||||
Organizes all .csproj files into:
|
||||
1. Main StellaOps.sln (all projects)
|
||||
2. Module-specific .sln files
|
||||
3. StellaOps.Infrastructure.sln (shared libraries)
|
||||
4. StellaOps.Tests.sln (global tests)
|
||||
"""
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Set, Tuple
|
||||
from collections import defaultdict
|
||||
|
||||
# Base directory
|
||||
BASE_DIR = Path(r"E:\dev\git.stella-ops.org")
|
||||
SRC_DIR = BASE_DIR / "src"
|
||||
|
||||
# Module names based on directory structure
|
||||
MODULES = [
|
||||
"AdvisoryAI", "AirGap", "Aoc", "Attestor", "Authority", "Bench",
|
||||
"BinaryIndex", "Cartographer", "Cli", "Concelier", "Cryptography",
|
||||
"EvidenceLocker", "Excititor", "ExportCenter", "Gateway", "Graph",
|
||||
"IssuerDirectory", "Notify", "Orchestrator", "Policy", "Replay",
|
||||
"SbomService", "Scanner", "Scheduler", "Signer", "Signals",
|
||||
"TaskRunner", "Telemetry", "VexHub", "VexLens", "VulnExplorer",
|
||||
"Web", "Zastava"
|
||||
]
|
||||
|
||||
# Project type GUIDs
|
||||
FAE04EC0_301F_11D3_BF4B_00C04F79EFBC = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}" # C# project
|
||||
SLN_FOLDER_GUID = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}" # Solution folder
|
||||
|
||||
|
||||
def generate_project_guid(project_path: str) -> str:
|
||||
"""Generate deterministic GUID based on project path"""
|
||||
# Use namespace UUID for deterministic generation
|
||||
namespace = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
|
||||
return str(uuid.uuid5(namespace, project_path)).upper()
|
||||
|
||||
|
||||
def get_module_from_path(project_path: Path) -> str:
|
||||
"""Determine module name from project path"""
|
||||
relative = project_path.relative_to(SRC_DIR)
|
||||
parts = relative.parts
|
||||
|
||||
# Check direct module directory
|
||||
if len(parts) > 0 and parts[0] in MODULES:
|
||||
return parts[0]
|
||||
|
||||
# Check __Libraries/StellaOps.<Module>.*
|
||||
if parts[0] == "__Libraries":
|
||||
project_name = parts[-1].replace(".csproj", "")
|
||||
for module in MODULES:
|
||||
if f"StellaOps.{module}" in project_name:
|
||||
return module
|
||||
|
||||
# Check __Tests/StellaOps.<Module>.*.Tests
|
||||
if parts[0] == "__Tests":
|
||||
project_name = parts[-1].replace(".csproj", "")
|
||||
for module in MODULES:
|
||||
if f"StellaOps.{module}" in project_name:
|
||||
return module
|
||||
# Global tests
|
||||
return "Tests"
|
||||
|
||||
# Check Integration tests
|
||||
if len(parts) > 1 and parts[0] == "__Tests" and parts[1] == "Integration":
|
||||
project_name = parts[-1].replace(".csproj", "")
|
||||
for module in MODULES:
|
||||
if f"StellaOps.{module}" in project_name:
|
||||
return module
|
||||
return "Tests"
|
||||
|
||||
# Default to Infrastructure for shared libraries
|
||||
if parts[0] == "__Libraries":
|
||||
return "Infrastructure"
|
||||
|
||||
return "Infrastructure"
|
||||
|
||||
|
||||
def find_all_projects() -> List[Path]:
|
||||
"""Find all .csproj files in src directory"""
|
||||
projects = []
|
||||
for root, dirs, files in os.walk(SRC_DIR):
|
||||
for file in files:
|
||||
if file.endswith(".csproj"):
|
||||
projects.append(Path(root) / file)
|
||||
return sorted(projects)
|
||||
|
||||
|
||||
def categorize_project(project_path: Path, module: str) -> str:
|
||||
"""Determine category for solution folder organization"""
|
||||
relative = project_path.relative_to(SRC_DIR)
|
||||
parts = relative.parts
|
||||
|
||||
# Test projects
|
||||
if "__Tests" in parts or project_path.name.endswith(".Tests.csproj"):
|
||||
return "Tests"
|
||||
|
||||
# Benchmark projects
|
||||
if "Bench" in parts or "Benchmark" in project_path.name:
|
||||
return "Benchmarks"
|
||||
|
||||
# Plugin projects
|
||||
if "Plugin" in project_path.name or "Connector" in project_path.name:
|
||||
return "Plugins"
|
||||
|
||||
# Library projects
|
||||
if "__Libraries" in parts:
|
||||
return "Libraries"
|
||||
|
||||
# Analyzer projects
|
||||
if "__Analyzers" in parts or "Analyzer" in project_path.name:
|
||||
return "Analyzers"
|
||||
|
||||
# Web services
|
||||
if "WebService" in project_path.name:
|
||||
return "WebServices"
|
||||
|
||||
# Workers
|
||||
if "Worker" in project_path.name:
|
||||
return "Workers"
|
||||
|
||||
# Core module projects
|
||||
return "Core"
|
||||
|
||||
|
||||
def generate_sln_header() -> str:
|
||||
"""Generate Visual Studio 2022 solution header"""
|
||||
return """Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
"""
|
||||
|
||||
|
||||
def generate_project_entry(project_path: Path, project_guid: str) -> str:
|
||||
"""Generate project entry for .sln file"""
|
||||
project_name = project_path.stem
|
||||
relative_path = project_path.relative_to(BASE_DIR)
|
||||
|
||||
return f'Project("{FAE04EC0_301F_11D3_BF4B_00C04F79EFBC}") = "{project_name}", "{relative_path}", "{{{project_guid}}}"\nEndProject'
|
||||
|
||||
|
||||
def generate_folder_entry(folder_name: str, folder_guid: str) -> str:
|
||||
"""Generate solution folder entry"""
|
||||
return f'Project("{SLN_FOLDER_GUID}") = "{folder_name}", "{folder_name}", "{{{folder_guid}}}"\nEndProject'
|
||||
|
||||
|
||||
def generate_nested_projects(folder_mappings: Dict[str, List[str]]) -> str:
|
||||
"""Generate NestedProjects section"""
|
||||
lines = ["\tGlobalSection(NestedProjects) = preSolution"]
|
||||
for folder_guid, project_guids in folder_mappings.items():
|
||||
for project_guid in project_guids:
|
||||
lines.append(f"\t\t{{{project_guid}}} = {{{folder_guid}}}")
|
||||
lines.append("\tEndGlobalSection")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_main_solution(projects: List[Path], module_assignments: Dict[str, List[Path]]) -> str:
|
||||
"""Generate main StellaOps.sln with all projects"""
|
||||
content = [generate_sln_header()]
|
||||
|
||||
# Track GUIDs
|
||||
project_guids: Dict[str, str] = {}
|
||||
folder_guids: Dict[str, str] = {}
|
||||
folder_mappings: Dict[str, List[str]] = defaultdict(list)
|
||||
|
||||
# Create folder structure: Module -> Category -> Projects
|
||||
for module in sorted(module_assignments.keys()):
|
||||
module_folder_guid = generate_project_guid(f"folder_{module}")
|
||||
folder_guids[module] = module_folder_guid
|
||||
content.append(generate_folder_entry(module, module_folder_guid))
|
||||
|
||||
# Group projects by category within module
|
||||
category_projects: Dict[str, List[Path]] = defaultdict(list)
|
||||
for project in module_assignments[module]:
|
||||
category = categorize_project(project, module)
|
||||
category_projects[category].append(project)
|
||||
|
||||
# Create category folders
|
||||
for category in sorted(category_projects.keys()):
|
||||
category_folder_name = f"{module}.{category}"
|
||||
category_folder_guid = generate_project_guid(f"folder_{category_folder_name}")
|
||||
folder_guids[category_folder_name] = category_folder_guid
|
||||
content.append(generate_folder_entry(category, category_folder_guid))
|
||||
folder_mappings[module_folder_guid].append(category_folder_guid)
|
||||
|
||||
# Add projects to category
|
||||
for project in sorted(category_projects[category]):
|
||||
project_guid = generate_project_guid(str(project))
|
||||
project_guids[str(project)] = project_guid
|
||||
content.append(generate_project_entry(project, project_guid))
|
||||
folder_mappings[category_folder_guid].append(project_guid)
|
||||
|
||||
# Add Global section
|
||||
content.append("Global")
|
||||
content.append("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution")
|
||||
content.append("\t\tDebug|Any CPU = Debug|Any CPU")
|
||||
content.append("\t\tRelease|Any CPU = Release|Any CPU")
|
||||
content.append("\tEndGlobalSection")
|
||||
|
||||
# Project configurations
|
||||
content.append("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution")
|
||||
for project_guid in project_guids.values():
|
||||
content.append(f"\t\t{{{project_guid}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU")
|
||||
content.append(f"\t\t{{{project_guid}}}.Debug|Any CPU.Build.0 = Debug|Any CPU")
|
||||
content.append(f"\t\t{{{project_guid}}}.Release|Any CPU.ActiveCfg = Release|Any CPU")
|
||||
content.append(f"\t\t{{{project_guid}}}.Release|Any CPU.Build.0 = Release|Any CPU")
|
||||
content.append("\tEndGlobalSection")
|
||||
|
||||
# Nested projects
|
||||
content.append(generate_nested_projects(folder_mappings))
|
||||
|
||||
content.append("EndGlobal")
|
||||
|
||||
return "\n".join(content)
|
||||
|
||||
|
||||
def generate_module_solution(module: str, projects: List[Path]) -> str:
|
||||
"""Generate module-specific .sln file"""
|
||||
content = [generate_sln_header()]
|
||||
|
||||
project_guids: Dict[str, str] = {}
|
||||
folder_guids: Dict[str, str] = {}
|
||||
folder_mappings: Dict[str, List[str]] = defaultdict(list)
|
||||
|
||||
# Group projects by category
|
||||
category_projects: Dict[str, List[Path]] = defaultdict(list)
|
||||
for project in projects:
|
||||
category = categorize_project(project, module)
|
||||
category_projects[category].append(project)
|
||||
|
||||
# Create category folders and add projects
|
||||
for category in sorted(category_projects.keys()):
|
||||
category_folder_guid = generate_project_guid(f"folder_{module}_{category}")
|
||||
folder_guids[category] = category_folder_guid
|
||||
content.append(generate_folder_entry(category, category_folder_guid))
|
||||
|
||||
for project in sorted(category_projects[category]):
|
||||
project_guid = generate_project_guid(str(project))
|
||||
project_guids[str(project)] = project_guid
|
||||
content.append(generate_project_entry(project, project_guid))
|
||||
folder_mappings[category_folder_guid].append(project_guid)
|
||||
|
||||
# Add Global section
|
||||
content.append("Global")
|
||||
content.append("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution")
|
||||
content.append("\t\tDebug|Any CPU = Debug|Any CPU")
|
||||
content.append("\t\tRelease|Any CPU = Release|Any CPU")
|
||||
content.append("\tEndGlobalSection")
|
||||
|
||||
# Project configurations
|
||||
content.append("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution")
|
||||
for project_guid in project_guids.values():
|
||||
content.append(f"\t\t{{{project_guid}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU")
|
||||
content.append(f"\t\t{{{project_guid}}}.Debug|Any CPU.Build.0 = Debug|Any CPU")
|
||||
content.append(f"\t\t{{{project_guid}}}.Release|Any CPU.ActiveCfg = Release|Any CPU")
|
||||
content.append(f"\t\t{{{project_guid}}}.Release|Any CPU.Build.0 = Release|Any CPU")
|
||||
content.append("\tEndGlobalSection")
|
||||
|
||||
# Nested projects
|
||||
content.append(generate_nested_projects(folder_mappings))
|
||||
|
||||
content.append("EndGlobal")
|
||||
|
||||
return "\n".join(content)
|
||||
|
||||
|
||||
def main():
|
||||
print("Finding all .csproj files...")
|
||||
all_projects = find_all_projects()
|
||||
print(f"Found {len(all_projects)} projects")
|
||||
|
||||
# Assign projects to modules
|
||||
module_assignments: Dict[str, List[Path]] = defaultdict(list)
|
||||
for project in all_projects:
|
||||
module = get_module_from_path(project)
|
||||
module_assignments[module].append(project)
|
||||
|
||||
# Print summary
|
||||
print("\nModule assignment summary:")
|
||||
for module in sorted(module_assignments.keys()):
|
||||
print(f" {module}: {len(module_assignments[module])} projects")
|
||||
|
||||
# Generate main solution
|
||||
print("\nGenerating main StellaOps.sln...")
|
||||
main_sln = generate_main_solution(all_projects, module_assignments)
|
||||
main_sln_path = SRC_DIR / "StellaOps.sln"
|
||||
with open(main_sln_path, 'w', encoding='utf-8-sig') as f:
|
||||
f.write(main_sln)
|
||||
print(f" Written: {main_sln_path}")
|
||||
print(f" Projects: {len(all_projects)}")
|
||||
|
||||
# Generate module-specific solutions
|
||||
print("\nGenerating module-specific solutions...")
|
||||
for module in sorted(module_assignments.keys()):
|
||||
if module in ["Infrastructure", "Tests"]:
|
||||
# These get special handling below
|
||||
continue
|
||||
|
||||
projects = module_assignments[module]
|
||||
if len(projects) == 0:
|
||||
continue
|
||||
|
||||
module_sln = generate_module_solution(module, projects)
|
||||
module_sln_path = SRC_DIR / f"StellaOps.{module}.sln"
|
||||
with open(module_sln_path, 'w', encoding='utf-8-sig') as f:
|
||||
f.write(module_sln)
|
||||
print(f" Written: {module_sln_path}")
|
||||
print(f" Projects: {len(projects)}")
|
||||
|
||||
# Generate Infrastructure solution
|
||||
if "Infrastructure" in module_assignments:
|
||||
print("\nGenerating StellaOps.Infrastructure.sln...")
|
||||
infra_projects = module_assignments["Infrastructure"]
|
||||
infra_sln = generate_module_solution("Infrastructure", infra_projects)
|
||||
infra_sln_path = SRC_DIR / "StellaOps.Infrastructure.sln"
|
||||
with open(infra_sln_path, 'w', encoding='utf-8-sig') as f:
|
||||
f.write(infra_sln)
|
||||
print(f" Written: {infra_sln_path}")
|
||||
print(f" Projects: {len(infra_projects)}")
|
||||
|
||||
# Generate Tests solution
|
||||
if "Tests" in module_assignments:
|
||||
print("\nGenerating StellaOps.Tests.sln...")
|
||||
test_projects = module_assignments["Tests"]
|
||||
test_sln = generate_module_solution("Tests", test_projects)
|
||||
test_sln_path = SRC_DIR / "StellaOps.Tests.sln"
|
||||
with open(test_sln_path, 'w', encoding='utf-8-sig') as f:
|
||||
f.write(test_sln)
|
||||
print(f" Written: {test_sln_path}")
|
||||
print(f" Projects: {len(test_projects)}")
|
||||
|
||||
# Verify each project is in exactly 2 solutions
|
||||
print("\n\nVerifying project membership...")
|
||||
project_solution_count: Dict[str, Set[str]] = defaultdict(set)
|
||||
|
||||
# Count main solution
|
||||
for project in all_projects:
|
||||
project_solution_count[str(project)].add("StellaOps.sln")
|
||||
|
||||
# Count module solutions
|
||||
for module, projects in module_assignments.items():
|
||||
if module == "Infrastructure":
|
||||
sln_name = "StellaOps.Infrastructure.sln"
|
||||
elif module == "Tests":
|
||||
sln_name = "StellaOps.Tests.sln"
|
||||
else:
|
||||
sln_name = f"StellaOps.{module}.sln"
|
||||
|
||||
for project in projects:
|
||||
project_solution_count[str(project)].add(sln_name)
|
||||
|
||||
# Check for violations
|
||||
violations = []
|
||||
for project, solutions in project_solution_count.items():
|
||||
if len(solutions) != 2:
|
||||
violations.append((project, solutions))
|
||||
|
||||
if violations:
|
||||
print(f"\n❌ ERROR: {len(violations)} projects are not in exactly 2 solutions:")
|
||||
for project, solutions in violations[:10]: # Show first 10
|
||||
print(f" {Path(project).name}: in {len(solutions)} solutions - {solutions}")
|
||||
if len(violations) > 10:
|
||||
print(f" ... and {len(violations) - 10} more")
|
||||
else:
|
||||
print("✅ All projects are in exactly 2 solutions!")
|
||||
|
||||
print("\n✅ Solution generation complete!")
|
||||
print(f" Total projects: {len(all_projects)}")
|
||||
print(f" Solutions created: {len(module_assignments) + 1}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user