Schema Generation #33
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: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| env: | |
| TARGET_BRANCH: schema-updates | |
| # This branch is used for automatic schema updates | |
| FALLBACK_BRANCH: develop | |
| jobs: | |
| generate-schemas: | |
| runs-on: windows-latest | |
| steps: | |
| - name: Resolve branch to use | |
| id: resolve_branch | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TARGET_BRANCH: ${{ env.TARGET_BRANCH }} | |
| FALLBACK_BRANCH: ${{ env.FALLBACK_BRANCH }} | |
| run: | | |
| $REPO = "github.com/$env:GITHUB_REPOSITORY" | |
| $targetBranchExists = git ls-remote --heads "https://x-access-token:$env:GITHUB_TOKEN@$REPO" $env:TARGET_BRANCH | |
| if ($targetBranchExists) { | |
| "branch=$env:TARGET_BRANCH" | Out-File -FilePath $env:GITHUB_OUTPUT -Append | |
| Write-Host "Using existing branch: $env:TARGET_BRANCH" | |
| } else { | |
| "branch=$env:FALLBACK_BRANCH" | Out-File -FilePath $env:GITHUB_OUTPUT -Append | |
| Write-Host "Using fallback branch: $env:FALLBACK_BRANCH" | |
| } | |
| shell: powershell | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| ref: ${{ steps.resolve_branch.outputs.branch }} | |
| fetch-depth: 0 | |
| - name: Show which branch was used | |
| run: "echo \"Using branch: ${{ steps.resolve_branch.outputs.branch }}\"" | |
| - 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 -Filter *.sln | |
| 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 '\.ci' } | |
| 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: Commit and push changes | |
| 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 checkout target branch (or switch if exists) | |
| $branch_name = "${{ env.TARGET_BRANCH }}" | |
| git checkout $branch_name 2>$null || git checkout -b $branch_name | |
| # Add all changes (including new files and directories) | |
| git add -A | |
| # Check if there are any changes to commit | |
| $changes = git status --porcelain | |
| if ($changes) { | |
| Write-Host "Changes detected, committing..." | |
| git commit -m "Automated schema generation update - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`n`nGenerated on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC')`n`nThis commit contains automatically generated JSON schema updates based on the latest BHoM repositories." | |
| # Push the branch | |
| git push origin $branch_name | |
| Write-Host "Successfully pushed changes to branch: $branch_name" | |
| # Check if a PR already exists for this branch | |
| $existingPr = gh pr list --head $branch_name --base develop --json number --jq '.[0].number' | |
| if ($existingPr) { | |
| Write-Host "PR already exists (#$existingPr), adding a comment..." | |
| gh pr comment $existingPr --body "Automated schema generation updates pushed to branch '$branch_name' on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')." | |
| Write-Host "✓ Comment added to existing PR" | |
| } else { | |
| Write-Host "Creating pull request..." | |
| $prTitle = "Automated Schema Updates - $(Get-Date -Format 'yyyy-MM-dd')" | |
| $prBody = "## Automated Schema Generation`n`nThis pull request contains automatically generated JSON schema updates.`n`n**Generation Details:**`n- Generated on: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC')`n- Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}`n`n**Changes:**`nThe schemas have been regenerated based on the latest versions of all BHoM repositories listed in \`\`Included-repositories.txt\`\`.`n`nPlease review the changes before merging." | |
| gh pr create --title "$prTitle" --body "$prBody" --head $branch_name --base develop | |
| Write-Host "✓ Pull request created successfully" | |
| } | |
| } else { | |
| Write-Host "No changes to commit" | |
| } | |
| shell: pwsh | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |