using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; namespace Semmle.BuildAnalyser { /// /// Manage the downloading of NuGet packages. /// Locates packages in a source tree and downloads all of the /// referenced assemblies to a temp folder. /// class NugetPackages { /// /// Create the package manager for a specified source tree. /// /// The source directory. public NugetPackages(string sourceDir) { SourceDirectory = sourceDir; PackageDirectory = computeTempDirectory(sourceDir); // Expect nuget.exe to be in a `nuget` directory under the directory containing this exe. var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location; nugetExe = Path.Combine(Path.GetDirectoryName(currentAssembly), "nuget", "nuget.exe"); if (!File.Exists(nugetExe)) throw new FileNotFoundException(string.Format("NuGet could not be found at {0}", nugetExe)); } /// /// Locate all NuGet packages but don't download them yet. /// public void FindPackages() { packages = new DirectoryInfo(SourceDirectory). EnumerateFiles("packages.config", SearchOption.AllDirectories). ToArray(); } // List of package files to download. FileInfo[] packages; /// /// The list of package files. /// public IEnumerable PackageFiles => packages; // Whether to delete the packages directory prior to each run. // Makes each build more reproducible. const bool cleanupPackages = true; public void Cleanup(IProgressMonitor pm) { var packagesDirectory = new DirectoryInfo(PackageDirectory); if (packagesDirectory.Exists) { try { packagesDirectory.Delete(true); } catch (System.IO.IOException ex) { pm.Warning(string.Format("Couldn't delete package directory - it's probably held open by something else: {0}", ex.Message)); } } } /// /// Download the packages to the temp folder. /// /// The progress monitor used for reporting errors etc. public void InstallPackages(IProgressMonitor pm) { if (cleanupPackages) { Cleanup(pm); } var packagesDirectory = new DirectoryInfo(PackageDirectory); if (!Directory.Exists(PackageDirectory)) { packagesDirectory.Create(); } foreach (var package in packages) { RestoreNugetPackage(package.FullName, pm); } } /// /// The source directory used. /// public string SourceDirectory { get; private set; } /// /// The computed packages directory. /// This will be in the Temp location /// so as to not trample the source tree. /// public string PackageDirectory { get; private set; } readonly SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider(); /// /// Computes a unique temp directory for the packages associated /// with this source tree. Use a SHA1 of the directory name. /// /// /// The full path of the temp directory. string computeTempDirectory(string srcDir) { var bytes = Encoding.Unicode.GetBytes(srcDir); var sha = sha1.ComputeHash(bytes); var sb = new StringBuilder(); foreach (var b in sha.Take(8)) sb.AppendFormat("{0:x2}", b); return Path.Combine(Path.GetTempPath(), "Semmle", "packages", sb.ToString()); } /// /// Restore all files in a specified package. /// /// The package file. /// Where to log progress/errors. void RestoreNugetPackage(string package, IProgressMonitor pm) { pm.NugetInstall(package); /* Use nuget.exe to install a package. * Note that there is a clutch of NuGet assemblies which could be used to * invoke this directly, which would arguably be nicer. However they are * really unwieldy and this solution works for now. */ string exe, args; if (Util.Win32.IsWindows()) { exe = nugetExe; args = string.Format("install -OutputDirectory {0} {1}", PackageDirectory, package); } else { exe = "mono"; args = string.Format("{0} install -OutputDirectory {1} {2}", nugetExe, PackageDirectory, package); } var pi = new ProcessStartInfo(exe, args) { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false }; try { using (var p = Process.Start(pi)) { string output = p.StandardOutput.ReadToEnd(); string error = p.StandardError.ReadToEnd(); p.WaitForExit(); if (p.ExitCode != 0) { pm.FailedNugetCommand(pi.FileName, pi.Arguments, output + error); } } } catch (Exception e) when (e is System.ComponentModel.Win32Exception || e is FileNotFoundException) { pm.FailedNugetCommand(pi.FileName, pi.Arguments, e.Message); } } readonly string nugetExe; } }