Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Use p/invoke rather than ugly bash commands to locate Mono on Unix #911

Merged
merged 4 commits into from
Jul 14, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 32 additions & 99 deletions src/OmniSharp.Abstractions/Utilities/PlatformHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;

namespace OmniSharp.Utilities
{
Expand All @@ -16,33 +16,47 @@ public static class PlatformHelper

public static bool IsWindows => Path.DirectorySeparatorChar == '\\';

private static string FindMonoPath()
{
// To locate Mono on unix, we use the 'which' command (https://en.wikipedia.org/wiki/Which_(Unix))
var monoFilePath = RunOnBashAndCaptureOutput("which", "mono");

if (string.IsNullOrEmpty(monoFilePath))
{
return null;
}

Console.WriteLine($"Discovered Mono file path: {monoFilePath}");
// http://man7.org/linux/man-pages/man3/realpath.3.html
// CharSet.Ansi is UTF8 on Unix
[DllImport("libc", EntryPoint = "realpath", CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr Unix_realpath(string path, IntPtr buffer);

// 'mono' is likely a symbolic link. Try to resolve it.
var resolvedMonoFilePath = ResolveSymbolicLink(monoFilePath);
// http://man7.org/linux/man-pages/man3/free.3.html
[DllImport("libc", EntryPoint = "free", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
private static extern void Unix_free(IntPtr ptr);

if (StringComparer.OrdinalIgnoreCase.Compare(monoFilePath, resolvedMonoFilePath) == 0)
/// <summary>
/// Returns the conanicalized absolute path from a given path, expanding symbolic links and resolving
/// references to /./, /../ and extra '/' path characters.
/// </summary>
private static string RealPath(string path)
{
if (IsWindows)
{
return monoFilePath;
throw new PlatformNotSupportedException($"{nameof(RealPath)} can only be called on Unix.");
}

Console.WriteLine($"Resolved symbolic link for Mono file path: {resolvedMonoFilePath}");
var ptr = Unix_realpath(path, IntPtr.Zero);
var result = Marshal.PtrToStringAnsi(ptr); // uses UTF8 on Unix
Unix_free(ptr);

return resolvedMonoFilePath;
return result;
}

private static string FindMonoPath()
{
return !IsWindows
? RealPath("mono")
: null;
}

private static string FindMonoXBuildFrameworksDirPath()
{
if (IsWindows)
{
return null;
}

const string defaultXBuildFrameworksDirPath = "/usr/lib/mono/xbuild-frameworks";
if (Directory.Exists(defaultXBuildFrameworksDirPath))
{
Expand Down Expand Up @@ -72,86 +86,5 @@ private static string FindMonoXBuildFrameworksDirPath()
? monoXBuildFrameworksDirPath
: null;
}

private static string ResolveSymbolicLink(string path)
{
var result = ResolveSymbolicLink(new List<string> { path });

return CanonicalizePath(result);
}

private static string ResolveSymbolicLink(List<string> paths)
{
while (!HasCycle(paths))
{
// We use 'readlink' to resolve symbolic links on unix. Note that OSX does not
// support the -f flag for recursively resolving symbolic links and canonicalzing
// the final path.

var originalPath = paths[paths.Count - 1];
var newPath = RunOnBashAndCaptureOutput("readlink", $"{originalPath}");

if (string.IsNullOrEmpty(newPath) ||
string.CompareOrdinal(originalPath, newPath) == 0)
{
return originalPath;
}

if (!newPath.StartsWith("/"))
{
var dir = File.Exists(originalPath)
? Path.GetDirectoryName(originalPath)
: originalPath;

newPath = Path.Combine(dir, newPath);
newPath = Path.GetFullPath(newPath);
}

paths.Add(newPath);
}

return null;
}

private static bool HasCycle(List<string> paths)
{
var target = paths[0];
for (var i = 1; i < paths.Count; i++)
{
var path = paths[i];

if (string.CompareOrdinal(target, path) == 0)
{
return true;
}
}

return false;
}

private static string CanonicalizePath(string path)
{
if (File.Exists(path))
{
return Path.Combine(
CanonicalizeDirectory(Path.GetDirectoryName(path)),
Path.GetFileName(path));
}
else
{
return CanonicalizeDirectory(path);
}
}

private static string CanonicalizeDirectory(string directoryName)
{
// Use "pwd -P" to get the directory name with all symbolic links on Unix.
return RunOnBashAndCaptureOutput("pwd", "-P", directoryName);
}

private static string RunOnBashAndCaptureOutput(string fileName, string arguments, string workingDirectory = null)
{
return ProcessHelper.RunAndCaptureOutput("/bin/bash", $"-c '{fileName} {arguments}'", workingDirectory);
}
}
}