diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 9deac8729..41f970dc6 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -1,202 +1,218 @@ - - - - ..\..\NuGet.ruleset - true - - - x86 - true - false - bin\Debug\ - DEBUG;TRACE - - - x86 - pdbonly - true - bin\Release\ - - - - {B34A6632-E627-4B66-8E0A-D2DA3BC96893} - Exe - Properties - NuGet - NuGet - - - - - - - - False - ..\..\lib\Microsoft.Web.XmlTransform.dll - - - - - - - - - - - - Common\MsBuildProjectUtility.cs - - - - - - - - - True - True - HelpCommandMarkdownTemplate.cshtml - - - - - - - - - - - - - - - - - - - - - - - - - Common\NuGetConstants.cs - - - - - Properties\CommonAssemblyInfo.cs - - - - - - - - - - - - - - - - - - NuGetCommand.resx - - - - - True - True - NuGetResources.resx - - - - - - - - - - - Common\CommonResources.cs - - - - - - Properties\CodeAnalysisDictionary.xml - Designer - - - - - {F879F274-EFA0-4157-8404-33A19B4E6AEC} - Core - - - - - Common\CommonResources.resx - CommonResources.cs - Designer - - - Designer - - - ResXFileCodeGenerator - NuGetResources.Designer.cs - Designer - - - - - - - - Code - RazorGenerator - HelpCommandMarkdownTemplate.cs - - - - - - $(MsBuildProjectDirectory)\..\.. - $(NuGetRoot)\Tools\ILMerge\ILMerge.exe - $(NuGetRoot)\Build\ilmerge.internalize.ignore.txt - NuGet.exe - $(OutputPath)Merged\$(ILMergeOutputFile) - $(OutputPath)Signed\$(ILMergeOutputFile) - $(ProgramFiles)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0 - $(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0 - /targetplatform:"v4, $(FrameworkPath)" /internalize:"$(ILMergeInternalizeIgnoreFile)" /target:exe /out:"Merged\$(ILMergeOutputFile)" /log:"Merged\ilmerge.msbuild.log" /allowDup NuGet.exe NuGet.Core.dll Microsoft.Web.XmlTransform.dll - /targetplatform:"v4, $(FrameworkPath)" /internalize:"$(ILMergeInternalizeIgnoreFile)" /target:exe /out:"Signed\$(ILMergeOutputFile)" /log:"Signed\ilmerge.msbuild.log" /allowDup /keyfile:"$(AssemblyOriginatorKeyFile)" /delaysign NuGet.exe NuGet.Core.dll Microsoft.Web.XmlTransform.dll - - - - - - - - - - - - - - - - - + + + + ..\..\NuGet.ruleset + true + $(UserProfile)\.nuget\packages + + + x86 + true + false + bin\Debug\ + DEBUG;TRACE + + + x86 + pdbonly + true + bin\Release\ + + + + {B34A6632-E627-4B66-8E0A-D2DA3BC96893} + Exe + Properties + NuGet + NuGet + + + + + + + + $(NuGetPackageRoot)\Microsoft.DiaSymReader\1.0.6\lib\portable-net45+win8\Microsoft.DiaSymReader.dll + + + False + ..\..\lib\Microsoft.Web.XmlTransform.dll + + + + + + + + + + + + Common\MsBuildProjectUtility.cs + + + + + + + + + True + True + HelpCommandMarkdownTemplate.cshtml + + + + + + + + + + + + + + + + + + + + + + + + + + Common\NuGetConstants.cs + + + + + Properties\CommonAssemblyInfo.cs + + + + + + + + + + + + + + + + + + NuGetCommand.resx + + + + + True + True + NuGetResources.resx + + + + + + + + + + + Common\CommonResources.cs + + + + + + PreserveNewest + false + + + PreserveNewest + false + + + + + Properties\CodeAnalysisDictionary.xml + Designer + + + + + {F879F274-EFA0-4157-8404-33A19B4E6AEC} + Core + + + + + Common\CommonResources.resx + CommonResources.cs + Designer + + + Designer + + + ResXFileCodeGenerator + NuGetResources.Designer.cs + Designer + + + + + + + + + Code + RazorGenerator + HelpCommandMarkdownTemplate.cs + + + + + + $(MsBuildProjectDirectory)\..\.. + $(NuGetRoot)\Tools\ILMerge\ILMerge.exe + $(NuGetRoot)\Build\ilmerge.internalize.ignore.txt + NuGet.exe + $(OutputPath)Merged\$(ILMergeOutputFile) + $(OutputPath)Signed\$(ILMergeOutputFile) + $(ProgramFiles)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0 + $(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0 + /targetplatform:"v4, $(FrameworkPath)" /internalize:"$(ILMergeInternalizeIgnoreFile)" /target:exe /out:"Merged\$(ILMergeOutputFile)" /log:"Merged\ilmerge.msbuild.log" /allowDup NuGet.exe NuGet.Core.dll Microsoft.Web.XmlTransform.dll + /targetplatform:"v4, $(FrameworkPath)" /internalize:"$(ILMergeInternalizeIgnoreFile)" /target:exe /out:"Signed\$(ILMergeOutputFile)" /log:"Signed\ilmerge.msbuild.log" /allowDup /keyfile:"$(AssemblyOriginatorKeyFile)" /delaysign NuGet.exe NuGet.Core.dll Microsoft.Web.XmlTransform.dll + + + + + + + + + + + + + + + + + - + --> + \ No newline at end of file diff --git a/src/CommandLine/Commands/ProjectFactory.cs b/src/CommandLine/Commands/ProjectFactory.cs index f6dbcbcda..08c60d6b8 100644 --- a/src/CommandLine/Commands/ProjectFactory.cs +++ b/src/CommandLine/Commands/ProjectFactory.cs @@ -173,6 +173,11 @@ public PackageBuilder CreateBuilder(string basePath) ProcessDependencies(builder); + if (IncludeSymbols) + { + AddMissingSourceFilesReferencedByPdb(builder); + } + // Set defaults if some required fields are missing if (String.IsNullOrEmpty(builder.Description)) { @@ -1093,5 +1098,58 @@ IEnumerable IFrameworkTargetable.SupportedFrameworks } } } + + private void AddMissingSourceFilesReferencedByPdb(PackageBuilder builder) + { + // We build a new symbol package from physical files, so it's safe to cast here (we need the SourcePath property). + var packageFiles = builder.Files.OfType().ToArray(); + + var pdbFiles = packageFiles.Where(file => file.Path.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase)); + var missingFiles = pdbFiles.SelectMany(pdbFile => GetMissingSourceFilesReferencedByPdb(pdbFile, packageFiles)); + + foreach (var file in missingFiles) + { + AddFileToBuilder(builder, file); + } + } + + private IEnumerable GetMissingSourceFilesReferencedByPdb(IPackageFile pdbFile, IEnumerable packageFiles) + { + try + { + var sourceFiles = PdbHelper.GetSourceFileNames(pdbFile); + var missingFiles = sourceFiles.Except(packageFiles.Select(file => file.SourcePath), StringComparer.OrdinalIgnoreCase); + + return missingFiles.Select(CreatePackageFileFromSourceFile).Where(file => file != null); + } + catch (Exception ex) + { + _logger.Log(MessageLevel.Warning, "{0} seems to be not a valid pdb file ({1})", pdbFile.Path, ex.Message); + } + + return Enumerable.Empty(); + } + + private PhysicalPackageFile CreatePackageFileFromSourceFile(string file) + { + var targetFilePath = Normalize(file); + + if (!File.Exists(file)) + { + Logger.Log(MessageLevel.Warning, LocalizedResourceManager.GetString("Warning_FileDoesNotExist"), targetFilePath); + return null; + } + + var projectName = Path.GetFileNameWithoutExtension(_project.FullPath); + + // if IncludeReferencedProjects is true and we are adding source files, + // add projectName as part of the target to avoid file conflicts. + var targetPath = IncludeReferencedProjects + ? Path.Combine(SourcesFolder, projectName, targetFilePath) + : Path.Combine(SourcesFolder, targetFilePath); + + return new PhysicalPackageFile { SourcePath = file, TargetPath = targetPath }; + } } } + diff --git a/src/CommandLine/Common/PdbHelper.cs b/src/CommandLine/Common/PdbHelper.cs new file mode 100644 index 000000000..e4f072c50 --- /dev/null +++ b/src/CommandLine/Common/PdbHelper.cs @@ -0,0 +1,257 @@ +namespace NuGet.Common +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.ComTypes; + using System.Threading; + + using Microsoft.DiaSymReader; + + using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG; + + internal static class PdbHelper + { + public static IEnumerable GetSourceFileNames(IPackageFile pdbFile) + { + using (var stream = new StreamAdapter(pdbFile.GetStream())) + { + var reader = SymReaderFactory.CreateNativeSymReader(stream); + + return reader.GetDocuments() + .Select(doc => doc.GetName()) + .Where(IsValidSourceFileName); + } + } + + private static bool IsValidSourceFileName(string sourceFileName) + { + return !string.IsNullOrEmpty(sourceFileName) && !IsTemporaryCompilerFile(sourceFileName); + } + + private static bool IsTemporaryCompilerFile(string sourceFileName) + { + //the VB compiler will include temporary files in its pdb files. + //the source file name will be similar to 17d14f5c-a337-4978-8281-53493378c1071.vb. + return sourceFileName.EndsWith("17d14f5c-a337-4978-8281-53493378c1071.vb", StringComparison.InvariantCultureIgnoreCase); + } + + private static class SymReaderFactory + { + [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] + [DllImport("Microsoft.DiaSymReader.Native.x86.dll", EntryPoint = "CreateSymReader")] + private extern static void CreateSymReader32(ref Guid id, [MarshalAs(UnmanagedType.IUnknown)]out object symReader); + + [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] + [DllImport("Microsoft.DiaSymReader.Native.amd64.dll", EntryPoint = "CreateSymReader")] + private extern static void CreateSymReader64(ref Guid id, [MarshalAs(UnmanagedType.IUnknown)]out object symReader); + + internal static ISymUnmanagedReader3 CreateNativeSymReader(IStream pdbStream) + { + object symReader = null; + var guid = default(Guid); + + if (IntPtr.Size == 4) + { + CreateSymReader32(ref guid, out symReader); + } + else + { + CreateSymReader64(ref guid, out symReader); + } + + var reader = (ISymUnmanagedReader3)symReader; + var hr = reader.Initialize(new DummyMetadataImport(), null, null, pdbStream); + Marshal.ThrowExceptionForHR(hr); + return reader; + } + + class DummyMetadataImport : IMetadataImport { } + } + + /// + /// Wrap a Stream so it's usable where we need an IStream + /// + sealed class StreamAdapter : IStream, IDisposable + { + Stream _stream; + IntPtr _pcbData = Marshal.AllocHGlobal(8); // enough to store long/int64, can be shared since we don't support multithreaded access to one file. + + /// + /// Create a new adapter around the given stream. + /// + /// The stream to wrap. + public StreamAdapter(Stream wrappedStream) + { + _stream = wrappedStream; + } + + ~StreamAdapter() + { + throw new InvalidOperationException("Stream adapter not disposed"); + } + + public void Clone(out IStream ppstm) + { + throw new NotSupportedException(); + } + + public void Commit(int grfCommitFlags) + { + } + + public void LockRegion(long libOffset, long cb, int dwLockType) + { + throw new NotSupportedException(); + } + + public void Revert() + { + throw new NotSupportedException(); + } + + public void UnlockRegion(long libOffset, long cb, int dwLockType) + { + throw new NotSupportedException(); + } + + public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) + { + throw new NotSupportedException(); + } + + public void Read(byte[] pv, int cb, IntPtr pcbRead) + { + var count = _stream.Read(pv, 0, cb); + if (pcbRead != IntPtr.Zero) + { + Marshal.WriteInt32(pcbRead, count); + } + } + + public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition) + { + var origin = (SeekOrigin)dwOrigin; + var pos = _stream.Seek(dlibMove, origin); + if (plibNewPosition != IntPtr.Zero) + { + Marshal.WriteInt64(plibNewPosition, pos); + } + } + + public void SetSize(long libNewSize) + { + _stream.SetLength(libNewSize); + } + + public void Stat(out STATSTG pstatstg, int grfStatFlag) + { + pstatstg = new STATSTG + { + type = 2, + cbSize = _stream.Length, + grfMode = 0 + }; + + if (_stream.CanRead && _stream.CanWrite) + { + pstatstg.grfMode |= 2; + } + else if (_stream.CanWrite && !_stream.CanRead) + { + pstatstg.grfMode |= 1; + } + } + + public void Write(byte[] pv, int cb, IntPtr pcbWritten) + { + _stream.Write(pv, 0, cb); + if (pcbWritten != IntPtr.Zero) + { + Marshal.WriteInt32(pcbWritten, cb); + } + } + + public void Dispose() + { + Interlocked.Exchange(ref _stream, null)?.Close(); + + var data = Interlocked.Exchange(ref _pcbData, IntPtr.Zero); + if (data != IntPtr.Zero) + { + Marshal.FreeHGlobal(_pcbData); + } + + GC.SuppressFinalize(this); + } + } + } + + [ComImport, Guid("7DAC8207-D3AE-4c75-9B67-92801A497D44"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), TypeIdentifier] + public interface IMetadataImport { } + + static class SymUnmanagedReaderExtensions + { + // Excerpt of http://source.roslyn.io/#Roslyn.Test.PdbUtilities/Shared/SymUnmanagedReaderExtensions.cs + + private const int E_FAIL = unchecked((int)0x80004005); + private const int E_NOTIMPL = unchecked((int)0x80004001); + + private delegate int ItemsGetter(TEntity entity, int bufferLength, out int count, TItem[] buffer); + + private static string ToString(char[] buffer) + { + if (buffer.Length == 0) + return string.Empty; + + Debug.Assert(buffer[buffer.Length - 1] == 0); + return new string(buffer, 0, buffer.Length - 1); + } + + private static void ValidateItems(int actualCount, int bufferLength) + { + if (actualCount != bufferLength) + { + throw new InvalidOperationException(string.Format("Read only {0} of {1} items.", actualCount, bufferLength)); + } + } + + private static TItem[] GetItems(TEntity entity, ItemsGetter getter) + { + int count; + var hr = getter(entity, 0, out count, null); + ThrowExceptionForHR(hr); + if (count == 0) + return new TItem[0]; + + var result = new TItem[count]; + hr = getter(entity, count, out count, result); + ThrowExceptionForHR(hr); + ValidateItems(count, result.Length); + return result; + } + + public static ISymUnmanagedDocument[] GetDocuments(this ISymUnmanagedReader reader) + { + return GetItems(reader, (ISymUnmanagedReader a, int b, out int c, ISymUnmanagedDocument[] d) => a.GetDocuments(b, out c, d)); + } + + internal static string GetName(this ISymUnmanagedDocument document) + { + return ToString(GetItems(document, (ISymUnmanagedDocument a, int b, out int c, char[] d) => a.GetUrl(b, out c, d))); + } + + internal static void ThrowExceptionForHR(int hr) + { + // E_FAIL indicates "no info". + // E_NOTIMPL indicates a lack of ISymUnmanagedReader support (in a particular implementation). + if (hr < 0 && hr != E_FAIL && hr != E_NOTIMPL) + { + Marshal.ThrowExceptionForHR(hr, new IntPtr(-1)); + } + } + } +} \ No newline at end of file diff --git a/src/CommandLine/Project.json b/src/CommandLine/Project.json new file mode 100644 index 000000000..e5209253a --- /dev/null +++ b/src/CommandLine/Project.json @@ -0,0 +1,17 @@ +{ + "dependencies": { + "Microsoft.DiaSymReader.Native": "1.2.0-rc", + "Microsoft.DiaSymReader": "1.0.6" + }, + + "frameworks": { + "net45": { } + }, + + "runtimes": { + "win7-x86": { }, + "win7-x64": { }, + "win8-x86": { }, + "win8-x64": { } + } +} \ No newline at end of file