From ed91fc03c9dfa1934bbdc75210c0c5e6d6323307 Mon Sep 17 00:00:00 2001 From: "Deepak Rathore (ALLYIS INC)" Date: Thu, 21 Aug 2025 23:06:25 +0530 Subject: [PATCH 01/17] feat: roslyn versio bump --- azure-pipelines/roslyn-version-bump.yml | 355 ++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 azure-pipelines/roslyn-version-bump.yml diff --git a/azure-pipelines/roslyn-version-bump.yml b/azure-pipelines/roslyn-version-bump.yml new file mode 100644 index 0000000000..103e0ba46e --- /dev/null +++ b/azure-pipelines/roslyn-version-bump.yml @@ -0,0 +1,355 @@ +trigger: none + +parameters: + - name: createPullRequest + displayName: Create Pull Request + type: boolean + default: true + - name: targetBranch + displayName: Target Branch for PR + type: string + default: main + +resources: + pipelines: + - pipeline: officialBuildCI + source: 327 + project: internal + branch: refs/heads/main + trigger: none + +pool: + vmImage: ubuntu-latest + +variables: + - name: RoslynStartSHA + value: "" + - name: RoslynEndSHA + value: $(resources.pipeline.officialBuildCI.sourceCommit) + - name: RoslynBuildNumber + value: $(resources.pipeline.officialBuildCI.runName) + - name: RoslynBuildId + value: $(resources.pipeline.officialBuildCI.runID) + - name: RoslynVersion + value: "" + +stages: + - stage: BumpRoslyn + displayName: Bump Roslyn Version + jobs: + - job: ProcessBump + displayName: Process Roslyn Bump + steps: + - checkout: self + persistCredentials: true + + - download: officialBuildCI + artifact: AssetManifests + displayName: Download AssetManifests from Roslyn build + + - task: UseDotNet@2 + displayName: Install .NET SDK + inputs: + version: 9.0.x + + - task: Bash@3 + displayName: Install tools + inputs: + targetType: inline + script: | + set -euo pipefail + # Install roslyn-tools + FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" + dotnet tool install -g Microsoft.RoslynTools --prerelease --add-source "$FEED" + echo "##vso[task.prependpath]$HOME/.dotnet/tools" + + # Install jq for JSON parsing + sudo apt-get update && sudo apt-get install -y jq + + - task: Bash@3 + displayName: Extract Roslyn version from AssetManifests + inputs: + targetType: inline + script: | + set -euo pipefail + + # The AssetManifests artifact is downloaded to this location + ASSET_MANIFEST_PATH="$(Pipeline.Workspace)/officialBuildCI/AssetManifests" + + # Find OfficialBuild.xml + XML_FILE="$ASSET_MANIFEST_PATH/OfficialBuild.xml" + + if [ ! -f "$XML_FILE" ]; then + echo "Error: OfficialBuild.xml not found at $XML_FILE" + ls -la "$ASSET_MANIFEST_PATH" || echo "AssetManifests directory not found" + exit 1 + fi + + # Extract version for Microsoft.CodeAnalysis package + VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) + + if [ -z "$VERSION" ]; then + # Try Microsoft.CodeAnalysis.Common + VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis\.Common"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) + fi + + if [ -n "$VERSION" ]; then + echo "##vso[task.setvariable variable=RoslynVersion]$VERSION" + echo "Latest Roslyn version from AssetManifest: $VERSION" + else + echo "Error: Could not extract version from AssetManifest" + exit 1 + fi + + # Also extract and verify commit from Build element + COMMIT=$(grep -oP ']*Commit="\K[^"]+' "$XML_FILE" | head -n1) + if [ -n "$COMMIT" ]; then + echo "Commit from AssetManifest: $COMMIT" + echo "Pipeline sourceCommit: $(RoslynEndSHA)" + fi + + - task: Bash@3 + displayName: Get current Roslyn SHA from package + inputs: + targetType: inline + script: | + set -euo pipefail + + # Read current version from package.json + CURRENT_VERSION=$(jq -r '.defaults.roslyn // empty' package.json) + + if [ -z "$CURRENT_VERSION" ]; then + echo "No roslyn version in package.json, this is first run" + echo "##vso[task.setvariable variable=RoslynStartSHA]0000000000000000000000000000000000000000" + exit 0 + fi + + echo "Current Roslyn version: $CURRENT_VERSION" + + # Download and extract commit SHA from NuGet package + TEMP_DIR=$(mktemp -d) + cd "$TEMP_DIR" + + PACKAGE_NAME="microsoft.codeanalysis.common" + DOTNET_TOOLS_FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2" + PACKAGE_URL="$DOTNET_TOOLS_FEED/$PACKAGE_NAME/$CURRENT_VERSION/$PACKAGE_NAME.$CURRENT_VERSION.nupkg" + + if curl -f -L -o package.nupkg "$PACKAGE_URL" 2>/dev/null; then + unzip -q package.nupkg + NUSPEC_FILE=$(find . -name "*.nuspec" -type f | head -n1) + if [ -n "$NUSPEC_FILE" ]; then + START_SHA=$(grep -oP 'repository[^>]*commit="\K[a-f0-9]{40}' "$NUSPEC_FILE" | head -n1 || echo "") + if [ -n "$START_SHA" ]; then + echo "##vso[task.setvariable variable=RoslynStartSHA]$START_SHA" + echo "Current Roslyn SHA: $START_SHA" + fi + fi + fi + + cd - >/dev/null + rm -rf "$TEMP_DIR" + + - task: Bash@3 + displayName: Get latest Roslyn build and version + inputs: + targetType: inline + script: | + set -euo pipefail + + echo "Getting latest Roslyn version from NuGet feed..." + + # Get the latest version from the dotnet-tools feed + PACKAGE_NAME="microsoft.codeanalysis.common" + PACKAGE_INDEX="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2/$PACKAGE_NAME/index.json" + + # Get all versions and pick the latest + VERSIONS=$(curl -s "$PACKAGE_INDEX" | jq -r '.versions[]' 2>/dev/null || echo "") + + if [ -z "$VERSIONS" ]; then + # Try dotnet8 feed as fallback + PACKAGE_INDEX="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/flat2/$PACKAGE_NAME/index.json" + VERSIONS=$(curl -s "$PACKAGE_INDEX" | jq -r '.versions[]' 2>/dev/null || echo "") + fi + + # Get the latest prerelease version + VERSION=$(echo "$VERSIONS" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+-' | tail -n1) + + if [ -z "$VERSION" ]; then + echo "Error: Could not find any Roslyn versions in feed" + exit 1 + fi + + echo "Latest version from feed: $VERSION" + echo "##vso[task.setvariable variable=RoslynVersion]$VERSION" + + # Download the package to get commit SHA + TEMP_DIR=$(mktemp -d) + cd "$TEMP_DIR" + + DOTNET_TOOLS_FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2" + PACKAGE_URL="$DOTNET_TOOLS_FEED/$PACKAGE_NAME/$VERSION/$PACKAGE_NAME.$VERSION.nupkg" + + if curl -f -L -o package.nupkg "$PACKAGE_URL" 2>/dev/null; then + unzip -q package.nupkg + NUSPEC_FILE=$(find . -name "*.nuspec" -type f | head -n1) + if [ -n "$NUSPEC_FILE" ]; then + END_SHA=$(grep -oP 'repository[^>]*commit="\K[a-f0-9]{40}' "$NUSPEC_FILE" | head -n1 || echo "") + if [ -n "$END_SHA" ]; then + echo "##vso[task.setvariable variable=RoslynEndSHA]$END_SHA" + echo "Latest Roslyn SHA: $END_SHA" + fi + fi + fi + + cd - >/dev/null + rm -rf "$TEMP_DIR" + + # If we couldn't get SHA, use GitHub as fallback + if [ -z "$END_SHA" ]; then + echo "Getting latest commit from GitHub..." + END_SHA=$(curl -s "https://api.github.com/repos/dotnet/roslyn/commits/main" | jq -r '.sha // empty') + echo "##vso[task.setvariable variable=RoslynEndSHA]$END_SHA" + fi + + - task: Bash@3 + displayName: Check if update needed + inputs: + targetType: inline + script: | + set -euo pipefail + + if [ "$(RoslynStartSHA)" = "$(RoslynEndSHA)" ]; then + echo "No new commits to process" + echo "##vso[task.setvariable variable=SkipUpdate]true" + else + echo "Update needed: $(RoslynStartSHA)..$(RoslynEndSHA)" + echo "##vso[task.setvariable variable=SkipUpdate]false" + fi + + - task: Bash@3 + displayName: Clone Roslyn repository + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git roslyn + + - task: Bash@3 + displayName: Setup auth for roslyn-tools + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + mkdir -p "$HOME/.roslyn-tools" + JSON=$(printf '{"GitHubToken":"$(System.AccessToken)","DevDivAzureDevOpsToken":"","DncEngAzureDevOpsToken":""}') + printf '%s' "$JSON" | base64 | tr -d '\n' > "$HOME/.roslyn-tools/settings" + + - task: Bash@3 + displayName: Generate PR list + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + cd roslyn + + # Run pr-finder with VSCode label + OUTPUT=$(roslyn-tools pr-finder \ + -s "$(RoslynStartSHA)" \ + -e "$(RoslynEndSHA)" \ + --format changelog \ + --label VSCode 2>/dev/null || echo "") + + if [ -z "$OUTPUT" ]; then + echo "(no PRs with required labels)" > ../pr-changelog.txt + else + printf "%s\n" "$OUTPUT" > ../pr-changelog.txt + fi + + cd .. + cat pr-changelog.txt + + - task: Bash@3 + displayName: Update CHANGELOG and package.json + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + + # Update package.json + jq --arg ver "$(RoslynVersion)" '.defaults.roslyn = $ver' package.json > package.json.tmp + mv package.json.tmp package.json + + # Update CHANGELOG.md + PR_LIST=$(cat pr-changelog.txt | sed 's/^/ /') + + # Read the current CHANGELOG + CHANGELOG_CONTENT=$(cat CHANGELOG.md) + + # Find and update the Roslyn bump line + # This is a simplified version - you may need to adjust based on your CHANGELOG format + echo "$CHANGELOG_CONTENT" | awk -v version="$(RoslynVersion)" -v prs="$PR_LIST" ' + /^\* Bump Roslyn to/ { + print "* Bump Roslyn to " version " (PR: [#TBD](TBD))" + if (prs != " (no PRs with required labels)") { + print prs + } + next + } + /^ \*/ && prev ~ /^\* Bump Roslyn to/ { + next + } + { + prev = $0 + print + } + ' > CHANGELOG.md.tmp + + mv CHANGELOG.md.tmp CHANGELOG.md + + - task: Bash@3 + displayName: Create and push branch + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + + # Configure git + git config user.name "Azure Pipelines" + git config user.email "azuredevops@microsoft.com" + + # Create branch + BRANCH_NAME="roslyn-bump/$(RoslynEndSHA)" + git checkout -b "$BRANCH_NAME" + + # Commit changes + git add package.json CHANGELOG.md + git commit -m "Bump Roslyn to $(RoslynVersion)" + + # Push branch + git push origin "$BRANCH_NAME" + + echo "##vso[task.setvariable variable=PrBranch]$BRANCH_NAME" + + - task: Bash@3 + displayName: Create Pull Request + condition: and(ne(variables['SkipUpdate'], 'true'), eq('${{ parameters.createPullRequest }}', 'true')) + inputs: + targetType: inline + script: | + set -euo pipefail + + # Create PR using Azure DevOps REST API + PR_TITLE="Bump Roslyn to $(RoslynVersion)" + PR_DESCRIPTION="Manual Roslyn version bump triggered by $(Build.RequestedFor).\n\n**Version:** \`$(RoslynVersion)\`\n**Commit Range:** \`$(RoslynStartSHA)...$(RoslynEndSHA)\`\n**Azure DevOps Build:** [$(RoslynBuildNumber)](https://dev.azure.com/dnceng/internal/_build/results?buildId=$(resources.pipeline.officialBuildCI.runID))\n\nSee CHANGELOG.md for included PRs." + + # You would need to use Azure DevOps REST API or GitHub API here + # This is a placeholder for the actual PR creation + echo "Pull request would be created with:" + echo "Title: $PR_TITLE" + echo "Branch: $(PrBranch)" + echo "Target: ${{ parameters.targetBranch }}" + echo "Description: $PR_DESCRIPTION" From 0a8d36ea5ed282a62e46afa2990025f6e39f7a98 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Fri, 22 Aug 2025 14:07:40 +0530 Subject: [PATCH 02/17] using 1ES PT --- azure-pipelines/roslyn-version-bump.yml | 616 ++++++++++++------------ 1 file changed, 295 insertions(+), 321 deletions(-) diff --git a/azure-pipelines/roslyn-version-bump.yml b/azure-pipelines/roslyn-version-bump.yml index 103e0ba46e..0a9726f04f 100644 --- a/azure-pipelines/roslyn-version-bump.yml +++ b/azure-pipelines/roslyn-version-bump.yml @@ -1,4 +1,5 @@ trigger: none +pr: none parameters: - name: createPullRequest @@ -11,6 +12,11 @@ parameters: default: main resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release pipelines: - pipeline: officialBuildCI source: 327 @@ -18,9 +24,6 @@ resources: branch: refs/heads/main trigger: none -pool: - vmImage: ubuntu-latest - variables: - name: RoslynStartSHA value: "" @@ -33,323 +36,294 @@ variables: - name: RoslynVersion value: "" -stages: - - stage: BumpRoslyn - displayName: Bump Roslyn Version - jobs: - - job: ProcessBump - displayName: Process Roslyn Bump - steps: - - checkout: self - persistCredentials: true - - - download: officialBuildCI - artifact: AssetManifests - displayName: Download AssetManifests from Roslyn build - - - task: UseDotNet@2 - displayName: Install .NET SDK - inputs: - version: 9.0.x - - - task: Bash@3 - displayName: Install tools - inputs: - targetType: inline - script: | - set -euo pipefail - # Install roslyn-tools - FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" - dotnet tool install -g Microsoft.RoslynTools --prerelease --add-source "$FEED" - echo "##vso[task.prependpath]$HOME/.dotnet/tools" - - # Install jq for JSON parsing - sudo apt-get update && sudo apt-get install -y jq - - - task: Bash@3 - displayName: Extract Roslyn version from AssetManifests - inputs: - targetType: inline - script: | - set -euo pipefail - - # The AssetManifests artifact is downloaded to this location - ASSET_MANIFEST_PATH="$(Pipeline.Workspace)/officialBuildCI/AssetManifests" - - # Find OfficialBuild.xml - XML_FILE="$ASSET_MANIFEST_PATH/OfficialBuild.xml" - - if [ ! -f "$XML_FILE" ]; then - echo "Error: OfficialBuild.xml not found at $XML_FILE" - ls -la "$ASSET_MANIFEST_PATH" || echo "AssetManifests directory not found" - exit 1 - fi - - # Extract version for Microsoft.CodeAnalysis package - VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) - - if [ -z "$VERSION" ]; then - # Try Microsoft.CodeAnalysis.Common - VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis\.Common"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) - fi - - if [ -n "$VERSION" ]; then - echo "##vso[task.setvariable variable=RoslynVersion]$VERSION" - echo "Latest Roslyn version from AssetManifest: $VERSION" - else - echo "Error: Could not extract version from AssetManifest" - exit 1 - fi - - # Also extract and verify commit from Build element - COMMIT=$(grep -oP ']*Commit="\K[^"]+' "$XML_FILE" | head -n1) - if [ -n "$COMMIT" ]; then - echo "Commit from AssetManifest: $COMMIT" - echo "Pipeline sourceCommit: $(RoslynEndSHA)" - fi - - - task: Bash@3 - displayName: Get current Roslyn SHA from package - inputs: - targetType: inline - script: | - set -euo pipefail - - # Read current version from package.json - CURRENT_VERSION=$(jq -r '.defaults.roslyn // empty' package.json) - - if [ -z "$CURRENT_VERSION" ]; then - echo "No roslyn version in package.json, this is first run" - echo "##vso[task.setvariable variable=RoslynStartSHA]0000000000000000000000000000000000000000" - exit 0 - fi - - echo "Current Roslyn version: $CURRENT_VERSION" - - # Download and extract commit SHA from NuGet package - TEMP_DIR=$(mktemp -d) - cd "$TEMP_DIR" - - PACKAGE_NAME="microsoft.codeanalysis.common" - DOTNET_TOOLS_FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2" - PACKAGE_URL="$DOTNET_TOOLS_FEED/$PACKAGE_NAME/$CURRENT_VERSION/$PACKAGE_NAME.$CURRENT_VERSION.nupkg" - - if curl -f -L -o package.nupkg "$PACKAGE_URL" 2>/dev/null; then - unzip -q package.nupkg - NUSPEC_FILE=$(find . -name "*.nuspec" -type f | head -n1) - if [ -n "$NUSPEC_FILE" ]; then - START_SHA=$(grep -oP 'repository[^>]*commit="\K[a-f0-9]{40}' "$NUSPEC_FILE" | head -n1 || echo "") - if [ -n "$START_SHA" ]; then - echo "##vso[task.setvariable variable=RoslynStartSHA]$START_SHA" - echo "Current Roslyn SHA: $START_SHA" +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + pool: + name: AzurePipelines-EO + image: 1ESPT-Ubuntu22.04 + os: linux + stages: + - stage: BumpRoslyn + displayName: Bump Roslyn Version + jobs: + - job: ProcessBump + displayName: Process Roslyn Bump + pool: + name: AzurePipelines-EO + image: 1ESPT-Ubuntu22.04 + os: linux + templateContext: + type: releaseJob + isProduction: false + inputs: + - input: pipelineArtifact + pipeline: officialBuildCI + artifactName: AssetManifests + destinationPath: $(Pipeline.Workspace)/officialBuildCI/AssetManifests + steps: + - checkout: self + persistCredentials: true + + - task: UseDotNet@2 + displayName: Install .NET SDK + inputs: + version: 9.0.x + + - task: Bash@3 + displayName: Install tools + inputs: + targetType: inline + script: | + set -euo pipefail + # Install roslyn-tools + FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" + dotnet tool install -g Microsoft.RoslynTools --prerelease --add-source "$FEED" + echo "##vso[task.prependpath]$HOME/.dotnet/tools" + + # Install jq for JSON parsing + sudo apt-get update && sudo apt-get install -y jq + + - task: Bash@3 + displayName: Extract Roslyn version from AssetManifests + inputs: + targetType: inline + script: | + set -euo pipefail + + # The AssetManifests artifact is downloaded to this location + ASSET_MANIFEST_PATH="$(Pipeline.Workspace)/officialBuildCI/AssetManifests" + + # Find OfficialBuild.xml + XML_FILE="$ASSET_MANIFEST_PATH/OfficialBuild.xml" + + if [ ! -f "$XML_FILE" ]; then + echo "Error: OfficialBuild.xml not found at $XML_FILE" + ls -la "$ASSET_MANIFEST_PATH" || echo "AssetManifests directory not found" + exit 1 + fi + + # Extract version for Microsoft.CodeAnalysis package + VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) + + if [ -z "$VERSION" ]; then + # Try Microsoft.CodeAnalysis.Common + VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis\.Common"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) + fi + + if [ -n "$VERSION" ]; then + echo "##vso[task.setvariable variable=RoslynVersion]$VERSION" + echo "Latest Roslyn version from AssetManifest: $VERSION" + else + echo "Error: Could not extract version from AssetManifest" + exit 1 fi - fi - fi - - cd - >/dev/null - rm -rf "$TEMP_DIR" - - - task: Bash@3 - displayName: Get latest Roslyn build and version - inputs: - targetType: inline - script: | - set -euo pipefail - - echo "Getting latest Roslyn version from NuGet feed..." - - # Get the latest version from the dotnet-tools feed - PACKAGE_NAME="microsoft.codeanalysis.common" - PACKAGE_INDEX="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2/$PACKAGE_NAME/index.json" - - # Get all versions and pick the latest - VERSIONS=$(curl -s "$PACKAGE_INDEX" | jq -r '.versions[]' 2>/dev/null || echo "") - - if [ -z "$VERSIONS" ]; then - # Try dotnet8 feed as fallback - PACKAGE_INDEX="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/flat2/$PACKAGE_NAME/index.json" - VERSIONS=$(curl -s "$PACKAGE_INDEX" | jq -r '.versions[]' 2>/dev/null || echo "") - fi - - # Get the latest prerelease version - VERSION=$(echo "$VERSIONS" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+-' | tail -n1) - - if [ -z "$VERSION" ]; then - echo "Error: Could not find any Roslyn versions in feed" - exit 1 - fi - - echo "Latest version from feed: $VERSION" - echo "##vso[task.setvariable variable=RoslynVersion]$VERSION" - - # Download the package to get commit SHA - TEMP_DIR=$(mktemp -d) - cd "$TEMP_DIR" - - DOTNET_TOOLS_FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2" - PACKAGE_URL="$DOTNET_TOOLS_FEED/$PACKAGE_NAME/$VERSION/$PACKAGE_NAME.$VERSION.nupkg" - - if curl -f -L -o package.nupkg "$PACKAGE_URL" 2>/dev/null; then - unzip -q package.nupkg - NUSPEC_FILE=$(find . -name "*.nuspec" -type f | head -n1) - if [ -n "$NUSPEC_FILE" ]; then - END_SHA=$(grep -oP 'repository[^>]*commit="\K[a-f0-9]{40}' "$NUSPEC_FILE" | head -n1 || echo "") - if [ -n "$END_SHA" ]; then - echo "##vso[task.setvariable variable=RoslynEndSHA]$END_SHA" - echo "Latest Roslyn SHA: $END_SHA" + + # Display the END SHA from pipeline resource + echo "Using END SHA from pipeline resource: $(RoslynEndSHA)" + + - task: Bash@3 + displayName: Get current Roslyn SHA from package + inputs: + targetType: inline + script: | + set -euo pipefail + + # Read current version from package.json + CURRENT_VERSION=$(jq -r '.defaults.roslyn // empty' package.json) + + if [ -z "$CURRENT_VERSION" ]; then + echo "No roslyn version in package.json, this is first run" + echo "##vso[task.setvariable variable=RoslynStartSHA]0000000000000000000000000000000000000000" + exit 0 fi - fi - fi - - cd - >/dev/null - rm -rf "$TEMP_DIR" - - # If we couldn't get SHA, use GitHub as fallback - if [ -z "$END_SHA" ]; then - echo "Getting latest commit from GitHub..." - END_SHA=$(curl -s "https://api.github.com/repos/dotnet/roslyn/commits/main" | jq -r '.sha // empty') - echo "##vso[task.setvariable variable=RoslynEndSHA]$END_SHA" - fi - - - task: Bash@3 - displayName: Check if update needed - inputs: - targetType: inline - script: | - set -euo pipefail - - if [ "$(RoslynStartSHA)" = "$(RoslynEndSHA)" ]; then - echo "No new commits to process" - echo "##vso[task.setvariable variable=SkipUpdate]true" - else - echo "Update needed: $(RoslynStartSHA)..$(RoslynEndSHA)" - echo "##vso[task.setvariable variable=SkipUpdate]false" - fi - - - task: Bash@3 - displayName: Clone Roslyn repository - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git roslyn - - - task: Bash@3 - displayName: Setup auth for roslyn-tools - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - mkdir -p "$HOME/.roslyn-tools" - JSON=$(printf '{"GitHubToken":"$(System.AccessToken)","DevDivAzureDevOpsToken":"","DncEngAzureDevOpsToken":""}') - printf '%s' "$JSON" | base64 | tr -d '\n' > "$HOME/.roslyn-tools/settings" - - - task: Bash@3 - displayName: Generate PR list - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - cd roslyn - - # Run pr-finder with VSCode label - OUTPUT=$(roslyn-tools pr-finder \ - -s "$(RoslynStartSHA)" \ - -e "$(RoslynEndSHA)" \ - --format changelog \ - --label VSCode 2>/dev/null || echo "") - - if [ -z "$OUTPUT" ]; then - echo "(no PRs with required labels)" > ../pr-changelog.txt - else - printf "%s\n" "$OUTPUT" > ../pr-changelog.txt - fi - - cd .. - cat pr-changelog.txt - - - task: Bash@3 - displayName: Update CHANGELOG and package.json - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - - # Update package.json - jq --arg ver "$(RoslynVersion)" '.defaults.roslyn = $ver' package.json > package.json.tmp - mv package.json.tmp package.json - - # Update CHANGELOG.md - PR_LIST=$(cat pr-changelog.txt | sed 's/^/ /') - - # Read the current CHANGELOG - CHANGELOG_CONTENT=$(cat CHANGELOG.md) - - # Find and update the Roslyn bump line - # This is a simplified version - you may need to adjust based on your CHANGELOG format - echo "$CHANGELOG_CONTENT" | awk -v version="$(RoslynVersion)" -v prs="$PR_LIST" ' - /^\* Bump Roslyn to/ { - print "* Bump Roslyn to " version " (PR: [#TBD](TBD))" - if (prs != " (no PRs with required labels)") { - print prs - } - next - } - /^ \*/ && prev ~ /^\* Bump Roslyn to/ { - next - } - { - prev = $0 - print - } - ' > CHANGELOG.md.tmp - - mv CHANGELOG.md.tmp CHANGELOG.md - - - task: Bash@3 - displayName: Create and push branch - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - - # Configure git - git config user.name "Azure Pipelines" - git config user.email "azuredevops@microsoft.com" - - # Create branch - BRANCH_NAME="roslyn-bump/$(RoslynEndSHA)" - git checkout -b "$BRANCH_NAME" - - # Commit changes - git add package.json CHANGELOG.md - git commit -m "Bump Roslyn to $(RoslynVersion)" - - # Push branch - git push origin "$BRANCH_NAME" - - echo "##vso[task.setvariable variable=PrBranch]$BRANCH_NAME" - - - task: Bash@3 - displayName: Create Pull Request - condition: and(ne(variables['SkipUpdate'], 'true'), eq('${{ parameters.createPullRequest }}', 'true')) - inputs: - targetType: inline - script: | - set -euo pipefail - - # Create PR using Azure DevOps REST API - PR_TITLE="Bump Roslyn to $(RoslynVersion)" - PR_DESCRIPTION="Manual Roslyn version bump triggered by $(Build.RequestedFor).\n\n**Version:** \`$(RoslynVersion)\`\n**Commit Range:** \`$(RoslynStartSHA)...$(RoslynEndSHA)\`\n**Azure DevOps Build:** [$(RoslynBuildNumber)](https://dev.azure.com/dnceng/internal/_build/results?buildId=$(resources.pipeline.officialBuildCI.runID))\n\nSee CHANGELOG.md for included PRs." - - # You would need to use Azure DevOps REST API or GitHub API here - # This is a placeholder for the actual PR creation - echo "Pull request would be created with:" - echo "Title: $PR_TITLE" - echo "Branch: $(PrBranch)" - echo "Target: ${{ parameters.targetBranch }}" - echo "Description: $PR_DESCRIPTION" + + echo "Current Roslyn version: $CURRENT_VERSION" + + # Download and extract commit SHA from NuGet package + TEMP_DIR=$(mktemp -d) + cd "$TEMP_DIR" + + PACKAGE_NAME="microsoft.codeanalysis.common" + DOTNET_TOOLS_FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2" + PACKAGE_URL="$DOTNET_TOOLS_FEED/$PACKAGE_NAME/$CURRENT_VERSION/$PACKAGE_NAME.$CURRENT_VERSION.nupkg" + + if curl -f -L -o package.nupkg "$PACKAGE_URL" 2>/dev/null; then + unzip -q package.nupkg + NUSPEC_FILE=$(find . -name "*.nuspec" -type f | head -n1) + if [ -n "$NUSPEC_FILE" ]; then + START_SHA=$(grep -oP 'repository[^>]*commit="\K[a-f0-9]{40}' "$NUSPEC_FILE" | head -n1 || echo "") + if [ -n "$START_SHA" ]; then + echo "##vso[task.setvariable variable=RoslynStartSHA]$START_SHA" + echo "Current Roslyn SHA: $START_SHA" + fi + fi + fi + + cd - >/dev/null + rm -rf "$TEMP_DIR" + + - task: Bash@3 + displayName: Check if update needed + inputs: + targetType: inline + script: | + set -euo pipefail + + echo "START SHA: $(RoslynStartSHA)" + echo "END SHA: $(RoslynEndSHA)" + echo "New Roslyn Version: $(RoslynVersion)" + + if [ "$(RoslynStartSHA)" = "$(RoslynEndSHA)" ]; then + echo "No new commits to process" + echo "##vso[task.setvariable variable=SkipUpdate]true" + else + echo "Update needed: $(RoslynStartSHA)..$(RoslynEndSHA)" + echo "##vso[task.setvariable variable=SkipUpdate]false" + fi + + - task: Bash@3 + displayName: Clone Roslyn repository + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git roslyn + + - task: Bash@3 + displayName: Setup auth for roslyn-tools + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + mkdir -p "$HOME/.roslyn-tools" + JSON=$(printf '{"GitHubToken":"$(System.AccessToken)","DevDivAzureDevOpsToken":"","DncEngAzureDevOpsToken":""}') + printf '%s' "$JSON" | base64 | tr -d '\n' > "$HOME/.roslyn-tools/settings" + + - task: Bash@3 + displayName: Generate PR list + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + cd roslyn + + # Run pr-finder with VSCode label + OUTPUT=$(roslyn-tools pr-finder \ + -s "$(RoslynStartSHA)" \ + -e "$(RoslynEndSHA)" \ + --format changelog \ + --label VSCode 2>/dev/null || echo "") + + if [ -z "$OUTPUT" ]; then + echo "(no PRs with required labels)" > ../pr-changelog.txt + else + printf "%s\n" "$OUTPUT" > ../pr-changelog.txt + fi + + cd .. + cat pr-changelog.txt + + - task: Bash@3 + displayName: Update CHANGELOG and package.json + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + + # Update package.json with new Roslyn version from AssetManifest + jq --arg ver "$(RoslynVersion)" '.defaults.roslyn = $ver' package.json > package.json.tmp + mv package.json.tmp package.json + + # Update CHANGELOG.md + PR_LIST=$(cat pr-changelog.txt | sed 's/^/ /') + + # Read the current CHANGELOG + CHANGELOG_CONTENT=$(cat CHANGELOG.md) + + # Find and update the Roslyn bump line + echo "$CHANGELOG_CONTENT" | awk -v version="$(RoslynVersion)" -v prs="$PR_LIST" ' + /^\* Bump Roslyn to/ { + print "* Bump Roslyn to " version " (PR: [#TBD](TBD))" + if (prs != " (no PRs with required labels)") { + print prs + } + next + } + /^ \*/ && prev ~ /^\* Bump Roslyn to/ { + next + } + { + prev = $0 + print + } + ' > CHANGELOG.md.tmp + + mv CHANGELOG.md.tmp CHANGELOG.md + + - task: Bash@3 + displayName: Create and push branch + condition: ne(variables['SkipUpdate'], 'true') + inputs: + targetType: inline + script: | + set -euo pipefail + + # Configure git + git config user.name "Azure Pipelines" + git config user.email "azuredevops@microsoft.com" + + # Create branch using the first 8 chars of END SHA for shorter branch name + SHORT_SHA=$(echo "$(RoslynEndSHA)" | cut -c1-8) + BRANCH_NAME="roslyn-bump/$SHORT_SHA" + git checkout -b "$BRANCH_NAME" + + # Commit changes + git add package.json CHANGELOG.md + git commit -m "Bump Roslyn to $(RoslynVersion)" + + # Push branch + git push origin "$BRANCH_NAME" + + echo "##vso[task.setvariable variable=PrBranch]$BRANCH_NAME" + + - task: Bash@3 + displayName: Create Pull Request + condition: and(ne(variables['SkipUpdate'], 'true'), eq('${{ parameters.createPullRequest }}', 'true')) + inputs: + targetType: inline + script: | + set -euo pipefail + + # Create PR using Azure DevOps REST API + PR_TITLE="Bump Roslyn to $(RoslynVersion)" + PR_DESCRIPTION="Manual Roslyn version bump triggered by $(Build.RequestedFor).\n\n**Version:** \`$(RoslynVersion)\`\n**Commit Range:** \`$(RoslynStartSHA)...$(RoslynEndSHA)\`\n**Azure DevOps Build:** [$(RoslynBuildNumber)](https://dev.azure.com/dnceng/internal/_build/results?buildId=$(RoslynBuildId))\n\nSee CHANGELOG.md for included PRs." + + # You would need to use Azure DevOps REST API or GitHub API here + # This is a placeholder for the actual PR creation + echo "Pull request would be created with:" + echo "Title: $PR_TITLE" + echo "Branch: $(PrBranch)" + echo "Target: ${{ parameters.targetBranch }}" + echo "Description: $PR_DESCRIPTION" + /^\* Bump Roslyn to/ { + print "* Bump Roslyn to " version " (PR: [#TBD](TBD))" + if (prs != " (no PRs with required labels)") { + print prs + } + next + } + /^ \*/ && prev ~ /^\* Bump Roslyn to/ { + next + } + { + prev = $0 + print + } + ' > CHANGELOG.md.tmp + + mv CHANGELOG.md.tmp CHANGELOG.md + From 1e19e616f4dae734951e92b588820e4fc1dded1e Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Mon, 25 Aug 2025 20:58:36 +0530 Subject: [PATCH 03/17] Using gulp taks --- azure-pipelines/roslyn-version-bump.yml | 300 +++-------------------- tasks/insertionTasks.ts | 308 ++++++++++++++++++++++++ 2 files changed, 340 insertions(+), 268 deletions(-) create mode 100644 tasks/insertionTasks.ts diff --git a/azure-pipelines/roslyn-version-bump.yml b/azure-pipelines/roslyn-version-bump.yml index 0a9726f04f..3856bc53e0 100644 --- a/azure-pipelines/roslyn-version-bump.yml +++ b/azure-pipelines/roslyn-version-bump.yml @@ -19,29 +19,25 @@ resources: ref: refs/tags/release pipelines: - pipeline: officialBuildCI - source: 327 + source: dotnet-roslyn-official project: internal - branch: refs/heads/main + branch: main trigger: none variables: - - name: RoslynStartSHA - value: "" - name: RoslynEndSHA value: $(resources.pipeline.officialBuildCI.sourceCommit) - name: RoslynBuildNumber value: $(resources.pipeline.officialBuildCI.runName) - name: RoslynBuildId value: $(resources.pipeline.officialBuildCI.runID) - - name: RoslynVersion - value: "" extends: - template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + template: v1/1ES.Unofficial.PipelineTemplate.yml@1ESPipelineTemplates parameters: pool: name: AzurePipelines-EO - image: 1ESPT-Ubuntu22.04 + image: AzurePipelinesUbuntu22.04compliant os: linux stages: - stage: BumpRoslyn @@ -51,16 +47,8 @@ extends: displayName: Process Roslyn Bump pool: name: AzurePipelines-EO - image: 1ESPT-Ubuntu22.04 + image: AzurePipelinesUbuntu22.04compliant os: linux - templateContext: - type: releaseJob - isProduction: false - inputs: - - input: pipelineArtifact - pipeline: officialBuildCI - artifactName: AssetManifests - destinationPath: $(Pipeline.Workspace)/officialBuildCI/AssetManifests steps: - checkout: self persistCredentials: true @@ -70,260 +58,36 @@ extends: inputs: version: 9.0.x - - task: Bash@3 - displayName: Install tools + - task: NodeTool@0 + displayName: Install Node.js inputs: - targetType: inline - script: | - set -euo pipefail - # Install roslyn-tools - FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" - dotnet tool install -g Microsoft.RoslynTools --prerelease --add-source "$FEED" - echo "##vso[task.prependpath]$HOME/.dotnet/tools" + versionSpec: '20.x' - # Install jq for JSON parsing - sudo apt-get update && sudo apt-get install -y jq - - - task: Bash@3 - displayName: Extract Roslyn version from AssetManifests - inputs: - targetType: inline - script: | - set -euo pipefail - - # The AssetManifests artifact is downloaded to this location - ASSET_MANIFEST_PATH="$(Pipeline.Workspace)/officialBuildCI/AssetManifests" - - # Find OfficialBuild.xml - XML_FILE="$ASSET_MANIFEST_PATH/OfficialBuild.xml" - - if [ ! -f "$XML_FILE" ]; then - echo "Error: OfficialBuild.xml not found at $XML_FILE" - ls -la "$ASSET_MANIFEST_PATH" || echo "AssetManifests directory not found" - exit 1 - fi - - # Extract version for Microsoft.CodeAnalysis package - VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) - - if [ -z "$VERSION" ]; then - # Try Microsoft.CodeAnalysis.Common - VERSION=$(grep -oP 'Id="Microsoft\.CodeAnalysis\.Common"[^>]*Version="\K[^"]+' "$XML_FILE" | head -n1) - fi - - if [ -n "$VERSION" ]; then - echo "##vso[task.setvariable variable=RoslynVersion]$VERSION" - echo "Latest Roslyn version from AssetManifest: $VERSION" - else - echo "Error: Could not extract version from AssetManifest" - exit 1 - fi - - # Display the END SHA from pipeline resource - echo "Using END SHA from pipeline resource: $(RoslynEndSHA)" - - - task: Bash@3 - displayName: Get current Roslyn SHA from package - inputs: - targetType: inline - script: | - set -euo pipefail - - # Read current version from package.json - CURRENT_VERSION=$(jq -r '.defaults.roslyn // empty' package.json) - - if [ -z "$CURRENT_VERSION" ]; then - echo "No roslyn version in package.json, this is first run" - echo "##vso[task.setvariable variable=RoslynStartSHA]0000000000000000000000000000000000000000" - exit 0 - fi - - echo "Current Roslyn version: $CURRENT_VERSION" - - # Download and extract commit SHA from NuGet package - TEMP_DIR=$(mktemp -d) - cd "$TEMP_DIR" - - PACKAGE_NAME="microsoft.codeanalysis.common" - DOTNET_TOOLS_FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2" - PACKAGE_URL="$DOTNET_TOOLS_FEED/$PACKAGE_NAME/$CURRENT_VERSION/$PACKAGE_NAME.$CURRENT_VERSION.nupkg" - - if curl -f -L -o package.nupkg "$PACKAGE_URL" 2>/dev/null; then - unzip -q package.nupkg - NUSPEC_FILE=$(find . -name "*.nuspec" -type f | head -n1) - if [ -n "$NUSPEC_FILE" ]; then - START_SHA=$(grep -oP 'repository[^>]*commit="\K[a-f0-9]{40}' "$NUSPEC_FILE" | head -n1 || echo "") - if [ -n "$START_SHA" ]; then - echo "##vso[task.setvariable variable=RoslynStartSHA]$START_SHA" - echo "Current Roslyn SHA: $START_SHA" - fi - fi - fi - - cd - >/dev/null - rm -rf "$TEMP_DIR" - - - task: Bash@3 - displayName: Check if update needed - inputs: - targetType: inline - script: | - set -euo pipefail - - echo "START SHA: $(RoslynStartSHA)" - echo "END SHA: $(RoslynEndSHA)" - echo "New Roslyn Version: $(RoslynVersion)" - - if [ "$(RoslynStartSHA)" = "$(RoslynEndSHA)" ]; then - echo "No new commits to process" - echo "##vso[task.setvariable variable=SkipUpdate]true" - else - echo "Update needed: $(RoslynStartSHA)..$(RoslynEndSHA)" - echo "##vso[task.setvariable variable=SkipUpdate]false" - fi - - - task: Bash@3 - displayName: Clone Roslyn repository - condition: ne(variables['SkipUpdate'], 'true') + - task: DownloadPipelineArtifact@2 + displayName: Download Asset Manifests inputs: - targetType: inline - script: | - git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git roslyn - - - task: Bash@3 - displayName: Setup auth for roslyn-tools - condition: ne(variables['SkipUpdate'], 'true') + source: specific + project: internal + pipeline: dotnet-roslyn-official + runVersion: specific + runId: $(RoslynBuildId) + artifact: AssetManifests + path: $(Pipeline.Workspace)/AssetManifests + + - pwsh: | + npm ci + npm install + npm install -g gulp + gulp installDependencies + displayName: 'Install npm dependencies and gulp' + + - task: Npm@1 + displayName: Run Roslyn insertion + env: + GITHUB_TOKEN: $(System.AccessToken) + GitHubPAT: $(System.AccessToken) inputs: - targetType: inline - script: | - set -euo pipefail - mkdir -p "$HOME/.roslyn-tools" - JSON=$(printf '{"GitHubToken":"$(System.AccessToken)","DevDivAzureDevOpsToken":"","DncEngAzureDevOpsToken":""}') - printf '%s' "$JSON" | base64 | tr -d '\n' > "$HOME/.roslyn-tools/settings" - - - task: Bash@3 - displayName: Generate PR list - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - cd roslyn - - # Run pr-finder with VSCode label - OUTPUT=$(roslyn-tools pr-finder \ - -s "$(RoslynStartSHA)" \ - -e "$(RoslynEndSHA)" \ - --format changelog \ - --label VSCode 2>/dev/null || echo "") - - if [ -z "$OUTPUT" ]; then - echo "(no PRs with required labels)" > ../pr-changelog.txt - else - printf "%s\n" "$OUTPUT" > ../pr-changelog.txt - fi - - cd .. - cat pr-changelog.txt - - - task: Bash@3 - displayName: Update CHANGELOG and package.json - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - - # Update package.json with new Roslyn version from AssetManifest - jq --arg ver "$(RoslynVersion)" '.defaults.roslyn = $ver' package.json > package.json.tmp - mv package.json.tmp package.json - - # Update CHANGELOG.md - PR_LIST=$(cat pr-changelog.txt | sed 's/^/ /') - - # Read the current CHANGELOG - CHANGELOG_CONTENT=$(cat CHANGELOG.md) - - # Find and update the Roslyn bump line - echo "$CHANGELOG_CONTENT" | awk -v version="$(RoslynVersion)" -v prs="$PR_LIST" ' - /^\* Bump Roslyn to/ { - print "* Bump Roslyn to " version " (PR: [#TBD](TBD))" - if (prs != " (no PRs with required labels)") { - print prs - } - next - } - /^ \*/ && prev ~ /^\* Bump Roslyn to/ { - next - } - { - prev = $0 - print - } - ' > CHANGELOG.md.tmp - - mv CHANGELOG.md.tmp CHANGELOG.md - - - task: Bash@3 - displayName: Create and push branch - condition: ne(variables['SkipUpdate'], 'true') - inputs: - targetType: inline - script: | - set -euo pipefail - - # Configure git - git config user.name "Azure Pipelines" - git config user.email "azuredevops@microsoft.com" - - # Create branch using the first 8 chars of END SHA for shorter branch name - SHORT_SHA=$(echo "$(RoslynEndSHA)" | cut -c1-8) - BRANCH_NAME="roslyn-bump/$SHORT_SHA" - git checkout -b "$BRANCH_NAME" - - # Commit changes - git add package.json CHANGELOG.md - git commit -m "Bump Roslyn to $(RoslynVersion)" - - # Push branch - git push origin "$BRANCH_NAME" - - echo "##vso[task.setvariable variable=PrBranch]$BRANCH_NAME" - - - task: Bash@3 - displayName: Create Pull Request - condition: and(ne(variables['SkipUpdate'], 'true'), eq('${{ parameters.createPullRequest }}', 'true')) - inputs: - targetType: inline - script: | - set -euo pipefail - - # Create PR using Azure DevOps REST API - PR_TITLE="Bump Roslyn to $(RoslynVersion)" - PR_DESCRIPTION="Manual Roslyn version bump triggered by $(Build.RequestedFor).\n\n**Version:** \`$(RoslynVersion)\`\n**Commit Range:** \`$(RoslynStartSHA)...$(RoslynEndSHA)\`\n**Azure DevOps Build:** [$(RoslynBuildNumber)](https://dev.azure.com/dnceng/internal/_build/results?buildId=$(RoslynBuildId))\n\nSee CHANGELOG.md for included PRs." - - # You would need to use Azure DevOps REST API or GitHub API here - # This is a placeholder for the actual PR creation - echo "Pull request would be created with:" - echo "Title: $PR_TITLE" - echo "Branch: $(PrBranch)" - echo "Target: ${{ parameters.targetBranch }}" - echo "Description: $PR_DESCRIPTION" - /^\* Bump Roslyn to/ { - print "* Bump Roslyn to " version " (PR: [#TBD](TBD))" - if (prs != " (no PRs with required labels)") { - print prs - } - next - } - /^ \*/ && prev ~ /^\* Bump Roslyn to/ { - next - } - { - prev = $0 - print - } - ' > CHANGELOG.md.tmp + command: custom + customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --createPullRequest=${{ parameters.createPullRequest }} --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(System.AccessToken) --dryRun=false' - mv CHANGELOG.md.tmp CHANGELOG.md diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts new file mode 100644 index 0000000000..7a8c02e132 --- /dev/null +++ b/tasks/insertionTasks.ts @@ -0,0 +1,308 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as gulp from 'gulp'; +import * as fs from 'fs'; +import * as path from 'path'; +import minimist from 'minimist'; +import { Octokit } from '@octokit/rest'; +import { exec } from 'child_process'; +import { promisify } from 'util'; +import fetch from 'node-fetch'; +import * as xml2js from 'xml2js'; +import * as AdmZip from 'adm-zip'; + +const execAsync = promisify(exec); + +interface InsertionOptions { + roslynVersion?: string; + roslynStartSHA?: string; + roslynEndSHA?: string; + roslynBuildId?: string; + roslynBuildNumber?: string; + assetManifestPath?: string; + createPullRequest?: boolean; + targetBranch?: string; + githubPAT?: string; + dryRun?: boolean; +} + +gulp.task('insertion:roslyn', async (): Promise => { + const options = minimist(process.argv.slice(2)); + + console.log('Starting Roslyn insertion process...'); + console.log(`Options: ${JSON.stringify(options, null, 2)}`); + + try { + // Step 1: Extract Roslyn version from AssetManifest + if (!options.assetManifestPath) { + throw new Error('assetManifestPath is required'); + } + + const newVersion = await extractRoslynVersionFromManifest(options.assetManifestPath); + if (!newVersion) { + throw new Error('Failed to extract Roslyn version from asset manifest'); + } + options.roslynVersion = newVersion; + console.log(`New Roslyn version: ${newVersion}`); + + // Step 2: Get current SHA from package + const currentSHA = await getCurrentRoslynSHA(); + options.roslynStartSHA = currentSHA || '0000000000000000000000000000000000000000'; + console.log(`Current Roslyn SHA: ${options.roslynStartSHA}`); + + // Step 3: Check if update needed + if (!options.roslynEndSHA) { + throw new Error('roslynEndSHA is required'); + } + + if (options.roslynStartSHA === options.roslynEndSHA) { + console.log('No new commits to process - versions are identical'); + return; + } + + console.log(`Update needed: ${options.roslynStartSHA}..${options.roslynEndSHA}`); + + // Step 4: Clone Roslyn repo and generate PR list + await cloneRoslynRepo(); + const prList = await generatePRList(options.roslynStartSHA, options.roslynEndSHA); + console.log('PR List generated:', prList); + + // Step 5: Update files + await updatePackageJson(options.roslynVersion); + await updateChangelog(options.roslynVersion, prList, options.roslynBuildNumber, options.roslynBuildId); + + // Step 6: Create branch and PR if not dry run + if (!options.dryRun) { + const branchName = await createBranch(options.roslynEndSHA, options.roslynVersion); + console.log(`Branch created: ${branchName}`); + + if (options.createPullRequest) { + await createPullRequest(branchName, options); + console.log('Pull request created successfully'); + } + } else { + console.log('Dry run mode - skipping branch and PR creation'); + } + + } catch (error) { + console.error('Insertion failed:', error); + throw error; + } finally { + // Cleanup + if (fs.existsSync('roslyn')) { + await execAsync('rm -rf roslyn'); + } + } +}); + +async function extractRoslynVersionFromManifest(manifestPath: string): Promise { + const xmlFile = path.join(manifestPath, 'OfficialBuild.xml'); + + if (!fs.existsSync(xmlFile)) { + console.error(`OfficialBuild.xml not found at ${xmlFile}`); + return null; + } + + const xmlContent = fs.readFileSync(xmlFile, 'utf8'); + const parser = new xml2js.Parser(); + const result = await parser.parseStringPromise(xmlContent); + + // Navigate the XML structure to find Microsoft.CodeAnalysis package + const packages = result?.Build?.Package || []; + for (const pkg of packages) { + const attrs = pkg.$; + if (attrs?.Id === 'Microsoft.CodeAnalysis' || attrs?.Id === 'Microsoft.CodeAnalysis.Common') { + return attrs.Version; + } + } + + return null; +} + +async function getCurrentRoslynSHA(): Promise { + const packageJsonContent = fs.readFileSync('package.json', 'utf8'); + const packageJson = JSON.parse(packageJsonContent); + const currentVersion = packageJson.defaults?.roslyn; + + if (!currentVersion) { + console.log('No roslyn version in package.json, this is first run'); + return null; + } + + console.log(`Current Roslyn version: ${currentVersion}`); + + // Download and extract commit SHA from NuGet package + const packageName = 'microsoft.codeanalysis.common'; + const feed = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2'; + const packageUrl = `${feed}/${packageName}/${currentVersion}/${packageName}.${currentVersion}.nupkg`; + + try { + const response = await fetch(packageUrl); + if (!response.ok) { + console.error(`Failed to download package: ${response.statusText}`); + return null; + } + + const buffer = await response.buffer(); + const tempFile = path.join(process.cwd(), 'temp-package.nupkg'); + fs.writeFileSync(tempFile, buffer); + + const zip = new AdmZip(tempFile); + const entries = zip.getEntries(); + + for (const entry of entries) { + if (entry.entryName.endsWith('.nuspec')) { + const nuspecContent = zip.readAsText(entry); + const shaMatch = nuspecContent.match(/repository[^>]*commit="([a-f0-9]{40})"/); + fs.unlinkSync(tempFile); + return shaMatch ? shaMatch[1] : null; + } + } + + fs.unlinkSync(tempFile); + } catch (error) { + console.error('Error getting current SHA:', error); + } + + return null; +} + +async function cloneRoslynRepo(): Promise { + console.log('Cloning Roslyn repository...'); + await execAsync('git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git roslyn'); +} + +async function generatePRList(startSHA: string, endSHA: string): Promise { + console.log(`Generating PR list from ${startSHA} to ${endSHA}...`); + + // Setup auth for roslyn-tools + const homeDir = process.env.HOME || process.env.USERPROFILE; + const settingsDir = path.join(homeDir!, '.roslyn-tools'); + if (!fs.existsSync(settingsDir)) { + fs.mkdirSync(settingsDir, { recursive: true }); + } + + const authJson = { + GitHubToken: process.env.GITHUB_TOKEN || '', + DevDivAzureDevOpsToken: '', + DncEngAzureDevOpsToken: '' + }; + const settingsFile = path.join(settingsDir, 'settings'); + fs.writeFileSync(settingsFile, Buffer.from(JSON.stringify(authJson)).toString('base64')); + + try { + const { stdout } = await execAsync( + `cd roslyn && roslyn-tools pr-finder -s "${startSHA}" -e "${endSHA}" --format changelog --label VSCode`, + { maxBuffer: 10 * 1024 * 1024 } // 10MB buffer + ); + return stdout || '(no PRs with required labels)'; + } catch (error) { + console.warn('PR finder failed, using empty list:', error); + return '(no PRs with required labels)'; + } +} + +async function updatePackageJson(newVersion: string): Promise { + console.log(`Updating package.json with Roslyn version ${newVersion}...`); + const packageJsonPath = 'package.json'; + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + + if (!packageJson.defaults) { + packageJson.defaults = {}; + } + packageJson.defaults.roslyn = newVersion; + + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); +} + +async function updateChangelog(version: string, prList: string, buildNumber?: string, buildId?: string): Promise { + console.log('Updating CHANGELOG.md...'); + const changelogPath = 'CHANGELOG.md'; + let changelogContent = fs.readFileSync(changelogPath, 'utf8'); + + // Format PR list with proper indentation + const formattedPRList = prList.split('\n').map(line => ` ${line}`).join('\n'); + + // Find and update the Roslyn bump line + const lines = changelogContent.split('\n'); + const newLines: string[] = []; + let skipNext = false; + + for (let i = 0; i < lines.length; i++) { + if (skipNext && lines[i].startsWith(' *')) { + continue; + } + skipNext = false; + + if (lines[i].match(/^\* Bump Roslyn to/)) { + const prLink = buildNumber && buildId + ? `[${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` + : '[#TBD](TBD)'; + newLines.push(`* Bump Roslyn to ${version} (PR: ${prLink})`); + if (prList !== '(no PRs with required labels)') { + newLines.push(formattedPRList); + } + skipNext = true; + } else { + newLines.push(lines[i]); + } + } + + fs.writeFileSync(changelogPath, newLines.join('\n')); +} + +async function createBranch(endSHA: string, version: string): Promise { + console.log('Creating and pushing branch...'); + + await execAsync('git config user.name "Azure Pipelines"'); + await execAsync('git config user.email "azuredevops@microsoft.com"'); + + const shortSHA = endSHA.substring(0, 8); + const branchName = `roslyn-bump/${shortSHA}`; + + await execAsync(`git checkout -b ${branchName}`); + await execAsync('git add package.json CHANGELOG.md'); + await execAsync(`git commit -m "Bump Roslyn to ${version}"`); + await execAsync(`git push origin ${branchName}`); + + return branchName; +} + +async function createPullRequest(branchName: string, options: InsertionOptions): Promise { + console.log('Creating pull request...'); + + if (!options.githubPAT) { + console.warn('No GitHub PAT provided, skipping PR creation'); + return; + } + + const octokit = new Octokit({ auth: options.githubPAT }); + + const prTitle = `Bump Roslyn to ${options.roslynVersion}`; + const prBody = `Automated Roslyn version bump. + +**Version:** \`${options.roslynVersion}\` +**Commit Range:** \`${options.roslynStartSHA}...${options.roslynEndSHA}\` +**Azure DevOps Build:** [${options.roslynBuildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${options.roslynBuildId}) + +See CHANGELOG.md for included PRs.`; + + try { + const { data } = await octokit.pulls.create({ + owner: 'dotnet', + repo: 'vscode-csharp', + title: prTitle, + body: prBody, + head: branchName, + base: options.targetBranch || 'main' + }); + + console.log(`Pull request created: ${data.html_url}`); + } catch (error) { + console.error('Failed to create PR:', error); + throw error; + } +} From 225a38fa95d1f83c3f11e1565a98a1f6802377b2 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Tue, 26 Aug 2025 23:40:07 +0530 Subject: [PATCH 04/17] resolved review comments --- azure-pipelines/roslyn-version-bump.yml | 7 +- tasks/insertionTasks.ts | 287 +++++++++++++++--------- 2 files changed, 181 insertions(+), 113 deletions(-) diff --git a/azure-pipelines/roslyn-version-bump.yml b/azure-pipelines/roslyn-version-bump.yml index 3856bc53e0..e5526773f2 100644 --- a/azure-pipelines/roslyn-version-bump.yml +++ b/azure-pipelines/roslyn-version-bump.yml @@ -81,6 +81,11 @@ extends: gulp installDependencies displayName: 'Install npm dependencies and gulp' + - script: | + echo "Cloning Roslyn repository..." + git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git $(Pipeline.Workspace)/roslyn + displayName: 'Clone Roslyn repository' + - task: Npm@1 displayName: Run Roslyn insertion env: @@ -88,6 +93,6 @@ extends: GitHubPAT: $(System.AccessToken) inputs: command: custom - customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --createPullRequest=${{ parameters.createPullRequest }} --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(System.AccessToken) --dryRun=false' + customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynRepoPath=$(Pipeline.Workspace)/roslyn --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --createPullRequest=${{ parameters.createPullRequest }} --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(System.AccessToken) --dryRun=false' diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index 7a8c02e132..7de5ba59ba 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -6,24 +6,32 @@ import * as gulp from 'gulp'; import * as fs from 'fs'; import * as path from 'path'; +import * as os from 'os'; +import minimist from 'minimist'; + +function logWarning(message: string): void { + console.log(`##vso[task.logissue type=warning]${message}`); +} + +function logError(message: string): void { + console.log(`##vso[task.logissue type=error]${message}`); +} + import minimist from 'minimist'; -import { Octokit } from '@octokit/rest'; import { exec } from 'child_process'; import { promisify } from 'util'; import fetch from 'node-fetch'; -import * as xml2js from 'xml2js'; import * as AdmZip from 'adm-zip'; const execAsync = promisify(exec); interface InsertionOptions { roslynVersion?: string; - roslynStartSHA?: string; roslynEndSHA?: string; roslynBuildId?: string; roslynBuildNumber?: string; assetManifestPath?: string; - createPullRequest?: boolean; + roslynRepoPath?: string; targetBranch?: string; githubPAT?: string; dryRun?: boolean; @@ -50,51 +58,47 @@ gulp.task('insertion:roslyn', async (): Promise => { // Step 2: Get current SHA from package const currentSHA = await getCurrentRoslynSHA(); - options.roslynStartSHA = currentSHA || '0000000000000000000000000000000000000000'; - console.log(`Current Roslyn SHA: ${options.roslynStartSHA}`); + if (!currentSHA) { + throw new Error('Could not determine current Roslyn SHA from package'); + } + console.log(`Current Roslyn SHA: ${currentSHA}`); // Step 3: Check if update needed if (!options.roslynEndSHA) { throw new Error('roslynEndSHA is required'); } - if (options.roslynStartSHA === options.roslynEndSHA) { + if (currentSHA === options.roslynEndSHA) { console.log('No new commits to process - versions are identical'); return; } - console.log(`Update needed: ${options.roslynStartSHA}..${options.roslynEndSHA}`); + console.log(`Update needed: ${currentSHA}..${options.roslynEndSHA}`); - // Step 4: Clone Roslyn repo and generate PR list - await cloneRoslynRepo(); - const prList = await generatePRList(options.roslynStartSHA, options.roslynEndSHA); + // Step 4: Verify Roslyn repo exists + if (!options.roslynRepoPath) { + throw new Error('roslynRepoPath is required'); + } + await verifyRoslynRepo(options.roslynRepoPath); + + // Step 5: Generate PR list + const prList = await generatePRList(currentSHA, options.roslynEndSHA, options.roslynRepoPath, options); console.log('PR List generated:', prList); - // Step 5: Update files + // Step 6: Update files await updatePackageJson(options.roslynVersion); await updateChangelog(options.roslynVersion, prList, options.roslynBuildNumber, options.roslynBuildId); - // Step 6: Create branch and PR if not dry run - if (!options.dryRun) { - const branchName = await createBranch(options.roslynEndSHA, options.roslynVersion); - console.log(`Branch created: ${branchName}`); - - if (options.createPullRequest) { - await createPullRequest(branchName, options); - console.log('Pull request created successfully'); - } - } else { - console.log('Dry run mode - skipping branch and PR creation'); - } + // Step 6: Create branch and PR + await createBranchAndPR(options.roslynVersion, options.roslynEndSHA, options); } catch (error) { - console.error('Insertion failed:', error); - throw error; - } finally { - // Cleanup - if (fs.existsSync('roslyn')) { - await execAsync('rm -rf roslyn'); + const errorMessage = error instanceof Error ? error.message : String(error); + logError(`Insertion failed: ${errorMessage}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); } + throw error; } }); @@ -102,7 +106,7 @@ async function extractRoslynVersionFromManifest(manifestPath: string): Promise { console.log(`Current Roslyn version: ${currentVersion}`); - // Download and extract commit SHA from NuGet package - const packageName = 'microsoft.codeanalysis.common'; - const feed = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/flat2'; - const packageUrl = `${feed}/${packageName}/${currentVersion}/${packageName}.${currentVersion}.nupkg`; - try { - const response = await fetch(packageUrl); - if (!response.ok) { - console.error(`Failed to download package: ${response.statusText}`); + const packageName = 'microsoft.codeanalysis.common'; + // Package names are always lower case in the .nuget folder. + const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), currentVersion); + const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); + + if (nuspecFiles.length === 0) { + logError(`No .nuspec file found in ${packageDir}`); return null; } - - const buffer = await response.buffer(); - const tempFile = path.join(process.cwd(), 'temp-package.nupkg'); - fs.writeFileSync(tempFile, buffer); - - const zip = new AdmZip(tempFile); - const entries = zip.getEntries(); - - for (const entry of entries) { - if (entry.entryName.endsWith('.nuspec')) { - const nuspecContent = zip.readAsText(entry); - const shaMatch = nuspecContent.match(/repository[^>]*commit="([a-f0-9]{40})"/); - fs.unlinkSync(tempFile); - return shaMatch ? shaMatch[1] : null; - } + + if (nuspecFiles.length > 1) { + logError(`Multiple .nuspec files found in ${packageDir}`); + return null; + } + + const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); + const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); + const results = /commit="(.*?)"/.exec(nuspecFile); + if (results == null || results.length === 0) { + logError('Failed to find commit number from nuspec file'); + return null; } + + if (results.length !== 2) { + logError('Unexpected regex match result from nuspec file.'); + return null; + } + + const commitNumber = results[1]; + console.log(`Found commit SHA: ${commitNumber}`); + return commitNumber; - fs.unlinkSync(tempFile); } catch (error) { - console.error('Error getting current SHA:', error); + const errorMessage = error instanceof Error ? error.message : String(error); + logError(`Error getting current SHA: ${errorMessage}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); + } + return null; } - - return null; } -async function cloneRoslynRepo(): Promise { - console.log('Cloning Roslyn repository...'); - await execAsync('git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git roslyn'); +async function verifyRoslynRepo(roslynRepoPath: string): Promise { + if (!fs.existsSync(roslynRepoPath)) { + throw new Error(`Roslyn repository not found at ${roslynRepoPath}`); + } + console.log(`Using Roslyn repository at ${roslynRepoPath}`); } -async function generatePRList(startSHA: string, endSHA: string): Promise { +async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: string, options: InsertionOptions): Promise { console.log(`Generating PR list from ${startSHA} to ${endSHA}...`); // Setup auth for roslyn-tools @@ -186,7 +199,7 @@ async function generatePRList(startSHA: string, endSHA: string): Promise } const authJson = { - GitHubToken: process.env.GITHUB_TOKEN || '', + GitHubToken: options.githubPAT || '', DevDivAzureDevOpsToken: '', DncEngAzureDevOpsToken: '' }; @@ -195,12 +208,16 @@ async function generatePRList(startSHA: string, endSHA: string): Promise try { const { stdout } = await execAsync( - `cd roslyn && roslyn-tools pr-finder -s "${startSHA}" -e "${endSHA}" --format changelog --label VSCode`, + `cd "${roslynRepoPath}" && roslyn-tools pr-finder -s "${startSHA}" -e "${endSHA}" --format changelog --label VSCode`, { maxBuffer: 10 * 1024 * 1024 } // 10MB buffer ); return stdout || '(no PRs with required labels)'; } catch (error) { - console.warn('PR finder failed, using empty list:', error); + const errorMessage = error instanceof Error ? error.message : String(error); + logWarning(`PR finder failed, using empty list: ${errorMessage}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); + } return '(no PRs with required labels)'; } } @@ -209,9 +226,9 @@ async function updatePackageJson(newVersion: string): Promise { console.log(`Updating package.json with Roslyn version ${newVersion}...`); const packageJsonPath = 'package.json'; const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); - + if (!packageJson.defaults) { - packageJson.defaults = {}; + throw new Error('Could not find defaults section in package.json'); } packageJson.defaults.roslyn = newVersion; @@ -224,10 +241,10 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st let changelogContent = fs.readFileSync(changelogPath, 'utf8'); // Format PR list with proper indentation - const formattedPRList = prList.split('\n').map(line => ` ${line}`).join('\n'); + const formattedPRList = prList.split('\n').map(line => ` ${line}`).join(os.EOL); // Find and update the Roslyn bump line - const lines = changelogContent.split('\n'); + const lines = changelogContent.split(/\r?\n/); const newLines: string[] = []; let skipNext = false; @@ -254,55 +271,101 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st fs.writeFileSync(changelogPath, newLines.join('\n')); } -async function createBranch(endSHA: string, version: string): Promise { - console.log('Creating and pushing branch...'); - - await execAsync('git config user.name "Azure Pipelines"'); - await execAsync('git config user.email "azuredevops@microsoft.com"'); - - const shortSHA = endSHA.substring(0, 8); - const branchName = `roslyn-bump/${shortSHA}`; - - await execAsync(`git checkout -b ${branchName}`); - await execAsync('git add package.json CHANGELOG.md'); - await execAsync(`git commit -m "Bump Roslyn to ${version}"`); - await execAsync(`git push origin ${branchName}`); - - return branchName; -} - -async function createPullRequest(branchName: string, options: InsertionOptions): Promise { - console.log('Creating pull request...'); - - if (!options.githubPAT) { - console.warn('No GitHub PAT provided, skipping PR creation'); - return; - } - - const octokit = new Octokit({ auth: options.githubPAT }); - - const prTitle = `Bump Roslyn to ${options.roslynVersion}`; - const prBody = `Automated Roslyn version bump. - -**Version:** \`${options.roslynVersion}\` -**Commit Range:** \`${options.roslynStartSHA}...${options.roslynEndSHA}\` -**Azure DevOps Build:** [${options.roslynBuildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${options.roslynBuildId}) - -See CHANGELOG.md for included PRs.`; - +async function createBranchAndPR(version: string, endSHA: string, options: InsertionOptions): Promise { try { - const { data } = await octokit.pulls.create({ + console.log('Creating and pushing branch...'); + + // Configure git user + await git(['config', '--local', 'user.name', 'Azure Pipelines']); + await git(['config', '--local', 'user.email', 'azuredevops@microsoft.com']); + + // Check for changes + const changedFiles = await git(['diff', '--name-only', 'HEAD']); + if (!changedFiles) { + console.log('No files changed, skipping branch creation'); + return; + } + + console.log(`Changed files: ${changedFiles}`); + + // Create and checkout new branch + const shortSHA = endSHA.substring(0, 8); + const branchName = `insertion/${shortSHA}`; + await git(['checkout', '-b', branchName]); + + // Stage and commit changes + await git(['add', 'package.json', 'CHANGELOG.md']); + await git(['commit', '-m', `Bump Roslyn to ${version} (${shortSHA})`]); + + // Configure remote with PAT for authentication + const pat = options.githubPAT; + if (!pat) { + throw new Error('GitHub PAT is required to push changes'); + } + + const remoteRepoAlias = 'targetRepo'; + await git( + [ + 'remote', + 'add', + remoteRepoAlias, + `https://${pat}@github.com/dotnet/vscode-csharp.git`, + ], + false // Don't log the command to avoid exposing the PAT + ); + + // Check if branch already exists + const lsRemote = await git(['ls-remote', '--heads', remoteRepoAlias, branchName]); + if (lsRemote.trim() !== '') { + console.log(`##vso[task.logissue type=warning]${branchName} already exists. Skipping push.`); + return; + } + + // Push the branch + await git(['push', '-u', remoteRepoAlias, branchName]); + + // Create PR if not in dry run mode + if (options.dryRun) { + console.log('Dry run: Would create PR for branch', branchName); + return; + } + + console.log('Creating pull request...'); + const octokit = new Octokit({ auth: pat }); + const title = `Bump Roslyn to ${version} (${shortSHA})`; + + // Check if PR already exists + const listPullRequest = await octokit.rest.pulls.list({ owner: 'dotnet', repo: 'vscode-csharp', - title: prTitle, - body: prBody, + head: `dotnet:${branchName}`, + state: 'open' + }); + + if (listPullRequest.data.length > 0) { + console.log(`Pull request already exists: ${listPullRequest.data[0].html_url}`); + return; + } + + // Create the PR + const pr = await octokit.rest.pulls.create({ + owner: 'dotnet', + repo: 'vscode-csharp', + title: title, head: branchName, - base: options.targetBranch || 'main' + base: options.targetBranch || 'main', + body: `This is an automated PR to update the Roslyn version to ${version} (${shortSHA})`, + maintainer_can_modify: true }); - console.log(`Pull request created: ${data.html_url}`); + console.log(`Created pull request: ${pr.data.html_url}`); + } catch (error) { - console.error('Failed to create PR:', error); + const errorMessage = error instanceof Error ? error.message : String(error); + logError(`Failed to create branch/PR: ${errorMessage}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); + } throw error; } } From e4cf965903c85a6810b7d64218abb5caae0219de Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Wed, 27 Aug 2025 00:19:38 +0530 Subject: [PATCH 05/17] renamed pipline --- ...-version-bump.yml => dotnet-vscode-csharp-insertion.yml} | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) rename azure-pipelines/{roslyn-version-bump.yml => dotnet-vscode-csharp-insertion.yml} (91%) diff --git a/azure-pipelines/roslyn-version-bump.yml b/azure-pipelines/dotnet-vscode-csharp-insertion.yml similarity index 91% rename from azure-pipelines/roslyn-version-bump.yml rename to azure-pipelines/dotnet-vscode-csharp-insertion.yml index e5526773f2..a26969a3e0 100644 --- a/azure-pipelines/roslyn-version-bump.yml +++ b/azure-pipelines/dotnet-vscode-csharp-insertion.yml @@ -2,10 +2,6 @@ trigger: none pr: none parameters: - - name: createPullRequest - displayName: Create Pull Request - type: boolean - default: true - name: targetBranch displayName: Target Branch for PR type: string @@ -93,6 +89,6 @@ extends: GitHubPAT: $(System.AccessToken) inputs: command: custom - customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynRepoPath=$(Pipeline.Workspace)/roslyn --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --createPullRequest=${{ parameters.createPullRequest }} --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(System.AccessToken) --dryRun=false' + customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynRepoPath=$(Pipeline.Workspace)/roslyn --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(System.AccessToken) --dryRun=false' From cc3f2df401656a80a34319a7ef28841636c211e4 Mon Sep 17 00:00:00 2001 From: Azure Pipelines Date: Wed, 27 Aug 2025 03:51:23 +0530 Subject: [PATCH 06/17] added insertion task in gulp --- gulpfile.ts | 1 + tasks/insertionTasks.ts | 118 +++++++++++++++++++++++----------------- 2 files changed, 68 insertions(+), 51 deletions(-) diff --git a/gulpfile.ts b/gulpfile.ts index ef52553ced..e4f8732493 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -12,3 +12,4 @@ require('./tasks/debuggerTasks'); require('./tasks/snapTasks'); require('./tasks/signingTasks'); require('./tasks/profilingTasks'); +require('./tasks/insertionTasks'); diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index 7de5ba59ba..0c74e9dfa0 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -8,6 +8,14 @@ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import minimist from 'minimist'; +import { exec, spawnSync} from 'child_process'; +import { promisify } from 'util'; +import * as xml2js from 'xml2js'; +import { Octokit } from '@octokit/rest'; +import { allNugetPackages, NugetPackageInfo, platformSpecificPackages } from './offlinePackagingTasks'; +import { PlatformInformation } from '../src/shared/platform'; + +const execAsync = promisify(exec); function logWarning(message: string): void { console.log(`##vso[task.logissue type=warning]${message}`); @@ -17,13 +25,26 @@ function logError(message: string): void { console.log(`##vso[task.logissue type=error]${message}`); } -import minimist from 'minimist'; -import { exec } from 'child_process'; -import { promisify } from 'util'; -import fetch from 'node-fetch'; -import * as AdmZip from 'adm-zip'; +async function git(args: string[], printCommand = true): Promise { + if (printCommand) { + console.log(`git ${args.join(' ')}`); + } -const execAsync = promisify(exec); + const git = spawnSync('git', args); + if (git.status != 0) { + const err = git.stderr.toString(); + if (printCommand) { + console.log(`Failed to execute git ${args.join(' ')}.`); + } + throw err; + } + + const stdout = git.stdout.toString(); + if (printCommand) { + console.log(stdout); + } + return stdout; +} interface InsertionOptions { roslynVersion?: string; @@ -57,7 +78,7 @@ gulp.task('insertion:roslyn', async (): Promise => { console.log(`New Roslyn version: ${newVersion}`); // Step 2: Get current SHA from package - const currentSHA = await getCurrentRoslynSHA(); + const currentSHA = await getCommitFromNugetAsync(allNugetPackages.roslyn); if (!currentSHA) { throw new Error('Could not determine current Roslyn SHA from package'); } @@ -126,59 +147,54 @@ async function extractRoslynVersionFromManifest(manifestPath: string): Promise { - const packageJsonContent = fs.readFileSync('package.json', 'utf8'); - const packageJson = JSON.parse(packageJsonContent); - const currentVersion = packageJson.defaults?.roslyn; - - if (!currentVersion) { - console.log('No roslyn version in package.json, this is first run'); +async function getCommitFromNugetAsync(packageInfo: NugetPackageInfo): Promise { + const packageJsonString = fs.readFileSync('./package.json').toString(); + const packageJson = JSON.parse(packageJsonString); + const packageVersion = packageJson['defaults'][packageInfo.packageJsonName]; + if (!packageVersion) { + logError(`Can't find ${packageInfo.packageJsonName} version in package.json`); return null; } - - console.log(`Current Roslyn version: ${currentVersion}`); - - try { - const packageName = 'microsoft.codeanalysis.common'; - // Package names are always lower case in the .nuget folder. - const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), currentVersion); - const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); - if (nuspecFiles.length === 0) { - logError(`No .nuspec file found in ${packageDir}`); - return null; - } + const platform = await PlatformInformation.GetCurrent(); + const vsixPlatformInfo = platformSpecificPackages.find( + (p) => p.platformInfo.platform === platform.platform && p.platformInfo.architecture === platform.architecture + )!; - if (nuspecFiles.length > 1) { - logError(`Multiple .nuspec files found in ${packageDir}`); - return null; - } + const packageName = packageInfo.getPackageName(vsixPlatformInfo); + console.log(`${packageName} version is ${packageVersion}`); - const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); - const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); - const results = /commit="(.*?)"/.exec(nuspecFile); - if (results == null || results.length === 0) { - logError('Failed to find commit number from nuspec file'); - return null; - } + // Nuget package should exist under out/.nuget/ since we have run the install dependencies task. + // Package names are always lower case in the .nuget folder. + const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), packageVersion); + const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); - if (results.length !== 2) { - logError('Unexpected regex match result from nuspec file.'); - return null; - } + if (nuspecFiles.length === 0) { + logError(`No .nuspec file found in ${packageDir}`); + return null; + } - const commitNumber = results[1]; - console.log(`Found commit SHA: ${commitNumber}`); - return commitNumber; - - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logError(`Error getting current SHA: ${errorMessage}`); - if (error instanceof Error && error.stack) { - console.log(`##[debug]${error.stack}`); - } + if (nuspecFiles.length > 1) { + logError(`Multiple .nuspec files found in ${packageDir}`); + return null; + } + + const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); + const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); + const results = /commit="(.*)"/.exec(nuspecFile); + if (results == null || results.length == 0) { + logError('Failed to find commit number from nuspec file'); return null; } + + if (results.length != 2) { + logError('Unexpected regex match result from nuspec file.'); + return null; + } + + const commitNumber = results[1]; + console.log(`commitNumber is ${commitNumber}`); + return commitNumber; } async function verifyRoslynRepo(roslynRepoPath: string): Promise { From cb782a8f62a0915bbfde41fc23accfe674524b52 Mon Sep 17 00:00:00 2001 From: Azure Pipelines Date: Mon, 1 Sep 2025 21:19:58 +0530 Subject: [PATCH 07/17] extracted helper functions --- tasks/gitHelpers.ts | 196 ++++++++++++++++++++ tasks/insertionTasks.ts | 393 ++++++++++++++++++---------------------- 2 files changed, 370 insertions(+), 219 deletions(-) create mode 100644 tasks/gitHelpers.ts diff --git a/tasks/gitHelpers.ts b/tasks/gitHelpers.ts new file mode 100644 index 0000000000..a7c20c9ba0 --- /dev/null +++ b/tasks/gitHelpers.ts @@ -0,0 +1,196 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { spawnSync } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import { Octokit } from '@octokit/rest'; +import { NugetPackageInfo } from './offlinePackagingTasks'; +import { PlatformInformation } from '../src/shared/platform'; +import { platformSpecificPackages } from './offlinePackagingTasks'; + +export interface GitOptions { + userName?: string; + email?: string; + commitSha: string; + targetRemoteRepo: string; + baseBranch: string; +} + +export interface BranchAndPROptions extends GitOptions { + githubPAT: string; + dryRun?: boolean; + branchPrefix?: string; // optional prefix for branch names (defaults to 'localization') +} + +// Logging utilities +export function logWarning(message: string): void { + console.log(`##vso[task.logissue type=warning]${message}`); +} + +export function logError(message: string): void { + console.log(`##vso[task.logissue type=error]${message}`); +} + +export async function git(args: string[], printCommand = true): Promise { + if (printCommand) { + console.log(`git ${args.join(' ')}`); + } + + const git = spawnSync('git', args); + if (git.status !== 0) { + const err = git.stderr.toString(); + if (printCommand) { + console.log(`Failed to execute git ${args.join(' ')}.`); + } + throw err; + } + + const stdout = git.stdout.toString(); + if (printCommand) { + console.log(stdout); + } + return stdout; +} + +export async function getCommitFromNugetAsync(packageInfo: NugetPackageInfo): Promise { + try { + const packageJsonString = fs.readFileSync('./package.json').toString(); + const packageJson = JSON.parse(packageJsonString); + const packageVersion = packageJson['defaults'][packageInfo.packageJsonName]; + if (!packageVersion) { + logError(`Can't find ${packageInfo.packageJsonName} version in package.json`); + return null; + } + + const platform = await PlatformInformation.GetCurrent(); + const vsixPlatformInfo = platformSpecificPackages.find( + (p) => p.platformInfo.platform === platform.platform && p.platformInfo.architecture === platform.architecture + )!; + + const packageName = packageInfo.getPackageName(vsixPlatformInfo); + console.log(`${packageName} version is ${packageVersion}`); + + // Nuget package should exist under out/.nuget/ since we have run the install dependencies task. + // Package names are always lower case in the .nuget folder. + const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), packageVersion); + const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); + + if (nuspecFiles.length === 0) { + logError(`No .nuspec file found in ${packageDir}`); + return null; + } + + if (nuspecFiles.length > 1) { + logError(`Multiple .nuspec files found in ${packageDir}`); + return null; + } + + const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); + const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); + const results = /commit="(.*?)"/.exec(nuspecFile); + if (results == null || results.length === 0) { + logError('Failed to find commit number from nuspec file'); + return null; + } + + if (results.length !== 2) { + logError('Unexpected regex match result from nuspec file.'); + return null; + } + + const commitNumber = results[1]; + console.log(`commitNumber is ${commitNumber}`); + return commitNumber; + } catch (error) { + logError(`Error getting commit from NuGet package: ${error}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); + } + throw error; + } +} + +export async function createBranchAndPR( + options: BranchAndPROptions, + title: string, + body?: string +): Promise { + const { githubPAT, targetRemoteRepo, baseBranch, dryRun } = options; + const remoteRepoAlias = 'target'; + const branchPrefix = options.branchPrefix || 'localization'; + const newBranchName = `${branchPrefix}/${options.commitSha}`; + + // Configure git user + if (options.userName && options.email) { + await git(['config', 'user.name', options.userName]); + await git(['config', 'user.email', options.email]); + } + + // Create and checkout new branch + await git(['checkout', '-b', newBranchName]); + + // Add and commit changes + await git(['add', '.']); + await git(['commit', '-m', title]); + + // Add remote and push + await git( + [ + 'remote', + 'add', + remoteRepoAlias, + `https://${options.userName}:${githubPAT}@github.com/dotnet/${targetRemoteRepo}.git`, + ], + false // Don't print command with PAT + ); + + await git(['fetch', remoteRepoAlias]); + + // Check if branch already exists on remote (refs/heads/) + const lsRemote = await git(['ls-remote', remoteRepoAlias, 'refs/heads/' + newBranchName]); + if (lsRemote.trim() !== '') { + console.log(`##vso[task.logissue type=error]${newBranchName} already exists in ${targetRemoteRepo}. Skip pushing.`); + return; + } + + if (!dryRun) { + // Push the newly created branch to the target remote + await git(['push', '-u', remoteRepoAlias, newBranchName]); + } else { + console.log('[DRY RUN] Would have pushed branch to remote'); + } + + const octokit = new Octokit({ auth: githubPAT }); + + // Check for existing PRs with same title + const listPullRequest = await octokit.rest.pulls.list({ + owner: 'dotnet', + repo: targetRemoteRepo, + }); + + if (listPullRequest.status !== 200) { + throw `Failed to get response from GitHub, http status code: ${listPullRequest.status}`; + } + + if (listPullRequest.data.some(pr => pr.title === title)) { + console.log('Pull request with the same name already exists. Skip creation.'); + return; + } + + if (!dryRun) { + const pullRequest = await octokit.rest.pulls.create({ + body: body || title, + owner: 'dotnet', + repo: targetRemoteRepo, + title: title, + head: newBranchName, + base: baseBranch, + }); + console.log(`Created pull request: ${pullRequest.data.html_url}.`); + } else { + console.log(`[DRY RUN] Would have created PR with title: ${title}`); + } +} diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index 0c74e9dfa0..cf5758f723 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -6,46 +6,16 @@ import * as gulp from 'gulp'; import * as fs from 'fs'; import * as path from 'path'; -import * as os from 'os'; import minimist from 'minimist'; -import { exec, spawnSync} from 'child_process'; +import { exec } from 'child_process'; import { promisify } from 'util'; import * as xml2js from 'xml2js'; -import { Octokit } from '@octokit/rest'; -import { allNugetPackages, NugetPackageInfo, platformSpecificPackages } from './offlinePackagingTasks'; -import { PlatformInformation } from '../src/shared/platform'; +import { allNugetPackages} from './offlinePackagingTasks'; +import {getCommitFromNugetAsync, logWarning, logError, createBranchAndPR } from './gitHelpers'; +import * as os from 'os'; const execAsync = promisify(exec); -function logWarning(message: string): void { - console.log(`##vso[task.logissue type=warning]${message}`); -} - -function logError(message: string): void { - console.log(`##vso[task.logissue type=error]${message}`); -} - -async function git(args: string[], printCommand = true): Promise { - if (printCommand) { - console.log(`git ${args.join(' ')}`); - } - - const git = spawnSync('git', args); - if (git.status != 0) { - const err = git.stderr.toString(); - if (printCommand) { - console.log(`Failed to execute git ${args.join(' ')}.`); - } - throw err; - } - - const stdout = git.stdout.toString(); - if (printCommand) { - console.log(stdout); - } - return stdout; -} - interface InsertionOptions { roslynVersion?: string; roslynEndSHA?: string; @@ -62,7 +32,6 @@ gulp.task('insertion:roslyn', async (): Promise => { const options = minimist(process.argv.slice(2)); console.log('Starting Roslyn insertion process...'); - console.log(`Options: ${JSON.stringify(options, null, 2)}`); try { // Step 1: Extract Roslyn version from AssetManifest @@ -106,12 +75,30 @@ gulp.task('insertion:roslyn', async (): Promise => { const prList = await generatePRList(currentSHA, options.roslynEndSHA, options.roslynRepoPath, options); console.log('PR List generated:', prList); + // Check if PR list is empty or contains no meaningful PRs + if (!prList || prList === '(no PRs with required labels)') { + console.log('No PRs with required labels found. Skipping insertion.'); + logWarning('No PRs with VSCode label found between the commits. Skipping insertion.'); + return; + } + // Step 6: Update files await updatePackageJson(options.roslynVersion); await updateChangelog(options.roslynVersion, prList, options.roslynBuildNumber, options.roslynBuildId); - // Step 6: Create branch and PR - await createBranchAndPR(options.roslynVersion, options.roslynEndSHA, options); + // Step 7: Create branch and PR + const prTitle = `Bump Roslyn to ${options.roslynVersion} (${options.roslynEndSHA?.substring(0, 8)})`; + const prBody = `This PR updates Roslyn to version ${options.roslynVersion} (${options.roslynEndSHA}).\n\n${prList}`; + + await createBranchAndPR({ + ...options, + commitSha: options.roslynEndSHA!, + targetRemoteRepo: 'vscode-csharp', + baseBranch: options.targetBranch || 'main', + branchPrefix: 'insertion', + githubPAT: options.githubPAT!, + dryRun: options.dryRun + }, prTitle, prBody); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); @@ -135,7 +122,6 @@ async function extractRoslynVersionFromManifest(manifestPath: string): Promise { - const packageJsonString = fs.readFileSync('./package.json').toString(); - const packageJson = JSON.parse(packageJsonString); - const packageVersion = packageJson['defaults'][packageInfo.packageJsonName]; - if (!packageVersion) { - logError(`Can't find ${packageInfo.packageJsonName} version in package.json`); - return null; - } - - const platform = await PlatformInformation.GetCurrent(); - const vsixPlatformInfo = platformSpecificPackages.find( - (p) => p.platformInfo.platform === platform.platform && p.platformInfo.architecture === platform.architecture - )!; - - const packageName = packageInfo.getPackageName(vsixPlatformInfo); - console.log(`${packageName} version is ${packageVersion}`); - - // Nuget package should exist under out/.nuget/ since we have run the install dependencies task. - // Package names are always lower case in the .nuget folder. - const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), packageVersion); - const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); - - if (nuspecFiles.length === 0) { - logError(`No .nuspec file found in ${packageDir}`); - return null; - } - - if (nuspecFiles.length > 1) { - logError(`Multiple .nuspec files found in ${packageDir}`); - return null; - } - - const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); - const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); - const results = /commit="(.*)"/.exec(nuspecFile); - if (results == null || results.length == 0) { - logError('Failed to find commit number from nuspec file'); - return null; - } - - if (results.length != 2) { - logError('Unexpected regex match result from nuspec file.'); - return null; - } - - const commitNumber = results[1]; - console.log(`commitNumber is ${commitNumber}`); - return commitNumber; -} - async function verifyRoslynRepo(roslynRepoPath: string): Promise { if (!fs.existsSync(roslynRepoPath)) { throw new Error(`Roslyn repository not found at ${roslynRepoPath}`); @@ -252,136 +188,155 @@ async function updatePackageJson(newVersion: string): Promise { } async function updateChangelog(version: string, prList: string, buildNumber?: string, buildId?: string): Promise { - console.log('Updating CHANGELOG.md...'); - const changelogPath = 'CHANGELOG.md'; - let changelogContent = fs.readFileSync(changelogPath, 'utf8'); - - // Format PR list with proper indentation - const formattedPRList = prList.split('\n').map(line => ` ${line}`).join(os.EOL); - - // Find and update the Roslyn bump line - const lines = changelogContent.split(/\r?\n/); - const newLines: string[] = []; - let skipNext = false; - - for (let i = 0; i < lines.length; i++) { - if (skipNext && lines[i].startsWith(' *')) { - continue; - } - skipNext = false; - - if (lines[i].match(/^\* Bump Roslyn to/)) { - const prLink = buildNumber && buildId - ? `[${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` - : '[#TBD](TBD)'; - newLines.push(`* Bump Roslyn to ${version} (PR: ${prLink})`); - if (prList !== '(no PRs with required labels)') { - newLines.push(formattedPRList); - } - skipNext = true; - } else { - newLines.push(lines[i]); - } - } - - fs.writeFileSync(changelogPath, newLines.join('\n')); -} + console.log('Updating CHANGELOG.md...'); + const changelogPath = 'CHANGELOG.md'; + const orig = fs.readFileSync(changelogPath, 'utf8'); -async function createBranchAndPR(version: string, endSHA: string, options: InsertionOptions): Promise { - try { - console.log('Creating and pushing branch...'); - - // Configure git user - await git(['config', '--local', 'user.name', 'Azure Pipelines']); - await git(['config', '--local', 'user.email', 'azuredevops@microsoft.com']); - - // Check for changes - const changedFiles = await git(['diff', '--name-only', 'HEAD']); - if (!changedFiles) { - console.log('No files changed, skipping branch creation'); - return; - } - - console.log(`Changed files: ${changedFiles}`); - - // Create and checkout new branch - const shortSHA = endSHA.substring(0, 8); - const branchName = `insertion/${shortSHA}`; - await git(['checkout', '-b', branchName]); - - // Stage and commit changes - await git(['add', 'package.json', 'CHANGELOG.md']); - await git(['commit', '-m', `Bump Roslyn to ${version} (${shortSHA})`]); - - // Configure remote with PAT for authentication - const pat = options.githubPAT; - if (!pat) { - throw new Error('GitHub PAT is required to push changes'); - } - - const remoteRepoAlias = 'targetRepo'; - await git( - [ - 'remote', - 'add', - remoteRepoAlias, - `https://${pat}@github.com/dotnet/vscode-csharp.git`, - ], - false // Don't log the command to avoid exposing the PAT - ); - - // Check if branch already exists - const lsRemote = await git(['ls-remote', '--heads', remoteRepoAlias, branchName]); - if (lsRemote.trim() !== '') { - console.log(`##vso[task.logissue type=warning]${branchName} already exists. Skipping push.`); - return; - } - - // Push the branch - await git(['push', '-u', remoteRepoAlias, branchName]); - - // Create PR if not in dry run mode - if (options.dryRun) { - console.log('Dry run: Would create PR for branch', branchName); - return; - } - - console.log('Creating pull request...'); - const octokit = new Octokit({ auth: pat }); - const title = `Bump Roslyn to ${version} (${shortSHA})`; - - // Check if PR already exists - const listPullRequest = await octokit.rest.pulls.list({ - owner: 'dotnet', - repo: 'vscode-csharp', - head: `dotnet:${branchName}`, - state: 'open' - }); - - if (listPullRequest.data.length > 0) { - console.log(`Pull request already exists: ${listPullRequest.data[0].html_url}`); - return; - } - - // Create the PR - const pr = await octokit.rest.pulls.create({ - owner: 'dotnet', - repo: 'vscode-csharp', - title: title, - head: branchName, - base: options.targetBranch || 'main', - body: `This is an automated PR to update the Roslyn version to ${version} (${shortSHA})`, - maintainer_can_modify: true - }); - - console.log(`Created pull request: ${pr.data.html_url}`); - - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logError(`Failed to create branch/PR: ${errorMessage}`); - if (error instanceof Error && error.stack) { - console.log(`##[debug]${error.stack}`); - } - throw error; - } + // Preserve original line endings + const originalHasCRLF = orig.indexOf('\r\n') !== -1; + const NL = os.EOL; + + // Normalize for processing + const text = orig.replace(/\r\n/g, NL); + + // Prepare PR list (filter out 'View Complete Diff' lines) + const formattedPRList = + prList + ? prList + .split(/\r?\n/) + .filter((l) => l.trim() && !l.includes('View Complete Diff')) + .map((line) => ` ${line}`) + .join(NL) + : ''; + + // Find the first top-level header "# ..." + const topHeaderRegex = /^# .*/m; + const headerMatch = topHeaderRegex.exec(text); + if (!headerMatch) { + const prLink = buildNumber && buildId + ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` + : '[#TBD](TBD)'; + let roslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; + if (formattedPRList && prList && prList !== '(no PRs with required labels)') { + roslynBlock += NL + formattedPRList; + } + roslynBlock += NL; + const out = originalHasCRLF ? (roslynBlock + text).replace(/\n/g, '\r\n') : roslynBlock + text; + fs.writeFileSync(changelogPath, out, 'utf8'); + console.log('CHANGELOG.md updated successfully'); + return; + } + + const headerStart = headerMatch.index; + const headerLineEnd = text.indexOf(NL, headerStart); + const headerLineEndIndex = headerLineEnd === -1 ? text.length : headerLineEnd; + + // Find end of this header section (next top-level header or EOF) + const nextTopHeaderRegex = /^# .*/gm; + nextTopHeaderRegex.lastIndex = headerLineEndIndex + 1; + const nextHeaderMatch = nextTopHeaderRegex.exec(text); + const sectionEndIndex = nextHeaderMatch ? nextHeaderMatch.index : text.length; + + const body = text.substring(headerLineEndIndex + 1, sectionEndIndex); + + // Split body into leading content + top-level bullet blocks (each starts with "* ") + const bulletStartRegex = /^\* /gm; + const starts: number[] = []; + let m: RegExpExecArray | null; + while ((m = bulletStartRegex.exec(body)) !== null) { + starts.push(m.index); + } + + let leading = ''; + let blocks: string[] = []; + if (starts.length === 0) { + leading = body; + blocks = []; + } else { + leading = body.slice(0, starts[0]); + for (let i = 0; i < starts.length; i++) { + const s = starts[i]; + const e = i + 1 < starts.length ? starts[i + 1] : body.length; + blocks.push(body.slice(s, e)); + } + } + + // Locate Roslyn and Razor blocks + let roslynIndex = -1; + let razorIndex = -1; + for (let i = 0; i < blocks.length; i++) { + const firstLine = blocks[i].split(NL, 1)[0].trim(); + if (/^\*\s*Bump\s+Roslyn\s+to/i.test(firstLine)) { + roslynIndex = i; + } else if (/^\*\s*Bump\s+Razor\s+to/i.test(firstLine)) { + razorIndex = i; + } + } + + // Prepare new Roslyn block + optional PR list + const prLink = buildNumber && buildId + ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` + : '[#TBD](TBD)'; + let newRoslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; + if (formattedPRList && prList && prList !== '(no PRs with required labels)') { + newRoslynBlock += NL + formattedPRList; + } + if (!newRoslynBlock.endsWith(NL)) { + newRoslynBlock += NL; + } + + // Rebuild blocks according to the rules + const newBlocks: string[] = []; + let roslynInserted = false; + + for (let i = 0; i < blocks.length; i++) { + if (i === roslynIndex) { + // Update version and PR link in place (preserving any existing bullet contents) + let updated = blocks[i].replace( + /^(\* Bump Roslyn to\s+)([^\s(]+)(?:\s*\(PR:[^)]+\))?/i, + `$1${version} (PR: ${prLink})` + ); + + // If we have a PR list and the existing block has no indented PR items, append it + if (formattedPRList && prList && prList !== '(no PRs with required labels)') { + if (!/\n\s{2}\*/.test(blocks[i]) && !updated.includes(formattedPRList)) { + if (!updated.endsWith(NL)) updated += NL; + updated += formattedPRList + NL; + } + } + + newBlocks.push(updated); + roslynInserted = true; + } else if (i === razorIndex) { + // Insert Roslyn before Razor if not already inserted + if (!roslynInserted) { + newBlocks.push(newRoslynBlock); + roslynInserted = true; + } + newBlocks.push(blocks[i]); + } else { + newBlocks.push(blocks[i]); + } + } + + // If Roslyn not inserted yet, place it at the top of the first section (after leading) + if (!roslynInserted) { + if (newBlocks.length > 0) { + newBlocks.unshift(newRoslynBlock); + } else { + // No bullet blocks found; append into leading + leading = leading + newRoslynBlock; + } + } + + // Assemble new body and full text + const rebuiltBlocks = newBlocks.join(NL); + const newBody = leading + rebuiltBlocks; + const newTextNormalized = text.slice(0, headerLineEndIndex + 1) + newBody + text.slice(sectionEndIndex); + + // Restore original newline style (normalize any newline to the original style) + const finalText = newTextNormalized.replace(/\r\n|\r|\n/g, originalHasCRLF ? '\r\n' : '\n'); + fs.writeFileSync(changelogPath, finalText, 'utf8'); + + console.log('CHANGELOG.md updated successfully'); } From 94aea42ea8ced535bce7bbb0a6cdd5c30174210a Mon Sep 17 00:00:00 2001 From: Azure Pipelines Date: Tue, 2 Sep 2025 19:14:32 +0530 Subject: [PATCH 08/17] improved changelog updation logic --- tasks/insertionTasks.ts | 42 ++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index cf5758f723..7e2214f32a 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -200,19 +200,21 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st const text = orig.replace(/\r\n/g, NL); // Prepare PR list (filter out 'View Complete Diff' lines) - const formattedPRList = - prList - ? prList - .split(/\r?\n/) - .filter((l) => l.trim() && !l.includes('View Complete Diff')) - .map((line) => ` ${line}`) - .join(NL) - : ''; + // Normalize each PR line (trim) so we don't accidentally double-indent when inserting. + const prLines = prList + ? prList + .split(/\r?\n/) + .filter((l) => l.trim() && !l.includes('View Complete Diff')) + .map((line) => line.trim()) + : []; + + const formattedPRList = prLines.length > 0 ? prLines.map((line) => ` ${line}`).join(NL) : ''; // Find the first top-level header "# ..." const topHeaderRegex = /^# .*/m; const headerMatch = topHeaderRegex.exec(text); if (!headerMatch) { + // No top-level header; prepend a Roslyn bump const prLink = buildNumber && buildId ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` : '[#TBD](TBD)'; @@ -297,11 +299,25 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st `$1${version} (PR: ${prLink})` ); - // If we have a PR list and the existing block has no indented PR items, append it - if (formattedPRList && prList && prList !== '(no PRs with required labels)') { - if (!/\n\s{2}\*/.test(blocks[i]) && !updated.includes(formattedPRList)) { - if (!updated.endsWith(NL)) updated += NL; - updated += formattedPRList + NL; + // If we have a PR list and the existing block does not already contain it, insert it before existing PR bullets. + if (prLines.length > 0 && prList && prList !== '(no PRs with required labels)') { + const firstPR = prLines[0]; + + // If the first PR is not already present in the block, insert our formatted PR list. + if (!updated.includes(firstPR)) { + // Try to locate the first existing PR bullet (lines like "\n * ...") + const prBulletIndex = updated.indexOf(NL + ' * '); + if (prBulletIndex !== -1) { + // Insert formatted PR list before the first existing PR bullet. + const insertPos = prBulletIndex + NL.length; // position just before the bullet line + const prefix = updated.slice(0, insertPos); + const suffix = updated.slice(insertPos); + updated = prefix + formattedPRList + NL + suffix; + } else { + // If no existing bullets found, append as before. + if (!updated.endsWith(NL)) updated += NL; + updated += formattedPRList + NL; + } } } From 4ff9705f90edaae0f0d1ce849f5a0f9a5047e217 Mon Sep 17 00:00:00 2001 From: Azure Pipelines Date: Tue, 2 Sep 2025 19:43:44 +0530 Subject: [PATCH 09/17] downloading latest roslyn-tools --- azure-pipelines/dotnet-vscode-csharp-insertion.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/azure-pipelines/dotnet-vscode-csharp-insertion.yml b/azure-pipelines/dotnet-vscode-csharp-insertion.yml index a26969a3e0..18f450f992 100644 --- a/azure-pipelines/dotnet-vscode-csharp-insertion.yml +++ b/azure-pipelines/dotnet-vscode-csharp-insertion.yml @@ -82,6 +82,14 @@ extends: git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git $(Pipeline.Workspace)/roslyn displayName: 'Clone Roslyn repository' + - script: | + echo "Installing roslyn-tools..." + dotnet tool install -g Microsoft.RoslynTools --prerelease --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json + echo "Verifying roslyn-tools installation..." + dotnet tool list -g + roslyn-tools --help + displayName: 'Install roslyn-tools CLI' + - task: Npm@1 displayName: Run Roslyn insertion env: From 94c8d620050321d273752cc6fc87b1ec1dadb8a5 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Thu, 4 Sep 2025 21:37:21 +0530 Subject: [PATCH 10/17] Pr reviews resolved --- .../dotnet-vscode-csharp-insertion.yml | 16 +- tasks/gitHelpers.ts | 27 +- tasks/insertionTasks.ts | 273 +++++------------- tasks/localizationTasks.ts | 81 ++---- 4 files changed, 119 insertions(+), 278 deletions(-) diff --git a/azure-pipelines/dotnet-vscode-csharp-insertion.yml b/azure-pipelines/dotnet-vscode-csharp-insertion.yml index 18f450f992..b84c64c637 100644 --- a/azure-pipelines/dotnet-vscode-csharp-insertion.yml +++ b/azure-pipelines/dotnet-vscode-csharp-insertion.yml @@ -27,13 +27,14 @@ variables: value: $(resources.pipeline.officialBuildCI.runName) - name: RoslynBuildId value: $(resources.pipeline.officialBuildCI.runID) + - template: dotnet-variables.yml extends: template: v1/1ES.Unofficial.PipelineTemplate.yml@1ESPipelineTemplates parameters: pool: - name: AzurePipelines-EO - image: AzurePipelinesUbuntu22.04compliant + name: netcore1espool-internal + image: 1es-ubuntu-2204 os: linux stages: - stage: BumpRoslyn @@ -42,8 +43,8 @@ extends: - job: ProcessBump displayName: Process Roslyn Bump pool: - name: AzurePipelines-EO - image: AzurePipelinesUbuntu22.04compliant + name: netcore1espool-internal + image: 1es-ubuntu-2204 os: linux steps: - checkout: self @@ -52,12 +53,9 @@ extends: - task: UseDotNet@2 displayName: Install .NET SDK inputs: - version: 9.0.x + version: $(defaultDotnetVersion) - - task: NodeTool@0 - displayName: Install Node.js - inputs: - versionSpec: '20.x' + - template: install-node.yml - task: DownloadPipelineArtifact@2 displayName: Download Asset Manifests diff --git a/tasks/gitHelpers.ts b/tasks/gitHelpers.ts index a7c20c9ba0..b76dffc753 100644 --- a/tasks/gitHelpers.ts +++ b/tasks/gitHelpers.ts @@ -12,8 +12,6 @@ import { PlatformInformation } from '../src/shared/platform'; import { platformSpecificPackages } from './offlinePackagingTasks'; export interface GitOptions { - userName?: string; - email?: string; commitSha: string; targetRemoteRepo: string; baseBranch: string; @@ -21,8 +19,10 @@ export interface GitOptions { export interface BranchAndPROptions extends GitOptions { githubPAT: string; - dryRun?: boolean; - branchPrefix?: string; // optional prefix for branch names (defaults to 'localization') + dryRun: boolean; + newBranchName: string; + userName?: string; + email?: string; } // Logging utilities @@ -116,17 +116,18 @@ export async function getCommitFromNugetAsync(packageInfo: NugetPackageInfo): Pr export async function createBranchAndPR( options: BranchAndPROptions, title: string, + commitMessage: string, body?: string ): Promise { - const { githubPAT, targetRemoteRepo, baseBranch, dryRun } = options; + const { githubPAT, targetRemoteRepo, baseBranch, dryRun, userName, email, newBranchName } = options; const remoteRepoAlias = 'target'; - const branchPrefix = options.branchPrefix || 'localization'; - const newBranchName = `${branchPrefix}/${options.commitSha}`; - // Configure git user - if (options.userName && options.email) { - await git(['config', 'user.name', options.userName]); - await git(['config', 'user.email', options.email]); + // Set git user configuration if provided in options + if (userName) { + await git(['config', '--local', 'user.name', userName]); + } + if (email) { + await git(['config', '--local', 'user.email', email]); } // Create and checkout new branch @@ -134,7 +135,7 @@ export async function createBranchAndPR( // Add and commit changes await git(['add', '.']); - await git(['commit', '-m', title]); + await git(['commit', '-m', commitMessage]); // Add remote and push await git( @@ -191,6 +192,6 @@ export async function createBranchAndPR( }); console.log(`Created pull request: ${pullRequest.data.html_url}.`); } else { - console.log(`[DRY RUN] Would have created PR with title: ${title}`); + console.log(`[DRY RUN] Would have created PR with title: "${title}" and body: "${body || title}"`); } } diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index 7e2214f32a..c40fa7ebe5 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -30,76 +30,79 @@ interface InsertionOptions { gulp.task('insertion:roslyn', async (): Promise => { const options = minimist(process.argv.slice(2)); - + console.log('Starting Roslyn insertion process...'); - + try { // Step 1: Extract Roslyn version from AssetManifest if (!options.assetManifestPath) { throw new Error('assetManifestPath is required'); } - + const newVersion = await extractRoslynVersionFromManifest(options.assetManifestPath); if (!newVersion) { throw new Error('Failed to extract Roslyn version from asset manifest'); } options.roslynVersion = newVersion; console.log(`New Roslyn version: ${newVersion}`); - + // Step 2: Get current SHA from package const currentSHA = await getCommitFromNugetAsync(allNugetPackages.roslyn); if (!currentSHA) { throw new Error('Could not determine current Roslyn SHA from package'); } console.log(`Current Roslyn SHA: ${currentSHA}`); - + // Step 3: Check if update needed if (!options.roslynEndSHA) { throw new Error('roslynEndSHA is required'); } - + if (currentSHA === options.roslynEndSHA) { console.log('No new commits to process - versions are identical'); return; } - + console.log(`Update needed: ${currentSHA}..${options.roslynEndSHA}`); - + // Step 4: Verify Roslyn repo exists if (!options.roslynRepoPath) { throw new Error('roslynRepoPath is required'); } await verifyRoslynRepo(options.roslynRepoPath); - + // Step 5: Generate PR list const prList = await generatePRList(currentSHA, options.roslynEndSHA, options.roslynRepoPath, options); console.log('PR List generated:', prList); - + // Check if PR list is empty or contains no meaningful PRs if (!prList || prList === '(no PRs with required labels)') { console.log('No PRs with required labels found. Skipping insertion.'); logWarning('No PRs with VSCode label found between the commits. Skipping insertion.'); return; } - + // Step 6: Update files await updatePackageJson(options.roslynVersion); await updateChangelog(options.roslynVersion, prList, options.roslynBuildNumber, options.roslynBuildId); - + // Step 7: Create branch and PR const prTitle = `Bump Roslyn to ${options.roslynVersion} (${options.roslynEndSHA?.substring(0, 8)})`; - const prBody = `This PR updates Roslyn to version ${options.roslynVersion} (${options.roslynEndSHA}).\n\n${prList}`; - + const prBody = `This PR updates Roslyn to version ${options.roslynVersion} (${options.roslynEndSha}).\n\n${prList}`; + const commitMessage = `Bump Roslyn to ${options.roslynVersion} (${options.roslynEndSha?.substring(0, 8)})`; + await createBranchAndPR({ ...options, commitSha: options.roslynEndSHA!, targetRemoteRepo: 'vscode-csharp', baseBranch: options.targetBranch || 'main', - branchPrefix: 'insertion', + newBranchName: `insertion/${options.roslynEndSHA}`, githubPAT: options.githubPAT!, - dryRun: options.dryRun - }, prTitle, prBody); - + dryRun: options.dryRun, + userName: options.userName, + email: options.email + }, prTitle, commitMessage, prBody); + } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logError(`Insertion failed: ${errorMessage}`); @@ -112,16 +115,16 @@ gulp.task('insertion:roslyn', async (): Promise => { async function extractRoslynVersionFromManifest(manifestPath: string): Promise { const xmlFile = path.join(manifestPath, 'OfficialBuild.xml'); - + if (!fs.existsSync(xmlFile)) { logError(`OfficialBuild.xml not found at ${xmlFile}`); return null; } - + const xmlContent = fs.readFileSync(xmlFile, 'utf8'); const parser = new xml2js.Parser(); const result = await parser.parseStringPromise(xmlContent); - + const packages = result?.Build?.Package || []; for (const pkg of packages) { const attrs = pkg.$; @@ -129,7 +132,7 @@ async function extractRoslynVersionFromManifest(manifestPath: string): Promise { async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: string, options: InsertionOptions): Promise { console.log(`Generating PR list from ${startSHA} to ${endSHA}...`); - + // Setup auth for roslyn-tools const homeDir = process.env.HOME || process.env.USERPROFILE; const settingsDir = path.join(homeDir!, '.roslyn-tools'); if (!fs.existsSync(settingsDir)) { fs.mkdirSync(settingsDir, { recursive: true }); } - + const authJson = { GitHubToken: options.githubPAT || '', DevDivAzureDevOpsToken: '', @@ -157,20 +160,20 @@ async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: }; const settingsFile = path.join(settingsDir, 'settings'); fs.writeFileSync(settingsFile, Buffer.from(JSON.stringify(authJson)).toString('base64')); - + try { const { stdout } = await execAsync( `cd "${roslynRepoPath}" && roslyn-tools pr-finder -s "${startSHA}" -e "${endSHA}" --format changelog --label VSCode`, { maxBuffer: 10 * 1024 * 1024 } // 10MB buffer ); - return stdout || '(no PRs with required labels)'; + return stdout || '(failed to generate PR list, see pipeline for details)'; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logWarning(`PR finder failed, using empty list: ${errorMessage}`); if (error instanceof Error && error.stack) { console.log(`##[debug]${error.stack}`); } - return '(no PRs with required labels)'; + return '(failed to generate PR list, see pipeline for details)'; } } @@ -183,176 +186,56 @@ async function updatePackageJson(newVersion: string): Promise { throw new Error('Could not find defaults section in package.json'); } packageJson.defaults.roslyn = newVersion; - + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); } async function updateChangelog(version: string, prList: string, buildNumber?: string, buildId?: string): Promise { - console.log('Updating CHANGELOG.md...'); - const changelogPath = 'CHANGELOG.md'; - const orig = fs.readFileSync(changelogPath, 'utf8'); - - // Preserve original line endings - const originalHasCRLF = orig.indexOf('\r\n') !== -1; - const NL = os.EOL; - - // Normalize for processing - const text = orig.replace(/\r\n/g, NL); - - // Prepare PR list (filter out 'View Complete Diff' lines) - // Normalize each PR line (trim) so we don't accidentally double-indent when inserting. - const prLines = prList - ? prList - .split(/\r?\n/) - .filter((l) => l.trim() && !l.includes('View Complete Diff')) - .map((line) => line.trim()) - : []; - - const formattedPRList = prLines.length > 0 ? prLines.map((line) => ` ${line}`).join(NL) : ''; - - // Find the first top-level header "# ..." - const topHeaderRegex = /^# .*/m; - const headerMatch = topHeaderRegex.exec(text); - if (!headerMatch) { - // No top-level header; prepend a Roslyn bump - const prLink = buildNumber && buildId - ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` - : '[#TBD](TBD)'; - let roslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; - if (formattedPRList && prList && prList !== '(no PRs with required labels)') { - roslynBlock += NL + formattedPRList; - } - roslynBlock += NL; - const out = originalHasCRLF ? (roslynBlock + text).replace(/\n/g, '\r\n') : roslynBlock + text; - fs.writeFileSync(changelogPath, out, 'utf8'); - console.log('CHANGELOG.md updated successfully'); - return; - } - - const headerStart = headerMatch.index; - const headerLineEnd = text.indexOf(NL, headerStart); - const headerLineEndIndex = headerLineEnd === -1 ? text.length : headerLineEnd; - - // Find end of this header section (next top-level header or EOF) - const nextTopHeaderRegex = /^# .*/gm; - nextTopHeaderRegex.lastIndex = headerLineEndIndex + 1; - const nextHeaderMatch = nextTopHeaderRegex.exec(text); - const sectionEndIndex = nextHeaderMatch ? nextHeaderMatch.index : text.length; - - const body = text.substring(headerLineEndIndex + 1, sectionEndIndex); - - // Split body into leading content + top-level bullet blocks (each starts with "* ") - const bulletStartRegex = /^\* /gm; - const starts: number[] = []; - let m: RegExpExecArray | null; - while ((m = bulletStartRegex.exec(body)) !== null) { - starts.push(m.index); - } - - let leading = ''; - let blocks: string[] = []; - if (starts.length === 0) { - leading = body; - blocks = []; - } else { - leading = body.slice(0, starts[0]); - for (let i = 0; i < starts.length; i++) { - const s = starts[i]; - const e = i + 1 < starts.length ? starts[i + 1] : body.length; - blocks.push(body.slice(s, e)); - } - } - - // Locate Roslyn and Razor blocks - let roslynIndex = -1; - let razorIndex = -1; - for (let i = 0; i < blocks.length; i++) { - const firstLine = blocks[i].split(NL, 1)[0].trim(); - if (/^\*\s*Bump\s+Roslyn\s+to/i.test(firstLine)) { - roslynIndex = i; - } else if (/^\*\s*Bump\s+Razor\s+to/i.test(firstLine)) { - razorIndex = i; - } - } - - // Prepare new Roslyn block + optional PR list - const prLink = buildNumber && buildId - ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` - : '[#TBD](TBD)'; - let newRoslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; - if (formattedPRList && prList && prList !== '(no PRs with required labels)') { - newRoslynBlock += NL + formattedPRList; - } - if (!newRoslynBlock.endsWith(NL)) { - newRoslynBlock += NL; - } - - // Rebuild blocks according to the rules - const newBlocks: string[] = []; - let roslynInserted = false; - - for (let i = 0; i < blocks.length; i++) { - if (i === roslynIndex) { - // Update version and PR link in place (preserving any existing bullet contents) - let updated = blocks[i].replace( - /^(\* Bump Roslyn to\s+)([^\s(]+)(?:\s*\(PR:[^)]+\))?/i, - `$1${version} (PR: ${prLink})` - ); - - // If we have a PR list and the existing block does not already contain it, insert it before existing PR bullets. - if (prLines.length > 0 && prList && prList !== '(no PRs with required labels)') { - const firstPR = prLines[0]; - - // If the first PR is not already present in the block, insert our formatted PR list. - if (!updated.includes(firstPR)) { - // Try to locate the first existing PR bullet (lines like "\n * ...") - const prBulletIndex = updated.indexOf(NL + ' * '); - if (prBulletIndex !== -1) { - // Insert formatted PR list before the first existing PR bullet. - const insertPos = prBulletIndex + NL.length; // position just before the bullet line - const prefix = updated.slice(0, insertPos); - const suffix = updated.slice(insertPos); - updated = prefix + formattedPRList + NL + suffix; - } else { - // If no existing bullets found, append as before. - if (!updated.endsWith(NL)) updated += NL; - updated += formattedPRList + NL; - } - } - } - - newBlocks.push(updated); - roslynInserted = true; - } else if (i === razorIndex) { - // Insert Roslyn before Razor if not already inserted - if (!roslynInserted) { - newBlocks.push(newRoslynBlock); - roslynInserted = true; - } - newBlocks.push(blocks[i]); - } else { - newBlocks.push(blocks[i]); - } - } - - // If Roslyn not inserted yet, place it at the top of the first section (after leading) - if (!roslynInserted) { - if (newBlocks.length > 0) { - newBlocks.unshift(newRoslynBlock); - } else { - // No bullet blocks found; append into leading - leading = leading + newRoslynBlock; - } - } - - // Assemble new body and full text - const rebuiltBlocks = newBlocks.join(NL); - const newBody = leading + rebuiltBlocks; - const newTextNormalized = text.slice(0, headerLineEndIndex + 1) + newBody + text.slice(sectionEndIndex); - - // Restore original newline style (normalize any newline to the original style) - const finalText = newTextNormalized.replace(/\r\n|\r|\n/g, originalHasCRLF ? '\r\n' : '\n'); - fs.writeFileSync(changelogPath, finalText, 'utf8'); - - console.log('CHANGELOG.md updated successfully'); + console.log('Updating CHANGELOG.md...'); + const changelogPath = 'CHANGELOG.md'; + const text = fs.readFileSync(changelogPath, 'utf8'); + const NL = os.EOL; + + // Prepare PR list (filter out 'View Complete Diff' lines) + const prLines = prList + ? prList + .split(/\r?\n/) + .filter((l) => l.trim() && !l.includes('View Complete Diff')) + .map((line) => line.trim()) + : []; + + const formattedPRList = prLines.length > 0 ? prLines.map((line) => ` ${line}`).join(NL) : ''; + + // Find the first top-level header "# ..." + const topHeaderRegex = /^(# .*?)(\r?\n|$)/m; + const headerMatch = topHeaderRegex.exec(text); + if (!headerMatch) { + throw new Error('CHANGELOG.md must contain at least one top-level header (#)'); + } + + const headerEndLineIndex = headerMatch.index + headerMatch[0].length; + + // Prepare new Roslyn block + const prLink = buildNumber && buildId + ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` + : '[#TBD](TBD)'; + + let newRoslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; + const shouldSkipPRList = prList === '(failed to generate PR list, see pipeline for details)'; + + if (!shouldSkipPRList && formattedPRList) { + newRoslynBlock += NL + formattedPRList; + } + newRoslynBlock += NL; // Ensure there's always a newline at the end + + // Insert the new block right after the header + const newText = + text.slice(0, headerEndLineIndex) + + NL + NL + // Add two newlines after the header + newRoslynBlock + + (text.length > headerEndLineIndex ? NL + text.slice(headerEndLineIndex) : ''); + + // Write the updated content back to the file + fs.writeFileSync(changelogPath, newText, 'utf8'); + console.log('CHANGELOG.md updated successfully'); } diff --git a/tasks/localizationTasks.ts b/tasks/localizationTasks.ts index bdb3d13c67..4fae9f9216 100644 --- a/tasks/localizationTasks.ts +++ b/tasks/localizationTasks.ts @@ -10,7 +10,7 @@ import { spawnSync } from 'node:child_process'; import * as path from 'path'; import * as util from 'node:util'; import { EOL } from 'node:os'; -import { Octokit } from '@octokit/rest'; +import { createBranchAndPR } from './gitHelpers'; type Options = { userName?: string; @@ -76,70 +76,29 @@ gulp.task('publish localization content', async () => { } console.log(`Changed files going to be staged: ${diff}`); - const newBranchName = `localization/${parsedArgs.commitSha}`; - // Make this optional so it can be tested locally by using dev's information. In real CI user name and email are always supplied. - if (parsedArgs.userName) { - await git(['config', '--local', 'user.name', parsedArgs.userName]); - } - if (parsedArgs.email) { - await git(['config', '--local', 'user.email', parsedArgs.email]); - } - - await git(['checkout', '-b', newBranchName]); - await git(['commit', '-m', `Localization result of ${parsedArgs.commitSha}.`]); - + const title = `Localization result based on ${parsedArgs.commitSha}`; + const commitMessage = `Localization result of ${parsedArgs.commitSha}`; const pat = process.env['GitHubPAT']; if (!pat) { throw 'No GitHub Pat found.'; } - - const remoteRepoAlias = 'targetRepo'; - await git( - [ - 'remote', - 'add', - remoteRepoAlias, - `https://${parsedArgs.userName}:${pat}@github.com/dotnet/${parsedArgs.targetRemoteRepo}.git`, - ], - // Note: don't print PAT to console - false - ); - await git(['fetch', remoteRepoAlias]); - - const lsRemote = await git(['ls-remote', remoteRepoAlias, 'refs/head/' + newBranchName]); - if (lsRemote.trim() !== '') { - // If the localization branch of this commit already exists, don't try to create another one. - console.log( - `##vso[task.logissue type=error]${newBranchName} already exists in ${parsedArgs.targetRemoteRepo}. Skip pushing.` + try { + await createBranchAndPR( + { + commitSha: parsedArgs.commitSha, + targetRemoteRepo: parsedArgs.targetRemoteRepo, + baseBranch: parsedArgs.baseBranch, + githubPAT: process.env['GitHubPAT'] || '', + dryRun: false, + newBranchName: `localization/${parsedArgs.commitSha}`, + userName: parsedArgs.userName, + email: parsedArgs.email + }, + title, + commitMessage ); - } else { - await git(['push', '-u', remoteRepoAlias]); - } - - const octokit = new Octokit({ auth: pat }); - const listPullRequest = await octokit.rest.pulls.list({ - owner: 'dotnet', - repo: parsedArgs.targetRemoteRepo, - }); - - if (listPullRequest.status != 200) { - throw `Failed get response from GitHub, http status code: ${listPullRequest.status}`; - } - - const title = `Localization result based on ${parsedArgs.commitSha}`; - if (listPullRequest.data.some((pr) => pr.title === title)) { - console.log('Pull request with the same name already exists. Skip creation.'); - return; + } catch (error) { + console.error('Error creating branch and PR:', error); + throw error; } - - const pullRequest = await octokit.rest.pulls.create({ - body: title, - owner: 'dotnet', - repo: parsedArgs.targetRemoteRepo, - title: title, - head: newBranchName, - base: parsedArgs.baseBranch, - }); - - console.log(`Created pull request: ${pullRequest.data.html_url}.`); }); From d2ff303d4cf48d1df163cd85b8548e060efa68d1 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Thu, 4 Sep 2025 23:32:38 +0530 Subject: [PATCH 11/17] update: removed auth setup from roslyn-tools --- tasks/insertionTasks.ts | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index c40fa7ebe5..d67120f87d 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -25,7 +25,7 @@ interface InsertionOptions { roslynRepoPath?: string; targetBranch?: string; githubPAT?: string; - dryRun?: boolean; + dryRun: boolean; } gulp.task('insertion:roslyn', async (): Promise => { @@ -76,7 +76,7 @@ gulp.task('insertion:roslyn', async (): Promise => { console.log('PR List generated:', prList); // Check if PR list is empty or contains no meaningful PRs - if (!prList || prList === '(no PRs with required labels)') { + if (!prList || prList === '(failed to generate PR list, see pipeline for details)') { console.log('No PRs with required labels found. Skipping insertion.'); logWarning('No PRs with VSCode label found between the commits. Skipping insertion.'); return; @@ -146,21 +146,6 @@ async function verifyRoslynRepo(roslynRepoPath: string): Promise { async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: string, options: InsertionOptions): Promise { console.log(`Generating PR list from ${startSHA} to ${endSHA}...`); - // Setup auth for roslyn-tools - const homeDir = process.env.HOME || process.env.USERPROFILE; - const settingsDir = path.join(homeDir!, '.roslyn-tools'); - if (!fs.existsSync(settingsDir)) { - fs.mkdirSync(settingsDir, { recursive: true }); - } - - const authJson = { - GitHubToken: options.githubPAT || '', - DevDivAzureDevOpsToken: '', - DncEngAzureDevOpsToken: '' - }; - const settingsFile = path.join(settingsDir, 'settings'); - fs.writeFileSync(settingsFile, Buffer.from(JSON.stringify(authJson)).toString('base64')); - try { const { stdout } = await execAsync( `cd "${roslynRepoPath}" && roslyn-tools pr-finder -s "${startSHA}" -e "${endSHA}" --format changelog --label VSCode`, @@ -204,7 +189,14 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st .map((line) => line.trim()) : []; - const formattedPRList = prLines.length > 0 ? prLines.map((line) => ` ${line}`).join(NL) : ''; + // Format PR list as sub-items with proper indentation + const formattedPRList = prLines.length > 0 + ? prLines.map((line) => { + // If the line already starts with *, keep it, otherwise add it + const cleanLine = line.startsWith('*') ? line.substring(1).trim() : line; + return ` * ${cleanLine}`; + }).join(NL) + : ''; // Find the first top-level header "# ..." const topHeaderRegex = /^(# .*?)(\r?\n|$)/m; @@ -221,18 +213,20 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st : '[#TBD](TBD)'; let newRoslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; - const shouldSkipPRList = prList === '(failed to generate PR list, see pipeline for details)'; + + // Add PR list as sub-items if available and valid + const shouldSkipPRList = !prList || prList === '(failed to generate PR list, see pipeline for details)'; if (!shouldSkipPRList && formattedPRList) { newRoslynBlock += NL + formattedPRList; } - newRoslynBlock += NL; // Ensure there's always a newline at the end // Insert the new block right after the header const newText = text.slice(0, headerEndLineIndex) + NL + NL + // Add two newlines after the header newRoslynBlock + + NL + // Add a newline after the block (text.length > headerEndLineIndex ? NL + text.slice(headerEndLineIndex) : ''); // Write the updated content back to the file From 9d73b2ee47eb0fb5d9bc71efc9d004996ed295a1 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Mon, 8 Sep 2025 20:14:33 +0530 Subject: [PATCH 12/17] Resolved review comments --- .../dotnet-vscode-csharp-insertion.yml | 11 +- tasks/createTagsTasks.ts | 56 +--------- tasks/gitHelpers.ts | 15 +-- tasks/insertionTasks.ts | 105 +++++++++++------- 4 files changed, 81 insertions(+), 106 deletions(-) diff --git a/azure-pipelines/dotnet-vscode-csharp-insertion.yml b/azure-pipelines/dotnet-vscode-csharp-insertion.yml index b84c64c637..4779605a7d 100644 --- a/azure-pipelines/dotnet-vscode-csharp-insertion.yml +++ b/azure-pipelines/dotnet-vscode-csharp-insertion.yml @@ -13,6 +13,9 @@ resources: type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release + - repository: RoslynMirror + type: git + name: internal/dotnet-roslyn pipelines: - pipeline: officialBuildCI source: dotnet-roslyn-official @@ -74,11 +77,9 @@ extends: npm install -g gulp gulp installDependencies displayName: 'Install npm dependencies and gulp' - - - script: | - echo "Cloning Roslyn repository..." - git clone --no-tags --filter=blob:none --depth=500 https://github.com/dotnet/roslyn.git $(Pipeline.Workspace)/roslyn - displayName: 'Clone Roslyn repository' + - checkout: RoslynMirror + displayName: 'Checkout Roslyn repository' + path: roslyn - script: | echo "Installing roslyn-tools..." diff --git a/tasks/createTagsTasks.ts b/tasks/createTagsTasks.ts index a5589e9d74..e92bde07d4 100644 --- a/tasks/createTagsTasks.ts +++ b/tasks/createTagsTasks.ts @@ -4,12 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as gulp from 'gulp'; -import * as fs from 'fs'; import minimist from 'minimist'; import { Octokit } from '@octokit/rest'; -import { allNugetPackages, NugetPackageInfo, platformSpecificPackages } from './offlinePackagingTasks'; -import { PlatformInformation } from '../src/shared/platform'; -import path from 'path'; +import { allNugetPackages } from './offlinePackagingTasks'; +import { getCommitFromNugetAsync } from './gitHelpers'; interface CreateTagsOptions { releaseVersion: string; @@ -194,53 +192,3 @@ function logWarning(message: string): void { function logError(message: string): void { console.log(`##vso[task.logissue type=error]${message}`); } - -async function getCommitFromNugetAsync(packageInfo: NugetPackageInfo): Promise { - const packageJsonString = fs.readFileSync('./package.json').toString(); - const packageJson = JSON.parse(packageJsonString); - const packageVersion = packageJson['defaults'][packageInfo.packageJsonName]; - if (!packageVersion) { - logError(`Can't find ${packageInfo.packageJsonName} version in package.json`); - return null; - } - - const platform = await PlatformInformation.GetCurrent(); - const vsixPlatformInfo = platformSpecificPackages.find( - (p) => p.platformInfo.platform === platform.platform && p.platformInfo.architecture === platform.architecture - )!; - - const packageName = packageInfo.getPackageName(vsixPlatformInfo); - console.log(`${packageName} version is ${packageVersion}`); - - // Nuget package should exist under out/.nuget/ since we have run the install dependencies task. - // Package names are always lower case in the .nuget folder. - const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), packageVersion); - const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); - - if (nuspecFiles.length === 0) { - logError(`No .nuspec file found in ${packageDir}`); - return null; - } - - if (nuspecFiles.length > 1) { - logError(`Multiple .nuspec files found in ${packageDir}`); - return null; - } - - const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); - const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); - const results = /commit="(.*)"/.exec(nuspecFile); - if (results == null || results.length == 0) { - logError('Failed to find commit number from nuspec file'); - return null; - } - - if (results.length != 2) { - logError('Unexpected regex match result from nuspec file.'); - return null; - } - - const commitNumber = results[1]; - console.log(`commitNumber is ${commitNumber}`); - return commitNumber; -} diff --git a/tasks/gitHelpers.ts b/tasks/gitHelpers.ts index b76dffc753..874135febb 100644 --- a/tasks/gitHelpers.ts +++ b/tasks/gitHelpers.ts @@ -7,9 +7,8 @@ import { spawnSync } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import { Octokit } from '@octokit/rest'; -import { NugetPackageInfo } from './offlinePackagingTasks'; +import { NugetPackageInfo, platformSpecificPackages } from './offlinePackagingTasks'; import { PlatformInformation } from '../src/shared/platform'; -import { platformSpecificPackages } from './offlinePackagingTasks'; export interface GitOptions { commitSha: string; @@ -118,7 +117,7 @@ export async function createBranchAndPR( title: string, commitMessage: string, body?: string -): Promise { +): Promise { const { githubPAT, targetRemoteRepo, baseBranch, dryRun, userName, email, newBranchName } = options; const remoteRepoAlias = 'target'; @@ -154,10 +153,10 @@ export async function createBranchAndPR( const lsRemote = await git(['ls-remote', remoteRepoAlias, 'refs/heads/' + newBranchName]); if (lsRemote.trim() !== '') { console.log(`##vso[task.logissue type=error]${newBranchName} already exists in ${targetRemoteRepo}. Skip pushing.`); - return; + return null; } - if (!dryRun) { + if (dryRun !== true) { // Push the newly created branch to the target remote await git(['push', '-u', remoteRepoAlias, newBranchName]); } else { @@ -178,10 +177,10 @@ export async function createBranchAndPR( if (listPullRequest.data.some(pr => pr.title === title)) { console.log('Pull request with the same name already exists. Skip creation.'); - return; + return null; } - if (!dryRun) { + if (dryRun !== true) { const pullRequest = await octokit.rest.pulls.create({ body: body || title, owner: 'dotnet', @@ -191,7 +190,9 @@ export async function createBranchAndPR( base: baseBranch, }); console.log(`Created pull request: ${pullRequest.data.html_url}.`); + return pullRequest.data.number; } else { console.log(`[DRY RUN] Would have created PR with title: "${title}" and body: "${body || title}"`); + return null; } } diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index d67120f87d..87f1e371db 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -11,7 +11,7 @@ import { exec } from 'child_process'; import { promisify } from 'util'; import * as xml2js from 'xml2js'; import { allNugetPackages} from './offlinePackagingTasks'; -import {getCommitFromNugetAsync, logWarning, logError, createBranchAndPR } from './gitHelpers'; +import {getCommitFromNugetAsync, logWarning, logError, createBranchAndPR, git } from './gitHelpers'; import * as os from 'os'; const execAsync = promisify(exec); @@ -29,7 +29,12 @@ interface InsertionOptions { } gulp.task('insertion:roslyn', async (): Promise => { - const options = minimist(process.argv.slice(2)); + const options = minimist(process.argv.slice(2), { + boolean: ['dryRun'], + default: { + dryRun: false, + }, + }); console.log('Starting Roslyn insertion process...'); @@ -75,23 +80,34 @@ gulp.task('insertion:roslyn', async (): Promise => { const prList = await generatePRList(currentSHA, options.roslynEndSHA, options.roslynRepoPath, options); console.log('PR List generated:', prList); - // Check if PR list is empty or contains no meaningful PRs - if (!prList || prList === '(failed to generate PR list, see pipeline for details)') { - console.log('No PRs with required labels found. Skipping insertion.'); - logWarning('No PRs with VSCode label found between the commits. Skipping insertion.'); + // Check if PR list is null or empty (generation failed or no matching PRs) + if (!prList) { + console.log('No PRs with required labels found or PR list generation failed. Skipping insertion.'); + logWarning('No PRs with VSCode label found between the commits or PR list generation failed. Skipping insertion.'); return; } // Step 6: Update files await updatePackageJson(options.roslynVersion); - await updateChangelog(options.roslynVersion, prList, options.roslynBuildNumber, options.roslynBuildId); // Step 7: Create branch and PR const prTitle = `Bump Roslyn to ${options.roslynVersion} (${options.roslynEndSHA?.substring(0, 8)})`; - const prBody = `This PR updates Roslyn to version ${options.roslynVersion} (${options.roslynEndSha}).\n\n${prList}`; - const commitMessage = `Bump Roslyn to ${options.roslynVersion} (${options.roslynEndSha?.substring(0, 8)})`; - await createBranchAndPR({ + // Include build information in the PR description + let prBody = `This PR updates Roslyn to version ${options.roslynVersion} (${options.roslynEndSHA}).\n\n`; + + // Add build link if build information is available + if (options.roslynBuildNumber && options.roslynBuildId) { + prBody += `Build: [#${options.roslynBuildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${options.roslynBuildId})\n\n`; + } + + // Add PR list + prBody += prList; + + const commitMessage = `Bump Roslyn to ${options.roslynVersion} (${options.roslynEndSHA?.substring(0, 8)})`; + + // Create the PR + const prNumber = await createBranchAndPR({ ...options, commitSha: options.roslynEndSHA!, targetRemoteRepo: 'vscode-csharp', @@ -103,6 +119,21 @@ gulp.task('insertion:roslyn', async (): Promise => { email: options.email }, prTitle, commitMessage, prBody); + // If PR was created and we're not in dry run mode, update the changelog with the PR number + if (prNumber && !options.dryRun) { + console.log(`PR #${prNumber} created. Updating changelog with PR link...`); + + // Update changelog with PR number (single call) + await updateChangelog(options.roslynVersion, prList, prNumber); + + // Create a second commit to include the updated changelog and push to update the PR + await git(['add', 'CHANGELOG.md']); + await git(['commit', '-m', `Update changelog with PR #${prNumber}`]); + await git(['push', 'target', `insertion/${options.roslynEndSHA}`]); + + console.log(`Changelog updated with PR #${prNumber} link.`); + } + } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logError(`Insertion failed: ${errorMessage}`); @@ -133,6 +164,7 @@ async function extractRoslynVersionFromManifest(manifestPath: string): Promise { console.log(`Using Roslyn repository at ${roslynRepoPath}`); } -async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: string, options: InsertionOptions): Promise { +async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: string, _options: InsertionOptions): Promise { console.log(`Generating PR list from ${startSHA} to ${endSHA}...`); try { @@ -151,14 +183,14 @@ async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: `cd "${roslynRepoPath}" && roslyn-tools pr-finder -s "${startSHA}" -e "${endSHA}" --format changelog --label VSCode`, { maxBuffer: 10 * 1024 * 1024 } // 10MB buffer ); - return stdout || '(failed to generate PR list, see pipeline for details)'; + return stdout && stdout.trim() ? stdout : null; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); - logWarning(`PR finder failed, using empty list: ${errorMessage}`); + logWarning(`PR finder failed: ${errorMessage}`); if (error instanceof Error && error.stack) { console.log(`##[debug]${error.stack}`); } - return '(failed to generate PR list, see pipeline for details)'; + return null; } } @@ -175,29 +207,12 @@ async function updatePackageJson(newVersion: string): Promise { fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); } -async function updateChangelog(version: string, prList: string, buildNumber?: string, buildId?: string): Promise { +async function updateChangelog(version: string, prList: string | null, prNumber?: number): Promise { console.log('Updating CHANGELOG.md...'); const changelogPath = 'CHANGELOG.md'; const text = fs.readFileSync(changelogPath, 'utf8'); const NL = os.EOL; - // Prepare PR list (filter out 'View Complete Diff' lines) - const prLines = prList - ? prList - .split(/\r?\n/) - .filter((l) => l.trim() && !l.includes('View Complete Diff')) - .map((line) => line.trim()) - : []; - - // Format PR list as sub-items with proper indentation - const formattedPRList = prLines.length > 0 - ? prLines.map((line) => { - // If the line already starts with *, keep it, otherwise add it - const cleanLine = line.startsWith('*') ? line.substring(1).trim() : line; - return ` * ${cleanLine}`; - }).join(NL) - : ''; - // Find the first top-level header "# ..." const topHeaderRegex = /^(# .*?)(\r?\n|$)/m; const headerMatch = topHeaderRegex.exec(text); @@ -208,17 +223,27 @@ async function updateChangelog(version: string, prList: string, buildNumber?: st const headerEndLineIndex = headerMatch.index + headerMatch[0].length; // Prepare new Roslyn block - const prLink = buildNumber && buildId - ? `[#${buildNumber}](https://dev.azure.com/dnceng/internal/_build/results?buildId=${buildId})` - : '[#TBD](TBD)'; + let newRoslynBlock = `* Bump Roslyn to ${version}`; - let newRoslynBlock = `* Bump Roslyn to ${version} (PR: ${prLink})`; + // Add PR number if available + if (prNumber) { + newRoslynBlock = `* Bump Roslyn to ${version} (PR: [#${prNumber}](https://github.com/dotnet/vscode-csharp/pull/${prNumber}))`; + } - // Add PR list as sub-items if available and valid - const shouldSkipPRList = !prList || prList === '(failed to generate PR list, see pipeline for details)'; + // Add PR list as sub-items if available + if (prList) { + const prLines = prList + .split(/\r?\n/) + .filter((l) => l.trim() && !l.includes('View Complete Diff')) + .map((line) => line.trim()); - if (!shouldSkipPRList && formattedPRList) { - newRoslynBlock += NL + formattedPRList; + const formattedPRList = prLines.length > 0 + ? prLines.map((line) => ` ${line}`).join(NL) + : ''; + + if (formattedPRList) { + newRoslynBlock += NL + formattedPRList; + } } // Insert the new block right after the header From 67c62afda609aeb74a5c34a416e3b90504a63895 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Tue, 9 Sep 2025 03:57:19 +0530 Subject: [PATCH 13/17] Resolved review comments --- tasks/gitHelpers.ts | 5 ----- tasks/insertionTasks.ts | 30 +++++++++++++++++------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/tasks/gitHelpers.ts b/tasks/gitHelpers.ts index 874135febb..8db98c8f4d 100644 --- a/tasks/gitHelpers.ts +++ b/tasks/gitHelpers.ts @@ -24,11 +24,6 @@ export interface BranchAndPROptions extends GitOptions { email?: string; } -// Logging utilities -export function logWarning(message: string): void { - console.log(`##vso[task.logissue type=warning]${message}`); -} - export function logError(message: string): void { console.log(`##vso[task.logissue type=error]${message}`); } diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index 87f1e371db..74e20071bd 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -11,7 +11,7 @@ import { exec } from 'child_process'; import { promisify } from 'util'; import * as xml2js from 'xml2js'; import { allNugetPackages} from './offlinePackagingTasks'; -import {getCommitFromNugetAsync, logWarning, logError, createBranchAndPR, git } from './gitHelpers'; +import {getCommitFromNugetAsync, createBranchAndPR, git } from './gitHelpers'; import * as os from 'os'; const execAsync = promisify(exec); @@ -28,6 +28,20 @@ interface InsertionOptions { dryRun: boolean; } +function logWarning(message: string, error?: unknown): void { + console.log(`##vso[task.logissue type=warning]${message}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); + } +} + +function logError(message: string, error?: unknown): void { + console.log(`##vso[task.logissue type=error]${message}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); + } +} + gulp.task('insertion:roslyn', async (): Promise => { const options = minimist(process.argv.slice(2), { boolean: ['dryRun'], @@ -135,11 +149,7 @@ gulp.task('insertion:roslyn', async (): Promise => { } } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logError(`Insertion failed: ${errorMessage}`); - if (error instanceof Error && error.stack) { - console.log(`##[debug]${error.stack}`); - } + logError(`Insertion failed: ${error instanceof Error ? error.message : String(error)}`, error); throw error; } }); @@ -185,11 +195,7 @@ async function generatePRList(startSHA: string, endSHA: string, roslynRepoPath: ); return stdout && stdout.trim() ? stdout : null; } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logWarning(`PR finder failed: ${errorMessage}`); - if (error instanceof Error && error.stack) { - console.log(`##[debug]${error.stack}`); - } + logWarning(`PR finder failed: ${error instanceof Error ? error.message : String(error)}`, error); return null; } } @@ -249,9 +255,7 @@ async function updateChangelog(version: string, prList: string | null, prNumber? // Insert the new block right after the header const newText = text.slice(0, headerEndLineIndex) + - NL + NL + // Add two newlines after the header newRoslynBlock + - NL + // Add a newline after the block (text.length > headerEndLineIndex ? NL + text.slice(headerEndLineIndex) : ''); // Write the updated content back to the file From f6d930b0964a039d869b503df8d1a5a8d64c9f2e Mon Sep 17 00:00:00 2001 From: Deepak Singh Rathore Date: Tue, 9 Sep 2025 04:17:48 +0530 Subject: [PATCH 14/17] Update azure-pipelines/dotnet-vscode-csharp-insertion.yml Co-authored-by: Joey Robichaud --- azure-pipelines/dotnet-vscode-csharp-insertion.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines/dotnet-vscode-csharp-insertion.yml b/azure-pipelines/dotnet-vscode-csharp-insertion.yml index 4779605a7d..3143f9cecb 100644 --- a/azure-pipelines/dotnet-vscode-csharp-insertion.yml +++ b/azure-pipelines/dotnet-vscode-csharp-insertion.yml @@ -86,7 +86,7 @@ extends: dotnet tool install -g Microsoft.RoslynTools --prerelease --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json echo "Verifying roslyn-tools installation..." dotnet tool list -g - roslyn-tools --help + roslyn-tools --version displayName: 'Install roslyn-tools CLI' - task: Npm@1 From 503c29f1ab7b95f32206a09dcb94e37734bc79f5 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Wed, 10 Sep 2025 15:38:47 +0530 Subject: [PATCH 15/17] Updated the PAT input --- azure-pipelines/dotnet-vscode-csharp-insertion.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/azure-pipelines/dotnet-vscode-csharp-insertion.yml b/azure-pipelines/dotnet-vscode-csharp-insertion.yml index 3143f9cecb..1fa49a3a60 100644 --- a/azure-pipelines/dotnet-vscode-csharp-insertion.yml +++ b/azure-pipelines/dotnet-vscode-csharp-insertion.yml @@ -92,10 +92,9 @@ extends: - task: Npm@1 displayName: Run Roslyn insertion env: - GITHUB_TOKEN: $(System.AccessToken) - GitHubPAT: $(System.AccessToken) + GitHubPAT: $(BotAccount-dotnet-bot-repo-PAT) inputs: command: custom - customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynRepoPath=$(Pipeline.Workspace)/roslyn --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(System.AccessToken) --dryRun=false' + customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynRepoPath=$(Pipeline.Workspace)/roslyn --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(BotAccount-dotnet-bot-repo-PAT) --dryRun=false' From b76f875d9ebbf12286332d143ae3eb178b91b228 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Wed, 10 Sep 2025 20:42:30 +0530 Subject: [PATCH 16/17] Removed gitHelpers and updated gitTasks --- tasks/createTagsTasks.ts | 2 +- tasks/gitHelpers.ts | 193 ------------------------------------- tasks/gitTasks.ts | 134 ++++++++++++++++++++++++- tasks/insertionTasks.ts | 2 +- tasks/localizationTasks.ts | 2 +- 5 files changed, 136 insertions(+), 197 deletions(-) delete mode 100644 tasks/gitHelpers.ts diff --git a/tasks/createTagsTasks.ts b/tasks/createTagsTasks.ts index e92bde07d4..606c524900 100644 --- a/tasks/createTagsTasks.ts +++ b/tasks/createTagsTasks.ts @@ -7,7 +7,7 @@ import * as gulp from 'gulp'; import minimist from 'minimist'; import { Octokit } from '@octokit/rest'; import { allNugetPackages } from './offlinePackagingTasks'; -import { getCommitFromNugetAsync } from './gitHelpers'; +import { getCommitFromNugetAsync } from './gitTasks'; interface CreateTagsOptions { releaseVersion: string; diff --git a/tasks/gitHelpers.ts b/tasks/gitHelpers.ts deleted file mode 100644 index 8db98c8f4d..0000000000 --- a/tasks/gitHelpers.ts +++ /dev/null @@ -1,193 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { spawnSync } from 'child_process'; -import * as fs from 'fs'; -import * as path from 'path'; -import { Octokit } from '@octokit/rest'; -import { NugetPackageInfo, platformSpecificPackages } from './offlinePackagingTasks'; -import { PlatformInformation } from '../src/shared/platform'; - -export interface GitOptions { - commitSha: string; - targetRemoteRepo: string; - baseBranch: string; -} - -export interface BranchAndPROptions extends GitOptions { - githubPAT: string; - dryRun: boolean; - newBranchName: string; - userName?: string; - email?: string; -} - -export function logError(message: string): void { - console.log(`##vso[task.logissue type=error]${message}`); -} - -export async function git(args: string[], printCommand = true): Promise { - if (printCommand) { - console.log(`git ${args.join(' ')}`); - } - - const git = spawnSync('git', args); - if (git.status !== 0) { - const err = git.stderr.toString(); - if (printCommand) { - console.log(`Failed to execute git ${args.join(' ')}.`); - } - throw err; - } - - const stdout = git.stdout.toString(); - if (printCommand) { - console.log(stdout); - } - return stdout; -} - -export async function getCommitFromNugetAsync(packageInfo: NugetPackageInfo): Promise { - try { - const packageJsonString = fs.readFileSync('./package.json').toString(); - const packageJson = JSON.parse(packageJsonString); - const packageVersion = packageJson['defaults'][packageInfo.packageJsonName]; - if (!packageVersion) { - logError(`Can't find ${packageInfo.packageJsonName} version in package.json`); - return null; - } - - const platform = await PlatformInformation.GetCurrent(); - const vsixPlatformInfo = platformSpecificPackages.find( - (p) => p.platformInfo.platform === platform.platform && p.platformInfo.architecture === platform.architecture - )!; - - const packageName = packageInfo.getPackageName(vsixPlatformInfo); - console.log(`${packageName} version is ${packageVersion}`); - - // Nuget package should exist under out/.nuget/ since we have run the install dependencies task. - // Package names are always lower case in the .nuget folder. - const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), packageVersion); - const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); - - if (nuspecFiles.length === 0) { - logError(`No .nuspec file found in ${packageDir}`); - return null; - } - - if (nuspecFiles.length > 1) { - logError(`Multiple .nuspec files found in ${packageDir}`); - return null; - } - - const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); - const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); - const results = /commit="(.*?)"/.exec(nuspecFile); - if (results == null || results.length === 0) { - logError('Failed to find commit number from nuspec file'); - return null; - } - - if (results.length !== 2) { - logError('Unexpected regex match result from nuspec file.'); - return null; - } - - const commitNumber = results[1]; - console.log(`commitNumber is ${commitNumber}`); - return commitNumber; - } catch (error) { - logError(`Error getting commit from NuGet package: ${error}`); - if (error instanceof Error && error.stack) { - console.log(`##[debug]${error.stack}`); - } - throw error; - } -} - -export async function createBranchAndPR( - options: BranchAndPROptions, - title: string, - commitMessage: string, - body?: string -): Promise { - const { githubPAT, targetRemoteRepo, baseBranch, dryRun, userName, email, newBranchName } = options; - const remoteRepoAlias = 'target'; - - // Set git user configuration if provided in options - if (userName) { - await git(['config', '--local', 'user.name', userName]); - } - if (email) { - await git(['config', '--local', 'user.email', email]); - } - - // Create and checkout new branch - await git(['checkout', '-b', newBranchName]); - - // Add and commit changes - await git(['add', '.']); - await git(['commit', '-m', commitMessage]); - - // Add remote and push - await git( - [ - 'remote', - 'add', - remoteRepoAlias, - `https://${options.userName}:${githubPAT}@github.com/dotnet/${targetRemoteRepo}.git`, - ], - false // Don't print command with PAT - ); - - await git(['fetch', remoteRepoAlias]); - - // Check if branch already exists on remote (refs/heads/) - const lsRemote = await git(['ls-remote', remoteRepoAlias, 'refs/heads/' + newBranchName]); - if (lsRemote.trim() !== '') { - console.log(`##vso[task.logissue type=error]${newBranchName} already exists in ${targetRemoteRepo}. Skip pushing.`); - return null; - } - - if (dryRun !== true) { - // Push the newly created branch to the target remote - await git(['push', '-u', remoteRepoAlias, newBranchName]); - } else { - console.log('[DRY RUN] Would have pushed branch to remote'); - } - - const octokit = new Octokit({ auth: githubPAT }); - - // Check for existing PRs with same title - const listPullRequest = await octokit.rest.pulls.list({ - owner: 'dotnet', - repo: targetRemoteRepo, - }); - - if (listPullRequest.status !== 200) { - throw `Failed to get response from GitHub, http status code: ${listPullRequest.status}`; - } - - if (listPullRequest.data.some(pr => pr.title === title)) { - console.log('Pull request with the same name already exists. Skip creation.'); - return null; - } - - if (dryRun !== true) { - const pullRequest = await octokit.rest.pulls.create({ - body: body || title, - owner: 'dotnet', - repo: targetRemoteRepo, - title: title, - head: newBranchName, - base: baseBranch, - }); - console.log(`Created pull request: ${pullRequest.data.html_url}.`); - return pullRequest.data.number; - } else { - console.log(`[DRY RUN] Would have created PR with title: "${title}" and body: "${body || title}"`); - return null; - } -} diff --git a/tasks/gitTasks.ts b/tasks/gitTasks.ts index 9230729e2e..781455df99 100644 --- a/tasks/gitTasks.ts +++ b/tasks/gitTasks.ts @@ -4,7 +4,29 @@ *--------------------------------------------------------------------------------------------*/ import { spawnSync } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; import { Octokit } from '@octokit/rest'; +import { NugetPackageInfo, platformSpecificPackages } from './offlinePackagingTasks'; +import { PlatformInformation } from '../src/shared/platform'; + +export interface GitOptions { + commitSha: string; + targetRemoteRepo: string; + baseBranch: string; +} + +export interface BranchAndPROptions extends GitOptions { + githubPAT: string; + dryRun: boolean; + newBranchName: string; + userName?: string; + email?: string; +} + +export function logError(message: string): void { + console.log(`##vso[task.logissue type=error]${message}`); +} /** * Execute a git command with optional logging @@ -55,7 +77,7 @@ export async function createCommit(branch: string, files: string[], commitMessag * Check if a branch exists on the remote repository */ export async function doesBranchExist(remoteAlias: string, branch: string): Promise { - const lsRemote = await git(['ls-remote', remoteAlias, 'refs/head/' + branch]); + const lsRemote = await git(['ls-remote', remoteAlias, 'refs/heads/' + branch]); return lsRemote.trim() !== ''; } @@ -146,3 +168,113 @@ export async function createPullRequest( return null; } } + +export async function getCommitFromNugetAsync(packageInfo: NugetPackageInfo): Promise { + try { + const packageJsonString = fs.readFileSync('./package.json').toString(); + const packageJson = JSON.parse(packageJsonString); + const packageVersion = packageJson['defaults'][packageInfo.packageJsonName]; + if (!packageVersion) { + logError(`Can't find ${packageInfo.packageJsonName} version in package.json`); + return null; + } + + const platform = await PlatformInformation.GetCurrent(); + const vsixPlatformInfo = platformSpecificPackages.find( + (p) => p.platformInfo.platform === platform.platform && p.platformInfo.architecture === platform.architecture + )!; + + const packageName = packageInfo.getPackageName(vsixPlatformInfo); + console.log(`${packageName} version is ${packageVersion}`); + + // Nuget package should exist under out/.nuget/ since we have run the install dependencies task. + // Package names are always lower case in the .nuget folder. + const packageDir = path.join('out', '.nuget', packageName.toLowerCase(), packageVersion); + const nuspecFiles = fs.readdirSync(packageDir).filter((file) => file.endsWith('.nuspec')); + + if (nuspecFiles.length === 0) { + logError(`No .nuspec file found in ${packageDir}`); + return null; + } + + if (nuspecFiles.length > 1) { + logError(`Multiple .nuspec files found in ${packageDir}`); + return null; + } + + const nuspecFilePath = path.join(packageDir, nuspecFiles[0]); + const nuspecFile = fs.readFileSync(nuspecFilePath).toString(); + const results = /commit="(.*?)"/.exec(nuspecFile); + if (results == null || results.length === 0) { + logError('Failed to find commit number from nuspec file'); + return null; + } + + if (results.length !== 2) { + logError('Unexpected regex match result from nuspec file.'); + return null; + } + + const commitNumber = results[1]; + console.log(`commitNumber is ${commitNumber}`); + return commitNumber; + } catch (error) { + logError(`Error getting commit from NuGet package: ${error}`); + if (error instanceof Error && error.stack) { + console.log(`##[debug]${error.stack}`); + } + throw error; + } +} + +export async function createBranchAndPR( + options: BranchAndPROptions, + title: string, + commitMessage: string, + body?: string +): Promise { + const { githubPAT, targetRemoteRepo, baseBranch, dryRun, userName, email, newBranchName } = options; + + // Configure git user credentials + await configureGitUser(userName, email); + + // Create branch and commit changes + await createCommit(newBranchName, ['.'], commitMessage); + + if (dryRun !== true) { + // Push branch to remote + await pushBranch(newBranchName, githubPAT, 'dotnet', targetRemoteRepo); + } else { + console.log('[DRY RUN] Would have pushed branch to remote'); + } + + // Check for existing PR and create new one if needed + const existingPRUrl = await findPRByTitle(githubPAT, 'dotnet', targetRemoteRepo, title); + if (existingPRUrl) { + console.log('Pull request with the same name already exists. Skip creation.'); + return null; + } + + if (dryRun !== true) { + const prUrl = await createPullRequest( + githubPAT, + 'dotnet', + targetRemoteRepo, + newBranchName, + title, + body || title, + baseBranch + ); + + if (prUrl) { + console.log(`Created pull request: ${prUrl}.`); + // Extract PR number from URL (format: https://github.com/owner/repo/pull/123) + const prNumberMatch = prUrl.match(/\/pull\/(\d+)$/); + return prNumberMatch ? parseInt(prNumberMatch[1], 10) : null; + } + return null; + } else { + console.log(`[DRY RUN] Would have created PR with title: "${title}" and body: "${body || title}"`); + return null; + } +} diff --git a/tasks/insertionTasks.ts b/tasks/insertionTasks.ts index 74e20071bd..fdb110ec74 100644 --- a/tasks/insertionTasks.ts +++ b/tasks/insertionTasks.ts @@ -11,7 +11,7 @@ import { exec } from 'child_process'; import { promisify } from 'util'; import * as xml2js from 'xml2js'; import { allNugetPackages} from './offlinePackagingTasks'; -import {getCommitFromNugetAsync, createBranchAndPR, git } from './gitHelpers'; +import {getCommitFromNugetAsync, createBranchAndPR, git } from './gitTasks'; import * as os from 'os'; const execAsync = promisify(exec); diff --git a/tasks/localizationTasks.ts b/tasks/localizationTasks.ts index 4fae9f9216..e3da516156 100644 --- a/tasks/localizationTasks.ts +++ b/tasks/localizationTasks.ts @@ -10,7 +10,7 @@ import { spawnSync } from 'node:child_process'; import * as path from 'path'; import * as util from 'node:util'; import { EOL } from 'node:os'; -import { createBranchAndPR } from './gitHelpers'; +import { createBranchAndPR } from './gitTasks'; type Options = { userName?: string; From 75177d4e0c6ff8016211ba30ead48fefd9735922 Mon Sep 17 00:00:00 2001 From: deepakrathore33 Date: Wed, 10 Sep 2025 20:44:56 +0530 Subject: [PATCH 17/17] removed unused env var --- azure-pipelines/dotnet-vscode-csharp-insertion.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/azure-pipelines/dotnet-vscode-csharp-insertion.yml b/azure-pipelines/dotnet-vscode-csharp-insertion.yml index 1fa49a3a60..533176b6c5 100644 --- a/azure-pipelines/dotnet-vscode-csharp-insertion.yml +++ b/azure-pipelines/dotnet-vscode-csharp-insertion.yml @@ -91,8 +91,6 @@ extends: - task: Npm@1 displayName: Run Roslyn insertion - env: - GitHubPAT: $(BotAccount-dotnet-bot-repo-PAT) inputs: command: custom customCommand: 'run gulp -- insertion:roslyn --assetManifestPath=$(Pipeline.Workspace)/AssetManifests --roslynRepoPath=$(Pipeline.Workspace)/roslyn --roslynEndSHA=$(RoslynEndSHA) --roslynBuildNumber=$(RoslynBuildNumber) --roslynBuildId=$(RoslynBuildId) --targetBranch=${{ parameters.targetBranch }} --githubPAT=$(BotAccount-dotnet-bot-repo-PAT) --dryRun=false'