Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Capture any unhandled error during install command #126

Merged
merged 18 commits into from
Jul 16, 2019
Merged
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
35 changes: 34 additions & 1 deletion Conan.VisualStudio.Core/ConanPathHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
Expand All @@ -23,6 +25,37 @@ public static string GetRelativePath(string baseDirectory, string location)
var relativeUri = baseUri.MakeRelativeUri(locationUri);
return Uri.UnescapeDataString(relativeUri.ToString()).Replace('/', Path.DirectorySeparatorChar);
}
public static bool ValidateConanExecutable(string exe, out string errorMessage)
{
errorMessage = null;
if (exe == null || exe == "")
return true;
try
{
var startInfo = new ProcessStartInfo
{
FileName = exe,
Arguments = "--version",
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true
};
var process = Process.Start(startInfo);

process.WaitForExit();

if (0 != process.ExitCode)
errorMessage = $"invalid conan executable {exe}: conan --version failed with error {process.ExitCode}";

return 0 == process.ExitCode;
}
catch (Win32Exception e)
{
errorMessage = $"invalid conan executable {exe}: {e.Message}";
return false;
}
}

public static string DetermineConanPathFromEnvironment()
{
Expand All @@ -36,7 +69,7 @@ public static string DetermineConanPathFromEnvironment()
{
var fileName = Path.ChangeExtension("conan", extension);
var filePath = Path.Combine(directory, fileName);
if (File.Exists(filePath))
if (File.Exists(filePath) && ValidateConanExecutable(filePath, out string errorMessage))
{
// to get the proper file name case:
return Directory.GetFiles(directory, fileName).Single();
Expand Down
10 changes: 5 additions & 5 deletions Conan.VisualStudio.Tests/Core/ConanPathHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void ConanPathIsDeterminedAutomatically()
{
var directory = FileSystemUtils.CreateTempDirectory();
const string extension = ".cmd";
var conanShim = FileSystemUtils.CreateTempFile(directory, "conan" + extension);
var conanShim = FileSystemUtils.CreateTempFile(directory, "conan" + extension, "exit 0");

Environment.SetEnvironmentVariable("PATH", directory);
Environment.SetEnvironmentVariable("PATHEXT", extension);
Expand All @@ -36,16 +36,16 @@ public void ConanPathIsDeterminedAutomatically()
public void PathDeterminerRespectPathExtOrder()
{
var directory = FileSystemUtils.CreateTempDirectory();
var comShim = FileSystemUtils.CreateTempFile(directory, "conan.com");
var comShim = FileSystemUtils.CreateTempFile(directory, "conan.cmd", "exit 0");
FileSystemUtils.CreateTempFile(directory, "conan.exe");
var batShim = FileSystemUtils.CreateTempFile(directory, "conan.bat");
var batShim = FileSystemUtils.CreateTempFile(directory, "conan.bat", "exit 0");

Environment.SetEnvironmentVariable("PATH", directory);

Environment.SetEnvironmentVariable("PATHEXT", ".COM;.EXE;.BAT");
Environment.SetEnvironmentVariable("PATHEXT", ".CMD;.EXE;.BAT");
Assert.AreEqual(comShim, ConanPathHelper.DetermineConanPathFromEnvironment());

Environment.SetEnvironmentVariable("PATHEXT", ".BAT;.EXE;.COM");
Environment.SetEnvironmentVariable("PATHEXT", ".BAT;.EXE;.CMD");
Assert.AreEqual(batShim, ConanPathHelper.DetermineConanPathFromEnvironment());
}

Expand Down
7 changes: 5 additions & 2 deletions Conan.VisualStudio.Tests/FileSystemUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ public static string CreateTempDirectory()
return path;
}

public static string CreateTempFile(string directory, string name)
public static string CreateTempFile(string directory, string name, string contents = "")
{
var path = Path.Combine(directory, name);
File.Create(path).Close();
FileStream f = File.Create(path);
byte[] info = new UTF8Encoding(true).GetBytes(contents);
f.Write(info, 0, info.Length);
f.Close();
return path;
}

Expand Down
25 changes: 25 additions & 0 deletions Conan.VisualStudio/ConanOptionsPage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.ComponentModel;
using System.Windows.Forms;
using Conan.VisualStudio.Core;
using Microsoft.VisualStudio.Shell;

Expand All @@ -14,6 +16,29 @@ public class ConanOptionsPage : DialogPage
private ConanBuildType? _conanBuild;
private bool? _conanUpdate;

protected override void OnApply(PageApplyEventArgs e)
{
if (!ValidateConanExecutableAndShowMessage(_conanExecutablePath))
{
e.ApplyBehavior = ApplyKind.Cancel;
}
else
{
base.OnApply(e);
}
}

private bool ValidateConanExecutableAndShowMessage(string exe)
{
if (!ConanPathHelper.ValidateConanExecutable(exe, out string errorMessage))
{
MessageBox.Show(errorMessage, "Conan extension: invalid conan executable",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
return true;
}

[Category("Conan")]
[DisplayName("Conan executable")]
[Description(@"Path to the Conan executable file, like C:\Python27\Scripts\conan.exe")]
Expand Down
7 changes: 5 additions & 2 deletions Conan.VisualStudio/Menu/AddConanDependsProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ protected internal override async Task MenuItemCallbackAsync()
return;
}

await _conanService.InstallAsync(vcProject);
await _conanService.IntegrateAsync(vcProject);
bool success = await _conanService.InstallAsync(vcProject);
if (success)
{
await _conanService.IntegrateAsync(vcProject);
}
}
}
}
7 changes: 5 additions & 2 deletions Conan.VisualStudio/Menu/AddConanDependsSolution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ protected internal override async Task MenuItemCallbackAsync()
{
if (_vcProjectService.IsConanProject(project))
{
await _conanService.InstallAsync(_vcProjectService.AsVCProject(project));
await _conanService.IntegrateAsync(_vcProjectService.AsVCProject(project));
bool success = await _conanService.InstallAsync(_vcProjectService.AsVCProject(project));
if (success)
{
await _conanService.IntegrateAsync(_vcProjectService.AsVCProject(project));
}
}
}
await TaskScheduler.Default;
Expand Down
91 changes: 53 additions & 38 deletions Conan.VisualStudio/Services/ConanService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Threading;
Expand Down Expand Up @@ -87,26 +88,26 @@ public async Task IntegrateAsync(VCProject vcProject)
}
}

public async Task InstallAsync(VCProject vcProject)
public async Task<bool> InstallAsync(VCProject vcProject)
{
var conanPath = _settingsService.GetConanExecutablePath();
if (conanPath == null)
if (conanPath == null || conanPath == "")
{
_errorListService.WriteError(
"Conan executable path is not set and Conan executable wasn't found automatically. " +
"Please set it up in the Tools → Settings → Conan menu.");
return;
return false;
}

var project = await _vcProjectService.ExtractConanProjectAsync(vcProject, _settingsService);
if (project == null)
{
_errorListService.WriteError("Unable to extract conan project!");
return;
return false;
}
var conan = new ConanRunner(_settingsService.LoadSettingFile(project), conanPath);

await InstallDependenciesAsync(conan, project);
return await InstallDependenciesAsync(conan, project);
}
private static void AppendLinesFunc(object packedParams)
{
Expand All @@ -125,7 +126,7 @@ private static void AppendLinesFunc(object packedParams)
}
}

private async Task InstallDependenciesAsync(ConanRunner conan, ConanProject project)
private async Task<bool> InstallDependenciesAsync(ConanRunner conan, ConanProject project)
{
foreach (var configuration in project.Configurations)
{
Expand All @@ -139,51 +140,65 @@ private async Task InstallDependenciesAsync(ConanRunner conan, ConanProject proj
ConanGeneratorType generator = _settingsService.GetConanGenerator();
ConanBuildType build = _settingsService.GetConanBuild();
bool update = _settingsService.GetConanUpdate();

ProcessStartInfo process = conan.Install(project, configuration, generator, build, update, _errorListService);

string message = $"[Conan.VisualStudio] Calling process '{process.FileName}' " +
$"with arguments '{process.Arguments}'";
Logger.Log(message);
await logStream.WriteLineAsync(message);

using (Process exeProcess = Process.Start(process))
try
{
int exitCode = await exeProcess.WaitForExitAsync();

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

Task outputReader = Task.Factory.StartNew(AppendLinesFunc,
Tuple.Create(logStream, exeProcess.StandardOutput),
token, TaskCreationOptions.None, TaskScheduler.Default);
Task errorReader = Task.Factory.StartNew(AppendLinesFunc,
Tuple.Create(logStream, exeProcess.StandardError),
token, TaskCreationOptions.None, TaskScheduler.Default);

Task.WaitAll(outputReader, errorReader);

if (exitCode != 0) {
message = $"Conan has returned exit code '{exitCode}' " +
$"while processing configuration '{configuration}'. " +
$"Please check file '{logFilePath}' for details.";

Logger.Log(message);
await logStream.WriteLineAsync(message);
_errorListService.WriteError(message, logFilePath);
return;
}
else
using (Process exeProcess = Process.Start(process))
{
message = $"[Conan.VisualStudio] Conan has succsessfully " +
$"installed configuration '{configuration}'";
Logger.Log(message);
await logStream.WriteLineAsync(message);
_errorListService.WriteMessage(message);
int exitCode = await exeProcess.WaitForExitAsync();

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

Task outputReader = Task.Factory.StartNew(AppendLinesFunc,
Tuple.Create(logStream, exeProcess.StandardOutput),
token, TaskCreationOptions.None, TaskScheduler.Default);
Task errorReader = Task.Factory.StartNew(AppendLinesFunc,
Tuple.Create(logStream, exeProcess.StandardError),
token, TaskCreationOptions.None, TaskScheduler.Default);

Task.WaitAll(outputReader, errorReader);

if (exitCode != 0)
{
message = $"Conan has returned exit code '{exitCode}' " +
$"while processing configuration '{configuration}'. " +
$"Please check file '{logFilePath}' for details.";

Logger.Log(message);
await logStream.WriteLineAsync(message);
_errorListService.WriteError(message, logFilePath);
return false;
}
else
{
message = $"[Conan.VisualStudio] Conan has succsessfully " +
$"installed configuration '{configuration}'";
Logger.Log(message);
await logStream.WriteLineAsync(message);
_errorListService.WriteMessage(message);
}
}
}
catch(System.ComponentModel.Win32Exception e)
{
message = $"[Conan.VisualStudio] Unhandled error running '{process.FileName}'" +
$": {e.Message}. Check log file '{logFilePath}' for details";
Logger.Log(message);
await logStream.WriteLineAsync(message);
_errorListService.WriteError(message);
return false;
}
}
}
return true;
}
}
}
2 changes: 1 addition & 1 deletion Conan.VisualStudio/Services/IConanService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ public interface IConanService
{
Task IntegrateAsync(VCProject vcProject);

Task InstallAsync(VCProject vcProject);
Task<bool> InstallAsync(VCProject vcProject);
}
}
7 changes: 5 additions & 2 deletions Conan.VisualStudio/VSConanPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,11 @@ private void InstallConanDeps(VCProject vcProject)
ThreadHelper.JoinableTaskFactory.RunAsync(
async delegate
{
await _conanService.InstallAsync(vcProject);
await _conanService.IntegrateAsync(vcProject);
bool success = await _conanService.InstallAsync(vcProject);
if (success)
{
await _conanService.IntegrateAsync(vcProject);
}
}
);
}
Expand Down