Skip to content
Open
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
24 changes: 11 additions & 13 deletions ScipDotnet/IndexCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
using Google.Protobuf;
using Microsoft.CodeAnalysis.Elfie.Extensions;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Scip;

Expand All @@ -13,7 +11,8 @@ namespace ScipDotnet;
public static class IndexCommandHandler
{
public static async Task<int> Process(
IHost host,
ILoggerFactory loggerFactory,
MSBuildWorkspace workspace,
List<FileInfo> projects,
string output,
FileInfo workingDirectory,
Expand All @@ -25,7 +24,7 @@ public static async Task<int> Process(
FileInfo? nugetConfigPath
)
{
var logger = host.Services.GetRequiredService<ILogger<IndexCommandOptions>>();
var logger = loggerFactory.CreateLogger<IndexCommandOptions>();
var matcher = new Matcher();
matcher.AddIncludePatterns(include.Count == 0 ? new[] { "**" } : include);
matcher.AddExcludePatterns(exclude);
Expand All @@ -49,18 +48,17 @@ public static async Task<int> Process(
skipDotnetRestore,
nugetConfigPath
);
await ScipIndex(host, options);
await ScipIndex(loggerFactory, workspace, options);


// Log msbuild workspace diagnostic information after the index command finishes
// We log the MSBuild failures as error since they are often blocking issues
// preventing indexing. However, we log msbuild warnings as debug since they
// do not block indexing usually and are much noisier
var workspaceLogger = host.Services.GetRequiredService<ILogger<MSBuildWorkspace>>();
var workspaceService = host.Services.GetRequiredService<MSBuildWorkspace>();
if (workspaceService.Diagnostics.Any())
var workspaceLogger = loggerFactory.CreateLogger<MSBuildWorkspace>();
if (workspace.Diagnostics.Any())
{
var diagnosticGroups = workspaceService.Diagnostics
var diagnosticGroups = workspace.Diagnostics
.GroupBy(d => new { d.Kind, d.Message })
.Select(g => new { g.Key.Kind, g.Key.Message, Count = g.Count() });
foreach (var diagnostic in diagnosticGroups)
Expand All @@ -83,10 +81,10 @@ public static async Task<int> Process(
private static FileInfo OutputFile(FileInfo workingDirectory, string output) =>
Path.IsPathRooted(output) ? new FileInfo(output) : new FileInfo(Path.Join(workingDirectory.FullName, output));

private static async Task ScipIndex(IHost host, IndexCommandOptions options)
private static async Task ScipIndex(ILoggerFactory loggerFactory, MSBuildWorkspace workspace, IndexCommandOptions options)
{
var stopwatch = Stopwatch.StartNew();
var indexer = host.Services.GetRequiredService<ScipProjectIndexer>();
var indexer = new ScipProjectIndexer(loggerFactory.CreateLogger<ScipProjectIndexer>());
var index = new Scip.Index
{
Metadata = new Metadata
Expand All @@ -100,7 +98,7 @@ private static async Task ScipIndex(IHost host, IndexCommandOptions options)
TextDocumentEncoding = TextEncoding.Utf8,
}
};
await foreach (var document in indexer.IndexDocuments(host, options))
await foreach (var document in indexer.IndexDocuments(workspace, options))
{
index.Documents.Add(document);
}
Expand Down Expand Up @@ -137,4 +135,4 @@ private static List<FileInfo> FindSolutionOrProjectFile(FileInfo workingDirector
workingDirectory.FullName, FixThisProblem("SOLUTION_FILE"));
return new List<FileInfo>();
}
}
}
168 changes: 103 additions & 65 deletions ScipDotnet/Program.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Hosting;
using System.CommandLine.NamingConventionBinder;
using System.CommandLine.Parsing;
using System.CommandLine;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace ScipDotnet;
Expand All @@ -19,69 +11,115 @@ public static class Program

public static async Task<int> Main(string[] args)
{
var projectsArgument = new Argument<List<FileInfo>>("projects")
{
Arity = ArgumentArity.ZeroOrMore,
Description = "Path to the .sln (solution) or .csproj/.vbproj file"
};
var outputOption = new Option<string>("--output")
{
Description = "Path to the output SCIP index file",
DefaultValueFactory = _ => "index.scip"
};
var workingDirectoryOption = new Option<FileInfo>("--working-directory")
{
Description = "The working directory",
DefaultValueFactory = _ => new FileInfo(Directory.GetCurrentDirectory())
};
var includeOption = new Option<List<string>>("--include")
{
Description = "Only index files that match the given file glob pattern",
Arity = ArgumentArity.ZeroOrMore,
DefaultValueFactory = _ => new List<string>()
};
var excludeOption = new Option<List<string>>("--exclude")
{
Description = "Only index files that match the given file glob pattern",
Arity = ArgumentArity.ZeroOrMore,
DefaultValueFactory = _ => new List<string>()
};
var allowGlobalSymbolDefinitionsOption = new Option<bool>("--allow-global-symbol-definitions")
{
Description = "If enabled, allow public symbol definitions to be accessible from other SCIP indexes. " +
"If disabled, then public symbols will only be visible within the index.",
DefaultValueFactory = _ => false
};
var dotnetRestoreTimeoutOption = new Option<int>("--dotnet-restore-timeout")
{
Description = @"The timeout (in ms) for the ""dotnet restore"" command",
DefaultValueFactory = _ => DotnetRestoreTimeout
};
var skipDotnetRestoreOption = new Option<bool>("--skip-dotnet-restore")
{
Description = @"Skip executing ""dotnet restore"" and assume it has been run externally.",
DefaultValueFactory = _ => false
};
var nugetConfigPathOption = new Option<FileInfo?>("--nuget-config-path")
{
Description = @"Provide a case sensitive custom path for ""dotnet restore"" to find the NuGet.config file. " +
@"If not provided, ""dotnet restore"" will search for the NuGet.config file recursively up the folder hierarchy " +
@"and in the default user and system config locations."
};

var indexCommand = new Command("index", "Index a solution file")
{
new Argument<FileInfo>("projects", "Path to the .sln (solution) or .csproj/.vbproj file")
{ Arity = ArgumentArity.ZeroOrMore },
new Option<string>("--output", () => "index.scip",
"Path to the output SCIP index file"),
new Option<FileInfo>("--working-directory",
() => new FileInfo(Directory.GetCurrentDirectory()),
"The working directory"),
new Option<List<string>>("--include", () => new List<string>(),
"Only index files that match the given file glob pattern")
{
Arity = ArgumentArity.ZeroOrMore,
},
new Option<List<string>>("--exclude", () => new List<string>(),
"Only index files that match the given file glob pattern")
{
Arity = ArgumentArity.ZeroOrMore
},
new Option<bool>("--allow-global-symbol-definitions", () => false,
"If enabled, allow public symbol definitions to be accessible from other SCIP indexes. " +
"If disabled, then public symbols will only be visible within the index."),
new Option<int>("--dotnet-restore-timeout", () => DotnetRestoreTimeout,
@"The timeout (in ms) for the ""dotnet restore"" command"),
new Option<bool>("--skip-dotnet-restore", () => false,
@"Skip executing ""dotnet restore"" and assume it has been run externally."),
new Option<FileInfo?>("--nuget-config-path", () => null,
@"Provide a case sensitive custom path for ""dotnet restore"" to find the NuGet.config file. " +
@"If not provided, ""dotnet restore"" will search for the NuGet.config file recursively up the folder hierarchy " +
@"and in the default user and system config locations."),
projectsArgument,
outputOption,
workingDirectoryOption,
includeOption,
excludeOption,
allowGlobalSymbolDefinitionsOption,
dotnetRestoreTimeoutOption,
skipDotnetRestoreOption,
nugetConfigPathOption
};
indexCommand.Handler = CommandHandler.Create(IndexCommandHandler.Process);
var rootCommand =
new RootCommand(
"SCIP indexer for the C# and Visual basic programming languages. Built with the Roslyn .NET compiler. Supports MSBuild.")
{
indexCommand,
};
var builder = new CommandLineBuilder(rootCommand);
return await builder.UseHost(_ => Host.CreateDefaultBuilder(), host =>

indexCommand.SetAction(async (parseResult, cancellationToken) =>
{
var projects = parseResult.GetValue(projectsArgument) ?? new List<FileInfo>();
var output = parseResult.GetValue(outputOption) ?? "index.scip";
var workingDirectory = parseResult.GetValue(workingDirectoryOption) ?? new FileInfo(Directory.GetCurrentDirectory());
var include = parseResult.GetValue(includeOption) ?? new List<string>();
var exclude = parseResult.GetValue(excludeOption) ?? new List<string>();
var allowGlobalSymbolDefinitions = parseResult.GetValue(allowGlobalSymbolDefinitionsOption);
var dotnetRestoreTimeout = parseResult.GetValue(dotnetRestoreTimeoutOption);
var skipDotnetRestore = parseResult.GetValue(skipDotnetRestoreOption);
var nugetConfigPath = parseResult.GetValue(nugetConfigPathOption);

using var loggerFactory = LoggerFactory.Create(builder =>
{
host.ConfigureAppConfiguration(b => b.AddInMemoryCollection());
host.ConfigureLogging(b => b.AddSimpleConsole(options =>
builder.AddSimpleConsole(options =>
{
options.IncludeScopes = true;
options.SingleLine = true;
options.TimestampFormat = "HH:mm:ss ";
}).AddFilter("Microsoft.Hosting.Lifetime", LogLevel.None));
host.ConfigureServices((_, collection) =>
collection
.AddSingleton(_ => CreateWorkspace())
.AddTransient<ScipProjectIndexer>()
.AddTransient(services => (Workspace)services.GetRequiredService<MSBuildWorkspace>())
);
})
.UseDefaults()
.Build()
.InvokeAsync(args);
}
});
builder.AddFilter("Microsoft.Hosting.Lifetime", LogLevel.None);
});

private static MSBuildWorkspace CreateWorkspace()
{
MSBuildLocator.RegisterDefaults();
return MSBuildWorkspace.Create();
MSBuildLocator.RegisterDefaults();
var workspace = MSBuildWorkspace.Create();

return await IndexCommandHandler.Process(
loggerFactory,
workspace,
projects,
output,
workingDirectory,
include,
exclude,
allowGlobalSymbolDefinitions,
dotnetRestoreTimeout,
skipDotnetRestore,
nugetConfigPath);
});

var rootCommand = new RootCommand(
"SCIP indexer for the C# and Visual basic programming languages. Built with the Roslyn .NET compiler. Supports MSBuild.")
{
indexCommand
};

return await rootCommand.Parse(args).InvokeAsync();
}
}
}
6 changes: 4 additions & 2 deletions ScipDotnet/ScipDotnet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Features" Version="4.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.4.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.CommandLine.Hosting" Version="0.4.0-alpha.22272.1" />
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
<PackageReference Include="System.CommandLine" Version="2.0.1" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
</ItemGroup>

Expand Down
16 changes: 6 additions & 10 deletions ScipDotnet/ScipProjectIndexer.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace ScipDotnet;
Expand Down Expand Up @@ -42,19 +40,19 @@ private void Restore(IndexCommandOptions options, FileInfo project)
}
}

public async IAsyncEnumerable<Scip.Document> IndexDocuments(IHost host, IndexCommandOptions options)
public async IAsyncEnumerable<Scip.Document> IndexDocuments(MSBuildWorkspace workspace, IndexCommandOptions options)
{
var indexedProjects = new HashSet<ProjectId>();
foreach (var project in options.ProjectsFile)
{
await foreach (var document in IndexProject(host, options, project, indexedProjects))
await foreach (var document in IndexProject(workspace, options, project, indexedProjects))
{
yield return document;
}
}
}

private async IAsyncEnumerable<Scip.Document> IndexProject(IHost host,
private async IAsyncEnumerable<Scip.Document> IndexProject(MSBuildWorkspace workspace,
IndexCommandOptions options,
FileInfo rootProject,
HashSet<ProjectId> indexedProjects)
Expand All @@ -67,11 +65,9 @@ private void Restore(IndexCommandOptions options, FileInfo project)
var projects = (string.Equals(rootProject.Extension, ".csproj") || string.Equals(rootProject.Extension, ".vbproj")
? new[]
{
await host.Services.GetRequiredService<MSBuildWorkspace>()
.OpenProjectAsync(rootProject.FullName)
await workspace.OpenProjectAsync(rootProject.FullName)
}
: (await host.Services.GetRequiredService<MSBuildWorkspace>()
.OpenSolutionAsync(rootProject.FullName)).Projects).ToList();
: (await workspace.OpenSolutionAsync(rootProject.FullName)).Projects).ToList();


options.Logger.LogDebug($"Found {projects.Count()} projects");
Expand Down Expand Up @@ -156,4 +152,4 @@ await host.Services.GetRequiredService<MSBuildWorkspace>()

return doc;
}
}
}