зеркало из https://github.com/xamarin/AndroidX.git
Merge branch 'main' into mu-20230329-androidx.media.players
This commit is contained in:
Коммит
92df7b494c
|
@ -7,6 +7,7 @@
|
|||
|
||||
<!-- Default TFM's we build for -->
|
||||
<_DefaultTargetFrameworks>net8.0-android</_DefaultTargetFrameworks>
|
||||
<_DefaultNetTargetFrameworks>net8.0</_DefaultNetTargetFrameworks>
|
||||
|
||||
<!-- Use an updated 'generator' -->
|
||||
<!-- It's ok to use "Windows" here because we only use managed code from this package -->
|
||||
|
|
|
@ -5,24 +5,19 @@ trigger:
|
|||
pr:
|
||||
- main
|
||||
|
||||
variables:
|
||||
BUILD_NUMBER: $(Build.BuildNumber)
|
||||
BUILD_COMMIT: $(Build.SourceVersion)
|
||||
|
||||
# Build variables
|
||||
mainBranchName: main # Name of Git "main" branch
|
||||
configuration: Release # Build configuration: 'Debug', 'Release'
|
||||
|
||||
# Reporting variables
|
||||
areaPath: DevDiv\VS Client - Runtime SDKs\Android # AzDo area path to log any issues
|
||||
|
||||
# Windows specific variables
|
||||
windowsAgentPoolName: Maui-1ESPT # Windows VM pool name
|
||||
windowsImage: 1ESPT-Windows2022 # Windows VM image name
|
||||
parameters:
|
||||
- name: RunExtendedTests
|
||||
displayName: Run Extended Tests?
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# macOS specific variables
|
||||
macosAgentPoolName: Azure Pipelines # macOS VM pool name
|
||||
macosImage: internal-macos12 # macOS VM image name
|
||||
variables:
|
||||
# Variables used by both AndroidX/GPS go in the template
|
||||
- template: build/ci/variables.yml@self
|
||||
|
||||
# Variables only used by AndroidX go here
|
||||
- name: skipUnitTests
|
||||
value: false
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
|
@ -45,7 +40,8 @@ extends:
|
|||
os: windows
|
||||
|
||||
stages:
|
||||
- stage: Build
|
||||
- stage: build_windows
|
||||
displayName: Build - Windows
|
||||
|
||||
jobs:
|
||||
- template: build/ci/build.yml@self
|
||||
|
@ -55,10 +51,20 @@ extends:
|
|||
name: $(windowsAgentPoolName)
|
||||
image: $(windowsImage)
|
||||
os: windows
|
||||
mainBranchName: $(mainBranchName)
|
||||
configuration: $(configuration)
|
||||
runAPIScan: true
|
||||
|
||||
- template: sign-artifacts/jobs/v2.yml@internal-templates
|
||||
parameters:
|
||||
artifactName: output-windows
|
||||
usePipelineArtifactTasks: true
|
||||
use1ESTemplate: true
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/')
|
||||
|
||||
- stage: build_mac
|
||||
displayName: Build - Mac
|
||||
dependsOn:
|
||||
|
||||
jobs:
|
||||
- template: build/ci/build.yml@self
|
||||
parameters:
|
||||
name: macos
|
||||
|
@ -66,13 +72,11 @@ extends:
|
|||
name: $(macosAgentPoolName)
|
||||
vmImage: $(macosImage)
|
||||
os: macOS
|
||||
mainBranchName: $(mainBranchName)
|
||||
configuration: $(configuration)
|
||||
|
||||
- template: sign-artifacts/jobs/v2.yml@internal-templates
|
||||
parameters:
|
||||
dependsOn: [ 'build_windows' ]
|
||||
artifactName: output-windows
|
||||
usePipelineArtifactTasks: true
|
||||
use1ESTemplate: true
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/')
|
||||
- template: build/ci/stage-extended-tests.yml@self
|
||||
parameters:
|
||||
stageCondition: and(succeeded(), eq('${{ parameters.RunExtendedTests }}', 'true'))
|
||||
buildPool:
|
||||
name: $(windowsAgentPoolName)
|
||||
image: $(windowsImage)
|
||||
os: windows
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
parameters:
|
||||
condition: succeeded()
|
||||
verbosity: # the build verbosity: 'minimal', 'normal', 'diagnostic'
|
||||
configuration: # the build configuration: 'Debug', 'Release'
|
||||
artifactsPath:
|
||||
skipUnitTests: false # do not run unit test step
|
||||
|
||||
validPackagePrefixes: # any NuGet prefixes that should pass validation
|
||||
- Xamarin
|
||||
|
@ -14,8 +10,8 @@ steps:
|
|||
- pwsh: |
|
||||
dotnet cake build.cake `
|
||||
--target=ci-build `
|
||||
--configuration="${{ parameters.configuration }}" `
|
||||
--verbosity="${{ parameters.verbosity }}"
|
||||
--configuration="$(configuration)" `
|
||||
--verbosity="$(verbosity)"
|
||||
displayName: 'Build packages'
|
||||
env:
|
||||
JavaSdkDirectory: $(JAVA_HOME)
|
||||
|
@ -27,7 +23,7 @@ steps:
|
|||
- pwsh: |
|
||||
dotnet cake validation.cake `
|
||||
--namespaces="${{ join(',', parameters.validPackagePrefixes) }}" `
|
||||
--verbosity="${{ parameters.verbosity }}"
|
||||
--verbosity="$(verbosity)"
|
||||
displayName: 'Run NuGet package validation'
|
||||
|
||||
- pwsh: |
|
||||
|
@ -35,7 +31,7 @@ steps:
|
|||
--artifacts="${{ parameters.artifactsPath }}" `
|
||||
--output="${{ parameters.artifactsPath }}/api-diff" `
|
||||
--cache="$(Agent.TempDirectory)/api-diff" `
|
||||
--verbosity="${{ parameters.verbosity }}"
|
||||
--verbosity="$(verbosity)"
|
||||
displayName: 'Generate API diff'
|
||||
|
||||
- pwsh: dotnet cake utilities.cake -t=verify-namespace-file
|
||||
|
@ -44,8 +40,8 @@ steps:
|
|||
- pwsh: |
|
||||
dotnet cake build.cake `
|
||||
--target=ci-samples `
|
||||
--configuration="${{ parameters.configuration }}" `
|
||||
--verbosity="${{ parameters.verbosity }}"
|
||||
--configuration="$(configuration)" `
|
||||
--verbosity="$(verbosity)"
|
||||
displayName: 'Build samples'
|
||||
env:
|
||||
JavaSdkDirectory: $(JAVA_HOME)
|
||||
|
@ -56,8 +52,8 @@ steps:
|
|||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run unit tests
|
||||
condition: ne(${{ parameters.skipUnitTests }}, 'true')
|
||||
condition: ne(variables['skipUnitTests'], 'true')
|
||||
inputs:
|
||||
command: test
|
||||
projects: util/**/*.Tests.csproj
|
||||
arguments: '-c ${{ parameters.configuration }}'
|
||||
arguments: '-c $(configuration)'
|
||||
|
|
|
@ -4,19 +4,9 @@ parameters:
|
|||
buildPool: # VM pool information
|
||||
|
||||
# Build Parameters
|
||||
mainBranchName: 'main' # Name of Git "main" branch
|
||||
configuration: 'Release' # Build configuration: 'Debug', 'Release'
|
||||
verbosity: 'normal' # Build verbosity: 'minimal', 'normal', 'diagnostic'
|
||||
timeoutInMinutes: 300 # Max job runtime in minutes
|
||||
runAPIScan: false # Run APIScan analysis
|
||||
|
||||
# Tool Parameters
|
||||
dotnetVersion: '8.0.301' # .NET version to install on agent
|
||||
dotnetWorkloadRollbackFile: 'workloads.json' # Rollback file specifying workload versions to install
|
||||
dotnetNuGetOrgSource: 'https://api.nuget.org/v3/index.json' # NuGet.org URL to find workloads
|
||||
dotnetWorkloadSource: 'https://aka.ms/dotnet6/nuget/index.json' # .NET engineering URL to find workloads
|
||||
skipUnitTests: false # Skip running unit tests
|
||||
|
||||
tools: # Additional .NET global tools to install
|
||||
- 'xamarin.androidbinderator.tool': '0.5.7'
|
||||
- 'Cake.Tool': '4.0.0'
|
||||
|
@ -47,23 +37,16 @@ jobs:
|
|||
steps:
|
||||
- template: setup-environment.yml
|
||||
parameters:
|
||||
dotnetVersion: ${{ parameters.dotnetVersion }}
|
||||
dotnetWorkloadRollbackFile: ${{ parameters.dotnetWorkloadRollbackFile }}
|
||||
dotnetWorkloadSource: ${{ parameters.dotnetWorkloadSource }}
|
||||
dotnetNuGetOrgSource: ${{ parameters.dotnetNuGetOrgSource }}
|
||||
dotnetTools: ${{ parameters.tools }}
|
||||
|
||||
- template: build-and-test.yml
|
||||
parameters:
|
||||
artifactsPath: ${{ parameters.artifactsPath }}
|
||||
verbosity: ${{ parameters.verbosity }}
|
||||
configuration: ${{ parameters.configuration }}
|
||||
skipUnitTests: ${{ parameters.skipUnitTests }}
|
||||
|
||||
- ${{ if eq(parameters.runAPIScan, true) }}:
|
||||
- template: api-scan.yml
|
||||
parameters:
|
||||
mainBranchName: ${{ parameters.mainBranchName }}
|
||||
mainBranchName: $(mainBranchName)
|
||||
|
||||
# Copy SignList.xml to output
|
||||
- pwsh: |
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
# Runs test(s) that are too expensive to run on every commit
|
||||
|
||||
parameters:
|
||||
jobName: # Job display name ('Android' or 'MAUI')
|
||||
buildPool: # VM pool information
|
||||
agentCount: # Agents to run in parallel
|
||||
testFilter: # Test category filter
|
||||
|
||||
tools: # Additional .NET global tools to install
|
||||
- 'dotnet-test-slicer' : '0.1.0-alpha7'
|
||||
|
||||
jobs:
|
||||
- job: ${{ parameters.jobName }}_package_tests
|
||||
displayName: ${{ parameters.jobName }} Package Tests
|
||||
strategy:
|
||||
parallel: ${{ parameters.agentCount }}
|
||||
pool: ${{ parameters.buildPool }}
|
||||
timeoutInMinutes: 480
|
||||
|
||||
steps:
|
||||
- template: setup-environment.yml
|
||||
parameters:
|
||||
dotnetTools: ${{ parameters.tools }}
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
artifactName: output-windows
|
||||
downloadPath: output
|
||||
|
||||
# Build test assembly
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Build unit tests
|
||||
inputs:
|
||||
command: build
|
||||
projects: $(testAssemblyProject)
|
||||
arguments: -c $(configuration)
|
||||
|
||||
# Figure out which tests this slice is running
|
||||
- pwsh: >-
|
||||
dotnet-test-slicer
|
||||
slice
|
||||
--test-assembly="$(testAssembly)"
|
||||
--test-filter="${{ parameters.testFilter }}"
|
||||
--slice-number=$(System.JobPositionInPhase)
|
||||
--total-slices=$(System.TotalJobsInPhase)
|
||||
--outfile="$(testAssembly).runsettings"
|
||||
displayName: Slice unit tests
|
||||
failOnStderr: true
|
||||
|
||||
# Run unit tests
|
||||
- task: DotNetCoreCLI@2
|
||||
inputs:
|
||||
command: test
|
||||
projects: $(testAssembly)
|
||||
arguments: >-
|
||||
--settings "$(testAssembly).runsettings"
|
||||
publishTestResults: true
|
||||
testRunTitle: ${{ parameters.jobName }} Package Tests - $(System.JobPositionInPhase)
|
||||
displayName: Run unit tests
|
||||
continueOnError: true
|
||||
timeoutInMinutes: 480
|
|
@ -1,23 +1,18 @@
|
|||
parameters:
|
||||
condition: succeeded()
|
||||
dotnetVersion: ''
|
||||
dotnetWorkloadRollbackFile: ''
|
||||
dotnetWorkloadSource: ''
|
||||
dotnetNuGetOrgSource: ''
|
||||
dotnetTools: []
|
||||
|
||||
steps:
|
||||
# before the build starts, make sure the tooling is as expected
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Use dotnet ${{ parameters.dotnetVersion }}'
|
||||
displayName: 'Use dotnet $(dotnetVersion)'
|
||||
inputs:
|
||||
version: ${{ parameters.dotnetVersion }}
|
||||
version: $(dotnetVersion)
|
||||
performMultiLevelLookup: true
|
||||
includePreviewVersions: true
|
||||
condition: ne('${{ parameters.dotnetVersion }}', '')
|
||||
condition: ne('$(dotnetVersion)', '')
|
||||
|
||||
- pwsh: |
|
||||
dotnet workload install maui --verbosity diag --from-rollback-file ${{ parameters.dotnetWorkloadRollbackFile }} --source ${{ parameters.dotnetWorkloadSource }} --source ${{ parameters.dotnetNuGetOrgSource }}
|
||||
dotnet workload install maui --verbosity diag --from-rollback-file $(dotnetWorkloadRollbackFile) --source $(dotnetWorkloadSource) --source $(dotnetNuGetOrgSource)
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "##vso[task.logissue type=error]Failed to install workloads."
|
||||
Write-Host "##vso[task.complete result=Failed;]"
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# Runs test(s) that are too expensive to run on every commit
|
||||
|
||||
parameters:
|
||||
stageCondition: # When to run this stage
|
||||
buildPool: # VM pool information
|
||||
|
||||
stages:
|
||||
- stage: extended_tests
|
||||
displayName: Extended Tests
|
||||
dependsOn: build_windows
|
||||
condition: ${{ parameters.stageCondition }}
|
||||
|
||||
jobs:
|
||||
|
||||
- template: job-extended-tests.yml
|
||||
parameters:
|
||||
jobName: Android
|
||||
agentCount: 6
|
||||
testFilter: cat = Android
|
||||
buildPool: ${{ parameters.buildPool }}
|
||||
|
||||
- template: job-extended-tests.yml
|
||||
parameters:
|
||||
jobName: MAUI
|
||||
agentCount: 10
|
||||
testFilter: cat = MAUI
|
||||
buildPool: ${{ parameters.buildPool }}
|
|
@ -0,0 +1,30 @@
|
|||
variables:
|
||||
|
||||
BUILD_NUMBER: $(Build.BuildNumber)
|
||||
BUILD_COMMIT: $(Build.SourceVersion)
|
||||
|
||||
# Build variables
|
||||
mainBranchName: main # Name of Git "main" branch
|
||||
configuration: Release # Build configuration: 'Debug', 'Release'
|
||||
verbosity: 'normal' # Build verbosity: 'minimal', 'normal', 'diagnostic'
|
||||
|
||||
# Reporting variables
|
||||
areaPath: DevDiv\VS Client - Runtime SDKs\Android # AzDo area path to log any issues
|
||||
|
||||
# Windows specific variables
|
||||
windowsAgentPoolName: Maui-1ESPT # Windows VM pool name
|
||||
windowsImage: 1ESPT-Windows2022 # Windows VM image name
|
||||
|
||||
# macOS specific variables
|
||||
macosAgentPoolName: Azure Pipelines # macOS VM pool name
|
||||
macosImage: internal-macos12 # macOS VM image name
|
||||
|
||||
# Tool variables
|
||||
dotnetVersion: '8.0.301' # .NET version to install on agent
|
||||
dotnetWorkloadRollbackFile: 'workloads.json' # Rollback file specifying workload versions to install
|
||||
dotnetNuGetOrgSource: 'https://api.nuget.org/v3/index.json' # NuGet.org URL to find workloads
|
||||
dotnetWorkloadSource: 'https://aka.ms/dotnet6/nuget/index.json' # .NET engineering URL to find workloads
|
||||
|
||||
# Extended test variables
|
||||
testAssemblyProject: tests/extended/ExtendedTests.csproj # Extended tests project file
|
||||
testAssembly: tests/extended/bin/$(configuration)/net8.0/ExtendedTests.dll # Extended tests compiled binary
|
|
@ -389,6 +389,9 @@ public class ArtifactModel
|
|||
[JsonProperty ("templateSet")]
|
||||
public string TemplateSet { get; set; }
|
||||
|
||||
[JsonProperty ("comments")]
|
||||
public string Comments { get; set; }
|
||||
|
||||
[JsonProperty ("metadata")]
|
||||
public Dictionary<string, string> Metadata { get; set; } = new Dictionary<string, string> ();
|
||||
|
||||
|
|
15
config.json
15
config.json
|
@ -2055,7 +2055,8 @@
|
|||
"nugetVersion": "1.1.0.11",
|
||||
"nugetId": "Xamarin.AndroidX.Wear.Tiles.Renderer",
|
||||
"dependencyOnly": false,
|
||||
"frozen": true
|
||||
"frozen": true,
|
||||
"comments": "Needs androidx.wear.protolayout.protolayout-renderer:1.1.0"
|
||||
},
|
||||
{
|
||||
"groupId": "androidx.wear.watchface",
|
||||
|
@ -2225,7 +2226,8 @@
|
|||
"nugetId": "Xamarin.Google.Android.InstallReferrer",
|
||||
"dependencyOnly": false,
|
||||
"frozen": true,
|
||||
"templateSet": "installreferrer"
|
||||
"templateSet": "installreferrer",
|
||||
"comments": "Changed from Apache to Android SDK License"
|
||||
},
|
||||
{
|
||||
"groupId": "com.google.accompanist",
|
||||
|
@ -2324,7 +2326,8 @@
|
|||
"nugetVersion": "1.1.18.12",
|
||||
"nugetId": "Xamarin.Google.Android.Material.Compose.Theme.Adapter",
|
||||
"dependencyOnly": false,
|
||||
"frozen": true
|
||||
"frozen": true,
|
||||
"comments": "Needs com.google.android.material.compose-theme-adapter-core:1.0.1"
|
||||
},
|
||||
{
|
||||
"groupId": "com.google.android.material",
|
||||
|
@ -2333,7 +2336,8 @@
|
|||
"nugetVersion": "1.0.18.11",
|
||||
"nugetId": "Xamarin.Google.Android.Material.Compose.Theme.Adapter3",
|
||||
"dependencyOnly": false,
|
||||
"frozen": true
|
||||
"frozen": true,
|
||||
"comments": "Needs com.google.android.material.compose-theme-adapter-core:1.0.1"
|
||||
},
|
||||
{
|
||||
"groupId": "com.google.android.material",
|
||||
|
@ -2342,7 +2346,8 @@
|
|||
"nugetVersion": "1.10.0.6",
|
||||
"nugetId": "Xamarin.Google.Android.Material",
|
||||
"dependencyOnly": false,
|
||||
"frozen": true
|
||||
"frozen": true,
|
||||
"comments": "Requires API-34"
|
||||
},
|
||||
{
|
||||
"groupId": "com.google.assistant.appactions",
|
||||
|
|
|
@ -21,11 +21,20 @@
|
|||
<!-- External packages -->
|
||||
<package pattern="Microsoft.Android.Ref.*" />
|
||||
<package pattern="Microsoft.Android.Runtime.*" />
|
||||
<package pattern="Microsoft.AspNetCore.*" />
|
||||
<package pattern="Microsoft.Extensions.*" />
|
||||
<package pattern="Microsoft.Graphics.*" />
|
||||
<package pattern="Microsoft.iOS.*" />
|
||||
<package pattern="Microsoft.MacCatalyst.*" />
|
||||
<package pattern="Microsoft.Maui.*" />
|
||||
<package pattern="Microsoft.NET.*" />
|
||||
<package pattern="Microsoft.Extensions.*" />
|
||||
<package pattern="Microsoft.NETCore.*" />
|
||||
<package pattern="Microsoft.Windows.*" />
|
||||
<package pattern="Microsoft.WindowsAppSDK" />
|
||||
<package pattern="Microsoft.WindowsDesktop.*" />
|
||||
|
||||
<!-- GPS packages -->
|
||||
<package pattern="Square.OkIO*" />
|
||||
<package pattern="Xamarin.Android.Glide*" />
|
||||
<package pattern="Xamarin.Google.Code.FindBugs.*" />
|
||||
<package pattern="Xamarin.Google.ErrorProne.*" />
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<Project>
|
||||
<!-- This Directory.Build.props is to prevent the one in the root from being used,
|
||||
which is tuned for building bindings packages. -->
|
||||
<PropertyGroup>
|
||||
<!-- Default TFM's we build for -->
|
||||
<_DefaultTargetFrameworks>net8.0-android</_DefaultTargetFrameworks>
|
||||
<_DefaultNetTargetFrameworks>net8.0</_DefaultNetTargetFrameworks>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -0,0 +1,195 @@
|
|||
#nullable disable
|
||||
|
||||
using System.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ExtendedTests;
|
||||
|
||||
public class BinderatorConfigFileParser
|
||||
{
|
||||
public static async Task<List<MyArray>> ParseConfigurationFile (string filename)
|
||||
{
|
||||
string json;
|
||||
|
||||
if (filename.StartsWith ("http")) {
|
||||
|
||||
// Configuration file URL
|
||||
using (var client = new HttpClient ())
|
||||
json = await client.GetStringAsync (filename);
|
||||
|
||||
} else {
|
||||
|
||||
// Local configuration file
|
||||
if (string.IsNullOrWhiteSpace (filename) || !File.Exists (filename)) {
|
||||
System.Console.WriteLine ($"Could not find configuration file: '{filename}'");
|
||||
Environment.Exit (-1);
|
||||
}
|
||||
|
||||
json = File.ReadAllText (filename);
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<List<MyArray>> (json);
|
||||
}
|
||||
|
||||
static async Task<List<ArtifactModel>> GetExternalDependencies (Options options)
|
||||
{
|
||||
var list = new List<ArtifactModel> ();
|
||||
|
||||
foreach (var file in options.DependencyConfigs) {
|
||||
var config = await ParseConfigurationFile (file);
|
||||
|
||||
list.AddRange (config.SelectMany (arr => arr.Artifacts.Where (a => !a.DependencyOnly)));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
// Configuration File Model
|
||||
public class Template
|
||||
{
|
||||
[JsonProperty ("templateFile")]
|
||||
public string TemplateFile { get; set; }
|
||||
|
||||
[JsonProperty ("outputFileRule")]
|
||||
public string OutputFileRule { get; set; }
|
||||
}
|
||||
|
||||
public class TemplateSetModel
|
||||
{
|
||||
[JsonProperty ("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty ("mavenRepositoryType")]
|
||||
public MavenRepoType? MavenRepositoryType { get; set; }
|
||||
|
||||
[JsonProperty ("mavenRepositoryLocation")]
|
||||
public string MavenRepositoryLocation { get; set; } = null;
|
||||
|
||||
[JsonProperty ("templates")]
|
||||
public List<Template> Templates { get; set; } = new List<Template> ();
|
||||
}
|
||||
|
||||
public class ArtifactModel
|
||||
{
|
||||
[JsonProperty ("groupId")]
|
||||
public string GroupId { get; set; }
|
||||
|
||||
[JsonProperty ("artifactId")]
|
||||
public string ArtifactId { get; set; }
|
||||
|
||||
[JsonProperty ("version")]
|
||||
public string Version { get; set; }
|
||||
|
||||
[JsonProperty ("nugetVersion")]
|
||||
public string NugetVersion { get; set; }
|
||||
|
||||
[JsonProperty ("nugetId")]
|
||||
public string NugetId { get; set; }
|
||||
|
||||
[DefaultValue ("")]
|
||||
[JsonProperty ("dependencyOnly")]
|
||||
public bool DependencyOnly { get; set; }
|
||||
|
||||
[JsonProperty ("frozen")]
|
||||
public bool Frozen { get; set; }
|
||||
|
||||
[JsonProperty ("excludedRuntimeDependencies")]
|
||||
public string ExcludedRuntimeDependencies { get; set; }
|
||||
|
||||
[JsonProperty ("templateSet")]
|
||||
public string TemplateSet { get; set; }
|
||||
|
||||
[JsonProperty ("metadata")]
|
||||
public Dictionary<string, string> Metadata { get; set; } = new Dictionary<string, string> ();
|
||||
|
||||
public bool ShouldSerializeMetadata () => Metadata.Any ();
|
||||
}
|
||||
|
||||
public class MyArray
|
||||
{
|
||||
[JsonProperty ("mavenRepositoryType")]
|
||||
public MavenRepoType MavenRepositoryType { get; set; }
|
||||
|
||||
[JsonProperty ("mavenRepositoryLocation")]
|
||||
public string MavenRepositoryLocation { get; set; }
|
||||
|
||||
[JsonProperty ("slnFile")]
|
||||
public string SlnFile { get; set; }
|
||||
|
||||
[JsonProperty ("strictRuntimeDependencies")]
|
||||
public bool StrictRuntimeDependencies { get; set; }
|
||||
|
||||
[JsonProperty ("excludedRuntimeDependencies")]
|
||||
public string ExcludedRuntimeDependencies { get; set; }
|
||||
|
||||
[JsonProperty ("additionalProjects")]
|
||||
public List<string> AdditionalProjects { get; set; }
|
||||
|
||||
[JsonProperty ("templates")]
|
||||
public List<Template> Templates { get; set; }
|
||||
|
||||
[JsonProperty ("artifacts")]
|
||||
public List<ArtifactModel> Artifacts { get; set; }
|
||||
|
||||
[JsonProperty ("templateSets")]
|
||||
public List<TemplateSetModel> TemplateSets { get; set; } = new List<TemplateSetModel> ();
|
||||
|
||||
public TemplateSetModel GetTemplateSet (string name)
|
||||
{
|
||||
// If an artifact doesn't specify a template set, first try using the original
|
||||
// single template list if it exists. If not, look for a template set called "default".
|
||||
if (string.IsNullOrEmpty (name)) {
|
||||
if (Templates.Any ())
|
||||
return new TemplateSetModel { Templates = Templates };
|
||||
|
||||
name = "default";
|
||||
}
|
||||
|
||||
var set = TemplateSets.FirstOrDefault (s => s.Name == name);
|
||||
|
||||
if (set == null)
|
||||
throw new ArgumentException ($"Could not find requested template set '{name}'");
|
||||
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
||||
public class Root
|
||||
{
|
||||
[JsonProperty ("MyArray")]
|
||||
public List<MyArray> MyArray { get; set; }
|
||||
}
|
||||
|
||||
public enum MavenRepoType
|
||||
{
|
||||
Url,
|
||||
Directory,
|
||||
Google,
|
||||
MavenCentral
|
||||
}
|
||||
|
||||
public class Options
|
||||
{
|
||||
public string ConfigFile { get; }
|
||||
public bool Update { get; }
|
||||
public bool Bump { get; }
|
||||
public bool Sort { get; }
|
||||
public bool Published { get; }
|
||||
public List<string> DependencyConfigs { get; }
|
||||
|
||||
public Options (IList<string> args)
|
||||
{
|
||||
// Config file must always be the first argument
|
||||
ConfigFile = args [0];
|
||||
|
||||
Update = args.Any (a => a.ToLowerInvariant () == "update");
|
||||
Bump = args.Any (a => a.ToLowerInvariant () == "bump");
|
||||
Sort = args.Any (a => a.ToLowerInvariant () == "sort");
|
||||
Published = args.Any (a => a.ToLowerInvariant () == "published");
|
||||
|
||||
DependencyConfigs = args.Where (a => a.StartsWith ("-dep:")).Select (a => a.Substring (5)).ToList ();
|
||||
}
|
||||
|
||||
public bool ShouldWriteOutput => Update || Bump || Sort;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,412 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ExtendedTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Functions for dealing with the specially formatted errors returned by
|
||||
/// build tools.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Various tools produce and consume CanonicalErrors in various formats.
|
||||
///
|
||||
/// DEVENV Format When Clicking on Items in the Output Window
|
||||
/// (taken from env\msenv\core\findutil.cpp ParseLocation function)
|
||||
///
|
||||
/// v:\dir\file.ext (loc) : msg
|
||||
/// \\server\share\dir\file.ext(loc):msg
|
||||
/// url
|
||||
///
|
||||
/// loc:
|
||||
/// (line)
|
||||
/// (line-line)
|
||||
/// (line,col)
|
||||
/// (line,col-col)
|
||||
/// (line,col,len)
|
||||
/// (line,col,line,col)
|
||||
///
|
||||
/// DevDiv Build Process
|
||||
/// (taken from tools\devdiv2.def)
|
||||
///
|
||||
/// To echo warnings and errors to the build console, the
|
||||
/// "description block" must be recognized by build. To do this,
|
||||
/// add a $(ECHO_COMPILING_COMMAND) or $(ECHO_PROCESSING_COMMAND)
|
||||
/// to the first line of the description block, e.g.
|
||||
///
|
||||
/// $(ECHO_COMPILING_CMD) Resgen_$<
|
||||
///
|
||||
/// Errors must have the format:
|
||||
///
|
||||
/// <text> : error [num]: <msg>
|
||||
///
|
||||
/// Warnings must have the format:
|
||||
///
|
||||
/// <text> : warning [num]: <msg>
|
||||
/// </remarks>
|
||||
internal static class CanonicalError
|
||||
{
|
||||
// Defines the main pattern for matching messages.
|
||||
private static readonly Lazy<Regex> s_originCategoryCodeTextExpression = new Lazy<Regex> (
|
||||
() => new Regex
|
||||
(
|
||||
// Beginning of line and any amount of whitespace.
|
||||
@"^\s*"
|
||||
// Match a [optional project number prefix 'ddd>'], single letter + colon + remaining filename, or
|
||||
// string with no colon followed by a colon.
|
||||
+ @"(((?<ORIGIN>(((\d+>)?[a-zA-Z]?:[^:]*)|([^:]*))):)"
|
||||
// Origin may also be empty. In this case there's no trailing colon.
|
||||
+ "|())"
|
||||
// Match the empty string or a string without a colon that ends with a space
|
||||
+ "(?<SUBCATEGORY>(()|([^:]*? )))"
|
||||
// Match 'error' or 'warning'.
|
||||
+ @"(?<CATEGORY>(error|warning))"
|
||||
// Match anything starting with a space that's not a colon/space, followed by a colon.
|
||||
// Error code is optional in which case "error"/"warning" can be followed immediately by a colon.
|
||||
+ @"( \s*(?<CODE>[^: ]*))?\s*:"
|
||||
// Whatever's left on this line, including colons.
|
||||
+ "(?<TEXT>.*)$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled
|
||||
));
|
||||
|
||||
private static readonly Lazy<Regex> s_originCategoryCodeTextExpression2 = new Lazy<Regex> (
|
||||
() => new Regex
|
||||
(
|
||||
@"^\s*(?<ORIGIN>(?<FILENAME>.*):(?<LOCATION>(?<LINE>[0-9]*):(?<COLUMN>[0-9]*))):(?<CATEGORY> error| warning):(?<TEXT>.*)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled
|
||||
));
|
||||
|
||||
// Matches and extracts filename and location from an 'origin' element.
|
||||
private static readonly Lazy<Regex> s_filenameLocationFromOrigin = new Lazy<Regex> (
|
||||
() => new Regex
|
||||
(
|
||||
"^" // Beginning of line
|
||||
+ @"(\d+>)?" // Optional ddd> project number prefix
|
||||
+ "(?<FILENAME>.*)" // Match anything.
|
||||
+ @"\(" // Find a parenthesis.
|
||||
+ @"(?<LOCATION>[\,,0-9,-]*)" // Match any combination of numbers and ',' and '-'
|
||||
+ @"\)\s*" // Find the closing paren then any amount of spaces.
|
||||
+ "$", // End-of-line
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled
|
||||
));
|
||||
|
||||
// Matches location that is a simple number.
|
||||
private static readonly Lazy<Regex> s_lineFromLocation = new Lazy<Regex> (
|
||||
() => new Regex // Example: line
|
||||
(
|
||||
"^" // Beginning of line
|
||||
+ "(?<LINE>[0-9]*)" // Match any number.
|
||||
+ "$", // End-of-line
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled
|
||||
));
|
||||
|
||||
// Matches location that is a range of lines.
|
||||
private static readonly Lazy<Regex> s_lineLineFromLocation = new Lazy<Regex> (
|
||||
() => new Regex // Example: line-line
|
||||
(
|
||||
"^" // Beginning of line
|
||||
+ "(?<LINE>[0-9]*)" // Match any number.
|
||||
+ "-" // Dash
|
||||
+ "(?<ENDLINE>[0-9]*)" // Match any number.
|
||||
+ "$", // End-of-line
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled
|
||||
));
|
||||
|
||||
// Matches location that is a line and column
|
||||
private static readonly Lazy<Regex> s_lineColFromLocation = new Lazy<Regex> (
|
||||
() => new Regex // Example: line,col
|
||||
(
|
||||
"^" // Beginning of line
|
||||
+ "(?<LINE>[0-9]*)" // Match any number.
|
||||
+ "," // Comma
|
||||
+ "(?<COLUMN>[0-9]*)" // Match any number.
|
||||
+ "$", // End-of-line
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled
|
||||
));
|
||||
|
||||
// Matches location that is a line and column-range
|
||||
private static readonly Lazy<Regex> s_lineColColFromLocation = new Lazy<Regex> (
|
||||
() => new Regex // Example: line,col-col
|
||||
(
|
||||
"^" // Beginning of line
|
||||
+ "(?<LINE>[0-9]*)" // Match any number.
|
||||
+ "," // Comma
|
||||
+ "(?<COLUMN>[0-9]*)" // Match any number.
|
||||
+ "-" // Dash
|
||||
+ "(?<ENDCOLUMN>[0-9]*)" // Match any number.
|
||||
+ "$", // End-of-line
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled
|
||||
));
|
||||
|
||||
// Matches location that is line,col,line,col
|
||||
private static readonly Lazy<Regex> s_lineColLineColFromLocation = new Lazy<Regex> (
|
||||
() => new Regex // Example: line,col,line,col
|
||||
(
|
||||
"^" // Beginning of line
|
||||
+ "(?<LINE>[0-9]*)" // Match any number.
|
||||
+ "," // Comma
|
||||
+ "(?<COLUMN>[0-9]*)" // Match any number.
|
||||
+ "," // Dash
|
||||
+ "(?<ENDLINE>[0-9]*)" // Match any number.
|
||||
+ "," // Dash
|
||||
+ "(?<ENDCOLUMN>[0-9]*)" // Match any number.
|
||||
+ "$", // End-of-line
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled
|
||||
));
|
||||
|
||||
/// <summary>
|
||||
/// Represents the parts of a decomposed canonical message.
|
||||
/// </summary>
|
||||
internal sealed class Parts
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the error category\severity level.
|
||||
/// </summary>
|
||||
internal enum Category
|
||||
{
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value used for unspecified line and column numbers, which are 1-relative.
|
||||
/// </summary>
|
||||
internal const int numberNotSpecified = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Parts"/> class.
|
||||
/// </summary>
|
||||
internal Parts ()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of the file or tool (not localized)
|
||||
/// </summary>
|
||||
internal string origin;
|
||||
|
||||
/// <summary>
|
||||
/// The line number.
|
||||
/// </summary>
|
||||
internal int line = numberNotSpecified;
|
||||
|
||||
/// <summary>
|
||||
/// The column number.
|
||||
/// </summary>
|
||||
internal int column = numberNotSpecified;
|
||||
|
||||
/// <summary>
|
||||
/// The ending line number.
|
||||
/// </summary>
|
||||
internal int endLine = numberNotSpecified;
|
||||
|
||||
/// <summary>
|
||||
/// The ending column number.
|
||||
/// </summary>
|
||||
internal int endColumn = numberNotSpecified;
|
||||
|
||||
/// <summary>
|
||||
/// The category/severity level
|
||||
/// </summary>
|
||||
internal Category category;
|
||||
|
||||
/// <summary>
|
||||
/// The sub category (localized)
|
||||
/// </summary>
|
||||
internal string subcategory;
|
||||
|
||||
/// <summary>
|
||||
/// The error code (not localized)
|
||||
/// </summary>
|
||||
internal string code;
|
||||
|
||||
/// <summary>
|
||||
/// The error message text (localized)
|
||||
/// </summary>
|
||||
internal string text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A small custom int conversion method that treats invalid entries as missing (0). This is done to work around tools
|
||||
/// that don't fully conform to the canonical message format - we still want to salvage what we can from the message.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns>'value' converted to int or 0 if it can't be parsed or is negative</returns>
|
||||
private static int ConvertToIntWithDefault (string value)
|
||||
{
|
||||
int result;
|
||||
var success = int.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
|
||||
|
||||
if (!success || result < 0) {
|
||||
result = Parts.numberNotSpecified;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompose an error or warning message into constituent parts. If the message isn't in the canonical form, return null.
|
||||
/// </summary>
|
||||
/// <remarks>This method is thread-safe, because the Regex class is thread-safe (per MSDN).</remarks>
|
||||
/// <param name="message"></param>
|
||||
/// <returns>Decomposed canonical message, or null.</returns>
|
||||
internal static Parts Parse (string message)
|
||||
{
|
||||
// An unusually long string causes pathologically slow Regex back-tracking.
|
||||
// To avoid that, only scan the first 400 characters. That's enough for
|
||||
// the longest possible prefix: MAX_PATH, plus a huge subcategory string, and an error location.
|
||||
// After the regex is done, we can append the overflow.
|
||||
var messageOverflow = string.Empty;
|
||||
if (message.Length > 400) {
|
||||
messageOverflow = message.Substring (400);
|
||||
message = message.Substring (0, 400);
|
||||
}
|
||||
|
||||
// If a tool has a large amount of output that isn't an error or warning (eg., "dir /s %hugetree%")
|
||||
// the regex below is slow. It's faster to pre-scan for "warning" and "error"
|
||||
// and bail out if neither are present.
|
||||
if (message.IndexOf ("warning", StringComparison.OrdinalIgnoreCase) == -1 &&
|
||||
message.IndexOf ("error", StringComparison.OrdinalIgnoreCase) == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var parsedMessage = new Parts ();
|
||||
|
||||
// First, split the message into three parts--Origin, Category, Code, Text.
|
||||
// Example,
|
||||
// Main.cs(17,20):Command line warning CS0168: The variable 'foo' is declared but never used
|
||||
// -------------- ------------ ------- ------ ----------------------------------------------
|
||||
// Origin SubCategory Cat. Code Text
|
||||
//
|
||||
// To accommodate absolute filenames in Origin, tolerate a colon in the second position
|
||||
// as long as its preceded by a letter.
|
||||
//
|
||||
// Localization Note:
|
||||
// Even in foreign-language versions of tools, the category field needs to be in English.
|
||||
// Also, if origin is a tool name, then that needs to be in English.
|
||||
//
|
||||
// Here's an example from the Japanese version of CL.EXE:
|
||||
// cl : ???? ??? warning D4024 : ?????????? 'AssemblyInfo.cs' ?????????????????? ???????????
|
||||
//
|
||||
// Here's an example from the Japanese version of LINK.EXE:
|
||||
// AssemblyInfo.cpp : fatal error LNK1106: ???????????? ??????????????: 0x6580 ??????????
|
||||
//
|
||||
var match = s_originCategoryCodeTextExpression.Value.Match (message);
|
||||
string category;
|
||||
if (!match.Success) {
|
||||
// try again with the Clang/GCC matcher
|
||||
// Example,
|
||||
// err.cpp:6:3: error: use of undeclared identifier 'force_an_error'
|
||||
// ----------- ----- ---------------------------------------------
|
||||
// Origin Cat. Text
|
||||
match = s_originCategoryCodeTextExpression2.Value.Match (message);
|
||||
if (!match.Success) {
|
||||
return null;
|
||||
}
|
||||
|
||||
category = match.Groups ["CATEGORY"].Value.Trim ();
|
||||
if (string.Equals (category, "error", StringComparison.OrdinalIgnoreCase)) {
|
||||
parsedMessage.category = Parts.Category.Error;
|
||||
} else if (string.Equals (category, "warning", StringComparison.OrdinalIgnoreCase)) {
|
||||
parsedMessage.category = Parts.Category.Warning;
|
||||
} else {
|
||||
// Not an error\warning message.
|
||||
return null;
|
||||
}
|
||||
parsedMessage.line = ConvertToIntWithDefault (match.Groups ["LINE"].Value.Trim ());
|
||||
parsedMessage.column = ConvertToIntWithDefault (match.Groups ["COLUMN"].Value.Trim ());
|
||||
parsedMessage.text = (match.Groups ["TEXT"].Value + messageOverflow).Trim ();
|
||||
parsedMessage.origin = match.Groups ["FILENAME"].Value.Trim ();
|
||||
|
||||
var explodedText = parsedMessage.text.Split ('\'', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (explodedText.Length > 0) {
|
||||
parsedMessage.code = "G" + explodedText [0].GetHashCode ().ToString ("X8");
|
||||
} else {
|
||||
parsedMessage.code = "G00000000";
|
||||
}
|
||||
|
||||
return parsedMessage;
|
||||
}
|
||||
|
||||
var origin = match.Groups ["ORIGIN"].Value.Trim ();
|
||||
category = match.Groups ["CATEGORY"].Value.Trim ();
|
||||
parsedMessage.code = match.Groups ["CODE"].Value.Trim ();
|
||||
parsedMessage.text = (match.Groups ["TEXT"].Value + messageOverflow).Trim ();
|
||||
parsedMessage.subcategory = match.Groups ["SUBCATEGORY"].Value.Trim ();
|
||||
|
||||
// Next, see if category is something that is recognized.
|
||||
if (string.Equals (category, "error", StringComparison.OrdinalIgnoreCase)) {
|
||||
parsedMessage.category = Parts.Category.Error;
|
||||
} else if (string.Equals (category, "warning", StringComparison.OrdinalIgnoreCase)) {
|
||||
parsedMessage.category = Parts.Category.Warning;
|
||||
} else {
|
||||
// Not an error\warning message.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Origin is not a simple file, but it still could be of the form,
|
||||
// foo.cpp(location)
|
||||
match = s_filenameLocationFromOrigin.Value.Match (origin);
|
||||
|
||||
if (match.Success) {
|
||||
// The origin is in the form,
|
||||
// foo.cpp(location)
|
||||
// Assume the filename exists, but don't verify it. What else could it be?
|
||||
var location = match.Groups ["LOCATION"].Value.Trim ();
|
||||
parsedMessage.origin = match.Groups ["FILENAME"].Value.Trim ();
|
||||
|
||||
// Now, take apart the location. It can be one of these:
|
||||
// loc:
|
||||
// (line)
|
||||
// (line-line)
|
||||
// (line,col)
|
||||
// (line,col-col)
|
||||
// (line,col,len)
|
||||
// (line,col,line,col)
|
||||
if (location.Length > 0) {
|
||||
match = s_lineFromLocation.Value.Match (location);
|
||||
if (match.Success) {
|
||||
parsedMessage.line = ConvertToIntWithDefault (match.Groups ["LINE"].Value.Trim ());
|
||||
} else {
|
||||
match = s_lineLineFromLocation.Value.Match (location);
|
||||
if (match.Success) {
|
||||
parsedMessage.line = ConvertToIntWithDefault (match.Groups ["LINE"].Value.Trim ());
|
||||
parsedMessage.endLine = ConvertToIntWithDefault (match.Groups ["ENDLINE"].Value.Trim ());
|
||||
} else {
|
||||
match = s_lineColFromLocation.Value.Match (location);
|
||||
if (match.Success) {
|
||||
parsedMessage.line = ConvertToIntWithDefault (match.Groups ["LINE"].Value.Trim ());
|
||||
parsedMessage.column = ConvertToIntWithDefault (match.Groups ["COLUMN"].Value.Trim ());
|
||||
} else {
|
||||
match = s_lineColColFromLocation.Value.Match (location);
|
||||
if (match.Success) {
|
||||
parsedMessage.line = ConvertToIntWithDefault (match.Groups ["LINE"].Value.Trim ());
|
||||
parsedMessage.column = ConvertToIntWithDefault (match.Groups ["COLUMN"].Value.Trim ());
|
||||
parsedMessage.endColumn = ConvertToIntWithDefault (match.Groups ["ENDCOLUMN"].Value.Trim ());
|
||||
} else {
|
||||
match = s_lineColLineColFromLocation.Value.Match (location);
|
||||
if (match.Success) {
|
||||
parsedMessage.line = ConvertToIntWithDefault (match.Groups ["LINE"].Value.Trim ());
|
||||
parsedMessage.column = ConvertToIntWithDefault (match.Groups ["COLUMN"].Value.Trim ());
|
||||
parsedMessage.endLine = ConvertToIntWithDefault (match.Groups ["ENDLINE"].Value.Trim ());
|
||||
parsedMessage.endColumn = ConvertToIntWithDefault (match.Groups ["ENDCOLUMN"].Value.Trim ());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The origin does not fit the filename(location) pattern.
|
||||
parsedMessage.origin = origin;
|
||||
}
|
||||
|
||||
return parsedMessage;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(_DefaultNetTargetFrameworks)</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliWrap" Version="3.6.6" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,24 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.10.34902.84
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtendedTests", "ExtendedTests.csproj", "{CB699113-2DD2-49D2-84F4-F6738E76D10D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {32519318-4EF6-4DCE-9E70-381F824180F5}
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{CB699113-2DD2-49D2-84F4-F6738E76D10D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CB699113-2DD2-49D2-84F4-F6738E76D10D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CB699113-2DD2-49D2-84F4-F6738E76D10D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CB699113-2DD2-49D2-84F4-F6738E76D10D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,182 @@
|
|||
using System.Text;
|
||||
using CliWrap;
|
||||
using CliWrap.Buffered;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace ExtendedTests;
|
||||
|
||||
[TestFixture]
|
||||
public class TestAllIndividualPackages
|
||||
{
|
||||
static string base_dir = "";
|
||||
static string test_dir = @"output\tests";
|
||||
static string configuration = "Release";
|
||||
static string platform_version = "29";
|
||||
static string net_version = "net8.0";
|
||||
|
||||
static TestAllIndividualPackages ()
|
||||
{
|
||||
// Find the repo base directory
|
||||
while (!File.Exists (Path.Combine (base_dir, "config.json")))
|
||||
base_dir = Path.Combine ("..", base_dir);
|
||||
|
||||
// Create the test directory
|
||||
Directory.CreateDirectory (Path.Combine (base_dir, test_dir));
|
||||
|
||||
// We need a Directory.Build.props file so we don't use the root one, it also
|
||||
// needs to turn off NuGet Central Package Management.
|
||||
var directory_props = Path.Combine (base_dir, test_dir, "Directory.Build.props");
|
||||
|
||||
var props_content = """
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
""";
|
||||
|
||||
if (!File.Exists (directory_props))
|
||||
File.WriteAllText (directory_props, props_content);
|
||||
|
||||
// Set up a NuGet.config file that allows us to use the locally built NuGet packages.
|
||||
// Note we also need to allow things to come from NuGet.org (*) in order to test when
|
||||
// NuGet resolves a mix of the new local packages and existing ones published on NuGet.org.
|
||||
var nuget_config_src = Path.Combine (base_dir, "samples", "NuGet.config");
|
||||
var nuget_config_dst = Path.Combine (base_dir, test_dir, "NuGet.config");
|
||||
|
||||
if (!File.Exists (nuget_config_dst)) {
|
||||
var contents = File.ReadAllText (nuget_config_src);
|
||||
contents = contents.Replace ("../output", "..");
|
||||
contents = contents.Replace ("../packages", "packages");
|
||||
contents = contents.Replace ("Microsoft.Android.Ref.*", "*");
|
||||
File.WriteAllText (nuget_config_dst, contents);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category ("Android")]
|
||||
[TestCaseSource (nameof (GetPackagesToTest))]
|
||||
public Task TestAndroidDotNetPackage (string id, string version)
|
||||
=> TestPackage (id, version, "android");
|
||||
|
||||
[Test]
|
||||
[Category ("MAUI")]
|
||||
[TestCaseSource (nameof (GetPackagesToTest))]
|
||||
public Task TestMauiPackage (string id, string version)
|
||||
=> TestPackage (id, version, "maui");
|
||||
|
||||
public static object [] GetPackagesToTest ()
|
||||
{
|
||||
var config_file = Path.Combine (base_dir, "config.json");
|
||||
var config = BinderatorConfigFileParser.ParseConfigurationFile (config_file).Result;
|
||||
|
||||
return config.FirstOrDefault ()?.Artifacts?.Where (a => !a.DependencyOnly).Select (a => new object [] { a.NugetId, a.NugetVersion }).ToArray () ?? Array.Empty<object> ();
|
||||
}
|
||||
|
||||
async Task TestPackage (string id, string version, string template)
|
||||
{
|
||||
var case_dir = Path.Combine (base_dir, test_dir, template, $"{id}Test");
|
||||
|
||||
// Test the package
|
||||
if (Directory.Exists (case_dir))
|
||||
Directory.Delete (case_dir, true);
|
||||
|
||||
Directory.CreateDirectory (case_dir);
|
||||
|
||||
// Create new dotnet project
|
||||
await RunAndAssertSuccess ($"new {template}", case_dir);
|
||||
|
||||
// - Replace <SupportedOSPlatformVersion> with the maximum version some packages require
|
||||
// - Remove the target frameworks that are not 'android'
|
||||
var proj_file = Directory.GetFiles (case_dir, "*.csproj").FirstOrDefault ();
|
||||
|
||||
if (proj_file is not null) {
|
||||
ReplaceInFile (proj_file, ">21</SupportedOSPlatformVersion>", $">{platform_version}</SupportedOSPlatformVersion>");
|
||||
ReplaceInFile (proj_file, ">21.0</SupportedOSPlatformVersion>", $">{platform_version}</SupportedOSPlatformVersion>");
|
||||
ReplaceInFile (proj_file, $";{net_version}-ios", "");
|
||||
ReplaceInFile (proj_file, $";{net_version}-maccatalyst", "");
|
||||
ReplaceInFile (proj_file, $";{net_version}-windows10.0.19041.0", "");
|
||||
}
|
||||
|
||||
// Add the package
|
||||
await RunAndAssertSuccess ($"add package {id} --version {version}", case_dir);
|
||||
|
||||
// Build the project
|
||||
await RunAndAssertSuccess ($"build -c {configuration} -bl", case_dir, true);
|
||||
|
||||
// If we're here, everything succeeded, so try to clean up the project directory
|
||||
try {
|
||||
Directory.Delete (case_dir, true);
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
static void ReplaceInFile (string filename, string oldValue, string newValue)
|
||||
{
|
||||
var contents = File.ReadAllText (filename);
|
||||
contents = contents.Replace (oldValue, newValue);
|
||||
File.WriteAllText (filename, contents);
|
||||
}
|
||||
|
||||
static async Task RunAndAssertSuccess (string arguments, string workingDir, bool isMSBuild = false)
|
||||
{
|
||||
var result = await Cli.Wrap ("dotnet")
|
||||
.WithArguments (arguments)
|
||||
.WithWorkingDirectory (workingDir)
|
||||
.WithValidation (CommandResultValidation.None)
|
||||
.ExecuteBufferedAsync ();
|
||||
|
||||
if (result.ExitCode == 0)
|
||||
return;
|
||||
|
||||
var sb = new StringBuilder ();
|
||||
|
||||
sb.AppendLine ($"Command '{arguments}' failed with exit code {result.ExitCode}.");
|
||||
|
||||
if (!isMSBuild) {
|
||||
sb.AppendLine ("Output:");
|
||||
sb.AppendLine (result.StandardOutput);
|
||||
sb.AppendLine ();
|
||||
sb.AppendLine ("Error:");
|
||||
sb.AppendLine (result.StandardError);
|
||||
|
||||
Assert.Fail (sb.ToString ());
|
||||
}
|
||||
|
||||
var errors = new List<string> ();
|
||||
var warnings = new List<string> ();
|
||||
|
||||
using (var sr = new StringReader (result.StandardOutput)) {
|
||||
string? line;
|
||||
|
||||
while ((line = sr.ReadLine ()) != null) {
|
||||
|
||||
// MSBuild prints all the messages out again after this message
|
||||
if (line == "Build succeeded." || line == "Build FAILED.")
|
||||
break;
|
||||
|
||||
if (CanonicalError.Parse (line) is CanonicalError.Parts parts) {
|
||||
var message = $"{parts.code}: {parts.text}";
|
||||
|
||||
if (parts.category == CanonicalError.Parts.Category.Warning)
|
||||
warnings.Add (message);
|
||||
else
|
||||
errors.Add (message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.Count > 0) {
|
||||
sb.AppendLine ("Errors:");
|
||||
errors.ForEach (e => sb.AppendLine (e));
|
||||
}
|
||||
|
||||
if (warnings.Count > 0) {
|
||||
sb.AppendLine ("Warnings:");
|
||||
warnings.ForEach (w => sb.AppendLine (w));
|
||||
}
|
||||
|
||||
Assert.Fail (sb.ToString ());
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче