diff --git a/.travis.yml b/.travis.yml index fddd66ab71..075a9b1520 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,55 +4,15 @@ dist: trusty env: global: secure: m2PtYwYOhaK0uFMZ19ZxApZwWZeAIq1dS//jx/5I3txpIWD+TfycQMAWYxycFJ/GJkeVF29P4Zz1uyS2XKKjPJpp2Pds98FNQyDv3OftpLAVa0drsjfhurVlBmSdrV7GH6ncKfvhd+h7KVK5vbZc+NeR4dH7eNvN/jraS//AMJg= -mono: - - 4.8.0 +mono: beta +dotnet: 1.0.4 os: - linux - osx osx_image: xcode7.3 -# Ensure MSBuild is installed -before_install: - - | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF - echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list - sudo apt-get -qq update - sudo apt-get install msbuild - fi - - - | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then - brew update - brew install jq - brew install openssl - ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/ - ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/ - brew list - fi - - - | - # This is a temporary fix to workaround a problem where Travis picks up a build of Mono on OSX that - # includes a broken version of MSBuild. - # - # See https://github.com/mono/msbuild/commit/cff4013ba3a69f82dc0ae96b3e15af700d8f74ef - # for the fix this is replicating. - if [ "$TRAVIS_OS_NAME" == "osx" ]; then - MSBUILD_BIN=/Library/Frameworks/Mono.framework/Versions/4.8.0/lib/mono/msbuild/15.0/bin - - if [ ! -d $MSBUILD_BIN ] || [ -f $MSBUILD_BIN/System.Reflection.Metadata.dll ]; then - echo "WORKAROUND: Time to remove System.Reflection.Metadata.dll workaround" - else - echo "WORKAROUND: Copying System.Reflection.Metadata.dll to Mono MSBuild" - sudo cp $MSBUILD_BIN/Roslyn/System.Reflection.Metadata.dll $MSBUILD_BIN/System.Reflection.Metadata.dll - fi - fi - - - msbuild /version - script: - - travis_retry ./build.sh --target TravisTestAll - - ./build.sh --target Travis --archive + - ./build.sh --target All --archive addons: apt: diff --git a/BUILD.md b/BUILD.md index 49fc459ed0..7c21f80a6a 100644 --- a/BUILD.md +++ b/BUILD.md @@ -6,7 +6,7 @@ In order to build OmniSharp, the [.NET 4.6 targeting pack](http://go.microsoft.c ## macOS -**Mono 4.8.0** or greater is required. You can install this using the latest [.pkg](http://www.mono-project.com/download/#download-mac) or install it view [Homebrew](https://brew.sh/): +**Mono 5.2.0** or greater is required. You can install this using the latest [.pkg](http://www.mono-project.com/download/#download-mac) or install it view [Homebrew](https://brew.sh/): ``` brew update @@ -18,13 +18,7 @@ brew install caskroom/cask/mono-mdk Because OmniSharp uses the .NET Core SDK as part of the build, not all Linux distros are supported. A good rule of thumb is to check the list [here](https://www.microsoft.com/net/download/linux) to see if your particular distro is supported. -**Mono 4.8.0** or greater is required. Each distro or derivative has it's own set of instructions for installing Mono which you can find [here](http://www.mono-project.com/download/#download-lin). - -In addition, the `msbuild` package must be installed. On Debian, Ubuntu, and derivatives, this can be achieved by first adding the Mono Project GPG signing key and package repository using the instructions [here](http://www.mono-project.com/docs/getting-started/install/linux/#debian-ubuntu-and-derivatives). Then, install msbuild via apt-get. - -``` -sudo apt-get install msbuild -``` +**Mono 5.2.0** or greater is required. Each distro or derivative has it's own set of instructions for installing Mono which you can find [here](http://www.mono-project.com/download/#download-lin). # Usage diff --git a/build.cake b/build.cake index d3c93f8057..a85a9730f8 100644 --- a/build.cake +++ b/build.cake @@ -3,6 +3,8 @@ #load "scripts/archiving.cake" #load "scripts/artifacts.cake" #load "scripts/msbuild.cake" +#load "scripts/platform.cake" +#load "scripts/validation.cake" using System.ComponentModel; using System.Net; @@ -12,15 +14,17 @@ var target = Argument("target", "Default"); var configuration = Argument("configuration", "Release"); var testConfiguration = Argument("test-configuration", "Debug"); var installFolder = Argument("install-path", - CombinePaths(Environment.GetEnvironmentVariable(IsRunningOnWindows() ? "USERPROFILE" : "HOME"), ".omnisharp", "local")); + CombinePaths(Environment.GetEnvironmentVariable(Platform.Current.IsWindows ? "USERPROFILE" : "HOME"), ".omnisharp", "local")); var requireArchive = HasArgument("archive"); var useGlobalDotNetSdk = HasArgument("use-global-dotnet-sdk"); Log.Context = Context; -var env = new BuildEnvironment(IsRunningOnWindows(), useGlobalDotNetSdk); +var env = new BuildEnvironment(useGlobalDotNetSdk); var buildPlan = BuildPlan.Load(env); +Information("Current platform: {0}", Platform.Current); + /// /// Clean artifacts. /// @@ -64,7 +68,7 @@ Task("PopulateRuntimes") .IsDependentOn("BuildEnvironment") .Does(() => { - if (IsRunningOnWindows() && string.Equals(Environment.GetEnvironmentVariable("APPVEYOR"), "True")) + if (Platform.Current.IsWindows && string.Equals(Environment.GetEnvironmentVariable("APPVEYOR"), "True")) { buildPlan.SetTargetRids( "default", // To allow testing the published artifact @@ -95,34 +99,44 @@ Task("PopulateRuntimes") void ParseDotNetInfoValues(IEnumerable lines, out string version, out string rid, out string basePath) { - var keyValueMap = new Dictionary(); + version = null; + rid = null; + basePath = null; + foreach (var line in lines) { - var index = line.IndexOf(":"); - if (index >= 0) + var colonIndex = line.IndexOf(':'); + if (colonIndex >= 0) { - var key = line.Substring(0, index).Trim(); - var value = line.Substring(index + 1).Trim(); + var name = line.Substring(0, colonIndex).Trim(); + var value = line.Substring(colonIndex + 1).Trim(); - if (!string.IsNullOrEmpty(key) && - !string.IsNullOrEmpty(value)) + if (string.IsNullOrWhiteSpace(version) && name.Equals("Version", StringComparison.OrdinalIgnoreCase)) + { + version = value; + } + else if (string.IsNullOrWhiteSpace(rid) && name.Equals("RID", StringComparison.OrdinalIgnoreCase)) + { + rid = value; + } + else if (string.IsNullOrWhiteSpace(basePath) && name.Equals("Base Path", StringComparison.OrdinalIgnoreCase)) { - keyValueMap.Add(key, value); + basePath = value; } } } - if (!keyValueMap.TryGetValue("Version", out version)) + if (string.IsNullOrWhiteSpace(version)) { throw new Exception("Could not locate Version in 'dotnet --info' output."); } - if (!keyValueMap.TryGetValue("RID", out rid)) + if (string.IsNullOrWhiteSpace(rid)) { throw new Exception("Could not locate RID in 'dotnet --info' output."); } - if (!keyValueMap.TryGetValue("Base Path", out basePath)) + if (string.IsNullOrWhiteSpace(basePath)) { throw new Exception("Could not locate Base Path in 'dotnet --info' output."); } @@ -144,7 +158,7 @@ void InstallDotNetSdk(BuildEnvironment env, BuildPlan plan, string version, stri client.DownloadFile(url, scriptFilePath); } - if (!IsRunningOnWindows()) + if (!Platform.Current.IsWindows) { Run("chmod", $"+x '{scriptFilePath}'"); } @@ -166,10 +180,7 @@ void InstallDotNetSdk(BuildEnvironment env, BuildPlan plan, string version, stri Run(env.ShellCommand, $"{env.ShellArgument} {scriptFilePath} {string.Join(" ", argList)}").ExceptionOnError($"Failed to Install .NET Core SDK {version}"); } -/// -/// Install/update build environment. -/// -Task("BuildEnvironment") +Task("InstallDotNetCoreSdk") .Does(() => { if (!useGlobalDotNetSdk) @@ -184,6 +195,9 @@ Task("BuildEnvironment") version: buildPlan.LegacyDotNetVersion, installFolder: env.Folders.LegacyDotNetSdk); + string DOTNET_CLI_UI_LANGUAGE = "DOTNET_CLI_UI_LANGUAGE"; + var originalUILanguageValue = Environment.GetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE); + Environment.SetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE, "en-US"); // Capture 'dotnet --info' output and parse out RID. var lines = new List(); @@ -196,6 +210,10 @@ Task("BuildEnvironment") { throw new Exception("Failed to run 'dotnet --info'"); } + finally + { + Environment.SetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE, originalUILanguageValue); + } string version, rid, basePath; ParseDotNetInfoValues(lines, out version, out rid, out basePath); @@ -214,6 +232,25 @@ Task("BuildEnvironment") Information(" Base Path: {0}", basePath); }); +Task("ValidateEnvironment") + .Does(() => +{ + if (!Platform.Current.IsWindows) + { + ValidateMonoVersion(buildPlan); + } +}); + +/// +/// Install/update build environment. +/// +Task("BuildEnvironment") + .IsDependentOn("ValidateEnvironment") + .IsDependentOn("InstallDotNetCoreSdk") + .Does(() => +{ +}); + /// /// Restore required NuGet packages. /// @@ -266,13 +303,13 @@ Task("PrepareTestAssets") void BuildProject(BuildEnvironment env, string projectName, string projectFilePath, string configuration) { - var command = IsRunningOnWindows() + var command = Platform.Current.IsWindows ? env.DotNetCommand : env.ShellCommand; - var arguments = IsRunningOnWindows() + var arguments = Platform.Current.IsWindows ? $"build \"{projectFilePath}\" --configuration {configuration} /v:d" - : $"{env.ShellArgument} msbuild.{env.ShellScriptFileExtension} \"{projectFilePath}\" /p:Configuration={configuration} /v:d"; + : $"{env.ShellArgument} msbuild \"{projectFilePath}\" /p:Configuration={configuration} /v:d"; var logFileName = CombinePaths(env.Folders.ArtifactsLogs, $"{projectName}-build.log"); @@ -319,13 +356,6 @@ Task("TestAll") .IsDependentOn("TestCore") .Does(() =>{}); -/// -/// Run all tests for Travis CI .NET Desktop and .NET Core -/// -Task("TravisTestAll") - .IsDependentOn("Cleanup") - .IsDependentOn("TestAll"); - /// /// Run tests for .NET Core (using .NET CLI). /// @@ -370,7 +400,7 @@ Task("Test") var logFile = CombinePaths(env.Folders.ArtifactsLogs, $"{testProject}-desktop-result.xml"); var arguments = $"\"{targetPath}\" -parallel none -xml \"{logFile}\" -notrait category=failing"; - if (IsRunningOnWindows()) + if (Platform.Current.IsWindows) { Run(xunitInstancePath, arguments, instanceFolder) .ExceptionOnError($"Test {testProject} failed for net46"); @@ -380,7 +410,7 @@ Task("Test") // Copy the Mono-built Microsoft.Build.* binaries to the test folder. DirectoryHelper.Copy($"{env.Folders.MonoMSBuildLib}", instanceFolder); - Run("mono", $"\"{xunitInstancePath}\" {arguments}", instanceFolder) + Run("mono", $"--assembly-loader=strict \"{xunitInstancePath}\" {arguments}", instanceFolder) .ExceptionOnError($"Test {testProject} failed for net46"); } } @@ -388,7 +418,7 @@ Task("Test") bool IsNetFrameworkOnUnix(string framework) { - return !IsRunningOnWindows() + return !Platform.Current.IsWindows && !framework.StartsWith("netcore") && !framework.StartsWith("netstandard"); } @@ -469,7 +499,7 @@ Task("OnlyPublish") var args = GetPublishArguments(projectFileName, rid, framework, configuration, outputFolder); args = IsNetFrameworkOnUnix(framework) - ? $"{env.ShellArgument} msbuild.{env.ShellScriptFileExtension} {args}" + ? $"{env.ShellArgument} msbuild {args}" : args; Information("Publishing {0} for {1}/{2}...", projectName, framework, rid); @@ -481,7 +511,7 @@ Task("OnlyPublish") DirectoryHelper.Copy($"{env.Folders.MSBuildBase}-{framework}", CombinePaths(outputFolder, "msbuild")); // For OSX/Linux net46 builds, copy the MSBuild libraries built for Mono. - if (!IsRunningOnWindows() && framework == "net46") + if (!Platform.Current.IsWindows && framework == "net46") { DirectoryHelper.Copy($"{env.Folders.MonoMSBuildLib}", outputFolder); } @@ -612,17 +642,6 @@ Task("Local") .IsDependentOn("LocalPublish") .IsDependentOn("TestPublished"); -/// -/// 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"); - /// /// Default Task aliases to Local. /// diff --git a/build.json b/build.json index b5116761b0..6876c03480 100644 --- a/build.json +++ b/build.json @@ -3,6 +3,7 @@ "DotNetChannel": "preview", "DotNetVersion": "1.0.4", "LegacyDotNetVersion": "1.0.0-preview2-1-003177", + "RequiredMonoVersion": "5.2.0.196", "DownloadURL": "https://omnisharpdownload.blob.core.windows.net/ext", "MSBuildRuntimeForMono": "Microsoft.Build.Runtime.Mono-alpha4.zip", "MSBuildLibForMono": "Microsoft.Build.Lib.Mono-alpha4.zip", diff --git a/msbuild.sh b/msbuild.sh deleted file mode 100755 index 26917a3c6b..0000000000 --- a/msbuild.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -SDK_DIR="$(cd "$(dirname "$0")"/.dotnet/sdk/1.0.4/ && pwd -P)" - -echo $SDK_DIR - -export MSBuildExtensionsPath=$SDK_DIR/ -export CscToolExe=$SDK_DIR/Roslyn/RunCsc.sh -export MSBuildSDKsPath=$SDK_DIR/Sdks - -msbuild "$@" \ No newline at end of file diff --git a/msbuild/15.0/Microsoft.Common.targets/ImportAfter/Microsoft.NuGet.ImportAfter.targets b/msbuild/15.0/Microsoft.Common.targets/ImportAfter/Microsoft.NuGet.ImportAfter.targets deleted file mode 100644 index 433922aacf..0000000000 --- a/msbuild/15.0/Microsoft.Common.targets/ImportAfter/Microsoft.NuGet.ImportAfter.targets +++ /dev/null @@ -1,18 +0,0 @@ - - - - - $(MSBuildExtensionsPath)\NuGet.targets - - - diff --git a/msbuild/15.0/SolutionFile/ImportAfter/Microsoft.NuGet.ImportAfter.targets b/msbuild/15.0/SolutionFile/ImportAfter/Microsoft.NuGet.ImportAfter.targets deleted file mode 100644 index 433922aacf..0000000000 --- a/msbuild/15.0/SolutionFile/ImportAfter/Microsoft.NuGet.ImportAfter.targets +++ /dev/null @@ -1,18 +0,0 @@ - - - - - $(MSBuildExtensionsPath)\NuGet.targets - - - diff --git a/scripts/archiving.cake b/scripts/archiving.cake index 1e45454ed5..2f97937478 100644 --- a/scripts/archiving.cake +++ b/scripts/archiving.cake @@ -1,3 +1,4 @@ +#load "common.cake" #load "runhelpers.cake" using System.IO.Compression; @@ -64,7 +65,7 @@ string GetBuildIdentifier(string runtime, string framework) void DoArchive(string runtime, string contentFolder, string archiveName) { // On all platforms use ZIP for Windows runtimes - if (runtime.Contains("win") || (runtime.Equals("default") && IsRunningOnWindows())) + if (runtime.Contains("win") || (runtime.Equals("default") && Platform.Current.IsWindows)) { var zipFile = $"{archiveName}.zip"; Zip(contentFolder, zipFile); @@ -74,7 +75,7 @@ void DoArchive(string runtime, string contentFolder, string archiveName) { var tarFile = $"{archiveName}.tar.gz"; // Use 7z to create TAR.GZ on Windows - if (IsRunningOnWindows()) + if (Platform.Current.IsWindows) { var tempFile = $"{archiveName}.tar"; try @@ -85,7 +86,7 @@ void DoArchive(string runtime, string contentFolder, string archiveName) .ExceptionOnError($"Compression failed for {contentFolder} {archiveName}"); System.IO.File.Delete(tempFile); } - catch(Win32Exception) + catch (Win32Exception) { Information("Warning: 7z not available on PATH to pack tar.gz results"); } diff --git a/scripts/artifacts.cake b/scripts/artifacts.cake index e368a7c7b6..b7f1216422 100644 --- a/scripts/artifacts.cake +++ b/scripts/artifacts.cake @@ -1,57 +1,98 @@ +#load "common.cake" #load "runhelpers.cake" +using System.Collections.Generic; + /// /// Generate the scripts which target the OmniSharp binaries. /// /// The root folder where the publised (or installed) binaries are located void CreateRunScript(string outputRoot, string scriptFolder) { - if (IsRunningOnWindows()) - { - var desktopScript = System.IO.Path.Combine(scriptFolder, "OmniSharp.cmd"); - var coreScript = System.IO.Path.Combine(scriptFolder, "OmniSharp.Core.cmd"); - var omniSharpPath = System.IO.Path.Combine(System.IO.Path.GetFullPath(outputRoot), "{0}", "OmniSharp"); - var content = new string[] { - "SETLOCAL", - "", - $"\"{omniSharpPath}\" %*" - }; - if (System.IO.File.Exists(desktopScript)) - { - System.IO.File.Delete(desktopScript); - } - content[2] = String.Format(content[2], "net46"); - System.IO.File.WriteAllLines(desktopScript, content); - if (System.IO.File.Exists(coreScript)) - { - System.IO.File.Delete(coreScript); - } - content[2] = String.Format(content[2], "netcoreapp1.1"); - System.IO.File.WriteAllLines(coreScript, content); + CreateScript(outputRoot, scriptFolder, "net46"); + CreateScript(outputRoot, scriptFolder, "netcoreapp1.1"); +} + +private void CreateScript(string outputRoot, string scriptFolder, string framework) +{ + var scriptPath = GetScriptPath(scriptFolder, framework); + var omniSharpPath = GetOmniSharpPath(outputRoot, framework); + var content = GetScriptContent(omniSharpPath, framework); + + if (FileHelper.Exists(scriptPath)) + { + FileHelper.Delete(scriptPath); + } + + FileHelper.WriteAllLines(scriptPath, content); + + if (!Platform.Current.IsWindows) + { + Run("chmod", $"+x \"{scriptPath}\""); + } +} + +private string GetScriptPath(string scriptFolder, string framework) +{ + var result = CombinePaths(scriptFolder, "OmniSharp"); + + if (IsCore(framework)) + { + result += ".Core"; + } + + if (Platform.Current.IsWindows) + { + result += ".cmd"; + } + + return result; +} + +private string GetOmniSharpPath(string outputRoot, string framework) +{ + var result = CombinePaths(PathHelper.GetFullPath(outputRoot), framework, "OmniSharp"); + + if (!IsCore(framework)) + { + result += ".exe"; + } + + return result; +} + +private string[] GetScriptContent(string omniSharpPath, string framework) +{ + var lines = new List(); + + if (Platform.Current.IsWindows) + { + lines.Add("SETLOCAL"); } else { - var desktopScript = System.IO.Path.Combine(scriptFolder, "OmniSharp"); - var coreScript = System.IO.Path.Combine(scriptFolder, "OmniSharp.Core"); - var omniSharpPath = System.IO.Path.Combine(System.IO.Path.GetFullPath(outputRoot), "{1}", "OmniSharp"); - var content = new string[] { - "#!/bin/bash", - "", - $"{{0}} \"{omniSharpPath}{{2}}\" \"$@\"" - }; - if (System.IO.File.Exists(desktopScript)) - { - System.IO.File.Delete(desktopScript); - } - content[2] = String.Format(content[2], "mono", "net46", ".exe"); - System.IO.File.WriteAllLines(desktopScript, content); - Run("chmod", $"+x \"{desktopScript}\""); - if (System.IO.File.Exists(coreScript)) - { - System.IO.File.Delete(coreScript); - } - content[2] = String.Format(content[2], "", "netcoreapp1.1", ""); - System.IO.File.WriteAllLines(coreScript, content); - Run("chmod", $"+x \"{desktopScript}\""); + lines.Add("#!/bin/bash"); } + + lines.Add(""); + + var arguments = Platform.Current.IsWindows + ? "%*" + : "\"$@\""; + + if (IsCore(framework) || Platform.Current.IsWindows) + { + lines.Add($"\"{omniSharpPath}\" {arguments}"); + } + else // !isCore && !Platform.Current.IsWindows, i.e. Mono + { + lines.Add($"mono --assembly-loader=strict \"{omniSharpPath}\" {arguments}"); + } + + return lines.ToArray(); +} + +private bool IsCore(string framework) +{ + return framework.StartsWith("netcoreapp"); } \ No newline at end of file diff --git a/scripts/common.cake b/scripts/common.cake index aaeecf4f5a..0ed077f1c1 100644 --- a/scripts/common.cake +++ b/scripts/common.cake @@ -1,5 +1,7 @@ #addin "Newtonsoft.Json" +#load "platform.cake" + using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -27,6 +29,12 @@ public static class FileHelper Log.Debug(Verbosity.Diagnostic, "Delete file: {0}.", path); System.IO.File.Delete(path); } + + public static bool Exists(string path) => + System.IO.File.Exists(path); + + public static void WriteAllLines(string path, string[] contents) => + System.IO.File.WriteAllLines(path, contents); } public static class DirectoryHelper @@ -92,7 +100,7 @@ public static class PathHelper System.IO.Path.GetFileName(path); public static string GetFullPath(string path) => - System.IO.Path.Combine(path); + System.IO.Path.GetFullPath(path); } string CombinePaths(params string[] paths) @@ -156,7 +164,7 @@ public class BuildEnvironment public string ShellArgument { get; } public string ShellScriptFileExtension { get; } - public BuildEnvironment(bool isWindows, bool useGlobalDotNetSdk) + public BuildEnvironment(bool useGlobalDotNetSdk) { this.WorkingDirectory = PathHelper.GetFullPath( System.IO.Directory.GetCurrentDirectory()); @@ -168,9 +176,9 @@ public class BuildEnvironment this.LegacyDotNetCommand = PathHelper.Combine(this.Folders.LegacyDotNetSdk, "dotnet"); - this.ShellCommand = isWindows ? "powershell" : "bash"; - this.ShellArgument = isWindows ? "-NoProfile /Command" : "-C"; - this.ShellScriptFileExtension = isWindows ? "ps1" : "sh"; + this.ShellCommand = Platform.Current.IsWindows ? "powershell" : "bash"; + this.ShellArgument = Platform.Current.IsWindows ? "-NoProfile /Command" : "-C"; + this.ShellScriptFileExtension = Platform.Current.IsWindows ? "ps1" : "sh"; } } @@ -183,6 +191,7 @@ public class BuildPlan public string DotNetChannel { get; set; } public string DotNetVersion { get; set; } public string LegacyDotNetVersion { get; set; } + public string RequiredMonoVersion { get; set; } public string DownloadURL { get; set; } public string MSBuildRuntimeForMono { get; set; } public string MSBuildLibForMono { get; set; } diff --git a/scripts/msbuild.cake b/scripts/msbuild.cake index 33007148f0..70d1e83326 100644 --- a/scripts/msbuild.cake +++ b/scripts/msbuild.cake @@ -5,7 +5,7 @@ using System.Net; void SetupMSBuild(BuildEnvironment env, BuildPlan plan) { - if (!IsRunningOnWindows()) + if (!Platform.Current.IsWindows) { AcquireMonoMSBuild(env, plan); } @@ -46,7 +46,7 @@ private void SetupMSBuildForFramework(BuildEnvironment env, string framework) DirectoryHelper.Delete(msbuildFolder, recursive: true); } - if (!IsRunningOnWindows() && framework == "net46") + if (!Platform.Current.IsWindows && framework == "net46") { Information("Copying Mono MSBuild runtime for {0}...", framework); DirectoryHelper.Copy(env.Folders.MonoMSBuildRuntime, msbuildFolder); diff --git a/scripts/platform.cake b/scripts/platform.cake new file mode 100644 index 0000000000..f07405e4cd --- /dev/null +++ b/scripts/platform.cake @@ -0,0 +1,100 @@ +using System; +using System.Diagnostics; + +public sealed class Platform +{ + public static Platform Current { get; } = GetCurrentPlatform(); + + private readonly string _os; + private readonly string _architecture; + + public bool IsWindows => _os == "Windows"; + public bool IsMacOS => _os == "macOS"; + public bool IsLinux => _os == "Linux"; + + public bool Is32Bit => _architecture == "x86"; + public bool Is64Bit => _architecture == "x64"; + + private Platform(string os, string architecture) + { + _os = os; + _architecture = architecture; + } + + public override string ToString() => $"{_os} ({_architecture})"; + + private static Platform GetCurrentPlatform() + { + string os, architecture; + + // Simple check to see if this is Windows. + var platformId = (int)Environment.OSVersion.Platform; + if (platformId <= 3 || platformId == 5) + { + os = "Windows"; + + if (Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") == "x86" && + Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432") == null) + { + architecture = "x86"; + } + else + { + architecture = "x64"; + } + } + else + { + // If this is not Windows, run 'uname' to get the OS name and architecture. + var output = RunAndCaptureOutput("uname", "-s -m"); + var values = output.Split(' '); + var osName = values[0]; + var osArch = values[1]; + + os = osName.Equals("Darwin", StringComparison.OrdinalIgnoreCase) + ? "MacOS" + : "Linux"; + + if (osArch.Equals("x86", StringComparison.OrdinalIgnoreCase)) + { + architecture = "x86"; + } + else if (osArch.Equals("x86_64", StringComparison.OrdinalIgnoreCase)) + { + architecture = "x64"; + } + else + { + throw new Exception($"Unexpected architecture: {osArch}"); + } + } + + return new Platform(os, architecture); + } + + private static string RunAndCaptureOutput(string fileName, string arguments, string workingDirectory = null) + { + var startInfo = new ProcessStartInfo(fileName, arguments) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + UseShellExecute = false, + WorkingDirectory = workingDirectory ?? string.Empty, + }; + + try + { + var process = Process.Start(startInfo); + + var output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + + return output.Trim(); + } + catch (Exception ex) + { + throw new Exception($"Failed to launch '{fileName}' with args, '{arguments}'", ex); + } + } +} \ No newline at end of file diff --git a/scripts/runhelpers.cake b/scripts/runhelpers.cake index c8f99bc6c2..aacbbf3e96 100644 --- a/scripts/runhelpers.cake +++ b/scripts/runhelpers.cake @@ -1,5 +1,8 @@ +#load "platform.cake" + using System.Collections.Generic; using System.Diagnostics; +using System.Text; /// /// Class encompassing the optional settings for running processes. @@ -172,6 +175,21 @@ ExitStatus Run(string command, string arguments, RunOptions runOptions) } } +string RunAndCaptureOutput(string command, string arguments, string workingDirectory = null) +{ + var output = new List(); + Run(command, arguments, new RunOptions(workingDirectory, output)) + .ExceptionOnError($"Failed to run '{command}' with arguments, '{arguments}'."); + + var builder = new StringBuilder(); + foreach (var line in output) + { + builder.AppendLine(line); + } + + return builder.ToString().Trim(); +} + /// /// Run tool with the given arguments /// @@ -211,7 +229,7 @@ private 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()) + if (Platform.Current.IsWindows) { StartProcess($"TASKKILL", new ProcessSettings diff --git a/scripts/validation.cake b/scripts/validation.cake new file mode 100644 index 0000000000..1996c0fe32 --- /dev/null +++ b/scripts/validation.cake @@ -0,0 +1,63 @@ +#load "common.cake" +#load "runhelpers.cake" + +void ValidateMonoVersion(BuildPlan plan) +{ + if (Platform.Current.IsWindows) + { + return; + } + + // We require a minimum version of Mono for building on macOS/Linux. The version is specified in + // 'RequiredMonoVersion' of build.json. + + var monoVersionOutput = new List(); + Run("mono", "--version", new RunOptions(output: monoVersionOutput)) + .ExceptionOnError($"Could not launch 'mono'. Please ensure that Mono {plan.RequiredMonoVersion} or later is installed and on the path."); + + var ErrorMessage = $"Could not detect Mono version. Please ensure that Mono {plan.RequiredMonoVersion} or later is installed and on the path."; + + if (monoVersionOutput.Count == 0) + { + throw new Exception(ErrorMessage); + } + + var monoVersionText = monoVersionOutput[0]; + + if (string.IsNullOrWhiteSpace(monoVersionText)) + { + throw new Exception(ErrorMessage); + } + + // The first line of Mono's '--version' output looks like so: + // + // Mono JIT compiler version 5.2.0.196 (2017-02/5077205 Thu May 18 16:11:37 EDT 2017) + // + // Our approach at parsing out the version number is to search for the open parenthesis, + // and then grab the word before that. + + var openParenIndex = monoVersionText.IndexOf("("); + if (openParenIndex < 0) + { + throw new Exception(ErrorMessage); + } + + monoVersionText = monoVersionText.Substring(0, openParenIndex).Trim(); + + var lastSpaceIndex = monoVersionText.LastIndexOf(" "); + if (lastSpaceIndex < 0) + { + throw new Exception(ErrorMessage); + } + + monoVersionText = monoVersionText.Substring(lastSpaceIndex).Trim(); + + var monoVersion = new System.Version(monoVersionText); + + if (monoVersion < new System.Version(plan.RequiredMonoVersion)) + { + throw new Exception($"Detected Mono {monoVersion}, but a version >= {plan.RequiredMonoVersion} is required."); + } + + Information("Detected Mono version {0}", monoVersion); +} \ No newline at end of file