diff --git a/Directory.Build.props b/Directory.Build.props
index 2ceb515..2a6b458 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -12,8 +12,9 @@
https://github.com/microsoft/DacFx
git
-
- 162.3.566
+ 162.4.92
+ 161.9142.1
+ 5.1.6
diff --git a/src/Microsoft.Build.Sql/Microsoft.Build.Sql.csproj b/src/Microsoft.Build.Sql/Microsoft.Build.Sql.csproj
index 218b087..2b3f573 100644
--- a/src/Microsoft.Build.Sql/Microsoft.Build.Sql.csproj
+++ b/src/Microsoft.Build.Sql/Microsoft.Build.Sql.csproj
@@ -13,9 +13,9 @@
-
+
-
+
diff --git a/src/Microsoft.Build.Sql/sdk/Sdk.props b/src/Microsoft.Build.Sql/sdk/Sdk.props
index f19be85..4b94526 100644
--- a/src/Microsoft.Build.Sql/sdk/Sdk.props
+++ b/src/Microsoft.Build.Sql/sdk/Sdk.props
@@ -6,12 +6,15 @@
+
true
false
$(MSBuildThisFileDirectory)..\tools\netstandard2.1
netstandard2.1
+
+ @(SupportedTargetFramework->'%(Alias)')
publish
diff --git a/test/Microsoft.Build.Sql.Tests/CodeAnalysisTests.cs b/test/Microsoft.Build.Sql.Tests/CodeAnalysisTests.cs
new file mode 100644
index 0000000..0d74ae6
--- /dev/null
+++ b/test/Microsoft.Build.Sql.Tests/CodeAnalysisTests.cs
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Collections.Generic;
+using System.IO;
+using NUnit.Framework;
+
+namespace Microsoft.Build.Sql.Tests
+{
+ [TestFixture]
+ public class CodeAnalysisTests : DotnetTestBase
+ {
+ [Test]
+ public void VerifyCodeAnalyzerFromProjectReference()
+ {
+ // Copy the analyzer project to a temp folder
+ string tempFolder = TestUtils.CreateTempDirectory();
+ TestUtils.CopyDirectoryRecursive(Path.Combine(this.CommonTestDataDirectory, "CodeAnalyzerSample"), tempFolder);
+
+ // Add the analyzer csproj as a ProjectReference to the test sqlproj
+ ProjectUtils.AddItemGroup(this.GetProjectFilePath(), "ProjectReference",
+ new string[] { Path.Combine(tempFolder, "CodeAnalyzerSample.csproj") },
+ item =>
+ {
+ item.AddMetadata("PrivateAssets", "All");
+ item.AddMetadata("ReferenceOutputAssembly", "False");
+ item.AddMetadata("OutputItemType", "Analyzer");
+ item.AddMetadata("SetTargetFramework", "TargetFramework=netstandard2.1");
+ });
+
+ // Set up code analysis properties
+ ProjectUtils.AddProperties(this.GetProjectFilePath(), new Dictionary()
+ {
+ { "RunSqlCodeAnalysis", "true" },
+ { "SqlCodeAnalysisRules", "+!CodeAnalyzerSample.TableNameRule001" } // Should fail build on this rule
+ });
+
+ int exitCode = this.RunDotnetCommandOnProject("build", out string stdOutput, out string stdError);
+
+ Assert.AreNotEqual(0, exitCode, "Build should have failed");
+ Assert.IsTrue(stdOutput.Contains("Table name [dbo].[NotAView] ends in View. This can cause confusion and should be avoided"), "Unexpected stderr");
+ }
+
+ [Test]
+ public void VerifyCodeAnalyzerFromPackageReference()
+ {
+ // Set up and create the analyzer package
+ string tempFolder = TestUtils.CreateTempDirectory();
+ TestUtils.CopyDirectoryRecursive(Path.Combine(CommonTestDataDirectory, "CodeAnalyzerSample"), tempFolder);
+ RunGenericDotnetCommand($"pack {Path.Combine(tempFolder, "CodeAnalyzerSample.csproj")} -o {tempFolder}", out _, out _);
+
+ // Copy analyzer package to local Nuget source
+ string analyzerPackagePath = Path.Combine(tempFolder, "CodeAnalyzerSample.1.0.0.nupkg");
+ FileAssert.Exists(analyzerPackagePath);
+ File.Copy(analyzerPackagePath, Path.Combine(WorkingDirectory, "pkg", "CodeAnalyzerSample.1.0.0.nupkg"));
+
+ // Add the analyzer package as a PackageReference to the test sqlproj
+ ProjectUtils.AddItemGroup(this.GetProjectFilePath(), "PackageReference",
+ new string[] { "CodeAnalyzerSample" },
+ item =>
+ {
+ item.AddMetadata("Version", "1.0.0");
+ });
+
+ // Set up code analysis properties
+ ProjectUtils.AddProperties(this.GetProjectFilePath(), new Dictionary()
+ {
+ { "RunSqlCodeAnalysis", "true" },
+ { "SqlCodeAnalysisRules", "+!CodeAnalyzerSample.TableNameRule001" } // Should fail build on this rule
+ });
+
+ int exitCode = this.RunDotnetCommandOnProject("build", out string stdOutput, out string stdError);
+
+ Assert.AreNotEqual(0, exitCode, "Build should have failed");
+ Assert.IsTrue(stdOutput.Contains("Table name [dbo].[NotAView] ends in View. This can cause confusion and should be avoided"), "Unexpected stderr");
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.Build.Sql.Tests/TestData/CodeAnalyzerSample/CodeAnalyzerSample.csproj b/test/Microsoft.Build.Sql.Tests/TestData/CodeAnalyzerSample/CodeAnalyzerSample.csproj
new file mode 100644
index 0000000..f98e8aa
--- /dev/null
+++ b/test/Microsoft.Build.Sql.Tests/TestData/CodeAnalyzerSample/CodeAnalyzerSample.csproj
@@ -0,0 +1,16 @@
+
+
+ Library
+ netstandard2.1
+ $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/Microsoft.Build.Sql.Tests/TestData/CodeAnalyzerSample/TableNameEndingInViewRule.cs b/test/Microsoft.Build.Sql.Tests/TestData/CodeAnalyzerSample/TableNameEndingInViewRule.cs
new file mode 100644
index 0000000..9dffce7
--- /dev/null
+++ b/test/Microsoft.Build.Sql.Tests/TestData/CodeAnalyzerSample/TableNameEndingInViewRule.cs
@@ -0,0 +1,73 @@
+//------------------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//----------------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.SqlServer.Dac.Extensibility;
+using Microsoft.SqlServer.Dac.CodeAnalysis;
+using Microsoft.SqlServer.Dac.Model;
+
+namespace CodeAnalyzerSample
+{
+
+ ///
+ /// Simple test class - note it doesn't use resources since these aren't handled by the test harness
+ /// that builds dll files
+ ///
+ [ExportCodeAnalysisRule("CodeAnalyzerSample.TableNameRule001",
+ "SampleRule",
+ Description = "Table names should not end in 'View'",
+ Category = "Naming",
+ PlatformCompatibility = TSqlPlatformCompatibility.OnPremises)]
+ class TableNameEndingInViewRule : SqlCodeAnalysisRule
+ {
+ private static readonly ModelTypeClass[] _supportedElementTypes = new[] { ModelSchema.Table };
+
+ public TableNameEndingInViewRule()
+ {
+ SupportedElementTypes = new[] { Table.TypeClass };
+ }
+
+ public override IList Analyze(SqlRuleExecutionContext ruleExecutionContext)
+ {
+ List problems = new List();
+
+ TSqlObject table = ruleExecutionContext.ModelElement;
+ if (table != null)
+ {
+ if (NameEndsInView(table.Name))
+ {
+ string problemDescription = string.Format("Table name {0} ends in View. This can cause confusion and should be avoided",
+ GetQualifiedTableName(table.Name));
+ SqlRuleProblem problem = new SqlRuleProblem(problemDescription, table);
+ problems.Add(problem);
+ }
+ }
+
+ return problems;
+ }
+
+ private bool NameEndsInView(ObjectIdentifier id)
+ {
+ return id.HasName && id.Parts.Last().EndsWith("View", StringComparison.OrdinalIgnoreCase);
+ }
+
+ private string GetQualifiedTableName(ObjectIdentifier id)
+ {
+ StringBuilder buf = new StringBuilder();
+ foreach (string part in id.Parts)
+ {
+ if (buf.Length > 0)
+ {
+ buf.Append('.');
+ }
+ buf.Append('[').Append(part).Append(']');
+ }
+ return buf.ToString();
+ }
+ }
+}
diff --git a/test/Microsoft.Build.Sql.Tests/TestData/VerifyCodeAnalyzerFromPackageReference/NotAView.sql b/test/Microsoft.Build.Sql.Tests/TestData/VerifyCodeAnalyzerFromPackageReference/NotAView.sql
new file mode 100644
index 0000000..1f67824
--- /dev/null
+++ b/test/Microsoft.Build.Sql.Tests/TestData/VerifyCodeAnalyzerFromPackageReference/NotAView.sql
@@ -0,0 +1,3 @@
+CREATE TABLE NotAView (
+ Col VARCHAR(255)
+)
\ No newline at end of file
diff --git a/test/Microsoft.Build.Sql.Tests/TestData/VerifyCodeAnalyzerFromProjectReference/NotAView.sql b/test/Microsoft.Build.Sql.Tests/TestData/VerifyCodeAnalyzerFromProjectReference/NotAView.sql
new file mode 100644
index 0000000..1f67824
--- /dev/null
+++ b/test/Microsoft.Build.Sql.Tests/TestData/VerifyCodeAnalyzerFromProjectReference/NotAView.sql
@@ -0,0 +1,3 @@
+CREATE TABLE NotAView (
+ Col VARCHAR(255)
+)
\ No newline at end of file