diff --git a/BUILD.md b/BUILD.md
new file mode 100644
index 00000000..603d42b4
--- /dev/null
+++ b/BUILD.md
@@ -0,0 +1,75 @@
+# Usage
+
+Run `build.(ps1|sh)` with the desired set of arguments (see below for options).
+The build script itself is `build.cake`, written in C# using the Cake build automation system.
+All build related activites should be encapsulated in this file for cross-platform access.
+
+# Arguments
+
+## Primary
+
+ `-target=TargetName`: The name of the build task/target to execute (see below for listing and details).
+ Defaults to `Default`.
+
+ `-configuration=(Release|Debug)`: The configuration to build.
+ Defaults to `Release`.
+
+## Extra
+
+ `-test-configuration=(Release|Debug)`: The configuration to use for the unit tests.
+ Defaults to `Debug`.
+
+ `-install-path=Path`: Path used for the **Install** target.
+ Defaults to `(%USERPROFILE%|$HOME)/.sqltoolsservice/local`
+
+ `-archive`: Enable the generation of publishable archives after a build.
+
+# Targets
+
+**Default**: Alias for Local.
+
+**Local**: Full build including testing for the machine-local runtime.
+
+**All**: Same as local, but targeting all runtimes selected by `PopulateRuntimes` in `build.cake`.
+ Currently configured to also build for a 32-bit Windows runtime on Windows machines.
+ No additional runtimes are currently selected on non-Windows machines.
+
+**Quick**: Local build which skips all testing.
+
+**Install**: Same as quick, but installs the generated binaries into `install-path`.
+
+**SetPackageVersions**: Updates the dependency versions found within `project.json` files using information from `depversion.json`.
+ Used for maintainence within the project, not needed for end-users. More information below.
+
+# Configuration files
+
+## build.json
+
+A number of build-related options, including folder names for different entities. Interesting options:
+
+**DotNetInstallScriptURL**: The URL where the .NET SDK install script is located.
+ Can be used to pin to a specific script version, if a breaking change occurs.
+
+**"DotNetChannel"**: The .NET SDK channel used for retreiving the tools.
+
+**"DotNetVersion"**: The .NET SDK version used for the build. Can be used to pin to a specific version.
+ Using the string `Latest` will retrieve the latest version.
+
+## depversion.json
+
+A listing of all dependencies (and their desired versions) used by `project.json` files throughout the project.
+Allows for quick and automatic updates to the dependency version numbers using the **SetPackageVersions** target.
+
+# Artifacts generated
+
+* Binaries of Microsoft.SqlTools.ServiceLayer and its libraries built for the local machine in `artifacts/publish/Microsoft.SqlTools.ServiceLayer/default/{framework}/`
+* Scripts to run Microsoft.SqlTools.ServiceLayer at `scripts/SQLTOOLSSERVICE(.Core)(.cmd)`
+ * These scripts are updated for every build and every install.
+ * The scripts point to the installed binary after and install, otherwise just the build folder (reset if a new build occurs without an install).
+* Binaries of Microsoft.SqlTools.ServiceLayer and its libraries cross-compiled for other runtimes (if selected in **PopulateRuntimes**) `artifacts/publish/Microsoft.SqlTools.ServiceLayer/{runtime}/{framework}/`
+* Test logs in `artifacts/logs`
+* Archived binaries in `artifacts/package` (only if `-archive` used on command line)
+
+# Requirements
+
+The build system requires Mono to be installed on non-Windows machines as Cake is not built using .NET Core (yet).
diff --git a/build.cake b/build.cake
new file mode 100644
index 00000000..61047b05
--- /dev/null
+++ b/build.cake
@@ -0,0 +1,507 @@
+#addin "Newtonsoft.Json"
+
+#load "scripts/runhelpers.cake"
+#load "scripts/archiving.cake"
+#load "scripts/artifacts.cake"
+
+using System.ComponentModel;
+using System.Net;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+// Basic arguments
+var target = Argument("target", "Default");
+var configuration = Argument("configuration", "Release");
+// Optional arguments
+var testConfiguration = Argument("test-configuration", "Debug");
+var installFolder = Argument("install-path", System.IO.Path.Combine(Environment.GetEnvironmentVariable(IsRunningOnWindows() ? "USERPROFILE" : "HOME"),
+ ".sqltoolsservice", "local"));
+var requireArchive = HasArgument("archive");
+
+// Working directory
+var workingDirectory = System.IO.Directory.GetCurrentDirectory();
+
+// System specific shell configuration
+var shell = IsRunningOnWindows() ? "powershell" : "bash";
+var shellArgument = IsRunningOnWindows() ? "-NoProfile /Command" : "-C";
+var shellExtension = IsRunningOnWindows() ? "ps1" : "sh";
+
+///
+/// Class representing build.json
+///
+public class BuildPlan
+{
+ public IDictionary TestProjects { get; set; }
+ public string BuildToolsFolder { get; set; }
+ public string ArtifactsFolder { get; set; }
+ public bool UseSystemDotNetPath { get; set; }
+ public string DotNetFolder { get; set; }
+ public string DotNetInstallScriptURL { get; set; }
+ public string DotNetChannel { get; set; }
+ public string DotNetVersion { get; set; }
+ public string[] Frameworks { get; set; }
+ public string[] Rids { get; set; }
+ public string MainProject { get; set; }
+}
+
+var buildPlan = JsonConvert.DeserializeObject(
+ System.IO.File.ReadAllText(System.IO.Path.Combine(workingDirectory, "build.json")));
+
+// Folders and tools
+var dotnetFolder = System.IO.Path.Combine(workingDirectory, buildPlan.DotNetFolder);
+var dotnetcli = buildPlan.UseSystemDotNetPath ? "dotnet" : System.IO.Path.Combine(System.IO.Path.GetFullPath(dotnetFolder), "dotnet");
+var toolsFolder = System.IO.Path.Combine(workingDirectory, buildPlan.BuildToolsFolder);
+
+var sourceFolder = System.IO.Path.Combine(workingDirectory, "src");
+var testFolder = System.IO.Path.Combine(workingDirectory, "test");
+
+var artifactFolder = System.IO.Path.Combine(workingDirectory, buildPlan.ArtifactsFolder);
+var publishFolder = System.IO.Path.Combine(artifactFolder, "publish");
+var logFolder = System.IO.Path.Combine(artifactFolder, "logs");
+var packageFolder = System.IO.Path.Combine(artifactFolder, "package");
+var scriptFolder = System.IO.Path.Combine(artifactFolder, "scripts");
+
+///
+/// Clean artifacts.
+///
+Task("Cleanup")
+ .Does(() =>
+{
+ if (System.IO.Directory.Exists(artifactFolder))
+ {
+ System.IO.Directory.Delete(artifactFolder, true);
+ }
+ System.IO.Directory.CreateDirectory(artifactFolder);
+ System.IO.Directory.CreateDirectory(logFolder);
+ System.IO.Directory.CreateDirectory(packageFolder);
+ System.IO.Directory.CreateDirectory(scriptFolder);
+});
+
+///
+/// Pre-build setup tasks.
+///
+Task("Setup")
+ .IsDependentOn("BuildEnvironment")
+ .IsDependentOn("PopulateRuntimes")
+ .Does(() =>
+{
+});
+
+///
+/// Populate the RIDs for the specific environment.
+/// Use default RID (+ win7-x86 on Windows) for now.
+///
+Task("PopulateRuntimes")
+ .IsDependentOn("BuildEnvironment")
+ .Does(() =>
+{
+ buildPlan.Rids = new string[]
+ {
+ "default", // To allow testing the published artifact
+ "win7-x64",
+ "win7-x86",
+ "ubuntu.14.04-x64",
+ "ubuntu.16.04-x64",
+ "centos.7-x64",
+ "rhel.7.2-x64",
+ "debian.8-x64",
+ "fedora.23-x64",
+ "opensuse.13.2-x64",
+ "osx.10.11-x64"
+ };
+});
+
+///
+/// Install/update build environment.
+///
+Task("BuildEnvironment")
+ .Does(() =>
+{
+ var installScript = $"dotnet-install.{shellExtension}";
+ System.IO.Directory.CreateDirectory(dotnetFolder);
+ var scriptPath = System.IO.Path.Combine(dotnetFolder, installScript);
+ using (WebClient client = new WebClient())
+ {
+ client.DownloadFile($"{buildPlan.DotNetInstallScriptURL}/{installScript}", scriptPath);
+ }
+ if (!IsRunningOnWindows())
+ {
+ Run("chmod", $"+x '{scriptPath}'");
+ }
+ var installArgs = $"-Channel {buildPlan.DotNetChannel}";
+ if (!String.IsNullOrEmpty(buildPlan.DotNetVersion))
+ {
+ installArgs = $"{installArgs} -Version {buildPlan.DotNetVersion}";
+ }
+ if (!buildPlan.UseSystemDotNetPath)
+ {
+ installArgs = $"{installArgs} -InstallDir {dotnetFolder}";
+ }
+ Run(shell, $"{shellArgument} {scriptPath} {installArgs}");
+ try
+ {
+ Run(dotnetcli, "--info");
+ }
+ catch (Win32Exception)
+ {
+ throw new Exception(".NET CLI binary cannot be found.");
+ }
+
+ System.IO.Directory.CreateDirectory(toolsFolder);
+
+ var nugetPath = Environment.GetEnvironmentVariable("NUGET_EXE");
+ var arguments = $"install xunit.runner.console -ExcludeVersion -NoCache -Prerelease -OutputDirectory \"{toolsFolder}\"";
+ if (IsRunningOnWindows())
+ {
+ Run(nugetPath, arguments);
+ }
+ else
+ {
+ Run("mono", $"\"{nugetPath}\" {arguments}");
+ }
+});
+
+///
+/// Restore required NuGet packages.
+///
+Task("Restore")
+ .IsDependentOn("Setup")
+ .Does(() =>
+{
+ RunRestore(dotnetcli, "restore", sourceFolder)
+ .ExceptionOnError("Failed to restore projects under source code folder.");
+ RunRestore(dotnetcli, "restore --infer-runtimes", testFolder)
+ .ExceptionOnError("Failed to restore projects under test code folder.");
+});
+
+///
+/// Build Test projects.
+///
+Task("BuildTest")
+ .IsDependentOn("Setup")
+ .IsDependentOn("Restore")
+ .Does(() =>
+{
+ foreach (var pair in buildPlan.TestProjects)
+ {
+ foreach (var framework in pair.Value)
+ {
+ var project = pair.Key;
+ var projectFolder = System.IO.Path.Combine(testFolder, project);
+ var runLog = new List();
+ Run(dotnetcli, $"build --framework {framework} --configuration {testConfiguration} \"{projectFolder}\"",
+ new RunOptions
+ {
+ StandardOutputListing = runLog
+ })
+ .ExceptionOnError($"Building test {project} failed for {framework}.");
+ System.IO.File.WriteAllLines(System.IO.Path.Combine(logFolder, $"{project}-{framework}-build.log"), runLog.ToArray());
+ }
+ }
+});
+
+///
+/// Run all tests for .NET Desktop and .NET Core
+///
+Task("TestAll")
+ .IsDependentOn("Test")
+ .IsDependentOn("TestCore")
+ .Does(() =>{});
+
+///
+/// Run all tests for Travis CI .NET Desktop and .NET Core
+///
+Task("TravisTestAll")
+ .IsDependentOn("Cleanup")
+ .IsDependentOn("TestAll")
+ .Does(() =>{});
+
+///
+/// Run tests for .NET Core (using .NET CLI).
+///
+Task("TestCore")
+ .IsDependentOn("Setup")
+ .IsDependentOn("Restore")
+ .Does(() =>
+{
+ var testProjects = buildPlan.TestProjects
+ .Where(pair => pair.Value.Any(framework => framework.Contains("netcoreapp")))
+ .Select(pair => pair.Key)
+ .ToList();
+
+ foreach (var testProject in testProjects)
+ {
+ var logFile = System.IO.Path.Combine(logFolder, $"{testProject}-core-result.xml");
+ var testWorkingDir = System.IO.Path.Combine(testFolder, testProject);
+ Run(dotnetcli, $"test -f netcoreapp1.0 -xml \"{logFile}\" -notrait category=failing", testWorkingDir)
+ .ExceptionOnError($"Test {testProject} failed for .NET Core.");
+ }
+});
+
+///
+/// Run tests for other frameworks (using XUnit2).
+///
+Task("Test")
+ .IsDependentOn("Setup")
+ .IsDependentOn("BuildTest")
+ .Does(() =>
+{
+ foreach (var pair in buildPlan.TestProjects)
+ {
+ foreach (var framework in pair.Value)
+ {
+ // Testing against core happens in TestCore
+ if (framework.Contains("netcoreapp"))
+ {
+ continue;
+ }
+
+ var project = pair.Key;
+ var frameworkFolder = System.IO.Path.Combine(testFolder, project, "bin", testConfiguration, framework);
+ var runtime = System.IO.Directory.GetDirectories(frameworkFolder).First();
+ var instanceFolder = System.IO.Path.Combine(frameworkFolder, runtime);
+
+ // Copy xunit executable to test folder to solve path errors
+ var xunitToolsFolder = System.IO.Path.Combine(toolsFolder, "xunit.runner.console", "tools");
+ var xunitInstancePath = System.IO.Path.Combine(instanceFolder, "xunit.console.exe");
+ System.IO.File.Copy(System.IO.Path.Combine(xunitToolsFolder, "xunit.console.exe"), xunitInstancePath, true);
+ System.IO.File.Copy(System.IO.Path.Combine(xunitToolsFolder, "xunit.runner.utility.desktop.dll"), System.IO.Path.Combine(instanceFolder, "xunit.runner.utility.desktop.dll"), true);
+ var targetPath = System.IO.Path.Combine(instanceFolder, $"{project}.dll");
+ var logFile = System.IO.Path.Combine(logFolder, $"{project}-{framework}-result.xml");
+ var arguments = $"\"{targetPath}\" -parallel none -xml \"{logFile}\" -notrait category=failing";
+ if (IsRunningOnWindows())
+ {
+ Run(xunitInstancePath, arguments, instanceFolder)
+ .ExceptionOnError($"Test {project} failed for {framework}");
+ }
+ else
+ {
+ Run("mono", $"\"{xunitInstancePath}\" {arguments}", instanceFolder)
+ .ExceptionOnError($"Test {project} failed for {framework}");
+ }
+ }
+ }
+});
+
+///
+/// Build, publish and package artifacts.
+/// Targets all RIDs specified in build.json unless restricted by RestrictToLocalRuntime.
+/// No dependencies on other tasks to support quick builds.
+///
+Task("OnlyPublish")
+ .IsDependentOn("Setup")
+ .Does(() =>
+{
+ var project = buildPlan.MainProject;
+ var projectFolder = System.IO.Path.Combine(sourceFolder, project);
+ foreach (var framework in buildPlan.Frameworks)
+ {
+ foreach (var runtime in buildPlan.Rids)
+ {
+ var outputFolder = System.IO.Path.Combine(publishFolder, project, runtime, framework);
+ var publishArguments = "publish";
+ if (!runtime.Equals("default"))
+ {
+ publishArguments = $"{publishArguments} --runtime {runtime}";
+ }
+ publishArguments = $"{publishArguments} --framework {framework} --configuration {configuration}";
+ publishArguments = $"{publishArguments} --output \"{outputFolder}\" \"{projectFolder}\"";
+ Run(dotnetcli, publishArguments)
+ .ExceptionOnError($"Failed to publish {project} / {framework}");
+
+ if (requireArchive)
+ {
+ Package(runtime, framework, outputFolder, packageFolder, buildPlan.MainProject.ToLower());
+ }
+ }
+ }
+ CreateRunScript(System.IO.Path.Combine(publishFolder, project, "default"), scriptFolder);
+});
+
+///
+/// Alias for OnlyPublish.
+/// Targets all RIDs as specified in build.json.
+///
+Task("AllPublish")
+ .IsDependentOn("Restore")
+ .IsDependentOn("OnlyPublish")
+ .Does(() =>
+{
+});
+
+///
+/// Restrict the RIDs for the local default.
+///
+Task("RestrictToLocalRuntime")
+ .IsDependentOn("Setup")
+ .Does(() =>
+{
+ buildPlan.Rids = new string[] {"default"};
+});
+
+///
+/// Alias for OnlyPublish.
+/// Restricts publishing to local RID.
+///
+Task("LocalPublish")
+ .IsDependentOn("Restore")
+ .IsDependentOn("RestrictToLocalRuntime")
+ .IsDependentOn("OnlyPublish")
+ .Does(() =>
+{
+});
+
+///
+/// Test the published binaries if they start up without errors.
+/// Uses builds corresponding to local RID.
+///
+Task("TestPublished")
+ .IsDependentOn("Setup")
+ .Does(() =>
+{
+ var project = buildPlan.MainProject;
+ var projectFolder = System.IO.Path.Combine(sourceFolder, project);
+ var scriptsToTest = new string[] {"SQLTOOLSSERVICE.Core"};//TODO
+ foreach (var script in scriptsToTest)
+ {
+ var scriptPath = System.IO.Path.Combine(scriptFolder, script);
+ var didNotExitWithError = Run($"{shell}", $"{shellArgument} \"{scriptPath}\" -s \"{projectFolder}\" --stdio",
+ new RunOptions
+ {
+ TimeOut = 10000
+ })
+ .DidTimeOut;
+ if (!didNotExitWithError)
+ {
+ throw new Exception($"Failed to run {script}");
+ }
+ }
+});
+
+///
+/// Clean install path.
+///
+Task("CleanupInstall")
+ .Does(() =>
+{
+ if (System.IO.Directory.Exists(installFolder))
+ {
+ System.IO.Directory.Delete(installFolder, true);
+ }
+ System.IO.Directory.CreateDirectory(installFolder);
+});
+
+///
+/// Quick build.
+///
+Task("Quick")
+ .IsDependentOn("Cleanup")
+ .IsDependentOn("LocalPublish")
+ .Does(() =>
+{
+});
+
+///
+/// Quick build + install.
+///
+Task("Install")
+ .IsDependentOn("Cleanup")
+ .IsDependentOn("LocalPublish")
+ .IsDependentOn("CleanupInstall")
+ .Does(() =>
+{
+ var project = buildPlan.MainProject;
+ foreach (var framework in buildPlan.Frameworks)
+ {
+ var outputFolder = System.IO.Path.GetFullPath(System.IO.Path.Combine(publishFolder, project, "default", framework));
+ var targetFolder = System.IO.Path.GetFullPath(System.IO.Path.Combine(installFolder, framework));
+ // Copy all the folders
+ foreach (var directory in System.IO.Directory.GetDirectories(outputFolder, "*", SearchOption.AllDirectories))
+ System.IO.Directory.CreateDirectory(System.IO.Path.Combine(targetFolder, directory.Substring(outputFolder.Length + 1)));
+ //Copy all the files
+ foreach (string file in System.IO.Directory.GetFiles(outputFolder, "*", SearchOption.AllDirectories))
+ System.IO.File.Copy(file, System.IO.Path.Combine(targetFolder, file.Substring(outputFolder.Length + 1)), true);
+ }
+ CreateRunScript(installFolder, scriptFolder);
+});
+
+///
+/// Full build targeting all RIDs specified in build.json.
+///
+Task("All")
+ .IsDependentOn("Cleanup")
+ .IsDependentOn("Restore")
+ .IsDependentOn("TestAll")
+ .IsDependentOn("AllPublish")
+ //.IsDependentOn("TestPublished")
+ .Does(() =>
+{
+});
+
+///
+/// Full build targeting local RID.
+///
+Task("Local")
+ .IsDependentOn("Cleanup")
+ .IsDependentOn("Restore")
+ .IsDependentOn("TestAll")
+ .IsDependentOn("LocalPublish")
+ // .IsDependentOn("TestPublished")
+ .Does(() =>
+{
+});
+
+///
+/// Build centered around producing the final artifacts for Travis
+///
+/// The tests are run as a different task "TestAll"
+///
+Task("Travis")
+ .IsDependentOn("Cleanup")
+ .IsDependentOn("Restore")
+ .IsDependentOn("AllPublish")
+ // .IsDependentOn("TestPublished")
+ .Does(() =>
+{
+});
+
+///
+/// Update the package versions within project.json files.
+/// Uses depversion.json file as input.
+///
+Task("SetPackageVersions")
+ .Does(() =>
+{
+ var jDepVersion = JObject.Parse(System.IO.File.ReadAllText(System.IO.Path.Combine(workingDirectory, "depversion.json")));
+ var projects = System.IO.Directory.GetFiles(sourceFolder, "project.json", SearchOption.AllDirectories).ToList();
+ projects.AddRange(System.IO.Directory.GetFiles(testFolder, "project.json", SearchOption.AllDirectories));
+ foreach (var project in projects)
+ {
+ var jProject = JObject.Parse(System.IO.File.ReadAllText(project));
+ var dependencies = jProject.SelectTokens("dependencies")
+ .Union(jProject.SelectTokens("frameworks.*.dependencies"))
+ .SelectMany(dependencyToken => dependencyToken.Children());
+ foreach (JProperty dependency in dependencies)
+ {
+ if (jDepVersion[dependency.Name] != null)
+ {
+ dependency.Value = jDepVersion[dependency.Name];
+ }
+ }
+ System.IO.File.WriteAllText(project, JsonConvert.SerializeObject(jProject, Formatting.Indented));
+ }
+});
+
+///
+/// Default Task aliases to Local.
+///
+Task("Default")
+ .IsDependentOn("Local")
+ .Does(() =>
+{
+});
+
+///
+/// Default to Local.
+///
+RunTarget(target);
diff --git a/build.json b/build.json
new file mode 100644
index 00000000..a0741723
--- /dev/null
+++ b/build.json
@@ -0,0 +1,18 @@
+{
+ "UseSystemDotNetPath": "true",
+ "DotNetFolder": ".dotnet",
+ "DotNetInstallScriptURL": "https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0-preview2/scripts/obtain",
+ "DotNetChannel": "preview",
+ "DotNetVersion": "1.0.0-preview2-003121",
+ "BuildToolsFolder": ".tools",
+ "ArtifactsFolder": "artifacts",
+ "TestProjects": {
+ "Microsoft.SqlTools.ServiceLayer.Test": [
+ "netcoreapp1.0"
+ ]
+ },
+ "Frameworks": [
+ "netcoreapp1.0"
+ ],
+ "MainProject": "Microsoft.SqlTools.ServiceLayer"
+}
diff --git a/build.ps1 b/build.ps1
new file mode 100644
index 00000000..68dc2c1e
--- /dev/null
+++ b/build.ps1
@@ -0,0 +1,3 @@
+$Env:SQLTOOLSSERVICE_PACKAGE_OSNAME = "win-x64"
+.\scripts\cake-bootstrap.ps1 -experimental @args
+exit $LASTEXITCODE
diff --git a/build.sh b/build.sh
new file mode 100644
index 00000000..90332a42
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+# Handle to many files on osx
+if [ "$TRAVIS_OS_NAME" == "osx" ] || [ `uname` == "Darwin" ]; then
+ ulimit -n 4096
+fi
+
+if [ "$TRAVIS_OS_NAME" == "osx" ] || [ `uname` == "Darwin" ]; then
+ export SQLTOOLSSERVICE_PACKAGE_OSNAME=osx-x64
+else
+ export SQLTOOLSSERVICE_PACKAGE_OSNAME=linux-x64
+fi
+bash ./scripts/cake-bootstrap.sh "$@"
diff --git a/scripts/archiving.cake b/scripts/archiving.cake
new file mode 100644
index 00000000..4b012986
--- /dev/null
+++ b/scripts/archiving.cake
@@ -0,0 +1,104 @@
+#load "runhelpers.cake"
+
+using System.IO.Compression;
+using System.Text.RegularExpressions;
+
+///
+/// Generate the build identifier based on the RID and framework identifier.
+/// Special rules when running on Travis (for publishing purposes).
+///
+/// The RID
+/// The framework identifier
+/// The designated build identifier
+string GetBuildIdentifier(string runtime, string framework)
+{
+ var runtimeShort = "";
+ // Default RID uses package name set in build script
+ if (runtime.Equals("default"))
+ {
+ runtimeShort = Environment.GetEnvironmentVariable("SQLTOOLSSERVICE_PACKAGE_OSNAME");
+ }
+ else
+ {
+ // Remove version number. Note: because there are separate versions for Ubuntu 14 and 16,
+ // we treat Ubuntu as a special case.
+ if (runtime.StartsWith("ubuntu.14"))
+ {
+ runtimeShort = "ubuntu14-x64";
+ }
+ else if (runtime.StartsWith("ubuntu.16"))
+ {
+ runtimeShort = "ubuntu16-x64";
+ }
+ else
+ {
+ runtimeShort = Regex.Replace(runtime, "(\\d|\\.)*-", "-");
+ }
+ }
+
+ return $"{runtimeShort}-{framework}";
+}
+
+///
+/// Generate an archive out of the given published folder.
+/// Use ZIP for Windows runtimes.
+/// Use TAR.GZ for non-Windows runtimes.
+/// Use 7z to generate TAR.GZ on Windows if available.
+///
+/// The RID
+/// The folder containing the files to package
+/// The target archive name (without extension)
+void DoArchive(string runtime, string contentFolder, string archiveName)
+{
+ // On all platforms use ZIP for Windows runtimes
+ if (runtime.Contains("win") || (runtime.Equals("default") && IsRunningOnWindows()))
+ {
+ var zipFile = $"{archiveName}.zip";
+ Zip(contentFolder, zipFile);
+ }
+ // On all platforms use TAR.GZ for Unix runtimes
+ else
+ {
+ var tarFile = $"{archiveName}.tar.gz";
+ // Use 7z to create TAR.GZ on Windows
+ if (IsRunningOnWindows())
+ {
+ var tempFile = $"{archiveName}.tar";
+ try
+ {
+ Run("7z", $"a \"{tempFile}\"", contentFolder)
+ .ExceptionOnError($"Tar-ing failed for {contentFolder} {archiveName}");
+ Run("7z", $"a \"{tarFile}\" \"{tempFile}\"", contentFolder)
+ .ExceptionOnError($"Compression failed for {contentFolder} {archiveName}");
+ System.IO.File.Delete(tempFile);
+ }
+ catch(Win32Exception)
+ {
+ Information("Warning: 7z not available on PATH to pack tar.gz results");
+ }
+ }
+ // Use tar to create TAR.GZ on Unix
+ else
+ {
+ Run("tar", $"czf \"{tarFile}\" .", contentFolder)
+ .ExceptionOnError($"Compression failed for {contentFolder} {archiveName}");
+ }
+ }
+}
+
+///
+/// Package a given output folder using a build identifier generated from the RID and framework identifier.
+///
+/// The RID
+/// The framework identifier
+/// The folder containing the files to package
+/// The destination folder for the archive
+/// The project name
+void Package(string runtime, string framework, string contentFolder, string packageFolder, string projectName)
+{
+ var buildIdentifier = GetBuildIdentifier(runtime, framework);
+ if (buildIdentifier != null)
+ {
+ DoArchive(runtime, contentFolder, $"{packageFolder}/{projectName}-{buildIdentifier}");
+ }
+}
\ No newline at end of file
diff --git a/scripts/artifacts.cake b/scripts/artifacts.cake
new file mode 100644
index 00000000..f448fe3f
--- /dev/null
+++ b/scripts/artifacts.cake
@@ -0,0 +1,43 @@
+#load "runhelpers.cake"
+
+///
+/// Generate the scripts which target the SQLTOOLSSERVICE binaries.
+///
+/// The root folder where the publised (or installed) binaries are located
+void CreateRunScript(string outputRoot, string scriptFolder)
+{
+ if (IsRunningOnWindows())
+ {
+ var coreScript = System.IO.Path.Combine(scriptFolder, "SQLTOOLSSERVICE.Core.cmd");
+ var sqlToolsServicePath = System.IO.Path.Combine(System.IO.Path.GetFullPath(outputRoot), "{0}", "SQLTOOLSSERVICE");
+ var content = new string[] {
+ "SETLOCAL",
+ "",
+ $"\"{sqlToolsServicePath}\" %*"
+ };
+ if (System.IO.File.Exists(coreScript))
+ {
+ System.IO.File.Delete(coreScript);
+ }
+ content[2] = String.Format(content[2], "netcoreapp1.0");
+ System.IO.File.WriteAllLines(coreScript, content);
+ }
+ else
+ {
+ var coreScript = System.IO.Path.Combine(scriptFolder, "SQLTOOLSSERVICE.Core");
+ var sqlToolsServicePath = System.IO.Path.Combine(System.IO.Path.GetFullPath(outputRoot), "{1}", "SQLTOOLSSERVICE");
+ var content = new string[] {
+ "#!/bin/bash",
+ "",
+ $"{{0}} \"{sqlToolsServicePath}{{2}}\" \"$@\""
+ };
+
+ if (System.IO.File.Exists(coreScript))
+ {
+ System.IO.File.Delete(coreScript);
+ }
+ content[2] = String.Format(content[2], "", "netcoreapp1.0", "");
+ System.IO.File.WriteAllLines(coreScript, content);
+ Run("chmod", $"+x \"{coreScript}\"");
+ }
+}
\ No newline at end of file
diff --git a/scripts/cake-bootstrap.ps1 b/scripts/cake-bootstrap.ps1
new file mode 100644
index 00000000..a87c1478
--- /dev/null
+++ b/scripts/cake-bootstrap.ps1
@@ -0,0 +1,110 @@
+<#
+
+.SYNOPSIS
+This is a Powershell script to bootstrap a Cake build.
+
+.DESCRIPTION
+This Powershell script will download NuGet if missing, restore NuGet tools (including Cake)
+and execute your Cake build script with the parameters you provide.
+
+.PARAMETER Script
+The build script to execute.
+.PARAMETER Target
+The build script target to run.
+.PARAMETER Configuration
+The build configuration to use.
+.PARAMETER Verbosity
+Specifies the amount of information to be displayed.
+Tells Cake to use the latest Roslyn release.
+.PARAMETER WhatIf
+Performs a dry run of the build script.
+No tasks will be executed.
+.PARAMETER Mono
+Tells Cake to use the Mono scripting engine.
+
+.LINK
+http://cakebuild.net
+
+#>
+
+[CmdletBinding()]
+Param(
+ [string]$Script = "build.cake",
+ [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
+ [string]$Verbosity = "Verbose",
+ [Alias("DryRun","Noop")]
+ [switch]$WhatIf,
+ [switch]$Mono,
+ [switch]$SkipToolPackageRestore,
+ [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
+ [string[]]$ScriptArgs
+)
+
+Write-Host "Preparing to run build script..."
+
+$PS_SCRIPT_ROOT = split-path -parent $MyInvocation.MyCommand.Definition;
+$TOOLS_DIR = Join-Path $PSScriptRoot "..\.tools"
+$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe"
+$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/v3.3.0/nuget.exe"
+$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe"
+$PACKAGES_CONFIG = Join-Path $PS_SCRIPT_ROOT "packages.config"
+
+# Should we use mono?
+$UseMono = "";
+if($Mono.IsPresent) {
+ Write-Verbose -Message "Using the Mono based scripting engine."
+ $UseMono = "-mono"
+}
+
+# Is this a dry run?
+$UseDryRun = "";
+if($WhatIf.IsPresent) {
+ $UseDryRun = "-dryrun"
+}
+
+# Make sure tools folder exists
+if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) {
+ Write-Verbose -Message "Creating tools directory..."
+ New-Item -Path $TOOLS_DIR -Type directory | out-null
+}
+
+# Try download NuGet.exe if not exists
+if (!(Test-Path $NUGET_EXE)) {
+ Write-Verbose -Message "Downloading NuGet.exe..."
+ try {
+ (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE)
+ } catch {
+ Throw "Could not download NuGet.exe."
+ }
+}
+
+# Save nuget.exe path to environment to be available to child processed
+$ENV:NUGET_EXE = $NUGET_EXE
+
+# Restore tools from NuGet?
+if(-Not $SkipToolPackageRestore.IsPresent)
+{
+ # Restore packages from NuGet.
+ Push-Location
+ Set-Location $TOOLS_DIR
+
+ Write-Verbose -Message "Restoring tools from NuGet..."
+ $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install $PACKAGES_CONFIG -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
+ Write-Verbose -Message ($NuGetOutput | out-string)
+
+ Pop-Location
+ if ($LASTEXITCODE -ne 0)
+ {
+ exit $LASTEXITCODE
+ }
+}
+
+# Make sure that Cake has been installed.
+if (!(Test-Path $CAKE_EXE)) {
+ Throw "Could not find Cake.exe at $CAKE_EXE"
+}
+
+# Start Cake
+Write-Host "Running build script..."
+Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $ScriptArgs"
+exit $LASTEXITCODE
diff --git a/scripts/cake-bootstrap.sh b/scripts/cake-bootstrap.sh
new file mode 100644
index 00000000..abc3ed43
--- /dev/null
+++ b/scripts/cake-bootstrap.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+###############################################################
+# This is the Cake bootstrapper script that is responsible for
+# downloading Cake and all specified tools from NuGet.
+###############################################################
+
+# Define directories.
+SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+TOOLS_DIR=$SCRIPT_DIR/../.tools
+export NUGET_EXE=$TOOLS_DIR/nuget.exe
+CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe
+PACKAGES_CONFIG=$SCRIPT_DIR/packages.config
+
+# Define default arguments.
+SCRIPT="build.cake"
+VERBOSITY="verbose"
+DRYRUN=
+SHOW_VERSION=false
+SCRIPT_ARGUMENTS=()
+
+# Parse arguments.
+for i in "$@"; do
+ case $1 in
+ -s|--script) SCRIPT="$2"; shift ;;
+ -v|--verbosity) VERBOSITY="$2"; shift ;;
+ -d|--dryrun) DRYRUN="-dryrun" ;;
+ --version) SHOW_VERSION=true ;;
+ --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;;
+ *) SCRIPT_ARGUMENTS+=("$1") ;;
+ esac
+ shift
+done
+
+# Make sure the tools folder exist.
+if [ ! -d "$TOOLS_DIR" ]; then
+ mkdir "$TOOLS_DIR"
+fi
+
+# Download NuGet if it does not exist.
+if [ ! -f "$NUGET_EXE" ]; then
+ echo "Downloading NuGet..."
+ curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/v3.3.0/nuget.exe
+ if [ $? -ne 0 ]; then
+ echo "An error occured while downloading nuget.exe."
+ exit 1
+ fi
+fi
+
+# Restore tools from NuGet.
+pushd "$TOOLS_DIR" >/dev/null
+mono "$NUGET_EXE" install "$PACKAGES_CONFIG" -ExcludeVersion -OutputDirectory "$TOOLS_DIR"
+if [ $? -ne 0 ]; then
+ echo "Could not restore NuGet packages."
+ exit 1
+fi
+popd >/dev/null
+
+# Make sure that Cake has been installed.
+if [ ! -f "$CAKE_EXE" ]; then
+ echo "Could not find Cake.exe at '$CAKE_EXE'."
+ exit 1
+fi
+
+# Start Cake
+if $SHOW_VERSION; then
+ exec mono "$CAKE_EXE" -version
+else
+ exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY $DRYRUN "${SCRIPT_ARGUMENTS[@]}"
+fi
diff --git a/scripts/packages.config b/scripts/packages.config
new file mode 100644
index 00000000..c4feb50f
--- /dev/null
+++ b/scripts/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/scripts/runhelpers.cake b/scripts/runhelpers.cake
new file mode 100644
index 00000000..03499601
--- /dev/null
+++ b/scripts/runhelpers.cake
@@ -0,0 +1,204 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+
+///
+/// Class encompassing the optional settings for running processes.
+///
+public class RunOptions
+{
+ ///
+ /// The working directory of the process.
+ ///
+ public string WorkingDirectory { get; set; }
+ ///
+ /// Container logging the StandardOutput content.
+ ///
+ public IList StandardOutputListing { get; set; }
+ ///
+ /// Desired maximum time-out for the process
+ ///
+ public int TimeOut { get; set; }
+}
+
+///
+/// Wrapper for the exit code and state.
+/// Used to query the result of an execution with method calls.
+///
+public struct ExitStatus
+{
+ private int _code;
+ private bool _timeOut;
+ ///
+ /// Default constructor when the execution finished.
+ ///
+ /// The exit code
+ public ExitStatus(int code)
+ {
+ this._code = code;
+ this._timeOut = false;
+ }
+ ///
+ /// Default constructor when the execution potentially timed out.
+ ///
+ /// The exit code
+ /// True if the execution timed out
+ public ExitStatus(int code, bool timeOut)
+ {
+ this._code = code;
+ this._timeOut = timeOut;
+ }
+ ///
+ /// Flag signalling that the execution timed out.
+ ///
+ public bool DidTimeOut { get { return _timeOut; } }
+ ///
+ /// Implicit conversion from ExitStatus to the exit code.
+ ///
+ /// The exit status
+ /// The exit code
+ public static implicit operator int(ExitStatus exitStatus)
+ {
+ return exitStatus._code;
+ }
+ ///
+ /// Trigger Exception for non-zero exit code.
+ ///
+ /// The message to use in the Exception
+ /// The exit status for further queries
+ public ExitStatus ExceptionOnError(string errorMessage)
+ {
+ if (this._code != 0)
+ {
+ throw new Exception(errorMessage);
+ }
+ return this;
+ }
+}
+
+///
+/// Run the given executable with the given arguments.
+///
+/// Executable to run
+/// Arguments
+/// The exit status for further queries
+ExitStatus Run(string exec, string args)
+{
+ return Run(exec, args, new RunOptions());
+}
+
+///
+/// Run the given executable with the given arguments.
+///
+/// Executable to run
+/// Arguments
+/// Working directory
+/// The exit status for further queries
+ExitStatus Run(string exec, string args, string workingDirectory)
+{
+ return Run(exec, args,
+ new RunOptions()
+ {
+ WorkingDirectory = workingDirectory
+ });
+}
+
+///
+/// Run the given executable with the given arguments.
+///
+/// Executable to run
+/// Arguments
+/// Optional settings
+/// The exit status for further queries
+ExitStatus Run(string exec, string args, RunOptions runOptions)
+{
+ var workingDirectory = runOptions.WorkingDirectory ?? System.IO.Directory.GetCurrentDirectory();
+ var process = System.Diagnostics.Process.Start(
+ new ProcessStartInfo(exec, args)
+ {
+ WorkingDirectory = workingDirectory,
+ UseShellExecute = false,
+ RedirectStandardOutput = runOptions.StandardOutputListing != null
+ });
+ if (runOptions.StandardOutputListing != null)
+ {
+ process.OutputDataReceived += (s, e) =>
+ {
+ if (e.Data != null)
+ {
+ runOptions.StandardOutputListing.Add(e.Data);
+ }
+ };
+ process.BeginOutputReadLine();
+ }
+ if (runOptions.TimeOut == 0)
+ {
+ process.WaitForExit();
+ return new ExitStatus(process.ExitCode);
+ }
+ else
+ {
+ bool finished = process.WaitForExit(runOptions.TimeOut);
+ if (finished)
+ {
+ return new ExitStatus(process.ExitCode);
+ }
+ else
+ {
+ KillProcessTree(process);
+ return new ExitStatus(0, true);
+ }
+ }
+}
+
+///
+/// Run restore with the given arguments
+///
+/// Executable to run
+/// Arguments
+/// Optional settings
+/// The exit status for further queries
+ExitStatus RunRestore(string exec, string args, string workingDirectory)
+{
+ Information("Restoring packages....");
+ var p = StartAndReturnProcess(exec,
+ new ProcessSettings
+ {
+ Arguments = args,
+ RedirectStandardOutput = true,
+ WorkingDirectory = workingDirectory
+ });
+ p.WaitForExit();
+ var exitCode = p.GetExitCode();
+
+ if (exitCode == 0)
+ {
+ Information("Package restore successful!");
+ }
+ else
+ {
+ Error(string.Join("\n", p.GetStandardOutput()));
+ }
+ return new ExitStatus(exitCode);
+}
+
+///
+/// Kill the given process and all its child processes.
+///
+/// Root process
+public void KillProcessTree(Process process)
+{
+ // Child processes are not killed on Windows by default
+ // Use TASKKILL to kill the process hierarchy rooted in the process
+ if (IsRunningOnWindows())
+ {
+ StartProcess($"TASKKILL",
+ new ProcessSettings
+ {
+ Arguments = $"/PID {process.Id} /T /F",
+ });
+ }
+ else
+ {
+ process.Kill();
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/project.json b/src/Microsoft.SqlTools.ServiceLayer/project.json
index a0a73439..02e977aa 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/project.json
+++ b/src/Microsoft.SqlTools.ServiceLayer/project.json
@@ -13,18 +13,29 @@
"Microsoft.SqlServer.Smo": "140.1.5",
"System.Security.SecureString": "4.0.0",
"System.Collections.Specialized": "4.0.1",
- "System.ComponentModel.TypeConverter": "4.1.0",
- "System.Diagnostics.TraceSource": "4.0.0"
+ "System.ComponentModel.TypeConverter": "4.1.0",
+ "System.Diagnostics.TraceSource": "4.0.0",
+ "NETStandard.Library": "1.6.0",
+ "Microsoft.NETCore.Runtime.CoreCLR": "1.0.2",
+ "Microsoft.NETCore.DotNetHostPolicy": "1.0.1",
+ "System.Diagnostics.Process": "4.1.0",
+ "System.Threading.Thread": "4.0.0"
},
"frameworks": {
"netcoreapp1.0": {
- "dependencies": {
- "Microsoft.NETCore.App": {
- "type": "platform",
- "version": "1.0.0"
- }
- },
- "imports": "dnxcore50"
+ "imports": "dnxcore50"
}
+ },
+ "runtimes": {
+ "win7-x64": {},
+ "win7-x86": {},
+ "osx.10.11-x64": {},
+ "ubuntu.14.04-x64": {},
+ "ubuntu.16.04-x64": {},
+ "centos.7-x64": {},
+ "rhel.7.2-x64": {},
+ "debian.8-x64": {},
+ "fedora.23-x64": {},
+ "opensuse.13.2-x64": {}
}
}