diff --git a/src/Package/PublicAPI.Shipped.txt b/src/Package/PublicAPI.Shipped.txt index 3ace950..c24344f 100644 --- a/src/Package/PublicAPI.Shipped.txt +++ b/src/Package/PublicAPI.Shipped.txt @@ -17,6 +17,4 @@ static Devlooped.DiagnosticExtensions.IsKind(this Microsoft.CodeAnalysis.Diagnos Devlooped.SponsorLinkSettings.SupportedDiagnostics.get -> System.Collections.Immutable.ImmutableArray Devlooped.SponsorLinkSettings.SupportedDiagnostics.set -> void virtual Devlooped.SponsorLink.OnDiagnostic(string! projectPath, Devlooped.DiagnosticKind kind) -> Microsoft.CodeAnalysis.Diagnostic? -static Devlooped.SponsorLinkSettings.Create(string! sponsorable, string! product) -> Devlooped.SponsorLinkSettings! -static Devlooped.SponsorLinkSettings.Create(string! sponsorable, string! product, string? packageId = null, string? diagnosticsIdPrefix = null, int pauseMin = 0, int pauseMax = 4000) -> Devlooped.SponsorLinkSettings! -static Devlooped.SponsorLinkSettings.Create(string! sponsorable, string! product, string? packageId = null, string? version = null, string? diagnosticsIdPrefix = null, int pauseMin = 0, int pauseMax = 4000, int? quietDays = null) -> Devlooped.SponsorLinkSettings! \ No newline at end of file +static Devlooped.SponsorLinkSettings.Create(string! sponsorable, string! product) -> Devlooped.SponsorLinkSettings! \ No newline at end of file diff --git a/src/Package/PublicAPI.Unshipped.txt b/src/Package/PublicAPI.Unshipped.txt index 4de5133..ab1f8de 100644 --- a/src/Package/PublicAPI.Unshipped.txt +++ b/src/Package/PublicAPI.Unshipped.txt @@ -3,4 +3,7 @@ Devlooped.SponsorStatus Devlooped.SponsorStatus.AppMissing = 0 -> Devlooped.SponsorStatus Devlooped.SponsorStatus.NotSponsoring = 1 -> Devlooped.SponsorStatus Devlooped.SponsorStatus.Sponsoring = 2 -> Devlooped.SponsorStatus -static Devlooped.SponsorCheck.CheckAsync(string! workingDirectory, string! sponsorable, string! product, string? packageId = null, string? version = null, System.Net.Http.HttpClient? http = null) -> System.Threading.Tasks.Task! \ No newline at end of file +static Devlooped.SponsorCheck.CheckAsync(string! workingDirectory, string! sponsorable, string! product, string? packageId = null, string? version = null, System.Net.Http.HttpClient? http = null) -> System.Threading.Tasks.Task! +static Devlooped.SponsorLinkSettings.Create(string! sponsorable, string! product, string? packageId = null, string? version = null, string? diagnosticsIdPrefix = null, int pauseMin = 0, int pauseMax = 4000, int? quietDays = null, bool transitive = false) -> Devlooped.SponsorLinkSettings! +static Devlooped.SponsorLinkSettings.Create(string! sponsorable, string! product, string? packageId, string? diagnosticsIdPrefix, int pauseMin, int pauseMax) -> Devlooped.SponsorLinkSettings! +static Devlooped.SponsorLinkSettings.Create(string! sponsorable, string! product, string? packageId, string? version, string? diagnosticsIdPrefix, int pauseMin, int pauseMax, int? quietDays) -> Devlooped.SponsorLinkSettings! \ No newline at end of file diff --git a/src/Package/SponsorLink.cs b/src/Package/SponsorLink.cs index 3baa11f..45c7664 100644 --- a/src/Package/SponsorLink.cs +++ b/src/Package/SponsorLink.cs @@ -216,6 +216,23 @@ void AnalyzeSponsors(CompilationAnalysisContext context) return; } + var dependency = context.Options.AdditionalFiles + .Where(x => context.Options.AnalyzerConfigOptionsProvider.GetOptions(x) is var fileOptions && + fileOptions.TryGetValue("build_metadata.AdditionalFiles.SourceItemType", out var itemType) && + itemType == "PackageDependencies" && + fileOptions.TryGetValue("build_metadata.AdditionalFiles.SourceIdentity", out var itemSpec) && + itemSpec == settings.PackageId) + .Select(x => context.Options.AnalyzerConfigOptionsProvider.GetOptions(x).TryGetValue("build_metadata.AdditionalFiles.ParentPackage", out var parent) ? + parent : null) + .ToImmutableList(); + + // We should not perform the check if the project is not referencing the package + // We detect this by checking the dependencies (see runtime targets which add + // PackageDependencies for the settings.PackageId from @SponsorablePackageId), + // and if the dependency is found and has a non-null ParentPackage, it means it's + // a transitive dependency. + // Note that we default to being non-transitive. + var shouldSkip = !settings.Transitive && dependency.Any(x => x != null); var (insideEditor, designTimeBuild) = ReadOptions(globalOptions); var info = new BuildInfo(projectFile!, insideEditor, designTimeBuild); @@ -500,15 +517,23 @@ void ReportExisting(CompilationAnalysisContext context, string? projectFile) static Diagnostic WriteMessage(string sponsorable, string product, string projectDir, Diagnostic diag) { - var objDir = Path.Combine(projectDir, "obj", "SponsorLink", sponsorable, product); - if (Directory.Exists(objDir)) + try { - foreach (var file in Directory.EnumerateFiles(objDir)) - File.Delete(file); - } + var objDir = Path.Combine(projectDir, "obj", "SponsorLink", sponsorable, product); + if (Directory.Exists(objDir)) + { + foreach (var file in Directory.EnumerateFiles(objDir)) + File.Delete(file); + } - Directory.CreateDirectory(objDir); - File.WriteAllText(Path.Combine(objDir, $"{diag.Id}.{diag.Severity}.txt"), diag.GetMessage()); + Directory.CreateDirectory(objDir); + File.WriteAllText(Path.Combine(objDir, $"{diag.Id}.{diag.Severity}.txt"), diag.GetMessage()); + } + // This is best-effort only. Cases where it can fail, is when bring run from + // the Roslyn analyzer host, which is in program files, and if the project path + // is empty, or in a folder that requires admin rights. + catch (IOException) { } + catch (UnauthorizedAccessException) { } return diag; } diff --git a/src/Package/SponsorLinkSettings.cs b/src/Package/SponsorLinkSettings.cs index def3224..4862186 100644 --- a/src/Package/SponsorLinkSettings.cs +++ b/src/Package/SponsorLinkSettings.cs @@ -30,6 +30,7 @@ public class SponsorLinkSettings /// /// The sponsor account to check for sponsorships. /// The product that uses SponsorLink. Used in diagnostics to clarify the product requesting the sponsor link check. + // BACKCOMPAT OVERLOAD -- DO NOT TOUCH public static SponsorLinkSettings Create(string sponsorable, string product) => Create(sponsorable, product, packageId: default, @@ -51,12 +52,13 @@ public static SponsorLinkSettings Create(string sponsorable, string product) /// a default one is determined from the and values. /// Min random milliseconds to apply during build for non-sponsoring users. Use 0 for no pause. /// Max random milliseconds to apply during build for non-sponsoring users. Use 0 for no pause. + // BACKCOMPAT OVERLOAD -- DO NOT TOUCH [EditorBrowsable(EditorBrowsableState.Never)] public static SponsorLinkSettings Create(string sponsorable, string product, - string? packageId = default, - string? diagnosticsIdPrefix = default, - int pauseMin = 0, - int pauseMax = DefaultMaxPause) => Create(sponsorable, product, + string? packageId, + string? diagnosticsIdPrefix, + int pauseMin, + int pauseMax) => Create(sponsorable, product, packageId: packageId, diagnosticsIdPrefix: diagnosticsIdPrefix, version: default, @@ -78,13 +80,46 @@ public static SponsorLinkSettings Create(string sponsorable, string product, /// Min random milliseconds to apply during build for non-sponsoring users. Use 0 for no pause. /// Max random milliseconds to apply during build for non-sponsoring users. Use 0 for no pause. /// Optional days to keep warnings quiet so the user has a chance to test the product undisturbed. + // BACKCOMPAT OVERLOAD -- DO NOT TOUCH + [EditorBrowsable(EditorBrowsableState.Never)] + public static SponsorLinkSettings Create(string sponsorable, string product, + string? packageId, + string? version, + string? diagnosticsIdPrefix, + int pauseMin, + int pauseMax, + int? quietDays) => Create(sponsorable, product, + packageId: packageId, + version: version, + diagnosticsIdPrefix: diagnosticsIdPrefix, + pauseMin: pauseMin, + pauseMax: pauseMax, + quietDays: quietDays, + transitive: false); + + /// + /// Creates the settings for with the given values. + /// + /// The sponsor account to check for sponsorships. + /// The product that uses SponsorLink. Used in diagnostics to clarify the product requesting the sponsor link check. + /// Optional NuGet package identifier of the product performing the check. Defaults to . + /// Used to determine installation time of the product and avoid pausing builds or emitting warnings during the + /// after install. + /// Optional product or package version. + /// Prefix to use for diagnostics with numbers 02,03,04 reported by default. If not provided, + /// a default one is determined from the and values. + /// Min random milliseconds to apply during build for non-sponsoring users. Use 0 for no pause. + /// Max random milliseconds to apply during build for non-sponsoring users. Use 0 for no pause. + /// Optional days to keep warnings quiet so the user has a chance to test the product undisturbed. + /// Whether the check is transitive (enforced when dependency is indirect in a project). public static SponsorLinkSettings Create(string sponsorable, string product, string? packageId = default, string? version = default, string? diagnosticsIdPrefix = default, int pauseMin = 0, int pauseMax = DefaultMaxPause, - int? quietDays = default) + int? quietDays = default, + bool transitive = default) { if (quietDays < 0) // Throwing would be a backwards incompatible change. @@ -129,7 +164,8 @@ public static SponsorLinkSettings Create(string sponsorable, string product, PauseMin = pauseMin, PauseMax = pauseMax, QuietDays = quietDays, - SupportedDiagnostics = SponsorLink.Diagnostics.GetDescriptors(sponsorable, diagnosticsIdPrefix) + SupportedDiagnostics = SponsorLink.Diagnostics.GetDescriptors(sponsorable, diagnosticsIdPrefix), + Transitive = transitive }; }