Skip to content

Commit 3e9bd1a

Browse files
authored
Support multiple solution files (#5)
Previously, running `scip-dotnet index` in a codebase with multiple `.sln` files failed to run. Now, we index all solutions instead. Indexing all solution files adds unnecessary overhead in some cases because some solution files don't appear to influence the generated SCIP index. However, the benefit of this change is that we can automatically index a repo without manual interaction from the user. If performance is an issue thne the user can manually specify which solution file to use.
1 parent b42bc52 commit 3e9bd1a

30 files changed

+240
-231
lines changed

.github/workflows/test.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ jobs:
2424
fail-fast: false
2525
matrix:
2626
os: [ubuntu-latest, windows-latest, macos-latest]
27-
name: Test
2827
steps:
2928
- uses: actions/checkout@v3
3029
- uses: actions/setup-dotnet@v3

ScipDotnet.Tests/SnapshotTests.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,20 +142,15 @@ private static string IndexDirectory(string directory)
142142
{
143143
FileName = "dotnet",
144144
Arguments = arguments,
145-
UseShellExecute = false,
146-
RedirectStandardOutput = true,
147145
WorkingDirectory = RootDirectory(),
148-
RedirectStandardError = true
149146
}
150147
};
151148
process.Start();
152149
process.WaitForExit();
153150
if (process.ExitCode != 0)
154151
{
155-
var stdout = process.StandardOutput.ReadToEnd();
156-
var stderr = process.StandardError.ReadToEnd();
157152
Assert.Fail(
158-
$"non-zero exit code {process.ExitCode} indexing {directory}\ndotnet {arguments}\n{stdout}{stderr}");
153+
$"non-zero exit code {process.ExitCode} indexing {directory}\ndotnet {arguments}");
159154
}
160155

161156
return Path.Join(directory, "index.scip");

ScipDotnet/IndexCommandHandler.cs

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,41 +11,42 @@ namespace ScipDotnet;
1111

1212
public static class IndexCommandHandler
1313
{
14-
public static async Task<int> Process(IHost host, FileInfo? projects, string output, FileInfo workingDirectory,
14+
public static async Task<int> Process(IHost host, List<FileInfo> projects, string output, FileInfo workingDirectory,
1515
List<string> include, List<string> exclude, bool allowGlobalSymbolDefinitions)
1616
{
1717
var logger = host.Services.GetRequiredService<ILogger<IndexCommandOptions>>();
1818
var matcher = new Matcher();
1919
matcher.AddIncludePatterns(include.Count == 0 ? new[] { "**" } : include);
2020
matcher.AddExcludePatterns(exclude);
2121

22-
var projectsFile = projects?.FullName ?? FindSolutionOrProjectFile(workingDirectory, logger);
23-
if (projectsFile == null)
22+
var projectFiles = projects.Count > 0
23+
? projects
24+
: FindSolutionOrProjectFile(workingDirectory, logger);
25+
if (!projectFiles.Any())
2426
{
2527
return 1;
2628
}
2729

28-
var options = new IndexCommandOptions(workingDirectory, OutputFile(workingDirectory, output),
29-
new FileInfo(projectsFile), logger, matcher,
30-
allowGlobalSymbolDefinitions);
30+
var options = new IndexCommandOptions(
31+
workingDirectory,
32+
OutputFile(workingDirectory, output),
33+
projectFiles,
34+
logger,
35+
matcher,
36+
allowGlobalSymbolDefinitions
37+
);
3138
await ScipIndex(host, options);
3239
return 0;
3340
}
3441

3542
private static FileInfo OutputFile(FileInfo workingDirectory, string output)
3643
{
37-
if (Path.IsPathRooted(output))
38-
{
39-
return new FileInfo(output);
40-
}
41-
42-
return new FileInfo(Path.Join(workingDirectory.FullName, output));
44+
return Path.IsPathRooted(output) ? new FileInfo(output) : new FileInfo(Path.Join(workingDirectory.FullName, output));
4345
}
4446

4547
private static async Task ScipIndex(IHost host, IndexCommandOptions options)
4648
{
4749
var stopwatch = Stopwatch.StartNew();
48-
options.Logger.LogInformation("Solution file {SolutionFileFullName}", options.ProjectsFile.FullName);
4950
var indexer = host.Services.GetRequiredService<ScipIndexer>();
5051
var index = new Scip.Index
5152
{
@@ -72,34 +73,26 @@ private static async Task ScipIndex(IHost host, IndexCommandOptions options)
7273

7374
private static string FixThisProblem(string examplePath)
7475
{
75-
return "To fix this problem, pass the path of a solution (.sln) or project (.csproj) file to the `scip-dotnet index` command. " +
76-
$"For example, run: scip-dotnet index {examplePath}";
76+
return
77+
"To fix this problem, pass the path of a solution (.sln) or project (.csproj) file to the `scip-dotnet index` command. " +
78+
$"For example, run: scip-dotnet index {examplePath}";
7779
}
78-
79-
private static string? FindSolutionOrProjectFile(FileInfo workingDirectory, ILogger<IndexCommandOptions> logger)
80+
81+
private static List<FileInfo> FindSolutionOrProjectFile(FileInfo workingDirectory, ILogger logger)
8082
{
8183
var paths = Directory.GetFiles(workingDirectory.FullName).Where(file =>
8284
string.Equals(Path.GetExtension(file), ".sln", StringComparison.OrdinalIgnoreCase) ||
8385
string.Equals(Path.GetExtension(file), ".csproj", StringComparison.OrdinalIgnoreCase)
8486
).ToList();
8587

86-
switch (paths.Count)
88+
if (paths.Count != 0)
8789
{
88-
case 0:
89-
logger.LogError(
90-
"No solution (.sln) or .csproj file detected in the working directory '{WorkingDirectory}'. {FixThis}",
91-
workingDirectory.FullName, FixThisProblem("SOLUTION_FILE"));
92-
return null;
93-
case 1:
94-
return paths.First();
90+
return paths.Select(path => new FileInfo(path)).ToList();
9591
}
9692

97-
var relativePaths = paths.Select(path => Path.GetRelativePath(workingDirectory.FullName, path)).ToList();
9893
logger.LogError(
99-
"Ambiguous solution files: {Join}. {FixThis}",
100-
String.Join(", ", relativePaths),
101-
FixThisProblem(relativePaths.First()));
102-
return null;
103-
94+
"No solution (.sln) or .csproj file detected in the working directory '{WorkingDirectory}'. {FixThis}",
95+
workingDirectory.FullName, FixThisProblem("SOLUTION_FILE"));
96+
return new List<FileInfo>();
10497
}
10598
}

ScipDotnet/IndexCommandOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace ScipDotnet;
66
public record IndexCommandOptions(
77
FileInfo WorkingDirectory,
88
FileInfo Output,
9-
FileInfo ProjectsFile,
9+
List<FileInfo> ProjectsFile,
1010
ILogger<IndexCommandOptions> Logger,
1111
Matcher Matcher,
1212
bool AllowGlobalSymbolDefinitions

ScipDotnet/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public static async Task<int> Main(string[] args)
2020
var indexCommand = new Command("index", "Index a solution file")
2121
{
2222
new Argument<FileInfo>("projects", "Path to the .sln (solution) or .csproj file")
23-
{ Arity = ArgumentArity.ZeroOrOne },
23+
{ Arity = ArgumentArity.ZeroOrMore },
2424
new Option<string>("--output", () => "index.scip",
2525
"Path to the output SCIP index file"),
2626
new Option<FileInfo>("--working-directory",

ScipDotnet/ScipCSharpSyntaxWalker.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,8 @@ private ScipSymbol CreateScipPackageSymbol(ISymbol sym)
243243
}
244244

245245

246-
return ScipSymbol.Package(sym.ContainingAssembly.Identity.Name,
246+
return ScipSymbol.Package(
247+
sym.ContainingAssembly.Identity.Name,
247248
sym.ContainingAssembly.Identity.Version.ToString());
248249
}
249250

ScipDotnet/ScipDotnet.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<PackageLicenseFile>LICENSE</PackageLicenseFile>
99
<PackageReadmeFile>readme.md</PackageReadmeFile>
1010
<PackageType>DotnetTool</PackageType>
11-
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
11+
<TargetFramework>net7.0</TargetFramework>
1212
<PackAsTool>true</PackAsTool>
1313
<AssemblyName>scip-dotnet</AssemblyName>
1414
<RepositoryUrl>https://github.com/sourcegraph/scip-dotnet</RepositoryUrl>

ScipDotnet/ScipIndexer.cs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,9 @@ public ScipIndexer(ILogger<ScipIndexer> logger)
2020

2121
private ILogger<ScipIndexer> Logger { get; }
2222

23-
private void Restore(IndexCommandOptions options)
23+
private static void Restore(IndexCommandOptions options, FileInfo project)
2424
{
25-
var arguments = options.ProjectsFile.FullName.EndsWith(".sln")
26-
? $"restore {options.ProjectsFile.FullName}"
27-
: "restore";
25+
var arguments = project.Extension.Equals(".sln") ? $"restore {project.FullName}" : "restore";
2826
var process = new Process()
2927
{
3028
StartInfo = new ProcessStartInfo()
@@ -40,15 +38,28 @@ private void Restore(IndexCommandOptions options)
4038

4139
public async IAsyncEnumerable<Scip.Document> IndexDocuments(IHost host, IndexCommandOptions options)
4240
{
43-
Restore(options);
44-
IEnumerable<Project> projects = string.Equals(options.ProjectsFile.Extension, ".csproj")
41+
var indexedProjects = new HashSet<ProjectId>();
42+
foreach (var project in options.ProjectsFile)
43+
{
44+
await foreach (var document in IndexProject(host, options, project, indexedProjects))
45+
{
46+
yield return document;
47+
}
48+
}
49+
}
50+
51+
private async IAsyncEnumerable<Scip.Document> IndexProject(IHost host, IndexCommandOptions options,
52+
FileInfo rootProject, HashSet<ProjectId> indexedProjects)
53+
{
54+
Restore(options, rootProject);
55+
IEnumerable<Project> projects = string.Equals(rootProject.Extension, ".csproj")
4556
? new[]
4657
{
4758
await host.Services.GetRequiredService<MSBuildWorkspace>()
48-
.OpenProjectAsync(options.ProjectsFile.FullName)
59+
.OpenProjectAsync(rootProject.FullName)
4960
}
5061
: (await host.Services.GetRequiredService<MSBuildWorkspace>()
51-
.OpenSolutionAsync(options.ProjectsFile.FullName)).Projects;
62+
.OpenSolutionAsync(rootProject.FullName)).Projects;
5263

5364
foreach (var project in projects)
5465
{
@@ -60,6 +71,13 @@ await host.Services.GetRequiredService<MSBuildWorkspace>()
6071
continue;
6172
}
6273

74+
if (indexedProjects.Contains(project.Id))
75+
{
76+
continue;
77+
}
78+
79+
indexedProjects.Add(project.Id);
80+
6381
var globals = new Dictionary<ISymbol, ScipSymbol>(SymbolEqualityComparer.Default);
6482

6583
foreach (var document in project.Documents)

ScipDotnet/ScipSymbol.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ public static ScipSymbol Global(ScipSymbol owner, SymbolDescriptor descriptor)
2626
}
2727

2828

29-
3029
public static ScipSymbol Local(int counter)
3130
{
3231
return new ScipSymbol("local " + counter);
@@ -37,7 +36,12 @@ public static ScipSymbol Local(int counter)
3736

3837
public static ScipSymbol Package(string name, string version)
3938
{
40-
return new ScipSymbol("scip-dotnet nuget " + name + " " + version + " ");
39+
return new ScipSymbol(
40+
"scip-dotnet nuget " +
41+
// SCIP package names and versions should use double-space to escape space characters.
42+
name.Replace(" ", " ") + " " +
43+
version.Replace(" ", " ") + " "
44+
);
4145
}
4246

4347
private static string DescriptorString(SymbolDescriptor desc)
@@ -75,5 +79,4 @@ private static bool IsSimpleIdentifier(string name)
7579
{
7680
return Regex.IsMatch(name, @"^[\w$+-]+$", RegexOptions.IgnoreCase);
7781
}
78-
79-
}
82+
}

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ cd scip-dotnet
7272
Finally, use `dotnet run` to run the indexer locally.
7373

7474
```shell
75-
dotnet run --framework net6.0 --project ScipDotnet -- index --working-directory PATH_TO_DIRECTORY_YOU_WANT_TO_INDEX
75+
dotnet run --project ScipDotnet -- index --working-directory PATH_TO_DIRECTORY_YOU_WANT_TO_INDEX
7676
```
7777

7878
Once scip-dotnet has finished indexing the project, there should be an

0 commit comments

Comments
 (0)