├── 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 | 
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
--------------------------------------------------------------------------------