Schema Generation #10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Schema Generation | |
| on: | |
| schedule: | |
| - cron: '0 1 * * 6' # Saturday at 1 AM UTC | |
| workflow_dispatch: | |
| jobs: | |
| generate-schemas: | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout develop branch | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: develop | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Setup MSBuild | |
| uses: microsoft/setup-msbuild@v2 | |
| - name: Setup NuGet.exe for use with actions | |
| uses: NuGet/[email protected] | |
| - name: Create .repos directory | |
| run: mkdir .repos | |
| shell: cmd | |
| - name: Read repository list | |
| id: read-repos | |
| run: | | |
| $repos = Get-Content "Included-repositories.txt" | Where-Object { $_.Trim() -ne "" } | |
| $repoList = $repos -join "," | |
| echo "repos=$repoList" >> $env:GITHUB_OUTPUT | |
| shell: powershell | |
| - name: Clone all repositories | |
| run: | | |
| $repos = "${{ steps.read-repos.outputs.repos }}" -split "," | |
| foreach ($repo in $repos) { | |
| $repo = $repo.Trim() | |
| if ($repo -ne "") { | |
| Write-Host "Cloning $repo..." | |
| $repoName = $repo.Split('/')[1] | |
| git clone https://github.com/$repo.git .repos/$repoName --branch develop --depth 1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "Failed to clone develop branch for $repo, trying main..." | |
| git clone https://github.com/$repo.git .repos/$repoName --branch main --depth 1 | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "Failed to clone main branch for $repo, trying master..." | |
| git clone https://github.com/$repo.git .repos/$repoName --branch master --depth 1 | |
| } | |
| } | |
| } | |
| } | |
| shell: powershell | |
| - name: Analyze dependencies and determine build order | |
| id: build-order | |
| timeout-minutes: 5 | |
| run: | | |
| # Get list of cloned repositories | |
| $validRepos = @() | |
| if (Test-Path ".repos") { | |
| Get-ChildItem ".repos" -Directory | ForEach-Object { $validRepos += $_.Name } | |
| } | |
| if ($validRepos.Count -eq 0) { | |
| Write-Host "No valid repositories to analyze" | |
| echo "order=" >> $env:GITHUB_OUTPUT | |
| exit 0 | |
| } | |
| Write-Host "Analyzing dependency order for $($validRepos.Count) repositories..." | |
| # Create dependency map | |
| $dependencyMap = @{} | |
| foreach ($repo in $validRepos) { | |
| $dependencyMap[$repo] = @() | |
| $depsFile = ".repos\$repo\dependencies.txt" | |
| if (Test-Path $depsFile) { | |
| Write-Host "Found dependencies.txt in $repo" | |
| $repoDeps = Get-Content $depsFile | ForEach-Object { | |
| $line = $_.Trim() | |
| if (![string]::IsNullOrWhiteSpace($line) -and !$line.StartsWith("#")) { | |
| # Extract just the repo name from ORG/REPO format | |
| $repoName = $line.Split('/')[-1] | |
| return $repoName | |
| } | |
| } | Where-Object { $_ -ne $null } | |
| # Only include dependencies that are in our valid repos list | |
| $validDeps = $repoDeps | Where-Object { $validRepos -contains $_ } | |
| $dependencyMap[$repo] = $validDeps | |
| if ($validDeps.Count -gt 0) { | |
| Write-Host "$repo depends on: $($validDeps -join ', ')" | |
| } else { | |
| Write-Host "$repo has no dependencies in current build set" | |
| } | |
| } else { | |
| Write-Host "$repo has no dependencies.txt file" | |
| } | |
| } | |
| # Topological sort to determine build order | |
| $script:buildOrder = @() | |
| $script:visited = @{} | |
| $script:visiting = @{} | |
| function Get-BuildOrder { | |
| param([string]$repo) | |
| Write-Host "Processing $repo for build order..." | |
| if ($script:visiting.ContainsKey($repo)) { | |
| Write-Host "⚠ Circular dependency detected involving $repo" | |
| return $false | |
| } | |
| if ($script:visited.ContainsKey($repo)) { | |
| Write-Host "$repo already processed" | |
| return $true | |
| } | |
| $script:visiting[$repo] = $true | |
| Write-Host "Visiting dependencies of $repo..." | |
| # Visit all dependencies first | |
| if ($dependencyMap.ContainsKey($repo) -and $dependencyMap[$repo].Count -gt 0) { | |
| Write-Host "$repo has dependencies: $($dependencyMap[$repo] -join ', ')" | |
| foreach ($dep in $dependencyMap[$repo]) { | |
| if ($validRepos -contains $dep) { | |
| Write-Host "Processing dependency: $dep" | |
| $result = Get-BuildOrder -repo $dep | |
| if (-not $result) { | |
| Write-Host "Failed to process dependency $dep" | |
| return $false | |
| } | |
| } else { | |
| Write-Host "Skipping dependency $dep (not in current build set)" | |
| } | |
| } | |
| } else { | |
| Write-Host "$repo has no dependencies" | |
| } | |
| $script:visiting.Remove($repo) | |
| $script:visited[$repo] = $true | |
| $script:buildOrder += $repo | |
| Write-Host "Added $repo to build order (position $($script:buildOrder.Count))" | |
| return $true | |
| } | |
| # Process all repositories | |
| Write-Host "Starting topological sort for $($validRepos.Count) repositories..." | |
| $sortSuccess = $true | |
| foreach ($repo in $validRepos) { | |
| if (-not $script:visited.ContainsKey($repo)) { | |
| Write-Host "Processing root repository: $repo" | |
| $result = Get-BuildOrder -repo $repo | |
| if (-not $result) { | |
| $sortSuccess = $false | |
| break | |
| } | |
| } | |
| } | |
| if (-not $sortSuccess) { | |
| Write-Host "⚠ Topological sort failed due to circular dependencies. Using original order." | |
| $script:buildOrder = $validRepos | |
| } | |
| $buildOrder = $script:buildOrder | |
| Write-Host "Determined build order:" | |
| for ($i = 0; $i -lt $buildOrder.Count; $i++) { | |
| Write-Host " $($i + 1). $($buildOrder[$i])" | |
| } | |
| $orderString = $buildOrder -join "," | |
| Write-Host "Final build order: $orderString" | |
| echo "order=$orderString" >> $env:GITHUB_OUTPUT | |
| shell: pwsh | |
| - name: Build repositories in dependency order (clean detection) | |
| shell: pwsh | |
| run: | | |
| $buildOrder = "${{ steps.build-order.outputs.order }}" -split "," | |
| foreach ($repo in $buildOrder) { | |
| $repo = $repo.Trim() | |
| if (-not $repo) { continue } | |
| Push-Location ".repos/$repo" | |
| Write-Host "Building $repo..." | |
| # Prefer a solution if present; otherwise build each csproj | |
| $solutions = Get-ChildItem -Recurse -Filter *.sln | Where-Object { $_.Directory.Name -notmatch '(Test|Tests|Example|Examples)' } | |
| if ($solutions) { | |
| # Build the first solution (or loop them if you have multiple) | |
| $sln = $solutions[0].FullName | |
| ../../.github/workflows/build.ps1 -SolutionOrProjectPath $sln | |
| } else { | |
| $projects = Get-ChildItem -Recurse -Filter *.csproj | Where-Object { $_.Directory.Name -notmatch '(Test|Tests|Example|Examples)' } | |
| foreach ($proj in $projects) { | |
| ../../.github/workflows/build.ps1 -SolutionOrProjectPath $proj.FullName | |
| } | |
| } | |
| Pop-Location | |
| } | |
| - name: Build Generation solution | |
| run: | | |
| if (Test-Path ".ci/Generation/SchemaGeneration") { | |
| Write-Host "Building SchemaGeneration solution..." | |
| Push-Location ".ci/Generation/SchemaGeneration" | |
| # Build the solution file | |
| $solutionFile = "SchemaGeneration.sln" | |
| if (Test-Path $solutionFile) { | |
| Write-Host "Building solution: $solutionFile" | |
| dotnet build "$solutionFile" --configuration Release --verbosity minimal | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "Dotnet build failed, trying MSBuild fallback..." | |
| nuget restore "$solutionFile" | |
| msbuild "$solutionFile" /p:Configuration=Release /p:Platform="Any CPU" /verbosity:minimal | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "Build failed" | |
| exit 1 | |
| } | |
| } | |
| } else { | |
| Write-Host "SchemaGeneration.sln not found" | |
| exit 1 | |
| } | |
| Pop-Location | |
| } else { | |
| Write-Host "SchemaGeneration folder not found at .ci/Generation/SchemaGeneration" | |
| exit 1 | |
| } | |
| shell: powershell | |
| - name: Run Generation executable | |
| run: | | |
| Write-Host "Looking for SchemaGeneration executable..." | |
| # Look for the executable in the Release build output first, then Debug | |
| $exePath = ".ci/Generation/SchemaGeneration/SchemaGeneration/bin/Release/net8.0/SchemaGeneration.exe" | |
| if (-not (Test-Path $exePath)) { | |
| $exePath = ".ci/Generation/SchemaGeneration/SchemaGeneration/bin/Debug/net8.0/SchemaGeneration.exe" | |
| } | |
| if (Test-Path $exePath) { | |
| $branchName = "${{ github.head_ref || github.ref_name }}" | |
| Write-Host "Running SchemaGeneration with branch: $branchName" | |
| Write-Host "Executable path: $exePath" | |
| & "$exePath" "$branchName" | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "SchemaGeneration failed with exit code: $LASTEXITCODE" | |
| exit 1 | |
| } | |
| } else { | |
| # Fallback: try to run with dotnet | |
| $dllPath = ".ci/Generation/SchemaGeneration/SchemaGeneration/bin/Release/net8.0/SchemaGeneration.dll" | |
| if (-not (Test-Path $dllPath)) { | |
| $dllPath = ".ci/Generation/SchemaGeneration/SchemaGeneration/bin/Debug/net8.0/SchemaGeneration.dll" | |
| } | |
| if (Test-Path $dllPath) { | |
| $branchName = "${{ github.head_ref || github.ref_name }}" | |
| Write-Host "Running SchemaGeneration DLL with branch: $branchName" | |
| Write-Host "DLL path: $dllPath" | |
| dotnet "$dllPath" "$branchName" | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "SchemaGeneration failed with exit code: $LASTEXITCODE" | |
| exit 1 | |
| } | |
| } else { | |
| Write-Host "SchemaGeneration executable or DLL not found" | |
| exit 1 | |
| } | |
| } | |
| shell: powershell | |
| - name: Check for changes | |
| id: check-changes | |
| run: | | |
| git add -A | |
| $changes = git diff --cached --name-only | |
| if ($changes) { | |
| echo "has_changes=true" >> $env:GITHUB_OUTPUT | |
| Write-Host "Changes detected:" | |
| $changes | ForEach-Object { Write-Host " $_" } | |
| } else { | |
| echo "has_changes=false" >> $env:GITHUB_OUTPUT | |
| Write-Host "No changes detected" | |
| } | |
| shell: powershell | |
| - name: Create Pull Request | |
| if: steps.check-changes.outputs.has_changes == 'true' | |
| run: | | |
| # Configure git | |
| git config --global user.name 'github-actions[bot]' | |
| git config --global user.email 'github-actions[bot]@users.noreply.github.com' | |
| # Create and switch to new branch | |
| $branchName = "schema-updates" | |
| git checkout -b $branchName | |
| # Commit changes | |
| git add -A | |
| git commit -m "Automated schema generation update | |
| Generated on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC') | |
| This PR contains automatically generated JSON schema updates based on the latest BHoM repositories." | |
| # Push branch | |
| git push origin $branchName --force | |
| # Create pull request using GitHub CLI | |
| $prTitle = "Automated Schema Updates - $(Get-Date -Format 'yyyy-MM-dd')" | |
| $prBody = @" | |
| ## Automated Schema Generation | |
| This pull request contains automatically generated JSON schema updates. | |
| **Generation Details:** | |
| - Generated on: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC') | |
| - Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| **Changes:** | |
| The schemas have been regenerated based on the latest versions of all BHoM repositories listed in ``Included-repositories.txt``. | |
| Please review the changes before merging. | |
| "@ | |
| # Install GitHub CLI if not available | |
| if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { | |
| Write-Host "Installing GitHub CLI..." | |
| winget install --id GitHub.cli | |
| } | |
| # Create PR | |
| $env:GH_TOKEN = "${{ secrets.GITHUB_TOKEN }}" | |
| gh pr create --title "$prTitle" --body "$prBody" --base develop --head $branchName | |
| shell: powershell | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |