// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote { /// /// Provide solution from given checksum /// /// TODO: change this to workspace service /// internal class SolutionService { public const string WorkspaceKind_RemoteWorkspace = "RemoteWorkspace"; // TODO: make this simple cache better // this simple cache hold onto the last solution created private static ValueTuple s_lastSolution; private readonly AssetService _assetService; public SolutionService(AssetService assetService) { _assetService = assetService; } public async Task GetSolutionAsync(Checksum solutionChecksum, CancellationToken cancellationToken) { if (s_lastSolution.Item1 == solutionChecksum) { return s_lastSolution.Item2; } // create new solution var solution = await CreateSolutionAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); // save it s_lastSolution = ValueTuple.Create(solutionChecksum, solution); return solution; } public async Task GetSolutionAsync(Checksum solutionChecksum, OptionSet optionSet, CancellationToken cancellationToken) { // since option belong to workspace, we can't share solution // create new solution var solution = await CreateSolutionAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); // set merged options solution.Workspace.Options = MergeOptions(solution.Workspace.Options, optionSet); // return new solution return solution; } private OptionSet MergeOptions(OptionSet workspaceOptions, OptionSet userOptions) { var newOptions = workspaceOptions; foreach (var key in userOptions.GetChangedOptions(workspaceOptions)) { newOptions = newOptions.WithChangedOption(key, userOptions.GetOption(key)); } return newOptions; } private async Task CreateSolutionAsync(Checksum solutionChecksum, CancellationToken cancellationToken) { // synchronize whole solution first await _assetService.SynchronizeSolutionAssetsAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); var solutionChecksumObject = await _assetService.GetAssetAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); var workspace = new AdhocWorkspace(RoslynServices.HostServices, workspaceKind: WorkspaceKind_RemoteWorkspace); var solutionInfo = await _assetService.GetAssetAsync(solutionChecksumObject.Info, cancellationToken).ConfigureAwait(false); var projects = new List(); foreach (var projectChecksum in solutionChecksumObject.Projects) { var projectSnapshot = await _assetService.GetAssetAsync(projectChecksum, cancellationToken).ConfigureAwait(false); var projectInfo = await _assetService.GetAssetAsync(projectSnapshot.Info, cancellationToken).ConfigureAwait(false); if (!workspace.Services.IsSupported(projectInfo.Language)) { // only add project our workspace supports. // workspace doesn't allow creating project with unknown languages continue; } var documents = new List(); foreach (var documentChecksum in projectSnapshot.Documents) { var documentSnapshot = await _assetService.GetAssetAsync(documentChecksum, cancellationToken).ConfigureAwait(false); var documentInfo = await _assetService.GetAssetAsync(documentSnapshot.Info, cancellationToken).ConfigureAwait(false); var textLoader = TextLoader.From( TextAndVersion.Create( await _assetService.GetAssetAsync(documentSnapshot.Text, cancellationToken).ConfigureAwait(false), VersionStamp.Create(), documentInfo.FilePath)); // TODO: do we need version? documents.Add( DocumentInfo.Create( documentInfo.Id, documentInfo.Name, documentInfo.Folders, documentInfo.SourceCodeKind, textLoader, documentInfo.FilePath, documentInfo.IsGenerated)); } var p2p = new List(); foreach (var checksum in projectSnapshot.ProjectReferences) { cancellationToken.ThrowIfCancellationRequested(); var reference = await _assetService.GetAssetAsync(checksum, cancellationToken).ConfigureAwait(false); p2p.Add(reference); } var metadata = new List(); foreach (var checksum in projectSnapshot.MetadataReferences) { cancellationToken.ThrowIfCancellationRequested(); var reference = await _assetService.GetAssetAsync(checksum, cancellationToken).ConfigureAwait(false); metadata.Add(reference); } var analyzers = new List(); foreach (var checksum in projectSnapshot.AnalyzerReferences) { cancellationToken.ThrowIfCancellationRequested(); var reference = await _assetService.GetAssetAsync(checksum, cancellationToken).ConfigureAwait(false); analyzers.Add(reference); } var additionals = new List(); foreach (var documentChecksum in projectSnapshot.AdditionalDocuments) { cancellationToken.ThrowIfCancellationRequested(); var documentSnapshot = await _assetService.GetAssetAsync(documentChecksum, cancellationToken).ConfigureAwait(false); var documentInfo = await _assetService.GetAssetAsync(documentSnapshot.Info, cancellationToken).ConfigureAwait(false); var textLoader = TextLoader.From( TextAndVersion.Create( await _assetService.GetAssetAsync(documentSnapshot.Text, cancellationToken).ConfigureAwait(false), VersionStamp.Create(), documentInfo.FilePath)); // TODO: do we need version? additionals.Add( DocumentInfo.Create( documentInfo.Id, documentInfo.Name, documentInfo.Folders, documentInfo.SourceCodeKind, textLoader, documentInfo.FilePath, documentInfo.IsGenerated)); } var compilationOptions = await _assetService.GetAssetAsync(projectSnapshot.CompilationOptions, cancellationToken).ConfigureAwait(false); var parseOptions = await _assetService.GetAssetAsync(projectSnapshot.ParseOptions, cancellationToken).ConfigureAwait(false); projects.Add( ProjectInfo.Create( projectInfo.Id, projectInfo.Version, projectInfo.Name, projectInfo.AssemblyName, projectInfo.Language, projectInfo.FilePath, projectInfo.OutputFilePath, compilationOptions, parseOptions, documents, p2p, metadata, analyzers, additionals, projectInfo.IsSubmission)); } return workspace.AddSolution(SolutionInfo.Create(solutionInfo.Id, solutionInfo.Version, solutionInfo.FilePath, projects)); } } }