Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ variables:
value: 'staging'
- name: storageAccountName
value: 'netsourceindexprod'
- name: stagingHost
- name: deploymentHost
value: 'staging.source.dot.net'
- ${{ else }}:
- name: poolName
Expand All @@ -58,6 +58,8 @@ variables:
value: 'validation'
- name: storageAccountName
value: 'netsourceindexvalidprod'
- name: deploymentHost
value: 'validation.source.dot.net'

extends:
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
Expand Down Expand Up @@ -221,26 +223,26 @@ extends:
inlineScript: |
az webapp restart --name $(webAppName) --slot $(temporaryDeploymentSlot) --resource-group $(resourceGroupName)

# FIXME: Health endpoints disabled till they can be audited: "https://$(stagingHost)/health", "https://$(stagingHost)/health/alive"
# FIXME: Health endpoints disabled till they can be audited: "https://$(deploymentHost)/health", "https://$(deploymentHost)/health/alive"
- pwsh: |
Start-Sleep 60
$urls = @(
"https://$(stagingHost)"
"https://$(stagingHost)/System.Private.CoreLib/src/runtime/src/libraries/System.Private.CoreLib/src/System/String.cs.html"
"https://$(deploymentHost)"
"https://$(deploymentHost)/System.Private.CoreLib/src/runtime/src/libraries/System.Private.CoreLib/src/System/String.cs.html"
)
foreach ($url in $urls) {
Write-Host "Testing URL: $url"
try {
$statusCode = Invoke-WebRequest $url -UseBasicParsing -SkipHttpErrorCheck | select -ExpandProperty StatusCode
if ($statusCode -ne 200) {
Write-Error "##vso[task.logissue type=warning;]Deployed staging website returned unexpected status code $statusCode from url $url"
Write-Error "##vso[task.logissue type=warning;]Deployed website returned unexpected status code $statusCode from url $url"
}
} catch {
Write-Error "##vso[task.logissue type=warning;]Failed to test URL $url : $_"
}
}
displayName: 🟣Test Deployed WebApp
condition: and(succeeded(), eq(variables['isOfficialBuild'], 'True'))
condition: succeeded()

- task: AzureCLI@2
displayName: 🟣Swap Staging Slot into Production
Expand Down
1 change: 1 addition & 0 deletions src/SourceBrowser/src/BinLogParser/BinLogParser.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic" />
<PackageReference Include="MSBuild.StructuredLogger" />
<PackageReference Include="NuGet.Frameworks" />
</ItemGroup>


Expand Down
47 changes: 47 additions & 0 deletions src/SourceBrowser/src/BinLogParser/CompilerInvocationScoring.cs

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Extracted from src/SourceBrowser/src/BinLogToSln/Program.cs, so it can be reused.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.IO;
using NuGet.Frameworks;

namespace Microsoft.SourceBrowser.BinLogParser
{
public static class CompilerInvocationScoring
{
public static int CalculateScore(string targetFramework, int sourceFileCount)
{
var score = sourceFileCount;
if (!string.IsNullOrEmpty(targetFramework))
{
var framework = NuGetFramework.Parse(targetFramework);
if (framework.Version != null)
{
score += framework.Version.Major * 1000 + framework.Version.Minor * 100;
}

if (framework.HasPlatform)
{
score += 500;
if (framework.Platform.Equals("linux", StringComparison.OrdinalIgnoreCase))
{
score += 100;
}
else if (framework.Platform.Equals("unix", StringComparison.OrdinalIgnoreCase))
{
score += 50;
}
}
}

return score;
}

public static bool IsReferenceAssembly(
string projectDirectory,
StringComparison comparison = StringComparison.Ordinal)
{
var projectFolder = Path.GetFileName(projectDirectory);
return string.Equals(projectFolder, "ref", comparison) ||
string.Equals(projectFolder, "stubs", comparison) ||
string.Equals(Path.GetFileName(Path.GetDirectoryName(projectDirectory)), "cycle-breakers", comparison);
}
}
}
1 change: 0 additions & 1 deletion src/SourceBrowser/src/BinLogToSln/BinLogToSln.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
<PackageReference Include="LibGit2Sharp" />
<PackageReference Include="Microsoft.CodeAnalysis" />
<PackageReference Include="Mono.Options" />
<PackageReference Include="NuGet.Frameworks" />
</ItemGroup>

<ItemGroup>
Expand Down
60 changes: 10 additions & 50 deletions src/SourceBrowser/src/BinLogToSln/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Microsoft.CodeAnalysis.Emit;
using Microsoft.SourceBrowser.BinLogParser;
using Mono.Options;
using NuGet.Frameworks;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
Expand Down Expand Up @@ -69,49 +68,17 @@ internal static int CalculateInvocationScore(CompilerInvocation invocation)
score -= 10000; // Heavy penalty for platform not supported assemblies
}

// 3. Newest TargetFramework version (third priority)
if (invocation.ProjectProperties?.TryGetValue("TargetFramework", out var targetFramework) == true &&
!string.IsNullOrEmpty(targetFramework))
var sourceFileCount = invocation.Parsed?.SourceFiles.Length ?? 0;
string targetFramework = null;
invocation.ProjectProperties?.TryGetValue("TargetFramework", out targetFramework);
try
{
try
{
var framework = NuGetFramework.Parse(targetFramework);

// Prefer newer frameworks (high weight)
if (framework.Version != null)
{
score += (int)(framework.Version.Major * 1000 + framework.Version.Minor * 100);
}

// 4. Has a platform (fourth priority)
// Prefer platform-specific frameworks
if (framework.HasPlatform)
{
score += 500;

if (framework.Platform.Equals("linux", StringComparison.OrdinalIgnoreCase))
{
score += 100; // Linux is preferred over other platforms
}
else if (framework.Platform.Equals("unix", StringComparison.OrdinalIgnoreCase))
{
score += 50; // Unix is also preferred, but less than Linux
}
}

}
catch (Exception ex)
{
Console.WriteLine($"Warning: Could not parse TargetFramework '{targetFramework}': {ex.Message}");
}
score += CompilerInvocationScoring.CalculateScore(targetFramework, sourceFileCount);
}

// 5. More source files (lowest priority)
var sourceFiles = invocation.Parsed?.SourceFiles;
if (sourceFiles.HasValue)
catch (Exception ex) when (ex is ArgumentException || ex is NuGet.Frameworks.FrameworkException)
{
int totalSourceFiles = sourceFiles.Value.Length;
score += totalSourceFiles; // Lower weight than other factors
Console.WriteLine($"Warning: Could not parse TargetFramework '{targetFramework}': {ex.Message}");
score += sourceFileCount;
}
}
catch (Exception ex)
Expand All @@ -131,16 +98,9 @@ private static bool ShouldExcludeInvocation(CompilerInvocation invocation)
return true;
}

string projectFolder = Path.GetFileName(invocation.ProjectDirectory);
if (projectFolder == "ref" || projectFolder == "stubs")
{
Console.WriteLine($"Skipping Ref Assembly project {invocation.ProjectFilePath}");
return true;
}

if (Path.GetFileName(Path.GetDirectoryName(invocation.ProjectDirectory)) == "cycle-breakers")
if (CompilerInvocationScoring.IsReferenceAssembly(invocation.ProjectDirectory))
{
Console.WriteLine($"Skipping Wpf Cycle-Breaker project {invocation.ProjectFilePath}");
Console.WriteLine($"Skipping reference assembly or cycle-breaker project {invocation.ProjectFilePath}");
return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.IO;
using System.Linq;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.SourceBrowser.HtmlGenerator;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand All @@ -11,6 +12,12 @@ namespace HtmlGenerator.Tests
[TestClass]
public class CompilerLogAssemblyNameTests
{
[ClassInitialize]
public static void Initialize(TestContext context)
{
MSBuildLocator.RegisterDefaults();
}

private static SolutionInfo CreateSolutionInfo(params string[] assemblyNames)
{
var projects = assemblyNames.Select(name =>
Expand Down Expand Up @@ -71,6 +78,95 @@ public void Implementation_compilation_precedes_reference_assembly_with_the_same
.ShouldBe(new[] { implementation.Id, unrelated.Id, referenceAssembly.Id });
}

[TestMethod]
public void Compiler_source_count_takes_priority_over_injected_generated_documents()
{
var generatedHeavyProject = CreateProjectInfo("A.dll", documentCount: 10);
var implementation = CreateProjectInfo("A.dll", documentCount: 1);
var original = SolutionInfo.Create(
SolutionId.CreateNewId(),
VersionStamp.Default,
projects: new[] { generatedHeavyProject, implementation });

var normalized = SolutionGenerator.NormalizeCompilerLogAssemblyNames(
original,
projectScores: new[] { 1, 10 },
projectFilePaths: SolutionGenerator.CreateCompilerLogProjectPathMap(
original, new[] { @"D:\repo\Generated.csproj", @"D:\repo\Implementation.csproj" }));

normalized.Projects.Select(p => p.Id)
.ShouldBe(new[] { implementation.Id, generatedHeavyProject.Id });

using var workspace = new AdhocWorkspace();
workspace.AddSolution(normalized);
workspace.CurrentSolution.Projects.Select(p => p.Id)
.ShouldBe(new[] { implementation.Id, generatedHeavyProject.Id });
}

[TestMethod]
[DataRow(2, 2, false)]
[DataRow(2, 2, true)]
[DataRow(1, 2, false)]
[DataRow(0, 2, false)]
[DataRow(2, 1, false)]
[DataRow(2, 0, false)]
[DataRow(2, 3, false)]
public void Compiler_log_scores_require_complete_aligned_metadata(int pathCount, int scoreCount, bool reversePaths)
{
var first = CreateProjectInfo("A.dll", documentCount: 1).WithFilePath("First.csproj");
var second = CreateProjectInfo("A.dll", documentCount: 10).WithFilePath("Second.csproj");
var solution = SolutionInfo.Create(
SolutionId.CreateNewId(), VersionStamp.Default, projects: new[] { first, second });
var paths = new[] { @"D:\repo\First.csproj", @"D:\repo\Second.csproj" }.Take(pathCount);
var pathMap = SolutionGenerator.CreateCompilerLogProjectPathMap(
solution, (reversePaths ? paths.Reverse() : paths).ToArray());

var normalized = SolutionGenerator.NormalizeCompilerLogAssemblyNames(
solution,
projectScores: new[] { 12_000, 11_000, 10_000 }.Take(scoreCount).ToArray(),
projectFilePaths: pathMap);

normalized.Projects.Select(project => project.Id).ShouldBe(
pathCount == 2 && scoreCount == 2 && !reversePaths
? new[] { first.Id, second.Id }
: new[] { second.Id, first.Id });
}

[TestMethod]
[DataRow("ref")]
[DataRow("REF")]
[DataRow("stubs")]
[DataRow(@"cycle-breakers\Foo")]
public void Compiler_log_project_score_deprioritizes_reference_assemblies(string projectFolder)
{
SolutionGenerator.CalculateCompilerLogProjectScore(
Path.Combine(@"D:\repo", projectFolder, "Foo.csproj"),
"net11.0",
sourceFileCount: 10_000)
.ShouldBe(int.MinValue);
SolutionGenerator.CalculateCompilerLogProjectScore(
@"D:\repo\src\libraries\Foo\src\Foo.csproj",
"net11.0",
sourceFileCount: 100)
.ShouldBeGreaterThan(int.MinValue);
}

[TestMethod]
[DataRow("net10.0", 10_100)]
[DataRow("net11.0", 11_100)]
[DataRow("net11.0-linux", 11_700)]
[DataRow("net11.0-unix", 11_650)]
[DataRow("net11.0-windows10.0.19041.0", 11_600)]
[DataRow("net48", 4_900)]
[DataRow("netstandard2.1", 2_200)]
[DataRow(null, 100)]
[DataRow("invalid-framework-name", 100)]
public void Compiler_log_project_score_matches_binlog_weights(string targetFramework, int expected)
{
SolutionGenerator.CalculateCompilerLogProjectScore(
@"D:\repo\Foo.csproj", targetFramework, sourceFileCount: 100).ShouldBe(expected);
}

[TestMethod]
public void Compiler_log_document_folders_match_BinLogToSln_link_behavior()
{
Expand Down Expand Up @@ -99,7 +195,7 @@ public void Compiler_log_document_folders_match_BinLogToSln_link_behavior()
projects: new[] { project });
var normalized = SolutionGenerator.NormalizeCompilerLogAssemblyNames(
solution,
compilerLogRepositoryRoots: new[] { repositoryRoot });
compilerLogDocumentRoots: new[] { repositoryRoot });
var documents = normalized.Projects.Single().Documents.ToDictionary(d => d.Name);

documents["Local.cs"].Folders.ShouldBe(new[] { "Internal" });
Expand Down Expand Up @@ -130,6 +226,47 @@ public void Existing_compiler_log_document_folders_are_preserved()
normalized.ShouldBeSameAs(document);
}

[TestMethod]
public void Compiler_log_parent_traversal_is_normalized_to_repository_relative_path()
{
const string repositoryRoot = @"D:\a\_work\1\s";
const string projectFilePath =
repositoryRoot + @"\src\runtime\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj";
var projectId = ProjectId.CreateNewId();
var document = DocumentInfo.Create(
DocumentId.CreateNewId(projectId),
"String.cs",
folders: new[] { "..", "..", "..", "..", "libraries", "System.Private.CoreLib", "src", "System" },
filePath: repositoryRoot + @"\src\runtime\src\libraries\System.Private.CoreLib\src\System\String.cs");
var project = ProjectInfo.Create(
projectId,
VersionStamp.Default,
name: "System.Private.CoreLib",
assemblyName: "System.Private.CoreLib.dll",
language: LanguageNames.CSharp,
filePath: "System.Private.CoreLib.csproj",
documents: new[] { document });
var solution = SolutionInfo.Create(
SolutionId.CreateNewId(),
VersionStamp.Default,
projects: new[] { project });

var normalized = SolutionGenerator.NormalizeCompilerLogAssemblyNames(
solution,
compilerLogDocumentRoots: new[] { repositoryRoot },
projectFilePaths: SolutionGenerator.CreateCompilerLogProjectPathMap(solution, new[] { projectFilePath }));
var normalizedDocument = normalized.Projects.Single().Documents.Single();

normalizedDocument.Folders.ShouldBe(
new[] { "src", "runtime", "src", "libraries", "System.Private.CoreLib", "src", "System" });

using var workspace = new AdhocWorkspace();
workspace.AddSolution(normalized);
var roslynDocument = workspace.CurrentSolution.Projects.Single().Documents.Single();
Paths.GetRelativeFilePathInProject(roslynDocument, project.FilePath).ShouldBe(
@"src\runtime\src\libraries\System.Private.CoreLib\src\System\String.cs");
}

private static ProjectInfo CreateProjectInfo(string assemblyName, int documentCount)
{
var projectId = ProjectId.CreateNewId();
Expand Down
Loading
Loading