├── .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 | [](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 | [](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 |
--------------------------------------------------------------------------------