├── appscreenshot.png ├── NativeAOTDependencyHelper.App ├── Assets │ ├── LargeTile.scale-100.png │ ├── LargeTile.scale-125.png │ ├── LargeTile.scale-150.png │ ├── LargeTile.scale-200.png │ ├── LargeTile.scale-400.png │ ├── SmallTile.scale-100.png │ ├── SmallTile.scale-125.png │ ├── SmallTile.scale-150.png │ ├── SmallTile.scale-200.png │ ├── SmallTile.scale-400.png │ ├── StoreLogo.scale-100.png │ ├── StoreLogo.scale-125.png │ ├── StoreLogo.scale-150.png │ ├── StoreLogo.scale-200.png │ ├── StoreLogo.scale-400.png │ ├── SplashScreen.scale-100.png │ ├── SplashScreen.scale-125.png │ ├── SplashScreen.scale-150.png │ ├── SplashScreen.scale-200.png │ ├── SplashScreen.scale-400.png │ ├── LockScreenLogo.scale-200.png │ ├── Square44x44Logo.scale-100.png │ ├── Square44x44Logo.scale-125.png │ ├── Square44x44Logo.scale-150.png │ ├── Square44x44Logo.scale-200.png │ ├── Square44x44Logo.scale-400.png │ ├── Wide310x150Logo.scale-100.png │ ├── Wide310x150Logo.scale-125.png │ ├── Wide310x150Logo.scale-150.png │ ├── Wide310x150Logo.scale-200.png │ ├── Wide310x150Logo.scale-400.png │ ├── Square150x150Logo.scale-100.png │ ├── Square150x150Logo.scale-125.png │ ├── Square150x150Logo.scale-150.png │ ├── Square150x150Logo.scale-200.png │ ├── Square150x150Logo.scale-400.png │ ├── Square44x44Logo.targetsize-16.png │ ├── Square44x44Logo.targetsize-24.png │ ├── Square44x44Logo.targetsize-256.png │ ├── Square44x44Logo.targetsize-32.png │ ├── Square44x44Logo.targetsize-48.png │ ├── Square44x44Logo.altform-unplated_targetsize-16.png │ ├── Square44x44Logo.altform-unplated_targetsize-256.png │ ├── Square44x44Logo.altform-unplated_targetsize-32.png │ ├── Square44x44Logo.altform-unplated_targetsize-48.png │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-16.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-24.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-256.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-32.png │ └── Square44x44Logo.altform-lightunplated_targetsize-48.png ├── Properties │ └── launchSettings.json ├── App.xaml ├── Controls │ ├── DetailsControl.xaml.cs │ └── DetailsControl.xaml ├── MainWindow.xaml.cs ├── app.manifest ├── MainWindow.xaml ├── Package.appxmanifest ├── App.xaml.cs ├── NativeAOTDependencyHelper.App.csproj ├── MainPage.xaml.cs └── Styles │ └── Button.xaml ├── .config └── dotnet-tools.json ├── nuget.config ├── NativeAOTDependencyHelper.ViewModels ├── Services │ ├── LogItemData.cs │ └── BasicLogger.cs ├── NativeAOTDependencyHelper.ViewModels.csproj ├── NuGetPackageViewModel.cs └── MainViewModel.cs ├── NativeAOTDependencyHelper.Core ├── Messages.cs ├── Services │ ├── ILogger.cs │ ├── GitHubOAuth │ │ └── GitHubOAuthService.cs │ ├── SolutionPackageIndex.cs │ └── TaskOrchestrator.cs ├── Models │ ├── PackageLoadStatus.cs │ ├── NuGetPackageProjectReference.cs │ ├── IReportItemProvider.cs │ ├── IDataSource.cs │ ├── ReportItem.cs │ └── NuGetPackageInfo.cs ├── NativeAOTDependencyHelper.Core.csproj ├── JsonModels │ ├── GitHubIssueSearchResult.cs │ ├── GitHubCodeSearchResult.cs │ ├── NuGetServiceIndex.cs │ ├── DotnetPackageList.cs │ └── NuGetPackageRegistration.cs ├── Checks │ ├── IsTrimmableMetadataCheck.cs │ ├── GitHubAotFlagCheck.cs │ ├── NuGetRecentlyUpdatedCheck.cs │ └── NuGetLatestVersionCheck.cs ├── Reports │ ├── GitHubAotIssuesReport.cs │ └── NuGetLicenseReport.cs ├── CredentialManager.cs ├── DotnetToolingInterop.cs └── Sources │ ├── GitHubIssueSearchDataSource.cs │ ├── GitHubAotCompatibleCodeSearchDataSource.cs │ └── NuGetDataSource.cs ├── CONTRIBUTING.md ├── LICENSE ├── settings.xamlstyler ├── SECURITY.md ├── .github └── workflows │ └── build.yml ├── ApplyXamlStyling.ps1 ├── NativeAOTDependencyHelper.sln ├── .gitignore ├── Readme.md └── .editorconfig /appscreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/appscreenshot.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/LargeTile.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/LargeTile.scale-100.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/LargeTile.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/LargeTile.scale-125.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/LargeTile.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/LargeTile.scale-150.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/LargeTile.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/LargeTile.scale-200.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/LargeTile.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/LargeTile.scale-400.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/SmallTile.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/SmallTile.scale-100.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/SmallTile.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/SmallTile.scale-125.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/SmallTile.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/SmallTile.scale-150.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/SmallTile.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/SmallTile.scale-200.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/SmallTile.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/SmallTile.scale-400.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/StoreLogo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/StoreLogo.scale-100.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/StoreLogo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/StoreLogo.scale-125.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/StoreLogo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/StoreLogo.scale-150.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/StoreLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/StoreLogo.scale-200.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/StoreLogo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/StoreLogo.scale-400.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/SplashScreen.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/SplashScreen.scale-100.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/SplashScreen.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/SplashScreen.scale-125.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/SplashScreen.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/SplashScreen.scale-150.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/SplashScreen.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/SplashScreen.scale-400.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.scale-100.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.scale-125.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.scale-150.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.scale-400.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Wide310x150Logo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Wide310x150Logo.scale-100.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Wide310x150Logo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Wide310x150Logo.scale-125.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Wide310x150Logo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Wide310x150Logo.scale-150.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Wide310x150Logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Wide310x150Logo.scale-400.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square150x150Logo.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square150x150Logo.scale-100.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square150x150Logo.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square150x150Logo.scale-125.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square150x150Logo.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square150x150Logo.scale-150.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square150x150Logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square150x150Logo.scale-400.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.targetsize-16.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.targetsize-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.targetsize-24.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.targetsize-256.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.targetsize-32.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.targetsize-48.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-unplated_targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-unplated_targetsize-16.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-unplated_targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-unplated_targetsize-256.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-unplated_targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-unplated_targetsize-32.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-unplated_targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-unplated_targetsize-48.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "xamlstyler.console": { 6 | "version": "3.2501.8", 7 | "commands": [ 8 | "xstyler" 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/NativeAOTDependencyHelper/HEAD/NativeAOTDependencyHelper.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "NativeAOTDependencyHelper.App (Package)": { 4 | "commandName": "MsixPackage" 5 | }, 6 | "NativeAOTDependencyHelper.App (Unpackaged)": { 7 | "commandName": "Project" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.ViewModels/Services/LogItemData.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | namespace NativeAOTDependencyHelper.ViewModels.Services; 6 | 7 | public record LogItemData(string Message, LogItemLevel Level) 8 | { 9 | } 10 | 11 | public enum LogItemLevel 12 | { 13 | Info, 14 | Warning, 15 | Error, 16 | } 17 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Messages.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.Services; 6 | 7 | namespace NativeAOTDependencyHelper.Core; 8 | 9 | /// 10 | /// Message sent when an error has been logged in the . 11 | /// 12 | public record LoggedErrorMessage(string Message); -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Services/ILogger.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace NativeAOTDependencyHelper.Core.Services; 8 | 9 | public interface ILogger 10 | { 11 | void Information(string message, [CallerMemberName] string member = ""); 12 | 13 | void Warning(string message, [CallerMemberName] string member = ""); 14 | 15 | void Error(Exception exception, string message, [CallerMemberName] string member = ""); 16 | } 17 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.ViewModels/NativeAOTDependencyHelper.ViewModels.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | preview 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Models/PackageLoadStatus.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | namespace NativeAOTDependencyHelper.Core.Models; 6 | 7 | public enum PackageLoadStatus 8 | { 9 | /// 10 | /// Package was successfully loaded and processed. 11 | /// 12 | Success, 13 | /// 14 | /// Package data is currently loading 15 | /// 16 | Loading, 17 | /// 18 | /// Package was found but there was an error processing it. 19 | /// 20 | Error, 21 | /// 22 | /// Package processing was cancelled 23 | /// 24 | Cancelled 25 | } -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/App.xaml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/NativeAOTDependencyHelper.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | preview 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to 4 | agree to a Contributor License Agreement (CLA) declaring that you have the right to, 5 | and actually do, grant us the rights to use your contribution. For details, visit 6 | https://cla.microsoft.com. 7 | 8 | When you submit a pull request, a CLA-bot will automatically determine whether you need 9 | to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the 10 | instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 11 | 12 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 13 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 14 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 15 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/JsonModels/GitHubIssueSearchResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.Models; 6 | 7 | namespace NativeAOTDependencyHelper.Core.JsonModels; 8 | 9 | public class GitHubIssueSearchResult 10 | { 11 | public int TotalItems { get; private set; } 12 | public Uri? IssuesQuery { get; private set; } 13 | public CheckStatus checkStatus { get; set; } = CheckStatus.Unavailable; 14 | 15 | public string? Error { get; set; } 16 | 17 | public GitHubIssueSearchResult(int totalItems, Uri? issuesQuery, string? error = null) 18 | { 19 | TotalItems = totalItems; 20 | IssuesQuery = issuesQuery; 21 | if (error != null) 22 | { 23 | Error = error; 24 | checkStatus = CheckStatus.Error; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Controls/DetailsControl.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using Microsoft.UI.Xaml; 6 | using Microsoft.UI.Xaml.Controls; 7 | using NativeAOTDependencyHelper.ViewModels; 8 | 9 | namespace NativeAOTDependencyHelper.App.Controls; 10 | 11 | public sealed partial class DetailsControl : UserControl 12 | { 13 | public DetailsControl() 14 | { 15 | this.InitializeComponent(); 16 | } 17 | 18 | public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(nameof(ViewModel), typeof(NuGetPackageViewModel), typeof(DetailsControl), new PropertyMetadata(defaultValue: null)); 19 | 20 | public NuGetPackageViewModel ViewModel 21 | { 22 | get => (NuGetPackageViewModel)GetValue(ViewModelProperty); 23 | set => SetValue(ViewModelProperty, value); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Services/GitHubOAuth/GitHubOAuthService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using Nito.AsyncEx; 6 | using Octokit; 7 | 8 | namespace NativeAOTDependencyHelper.Core.Services; 9 | 10 | public class GitHubOAuthService(CredentialManager credentialManager) 11 | { 12 | const string clientName = "NativeAOTDependencyHelper"; 13 | 14 | private GitHubClient? _gitHubClient; 15 | 16 | private readonly AsyncLock _mutex = new(); 17 | 18 | public async Task StartAuthRequest() 19 | { 20 | using (await _mutex.LockAsync()) 21 | { 22 | if (_gitHubClient != null) return _gitHubClient; 23 | 24 | _gitHubClient = new GitHubClient(new ProductHeaderValue(clientName)) 25 | { 26 | Credentials = new Credentials(credentialManager.ReadCredential()) 27 | }; 28 | return _gitHubClient; 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using Microsoft.UI; 6 | using Microsoft.UI.Xaml; 7 | 8 | // To learn more about WinUI, the WinUI project structure, 9 | // and more about our project templates, see: http://aka.ms/winui-project-info. 10 | 11 | namespace NativeAOTDependencyHelper.App; 12 | 13 | /// 14 | /// An empty window that can be used on its own or navigated to within a Frame. 15 | /// 16 | public sealed partial class MainWindow : Window 17 | { 18 | public MainWindow() 19 | { 20 | this.InitializeComponent(); 21 | this.AppWindow.TitleBar.ExtendsContentIntoTitleBar = true; 22 | this.AppWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent; 23 | this.AppWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; 24 | this.AppWindow.TitleBar.PreferredHeightOption = Microsoft.UI.Windowing.TitleBarHeightOption.Tall; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | PerMonitorV2 17 | 18 | 19 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/JsonModels/GitHubCodeSearchResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.Models; 6 | using System.Text.Json.Serialization; 7 | 8 | namespace NativeAOTDependencyHelper.Core.JsonModels; 9 | 10 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 11 | public class GitHubCodeSearchResult 12 | { 13 | public string Name { get; set; } = string.Empty; 14 | 15 | public string Path { get; set; } = string.Empty; 16 | 17 | public string Url { get; set; } = string.Empty; 18 | 19 | [JsonPropertyName("download_url")] 20 | public string DownloadUrl { get; set; } = string.Empty; 21 | 22 | public string Type { get; set; } = string.Empty; 23 | 24 | public string Encoding { get; set; } = string.Empty; 25 | 26 | public bool? IsAotCompatible { get; set; } 27 | 28 | public CheckStatus CheckStatus { get; set; } = CheckStatus.Unavailable; 29 | 30 | public string? Error { get; set; } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/JsonModels/NuGetServiceIndex.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Text.Json.Serialization; 6 | 7 | namespace NativeAOTDependencyHelper.Core.JsonModels; 8 | 9 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 10 | public class NuGetServiceIndex 11 | { 12 | public string Version { get; set; } = string.Empty; 13 | public NuGetResource[] Resources { get; set; } = Array.Empty(); 14 | 15 | [JsonPropertyName("@context")] 16 | public NuGetServiceContext? Context { get; set; } 17 | } 18 | 19 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 20 | public class NuGetServiceContext 21 | { 22 | [JsonPropertyName("@vocab")] 23 | public string Vocab { get; set; } = string.Empty; 24 | 25 | public string Comment { get; set; } = string.Empty; 26 | } 27 | 28 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 29 | public class NuGetResource 30 | { 31 | [JsonPropertyName("@id")] 32 | public string Id { get; set; } = string.Empty; 33 | 34 | [JsonPropertyName("@type")] 35 | public string Type { get; set; } = string.Empty; 36 | 37 | public string Comment { get; set; } = string.Empty; 38 | 39 | public string ClientVersion { get; set; } = string.Empty; 40 | } 41 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/JsonModels/DotnetPackageList.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Text.Json.Serialization; 6 | 7 | namespace NativeAOTDependencyHelper.Core.JsonModels; 8 | 9 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 10 | public class DotnetPackageList 11 | { 12 | public int Version { get; set; } 13 | 14 | public string Parameters { get; set; } = string.Empty; 15 | 16 | public Project[] Projects { get; set; } = Array.Empty(); 17 | } 18 | 19 | public class Project 20 | { 21 | public string Path { get; set; } = string.Empty; 22 | 23 | public ProjectFramework[] Frameworks { get; set; } = Array.Empty(); 24 | } 25 | 26 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 27 | public class ProjectFramework 28 | { 29 | public string Framework { get; set; } = string.Empty; 30 | 31 | public NuGetPackage[] TopLevelPackages { get; set; } = Array.Empty(); 32 | 33 | public NuGetPackage[] TransitivePackages { get; set; } = Array.Empty(); 34 | } 35 | 36 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 37 | public class NuGetPackage 38 | { 39 | public string Id { get; set; } = string.Empty; 40 | 41 | public string RequestedVersion { get; set; } = string.Empty; 42 | 43 | public string ResolvedVersion { get; set; } = string.Empty; 44 | } 45 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Checks/IsTrimmableMetadataCheck.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.JsonModels; 6 | using NativeAOTDependencyHelper.Core.Models; 7 | using NativeAOTDependencyHelper.Core.Services; 8 | 9 | 10 | namespace NativeAOTDependencyHelper.Core.Checks; 11 | public class IsTrimmableMetadataCheck(TaskOrchestrator _orchestrator, IDataSource _nugetSource) : IReportItemProvider 12 | { 13 | public string Name => "IsTrimmable Assembly Flag"; 14 | public ReportCategory Category => ReportCategory.AOTCompatibility; 15 | public int SortOrder => 4; 16 | public string Description => "Is the package assembly metadata marked as trimmable?"; 17 | public ReportType Type => ReportType.Check; 18 | public async Task ProcessPackage(NuGetPackageInfo package, CancellationToken cancellationToken) 19 | { 20 | var packageMetadata = await _orchestrator.GetDataFromSourceForPackageAsync(_nugetSource, package, cancellationToken); 21 | if (packageMetadata != null) 22 | { 23 | if (packageMetadata.IsTrimmable) return new AOTCheckItem(this, CheckStatus.Passed, "Assembly is marked as trimmable"); 24 | return new AOTCheckItem(this, CheckStatus.Warning, "Assembly is not marked as trimmable. However, if IsAotCompatible flag is true, then the package is also trimmable."); 25 | } 26 | return new AOTCheckItem(this, CheckStatus.Error, "Error fetching NuGet package registration."); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Models/NuGetPackageProjectReference.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | namespace NativeAOTDependencyHelper.Core.Models; 6 | 7 | /// 8 | /// Represents a .NET Project's reference to a NuGet package, contained within . 9 | /// 10 | /// The filepath to the project that uses this package. 11 | /// The target framework this package is evaluating for. 12 | /// The version which the project requested for this package. 13 | /// The version NuGet decided to use for this package. 14 | /// How many levels removed is this package from the top-level packages for this project? If zero, it is a top-level reference directly included by a project. 15 | public record NuGetPackageProjectReference(string ParentProjectPath, 16 | string Framework, 17 | string RequestedVersion, 18 | string ResolvedVersion, 19 | int TransitiveLayer) 20 | { 21 | /// 22 | /// Gets whether this is a transitive reference or not (dependency of another directly referenced package). true when is non-zero. 23 | /// 24 | public bool IsTransitive => TransitiveLayer > 0; 25 | 26 | public string Name => Path.GetFileName(ParentProjectPath); 27 | } 28 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Checks/GitHubAotFlagCheck.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.JsonModels; 6 | using NativeAOTDependencyHelper.Core.Models; 7 | using NativeAOTDependencyHelper.Core.Services; 8 | 9 | namespace NativeAOTDependencyHelper.Core.Checks; 10 | 11 | public class GitHubAotFlagCheck(TaskOrchestrator _orchestrator, IDataSource _gitHubSource) : IReportItemProvider 12 | { 13 | public string Name => "IsAotCompatible Flag"; 14 | 15 | public int SortOrder => 10; 16 | 17 | public ReportCategory Category => ReportCategory.AOTCompatibility; 18 | 19 | public ReportType Type => ReportType.Check; 20 | 21 | public string Description => "Searches GitHub source code (if available) for the project flag"; 22 | 23 | public async Task ProcessPackage(NuGetPackageInfo package, CancellationToken cancellationToken) 24 | { 25 | var packageMetadata = await _orchestrator.GetDataFromSourceForPackageAsync(_gitHubSource, package, cancellationToken); 26 | if (packageMetadata == null) return new AOTCheckItem(this, CheckStatus.Unavailable, "Flag not found for package."); 27 | if (packageMetadata.Error != null) return new AOTCheckItem(this, CheckStatus.Error, $"Error performing GitHub code search: {packageMetadata.Error}", null, "Error performing GitHub AOT tag code search."); 28 | if (packageMetadata.DownloadUrl == null) return new AOTCheckItem(this, packageMetadata.CheckStatus, "Flag found, but source file could not be retrieved. Please check repository for more details."); 29 | return new AOTCheckItem(this, packageMetadata.CheckStatus, "Click to navigate to source file", new Uri(packageMetadata.DownloadUrl)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Reports/GitHubAotIssuesReport.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.JsonModels; 6 | using NativeAOTDependencyHelper.Core.Models; 7 | using NativeAOTDependencyHelper.Core.Services; 8 | 9 | namespace NativeAOTDependencyHelper.Core.Reports; 10 | 11 | public class GitHubAotIssuesReport(TaskOrchestrator _orchestrator, IDataSource _gitHubSource) : IReportItemProvider 12 | { 13 | public string Name => "AOT-related GitHub issues"; 14 | 15 | public int SortOrder => 10; 16 | 17 | public ReportCategory Category => ReportCategory.AOTCompatibility; 18 | 19 | public ReportType Type => ReportType.Report; 20 | 21 | public string Description => "Searches GitHub (if available) for open issues containing 'AOT'"; 22 | 23 | public async Task ProcessPackage(NuGetPackageInfo package, CancellationToken cancellationToken) 24 | { 25 | if (cancellationToken.IsCancellationRequested) return null; 26 | var packageMetadata = await _orchestrator.GetDataFromSourceForPackageAsync(_gitHubSource, package, cancellationToken); 27 | if (packageMetadata == null) return new ReportItem(this, "No repository data given to search for AOT-related issues", null); 28 | if (packageMetadata.Error != null) return new ReportItem(this, "Error performing GitHub issues search for query " + packageMetadata.IssuesQuery, null, "Error performing GitHub issues search request: " + packageMetadata.Error); 29 | if (packageMetadata.TotalItems == 0) return new ReportItem(this, "No open issues found. View search query.", packageMetadata.IssuesQuery); 30 | return new ReportItem(this, $"{packageMetadata.TotalItems} open issues found on GitHub", packageMetadata.IssuesQuery); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /settings.xamlstyler: -------------------------------------------------------------------------------- 1 | { 2 | "AttributesTolerance": 1, 3 | "KeepFirstAttributeOnSameLine": true, 4 | "MaxAttributeCharactersPerLine": 0, 5 | "MaxAttributesPerLine": 1, 6 | "NewlineExemptionElements": "toolkit:Case, RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, Condition, Setter", 7 | "SeparateByGroups": false, 8 | "AttributeIndentation": 0, 9 | "AttributeIndentationStyle": 1, 10 | "RemoveDesignTimeReferences": false, 11 | "EnableAttributeReordering": true, 12 | "AttributeOrderingRuleGroups": [ 13 | "x:Class", 14 | "xmlns, xmlns:x", 15 | "xmlns:*", 16 | "x:Key, Key, x:Name, Name, x:Uid, Uid, Title", 17 | "Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom", 18 | "Value", 19 | "Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight", 20 | "Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex", 21 | "*:*, *", 22 | "PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint", 23 | "mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText", 24 | "Storyboard.*, From, To, Duration" 25 | ], 26 | "FirstLineAttributes": "", 27 | "OrderAttributesByName": true, 28 | "PutEndingBracketOnNewLine": false, 29 | "RemoveEndingTagOfEmptyElement": true, 30 | "SpaceBeforeClosingSlash": true, 31 | "RootElementLineBreakRule": 0, 32 | "ReorderVSM": 1, 33 | "ReorderGridChildren": false, 34 | "ReorderCanvasChildren": false, 35 | "ReorderSetters": 0, 36 | "FormatMarkupExtension": true, 37 | "NoNewLineMarkupExtensions": "x:Bind, Binding", 38 | "ThicknessSeparator": 2, 39 | "ThicknessAttributes": "Margin, Padding, BorderThickness, ThumbnailClipMargin", 40 | "FormatOnSave": true, 41 | "CommentPadding": 2, 42 | "IndentSize": 4 43 | } -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Services/SolutionPackageIndex.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.JsonModels; 6 | using NativeAOTDependencyHelper.Core.Models; 7 | 8 | namespace NativeAOTDependencyHelper.Core.Services; 9 | 10 | /// 11 | /// This is a special root data source which is expected to be used by all other data sources. 12 | /// 13 | public class SolutionPackageIndex(DotnetToolingInterop _dotnetInterop, ILogger _logger) 14 | { 15 | public string Name => "Root list of Packages"; 16 | 17 | public string Description => "Package information contained within a Solution file retrieves with the dotnet cmdline tool. This is the root source of information for other data sources."; 18 | 19 | public DotnetPackageList? RawPackageList { get; private set; } 20 | 21 | public IEnumerable? Packages { get; private set; } 22 | 23 | public bool HasLoaded { get; private set; } 24 | 25 | public async Task InitializeAsync(string solutionFilePath) 26 | { 27 | HasLoaded = false; 28 | // https://learn.microsoft.com/dotnet/core/tools/dotnet-list-package 29 | RawPackageList = await _dotnetInterop.GetTransitiveDependencyListAsync(solutionFilePath); 30 | 31 | if (RawPackageList != null 32 | && RawPackageList.Projects != null 33 | && RawPackageList.Projects.Length > 0) 34 | { 35 | Packages = NuGetPackageInfo.FromJsonModels(RawPackageList); 36 | 37 | HasLoaded = true; 38 | _logger.Information($"Loaded Package Information for {solutionFilePath}"); 39 | } 40 | else 41 | { 42 | _logger.Error(new InvalidOperationException("Couldn't load package dependency data"), $"Issue processing solution {solutionFilePath}"); 43 | } 44 | 45 | return HasLoaded; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/CredentialManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using Meziantou.Framework.Win32; 6 | 7 | namespace NativeAOTDependencyHelper.Core; 8 | 9 | /// 10 | /// CredentialManager helper class copied from the Microsoft Store CLI project. 11 | /// 12 | public class CredentialManager 13 | { 14 | internal string ApplicationName { get; set; } = "NativeAOTDependencyHelper"; 15 | 16 | private string defaultUserName = "default"; 17 | 18 | public bool HasCredential { get; private set; } = false; 19 | 20 | private string GetCredentialName() => $"{ApplicationName}:user={defaultUserName}"; 21 | 22 | public CredentialManager() 23 | { 24 | HasCredential = !string.IsNullOrEmpty(ReadCredential()); 25 | } 26 | 27 | public string? ReadCredential() 28 | { 29 | var clientCredential = Meziantou.Framework.Win32.CredentialManager.ReadCredential(GetCredentialName()); 30 | 31 | if (clientCredential != null) 32 | { 33 | return clientCredential.Password; 34 | } 35 | 36 | return string.Empty; 37 | } 38 | 39 | public void WriteCredential(string secret) 40 | { 41 | Meziantou.Framework.Win32.CredentialManager.WriteCredential(GetCredentialName(), defaultUserName, secret, CredentialPersistence.LocalMachine); 42 | HasCredential = !String.IsNullOrEmpty(secret); 43 | } 44 | 45 | public void ClearCredentials(string userName) 46 | { 47 | try 48 | { 49 | var credentialName = GetCredentialName(); 50 | if (Meziantou.Framework.Win32.CredentialManager.EnumerateCredentials(credentialName).Any()) 51 | { 52 | Meziantou.Framework.Win32.CredentialManager.DeleteCredential(credentialName); 53 | } 54 | HasCredential = false; 55 | } 56 | catch 57 | { 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.ViewModels/Services/BasicLogger.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using CommunityToolkit.Mvvm.Messaging; 6 | using NativeAOTDependencyHelper.Core; 7 | using NativeAOTDependencyHelper.Core.Services; 8 | using System.Collections.ObjectModel; 9 | using System.Runtime.CompilerServices; 10 | 11 | namespace NativeAOTDependencyHelper.ViewModels.Services; 12 | 13 | public class BasicLogger(TaskScheduler _uiScheduler) : ILogger 14 | { 15 | public ObservableCollection LogItems { get; private set; } = new(); 16 | 17 | public void Clear() 18 | { 19 | Task.Factory.StartNew(() => 20 | { 21 | LogItems.Clear(); 22 | }, CancellationToken.None, TaskCreationOptions.None, _uiScheduler); 23 | } 24 | 25 | public void Information(string message, [CallerMemberName] string member = "") 26 | { 27 | Task.Factory.StartNew(() => 28 | { 29 | LogItems.Add(new($"{DateTime.Now} - {member} - {message}", LogItemLevel.Info)); 30 | }, CancellationToken.None, TaskCreationOptions.None, _uiScheduler); 31 | } 32 | 33 | public void Warning(string message, [CallerMemberName] string member = "") 34 | { 35 | Task.Factory.StartNew(() => 36 | { 37 | LogItems.Add(new($"{DateTime.Now} - {member} - {message}", LogItemLevel.Warning)); 38 | }, CancellationToken.None, TaskCreationOptions.None, _uiScheduler); 39 | } 40 | 41 | public void Error(Exception exception, string message, [CallerMemberName] string member = "") 42 | { 43 | Task.Factory.StartNew(() => 44 | { 45 | LogItems.Add(new($"{DateTime.Now} - {member} - {message}:\n\t{exception.Message}\n\t{exception.StackTrace}", LogItemLevel.Error)); 46 | 47 | WeakReferenceMessenger.Default.Send(new(message)); 48 | }, CancellationToken.None, TaskCreationOptions.None, _uiScheduler); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Package.appxmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | NativeAOTDependencyHelper.App 19 | mhawker 20 | Assets\StoreLogo.png 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Models/IReportItemProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | namespace NativeAOTDependencyHelper.Core.Models; 6 | 7 | /// 8 | /// Defines a singleton provider of items. 9 | /// Use constructor injection to get access to desired . 10 | /// 11 | public interface IReportItemProvider 12 | { 13 | /// 14 | /// Gets the name of the report item provider. 15 | /// 16 | public string Name { get; } 17 | 18 | /// 19 | /// Gets the category of the information this provider provides. 20 | /// 21 | public ReportCategory Category { get; } 22 | 23 | /// 24 | /// Gets the which is either a "Check" or "Report". Checks return a instead of a . 25 | /// 26 | public ReportType Type { get; } 27 | 28 | /// 29 | /// Gets the desired sort order for this item within the list of reports. 30 | /// 31 | public int SortOrder { get; } 32 | 33 | /// 34 | /// Gets a description of the report type for the provider. 35 | /// 36 | public string Description { get; } 37 | 38 | /// 39 | /// Processes data from the given data sources (will happen for each package) and returns a new instance of to add to that package's checklist. 40 | /// 41 | /// Instances of the requested in , will be in the same order as requested or empty if none. 42 | /// A to cancel the operation. 43 | /// A new (or ) for the given . 44 | public Task ProcessPackage(NuGetPackageInfo package, CancellationToken cancellationToken); 45 | } 46 | 47 | public enum ReportCategory 48 | { 49 | Informational, 50 | Health, 51 | AOTCompatibility 52 | } 53 | 54 | public enum ReportType 55 | { 56 | Report, 57 | Check 58 | } -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Reports/NuGetLicenseReport.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.JsonModels; 6 | using NativeAOTDependencyHelper.Core.Models; 7 | using NativeAOTDependencyHelper.Core.Services; 8 | 9 | namespace NativeAOTDependencyHelper.Core.Reports; 10 | 11 | public class NuGetLicenseReport(TaskOrchestrator _orchestrator, IDataSource _nugetSource) : IReportItemProvider 12 | { 13 | public string Name => "Package License"; 14 | 15 | public ReportCategory Category => ReportCategory.Informational; 16 | 17 | public int SortOrder => 10; 18 | 19 | public string Description => "Displays the license of the dependency"; 20 | 21 | public ReportType Type => ReportType.Report; 22 | 23 | public async Task ProcessPackage(NuGetPackageInfo package, CancellationToken cancellationToken) 24 | { 25 | var packageMetadata = await _orchestrator.GetDataFromSourceForPackageAsync(_nugetSource, package, cancellationToken); 26 | 27 | if (packageMetadata?.Items.LastOrDefault() is RegistrationListings registrationList) 28 | { 29 | var latest = registrationList.Upper; 30 | foreach (var registration in registrationList.Items) 31 | { 32 | if (cancellationToken.IsCancellationRequested) return null; 33 | if (registration.CatalogEntry.Version == latest) 34 | { 35 | // TODO: Do we care if this fails? 36 | Uri.TryCreate(registration.CatalogEntry.LicenseUrl, UriKind.Absolute, out var licenseUri); 37 | 38 | if (String.IsNullOrEmpty(registration.CatalogEntry.LicenseExpression)) 39 | { 40 | if (licenseUri != null) return new ReportItem(this, "Click to view license details", licenseUri); 41 | else return new ReportItem(this, "License information not available", licenseUri); 42 | } 43 | 44 | return new ReportItem(this, $"License: {registration.CatalogEntry.LicenseExpression}", licenseUri); 45 | } 46 | } 47 | } 48 | 49 | return new ReportItem(this, "Could not find license information."); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Models/IDataSource.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | namespace NativeAOTDependencyHelper.Core.Models; 6 | 7 | /// 8 | /// Defines a source of data which will be needed for various 9 | /// 10 | public interface IDataSource // TODO: Needed to make this generic for how we implement the InfoForPackage call, but then not sure how this will effect getting an aggregate list from the service provider... maybe that doesn't matter? 11 | { 12 | public string Name { get; } 13 | 14 | public string Description { get; } 15 | 16 | /// 17 | /// Gets whether or not this data source has been initialized and is ready for requests via . If this is false, the will call . Be sure to set this to true once your has been called. 18 | /// 19 | public bool IsInitialized { get; } 20 | 21 | // TODO: Probably need a flag for if we're in an error state and can't continue? 22 | 23 | /// 24 | /// Called before the data source is used in case anything needs to be setup ahead of time. 25 | /// 26 | /// True if the datasource is ready to retrieve more data about packages. 27 | public Task InitializeAsync(); 28 | 29 | /// 30 | /// Called by the to retrieve the requested metadata for the given package. This result will be cached for other s to use if looking at the same info for this data source. 31 | /// 32 | /// The metadata type this datasource returns. 33 | /// The for the package that data is being requested. 34 | /// A to cancel the operation. 35 | /// The requested information of the requested type parameter from the data source or null. 36 | public Task GetInfoForPackageAsync(NuGetPackageInfo package, CancellationToken cancellationToken); 37 | } 38 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/DotnetToolingInterop.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.JsonModels; 6 | using NativeAOTDependencyHelper.Core.Services; 7 | using System.Diagnostics; 8 | using System.Text.Json; 9 | 10 | namespace NativeAOTDependencyHelper.Core; 11 | 12 | public class DotnetToolingInterop(ILogger _logger) 13 | { 14 | private static readonly JsonSerializerOptions JsonOptions = new() 15 | { 16 | PropertyNameCaseInsensitive = true, 17 | }; 18 | 19 | public async Task CheckDotnetVersion() 20 | { 21 | Process process = new(); 22 | process.StartInfo.FileName = "dotnet.exe"; 23 | process.StartInfo.Arguments = "--version"; 24 | process.StartInfo.RedirectStandardOutput = true; 25 | process.StartInfo.UseShellExecute = false; 26 | process.StartInfo.CreateNoWindow = true; 27 | 28 | process.Start(); 29 | 30 | var version = await process.StandardOutput.ReadToEndAsync(); 31 | 32 | await process.WaitForExitAsync(); 33 | 34 | return version; 35 | } 36 | 37 | public async Task GetTransitiveDependencyListAsync(string solutionpath) 38 | { 39 | try 40 | { 41 | Process process = new(); 42 | process.StartInfo.FileName = "dotnet.exe"; 43 | process.StartInfo.Arguments = $"list {solutionpath} package --include-transitive --format json"; 44 | process.StartInfo.RedirectStandardOutput = true; 45 | process.StartInfo.UseShellExecute = false; 46 | process.StartInfo.CreateNoWindow = true; 47 | 48 | process.Start(); 49 | 50 | var json = await process.StandardOutput.ReadToEndAsync(); 51 | 52 | await process.WaitForExitAsync(); 53 | 54 | if (json.StartsWith("error:")) 55 | { 56 | _logger.Error(new InvalidOperationException(json), $"Error processing solution file {solutionpath}"); 57 | return null; 58 | } 59 | 60 | return JsonSerializer.Deserialize(json, JsonOptions); 61 | } 62 | catch (Exception ex) 63 | { 64 | _logger.Error(ex, $"Issue parsing output of `dotnet list {solutionpath} package --include-transitive --format json`"); 65 | return null; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Models/ReportItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | namespace NativeAOTDependencyHelper.Core.Models; 6 | 7 | /// 8 | /// Defines an item result to report general data back from a . 9 | /// 10 | /// reference to the provider of this data 11 | /// additional detail text about the state of the report 12 | /// (optionally) a link to direct to the result/source of the report information. 13 | public record ReportItem(IReportItemProvider Provider, string ReportDetails, Uri? ResultDetailLink = null, string? ProcessingError = null); 14 | 15 | /// 16 | /// Defines a special type of that additional performs a validation check, ; that'll report status towards AOT compatibility reliant on an . 17 | /// 18 | /// the status/result of the check, 19 | /// reference to the provider of this data 20 | /// additional detail text about the state of the report 21 | /// (optionally) a link to direct to the result/source of the report information. 22 | public record AOTCheckItem(IReportItemProvider Provider, CheckStatus Status, string ReportDetails, Uri? ResultDetailLink = null, string? ProcessingError = null) : ReportItem(Provider, ReportDetails, ResultDetailLink, ProcessingError); 23 | 24 | /// 25 | /// The various possible outcomes of a check returnable in an . 26 | /// 27 | public enum CheckStatus 28 | { 29 | /// 30 | /// Returned if the check encountered an error while processing. 31 | /// 32 | Error, 33 | /// 34 | /// Returned when the check has been performed, but the result requires investigation/indicates a potential issue with AOT compatibility. 35 | /// 36 | Warning, 37 | /// 38 | /// Returned when the check has passed and does not indicate a potential issue with AOT compatibility; Note: this does not guarantee AOT compatibility. 39 | /// 40 | Passed, 41 | /// 42 | /// Returned when the source to perform the check is unavailable and the check cannot be verified 43 | /// 44 | Unavailable 45 | } -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Checks/NuGetRecentlyUpdatedCheck.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.JsonModels; 6 | using NativeAOTDependencyHelper.Core.Models; 7 | using NativeAOTDependencyHelper.Core.Services; 8 | 9 | namespace NativeAOTDependencyHelper.Core.Checks; 10 | 11 | public class NuGetRecentlyUpdatedCheck(TaskOrchestrator _orchestrator, IDataSource _nugetSource) : IReportItemProvider 12 | { 13 | /// 14 | /// Gets how many months old a package must be within to be considered recently updated. 15 | /// 16 | private const int NumberOfMonthsToBeRecentlyUpdated = 12; 17 | 18 | public string Name => "NuGet Package Up-to-date"; 19 | 20 | public ReportCategory Category => ReportCategory.Health; 21 | 22 | public int SortOrder => 5; 23 | 24 | public string Description => "Has this package been updated in the past 12 months?"; 25 | 26 | public ReportType Type => ReportType.Check; 27 | 28 | public async Task ProcessPackage(NuGetPackageInfo package, CancellationToken cancellationToken) 29 | { 30 | var packageMetadata = await _orchestrator.GetDataFromSourceForPackageAsync(_nugetSource, package, cancellationToken); 31 | 32 | bool found = false; 33 | bool isRecent = false; 34 | DateTime? publishedDate = null; 35 | 36 | if (packageMetadata?.Items.LastOrDefault() is RegistrationListings registrationList) 37 | { 38 | var latest = registrationList.Upper; 39 | foreach (var registration in registrationList.Items) 40 | { 41 | if (cancellationToken.IsCancellationRequested) return null; 42 | if (registration.CatalogEntry.Version == latest) 43 | { 44 | found = true; 45 | publishedDate = registration.CatalogEntry.Published; 46 | // Has the package been updated in the last year? 47 | isRecent = publishedDate > DateTime.Now.AddMonths(-NumberOfMonthsToBeRecentlyUpdated); 48 | break; 49 | } 50 | } 51 | } 52 | 53 | if (!found) 54 | { 55 | return new AOTCheckItem(this, CheckStatus.Unavailable, "Could not find latest package details"); 56 | } 57 | 58 | return new AOTCheckItem(this, isRecent ? CheckStatus.Passed : CheckStatus.Warning, $"Package last updated: {publishedDate}"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | 2 | # https://docs.github.com/actions/using-workflows/about-workflows 3 | # https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions 4 | 5 | name: CI 6 | 7 | # Controls when the action will run. 8 | on: 9 | # Triggers the workflow on push or pull request events but only for the main or release branches 10 | push: 11 | branches: [ main ] 12 | pull_request: 13 | branches: [ main ] 14 | 15 | # Allows you to run this workflow manually from the Actions tab 16 | workflow_dispatch: 17 | merge_group: 18 | 19 | env: 20 | DOTNET_VERSION: ${{ '9.0.100' }} 21 | MSBUILD_VERBOSITY: normal 22 | IS_MAIN: ${{ github.ref == 'refs/heads/main' }} 23 | IS_PR: ${{ startsWith(github.ref, 'refs/pull/') }} 24 | IS_RELEASE: ${{ startsWith(github.ref, 'refs/heads/rel/') }} 25 | 26 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 27 | jobs: 28 | # This workflow contains a single job called "Xaml-Style-Check" 29 | Xaml-Style-Check: 30 | runs-on: windows-latest 31 | 32 | # Steps represent a sequence of tasks that will be executed as part of the job 33 | steps: 34 | - name: Install .NET SDK v${{ env.DOTNET_VERSION }} 35 | uses: actions/setup-dotnet@v4 36 | with: 37 | dotnet-version: ${{ env.DOTNET_VERSION }} 38 | 39 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 40 | - name: Checkout Repository 41 | uses: actions/checkout@v4 42 | with: 43 | submodules: recursive 44 | 45 | # Restore Tools from Manifest list in the Repository 46 | - name: Restore dotnet tools 47 | run: dotnet tool restore 48 | 49 | - name: Check XAML Styling 50 | run: powershell -version 5.1 -command "./ApplyXamlStyling.ps1 -Passive" -ErrorAction Stop 51 | 52 | # Build WinUI App 53 | build: 54 | needs: [Xaml-Style-Check] 55 | runs-on: windows-latest 56 | 57 | # Steps represent a sequence of tasks that will be executed as part of the job 58 | steps: 59 | - name: Install .NET SDK v${{ env.DOTNET_VERSION }} 60 | uses: actions/setup-dotnet@v4 61 | with: 62 | dotnet-version: ${{ env.DOTNET_VERSION }} 63 | 64 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 65 | - name: Checkout Repository 66 | uses: actions/checkout@v4 67 | 68 | # Restore Tools from Manifest list in the Repository 69 | - name: Restore dotnet tools 70 | run: dotnet tool restore 71 | 72 | - name: Add msbuild to PATH 73 | uses: microsoft/setup-msbuild@v2 74 | with: 75 | vs-version: '[17.12,)' 76 | 77 | # Build solution 78 | - name: MSBuild 79 | run: > 80 | msbuild.exe /restore 81 | /p:Configuration=Release 82 | /m 83 | /v:${{ env.MSBUILD_VERBOSITY }} 84 | NativeAOTDependencyHelper.sln 85 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.ViewModels/NuGetPackageViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using CommunityToolkit.Mvvm.ComponentModel; 6 | using NativeAOTDependencyHelper.Core.Models; 7 | using System.Collections.ObjectModel; 8 | 9 | namespace NativeAOTDependencyHelper.ViewModels; 10 | 11 | /// 12 | /// Wrapper of all data we need to display in UI. 13 | /// 14 | public partial class NuGetPackageViewModel(NuGetPackageInfo _packageInfo, int _totalChecks) : ObservableObject 15 | { 16 | public NuGetPackageInfo Info { get; } = _packageInfo; 17 | 18 | // TODO: Summarize the number of checks/total and stuff here from MainViewModel 19 | public ObservableCollection ReportItems { get; } = new(); 20 | 21 | public ObservableCollection CheckItems { get; } = new(); 22 | 23 | public bool HasAnyFailedChecks => CheckItems.Any(check => check.Status != CheckStatus.Passed); 24 | 25 | public PackageCheckSummary CheckSummary 26 | { 27 | get 28 | { 29 | if (PassedChecks == TotalChecks) 30 | { 31 | return PackageCheckSummary.AllPassed; 32 | } 33 | else if (PassedChecks == 0) 34 | { 35 | return PackageCheckSummary.NonePassed; 36 | } 37 | 38 | return PackageCheckSummary.SomePassed; 39 | } 40 | } 41 | 42 | /// 43 | /// Total reports + checks (as checks are reports) 44 | /// 45 | [ObservableProperty] 46 | public partial int TotalReports { get; set; } = _totalChecks; 47 | 48 | /// 49 | /// Number of reports + checks currently completed 50 | /// 51 | [ObservableProperty] 52 | [NotifyPropertyChangedFor(nameof(HasAnyFailedChecks))] 53 | [NotifyPropertyChangedFor(nameof(TotalErrorCount))] 54 | [NotifyPropertyChangedFor(nameof(PassedChecks))] 55 | [NotifyPropertyChangedFor(nameof(TotalChecks))] 56 | [NotifyPropertyChangedFor(nameof(CheckSummary))] 57 | public partial int ReportsCompleted { get; set; } 58 | 59 | /// 60 | /// Number of processing errors that occured while generating both checks and reports. 61 | /// 62 | public int TotalErrorCount => CheckItems.Count(check => check.Status == CheckStatus.Error) + ReportItems.Count(report => report.ProcessingError != null); 63 | 64 | /// 65 | /// Number of passed checks. 66 | /// 67 | public int PassedChecks => CheckItems.Count(check => check.Status == CheckStatus.Passed); 68 | 69 | public int TotalChecks => CheckItems.Count; 70 | 71 | [ObservableProperty] 72 | public partial PackageLoadStatus LoadStatus { get; set; } = PackageLoadStatus.Loading; 73 | 74 | public List ProcessingErrors { get; } = new(); 75 | 76 | /// 77 | /// Gets or sets the number of GitHub issues found with the text "AOT". 78 | /// TODO: We should have the status/links for those, expand once we hook into the data source there... 79 | /// 80 | [ObservableProperty] 81 | public partial int? NumberOfAOTIssues { get; set; } 82 | } 83 | 84 | public enum PackageCheckSummary 85 | { 86 | NonePassed, 87 | SomePassed, 88 | AllPassed, 89 | } -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Checks/NuGetLatestVersionCheck.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.JsonModels; 6 | using NativeAOTDependencyHelper.Core.Models; 7 | using NativeAOTDependencyHelper.Core.Services; 8 | using NativeAOTDependencyHelper.Core.Sources; 9 | using System.Text; 10 | 11 | namespace NativeAOTDependencyHelper.Core.Checks; 12 | 13 | /// 14 | /// Checks if you're on the latest version of the package. 15 | /// 16 | /// 17 | /// 18 | public class NuGetLatestVersionCheck(TaskOrchestrator _orchestrator, IDataSource _nugetSource) : IReportItemProvider 19 | { 20 | public string Name => "NuGet Package Latest"; 21 | 22 | public ReportCategory Category => ReportCategory.Health; 23 | 24 | public int SortOrder => 3; 25 | 26 | public string Description => "Are you referencing the latest version available for the NuGet package?"; 27 | 28 | public ReportType Type => ReportType.Check; 29 | 30 | public async Task ProcessPackage(NuGetPackageInfo package, CancellationToken cancellationToken) 31 | { 32 | var packageMetadata = await _orchestrator.GetDataFromSourceForPackageAsync(_nugetSource, package, cancellationToken); 33 | 34 | bool found = false; 35 | string? latest = null; 36 | StringBuilder sb = new(); 37 | 38 | if (packageMetadata?.Items.LastOrDefault() is RegistrationListings registrationList) 39 | { 40 | // TODO: We probably want to look for the latest stable release vs. flagging pre-releases 41 | // On the same line, if they're using the latest pre-release that shouldn't get flagged. 42 | latest = registrationList.Upper; 43 | foreach (var registration in registrationList.Items) 44 | { 45 | if (cancellationToken.IsCancellationRequested) return null; 46 | if (registration.CatalogEntry.Version == latest) 47 | { 48 | // Check each project's version 49 | foreach (var project in package.ProjectReferences) 50 | { 51 | // TODO: We probably have to do something with ResolvedVersion as well, 52 | // at least in the case where there are no requested versions (i.e. pure transitive across all projects) 53 | if (!string.IsNullOrWhiteSpace(project.RequestedVersion) 54 | && project.RequestedVersion != latest) 55 | { 56 | found = true; 57 | 58 | sb.AppendLine($"{project.Name} using {project.RequestedVersion} - latest is {latest}"); 59 | } 60 | } 61 | break; 62 | } 63 | } 64 | } 65 | else 66 | { 67 | return new AOTCheckItem(this, CheckStatus.Error, "Could not read package registration data."); 68 | } 69 | 70 | if (!found) 71 | { 72 | return new AOTCheckItem(this, CheckStatus.Passed, "All projects using latest version"); 73 | } 74 | 75 | return new AOTCheckItem(this, CheckStatus.Warning, sb.ToString()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/App.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.UI.Xaml; 7 | using NativeAOTDependencyHelper.Core; 8 | using NativeAOTDependencyHelper.Core.Checks; 9 | using NativeAOTDependencyHelper.Core.JsonModels; 10 | using NativeAOTDependencyHelper.Core.Models; 11 | using NativeAOTDependencyHelper.Core.Reports; 12 | using NativeAOTDependencyHelper.Core.Services; 13 | using NativeAOTDependencyHelper.Core.Sources; 14 | using NativeAOTDependencyHelper.ViewModels; 15 | using NativeAOTDependencyHelper.ViewModels.Services; 16 | using System; 17 | using System.Threading.Tasks; 18 | 19 | // To learn more about WinUI, the WinUI project structure, 20 | // and more about our project templates, see: http://aka.ms/winui-project-info. 21 | 22 | namespace NativeAOTDependencyHelper.App; 23 | 24 | /// 25 | /// Provides application-specific behavior to supplement the default Application class. 26 | /// 27 | public partial class App : Application 28 | { 29 | private Window _window; 30 | 31 | public nint MainWindowHwnd => WinRT.Interop.WindowNative.GetWindowHandle(_window); 32 | 33 | public IServiceProvider Services { get; } 34 | 35 | /// 36 | /// Initializes the singleton application object. This is the first line of authored code 37 | /// executed, and as such is the logical equivalent of main() or WinMain(). 38 | /// 39 | public App() 40 | { 41 | Services = ConfigureServices(); 42 | 43 | InitializeComponent(); 44 | } 45 | 46 | /// 47 | /// Invoked when the application is launched. 48 | /// 49 | /// Details about the launch request and process. 50 | protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) 51 | { 52 | _window = new MainWindow(); 53 | _window.Activate(); 54 | } 55 | 56 | private static IServiceProvider ConfigureServices() 57 | { 58 | ServiceCollection services = new(); 59 | 60 | // View Models 61 | services.AddSingleton(); 62 | 63 | // Root service 64 | services.AddSingleton(); 65 | services.AddSingleton(); 66 | services.AddSingleton(); 67 | services.AddSingleton(TaskScheduler.FromCurrentSynchronizationContext()); // Grab copy of UI thread 68 | 69 | // Data Sources 70 | services.AddSingleton, NuGetDataSource>(); 71 | services.AddSingleton, GitHubIssueSearchDataSource>(); 72 | services.AddSingleton, GitHubAotCompatibleCodeSearchDataSource>(); 73 | 74 | // Other services 75 | services.AddSingleton(); 76 | services.AddSingleton(); 77 | services.AddSingleton(); 78 | 79 | // Checks 80 | services.AddSingleton(); 81 | services.AddSingleton(); 82 | services.AddSingleton(); 83 | services.AddSingleton(); 84 | services.AddSingleton(); 85 | 86 | // Reports 87 | services.AddSingleton(); 88 | 89 | return services.BuildServiceProvider(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Sources/GitHubIssueSearchDataSource.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.JsonModels; 6 | using NativeAOTDependencyHelper.Core.Models; 7 | using NativeAOTDependencyHelper.Core.Services; 8 | using Nito.AsyncEx; 9 | using Octokit; 10 | 11 | namespace NativeAOTDependencyHelper.Core.Sources; 12 | 13 | public class GitHubIssueSearchDataSource(TaskOrchestrator _orchestrator, IDataSource _nugetSource, GitHubOAuthService gitHubOAuthService, ILogger _logger) : IDataSource 14 | { 15 | public string Name => "GitHub AOT Issue Search"; 16 | 17 | public string Description => "Searches GitHub repo for open issues related to AOT"; 18 | 19 | public bool IsInitialized { get; private set; } 20 | 21 | private GitHubClient? _gitHubClient; 22 | 23 | private const string gitHubUrl = "https://github.com/"; 24 | 25 | private readonly AsyncLock _mutex = new(); 26 | 27 | public async Task InitializeAsync() 28 | { 29 | _gitHubClient = await gitHubOAuthService?.StartAuthRequest(); 30 | IsInitialized = _gitHubClient != null; 31 | if (!IsInitialized) 32 | { 33 | _logger.Warning("GitHub Issue Source hasn't authenticated to GitHub"); 34 | } 35 | else 36 | { 37 | _logger.Information("GitHub Issue Source Authorized for GitHub"); 38 | } 39 | return _gitHubClient != null; 40 | } 41 | 42 | public async Task GetInfoForPackageAsync(NuGetPackageInfo package, CancellationToken cancellationToken) 43 | { 44 | using (await _mutex.LockAsync()) 45 | { 46 | // We mutex the datasource and artificially delay here as GH API is rate limited 5000/hr - https://docs.github.com/rest/using-the-rest-api/rate-limits-for-the-rest-api 47 | await Task.Delay(1000); // We could probably lessen this, but for now leaving as 1000 (over 720) in case we add more checks elsewhere, we should probably manage this in the AuthService centrally or something? 48 | 49 | NuGetPackageRegistration? packageMetadata = await _orchestrator.GetDataFromSourceForPackageAsync(_nugetSource, package, cancellationToken); 50 | if (packageMetadata?.RepositoryUrl == null || !packageMetadata.RepositoryUrl.Contains(gitHubUrl)) return null; 51 | // Parsing repo path. Remove .git url suffix since this doesn't work in search 52 | 53 | var repoUrl = packageMetadata?.RepositoryUrl.Replace(".git", ""); 54 | var repoPath = repoUrl?.Replace(gitHubUrl, ""); 55 | 56 | var request = new SearchIssuesRequest("aot") 57 | { 58 | Repos = new RepositoryCollection { repoPath }, 59 | Type = IssueTypeQualifier.Issue, 60 | State = ItemState.Open 61 | }; 62 | 63 | var queryUri = new Uri($"{repoUrl}/issues?q=type%3Aissue%20state%3Aopen%20aot"); 64 | try 65 | { 66 | var result = await _gitHubClient?.Search.SearchIssues(request); 67 | if (result == null) return null; 68 | return new GitHubIssueSearchResult(result.TotalCount, queryUri); 69 | } 70 | catch (OperationCanceledException e) 71 | { 72 | return new GitHubIssueSearchResult(0, queryUri, e.Message); 73 | } 74 | catch (Exception e) 75 | { 76 | _logger.Error(e, $"Error searching GitHub Issues for {package.Name}"); 77 | return new GitHubIssueSearchResult(0, queryUri, e.Message); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/NativeAOTDependencyHelper.App.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | WinExe 4 | net9.0-windows10.0.19041.0 5 | 10.0.17763.0 6 | NativeAOTDependencyHelper.App 7 | app.manifest 8 | x86;x64;ARM64 9 | win-x86;win-x64;win-arm64 10 | win-$(Platform).pubxml 11 | true 12 | true 13 | preview 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | MSBuild:Compile 59 | 60 | 61 | 62 | 63 | MSBuild:Compile 64 | 65 | 66 | 67 | 68 | MSBuild:Compile 69 | 70 | 71 | 72 | 77 | 78 | true 79 | 80 | -------------------------------------------------------------------------------- /ApplyXamlStyling.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Modify XAML files to adhere to XAML Styler settings. 4 | 5 | .DESCRIPTION 6 | The Apply XAML Stying Script can be used to check or modify XAML files with the repo's XAML Styler settings. 7 | Learn more about XAML Styler at https://github.com/Xavalon/XamlStyler 8 | 9 | By default, uses git status to check all new or modified files. 10 | 11 | Use "PS> Help .\ApplyXamlStyling.ps1 -Full" for more details on parameters. 12 | 13 | .PARAMETER LastCommit 14 | Runs against last commit vs. current changes 15 | 16 | .PARAMETER Unstaged 17 | Runs against unstaged changed files 18 | 19 | .PARAMETER Staged 20 | Runs against staged files vs. current changes 21 | 22 | .PARAMETER Main 23 | Runs against main vs. current branch 24 | 25 | .PARAMETER Passive 26 | Runs a passive check against all files in the repo for the CI 27 | 28 | .EXAMPLE 29 | PS> .\ApplyXamlStyling.ps1 -Main 30 | #> 31 | param( 32 | [switch]$LastCommit = $false, 33 | [switch]$Unstaged = $false, 34 | [switch]$Staged = $false, 35 | [switch]$Main = $false, 36 | [switch]$Passive = $false 37 | ) 38 | 39 | Write-Output "Use 'Help .\ApplyXamlStyling.ps1' for more info or '-Main' to run against all files." 40 | Write-Output "" 41 | Write-Output "Restoring dotnet tools..." 42 | dotnet tool restore 43 | 44 | if (-not $Passive) 45 | { 46 | # Look for unstaged changed files by default 47 | $gitDiffCommand = "git status -s --porcelain" 48 | 49 | if ($Main) 50 | { 51 | Write-Output 'Checking Current Branch against `main` Files Only' 52 | $branch = git status | Select-String -Pattern "On branch (?.*)$" 53 | if ($null -eq $branch.Matches) 54 | { 55 | $branch = git status | Select-String -Pattern "HEAD detached at (?.*)$" 56 | if ($null -eq $branch.Matches) 57 | { 58 | Write-Error 'Don''t know how to fetch branch from `git status`:' 59 | git status | Write-Error 60 | exit 1 61 | } 62 | } 63 | $branch = $branch.Matches.groups[1].Value 64 | $gitDiffCommand = "git diff origin/main $branch --name-only --diff-filter=ACM" 65 | } 66 | elseif ($Unstaged) 67 | { 68 | # Look for unstaged files 69 | Write-Output "Checking Unstaged Files" 70 | $gitDiffCommand = "git diff --name-only --diff-filter=ACM" 71 | } 72 | elseif ($Staged) 73 | { 74 | # Look for staged files 75 | Write-Output "Checking Staged Files Only" 76 | $gitDiffCommand = "git diff --cached --name-only --diff-filter=ACM" 77 | } 78 | elseif ($LastCommit) 79 | { 80 | # Look at last commit files 81 | Write-Output "Checking the Last Commit's Files Only" 82 | $gitDiffCommand = "git diff HEAD^ HEAD --name-only --diff-filter=ACM" 83 | } 84 | else 85 | { 86 | Write-Output "Checking Git Status Files Only" 87 | } 88 | 89 | Write-Output "Running Git Diff: $gitDiffCommand" 90 | $files = Invoke-Expression $gitDiffCommand | Select-String -Pattern "\.xaml$" 91 | 92 | if (-not $Passive -and -not $Main -and -not $Unstaged -and -not $Staged -and -not $LastCommit) 93 | { 94 | # Remove 'status' column of 3 characters at beginning of lines 95 | $files = $files | ForEach-Object { $_.ToString().Substring(3) } 96 | } 97 | 98 | if ($files.count -gt 0) 99 | { 100 | dotnet tool run xstyler -c .\settings.xamlstyler -f $files 101 | } 102 | else 103 | { 104 | Write-Output "No XAML Files found to style..." 105 | } 106 | } 107 | else 108 | { 109 | Write-Output "Checking all files (passively)" 110 | $files = Get-ChildItem *.xaml -Recurse | Select-Object -ExpandProperty FullName | Where-Object { $_ -notmatch "(\\obj\\)|(\\bin\\)" } 111 | 112 | if ($files.count -gt 0) 113 | { 114 | dotnet tool run xstyler -p -c .\settings.xamlstyler -f $files 115 | 116 | if ($lastExitCode -eq 1) 117 | { 118 | Write-Error 'XAML Styling is incorrect, please run `ApplyXamlStyling.ps1 -Main` locally.' 119 | } 120 | 121 | # Return XAML Styler Status 122 | exit $lastExitCode 123 | } 124 | else 125 | { 126 | exit 0 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using CommunityToolkit.Mvvm.Messaging; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.UI.Xaml; 8 | using Microsoft.UI.Xaml.Controls; 9 | using NativeAOTDependencyHelper.Core; 10 | using NativeAOTDependencyHelper.Core.Services; 11 | using NativeAOTDependencyHelper.ViewModels; 12 | using NativeAOTDependencyHelper.ViewModels.Services; 13 | using System; 14 | using Windows.Storage.Pickers; 15 | 16 | namespace NativeAOTDependencyHelper.App; 17 | 18 | /// 19 | /// Main page for our application, hosted in our . 20 | /// 21 | public sealed partial class MainPage : Page, 22 | IRecipient 23 | { 24 | public MainViewModel ViewModel { get; } = ((App)App.Current).Services.GetService(); 25 | 26 | public BasicLogger Logger { get; private set; } = ((App)App.Current).Services.GetService() as BasicLogger; 27 | 28 | public CredentialManager CredentialManager { get; } = ((App)App.Current).Services.GetService(); 29 | 30 | private string _currentLoadPath = null; 31 | 32 | private object _lastSelectedItem = null; 33 | 34 | public MainPage() 35 | { 36 | InitializeComponent(); 37 | 38 | // Explicitly register our interaces as RegisterAll uses reflection 39 | WeakReferenceMessenger.Default.Register(this); 40 | 41 | ViewModel.DotnetVersionCommand.Execute(this); 42 | } 43 | 44 | public void Receive(LoggedErrorMessage message) 45 | { 46 | //OperationalLogExpander.IsExpanded = true; 47 | } 48 | 49 | private async void OpenSolution_Click(object sender, RoutedEventArgs e) 50 | { 51 | // Clear log 52 | Logger.Clear(); // TODO: Do we just make this part of the interface and handle in the MainViewModel? 53 | 54 | // Create a file picker 55 | FileOpenPicker openPicker = new(); 56 | 57 | // Initialize the file picker with the window handle (HWND). 58 | WinRT.Interop.InitializeWithWindow.Initialize(openPicker, ((App)App.Current).MainWindowHwnd); 59 | 60 | // Set options for your file picker 61 | openPicker.ViewMode = PickerViewMode.List; 62 | openPicker.FileTypeFilter.Add(".sln"); 63 | openPicker.FileTypeFilter.Add(".csproj"); 64 | 65 | // Open the picker for the user to pick a file 66 | var file = await openPicker.PickSingleFileAsync(); 67 | 68 | // If no file was selected, cancel operation 69 | if (file == null) return; 70 | 71 | // Caching this to enable project reload 72 | _currentLoadPath = file.Path; 73 | 74 | await ViewModel.ProcessSolutionFileCommand.ExecuteAsync(file.Path); 75 | } 76 | 77 | private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e) 78 | { 79 | CredentialManager.WriteCredential(CredentialInputBox.Password); 80 | ViewModel.UpdateIsOpenSolutionEnabledProperty(); 81 | } 82 | 83 | private async void ReloadSolution_Click(object sender, RoutedEventArgs e) 84 | { 85 | ViewModel.OnProcessFinished(); 86 | if (_currentLoadPath != null) await ViewModel.ProcessSolutionFileCommand.ExecuteAsync(_currentLoadPath); 87 | } 88 | 89 | private void DependencyView_SelectionChanged(object sender, SelectionChangedEventArgs e) 90 | { 91 | // When a package is selected, hide the log view. 92 | if (e.AddedItems.Count != 0) 93 | { 94 | // We're explicitly selecting a package, so we don't care about what we last had selected anymore. 95 | _lastSelectedItem = null; 96 | LogToggleButton.IsChecked = false; 97 | } 98 | } 99 | 100 | private void LogToggleButton_Checked(object sender, RoutedEventArgs e) 101 | { 102 | _lastSelectedItem = DependencyView.SelectedItem; 103 | 104 | // Deselect any package when the log view is open. 105 | DependencyView.SelectedItem = null; 106 | } 107 | 108 | private void LogToggleButton_Unchecked(object sender, RoutedEventArgs e) 109 | { 110 | if (_lastSelectedItem != null) 111 | { 112 | // Select last selected package when we're just hiding it, but not selecting a different package. 113 | DependencyView.SelectedItem = _lastSelectedItem; 114 | } 115 | 116 | _lastSelectedItem = null; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Models/NuGetPackageInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.JsonModels; 6 | 7 | namespace NativeAOTDependencyHelper.Core.Models; 8 | 9 | /// 10 | /// Core base class representing base-level nuget package info from solution file. 11 | /// Remapped from the Json data provided by . 12 | /// 13 | /// The of this package. 14 | /// List of projects which reference this package. // TODO: This could be a problem being a record-type if we add additional references here by transitive dependencies... 15 | public record NuGetPackageInfo(string Name, NuGetPackageProjectReference[] ProjectReferences) 16 | { 17 | /// 18 | /// Gets whether this NuGetPackage is ONLY included due to transitive references and has no direct references by projects. 19 | /// 20 | public bool IsTransitiveOnly => ProjectReferences.All(p => p.IsTransitive); 21 | 22 | public override int GetHashCode() => HashCode.Combine(Name, ProjectReferences); 23 | 24 | /// 25 | /// Takes the Json data from and flattens into a straight list of for passing to . 26 | /// 27 | /// 28 | /// 29 | public static IEnumerable FromJsonModels(DotnetPackageList packageList) 30 | { 31 | // TODO: Would be nice if we can stream the Json deserialization and then process things by package one-by-one here too? 32 | int commonPathIndex = GetFirstDifferentCharacter(packageList.Projects.Select(p => p.Path)); 33 | 34 | if (commonPathIndex < 0) 35 | { 36 | throw new IOException("Invalid project path. Please use a valid .sln file"); 37 | } 38 | 39 | Dictionary> _uniquePackageIndex = new(); 40 | 41 | foreach (var project in packageList.Projects) 42 | { 43 | foreach (var framework in project.Frameworks) 44 | { 45 | // Layer is either TopLevel (0) or Transitive (1) 46 | for (int layer = 0; layer <= 1; layer++) 47 | { 48 | // See Switch Expression: https://learn.microsoft.com/dotnet/csharp/language-reference/operators/switch-expression 49 | foreach (var package in layer switch 50 | { 51 | 0 => framework.TopLevelPackages, 52 | 1 => framework.TransitivePackages, 53 | _ => throw new IndexOutOfRangeException() 54 | }) 55 | { 56 | if (!_uniquePackageIndex.ContainsKey(package.Id)) 57 | { 58 | _uniquePackageIndex[package.Id] = new(); 59 | } 60 | 61 | _uniquePackageIndex[package.Id].Add(new NuGetPackageProjectReference( 62 | ParentProjectPath: project.Path.Substring(commonPathIndex), 63 | Framework: framework.Framework, 64 | RequestedVersion: package.RequestedVersion, 65 | ResolvedVersion: package.ResolvedVersion, 66 | TransitiveLayer: layer)); 67 | } 68 | } 69 | } 70 | } 71 | 72 | // Construct final package info with references to all projects used 73 | foreach ((var packageId, var projects) in _uniquePackageIndex) 74 | { 75 | yield return new NuGetPackageInfo(packageId, projects.ToArray()); 76 | } 77 | } 78 | 79 | /// 80 | /// Given a list of strings, finds the first index where there's not the same character at that index. 81 | /// 82 | /// List of strings 83 | /// index of first difference, -1 if null or only single string 84 | private static int GetFirstDifferentCharacter(IEnumerable strings) 85 | { 86 | if (strings == null) return -1; 87 | if (strings.Count() == 1) return 0; 88 | 89 | int i = 0; 90 | string firstString = strings.First(); 91 | 92 | while (firstString.Length > i) 93 | { 94 | foreach (var str in strings.Skip(1)) 95 | { 96 | // If any of the other strings characters don't match our firststring character, that's it 97 | if (str.Length == i || firstString[i] != str[i]) 98 | { 99 | return i; 100 | } 101 | } 102 | i++; 103 | } 104 | 105 | return i; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Sources/GitHubAotCompatibleCodeSearchDataSource.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.JsonModels; 6 | using NativeAOTDependencyHelper.Core.Models; 7 | using NativeAOTDependencyHelper.Core.Services; 8 | using Nito.AsyncEx; 9 | using Octokit; 10 | using System.Net.Http.Json; 11 | using System.Xml.Linq; 12 | using CheckStatus = NativeAOTDependencyHelper.Core.Models.CheckStatus; 13 | 14 | namespace NativeAOTDependencyHelper.Core.Sources; 15 | 16 | /** 17 | * Performs a code search request to see if the project contains definitions for the flag 18 | */ 19 | public class GitHubAotCompatibleCodeSearchDataSource(TaskOrchestrator _orchestrator, IDataSource _nugetSource, GitHubOAuthService gitHubOAuthService, ILogger _logger) : IDataSource 20 | { 21 | public string Name => "GitHub IsAotCompatible Code Search"; 22 | 23 | public string Description => "Retrieves information about the package source from its GitHub repository, if available. Looks for the IsAotCompatible flag."; 24 | 25 | public bool IsInitialized { get; private set; } 26 | 27 | private GitHubClient? _githubClient; 28 | 29 | private static HttpClient _httpClient = new(); 30 | 31 | private readonly AsyncLock _mutex = new(); 32 | 33 | public async Task InitializeAsync() 34 | { 35 | _githubClient = await gitHubOAuthService?.StartAuthRequest(); 36 | IsInitialized = _githubClient != null; 37 | if (!IsInitialized) 38 | { 39 | _logger.Warning("GitHub Code Source hasn't authenticated to GitHub"); 40 | } 41 | else 42 | { 43 | _logger.Information("GitHub Code Source Authorized for GitHub"); 44 | } 45 | return _githubClient != null; 46 | } 47 | 48 | public async Task GetInfoForPackageAsync(NuGetPackageInfo package, CancellationToken cancellationToken) 49 | { 50 | SearchCodeResult result; 51 | 52 | try 53 | { 54 | using (await _mutex.LockAsync()) 55 | { 56 | // We mutex the datasource and artificially delay here as Code Search API is rate limited 10/min - https://docs.github.com/rest/search/search 57 | await Task.Delay(6250); // Technically, 6000, but adding a bit of buffer. 58 | 59 | // GitHub search code request to fetch source file url that contains tag 60 | cancellationToken.ThrowIfCancellationRequested(); 61 | NuGetPackageRegistration? packageMetadata = await _orchestrator.GetDataFromSourceForPackageAsync(_nugetSource, package, cancellationToken); 62 | if (packageMetadata?.RepositoryUrl == null) return null; 63 | var repoPath = packageMetadata?.RepositoryUrl.Replace("https://github.com/", ""); 64 | var request = new SearchCodeRequest(" tag 82 | cancellationToken.ThrowIfCancellationRequested(); 83 | var repoInfo = await _httpClient.GetFromJsonAsync(gitSource, cancellationToken); // TODO: Check if this will rate limit us too? 84 | var sourceFile = await _httpClient.GetAsync(repoInfo?.DownloadUrl, cancellationToken); 85 | var sourceXml = await sourceFile.Content.ReadAsStreamAsync(); 86 | XDocument doc = XDocument.Load(sourceXml); 87 | 88 | var aotTag = doc.Descendants("PropertyGroup") 89 | .Elements("IsAotCompatible") 90 | .FirstOrDefault(); 91 | 92 | if (aotTag != null) 93 | { 94 | repoInfo.IsAotCompatible = aotTag != null && aotTag.Value == "true"; 95 | repoInfo.CheckStatus = CheckStatus.Passed; 96 | } 97 | else 98 | { 99 | repoInfo.CheckStatus = CheckStatus.Warning; 100 | } 101 | return repoInfo; 102 | } 103 | catch (OperationCanceledException e) 104 | { 105 | return new GitHubCodeSearchResult 106 | { 107 | CheckStatus = CheckStatus.Error, 108 | Error = e.Message 109 | }; 110 | } 111 | catch (Exception e) 112 | { 113 | _logger.Error(e, $"Error searching GitHub Code for {package.Name}"); 114 | return new GitHubCodeSearchResult 115 | { 116 | CheckStatus = CheckStatus.Error, 117 | Error = e.Message 118 | }; 119 | } 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Services/TaskOrchestrator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using Microsoft.Extensions.DependencyInjection; 6 | using NativeAOTDependencyHelper.Core.Models; 7 | using Nito.AsyncEx; 8 | using System.Collections.Concurrent; 9 | 10 | namespace NativeAOTDependencyHelper.Core.Services; 11 | 12 | /// 13 | /// Helper class which orchestrates/reports on all tasks and caches results for reports/checks. 14 | /// 15 | public class TaskOrchestrator(SolutionPackageIndex _servicePackageIndex, IServiceProvider _serviceProvider, ILogger _logger) 16 | { 17 | public event EventHandler? StartedProcessingPackage; 18 | 19 | public event EventHandler? FinishedProcessingPackage; 20 | 21 | public event EventHandler? ReportPackageProgress; 22 | 23 | public int NumberOfProviders { get; private set; } 24 | 25 | private ConcurrentDictionary _dataSourceInitializeLocks = new(); 26 | 27 | private ConcurrentDictionary<(Type, NuGetPackageInfo), object?> _resultCache = new(); 28 | 29 | public async Task ProcessSolutionAsync(string solutionFilePath, CancellationToken cancellationToken) 30 | { 31 | _logger.Information($"Initializing Solution: {solutionFilePath}"); 32 | 33 | if (await _servicePackageIndex.InitializeAsync(solutionFilePath) 34 | && _servicePackageIndex.Packages != null 35 | && _servicePackageIndex.Packages.Any()) 36 | { 37 | var providers = _serviceProvider.GetServices().ToArray(); 38 | 39 | NumberOfProviders = providers.Length; 40 | 41 | List tasks = new(); 42 | 43 | foreach (var package in _servicePackageIndex.Packages) 44 | { 45 | if (cancellationToken.IsCancellationRequested) return false; 46 | _logger.Information($"Processing Package: {package.Name}"); 47 | StartedProcessingPackage?.Invoke(this, new(package)); 48 | // https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming 49 | // https://sergeyteplyakov.github.io/Blog/async/2019/05/21/The-Dangers-of-Task.Factory.StartNew.html 50 | // https://learn.microsoft.com/archive/msdn-magazine/2011/february/msdn-magazine-parallel-computing-it-s-all-about-the-synchronizationcontext 51 | tasks.Add(Task.Factory.StartNew(async () => 52 | { 53 | foreach (var reporter in providers) 54 | { 55 | // TODO: Do we want a background task per reporter to be tracking (and do this in parallel)? 56 | if (cancellationToken.IsCancellationRequested) break; 57 | var report = await reporter.ProcessPackage(package, cancellationToken); 58 | if (report != null) ReportPackageProgress?.Invoke(this, new(package, report)); 59 | } 60 | 61 | // TODO: If we do background the reporters, then we'll want to wait for them all to be done before reporting the package is finished... 62 | FinishedProcessingPackage?.Invoke(this, new(package)); 63 | _logger.Information($"Finished Package: {package.Name}"); 64 | }, cancellationToken)); 65 | } 66 | 67 | Task.WaitAll(tasks.ToArray()); 68 | 69 | // TODO: Do we want to record time to complete? 70 | 71 | return true; 72 | } 73 | 74 | _logger.Warning("No Packages to Process or Issue Loading Package Index"); 75 | 76 | return false; 77 | } 78 | 79 | public async Task GetDataFromSourceForPackageAsync(IDataSource dataSource, NuGetPackageInfo package, CancellationToken cancellationToken) 80 | { 81 | // TODO: We may want to investigate if we can grab all the generic reporter interfaces from the services collection and initialize them before we start processing instead... 82 | if (cancellationToken.IsCancellationRequested) return default; 83 | using (await _dataSourceInitializeLocks.GetOrAdd(typeof(T), new AsyncLock()).LockAsync()) 84 | { 85 | if (!dataSource.IsInitialized) 86 | { 87 | _logger.Information($"Initializing DataSource: {dataSource.Name}"); 88 | await dataSource.InitializeAsync(); 89 | } 90 | 91 | // TODO: We don't need to lock the whole datasource for this... but then it seems excessive to lock on every pairing here... (even though that's what we need). Think about the approach here more. 92 | // Note: We CANNOT use GetOrAdd on our ConcurrentDictionary here as that doesn't guarantee that the factory method will only be called once. 93 | if (cancellationToken.IsCancellationRequested) 94 | { 95 | return default; 96 | } 97 | if (_resultCache.TryGetValue((dataSource.GetType(), package), out var result)) 98 | { 99 | _logger.Information($"Returning Cache: {dataSource.Name} - {package.Name}"); 100 | return (T?)result; 101 | } 102 | else 103 | { 104 | _logger.Information($"Fetching Data: {dataSource.Name} - {package.Name}"); 105 | var resultNew = await dataSource.GetInfoForPackageAsync(package, cancellationToken); 106 | _resultCache[(dataSource.GetType(), package)] = resultNew; 107 | return resultNew; 108 | } 109 | } 110 | } 111 | } 112 | 113 | public class ProcessingPackageEventArgs(NuGetPackageInfo _nugetPackage) : EventArgs 114 | { 115 | public NuGetPackageInfo Package => _nugetPackage; 116 | } 117 | 118 | public class ReportPackageProgressEventArgs(NuGetPackageInfo _nugetPackage, ReportItem _reportItem) : EventArgs 119 | { 120 | public NuGetPackageInfo Package = _nugetPackage; 121 | 122 | public ReportItem ReportItem => _reportItem; 123 | } -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.11.35327.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeAOTDependencyHelper.App", "NativeAOTDependencyHelper.App\NativeAOTDependencyHelper.App.csproj", "{40B3011B-6D58-45BD-98E6-870EB1C75BB1}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeAOTDependencyHelper.Core", "NativeAOTDependencyHelper.Core\NativeAOTDependencyHelper.Core.csproj", "{170D2190-BC14-4B42-9D4D-4D7DA36680D5}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeAOTDependencyHelper.ViewModels", "NativeAOTDependencyHelper.ViewModels\NativeAOTDependencyHelper.ViewModels.csproj", "{E20FA404-676D-4A64-951C-68E3E49A0F84}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Config", "Config", "{F58BA6FD-7033-4A4B-B42C-62CC54F47AAA}" 13 | ProjectSection(SolutionItems) = preProject 14 | .editorconfig = .editorconfig 15 | .gitignore = .gitignore 16 | .config\dotnet-tools.json = .config\dotnet-tools.json 17 | nuget.config = nuget.config 18 | settings.xamlstyler = settings.xamlstyler 19 | EndProjectSection 20 | EndProject 21 | Global 22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 23 | Debug|Any CPU = Debug|Any CPU 24 | Debug|ARM64 = Debug|ARM64 25 | Debug|x64 = Debug|x64 26 | Debug|x86 = Debug|x86 27 | Release|Any CPU = Release|Any CPU 28 | Release|ARM64 = Release|ARM64 29 | Release|x64 = Release|x64 30 | Release|x86 = Release|x86 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Debug|Any CPU.ActiveCfg = Debug|x64 34 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Debug|Any CPU.Build.0 = Debug|x64 35 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Debug|Any CPU.Deploy.0 = Debug|x64 36 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Debug|ARM64.ActiveCfg = Debug|ARM64 37 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Debug|ARM64.Build.0 = Debug|ARM64 38 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Debug|ARM64.Deploy.0 = Debug|ARM64 39 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Debug|x64.ActiveCfg = Debug|x64 40 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Debug|x64.Build.0 = Debug|x64 41 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Debug|x64.Deploy.0 = Debug|x64 42 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Debug|x86.ActiveCfg = Debug|x86 43 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Debug|x86.Build.0 = Debug|x86 44 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Debug|x86.Deploy.0 = Debug|x86 45 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Release|Any CPU.ActiveCfg = Release|x64 46 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Release|Any CPU.Build.0 = Release|x64 47 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Release|Any CPU.Deploy.0 = Release|x64 48 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Release|ARM64.ActiveCfg = Release|ARM64 49 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Release|ARM64.Build.0 = Release|ARM64 50 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Release|ARM64.Deploy.0 = Release|ARM64 51 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Release|x64.ActiveCfg = Release|x64 52 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Release|x64.Build.0 = Release|x64 53 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Release|x64.Deploy.0 = Release|x64 54 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Release|x86.ActiveCfg = Release|x86 55 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Release|x86.Build.0 = Release|x86 56 | {40B3011B-6D58-45BD-98E6-870EB1C75BB1}.Release|x86.Deploy.0 = Release|x86 57 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 58 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Debug|Any CPU.Build.0 = Debug|Any CPU 59 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Debug|ARM64.ActiveCfg = Debug|Any CPU 60 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Debug|ARM64.Build.0 = Debug|Any CPU 61 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Debug|x64.ActiveCfg = Debug|Any CPU 62 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Debug|x64.Build.0 = Debug|Any CPU 63 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Debug|x86.ActiveCfg = Debug|Any CPU 64 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Debug|x86.Build.0 = Debug|Any CPU 65 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Release|Any CPU.Build.0 = Release|Any CPU 67 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Release|ARM64.ActiveCfg = Release|Any CPU 68 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Release|ARM64.Build.0 = Release|Any CPU 69 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Release|x64.ActiveCfg = Release|Any CPU 70 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Release|x64.Build.0 = Release|Any CPU 71 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Release|x86.ActiveCfg = Release|Any CPU 72 | {170D2190-BC14-4B42-9D4D-4D7DA36680D5}.Release|x86.Build.0 = Release|Any CPU 73 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 74 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Debug|Any CPU.Build.0 = Debug|Any CPU 75 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Debug|ARM64.ActiveCfg = Debug|Any CPU 76 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Debug|ARM64.Build.0 = Debug|Any CPU 77 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Debug|x64.ActiveCfg = Debug|Any CPU 78 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Debug|x64.Build.0 = Debug|Any CPU 79 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Debug|x86.ActiveCfg = Debug|Any CPU 80 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Debug|x86.Build.0 = Debug|Any CPU 81 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Release|Any CPU.ActiveCfg = Release|Any CPU 82 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Release|Any CPU.Build.0 = Release|Any CPU 83 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Release|ARM64.ActiveCfg = Release|Any CPU 84 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Release|ARM64.Build.0 = Release|Any CPU 85 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Release|x64.ActiveCfg = Release|Any CPU 86 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Release|x64.Build.0 = Release|Any CPU 87 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Release|x86.ActiveCfg = Release|Any CPU 88 | {E20FA404-676D-4A64-951C-68E3E49A0F84}.Release|x86.Build.0 = Release|Any CPU 89 | EndGlobalSection 90 | GlobalSection(SolutionProperties) = preSolution 91 | HideSolutionNode = FALSE 92 | EndGlobalSection 93 | GlobalSection(ExtensibilityGlobals) = postSolution 94 | SolutionGuid = {7FB82C30-DDBA-4659-BC71-2F20F0FD8F83} 95 | EndGlobalSection 96 | EndGlobal 97 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/Sources/NuGetDataSource.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using NativeAOTDependencyHelper.Core.JsonModels; 6 | using NativeAOTDependencyHelper.Core.Models; 7 | using NativeAOTDependencyHelper.Core.Services; 8 | using NuGet.Configuration; 9 | using System.Net.Http.Json; 10 | using System.Reflection; 11 | using System.Runtime.InteropServices; 12 | using System.Xml.Linq; 13 | 14 | namespace NativeAOTDependencyHelper.Core.Sources; 15 | 16 | public class NuGetDataSource(ILogger _logger) : IDataSource 17 | { 18 | public string Name => "NuGet.org Package Information"; 19 | 20 | public string Description => "Retrieves information about package metadata from NuGet.org"; 21 | 22 | public bool IsInitialized { get; private set; } 23 | 24 | public string? PackageBaseAddress => _serviceTypeToUri.TryGetValue("PackageBaseAddress/3.0.0", out var value) ? value : null; 25 | 26 | public string? RegistrationsBaseUrl => _serviceTypeToUri.TryGetValue("RegistrationsBaseUrl", out var value) ? value : null; 27 | 28 | private static Dictionary _serviceTypeToUri = new(); 29 | 30 | // We're sharing this for all main calls within our source. 31 | // HttpClient lifecycle management best practices: 32 | // https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use 33 | private static HttpClient _sharedHttpClient = new(); 34 | 35 | /// 36 | /// We need to lookup the service url for calling our main nuget service. 37 | /// 38 | public async Task InitializeAsync() 39 | { 40 | // Get service index 41 | var index = await _sharedHttpClient.GetFromJsonAsync("https://api.nuget.org/v3/index.json"); 42 | 43 | if (index == null) 44 | { 45 | _logger.Warning("Issue initializing NuGet data source"); 46 | return false; 47 | } 48 | 49 | foreach (var service in index.Resources) 50 | { 51 | _serviceTypeToUri[service.Type] = service.Id; 52 | } 53 | IsInitialized = true; 54 | _logger.Information("NuGet Data Source Initialized"); 55 | return false; 56 | } 57 | 58 | public async Task GetInfoForPackageAsync(NuGetPackageInfo package, CancellationToken cancellationToken) 59 | { 60 | try 61 | { 62 | // Type = RegistrationsBaseUrl 63 | cancellationToken.ThrowIfCancellationRequested(); 64 | var registration = await _sharedHttpClient.GetFromJsonAsync($"{RegistrationsBaseUrl}{package.Name.ToLower()}/index.json", cancellationToken); 65 | var version = registration?.Items?.FirstOrDefault()?.Upper; 66 | if (registration != null) registration.IsTrimmable = GetAssemblyMetadata(package.Name, version!); 67 | 68 | return await GetMetadataFromNuspec(registration, package.Name, version!); 69 | } 70 | catch (OperationCanceledException e) 71 | { 72 | return new NuGetPackageRegistration 73 | { 74 | Id = package.Name, 75 | Error = e.Message 76 | }; 77 | } 78 | catch (Exception e) 79 | { 80 | _logger.Error(e, $"Error retrieving NuGet package info for {package.Name}"); 81 | return new NuGetPackageRegistration 82 | { 83 | Id = package.Name, 84 | Error = e.Message 85 | }; 86 | } 87 | } 88 | 89 | public bool GetAssemblyMetadata(string packageName, string version) 90 | { 91 | var globalPackagePath = SettingsUtility.GetGlobalPackagesFolder(Settings.LoadDefaultSettings(root: null)); 92 | var packagePath = Path.Combine(globalPackagePath, packageName.ToLower(), version); 93 | 94 | 95 | if (Directory.Exists(packagePath)) 96 | { 97 | string[] runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll"); 98 | string[] paths = Directory.GetFiles(packagePath, "*.dll", SearchOption.AllDirectories); 99 | var resolver = new PathAssemblyResolver(runtimeAssemblies.Concat(paths)); 100 | using var context = new MetadataLoadContext(resolver); 101 | 102 | foreach (var path in paths) 103 | { 104 | try 105 | { 106 | Assembly assembly = context.LoadFromAssemblyPath(path); 107 | var attributes = assembly.GetCustomAttributesData(); 108 | 109 | foreach (var attribute in attributes) 110 | { 111 | if (attribute.AttributeType.Name == typeof(AssemblyMetadataAttribute).Name && 112 | attribute.ConstructorArguments.Count == 2 && 113 | attribute.ConstructorArguments[0].Value?.ToString()?.ToLower() == "istrimmable") 114 | { 115 | return attribute.ConstructorArguments[1].Value?.ToString()?.ToLower() == "true"; 116 | } 117 | } 118 | } 119 | catch (Exception e) 120 | { 121 | _logger.Error(e, $"Error loading assembly {path}"); 122 | } 123 | } 124 | } 125 | else 126 | { 127 | Console.WriteLine($"Package {packageName} {version} not found in the global packages folder."); 128 | } 129 | return false; 130 | } 131 | 132 | private async Task GetMetadataFromNuspec(NuGetPackageRegistration? registration, string packageId, string version) 133 | { 134 | if (registration == null || PackageBaseAddress == null || version == null) return null; 135 | 136 | // Type = PackageBaseAddress/3.0.0 137 | var response = await _sharedHttpClient.GetAsync($"{PackageBaseAddress}{packageId.ToLower()}/{version}/{packageId.ToLower()}.nuspec"); 138 | response.EnsureSuccessStatusCode(); 139 | 140 | using var nuspecStream = await response.Content.ReadAsStreamAsync(); 141 | registration.Metadata = XDocument.Load(nuspecStream); 142 | var repository = from element in registration.Metadata.Descendants() 143 | where element.Name.LocalName == "repository" 144 | select element; 145 | 146 | if (repository != null) 147 | { 148 | registration.RepositoryUrl = repository?.FirstOrDefault()?.Attribute("url")?.Value; 149 | } 150 | 151 | return registration; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | 7 | # User-specific files 8 | *.rsuser 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Mono auto generated files 18 | mono_crash.* 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | *.g.cs 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # StyleCop 67 | StyleCopReport.xml 68 | 69 | # Files built by Visual Studio 70 | *_i.c 71 | *_p.c 72 | *_h.h 73 | *.ilk 74 | *.meta 75 | *.obj 76 | *.iobj 77 | *.pch 78 | *.pdb 79 | *.ipdb 80 | *.pgc 81 | *.pgd 82 | *.rsp 83 | *.sbr 84 | *.tlb 85 | *.tli 86 | *.tlh 87 | *.tmp 88 | *.tmp_proj 89 | *_wpftmp.csproj 90 | *.log 91 | *.vspscc 92 | *.vssscc 93 | .builds 94 | *.pidb 95 | *.svclog 96 | *.scc 97 | 98 | # Chutzpah Test files 99 | _Chutzpah* 100 | 101 | # Visual C++ cache files 102 | ipch/ 103 | *.aps 104 | *.ncb 105 | *.opendb 106 | *.opensdf 107 | *.sdf 108 | *.cachefile 109 | *.VC.db 110 | *.VC.VC.opendb 111 | 112 | # Visual Studio profiler 113 | *.psess 114 | *.vsp 115 | *.vspx 116 | *.sap 117 | 118 | # Visual Studio Trace Files 119 | *.e2e 120 | 121 | # TFS 2012 Local Workspace 122 | $tf/ 123 | 124 | # Guidance Automation Toolkit 125 | *.gpState 126 | 127 | # ReSharper is a .NET coding add-in 128 | _ReSharper*/ 129 | *.[Rr]e[Ss]harper 130 | *.DotSettings.user 131 | 132 | # TeamCity is a build add-in 133 | _TeamCity* 134 | 135 | # DotCover is a Code Coverage Tool 136 | *.dotCover 137 | 138 | # AxoCover is a Code Coverage Tool 139 | .axoCover/* 140 | !.axoCover/settings.json 141 | 142 | # Visual Studio code coverage results 143 | *.coverage 144 | *.coveragexml 145 | 146 | # NCrunch 147 | _NCrunch_* 148 | .*crunch*.local.xml 149 | nCrunchTemp_* 150 | 151 | # MightyMoose 152 | *.mm.* 153 | AutoTest.Net/ 154 | 155 | # Web workbench (sass) 156 | .sass-cache/ 157 | 158 | # Installshield output folder 159 | [Ee]xpress/ 160 | 161 | # DocProject is a documentation generator add-in 162 | DocProject/buildhelp/ 163 | DocProject/Help/*.HxT 164 | DocProject/Help/*.HxC 165 | DocProject/Help/*.hhc 166 | DocProject/Help/*.hhk 167 | DocProject/Help/*.hhp 168 | DocProject/Help/Html2 169 | DocProject/Help/html 170 | 171 | # Click-Once directory 172 | publish/ 173 | 174 | # Publish Web Output 175 | *.[Pp]ublish.xml 176 | *.azurePubxml 177 | # Note: Comment the next line if you want to checkin your web deploy settings, 178 | # but database connection strings (with potential passwords) will be unencrypted 179 | *.pubxml 180 | *.publishproj 181 | 182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 183 | # checkin your Azure Web App publish settings, but sensitive information contained 184 | # in these scripts will be unencrypted 185 | PublishScripts/ 186 | 187 | # NuGet Packages 188 | **/nuget.exe 189 | *.nupkg 190 | # NuGet Symbol Packages 191 | *.snupkg 192 | # The packages folder can be ignored because of Package Restore 193 | **/[Pp]ackages/* 194 | # except build/, which is used as an MSBuild target. 195 | !**/[Pp]ackages/build/ 196 | # Uncomment if necessary however generally it will be regenerated when needed 197 | #!**/[Pp]ackages/repositories.config 198 | # NuGet v3's project.json files produces more ignorable files 199 | *.nuget.props 200 | *.nuget.targets 201 | 202 | # Microsoft Azure Build Output 203 | csx/ 204 | *.build.csdef 205 | 206 | # Microsoft Azure Emulator 207 | ecf/ 208 | rcf/ 209 | 210 | # Windows Store app package directories and files 211 | AppPackages/ 212 | BundleArtifacts/ 213 | Package.StoreAssociation.xml 214 | _pkginfo.txt 215 | *.appx 216 | *.appxbundle 217 | *.appxupload 218 | 219 | # Visual Studio cache files 220 | # files ending in .cache can be ignored 221 | *.[Cc]ache 222 | # but keep track of directories ending in .cache 223 | !?*.[Cc]ache/ 224 | 225 | # Others 226 | ClientBin/ 227 | ~$* 228 | *~ 229 | *.dbmdl 230 | *.dbproj.schemaview 231 | *.jfm 232 | *.pfx 233 | *.publishsettings 234 | orleans.codegen.cs 235 | 236 | # Including strong name files can present a security risk 237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 238 | #*.snk 239 | 240 | # Since there are multiple workflows, uncomment next line to ignore bower_components 241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 242 | #bower_components/ 243 | 244 | # RIA/Silverlight projects 245 | Generated_Code/ 246 | 247 | # Backup & report files from converting an old project file 248 | # to a newer Visual Studio version. Backup files are not needed, 249 | # because we have git ;-) 250 | _UpgradeReport_Files/ 251 | Backup*/ 252 | UpgradeLog*.XML 253 | UpgradeLog*.htm 254 | ServiceFabricBackup/ 255 | *.rptproj.bak 256 | 257 | # SQL Server files 258 | *.mdf 259 | *.ldf 260 | *.ndf 261 | 262 | # Business Intelligence projects 263 | *.rdl.data 264 | *.bim.layout 265 | *.bim_*.settings 266 | *.rptproj.rsuser 267 | *- [Bb]ackup.rdl 268 | *- [Bb]ackup ([0-9]).rdl 269 | *- [Bb]ackup ([0-9][0-9]).rdl 270 | 271 | # Microsoft Fakes 272 | FakesAssemblies/ 273 | 274 | # GhostDoc plugin setting file 275 | *.GhostDoc.xml 276 | 277 | # Node.js Tools for Visual Studio 278 | .ntvs_analysis.dat 279 | node_modules/ 280 | 281 | # Visual Studio 6 build log 282 | *.plg 283 | 284 | # Visual Studio 6 workspace options file 285 | *.opt 286 | 287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 288 | *.vbw 289 | 290 | # Visual Studio LightSwitch build output 291 | **/*.HTMLClient/GeneratedArtifacts 292 | **/*.DesktopClient/GeneratedArtifacts 293 | **/*.DesktopClient/ModelManifest.xml 294 | **/*.Server/GeneratedArtifacts 295 | **/*.Server/ModelManifest.xml 296 | _Pvt_Extensions 297 | 298 | # Paket dependency manager 299 | .paket/paket.exe 300 | paket-files/ 301 | 302 | # FAKE - F# Make 303 | .fake/ 304 | 305 | # CodeRush personal settings 306 | .cr/personal 307 | 308 | # Python Tools for Visual Studio (PTVS) 309 | __pycache__/ 310 | *.pyc 311 | 312 | # Cake - Uncomment if you are using it 313 | # tools/** 314 | # !tools/packages.config 315 | 316 | # Tabs Studio 317 | *.tss 318 | 319 | # Telerik's JustMock configuration file 320 | *.jmconfig 321 | 322 | # BizTalk build output 323 | *.btp.cs 324 | *.btm.cs 325 | *.odx.cs 326 | *.xsd.cs 327 | 328 | # OpenCover UI analysis results 329 | OpenCover/ 330 | 331 | # Azure Stream Analytics local run output 332 | ASALocalRun/ 333 | 334 | # MSBuild Binary and Structured Log 335 | *.binlog 336 | 337 | # NVidia Nsight GPU debugger configuration file 338 | *.nvuser 339 | 340 | # MFractors (Xamarin productivity tool) working folder 341 | .mfractor/ 342 | 343 | # Local History for Visual Studio 344 | .localhistory/ 345 | 346 | # BeatPulse healthcheck temp database 347 | healthchecksdb 348 | 349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 350 | MigrationBackup/ 351 | 352 | # Ionide (cross platform F# VS Code tools) working folder 353 | .ionide/ 354 | 355 | # JetBrains Rider files 356 | .idea/ 357 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using CommunityToolkit.Mvvm.ComponentModel; 6 | using CommunityToolkit.Mvvm.Input; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using NativeAOTDependencyHelper.Core; 9 | using NativeAOTDependencyHelper.Core.Models; 10 | using NativeAOTDependencyHelper.Core.Services; 11 | using System.Collections.ObjectModel; 12 | 13 | namespace NativeAOTDependencyHelper.ViewModels; 14 | 15 | public partial class MainViewModel(IServiceProvider _serviceProvider, TaskScheduler _uiScheduler, DotnetToolingInterop _dotnetInterop, ILogger _logger, CredentialManager _credentialManager) : ObservableObject 16 | { 17 | public IAsyncRelayCommand DotnetVersionCommand { get; } = new AsyncRelayCommand(_dotnetInterop.CheckDotnetVersion); 18 | 19 | [ObservableProperty] 20 | public partial ObservableCollection Packages { get; set; } = new(); 21 | 22 | [ObservableProperty] 23 | public partial int PackagesProcessed { get; set; } 24 | 25 | [ObservableProperty] 26 | [NotifyPropertyChangedFor(nameof(TotalChecks))] 27 | [NotifyPropertyChangedFor(nameof(IsViewEmpty))] 28 | public partial int TotalPackages { get; set; } 29 | 30 | public int ChecksPerPackage => _taskOrchestrator?.NumberOfProviders ?? 0; 31 | 32 | [ObservableProperty] 33 | public partial int ChecksProcessed { get; set; } 34 | 35 | public int TotalChecks => TotalPackages * ChecksPerPackage; 36 | 37 | [ObservableProperty] 38 | [NotifyPropertyChangedFor(nameof(IsOpenSolutionEnabled))] 39 | public partial bool IsWorking { get; set; } 40 | 41 | public bool IsViewEmpty => Packages.Count == 0; 42 | 43 | public bool IsOpenSolutionEnabled => (!IsWorking || IsCancelled) && _credentialManager.HasCredential && (IsTaskSuccessful(DotnetVersionCommand.ExecutionTask.Status)); 44 | 45 | private TaskOrchestrator? _taskOrchestrator; 46 | 47 | public IReportItemProvider[]? GetReportAndCheckTypes => _serviceProvider?.GetServices().ToArray(); 48 | 49 | private static bool IsTaskSuccessful(TaskStatus status) => status == TaskStatus.RanToCompletion; 50 | 51 | private CancellationTokenSource? _cancellationToken; 52 | 53 | public bool IsCancelled => _cancellationToken?.IsCancellationRequested ?? false; 54 | 55 | public void CancelProcess() 56 | { 57 | if (_cancellationToken != null) 58 | { 59 | _logger.Warning("Cancellation requested. Cancelling all running processes."); 60 | foreach (NuGetPackageViewModel package in Packages) 61 | { 62 | if (package.LoadStatus == PackageLoadStatus.Loading) 63 | { 64 | package.LoadStatus = PackageLoadStatus.Cancelled; 65 | } 66 | } 67 | 68 | OnProcessFinished(); 69 | UpdateIsOpenSolutionEnabledProperty(); 70 | } 71 | } 72 | 73 | public void OnProcessFinished() 74 | { 75 | IsWorking = false; 76 | if (_taskOrchestrator != null) 77 | { 78 | _taskOrchestrator.StartedProcessingPackage -= _taskOrchestrator_StartedProcessingPackage; 79 | _taskOrchestrator.ReportPackageProgress -= _taskOrchestrator_ReportPackageProgress; 80 | _taskOrchestrator.FinishedProcessingPackage -= _taskOrchestrator_FinishedProcessingPackage; 81 | } 82 | _cancellationToken?.Cancel(); 83 | _cancellationToken?.Dispose(); 84 | _cancellationToken = null; 85 | } 86 | 87 | 88 | // TODO: Have error string to report back issues initializing? 89 | 90 | public void UpdateIsOpenSolutionEnabledProperty() => OnPropertyChanged(nameof(IsOpenSolutionEnabled)); 91 | 92 | [RelayCommand] 93 | public async Task ProcessSolutionFileAsync(string filepath) 94 | { 95 | _taskOrchestrator = _serviceProvider?.GetService(); 96 | 97 | // Ensure we start fresh each time! 98 | Packages.Clear(); 99 | PackagesProcessed = 0; 100 | TotalPackages = 0; 101 | ChecksProcessed = 0; 102 | 103 | _cancellationToken = new CancellationTokenSource(); 104 | 105 | if (_taskOrchestrator != null) 106 | { 107 | // Keep track of what's happening, progress, and completion 108 | _taskOrchestrator.StartedProcessingPackage += _taskOrchestrator_StartedProcessingPackage; 109 | _taskOrchestrator.ReportPackageProgress += _taskOrchestrator_ReportPackageProgress; 110 | _taskOrchestrator.FinishedProcessingPackage += _taskOrchestrator_FinishedProcessingPackage; 111 | 112 | IsWorking = true; 113 | 114 | var result = await _taskOrchestrator.ProcessSolutionAsync(filepath, _cancellationToken.Token); 115 | 116 | if (!result) 117 | { 118 | _logger.Error(new InvalidOperationException($"There was an issue processing the solution {filepath}"), "Processing Error"); 119 | } 120 | 121 | return result; 122 | } 123 | 124 | return false; 125 | } 126 | 127 | private void _taskOrchestrator_StartedProcessingPackage(object? sender, ProcessingPackageEventArgs e) 128 | { 129 | Task.Factory.StartNew(() => 130 | { 131 | // Add our package to our list now that it's started processing. 132 | Packages.Add(new(e.Package, ChecksPerPackage)); 133 | // Keep track of how many packages we have. 134 | TotalPackages++; 135 | }, _cancellationToken == null ? CancellationToken.None : _cancellationToken.Token, TaskCreationOptions.None, _uiScheduler).Wait(); 136 | } 137 | 138 | private void _taskOrchestrator_ReportPackageProgress(object? sender, ReportPackageProgressEventArgs e) 139 | { 140 | // Need to report/modify the collection on the UI thread. 141 | // https://learn.microsoft.com/archive/msdn-magazine/2011/february/msdn-magazine-parallel-computing-it-s-all-about-the-synchronizationcontext 142 | Task reportProgressTask = Task.Factory.StartNew(() => 143 | { 144 | // TODO: Make a better lookup here or can we just have an ObservableDictionary? 145 | var package = Packages.FirstOrDefault(p => p.Info == e.Package); 146 | 147 | if (package != null) 148 | { 149 | if (e.ReportItem is AOTCheckItem check) 150 | { 151 | package.CheckItems.Add(check); 152 | } 153 | else if (e.ReportItem is ReportItem item) 154 | { 155 | package.ReportItems.Add(item); 156 | } 157 | 158 | if (e.ReportItem.ProcessingError != null 159 | || (e.ReportItem is AOTCheckItem check2 && check2.Status == CheckStatus.Error)) 160 | { 161 | package.ProcessingErrors.Add(e.ReportItem.ProcessingError ?? e.ReportItem.ReportDetails); 162 | package.LoadStatus = PackageLoadStatus.Error; 163 | } 164 | package.ReportsCompleted++; 165 | } 166 | 167 | ChecksProcessed++; 168 | }, _cancellationToken == null ? CancellationToken.None : _cancellationToken.Token, TaskCreationOptions.None, _uiScheduler); 169 | if (!reportProgressTask.IsCanceled) reportProgressTask.Wait(); 170 | } 171 | 172 | private void _taskOrchestrator_FinishedProcessingPackage(object? sender, ProcessingPackageEventArgs e) 173 | { 174 | Task.Factory.StartNew(() => 175 | { 176 | 177 | // Keep track of how many packages we've processed 178 | var package = Packages.FirstOrDefault(p => p.Info == e.Package); 179 | if (package != null) 180 | { 181 | if (package.ProcessingErrors.Count > 0) 182 | { 183 | package.LoadStatus = PackageLoadStatus.Error; 184 | } 185 | else 186 | { 187 | package.LoadStatus = PackageLoadStatus.Success; 188 | } 189 | PackagesProcessed++; 190 | } 191 | 192 | // All of our processing is done! 193 | if (PackagesProcessed == TotalPackages) 194 | { 195 | OnProcessFinished(); 196 | } 197 | }, _cancellationToken == null ? CancellationToken.None : _cancellationToken.Token, TaskCreationOptions.None, _uiScheduler).Wait(); 198 | 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.Core/JsonModels/NuGetPackageRegistration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation 2 | // The Microsoft Corporation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Text.Json.Serialization; 6 | using System.Xml.Linq; 7 | 8 | namespace NativeAOTDependencyHelper.Core.JsonModels; 9 | 10 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 11 | public class NuGetPackageRegistration 12 | { 13 | [JsonPropertyName("@id")] 14 | public string Id { get; set; } = string.Empty; 15 | 16 | [JsonPropertyName("@type")] 17 | public string[] Type { get; set; } = Array.Empty(); 18 | public string CommitId { get; set; } = string.Empty; 19 | public DateTime CommitTimeStamp { get; set; } = DateTime.MinValue; 20 | public int Count { get; set; } = 0; 21 | public RegistrationListings[] Items { get; set; } = Array.Empty(); 22 | public NuGetRegistrationContext? Context { get; set; } 23 | public string? RepositoryUrl { get; set; } 24 | 25 | public string? Error { get; set; } 26 | 27 | public XDocument? Metadata { get; set; } 28 | 29 | public bool IsTrimmable { get; set; } = false; 30 | } 31 | 32 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 33 | public class NuGetRegistrationContext 34 | { 35 | public string Vocab { get; set; } = string.Empty; 36 | public string Catalog { get; set; } = string.Empty; 37 | public string Xsd { get; set; } = string.Empty; 38 | public Items Items { get; set; } = new Items(); 39 | public CommitTimeStamp CommitTimeStamp { get; set; } = new CommitTimeStamp(); 40 | public CommitId CommitId { get; set; } = new CommitId(); 41 | public Count Count { get; set; } = new Count(); 42 | public Parent Parent { get; set; } = new Parent(); 43 | public Tags Tags { get; set; } = new Tags(); 44 | public Reasons Reasons { get; set; } = new Reasons(); 45 | public PackageTargetFrameworks PackageTargetFrameworks { get; set; } = new PackageTargetFrameworks(); 46 | public DependencyGroups DependencyGroups { get; set; } = new DependencyGroups(); 47 | public Dependencies Dependencies { get; set; } = new Dependencies(); 48 | public PackageContent PackageContent { get; set; } = new PackageContent(); 49 | public Published Published { get; set; } = new Published(); 50 | public Registration Registration { get; set; } = new Registration(); 51 | } 52 | 53 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 54 | public class Items 55 | { 56 | [JsonPropertyName("@id")] 57 | public string Id { get; set; } = string.Empty; 58 | 59 | [JsonPropertyName("@container")] 60 | public string Container { get; set; } = string.Empty; 61 | } 62 | 63 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 64 | public class CommitTimeStamp 65 | { 66 | [JsonPropertyName("@id")] 67 | public string Id { get; set; } = string.Empty; 68 | 69 | [JsonPropertyName("@type")] 70 | public string Type { get; set; } = string.Empty; 71 | } 72 | 73 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 74 | public class CommitId 75 | { 76 | [JsonPropertyName("@id")] 77 | public string Id { get; set; } = string.Empty; 78 | } 79 | 80 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 81 | public class Count 82 | { 83 | [JsonPropertyName("@id")] 84 | public string Id { get; set; } = string.Empty; 85 | } 86 | 87 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 88 | public class Parent 89 | { 90 | [JsonPropertyName("@id")] 91 | public string Id { get; set; } = string.Empty; 92 | 93 | [JsonPropertyName("@type")] 94 | public string Type { get; set; } = string.Empty; 95 | } 96 | 97 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 98 | public class Tags 99 | { 100 | [JsonPropertyName("@id")] 101 | public string Id { get; set; } = string.Empty; 102 | 103 | [JsonPropertyName("@container")] 104 | public string Container { get; set; } = string.Empty; 105 | } 106 | 107 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 108 | public class Reasons 109 | { 110 | [JsonPropertyName("@container")] 111 | public string Container { get; set; } = string.Empty; 112 | } 113 | 114 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 115 | public class PackageTargetFrameworks 116 | { 117 | [JsonPropertyName("@id")] 118 | public string Id { get; set; } = string.Empty; 119 | [JsonPropertyName("@container")] 120 | public string Container { get; set; } = string.Empty; 121 | } 122 | 123 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 124 | public class DependencyGroups 125 | { 126 | [JsonPropertyName("@id")] 127 | public string Id { get; set; } = string.Empty; 128 | 129 | [JsonPropertyName("@container")] 130 | public string Container { get; set; } = string.Empty; 131 | } 132 | 133 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 134 | public class Dependencies 135 | { 136 | [JsonPropertyName("@id")] 137 | public string Id { get; set; } = string.Empty; 138 | [JsonPropertyName("@container")] 139 | public string Container { get; set; } = string.Empty; 140 | } 141 | 142 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 143 | public class PackageContent 144 | { 145 | [JsonPropertyName("@type")] 146 | public string Type { get; set; } = string.Empty; 147 | } 148 | 149 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 150 | public class Published 151 | { 152 | [JsonPropertyName("@type")] 153 | public string Type { get; set; } = string.Empty; 154 | } 155 | 156 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 157 | public class Registration 158 | { 159 | [JsonPropertyName("@type")] 160 | public string Type { get; set; } = string.Empty; 161 | } 162 | 163 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 164 | public class RegistrationListings 165 | { 166 | [JsonPropertyName("@id")] 167 | public string Id { get; set; } = string.Empty; 168 | [JsonPropertyName("@type")] 169 | public string Type { get; set; } = string.Empty; 170 | public string CommitId { get; set; } = string.Empty; 171 | public DateTime CommitTimeStamp { get; set; } = DateTime.MinValue; 172 | public int Count { get; set; } = 0; 173 | public CatalogEntryMetadata[] Items { get; set; } = Array.Empty(); 174 | public string Parent { get; set; } = string.Empty; 175 | public string Lower { get; set; } = string.Empty; 176 | public string Upper { get; set; } = string.Empty; 177 | } 178 | 179 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 180 | public class CatalogEntryMetadata 181 | { 182 | [JsonPropertyName("@id")] 183 | public string Id { get; set; } = string.Empty; 184 | [JsonPropertyName("@type")] 185 | public string Type { get; set; } = string.Empty; 186 | public string CommitId { get; set; } = string.Empty; 187 | public DateTime CommitTimeStamp { get; set; } = DateTime.MinValue; 188 | public CatalogEntry CatalogEntry { get; set; } = new CatalogEntry(); 189 | public string PackageContent { get; set; } = string.Empty; 190 | public string Registration { get; set; } = string.Empty; 191 | } 192 | 193 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 194 | public class CatalogEntry 195 | { 196 | [JsonPropertyName("@id")] 197 | public string IdUrl { get; set; } = string.Empty; 198 | [JsonPropertyName("@type")] 199 | public string Type { get; set; } = string.Empty; 200 | public string Authors { get; set; } = string.Empty; 201 | public PackageDependencyGroup[] DependencyGroups { get; set; } = Array.Empty(); 202 | public string Description { get; set; } = string.Empty; 203 | public string IconUrl { get; set; } = string.Empty; 204 | [JsonPropertyName("id")] 205 | public string IdName { get; set; } = string.Empty; 206 | public string Language { get; set; } = string.Empty; 207 | public string LicenseExpression { get; set; } = string.Empty; 208 | public string LicenseUrl { get; set; } = string.Empty; 209 | public bool Listed { get; set; } = false; 210 | public string MinClientVersion { get; set; } = string.Empty; 211 | public string PackageContent { get; set; } = string.Empty; 212 | public string ProjectUrl { get; set; } = string.Empty; 213 | public DateTime Published { get; set; } = DateTime.MinValue; 214 | public bool RequireLicenseAcceptance { get; set; } = false; 215 | public string Summary { get; set; } = string.Empty; 216 | public string[] Tags { get; set; } = Array.Empty(); 217 | public string Title { get; set; } = string.Empty; 218 | public string Version { get; set; } = string.Empty; 219 | } 220 | 221 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 222 | public class PackageDependencyGroup 223 | { 224 | [JsonPropertyName("@id")] 225 | public string Id { get; set; } = string.Empty; 226 | [JsonPropertyName("@type")] 227 | public string Type { get; set; } = string.Empty; 228 | public string TargetFramework { get; set; } = string.Empty; 229 | public PackageDependency[] Dependencies { get; set; } = Array.Empty(); 230 | } 231 | 232 | [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] 233 | public class PackageDependency 234 | { 235 | [JsonPropertyName("@id")] 236 | public string IdUrl { get; set; } = string.Empty; 237 | public string Type { get; set; } = string.Empty; 238 | public string Range { get; set; } = string.Empty; 239 | public string IdName { get; set; } = string.Empty; 240 | public string Registration { get; set; } = string.Empty; 241 | } 242 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Native AOT Dependency Helper Sample 3 | 4 | This is a sample application which shows how to leverage MVVM techniques (using the [MVVM Toolkit](https://aka.ms/mvvmtoolkit/docs)) with [WinUI](https://aka.ms/winui). 5 | 6 | You can learn more about the architecture used in this app from [this .NET Conf sample here](https://github.com/michael-hawker/MVVMNetConfApp) from the [MVVM Building Blocks for WinUI and WPF Development](https://www.youtube.com/watch?v=83UVWrfYreU&list=PLfYoThk3lXST-jocq53plkTnHy463d4xo&index=2) talk. 7 | 8 | The app sceanario itself is meant for .NET developers who are looking to use [Native AOT](https://learn.microsoft.com/dotnet/core/deploying/native-aot/) to publish their application. (Further notes for [WinUI developers are here](https://learn.microsoft.com/windows/apps/windows-app-sdk/stable-channel#native-aot-support)). It provides an early litmus test for checking if your depedencies are AOT compatible before trying to enable and fix issues within your app itself. 9 | 10 | If you are a library author and looking to understand how to make your library AOT compatible, [please see this blog here](https://devblogs.microsoft.com/dotnet/creating-aot-compatible-libraries/) at how to enable [IsAotCompatible](https://learn.microsoft.com/dotnet/core/deploying/native-aot/#aot-compatibility-analyzers) in your library. 11 | 12 | ## Overview 13 | 14 | To use this app, simple clone and build the project within Visual Studio 17.12+ with the "Windows application development" workload. 15 | 16 | ![alt text](appscreenshot.png) 17 | 18 | Then click on the "Open .sln" button to get started. 19 | 20 | The app will ask you to authenticate with GitHub to perform code searches on open source dependencies. 21 | 22 | Once processing has completed, you'll be able to click on each dependency to see the information retrieved and status of checks performed. 23 | 24 | ## Current information and checks retrieved 25 | 26 | The tool bubbles up the following data and performs a series of 'checks' to gauge how well a dependency may support AOT. However, it is not a blanket assurance that something will or won't support AOT for any given scenario. Just a starting point for understanding on your journey. 27 | 28 | This information is retrieved and bubbled up for each dependency in your project, as well as general information like the package Id, whether or not its directly referenced or a transitive dependency, as well as the requested and resolved versions from each referencing project. 29 | 30 | "Reports" just provide general information to bubble up for each package. 31 | 32 | "Checks" look specifically for things that may indicate potential issues with AOT compatibility. Passing a check does **not** guarantee AOT compatibility, conversely failing a check indicated something to investigate or be aware of vs. it being strictly not able to function when AOT is turned on. These are aids to discovery of potentially short comings, known issues, or gaps with your dependency chain to be aware of before attempting to add this functionality to your application. 33 | 34 | | Type | Name | Description | 35 | |--------|-------------------|-------------------------------------------| 36 | | Report | Package License | Displays the license of the dependency | 37 | | Report | GitHub AOT Issues | Searches GitHub (if available) for open issues containing "AOT" | 38 | | Check | GitHub AOT Flag | Searches GitHub source code (if available) for the ["IsAotCompatible" project flag](https://learn.microsoft.com/dotnet/core/deploying/native-aot/#aot-compatibility-analyzers) | 39 | | Check | NuGet Latest Version | Are you referencing the latest version available for the NuGet package | 40 | | Check | NuGet Recently Updated | Has this package been updated in the last year? | 41 | 42 | All items contribute to overall progress of the scan; however, only checks are used as a final status per package overall. 43 | 44 | Checks can have the following statuses: 45 | 46 | - Unavailable - Not enough data/info was available to perform the check 47 | - Error - An exception occurred while processing the check 48 | - Warning - Something was discovered to investigate further 49 | - Passed - Nothing outside the ordinary was discovered, though **does not** guarantee AOT compatibility 50 | 51 | ## Architecture 52 | 53 | This section covers more about how this application is structured. 54 | 55 | It consists of the three main projects below. 56 | 57 | ### NativeAOTDependencyHelper.Core 58 | 59 | This is a base .NET Class Library which holds the majority of the logic in retrieving data and perfoming the analysis of a solution. 60 | 61 | It is mean to be platform agnostic though does depend on being able to run the `dotnet` command line tool. 62 | 63 | All the logic for various reports and checks is contained within here. That process is initiated by the `TaskOrchestrator` and retrieves the initial dependencies in the `SolutionPackageIndex`. 64 | 65 | The `TaskOrchestrator` is responsible for parallelizing the various checks across all the different NuGet packages, but also ensuring that data is cached for reports/checks that rely on the same data (e.g. NuGet package listing). It handles all the threading and locking so that reports/checks don't need to worry about this and can remain relatively simple. 66 | 67 | It is just important for reports/checks to call `GetDataFromSourceForPackageAsync` in order to retrieve data from a data source, as this will ensure that only a single remote request is made and cached data is used if available. 68 | 69 | The `IDataSource` interface defines the contract in retrieving information about a specific NuGet package from its `NuGetPackageInfo`. The list of `NuGetPackageInfo` is provided by the `SolutionPackageIndex` as the starting point based on the provided information about the solutions/project dependencies and is just a basic object containing the NuGet package names (and which projects reference it with the requested version). 70 | 71 | There are currently three data sources available: 72 | 73 | - `NuGetDataSource` - This queries the [NuGet.org API](https://learn.microsoft.com/nuget/api/overview) to retrieve additional data about each package from the Service Index and its Nuspec file in package registration. 74 | - `GitHubIssueSearchDataSource` - This queries [GitHub Issues](https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28) to look for "AOT" in open issues. 75 | - `GitHubAOTCompatibleCodeSearchDataSource` - This queries [GitHub Code Search](https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-code) to look for the `IsAotCompatbile` flag. 76 | 77 | Data sources have an `InitializeAsync` function to implement and then a `GetInfoForPackageAsync` method to retrieve data about the given NuGet package. If they require data from other data sources, they should also retrieve that through the `GetDataFromSourceForPackageAsync` Task Orchestrator method. Dependencies are retrieved through [Dependency Injection](https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/ioc), registration is in `App.xaml.cs` in the main application in `ConfigureServices`. 78 | 79 | [`Octokit`](https://github.com/octokit/octokit.net) is used to retrieve GitHub data, otherwise we use standard HTTP REST for interacting with NuGet.org and use `System.Text.Json` for deserializing data. Authentication to GitHub is handled in `GitHubOAuthService`. The `ILogger` interface can be used to provide information in the UI operational log. 80 | 81 | Both reports and checks implement the `IReportItemProvider` interface which provides information about the name and description of the item and simply implment the `ProcessPackage` method. Which takes in a `NuGetPackageInfo` and returns either a `ReportItem` for a report or an `AOTCheckItem` (which is itself a `ReportItem` + `CheckStatus`) for a check. 82 | 83 | Remember that both new data sources and reports/checks need to be registered in the service configruation in `App.xaml.cs` in order to be detected and processed. This is also how they'll be able to access other dependent services and data sources through constructor injection. See the existing data sources, reports, and checks for examples. 84 | 85 | ### NativeAOTDependencyHelper.ViewModels 86 | 87 | This is a relatively light project which contains ViewModels and helpers for encapsulating data from the Core project. It is also platform agnostic, thanks to the MVVM Toolkit. 88 | 89 | It mostly consists of data aggregation calculations, progress tracking, and delegating to the UI thread. As well as the high-level `ICommand` (provided by `RelayCommand`) to call the underlying core methods to initiate the scanning process. See `ProcessSolutionFileAsync` in `MainViewModel`. 90 | 91 | It also provided the implementation of the BasicLogger on top of the `ILogger` interface storing them as descrete typed records (for coloring within the UI via the new Windows Community Toolkit's [`SwitchConverter`](https://github.com/CommunityToolkit/Windows/pull/550) - created originally for this purpose in this repo!). 92 | 93 | ### NativeAOTDependencyHelper.App 94 | 95 | This is the View layer and contains the WinUI application itself and how the data is presented. 96 | 97 | Much of the view is currently contained within `MainPage.xaml` (using `MainViewModel`) with details for specific NuGet package info retrieved displayed in the `DetailsControl.xaml` user control (which uses the `NuGetPackageViewModel`). `MainWindow.xaml*` are responsible for configuring extending into the title bar of the application. 98 | 99 | The majority of work is performed through data binding using MVVM and XAML techniques. 100 | 101 | We leverage a variety of controls from the [Windows Community Toolkit](https://aka.ms/toolkit/windows), such as the Settings Controls, `SwitchPresenter`, `SwitchConverter`, and converters. 102 | 103 | All the Dependency Injected services and components are registered in `App.xaml.cs`'s `ConfigureServices` method. 104 | 105 | ### Third-Party Notice 106 | 107 | Via NuGet packages, this project depends on the following MIT licensed projects: 108 | 109 | - The MVVM Toolkit 110 | - The Windows Community Toolkit 111 | - Microsoft.Extensions.DependencyInjection 112 | - Nito.AsyncEx 113 | - OctoKit 114 | 115 | ### Trademark Notice 116 | 117 | Trademarks This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft’s Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party’s policies. 118 | 119 | ### License 120 | 121 | This project is licensed under the MIT License, see the [LICENSE](LICENSE) file for more details. 122 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Controls/DetailsControl.xaml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 0 16 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 38 | 39 | 42 | 45 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 63 | 64 | 65 | 67 | 68 | 71 | 72 | 74 | 75 | 76 | 77 | 79 | 80 | 81 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 103 | 106 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 123 | 124 | 125 | 128 | 129 | 130 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 143 | 144 | 147 | 150 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 165 | 169 | 170 | 172 | 174 | 175 | 177 | 180 | 181 | 182 | 184 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /NativeAOTDependencyHelper.App/Styles/Button.xaml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 18 | 20 | 22 | 23 | 25 | 27 | 29 | 31 | 32 | 34 | 36 | 38 | 40 | 41 | 42 | 43 | 44 | 46 | 48 | 50 | 52 | 53 | 55 | 57 | 59 | 61 | 62 | 64 | 66 | 68 | 70 | 71 | 72 | 73 | 74 | 76 | 78 | 80 | 82 | 83 | 85 | 87 | 89 | 91 | 92 | 94 | 96 | 98 | 100 | 101 | 102 | 103 | 219 | 220 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | # Generated code 5 | [*{_AssemblyInfo.cs,.g.cs}] 6 | generated_code = true 7 | 8 | # All files 9 | [*] 10 | 11 | file_header_template = Copyright (c) Microsoft Corporation\r\nThe Microsoft Corporation licenses this file to you under the MIT license.\r\nSee the LICENSE file in the project root for more information. 12 | 13 | #### Core EditorConfig Options #### 14 | 15 | # Encoding 16 | charset = utf-8 17 | 18 | # Indentation and spacing 19 | indent_size = 4 20 | indent_style = space 21 | tab_width = 4 22 | 23 | # New line preferences 24 | end_of_line = crlf 25 | trim_trailing_whitespace = true 26 | insert_final_newline = true 27 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 28 | dotnet_style_coalesce_expression = true:suggestion 29 | dotnet_style_null_propagation = true:suggestion 30 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 31 | dotnet_style_prefer_auto_properties = true:silent 32 | dotnet_style_object_initializer = true:suggestion 33 | dotnet_style_collection_initializer = true:suggestion 34 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 35 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 36 | dotnet_style_prefer_conditional_expression_over_return = true:silent 37 | dotnet_style_explicit_tuple_names = true:suggestion 38 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 39 | dotnet_style_prefer_compound_assignment = true:suggestion 40 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 41 | dotnet_style_namespace_match_folder = true:suggestion 42 | dotnet_style_prefer_simplified_interpolation = true:suggestion 43 | dotnet_style_readonly_field = true:suggestion 44 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 45 | dotnet_style_predefined_type_for_member_access = true:silent 46 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 47 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent 48 | dotnet_style_allow_statement_immediately_after_block_experimental = true:silent 49 | dotnet_code_quality_unused_parameters = all:suggestion 50 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 51 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 52 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 53 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 54 | dotnet_style_qualification_for_field = true:silent 55 | dotnet_style_qualification_for_property = true:silent 56 | dotnet_style_qualification_for_method = true:silent 57 | dotnet_style_qualification_for_event = true:silent 58 | 59 | #### Build files #### 60 | 61 | # Solution files 62 | [*.{sln,slnx}] 63 | tab_width = 4 64 | indent_size = 4 65 | indent_style = tab 66 | 67 | # Configuration files 68 | [*.{json,xml,yml,config,runsettings}] 69 | indent_size = 2 70 | 71 | # MSBuild files 72 | [*.{slnf,props,targets,projitems,csproj,shproj}] 73 | indent_size = 2 74 | 75 | #### Source files #### 76 | 77 | # Markdown files 78 | [*.md] 79 | indent_size = 2 80 | insert_final_newline = true 81 | 82 | # C# files 83 | [*.cs] 84 | 85 | #### .NET Coding Conventions #### 86 | 87 | # this. and Me. preferences 88 | dotnet_style_qualification_for_event = true:silent 89 | dotnet_style_qualification_for_field = true:silent 90 | dotnet_style_qualification_for_method = true:silent 91 | dotnet_style_qualification_for_property = true:silent 92 | 93 | # Language keywords vs BCL types preferences 94 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 95 | dotnet_style_predefined_type_for_member_access = true:silent 96 | 97 | # Parentheses preferences 98 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 99 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 100 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 101 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 102 | 103 | # Modifier preferences 104 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 105 | 106 | # Expression-level preferences 107 | csharp_style_deconstructed_variable_declaration = true:suggestion 108 | csharp_style_inlined_variable_declaration = true:silent 109 | csharp_style_throw_expression = true:suggestion 110 | dotnet_style_coalesce_expression = true:suggestion 111 | dotnet_style_collection_initializer = true:suggestion 112 | dotnet_style_explicit_tuple_names = true:suggestion 113 | dotnet_style_null_propagation = true:suggestion 114 | dotnet_style_object_initializer = true:suggestion 115 | dotnet_style_prefer_auto_properties = true:silent 116 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 117 | dotnet_style_prefer_conditional_expression_over_return = true:silent 118 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 119 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 120 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 121 | 122 | # Field preferences 123 | dotnet_style_readonly_field = true:suggestion 124 | 125 | #### C# Coding Conventions #### 126 | 127 | # var preferences 128 | csharp_style_var_elsewhere = true:silent 129 | csharp_style_var_for_built_in_types = true:silent 130 | csharp_style_var_when_type_is_apparent = true:silent 131 | 132 | # Expression-bodied members 133 | csharp_style_expression_bodied_accessors = false:silent 134 | csharp_style_expression_bodied_constructors = false:silent 135 | csharp_style_expression_bodied_indexers = false:silent 136 | csharp_style_expression_bodied_lambdas = true:silent 137 | csharp_style_expression_bodied_methods = false:silent 138 | csharp_style_expression_bodied_operators = false:silent 139 | csharp_style_expression_bodied_properties = false:silent 140 | 141 | # Pattern matching preferences 142 | csharp_style_pattern_matching_over_as_with_null_check = true:silent 143 | csharp_style_pattern_matching_over_is_with_cast_check = true:silent 144 | 145 | # Null-checking preferences 146 | csharp_style_conditional_delegate_call = true:suggestion 147 | 148 | # Modifier preferences 149 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async 150 | 151 | # Code-block preferences 152 | csharp_prefer_braces = true:suggestion 153 | 154 | # Expression-level preferences 155 | csharp_prefer_simple_default_expression = true:suggestion 156 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 157 | 158 | #### C# Formatting Rules #### 159 | 160 | # New line preferences 161 | csharp_new_line_before_catch = true 162 | csharp_new_line_before_else = true 163 | csharp_new_line_before_finally = true 164 | csharp_new_line_before_members_in_anonymous_types = true 165 | csharp_new_line_before_members_in_object_initializers = true 166 | csharp_new_line_before_open_brace = all 167 | csharp_new_line_between_query_expression_clauses = true 168 | 169 | # Indentation preferences 170 | csharp_indent_block_contents = true 171 | csharp_indent_braces = false 172 | csharp_indent_case_contents = true 173 | csharp_indent_case_contents_when_block = false 174 | csharp_indent_labels = no_change 175 | csharp_indent_switch_labels = true 176 | 177 | # Space preferences 178 | csharp_space_after_cast = false 179 | csharp_space_after_colon_in_inheritance_clause = true 180 | csharp_space_after_comma = true 181 | csharp_space_after_dot = false 182 | csharp_space_after_keywords_in_control_flow_statements = true 183 | csharp_space_after_semicolon_in_for_statement = true 184 | csharp_space_around_binary_operators = before_and_after 185 | csharp_space_around_declaration_statements = false 186 | csharp_space_before_colon_in_inheritance_clause = true 187 | csharp_space_before_comma = false 188 | csharp_space_before_dot = false 189 | csharp_space_before_open_square_brackets = false 190 | csharp_space_before_semicolon_in_for_statement = false 191 | csharp_space_between_empty_square_brackets = false 192 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 193 | csharp_space_between_method_call_name_and_opening_parenthesis = false 194 | csharp_space_between_method_call_parameter_list_parentheses = false 195 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 196 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 197 | csharp_space_between_method_declaration_parameter_list_parentheses = false 198 | csharp_space_between_parentheses = false 199 | csharp_space_between_square_brackets = false 200 | 201 | # Wrapping preferences 202 | csharp_preserve_single_line_blocks = true 203 | csharp_preserve_single_line_statements = true 204 | 205 | # Naming Symbols 206 | 207 | # constant_fields - Define constant fields 208 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 209 | dotnet_naming_symbols.constant_fields.required_modifiers = const 210 | # non_private_readonly_fields - Define public, internal and protected readonly fields 211 | dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected 212 | dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field 213 | dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly 214 | # static_readonly_fields - Define static and readonly fields 215 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field 216 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly 217 | # private_readonly_fields - Define private readonly fields 218 | dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private 219 | dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field 220 | dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly 221 | # public_internal_fields - Define public and internal fields 222 | dotnet_naming_symbols.public_internal_protected_fields.applicable_accessibilities = public, internal, protected 223 | dotnet_naming_symbols.public_internal_protected_fields.applicable_kinds = field 224 | # private_protected_fields - Define private and protected fields 225 | dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected 226 | dotnet_naming_symbols.private_protected_fields.applicable_kinds = field 227 | # public_symbols - Define any public symbol 228 | dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal 229 | dotnet_naming_symbols.public_symbols.applicable_kinds = method, property, event, delegate 230 | # parameters - Defines any parameter 231 | dotnet_naming_symbols.parameters.applicable_kinds = parameter 232 | # non_interface_types - Defines class, struct, enum and delegate types 233 | dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate 234 | # interface_types - Defines interfaces 235 | dotnet_naming_symbols.interface_types.applicable_kinds = interface 236 | 237 | # Naming Styles 238 | 239 | # camel_case - Define the camelCase style 240 | dotnet_naming_style.camel_case.capitalization = camel_case 241 | # pascal_case - Define the Pascal_case style 242 | dotnet_naming_style.pascal_case.capitalization = pascal_case 243 | # first_upper - The first character must start with an upper-case character 244 | dotnet_naming_style.first_upper.capitalization = first_word_upper 245 | # prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I' 246 | dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case 247 | dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I 248 | 249 | # Naming Rules 250 | 251 | # Async 252 | dotnet_naming_rule.async_methods_end_in_async.severity = silent 253 | dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods 254 | dotnet_naming_rule.async_methods_end_in_async.style = end_in_async 255 | 256 | dotnet_naming_symbols.any_async_methods.applicable_kinds = method 257 | dotnet_naming_symbols.any_async_methods.applicable_accessibilities = * 258 | dotnet_naming_symbols.any_async_methods.required_modifiers = async 259 | 260 | dotnet_naming_style.end_in_async.required_suffix = Async 261 | dotnet_naming_style.end_in_async.capitalization = pascal_case 262 | 263 | # Constant fields must be PascalCase 264 | dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = silent 265 | dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields 266 | dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case 267 | # Public, internal and protected readonly fields must be PascalCase 268 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.severity = silent 269 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.symbols = non_private_readonly_fields 270 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pascal_case 271 | # Static readonly fields must be PascalCase 272 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = silent 273 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields 274 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case 275 | # Private readonly fields must be camelCase 276 | dotnet_naming_rule.private_readonly_fields_must_be_camel_case.severity = silent 277 | dotnet_naming_rule.private_readonly_fields_must_be_camel_case.symbols = private_readonly_fields 278 | dotnet_naming_rule.private_readonly_fields_must_be_camel_case.style = camel_case 279 | # Public and internal fields must be PascalCase 280 | dotnet_naming_rule.public_internal_protected_fields_must_be_pascal_case.severity = silent 281 | dotnet_naming_rule.public_internal_protected_fields_must_be_pascal_case.symbols = public_internal_protected_fields 282 | dotnet_naming_rule.public_internal_protected_fields_must_be_pascal_case.style = pascal_case 283 | # Private and protected fields must be camelCase 284 | dotnet_naming_rule.private_fields_must_be_camel_case.severity = silent 285 | dotnet_naming_rule.private_fields_must_be_camel_case.symbols = private_protected_fields 286 | dotnet_naming_rule.private_fields_must_be_camel_case.style = prefix_private_field_with_underscore 287 | # Public members must be capitalized 288 | dotnet_naming_rule.public_members_must_be_capitalized.severity = silent 289 | dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols 290 | dotnet_naming_rule.public_members_must_be_capitalized.style = first_upper 291 | # Parameters must be camelCase 292 | dotnet_naming_rule.parameters_must_be_camel_case.severity = silent 293 | dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters 294 | dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case 295 | # Class, struct, enum and delegates must be PascalCase 296 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = silent 297 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types 298 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case 299 | # Interfaces must be PascalCase and start with an 'I' 300 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = silent 301 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types 302 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interface_interface_with_i 303 | # prefix_private_field_with_underscore - Private fields must be prefixed with _ 304 | dotnet_naming_style.prefix_private_field_with_underscore.capitalization = camel_case 305 | dotnet_naming_style.prefix_private_field_with_underscore.required_prefix = _ 306 | 307 | # .NET Code Analysis 308 | 309 | dotnet_diagnostic.CA1001.severity = warning 310 | dotnet_diagnostic.CA1009.severity = warning 311 | dotnet_diagnostic.CA1016.severity = warning 312 | dotnet_diagnostic.CA1033.severity = warning 313 | dotnet_diagnostic.CA1049.severity = warning 314 | dotnet_diagnostic.CA1060.severity = warning 315 | dotnet_diagnostic.CA1061.severity = warning 316 | dotnet_diagnostic.CA1063.severity = warning 317 | dotnet_diagnostic.CA1065.severity = warning 318 | dotnet_diagnostic.CA1301.severity = warning 319 | dotnet_diagnostic.CA1400.severity = warning 320 | dotnet_diagnostic.CA1401.severity = warning 321 | dotnet_diagnostic.CA1403.severity = warning 322 | dotnet_diagnostic.CA1404.severity = warning 323 | dotnet_diagnostic.CA1405.severity = warning 324 | dotnet_diagnostic.CA1410.severity = warning 325 | dotnet_diagnostic.CA1415.severity = warning 326 | dotnet_diagnostic.CA1821.severity = warning 327 | dotnet_diagnostic.CA1900.severity = warning 328 | dotnet_diagnostic.CA1901.severity = warning 329 | dotnet_diagnostic.CA2002.severity = warning 330 | dotnet_diagnostic.CA2100.severity = warning 331 | dotnet_diagnostic.CA2101.severity = warning 332 | dotnet_diagnostic.CA2108.severity = warning 333 | dotnet_diagnostic.CA2111.severity = warning 334 | dotnet_diagnostic.CA2112.severity = warning 335 | dotnet_diagnostic.CA2114.severity = warning 336 | dotnet_diagnostic.CA2116.severity = warning 337 | dotnet_diagnostic.CA2117.severity = warning 338 | dotnet_diagnostic.CA2122.severity = warning 339 | dotnet_diagnostic.CA2123.severity = warning 340 | dotnet_diagnostic.CA2124.severity = warning 341 | dotnet_diagnostic.CA2126.severity = warning 342 | dotnet_diagnostic.CA2131.severity = warning 343 | dotnet_diagnostic.CA2132.severity = warning 344 | dotnet_diagnostic.CA2133.severity = warning 345 | dotnet_diagnostic.CA2134.severity = warning 346 | dotnet_diagnostic.CA2137.severity = warning 347 | dotnet_diagnostic.CA2138.severity = warning 348 | dotnet_diagnostic.CA2140.severity = warning 349 | dotnet_diagnostic.CA2141.severity = warning 350 | dotnet_diagnostic.CA2146.severity = warning 351 | dotnet_diagnostic.CA2147.severity = warning 352 | dotnet_diagnostic.CA2149.severity = warning 353 | dotnet_diagnostic.CA2200.severity = warning 354 | dotnet_diagnostic.CA2202.severity = warning 355 | dotnet_diagnostic.CA2207.severity = warning 356 | dotnet_diagnostic.CA2212.severity = warning 357 | dotnet_diagnostic.CA2213.severity = warning 358 | dotnet_diagnostic.CA2214.severity = warning 359 | dotnet_diagnostic.CA2216.severity = warning 360 | dotnet_diagnostic.CA2220.severity = warning 361 | dotnet_diagnostic.CA2229.severity = warning 362 | dotnet_diagnostic.CA2231.severity = warning 363 | dotnet_diagnostic.CA2232.severity = warning 364 | dotnet_diagnostic.CA2235.severity = warning 365 | dotnet_diagnostic.CA2236.severity = warning 366 | dotnet_diagnostic.CA2237.severity = warning 367 | dotnet_diagnostic.CA2238.severity = warning 368 | dotnet_diagnostic.CA2240.severity = warning 369 | dotnet_diagnostic.CA2241.severity = warning 370 | dotnet_diagnostic.CA2242.severity = warning 371 | 372 | # StyleCop Code Analysis 373 | 374 | # Closing parenthesis should be spaced correctly: "foo()!" 375 | dotnet_diagnostic.SA1009.severity = none 376 | 377 | # Hide warnings when using the new() expression from C# 9. 378 | dotnet_diagnostic.SA1000.severity = none 379 | 380 | dotnet_diagnostic.SA1011.severity = none 381 | dotnet_diagnostic.SA1101.severity = none 382 | 383 | # Hide warnings when accessing properties without "this". 384 | dotnet_diagnostic.SA1101.severity = none 385 | dotnet_diagnostic.SA1118.severity = none 386 | dotnet_diagnostic.SA1200.severity = none 387 | dotnet_diagnostic.SA1201.severity = none 388 | dotnet_diagnostic.SA1202.severity = none 389 | dotnet_diagnostic.SA1309.severity = none 390 | dotnet_diagnostic.SA1310.severity = none 391 | 392 | # Hide warnings for record parameters. 393 | dotnet_diagnostic.SA1313.severity = none 394 | 395 | # TypeParameterNamesMustBeginWithT: We do have a few templates that don't start with T. We need to double check that changing this is not a breaking change. If not, we can re-enable this. 396 | dotnet_diagnostic.SA1314.severity = none 397 | 398 | # UseTrailingCommasInMultiLineInitializers: This would also mean a lot of changes at the end of all multiline initializers. It's also debatable if we want this or not. 399 | dotnet_diagnostic.SA1413.severity = none 400 | 401 | dotnet_diagnostic.SA1600.severity = none 402 | dotnet_diagnostic.SA1602.severity = none 403 | dotnet_diagnostic.SA1611.severity = none 404 | 405 | # DocumentationTextMustEndWithAPeriod: Let's enable this rule back when we shift to WinUI3 (v8.x). If we do it now, it would mean more than 400 file changes. 406 | dotnet_diagnostic.SA1629.severity = none 407 | 408 | dotnet_diagnostic.SA1633.severity = none 409 | dotnet_diagnostic.SA1634.severity = none 410 | dotnet_diagnostic.SA1652.severity = none 411 | csharp_using_directive_placement = outside_namespace:silent 412 | csharp_prefer_simple_using_statement = true:suggestion 413 | csharp_style_namespace_declarations = file_scoped:warning 414 | csharp_style_expression_bodied_local_functions = false:silent 415 | csharp_style_prefer_null_check_over_type_check = true:suggestion 416 | csharp_style_prefer_local_over_anonymous_function = true:suggestion 417 | csharp_style_prefer_index_operator = true:suggestion 418 | csharp_style_prefer_range_operator = true:suggestion 419 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion 420 | csharp_style_prefer_tuple_swap = true:suggestion 421 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion 422 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent 423 | csharp_prefer_static_local_function = true:suggestion 424 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent 425 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent 426 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent 427 | csharp_style_prefer_pattern_matching = true:silent 428 | csharp_style_prefer_switch_expression = true:suggestion 429 | csharp_style_prefer_not_pattern = true:suggestion 430 | csharp_style_prefer_extended_property_pattern = true:suggestion 431 | 432 | # Require file header 433 | dotnet_diagnostic.IDE0073.severity = warning 434 | 435 | # Uno platform exposes IDisposable on Storyboard publicly when it should be internal. Ignore this. 436 | dotnet_code_quality.CA1001.excluded_type_names_with_derived_types = T:Windows.UI.Xaml.Media.Animation.Storyboard --------------------------------------------------------------------------------