Skip to content

Commit

Permalink
Delay lookup of dotnet cli exe path and fall back to finding dotnet o…
Browse files Browse the repository at this point in the history
…n PATH

closes #1189
  • Loading branch information
belav committed Mar 1, 2024
1 parent be54f7d commit 0571298
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 33 deletions.
4 changes: 4 additions & 0 deletions Src/CSharpier.Rider/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

# csharpier-rider Changelog

## [1.6.1]
- Delay lookup of '.NET CLI executable path' until it is needed
- Fall back to looking for dotnet on PATH if '.NET CLI executable path' is not available

## [1.6.0]
- Better support for dotnet commands.
- Uses the Rider setting for '.NET CLI executable path' for running dotnet commands
Expand Down
2 changes: 1 addition & 1 deletion Src/CSharpier.Rider/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

pluginGroup = com.intellij.csharpier
pluginName = csharpier
pluginVersion = 1.6.0
pluginVersion = 1.6.1

# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
# for insight into build numbers and IntelliJ Platform versions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private void startProcess() {
try {
var processBuilder = new ProcessBuilder(this.csharpierPath, "--pipe-multiple-files");
processBuilder.environment().put("DOTNET_NOLOGO", "1");
processBuilder.environment().put("DOTNET_ROOT", this.dotNetProvider.getDotNetROot());
processBuilder.environment().put("DOTNET_ROOT", this.dotNetProvider.getDotNetRoot());
this.process = processBuilder.start();

var charset = this.useUtf8 ? "utf-8" : Charset.defaultCharset().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,6 @@ public CSharpierProcessProvider(@NotNull Project project) {
this.project = project;
this.customPathInstaller = new CustomPathInstaller(project);

for (var fileEditor : FileEditorManager.getInstance(project).getAllEditors()) {
var path = fileEditor.getFile().getPath();
if (path.toLowerCase().endsWith(".cs")) {
this.findAndWarmProcess(path);
}
}

EditorFactory.getInstance().getEventMulticaster().addDocumentListener(this, this);
}

Expand All @@ -72,6 +65,9 @@ public void documentChanged(@NotNull DocumentEvent event) {
}

private void findAndWarmProcess(String filePath) {
if (!DotNetProvider.getInstance(this.project).foundDotNet()) {
return;
}
var directory = Path.of(filePath).getParent().toString();
var now = Instant.now().toEpochMilli();
var lastWarmed = this.lastWarmedByDirectory.getOrDefault(directory, Long.valueOf(0));
Expand All @@ -98,6 +94,10 @@ private void findAndWarmProcess(String filePath) {
}

public ICSharpierProcess getProcessFor(String filePath) {
if (!DotNetProvider.getInstance(this.project).foundDotNet()) {
return NullCSharpierProcess.Instance;
}

var directory = Path.of(filePath).getParent().toString();
var version = this.csharpierVersionByDirectory.getOrDefault(directory, null);
if (version == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ private void startProcess() {
var processBuilder = new ProcessBuilder(this.csharpierPath, "--server");
processBuilder.redirectErrorStream(true);
processBuilder.environment().put("DOTNET_NOLOGO", "1");
processBuilder.environment().put("DOTNET_ROOT", this.dotNetProvider.getDotNetROot());
processBuilder.environment().put("DOTNET_ROOT", this.dotNetProvider.getDotNetRoot());
this.process = processBuilder.start();

var reader = new BufferedReader(new InputStreamReader(this.process.getInputStream()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public String formatFile(String content, String fileName) {
this.logger.debug("Running " + this.csharpierPath + " --write-stdout");
var processBuilder = new ProcessBuilder(this.csharpierPath, "--write-stdout");
processBuilder.environment().put("DOTNET_NOLOGO", "1");
processBuilder.environment().put("DOTNET_ROOT", this.dotNetProvider.getDotNetROot());
processBuilder.environment().put("DOTNET_ROOT", this.dotNetProvider.getDotNetRoot());
processBuilder.redirectErrorStream(true);
var process = processBuilder.start();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ public void setUseServer(boolean useServer) {
this.useServer = useServer;
}

private boolean skipCliExePath;

public boolean getSkipCliExePath() {
return this.skipCliExePath;
}

public void setSkipCliExePath(boolean skipCliExePath) {
this.skipCliExePath = skipCliExePath;
}

@Override
public CSharpierSettings getState() {
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class CSharpierSettingsComponent implements SearchableConfigurable {
private final Project project;
private JBCheckBox runOnSaveCheckBox = new JBCheckBox("Run on Save");
private JBCheckBox useServerCheckBox = new JBCheckBox("Use CSharpier Server - experimental support as of 0.27.2");
private JBCheckBox skipCliExePath = new JBCheckBox("Skip CLI executable path");
private JBTextField customPathTextField = new JBTextField();

public CSharpierSettingsComponent(@NotNull Project project) {
Expand Down Expand Up @@ -77,6 +78,7 @@ private JComponent createSectionHeader(String label) {
.setFormLeftIndent(leftIndent)
.addLabeledComponent(new JBLabel("Directory of custom dotnet-csharpier:"), this.customPathTextField, topInset, false)
.addComponent(this.useServerCheckBox, topInset)
.addComponent(this.skipCliExePath, topInset)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
Expand All @@ -85,6 +87,7 @@ private JComponent createSectionHeader(String label) {
public boolean isModified() {
return CSharpierSettings.getInstance(this.project).getRunOnSave() != this.runOnSaveCheckBox.isSelected()
|| CSharpierSettings.getInstance(this.project).getUseServer() != this.useServerCheckBox.isSelected()
|| CSharpierSettings.getInstance(this.project).getSkipCliExePath() != this.skipCliExePath.isSelected()
|| CSharpierSettings.getInstance(this.project).getCustomPath() != this.customPathTextField.getText();
}

Expand All @@ -95,6 +98,7 @@ public void apply() throws ConfigurationException {
settings.setRunOnSave(this.runOnSaveCheckBox.isSelected());
settings.setCustomPath(this.customPathTextField.getText());
settings.setUseServer(this.useServerCheckBox.isSelected());
settings.setSkipCliExePath(this.skipCliExePath.isSelected());
}

@Override
Expand All @@ -103,5 +107,6 @@ public void reset() {
this.runOnSaveCheckBox.setSelected(settings.getRunOnSave());
this.useServerCheckBox.setSelected(settings.getUseServer());
this.customPathTextField.setText(settings.getCustomPath());
this.skipCliExePath.setSelected(settings.getSkipCliExePath());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
public class CSharpierStartup implements StartupActivity, DumbAware {
@Override
public void runActivity(@NotNull Project project) {
DotNetProvider.getInstance(project);
CSharpierProcessProvider.getInstance(project);
ApplicationManager.getApplication().getService(ReformatWithCSharpierOnSave.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import java.io.File;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -51,7 +50,7 @@ public boolean ensureVersionInstalled(String version) throws Exception {

private boolean validateInstall(String pathToDirectoryForVersion, String version) {
try {
var env = Map.of("DOTNET_ROOT", this.dotNetProvider.getDotNetROot());
var env = Map.of("DOTNET_ROOT", this.dotNetProvider.getDotNetRoot());

var command = List.of(this.getPathForVersion(version), "--version" );
var output = ProcessHelper.executeCommand(command, env, new File(pathToDirectoryForVersion)).trim();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.intellij.csharpier;

import com.intellij.openapi.diagnostic.Logger;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

public class DotNetFinder {

public static String findOnPath(Logger logger) {
logger.debug("Trying to find dotnet on PATH");

var env = Map.of(
"DOTNET_CLI_UI_LANGUAGE", "en-US",
"DOTNET_NOLOGO", "1",
"DOTNET_CLI_TELEMETRY_OPTOUT", "1",
"DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "1");
var dotnetInfo = ProcessHelper.executeCommand(List.of("dotnet", "--info"), env, null);
if (dotnetInfo == null) {
return null;
}

String version = null;

String[] lines = dotnetInfo.split("\\r?\\n");
for (var line : lines) {
var pattern = Pattern.compile("^\\s*Version:\\s*([^\\s].*)$");
var matcher = pattern.matcher(line);

if (matcher.find()) {
version = matcher.group(1);
}
}

if (version == null) {
return null;
}

var runtimesOutput = ProcessHelper.executeCommand(List.of("dotnet", "--list-runtimes"), env, null);
var runtimeVersions = new HashMap<String, List<RuntimeInfo>>();
lines = runtimesOutput.split("\\r?\\n");
for (var line : lines) {
var pattern = Pattern.compile("^([\\w.]+) ([^ ]+) \\[([^\\]]+)\\]$");
var matcher = pattern.matcher(line);

if (matcher.find()) {
var runtime = matcher.group(1);
var runtimeVersion = matcher.group(2);
var path = matcher.group(3);

if (!runtimeVersions.containsKey(runtime)) {
var versions = new ArrayList<RuntimeInfo>();
runtimeVersions.put(runtime, versions);
}

var runtimeInfo = new RuntimeInfo();
runtimeInfo.Version = runtimeVersion;
runtimeInfo.Path = path;
runtimeVersions.get(runtime).add(runtimeInfo);
}
}

return findDotNetFromRuntimes(runtimeVersions, logger);
}

private static String findDotNetFromRuntimes(HashMap<String, List<RuntimeInfo>> runtimes, Logger logger) {
var requiredRuntimeVersion = "6.0.0";

var coreRuntimeVersions = runtimes.get("Microsoft.NETCore.App");
RuntimeInfo matchingRuntime = null;
for (var runtime : coreRuntimeVersions) {
// We consider a match if the runtime is greater than or equal to the required version since we roll forward.
if (compareVersions(runtime.Version, requiredRuntimeVersion) > 0) {
logger.debug("Using " + runtime.Path + " with version " + runtime.Version);
matchingRuntime = runtime;
break;
}
}

if (matchingRuntime == null) {
return null;
}

// The .NET install layout is a well known structure on all platforms.
// See https://github.com/dotnet/designs/blob/main/accepted/2020/install-locations.md#net-core-install-layout
//
// Therefore we know that the runtime path is always in <install root>/shared/<runtime name>
// and the dotnet executable is always at <install root>/dotnet(.exe).
//
// Since dotnet --list-runtimes will always use the real assembly path to output the runtime folder (no symlinks!)
// we know the dotnet executable will be two folders up in the install root.
var runtimeFolderPath = matchingRuntime.Path;
var installFolder = Paths.get(runtimeFolderPath).getParent().getParent().toString();
var dotnetExecutablePath = Paths.get(installFolder, System.getProperty("os.name").contains("Windows") ? "dotnet.exe" : "dotnet").toString();

if (!Files.exists(Paths.get(dotnetExecutablePath))) {
throw new RuntimeException(String.format("dotnet executable path does not exist: %s, dotnet installation may be corrupt.", dotnetExecutablePath));
}

return dotnetExecutablePath;
}

private static int compareVersions(String version1, String version2) {
String[] parts1 = version1.split("\\.");
String[] parts2 = version2.split("\\.");

int minLength = Math.min(parts1.length, parts2.length);

for (int i = 0; i < minLength; i++) {
int part1 = Integer.parseInt(parts1[i]);
int part2 = Integer.parseInt(parts2[i]);

if (part1 < part2) {
return -1;
} else if (part1 > part2) {
return 1;
}
}

return Integer.compare(parts1.length, parts2.length);
}
}

class RuntimeInfo {
public String Version;
public String Path;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,17 @@
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.jetbrains.rider.runtime.RiderDotNetActiveRuntimeHost;
import com.jetbrains.rider.runtime.dotNetCore.DotNetCoreRuntime;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Map;
import java.util.List;
import java.util.*;

public class DotNetProvider {
private final Logger logger = CSharpierLogger.getInstance();
private final Project project;
private String dotNetRoot;
private DotNetCoreRuntime dotNetCoreRuntime;
private String cliExePath;

public DotNetProvider(@NotNull Project project) {
this.project = project;
Expand All @@ -27,9 +24,8 @@ public DotNetProvider(@NotNull Project project) {
if (!foundDotNet) {

var title = "CSharpier unable to run dotnet commands";
var message = "CSharpier was unable to determine how to run dotnet commands. Ensure that '.NET CLI executable path' is set properly in your settings and restart.";
var notification = NotificationGroupManager.getInstance().getNotificationGroup("CSharpier")
.createNotification(title, message, NotificationType.WARNING);
var message = "CSharpier was unable to determine how to run dotnet commands. Ensure that '.NET CLI executable path' is set properly in your settings or dotnet is available on PATH and restart.";
var notification = NotificationGroupManager.getInstance().getNotificationGroup("CSharpier").createNotification(title, message, NotificationType.WARNING);
notification.notify(this.project);
}
}
Expand All @@ -40,15 +36,23 @@ static DotNetProvider getInstance(@NotNull Project project) {

private boolean findDotNet() {
try {
this.dotNetCoreRuntime = RiderDotNetActiveRuntimeHost.Companion.getInstance(project).getDotNetCoreRuntime().getValue();
var dotNetCoreRuntime = RiderDotNetActiveRuntimeHost.Companion.getInstance(project).getDotNetCoreRuntime().getValue();

if (dotNetCoreRuntime.getCliExePath() != null) {
logger.debug("Using dotnet found from RiderDotNetActiveRuntimeHost at " + dotNetCoreRuntime.getCliExePath());
if (!CSharpierSettings.getInstance(this.project).getSkipCliExePath()
&& dotNetCoreRuntime != null
&& dotNetCoreRuntime.getCliExePath() != null) {
this.logger.debug("Using dotnet found from RiderDotNetActiveRuntimeHost at " + dotNetCoreRuntime.getCliExePath());
this.cliExePath = dotNetCoreRuntime.getCliExePath();
} else {
return false;
this.cliExePath = DotNetFinder.findOnPath(this.logger);

if (this.cliExePath == null) {
return false;
}
this.logger.debug("Found dotnet at " + this.cliExePath);
}

dotNetRoot = Paths.get(dotNetCoreRuntime.getCliExePath()).getParent().toString();
this.dotNetRoot = Paths.get(this.cliExePath).getParent().toString();

return true;
} catch (Exception ex) {
Expand All @@ -60,17 +64,20 @@ private boolean findDotNet() {

public String execDotNet(List<String> command, File workingDirectory) {
var commands = new ArrayList<>(command);
commands.add(0, this.dotNetCoreRuntime.getCliExePath());
commands.add(0, this.cliExePath);

var env = Map.of(
"DOTNET_NOLOGO", "1",
"DOTNET_CLI_TELEMETRY_OPTOUT", "1",
"DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "1");
var env = Map.of("DOTNET_NOLOGO", "1", "DOTNET_CLI_TELEMETRY_OPTOUT", "1", "DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "1");

return ProcessHelper.executeCommand(commands, env, workingDirectory);
}

public String getDotNetROot() {
public String getDotNetRoot() {
return this.dotNetRoot;
}

public boolean foundDotNet() {
return this.cliExePath != null;
}


}

0 comments on commit 0571298

Please sign in to comment.