/// <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)); }
/// <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()); } } }
/// <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); }
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); } } }
/// <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()); }
/// <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; } }
/// <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); } }
/// <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); } }
/// <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; } }
/// <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; } }
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); } }
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)); } }