You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2019/08/07 22:26:03 UTC

[lucenenet] 06/42: Created azure-pipelines.yml for Azure DevOps

This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git

commit 9f3fb72eb8678cd5e41326e4f26b6c83a928bc59
Author: Shad Storhaug <sh...@shadstorhaug.com>
AuthorDate: Sun Jul 14 02:12:20 2019 +0700

    Created azure-pipelines.yml for Azure DevOps
---
 Directory.Build.props                              |  10 +
 Directory.Build.targets                            |  27 ++
 Lucene.Net.sln                                     |  10 +-
 azure-pipelines.yml                                | 377 +++++++++++++++++++++
 build/Dependencies.props                           |  20 ++
 build/TestReferences.Common.targets                |  20 ++
 build/azure-templates/publish-nuget-packages.yml   |  64 ++++
 .../publish-test-results-for-target-frameworks.yml |  67 ++++
 .../publish-test-results-for-test-projects.yml     | 282 +++++++++++++++
 build/azure-templates/publish-test-results.yml     |  81 +++++
 build/azure-templates/run-tests-on-os.yml          | 211 ++++++++++++
 .../show-all-environment-variables.yml             |  28 ++
 build/azure-templates/show-all-files.yml           |  26 ++
 build/build.ps1                                    | 314 ++++++++++++-----
 .../Analysis/Th/TestThaiAnalyzer.cs                |  42 +++
 15 files changed, 1496 insertions(+), 83 deletions(-)

diff --git a/Directory.Build.props b/Directory.Build.props
index 31f0f94..16f9c18 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -37,6 +37,9 @@
 
   <PropertyGroup Label="NuGet Package Defaults">
     <IsPackable>false</IsPackable>
+    <IncludeSymbols>true</IncludeSymbols>
+    <!-- This is the new symbols format (the only one currently supported at NuGet.org) -->
+    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
   </PropertyGroup>
   
   <PropertyGroup Label="Copyright Info">
@@ -48,4 +51,11 @@
     <CopyrightYearRange Condition=" '$(BeginCopyrightYear)' == '$(CurrentYear)' ">$(CurrentYear)</CopyrightYearRange>
     <Copyright>Copyright © $(CopyrightYearRange) $(Company)</Copyright>
   </PropertyGroup>
+
+  <!-- Settings to override the above Version of Builds. These can be used to 
+      "freeze" the build number for a release, so whether building within 
+      an IDE or from the commmand line, the version is always what is 
+      in Version.props, if it exists and the PrepareForBuild argument 
+      passed into build.ps1 is 'false'. -->
+  <Import Project="Version.props" Condition="Exists('Version.props')" />
 </Project>
\ No newline at end of file
diff --git a/Directory.Build.targets b/Directory.Build.targets
new file mode 100644
index 0000000..04e1c76
--- /dev/null
+++ b/Directory.Build.targets
@@ -0,0 +1,27 @@
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+<Project>
+  <PropertyGroup>
+    <!-- NuGet.org only supports portable debug symbol format: 
+         https://docs.microsoft.com/en-us/nuget/create-packages/symbol-packages-snupkg#nugetorg-symbol-package-constraints -->
+    <DebugType Condition=" '$(PortableDebugTypeOnly)' == 'true' ">portable</DebugType>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/Lucene.Net.sln b/Lucene.Net.sln
index 904843e..bde08f5 100644
--- a/Lucene.Net.sln
+++ b/Lucene.Net.sln
@@ -1,4 +1,3 @@
-
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
 
@@ -30,6 +29,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "azure-templates", "azure-te
 		build\azure-templates\run-tests-on-os.yml = build\azure-templates\run-tests-on-os.yml
 		build\azure-templates\show-all-environment-variables.yml = build\azure-templates\show-all-environment-variables.yml
 		build\azure-templates\show-all-files.yml = build\azure-templates\show-all-files.yml
+		build\azure-templates\publish-test-results.yml = build\azure-templates\publish-test-results.yml
+		build\azure-templates\publish-test-results-for-target-frameworks.yml = build\azure-templates\publish-test-results-for-target-frameworks.yml
+		build\azure-templates\publish-test-results-for-test-projects.yml = build\azure-templates\publish-test-results-for-test-projects.yml
+		build\azure-templates\run-tests-on-os.yml = build\azure-templates\run-tests-on-os.yml
+		build\azure-templates\show-all-environment-variables.yml = build\azure-templates\show-all-environment-variables.yml
+		build\azure-templates\show-all-files.yml = build\azure-templates\show-all-files.yml
 	EndProjectSection
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet", "dotnet", "{8CA61D33-3590-4024-A304-7B1F75B50653}"
@@ -39,6 +44,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet", "dotnet", "{8CA61D
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{4016BDAB-6C33-4D1E-9439-57B416EA45D5}"
 	ProjectSection(SolutionItems) = preProject
+		azure-pipelines.yml = azure-pipelines.yml
 		build.bat = build.bat
 		build\build.ps1 = build\build.ps1
 		build\Dependencies.props = build\Dependencies.props
@@ -64,6 +70,7 @@ EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4DF0A2A1-B9C7-4EE5-BAF0-BEEF53E34220}"
 	ProjectSection(SolutionItems) = preProject
 		Directory.Build.props = Directory.Build.props
+		Directory.Build.targets = Directory.Build.targets
 	EndProjectSection
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lucene.Net", "src\Lucene.Net\Lucene.Net.csproj", "{3A0AA37E-2B7B-4416-B528-DA4E0E6A6706}"
@@ -401,6 +408,7 @@ Global
 		HideSolutionNode = FALSE
 	EndGlobalSection
 	GlobalSection(NestedProjects) = preSolution
+		{05CE3A39-40D4-452D-AFE0-E57E536A08C6} = {4016BDAB-6C33-4D1E-9439-57B416EA45D5}
 		{4DF7EACE-2B25-43F6-B558-8520BF20BD76} = {8CA61D33-3590-4024-A304-7B1F75B50653}
 		{EFB2E31A-5917-49D5-A808-FE5061A550B4} = {8CA61D33-3590-4024-A304-7B1F75B50653}
 		{119BBACD-D4DB-4E3B-922F-3DA83E0B29E2} = {4DF7EACE-2B25-43F6-B558-8520BF20BD76}
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 0000000..48ddfaf
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,377 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: 'vNext$(rev:.r)' # Format for build number (will be overridden)
+
+# DevOps Setup: Define the following pipeline level variables in Azure DevOps build pipeline
+#
+# ArtifactFeedID: (Optional - set to your Azure DevOps Artifact (NuGet) feed. If not provided, publish job will be skipped.)
+# BuildConfiguration: (Optional. Defaults to 'Release')
+# BuildPlatform: (Optional. Defaults to 'Any CPU')
+# IsRelease: (Optional. By default the Release job is disabled, setting this to 'true' will enable it)
+# RunTests: 'true' (Optional - set to 'false' to disable test jobs - useful for debugging. If not provided, tests will be run.)
+
+# Versioning Variables
+
+# BuildCounterSeed: (Optional - Set in conjunction with VersionSuffix, will cause the build counter to begin at this value. Note that it is set once, to reset is an API call.)
+# PackageVersion: (Optional - This can be used to explicitly set the whole version number to a specific version, i.e. 4.8.0-beta00005. It overrides all other version settings.)
+# PreReleaseCounterPattern: (Optional. Set to '0000000000' in ci pipeline or '00000' in release pipeline. The default is '0000000000'. This setting has no effect if VersionSuffix is ''.)
+# VersionSuffix: (Optional. Defaults to 'ci'. Set to 'beta' or 'rc' or '' in production pipeline.)
+
+variables:
+- name: BuildCounter
+  value: $[counter(variables['VersionSuffix'],coalesce(variables['BuildCounterSeed'], 1250))]
+- name: BinaryArtifactName
+  value: 'testbinaries'
+- name: NuGetArtifactName
+  value: 'nuget'
+- name: ReleaseArtifactName
+  value: 'release'
+- name: TestResultsArtifactName
+  value: 'testresults'
+- name: VersionArtifactName
+  value: 'version'
+- name: BuildNumberFileName
+  value: 'buildNumber.txt'
+- name: PackageVersionFileName
+  value: 'packageVersion.txt'
+- name: FileVersionFileName
+  value: 'fileVersion.txt'
+- name: BuildDirectory # Where the build scripts and configs are
+  value: '$(System.DefaultWorkingDirectory)/build'
+- name: PublishDirectory # Test binaries directory
+  value: '$(Build.ArtifactStagingDirectory)/$(BinaryArtifactName)'
+- name: NuGetArtifactDirectory # NuGet binaries directory
+  value: '$(Build.ArtifactStagingDirectory)/$(NuGetArtifactName)'
+- name: PublishedArtifactZipFileName
+  value: 'published.zip'
+
+stages:
+- stage: Build_Stage
+  displayName: 'Build Stage:'
+  jobs:
+
+  - job: Build
+    pool:
+      vmImage: 'windows-2019'
+
+    variables:
+      PublishTempDirectory: '$(Build.BinariesDirectory)/publish'
+
+    steps:
+    - powershell: |
+        $configuration = if ($env:BUILDCONFIGURATION) { $env:BUILDCONFIGURATION } else { "Release" }
+        Write-Host "##vso[task.setvariable variable=BuildConfiguration;]$configuration"
+      displayName: 'Setup Default Variable Values'
+
+    - task: DotNetCoreInstaller@0
+      displayName: 'Use .NET Core sdk 2.2.300'
+      inputs:
+        version: 2.2.300
+
+    - powershell: |
+        Import-Module "$(BuildDirectory)/psake.psm1"
+        $generateBuildBat = if ($Env:ISRELEASE -eq 'true') { 'true' } else { 'false' }
+        $parameters = @{}
+        $properties = @{
+            backup_files='false';
+            publish_directory='$(PublishTempDirectory)';
+            nuget_package_directory='$(NuGetArtifactDirectory)'
+            # Lock the build.bat so it only builds this version in the release distribution
+            generateBuildBat=$generateBuildBat
+        }
+        [string[]]$tasks = 'Pack'
+        if ($Env:RunTests -ne 'false') {
+            [string[]]$tasks = 'Pack','Publish'
+        }
+        Invoke-psake $(BuildDirectory)/build.ps1 -Task $tasks -properties $properties -parameters $parameters
+        exit !($psake.build_success)
+      displayName: 'PSake Build, Pack, and Publish'
+
+    - template: 'build/azure-templates/show-all-environment-variables.yml'
+
+    - task: ArchiveFiles@2
+      displayName: 'Zip $(PublishTempDirectory)'
+      inputs:
+        rootFolderOrFile: '$(PublishTempDirectory)'
+        includeRootFolder: false
+        archiveFile: '$(PublishDirectory)/$(PublishedArtifactZipFileName)'
+      condition: and(succeeded(), ne(variables['RunTests'], 'false'))
+
+    - powershell: |
+        $dir = '$(Build.ArtifactStagingDirectory)/$(VersionArtifactName)'
+        if (!(Test-Path $dir)) { New-Item -ItemType Directory -Path "$dir" -Force }
+        '$(PackageVersion)' | Out-File -FilePath "$dir/$(PackageVersionFileName)" -Force
+        '$(FileVersion)' | Out-File -FilePath "$dir/$(FileVersionFileName)" -Force
+        '$(Build.BuildNumber)' | Out-File -FilePath "$dir/$(BuildNumberFileName)" -Force
+      displayName: 'Write Versions to Files'
+
+    # If this is a release pipeline, copy the build.bat and Version.props files as version artifacts, which will 
+    # overwrite the build.bat and Version.props files of the release.
+    - task: CopyFiles@2
+      displayName: 'Copy build.bat and Version.props Files to: /$(VersionArtifactName)'
+      inputs:
+        SourceFolder: '$(System.DefaultWorkingDirectory)'
+        Contents: |
+          build.bat
+          Version.props
+        TargetFolder: '$(Build.ArtifactStagingDirectory)/$(VersionArtifactName)'
+      condition: and(succeeded(), eq(variables['IsRelease'], 'true'))
+
+    - task: PublishBuildArtifacts@1
+      displayName: 'Publish Artifact: $(VersionArtifactName)'
+      inputs:
+        PathtoPublish: '$(Build.ArtifactStagingDirectory)/$(VersionArtifactName)'
+        ArtifactName: '$(VersionArtifactName)'
+
+    # Copy the .pdb files as build artifacts, which will 
+    # later be used to push to the Azure Artifacts symbol server.
+    - task: CopyFiles@2
+      displayName: 'Copy .pdb Files to: /$(NuGetArtifactName)'
+      inputs:
+        SourceFolder: '$(System.DefaultWorkingDirectory)'
+        Contents: '**/bin/$(BuildConfiguration)/**/*.pdb'
+        TargetFolder: '$(Build.ArtifactStagingDirectory)/$(NuGetArtifactName)'
+
+    - task: PublishBuildArtifacts@1
+      displayName: 'Publish Artifact: $(BinaryArtifactName)'
+      inputs:
+        PathtoPublish: '$(Build.ArtifactStagingDirectory)/$(BinaryArtifactName)'
+        ArtifactName: '$(BinaryArtifactName)'
+      condition: and(succeeded(), ne(variables['RunTests'], 'false'))
+
+    - task: PublishBuildArtifacts@1
+      displayName: 'Publish Artifact: $(NuGetArtifactName)'
+      inputs:
+        PathtoPublish: '$(Build.ArtifactStagingDirectory)/$(NuGetArtifactName)'
+        ArtifactName: '$(NuGetArtifactName)'
+
+
+- stage: Test_Stage
+  displayName: 'Test Stage:'
+  jobs:
+  - job: Test_netcoreapp2_1
+    condition: and(succeeded(), ne(variables['RunTests'], 'false'))
+    strategy:
+      matrix:
+        Windows:
+          osName: 'Windows'
+          imageName: 'windows-2019'
+          maximumAllowedFailures: 4 # Maximum allowed failures for a successful build
+        Linux:
+          osName: 'Linux'
+          imageName: 'ubuntu-16.04'
+          maximumAllowedFailures: 30 # Maximum allowed failures for a successful build
+        macOS:
+          osName: 'macOS'
+          imageName: 'macOS-10.14'
+          maximumAllowedFailures: 30 # Maximum allowed failures for a successful build
+    displayName: 'Test netcoreapp2.1 on'
+    pool:
+      vmImage: $(imageName)
+    steps:
+    - template: 'build/azure-templates/run-tests-on-os.yml'
+      parameters:
+        osName: $(osName)
+        testTargetFrameworks: 'netcoreapp2.1'
+        testResultsArtifactName: '$(TestResultsArtifactName)'
+        publishedArtifactZipFileName: '$(PublishedArtifactZipFileName)'
+        maximumParallelJobs: 8
+        maximumAllowedFailures: $(maximumAllowedFailures)
+
+  - job: Test_netcoreapp1_0
+    condition: and(succeeded(), ne(variables['RunTests'], 'false'))
+    strategy:
+      matrix:
+        Windows:
+          osName: 'Windows'
+          imageName: 'windows-2019'
+          maximumAllowedFailures: 4 # Maximum allowed failures for a successful build
+        Linux:
+          osName: 'Linux'
+          imageName: 'ubuntu-16.04'
+          maximumAllowedFailures: 30 # Maximum allowed failures for a successful build
+        macOS:
+          osName: 'macOS'
+          imageName: 'macOS-10.14'
+          maximumAllowedFailures: 30 # Maximum allowed failures for a successful build
+    displayName: 'Test netcoreapp1.0 on'
+    pool:
+      vmImage: $(imageName)
+    steps:
+    - template: 'build/azure-templates/run-tests-on-os.yml'
+      parameters:
+        osName: $(osName)
+        testTargetFrameworks: 'netcoreapp1.0'
+        testResultsArtifactName: '$(TestResultsArtifactName)'
+        publishedArtifactZipFileName: '$(PublishedArtifactZipFileName)'
+        maximumParallelJobs: 8
+        maximumAllowedFailures: $(maximumAllowedFailures)
+
+  - job: Test_net451
+    condition: and(succeeded(), ne(variables['RunTests'], 'false'))
+    displayName: 'Test net451 on Windows'
+    pool:
+      vmImage: 'windows-2019'
+    steps:
+    - template: 'build/azure-templates/run-tests-on-os.yml'
+      parameters:
+        osName: 'Windows'
+        testTargetFrameworks: 'net451'
+        testResultsArtifactName: '$(TestResultsArtifactName)'
+        publishedArtifactZipFileName: '$(PublishedArtifactZipFileName)'
+        maximumParallelJobs: 8
+        maximumAllowedFailures: 4 # Maximum allowed failures for a successful build
+
+
+- stage: Publish_Stage
+  displayName: 'Publish Stage:'
+  jobs:
+
+  # Optional job to push to Azure Artifact feed. Just pass in
+  # the GUID of the artifact feed as ArtifactFeedID to enable.
+  - job: Publish_Azure_Artifacts
+    condition: and(succeeded(), ne(variables['ArtifactFeedID'], ''))
+    pool:
+      vmImage: 'windows-2019'
+
+    steps:
+    - template: 'build/azure-templates/show-all-environment-variables.yml'
+
+    - task: DownloadBuildArtifacts@0
+      displayName: 'Download Build Artifacts: $(VersionArtifactName)'
+      inputs:
+        artifactName: '$(VersionArtifactName)'
+        downloadPath: '$(Build.ArtifactStagingDirectory)'
+
+      # NOTE: We are setting Build.BuildNumber here to the NuGet package version to work around the limitation that
+      # the version cannot be passed to the Index Sources & Publish Symbols task.
+    - powershell: |
+        $version = Get-Content '$(Build.ArtifactStagingDirectory)/$(VersionArtifactName)/$(PackageVersionFileName)' -Raw
+        Write-Host "##vso[task.setvariable variable=PackageVersion;]$version"
+        Write-Host "##vso[task.setvariable variable=Build.BuildNumber;]$version"
+      displayName: 'Read PackageVersion from File to Build.BuildNumber'
+
+    - template: 'build/azure-templates/show-all-environment-variables.yml'
+
+    - template: 'build/azure-templates/publish-nuget-packages.yml'
+      parameters:
+        artifactFeedID: '$(ArtifactFeedID)'
+        nugetArtifactName: '$(NuGetArtifactName)'
+
+
+- stage: Release_Stage
+  displayName: 'Release Stage:'
+  jobs:
+  - job: Release
+    condition: and(succeeded(), eq(variables['IsRelease'], 'true'))
+    displayName: 'Build Release Artifacts for [VOTE]'
+    pool:
+      vmImage: 'windows-2019'
+
+    steps:
+    - template: 'build/azure-templates/show-all-environment-variables.yml'
+
+    - task: DownloadBuildArtifacts@0
+      displayName: 'Download Build Artifacts: $(NuGetArtifactName)'
+      inputs:
+        artifactName: '$(NuGetArtifactName)'
+        downloadPath: '$(Build.ArtifactStagingDirectory)'
+
+    - task: DownloadBuildArtifacts@0
+      displayName: 'Download Build Artifacts: $(VersionArtifactName)'
+      inputs:
+        artifactName: '$(VersionArtifactName)'
+        downloadPath: '$(Build.ArtifactStagingDirectory)'
+
+    - template: 'build/azure-templates/show-all-files.yml' # Uncomment for debugging
+
+      # NOTE: We are setting Build.BuildNumber here to the NuGet package version to work around the limitation that
+      # the version cannot be passed to the Index Sources & Publish Symbols task.
+    - powershell: |
+        $version = Get-Content '$(Build.ArtifactStagingDirectory)/$(VersionArtifactName)/$(PackageVersionFileName)' -Raw
+        $vcsLabel = 'Lucene.Net_' + $version.Replace('.', '_').Replace('-', '_')
+        Write-Host "##vso[task.setvariable variable=VCSLabel;]$vcsLabel"
+        Write-Host "##vso[task.setvariable variable=PackageVersion;]$version"
+        Write-Host "##vso[task.setvariable variable=Build.BuildNumber;]$version"
+      displayName: 'Build VCS Label and Rehydrate Version Variables'
+
+    - powershell: |
+        $files = 'build.bat','Version.props'
+        foreach ($file in $files) {
+            Copy-Item -Path "$(Build.ArtifactStagingDirectory)/$(VersionArtifactName)/$file" -Destination "$(Build.SourcesDirectory)/$file" -Force -ErrorAction Continue
+        }
+      displayName: 'Update build.bat and Version.props to build only version $(PackageVersion)'
+
+    - template: 'build/azure-templates/show-all-environment-variables.yml'
+
+    - task: CopyFiles@2
+      displayName: 'Copy Source Code Files to: $(Build.ArtifactStagingDirectory)/srctemp'
+      inputs:
+        SourceFolder: '$(Build.SourcesDirectory)'
+        Contents: |
+         **
+         !.git/**/*
+         !branding/**/*
+         !release/**/*
+         !src/**/bin/**/*
+         !src/**/obj/**/*
+        TargetFolder: '$(Build.ArtifactStagingDirectory)/srctemp'
+
+    - task: ArchiveFiles@2
+      displayName: 'Archive Source Code Files'
+      inputs:
+        rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/srctemp'
+        includeRootFolder: false
+        archiveFile: '$(Build.ArtifactStagingDirectory)/$(ReleaseArtifactName)/Apache-Lucene.Net-$(PackageVersion).src.zip'
+
+    - task: CopyFiles@2
+      displayName: 'Copy License/Notice Files to: $(Build.ArtifactStagingDirectory)/$(NuGetArtifactName)'
+      inputs:
+        SourceFolder: '$(Build.SourcesDirectory)'
+        Contents: |
+         LICENSE.txt
+         NOTICE.txt
+        TargetFolder: '$(Build.ArtifactStagingDirectory)/$(NuGetArtifactName)'
+
+    - task: ArchiveFiles@2
+      displayName: 'Archive Binary Files'
+      inputs:
+        rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/$(NuGetArtifactName)'
+        includeRootFolder: false
+        archiveFile: '$(Build.ArtifactStagingDirectory)/$(ReleaseArtifactName)/Apache-Lucene.Net-$(PackageVersion).bin.zip'
+
+    - powershell: |
+        $dir = '$(Build.ArtifactStagingDirectory)/$(ReleaseArtifactName)'
+        if (!(Test-Path $dir)) { New-Item -ItemType Directory -Path "$dir" -Force }
+        $nl = [Environment]::NewLine
+        "TODO: Review: http://www.apache.org/legal/release-policy.html" + $nl + `
+        "TODO: Tag Repository" + $nl + `
+        "  commit: $(Build.SourceVersion)" + $nl + `
+        "  tag: $(VCSLabel)" + $nl + `
+        "TODO: Sign release artifacts (see https://www.apache.org/dev/release-signing.html)" + $nl + `
+        "TODO: Push release artifacts to dev (https://dist.apache.org/repos/dist/dev/lucenenet/)" + $nl + `
+        "TODO: Start release [VOTE] (see https://www.apache.org/foundation/voting.html)" + $nl | Out-File -FilePath "$dir/RELEASE-TODO.txt" -Force
+      displayName: 'Write RELEASE-TODO.txt'
+
+    - task: PublishBuildArtifacts@1
+      displayName: 'Publish Artifact: $(ReleaseArtifactName)'
+      inputs:
+        PathtoPublish: '$(Build.ArtifactStagingDirectory)/$(ReleaseArtifactName)'
+        ArtifactName: '$(ReleaseArtifactName)'
+
+# LUCENENET TODO: Write VCS Label (git tag) automatically
\ No newline at end of file
diff --git a/build/Dependencies.props b/build/Dependencies.props
index 4e20f0b..b46153b 100644
--- a/build/Dependencies.props
+++ b/build/Dependencies.props
@@ -1,3 +1,23 @@
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
 <Project> 
   <PropertyGroup Label="NuGet Package Reference Versions">
     <Antlr3RuntimePackageVersion>3.5.1</Antlr3RuntimePackageVersion>
diff --git a/build/TestReferences.Common.targets b/build/TestReferences.Common.targets
index b2986a6..6411a7b 100644
--- a/build/TestReferences.Common.targets
+++ b/build/TestReferences.Common.targets
@@ -1,3 +1,23 @@
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
 <Project>
   <ItemGroup Label="Test Project Common References">
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
diff --git a/build/azure-templates/publish-nuget-packages.yml b/build/azure-templates/publish-nuget-packages.yml
new file mode 100644
index 0000000..7d5ab61
--- /dev/null
+++ b/build/azure-templates/publish-nuget-packages.yml
@@ -0,0 +1,64 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Downloads an artifact with the specified nugetArtifactName,
+# pushes any .nupkg files to the specified artifactFeedID,
+# and pushes any debugging symbols (.pdb files) in the artifact
+# (except for those named *.Tests.pdb) to TeamServices.
+
+parameters:
+  nugetArtifactName: 'nuget' # The name of the artifact where the NuGet assets (.nupkg and .pdb files) can be downloaded
+  artifactFeedID: '' # The GUID of the Azure Artifacts NuGet feed
+  testSymbolFilesConvention: '**/*.Tests*.pdb' # The glob pattern (within the nugetArtifact) where to look for test project symbols (.pdb) files, so they can be distinguished from other project file types.
+
+steps:
+- powershell: |
+    function EnsureNotNullOrEmpty([string]$param, [string]$nameOfParam) {
+        if ([string]::IsNullOrEmpty($param)) {
+            Write-Host "##vso[task.logissue type=error;]Missing template parameter \"$nameOfParam\""
+            Write-Host "##vso[task.complete result=Failed;]"
+        }
+    }
+    EnsureNotNullOrEmpty('${{ parameters.nugetArtifactName }}', 'nugetArtifactName')
+    EnsureNotNullOrEmpty('${{ parameters.artifactFeedID }}', 'artifactFeedID')
+    EnsureNotNullOrEmpty('${{ parameters.testSymbolFilesConvention }}', 'testSymbolFilesConvention')
+  displayName: 'Validate Template Parameters'
+
+- task: DownloadBuildArtifacts@0
+  displayName: 'Download Build Artifacts: ${{ parameters.nugetArtifactName }}'
+  inputs:
+    artifactName: ${{ parameters.nugetArtifactName }}
+    downloadPath: '$(Build.ArtifactStagingDirectory)'
+
+- task: NuGetCommand@2
+  displayName: 'NuGet push'
+  inputs:
+    command: push
+    packagesToPush: '$(Build.ArtifactStagingDirectory)/${{ parameters.nugetArtifactName }}/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/${{ parameters.nugetArtifactName }}/**/*.symbols.nupkg'
+    publishVstsFeed: '/${{ parameters.artifactFeedID }}'
+    allowPackageConflicts: true
+
+- task: PublishSymbols@2
+  displayName: 'Publish symbols path'
+  inputs:
+    SymbolsFolder: '$(Build.ArtifactStagingDirectory)/${{ parameters.nugetArtifactName }}'
+    SearchPattern: |
+      **/bin/**/*.pdb
+      !${{ parameters.testSymbolFilesConvention }}
+    IndexSources: true
+    PublishSymbols: true
+    SymbolServerType: TeamServices
\ No newline at end of file
diff --git a/build/azure-templates/publish-test-results-for-target-frameworks.yml b/build/azure-templates/publish-test-results-for-target-frameworks.yml
new file mode 100644
index 0000000..cc3af4f
--- /dev/null
+++ b/build/azure-templates/publish-test-results-for-target-frameworks.yml
@@ -0,0 +1,67 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Runs Publish Test Results task for a specific framework
+
+parameters:
+  testProjectName: '' # The name of the test project
+  osName: '' # The name of the operating system for display purposes.
+  testResultsFormat: VSTest # Specify the format of the results files you want to publish. The following formats are supported: JUnit, NUnit, VSTest, XUnit, CTest
+  testResultsArtifactName: 'testresults' # The name of the Azure DevOps build artifact where the test results will be published. Default 'testresults'.
+  testResultsFileName: 'TestResults.trx' # The name of the file (not path) of the test results. Default 'TestResults.trx'.
+
+steps:
+- powershell: |
+    function EnsureNotNullOrEmpty([string]$param, [string]$nameOfParam) {
+        if ([string]::IsNullOrEmpty($param)) {
+            Write-Host "##vso[task.logissue type=error;]Missing template parameter \"$nameOfParam\""
+            Write-Host "##vso[task.complete result=Failed;]"
+        }
+    }
+    EnsureNotNullOrEmpty('${{ parameters.testProjectName }}', 'testProjectName')
+    EnsureNotNullOrEmpty('${{ parameters.osName }}', 'osName')
+    EnsureNotNullOrEmpty('${{ parameters.testResultsFormat }}', 'testResultsFormat')
+    EnsureNotNullOrEmpty('${{ parameters.testResultsArtifactName }}', 'testResultsArtifactName')
+    EnsureNotNullOrEmpty('${{ parameters.testResultsFileName }}', 'testResultsFileName')
+  displayName: 'Validate Template Parameters'
+
+- template: publish-test-results.yml
+  parameters:
+    framework: 'netcoreapp2.1'
+    testProjectName: '${{ parameters.testProjectName }}'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results.yml
+  parameters:
+    framework: 'netcoreapp1.0'
+    testProjectName: '${{ parameters.testProjectName }}'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results.yml
+  parameters:
+    framework: 'net451'
+    testProjectName: '${{ parameters.testProjectName }}'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
\ No newline at end of file
diff --git a/build/azure-templates/publish-test-results-for-test-projects.yml b/build/azure-templates/publish-test-results-for-test-projects.yml
new file mode 100644
index 0000000..62db26f
--- /dev/null
+++ b/build/azure-templates/publish-test-results-for-test-projects.yml
@@ -0,0 +1,282 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,-with-title
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Runs Publish Test Results task for all Lucene.Net test projects
+
+# The tasks can be easily re-generated (except for any special cases) using the following powershell script
+
+#$source_directory = "F:/Projects/lucenenet" # Change to your repo location
+#
+#$testProjects = Get-ChildItem -Path "$source_directory/**/*.csproj" -Recurse | ? { $_.Directory.Name.Contains(".Tests") } | Sort-Object -Property FullName
+#
+#[string]$output = ''
+#
+#foreach ($testProject in $testProjects) {
+#    $projectName = [System.IO.Path]::GetFileNameWithoutExtension($testProject)
+#
+#    $block = `
+#        "- template: publish-test-results-for-target-frameworks.yml`n" + `
+#        "  parameters:`n" + `
+#        "    testProjectName: '$projectName'`n" + `
+#        "    osName: '$`{`{ parameters.osName `}`}'`n" + `
+#        "    testResultsFormat: '$`{`{ parameters.testResultsFormat `}`}'`n" + `
+#        "    testResultsArtifactName: '$`{`{ parameters.testResultsArtifactName `}`}'`n" + `
+#        "    testResultsFileName: '$`{`{ parameters.testResultsFileName `}`}'`n" + `
+#        "`n"
+#
+#    $output = "$output$block"
+#}
+#
+#Set-Clipboard -Value $output
+
+parameters:
+  osName: '' # The name of the operating system for display purposes.
+  testResultsFormat: VSTest # Specify the format of the results files you want to publish. The following formats are supported: JUnit, NUnit, VSTest, XUnit, CTest
+  testResultsArtifactName: 'testresults' # The name of the Azure DevOps build artifact where the test results will be published. Default 'testresults'.
+  testResultsFileName: 'TestResults.trx' # The name of the file (not path) of the test results. Default 'TestResults.trx'.
+
+steps:
+- powershell: |
+    function EnsureNotNullOrEmpty([string]$param, [string]$nameOfParam) {
+        if ([string]::IsNullOrEmpty($param)) {
+            Write-Host "##vso[task.logissue type=error;]Missing template parameter \"$nameOfParam\""
+            Write-Host "##vso[task.complete result=Failed;]"
+        }
+    }
+    EnsureNotNullOrEmpty('${{ parameters.osName }}', 'osName')
+    EnsureNotNullOrEmpty('${{ parameters.testResultsFormat }}', 'testResultsFormat')
+    EnsureNotNullOrEmpty('${{ parameters.testResultsArtifactName }}', 'testResultsArtifactName')
+    EnsureNotNullOrEmpty('${{ parameters.testResultsFileName }}', 'testResultsFileName')
+  displayName: 'Validate Template Parameters'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.ICU'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+# Special case: Only supports .netcoreapp2.1
+- template: publish-test-results.yml
+  parameters:
+    framework: 'netcoreapp2.1'
+    testProjectName: 'Lucene.Net.Tests.Cli'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests._A-I'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests._J-U'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests._U-Z'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Analysis.Common'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Analysis.Kuromoji'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Analysis.Phonetic'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Analysis.SmartCn'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Analysis.Stempel'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Benchmark'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Classification'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Codecs'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Demo'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Expressions'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Facet'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Grouping'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Highlighter'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Join'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Memory'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Misc'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Queries'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.QueryParser'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Replicator'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Sandbox'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Spatial'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
+
+- template: publish-test-results-for-target-frameworks.yml
+  parameters:
+    testProjectName: 'Lucene.Net.Tests.Suggest'
+    osName: '${{ parameters.osName }}'
+    testResultsFormat: '${{ parameters.testResultsFormat }}'
+    testResultsArtifactName: '${{ parameters.testResultsArtifactName }}'
+    testResultsFileName: '${{ parameters.testResultsFileName }}'
\ No newline at end of file
diff --git a/build/azure-templates/publish-test-results.yml b/build/azure-templates/publish-test-results.yml
new file mode 100644
index 0000000..b7c9d0a
--- /dev/null
+++ b/build/azure-templates/publish-test-results.yml
@@ -0,0 +1,81 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Runs Publish Test Results task for a specific framework
+
+parameters:
+  testProjectName: '' # The name of the test project
+  framework: '' # The target framework for display purposes.
+  osName: '' # The name of the operating system for display purposes.
+  testResultsFormat: VSTest # Specify the format of the results files you want to publish. The following formats are supported: JUnit, NUnit, VSTest, XUnit, CTest
+  testResultsArtifactName: 'testresults' # The name of the Azure DevOps build artifact where the test results will be published. Default 'testresults'.
+  testResultsFileName: 'TestResults.trx' # The name of the file (not path) of the test results. Default 'TestResults.trx'.
+
+steps:
+- powershell: |
+    function EnsureNotNullOrEmpty([string]$param, [string]$nameOfParam) {
+        if ([string]::IsNullOrEmpty($param)) {
+            Write-Host "##vso[task.logissue type=error;]Missing template parameter \"$nameOfParam\""
+            Write-Host "##vso[task.complete result=Failed;]"
+        }
+    }
+    EnsureNotNullOrEmpty('${{ parameters.testProjectName }}', 'testProjectName')
+    EnsureNotNullOrEmpty('${{ parameters.framework }}', 'framework')
+    EnsureNotNullOrEmpty('${{ parameters.osName }}', 'osName')
+    EnsureNotNullOrEmpty('${{ parameters.testResultsFormat }}', 'testResultsFormat')
+    EnsureNotNullOrEmpty('${{ parameters.testResultsArtifactName }}', 'testResultsArtifactName')
+    EnsureNotNullOrEmpty('${{ parameters.testResultsFileName }}', 'testResultsFileName')
+  displayName: 'Validate Template Parameters'
+
+#- template: 'show-all-files.yml' # Uncomment for debugging
+
+- powershell: |
+    $testResultsFileName = "$(Build.ArtifactStagingDirectory)/${{ parameters.testResultsArtifactName }}/${{ parameters.osName }}/${{ parameters.framework }}/${{ parameters.testProjectName }}/${{ parameters.testResultsFileName }}"
+    $testResultsFileExists = Test-Path $testResultsFileName
+    if ($testResultsFileExists) {
+
+        $reader = [System.Xml.XmlReader]::Create($testResultsFileName)
+        try {
+            while ($reader.Read()) {
+                if ($reader.NodeType -eq [System.Xml.XmlNodeType]::Element -and $reader.Name -eq 'Counters') {
+                    $failed = $reader.GetAttribute('failed')
+                    $passed = $reader.GetAttribute('passed')
+                    $ignored = (([int]$reader.GetAttribute('total')) - ([int]$reader.GetAttribute('executed'))).ToString()
+                    $testResults = "Tests failed: $failed, passed: $passed, ignored: $ignored"
+                    Write-Host "##vso[task.setvariable variable=TestResults;]$testResults"
+                    # Report a running total of failures
+                    $totalFailures = ([int]$Env:TOTALFAILURES + [int]$failed).ToString()
+                    Write-Host "##vso[task.setvariable variable=TotalFailures;]$totalFailures"
+                    break;
+                }
+            }
+        } finally {
+            $reader.Dispose()
+        }
+    } else {
+        Write-Host "WARNING: File not found: $testResultsFileName"
+    }
+    Write-Host "##vso[task.setvariable variable=TestResultsFileExists;]$testResultsFileExists"      
+  displayName: 'Parse Test Results File'
+
+- task: PublishTestResults@2
+  displayName: 'Publish Test Results ${{ parameters.testProjectName }},${{ parameters.framework }}'
+  inputs:
+    testResultsFormat: ${{ parameters.testResultsFormat }}
+    testResultsFiles: '$(Build.ArtifactStagingDirectory)/${{ parameters.testResultsArtifactName }}/${{ parameters.osName }}/${{ parameters.framework }}/${{ parameters.testProjectName }}/${{ parameters.testResultsFileName }}'
+    testRunTitle: '${{ parameters.testProjectName }} - ${{ parameters.framework }} - ${{ parameters.osName }} | $(TestResults)'
+  condition: and(succeeded(), eq(variables['TestResultsFileExists'], 'true'))
\ No newline at end of file
diff --git a/build/azure-templates/run-tests-on-os.yml b/build/azure-templates/run-tests-on-os.yml
new file mode 100644
index 0000000..e9e605e
--- /dev/null
+++ b/build/azure-templates/run-tests-on-os.yml
@@ -0,0 +1,211 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Downloads test binaries and executes tests using dotnet vstest,
+# runs the tests for each project on a background job in parallel,
+# then uploads the results to Azure DevOps pipelines
+
+parameters:
+  osName: 'Windows' # The name of the operating system for display purposes.
+  testTargetFrameworks: '' # A semicolon separated list of target frameworks indicating which frameworks tests will be run on. See: https://docs.microsoft.com/en-us/dotnet/standard/frameworks.
+  binaryArtifactName: 'testbinaries' # The name of the Azure DevOps build artifact where the test assemblies will be downloaded from. Default 'testbinaries'.
+  testResultsArtifactName: 'testresults' # The name of the Azure DevOps build artifact where the test results will be published. Default 'testresults'.
+  vsTestPlatform: 'x64' # Target platform architecture used for test execution. Valid values are x86, x64, and ARM.
+  testBinaryFilesPattern: '\.*\.Tests\.?[^\\/]*?\.?[^\\/]*?.dll$' # The regex pattern (within $(System.DefaultWorkingDirectory)/**/<TargetFramework>/) where to look for test .dll files, so they can be distinguished from other .dll file types.
+  testResultsFileName: 'TestResults.trx' # The name of the file (not path) of the test results. Default 'TestResults.trx'.
+  publishedArtifactZipFileName: 'published.zip' # The name of the zip file (within the Azure DevOps build artifact binaryArtifactName) where the published test binaries can be extracted from. Default 'published.zip'.
+  maximumParallelJobs: 8
+  maximumAllowedFailures: 0
+
+steps:
+- powershell: |
+    function EnsureNotNullOrEmpty([string]$param, [string]$nameOfParam) {
+        if ([string]::IsNullOrEmpty($param)) {
+            Write-Host "##vso[task.logissue type=error;]Missing template parameter \"$nameOfParam\""
+            Write-Host "##vso[task.complete result=Failed;]"
+        }
+    }
+    EnsureNotNullOrEmpty('${{ parameters.osName }}', 'osName')
+    EnsureNotNullOrEmpty('${{ parameters.testTargetFrameworks }}', 'testTargetFrameworks')
+    EnsureNotNullOrEmpty('${{ parameters.binaryArtifactName }}', 'binaryArtifactName')
+    EnsureNotNullOrEmpty('${{ parameters.testResultsArtifactName }}', 'testResultsArtifactName')
+    EnsureNotNullOrEmpty('${{ parameters.vsTestPlatform }}', 'vsTestPlatform')
+    EnsureNotNullOrEmpty('${{ parameters.testBinaryFilesPattern }}', 'testBinaryFilesPattern')
+    EnsureNotNullOrEmpty('${{ parameters.testResultsFileName }}', 'testResultsFileName')
+    EnsureNotNullOrEmpty('${{ parameters.publishedArtifactZipFileName }}', 'publishedArtifactZipFileName')
+    EnsureNotNullOrEmpty('${{ parameters.maximumParallelJobs }}', 'maximumParallelJobs')
+    EnsureNotNullOrEmpty('${{ parameters.maximumAllowedFailures }}', 'maximumAllowedFailures')
+  displayName: 'Validate Template Parameters'
+
+- task: DownloadBuildArtifacts@0
+  displayName: 'Download Build Artifacts: ${{ parameters.binaryArtifactName }}'
+  inputs:
+    artifactName: ${{ parameters.binaryArtifactName }}
+    downloadPath: '$(System.DefaultWorkingDirectory)'
+
+#- template: 'show-all-files.yml' # Uncomment for debugging
+
+- task: ExtractFiles@1
+  displayName: 'Extract files from ${{ parameters.publishedArtifactZipFileName }}'
+  inputs:
+    archiveFilePatterns: '${{ parameters.binaryArtifactName }}/${{ parameters.publishedArtifactZipFileName }}'
+    destinationFolder: '$(System.DefaultWorkingDirectory)/${{ parameters.binaryArtifactName }}'
+    cleanDestinationFolder: false
+
+- powershell: |
+    $testTargetFrameworksString = '${{ parameters.testTargetFrameworks }}'
+    $testBinaryRootDirectory = "$(System.DefaultWorkingDirectory)"
+    $testResultsArtifactDirectory = "${{ format('$(Build.ArtifactStagingDirectory)/{0}',parameters.testResultsArtifactName) }}"
+    $testPlatform = '${{ parameters.vsTestPlatform }}'
+    $testOSName = '${{ parameters.osName }}'
+    $testBinaryFilesPattern = '${{ parameters.testBinaryFilesPattern }}'
+    $testResultsFileName = '${{ parameters.testResultsFileName }}'
+    $maximumParalellJobs = '${{ parameters.maximumParallelJobs }}'
+    $testTargetFrameworks = $testTargetFrameworksString.Split([char]',',[char]';')
+    
+    function SeparateVersionDigits([string]$digits) {
+        return (&{ for ($i = 0;$i -lt $digits.Length;$i++) { $digits.Substring($i,1) }}) -join '.'
+    }
+    
+    # Convert $framework (i.e. net461) into format for dotnet vstest (i.e. .NETFramework,Version=4.6.1)
+    function ConvertFrameworkName([string]$framework) {
+        $match = [regex]::Match($framework, '^net(\d+)$') # .NET Framework
+        if ($match.Success) {
+            $ver = SeparateVersionDigits($match.Groups[1].Value)
+            return ".NETFramework,Version=v$($ver)"
+        }
+        $match = [regex]::Match($framework, '^netcoreapp(\d+\.\d+(?:\.\d+)?)$') # .NET Core
+        if ($match.Success) {
+            $ver = $match.Groups[1].Value
+            return ".NETCoreApp,Version=v$($ver)"
+        } 
+        $match = [regex]::Match($framework, '^uap(\d+\.\d+)?$') # Universal Windows Platform
+        if ($match.Success) {
+            $ver = $match.Groups[1].Value
+            $ver = if ([string]::IsNullOrEmpty($ver)) { '10' } else { $ver.Replace('.0','').Replace('.','') }
+            return "FrameworkUap$($ver)"
+        }
+        return $framework
+    }
+    
+    function IsSupportedFramework([string]$framework) {
+        if ($IsWindows -eq $null) {
+            $IsWindows = $env:OS.StartsWith('Win')
+        }
+        if (!$IsWindows -and !$framework.StartsWith('netcoreapp')) {
+            return $false
+        }
+        return $true
+    }
+    
+    function RunTests([string]$framework, [string]$fileRegexPattern) {
+        if (!(IsSupportedFramework($framework))) { continue }
+    
+        $testBinaries = Get-ChildItem -Path "$testBinaryRootDirectory" -File -Recurse | Where-Object {$_.FullName -match "$framework"} | Where-Object {$_.FullName -match "$fileRegexPattern"} | Sort-Object -Property FullName
+        Write-Host $testBinaries
+        foreach ($testBinary in $testBinaries) {
+            $testName = [System.IO.Path]::GetFileNameWithoutExtension($testBinary.FullName)
+            $testDirectory = $testBinary.Directory.Name
+    
+            # Safety check - only run tests for the DLL that matches the directory name so we don't run the same one twice
+            if (!($testName -eq $testDirectory)) { continue }
+    
+            if ($maximumParalellJobs -gt 1) {
+                # Pause if we have queued too many parallel jobs
+                $running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
+                if ($running.Count -ge $maximumParalellJobs) {
+                    Write-Host ""
+                    Write-Host "  Running tests in parallel on $($running.Count) projects." -ForegroundColor Cyan
+                    Write-Host "  Next in queue is $testName on $framework. This will take a bit, please wait..." -ForegroundColor Cyan
+                    $running | Wait-Job -Any | Out-Null
+                }
+            }
+    
+            $fwork = ConvertFrameworkName($framework)
+            $testResultDirectory = "$testResultsArtifactDirectory/$testOSName/$framework/$testName"                
+            if (!(Test-Path "$testResultDirectory")) {
+                New-Item "$testResultDirectory" -ItemType Directory -Force
+            }
+    
+            $testExpression = "dotnet vstest ""$($testBinary.FullName)"" --Framework:""$fwork"" --Platform:""$testPlatform""" + `
+                " --logger:""console;verbosity=normal"" --logger:""trx;LogFileName=$testResultsFileName""" + `
+                " --ResultsDirectory:""$testResultDirectory"" --Blame"
+    
+            Write-Host "Testing '$($testBinary.FullName)' on framework '$fwork' and outputting test results to '$testResultDirectory/$testResultsFileName'..."
+            Write-Host $testExpression -ForegroundColor Magenta
+            if ($maximumParalellJobs -le 1) {
+                Invoke-Expression $testExpression # For running in the foreground
+            } else {
+    
+                $testExpression += " > ""$testResultDirectory/dotnet-vstest.log"" 2> ""$testResultDirectory/dotnet-vstest-error.log"""
+                $scriptBlock = {
+                    param([string]$testExpression)
+                    Invoke-Expression $testExpression
+                }
+    
+                # Execute the jobs in parallel
+                Start-Job $scriptBlock -ArgumentList $testExpression
+            }
+        }
+    }
+    
+    foreach ($framework in $testTargetFrameworks) {
+        RunTests -Framework "$framework" -FileRegexPattern "$testBinaryFilesPattern"
+    }
+    
+    if ($maximumParalellJobs -gt 1) {
+        # Wait for it all to complete
+        do {
+            $running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
+            if ($running.Count -gt 0) {
+                Write-Host ""
+                Write-Host "  Almost finished, only $($running.Count) projects left..." -ForegroundColor Cyan
+                $running | Wait-Job -Any
+            }
+        } until ($running.Count -eq 0)
+    }
+    
+    $global:LASTEXITCODE = 0 # Force the script to continue on error
+  displayName: 'dotnet vstest ${{ parameters.testTargetFrameworks }}'
+  ignoreLASTEXITCODE: true
+
+#- template: 'show-all-files.yml' # Uncomment for debugging
+
+- task: PublishBuildArtifacts@1
+  displayName: 'Publish Artifact: ${{ parameters.testResultsArtifactName }}'
+  inputs:
+    PathtoPublish: '$(Build.ArtifactStagingDirectory)/${{ parameters.testResultsArtifactName }}'
+    ArtifactName: '${{ parameters.testResultsArtifactName }}'
+  condition: succeededOrFailed()
+
+# Due to the fact that it is not possible to loop a task and
+# it would be a ton of work to make a replacement for the
+# Publish Test Results task or the (deprecated) TfsPublisher
+# our only other option is to make a task for every supported
+# platform and project and update it whenever a new platform 
+# is targeted or test project is created in Lucene.Net.
+
+- template: 'publish-test-results-for-test-projects.yml'
+  parameters:
+    osName: '${{ parameters.osName }}'
+
+- pwsh: |
+    $maximumAllowedFailures = '${{ parameters.maximumAllowedFailures }}'
+    if ([int]$Env:TOTALFAILURES -gt [int]$maximumAllowedFailures) {
+        Write-Host "##vso[task.logissue type=error;]Test run failed due to too many failed tests. Maximum failures allowed: $maximumAllowedFailures, total failures: $($Env:TOTALFAILURES)."
+        Write-Host "##vso[task.complete result=Failed;]"
+    }
\ No newline at end of file
diff --git a/build/azure-templates/show-all-environment-variables.yml b/build/azure-templates/show-all-environment-variables.yml
new file mode 100644
index 0000000..1815e62
--- /dev/null
+++ b/build/azure-templates/show-all-environment-variables.yml
@@ -0,0 +1,28 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Writes all environment variables to the host (helpful for debugging)
+
+steps:
+- powershell: |
+    $environmentVars = Get-ChildItem -path env:* | sort Name
+    foreach($var in $environmentVars) {
+        $keyname = $var.Key
+        $keyvalue = $var.Value
+        Write-Output "${keyname}: $keyvalue"
+    }
+  displayName: 'Show all Environment Variables'
\ No newline at end of file
diff --git a/build/azure-templates/show-all-files.yml b/build/azure-templates/show-all-files.yml
new file mode 100644
index 0000000..9591775
--- /dev/null
+++ b/build/azure-templates/show-all-files.yml
@@ -0,0 +1,26 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Downloads test binaries and executes tests using dotnet vstest,
+# runs the tests for each project on a background job in parallel,
+# then uploads the results to Azure DevOps pipelines
+
+# Writes all file names (from the parent directory) to the host (helpful for debugging)
+
+steps:
+- powershell: cd ..;dir -r  | Where-Object {$_.PsIsContainer -eq $false} | % { $_.FullName }
+  displayName: 'Show all Files'
\ No newline at end of file
diff --git a/build/build.ps1 b/build/build.ps1
index 6ba4106..a29d765 100644
--- a/build/build.ps1
+++ b/build/build.ps1
@@ -29,22 +29,23 @@ properties {
 	[string]$sdkPath = "$env:programfiles/dotnet/sdk"
 	[string]$sdkVersion = "2.2.300"
 	[string]$globalJsonFile = "$base_directory/global.json"
+	[string]$versionPropsFile = "$base_directory/Version.props"
+	[string]$build_bat = "$base_directory/build.bat"
 
 	[string]$buildCounter     = $(if ($buildCounter) { $buildCounter } else { $env:BuildCounter }) #NOTE: Pass in as a parameter (not a property) or environment variable to override
-	[string]$preReleaseCounterPattern = $(if ($preReleaseCounterPattern) { $preReleaseCounterPattern } else { if ($env:PreReleaseCounterPattern) { $env:PreReleaseCounterPattern } else { "00000" } })  #NOTE: Pass in as a parameter (not a property) or environment variable to override
-	[string]$versionSuffix    = $(if ($versionSuffix) { $versionSuffix } else { $env:VersionSuffix })  #NOTE: Pass in as a parameter (not a property) or environment variable to override
+	[string]$preReleaseCounterPattern = $(if ($preReleaseCounterPattern) { $preReleaseCounterPattern } else { if ($env:PreReleaseCounterPattern) { $env:PreReleaseCounterPattern } else { "0000000000" } })  #NOTE: Pass in as a parameter (not a property) or environment variable to override
+	[string]$versionSuffix    = $(if ($versionSuffix -ne $null) { $versionSuffix } else { if ($env:VersionSuffix -ne $null) { $env:VersionSuffix } else { 'ci' }}) #NOTE: Pass in as a parameter (not a property) or environment variable to override
 	[string]$packageVersion   = Get-Package-Version #NOTE: Pass in as a parameter (not a property) or environment variable to override
 	[string]$version          = Get-Version
-	[string]$configuration    = "Release"
+	[string]$configuration    = $(if ($configuration) { $configuration } else { if ($env:BuildConfiguration) { $env:BuildConfiguration } else { "Release" } })  #NOTE: Pass in as a parameter (not a property) or environment variable to override
+	[string]$platform   = $(if ($platform) { $platform } else { if ($env:BuildPlatform) { $env:BuildPlatform } else { "Any CPU" } })  #NOTE: Pass in as a parameter (not a property) or environment variable to override
 	[bool]$backup_files       = $true
 	[bool]$prepareForBuild    = $true
 	[bool]$generateBuildBat   = $false
+	[bool]$zipPublishedArtifacts = $false
+	[string]$publishedArtifactZipFileName = "artifact.zip"
 
-	[string]$build_bat = "$base_directory/build.bat"
-	[string]$copyright_year = [DateTime]::Today.Year.ToString() #Get the current year from the system
-	[string]$copyright = "Copyright " + $([char]0x00A9) + " 2006 - $copyright_year The Apache Software Foundation"
-	[string]$company_name = "The Apache Software Foundation"
-	[string]$product_name = "Lucene.Net"
+	[int]$maximumParalellJobs = 8
 	
 	#test paramters
 	[string]$frameworks_to_test = "netcoreapp2.1,netcoreapp1.0,net451"
@@ -56,11 +57,11 @@ if ($IsWindows -eq $null) {
 	$IsWindows = $Env:OS.StartsWith('Windows')
 }
 
-
 task default -depends Pack
 
 task Clean -description "This task cleans up the build directory" {
 	Write-Host "##teamcity[progressMessage 'Cleaning']"
+	Write-Host "##vso[task.setprogress]'Cleaning'"
 	Remove-Item $release_directory -Force -Recurse -ErrorAction SilentlyContinue
 	Get-ChildItem $base_directory -Include *.bak -Recurse | foreach ($_) {Remove-Item $_.FullName}
 }
@@ -74,6 +75,7 @@ task UpdateLocalSDKVersion -description "Backs up the project.json file and pins
 
 task InstallSDK -description "This task makes sure the correct SDK version is installed to build" -ContinueOnError {
 	Write-Host "##teamcity[progressMessage 'Installing SDK $sdkVersion']"
+	Write-Host "##vso[task.setprogress]'Installing SDK $sdkVersion'"
 	$installed = Is-Sdk-Version-Installed $sdkVersion
 	if (!$installed) {
 		Write-Host "Requires SDK version $sdkVersion, installing..." -ForegroundColor Red
@@ -89,9 +91,11 @@ task InstallSDK -description "This task makes sure the correct SDK version is in
 }
 
 task Init -depends InstallSDK, UpdateLocalSDKVersion -description "This task makes sure the build environment is correctly setup" {
-	#Update TeamCity or MyGet with packageVersion
+	#Update TeamCity, MyGet, or Azure Pipelines with packageVersion
 	Write-Output "##teamcity[buildNumber '$packageVersion']"
 	Write-Output "##myget[buildNumber '$packageVersion']"
+	Write-Host "##vso[task.setvariable variable=Build.BuildNumber;]$packageVersion"
+	Write-Host "##vso[build.updatebuildnumber]$packageVersion"
 
 	& dotnet.exe --version
 	& dotnet.exe --info
@@ -106,12 +110,14 @@ task Init -depends InstallSDK, UpdateLocalSDKVersion -description "This task mak
 	Write-Host "Package Version: $packageVersion"
 	Write-Host "Version: $version"
 	Write-Host "Configuration: $configuration"
+	Write-Host "Platform: $platform"
 
 	Ensure-Directory-Exists "$release_directory"
 }
 
 task Restore -description "This task restores the dependencies" {
 	Write-Host "##teamcity[progressMessage 'Restoring']"
+	Write-Host "##vso[task.setprogress]'Restoring'"
 	Exec { 
 		& dotnet.exe restore $solutionFile --no-dependencies /p:TestFrameworks=true
 	}
@@ -119,42 +125,25 @@ task Restore -description "This task restores the dependencies" {
 
 task Compile -depends Clean, Init, Restore -description "This task compiles the solution" {
 	Write-Host "##teamcity[progressMessage 'Compiling']"
+	Write-Host "##vso[task.setprogress]'Compiling'"
 	try {
 		if ($prepareForBuild -eq $true) {
 			Prepare-For-Build
 		}
 
-		#Use only the major version as the assembly version.
-		#This ensures binary compatibility unless the major version changes.
-		$version-match "(^\d+)"
-		$assemblyVersion = $Matches[0]
-		$assemblyVersion = "$assemblyVersion.0.0"
-
-		Write-Host "Assembly version set to: $assemblyVersion" -ForegroundColor Green
-
-		$pv = $packageVersion
-		#check for presense of Git
-		& where.exe git.exe
-		if ($LASTEXITCODE -eq 0) {
-			$gitCommit = ((git rev-parse --verify --short=10 head) | Out-String).Trim()
-			$pv = "$packageVersion commit:[$gitCommit]"
-		}
-
-		Write-Host "Assembly informational version set to: $pv" -ForegroundColor Green
-
-		$testFrameworks = $frameworks_to_test.Replace(',', ';')
+		$testFrameworks = [string]::Join(';', (Get-FrameworksToTest))
 
 		Write-Host "TestFrameworks set to: $testFrameworks" -ForegroundColor Green
 
 		Exec {
+			# NOTE: Version information is not passed in at the command line,
+			# instead it is output to the Version.props file. This file is then
+			# used during a release to "freeze" the build at a specific version
+			# so it is always a constant in release distributions.
 			& dotnet.exe msbuild $solutionFile /t:Build `
 				/p:Configuration=$configuration `
-				/p:AssemblyVersion=$assemblyVersion `
-				/p:FileVersion=$version `
-				/p:InformationalVersion=$pv `
-				/p:Product=$product_name `
-				/p:Company=$company_name `
-				/p:Copyright=$copyright `
+				/p:Platform=$platform `
+				/p:PortableDebugTypeOnly=true `
 				/p:TestFrameworks=true # workaround for parsing issue: https://github.com/Microsoft/msbuild/issues/471#issuecomment-181963350
 		}
 
@@ -168,18 +157,27 @@ task Compile -depends Clean, Init, Restore -description "This task compiles the
 
 task Pack -depends Compile -description "This task creates the NuGet packages" {
 	Write-Host "##teamcity[progressMessage 'Packing']"
+	Write-Host "##vso[task.setprogress]'Packing'"
 	#create the nuget package output directory
 	Ensure-Directory-Exists "$nuget_package_directory"
 
 	try {
 		Exec {
-			& dotnet.exe pack $solutionFile --configuration $Configuration --output $nuget_package_directory --no-build --include-symbols /p:PackageVersion=$packageVersion
+			# NOTE: Package version information is not passed in at the command line,
+			# instead it is output to the Version.props file. This file is then
+			# used during a release to "freeze" the build at a specific version
+			# so it is always a constant in release distributions.
+			& dotnet.exe pack $solutionFile --configuration $configuration --output $nuget_package_directory --no-build
 		}
 
 		$success = $true
 	} finally {
 		#if ($success -ne $true) {
 			Restore-Files $backedUpFiles
+			#Remove Version.props, as we don't want it to be committed to the repository
+			if ($backup_files -eq $true -and (Test-Path -Path "$versionPropsFile") -eq $true) {
+				Remove-Item -Path "$versionPropsFile" -Force
+			}
 		#}
 	}
 }
@@ -192,11 +190,23 @@ task Publish -depends Compile -description "This task uses dotnet publish to pac
 	Write-Host "##vso[task.setprogress]'Publishing'"
 
 	try {
-		$frameworksToTest = $frameworks_to_test -split "\s*?,\s*?"
+		$frameworksToTest = Get-FrameworksToTest
+
+		if ($zipPublishedArtifacts) {
+			$outDirectory = New-TemporaryDirectory
+		} else {
+			$outDirectory = $publish_directory
+		}
 		
 		foreach ($framework in $frameworksToTest) {
-			$testProjects = Get-ChildItem -Path "$source_directory/**/*.csproj" -Recurse | ? { $_.Directory.Name.Contains(".Tests") } | ForEach-Object { $_.FullName }
+			$testProjects = Get-ChildItem -Path "$source_directory/**/*.csproj" -Recurse | ? { $_.Directory.Name.Contains(".Tests") } | Select -ExpandProperty FullName
 			foreach ($testProject in $testProjects) {
+				# Pause if we have queued too many parallel jobs
+				$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
+				if ($running.Count -ge $maximumParalellJobs) {
+					$running | Wait-Job -Any | Out-Null
+				}
+
 				$projectName = [System.IO.Path]::GetFileNameWithoutExtension($testProject)
 
 				# Special case - our CLI tool only supports .NET Core 2.1
@@ -204,36 +214,43 @@ task Publish -depends Compile -description "This task uses dotnet publish to pac
 					continue
 				}
 
+				$logPath = "$outDirectory/$framework"
+				$outputPath = "$logPath/$projectName"
+
 				# Do this first so there is no conflict
-				$outputPath = "$publish_directory/$framework/$projectName"
 				Ensure-Directory-Exists $outputPath
 
 				$scriptBlock = {
-					param([string]$testProject, [string]$publish_directory, [string]$framework, [string]$configuration, [string]$projectName)
-					$logPath = "$publish_directory/$framework"
-					$outputPath = "$logPath/$projectName"
+					param([string]$testProject, [string]$outputPath, [string]$logPath, [string]$framework, [string]$configuration, [string]$projectName)
 					Write-Host "Publishing '$testProject' on '$framework' to '$outputPath'..."
 					# Note: Cannot use Psake Exec in background
-					dotnet publish "$testProject" --output "$outputPath" --framework "$framework" --configuration "$configuration" --no-build --verbosity Detailed /p:TestFrameworks=true > "$logPath/$projectName-dotnet-publish.log" 2> "$logPath/$projectName-dotnet-publish-error.log"
+					dotnet publish "$testProject" --output "$outputPath" --framework "$framework" --configuration "$configuration" --no-build --verbosity Detailed /p:TestFrameworks=true /p:Platform="$platform" > "$logPath/$projectName-dotnet-publish.log" 2> "$logPath/$projectName-dotnet-publish-error.log"
 				}
 
 				# Execute the jobs in parallel
-				Start-Job $scriptBlock -ArgumentList $testProject,$publish_directory,$framework,$configuration,$projectName
+				Start-Job $scriptBlock -ArgumentList $testProject,$outputPath,$logPath,$framework,$configuration,$projectName
 			}
 		}
 
-		Write-Host "Executing dotnet publish of all projects in parallel. This will take a bit, please wait..."
-
-		Get-Job
-
 		# Wait for it all to complete
-        While (Get-Job -State "Running") {
-			Start-Sleep 10
-		}
+		do {
+			$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
+			if ($running.Count -gt 0) {
+				Write-Host ""
+				Write-Host "  Almost finished, only $($running.Count) projects left to publish..." -ForegroundColor Cyan
+				$running | Wait-Job -Any | Out-Null
+			}
+		} until ($running.Count -eq 0)
 
 		# Getting the information back from the jobs (time consuming)
 		#Get-Job | Receive-Job
 
+		if ($zipPublishedArtifacts) {
+			Ensure-Directory-Exists $publish_directory
+			Add-Type -assembly "System.IO.Compression.Filesystem"
+			[System.IO.Compression.ZipFile]::CreateFromDirectory($outDirectory, "$publish_directory/$publishedArtifactZipFileName")
+		}
+
 		$success = $true
 	} finally {
 		#if ($success -ne $true) {
@@ -244,27 +261,48 @@ task Publish -depends Compile -description "This task uses dotnet publish to pac
 
 task Test -depends InstallSDK, UpdateLocalSDKVersion, Restore -description "This task runs the tests" {
 	Write-Host "##teamcity[progressMessage 'Testing']"
+	Write-Host "##vso[task.setprogress]'Testing'"
 	Write-Host "Running tests..." -ForegroundColor DarkCyan
 
 	pushd $base_directory
 	$testProjects = Get-ChildItem -Path "$source_directory/**/*.csproj" -Recurse | ? { $_.Directory.Name.Contains(".Tests") }
 	popd
 
-	Write-Host "frameworks_to_test: $frameworks_to_test" -ForegroundColor Yellow
+	$testProjects = $testProjects | Sort-Object -Property FullName
 
-	$frameworksToTest = $frameworks_to_test -split "\s*?,\s*?"
+	$frameworksToTest = Get-FrameworksToTest
+	
+	Write-Host "frameworksToTest: $frameworksToTest" -ForegroundColor Yellow
+
+	[int]$totalProjects = $testProjects.Length * $frameworksToTest.Length
+	[int]$remainingProjects = $totalProjects
 
-	foreach ($framework in $frameworksToTest) {
-		Write-Host "Framework: $framework" -ForegroundColor Blue
+	Ensure-Directory-Exists $test_results_directory
 
-		foreach ($testProject in $testProjects) {
+	foreach ($testProject in $testProjects) {
+
+		foreach ($framework in $frameworksToTest) {
 			$testName = $testProject.Directory.Name
 
 			# Special case - our CLI tool only supports .NET Core 2.1
 			if ($testName.Contains("Tests.Cli") -and (!$framework.StartsWith("netcoreapp2."))) {
+				$totalProjects--
+				$remainingProjects--
 				continue
 			}
 
+			Write-Host "  Next Project in Queue: $testName, Framework: $framework" -ForegroundColor Yellow
+
+			# Pause if we have queued too many parallel jobs
+			$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
+			if ($running.Count -ge $maximumParalellJobs) {
+				Write-Host ""
+				Write-Host "  Running tests in parallel on $($running.Count) projects out of approximately $totalProjects total." -ForegroundColor Cyan
+				Write-Host "  $remainingProjects projects are waiting in the queue to run. This will take a bit, please wait..." -ForegroundColor Cyan
+				$running | Wait-Job -Any | Out-Null
+			}
+			$remainingProjects -= 1
+
 			$testResultDirectory = "$test_results_directory/$framework/$testName"
 			Ensure-Directory-Exists $testResultDirectory
 
@@ -290,15 +328,33 @@ task Test -depends InstallSDK, UpdateLocalSDKVersion, Restore -description "This
 
 			Write-Host $testExpression -ForegroundColor Magenta
 
-			Invoke-Expression $testExpression
-			# fail the build on negative exit codes (NUnit errors - if positive it is a test count or, if 1, it could be a dotnet error)
-			if ($LASTEXITCODE -lt 0) {
-				throw "Test execution failed"
+			$scriptBlock = {
+				param([string]$testExpression, [string]$testResultDirectory)
+				$testExpression = "$testExpression > '$testResultDirectory/dotnet-test.log' 2> '$testResultDirectory/dotnet-test-error.log'"
+				Invoke-Expression $testExpression
 			}
+
+			# Execute the jobs in parallel
+			Start-Job $scriptBlock -ArgumentList $testExpression,$testResultDirectory
+
+			#Invoke-Expression $testExpression
+			## fail the build on negative exit codes (NUnit errors - if positive it is a test count or, if 1, it could be a dotnet error)
+			#if ($LASTEXITCODE -lt 0) {
+			#	throw "Test execution failed"
+			#}
 		}
 	}
 
-	Summarize-Test-Results
+	do {
+		$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
+		if ($running.Count -gt 0) {
+			Write-Host ""
+			Write-Host "  Almost finished, only $($running.Count) test projects left..." -ForegroundColor Cyan
+			$running | Wait-Job -Any
+		}
+	} until ($running.Count -eq 0)
+
+	Summarize-Test-Results -FrameworksToTest $frameworksToTest
 }
 
 function Get-Package-Version() {
@@ -345,6 +401,19 @@ function Get-Version() {
 	return $version
 }
 
+function Get-FrameworksToTest() {
+    $frameworksToTest = New-Object Collections.Generic.List[string]
+    $frameworks = $frameworks_to_test -split "\s*?,\s*?"
+    foreach ($framework in $frameworks) {
+        if ($IsWindows) {
+            $frameworksToTest.Add($framework)
+        } elseif ($framework.StartsWith('netcore')) {
+            $frameworksToTest.Add($framework)
+        }
+    }
+    return [System.Linq.Enumerable]::ToArray($frameworksToTest)
+}
+
 function Is-Sdk-Version-Installed([string]$sdkVersion) {
 	& where.exe dotnet.exe | Out-Null
 	if ($LASTEXITCODE -eq 0) {
@@ -375,6 +444,35 @@ function Is-Sdk-Version-Installed([string]$sdkVersion) {
 }
 
 function Prepare-For-Build() {
+	#Use only the major version as the assembly version.
+	#This ensures binary compatibility unless the major version changes.
+	$version -match "(^\d+)"
+	$assemblyVersion = $Matches[0]
+	$assemblyVersion = "$assemblyVersion.0.0"
+
+	Write-Host "Assembly version set to: $assemblyVersion" -ForegroundColor Green
+
+	$informationalVersion = $packageVersion
+	#check for presense of Git
+	& where.exe git.exe
+	if ($LASTEXITCODE -eq 0) {
+		$gitCommit = ((git rev-parse --verify --short=10 head) | Out-String).Trim()
+		$informationalVersion = "$packageVersion commit:[$gitCommit]"
+	}
+
+	Write-Host "##vso[task.setvariable variable=AssemblyVersion;]$assemblyVersion"
+	Write-Host "##vso[task.setvariable variable=FileVersion;]$version"
+	Write-Host "##vso[task.setvariable variable=InformationalVersion;]$informationalVersion"
+	Write-Host "##vso[task.setvariable variable=PackageVersion;]$packageVersion"
+
+	Write-Host "Assembly informational version set to: $informationalVersion" -ForegroundColor Green
+
+	Generate-Version-Props `
+		-AssemblyVersion $assemblyVersion `
+		-FileVersion $version `
+		-InformationalVersion $informationalVersion `
+		-PackageVersion $packageVersion `
+		-File $versionPropsFile
 	Update-Constants-Version $packageVersion
 
 	if ($generateBuildBat -eq $true) {
@@ -411,6 +509,50 @@ $fileText = "{
 	Out-File -filePath $file -encoding UTF8 -inputObject $fileText
 }
 
+function Generate-Version-Props {
+param(
+	[string]$assemblyVersion,
+	[string]$fileVersion,
+	[string]$informationalVersion,
+	[string]$packageVersion,
+	[string]$file = $(throw "file is a required parameter.")
+)
+
+$fileText = "<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ ""License""); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ ""AS IS"" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+<Project>
+  <PropertyGroup Label=""Version Override Properties"">
+    <AssemblyVersion>$assemblyVersion</AssemblyVersion>
+    <FileVersion>$fileVersion</FileVersion>
+    <InformationalVersion>$informationalVersion</InformationalVersion>
+    <PackageVersion>$packageVersion</PackageVersion>
+  </PropertyGroup>
+</Project>"
+	$dir = [System.IO.Path]::GetDirectoryName($file)
+	Ensure-Directory-Exists $dir
+
+	Write-Host "Generating Version.props file: $file"
+	Out-File -filePath $file -encoding UTF8 -inputObject $fileText
+}
+
 function Generate-Build-Bat {
 param(
 	[string]$file = $(throw "file is a required parameter.")
@@ -494,9 +636,7 @@ function New-CountersObject ([string]$project, [string]$outcome, [int]$total, [i
     return $counters
 }
 
-function Summarize-Test-Results() {
-    Write-Host "frameworks_to_test: $frameworks_to_test" -ForegroundColor Gray
-    $frameworksToTest = $frameworks_to_test -split "\s*?,\s*?"
+function Summarize-Test-Results([string[]]$frameworksToTest) {
 
     foreach ($framework in $frameworksToTest) {
         pushd $base_directory
@@ -517,11 +657,11 @@ function Summarize-Test-Results() {
 
         Write-Host ""
         Write-Host ""
-        Write-Host "************************************************************************************************************" -ForegroundColor Yellow
-        Write-Host "*                                                                                                          *" -ForegroundColor Yellow
-        Write-Host "*                                        Test Summary For $framework"  -ForegroundColor Yellow
-        Write-Host "*                                                                                                          *" -ForegroundColor Yellow
-        Write-Host "************************************************************************************************************" -ForegroundColor Yellow
+        Write-Host "**********************************************************************" -ForegroundColor Yellow
+        Write-Host "*                                                                    *" -ForegroundColor Yellow
+        Write-Host "*                        Test Summary For $framework"  -ForegroundColor Yellow
+        Write-Host "*                                                                    *" -ForegroundColor Yellow
+        Write-Host "**********************************************************************" -ForegroundColor Yellow
 
         foreach ($testReport in $testReports) {
             $testName = [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName($testReport))
@@ -555,15 +695,17 @@ function Summarize-Test-Results() {
                         $skippedCountForFramework += $counters.Skipped
 
                         $format = @{Expression={$_.Project};Label='Project';Width=35},
-                            @{Expression={$_.Outcome};Label='Outcome';Width=9},
-                            @{Expression={$_.Total};Label='Total';Width=8},
-                            @{Expression={$_.Executed};Label='Executed';Width=10},
-                            @{Expression={$_.Passed};Label='Passed';Width=8},
-                            @{Expression={$_.Failed};Label='Failed';Width=8},
-                            @{Expression={$_.Warning};Label='Warning';Width=9},
+                            @{Expression={$_.Outcome};Label='Outcome';Width=7},
+                            @{Expression={$_.Total};Label='Total';Width=6},
+                            @{Expression={$_.Executed};Label='Executed';Width=8},
+                            @{Expression={$_.Passed};Label='Passed';Width=6},
+                            @{Expression={$_.Failed};Label='Failed';Width=6},
+                            @{Expression={$_.Warning};Label='Warning';Width=7},
                             @{Expression={$_.Inconclusive};Label='Inconclusive';Width=14}
 
-                        $Counters | Format-Table $format
+						if ($counters.Failed -gt 0) {
+							$Counters | Format-Table $format
+						}
                     }
                 }
 
@@ -575,12 +717,12 @@ function Summarize-Test-Results() {
 
         # FOOTER FOR FRAMEWORK
 
-        Write-Host "************************************************************************************************************" -ForegroundColor Magenta
-        Write-Host "*                                                                                                          *" -ForegroundColor Magenta
-        Write-Host "*                                        Totals For $framework"  -ForegroundColor Magenta
-        Write-Host "*                                                                                                          *" -ForegroundColor Magenta
-        Write-Host "************************************************************************************************************" -ForegroundColor Magenta
-        Write-Host ""
+        #Write-Host "**********************************************************************" -ForegroundColor Magenta
+        #Write-Host "*                                                                    *" -ForegroundColor Magenta
+        #Write-Host "*                           Totals For $framework"  -ForegroundColor Magenta
+        #Write-Host "*                                                                    *" -ForegroundColor Magenta
+        #Write-Host "**********************************************************************" -ForegroundColor Magenta
+        #Write-Host ""
         $foreground = if ($outcomeForFramework -eq 'Failed') { 'Red' } else { 'Green' }
         Write-Host "Result: " -NoNewline; Write-Host "$outcomeForFramework" -ForegroundColor $foreground
         Write-Host ""
@@ -594,6 +736,8 @@ function Summarize-Test-Results() {
         Write-Host "Warning: " -NoNewline; Write-Host "$warningCountForFramework" -ForegroundColor $foreground
         $foreground = if ($failedCountForFramework -gt 0) { 'Cyan' } else { (Get-Host).UI.RawUI.ForegroundColor }
         Write-Host "Inconclusive: " -NoNewline; Write-Host "$inconclusiveCountForFramework" -ForegroundColor $foreground
+		Write-Host ""
+		Write-Host "See the .trx logs in $test_results_directory/$framework for more details." -ForegroundColor DarkCyan
     }
 }
 
@@ -631,4 +775,10 @@ function Ensure-Directory-Exists([string] $path) {
 	if (!(Test-Path $path)) {
 		New-Item $path -ItemType Directory
 	}
+}
+
+function New-TemporaryDirectory {
+    $parent = [System.IO.Path]::GetTempPath()
+    [string] $name = [System.Guid]::NewGuid()
+    New-Item -ItemType Directory -Path (Join-Path $parent $name)
 }
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Th/TestThaiAnalyzer.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Th/TestThaiAnalyzer.cs
index c208618..4dc1c2a 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Th/TestThaiAnalyzer.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Th/TestThaiAnalyzer.cs
@@ -92,6 +92,48 @@ namespace Lucene.Net.Analysis.Th
             AssertAnalyzesTo(analyzer, "ประโยคว่า The quick brown fox jumped over the lazy dogs", new string[] { "ประโยค", "ว่า", "quick", "brown", "fox", "jumped", "over", "lazy", "dogs" });
         }
 
+        // Ellision character
+        private static readonly char THAI_PAIYANNOI = (char)0x0E2F;
+        // Repeat character
+        private static readonly char THAI_MAIYAMOK = (char)0x0E46;
+
+        [Test]
+        [LuceneNetSpecific]
+        public virtual void TestThaiBreakEngineInitializerCode()
+        {
+            // Initialize UnicodeSets
+            var fThaiWordSet = new ICU4N.Text.UnicodeSet();
+            var fMarkSet = new ICU4N.Text.UnicodeSet();
+            var fBeginWordSet = new ICU4N.Text.UnicodeSet();
+            var fSuffixSet = new ICU4N.Text.UnicodeSet();
+
+            fThaiWordSet.ApplyPattern("[[:Thai:]&[:LineBreak=SA:]]");
+            fThaiWordSet.Compact();
+
+            fMarkSet.ApplyPattern("[[:Thai:]&[:LineBreak=SA:]&[:M:]]");
+            fMarkSet.Add(0x0020);
+            var fEndWordSet = new ICU4N.Text.UnicodeSet(fThaiWordSet);
+            fEndWordSet.Remove(0x0E31); // MAI HAN-AKAT
+            fEndWordSet.Remove(0x0E40, 0x0E44); // SARA E through SARA AI MAIMALAI
+            fBeginWordSet.Add(0x0E01, 0x0E2E); //KO KAI through HO NOKHUK
+            fBeginWordSet.Add(0x0E40, 0x0E44); // SARA E through SARA AI MAIMALAI
+            fSuffixSet.Add(THAI_PAIYANNOI);
+            fSuffixSet.Add(THAI_MAIYAMOK);
+
+            // Compact for caching
+            fMarkSet.Compact();
+            fEndWordSet.Compact();
+            fBeginWordSet.Compact();
+            fSuffixSet.Compact();
+
+            // Freeze the static UnicodeSet
+            fThaiWordSet.Freeze();
+            fMarkSet.Freeze();
+            fEndWordSet.Freeze();
+            fBeginWordSet.Freeze();
+            fSuffixSet.Freeze();
+        }
+
         /*
          * Test that position increments are adjusted correctly for stopwords.
          */