├── .github └── workflows │ └── main.yml ├── .gitignore ├── .vscode └── settings.json ├── Clean-Old-DotNet6-Previews.ps1 ├── LICENSE ├── MauiCheck.sln ├── MauiCheck ├── CheckCommand.cs ├── CheckSettings.cs ├── Checkups │ ├── AndroidEmulatorCheckup.cs │ ├── AndroidSdkCheckup.cs │ ├── DotNetCheckup.cs │ ├── DotNetSdkCheckupContributor.cs │ ├── DotNetWorkloadsCheckup.cs │ ├── EdgeWebView2Checkup.cs │ ├── OpenJdkCheckup.cs │ ├── VisualStudioMacCheckup.cs │ ├── VisualStudioWindowsCheckup.cs │ └── XCodeCheckup.cs ├── ConfigCommand.cs ├── ConfigSettings.cs ├── DotNet │ ├── DotNetSdk.cs │ └── DotNetWorkloadManager.cs ├── Extensions.cs ├── IManifestChannelSettings.cs ├── Icon.cs ├── ListCheckupSettings.cs ├── ListCheckupsCommand.cs ├── Manifest │ ├── Android.cs │ ├── AndroidEmulator.cs │ ├── AndroidPackage.cs │ ├── Check.cs │ ├── DotNet.cs │ ├── DotNetSdk.cs │ ├── FilePermissions.cs │ ├── Manifest.cs │ ├── MinExactVersion.cs │ ├── NuGetFeedVariableMapper.cs │ ├── NuGetPackage.cs │ ├── OpenJdk.cs │ ├── Urls.cs │ ├── VariableMapper.cs │ ├── XPathVariableMapping.cs │ └── XmlVariableMapper.cs ├── MauiCheck.csproj ├── Models │ ├── Checkup.cs │ ├── CheckupContributor.cs │ ├── CheckupDependency.cs │ ├── CheckupManager.cs │ ├── CheckupStatusEventArgs.cs │ ├── DiagnosticResult.cs │ ├── DotNetGlobalJson.cs │ ├── DotNetGlobalJsonSdk.cs │ ├── RemedyStatusEventArgs.cs │ ├── SharedState.cs │ ├── Solution.cs │ ├── Status.cs │ └── Suggestion.cs ├── Process │ ├── ProcessArgumentBuilder.cs │ └── ShellProcessRunner.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Solutions │ ├── ActionSolution.cs │ ├── BootsSolution.cs │ ├── CreateFileSolution.cs │ ├── DotNetSdkScriptInstallSolution.cs │ └── MsInstallerSolution.cs ├── ToolInfo.cs ├── Util.cs └── app.manifest ├── README.md ├── libs ├── Boots.Core.dll ├── Microsoft.Deployment.DotNet.Releases.dll ├── Microsoft.DotNet.MSBuildSdkResolver.dll ├── Mono.AndroidTools.dll ├── Newtonsoft.Json.dll ├── Xamarin.Android.Tools.AndroidSdk.dll ├── Xamarin.AndroidTools.Ide.dll ├── Xamarin.AndroidTools.dll ├── Xamarin.Installer.AndroidSDK.Manager.dll ├── Xamarin.Installer.AndroidSDK.dll └── Xamarin.Installer.Common.dll └── manifests ├── README.md ├── maui-preview.manifest.json └── maui.manifest.json /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | release: 7 | types: [published] 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build: 14 | name: Build 15 | env: 16 | NUPKG_MAJOR: 0.999 17 | CODESIGN_PFX: ${{ secrets.CODESIGN_PFX }} 18 | APPLE_CERT_CN: ${{ secrets.APPLE_CERT_CN }} 19 | 20 | runs-on: windows-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v2 24 | 25 | - name: Setup .NET 8.0.x Preview 26 | uses: actions/setup-dotnet@v1 27 | with: 28 | dotnet-version: '8.0.x' 29 | include-prerelease: true 30 | 31 | - name: Build 32 | shell: pwsh 33 | run: | 34 | $VERSION="$env:NUPKG_MAJOR-ci$env:GITHUB_RUN_ID" 35 | if ($env:GITHUB_EVENT_NAME -eq "release") { 36 | $VERSION = $env:GITHUB_REF.Substring($env:GITHUB_REF.LastIndexOf('/') + 1) 37 | } 38 | echo "::set-output name=pkgverci::$VERSION" 39 | echo "PACKAGE VERSION: $VERSION" 40 | 41 | New-Item -ItemType Directory -Force -Path ./artifacts 42 | New-Item -ItemType Directory -Force -Path ./output 43 | 44 | $pfxPath = Join-Path -Path $pwd -ChildPath "codesigncert.pfx" 45 | [IO.File]::WriteAllBytes($pfxPath,[System.Convert]::FromBase64String($env:CODESIGN_PFX)) 46 | 47 | dotnet build --configuration Release ./MauiCheck.sln 48 | dotnet pack --output ./artifacts -p:PackageVersion=$VERSION -p:SigningCertificatePfxFile=$pfxPath --configuration Release ./MauiCheck/MauiCheck.csproj 49 | 50 | - name: Upload Artifacts 51 | uses: actions/upload-artifact@v1 52 | with: 53 | name: NuGet 54 | path: ./artifacts 55 | 56 | 57 | maccodesign: 58 | runs-on: macos-latest 59 | name: Mac Signing 60 | needs: 61 | - build 62 | steps: 63 | - name: Download Artifacts 64 | uses: actions/download-artifact@v1 65 | with: 66 | name: NuGet 67 | 68 | - name: Import Apple Cert 69 | uses: apple-actions/import-codesign-certs@v2 70 | with: 71 | p12-file-base64: ${{ secrets.APPLE_CERT }} 72 | p12-password: ${{ secrets.APPLE_CERT_PWD }} 73 | 74 | - name: Sign and Repackage 75 | shell: pwsh 76 | run: | 77 | dotnet tool install -g nupkgwrench 78 | nupkgwrench extract ./NuGet/*.nupkg --output ./tmp 79 | 80 | security find-identity -p codesigning ~/Library/Keychains/signing_temp.keychain-db 81 | 82 | codesign --sign "$env:APPLE_CERT_CN" "$pwd/tmp/**/maui-check" --keychain ~/Library/Keychains/signing_temp.keychain-db 83 | 84 | New-Item -ItemType Directory -Force -Path ./NuGetSigned 85 | nupkgwrench compress ./tmp --output ./NuGetSigned/ 86 | $pfxPath = Join-Path -Path $pwd -ChildPath "codesigncert.pfx" 87 | [IO.File]::WriteAllBytes($pfxPath,[System.Convert]::FromBase64String($env:CODESIGN_PFX)) 88 | dotnet nuget sign ./NuGetSigned/*.nupkg --certificate-path $pfxPath --timestamper http://timestamp.entrust.net/TSS/RFC3161sha2TS 89 | 90 | - name: Upload Artifacts 91 | uses: actions/upload-artifact@v1 92 | with: 93 | name: NuGetSigned 94 | path: ./NuGetSigned 95 | 96 | publish: 97 | name: Publish 98 | needs: 99 | - maccodesign 100 | runs-on: windows-latest 101 | steps: 102 | - name: Download Artifacts 103 | uses: actions/download-artifact@v1 104 | with: 105 | name: NuGetSigned 106 | 107 | - name: Push NuGet 108 | if: github.event_name == 'release' 109 | run: | 110 | dotnet nuget push NuGetSigned\*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_ORG_API_KEY }} --skip-duplicate 111 | -------------------------------------------------------------------------------- /.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 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | 352 | .DS_Store 353 | 354 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dotnet.defaultSolution": "MauiCheck.sln" 3 | } -------------------------------------------------------------------------------- /Clean-Old-DotNet6-Previews.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter(HelpMessage="DotNet SDK Root install directory")] 3 | [string]$DotnetRoot = $env:DOTNET_ROOT, 4 | [Parameter(HelpMessage="Deletes SDK/Runtime files as well - only use this if you know what you are doing!")] 5 | [switch]$FullDelete = $false 6 | ) 7 | 8 | $psMajorVersion = $host.Version.Major 9 | 10 | if ($psMajorVersion -lt 7) 11 | { 12 | throw "This script requires PowerShell v7 or newer." 13 | } 14 | 15 | $requireAdmin = $false 16 | 17 | # Otherwise look for the default paths 18 | if (-not ($DotnetRoot)) 19 | { 20 | # Admin / sudo required for global installs 21 | $requireAdmin = $true 22 | 23 | if ($IsMacOS) 24 | { 25 | $DotnetRoot = '/usr/local/share/dotnet/' 26 | } elseif ($IsWindows) 27 | { 28 | $DotnetRoot = Join-Path -Path $env:ProgramFiles -ChildPath 'dotnet' 29 | } 30 | } 31 | 32 | # Make sure the SDK path exists 33 | if (-not (Test-Path $DotnetRoot)) 34 | { 35 | throw "dotnet SDK root not found" 36 | } 37 | 38 | # If modifying a global install we need admin/sudo, so check the context if we are 39 | if ($requireAdmin) 40 | { 41 | if ($IsWindows) 42 | { 43 | if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) 44 | { 45 | throw "Adminstrator privilege required to modify global dotnet install. Re-run this script under an elevated terminal." 46 | } 47 | } 48 | else 49 | { 50 | if (-NOT ($(whoami) -eq 'root')) 51 | { 52 | throw "Superuser privilege required to modify global dotnet install. Re-run this script with sudo." 53 | } 54 | } 55 | } 56 | 57 | $rmPaths = @( 58 | 'packs/Microsoft.Android.*', 59 | 'packs/Microsoft.iOS.*', 60 | 'packs/Microsoft.MacCatalyst.*', 61 | 'packs/Microsoft.macOS.*', 62 | 'packs/Microsoft.Maui.*', 63 | 'packs/Microsoft.NET.Runtime.*', 64 | 'packs/Microsoft.NETCore.App.Runtime.AOT.*', 65 | 'packs/Microsoft.NETCore.App.Runtime.Mono.*', 66 | 'packs/Microsoft.tvOS.*', 67 | 'templates/6.0*', 68 | 'metadata/*' 69 | ) 70 | 71 | # Delete ALL dotnet6 preview files including host runtime and sdk 72 | if ($FullDelete) 73 | { 74 | # macOS stores some shared bits in a .app file and windows does not use this extension 75 | $osAppExt = '' 76 | if ($IsMacOS) 77 | { 78 | $osAppExt = '.app' 79 | } 80 | 81 | $rmPaths += @( 82 | 'sdk-manifests/6.0.*', 83 | 'host/fxr/6.0*', 84 | 'sdk/6.0*', 85 | "shared/Microsoft.AspNetCore.App$osAppExt/6.0*", 86 | "shared/Microsoft.NETCore.App$osAppExt/6.0*" 87 | ) 88 | } 89 | 90 | 91 | foreach ($rmPath in $rmPaths) 92 | { 93 | Remove-Item -Recurse -Force -Path (Join-Path -Path $DotnetRoot -ChildPath $rmPath) 94 | } 95 | 96 | if (Test-Path ~/.templateengine) 97 | { 98 | Remove-Item -Recurse -Force -Path ~/.templateengine 99 | } 100 | 101 | if (Test-Path ~/.dotnet/sdk-advertising) 102 | { 103 | Remove-Item -Recurse -Force -Path "~/.dotnet/sdk-advertising/6.0*" 104 | } 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jonathan Dick 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. 22 | -------------------------------------------------------------------------------- /MauiCheck.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.33711.374 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CF98FDE3-0227-48C4-876B-346013A7D70A}" 7 | ProjectSection(SolutionItems) = preProject 8 | manifests\maui-preview.manifest.json = manifests\maui-preview.manifest.json 9 | manifests\maui.manifest.json = manifests\maui.manifest.json 10 | manifests\README.md = manifests\README.md 11 | EndProjectSection 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiCheck", "MauiCheck\MauiCheck.csproj", "{AA2080B9-50D8-4FF8-A85D-DD952D955D22}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {AA2080B9-50D8-4FF8-A85D-DD952D955D22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {AA2080B9-50D8-4FF8-A85D-DD952D955D22}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {AA2080B9-50D8-4FF8-A85D-DD952D955D22}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {AA2080B9-50D8-4FF8-A85D-DD952D955D22}.Release|Any CPU.Build.0 = Release|Any CPU 25 | EndGlobalSection 26 | GlobalSection(SolutionProperties) = preSolution 27 | HideSolutionNode = FALSE 28 | EndGlobalSection 29 | GlobalSection(ExtensibilityGlobals) = postSolution 30 | SolutionGuid = {9BE3B771-1D2F-47B0-8A8E-66648E09DE00} 31 | EndGlobalSection 32 | EndGlobal 33 | -------------------------------------------------------------------------------- /MauiCheck/CheckCommand.cs: -------------------------------------------------------------------------------- 1 | using DotNetCheck.Checkups; 2 | using DotNetCheck.Models; 3 | using NuGet.Versioning; 4 | using Spectre.Console; 5 | using Spectre.Console.Cli; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics; 9 | using System.Diagnostics.CodeAnalysis; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace DotNetCheck.Cli 15 | { 16 | public class CheckCommand : AsyncCommand 17 | { 18 | public override async Task ExecuteAsync(CommandContext context, CheckSettings settings) 19 | { 20 | Util.Verbose = settings.Verbose; 21 | Util.LogFile = settings.LogFile; 22 | Util.CI = settings.CI; 23 | if (settings.CI) 24 | settings.NonInteractive = true; 25 | 26 | Console.Title = ToolInfo.ToolName; 27 | 28 | AnsiConsole.Write( 29 | new FigletText(".NET MAUI").LeftJustified().Color(Color.Green)); 30 | 31 | AnsiConsole.MarkupLine($"[underline bold green]{Icon.Ambulance} {ToolInfo.ToolName} v{ToolInfo.CurrentVersion} {Icon.Recommend}[/]"); 32 | AnsiConsole.Write(new Rule()); 33 | 34 | AnsiConsole.MarkupLine("This tool will attempt to evaluate your .NET MAUI development environment."); 35 | AnsiConsole.MarkupLine("If problems are detected, this tool may offer the option to try and fix them for you, or suggest a way to fix them yourself."); 36 | AnsiConsole.WriteLine(); 37 | AnsiConsole.MarkupLine("Thanks for choosing .NET MAUI!"); 38 | AnsiConsole.Write(new Rule()); 39 | 40 | if (!Util.IsAdmin() && Util.IsWindows) 41 | { 42 | var suTxt = Util.IsWindows ? "Administrator" : "Superuser (su)"; 43 | 44 | AnsiConsole.MarkupLine($"[bold red]{Icon.Bell} {suTxt} is required to fix most issues. Consider exiting and running the tool with {suTxt} permissions.[/]"); 45 | 46 | AnsiConsole.Write(new Rule()); 47 | 48 | if (!settings.NonInteractive) 49 | { 50 | if (!AnsiConsole.Confirm("Would you still like to continue?", false)) 51 | return 1; 52 | } 53 | } 54 | 55 | var cts = new System.Threading.CancellationTokenSource(); 56 | 57 | var checkupStatus = new Dictionary(); 58 | var sharedState = new SharedState(); 59 | 60 | sharedState.SetEnvironmentVariable("MAUI_CHECK_SETTINGS_FIX", settings.Fix.ToString()); 61 | sharedState.SetEnvironmentVariable("MAUI_CHECK_SETTINGS_CI", settings.CI.ToString()); 62 | sharedState.SetEnvironmentVariable("MAUI_CHECK_SETTINGS_NONINTERACTIVE", settings.NonInteractive.ToString()); 63 | 64 | var results = new Dictionary(); 65 | var consoleStatus = AnsiConsole.Status(); 66 | 67 | var skippedChecks = new List(); 68 | 69 | AnsiConsole.Markup($"[bold blue]{Icon.Thinking} Synchronizing configuration...[/]"); 70 | 71 | var channel = ManifestChannel.Default; 72 | if (settings.Preview) 73 | channel = ManifestChannel.Preview; 74 | if (settings.Main) 75 | { 76 | AnsiConsole.Markup($"[bold orange]{Icon.Warning} 'main' is deprecated, using 'preview' instead...[/]"); 77 | channel = ManifestChannel.Preview; 78 | } 79 | 80 | var manifest = await ToolInfo.LoadManifest(settings.Manifest, channel); 81 | 82 | if (!ToolInfo.Validate(manifest)) 83 | { 84 | ToolInfo.ExitPrompt(settings.NonInteractive); 85 | return -1; 86 | } 87 | 88 | AnsiConsole.MarkupLine(" ok"); 89 | AnsiConsole.Markup($"[bold blue]{Icon.Thinking} Scheduling appointments...[/]"); 90 | 91 | if (!string.IsNullOrEmpty(settings.DotNetSdkRoot)) 92 | { 93 | sharedState.SetEnvironmentVariable("DOTNET_ROOT", settings.DotNetSdkRoot); 94 | } 95 | 96 | if (settings.ForceDotNet) 97 | sharedState.SetEnvironmentVariable("DOTNET_FORCE", "true"); 98 | if (settings.CI) 99 | sharedState.SetEnvironmentVariable("CI", "true"); 100 | 101 | var checkups = CheckupManager.BuildCheckupGraph(manifest, sharedState); 102 | 103 | AnsiConsole.MarkupLine(" ok"); 104 | 105 | var checkupId = string.Empty; 106 | 107 | for (int i = 0; i < checkups.Count(); i++) 108 | { 109 | var checkup = checkups.ElementAt(i); 110 | 111 | // Set the manifest 112 | checkup.Manifest = manifest; 113 | 114 | // If the ID is the same, it's a retry 115 | var isRetry = checkupId == checkup.Id; 116 | 117 | // Track the last used id so we can detect retry 118 | checkupId = checkup.Id; 119 | 120 | if (!checkup.ShouldExamine(sharedState)) 121 | { 122 | checkupStatus[checkup.Id] = Models.Status.Ok; 123 | continue; 124 | } 125 | 126 | var skipCheckup = false; 127 | 128 | var dependencies = checkup.DeclareDependencies(checkups.Select(c => c.Id)); 129 | 130 | // Make sure our dependencies succeeded first 131 | if (dependencies?.Any() ?? false) 132 | { 133 | foreach (var dep in dependencies) 134 | { 135 | var depCheckup = checkups.FirstOrDefault(c => c.Id.StartsWith(dep.CheckupId, StringComparison.OrdinalIgnoreCase)); 136 | 137 | if (depCheckup != null && depCheckup.IsPlatformSupported(Util.Platform)) 138 | { 139 | if (!checkupStatus.TryGetValue(dep.CheckupId, out var depStatus) || depStatus == Models.Status.Error) 140 | { 141 | skipCheckup = dep.IsRequired; 142 | break; 143 | } 144 | } 145 | } 146 | } 147 | 148 | // See if --skip was specified 149 | if (settings.Skip?.Any(s => s.Equals(checkup.Id, StringComparison.OrdinalIgnoreCase) 150 | || s.Equals(checkup.GetType().Name, StringComparison.OrdinalIgnoreCase)) ?? false) 151 | skipCheckup = true; 152 | 153 | if (skipCheckup) 154 | { 155 | skippedChecks.Add(checkup.Id); 156 | checkupStatus[checkup.Id] = Models.Status.Error; 157 | AnsiConsole.WriteLine(); 158 | AnsiConsole.MarkupLine($"[bold red]{Icon.Error} Skipped: " + checkup.Title + "[/]"); 159 | continue; 160 | } 161 | 162 | checkup.OnStatusUpdated += checkupStatusUpdated; 163 | 164 | AnsiConsole.WriteLine(); 165 | AnsiConsole.MarkupLine($"[bold]{Icon.Checking} " + checkup.Title + " Checkup[/]..."); 166 | Console.Title = checkup.Title; 167 | 168 | DiagnosticResult diagnosis = null; 169 | 170 | try 171 | { 172 | diagnosis = await checkup.Examine(sharedState); 173 | } 174 | catch (Exception ex) 175 | { 176 | Util.Exception(ex); 177 | diagnosis = new DiagnosticResult(Models.Status.Error, checkup, ex.Message); 178 | } 179 | 180 | results[checkup.Id] = diagnosis; 181 | 182 | // Cache the status for dependencies 183 | checkupStatus[checkup.Id] = diagnosis.Status; 184 | 185 | if (diagnosis.Status == Models.Status.Ok) 186 | continue; 187 | 188 | var statusEmoji = diagnosis.Status == Models.Status.Error ? Icon.Error : Icon.Warning; 189 | var statusColor = diagnosis.Status == Models.Status.Error ? "red" : "darkorange3_1"; 190 | 191 | var msg = !string.IsNullOrEmpty(diagnosis.Message) ? " - " + diagnosis.Message : string.Empty; 192 | 193 | if (diagnosis.HasSuggestion) 194 | { 195 | Console.WriteLine(); 196 | AnsiConsole.Render(new Rule()); 197 | AnsiConsole.MarkupLine($"[bold blue]{Icon.Recommend} Recommendation:[/][blue] {diagnosis.Suggestion.Name}[/]"); 198 | 199 | if (!string.IsNullOrEmpty(diagnosis.Suggestion.Description)) 200 | AnsiConsole.MarkupLine("" + diagnosis.Suggestion.Description + ""); 201 | 202 | AnsiConsole.Render(new Rule()); 203 | Console.WriteLine(); 204 | 205 | // See if we should fix 206 | // needs to have a remedy available to even bother asking/trying 207 | var doFix = diagnosis.Suggestion.HasSolution 208 | && ( 209 | // --fix + --non-interactive == auto fix, no prompt 210 | (settings.NonInteractive && settings.Fix) 211 | // interactive (default) + prompt/confirm they want to fix 212 | || (!settings.NonInteractive && AnsiConsole.Confirm($"[bold]{Icon.Bell} Attempt to fix?[/]")) 213 | ); 214 | 215 | if (doFix && !isRetry) 216 | { 217 | var isAdmin = Util.IsAdmin(); 218 | 219 | var adminMsg = Util.IsWindows ? 220 | $"{Icon.Bell} [red]Administrator Permissions Required. Try opening a new console as Administrator and running this tool again.[/]" 221 | : $"{Icon.Bell} [red]Super User Permissions Required. Try running this tool again with 'sudo'.[/]"; 222 | 223 | var didFix = false; 224 | 225 | foreach (var remedy in diagnosis.Suggestion.Solutions) 226 | { 227 | try 228 | { 229 | remedy.OnStatusUpdated += remedyStatusUpdated; 230 | 231 | AnsiConsole.MarkupLine($"{Icon.Thinking} Attempting to fix: " + checkup.Title); 232 | 233 | await remedy.Implement(sharedState, cts.Token); 234 | 235 | didFix = true; 236 | AnsiConsole.MarkupLine($"[bold]Fix applied. Checking again...[/]"); 237 | } 238 | catch (Exception x) when (x is AccessViolationException || x is UnauthorizedAccessException) 239 | { 240 | Util.Exception(x); 241 | AnsiConsole.Markup(adminMsg); 242 | } 243 | catch (Exception ex) 244 | { 245 | Util.Exception(ex); 246 | AnsiConsole.MarkupLine("[bold red]Fix failed - " + ex.Message + "[/]"); 247 | } 248 | finally 249 | { 250 | remedy.OnStatusUpdated -= remedyStatusUpdated; 251 | } 252 | } 253 | 254 | // RETRY The check again 255 | if (didFix) 256 | i--; 257 | } 258 | } 259 | 260 | checkup.OnStatusUpdated -= checkupStatusUpdated; 261 | } 262 | 263 | AnsiConsole.Render(new Rule()); 264 | AnsiConsole.WriteLine(); 265 | 266 | var erroredChecks = results.Values.Where(d => d.Status == Models.Status.Error && !skippedChecks.Contains(d.Checkup.Id)); 267 | 268 | foreach (var ec in erroredChecks) 269 | Util.Log($"Checkup had Error status: {ec.Checkup.Id}"); 270 | 271 | var hasErrors = erroredChecks.Any(); 272 | 273 | var warningChecks = results.Values.Where(d => d.Status == Models.Status.Warning && !skippedChecks.Contains(d.Checkup.Id)); 274 | var hasWarnings = warningChecks.Any(); 275 | 276 | if (hasErrors) 277 | { 278 | AnsiConsole.Console.WriteLine(); 279 | 280 | foreach (var ec in erroredChecks) 281 | Util.Log($"{ec.Checkup.Id}: {ec.Message}"); 282 | 283 | AnsiConsole.MarkupLine($"[bold red]{Icon.Bell} There were one or more problems detected.[/]"); 284 | AnsiConsole.MarkupLine($"[bold red]Please review the errors and correct them and run {ToolInfo.ToolCommand} again.[/]"); 285 | } 286 | else if (hasWarnings) 287 | { 288 | AnsiConsole.Console.WriteLine(); 289 | 290 | foreach (var wc in warningChecks) 291 | Util.Log($"{wc.Checkup.Id}: {wc.Message}"); 292 | 293 | AnsiConsole.Console.WriteLine(); 294 | AnsiConsole.MarkupLine($"[bold darkorange3_1]{Icon.Warning} Things look almost great, except some pesky warning(s) which may or may not be a problem, but at least if they are, you'll know where to start searching![/]"); 295 | } 296 | else 297 | { 298 | AnsiConsole.MarkupLine($"[bold blue]{Icon.Success} Congratulations, everything looks great![/]"); 299 | } 300 | 301 | Console.Title = ToolInfo.ToolName; 302 | 303 | ToolInfo.ExitPrompt(settings.NonInteractive); 304 | 305 | Util.Log($"Has Errors? {hasErrors}"); 306 | var exitCode = hasErrors ? 1 : 0; 307 | Environment.ExitCode = exitCode; 308 | 309 | return exitCode; 310 | } 311 | 312 | void checkupStatusUpdated(object sender, CheckupStatusEventArgs e) 313 | { 314 | var msg = ""; 315 | if (e.Status == Models.Status.Error) 316 | msg = $"[red]{Icon.Error} {e.Message}[/]"; 317 | else if (e.Status == Models.Status.Warning) 318 | msg = $"[darkorange3_1]{Icon.Warning} {e.Message}[/]"; 319 | else if (e.Status == Models.Status.Ok) 320 | msg = $"[green]{Icon.Success} {e.Message}[/]"; 321 | else 322 | msg = $"{Icon.ListItem} {e.Message}"; 323 | 324 | AnsiConsole.MarkupLine(" " + msg); 325 | } 326 | 327 | void remedyStatusUpdated(object sender, RemedyStatusEventArgs e) 328 | { 329 | AnsiConsole.MarkupLine(" " + e.Message); 330 | } 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /MauiCheck/CheckSettings.cs: -------------------------------------------------------------------------------- 1 | using Spectre.Console.Cli; 2 | 3 | namespace DotNetCheck 4 | { 5 | public class CheckSettings : CommandSettings, IManifestChannelSettings 6 | { 7 | [CommandOption("-m|--manifest ")] 8 | public string Manifest { get; set; } 9 | 10 | [CommandOption("-f|--fix")] 11 | public bool Fix { get; set; } 12 | 13 | [CommandOption("-n|--non-interactive")] 14 | public bool NonInteractive { get; set; } 15 | 16 | [CommandOption("-s|--skip ")] 17 | public string[] Skip { get; set; } 18 | 19 | [CommandOption("--pre|--preview|-d|--dev")] 20 | public bool Preview { get; set; } 21 | 22 | [CommandOption("--main")] 23 | public bool Main { get; set; } 24 | 25 | [CommandOption("--dotnet ")] 26 | public string DotNetSdkRoot { get; set; } 27 | 28 | [CommandOption("--force-dotnet")] 29 | public bool ForceDotNet { get; set; } 30 | 31 | [CommandOption("-v|--verbose")] 32 | public bool Verbose { get; set; } 33 | 34 | [CommandOption("--ci")] 35 | public bool CI { get; set; } 36 | 37 | [CommandOption("-l|--logfile ")] 38 | public string LogFile { get; set; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MauiCheck/Checkups/AndroidEmulatorCheckup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using DotNetCheck.Models; 6 | using DotNetCheck.Manifest; 7 | using NuGet.Versioning; 8 | using DotNetCheck.Solutions; 9 | using Xamarin.Installer.AndroidSDK; 10 | using Xamarin.Installer.AndroidSDK.Manager; 11 | using System.IO; 12 | using AndroidSdk; 13 | 14 | namespace DotNetCheck.Checkups 15 | { 16 | public class AndroidEmulatorCheckup : Checkup 17 | { 18 | public override IEnumerable DeclareDependencies(IEnumerable checkupIds) 19 | => new [] { new CheckupDependency("androidsdk") }; 20 | 21 | public IEnumerable RequiredEmulators 22 | => Manifest?.Check?.Android?.Emulators; 23 | 24 | public override string Id => "androidemulator"; 25 | 26 | public override string Title => "Android Emulator"; 27 | 28 | public override bool ShouldExamine(SharedState history) 29 | => RequiredEmulators?.Any() ?? false; 30 | 31 | public override Task Examine(SharedState history) 32 | { 33 | AndroidSdk.AvdManager avdManager = null; 34 | 35 | var javaHome = history.GetEnvironmentVariable("JAVA_HOME"); 36 | string java = null; 37 | if (!string.IsNullOrEmpty(javaHome) && Directory.Exists(javaHome)) 38 | java = Path.Combine(javaHome, "bin", "java" + (Util.IsWindows ? ".exe" : "")); 39 | 40 | var avdNames = new List(); 41 | 42 | // Try invoking the java avdmanager library first 43 | if (File.Exists(java)) 44 | { 45 | avdManager = new AndroidSdk.AvdManager( 46 | history.GetEnvironmentVariable("ANDROID_SDK_ROOT") ?? history.GetEnvironmentVariable("ANDROID_HOME")); 47 | avdNames.AddRange(avdManager.ListAvds().Select(a => a.Name)); 48 | } 49 | 50 | // Fallback to manually reading the avd files 51 | if (!avdNames.Any()) 52 | avdNames.AddRange(new AvdLocator().ListAvds(history.GetEnvironmentVariable("ANDROID_SDK_ROOT") ?? history.GetEnvironmentVariable("ANDROID_HOME")) 53 | .Select(a => a.DisplayName ?? a.DeviceName ?? a.Target)); 54 | 55 | if (avdNames.Any()) 56 | { 57 | var emu = avdNames.FirstOrDefault(); 58 | 59 | ReportStatus($"Emulator: {emu} found.", Status.Ok); 60 | 61 | return Task.FromResult(DiagnosticResult.Ok(this)); 62 | } 63 | 64 | // If we got here, there are no emulators at all 65 | var missingEmulators = RequiredEmulators; 66 | 67 | if (!missingEmulators.Any()) 68 | return Task.FromResult(DiagnosticResult.Ok(this)); 69 | 70 | AndroidSdk.AvdManager.AvdDevice preferredDevice = null; 71 | 72 | try 73 | { 74 | if (avdManager != null) 75 | { 76 | var devices = avdManager.ListDevices(); 77 | 78 | preferredDevice = devices.FirstOrDefault(d => d.Name.Contains("pixel", StringComparison.OrdinalIgnoreCase)); 79 | } 80 | } 81 | catch (Exception ex) 82 | { 83 | var msg = "Unable to find any Android Emulators. You can use Visual Studio to create one if necessary: [underline]https://docs.microsoft.com/xamarin/android/get-started/installation/android-emulator/device-manager[/]"; 84 | 85 | ReportStatus(msg, Status.Warning); 86 | 87 | Util.Exception(ex); 88 | return Task.FromResult( 89 | new DiagnosticResult(Status.Warning, this, msg)); 90 | } 91 | 92 | return Task.FromResult(new DiagnosticResult( 93 | Status.Error, 94 | this, 95 | new Suggestion("Create an Android Emulator", 96 | missingEmulators.Select(me => 97 | new ActionSolution((sln, cancel) => 98 | { 99 | try 100 | { 101 | var installer = new AndroidSDKInstaller(new Helper(), AndroidManifestType.GoogleV2); 102 | installer.Discover(); 103 | 104 | var sdkInstance = installer.FindInstance(null); 105 | 106 | var installedPackages = sdkInstance.Components.AllInstalled(true); 107 | 108 | var sdkPackage = installedPackages.FirstOrDefault(p => p.Path.Equals(me.SdkId, StringComparison.OrdinalIgnoreCase)); 109 | 110 | if (sdkPackage == null && (me.AlternateSdkIds?.Any() ?? false)) 111 | sdkPackage = installedPackages.FirstOrDefault(p => me.AlternateSdkIds.Any(a => a.Equals(p.Path, StringComparison.OrdinalIgnoreCase))); 112 | 113 | var sdkId = sdkPackage?.Path ?? me.SdkId; 114 | 115 | avdManager.Create($"Android_Emulator_{me.ApiLevel}", sdkId, device: preferredDevice?.Id, force: true); 116 | return Task.CompletedTask; 117 | } 118 | catch (Exception ex) 119 | { 120 | ReportStatus("Unable to create Emulator. Use Visual Studio to create one instead: https://docs.microsoft.com/xamarin/android/get-started/installation/android-emulator/device-manager", Status.Warning); 121 | Util.Exception(ex); 122 | } 123 | 124 | return Task.CompletedTask; 125 | })).ToArray()))); 126 | } 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /MauiCheck/Checkups/AndroidSdkCheckup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | using DotNetCheck.Models; 8 | using Xamarin.Android.Tools; 9 | using Xamarin.Installer.AndroidSDK; 10 | using Xamarin.Installer.AndroidSDK.Common; 11 | using Xamarin.Installer.AndroidSDK.Manager; 12 | 13 | namespace DotNetCheck.Checkups 14 | { 15 | public class AndroidSdkPackagesCheckup : Models.Checkup 16 | { 17 | public override IEnumerable DeclareDependencies(IEnumerable checkupIds) 18 | => new [] { new CheckupDependency("openjdk") }; 19 | 20 | public IEnumerable RequiredPackages 21 | => Manifest?.Check?.Android?.Packages; 22 | 23 | public override string Id => "androidsdk"; 24 | 25 | public override string Title => "Android SDK"; 26 | 27 | List temporaryFiles = new List(); 28 | 29 | public override bool ShouldExamine(SharedState history) 30 | => RequiredPackages?.Any() ?? false; 31 | 32 | string[] macSdkLocations = new string[] 33 | { 34 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Android", "sdk"), 35 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Android", "android-sdk-macosx"), 36 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Developer", "Xamarin", "Android", "sdk"), 37 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Developer", "Xamarin", "android-sdk-macosx"), 38 | }; 39 | 40 | string[] unixSdkLocations = new string[] { }; 41 | 42 | string[] winSdkLocations = new string[] 43 | { 44 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Android", "android-sdk") 45 | }; 46 | 47 | public string FindBestSdkLocation() 48 | { 49 | var possibleLocations = Util.IsWindows ? winSdkLocations : (Util.IsMac ? macSdkLocations : unixSdkLocations); 50 | 51 | foreach (var p in possibleLocations) 52 | { 53 | if (Directory.Exists(p) && (Directory.GetFileSystemEntries(p)?.Any() ?? false)) 54 | return p; 55 | } 56 | 57 | return DefaultSdkLocation; 58 | } 59 | 60 | public string DefaultSdkLocation 61 | => Util.IsWindows 62 | ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Android", "android-sdk") 63 | : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Android", "sdk"); 64 | 65 | class AndroidComponentWrapper 66 | { 67 | public IAndroidComponent Component 68 | { 69 | get;set; 70 | } 71 | 72 | public override string ToString() 73 | { 74 | return Component.Path + " - " + Component.FileSystemPath; 75 | } 76 | } 77 | 78 | public override Task Examine(SharedState history) 79 | { 80 | var jdkPath = history.GetEnvironmentVariable("JAVA_HOME") ?? Environment.GetEnvironmentVariable("JAVA_HOME"); 81 | 82 | string androidSdkPath = null; 83 | 84 | try 85 | { 86 | // Set the logger to override the default one that is set in this library 87 | // So we can catch output from failed path lookups that are otherwise swallowed 88 | var _ = new AndroidSdkInfo((traceLevel, msg) => 89 | { 90 | 91 | if (Util.Verbose || traceLevel == System.Diagnostics.TraceLevel.Error) 92 | Util.LogAlways(msg); 93 | 94 | }, androidSdkPath, null, jdkPath); 95 | } 96 | catch (Exception ex) 97 | { 98 | Util.Exception(ex); 99 | } 100 | 101 | if (string.IsNullOrEmpty(androidSdkPath)) 102 | androidSdkPath = FindBestSdkLocation(); 103 | 104 | var missingPackages = new List(); 105 | 106 | var installer = new AndroidSDKInstaller(new Helper(), AndroidManifestType.GoogleV2); 107 | 108 | installer.Discover(new List { androidSdkPath }); 109 | 110 | var sdkInstance = installer.FindInstance(androidSdkPath); 111 | 112 | if (string.IsNullOrEmpty(sdkInstance?.Path)) 113 | { 114 | return Task.FromResult( 115 | new DiagnosticResult( 116 | Status.Error, 117 | this, 118 | "Failed to find Android SDK.", 119 | new Suggestion("Install the Android SDK", 120 | "For more information see: [underline]https://aka.ms/dotnet-androidsdk-help[/]"))); 121 | } 122 | 123 | history.SetEnvironmentVariable("ANDROID_SDK_ROOT", sdkInstance.Path); 124 | history.SetEnvironmentVariable("ANDROID_HOME", sdkInstance.Path); 125 | 126 | var installed = sdkInstance?.Components?.AllInstalled(true); 127 | 128 | foreach (var package in RequiredPackages) 129 | { 130 | var v = !string.IsNullOrWhiteSpace(package.Version) ? new AndroidRevision(package.Version) : null; 131 | 132 | var installedPkg = FindInstalledPackage(installed, package) 133 | ?? FindInstalledPackage(installed, package.Alternatives?.ToArray()); 134 | 135 | if (installedPkg == null) 136 | { 137 | var pkgToInstall = sdkInstance?.Components?.AllNotInstalled()? 138 | .FirstOrDefault(p => p.Path.Equals(package.Path.Trim(), StringComparison.OrdinalIgnoreCase) 139 | && p.Revision >= (v ?? p.Revision)); 140 | 141 | ReportStatus($"{package.Path} ({package.Version}) missing.", Status.Error); 142 | 143 | if (pkgToInstall != null) 144 | missingPackages.Add(pkgToInstall); 145 | } 146 | else 147 | { 148 | if (!package.Path.Equals(installedPkg.Path) || v != (installedPkg.Revision ?? installedPkg.InstalledRevision)) 149 | ReportStatus($"{installedPkg.Path} ({installedPkg.InstalledRevision ?? installedPkg.Revision})", Status.Ok); 150 | else 151 | ReportStatus($"{package.Path} ({package.Version})", Status.Ok); 152 | } 153 | } 154 | 155 | if (!missingPackages.Any()) 156 | return Task.FromResult(DiagnosticResult.Ok(this)); 157 | 158 | 159 | var installationSet = installer.GetInstallationSet(sdkInstance, missingPackages); 160 | 161 | var desc = 162 | @$"Your Android SDK has missing or outdated packages. 163 | You can use the Android SDK Manager to install / update them. 164 | For more information see: [underline]https://aka.ms/dotnet-androidsdk-help[/]"; 165 | 166 | return Task.FromResult(new DiagnosticResult( 167 | Status.Error, 168 | this, 169 | new Suggestion("Install or Update Android SDK packages", 170 | desc, 171 | new Solutions.ActionSolution(async (sln, cancelToken) => 172 | { 173 | try 174 | { 175 | var downloads = installer.GetDownloadItems(installationSet); 176 | using (var httpClient = new HttpClient()) 177 | { 178 | // Provide a default timeout value of 7 minutes if a value is not provided. 179 | httpClient.Timeout = TimeSpan.FromMinutes(120); 180 | await Task.WhenAll(downloads.Select(d => Download(httpClient, d))); 181 | } 182 | 183 | installer.Install(sdkInstance, installationSet); 184 | } 185 | catch (Exception ex) 186 | { 187 | Util.Exception(ex); 188 | } 189 | finally 190 | { 191 | foreach (var temp in temporaryFiles) 192 | { 193 | if (File.Exists(temp)) 194 | { 195 | try 196 | { 197 | File.Delete(temp); 198 | } 199 | catch { } 200 | } 201 | } 202 | } 203 | })))); 204 | } 205 | 206 | IAndroidComponent FindInstalledPackage(IEnumerable installed, params Manifest.AndroidPackage[] acceptablePackages) 207 | { 208 | if (acceptablePackages?.Any() ?? false) 209 | { 210 | foreach (var p in acceptablePackages) 211 | { 212 | var v = !string.IsNullOrWhiteSpace(p.Version) ? new AndroidRevision(p.Version) : null; 213 | 214 | var installedPkg = installed.FirstOrDefault( 215 | i => i.Path.Equals(p.Path.Trim(), StringComparison.OrdinalIgnoreCase) 216 | && (i.Revision >= (v ?? i.Revision) || i.InstalledRevision >= (v ?? i.Revision))); 217 | 218 | if (installedPkg != null) 219 | return installedPkg; 220 | } 221 | } 222 | 223 | return default; 224 | } 225 | 226 | async Task Download(HttpClient httpClient, Archive archive) 227 | { 228 | ReportStatus($"Downloading {archive.Url} ...", null); 229 | 230 | using (var response = await httpClient.GetAsync(archive.Url, HttpCompletionOption.ResponseHeadersRead)) 231 | { 232 | response.EnsureSuccessStatusCode(); 233 | var fileLength = response.Content.Headers.ContentLength.Value; 234 | var path = Path.GetTempFileName(); 235 | temporaryFiles.Add(path); 236 | using (var fileStream = File.OpenWrite(path)) 237 | { 238 | using (var httpStream = await response.Content.ReadAsStreamAsync()) 239 | { 240 | var buffer = new byte[16 * 1024]; 241 | int bytesRead; 242 | double bytesWritten = 0; 243 | double previousProgress = 0; 244 | while ((bytesRead = httpStream.Read(buffer, 0, buffer.Length)) > 0) 245 | { 246 | fileStream.Write(buffer, 0, bytesRead); 247 | bytesWritten += bytesRead; 248 | // Log download progress roughly every 10%. 249 | var progress = bytesWritten / fileLength; 250 | if (progress - previousProgress > .10) 251 | { 252 | ReportStatus($"Downloaded {progress:P0} of {Path.GetFileName(archive.Url.AbsolutePath)} ...", null); 253 | previousProgress = progress; 254 | } 255 | } 256 | fileStream.Flush(); 257 | } 258 | } 259 | ReportStatus($"Wrote '{archive.Url}' to '{path}'.", null); 260 | archive.DownloadedFilePath = path; 261 | } 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /MauiCheck/Checkups/DotNetCheckup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using DotNetCheck.DotNet; 7 | using DotNetCheck.Models; 8 | using DotNetCheck.Solutions; 9 | using Microsoft.NET.Sdk.WorkloadManifestReader; 10 | using Newtonsoft.Json.Linq; 11 | using NuGet.Versioning; 12 | 13 | namespace DotNetCheck.Checkups 14 | { 15 | public class DotNetCheckup : Checkup 16 | { 17 | public IEnumerable RequiredSdks 18 | => Manifest?.Check?.DotNet?.Sdks; 19 | 20 | public override string Id => "dotnet"; 21 | 22 | public override string Title => $".NET SDK"; 23 | 24 | string SdkListToString() 25 | => (RequiredSdks?.Any() ?? false) ? "(" + string.Join(", ", RequiredSdks.Select(s => s.Version)) + ")" : string.Empty; 26 | 27 | public override bool ShouldExamine(SharedState history) 28 | => RequiredSdks?.Any() ?? false; 29 | 30 | public override async Task Examine(SharedState history) 31 | { 32 | var dn = new DotNetSdk(history); 33 | 34 | var missingDiagnosis = new DiagnosticResult(Status.Error, this, new Suggestion(".NET SDK not installed")); 35 | 36 | if (!dn.Exists) 37 | return missingDiagnosis; 38 | 39 | var sdks = await dn.GetSdks(); 40 | 41 | var missingSdks = new List(); 42 | var sentinelFiles = new List(); 43 | 44 | if (RequiredSdks?.Any() ?? false) 45 | { 46 | foreach (var rs in RequiredSdks) 47 | { 48 | var rsVersion = NuGetVersion.Parse(rs.Version); 49 | 50 | if (!sdks.Any(s => (rs.RequireExact && s.Version == rsVersion) || (!rs.RequireExact && s.Version >= rsVersion))) 51 | missingSdks.Add(rs); 52 | } 53 | } 54 | 55 | DotNetSdkInfo bestSdk = null; 56 | 57 | foreach (var sdk in sdks) 58 | { 59 | // See if the sdk is one of the required sdk's 60 | var requiredSdk = RequiredSdks.FirstOrDefault(rs => sdk.Version == NuGetVersion.Parse(rs.Version)); 61 | 62 | if (requiredSdk != null) 63 | { 64 | if (bestSdk == null || sdk.Version > bestSdk.Version) 65 | bestSdk = sdk; 66 | 67 | ReportStatus($"{sdk.Version} - {sdk.Directory}", Status.Ok); 68 | } 69 | else 70 | ReportStatus($"{sdk.Version} - {sdk.Directory}", null); 71 | } 72 | 73 | // If we didn't get the exact one before, let's find a new enough one 74 | if (bestSdk == null) 75 | bestSdk = sdks.OrderByDescending(s => s.Version)?.FirstOrDefault(); 76 | 77 | // Find newest compatible sdk 78 | if (bestSdk != null) 79 | { 80 | history.SetEnvironmentVariable("DOTNET_SDK", bestSdk.Directory.FullName); 81 | history.SetEnvironmentVariable("DOTNET_SDK_VERSION", bestSdk.Version.ToString()); 82 | } 83 | 84 | if (missingSdks.Any()) 85 | { 86 | var str = SdkListToString(); 87 | 88 | var remedies = new List(); 89 | 90 | if (Util.CI) 91 | { 92 | remedies.AddRange(missingSdks 93 | .Select(ms => new DotNetSdkScriptInstallSolution(ms.Version))); 94 | } 95 | else 96 | { 97 | remedies.AddRange(missingSdks 98 | .Where(ms => !ms.Url.AbsolutePath.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) 99 | .Select(ms => new BootsSolution(ms.Url, ".NET SDK " + ms.Version) as Solution)); 100 | 101 | remedies.AddRange(missingSdks 102 | .Where(ms => ms.Url.AbsolutePath.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) 103 | .Select(ms => new MsInstallerSolution(ms.Url, ".NET SDK " + ms.Version))); 104 | } 105 | 106 | return new DiagnosticResult(Status.Error, this, $".NET SDK {str} not installed.", 107 | new Suggestion($"Download .NET SDK {str}", 108 | remedies.ToArray())); 109 | } 110 | 111 | return new DiagnosticResult(Status.Ok, this); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /MauiCheck/Checkups/DotNetSdkCheckupContributor.cs: -------------------------------------------------------------------------------- 1 | using DotNetCheck.Manifest; 2 | using DotNetCheck.Models; 3 | using NuGet.Versioning; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace DotNetCheck.Checkups 10 | { 11 | public class DotNetSdkCheckupContributor : CheckupContributor 12 | { 13 | public override IEnumerable Contribute(Manifest.Manifest manifest, SharedState sharedState) 14 | { 15 | var sdks = manifest?.Check?.DotNet?.Sdks; 16 | 17 | if (sdks?.Any() ?? false) 18 | { 19 | foreach (var sdk in sdks) 20 | { 21 | var pkgSrcs = sdk?.PackageSources?.ToArray() ?? Array.Empty(); 22 | 23 | string sdkVersion; 24 | if (!sharedState.TryGetEnvironmentVariable("DOTNET_SDK_VERSION", out sdkVersion)) 25 | sdkVersion = sdk.Version; 26 | 27 | if (sdk.WorkloadRollback is not null) 28 | { 29 | var workloadIds = sdk.WorkloadIds ?? new List { "maui" }; 30 | 31 | yield return new DotNetWorkloadsCheckup(sharedState, sdkVersion, sdk.WorkloadRollback, workloadIds.ToArray(), pkgSrcs); 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /MauiCheck/Checkups/DotNetWorkloadsCheckup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | using DotNetCheck.DotNet; 8 | using DotNetCheck.Models; 9 | using DotNetCheck.Solutions; 10 | using Microsoft.NET.Sdk.WorkloadManifestReader; 11 | using NuGet.Versioning; 12 | 13 | namespace DotNetCheck.Checkups 14 | { 15 | public class DotNetWorkloadsCheckup 16 | : Checkup 17 | { 18 | public DotNetWorkloadsCheckup() : base() 19 | { 20 | throw new Exception("Do not IOC this type directly"); 21 | } 22 | 23 | public DotNetWorkloadsCheckup(SharedState sharedState, string sdkVersion, Uri workloadRollback, string[] workloadIds, params string[] nugetPackageSources) : base() 24 | { 25 | var dotnet = new DotNetSdk(sharedState); 26 | 27 | SdkRoot = dotnet.DotNetSdkLocation.FullName; 28 | SdkVersion = sdkVersion; 29 | WorkloadRollback = workloadRollback; 30 | WorkloadIds = workloadIds; 31 | NuGetPackageSources = nugetPackageSources; 32 | } 33 | 34 | public readonly string SdkRoot; 35 | public readonly string SdkVersion; 36 | public readonly string[] NuGetPackageSources; 37 | public readonly Uri WorkloadRollback; 38 | 39 | public readonly string[] WorkloadIds; 40 | 41 | 42 | string rollbackJson = string.Empty; 43 | 44 | public async Task GetRollbackJson() 45 | { 46 | if (string.IsNullOrEmpty(rollbackJson)) 47 | { 48 | var http = new HttpClient(); 49 | rollbackJson = await http.GetStringAsync(WorkloadRollback).ConfigureAwait(false); 50 | } 51 | 52 | return rollbackJson; 53 | } 54 | 55 | public override IEnumerable DeclareDependencies(IEnumerable checkupIds) 56 | => new[] { new CheckupDependency("dotnet") }; 57 | 58 | public override string Id => "dotnetworkloads-" + SdkVersion; 59 | 60 | public override string Title => $".NET SDK - Workloads ({SdkVersion})"; 61 | 62 | static bool wasForceRunAlready = false; 63 | 64 | public override async Task Examine(SharedState history) 65 | { 66 | if (!history.TryGetEnvironmentVariable("DOTNET_SDK_VERSION", out var sdkVersion)) 67 | sdkVersion = SdkVersion; 68 | 69 | var force = history.TryGetEnvironmentVariable("DOTNET_FORCE", out var forceDotNet) 70 | && (forceDotNet?.Equals("true", StringComparison.OrdinalIgnoreCase) ?? false) 71 | && !wasForceRunAlready; 72 | 73 | // Don't allow multiple force runs, just the first 74 | if (force) 75 | wasForceRunAlready = true; 76 | 77 | var workloadManager = new DotNetWorkloadManager(SdkRoot, sdkVersion, NuGetPackageSources); 78 | 79 | //var installedPackageWorkloads = workloadManager.GetInstalledWorkloads(); 80 | 81 | var installedWorkloads = workloadManager.GetInstalledWorkloadManifestIdsAndVersions(); 82 | 83 | var requiredWorkloads = workloadManager.ParseRollback(await GetRollbackJson()); 84 | 85 | // Get rollback versions 86 | // check for matches 87 | var missingWorkloads = false; 88 | 89 | foreach (var rp in requiredWorkloads) 90 | { 91 | var workloadVersion = rp.Value.Version; 92 | var sdkBand = rp.Value.SdkBand; 93 | 94 | var installedVersions = installedWorkloads 95 | .Where(ip => ip.Key.Equals(rp.Key, StringComparison.OrdinalIgnoreCase)) 96 | .Select(s => (s.Key, s.Value)); 97 | 98 | // By default allow >= version, but --force will cause exact version matching 99 | if (installedVersions.Any(iv => iv.Item2 == workloadVersion || (!force && iv.Item2 >= workloadVersion))) 100 | { 101 | ReportStatus($"{rp.Key} ({workloadVersion}) installed.", Status.Ok); 102 | } 103 | else 104 | { 105 | ReportStatus($"{rp.Key} ({workloadVersion}) not installed.", Status.Error); 106 | missingWorkloads = true; 107 | } 108 | } 109 | 110 | if (!missingWorkloads && !force) 111 | return DiagnosticResult.Ok(this); 112 | 113 | var canPerform = true; 114 | 115 | if (Util.IsWindows) 116 | { 117 | var interactive = !history.GetEnvironmentVariableFlagSet("MAUI_CHECK_SETTINGS_NONINTERACTIVE"); 118 | 119 | Spectre.Console.AnsiConsole.MarkupLine($"[bold red]{Icon.Bell} Managing Workload installation from the CLI is [underline]NOT recommended[/]. Instead you should install the latest Visual Studio preview to automatically get the newest release of .NET MAUI workloads installed.[/]"); 120 | 121 | // If this is not a CI / Fix flag install, ask if we want to confirm to continue the CLI install after seeing the warning 122 | if (interactive) 123 | canPerform = Spectre.Console.AnsiConsole.Confirm("Are you sure you would like to continue the CLI workload installation?", false); 124 | } 125 | 126 | return new DiagnosticResult( 127 | Status.Error, 128 | this, 129 | canPerform ? 130 | new Suggestion("Install or Update SDK Workloads", 131 | new ActionSolution(async (sln, cancel) => 132 | { 133 | if (history.GetEnvironmentVariableFlagSet("DOTNET_FORCE")) 134 | { 135 | try 136 | { 137 | await workloadManager.Repair(); 138 | } 139 | catch (Exception ex) 140 | { 141 | ReportStatus("Warning: Workload repair failed", Status.Warning); 142 | } 143 | } 144 | 145 | await workloadManager.Install(await GetRollbackJson(), WorkloadIds); 146 | })) 147 | : new Suggestion("Install the latest Visual Studio Preview", "To install or update to the latest workloads for .NET MAUI, install the latest Visual Studio Preview and choose .NET MAUI in the list of features to install under the .NET Mobile Workload: [underline]https://visualstudio.microsoft.com/vs/preview/[/]")); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /MauiCheck/Checkups/EdgeWebView2Checkup.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using DotNetCheck.Models; 3 | 4 | namespace DotNetCheck.Checkups 5 | { 6 | public class EdgeWebView2Checkup : Checkup 7 | { 8 | public override bool IsPlatformSupported(Platform platform) 9 | => platform == Platform.Windows; 10 | 11 | public override string Id => "edgewebview2"; 12 | 13 | public override string Title => $"Edge WebView2"; 14 | 15 | public override bool ShouldExamine(SharedState history) 16 | => Manifest?.Check?.VSWin != null 17 | && !(history.GetEnvironmentVariable("CI") ?? "false").Equals("true", System.StringComparison.OrdinalIgnoreCase); 18 | 19 | public override Task Examine(SharedState history) 20 | { 21 | // Info here: https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment 22 | string WebView2RegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"; 23 | 24 | var webView2VersionObject = Microsoft.Win32.Registry.GetValue( 25 | keyName: WebView2RegistryKey, 26 | valueName: "pv", 27 | defaultValue: null); 28 | 29 | var webView2Version = webView2VersionObject as string; 30 | 31 | var isWebView2Installed = !string.IsNullOrEmpty(webView2Version); 32 | if (isWebView2Installed) 33 | { 34 | ReportStatus($"Found Edge WebView2 version {webView2Version}", Status.Ok); 35 | return Task.FromResult(DiagnosticResult.Ok(this)); 36 | } 37 | 38 | return Task.FromResult(new DiagnosticResult( 39 | Status.Error, 40 | this, 41 | new Suggestion($"Download Edge WebView2 from https://developer.microsoft.com/microsoft-edge/webview2/"))); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /MauiCheck/Checkups/OpenJdkCheckup.cs: -------------------------------------------------------------------------------- 1 | using DotNetCheck.Models; 2 | using DotNetCheck.Solutions; 3 | using NuGet.Versioning; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Text.RegularExpressions; 10 | using System.Threading.Tasks; 11 | using Xamarin.Android.Tools; 12 | 13 | namespace DotNetCheck.Checkups 14 | { 15 | public class OpenJdkInfo 16 | { 17 | public OpenJdkInfo(string javaCFile, NuGetVersion version) 18 | { 19 | JavaC = new FileInfo(javaCFile); 20 | Version = version; 21 | } 22 | 23 | public FileInfo JavaC { get; set; } 24 | 25 | public DirectoryInfo Directory 26 | => new DirectoryInfo(Path.Combine(JavaC.Directory.FullName, "..")); 27 | 28 | public NuGetVersion Version { get; set; } 29 | } 30 | 31 | public class OpenJdkCheckup : Models.Checkup 32 | { 33 | public NuGetVersion Version 34 | => Extensions.ParseVersion(Manifest?.Check?.OpenJdk?.CompatVersion, new NuGetVersion("1.8.0-25")); 35 | 36 | public bool RequireExact 37 | => Manifest?.Check?.OpenJdk?.RequireExact ?? false; 38 | 39 | public override string Id => "openjdk"; 40 | 41 | public override string Title => $"OpenJDK {Version}"; 42 | 43 | static string PlatformJavaCExtension => Util.IsWindows ? ".exe" : string.Empty; 44 | 45 | public override bool ShouldExamine(SharedState history) 46 | => Manifest?.Check?.OpenJdk != null; 47 | 48 | public override Task Examine(SharedState history) 49 | { 50 | var xamJdks = new List(); 51 | try 52 | { 53 | var xamSdkInfo = new AndroidSdkInfo((traceLevel, msg) => Util.Log(msg), null, null, null); 54 | 55 | if (!string.IsNullOrEmpty(xamSdkInfo.JavaSdkPath)) 56 | SearchDirectoryForJdks(xamJdks, xamSdkInfo.JavaSdkPath); 57 | } 58 | catch (Exception ex) 59 | { 60 | Util.Exception(ex); 61 | } 62 | 63 | var jdks = xamJdks.Concat(FindJdks()) 64 | .GroupBy(j => j.Directory.FullName) 65 | .Select(g => g.First()); 66 | 67 | var ok = false; 68 | 69 | foreach (var jdk in jdks) 70 | { 71 | if ((jdk.JavaC.FullName.Contains("microsoft", StringComparison.OrdinalIgnoreCase) || jdk.JavaC.FullName.Contains("openjdk", StringComparison.OrdinalIgnoreCase)) 72 | && jdk.Version.IsCompatible(Version, RequireExact ? Version : null)) 73 | { 74 | ok = true; 75 | ReportStatus($"{jdk.Version} ({jdk.Directory})", Status.Ok); 76 | history.SetEnvironmentVariable("JAVA_HOME", jdk.Directory.FullName); 77 | 78 | // Try and set the global env var on windows if it's not set 79 | if (Util.IsWindows && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("JAVA_HOME"))) 80 | { 81 | try 82 | { 83 | Environment.SetEnvironmentVariable("JAVA_HOME", jdk.Directory.FullName, EnvironmentVariableTarget.Machine); 84 | ReportStatus($"Set Environment Variable: JAVA_HOME={jdk.Directory.FullName}", Status.Ok); 85 | } catch { } 86 | } 87 | } 88 | else 89 | ReportStatus($"{jdk.Version} ({jdk.Directory.FullName})", null); 90 | } 91 | 92 | if (ok) 93 | return Task.FromResult(DiagnosticResult.Ok(this)); 94 | 95 | return Task.FromResult(new DiagnosticResult(Status.Error, this, 96 | new Suggestion("Install OpenJDK11", 97 | new BootsSolution(Manifest?.Check?.OpenJdk?.Url, "Download and Install Microsoft OpenJDK 11")))); 98 | } 99 | 100 | IEnumerable FindJdks() 101 | { 102 | var paths = new List(); 103 | 104 | if (Util.IsWindows) 105 | { 106 | SearchDirectoryForJdks(paths, 107 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Android", "Jdk"), true); 108 | 109 | var pfmsDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Microsoft"); 110 | 111 | try 112 | { 113 | if (Directory.Exists(pfmsDir)) 114 | { 115 | var msJdkDirs = Directory.EnumerateDirectories(pfmsDir, "jdk-*", SearchOption.TopDirectoryOnly); 116 | foreach (var msJdkDir in msJdkDirs) 117 | SearchDirectoryForJdks(paths, msJdkDir, false); 118 | } 119 | } 120 | catch (Exception ex) 121 | { 122 | Util.Exception(ex); 123 | } 124 | 125 | SearchDirectoryForJdks(paths, 126 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Microsoft", "Jdk"), true); 127 | } else if (Util.IsMac) 128 | { 129 | var ms11Dir = Path.Combine("/Library", "Java", "JavaVirtualMachines", "microsoft-11.jdk", "Contents", "Home"); 130 | SearchDirectoryForJdks(paths, ms11Dir, true); 131 | 132 | var msDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Developer", "Xamarin", "jdk"); 133 | SearchDirectoryForJdks(paths, msDir, true); 134 | 135 | // /Library/Java/JavaVirtualMachines/ 136 | try 137 | { 138 | var javaVmDir = Path.Combine("Library", "Java", "JavaVirtualMachines"); 139 | 140 | if (Directory.Exists(javaVmDir)) 141 | { 142 | var javaVmJdkDirs = Directory.EnumerateDirectories(javaVmDir, "*.jdk", SearchOption.TopDirectoryOnly); 143 | foreach (var javaVmJdkDir in javaVmJdkDirs) 144 | SearchDirectoryForJdks(paths, javaVmDir, true); 145 | 146 | javaVmJdkDirs = Directory.EnumerateDirectories(javaVmDir, "jdk-*", SearchOption.TopDirectoryOnly); 147 | foreach (var javaVmJdkDir in javaVmJdkDirs) 148 | SearchDirectoryForJdks(paths, javaVmDir, true); 149 | } 150 | } 151 | catch (Exception ex) 152 | { 153 | Util.Exception(ex); 154 | } 155 | } 156 | 157 | SearchDirectoryForJdks(paths, Environment.GetEnvironmentVariable("JAVA_HOME") ?? string.Empty, true); 158 | SearchDirectoryForJdks(paths, Environment.GetEnvironmentVariable("JDK_HOME") ?? string.Empty, true); 159 | 160 | var environmentPaths = Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? Array.Empty(); 161 | 162 | foreach (var envPath in environmentPaths) 163 | { 164 | if (envPath.Contains("java", StringComparison.OrdinalIgnoreCase) || envPath.Contains("jdk", StringComparison.OrdinalIgnoreCase)) 165 | SearchDirectoryForJdks(paths, envPath, true); 166 | } 167 | 168 | return paths 169 | .GroupBy(i => i.JavaC.FullName) 170 | .Select(g => g.First()); 171 | } 172 | 173 | void SearchDirectoryForJdks(IList found, string directory, bool recursive = true) 174 | { 175 | if (string.IsNullOrEmpty(directory)) 176 | return; 177 | 178 | var dir = new DirectoryInfo(directory); 179 | 180 | if (dir.Exists) 181 | { 182 | var files = dir.EnumerateFileSystemInfos($"javac{PlatformJavaCExtension}", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); 183 | 184 | foreach (var file in files) 185 | { 186 | if (!found.Any(f => f.JavaC.FullName.Equals(file.FullName)) && TryGetJavaJdkInfo(file.FullName, out var jdkInfo)) 187 | found.Add(jdkInfo); 188 | } 189 | } 190 | } 191 | 192 | static readonly Regex rxJavaCVersion = new Regex("[0-9\\.\\-_]+", RegexOptions.Singleline); 193 | 194 | bool TryGetJavaJdkInfo(string javacFilename, out OpenJdkInfo javaJdkInfo) 195 | { 196 | var r = ShellProcessRunner.Run(javacFilename, "-version"); 197 | var m = rxJavaCVersion.Match(r.GetOutput() ?? string.Empty); 198 | 199 | var v = m?.Value; 200 | 201 | if (!string.IsNullOrEmpty(v) && NuGetVersion.TryParse(v, out var version)) 202 | { 203 | javaJdkInfo = new OpenJdkInfo(javacFilename, version); 204 | return true; 205 | } 206 | 207 | javaJdkInfo = default; 208 | return false; 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /MauiCheck/Checkups/VisualStudioMacCheckup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Claunia.PropertyList; 7 | using DotNetCheck.Models; 8 | using NuGet.Versioning; 9 | 10 | namespace DotNetCheck.Checkups 11 | { 12 | public class VisualStudioMacCheckup : Checkup 13 | { 14 | public override bool IsPlatformSupported(Platform platform) 15 | => platform == Platform.OSX; 16 | 17 | public NuGetVersion MinimumVersion 18 | => Extensions.ParseVersion("17.5.0", new NuGetVersion("17.5.0")); 19 | 20 | public NuGetVersion ExactVersion 21 | => Extensions.ParseVersion(null); 22 | 23 | public bool Optional 24 | => true; 25 | 26 | public override string Id => "vsmac"; 27 | 28 | public override string Title => $"Visual Studio {MinimumVersion.ThisOrExact(ExactVersion)}"; 29 | 30 | public override bool ShouldExamine(SharedState history) 31 | => false; 32 | 33 | public override async Task Examine(SharedState history) 34 | { 35 | var vsinfo = await GetMacInfo(); 36 | 37 | var ok = false; 38 | 39 | foreach (var vs in vsinfo) 40 | { 41 | if (vs.Version.IsCompatible(MinimumVersion, ExactVersion)) 42 | { 43 | ok = true; 44 | ReportStatus($"Visual Studio for Mac ({vs.Version})", Status.Ok); 45 | } 46 | else 47 | { 48 | ReportStatus($"Visual Studio for Mac ({vs.Version})", null); 49 | } 50 | } 51 | 52 | 53 | // Check VSCode sentinel files, ie: 54 | // ~/.vscode/extensions/ms-dotnettools.csharp-1.23.9/.omnisharp/1.37.8-beta.15/omnisharp/.msbuild/Current/Bin 55 | 56 | var vscodeExtPath = Path.Combine( 57 | Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), 58 | ".vscode", 59 | "extensions"); 60 | 61 | var sentinelFiles = new List(); 62 | var vscodeExtDir = new DirectoryInfo(vscodeExtPath); 63 | 64 | if (vscodeExtDir.Exists) 65 | { 66 | var sdkResolverDirs = Directory.EnumerateDirectories(vscodeExtPath, "*Microsoft.DotNet.MSBuildSdkResolver", SearchOption.AllDirectories); 67 | 68 | if (sdkResolverDirs?.Any() ?? false) 69 | { 70 | foreach (var r in sdkResolverDirs) 71 | { 72 | if (!Directory.Exists(r)) 73 | continue; 74 | 75 | var sentinelFile = Path.Combine(r, "EnableWorkloadResolver.sentinel"); 76 | 77 | sentinelFiles.Add(sentinelFile); 78 | } 79 | } 80 | } 81 | 82 | if (sentinelFiles.Any()) 83 | history.ContributeState(this, "sentinel_files", sentinelFiles.ToArray()); 84 | 85 | if (ok || Optional) 86 | return DiagnosticResult.Ok(this); 87 | 88 | return new DiagnosticResult(Status.Error, this); 89 | } 90 | 91 | Task> GetMacInfo() 92 | { 93 | var items = new List(); 94 | 95 | var likelyPaths = new List { 96 | "/Applications/Visual Studio.app/" 97 | }; 98 | 99 | 100 | foreach (var likelyPath in likelyPaths) 101 | { 102 | var path = Path.Combine(likelyPath, "Contents", "Info.plist"); 103 | 104 | if (File.Exists(path)) 105 | { 106 | var plist = (NSDictionary)Claunia.PropertyList.PropertyListParser.Parse(path); 107 | 108 | var bvs = plist["CFBundleVersion"].ToString(); 109 | 110 | if (!string.IsNullOrEmpty(bvs) && NuGetVersion.TryParse(bvs, out var ver)) 111 | items.Add(new VisualStudioInfo 112 | { 113 | Path = likelyPath, 114 | Version = ver 115 | }); 116 | } 117 | } 118 | 119 | return Task.FromResult>(items); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /MauiCheck/Checkups/VisualStudioWindowsCheckup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.Json; 6 | using System.Threading.Tasks; 7 | using DotNetCheck.Models; 8 | using NuGet.Versioning; 9 | 10 | namespace DotNetCheck.Checkups 11 | { 12 | public class VisualStudioWindowsCheckup : Checkup 13 | { 14 | public override bool IsPlatformSupported(Platform platform) 15 | => platform == Platform.Windows; 16 | 17 | public NuGetVersion MinimumVersion 18 | => Extensions.ParseVersion(Manifest?.Check?.VSWin?.MinimumVersion, new NuGetVersion("16.11.0")); 19 | 20 | public NuGetVersion ExactVersion 21 | => Extensions.ParseVersion(Manifest?.Check?.VSWin?.ExactVersion); 22 | 23 | public override string Id => "vswin"; 24 | 25 | public override string Title => $"Visual Studio {MinimumVersion.ThisOrExact(ExactVersion)}"; 26 | 27 | public override bool ShouldExamine(SharedState history) 28 | => Manifest?.Check?.VSWin != null; 29 | 30 | public override async Task Examine(SharedState history) 31 | { 32 | var vsinfo = await GetWindowsInfo(); 33 | 34 | foreach (var vi in vsinfo) 35 | { 36 | if (vi.Version.IsCompatible(MinimumVersion, ExactVersion)) 37 | ReportStatus($"{vi.Version} - {vi.Path}", Status.Ok); 38 | else 39 | ReportStatus($"{vi.Version}", null); 40 | } 41 | 42 | if (vsinfo.Any(vs => vs.Version.IsCompatible(MinimumVersion, ExactVersion))) 43 | return DiagnosticResult.Ok(this); 44 | 45 | ReportStatus($"Missing Visual Studio >= {MinimumVersion.ThisOrExact(ExactVersion)}", Status.Error); 46 | 47 | return new DiagnosticResult(Status.Error, this); 48 | } 49 | 50 | Task> GetWindowsInfo() 51 | { 52 | var items = new List(); 53 | 54 | var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), 55 | "Microsoft Visual Studio", "Installer", "vswhere.exe"); 56 | 57 | 58 | if (!File.Exists(path)) 59 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), 60 | "Microsoft Visual Studio", "Installer", "vswhere.exe"); 61 | 62 | if (!File.Exists(path)) 63 | return default; 64 | 65 | var r = ShellProcessRunner.Run(path, 66 | "-all -requires Microsoft.Component.MSBuild -format json -prerelease"); 67 | 68 | var str = r.GetOutput(); 69 | 70 | var json = JsonDocument.Parse(str); 71 | 72 | foreach (var vsjson in json.RootElement.EnumerateArray()) 73 | { 74 | if (!vsjson.TryGetProperty("catalog", out var vsCat) || !vsCat.TryGetProperty("productSemanticVersion", out var vsSemVer)) 75 | continue; 76 | 77 | if (!NuGetVersion.TryParse(vsSemVer.GetString(), out var semVer)) 78 | continue; 79 | 80 | if (!vsjson.TryGetProperty("installationPath", out var installPath)) 81 | continue; 82 | 83 | items.Add(new VisualStudioInfo 84 | { 85 | Version = semVer, 86 | Path = installPath.GetString() 87 | }); 88 | } 89 | 90 | return Task.FromResult>(items); 91 | } 92 | } 93 | 94 | public struct VisualStudioInfo 95 | { 96 | public string Path { get; set; } 97 | 98 | public NuGetVersion Version { get; set; } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /MauiCheck/Checkups/XCodeCheckup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Claunia.PropertyList; 7 | using DotNetCheck.Models; 8 | using NuGet.Versioning; 9 | 10 | namespace DotNetCheck.Checkups 11 | { 12 | public class XCodeCheckup : Checkup 13 | { 14 | const string BugCommandLineToolsPath = "/Library/Developer/CommandLineTools"; 15 | 16 | public override bool IsPlatformSupported(Platform platform) 17 | => platform == Platform.OSX; 18 | 19 | public NuGetVersion MinimumVersion 20 | => Extensions.ParseVersion(Manifest?.Check?.XCode?.MinimumVersion); 21 | 22 | public string MinimumVersionName 23 | => Manifest?.Check?.XCode?.MinimumVersionName; 24 | 25 | public NuGetVersion ExactVersion 26 | => Extensions.ParseVersion(Manifest?.Check?.XCode?.ExactVersion); 27 | 28 | public string ExactVersionName 29 | => Manifest?.Check?.XCode?.ExactVersionName; 30 | 31 | public string VersionName 32 | => ExactVersionName ?? MinimumVersionName ?? ExactVersion?.ToString() ?? MinimumVersion?.ToString(); 33 | 34 | public override string Id => "xcode"; 35 | 36 | public override string Title => $"XCode {VersionName}"; 37 | 38 | public override bool ShouldExamine(SharedState history) 39 | => Manifest?.Check?.XCode != null; 40 | 41 | public override Task Examine(SharedState history) 42 | { 43 | var selected = GetSelectedXCode(); 44 | 45 | if (selected.Version.IsCompatible(MinimumVersion, ExactVersion)) 46 | { 47 | // Selected version is good 48 | ReportStatus($"Xcode.app ({VersionName})", Status.Ok); 49 | return Task.FromResult(DiagnosticResult.Ok(this)); 50 | } 51 | 52 | XCodeInfo eligibleXcode = null; 53 | 54 | var xcodes = FindXCodeInstalls(); 55 | 56 | foreach (var x in xcodes) 57 | { 58 | if (x.Version.IsCompatible(MinimumVersion, ExactVersion)) 59 | { 60 | eligibleXcode = x; 61 | break; 62 | } 63 | } 64 | 65 | if (eligibleXcode != null) 66 | { 67 | // If this is the case, they need to run xcode-select -s 68 | ReportStatus($"No Xcode.app or an incompatible Xcode.app version is selected, but one was found at ({eligibleXcode.Path})", Status.Error); 69 | 70 | return Task.FromResult(new DiagnosticResult( 71 | Status.Error, 72 | this, 73 | new Suggestion("Run xcode-select -s ", 74 | new Solutions.ActionSolution((sln, cancelToken) => 75 | { 76 | ShellProcessRunner.Run("xcode-select", "-s " + eligibleXcode.Path); 77 | return Task.CompletedTask; 78 | })))); 79 | } 80 | 81 | ReportStatus($"Xcode.app ({VersionName}) not installed.", Status.Error); 82 | 83 | return Task.FromResult(new DiagnosticResult( 84 | Status.Error, 85 | this, 86 | new Suggestion($"Download XCode {VersionName}"))); 87 | } 88 | 89 | XCodeInfo GetSelectedXCode() 90 | { 91 | var r = ShellProcessRunner.Run("xcode-select", "-p"); 92 | 93 | var xcodeSelectedPath = r.GetOutput().Trim(); 94 | 95 | if (!string.IsNullOrEmpty(xcodeSelectedPath)) 96 | { 97 | if (xcodeSelectedPath.Equals(BugCommandLineToolsPath)) 98 | throw new InvalidDataException(); 99 | 100 | var infoPlist = Path.Combine(xcodeSelectedPath, "..", "Info.plist"); 101 | if (File.Exists(infoPlist)) 102 | { 103 | return GetXcodeInfo( 104 | Path.GetFullPath( 105 | Path.Combine(xcodeSelectedPath, "..", "..")), true); 106 | } 107 | } 108 | 109 | return null; 110 | } 111 | 112 | public static readonly string[] LikelyPaths = new [] 113 | { 114 | "/Applications/Xcode.app", 115 | "/Applications/Xcode-beta.app", 116 | }; 117 | 118 | IEnumerable FindXCodeInstalls() 119 | { 120 | foreach (var p in LikelyPaths) 121 | { 122 | var i = GetXcodeInfo(p, false); 123 | if (i != null) 124 | yield return i; 125 | } 126 | } 127 | 128 | XCodeInfo GetXcodeInfo(string path, bool selected) 129 | { 130 | var versionPlist = Path.Combine(path, "Contents", "version.plist"); 131 | 132 | if (File.Exists(versionPlist)) 133 | { 134 | NSDictionary rootDict = (NSDictionary)PropertyListParser.Parse(versionPlist); 135 | string cfBundleVersion = rootDict.ObjectForKey("CFBundleVersion")?.ToString(); 136 | string cfBundleShortVersion = rootDict.ObjectForKey("CFBundleShortVersionString")?.ToString(); 137 | string productBuildVersion = rootDict.ObjectForKey("ProductBuildVersion")?.ToString(); 138 | 139 | if (NuGetVersion.TryParse(cfBundleVersion, out var v)) 140 | return new XCodeInfo(v, cfBundleShortVersion, productBuildVersion, path, selected); 141 | } 142 | else 143 | { 144 | var infoPlist = Path.Combine(path, "Contents", "Info.plist"); 145 | 146 | if (File.Exists(infoPlist)) 147 | { 148 | NSDictionary rootDict = (NSDictionary)PropertyListParser.Parse(infoPlist); 149 | string cfBundleVersion = rootDict.ObjectForKey("CFBundleVersion")?.ToString(); 150 | string cfBundleShortVersion = rootDict.ObjectForKey("CFBundleShortVersionString")?.ToString(); 151 | if (NuGetVersion.TryParse(cfBundleVersion, out var v)) 152 | return new XCodeInfo(v, cfBundleShortVersion, string.Empty, path, selected); 153 | } 154 | } 155 | return null; 156 | } 157 | } 158 | 159 | public record XCodeInfo(NuGetVersion Version, string VersionString, string BuildVersion, string Path, bool Selected); 160 | } 161 | -------------------------------------------------------------------------------- /MauiCheck/ConfigCommand.cs: -------------------------------------------------------------------------------- 1 | using DotNetCheck.Models; 2 | using Newtonsoft.Json; 3 | using NuGet.Configuration; 4 | using NuGet.Versioning; 5 | using Spectre.Console; 6 | using Spectre.Console.Cli; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace DotNetCheck 15 | { 16 | public class ConfigCommand : AsyncCommand 17 | { 18 | public override async Task ExecuteAsync(CommandContext context, ConfigSettings settings) 19 | { 20 | AnsiConsole.Markup($"[bold blue]{Icon.Thinking} Synchronizing configuration...[/]"); 21 | 22 | var manifest = await ToolInfo.LoadManifest(settings.Manifest, settings.GetManifestChannel()); 23 | 24 | if (!ToolInfo.Validate(manifest)) 25 | { 26 | ToolInfo.ExitPrompt(settings.NonInteractive); 27 | return -1; 28 | } 29 | 30 | AnsiConsole.MarkupLine(" ok"); 31 | 32 | var manifestDotNetSdk = manifest?.Check?.DotNet?.Sdks?.FirstOrDefault(); 33 | var manifestNuGetSources = manifestDotNetSdk?.PackageSources; 34 | 35 | if (manifestDotNetSdk != null 36 | && (settings.DotNetAllowPrerelease.HasValue 37 | || !string.IsNullOrEmpty(settings.DotNetRollForward) 38 | || settings.DotNetVersion)) 39 | { 40 | // Write global.json or update global.json 41 | /* 42 | * 43 | * { 44 | * "sdk": { 45 | * "version" : "", 46 | * "allowPrerelease": true, 47 | * "rollForward": "patch|feature|minor|major|latestPatch|latestFeature|latestMinor|latestMajor|disable" 48 | * } 49 | * } 50 | */ 51 | 52 | const string globalJsonFile = "global.json"; 53 | 54 | 55 | DotNetGlobalJson globaljson = default; 56 | 57 | if (File.Exists(globalJsonFile)) 58 | { 59 | try { globaljson = JsonConvert.DeserializeObject(File.ReadAllText(globalJsonFile)); } 60 | catch { } 61 | } 62 | 63 | if (globaljson == null) 64 | globaljson = new DotNetGlobalJson(); 65 | 66 | if (settings.DotNetVersion) 67 | { 68 | Util.Log($"Setting version in global.json: {manifestDotNetSdk.Version}"); 69 | globaljson.Sdk.Version = manifestDotNetSdk.Version; 70 | AnsiConsole.MarkupLine($"[green]{Icon.Success} Set global.json 'version': {manifestDotNetSdk.Version}[/]"); 71 | } 72 | 73 | if (settings?.DotNetAllowPrerelease.HasValue ?? false) 74 | { 75 | Util.Log($"Setting allowPrerelease in global.json: {settings.DotNetAllowPrerelease.Value}"); 76 | globaljson.Sdk.AllowPrerelease = settings.DotNetAllowPrerelease.Value; 77 | AnsiConsole.MarkupLine($"[green]{Icon.Success} Set global.json 'allowPrerelease': {settings.DotNetAllowPrerelease.Value}[/]"); 78 | } 79 | 80 | if (!string.IsNullOrEmpty(settings.DotNetRollForward)) 81 | { 82 | if (!DotNetGlobalJsonSdk.ValidRollForwardValues.Contains(settings.DotNetRollForward)) 83 | throw new ArgumentException("Invalid rollForward value specified. Must be one of: ", string.Join(", ", DotNetGlobalJsonSdk.ValidRollForwardValues)); 84 | 85 | Util.Log($"Setting rollForward in global.json: {settings.DotNetRollForward}"); 86 | globaljson.Sdk.RollForward = settings.DotNetRollForward; 87 | AnsiConsole.MarkupLine($"[green]{Icon.Success} Set global.json 'rollForward': {settings.DotNetRollForward}[/]"); 88 | } 89 | 90 | File.WriteAllText(globalJsonFile, JsonConvert.SerializeObject(globaljson, Formatting.Indented)); 91 | } 92 | 93 | if ((manifestNuGetSources?.Any() ?? false) && settings.NuGetSources) 94 | { 95 | // write nuget.config or update 96 | var nugetConfigFile = "NuGet.config"; 97 | var nugetRoot = Directory.GetCurrentDirectory(); 98 | 99 | ISettings ns = new Settings(nugetRoot, nugetConfigFile); 100 | 101 | if (File.Exists(Path.Combine(nugetRoot, nugetConfigFile))) 102 | { 103 | try 104 | { 105 | ns = Settings.LoadSpecificSettings(nugetRoot, nugetConfigFile); 106 | } 107 | catch { } 108 | } 109 | 110 | var packageSourceProvider = new PackageSourceProvider(ns); 111 | 112 | var addedAny = false; 113 | 114 | foreach (var src in manifestNuGetSources) 115 | { 116 | var srcExists = false; 117 | try 118 | { 119 | var existingSrc = packageSourceProvider.GetPackageSourceBySource(src); 120 | 121 | if (existingSrc != null) 122 | srcExists = true; 123 | } 124 | catch { } 125 | 126 | if (srcExists) 127 | { 128 | Util.Log($"PackageSource already exists in NuGet.config: {src}"); 129 | AnsiConsole.MarkupLine($"{Icon.ListItem} PackageSource exists in NuGet.config: {src}"); 130 | } 131 | else 132 | { 133 | Util.Log($"Adding PackageSource to NuGet.config: {src}"); 134 | packageSourceProvider.AddPackageSource(new PackageSource(src)); 135 | addedAny = true; 136 | 137 | AnsiConsole.MarkupLine($"[green]{Icon.Success} Added PackageSource to NuGet.config: {src}[/]"); 138 | } 139 | } 140 | 141 | if (addedAny) 142 | { 143 | Util.Log($"Saving NuGet.config"); 144 | ns.SaveToDisk(); 145 | } 146 | } 147 | 148 | ToolInfo.ExitPrompt(settings.NonInteractive); 149 | return 0; 150 | } 151 | 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /MauiCheck/ConfigSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Spectre.Console.Cli; 3 | 4 | namespace DotNetCheck 5 | { 6 | public class ConfigSettings : CommandSettings, IManifestChannelSettings 7 | { 8 | [CommandOption("-m|--manifest ")] 9 | public string Manifest { get; set; } 10 | 11 | [CommandOption("--pre|--preview|-d|--dev")] 12 | public bool Preview { get; set; } 13 | 14 | [CommandOption("--main")] 15 | public bool Main { get; set; } 16 | 17 | [CommandOption("-n|--non-interactive")] 18 | public bool NonInteractive { get; set; } 19 | 20 | [CommandOption("--nuget|--nuget-sources")] 21 | public bool NuGetSources { get; set; } 22 | 23 | [CommandOption("--dotnet|--dotnet-version")] 24 | public bool DotNetVersion { get; set; } 25 | 26 | [CommandOption("--dotnet-pre ")] 27 | public bool? DotNetAllowPrerelease { get; set; } 28 | 29 | [CommandOption("--dotnet-rollforward