├── .prettierrc.yaml ├── src ├── .editorconfig ├── VSInsertionMetadata │ ├── ProfilingInputs.props │ └── Library.VSInsertionMetadata.proj ├── AssemblyInfo.cs ├── AssemblyInfo.vb ├── Directory.Build.targets ├── Directory.Build.props ├── Microsoft.VisualStudio.SolutionPersistence │ ├── Utilities │ │ ├── ComparerExtensions.cs │ │ ├── Singleton`1.cs │ │ ├── ParseUtilities.cs │ │ ├── ListStructEnumerable`1.cs │ │ ├── DefaultIdGenerator.cs │ │ └── Argument.cs │ ├── Serializer │ │ ├── Xml │ │ │ ├── XmlDecorators │ │ │ │ ├── XmlConfigurationBuild.cs │ │ │ │ ├── XmlConfigurationDeploy.cs │ │ │ │ ├── XmlConfigurationBuildType.cs │ │ │ │ ├── XmlConfigurationPlatform.cs │ │ │ │ ├── XmlFile.cs │ │ │ │ ├── XmlPlatform.cs │ │ │ │ ├── XmlBuildType.cs │ │ │ │ ├── XmlBuildDependency.cs │ │ │ │ ├── IItemRefDecorator.cs │ │ │ │ ├── XmlProperty.cs │ │ │ │ ├── XmlProject.ApplyModel.cs │ │ │ │ ├── XmlContainerWithProperties.cs │ │ │ │ └── XmlSolution.ApplyModel.cs │ │ │ ├── XmlDomUtilities.cs │ │ │ ├── XmlElementSubElementsEnumerable.cs │ │ │ ├── XmlElementAttributes.cs │ │ │ ├── SlnXMLSerializer.Reader.cs │ │ │ ├── XmlElementSubElements.cs │ │ │ ├── SlnXmlModelExtension.cs │ │ │ ├── SlnxSerializerSettings.cs │ │ │ ├── SlnXMLSerializer.cs │ │ │ └── LineInfoXmlDocument.cs │ │ ├── SlnV12 │ │ │ ├── SlnV12SerializerSettings.cs │ │ │ ├── SlnV12ModelExtension.cs │ │ │ ├── SectionName.cs │ │ │ └── SlnConstants.cs │ │ ├── SolutionSerializers.cs │ │ └── SingleFileSerializerBase`1.cs │ ├── AssemblyInfo.cs │ ├── Model │ │ ├── BuildTypeNames.cs │ │ ├── ISerializerModelExtension.cs │ │ ├── ProjectConfigMapping.cs │ │ ├── PlatformNames.cs │ │ ├── StringTable.cs │ │ └── ConfigurationRule.cs │ ├── Microsoft.VisualStudio.SolutionPersistence.csproj │ ├── RequiredNetFramework.cs │ └── LocalUsings.cs └── OptProf.targets ├── azure-pipelines ├── no_strongname.txt ├── schedule-only-steps.yml ├── no_authenticode.txt ├── NuGetSbom.props ├── BuildStageVariables.yml ├── release-deployment-prep.yml ├── falsepositives.gdnsuppress ├── GlobalVariables.yml ├── TSAOptions.json ├── PoliCheckExclusions.xml ├── Get-InsertionPRId.ps1 ├── publish-codecoverage.yml ├── WIFtoPATauth.yml ├── microbuild.after.yml ├── install-dependencies.yml ├── publish_artifacts.ps1 ├── microbuild.before.yml ├── PostPRMessage.ps1 ├── publish-symbols.yml ├── Merge-CodeCoverage.ps1 ├── dotnet.yml └── apiscan.yml ├── docfx ├── .gitignore ├── docs │ ├── features.md │ ├── toc.yml │ └── getting-started.md ├── toc.yml ├── index.md └── docfx.json ├── CodeQL.yml ├── tools ├── variables │ ├── BusinessGroupName.ps1 │ ├── SymbolsFeatureName.ps1 │ ├── InsertTargetBranch.ps1 │ ├── VstsDropNames.ps1 │ ├── DotNetSdkVersion.ps1 │ ├── LocLanguages.ps1 │ ├── ProfilingInputsDropName.ps1 │ ├── InsertPropsValues.ps1 │ ├── _all.ps1 │ ├── InsertJsonValues.ps1 │ ├── InsertVersionsValues.ps1 │ └── _define.ps1 ├── artifacts │ ├── projectAssetsJson.ps1 │ ├── test_symbols.ps1 │ ├── build_logs.ps1 │ ├── deployables.ps1 │ ├── symbols.ps1 │ ├── testResults.ps1 │ ├── APIScanInputs.ps1 │ ├── LocBin.ps1 │ ├── coverageResults.ps1 │ ├── VSInsertion.ps1 │ └── Variables.ps1 ├── Get-TempToolsPath.ps1 ├── Get-ArtifactsStagingDirectory.ps1 ├── Get-ProcDump.ps1 ├── Get-NuGetTool.ps1 ├── Get-LibTemplateBasis.ps1 ├── publish-CodeCov.ps1 ├── Check-DotNetSdk.ps1 ├── Check-DotNetRuntime.ps1 ├── Prepare-Legacy-Symbols.ps1 ├── Convert-PDB.ps1 ├── Get-SymbolFiles.ps1 └── Install-NuGetPackage.ps1 ├── settings.VisualStudio.json ├── test ├── Microsoft.VisualStudio.SolutionPersistence.Tests │ ├── SlnAssets │ │ ├── Min.slnx.xml │ │ ├── BlankSolution.slnx.xml │ │ ├── Invalid │ │ │ ├── WrongRoot.slnx.xml │ │ │ ├── Invalid.sln.txt │ │ │ ├── VersionFuture.slnx.xml │ │ │ ├── VersionInvalid.slnx.xml │ │ │ ├── SolutionFolder-NoEndSlash.slnx.xml │ │ │ ├── SolutionFolder-WrongSlash.slnx.xml │ │ │ ├── SolutionFolder-NoStartSlash.slnx.xml │ │ │ ├── DuplicateFolders.slnx.xml │ │ │ ├── DuplicateProjects.slnx.xml │ │ │ ├── Version14.sln.txt │ │ │ ├── VersionFuture.sln.txt │ │ │ ├── ExtraLines.sln.txt │ │ │ ├── DuplicateFolderId.slnx.xml │ │ │ ├── BasedOnLoop.slnx.xml │ │ │ ├── DuplicateProjectId.slnx.xml │ │ │ ├── MissingEnd.sln.txt │ │ │ ├── DuplicateFolderId.sln.txt │ │ │ ├── InvalidProjectType.sln.txt │ │ │ ├── PathSlashes.slnx.xml │ │ │ ├── LegacyValues.slnx.xml │ │ │ ├── DuplicateItemId.sln.txt │ │ │ └── DuplicateProjectId.sln.txt │ │ ├── Version.slnx.xml │ │ ├── VersionMin.slnx.xml │ │ ├── SlnxWhitespace │ │ │ ├── JustProperties-empty.slnx.xml │ │ │ ├── JustProperties-nocomments.slnx.xml │ │ │ ├── JustProperties-no2no4.slnx.xml │ │ │ ├── JustProperties.slnx.xml │ │ │ └── JustProperties-add0add7.slnx.xml │ │ ├── ProjectTypeId.slnx.xml │ │ ├── FolderId.slnx.xml │ │ ├── Min.sln.txt │ │ ├── ProjectType.slnx.xml │ │ ├── LegacyValues-TrimVS.slnx.xml │ │ ├── Report Project.slnx.xml │ │ ├── SingleNativeProject.slnx.xml │ │ ├── ProjectTypeConfig.slnx.xml │ │ ├── BlankSolution.sln.txt │ │ ├── LegacyValues-NoObsolete.slnx.xml │ │ ├── Configurations │ │ │ ├── MissingConfigurations.slnx.xml │ │ │ └── MissingConfigurations.sln.txt │ │ ├── PathSlashes.slnx.xml │ │ ├── FolderId.sln.txt │ │ ├── Report Project.sln.txt │ │ ├── DuplicateProjectId-After.sln.txt │ │ ├── ProjectTypeConfig.sln.txt │ │ └── SingleNativeProject.sln.txt │ ├── xunit.runner.json │ ├── ResourceName.cs │ ├── ResourceStream.cs │ ├── FileContents.cs │ ├── Utilities │ │ ├── StringExtensions.cs │ │ └── CollectionExtensions.cs │ ├── Microsoft.VisualStudio.SolutionPersistence.Tests.csproj │ ├── Serialization │ │ ├── ProjectTypes.cs │ │ ├── Format.cs │ │ ├── MakeSlnx.MakeSlnxFixture.cs │ │ └── StringTests.cs │ └── LocalUsings.cs ├── Directory.Build.targets ├── Directory.Build.props └── .editorconfig ├── global.json ├── .azuredevops └── dependabot.yml ├── Directory.Build.targets ├── loc └── lci │ └── Microsoft.VisualStudio.SolutionPersistence.dll.lci ├── azurepipelines-coverage.yml ├── version.json ├── CODE_OF_CONDUCT.md ├── .vscode ├── tasks.json ├── launch.json ├── extensions.json └── settings.json ├── .config └── dotnet-tools.json ├── init.cmd ├── nuget.config ├── .devcontainer ├── devcontainer.json └── Dockerfile ├── Directory.Build.rsp ├── azure-pipelines.yml ├── stylecop.json ├── LICENSE ├── SolutionPersistence.slnx ├── .github ├── copilot-instructions.md └── workflows │ └── copilot-setup-steps.yml ├── SUPPORT.md ├── Directory.Packages.props └── README.md /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /azure-pipelines/no_strongname.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docfx/.gitignore: -------------------------------------------------------------------------------- 1 | _site/ 2 | api/ 3 | -------------------------------------------------------------------------------- /docfx/docs/features.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /CodeQL.yml: -------------------------------------------------------------------------------- 1 | path_classifiers: 2 | library: 3 | - 'test/**' 4 | -------------------------------------------------------------------------------- /tools/variables/BusinessGroupName.ps1: -------------------------------------------------------------------------------- 1 | 'Visual Studio - VS Core' 2 | -------------------------------------------------------------------------------- /tools/variables/SymbolsFeatureName.ps1: -------------------------------------------------------------------------------- 1 | 'Microsoft.VisualStudio.SolutionPersistence' 2 | -------------------------------------------------------------------------------- /docfx/toc.yml: -------------------------------------------------------------------------------- 1 | items: 2 | - name: Docs 3 | href: docs/ 4 | - name: API 5 | href: api/ 6 | -------------------------------------------------------------------------------- /settings.VisualStudio.json: -------------------------------------------------------------------------------- 1 | { 2 | "textEditor.codeCleanup.profile": "profile1" 3 | } 4 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Min.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/BlankSolution.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/WrongRoot.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Version.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/Invalid.sln.txt: -------------------------------------------------------------------------------- 1 | This is an invalid sln file. -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/VersionMin.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/VersionFuture.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/VersionInvalid.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docfx/docs/toc.yml: -------------------------------------------------------------------------------- 1 | items: 2 | - name: Features 3 | href: features.md 4 | - name: Getting Started 5 | href: getting-started.md 6 | -------------------------------------------------------------------------------- /tools/variables/InsertTargetBranch.ps1: -------------------------------------------------------------------------------- 1 | # This is the default branch of the VS repo that we will use to insert into VS. 2 | 'main' 3 | -------------------------------------------------------------------------------- /tools/variables/VstsDropNames.ps1: -------------------------------------------------------------------------------- 1 | "Products/$env:SYSTEM_TEAMPROJECT/$env:BUILD_REPOSITORY_NAME/$env:BUILD_SOURCEBRANCHNAME/$env:BUILD_BUILDID" 2 | -------------------------------------------------------------------------------- /azure-pipelines/schedule-only-steps.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - powershell: echo "##vso[build.addbuildtag]auto-insertion" 3 | displayName: Tag for auto-insertion 4 | -------------------------------------------------------------------------------- /tools/variables/DotNetSdkVersion.ps1: -------------------------------------------------------------------------------- 1 | $globalJson = Get-Content -LiteralPath "$PSScriptRoot\..\..\global.json" | ConvertFrom-Json 2 | $globalJson.sdk.version 3 | -------------------------------------------------------------------------------- /azure-pipelines/no_authenticode.txt: -------------------------------------------------------------------------------- 1 | bin\packages\release\vsix\_manifest\manifest.cat,sbom signed 2 | bin\packages\release\vsix\_manifest\spdx_2.2\manifest.cat,sbom signed 3 | -------------------------------------------------------------------------------- /docfx/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | _layout: landing 3 | --- 4 | 5 | # Overview 6 | 7 | This is your docfx landing page. 8 | 9 | Click "Docs" across the top to get started. 10 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/SolutionFolder-NoEndSlash.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/SolutionFolder-WrongSlash.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/SolutionFolder-NoStartSlash.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/SlnxWhitespace/JustProperties-empty.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", 3 | "shadowCopy": false 4 | } 5 | -------------------------------------------------------------------------------- /src/VSInsertionMetadata/ProfilingInputs.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/DuplicateFolders.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/ProjectTypeId.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tools/variables/LocLanguages.ps1: -------------------------------------------------------------------------------- 1 | ## For faster PR/CI builds localize only for 2 languages, ENU and JPN provide good enough coverage 2 | if ($env:BUILD_REASON -eq 'PullRequest') { 3 | 'ENU,JPN' 4 | } else { 5 | 'VS' 6 | } 7 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.305", 4 | "rollForward": "patch", 5 | "allowPrerelease": false 6 | }, 7 | "msbuild-sdks": { 8 | "Microsoft.Build.NoTargets": "3.7.56" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /azure-pipelines/NuGetSbom.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 2 5 | 6 | 7 | -------------------------------------------------------------------------------- /azure-pipelines/BuildStageVariables.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 3 | BuildConfiguration: Release 4 | NUGET_PACKAGES: $(Agent.TempDirectory)/.nuget/packages/ 5 | # #codecov_token: # Get a new one from https://codecov.io/ 6 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/FolderId.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/DuplicateProjects.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Min.sln.txt: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Global 4 | GlobalSection(SolutionProperties) = preSolution 5 | HideSolutionNode = FALSE 6 | EndGlobalSection 7 | EndGlobal 8 | -------------------------------------------------------------------------------- /tools/artifacts/projectAssetsJson.ps1: -------------------------------------------------------------------------------- 1 | $ObjRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\obj") 2 | 3 | if (!(Test-Path $ObjRoot)) { return } 4 | 5 | @{ 6 | "$ObjRoot" = ( 7 | (Get-ChildItem "$ObjRoot\project.assets.json" -Recurse) 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/Version14.sln.txt: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 14.00 3 | Global 4 | GlobalSection(SolutionProperties) = preSolution 5 | HideSolutionNode = FALSE 6 | EndGlobalSection 7 | EndGlobal 8 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/VersionFuture.sln.txt: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 15.00 3 | Global 4 | GlobalSection(SolutionProperties) = preSolution 5 | HideSolutionNode = FALSE 6 | EndGlobalSection 7 | EndGlobal 8 | -------------------------------------------------------------------------------- /tools/artifacts/test_symbols.ps1: -------------------------------------------------------------------------------- 1 | $BinPath = [System.IO.Path]::GetFullPath("$PSScriptRoot/../../bin") 2 | if (!(Test-Path $BinPath)) { return } 3 | $symbolfiles = & "$PSScriptRoot/../Get-SymbolFiles.ps1" -Path $BinPath -Tests | Get-Unique 4 | 5 | @{ 6 | "$BinPath" = $SymbolFiles; 7 | } 8 | -------------------------------------------------------------------------------- /azure-pipelines/release-deployment-prep.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - download: CI 3 | artifact: Variables-Windows 4 | displayName: 🔻 Download Variables-Windows artifact 5 | - powershell: $(Pipeline.Workspace)/CI/Variables-Windows/_define.ps1 6 | displayName: ⚙️ Set pipeline variables based on artifacts 7 | -------------------------------------------------------------------------------- /.azuredevops/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://eng.ms/docs/products/dependabot/configuration/version_updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: nuget 7 | directory: / 8 | schedule: 9 | interval: monthly 10 | -------------------------------------------------------------------------------- /tools/artifacts/build_logs.ps1: -------------------------------------------------------------------------------- 1 | $ArtifactStagingFolder = & "$PSScriptRoot/../Get-ArtifactsStagingDirectory.ps1" 2 | 3 | if (!(Test-Path $ArtifactStagingFolder/build_logs)) { return } 4 | 5 | @{ 6 | "$ArtifactStagingFolder/build_logs" = (Get-ChildItem -Recurse "$ArtifactStagingFolder/build_logs") 7 | } 8 | -------------------------------------------------------------------------------- /src/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Runtime.InteropServices; 5 | 6 | [assembly: DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] 7 | -------------------------------------------------------------------------------- /src/AssemblyInfo.vb: -------------------------------------------------------------------------------- 1 | ' Copyright (c) Microsoft Corporation. All rights reserved. 2 | ' Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | Imports System.Runtime.InteropServices 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/ProjectType.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /tools/variables/ProfilingInputsDropName.ps1: -------------------------------------------------------------------------------- 1 | if ($env:SYSTEM_TEAMPROJECT) { 2 | "ProfilingInputs/$env:SYSTEM_TEAMPROJECT/$env:BUILD_REPOSITORY_NAME/$env:BUILD_SOURCEBRANCHNAME/$env:BUILD_BUILDID" 3 | } else { 4 | Write-Warning "No Azure Pipelines build detected. No Azure Pipelines drop name will be computed." 5 | } 6 | -------------------------------------------------------------------------------- /azure-pipelines/falsepositives.gdnsuppress: -------------------------------------------------------------------------------- 1 | { 2 | "version": "latest", 3 | "suppressionSets": { 4 | "falsepositives": { 5 | "name": "falsepositives", 6 | "createdDate": "2021-12-03 00:23:08Z", 7 | "lastUpdatedDate": "2021-12-03 00:23:08Z" 8 | } 9 | }, 10 | "results": { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /azure-pipelines/GlobalVariables.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | # These variables are required for MicroBuild tasks 3 | TeamName: VS IDE 4 | TeamEmail: vsidemicrobuild@microsoft.com 5 | # These variables influence insertion pipelines 6 | ContainsVsix: false # This should be true when the repo builds a VSIX that should be inserted to VS. 7 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/LegacyValues-TrimVS.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/ExtraLines.sln.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Microsoft Visual Studio Solution File, Format Version 12.00 5 | 6 | Global 7 | 8 | GlobalSection(SolutionProperties) = preSolution 9 | 10 | HideSolutionNode = FALSE 11 | 12 | EndGlobalSection 13 | 14 | EndGlobal 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /loc/lci/Microsoft.VisualStudio.SolutionPersistence.dll.lci: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docfx/docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Installation 4 | 5 | Consume this library via its NuGet Package. 6 | Click on the badge to find its latest version and the instructions for consuming it that best apply to your project. 7 | 8 | [![NuGet package](https://img.shields.io/nuget/v/Library.svg)](https://nuget.org/packages/Library) 9 | 10 | ## Usage 11 | 12 | TODO 13 | -------------------------------------------------------------------------------- /azurepipelines-coverage.yml: -------------------------------------------------------------------------------- 1 | # https://learn.microsoft.com/azure/devops/pipelines/test/codecoverage-for-pullrequests 2 | coverage: 3 | status: 4 | comments: on # add comment to PRs reporting diff in coverage of modified files 5 | diff: # diff coverage is code coverage only for the lines changed in a pull request. 6 | target: 70% # set this to a desired %. Default is 70% 7 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/ResourceName.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | /// 5 | /// Represents a resource name and an identifier. 6 | /// 7 | public record struct ResourceName(string Name, string FullResourceId); 8 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/ResourceStream.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | /// 5 | /// Represents a resource loaded from the assembly. 6 | /// 7 | public record struct ResourceStream(string Name, Stream Stream); 8 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "1.0", 4 | "versionHeightOffset": "10", 5 | "publicReleaseRefSpec": [ 6 | "^refs/heads/main$", 7 | "^refs/heads/v\\d+(?:\\.\\d+)?$" 8 | ], 9 | "cloudBuild": { 10 | "setVersionVariables": false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/DuplicateFolderId.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /tools/Get-TempToolsPath.ps1: -------------------------------------------------------------------------------- 1 | if ($env:AGENT_TEMPDIRECTORY) { 2 | $path = "$env:AGENT_TEMPDIRECTORY\$env:BUILD_BUILDID" 3 | } elseif ($env:localappdata) { 4 | $path = "$env:localappdata\gitrepos\tools" 5 | } else { 6 | $path = "$PSScriptRoot\..\obj\tools" 7 | } 8 | 9 | if (!(Test-Path $path)) { 10 | New-Item -ItemType Directory -Path $Path | Out-Null 11 | } 12 | 13 | (Resolve-Path $path).Path 14 | -------------------------------------------------------------------------------- /tools/artifacts/deployables.ps1: -------------------------------------------------------------------------------- 1 | $RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..") 2 | $BuildConfiguration = $env:BUILDCONFIGURATION 3 | if (!$BuildConfiguration) { 4 | $BuildConfiguration = 'Debug' 5 | } 6 | 7 | $PackagesRoot = "$RepoRoot/bin/Packages/$BuildConfiguration" 8 | 9 | if (!(Test-Path $PackagesRoot)) { return } 10 | 11 | @{ 12 | "$PackagesRoot" = (Get-ChildItem $PackagesRoot -Recurse) 13 | } 14 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/BasedOnLoop.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Report Project.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/FileContents.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | /// 5 | /// Represents the contents of a file with a version of the file that has all lines concatenated and a list of lines. 6 | /// 7 | public record struct FileContents(string FullString, List Lines); 8 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/DuplicateProjectId.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/SingleNativeProject.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tools/artifacts/symbols.ps1: -------------------------------------------------------------------------------- 1 | $BinPath = [System.IO.Path]::GetFullPath("$PSScriptRoot/../../bin") 2 | $3rdPartyPath = [System.IO.Path]::GetFullPath("$PSScriptRoot/../../obj/SymbolsPackages") 3 | if (!(Test-Path $BinPath)) { return } 4 | $symbolfiles = & "$PSScriptRoot/../Get-SymbolFiles.ps1" -Path $BinPath | Get-Unique 5 | $3rdPartyFiles = & "$PSScriptRoot/../Get-3rdPartySymbolFiles.ps1" 6 | 7 | @{ 8 | "$BinPath" = $SymbolFiles; 9 | "$3rdPartyPath" = $3rdPartyFiles; 10 | } 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /tools/variables/InsertPropsValues.ps1: -------------------------------------------------------------------------------- 1 | $InsertedPkgs = (& "$PSScriptRoot\..\artifacts\VSInsertion.ps1") 2 | 3 | $icv=@() 4 | foreach ($kvp in $InsertedPkgs.GetEnumerator()) { 5 | $kvp.Value |% { 6 | if ($_.Name -match "^(.*?)\.(\d+\.\d+\.\d+(?:\.\d+)?(?:-.*?)?)(?:\.symbols)?\.nupkg$") { 7 | $id = $Matches[1] 8 | $version = $Matches[2] 9 | $icv += "$id=$version" 10 | } 11 | } 12 | } 13 | 14 | Write-Output ([string]::join(',',$icv)) 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tools/variables/_all.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | 3 | <# 4 | .SYNOPSIS 5 | This script returns a hashtable of build variables that should be set 6 | at the start of a build or release definition's execution. 7 | #> 8 | 9 | [CmdletBinding(SupportsShouldProcess = $true)] 10 | param ( 11 | ) 12 | 13 | $vars = @{} 14 | 15 | Get-ChildItem "$PSScriptRoot\*.ps1" -Exclude "_*" |% { 16 | Write-Host "Computing $($_.BaseName) variable" 17 | $vars[$_.BaseName] = & $_ 18 | } 19 | 20 | $vars 21 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/ProjectTypeConfig.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/SlnxWhitespace/JustProperties-nocomments.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/BlankSolution.sln.txt: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.10.34714.246 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Global 6 | GlobalSection(SolutionProperties) = preSolution 7 | HideSolutionNode = FALSE 8 | EndGlobalSection 9 | GlobalSection(ExtensibilityGlobals) = postSolution 10 | SolutionGuid = {F0184B06-EDD9-4FB6-8B85-C5B8469CE8A2} 11 | EndGlobalSection 12 | EndGlobal 13 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/SlnxWhitespace/JustProperties-no2no4.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Just a value? 10 | 11 | 12 | -------------------------------------------------------------------------------- /tools/artifacts/testResults.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | ) 4 | 5 | $result = @{} 6 | 7 | $testRoot = Resolve-Path "$PSScriptRoot\..\..\test" 8 | $result[$testRoot] = (Get-ChildItem "$testRoot\TestResults" -Recurse -Directory | Get-ChildItem -Recurse -File) 9 | 10 | $artifactStaging = & "$PSScriptRoot/../Get-ArtifactsStagingDirectory.ps1" 11 | $testlogsPath = Join-Path $artifactStaging "test_logs" 12 | if (Test-Path $testlogsPath) { 13 | $result[$testlogsPath] = Get-ChildItem $testlogsPath -Recurse; 14 | } 15 | 16 | $result 17 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Attach", 9 | "type": "coreclr", 10 | "request": "attach", 11 | "processId": "${command:pickProcess}" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /azure-pipelines/TSAOptions.json: -------------------------------------------------------------------------------- 1 | { 2 | "tsaVersion": "TsaV2", 3 | "codebase": "NewOrUpdate", 4 | "codebaseName": "LibraryName", 5 | "tsaStamp": "DevDiv", 6 | "tsaEnvironment": "PROD", 7 | "notificationAliases": [ 8 | "vsidemicrobuild@microsoft.com" 9 | ], 10 | "codebaseAdmins": [ 11 | "REDMOND\\andarno" 12 | ], 13 | "instanceUrl": "https://devdiv.visualstudio.com", 14 | "projectName": "DevDiv", 15 | "areaPath": "DevDiv\\VS Core", 16 | "iterationPath": "DevDiv", 17 | "alltools": true, 18 | "repositoryName": "Library.Template" 19 | } 20 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | README.md 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tools/Get-ArtifactsStagingDirectory.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [switch]$CleanIfLocal 3 | ) 4 | if ($env:BUILD_ARTIFACTSTAGINGDIRECTORY) { 5 | $ArtifactStagingFolder = $env:BUILD_ARTIFACTSTAGINGDIRECTORY 6 | } elseif ($env:RUNNER_TEMP) { 7 | $ArtifactStagingFolder = Join-Path $env:RUNNER_TEMP _artifacts 8 | } else { 9 | $ArtifactStagingFolder = [System.IO.Path]::GetFullPath("$PSScriptRoot/../obj/_artifacts") 10 | if ($CleanIfLocal -and (Test-Path $ArtifactStagingFolder)) { 11 | Remove-Item $ArtifactStagingFolder -Recurse -Force 12 | } 13 | } 14 | 15 | $ArtifactStagingFolder 16 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Utilities/ComparerExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Utilities; 5 | 6 | internal static class ComparerExtensions 7 | { 8 | internal static bool Equals(this IComparer comparer, T x, T y) 9 | { 10 | return comparer is IEqualityComparer equalityComparer ? equalityComparer.Equals(x, y) : comparer.Compare(x, y) == 0; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/MissingEnd.sln.txt: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreConsole", "src\CoreConsole\CoreConsole.csproj", "{51D540B2-1272-4954-91AA-184BA3D820CB}" 4 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreWebApplication", "src\CoreWebApplication\CoreWebApplication.csproj", "{799413B8-394A-4DF0-A8CB-9F1715603C36}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionProperties) = preSolution 8 | HideSolutionNode = FALSE 9 | EndGlobalSection 10 | EndGlobal 11 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Utilities/Singleton`1.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Utilities; 5 | 6 | // Helper struct that can be used in a pattern to 7 | // create a singleton in a lazy thread safe way. 8 | internal readonly struct Singleton 9 | where T : new() 10 | { 11 | internal static readonly T Instance; 12 | 13 | static Singleton() 14 | { 15 | Instance = new T(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/VSInsertionMetadata/Library.VSInsertionMetadata.proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | true 5 | $(RepoRootPath)bin\Packages\$(Configuration)\VSRepo\ 6 | false 7 | false 8 | Contains metadata for insertion into VS. 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tools/artifacts/APIScanInputs.ps1: -------------------------------------------------------------------------------- 1 | $inputs = & "$PSScriptRoot/symbols.ps1" 2 | 3 | if (!$inputs) { return } 4 | 5 | # Filter out specific files that target OS's that are not subject to APIScan. 6 | # Files that are subject but are not supported must be scanned and an SEL exception filed. 7 | $outputs = @{} 8 | $forbiddenSubPaths = @( 9 | , 'linux-*' 10 | , 'osx*' 11 | ) 12 | 13 | $inputs.GetEnumerator() | % { 14 | $list = $_.Value | ? { 15 | $path = $_.Replace('\', '/') 16 | return !($forbiddenSubPaths | ? { $path -like "*/$_/*" }) 17 | } 18 | $outputs[$_.Key] = $list 19 | } 20 | 21 | 22 | $outputs 23 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/LegacyValues-NoObsolete.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tools/artifacts/LocBin.ps1: -------------------------------------------------------------------------------- 1 | # Identify LCE files and the binary files they describe 2 | $BinRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\bin") 3 | if (!(Test-Path $BinRoot)) { return } 4 | 5 | $FilesToCopy = @() 6 | $FilesToCopy += Get-ChildItem -Recurse -File -Path $BinRoot |? { $_.FullName -match '\\Localize\\' } 7 | 8 | Get-ChildItem -rec "$BinRoot\*.lce" -File | % { 9 | $FilesToCopy += $_ 10 | $FilesToCopy += $_.FullName.SubString(0, $_.FullName.Length - 4) 11 | } 12 | 13 | $FilesToCopy += Get-ChildItem -rec "$BinRoot\*.lcg" -File | % { [xml](Get-Content $_) } | % { $_.lcx.name } 14 | 15 | @{ 16 | "$BinRoot" = $FilesToCopy; 17 | } 18 | -------------------------------------------------------------------------------- /tools/Get-ProcDump.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Downloads 32-bit and 64-bit procdump executables and returns the path to where they were installed. 4 | #> 5 | $version = '0.0.1' 6 | $baseDir = "$PSScriptRoot\..\obj\tools" 7 | $procDumpToolPath = "$baseDir\procdump.$version\bin" 8 | if (-not (Test-Path $procDumpToolPath)) { 9 | if (-not (Test-Path $baseDir)) { New-Item -Type Directory -Path $baseDir | Out-Null } 10 | $baseDir = (Resolve-Path $baseDir).Path # Normalize it 11 | & (& $PSScriptRoot\Get-NuGetTool.ps1) install procdump -version $version -PackageSaveMode nuspec -OutputDirectory $baseDir | Out-Null 12 | } 13 | 14 | (Resolve-Path $procDumpToolPath).Path 15 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlConfigurationBuild.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | using Microsoft.VisualStudio.SolutionPersistence.Model; 6 | 7 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 8 | 9 | internal sealed class XmlConfigurationBuild(SlnxFile root, XmlElement element) : 10 | XmlConfiguration(root, element, Keyword.Build) 11 | { 12 | internal override BuildDimension Dimension => BuildDimension.Build; 13 | } 14 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/DuplicateFolderId.sln.txt: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Apple", "Apple", "{11111111-1111-1111-1111-111111111111}" 4 | EndProject 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mango", "Mango", "{11111111-1111-1111-1111-111111111111}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pear", "Pear", "{11111111-1111-1111-1111-111111111111}" 8 | EndProject 9 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Orange", "Orange", "{11111111-1111-1111-1111-111111111111}" 10 | EndProject 11 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/SlnxWhitespace/JustProperties.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Just a value? 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlConfigurationDeploy.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | using Microsoft.VisualStudio.SolutionPersistence.Model; 6 | 7 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 8 | 9 | internal sealed class XmlConfigurationDeploy(SlnxFile root, XmlElement element) : 10 | XmlConfiguration(root, element, Keyword.Deploy) 11 | { 12 | internal override BuildDimension Dimension => BuildDimension.Deploy; 13 | } 14 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Runtime.CompilerServices; 5 | 6 | [assembly: InternalsVisibleTo("Microsoft.VisualStudio.SolutionPersistence.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] 7 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlConfigurationBuildType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | using Microsoft.VisualStudio.SolutionPersistence.Model; 6 | 7 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 8 | 9 | internal sealed class XmlConfigurationBuildType(SlnxFile root, XmlElement element) : 10 | XmlConfiguration(root, element, Keyword.BuildType) 11 | { 12 | internal override BuildDimension Dimension => BuildDimension.BuildType; 13 | } 14 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlConfigurationPlatform.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | using Microsoft.VisualStudio.SolutionPersistence.Model; 6 | 7 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 8 | 9 | internal sealed class XmlConfigurationPlatform(SlnxFile root, XmlElement element) : 10 | XmlConfiguration(root, element, Keyword.Platform) 11 | { 12 | internal override BuildDimension Dimension => BuildDimension.Platform; 13 | } 14 | -------------------------------------------------------------------------------- /tools/variables/InsertJsonValues.ps1: -------------------------------------------------------------------------------- 1 | $vstsDropNames = & "$PSScriptRoot\VstsDropNames.ps1" 2 | $BuildConfiguration = $env:BUILDCONFIGURATION 3 | if (!$BuildConfiguration) { 4 | $BuildConfiguration = 'Debug' 5 | } 6 | 7 | $BasePath = "$PSScriptRoot\..\..\bin\Packages\$BuildConfiguration\Vsix" 8 | 9 | if (Test-Path $BasePath) { 10 | $vsmanFiles = @() 11 | Get-ChildItem $BasePath *.vsman -Recurse -File |% { 12 | $version = (Get-Content $_.FullName | ConvertFrom-Json).info.buildVersion 13 | $fn = $_.Name 14 | $vsmanFiles += "$fn{$version}=https://vsdrop.corp.microsoft.com/file/v1/$vstsDropNames;$fn" 15 | } 16 | 17 | [string]::join(',',$vsmanFiles) 18 | } 19 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDomUtilities.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; 7 | 8 | internal static class XmlDomUtilities 9 | { 10 | public static XmlElementAttributes Attributes(this XmlElement? element) => new XmlElementAttributes(element?.Attributes); 11 | 12 | public static XmlElementSubElementsEnumerable ChildElements(this XmlNode? element) => new XmlElementSubElementsEnumerable(element, filterByName: null); 13 | } 14 | -------------------------------------------------------------------------------- /tools/variables/InsertVersionsValues.ps1: -------------------------------------------------------------------------------- 1 | # When you need binding redirects in the VS repo updated to match 2 | # assemblies that you build here, remove the "return" statement 3 | # and update the hashtable below with the T4 macro you'll use for 4 | # your libraries as defined in the src\ProductData\AssemblyVersions.tt file. 5 | return 6 | 7 | $MacroName = 'MicrosoftVisualStudioSolutionPersistenceVersion' 8 | $SampleProject = "$PSScriptRoot\..\..\src\Microsoft.VisualStudio.SolutionPersistence" 9 | [string]::join(',',(@{ 10 | ($MacroName) = & { (dotnet nbgv get-version --project $SampleProject --format json | ConvertFrom-Json).AssemblyVersion }; 11 | }.GetEnumerator() |% { "$($_.key)=$($_.value)" })) 12 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "powershell": { 6 | "version": "7.5.3", 7 | "commands": [ 8 | "pwsh" 9 | ], 10 | "rollForward": false 11 | }, 12 | "dotnet-coverage": { 13 | "version": "17.14.2", 14 | "commands": [ 15 | "dotnet-coverage" 16 | ], 17 | "rollForward": false 18 | }, 19 | "nbgv": { 20 | "version": "3.7.115", 21 | "commands": [ 22 | "nbgv" 23 | ], 24 | "rollForward": false 25 | }, 26 | "docfx": { 27 | "version": "2.78.3", 28 | "commands": [ 29 | "docfx" 30 | ], 31 | "rollForward": false 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /init.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | SETLOCAL 3 | set PS1UnderCmd=1 4 | 5 | :: Get the datetime in a format that can go in a filename. 6 | set _my_datetime=%date%_%time% 7 | set _my_datetime=%_my_datetime: =_% 8 | set _my_datetime=%_my_datetime::=% 9 | set _my_datetime=%_my_datetime:/=_% 10 | set _my_datetime=%_my_datetime:.=_% 11 | set CmdEnvScriptPath=%temp%\envvarscript_%_my_datetime%.cmd 12 | 13 | powershell.exe -NoProfile -NoLogo -ExecutionPolicy bypass -Command "try { & '%~dpn0.ps1' %*; exit $LASTEXITCODE } catch { write-host $_; exit 1 }" 14 | 15 | :: Set environment variables in the parent cmd.exe process. 16 | IF EXIST "%CmdEnvScriptPath%" ( 17 | ENDLOCAL 18 | CALL "%CmdEnvScriptPath%" 19 | DEL "%CmdEnvScriptPath%" 20 | ) 21 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlFile.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 7 | 8 | /// 9 | /// Child of a Folder that represents a file in a solution folder. 10 | /// 11 | internal sealed class XmlFile(SlnxFile root, XmlElement element) : 12 | XmlDecorator(root, element, Keyword.File), 13 | IItemRefDecorator 14 | { 15 | public Keyword ItemRefAttribute => Keyword.Path; 16 | 17 | internal string Path => this.ItemRef; 18 | } 19 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlPlatform.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 7 | 8 | /// 9 | /// Child to Configurations that represents a platform (e.g. x86/x64). 10 | /// 11 | internal sealed class XmlPlatform(SlnxFile root, XmlElement element) : 12 | XmlDecorator(root, element, Keyword.Platform), 13 | IItemRefDecorator 14 | { 15 | public Keyword ItemRefAttribute => Keyword.Name; 16 | 17 | internal string Name => this.ItemRef; 18 | } 19 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlBuildType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 7 | 8 | /// 9 | /// Child to Configurations that represents a build type (e.g. Debug/Release). 10 | /// 11 | internal sealed class XmlBuildType(SlnxFile root, XmlElement element) : 12 | XmlDecorator(root, element, Keyword.BuildType), 13 | IItemRefDecorator 14 | { 15 | public Keyword ItemRefAttribute => Keyword.Name; 16 | 17 | internal string Name => this.ItemRef; 18 | } 19 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlBuildDependency.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 7 | 8 | /// 9 | /// Child to a Project that represents a build dependency. 10 | /// 11 | internal sealed class XmlBuildDependency(SlnxFile root, XmlElement element) : 12 | XmlDecorator(root, element, Keyword.BuildDependency), 13 | IItemRefDecorator 14 | { 15 | public Keyword ItemRefAttribute => Keyword.Project; 16 | 17 | internal string Project => this.ItemRef; 18 | } 19 | -------------------------------------------------------------------------------- /azure-pipelines/PoliCheckExclusions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | NODE_MODULES|.STORE 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/SlnxWhitespace/JustProperties-add0add7.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Just a value? 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Utilities/ParseUtilities.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Utilities; 7 | 8 | internal static class ParseUtilities 9 | { 10 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 11 | internal static StringSpan SliceToLast(this StringSpan span, char delimiter) 12 | { 13 | int pos = span.LastIndexOf(delimiter); 14 | return pos < 0 ? StringSpan.Empty : span.Slice(pos); 15 | } 16 | 17 | internal static bool IsWhiteSpace(this char c) => c == '\0' || char.IsWhiteSpace(c); 18 | } 19 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlElementSubElementsEnumerable.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; 7 | 8 | internal readonly ref struct XmlElementSubElementsEnumerable(XmlNode? element, string? filterByName) 9 | { 10 | public readonly XmlElementSubElements GetEnumerator() => new XmlElementSubElements(element, filterByName); 11 | 12 | internal readonly bool Any() 13 | { 14 | foreach (XmlElement any in this) 15 | { 16 | return true; 17 | } 18 | 19 | return false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dev space", 3 | "dockerFile": "Dockerfile", 4 | "customizations": { 5 | "vscode": { 6 | "settings": { 7 | "terminal.integrated.shell.linux": "/usr/bin/pwsh" 8 | }, 9 | "extensions": [ 10 | "ms-azure-devops.azure-pipelines", 11 | "ms-dotnettools.csharp", 12 | "k--kato.docomment", 13 | "editorconfig.editorconfig", 14 | "esbenp.prettier-vscode", 15 | "pflannery.vscode-versionlens", 16 | "davidanson.vscode-markdownlint", 17 | "dotjoshjohnson.xml", 18 | "ms-vscode-remote.remote-containers", 19 | "ms-azuretools.vscode-docker", 20 | "tintoy.msbuild-project-tools" 21 | ] 22 | } 23 | }, 24 | "postCreateCommand": "./init.ps1 -InstallLocality machine" 25 | } 26 | -------------------------------------------------------------------------------- /tools/Get-NuGetTool.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Downloads the NuGet.exe tool and returns the path to it. 4 | .PARAMETER NuGetVersion 5 | The version of the NuGet tool to acquire. 6 | #> 7 | Param( 8 | [Parameter()] 9 | [string]$NuGetVersion='6.14.0' 10 | ) 11 | 12 | $toolsPath = & "$PSScriptRoot\Get-TempToolsPath.ps1" 13 | $binaryToolsPath = Join-Path $toolsPath $NuGetVersion 14 | if (!(Test-Path $binaryToolsPath)) { $null = mkdir $binaryToolsPath } 15 | $nugetPath = Join-Path $binaryToolsPath nuget.exe 16 | 17 | if (!(Test-Path $nugetPath)) { 18 | Write-Host "Downloading nuget.exe $NuGetVersion..." -ForegroundColor Yellow 19 | (New-Object System.Net.WebClient).DownloadFile("https://dist.nuget.org/win-x86-commandline/v$NuGetVersion/NuGet.exe", $nugetPath) 20 | } 21 | 22 | return (Resolve-Path $nugetPath).Path 23 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Configurations/MissingConfigurations.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/InvalidProjectType.sln.txt: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.14.35819.311 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("NotAGuid") = "InvalidProjectType", "InvalidProjectType.csproj", "{8A1FD751-4BAA-467E-A9FA-77239CA44A61}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(SolutionProperties) = preSolution 13 | HideSolutionNode = FALSE 14 | EndGlobalSection 15 | GlobalSection(ExtensibilityGlobals) = postSolution 16 | SolutionGuid = {CFB61351-9264-45B3-BBE0-BAC5D49C87CE} 17 | EndGlobalSection 18 | EndGlobal 19 | -------------------------------------------------------------------------------- /docfx/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "src": "../src/Library", 7 | "files": [ 8 | "**/*.csproj" 9 | ] 10 | } 11 | ], 12 | "dest": "api" 13 | } 14 | ], 15 | "build": { 16 | "content": [ 17 | { 18 | "files": [ 19 | "**/*.{md,yml}" 20 | ], 21 | "exclude": [ 22 | "_site/**" 23 | ] 24 | } 25 | ], 26 | "resource": [ 27 | { 28 | "files": [ 29 | "images/**" 30 | ] 31 | } 32 | ], 33 | "xref": [ 34 | "https://learn.microsoft.com/en-us/dotnet/.xrefmap.json" 35 | ], 36 | "output": "_site", 37 | "template": [ 38 | "default", 39 | "modern" 40 | ], 41 | "globalMetadata": { 42 | "_appName": "Library", 43 | "_appTitle": "Library", 44 | "_enableSearch": true, 45 | "pdf": false 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Directory.Build.rsp: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # This file contains command-line options that MSBuild will process as part of 3 | # every build, unless the "/noautoresponse" switch is specified. 4 | # 5 | # MSBuild processes the options in this file first, before processing the 6 | # options on the command line. As a result, options on the command line can 7 | # override the options in this file. However, depending on the options being 8 | # set, the overriding can also result in conflicts. 9 | # 10 | # NOTE: The "/noautoresponse" switch cannot be specified in this file, nor in 11 | # any response file that is referenced by this file. 12 | #------------------------------------------------------------------------------ 13 | /nr:false 14 | /m 15 | /verbosity:minimal 16 | /clp:Summary;ForceNoAlign 17 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | batch: true 3 | branches: 4 | include: 5 | - main 6 | - 'v*.*' 7 | - microbuild 8 | - 'validate/*' 9 | paths: 10 | exclude: 11 | - doc/ 12 | - '*.md' 13 | - .vscode/ 14 | - .github/ 15 | - azure-pipelines/release.yml 16 | 17 | parameters: 18 | - name: EnableMacOSBuild 19 | displayName: Build on macOS 20 | type: boolean 21 | default: false # macOS is often bogged down in Azure Pipelines 22 | - name: RunTests 23 | displayName: Run tests 24 | type: boolean 25 | default: true 26 | 27 | variables: 28 | - template: /azure-pipelines/BuildStageVariables.yml@self 29 | 30 | jobs: 31 | - template: azure-pipelines/build.yml 32 | parameters: 33 | Is1ESPT: false 34 | EnableMacOSBuild: ${{ parameters.EnableMacOSBuild }} 35 | RunTests: ${{ parameters.RunTests }} 36 | -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "documentationRules": { 5 | "companyName": "Microsoft Corporation", 6 | "copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license. See {licenseFile} file in the project root for full license information.", 7 | "variables": { 8 | "licenseName": "MIT", 9 | "licenseFile": "LICENSE" 10 | }, 11 | "fileNamingConvention": "metadata", 12 | "documentInternalElements": false, 13 | "documentExposedElements": true, 14 | "xmlHeader": false 15 | }, 16 | "orderingRules": { 17 | "usingDirectivesPlacement": "outsideNamespace" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "ms-azure-devops.azure-pipelines", 7 | "ms-dotnettools.csharp", 8 | "k--kato.docomment", 9 | "editorconfig.editorconfig", 10 | "esbenp.prettier-vscode", 11 | "pflannery.vscode-versionlens", 12 | "davidanson.vscode-markdownlint", 13 | "dotjoshjohnson.xml", 14 | "ms-vscode-remote.remote-containers", 15 | "ms-azuretools.vscode-docker", 16 | "tintoy.msbuild-project-tools" 17 | ], 18 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 19 | "unwantedRecommendations": [] 20 | } 21 | -------------------------------------------------------------------------------- /azure-pipelines/Get-InsertionPRId.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Look up the pull request URL of the insertion PR. 4 | #> 5 | $stagingFolder = $env:BUILD_STAGINGDIRECTORY 6 | if (!$stagingFolder) { 7 | $stagingFolder = $env:SYSTEM_DEFAULTWORKINGDIRECTORY 8 | if (!$stagingFolder) { 9 | Write-Error "This script must be run in an Azure Pipeline." 10 | exit 1 11 | } 12 | } 13 | $markdownFolder = Join-Path $stagingFolder (Join-Path 'MicroBuild' 'Output') 14 | $markdownFile = Join-Path $markdownFolder 'PullRequestUrl.md' 15 | if (!(Test-Path $markdownFile)) { 16 | Write-Error "This script should be run after the MicroBuildInsertVsPayload task." 17 | exit 2 18 | } 19 | 20 | $insertionPRUrl = Get-Content $markdownFile 21 | if (!($insertionPRUrl -match 'https:.+?/pullrequest/(\d+)')) { 22 | Write-Error "Failed to parse pull request URL: $insertionPRUrl" 23 | exit 3 24 | } 25 | 26 | $Matches[1] 27 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/PathSlashes.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/PathSlashes.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tools/Get-LibTemplateBasis.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns the name of the well-known branch in the Library.Template repository upon which HEAD is based. 4 | #> 5 | [CmdletBinding(SupportsShouldProcess = $true)] 6 | Param( 7 | [switch]$ErrorIfNotRelated 8 | ) 9 | 10 | # This list should be sorted in order of decreasing specificity. 11 | $branchMarkers = @( 12 | @{ commit = 'fd0a7b25ccf030bbd16880cca6efe009d5b1fffc'; branch = 'microbuild' }; 13 | @{ commit = '05f49ce799c1f9cc696d53eea89699d80f59f833'; branch = 'main' }; 14 | ) 15 | 16 | foreach ($entry in $branchMarkers) { 17 | if (git rev-list HEAD | Select-String -Pattern $entry.commit) { 18 | return $entry.branch 19 | } 20 | } 21 | 22 | if ($ErrorIfNotRelated) { 23 | Write-Error "Library.Template has not been previously merged with this repo. Please review https://github.com/AArnott/Library.Template/tree/main?tab=readme-ov-file#readme for instructions." 24 | exit 1 25 | } 26 | -------------------------------------------------------------------------------- /src/OptProf.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | IBC 5 | Common7\IDE\PrivateAssemblies\$(TargetFileName) 6 | /ExeConfig:"%VisualStudio.InstallationUnderTest.Path%\Common7\IDE\vsn.exe" 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/IItemRefDecorator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 5 | 6 | /// 7 | /// Represents an XmlElement decorator that is used in a collection where each 8 | /// item has a unique ItemRef attribute. 9 | /// 10 | internal interface IItemRefDecorator 11 | { 12 | /// 13 | /// Gets the attribute name that contains the item reference. 14 | /// 15 | /// 16 | /// For some complicated elements, this may be a compound attribute. 17 | /// 18 | Keyword ItemRefAttribute { get; } 19 | 20 | /// 21 | /// Gets the unique identifier for the item. 22 | /// 23 | string ItemRef { get; } 24 | } 25 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/LegacyValues.slnx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tools/publish-CodeCov.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Uploads code coverage to codecov.io 4 | .PARAMETER CodeCovToken 5 | Code coverage token to use 6 | .PARAMETER PathToCodeCoverage 7 | Path to root of code coverage files 8 | .PARAMETER Name 9 | Name to upload with codecoverge 10 | .PARAMETER Flags 11 | Flags to upload with codecoverge 12 | #> 13 | [CmdletBinding()] 14 | Param ( 15 | [Parameter(Mandatory=$true)] 16 | [string]$CodeCovToken, 17 | [Parameter(Mandatory=$true)] 18 | [string]$PathToCodeCoverage, 19 | [string]$Name, 20 | [string]$Flags 21 | ) 22 | 23 | $RepoRoot = (Resolve-Path "$PSScriptRoot/..").Path 24 | 25 | Get-ChildItem -Recurse -LiteralPath $PathToCodeCoverage -Filter "*.cobertura.xml" | % { 26 | $relativeFilePath = Resolve-Path -relative $_.FullName 27 | 28 | Write-Host "Uploading: $relativeFilePath" -ForegroundColor Yellow 29 | & (& "$PSScriptRoot/Get-CodeCovTool.ps1") -t $CodeCovToken -f $relativeFilePath -R $RepoRoot -F $Flags -n $Name 30 | } 31 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # Refer to https://hub.docker.com/_/microsoft-dotnet-sdk for available versions 2 | FROM mcr.microsoft.com/dotnet/sdk:9.0.305-noble@sha256:604ef064c6d91068eeb9d946036d8ffadbe25589c4cd77a230fc96e0f6d01d72 3 | 4 | # Installing mono makes `dotnet test` work without errors even for net472. 5 | # But installing it takes a long time, so it's excluded by default. 6 | #RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF 7 | #RUN echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | tee /etc/apt/sources.list.d/mono-official-stable.list 8 | #RUN apt-get update 9 | #RUN DEBIAN_FRONTEND=noninteractive apt-get install -y mono-devel 10 | 11 | # Clear the NUGET_XMLDOC_MODE env var so xml api doc files get unpacked, allowing a rich experience in Intellisense. 12 | # See https://github.com/dotnet/dotnet-docker/issues/2790 for a discussion on this, where the prioritized use case 13 | # was *not* devcontainers, sadly. 14 | ENV NUGET_XMLDOC_MODE= 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.trimTrailingWhitespace": true, 3 | "files.insertFinalNewline": true, 4 | "files.trimFinalNewlines": true, 5 | "azure-pipelines.1ESPipelineTemplatesSchemaFile": true, 6 | "omnisharp.enableEditorConfigSupport": true, 7 | "omnisharp.enableRoslynAnalyzers": true, 8 | "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true, 9 | "editor.formatOnSave": true, 10 | "[xml]": { 11 | "editor.wordWrap": "off" 12 | }, 13 | // Treat these files as Azure Pipelines files 14 | "files.associations": { 15 | "**/azure-pipelines/**/*.yml": "azure-pipelines", 16 | "azure-pipelines.yml": "azure-pipelines" 17 | }, 18 | // Use Prettier as the default formatter for Azure Pipelines files. 19 | // Needs to be explicitly configured: https://github.com/Microsoft/azure-pipelines-vscode#document-formatting 20 | "[azure-pipelines]": { 21 | "editor.defaultFormatter": "esbenp.prettier-vscode", 22 | "editor.formatOnSave": false // enable this when they conform 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlElementAttributes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; 7 | 8 | /// 9 | /// Provides a way to enumerate over xml attributes. 10 | /// 11 | internal ref struct XmlElementAttributes(XmlAttributeCollection? element) 12 | { 13 | private int index = -1; 14 | 15 | public readonly int Count => element?.Count ?? 0; 16 | 17 | public readonly XmlAttribute Current => element![this.index]; 18 | 19 | public readonly XmlElementAttributes GetEnumerator() => new XmlElementAttributes(element); 20 | 21 | public bool MoveNext() 22 | { 23 | if (element is null || this.index >= element.Count) 24 | { 25 | return false; 26 | } 27 | 28 | return ++this.index < element.Count; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/SlnV12/SlnV12SerializerSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.SlnV12; 5 | 6 | /// 7 | /// Custom settings for the serializer. 8 | /// 9 | /// 10 | /// Initializes a new instance of the struct. 11 | /// Create a new settings with values from the specified settings. 12 | /// 13 | /// The settings to copy. 14 | public readonly struct SlnV12SerializerSettings(SlnV12SerializerSettings settings) 15 | { 16 | /// 17 | /// Gets encoding to use when writing the solution file. 18 | /// Only ASCII, UTF-8, and UTF-16 are supported. 19 | /// 20 | public Encoding? Encoding { get; init; } = settings.Encoding; 21 | } 22 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/FolderId.sln.txt: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Foo", "Foo", "{A340195B-735B-34A1-F5D5-445C9F08470D}" 3 | EndProject 4 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Foo", "Foo", "{F508AC4D-2574-0F7F-5175-5C248303655F}" 5 | EndProject 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Foo", "Foo", "{45DA1D9E-8A3F-8D58-BB91-EFD6A5B29A17}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Foo", "Foo", "{6647119C-3B2A-3090-A1B3-560120CC3977}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionProperties) = preSolution 12 | HideSolutionNode = FALSE 13 | EndGlobalSection 14 | GlobalSection(NestedProjects) = preSolution 15 | {F508AC4D-2574-0F7F-5175-5C248303655F} = {A340195B-735B-34A1-F5D5-445C9F08470D} 16 | {45DA1D9E-8A3F-8D58-BB91-EFD6A5B29A17} = {F508AC4D-2574-0F7F-5175-5C248303655F} 17 | {6647119C-3B2A-3090-A1B3-560120CC3977} = {45DA1D9E-8A3F-8D58-BB91-EFD6A5B29A17} 18 | EndGlobalSection 19 | EndGlobal 20 | -------------------------------------------------------------------------------- /tools/artifacts/coverageResults.ps1: -------------------------------------------------------------------------------- 1 | $RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..") 2 | 3 | $coverageFiles = @(Get-ChildItem "$RepoRoot/test/*.cobertura.xml" -Recurse | Where {$_.FullName -notlike "*/In/*" -and $_.FullName -notlike "*\In\*" }) 4 | 5 | # Prepare code coverage reports for merging on another machine 6 | $repoRoot = $env:SYSTEM_DEFAULTWORKINGDIRECTORY 7 | if (!$repoRoot) { $repoRoot = $env:GITHUB_WORKSPACE } 8 | if ($repoRoot) { 9 | Write-Host "Substituting $repoRoot with `"{reporoot}`"" 10 | $coverageFiles |% { 11 | $content = Get-Content -LiteralPath $_ |% { $_ -Replace [regex]::Escape($repoRoot), "{reporoot}" } 12 | Set-Content -LiteralPath $_ -Value $content -Encoding UTF8 13 | } 14 | } else { 15 | Write-Warning "coverageResults: Cloud build not detected. Machine-neutral token replacement skipped." 16 | } 17 | 18 | if (!((Test-Path $RepoRoot\bin) -and (Test-Path $RepoRoot\obj))) { return } 19 | 20 | @{ 21 | $RepoRoot = ( 22 | $coverageFiles + 23 | (Get-ChildItem "$RepoRoot\obj\*.cs" -Recurse) 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /tools/Check-DotNetSdk.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Checks whether the .NET Core SDK required by this repo is installed. 4 | #> 5 | [CmdletBinding()] 6 | Param ( 7 | ) 8 | 9 | $dotnet = Get-Command dotnet -ErrorAction SilentlyContinue 10 | if (!$dotnet) { 11 | # Nothing is installed. 12 | Write-Output $false 13 | exit 1 14 | } 15 | 16 | # We need to set the current directory so dotnet considers the SDK required by our global.json file. 17 | Push-Location "$PSScriptRoot\.." 18 | try { 19 | dotnet -h 2>&1 | Out-Null 20 | if (($LASTEXITCODE -eq 129) -or # On Linux 21 | ($LASTEXITCODE -eq -2147450751) # On Windows 22 | ) { 23 | # These exit codes indicate no matching SDK exists. 24 | Write-Output $false 25 | exit 2 26 | } 27 | 28 | # The required SDK is already installed! 29 | Write-Output $true 30 | exit 0 31 | } catch { 32 | # I don't know why, but on some build agents (e.g. MicroBuild), an exception is thrown from the `dotnet` invocation when a match is not found. 33 | Write-Output $false 34 | exit 3 35 | } finally { 36 | Pop-Location 37 | } 38 | -------------------------------------------------------------------------------- /azure-pipelines/publish-codecoverage.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: EnableMacOSBuild 3 | type: boolean 4 | - name: EnableLinuxBuild 5 | type: boolean 6 | 7 | steps: 8 | - download: current 9 | artifact: coverageResults-Windows 10 | displayName: 🔻 Download Windows code coverage results 11 | continueOnError: true 12 | - ${{ if parameters.EnableLinuxBuild }}: 13 | - download: current 14 | artifact: coverageResults-Linux 15 | displayName: 🔻 Download Linux code coverage results 16 | continueOnError: true 17 | - ${{ if parameters.EnableMacOSBuild }}: 18 | - download: current 19 | artifact: coverageResults-macOS 20 | displayName: 🔻 Download macOS code coverage results 21 | continueOnError: true 22 | - powershell: azure-pipelines/Merge-CodeCoverage.ps1 -Path '$(Pipeline.Workspace)' -OutputFile coveragereport/merged.cobertura.xml -Format Cobertura -Verbose 23 | displayName: ⚙ Merge coverage 24 | - task: PublishCodeCoverageResults@2 25 | displayName: 📢 Publish code coverage results to Azure DevOps 26 | inputs: 27 | summaryFileLocation: coveragereport/merged.cobertura.xml 28 | failIfCoverageEmpty: true 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Microsoft Corporation 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 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Model/BuildTypeNames.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Model; 5 | 6 | internal static class BuildTypeNames 7 | { 8 | internal const string All = PlatformNames.All; 9 | internal const string Missing = PlatformNames.Missing; 10 | 11 | internal const string Debug = nameof(Debug); 12 | internal const string Release = nameof(Release); 13 | 14 | internal static string ToStringKnown(string buildType) 15 | { 16 | return TryGetKnown(buildType.AsSpan(), out string? value) ? value : buildType; 17 | } 18 | 19 | internal static bool TryGetKnown(StringSpan buildType, [NotNullWhen(true)] out string? value) 20 | { 21 | value = buildType switch 22 | { 23 | All => All, 24 | Missing => Missing, 25 | Debug => Debug, 26 | Release => Release, 27 | _ => null, 28 | }; 29 | return value is not null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SolutionPersistence.slnx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/Utilities/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Utilities; 5 | 6 | internal static class StringExtensions 7 | { 8 | #if NETFRAMEWORK 9 | public static bool StartsWith(this StringSpan span, string str) => span.StartsWith(str.AsSpan(), StringComparison.Ordinal); 10 | 11 | public static bool EndsWith(this StringSpan span, string str) => span.EndsWith(str.AsSpan(), StringComparison.Ordinal); 12 | 13 | public static bool EqualsOrdinal(this StringSpan span, string str) => span.Equals(str.AsSpan(), StringComparison.Ordinal); 14 | #endif 15 | 16 | public static bool StartsWithIgnoreCase(this StringSpan span, string str) => span.StartsWith(str.AsSpan(), StringComparison.OrdinalIgnoreCase); 17 | 18 | public static bool EndsWithIgnoreCase(this StringSpan span, string str) => span.EndsWith(str.AsSpan(), StringComparison.OrdinalIgnoreCase); 19 | 20 | public static bool EqualsOrdinalIgnoreCase(this StringSpan span, string str) => span.Equals(str.AsSpan(), StringComparison.OrdinalIgnoreCase); 21 | } 22 | -------------------------------------------------------------------------------- /azure-pipelines/WIFtoPATauth.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: deadPATServiceConnectionId # The GUID of the PAT-based service connection whose access token must be replaced. 3 | type: string 4 | - name: wifServiceConnectionName # The name of the WIF service connection to use to get the access token. 5 | type: string 6 | - name: resource # The scope for which the access token is requested. 7 | type: string 8 | default: 499b84ac-1321-427f-aa17-267ca6975798 # Azure Artifact feeds (any of them) 9 | 10 | steps: 11 | - task: AzureCLI@2 12 | displayName: 🔏 Authenticate with WIF service connection 13 | inputs: 14 | azureSubscription: ${{ parameters.wifServiceConnectionName }} 15 | scriptType: pscore 16 | scriptLocation: inlineScript 17 | inlineScript: | 18 | $accessToken = az account get-access-token --query accessToken --resource '${{ parameters.resource }}' -o tsv 19 | # Set the access token as a secret, so it doesn't get leaked in the logs 20 | Write-Host "##vso[task.setsecret]$accessToken" 21 | # Override the apitoken of the nuget service connection, for the duration of this stage 22 | Write-Host "##vso[task.setendpoint id=${{ parameters.deadPATServiceConnectionId }};field=authParameter;key=apitoken]$accessToken" 23 | -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | # Copilot instructions for this repository 2 | 3 | ## High level guidance 4 | 5 | * Review the `CONTRIBUTING.md` file for instructions to build and test the software. 6 | * Set the `NBGV_GitEngine` environment variable to `Disabled` before running any `dotnet` or `msbuild` commands. 7 | 8 | ## Software Design 9 | 10 | * Design APIs to be highly testable, and all functionality should be tested. 11 | * Avoid introducing binary breaking changes in public APIs of projects under `src` unless their project files have `IsPackable` set to `false`. 12 | 13 | ## Testing 14 | 15 | * There should generally be one test project (under the `test` directory) per shipping project (under the `src` directory). Test projects are named after the project being tested with a `.Test` suffix. 16 | * Tests should use the Xunit testing framework. 17 | * Some tests are known to be unstable. When running tests, you should skip the unstable ones by running `dotnet test --filter "TestCategory!=FailsInCloudTest"`. 18 | 19 | ## Coding style 20 | 21 | * Honor StyleCop rules and fix any reported build warnings *after* getting tests to pass. 22 | * In C# files, use namespace *statements* instead of namespace *blocks* for all new files. 23 | * Add API doc comments to all new public and internal members. 24 | -------------------------------------------------------------------------------- /tools/Check-DotNetRuntime.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Checks whether a given .NET Core runtime is installed. 4 | #> 5 | [CmdletBinding()] 6 | Param ( 7 | [Parameter()] 8 | [ValidateSet('Microsoft.AspNetCore.App','Microsoft.NETCore.App')] 9 | [string]$Runtime='Microsoft.NETCore.App', 10 | [Parameter(Mandatory=$true)] 11 | [Version]$Version 12 | ) 13 | 14 | $dotnet = Get-Command dotnet -ErrorAction SilentlyContinue 15 | if (!$dotnet) { 16 | # Nothing is installed. 17 | Write-Output $false 18 | exit 1 19 | } 20 | 21 | Function IsVersionMatch { 22 | Param( 23 | [Parameter()] 24 | $actualVersion 25 | ) 26 | return $actualVersion -and 27 | $Version.Major -eq $actualVersion.Major -and 28 | $Version.Minor -eq $actualVersion.Minor -and 29 | (($Version.Build -eq -1) -or ($Version.Build -eq $actualVersion.Build)) -and 30 | (($Version.Revision -eq -1) -or ($Version.Revision -eq $actualVersion.Revision)) 31 | } 32 | 33 | $installedRuntimes = dotnet --list-runtimes |? { $_.Split()[0] -ieq $Runtime } |% { $v = $null; [Version]::tryparse($_.Split()[1], [ref] $v); $v } 34 | $matchingRuntimes = $installedRuntimes |? { IsVersionMatch -actualVersion $_ } 35 | if (!$matchingRuntimes) { 36 | Write-Output $false 37 | exit 1 38 | } 39 | 40 | Write-Output $true 41 | exit 0 42 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/SlnV12/SlnV12ModelExtension.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.VisualStudio.SolutionPersistence.Model; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.SlnV12; 7 | 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | [method: SetsRequiredMembers] 12 | internal sealed class SlnV12ModelExtension(ISolutionSerializer serializer, SlnV12SerializerSettings settings) 13 | : ISerializerModelExtension 14 | { 15 | [SetsRequiredMembers] 16 | public SlnV12ModelExtension(ISolutionSerializer serializer, SlnV12SerializerSettings settings, string? fullPath) 17 | : this(serializer, settings) 18 | { 19 | this.SolutionFileFullPath = fullPath; 20 | } 21 | 22 | /// 23 | public required ISolutionSerializer Serializer { get; init; } = serializer; 24 | 25 | /// 26 | public bool Tarnished { get; init; } 27 | 28 | /// 29 | public SlnV12SerializerSettings Settings { get; } = settings; 30 | 31 | internal string? SolutionFileFullPath { get; init; } 32 | } 33 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlProperty.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 7 | 8 | /// 9 | /// Child of a Properties node that represents a property name/value pair. 10 | /// 11 | internal sealed partial class XmlProperty(SlnxFile root, XmlElement element) : 12 | XmlDecorator(root, element, Keyword.Property), 13 | IItemRefDecorator 14 | { 15 | public Keyword ItemRefAttribute => Keyword.Name; 16 | 17 | internal string Name => this.ItemRef; 18 | 19 | internal string Value 20 | { 21 | get => this.GetXmlAttribute(Keyword.Value) ?? string.Empty; 22 | set => this.UpdateXmlAttribute(Keyword.Value, value); 23 | } 24 | 25 | // Update the Xml DOM with changes from the model. 26 | internal bool ApplyModelToXml(string newValue) 27 | { 28 | // Don't update the value if it is already the same. 29 | if (StringComparer.Ordinal.Equals(this.Value, newValue)) 30 | { 31 | return false; 32 | } 33 | 34 | this.Value = newValue; 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Model/ISerializerModelExtension.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Model; 5 | 6 | /// 7 | /// Allows the serializer to extend the model with properties that are specific to the serializer. 8 | /// 9 | public interface ISerializerModelExtension 10 | { 11 | /// 12 | /// Gets the serializer that is extending the model. 13 | /// 14 | ISolutionSerializer Serializer { get; } 15 | 16 | /// 17 | /// Gets a value indicating whether there were correctable errors in the file 18 | /// that would be fixed by saving the model again. 19 | /// 20 | bool Tarnished { get; } 21 | } 22 | 23 | /// 24 | /// Allows the serializer to extend the model with properties that are specific to the serializer. 25 | /// 26 | /// The settings type for the serializer. 27 | public interface ISerializerModelExtension : ISerializerModelExtension 28 | { 29 | /// 30 | /// Gets the settings that are specific to the serializer. 31 | /// 32 | TSettings Settings { get; } 33 | } 34 | -------------------------------------------------------------------------------- /tools/artifacts/VSInsertion.ps1: -------------------------------------------------------------------------------- 1 | # This artifact captures everything needed to insert into VS (NuGet packages, insertion metadata, etc.) 2 | 3 | [CmdletBinding()] 4 | Param ( 5 | ) 6 | 7 | if ($IsMacOS -or $IsLinux) { 8 | # We only package up for insertions on Windows agents since they are where optprof can happen. 9 | Write-Verbose "Skipping VSInsertion artifact since we're not on Windows." 10 | return @{} 11 | } 12 | 13 | $RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..") 14 | $BuildConfiguration = $env:BUILDCONFIGURATION 15 | if (!$BuildConfiguration) { 16 | $BuildConfiguration = 'Debug' 17 | } 18 | 19 | $PackagesRoot = "$RepoRoot/bin/Packages/$BuildConfiguration" 20 | $NuGetPackages = "$PackagesRoot/NuGet" 21 | $VsixPackages = "$PackagesRoot/Vsix" 22 | 23 | if (!(Test-Path $NuGetPackages) -and !(Test-Path $VsixPackages)) { 24 | Write-Warning "Skipping because NuGet and VSIX packages haven't been built yet." 25 | return @{} 26 | } 27 | 28 | $result = @{ 29 | "$NuGetPackages" = (Get-ChildItem $NuGetPackages -Recurse) 30 | } 31 | 32 | if (Test-Path $VsixPackages) { 33 | $result["$PackagesRoot"] += Get-ChildItem $VsixPackages -Recurse 34 | } 35 | 36 | if ($env:IsOptProf) { 37 | $VSRepoPackages = "$PackagesRoot/VSRepo" 38 | $result["$VSRepoPackages"] = (Get-ChildItem "$VSRepoPackages\*.VSInsertionMetadata.*.nupkg"); 39 | } 40 | 41 | $result 42 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/DuplicateItemId.sln.txt: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.14.35819.311 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DuplicateFolderId", "DuplicateFolderId", "{11111111-1111-1111-1111-111111111111}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DuplicateProjectId", "DuplicateProjectId.csproj", "{11111111-1111-1111-1111-111111111111}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {11111111-1111-1111-1111-111111111111}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {11111111-1111-1111-1111-111111111111}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {11111111-1111-1111-1111-111111111111}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {11111111-1111-1111-1111-111111111111}.Release|Any CPU.Build.0 = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(SolutionProperties) = preSolution 21 | HideSolutionNode = FALSE 22 | EndGlobalSection 23 | GlobalSection(ExtensibilityGlobals) = postSolution 24 | SolutionGuid = {E0F1003A-31EB-42B8-8684-F82D4D7B9108} 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /tools/Prepare-Legacy-Symbols.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [string]$Path 3 | ) 4 | 5 | $ArtifactStagingFolder = & "$PSScriptRoot/Get-ArtifactsStagingDirectory.ps1" 6 | $ArtifactStagingFolder += '/symbols-legacy' 7 | robocopy $Path $ArtifactStagingFolder /mir /njh /njs /ndl /nfl 8 | $WindowsPdbSubDirName = 'symstore' 9 | 10 | Get-ChildItem "$ArtifactStagingFolder\*.pdb" -Recurse |% { 11 | $dllPath = "$($_.Directory)/$($_.BaseName).dll" 12 | $exePath = "$($_.Directory)/$($_.BaseName).exe" 13 | if (Test-Path $dllPath) { 14 | $BinaryImagePath = $dllPath 15 | } elseif (Test-Path $exePath) { 16 | $BinaryImagePath = $exePath 17 | } else { 18 | Write-Warning "`"$_`" found with no matching binary file." 19 | $BinaryImagePath = $null 20 | } 21 | 22 | if ($BinaryImagePath) { 23 | # Convert the PDB to legacy Windows PDBs 24 | Write-Host "Converting PDB for $_" -ForegroundColor DarkGray 25 | $WindowsPdbDir = "$($_.Directory.FullName)\$WindowsPdbSubDirName" 26 | if (!(Test-Path $WindowsPdbDir)) { mkdir $WindowsPdbDir | Out-Null } 27 | $legacyPdbPath = "$WindowsPdbDir\$($_.BaseName).pdb" 28 | & "$PSScriptRoot\Convert-PDB.ps1" -DllPath $BinaryImagePath -PdbPath $_ -OutputPath $legacyPdbPath 29 | if ($LASTEXITCODE -ne 0) { 30 | Write-Warning "PDB conversion of `"$_`" failed." 31 | } 32 | 33 | Move-Item $legacyPdbPath $_ -Force 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/SlnXMLSerializer.Reader.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | using Microsoft.VisualStudio.SolutionPersistence.Model; 6 | using Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 7 | 8 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; 9 | 10 | internal sealed partial class SlnXmlSerializer 11 | { 12 | private sealed partial class Reader 13 | { 14 | private readonly string? fullPath; 15 | private readonly XmlDocument xmlDocument; 16 | 17 | internal Reader(string? fullPath, Stream readerStream) 18 | { 19 | this.fullPath = fullPath; 20 | 21 | // We ideally want to preserver whitespace, but if this is on 22 | // we need to manually handle preserving all indenting and new lines 23 | // when elements are added or removed. 24 | this.xmlDocument = new LineInfoXmlDocument() { PreserveWhitespace = true }; 25 | this.xmlDocument.Load(readerStream); 26 | } 27 | 28 | internal SolutionModel Parse() 29 | { 30 | SlnxFile slnxFile = new SlnxFile(this.xmlDocument, new SlnxSerializerSettings(), null, this.fullPath); 31 | return slnxFile.ToModel(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/Microsoft.VisualStudio.SolutionPersistence.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | $(TargetFrameworks);net472 6 | Exe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlElementSubElements.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; 7 | 8 | /// 9 | /// Provides a way to enumerate over xml child elements. 10 | /// 11 | internal ref struct XmlElementSubElements(XmlNode? element, string? filterByName) 12 | { 13 | private XmlNode? child; 14 | 15 | public readonly XmlElement Current => (object.ReferenceEquals(this.child, element) ? null : this.child as XmlElement)!; 16 | 17 | public bool MoveNext() 18 | { 19 | // use element as "sentinel end value", null as before first. (if element is null it is also an end as coincidence). 20 | if (object.ReferenceEquals(this.child, element) || element is null) 21 | { 22 | return false; 23 | } 24 | 25 | do 26 | { 27 | this.child = this.child is null ? element.FirstChild : this.child.NextSibling; 28 | if (this.child is XmlElement) 29 | { 30 | if (filterByName is null || this.child.Name == filterByName) 31 | { 32 | return true; 33 | } 34 | } 35 | } 36 | while (this.child is not null); 37 | 38 | this.child = element; 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/SlnXmlModelExtension.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.VisualStudio.SolutionPersistence.Model; 5 | using Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 6 | 7 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; 8 | 9 | /// 10 | /// Initializes a new instance of the class. 11 | /// 12 | [method: SetsRequiredMembers] 13 | internal sealed class SlnXmlModelExtension(ISolutionSerializer serializer, SlnxSerializerSettings settings) 14 | : ISerializerModelExtension 15 | { 16 | [SetsRequiredMembers] 17 | internal SlnXmlModelExtension(ISolutionSerializer serializer, SlnxSerializerSettings settings, SlnxFile root) 18 | : this(serializer, settings) 19 | { 20 | this.Root = root; 21 | } 22 | 23 | /// 24 | public required ISolutionSerializer Serializer { get; init; } = serializer; 25 | 26 | /// 27 | public required SlnxSerializerSettings Settings { get; init; } = settings; 28 | 29 | /// 30 | public bool Tarnished => this.Root?.Tarnished ?? false; 31 | 32 | internal SlnxFile? Root { get; init; } 33 | 34 | internal string? SolutionFileFullPath => this.Root?.FullPath; 35 | 36 | internal Version? Version => this.Root?.FileVersion; 37 | } 38 | -------------------------------------------------------------------------------- /azure-pipelines/microbuild.after.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: EnableOptProf 3 | type: boolean 4 | default: false 5 | - name: IsOptProf 6 | type: boolean 7 | default: false 8 | - name: SkipCodesignVerify 9 | type: boolean 10 | 11 | steps: 12 | - ${{ if not(parameters.SkipCodesignVerify) }}: 13 | - task: MicroBuildCodesignVerify@3 14 | displayName: 🔍 Verify Signed Files 15 | inputs: 16 | ApprovalListPathForSigs: $(Build.SourcesDirectory)\azure-pipelines\no_strongname.txt 17 | ApprovalListPathForCerts: $(Build.SourcesDirectory)\azure-pipelines\no_authenticode.txt 18 | TargetFolders: | 19 | $(Build.SourcesDirectory)/bin/Packages/$(BuildConfiguration) 20 | condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) 21 | 22 | - ${{ if parameters.IsOptProf }}: 23 | - task: ms-vscs-artifact.build-tasks.artifactDropTask-1.artifactDropTask@0 24 | inputs: 25 | dropServiceURI: https://devdiv.artifacts.visualstudio.com 26 | buildNumber: $(ProfilingInputsDropName) 27 | sourcePath: $(Build.ArtifactStagingDirectory)\OptProf\ProfilingInputs 28 | toLowerCase: false 29 | usePat: true 30 | displayName: 📢 Publish to Artifact Services - ProfilingInputs 31 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) 32 | 33 | - task: PublishBuildArtifacts@1 34 | inputs: 35 | PathtoPublish: $(Build.ArtifactStagingDirectory)/InsertionOutputs 36 | ArtifactName: InsertionOutputs 37 | ArtifactType: Container 38 | displayName: 📢 Publish InsertionOutputs as Azure DevOps artifacts 39 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Report Project.sln.txt: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.14.35821.254 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F14B399A-7131-4C87-9E4B-1186C45EF12D}") = "Report Project", "Report Project.rptproj", "{5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Default = Debug|Default 11 | DebugLocal|Default = DebugLocal|Default 12 | Release|Default = Release|Default 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.Debug|Default.ActiveCfg = Debug 16 | {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.Debug|Default.Build.0 = Debug 17 | {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.Debug|Default.Deploy.0 = Debug 18 | {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.DebugLocal|Default.ActiveCfg = DebugLocal 19 | {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.DebugLocal|Default.Build.0 = DebugLocal 20 | {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.Release|Default.ActiveCfg = Release 21 | {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.Release|Default.Build.0 = Release 22 | {5F864A61-8A8B-9ED2-A92E-9ABB172CC8D7}.Release|Default.Deploy.0 = Release 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {AB744758-6EDA-4264-84B6-7396FCA3E003} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /azure-pipelines/install-dependencies.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: initArgs 3 | type: string 4 | default: '' 5 | - name: needsAzurePublicFeeds 6 | type: boolean 7 | default: true # If nuget.config pulls from the azure-public account, we need to authenticate when building on the devdiv account. 8 | 9 | steps: 10 | - ${{ if and(parameters.needsAzurePublicFeeds, eq(variables['system.collectionId'], '011b8bdf-6d56-4f87-be0d-0092136884d9')) }}: 11 | - template: WIFtoPATauth.yml 12 | parameters: 13 | wifServiceConnectionName: azure-public/vside package pull 14 | deadPATServiceConnectionId: 0ae39abc-4d06-4436-a7b5-865833df49db # azure-public/msft_consumption 15 | 16 | - task: NuGetAuthenticate@1 17 | displayName: 🔏 Authenticate NuGet feeds 18 | inputs: 19 | ${{ if and(parameters.needsAzurePublicFeeds, eq(variables['system.collectionId'], '011b8bdf-6d56-4f87-be0d-0092136884d9')) }}: 20 | nuGetServiceConnections: azure-public/msft_consumption 21 | 22 | - powershell: | 23 | $AccessToken = '$(System.AccessToken)' # Avoid specifying the access token directly on the init.ps1 command line to avoid it showing up in errors 24 | .\init.ps1 -AccessToken $AccessToken ${{ parameters['initArgs'] }} -UpgradePrerequisites -NoNuGetCredProvider 25 | dotnet --info 26 | 27 | # Print mono version if it is present. 28 | if (Get-Command mono -ErrorAction SilentlyContinue) { 29 | mono --version 30 | } 31 | displayName: ⚙ Install prerequisites 32 | 33 | - powershell: tools/variables/_define.ps1 34 | failOnStderr: true 35 | displayName: ⚙ Set pipeline variables based on source 36 | name: SetPipelineVariables 37 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Model/ProjectConfigMapping.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Model; 5 | 6 | /// 7 | /// Represents the project configuration and build/deploy settings. 8 | /// This is used to create an expanded mapping of every solution configuration to every project configuration. 9 | /// 10 | [method: SetsRequiredMembers] 11 | internal readonly struct ProjectConfigMapping(string buildType, string platform, bool build, bool deploy) 12 | { 13 | internal required string BuildType { get; init; } = buildType; 14 | 15 | internal required string Platform { get; init; } = platform; 16 | 17 | internal bool Build { get; init; } = build; 18 | 19 | internal bool Deploy { get; init; } = deploy; 20 | 21 | internal readonly bool IsValidBuildType => !string.IsNullOrEmpty(this.BuildType) && this.BuildType != BuildTypeNames.All; 22 | 23 | internal readonly bool IsValidPlatform => !string.IsNullOrEmpty(this.Platform) && this.Platform != PlatformNames.All; 24 | 25 | internal readonly bool IsSame(in ProjectConfigMapping other) 26 | { 27 | return other.Build == this.Build && 28 | other.Deploy == this.Deploy && 29 | StringComparer.Ordinal.Equals(this.BuildType, other.BuildType) && 30 | (this.Platform == other.Platform || StringComparer.Ordinal.Equals(PlatformNames.Canonical(this.Platform), PlatformNames.Canonical(other.Platform))); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/ProjectTypes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; 5 | 6 | namespace Serialization; 7 | 8 | /// 9 | /// Tests related to project types in the model. 10 | /// 11 | public sealed class ProjectTypes 12 | { 13 | /// 14 | /// Keeps the checked in built-in project types test in sync with the code version. 15 | /// 16 | [Fact] 17 | public async Task BuiltInProjectTypes() 18 | { 19 | // Turn the build-in project types into a solution model. 20 | SolutionModel builtInTypesModel = new SolutionModel 21 | { 22 | ProjectTypes = ProjectTypeTable.BuiltInTypes.ProjectTypes, 23 | SerializerExtension = SolutionSerializers.SlnXml.CreateModelExtension( 24 | new SlnxSerializerSettings() 25 | { 26 | // Use 4 spaces for checked in built-in slnx. 27 | IndentChars = " ", 28 | }), 29 | }; 30 | 31 | FileContents builtInFromCodeLines = await builtInTypesModel.ToLinesAsync(SolutionSerializers.SlnXml); 32 | 33 | AssertSolutionsAreEqual(builtInFromCodeLines, SlnAssets.XmlBuiltInProjectTypes.ToLines()); 34 | } 35 | 36 | /// 37 | /// Attempt to set project types to invalid values. 38 | /// 39 | [Fact] 40 | public void InvalidTypes() 41 | { 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tools/variables/_define.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script translates the variables returned by the _all.ps1 script 4 | into commands that instruct Azure Pipelines to actually set those variables for other pipeline tasks to consume. 5 | 6 | The build or release definition may have set these variables to override 7 | what the build would do. So only set them if they have not already been set. 8 | #> 9 | 10 | [CmdletBinding()] 11 | param ( 12 | ) 13 | 14 | (& "$PSScriptRoot\_all.ps1").GetEnumerator() |% { 15 | # Always use ALL CAPS for env var names since Azure Pipelines converts variable names to all caps and on non-Windows OS, env vars are case sensitive. 16 | $keyCaps = $_.Key.ToUpper() 17 | if ((Test-Path "env:$keyCaps") -and (Get-Content "env:$keyCaps")) { 18 | Write-Host "Skipping setting $keyCaps because variable is already set to '$(Get-Content env:$keyCaps)'." -ForegroundColor Cyan 19 | } else { 20 | Write-Host "$keyCaps=$($_.Value)" -ForegroundColor Yellow 21 | if ($env:TF_BUILD) { 22 | # Create two variables: the first that can be used by its simple name and accessible only within this job. 23 | Write-Host "##vso[task.setvariable variable=$keyCaps]$($_.Value)" 24 | # and the second that works across jobs and stages but must be fully qualified when referenced. 25 | Write-Host "##vso[task.setvariable variable=$keyCaps;isOutput=true]$($_.Value)" 26 | } elseif ($env:GITHUB_ACTIONS) { 27 | Add-Content -LiteralPath $env:GITHUB_ENV -Value "$keyCaps=$($_.Value)" 28 | } 29 | Set-Item -LiteralPath "env:$keyCaps" -Value $_.Value 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # TODO: The maintainer of this repo has not yet edited this file 2 | 3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? 4 | 5 | - **No CSS support:** Review the template below and edit as necessary. 6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). 7 | - **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide. 8 | 9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.* 10 | 11 | # Support 12 | 13 | ## How to file issues and get help 14 | 15 | This project uses GitHub Issues to track bugs and feature requests. 16 | Please search the existing issues before filing new issues to avoid duplicates. 17 | For new issues, file your bug or feature request as a new Issue. 18 | 19 | Note that this repo is primarily used for Visual Studio and related products and support will be focused on those scenarios. 20 | 21 | ## Microsoft Support Policy 22 | 23 | Microsoft support for this software is available only for its use in officially supported products such as Visual Studio. 24 | Support and servicing is limited to the latest released version. 25 | For more information, see [Visual Studio Product Lifecycle and Servicing](https://learn.microsoft.com/visualstudio/productinfo/vs-servicing). 26 | Assisted support is available from a professional support engineer by opening a ticket with the [Microsoft assisted support team](https://support.serviceshub.microsoft.com/supportforbusiness/onboarding). 27 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/Utilities/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Utilities; 5 | 6 | internal static class CollectionExtensions 7 | { 8 | /// 9 | /// Creates an array from a with a selector to transform the items. 10 | /// 11 | /// The item type of the input collection. 12 | /// The item type of the new array. 13 | /// The input collection. 14 | /// A way to convert TSource to TResult. 15 | /// An array of the new items. 16 | public static TResult[] ToArray(this IReadOnlyCollection collection, Func selector) 17 | { 18 | Argument.ThrowIfNull(collection, nameof(collection)); 19 | 20 | TResult[] array = new TResult[collection.Count]; 21 | int index = 0; 22 | foreach (TSource item in collection) 23 | { 24 | array[index] = selector(item); 25 | index++; 26 | } 27 | 28 | return array; 29 | } 30 | 31 | public static int IndexOf(this IReadOnlyList list, T item) 32 | { 33 | for (int i = 0; i < list.Count; ++i) 34 | { 35 | if (EqualityComparer.Default.Equals(list[i], item)) 36 | { 37 | return i; 38 | } 39 | } 40 | 41 | return -1; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Utilities/ListStructEnumerable`1.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence; 5 | 6 | /// 7 | /// Creates a enumerable struct wrapper around a list that might be null. 8 | /// 9 | internal readonly ref struct ListStructEnumerable(List? list) 10 | { 11 | private static readonly List EmptyList = []; 12 | 13 | internal int Count => list?.Count ?? 0; 14 | 15 | internal List.Enumerator GetEnumerator() => (list ?? EmptyList).GetEnumerator(); 16 | } 17 | 18 | internal readonly ref struct ReadOnlyListStructEnumerable(IReadOnlyList? list) 19 | { 20 | public ReadOnlyListStructEnumerator GetEnumerator() => new ReadOnlyListStructEnumerator(list); 21 | } 22 | 23 | internal ref struct ReadOnlyListStructEnumerator(IReadOnlyList? list) 24 | { 25 | private int index = -1; 26 | 27 | public readonly T Current => list![this.index]; 28 | 29 | public bool MoveNext() => ++this.index < (list?.Count ?? 0); 30 | } 31 | 32 | internal readonly ref struct ReadOnlyListStructReverseEnumerable(IReadOnlyList? list) 33 | { 34 | public ReadOnlyListStructReverseEnumerator GetEnumerator() => new ReadOnlyListStructReverseEnumerator(list); 35 | } 36 | 37 | internal ref struct ReadOnlyListStructReverseEnumerator(IReadOnlyList? list) 38 | { 39 | private int index = list?.Count ?? 0; 40 | 41 | public readonly T Current => list![this.index]; 42 | 43 | public bool MoveNext() 44 | { 45 | this.index--; 46 | return this.index >= 0; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/copilot-setup-steps.yml: -------------------------------------------------------------------------------- 1 | name: 💪🏼 Copilot Setup Steps 2 | 3 | # Automatically run the setup steps when they are changed to allow for easy validation, and 4 | # allow manual testing through the repository's "Actions" tab 5 | on: 6 | workflow_dispatch: 7 | push: 8 | branches: 9 | - main 10 | paths: 11 | - .github/workflows/copilot-setup-steps.yml 12 | pull_request: 13 | paths: 14 | - .github/workflows/copilot-setup-steps.yml 15 | 16 | jobs: 17 | # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. 18 | copilot-setup-steps: 19 | runs-on: ubuntu-latest 20 | # Set the permissions to the lowest permissions possible needed for your steps. 21 | # Copilot will be given its own token for its operations. 22 | permissions: 23 | # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. 24 | contents: read 25 | 26 | # You can define any steps you want, and they will run before the agent starts. 27 | # If you do not check out your code, Copilot will do this for you. 28 | steps: 29 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 30 | with: 31 | fetch-depth: 0 # avoid shallow clone so nbgv can do its work. 32 | - name: ⚙ Install prerequisites 33 | run: | 34 | ./init.ps1 -UpgradePrerequisites -NoNuGetCredProvider 35 | dotnet --info 36 | 37 | # Print mono version if it is present. 38 | if (Get-Command mono -ErrorAction SilentlyContinue) { 39 | mono --version 40 | } 41 | shell: pwsh 42 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/DuplicateProjectId-After.sln.txt: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.14.35819.311 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DuplicateProjectId", "DuplicateProjectId.csproj", "{8BADBEEF-1111-2222-3333-444444444444}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DuplicateProjectIdOpposite", "DuplicateProjectIdOpposite.csproj", "{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {8BADBEEF-1111-2222-3333-444444444444}.Debug|Any CPU.ActiveCfg = Release|Any CPU 16 | {8BADBEEF-1111-2222-3333-444444444444}.Debug|Any CPU.Build.0 = Release|Any CPU 17 | {8BADBEEF-1111-2222-3333-444444444444}.Release|Any CPU.ActiveCfg = Debug|Any CPU 18 | {8BADBEEF-1111-2222-3333-444444444444}.Release|Any CPU.Build.0 = Debug|Any CPU 19 | {FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}.Debug|Any CPU.ActiveCfg = Release|Any CPU 20 | {FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}.Debug|Any CPU.Build.0 = Release|Any CPU 21 | {FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}.Release|Any CPU.ActiveCfg = Debug|Any CPU 22 | {FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}.Release|Any CPU.Build.0 = Debug|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {E0F1003A-31EB-42B8-8684-F82D4D7B9108} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Invalid/DuplicateProjectId.sln.txt: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.14.35819.311 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DuplicateProjectId", "DuplicateProjectId.csproj", "{8BADBEEF-1111-2222-3333-444444444444}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DuplicateProjectIdOpposite", "DuplicateProjectIdOpposite.csproj", "{8BADBEEF-1111-2222-3333-444444444444}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {8BADBEEF-1111-2222-3333-444444444444}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {8BADBEEF-1111-2222-3333-444444444444}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {8BADBEEF-1111-2222-3333-444444444444}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {8BADBEEF-1111-2222-3333-444444444444}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {8BADBEEF-1111-2222-3333-444444444444}.Debug|Any CPU.ActiveCfg = Release|Any CPU 20 | {8BADBEEF-1111-2222-3333-444444444444}.Debug|Any CPU.Build.0 = Release|Any CPU 21 | {8BADBEEF-1111-2222-3333-444444444444}.Release|Any CPU.ActiveCfg = Debug|Any CPU 22 | {8BADBEEF-1111-2222-3333-444444444444}.Release|Any CPU.Build.0 = Debug|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {E0F1003A-31EB-42B8-8684-F82D4D7B9108} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/ProjectTypeConfig.sln.txt: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 17.0.31903.59 6 | Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Project6", "Project6.wapproj", "{CA5CAD1A-224A-4171-B13A-F16E576FDD12}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Win32 = Release|Win32 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|Win32.ActiveCfg = Debug|x86 19 | {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|Win32.Build.0 = Debug|x86 20 | {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|Win32.Deploy.0 = Debug|x86 21 | {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|x64.ActiveCfg = Debug|x64 22 | {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|x64.Build.0 = Debug|x64 23 | {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|x64.Deploy.0 = Debug|x64 24 | {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|x86.ActiveCfg = Debug|x86 25 | {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|x86.Build.0 = Debug|x86 26 | {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|x86.Deploy.0 = Debug|x86 27 | {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|Win32.ActiveCfg = Release|x86 28 | {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|x64.ActiveCfg = Release|x64 29 | {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|x86.ActiveCfg = Release|x86 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Microsoft.VisualStudio.SolutionPersistence.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net472;net8.0 4 | 5 | 6 | 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | True 34 | True 35 | Errors.resx 36 | 37 | 38 | 39 | 40 | 41 | ResXFileCodeGenerator 42 | Errors.Designer.cs 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/Format.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Serialization; 5 | 6 | /// 7 | /// Tests related to formatting the solution file. 8 | /// 9 | public class Format 10 | { 11 | /// 12 | /// Test that SLN converts to UTF-8 automatically from ASCII. 13 | /// 14 | [Fact] 15 | public async Task ConvertASCIItoUTF8Async() 16 | { 17 | const string asciiFolderName = "directory123"; 18 | const string utf8FolderName = "répertoire123"; 19 | 20 | SolutionModel solution = new SolutionModel(); 21 | _ = solution.AddFolder($"/{asciiFolderName}/"); 22 | 23 | solution.SerializerExtension = SolutionSerializers.SlnFileV12.CreateModelExtension(); 24 | 25 | int codePageASCII = Encoding.ASCII.CodePage; 26 | int codePageUTF8 = Encoding.UTF8.CodePage; 27 | 28 | (SolutionModel asciiModel, FileContents asciiLines) = await SlnTestHelper.SaveAndReopenModelAsync(SolutionSerializers.SlnFileV12, solution, 1024 * 100); 29 | Assert.Equal(codePageASCII, GetSlnEncoding(asciiModel).CodePage); 30 | Assert.Contains(asciiFolderName, asciiLines.FullString); 31 | 32 | // Add a folder that requires encoding. 33 | _ = solution.AddFolder($"/{utf8FolderName}/"); 34 | 35 | (SolutionModel utf8Model, FileContents utf8Lines) = await SlnTestHelper.SaveAndReopenModelAsync(SolutionSerializers.SlnFileV12, solution, 1024 * 100); 36 | Assert.Equal(codePageUTF8, GetSlnEncoding(utf8Model).CodePage); 37 | Assert.Contains(asciiFolderName, utf8Lines.FullString); 38 | Assert.Contains(utf8FolderName, utf8Lines.FullString); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/SolutionSerializers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.VisualStudio.SolutionPersistence.Serializer.SlnV12; 5 | using Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; 6 | 7 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer; 8 | 9 | /// 10 | /// Solution serializers implemented by this package. 11 | /// 12 | public static class SolutionSerializers 13 | { 14 | /// 15 | /// Gets the .sln V12 solution serializer. 16 | /// 17 | public static ISolutionSingleFileSerializer SlnFileV12 => SlnFileV12Serializer.Instance; 18 | 19 | /// 20 | /// Gets the .slnx XML solution serializer. 21 | /// 22 | public static ISolutionSingleFileSerializer SlnXml => SlnXmlSerializer.Instance; 23 | 24 | /// 25 | /// Gets all the solution serializers implemented by this package. 26 | /// 27 | public static IReadOnlyCollection Serializers => [SlnFileV12, SlnXml]; 28 | 29 | /// 30 | /// Finds a serializer that supports opening the given solution moniker. 31 | /// 32 | /// A moniker to a solution location. 33 | /// A serializer that supports the solution moniker. 34 | public static ISolutionSerializer? GetSerializerByMoniker(string moniker) 35 | { 36 | foreach (ISolutionSerializer serializer in Serializers) 37 | { 38 | if (serializer.IsSupported(moniker)) 39 | { 40 | return serializer; 41 | } 42 | } 43 | 44 | return null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /azure-pipelines/publish_artifacts.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script translates all the artifacts described by _all.ps1 4 | into commands that instruct Azure Pipelines to actually collect those artifacts. 5 | #> 6 | 7 | [CmdletBinding()] 8 | param ( 9 | [string]$ArtifactNameSuffix, 10 | [switch]$StageOnly, 11 | [switch]$AvoidSymbolicLinks 12 | ) 13 | 14 | Function Set-PipelineVariable($name, $value) { 15 | if ((Test-Path "Env:\$name") -and (Get-Item "Env:\$name").Value -eq $value) { 16 | return # already set 17 | } 18 | 19 | #New-Item -LiteralPath "Env:\$name".ToUpper() -Value $value -Force | Out-Null 20 | Write-Host "##vso[task.setvariable variable=$name]$value" 21 | } 22 | 23 | Function Test-ArtifactUploaded($artifactName) { 24 | $varName = "ARTIFACTUPLOADED_$($artifactName.ToUpper())" 25 | Test-Path "env:$varName" 26 | } 27 | 28 | & "$PSScriptRoot/../tools/artifacts/_stage_all.ps1" -ArtifactNameSuffix $ArtifactNameSuffix -AvoidSymbolicLinks:$AvoidSymbolicLinks |% { 29 | # Set a variable which will out-live this script so that a subsequent attempt to collect and upload artifacts 30 | # will skip this one from a check in the _all.ps1 script. 31 | Set-PipelineVariable "ARTIFACTSTAGED_$($_.Name.ToUpper())" 'true' 32 | Write-Host "Staged artifact $($_.Name) to $($_.Path)" 33 | 34 | if (!$StageOnly) { 35 | if (Test-ArtifactUploaded $_.Name) { 36 | Write-Host "Skipping $($_.Name) because it has already been uploaded." -ForegroundColor DarkGray 37 | } else { 38 | Write-Host "##vso[artifact.upload containerfolder=$($_.Name);artifactname=$($_.Name);]$($_.Path)" 39 | 40 | # Set a variable which will out-live this script so that a subsequent attempt to collect and upload artifacts 41 | # will skip this one from a check in the _all.ps1 script. 42 | Set-PipelineVariable "ARTIFACTUPLOADED_$($_.Name.ToUpper())" 'true' 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # SA1600: Elements should be documented 4 | # This is test code, but we want to ensure each test explains its purpose. 5 | dotnet_diagnostic.SA1600.severity = warning 6 | 7 | # SA1601: Partial elements should be documented 8 | dotnet_diagnostic.SA1601.severity = silent 9 | 10 | # SA1602: Enumeration items should be documented 11 | dotnet_diagnostic.SA1602.severity = silent 12 | 13 | # SA1615: Element return value should be documented 14 | dotnet_diagnostic.SA1615.severity = silent 15 | 16 | # VSTHRD103: Call async methods when in an async method 17 | dotnet_diagnostic.VSTHRD103.severity = silent 18 | 19 | # VSTHRD111: Use .ConfigureAwait(bool) 20 | dotnet_diagnostic.VSTHRD111.severity = none 21 | 22 | # VSTHRD200: Use Async suffix for async methods 23 | dotnet_diagnostic.VSTHRD200.severity = silent 24 | 25 | # CA1014: Mark assemblies with CLSCompliant 26 | dotnet_diagnostic.CA1014.severity = none 27 | 28 | # CA1050: Declare types in namespaces 29 | dotnet_diagnostic.CA1050.severity = none 30 | 31 | # CA1303: Do not pass literals as localized parameters 32 | dotnet_diagnostic.CA1303.severity = none 33 | 34 | # CS1591: Missing XML comment for publicly visible type or member 35 | dotnet_diagnostic.CS1591.severity = silent 36 | 37 | # CA1707: Identifiers should not contain underscores 38 | dotnet_diagnostic.CA1707.severity = silent 39 | 40 | # CA1062: Validate arguments of public methods 41 | dotnet_diagnostic.CA1062.severity = suggestion 42 | 43 | # CA1063: Implement IDisposable Correctly 44 | dotnet_diagnostic.CA1063.severity = silent 45 | 46 | # CA1816: Dispose methods should call SuppressFinalize 47 | dotnet_diagnostic.CA1816.severity = silent 48 | 49 | # CA2007: Consider calling ConfigureAwait on the awaited task 50 | dotnet_diagnostic.CA2007.severity = none 51 | 52 | # SA1401: Fields should be private 53 | dotnet_diagnostic.SA1401.severity = silent 54 | 55 | # SA1133: Do not combine attributes 56 | dotnet_diagnostic.SA1133.severity = silent 57 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/SlnxSerializerSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; 5 | 6 | /// 7 | /// Allows customization of the behavior of the serializer. 8 | /// 9 | /// 10 | /// Initializes a new instance of the struct. 11 | /// Create a new settings with values from the specified settings. 12 | /// 13 | /// The settings to copy. 14 | public readonly struct SlnxSerializerSettings(SlnxSerializerSettings settings) 15 | { 16 | /// 17 | /// Gets a value indicating whether to keep whitespace when writing the solution file. 18 | /// If this is , the solution file will be written with the same whitespace as the original file. 19 | /// Default is . 20 | /// 21 | public bool? PreserveWhitespace { get; init; } = settings.PreserveWhitespace; 22 | 23 | /// 24 | /// Gets the characters to use for indentation when writing the solution file. 25 | /// Default is two spaces. 26 | /// 27 | public string? IndentChars { get; init; } = settings.IndentChars; 28 | 29 | /// 30 | /// Gets the characters to use for new lines when writing the solution file. 31 | /// Default is the system's new line characters. 32 | /// 33 | public string? NewLine { get; init; } = settings.NewLine; 34 | 35 | /// 36 | /// Gets a value indicating whether to remove unneccessary Visual Studio properties from the solution file. 37 | /// 38 | public bool? TrimVisualStudioProperties { get; init; } = settings.TrimVisualStudioProperties; 39 | } 40 | -------------------------------------------------------------------------------- /tools/artifacts/Variables.ps1: -------------------------------------------------------------------------------- 1 | # This artifact captures all variables defined in the ..\variables folder. 2 | # It "snaps" the values of these variables where we can compute them during the build, 3 | # and otherwise captures the scripts to run later during an Azure Pipelines environment release. 4 | 5 | $RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot/../..") 6 | $ArtifactBasePath = "$RepoRoot/obj/_artifacts" 7 | $VariablesArtifactPath = Join-Path $ArtifactBasePath variables 8 | if (-not (Test-Path $VariablesArtifactPath)) { New-Item -ItemType Directory -Path $VariablesArtifactPath | Out-Null } 9 | 10 | # Copy variables, either by value if the value is calculable now, or by script 11 | Get-ChildItem "$PSScriptRoot/../variables" |% { 12 | $value = $null 13 | if (-not $_.BaseName.StartsWith('_')) { # Skip trying to interpret special scripts 14 | # First check the environment variables in case the variable was set in a queued build 15 | # Always use all caps for env var access because Azure Pipelines converts variables to upper-case for env vars, 16 | # and on non-Windows env vars are case sensitive. 17 | $envVarName = $_.BaseName.ToUpper() 18 | if (Test-Path env:$envVarName) { 19 | $value = Get-Content "env:$envVarName" 20 | } 21 | 22 | # If that didn't give us anything, try executing the script right now from its original position 23 | if (-not $value) { 24 | $value = & $_.FullName 25 | } 26 | 27 | if ($value) { 28 | # We got something, so wrap it with quotes so it's treated like a literal value. 29 | $value = "'$value'" 30 | } 31 | } 32 | 33 | # If that didn't get us anything, just copy the script itself 34 | if (-not $value) { 35 | $value = Get-Content -LiteralPath $_.FullName 36 | } 37 | 38 | Set-Content -LiteralPath "$VariablesArtifactPath/$($_.Name)" -Value $value 39 | } 40 | 41 | @{ 42 | "$VariablesArtifactPath" = (Get-ChildItem $VariablesArtifactPath -Recurse); 43 | } 44 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/RequiredNetFramework.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if NETFRAMEWORK 5 | 6 | namespace System.Diagnostics.CodeAnalysis 7 | { 8 | [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Combine .NET Framework adapters")] 9 | [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] 10 | internal sealed class SetsRequiredMembersAttribute : Attribute 11 | { 12 | } 13 | } 14 | 15 | #pragma warning disable SA1403 // File may only contain a single namespace 16 | namespace System.Runtime.CompilerServices 17 | #pragma warning restore SA1403 // File may only contain a single namespace 18 | { 19 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] 20 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Combine .NET Framework adapters")] 21 | internal sealed class RequiredMemberAttribute : Attribute 22 | { 23 | } 24 | 25 | [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] 26 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Combine .NET Framework adapters")] 27 | internal sealed class CompilerFeatureRequiredAttribute : Attribute 28 | { 29 | public const string RefStructs = nameof(RefStructs); 30 | public const string RequiredMembers = nameof(RequiredMembers); 31 | 32 | public CompilerFeatureRequiredAttribute(string featureName) 33 | { 34 | this.FeatureName = featureName; 35 | } 36 | 37 | public string FeatureName { get; } 38 | 39 | public bool IsOptional { get; init; } 40 | } 41 | } 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/Configurations/MissingConfigurations.sln.txt: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.12.35128.42 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Good", "Good\Good.vcxproj", "{B0C8586B-AD06-484D-A425-78449EEB0AFA}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Missing", "Missing\Missing.vcxproj", "{9A02DE5C-4113-46DE-BD88-90946A757B54}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Partial", "Partial\Partial.vcxproj", "{D53DB013-AEE1-48D8-874F-509000819E30}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|x64 = Debug|x64 15 | Debug|x86 = Debug|x86 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {B0C8586B-AD06-484D-A425-78449EEB0AFA}.Debug|x64.ActiveCfg = Debug|x64 21 | {B0C8586B-AD06-484D-A425-78449EEB0AFA}.Debug|x64.Build.0 = Debug|x64 22 | {B0C8586B-AD06-484D-A425-78449EEB0AFA}.Debug|x86.ActiveCfg = Debug|Win32 23 | {B0C8586B-AD06-484D-A425-78449EEB0AFA}.Debug|x86.Build.0 = Debug|Win32 24 | {B0C8586B-AD06-484D-A425-78449EEB0AFA}.Release|x64.ActiveCfg = Release|x64 25 | {B0C8586B-AD06-484D-A425-78449EEB0AFA}.Release|x64.Build.0 = Release|x64 26 | {B0C8586B-AD06-484D-A425-78449EEB0AFA}.Release|x86.ActiveCfg = Release|Win32 27 | {B0C8586B-AD06-484D-A425-78449EEB0AFA}.Release|x86.Build.0 = Release|Win32 28 | {D53DB013-AEE1-48D8-874F-509000819E30}.Debug|x86.Build.0 = ? 29 | {D53DB013-AEE1-48D8-874F-509000819E30}.Release|x64.ActiveCfg = Release|x64 30 | {D53DB013-AEE1-48D8-874F-509000819E30}.Release|x64.Build.0 = Release|x64 31 | EndGlobalSection 32 | GlobalSection(SolutionProperties) = preSolution 33 | HideSolutionNode = FALSE 34 | EndGlobalSection 35 | GlobalSection(ExtensibilityGlobals) = postSolution 36 | SolutionGuid = {E9CA6CCF-2D18-44D5-BA66-7F71932797E7} 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /azure-pipelines/microbuild.before.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: EnableLocalization 3 | type: boolean 4 | default: false 5 | - name: EnableOptProf 6 | type: boolean 7 | default: false 8 | - name: IsOptProf 9 | type: boolean 10 | default: false 11 | - name: ShouldSkipOptimize 12 | type: boolean 13 | default: false 14 | - name: RealSign 15 | type: boolean 16 | 17 | steps: 18 | - ${{ if and(not(parameters.IsOptProf), ne(variables['Build.Reason'], 'PullRequest')) }}: 19 | # notice@0 requires CG detection to run first, and non-default branches don't inject it automatically. 20 | # default branch injection (main) is happening too late for notice@0 to run successfully. Adding this as a workaround. 21 | - task: ComponentGovernanceComponentDetection@0 22 | displayName: 🔍 Component Detection 23 | 24 | - task: notice@0 25 | displayName: 🛠️ Generate NOTICE file 26 | inputs: 27 | outputfile: $(System.DefaultWorkingDirectory)/obj/NOTICE 28 | outputformat: text 29 | retryCountOnTaskFailure: 3 # fails when the cloud service is overloaded 30 | continueOnError: ${{ not(parameters.RealSign) }} # Tolerate failures when we're not building something that may ship. 31 | 32 | - ${{ if parameters.IsOptProf }}: 33 | # We have to install these plugins ourselves for Optprof runs because those pipelines haven't migrated to 1ES PT yet. 34 | - task: MicroBuildOptProfPlugin@6 35 | inputs: 36 | ProfilingInputsDropName: $(ProfilingInputsDropName) 37 | OptimizationInputsLookupMethod: DropPrefix 38 | DropNamePrefix: OptimizationInputs/$(System.TeamProject)/$(Build.Repository.Name) 39 | ShouldSkipOptimize: ${{ parameters.ShouldSkipOptimize }} 40 | AccessToken: $(System.AccessToken) 41 | displayName: 🔧 Install OptProf Plugin 42 | 43 | - task: MicroBuildSigningPlugin@4 44 | inputs: 45 | signType: Test 46 | zipSources: false 47 | displayName: 🔧 Install MicroBuild Signing Plugin 48 | 49 | - ${{ if parameters.EnableLocalization }}: 50 | - task: MicroBuildLocalizationPlugin@4 51 | inputs: 52 | languages: $(LocLanguages) 53 | displayName: 🔧 Install MicroBuild Localization Plugin 54 | -------------------------------------------------------------------------------- /azure-pipelines/PostPRMessage.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding(SupportsShouldProcess = $true)] 2 | param( 3 | [Parameter(Mandatory=$true)] 4 | $AccessToken, 5 | [Parameter(Mandatory=$true)] 6 | $Markdown, 7 | [ValidateSet('Active','ByDesign','Closed','Fixed','Pending','Unknown','WontFix')] 8 | $CommentState='Active' 9 | ) 10 | 11 | # See https://learn.microsoft.com/dotnet/api/microsoft.teamfoundation.sourcecontrol.webapi.commentthreadstatus 12 | if ($CommentState -eq 'Active') { 13 | $StatusCode = 1 14 | } elseif ($CommentState -eq 'ByDesign') { 15 | $StatusCode = 5 16 | } elseif ($CommentState -eq 'Closed') { 17 | $StatusCode = 4 18 | } elseif ($CommentState -eq 'Fixed') { 19 | $StatusCode = 2 20 | } elseif ($CommentState -eq 'Pending') { 21 | $StatusCode = 6 22 | } elseif ($CommentState -eq 'Unknown') { 23 | $StatusCode = 0 24 | } elseif ($CommentState -eq 'WontFix') { 25 | $StatusCode = 3 26 | } 27 | 28 | # Build the JSON body up 29 | $body = ConvertTo-Json @{ 30 | comments = @(@{ 31 | parentCommentId = 0 32 | content = $Markdown 33 | commentType = 1 34 | }) 35 | status = $StatusCode 36 | } 37 | 38 | Write-Verbose "Posting JSON payload: `n$Body" 39 | 40 | # Post the message to the Pull Request 41 | # https://learn.microsoft.com/rest/api/azure/devops/git/pull-request-threads 42 | $url = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/git/repositories/$($env:BUILD_REPOSITORY_NAME)/pullRequests/$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)/threads?api-version=5.1" 43 | if ($PSCmdlet.ShouldProcess($url, 'Post comment via REST call')) { 44 | try { 45 | if (!$env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI) { 46 | Write-Error "Posting to the pull request requires that the script is running in an Azure Pipelines context." 47 | exit 1 48 | } 49 | Write-Host "Posting PR comment to: $url" 50 | Invoke-RestMethod -Uri $url -Method POST -Headers @{Authorization = "Bearer $AccessToken"} -Body $Body -ContentType application/json 51 | } 52 | catch { 53 | Write-Error $_ 54 | Write-Error $_.Exception.Message 55 | exit 2 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/SlnXMLSerializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.VisualStudio.SolutionPersistence.Model; 5 | using Microsoft.VisualStudio.SolutionPersistence.Utilities; 6 | 7 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; 8 | 9 | internal sealed partial class SlnXmlSerializer : SingleFileSerializerBase 10 | { 11 | private const string Extension = ".slnx"; 12 | 13 | private const string SerializerName = "Slnx"; 14 | 15 | [Obsolete("Use Instance")] 16 | public SlnXmlSerializer() 17 | { 18 | } 19 | 20 | /// 21 | public override string Name => SerializerName; 22 | 23 | internal static SlnXmlSerializer Instance => Singleton.Instance; 24 | 25 | private protected override string FileExtension => Extension; 26 | 27 | /// 28 | public override ISerializerModelExtension CreateModelExtension() 29 | { 30 | return this.CreateModelExtension(new SlnxSerializerSettings() 31 | { 32 | // For new documents want to do standard indentation. 33 | PreserveWhitespace = false, 34 | IndentChars = " ", 35 | NewLine = Environment.NewLine, 36 | }); 37 | } 38 | 39 | /// 40 | public override ISerializerModelExtension CreateModelExtension(SlnxSerializerSettings settings) 41 | { 42 | return new SlnXmlModelExtension(this, settings); 43 | } 44 | 45 | private protected override Task ReadModelAsync(string? fullPath, Stream reader, CancellationToken cancellationToken) 46 | { 47 | Reader parser = new Reader(fullPath, reader); 48 | return Task.FromResult(parser.Parse()); 49 | } 50 | 51 | private protected override Task WriteModelAsync(string? fullPath, SolutionModel model, Stream writerStream, CancellationToken cancellationToken) 52 | { 53 | return Writer.SaveAsync(fullPath, model, writerStream); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlProject.ApplyModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.VisualStudio.SolutionPersistence.Model; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 7 | 8 | internal sealed partial class XmlProject 9 | { 10 | // Update the Xml DOM with changes from the model. 11 | internal bool ApplyModelToXml(SolutionProjectModel modelProject) 12 | { 13 | bool modified = false; 14 | 15 | // Attributes 16 | string type = this.Root.ProjectTypes.GetConciseType(modelProject); 17 | if (!StringComparer.Ordinal.Equals(this.Type, type)) 18 | { 19 | this.Type = type.NullIfEmpty(); 20 | modified = true; 21 | } 22 | 23 | string? displayName = 24 | modelProject.DisplayName is null || StringExtensions.EqualsOrdinal(this.DefaultDisplayName, modelProject.ActualDisplayName) ? 25 | null : 26 | modelProject.DisplayName; 27 | if (!StringComparer.Ordinal.Equals(this.DisplayName, displayName)) 28 | { 29 | this.DisplayName = displayName; 30 | modified = true; 31 | } 32 | 33 | Guid id = modelProject.IsDefaultId ? Guid.Empty : modelProject.Id; 34 | if (this.Id != id) 35 | { 36 | this.Id = id; 37 | modified = true; 38 | } 39 | 40 | // BuildDependencies 41 | modified |= this.ApplyModelItemsToXml( 42 | itemRefs: modelProject.Dependencies?.ToList(dependencyProject => this.Root.ConvertToUserPath(dependencyProject.FilePath)), 43 | decoratorItems: ref this.buildDependencies, 44 | decoratorElementName: Keyword.BuildDependency); 45 | 46 | // Configurations 47 | modified |= this.configurationRules.ApplyModelToXml(this, modelProject.ProjectConfigurationRules); 48 | 49 | // Properties 50 | modified |= this.ApplyModelToXml(modelProject.Properties); 51 | 52 | return modified; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/LocalUsings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if NETFRAMEWORK 5 | global using Microsoft.IO; 6 | 7 | // Listing the types in System.IO that aren't in Microsoft.IO is a 8 | // little tedious, but this should prevent any need to include the entire 9 | // namespace which might lead to accidental usage of old types. 10 | global using BinaryReader = System.IO.BinaryReader; 11 | global using BinaryWriter = System.IO.BinaryWriter; 12 | global using DirectoryNotFoundException = System.IO.DirectoryNotFoundException; 13 | global using DriveInfo = System.IO.DriveInfo; 14 | global using DriveType = System.IO.DriveType; 15 | global using FileAccess = System.IO.FileAccess; 16 | global using FileAttributes = System.IO.FileAttributes; 17 | global using FileLoadException = System.IO.FileLoadException; 18 | global using FileMode = System.IO.FileMode; 19 | global using FileNotFoundException = System.IO.FileNotFoundException; 20 | global using FileShare = System.IO.FileShare; 21 | global using FileStream = System.IO.FileStream; 22 | global using InvalidDataException = System.IO.InvalidDataException; 23 | global using IOException = System.IO.IOException; 24 | global using MemoryStream = System.IO.MemoryStream; 25 | global using PathTooLongException = System.IO.PathTooLongException; 26 | global using SeekOrigin = System.IO.SeekOrigin; 27 | global using Stream = System.IO.Stream; 28 | global using StreamReader = System.IO.StreamReader; 29 | global using StreamWriter = System.IO.StreamWriter; 30 | global using StringReader = System.IO.StringReader; 31 | global using StringWriter = System.IO.StringWriter; 32 | global using TextReader = System.IO.TextReader; 33 | global using TextWriter = System.IO.TextWriter; 34 | #else 35 | global using System.IO; 36 | #endif 37 | 38 | global using System; 39 | global using System.Buffers; 40 | global using System.Collections.Generic; 41 | global using System.Diagnostics.CodeAnalysis; 42 | global using System.Text; 43 | global using System.Threading; 44 | global using System.Threading.Tasks; 45 | global using StringSpan = System.ReadOnlySpan; 46 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/SlnAssets/SingleNativeProject.sln.txt: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.11.34808.264 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SingleNativeProject", "SingleNativeProject.vcxproj", "{5A3C1479-A21D-4D3A-A993-36A1F8A1307F}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|ARM64 = Debug|ARM64 10 | Debug|Mobile = Debug|Mobile 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|ARM64 = Release|ARM64 14 | Release|Mobile = Release|Mobile 15 | Release|x64 = Release|x64 16 | Release|x86 = Release|x86 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Debug|ARM64.ActiveCfg = Debug|ARM64 20 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Debug|Mobile.ActiveCfg = Debug|ARM64 21 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Debug|Mobile.Build.0 = Debug|ARM64 22 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Debug|x64.ActiveCfg = Debug|x64 23 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Debug|x64.Build.0 = Debug|x64 24 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Debug|x86.ActiveCfg = Debug|Win32 25 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Debug|x86.Build.0 = Debug|Win32 26 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Release|ARM64.ActiveCfg = Release|ARM64 27 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Release|Mobile.ActiveCfg = Release|ARM64 28 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Release|Mobile.Build.0 = Release|ARM64 29 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Release|x64.ActiveCfg = Release|x64 30 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Release|x64.Build.0 = Release|x64 31 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Release|x86.ActiveCfg = Release|Win32 32 | {5A3C1479-A21D-4D3A-A993-36A1F8A1307F}.Release|x86.Build.0 = Release|Win32 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | GlobalSection(ExtensibilityGlobals) = postSolution 38 | SolutionGuid = {86223AD5-3968-4A6D-B4F8-4F26045B4357} 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Utilities/DefaultIdGenerator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Security.Cryptography; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Utilities; 7 | 8 | /// 9 | /// Helper for turning unique string identifiers into guid identifiers. 10 | /// 11 | internal static class DefaultIdGenerator 12 | { 13 | internal static Guid CreateIdFrom(string uniqueName) 14 | { 15 | byte[] bytes = StringToBytes(uniqueName); 16 | return MakeId(bytes, null); 17 | } 18 | 19 | internal static Guid CreateIdFrom(Guid parentItemId, string name) 20 | { 21 | if (string.IsNullOrEmpty(name)) 22 | { 23 | return Guid.Empty; 24 | } 25 | 26 | byte[] parentData = parentItemId.ToByteArray(); 27 | byte[] itemData = StringToBytes(name); 28 | return MakeId(parentData, itemData); 29 | } 30 | 31 | private static byte[] StringToBytes(string text) 32 | { 33 | return Encoding.UTF8.GetBytes(text.Replace('\\', '/').ToUpperInvariant()); 34 | } 35 | 36 | private static Guid MakeId(byte[] data1, byte[]? data2) 37 | { 38 | if (data1.IsNullOrEmpty() && data2.IsNullOrEmpty()) 39 | { 40 | return Guid.Empty; 41 | } 42 | 43 | #if NETFRAMEWORK 44 | byte[] allData = data2 is null ? data1 : [.. data1, .. data2]; 45 | 46 | using (SHA256 hash = SHA256.Create()) 47 | { 48 | byte[] hashBytes = hash.ComputeHash(allData); 49 | byte[] guidBytes = new byte[16]; 50 | Array.Copy(hashBytes, guidBytes, 16); 51 | return new Guid(guidBytes); 52 | } 53 | #else 54 | ReadOnlySpan allData = data2 is null ? data1 : [.. data1, .. data2]; 55 | 56 | Span hashBytes = stackalloc byte[32]; 57 | if (!SHA256.TryHashData(allData, hashBytes, out int bytesWritten) || bytesWritten <= 16) 58 | { 59 | throw new InvalidOperationException(); 60 | } 61 | 62 | return new Guid(hashBytes.Slice(0, 16)); 63 | #endif 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tools/Convert-PDB.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Converts between Windows PDB and Portable PDB formats. 4 | .PARAMETER DllPath 5 | The path to the DLL whose PDB is to be converted. 6 | .PARAMETER PdbPath 7 | The path to the PDB to convert. May be omitted if the DLL was compiled on this machine and the PDB is still at its original path. 8 | .PARAMETER OutputPath 9 | The path of the output PDB to write. 10 | #> 11 | [CmdletBinding()] 12 | Param( 13 | [Parameter(Mandatory=$true,Position=0)] 14 | [string]$DllPath, 15 | [Parameter()] 16 | [string]$PdbPath, 17 | [Parameter(Mandatory=$true,Position=1)] 18 | [string]$OutputPath 19 | ) 20 | 21 | if ($IsMacOS -or $IsLinux) { 22 | Write-Error "This script only works on Windows" 23 | return 24 | } 25 | 26 | $version = '1.1.0-beta2-21101-01' 27 | $baseDir = "$PSScriptRoot/../obj/tools" 28 | $pdb2pdbpath = "$baseDir/Microsoft.DiaSymReader.Pdb2Pdb.$version/tools/Pdb2Pdb.exe" 29 | if (-not (Test-Path $pdb2pdbpath)) { 30 | if (-not (Test-Path $baseDir)) { New-Item -Type Directory -Path $baseDir | Out-Null } 31 | $baseDir = (Resolve-Path $baseDir).Path # Normalize it 32 | Write-Verbose "& (& $PSScriptRoot/Get-NuGetTool.ps1) install Microsoft.DiaSymReader.Pdb2Pdb -version $version -PackageSaveMode nuspec -OutputDirectory $baseDir -Source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json | Out-Null" 33 | # This package originally comes from the https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json feed. 34 | # Add this feed as an upstream to whatever feed is in nuget.config if this step fails. 35 | & (& $PSScriptRoot/Get-NuGetTool.ps1) install Microsoft.DiaSymReader.Pdb2Pdb -version $version -PackageSaveMode nuspec -OutputDirectory $baseDir | Out-Null 36 | if ($LASTEXITCODE -ne 0) { 37 | Write-Error "Failed to install Microsoft.DiaSymReader.Pdb2Pdb. Consider adding https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json as an upstream to your nuget.config feed." 38 | return 39 | } 40 | } 41 | 42 | $args = $DllPath,'/out',$OutputPath,'/nowarn','0021' 43 | if ($PdbPath) { 44 | $args += '/pdb',$PdbPath 45 | } 46 | 47 | Write-Verbose "$pdb2pdbpath $args" 48 | & $pdb2pdbpath $args 49 | -------------------------------------------------------------------------------- /tools/Get-SymbolFiles.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Collect the list of PDBs built in this repo. 4 | .PARAMETER Path 5 | The directory to recursively search for PDBs. 6 | .PARAMETER Tests 7 | A switch indicating to find PDBs only for test binaries instead of only for shipping shipping binaries. 8 | #> 9 | [CmdletBinding()] 10 | param ( 11 | [parameter(Mandatory=$true)] 12 | [string]$Path, 13 | [switch]$Tests 14 | ) 15 | 16 | $ActivityName = "Collecting symbols from $Path" 17 | Write-Progress -Activity $ActivityName -CurrentOperation "Discovery PDB files" 18 | $PDBs = Get-ChildItem -rec "$Path/*.pdb" 19 | 20 | # Filter PDBs to product OR test related. 21 | $testregex = "unittest|tests|\.test\." 22 | 23 | Write-Progress -Activity $ActivityName -CurrentOperation "De-duplicating symbols" 24 | $PDBsByHash = @{} 25 | $i = 0 26 | $PDBs |% { 27 | Write-Progress -Activity $ActivityName -CurrentOperation "De-duplicating symbols" -PercentComplete (100 * $i / $PDBs.Length) 28 | $hash = Get-FileHash $_ 29 | $i++ 30 | Add-Member -InputObject $_ -MemberType NoteProperty -Name Hash -Value $hash.Hash 31 | Write-Output $_ 32 | } | Sort-Object CreationTime |% { 33 | # De-dupe based on hash. Prefer the first match so we take the first built copy. 34 | if (-not $PDBsByHash.ContainsKey($_.Hash)) { 35 | $PDBsByHash.Add($_.Hash, $_.FullName) 36 | Write-Output $_ 37 | } 38 | } |? { 39 | if ($Tests) { 40 | $_.FullName -match $testregex 41 | } else { 42 | $_.FullName -notmatch $testregex 43 | } 44 | } |% { 45 | # Collect the DLLs/EXEs as well. 46 | $rootName = Join-Path $_.Directory $_.BaseName 47 | if ($rootName.EndsWith('.ni')) { 48 | $rootName = $rootName.Substring(0, $rootName.Length - 3) 49 | } 50 | 51 | $dllPath = "$rootName.dll" 52 | $exePath = "$rootName.exe" 53 | if (Test-Path $dllPath) { 54 | $BinaryImagePath = $dllPath 55 | } elseif (Test-Path $exePath) { 56 | $BinaryImagePath = $exePath 57 | } else { 58 | Write-Warning "`"$_`" found with no matching binary file." 59 | $BinaryImagePath = $null 60 | } 61 | 62 | if ($BinaryImagePath) { 63 | Write-Output $BinaryImagePath 64 | Write-Output $_.FullName 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlContainerWithProperties.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | using Microsoft.VisualStudio.SolutionPersistence.Model; 6 | 7 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 8 | 9 | /// 10 | /// Represents a decorator that wraps an that is a container element with properties. 11 | /// 12 | internal abstract partial class XmlContainerWithProperties(SlnxFile root, XmlElement element, Keyword elementName) : 13 | XmlContainer(root, element, elementName) 14 | { 15 | #pragma warning disable SA1401 // Fields should be private 16 | private protected ItemRefList propertyBags = new ItemRefList(); 17 | #pragma warning restore SA1401 // Fields should be private 18 | 19 | /// 20 | internal override XmlDecorator? ChildDecoratorFactory(XmlElement element, Keyword elementName) 21 | { 22 | return elementName switch 23 | { 24 | Keyword.Properties => new XmlProperties(this.Root, element), 25 | _ => base.ChildDecoratorFactory(element, elementName), 26 | }; 27 | } 28 | 29 | /// 30 | internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) 31 | { 32 | switch (childDecorator) 33 | { 34 | case XmlProperties properties: 35 | this.propertyBags.Add(properties); 36 | break; 37 | } 38 | 39 | base.OnNewChildDecoratorAdded(childDecorator); 40 | } 41 | 42 | // Update the Xml DOM with changes from the model. 43 | internal bool ApplyModelToXml(IReadOnlyList? modelPropertyBags) 44 | { 45 | return this.ApplyModelItemsToXml( 46 | modelItems: modelPropertyBags?.ToList(propertyBag => (ItemRef: propertyBag.Id, Item: propertyBag)), 47 | decoratorItems: ref this.propertyBags, 48 | decoratorElementName: Keyword.Properties, 49 | applyModelToXml: static (newProperties, newValue) => newProperties.ApplyModelToXml(newValue)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /azure-pipelines/publish-symbols.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: EnableMacOSBuild 3 | type: boolean 4 | - name: EnableLinuxBuild 5 | type: boolean 6 | 7 | steps: 8 | - task: DownloadPipelineArtifact@2 9 | inputs: 10 | artifact: symbols-Windows 11 | path: $(Pipeline.Workspace)/symbols/Windows 12 | displayName: 🔻 Download Windows symbols 13 | continueOnError: true 14 | - ${{ if parameters.EnableLinuxBuild }}: 15 | - task: DownloadPipelineArtifact@2 16 | inputs: 17 | artifact: symbols-Linux 18 | path: $(Pipeline.Workspace)/symbols/Linux 19 | displayName: 🔻 Download Linux symbols 20 | continueOnError: true 21 | - ${{ if parameters.EnableMacOSBuild }}: 22 | - task: DownloadPipelineArtifact@2 23 | inputs: 24 | artifact: symbols-macOS 25 | path: $(Pipeline.Workspace)/symbols/macOS 26 | displayName: 🔻 Download macOS symbols 27 | continueOnError: true 28 | 29 | - task: DownloadPipelineArtifact@2 30 | inputs: 31 | artifact: test_symbols-Windows 32 | path: $(Pipeline.Workspace)/test_symbols/Windows 33 | displayName: 🔻 Download Windows test symbols 34 | continueOnError: true 35 | - ${{ if parameters.EnableLinuxBuild }}: 36 | - task: DownloadPipelineArtifact@2 37 | inputs: 38 | artifact: test_symbols-Linux 39 | path: $(Pipeline.Workspace)/test_symbols/Linux 40 | displayName: 🔻 Download Linux test symbols 41 | continueOnError: true 42 | - ${{ if parameters.EnableMacOSBuild }}: 43 | - task: DownloadPipelineArtifact@2 44 | inputs: 45 | artifact: test_symbols-macOS 46 | path: $(Pipeline.Workspace)/test_symbols/macOS 47 | displayName: 🔻 Download macOS test symbols 48 | continueOnError: true 49 | 50 | - task: PublishSymbols@2 51 | inputs: 52 | SymbolsFolder: $(Pipeline.Workspace)/symbols 53 | SearchPattern: '**/*.pdb' 54 | IndexSources: false 55 | SymbolServerType: TeamServices 56 | displayName: 📢 Publish symbols 57 | 58 | - task: PublishSymbols@2 59 | inputs: 60 | SymbolsFolder: $(Pipeline.Workspace)/test_symbols 61 | SearchPattern: '**/*.pdb' 62 | IndexSources: false 63 | SymbolServerType: TeamServices 64 | displayName: 📢 Publish test symbols 65 | 66 | - powershell: tools/Prepare-Legacy-Symbols.ps1 -Path $(Pipeline.Workspace)/symbols/Windows 67 | displayName: ⚙ Prepare symbols for symbol archival 68 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | true 7 | 8 | 2.0.201 9 | 10 | 11 | 12 | 13 | 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 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Utilities/Argument.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence; 5 | 6 | internal static class Argument 7 | { 8 | #if NETFRAMEWORK 9 | 10 | /// Throws an if is null. 11 | /// The reference type argument to validate as non-null. 12 | /// The name of the parameter with which corresponds. 13 | internal static void ThrowIfNull([NotNull] object? argument, string? paramName) 14 | { 15 | if (argument is null) 16 | { 17 | Throw(paramName); 18 | } 19 | } 20 | 21 | /// Throws an if is null or empty. 22 | /// The reference type argument to validate as non-null or empty. 23 | /// The name of the parameter with which corresponds. 24 | internal static void ThrowIfNullOrEmpty([NotNull] string? argument, string? paramName) 25 | { 26 | if (argument.IsNullOrEmpty()) 27 | { 28 | Throw(paramName); 29 | } 30 | } 31 | 32 | [DoesNotReturn] 33 | internal static void Throw(string? paramName) => throw new ArgumentNullException(paramName); 34 | 35 | #else 36 | 37 | /// 38 | internal static void ThrowIfNull([NotNull] object? argument, [System.Runtime.CompilerServices.CallerArgumentExpression(nameof(argument))] string? paramName = null) 39 | { 40 | ArgumentNullException.ThrowIfNull(argument, paramName); 41 | } 42 | 43 | /// 44 | internal static void ThrowIfNullOrEmpty([NotNull] string? argument, [System.Runtime.CompilerServices.CallerArgumentExpression(nameof(argument))] string? paramName = null) 45 | { 46 | ArgumentException.ThrowIfNullOrEmpty(argument, paramName); 47 | } 48 | 49 | #endif 50 | } 51 | -------------------------------------------------------------------------------- /azure-pipelines/Merge-CodeCoverage.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | 3 | <# 4 | .SYNOPSIS 5 | Merges code coverage reports. 6 | .PARAMETER Path 7 | The path(s) to search for Cobertura code coverage reports. 8 | .PARAMETER Format 9 | The format for the merged result. The default is Cobertura 10 | .PARAMETER OutputDir 11 | The directory the merged result will be written to. The default is `coveragereport` in the root of this repo. 12 | #> 13 | [CmdletBinding()] 14 | Param( 15 | [Parameter(Mandatory=$true)] 16 | [string[]]$Path, 17 | [ValidateSet('Badges', 'Clover', 'Cobertura', 'CsvSummary', 'Html', 'Html_Dark', 'Html_Light', 'HtmlChart', 'HtmlInline', 'HtmlInline_AzurePipelines', 'HtmlInline_AzurePipelines_Dark', 'HtmlInline_AzurePipelines_Light', 'HtmlSummary', 'JsonSummary', 'Latex', 'LatexSummary', 'lcov', 'MarkdownSummary', 'MHtml', 'PngChart', 'SonarQube', 'TeamCitySummary', 'TextSummary', 'Xml', 'XmlSummary')] 18 | [string]$Format='Cobertura', 19 | [string]$OutputFile=("$PSScriptRoot/../coveragereport/merged.cobertura.xml") 20 | ) 21 | 22 | $RepoRoot = [string](Resolve-Path $PSScriptRoot/..) 23 | Push-Location $RepoRoot 24 | try { 25 | Write-Verbose "Searching $Path for *.cobertura.xml files" 26 | $reports = Get-ChildItem -Recurse $Path -Filter *.cobertura.xml 27 | 28 | if ($reports) { 29 | $reports |% { $_.FullName } |% { 30 | # In addition to replacing {reporoot}, we also normalize on one kind of slash so that the report aggregates data for a file whether data was collected on Windows or not. 31 | $xml = [xml](Get-Content -LiteralPath $_) 32 | $xml.coverage.packages.package.classes.class |? { $_.filename} |% { 33 | $_.filename = $_.filename.Replace('{reporoot}', $RepoRoot).Replace([IO.Path]::AltDirectorySeparatorChar, [IO.Path]::DirectorySeparatorChar) 34 | } 35 | 36 | $xml.Save($_) 37 | } 38 | 39 | $Inputs = $reports |% { Resolve-Path -relative $_.FullName } 40 | 41 | if ((Split-Path $OutputFile) -and -not (Test-Path (Split-Path $OutputFile))) { 42 | New-Item -Type Directory -Path (Split-Path $OutputFile) | Out-Null 43 | } 44 | 45 | & dotnet dotnet-coverage merge $Inputs -o $OutputFile -f cobertura 46 | } else { 47 | Write-Error "No reports found to merge." 48 | } 49 | } finally { 50 | Pop-Location 51 | } 52 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Model/PlatformNames.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Model; 5 | 6 | internal static class PlatformNames 7 | { 8 | internal const string All = "*"; 9 | 10 | // Some project types do not support platforms, so this either indicates 11 | // the project type doesn't support platforms or that the platform mapping is missing. 12 | internal const string Missing = "?"; 13 | 14 | // Used if the project type doesn't support platforms. 15 | internal const string Default = nameof(Default); 16 | 17 | internal const string AnyCPU = nameof(AnyCPU); 18 | internal const string AnySpaceCPU = "Any CPU"; 19 | internal const string Win32 = nameof(Win32); 20 | #pragma warning disable SA1303 // Const field names should begin with upper-case letter 21 | internal const string x64 = nameof(x64); 22 | internal const string x86 = nameof(x86); 23 | internal const string arm = nameof(arm); 24 | internal const string arm64 = nameof(arm64); 25 | #pragma warning restore SA1303 // Const field names should begin with upper-case letter 26 | 27 | // All caps to intern this common version. 28 | internal const string ARM = nameof(ARM); 29 | internal const string ARM64 = nameof(ARM64); 30 | 31 | internal static string Canonical(string platform) => string.Equals(platform, AnySpaceCPU, StringComparison.OrdinalIgnoreCase) ? AnyCPU : platform; 32 | 33 | internal static string ToStringKnown(string platform) 34 | { 35 | return TryGetKnown(platform.AsSpan(), out string? value) ? value : platform; 36 | } 37 | 38 | internal static bool TryGetKnown(StringSpan platform, [NotNullWhen(true)] out string? value) 39 | { 40 | value = platform switch 41 | { 42 | All => All, 43 | Missing => Missing, 44 | Default => Default, 45 | AnyCPU => AnyCPU, 46 | AnySpaceCPU => AnySpaceCPU, 47 | Win32 => Win32, 48 | x64 => x64, 49 | x86 => x86, 50 | arm => arm, 51 | arm64 => arm64, 52 | ARM => ARM, 53 | ARM64 => ARM64, 54 | _ => null, 55 | }; 56 | return value is not null; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/LineInfoXmlDocument.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Xml; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; 7 | 8 | /// 9 | /// Ensure XmlElements are created with so that errors on elements 10 | /// can be reported with line and position information. 11 | /// 12 | internal sealed class LineInfoXmlDocument : XmlDocument 13 | { 14 | private IXmlLineInfo? xmlLineInfo; 15 | 16 | public override XmlElement CreateElement(string? prefix, string localName, string? namespaceURI) 17 | { 18 | return this.xmlLineInfo is not null && this.xmlLineInfo.HasLineInfo() ? 19 | new LineInfoXmlElement(prefix, localName, namespaceURI, this, this.xmlLineInfo.LineNumber, this.xmlLineInfo.LinePosition) : 20 | new LineInfoXmlElement(prefix, localName, namespaceURI, this); 21 | } 22 | 23 | public override void Load(XmlReader reader) 24 | { 25 | this.xmlLineInfo = reader as IXmlLineInfo; 26 | try 27 | { 28 | base.Load(reader); 29 | } 30 | finally 31 | { 32 | this.xmlLineInfo = null; 33 | } 34 | } 35 | 36 | // Extend XmlElement to include line and position information. 37 | internal sealed class LineInfoXmlElement : XmlElement, IXmlLineInfo 38 | { 39 | private readonly bool hasLineInfo; 40 | 41 | internal LineInfoXmlElement(string? prefix, string localName, string? namespaceURI, XmlDocument doc) 42 | : base(prefix, localName, namespaceURI, doc) 43 | { 44 | } 45 | 46 | internal LineInfoXmlElement(string? prefix, string localName, string? namespaceURI, XmlDocument doc, int line, int column) 47 | : base(prefix, localName, namespaceURI, doc) 48 | { 49 | this.hasLineInfo = true; 50 | this.LineNumber = line; 51 | this.LinePosition = column; 52 | } 53 | 54 | /// 55 | public int LineNumber { get; } 56 | 57 | /// 58 | public int LinePosition { get; } 59 | 60 | /// 61 | public bool HasLineInfo() => this.hasLineInfo; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /azure-pipelines/dotnet.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: RunTests 3 | - name: IsOptProf 4 | type: boolean 5 | default: false 6 | - name: Is1ESPT 7 | type: boolean 8 | - name: BuildRequiresAccessToken 9 | type: boolean 10 | default: false 11 | 12 | steps: 13 | 14 | - script: dotnet build -t:build,pack --no-restore -c $(BuildConfiguration) -warnAsError -warnNotAsError:NU1901,NU1902,NU1903,NU1904,LOCTASK002 /bl:"$(Build.ArtifactStagingDirectory)/build_logs/build.binlog" 15 | displayName: 🛠 dotnet build 16 | ${{ if parameters.BuildRequiresAccessToken }}: 17 | env: 18 | SYSTEM_ACCESSTOKEN: $(System.AccessToken) 19 | 20 | - ${{ if not(parameters.IsOptProf) }}: 21 | - powershell: tools/dotnet-test-cloud.ps1 -Configuration $(BuildConfiguration) -Agent $(Agent.JobName) -PublishResults 22 | displayName: 🧪 dotnet test 23 | condition: and(succeeded(), ${{ parameters.RunTests }}) 24 | 25 | - ${{ if parameters.IsOptProf }}: 26 | - script: dotnet pack src\VSInsertionMetadata -c $(BuildConfiguration) -warnaserror /bl:"$(Build.ArtifactStagingDirectory)/build_logs/VSInsertion-Pack.binlog" 27 | displayName: 🔧 dotnet pack VSInsertionMetadata 28 | 29 | - powershell: tools/variables/_define.ps1 30 | failOnStderr: true 31 | displayName: ⚙ Update pipeline variables based on build outputs 32 | condition: succeededOrFailed() 33 | 34 | - ${{ if parameters.Is1ESPT }}: 35 | - powershell: azure-pipelines/publish_artifacts.ps1 -StageOnly -AvoidSymbolicLinks -ArtifactNameSuffix "-$(Agent.JobName)" -Verbose 36 | failOnStderr: true 37 | displayName: 📢 Stage artifacts 38 | condition: succeededOrFailed() 39 | - ${{ else }}: 40 | - powershell: azure-pipelines/publish_artifacts.ps1 -ArtifactNameSuffix "-$(Agent.JobName)" -Verbose 41 | failOnStderr: true 42 | displayName: 📢 Publish artifacts 43 | condition: succeededOrFailed() 44 | 45 | - ${{ if and(ne(variables['codecov_token'], ''), parameters.RunTests) }}: 46 | - powershell: | 47 | $ArtifactStagingFolder = & "tools/Get-ArtifactsStagingDirectory.ps1" 48 | $CoverageResultsFolder = Join-Path $ArtifactStagingFolder "coverageResults-$(Agent.JobName)" 49 | tools/publish-CodeCov.ps1 -CodeCovToken "$(codecov_token)" -PathToCodeCoverage "$CoverageResultsFolder" -Name "$(Agent.JobName) Coverage Results" -Flags "$(Agent.JobName)" 50 | displayName: 📢 Publish code coverage results to codecov.io 51 | timeoutInMinutes: 3 52 | continueOnError: true 53 | -------------------------------------------------------------------------------- /azure-pipelines/apiscan.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: windowsPool 3 | type: object 4 | - name: RealSign 5 | type: boolean 6 | 7 | jobs: 8 | - job: apiscan 9 | displayName: APIScan 10 | dependsOn: Windows 11 | pool: ${{ parameters.windowsPool }} 12 | timeoutInMinutes: 120 13 | templateContext: 14 | ${{ if not(parameters.RealSign) }}: 15 | mb: 16 | signing: # if the build is test-signed, install the signing plugin so that CSVTestSignPolicy.xml is available 17 | enabled: true 18 | zipSources: false 19 | signType: test 20 | outputs: 21 | - output: pipelineArtifact 22 | displayName: 📢 collect apiscan artifact 23 | targetPath: $(Pipeline.Workspace)/.gdn/.r/apiscan/001/Logs 24 | artifactName: apiscan-logs 25 | condition: succeededOrFailed() 26 | variables: 27 | - name: SymbolsFeatureName 28 | value: $[ dependencies.Windows.outputs['SetPipelineVariables.SymbolsFeatureName'] ] 29 | - name: NBGV_MajorMinorVersion 30 | value: $[ dependencies.Windows.outputs['nbgv.NBGV_MajorMinorVersion'] ] 31 | - ${{ if eq(variables['system.collectionId'], '011b8bdf-6d56-4f87-be0d-0092136884d9') }}: 32 | # https://dev.azure.com/devdiv/DevDiv/_wiki/wikis/DevDiv.wiki/25351/APIScan-step-by-step-guide-to-setting-up-a-Pipeline 33 | - group: VSEng sponsored APIScan # Expected to provide ApiScanClientId 34 | steps: 35 | # We need TSAOptions.json 36 | - checkout: self 37 | fetchDepth: 1 38 | 39 | - download: current 40 | artifact: APIScanInputs 41 | displayName: 🔻 Download APIScanInputs artifact 42 | 43 | - task: APIScan@2 44 | displayName: 🔍 Run APIScan 45 | inputs: 46 | softwareFolder: $(Pipeline.Workspace)/APIScanInputs 47 | softwareName: $(SymbolsFeatureName) 48 | softwareVersionNum: $(NBGV_MajorMinorVersion) 49 | isLargeApp: false 50 | toolVersion: Latest 51 | preserveLogsFolder: true 52 | azureSubscription: VSEng-APIScanSC 53 | env: 54 | AzureServicesAuthConnectionString: $(APIScanAuthConnectionString) 55 | SYSTEM_ACCESSTOKEN: $(System.AccessToken) 56 | 57 | # File bugs when APIScan finds issues 58 | - task: TSAUpload@2 59 | displayName: 🪳 TSA upload 60 | inputs: 61 | GdnPublishTsaOnboard: True 62 | GdnPublishTsaConfigFile: $(Build.SourcesDirectory)\azure-pipelines\TSAOptions.json 63 | condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) 64 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/SlnV12/SectionName.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.SlnV12; 5 | 6 | internal static class SectionName 7 | { 8 | // A property for items directory on the solution or project. 9 | internal const string VisualStudio = "Visual Studio"; 10 | internal const string SolutionProperties = nameof(SolutionProperties); 11 | internal const string ExtensibilityGlobals = nameof(ExtensibilityGlobals); 12 | internal const string NestedProjects = nameof(NestedProjects); 13 | internal const string SolutionConfigurationPlatforms = nameof(SolutionConfigurationPlatforms); 14 | internal const string ProjectConfigurationPlatforms = nameof(ProjectConfigurationPlatforms); 15 | 16 | // Shared project system properties. 17 | internal const string SharedMSBuildProjectFiles = nameof(SharedMSBuildProjectFiles); 18 | 19 | // Project's build dependencies. 20 | internal const string ProjectDependencies = nameof(ProjectDependencies); 21 | 22 | // Solution Folder's files. 23 | internal const string SolutionItems = nameof(SolutionItems); 24 | 25 | // Convert section names to the already interned constants. 26 | internal static string InternKnownSectionName(string sectionName) 27 | { 28 | return 29 | StringComparer.OrdinalIgnoreCase.Equals(sectionName, SolutionProperties) ? SolutionProperties : 30 | StringComparer.OrdinalIgnoreCase.Equals(sectionName, ExtensibilityGlobals) ? ExtensibilityGlobals : 31 | StringComparer.OrdinalIgnoreCase.Equals(sectionName, NestedProjects) ? NestedProjects : 32 | StringComparer.OrdinalIgnoreCase.Equals(sectionName, SolutionConfigurationPlatforms) ? SolutionConfigurationPlatforms : 33 | StringComparer.OrdinalIgnoreCase.Equals(sectionName, ProjectConfigurationPlatforms) ? ProjectConfigurationPlatforms : 34 | StringComparer.OrdinalIgnoreCase.Equals(sectionName, SharedMSBuildProjectFiles) ? SharedMSBuildProjectFiles : 35 | StringComparer.OrdinalIgnoreCase.Equals(sectionName, ProjectDependencies) ? ProjectDependencies : 36 | StringComparer.OrdinalIgnoreCase.Equals(sectionName, SolutionItems) ? SolutionItems : 37 | sectionName; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/LocalUsings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #if NETFRAMEWORK 5 | global using Microsoft.IO; 6 | 7 | // Listing the types in System.IO that aren't in Microsoft.IO is a 8 | // little tedious, but this should prevent any need to include the entire 9 | // namespace which might lead to accidental usage of old types. 10 | global using BinaryReader = System.IO.BinaryReader; 11 | global using BinaryWriter = System.IO.BinaryWriter; 12 | global using DirectoryNotFoundException = System.IO.DirectoryNotFoundException; 13 | global using DriveInfo = System.IO.DriveInfo; 14 | global using DriveType = System.IO.DriveType; 15 | global using FileAccess = System.IO.FileAccess; 16 | global using FileAttributes = System.IO.FileAttributes; 17 | global using FileLoadException = System.IO.FileLoadException; 18 | global using FileMode = System.IO.FileMode; 19 | global using FileNotFoundException = System.IO.FileNotFoundException; 20 | global using FileShare = System.IO.FileShare; 21 | global using FileStream = System.IO.FileStream; 22 | global using InvalidDataException = System.IO.InvalidDataException; 23 | global using IOException = System.IO.IOException; 24 | global using MemoryStream = System.IO.MemoryStream; 25 | global using PathTooLongException = System.IO.PathTooLongException; 26 | global using SeekOrigin = System.IO.SeekOrigin; 27 | global using Stream = System.IO.Stream; 28 | global using StreamReader = System.IO.StreamReader; 29 | global using StreamWriter = System.IO.StreamWriter; 30 | global using StringReader = System.IO.StringReader; 31 | global using StringWriter = System.IO.StringWriter; 32 | global using TextReader = System.IO.TextReader; 33 | global using TextWriter = System.IO.TextWriter; 34 | #else 35 | global using System.IO; 36 | #endif 37 | 38 | global using System; 39 | global using System.Collections.Generic; 40 | global using System.Diagnostics.CodeAnalysis; 41 | global using System.Text; 42 | global using System.Threading; 43 | global using System.Threading.Tasks; 44 | global using Microsoft.VisualStudio.SolutionPersistence; 45 | global using Microsoft.VisualStudio.SolutionPersistence.Model; 46 | global using Microsoft.VisualStudio.SolutionPersistence.Serializer; 47 | global using Utilities; 48 | global using Xunit; 49 | global using static Utilities.SlnTestHelper; 50 | global using StringSpan = System.ReadOnlySpan; 51 | -------------------------------------------------------------------------------- /tools/Install-NuGetPackage.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Installs a NuGet package. 4 | .PARAMETER PackageID 5 | The Package ID to install. 6 | .PARAMETER Version 7 | The version of the package to install. If unspecified, the latest stable release is installed. 8 | .PARAMETER Source 9 | The package source feed to find the package to install from. 10 | .PARAMETER Prerelease 11 | Include prerelease packages when searching for the latest version. 12 | .PARAMETER ExcludeVersion 13 | Installs the package without adding the version to the folder name. 14 | .PARAMETER DirectDownload 15 | Bypass the local cache when downloading packages. 16 | .PARAMETER PackagesDir 17 | The directory to install the package to. By default, it uses the Packages folder at the root of the repo. 18 | .PARAMETER ConfigFile 19 | The nuget.config file to use. By default, it uses :/nuget.config. 20 | .OUTPUTS 21 | System.String. The path to the installed package. 22 | #> 23 | [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Low')] 24 | Param( 25 | [Parameter(Position=1,Mandatory=$true)] 26 | [string]$PackageId, 27 | [Parameter()] 28 | [string]$Version, 29 | [Parameter()] 30 | [string]$Source, 31 | [Parameter()] 32 | [switch]$Prerelease, 33 | [Parameter()] 34 | [switch]$ExcludeVersion, 35 | [Parameter()] 36 | [switch]$DirectDownload, 37 | [Parameter()] 38 | [string]$PackagesDir="$PSScriptRoot\..\packages", 39 | [Parameter()] 40 | [string]$ConfigFile="$PSScriptRoot\..\nuget.config", 41 | [Parameter()] 42 | [ValidateSet('Quiet','Normal','Detailed')] 43 | [string]$Verbosity='normal' 44 | ) 45 | 46 | $nugetPath = & "$PSScriptRoot\Get-NuGetTool.ps1" 47 | 48 | Write-Verbose "Installing $PackageId..." 49 | $nugetArgs = "Install",$PackageId,"-OutputDirectory",$PackagesDir,'-ConfigFile',$ConfigFile 50 | if ($Version) { $nugetArgs += "-Version",$Version } 51 | if ($Source) { $nugetArgs += "-FallbackSource",$Source } 52 | if ($Prerelease) { $nugetArgs += "-Prerelease" } 53 | if ($ExcludeVersion) { $nugetArgs += '-ExcludeVersion' } 54 | if ($DirectDownload) { $nugetArgs += '-DirectDownload' } 55 | $nugetArgs += '-Verbosity',$Verbosity 56 | 57 | if ($PSCmdlet.ShouldProcess($PackageId, 'nuget install')) { 58 | $p = Start-Process $nugetPath $nugetArgs -NoNewWindow -Wait -PassThru 59 | if ($null -ne $p.ExitCode -and $p.ExitCode -ne 0) { throw } 60 | } 61 | 62 | # Provide the path to the installed package directory to our caller. 63 | Write-Output (Get-ChildItem "$PackagesDir\$PackageId.*")[0].FullName 64 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Model/StringTable.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Model; 5 | 6 | /// 7 | /// Table of strings that can be used to remove duplicate string allocations. 8 | /// This is helpful in the solution model as several types of strings are repeated. 9 | /// 10 | public sealed class StringTable 11 | { 12 | // string deduplication facility (we can expect a lot of similar strings in solution files we want to compact while building the model). 13 | private readonly HashSet strings = new HashSet(StringComparer.Ordinal); 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// Creates an empty string table. 18 | /// 19 | public StringTable() 20 | { 21 | } 22 | 23 | /// 24 | /// Attempts to get a string from the table. 25 | /// If not found the string is added to the table. 26 | /// 27 | /// The string to search for. 28 | /// The string to use in the model. 29 | public string GetString(StringSpan str) 30 | { 31 | if (str.IsEmpty) 32 | { 33 | return string.Empty; 34 | } 35 | 36 | // CONSIDER: We could use hashcodes to try to find strings without allocating. 37 | return this.GetString(str.ToString()); 38 | } 39 | 40 | /// 41 | /// Attempts to get a string from the table. 42 | /// If not found the string is added to the table. 43 | /// 44 | /// The string to search for. 45 | /// The string to use in the model. 46 | [return: NotNullIfNotNull(nameof(str))] 47 | public string? GetString(string? str) 48 | { 49 | if (str is null) 50 | { 51 | return null; 52 | } 53 | 54 | if (str.Length == 0) 55 | { 56 | return string.Empty; 57 | } 58 | 59 | if (this.strings.TryGetValue(str, out string? result)) 60 | { 61 | return result; 62 | } 63 | 64 | _ = this.strings.Add(str); 65 | return str; 66 | } 67 | 68 | internal void AddString(string str) 69 | { 70 | _ = this.GetString(str); 71 | } 72 | 73 | // Used to test the string table. 74 | internal bool Contains(string str) 75 | { 76 | return this.strings.Contains(str); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microsoft.VisualStudio.SolutionPersistence 2 | 3 | ## About 4 | 5 | Shared serializers and models for Visual Studio solution files. Handles traditional .sln file and new .slnx file. 6 | 7 | [![NuGet package](https://img.shields.io/nuget/v/Microsoft.VisualStudio.SolutionPersistence.svg)](https://nuget.org/packages/Microsoft.VisualStudio.SolutionPersistence) 8 | 9 | 10 | ## Features 11 | 12 | * Serializers for solution files, traditional text based .sln file, and XML based .slnx file. 13 | * Object model for manipulating file contents in common way. 14 | * SLN File Format 15 | - Consistent with legacy Visual Studio parsing code to ensure consistent behavior when reading .sln files. 16 | - Model extension that allows specifying the file encoding. 17 | * SLNX File Format 18 | - Simplified format to make merge conflicts easier to resolve. 19 | - Preserves user added elements, comments and whitespace when possible. Updaing only modifies changed elements to reduce chances of changing user content. 20 | - Simplified configuration rules and default logic reduce the file size. 21 | - Model extension that allows specifying XML formatting. 22 | 23 | ## Using 24 | 25 | The entry point to serializers can be found on the SolutionSerializers static class. This has the helper GetSerializerByMoniker that can pick the serializer for a file extension, or a specific serializers can be used. 26 | 27 | SolutionModel is the class that represents a solution, it has a SolutionProjectModel for each project in the solution as well as SolutionFolderModel to represent solution folders (which are logical constructs and do not necessarily represent directories). 28 | 29 | For the most part any serializer specific concepts are removed from the model, but it does allow for serializer specific properties to be added using a ISerializerModelExtension. Each serializer has a CreateModelExtension method for creating a default model extension, as well as overloads for specifying options that are specific to each file format. For example .sln files support writing different encodings (ASCII, UTF-8 w/ BOM, and UTF-16). While .slnx files have options to customize XML formatting. 30 | 31 | See [Samples Wiki](https://github.com/microsoft/vs-solutionpersistence/wiki/Samples) 32 | 33 | ## Trademark 34 | 35 | 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. 36 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/MakeSlnx.MakeSlnxFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Serialization; 5 | 6 | /// 7 | /// Fixture for tests. 8 | /// 9 | public sealed partial class MakeSlnx 10 | { 11 | /// 12 | /// The temp subdirectory name for the output directory. 13 | /// 14 | public const string OutputDirectory = "OutputSln"; 15 | 16 | /// 17 | /// Used by tests to ensure temp directory structure is created and 18 | /// empty before the tests are run. 19 | /// 20 | public class MakeSlnxFixture 21 | { 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | public MakeSlnxFixture() 26 | { 27 | // Split output based on the .NET version (so we can run tests in parallel) 28 | string netVersion = "net" + Environment.Version.ToString(3); 29 | 30 | string outputDirectory = Path.Combine(Path.GetTempPath(), OutputDirectory, netVersion); 31 | if (Directory.Exists(outputDirectory)) 32 | { 33 | Directory.Delete(outputDirectory, true); 34 | } 35 | 36 | _ = Directory.CreateDirectory(outputDirectory); 37 | 38 | this.SlnToSlnxDirectory = Path.Join(outputDirectory, "slnToSlnx"); 39 | this.SlnViaSlnxDirectory = Path.Join(outputDirectory, "slnViaSlnx"); 40 | this.SlnxToSlnDirectory = Path.Join(outputDirectory, "slnxToSln"); 41 | _ = Directory.CreateDirectory(this.SlnToSlnxDirectory); 42 | _ = Directory.CreateDirectory(this.SlnViaSlnxDirectory); 43 | _ = Directory.CreateDirectory(this.SlnxToSlnDirectory); 44 | } 45 | 46 | /// 47 | /// Gets the full path to the directory where the SLN files are converted to SLNX. 48 | /// 49 | public string SlnToSlnxDirectory { get; private set; } 50 | 51 | /// 52 | /// Gets the full path to the directory where the SLN files are converted to SLNX and back to SLN. 53 | /// 54 | public string SlnViaSlnxDirectory { get; private set; } 55 | 56 | /// 57 | /// Gets the full path to the directory where the SLNX files are converted to SLN. 58 | /// 59 | public string SlnxToSlnDirectory { get; private set; } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Model/ConfigurationRule.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Model; 5 | 6 | /// 7 | /// Type of build dimension that can be configured. 8 | /// 9 | public enum BuildDimension : byte 10 | { 11 | /// 12 | /// Represents the build type of a project. (For example Debug or Release). 13 | /// 14 | BuildType, 15 | 16 | /// 17 | /// Represents the platform of a project. (For example Any CPU or x64). 18 | /// 19 | Platform, 20 | 21 | /// 22 | /// Determines if the project should be built. 23 | /// 24 | Build, 25 | 26 | /// 27 | /// Determines if the project should be deployed. 28 | /// 29 | Deploy, 30 | } 31 | 32 | /// 33 | /// Represents a rule that maps a solution configuration dimension to a project configuration dimension. 34 | /// For example a Platform dimension might map "Any CPU" -> "x64" for a C++ project. 35 | /// Dimensions: BuildType (e.g. Debug, Release), Platform (e.g. Any CPU, x64). 36 | /// 37 | public readonly struct ConfigurationRule( 38 | BuildDimension dimension, 39 | string solutionBuildType, 40 | string solutionPlatform, 41 | string projectValue) 42 | { 43 | /// 44 | /// The dimension that is being configured. 45 | /// 46 | public readonly BuildDimension Dimension = dimension; 47 | 48 | /// 49 | /// The solution build type that gets mapped to the project value. 50 | /// If string.Empty, then the project value is applied for all solution build types. 51 | /// 52 | public readonly string SolutionBuildType = solutionBuildType == BuildTypeNames.All ? string.Empty : solutionBuildType; 53 | 54 | /// 55 | /// The solution platform that gets mapped to the project value. 56 | /// If string.Empty, then the project value is applied for all solution platforms. 57 | /// 58 | public readonly string SolutionPlatform = solutionPlatform == PlatformNames.All ? string.Empty : solutionPlatform; 59 | 60 | /// 61 | /// The value that the project configuration should be set to. 62 | /// For BuildType or Dimension, this string represents the project configuration value. 63 | /// For Build or Deploy, this string is a string boolean value. (True or False). 64 | /// 65 | public readonly string ProjectValue = projectValue; 66 | } 67 | -------------------------------------------------------------------------------- /test/Microsoft.VisualStudio.SolutionPersistence.Tests/Serialization/StringTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Reflection; 5 | using Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml; 6 | 7 | namespace Serialization; 8 | 9 | /// 10 | /// Tests related to processing strings. 11 | /// 12 | public sealed class StringTests 13 | { 14 | private readonly StringTable stringTable = new StringTable().WithSolutionConstants(); 15 | 16 | /// 17 | /// Make sure all keywords have a string representation. 18 | /// 19 | [Fact] 20 | public void KeywordStrings() 21 | { 22 | // Validate that if any new keywords are added to the enum, they are in the mapping tables. 23 | foreach (Keyword keyword in Enum.GetValues(typeof(Keyword))) 24 | { 25 | if (keyword is not Keyword.Unknown and not < 0 and not >= Keyword.MaxProp) 26 | { 27 | string keywordStr = keyword.ToXmlString(); 28 | Assert.True(keywordStr is not null, $"Keyword {keyword} does not have a string representation."); 29 | Assert.True(this.stringTable.Contains(keywordStr), $"StringTable missing {keyword.ToXmlString()}"); 30 | } 31 | } 32 | } 33 | 34 | /// 35 | /// Make sure all build type names have a string representation. 36 | /// 37 | [Fact] 38 | public void BuildTypeNamesTest() 39 | { 40 | foreach (FieldInfo fieldInfo in typeof(BuildTypeNames).GetFields(BindingFlags.NonPublic | BindingFlags.Static)) 41 | { 42 | string value = fieldInfo.GetValue(null) as string ?? string.Empty; 43 | Assert.True(BuildTypeNames.TryGetKnown(value.AsSpan(), out string? str), $"BuildType lookup missing {value}"); 44 | Assert.True(this.stringTable.Contains(str), $"StringTable missing {str}"); 45 | } 46 | } 47 | 48 | /// 49 | /// Make sure all platform names have a string representation. 50 | /// 51 | [Fact] 52 | public void PlatformNamesTest() 53 | { 54 | foreach (FieldInfo fieldInfo in typeof(PlatformNames).GetFields(BindingFlags.NonPublic | BindingFlags.Static)) 55 | { 56 | string value = fieldInfo.GetValue(null) as string ?? string.Empty; 57 | Assert.True(PlatformNames.TryGetKnown(value.AsSpan(), out string? str), $"Platform lookup missing {value}"); 58 | Assert.True(this.stringTable.Contains(str), $"StringTable missing {str}"); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/Xml/XmlDecorators/XmlSolution.ApplyModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.VisualStudio.SolutionPersistence.Model; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.Xml.XmlDecorators; 7 | 8 | /// 9 | /// Represents the root Solution XML element in the slnx file. 10 | /// These methods are used to update the Xml DOM with changes from the model. 11 | /// 12 | internal sealed partial class XmlSolution 13 | { 14 | // Update the Xml DOM with changes from the model. 15 | internal bool ApplyModelToXml(SolutionModel modelSolution) 16 | { 17 | bool modified = false; 18 | 19 | // Attributes 20 | string description = modelSolution.Description ?? string.Empty; 21 | if (!StringComparer.Ordinal.Equals(this.Description, description)) 22 | { 23 | this.Description = description; 24 | modified = true; 25 | } 26 | 27 | // Configurations 28 | // Use the item ref logic to allow only a single "Configurations" element, and use string.Empty as the item ref. 29 | modified |= this.ApplyModelItemsToXml( 30 | modelItems: modelSolution.IsConfigurationImplicit() ? null : [(ItemRef: string.Empty, Item: modelSolution)], 31 | ref this.configurationsSingle, 32 | Keyword.Configurations, 33 | applyModelToXml: static (newConfigs, newValue) => newConfigs.ApplyModelToXml(newValue)); 34 | 35 | // Folders 36 | modified |= this.ApplyModelItemsToXml( 37 | modelItems: modelSolution.SolutionFolders.ToList(folder => (folder.ItemRef, Item: folder)), 38 | ref this.folders, 39 | Keyword.Folder, 40 | applyModelToXml: static (newFolder, newValue) => newFolder.ApplyModelToXml(newValue)); 41 | 42 | // Projects 43 | List<(string ItemRef, SolutionProjectModel Item)> solutionProjects = modelSolution.SolutionProjects.WhereToList( 44 | (project, _) => project.Parent is null, 45 | (project, _) => (ItemRef: this.Root.ConvertToUserPath(project.ItemRef), Item: project), 46 | (object?)null); 47 | modified |= this.ApplyModelItemsToXml( 48 | modelItems: solutionProjects, 49 | ref this.rootProjects, 50 | Keyword.Project, 51 | applyModelToXml: static (newProject, newValue) => newProject.ApplyModelToXml(newValue)); 52 | 53 | // Properties 54 | modified |= this.ApplyModelToXml(modelSolution.Properties); 55 | 56 | return modified; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/SlnV12/SlnConstants.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer.SlnV12; 5 | 6 | internal static class SlnConstants 7 | { 8 | internal const string ProjectSeparators = " ()=\","; 9 | internal const string SectionSeparators = " \t()="; 10 | internal const string SectionSeparators2 = "\t()="; 11 | internal const string VersionSeparators = " ="; 12 | internal const char DoubleQuote = '"'; 13 | 14 | internal const string SLNFileHeaderNoVersion = "Microsoft Visual Studio Solution File, Format Version"; 15 | internal const string SLNFileHeaderVersion = " 12.00"; 16 | 17 | // Special property Visual Studio property names 18 | internal const string HideSolutionNode = nameof(HideSolutionNode); 19 | internal const string SolutionGuid = nameof(SolutionGuid); 20 | 21 | // Special property names 22 | internal const string Description = nameof(Description); 23 | 24 | // Used in .SLN to determine with version of VS to open when opening from explorer. 25 | internal const string OpenWithPrefix = "# "; 26 | 27 | internal const string TagProjectStart = "Project("; 28 | internal const string TagProjectSectionStart = "ProjectSection("; 29 | internal const string TagGlobalSectionStart = "GlobalSection("; 30 | 31 | internal const string TagProject = "Project"; 32 | internal const string TagGlobal = "Global"; 33 | internal const string TagSection = "Section"; 34 | internal const string TagGlobalSection = "GlobalSection"; 35 | internal const string TagProjectSection = "ProjectSection"; 36 | 37 | internal const string TagEndProject = "EndProject"; 38 | internal const string TagEndGlobal = "EndGlobal"; 39 | internal const string TagEndGlobalSection = "EndGlobalSection"; 40 | internal const string TagEndProjectSection = "EndProjectSection"; 41 | 42 | internal const string TagPreSolution = "preSolution"; 43 | internal const string TagPostSolution = "postSolution"; 44 | internal const string TagPreProject = "preProject"; 45 | internal const string TagPostProject = "postProject"; 46 | 47 | internal const string TagVisualStudioVersion = "VisualStudioVersion"; 48 | internal const string TagMinimumVisualStudioVersion = "MinimumVisualStudioVersion"; 49 | internal const string TagAssignValue = " = "; 50 | internal const string TagQuoteCommaQuote = "\", \""; 51 | internal const string TagTabTab = "\t\t"; 52 | 53 | // This should only be use in SLN files. 54 | internal static string ToSlnString(this Guid guid) => guid.ToString("B").ToUpperInvariant(); 55 | } 56 | -------------------------------------------------------------------------------- /src/Microsoft.VisualStudio.SolutionPersistence/Serializer/SingleFileSerializerBase`1.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.VisualStudio.SolutionPersistence.Model; 5 | 6 | namespace Microsoft.VisualStudio.SolutionPersistence.Serializer; 7 | 8 | internal abstract class SingleFileSerializerBase : ISolutionSingleFileSerializer 9 | { 10 | public abstract string Name { get; } 11 | 12 | public string DefaultFileExtension => this.FileExtension; 13 | 14 | private protected abstract string FileExtension { get; } 15 | 16 | public abstract ISerializerModelExtension CreateModelExtension(); 17 | 18 | public abstract ISerializerModelExtension CreateModelExtension(TSettings settings); 19 | 20 | Task ISolutionSingleFileSerializer.OpenAsync(Stream reader, CancellationToken cancellationToken) 21 | { 22 | cancellationToken.ThrowIfCancellationRequested(); 23 | return this.ReadModelAsync(fullPath: null, reader, cancellationToken); 24 | } 25 | 26 | Task ISolutionSingleFileSerializer.SaveAsync(Stream writer, SolutionModel model, CancellationToken cancellationToken) 27 | { 28 | cancellationToken.ThrowIfCancellationRequested(); 29 | return this.WriteModelAsync(fullPath: null, model, writer, cancellationToken); 30 | } 31 | 32 | bool ISolutionSerializer.IsSupported(string fullPath) 33 | { 34 | return Path.GetExtension(fullPath.AsSpan()).EqualsOrdinalIgnoreCase(this.FileExtension); 35 | } 36 | 37 | async Task ISolutionSerializer.OpenAsync(string moniker, CancellationToken cancellationToken) 38 | { 39 | using (FileStream reader = File.OpenRead(moniker)) 40 | { 41 | return await this.ReadModelAsync(moniker, reader, cancellationToken); 42 | } 43 | } 44 | 45 | async Task ISolutionSerializer.SaveAsync(string moniker, SolutionModel model, CancellationToken cancellationToken) 46 | { 47 | string? directory = Path.GetDirectoryName(moniker); 48 | if (directory is not null && !Directory.Exists(directory)) 49 | { 50 | _ = Directory.CreateDirectory(directory); 51 | } 52 | 53 | using (FileStream writer = File.OpenWrite(moniker)) 54 | { 55 | await this.WriteModelAsync(moniker, model, writer, cancellationToken); 56 | } 57 | } 58 | 59 | private protected abstract Task ReadModelAsync(string? fullPath, Stream reader, CancellationToken cancellationToken); 60 | 61 | private protected abstract Task WriteModelAsync(string? fullPath, SolutionModel model, Stream writerStream, CancellationToken cancellationToken); 62 | } 63 | --------------------------------------------------------------------------------