Example #1
0
        /// <summary>
        /// Create a build operation that will create a directory
        /// </summary>
        public static BuildOperation CreateCreateDirectoryOperation(
            Path workingDirectory,
            Path directory)
        {
            if (directory.HasFileName())
            {
                throw new ArgumentException("Cannot create a directory with a filename.");
            }

            var title = $"MakeDir [{directory}]";

            var moduleName   = LifetimeManager.Get <IProcessManager>().GetCurrentProcessFileName();
            var moduleFolder = moduleName.GetParent().GetParent().GetParent().GetParent();

            var program    = moduleFolder + new Path("mkdir.exe");
            var inputFiles = new List <Path>()
            {
            };
            var outputFiles = new List <Path>()
            {
                directory,
            };

            // Build the arguments
            var arguments = $"\"{directory}\"";

            return(new BuildOperation(
                       title,
                       workingDirectory,
                       program,
                       arguments,
                       inputFiles,
                       outputFiles));
        }
Example #2
0
        /// <summary>
        /// Create a build operation that will copy a file
        /// </summary>
        public static BuildOperation CreateCopyFileOperation(
            Path workingDirectory,
            Path source,
            Path destination)
        {
            var title = $"Copy [{source}] -> [{destination}]";

            var moduleName   = LifetimeManager.Get <IProcessManager>().GetCurrentProcessFileName();
            var moduleFolder = moduleName.GetParent().GetParent().GetParent().GetParent();

            var program    = moduleFolder + new Path("copy.exe");
            var inputFiles = new List <Path>()
            {
                source,
            };
            var outputFiles = new List <Path>()
            {
                destination,
            };

            // Build the arguments
            var arguments = $"\"{source}\" \"{destination}\"";

            return(new BuildOperation(
                       title,
                       workingDirectory,
                       program,
                       arguments,
                       inputFiles,
                       outputFiles));
        }
        /// <summary>
        /// Save the config to file
        /// </summary>
        public static async Task SaveToFileAsync(
            Path configFile,
            LocalUserConfig config)
        {
            // Ensure the parent folder exists
            var folder = configFile.GetParent();

            if (!LifetimeManager.Get <IFileSystem>().Exists(folder))
            {
                Log.Info($"Creating directory {folder}");
                LifetimeManager.Get <IFileSystem>().CreateDirectory2(folder);
            }

            // Open the file to write to
            var file = LifetimeManager.Get <IFileSystem>().OpenWrite(configFile, true);

            // Serialize the contents of the recipe
            var documentSyntax = config.MirrorSyntax;

            if (documentSyntax == null)
            {
                throw new ArgumentException("The provided config does not have a mirrored syntax tree.", nameof(config));
            }

            // Write the recipe to the file stream
            await ValueTableTomlUtilities.SerializeAsync(documentSyntax, file.GetOutStream());
        }
        /// <summary>
        /// Attempt to load from file
        /// </summary>
        public static async Task <(bool IsSuccess, LocalUserConfig Result)> TryLoadLocalUserConfigFromFileAsync(
            Path localUserConfigFile)
        {
            // Verify the requested file exists
            Log.Diag("Load Local User Config: " + localUserConfigFile.ToString());
            if (!LifetimeManager.Get <IFileSystem>().Exists(localUserConfigFile))
            {
                Log.Warning("Local User Config file does not exist.");
                return(false, new LocalUserConfig());
            }

            // Open the file to read from
            var file = LifetimeManager.Get <IFileSystem>().OpenRead(localUserConfigFile);

            // Open the file to read from
            using (var reader = new System.IO.StreamReader(file.GetInStream()))
            {
                // Read the contents of the local user config file
                try
                {
                    var result = ValueTableTomlUtilities.Deserialize(
                        localUserConfigFile,
                        await reader.ReadToEndAsync());
                    return(true, new LocalUserConfig(result));
                }
                catch (Exception ex)
                {
                    Log.Error("Deserialize Threw: " + ex.Message);
                    Log.Info("Failed to parse local user config.");
                    return(false, new LocalUserConfig());
                }
            }
        }
Example #5
0
        /// <summary>
        /// Ensure the staging directory exists
        /// </summary>
        static Path EnsureStagingDirectoryExists(Path packageStore)
        {
            var stagingDirectory = packageStore + new Path(StagingFolderName);

            if (LifetimeManager.Get <IFileSystem>().Exists(stagingDirectory))
            {
                Log.Warning("The staging directory already exists from a previous failed run");
                LifetimeManager.Get <IFileSystem>().DeleteDirectory(stagingDirectory, true);
            }

            // Create the folder
            LifetimeManager.Get <IFileSystem>().CreateDirectory2(stagingDirectory);

            return(stagingDirectory);
        }
Example #6
0
 private static void AddAllFilesRecursive(Path directory, Path workingDirectory, ZipArchive archive)
 {
     foreach (var child in LifetimeManager.Get <IFileSystem>().GetDirectoryChildren(directory))
     {
         if (child.IsDirectory)
         {
             AddAllFilesRecursive(child.Path, workingDirectory, archive);
         }
         else
         {
             var relativePath = child.Path.GetRelativeTo(workingDirectory);
             var relativeName = relativePath.ToString().Substring(2);
             var fileEentry   = archive.CreateEntryFromFile(child.Path.ToString(), relativeName);
         }
     }
 }
Example #7
0
        /// <summary>
        /// Save the recipe to file
        /// </summary>
        public static async Task SaveToFileAsync(
            Path recipeFile,
            Recipe recipe)
        {
            // Open the file to write to
            var file = LifetimeManager.Get <IFileSystem>().OpenWrite(recipeFile, true);

            // Serialize the contents of the recipe
            var documentSyntax = recipe.MirrorSyntax;

            if (documentSyntax == null)
            {
                throw new ArgumentException("The provided recipe does not have a mirrored syntax tree.", nameof(recipe));
            }

            // Write the recipe to the file stream
            await ValueTableTomlUtilities.SerializeAsync(documentSyntax, file.GetOutStream());
        }
Example #8
0
        /// <summary>
        /// Install a package
        /// </summary>
        public static async Task InstallPackageReferenceAsync(Path workingDirectory, string packageReference)
        {
            var recipePath =
                workingDirectory +
                BuildConstants.RecipeFileName;

            var(isSuccess, recipe) = await RecipeExtensions.TryLoadRecipeFromFileAsync(recipePath);

            if (!isSuccess)
            {
                throw new InvalidOperationException("Could not load the recipe file.");
            }

            var packageStore = LifetimeManager.Get <IFileSystem>().GetUserProfileDirectory() +
                               new Path(".soup/packages/");

            Log.Info("Using Package Store: " + packageStore.ToString());

            // Create the staging directory
            var stagingPath = EnsureStagingDirectoryExists(packageStore);

            try
            {
                // Parse the package reference to get the name
                var    targetPackageReference = PackageReference.Parse(packageReference);
                string packageName            = packageReference;
                if (!targetPackageReference.IsLocal)
                {
                    packageName = targetPackageReference.GetName;
                }

                // Check if the package is already installed
                var packageNameNormalized = packageName.ToUpperInvariant();
                if (recipe.HasRuntimeDependencies)
                {
                    foreach (var dependency in recipe.RuntimeDependencies)
                    {
                        if (!dependency.IsLocal)
                        {
                            var dependencyNameNormalized = dependency.GetName.ToUpperInvariant();
                            if (dependencyNameNormalized == packageNameNormalized)
                            {
                                Log.Warning("Package already installed.");
                                return;
                            }
                        }
                    }
                }

                // Get the latest version if no version provided
                if (targetPackageReference.IsLocal)
                {
                    var packageModel = await GetPackageModelAsync(recipe.Language, packageName);

                    var latestVersion = new SemanticVersion(packageModel.Latest.Major, packageModel.Latest.Minor, packageModel.Latest.Patch);
                    Log.HighPriority("Latest Version: " + latestVersion.ToString());
                    targetPackageReference = new PackageReference(packageModel.Name, latestVersion);
                }

                var closure = new Dictionary <string, IDictionary <string, PackageReference> >();
                await CheckRecursiveEnsurePackageDownloadedAsync(
                    recipe.Language,
                    targetPackageReference.GetName,
                    targetPackageReference.Version,
                    packageStore,
                    stagingPath,
                    closure);

                // Cleanup the working directory
                Log.Info("Deleting staging directory");
                LifetimeManager.Get <IFileSystem>().DeleteDirectory(stagingPath, true);

                // Register the package in the recipe
                Log.Info("Adding reference to recipe");
                recipe.AddRuntimeDependency(targetPackageReference.ToString());

                // Save the state of the recipe
                await RecipeExtensions.SaveToFileAsync(recipePath, recipe);
            }
            catch (Exception)
            {
                // Cleanup the staging directory and accept that we failed
                LifetimeManager.Get <IFileSystem>().DeleteDirectory(stagingPath, true);
                throw;
            }
        }
Example #9
0
        /// <summary>
        /// Ensure a package version is downloaded
        /// </summary>
        private static async Task EnsurePackageDownloadedAsync(
            string languageName,
            string packageName,
            SemanticVersion packageVersion,
            Path packagesDirectory,
            Path stagingDirectory)
        {
            Log.HighPriority($"Install Package: {languageName} {packageName}@{packageVersion}");

            var languageRootFolder   = packagesDirectory + new Path(languageName);
            var packageRootFolder    = languageRootFolder + new Path(packageName);
            var packageVersionFolder = packageRootFolder + new Path(packageVersion.ToString()) + new Path("/");

            // Check if the package version already exists
            if (LifetimeManager.Get <IFileSystem>().Exists(packageVersionFolder))
            {
                Log.HighPriority("Found local version");
            }
            else
            {
                // Download the archive
                Log.HighPriority("Downloading package");
                var archivePath = stagingDirectory + new Path(packageName + ".zip");
                using (var httpClient = new HttpClient())
                {
                    var client = new Api.Client.PackageVersionClient(httpClient)
                    {
                        BaseUrl = SoupApiEndpoint,
                    };

                    try
                    {
                        var result = await client.DownloadPackageVersionAsync(languageName, packageName, packageVersion.ToString());

                        // Write the contents to disk, scope cleanup
                        using (var archiveWriteFile = LifetimeManager.Get <IFileSystem>().OpenWrite(archivePath, true))
                        {
                            await result.Stream.CopyToAsync(archiveWriteFile.GetOutStream());
                        }
                    }
                    catch (Api.Client.ApiException ex)
                    {
                        if (ex.StatusCode == 404)
                        {
                            Log.HighPriority("Package Version Missing");
                            throw new HandledException();
                        }
                        else
                        {
                            throw;
                        }
                    }
                }

                // Create the package folder to extract to
                var stagingVersionFolder = stagingDirectory + new Path($"{languageName}_{packageName}_{packageVersion}/");
                LifetimeManager.Get <IFileSystem>().CreateDirectory2(stagingVersionFolder);

                // Unpack the contents of the archive
                ZipFile.ExtractToDirectory(archivePath.ToString(), stagingVersionFolder.ToString());

                // Delete the archive file
                LifetimeManager.Get <IFileSystem>().DeleteFile(archivePath);

                // Ensure the package root folder exists
                if (!LifetimeManager.Get <IFileSystem>().Exists(packageRootFolder))
                {
                    // Create the folder
                    LifetimeManager.Get <IFileSystem>().CreateDirectory2(packageRootFolder);
                }

                // Move the extracted contents into the version folder
                LifetimeManager.Get <IFileSystem>().Rename(stagingVersionFolder, packageVersionFolder);
            }
        }
Example #10
0
        /// <summary>
        /// Ensure a package version is downloaded
        /// </summary>
        private static async Task CheckRecursiveEnsurePackageDownloadedAsync(
            string languageName,
            string packageName,
            SemanticVersion packageVersion,
            Path packagesDirectory,
            Path stagingDirectory,
            IDictionary <string, IDictionary <string, PackageReference> > closure)
        {
            if (closure.ContainsKey(languageName) && closure[languageName].ContainsKey(packageName))
            {
                Log.Diag("Recipe already processed.");
            }
            else
            {
                // Add the new package to the closure
                if (!closure.ContainsKey(languageName))
                {
                    closure.Add(languageName, new Dictionary <string, PackageReference>());
                }
                closure[languageName].Add(packageName, new PackageReference(packageName, packageVersion));

                Log.HighPriority($"Install Package: {languageName} {packageName}@{packageVersion}");

                var languageRootFolder   = packagesDirectory + new Path(languageName);
                var packageRootFolder    = languageRootFolder + new Path(packageName);
                var packageVersionFolder = packageRootFolder + new Path(packageVersion.ToString()) + new Path("/");

                // Check if the package version already exists
                if (LifetimeManager.Get <IFileSystem>().Exists(packageVersionFolder))
                {
                    Log.HighPriority("Found local version");
                }
                else
                {
                    // Download the archive
                    Log.HighPriority("Downloading package");
                    var archivePath = stagingDirectory + new Path(packageName + ".zip");
                    using (var httpClient = new HttpClient())
                    {
                        var client = new Api.Client.PackageVersionClient(httpClient)
                        {
                            BaseUrl = SoupApiEndpoint,
                        };

                        try
                        {
                            var result = await client.DownloadPackageVersionAsync(languageName, packageName, packageVersion.ToString());

                            // Write the contents to disk, scope cleanup
                            using (var archiveWriteFile = LifetimeManager.Get <IFileSystem>().OpenWrite(archivePath, true))
                            {
                                await result.Stream.CopyToAsync(archiveWriteFile.GetOutStream());
                            }
                        }
                        catch (Api.Client.ApiException ex)
                        {
                            if (ex.StatusCode == 404)
                            {
                                Log.HighPriority("Package Version Missing");
                                throw new HandledException();
                            }
                            else
                            {
                                throw;
                            }
                        }
                    }

                    // Create the package folder to extract to
                    var stagingVersionFolder = stagingDirectory + new Path($"{languageName}_{packageName}_{packageVersion}/");
                    LifetimeManager.Get <IFileSystem>().CreateDirectory2(stagingVersionFolder);

                    // Unpack the contents of the archive
                    ZipFile.ExtractToDirectory(archivePath.ToString(), stagingVersionFolder.ToString());

                    // Delete the archive file
                    LifetimeManager.Get <IFileSystem>().DeleteFile(archivePath);

                    // Ensure the package root folder exists
                    if (!LifetimeManager.Get <IFileSystem>().Exists(packageRootFolder))
                    {
                        // Create the folder
                        LifetimeManager.Get <IFileSystem>().CreateDirectory2(packageRootFolder);
                    }

                    // Move the extracted contents into the version folder
                    LifetimeManager.Get <IFileSystem>().Rename(stagingVersionFolder, packageVersionFolder);
                }

                var recipePath =
                    packageVersionFolder +
                    BuildConstants.RecipeFileName;
                var(isSuccess, recipe) = await RecipeExtensions.TryLoadRecipeFromFileAsync(recipePath);

                if (!isSuccess)
                {
                    throw new InvalidOperationException("Could not load the recipe file.");
                }

                // Install recursive dependencies
                await RestoreRecursiveDependenciesAsync(
                    packageVersionFolder,
                    recipe,
                    packagesDirectory,
                    stagingDirectory,
                    closure);
            }
        }
Example #11
0
        /// <summary>
        /// Restore packages
        /// </summary>
        public static async Task RestorePackagesAsync(Path workingDirectory)
        {
            var packageStore = LifetimeManager.Get <IFileSystem>().GetUserProfileDirectory() +
                               new Path(".soup/packages/");

            Log.Diag("Using Package Store: " + packageStore.ToString());

            // Create the staging directory
            var stagingPath = EnsureStagingDirectoryExists(packageStore);

            try
            {
                var packageLockPath =
                    workingDirectory +
                    BuildConstants.PackageLockFileName;
                var loadPackageLock = await PackageLockExtensions.TryLoadFromFileAsync(packageLockPath);

                if (loadPackageLock.IsSuccess)
                {
                    Log.Info("Restore from package lock");
                    await RestorePackageLockAsync(
                        packageStore,
                        stagingPath,
                        loadPackageLock.Result);
                }
                else
                {
                    Log.Info("Discovering full closure");
                    var closure = new Dictionary <string, IDictionary <string, PackageReference> >();
                    await CheckRestoreRecursiveDependenciesAsync(
                        workingDirectory,
                        packageStore,
                        stagingPath,
                        closure);

                    // Build up the package lock file
                    var packageLock = new PackageLock();
                    foreach (var languageClosure in closure)
                    {
                        foreach (var package in languageClosure.Value)
                        {
                            Log.Diag($"{languageClosure.Key} {package.Key} -> {package.Value}");
                            packageLock.AddProject(languageClosure.Key, package.Key, package.Value.ToString());
                        }
                    }

                    // Save the updated package lock
                    await PackageLockExtensions.SaveToFileAsync(packageLockPath, packageLock);
                }

                // Cleanup the working directory
                Log.Diag("Deleting staging directory");
                LifetimeManager.Get <IFileSystem>().DeleteDirectory(stagingPath, true);
            }
            catch (Exception)
            {
                // Cleanup the staging directory and accept that we failed
                LifetimeManager.Get <IFileSystem>().DeleteDirectory(stagingPath, true);
                throw;
            }
        }
Example #12
0
        /// <summary>
        /// Publish a package
        /// </summary>
        public static async Task PublishPackageAsync(Path workingDirectory)
        {
            Log.Info($"Publish Project: {workingDirectory}");

            var recipePath =
                workingDirectory +
                BuildConstants.RecipeFileName;

            var(isSuccess, recipe) = await RecipeExtensions.TryLoadRecipeFromFileAsync(recipePath);

            if (!isSuccess)
            {
                throw new InvalidOperationException("Could not load the recipe file.");
            }

            var packageStore = LifetimeManager.Get <IFileSystem>().GetUserProfileDirectory() +
                               new Path(".soup/packages/");

            Log.Info("Using Package Store: " + packageStore.ToString());

            // Create the staging directory
            var stagingPath = EnsureStagingDirectoryExists(packageStore);

            try
            {
                var archivePath = stagingPath + new Path(recipe.Name + ".zip");

                // Create the archive of the package
                using (var writeArchiveFile = LifetimeManager.Get <IFileSystem>().OpenWrite(archivePath, true))
                    using (var zipArchive = new ZipArchive(writeArchiveFile.GetOutStream(), ZipArchiveMode.Create, false))
                    {
                        AddPackageFiles(workingDirectory, zipArchive);
                    }

                // Authenticate the user
                Log.Info("Request Authentication Token");
                var accessToken = await AuthenticationManager.EnsureSignInAsync();

                // Publish the archive
                Log.Info("Publish package");
                using (var httpClient = new HttpClient())
                {
                    var packageClient = new Api.Client.PackageClient(httpClient)
                    {
                        BaseUrl     = SoupApiEndpoint,
                        BearerToken = accessToken,
                    };

                    // Check if the package exists
                    bool packageExists = false;
                    try
                    {
                        var package = await packageClient.GetPackageAsync(recipe.Language, recipe.Name);

                        packageExists = true;
                    }
                    catch (Api.Client.ApiException ex)
                    {
                        if (ex.StatusCode == 404)
                        {
                            packageExists = false;
                        }
                        else
                        {
                            throw;
                        }
                    }

                    // Create the package if it does not exist
                    if (!packageExists)
                    {
                        var createPackageModel = new Api.Client.PackageCreateOrUpdateModel()
                        {
                            Description = string.Empty,
                        };
                        await packageClient.CreateOrUpdatePackageAsync(recipe.Language, recipe.Name, createPackageModel);
                    }

                    var packageVersionClient = new Api.Client.PackageVersionClient(httpClient)
                    {
                        BaseUrl     = SoupApiEndpoint,
                        BearerToken = accessToken,
                    };

                    using (var readArchiveFile = LifetimeManager.Get <IFileSystem>().OpenRead(archivePath))
                    {
                        try
                        {
                            await packageVersionClient.PublishPackageVersionAsync(
                                recipe.Language,
                                recipe.Name,
                                recipe.Version.ToString(),
                                new Api.Client.FileParameter(readArchiveFile.GetInStream(), string.Empty, "application/zip"));

                            Log.Info("Package published");
                        }
                        catch (Api.Client.ApiException ex)
                        {
                            if (ex.StatusCode == 409)
                            {
                                Log.Info("Package version already exists");
                            }
                            else
                            {
                                throw;
                            }
                        }
                    }
                }

                // Cleanup the staging directory
                Log.Info("Cleanup staging directory");
                LifetimeManager.Get <IFileSystem>().DeleteDirectory(stagingPath, true);
            }
            catch (Exception)
            {
                // Cleanup the staging directory and accept that we failed
                Log.Info("Publish Failed: Cleanup staging directory");
                LifetimeManager.Get <IFileSystem>().DeleteDirectory(stagingPath, true);
                throw;
            }
        }
Example #13
0
        public static async Task <int> Main(string[] args)
        {
            try
            {
                var traceFlags =
                    TraceEventFlag.Information |
                    TraceEventFlag.HighPriority |
                    TraceEventFlag.Critical |
                    TraceEventFlag.Warning |
                    TraceEventFlag.Error;
                Log.RegisterListener(new ConsoleTraceListener("", new EventTypeFilter(traceFlags), false, false));
                LifetimeManager.RegisterSingleton <IFileSystem, RuntimeFileSystem>();

                LifetimeManager.RegisterSingleton <IProcessManager, RuntimeProcessManager>();

                if (args.Length != 0)
                {
                    PrintUsage();
                    return(-1);
                }

                // Load up the Local User Config
                var localUserConfigPath = LifetimeManager.Get <IFileSystem>().GetUserProfileDirectory() +
                                          new Path(".soup/LocalUserConfig.toml");
                var(loadConfigResult, userConfig) = await LocalUserConfigExtensions.TryLoadLocalUserConfigFromFileAsync(localUserConfigPath);

                if (!loadConfigResult)
                {
                    Log.Info("No existing local user config.");
                }

                // Find the Roslyn SDKs
                var roslynInstallPath = await VSWhereUtilities.FindRoslynInstallAsync();

                var roslynSDK = userConfig.EnsureSDK("Roslyn");
                roslynSDK.SourceDirectories = new List <Path>()
                {
                    roslynInstallPath,
                };
                roslynSDK.SetProperties(
                    new Dictionary <string, string>()
                {
                    { "ToolsRoot", roslynInstallPath.ToString() },
                });

                var dotnetSDKInstallPath = new Path("C:/Program Files/dotnet/");
                var dotnetSDK            = userConfig.EnsureSDK("DotNet");
                dotnetSDK.SourceDirectories = new List <Path>()
                {
                    dotnetSDKInstallPath,
                };
                dotnetSDK.SetProperties(
                    new Dictionary <string, string>()
                {
                });

                var(msvcVersion, msvcInstallPath) = await VSWhereUtilities.FindMSVCInstallAsync();

                var msvcSDK = userConfig.EnsureSDK("MSVC");
                msvcSDK.SourceDirectories = new List <Path>()
                {
                    msvcInstallPath,
                };
                msvcSDK.SetProperties(
                    new Dictionary <string, string>()
                {
                    { "Version", msvcVersion },
                    { "VCToolsRoot", msvcInstallPath.ToString() },
                });

                var(windowsSDKVersion, windowsSDKInstallPath) = WindowsSDKUtilities.FindWindows10Kit();
                var windowsSDK = userConfig.EnsureSDK("Windows");
                windowsSDK.SourceDirectories = new List <Path>()
                {
                    windowsSDKInstallPath,
                };
                windowsSDK.SetProperties(
                    new Dictionary <string, string>()
                {
                    { "Version", windowsSDKVersion },
                    { "RootPath", windowsSDKInstallPath.ToString() },
                });

                var netFXToolsPath = WindowsSDKUtilities.FindNetFXTools();
                var netFXToolsSDK  = userConfig.EnsureSDK("NetFXTools");
                netFXToolsSDK.SourceDirectories = new List <Path>()
                {
                    netFXToolsPath,
                };
                netFXToolsSDK.SetProperties(
                    new Dictionary <string, string>()
                {
                    { "ToolsRoot", netFXToolsPath.ToString() },
                });

                // Save the result
                await LocalUserConfigExtensions.SaveToFileAsync(localUserConfigPath, userConfig);

                return(0);
            }
            catch (HandledException)
            {
                return(-1);
            }
            catch (Exception ex)
            {
                Log.HighPriority($"Unhandled Error: {ex}");
                return(-2);
            }
        }
Example #14
0
        private static async Task <Path> FindVSInstallRootAsync(string requires)
        {
            // Find a copy of visual studio that has the required VisualCompiler
            var executablePath   = new Path("C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe");
            var workingDirectory = new Path("./");
            var argumentList     = new List <string>()
            {
                "-latest",
                "-products",
                "*",
                "-requires",
                requires,
                "-property",
                "installationPath",
            };

            // Check if we should include pre-release versions
            bool includePrerelease = true;

            if (includePrerelease)
            {
                argumentList.Add("-prerelease");
            }

            // Execute the requested target
            var arguments = CombineArguments(argumentList);

            Log.Info(executablePath.ToString() + " " + arguments);
            var process = LifetimeManager.Get <IProcessManager>().CreateProcess(
                executablePath,
                arguments,
                workingDirectory);

            process.Start();
            await process.WaitForExitAsync();

            var stdOut   = process.GetStandardOutput();
            var stdErr   = process.GetStandardError();
            var exitCode = process.GetExitCode();

            if (!string.IsNullOrEmpty(stdErr))
            {
                Log.Error("VSWhere failed.");
                Log.Error(stdErr);
                throw new HandledException();
            }

            if (exitCode != 0)
            {
                // TODO: Return error code
                Log.Error("VSWhere failed.");
                throw new HandledException();
            }

            // The first line is the path
            using (var reader = new System.IO.StringReader(stdOut))
            {
                var path = reader.ReadLine();
                if (path is null)
                {
                    Log.Error("Failed to parse vswhere output.");
                    throw new HandledException();
                }

                return(new Path(path));
            }
        }