コード例 #1
0
 private static bool ValidatePdb(FileWithDebugData input,
                                 Stream pdbStream,
                                 List <PackageFile> noSourceLink,
                                 List <(PackageFile file, string errors)> sourceLinkErrors,
コード例 #2
0
        private async Task CalculateValidity(IReadOnlyList <PackageFile> files)
        {
            var filesWithPdb = (from pf in files
                                let ext = Path.GetExtension(pf.Path)
                                          where ".dll".Equals(ext, StringComparison.OrdinalIgnoreCase) ||
                                          ".exe".Equals(ext, StringComparison.OrdinalIgnoreCase) ||
                                          ".winmd".Equals(ext, StringComparison.OrdinalIgnoreCase)
                                          select new FileWithPdb
            {
                Primary = pf,
                Pdb = pf.GetAssociatedFiles().FirstOrDefault(af => ".pdb".Equals(Path.GetExtension(af.Path), StringComparison.OrdinalIgnoreCase))
            })
                               .ToList();


            var sourceLinkErrors = new List <(PackageFile file, string errors)>();
            var noSourceLink     = new List <PackageFile>();
            var noSymbols        = new List <FileWithDebugData>();
            var untrackedSources = new List <FileWithDebugData>();
            var nonDeterministic = new List <PackageFile>();

            var allFilePaths = filesWithPdb.ToDictionary(pf => pf.Primary.Path);

            var pdbChecksumValid = true;

            foreach (var file in filesWithPdb.ToArray()) // work on array as we'll remove items that are satellite assemblies as we go
            {
                // Skip satellite assemblies
                if (IsSatelliteAssembly(file.Primary.Path))
                {
                    filesWithPdb.Remove(allFilePaths[file.Primary.Path]);
                    continue;
                }

                // If we have a PDB, try loading that first. If not, may be embedded.
                // Local checks first
                if (file.Pdb != null)
                {
                    var filePair = new FileWithDebugData(file.Primary, null);
                    if (!ValidatePdb(filePair, file.Pdb.GetStream(), noSourceLink, sourceLinkErrors, untrackedSources, nonDeterministic))
                    {
                        pdbChecksumValid = false;
                        noSymbols.Add(filePair);
                    }
                }
                else // No PDB, see if it's embedded
                {
                    try
                    {
                        using var str      = file.Primary.GetStream();
                        using var tempFile = new TemporaryFile(str);

                        var assemblyMetadata = AssemblyMetadataReader.ReadMetaData(tempFile.FileName);

                        if (assemblyMetadata?.DebugData.HasDebugInfo == true)
                        {
                            // we have an embedded pdb
                            if (!assemblyMetadata.DebugData.HasSourceLink)
                            {
                                noSourceLink.Add(file.Primary);
                            }

                            if (assemblyMetadata.DebugData.SourceLinkErrors.Count > 0)
                            {
                                // Has source link errors
                                sourceLinkErrors.Add((file.Primary, string.Join("\n", assemblyMetadata.DebugData.SourceLinkErrors)));
                            }

                            // Check for non-embedded sources
                            if (assemblyMetadata.DebugData.UntrackedSources.Count > 0)
                            {
                                var filePair = new FileWithDebugData(file.Primary, assemblyMetadata.DebugData);
                                untrackedSources.Add(filePair);
                            }

                            // Check for deterministic sources
                            if (!assemblyMetadata.DebugData.SourcesAreDeterministic)
                            {
                                nonDeterministic.Add(file.Primary);
                            }
                        }
                        else // no embedded pdb, try to look for it elsewhere
                        {
                            noSymbols.Add(new FileWithDebugData(file.Primary, assemblyMetadata?.DebugData));
                        }
                    }
                    catch // an error occured, no symbols
                    {
                        noSymbols.Add(new FileWithDebugData(file.Primary, null));
                    }
                }
            }


            var requireExternal = false;

            // See if any pdb's are missing and check for a snupkg on NuGet.org.
            if (noSymbols.Count > 0 && _publishedOnNuGetOrg)
            {
                // try to get on NuGet.org
                // https://www.nuget.org/api/v2/symbolpackage/Newtonsoft.Json/12.0.3 -- Will redirect


                try
                {
#pragma warning disable CA2234 // Pass system uri objects instead of strings
                    var response = await _httpClient.GetAsync($"https://www.nuget.org/api/v2/symbolpackage/{_package.Id}/{_package.Version.ToNormalizedString()}").ConfigureAwait(false);

#pragma warning restore CA2234                        // Pass system uri objects instead of strings

                    if (response.IsSuccessStatusCode) // we'll get a 404 if none
                    {
                        requireExternal = true;

                        using var getStream = await response.Content.ReadAsStreamAsync();

                        using var tempFile = new TemporaryFile(getStream, ".snupkg");
                        using var package  = new ZipPackage(tempFile.FileName);

                        // Look for pdb's for the missing files
                        var dict = package.GetFiles().ToDictionary(k => k.Path);

                        foreach (var file in noSymbols.ToArray()) // from a copy so we can remove as we go
                        {
                            // file to look for
                            var pdbpath = Path.ChangeExtension(file.File.Path, ".pdb");

                            if (dict.TryGetValue(pdbpath, out var pdbfile))
                            {
                                // Validate
                                if (ValidatePdb(file, pdbfile.GetStream(), noSourceLink, sourceLinkErrors, untrackedSources, nonDeterministic))
                                {
                                    noSymbols.Remove(file);
                                }
                                else
                                {
                                    pdbChecksumValid = false;
                                }
                            }
                        }
                    }
                }
                catch // Could not check, leave status as-is
                {
                }
            }

            // Check for Microsoft assemblies on the Microsoft symbol server
            if (noSymbols.Count > 0)
            {
                var microsoftFiles = noSymbols.Where(f => f.DebugData != null && IsMicrosoftFile(f.File)).ToList();

                foreach (var file in microsoftFiles)
                {
                    var pdbStream = await GetSymbolsAsync(file.DebugData !.SymbolKeys);

                    if (pdbStream != null)
                    {
                        requireExternal = true;

                        // Found a PDB for it
                        if (ValidatePdb(file, pdbStream, noSourceLink, sourceLinkErrors, untrackedSources, nonDeterministic))
                        {
                            noSymbols.Remove(file);
                        }
                        else
                        {
                            pdbChecksumValid = false;
                        }
                    }
                }
            }

            if (noSymbols.Count == 0 && noSourceLink.Count == 0 && sourceLinkErrors.Count == 0)
            {
                if (untrackedSources.Count > 0)
                {
                    SourceLinkResult = SymbolValidationResult.HasUntrackedSources;

                    var sb = new StringBuilder("Contains untracked sources:\n");
                    sb.AppendLine("To Fix:");
                    sb.AppendLine("<EmbedUntrackedSources>true</EmbedUntrackedSources>");

                    foreach (var untracked in untrackedSources)
                    {
                        sb.AppendLine($"Assembly: {untracked.File.Path}");

                        foreach (var source in untracked.DebugData !.UntrackedSources)
                        {
                            sb.AppendLine($"  {source}");
                        }

                        sb.AppendLine();
                    }

                    SourceLinkErrorMessage = sb.ToString();
                }
                else if (filesWithPdb.Count == 0)
                {
                    SourceLinkResult       = SymbolValidationResult.NothingToValidate;
                    SourceLinkErrorMessage = "No files found to validate";
                }
                else if (requireExternal)
                {
                    SourceLinkResult       = SymbolValidationResult.ValidExternal;
                    SourceLinkErrorMessage = null;
                }
                else
                {
                    SourceLinkResult       = SymbolValidationResult.Valid;
                    SourceLinkErrorMessage = null;
                }
            }
            else
            {
                var found = false;
                var sb    = new StringBuilder();
                if (noSourceLink.Count > 0)
                {
                    SourceLinkResult = SymbolValidationResult.NoSourceLink;

                    sb.AppendLine($"Missing Source Link for:\n{string.Join("\n", noSourceLink.Select(p => p.Path)) }");
                    found = true;
                }

                if (sourceLinkErrors.Count > 0)
                {
                    SourceLinkResult = SymbolValidationResult.InvalidSourceLink;

                    if (found)
                    {
                        sb.AppendLine();
                    }

                    foreach (var(file, errors) in sourceLinkErrors)
                    {
                        sb.AppendLine($"Source Link errors for {file.Path}:\n{string.Join("\n", errors) }");
                    }

                    found = true;
                }

                if (noSymbols.Count > 0) // No symbols "wins" as it's more severe
                {
                    SourceLinkResult = SymbolValidationResult.NoSymbols;

                    if (found)
                    {
                        sb.AppendLine();
                    }

                    if (!pdbChecksumValid)
                    {
                        sb.AppendLine("Some PDB's checksums do not match their PE files and are shown as missing.");
                    }

                    sb.AppendLine($"Missing Symbols for:\n{string.Join("\n", noSymbols.Select(p => p.File.Path)) }");
                }

                SourceLinkErrorMessage = sb.ToString();
            }

            if (SourceLinkResult == SymbolValidationResult.NothingToValidate)
            {
                DeterministicResult       = DeterministicResult.NothingToValidate;
                DeterministicErrorMessage = null;
            }
            else if (SourceLinkResult == SymbolValidationResult.NoSymbols)
            {
                DeterministicResult       = DeterministicResult.NonDeterministic;
                DeterministicErrorMessage = "Missing Symbols";
            }
            else if (nonDeterministic.Count > 0)
            {
                DeterministicResult = DeterministicResult.NonDeterministic;

                var sb = new StringBuilder();
                sb.AppendLine("Ensure that the following properties are enabled for CI builds\nand you're using at least the 2.1.300 SDK:");
                sb.AppendLine("<Deterministic>true</Deterministic>");
                sb.AppendLine("<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>");
                sb.AppendLine();
                sb.AppendLine("The following assemblies have not been compiled with deterministic settings:");

                foreach (var file in nonDeterministic)
                {
                    sb.AppendLine(file.Path);
                }

                DeterministicErrorMessage = sb.ToString();
            }
            else if (SourceLinkResult == SymbolValidationResult.HasUntrackedSources)
            {
                DeterministicResult       = DeterministicResult.HasUntrackedSources;
                DeterministicErrorMessage = null;
            }
            else
            {
                DeterministicResult       = DeterministicResult.Valid;
                DeterministicErrorMessage = null;
            }
        }