|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Serialization;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote;
/// <summary>
/// Provides corresponding data of the given checksum
/// </summary>
internal abstract class AbstractAssetProvider
{
/// <summary>
/// return data of type T whose checksum is the given checksum
/// </summary>
public abstract ValueTask<T> GetAssetAsync<T>(AssetPath assetPath, Checksum checksum, CancellationToken cancellationToken);
public abstract Task GetAssetsAsync<T, TArg>(AssetPath assetPath, HashSet<Checksum> checksums, Action<Checksum, T, TArg>? callback, TArg? arg, CancellationToken cancellationToken);
public async Task<SolutionInfo> CreateSolutionInfoAsync(
Checksum solutionChecksum,
SolutionServices solutionServices,
CancellationToken cancellationToken)
{
var solutionCompilationChecksums = await GetAssetAsync<SolutionCompilationStateChecksums>(AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false);
var solutionChecksums = await GetAssetAsync<SolutionStateChecksums>(AssetPathKind.SolutionStateChecksums, solutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false);
var solutionAttributes = await GetAssetAsync<SolutionInfo.SolutionAttributes>(AssetPathKind.SolutionAttributes, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false);
await GetAssetAsync<SourceGeneratorExecutionVersionMap>(AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false);
// Fetch all the project state checksums up front. That allows getting all the data in a single call, and
// enables parallel fetching of the projects below.
using var _1 = ArrayBuilder<Task<ProjectInfo>>.GetInstance(solutionChecksums.Projects.Length, out var projectsTasks);
await this.GetAssetHelper<ProjectStateChecksums>().GetAssetsAsync(
AssetPathKind.ProjectStateChecksums,
solutionChecksums.Projects.Checksums,
static (_, projectStateChecksums, args) =>
{
var (@this, projectsTasks, solutionServices, cancellationToken) = args;
projectsTasks.Add(@this.CreateProjectInfoAsync(projectStateChecksums, solutionServices, cancellationToken));
},
(@this: this, projectsTasks, solutionServices, cancellationToken),
cancellationToken).ConfigureAwait(false);
// Deserialize the analyzer references, then wrap them in a new isolated analyzer reference set that has its own ALC
var analyzerReference = await this.GetAssetsArrayAsync<AnalyzerReference>(
AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false);
var isolatedAnalyzerReferences = await IsolatedAnalyzerReferenceSet.CreateIsolatedAnalyzerReferencesAsync(
useAsync: true, analyzerReference, solutionServices, cancellationToken).ConfigureAwait(false);
var fallbackAnalyzerOptions = await GetAssetAsync<ImmutableDictionary<string, StructuredAnalyzerConfigOptions>>(AssetPathKind.SolutionFallbackAnalyzerOptions, solutionChecksums.FallbackAnalyzerOptions, cancellationToken).ConfigureAwait(false);
// Fetch the projects in parallel.
var projects = await Task.WhenAll(projectsTasks).ConfigureAwait(false);
return SolutionInfo.Create(
solutionAttributes.Id,
solutionAttributes.Version,
solutionAttributes.FilePath,
ImmutableCollectionsMarshal.AsImmutableArray(projects),
isolatedAnalyzerReferences,
fallbackAnalyzerOptions).WithTelemetryId(solutionAttributes.TelemetryId);
}
public async Task<ProjectInfo> CreateProjectInfoAsync(
ProjectStateChecksums projectChecksums,
SolutionServices solutionServices,
CancellationToken cancellationToken)
{
await Task.Yield();
var projectId = projectChecksums.ProjectId;
var attributes = await GetAssetAsync<ProjectInfo.ProjectAttributes>(new(AssetPathKind.ProjectAttributes, projectId), projectChecksums.Info, cancellationToken).ConfigureAwait(false);
Contract.ThrowIfFalse(RemoteSupportedLanguages.IsSupported(attributes.Language));
var compilationOptions = attributes.FixUpCompilationOptions(
await GetAssetAsync<CompilationOptions>(new(AssetPathKind.ProjectCompilationOptions, projectId), projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false));
var parseOptionsTask = GetAssetAsync<ParseOptions>(new(AssetPathKind.ProjectParseOptions, projectId), projectChecksums.ParseOptions, cancellationToken);
var projectReferencesTask = this.GetAssetsArrayAsync<ProjectReference>(new(AssetPathKind.ProjectProjectReferences, projectId), projectChecksums.ProjectReferences, cancellationToken);
var metadataReferencesTask = this.GetAssetsArrayAsync<MetadataReference>(new(AssetPathKind.ProjectMetadataReferences, projectId), projectChecksums.MetadataReferences, cancellationToken);
var analyzerReferencesTask = this.GetAssetsArrayAsync<AnalyzerReference>(new(AssetPathKind.ProjectAnalyzerReferences, projectId), projectChecksums.AnalyzerReferences, cancellationToken);
// Attempt to fetch all the documents for this project in bulk. This will allow for all the data to be fetched
// efficiently. We can then go and create the DocumentInfos for each document in the project.
await SynchronizeProjectDocumentsAsync(projectChecksums, cancellationToken).ConfigureAwait(false);
var documentInfosTask = CreateDocumentInfosAsync(projectChecksums.Documents);
var additionalDocumentInfosTask = CreateDocumentInfosAsync(projectChecksums.AdditionalDocuments);
var analyzerConfigDocumentInfosTask = CreateDocumentInfosAsync(projectChecksums.AnalyzerConfigDocuments);
// Deserialize the analyzer references, then wrap them in a new isolated analyzer reference set that has its own ALC.
var isolatedAnalyzerReferencesTask = IsolatedAnalyzerReferenceSet.CreateIsolatedAnalyzerReferencesAsync(
useAsync: true,
await analyzerReferencesTask.ConfigureAwait(false),
solutionServices,
cancellationToken);
return ProjectInfo.Create(
attributes,
compilationOptions,
await parseOptionsTask.ConfigureAwait(false),
await documentInfosTask.ConfigureAwait(false),
await projectReferencesTask.ConfigureAwait(false),
await metadataReferencesTask.ConfigureAwait(false),
await isolatedAnalyzerReferencesTask.ConfigureAwait(false),
await additionalDocumentInfosTask.ConfigureAwait(false),
await analyzerConfigDocumentInfosTask.ConfigureAwait(false),
hostObjectType: null); // TODO: https://github.com/dotnet/roslyn/issues/62804
async Task<ImmutableArray<DocumentInfo>> CreateDocumentInfosAsync(DocumentChecksumsAndIds checksumsAndIds)
{
var documentInfos = new FixedSizeArrayBuilder<DocumentInfo>(checksumsAndIds.Length);
foreach (var (attributeChecksum, textChecksum, documentId) in checksumsAndIds)
{
cancellationToken.ThrowIfCancellationRequested();
documentInfos.Add(await CreateDocumentInfoAsync(documentId, attributeChecksum, textChecksum, cancellationToken).ConfigureAwait(false));
}
return documentInfos.MoveToImmutable();
}
}
public async Task SynchronizeProjectDocumentsAsync(
ProjectStateChecksums projectChecksums, CancellationToken cancellationToken)
{
await Task.Yield();
using var _1 = PooledHashSet<Checksum>.GetInstance(out var attributeChecksums);
using var _2 = PooledHashSet<Checksum>.GetInstance(out var textChecksums);
projectChecksums.Documents.AttributeChecksums.AddAllTo(attributeChecksums);
projectChecksums.AdditionalDocuments.AttributeChecksums.AddAllTo(attributeChecksums);
projectChecksums.AnalyzerConfigDocuments.AttributeChecksums.AddAllTo(attributeChecksums);
projectChecksums.Documents.TextChecksums.AddAllTo(textChecksums);
projectChecksums.AdditionalDocuments.TextChecksums.AddAllTo(textChecksums);
projectChecksums.AnalyzerConfigDocuments.TextChecksums.AddAllTo(textChecksums);
var attributesTask = this.GetAssetsAsync<DocumentInfo.DocumentAttributes>(
assetPath: new(AssetPathKind.DocumentAttributes, projectChecksums.ProjectId),
attributeChecksums,
cancellationToken);
var textTask = this.GetAssetsAsync<SerializableSourceText>(
assetPath: new(AssetPathKind.DocumentText, projectChecksums.ProjectId),
textChecksums,
cancellationToken);
await Task.WhenAll(attributesTask, textTask).ConfigureAwait(false);
}
public async Task<DocumentInfo> CreateDocumentInfoAsync(
DocumentId documentId, Checksum attributeChecksum, Checksum textChecksum, CancellationToken cancellationToken)
{
var attributes = await GetAssetAsync<DocumentInfo.DocumentAttributes>(new(AssetPathKind.DocumentAttributes, documentId), attributeChecksum, cancellationToken).ConfigureAwait(false);
var serializableSourceText = await GetAssetAsync<SerializableSourceText>(new(AssetPathKind.DocumentText, documentId), textChecksum, cancellationToken).ConfigureAwait(false);
var textLoader = serializableSourceText.ToTextLoader(attributes.FilePath);
// TODO: do we need version?
return new DocumentInfo(attributes, textLoader, documentServiceProvider: null);
}
public AssetHelper<T> GetAssetHelper<T>()
=> new(this);
public readonly struct AssetHelper<T>(AbstractAssetProvider assetProvider)
{
public Task GetAssetsAsync<TArg>(AssetPath assetPath, HashSet<Checksum> checksums, Action<Checksum, T, TArg>? callback, TArg? arg, CancellationToken cancellationToken)
=> assetProvider.GetAssetsAsync(assetPath, checksums, callback, arg, cancellationToken);
public Task GetAssetsAsync<TArg>(AssetPath assetPath, ChecksumCollection checksums, Action<Checksum, T, TArg>? callback, TArg? arg, CancellationToken cancellationToken)
=> assetProvider.GetAssetsAsync(assetPath, checksums, callback, arg, cancellationToken);
}
}
|