Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feature/csproj #100

Merged
merged 11 commits into from
Aug 24, 2017
Merged
13 changes: 11 additions & 2 deletions src/Dotnet.Script.Core/Dotnet.Script.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<Description>A cross platform library allowing you to run C# (CSX) scripts from a project.json dependency definition file and with support for debugging. Based on Roslyn.</Description>
<VersionPrefix>0.12.0</VersionPrefix>
<VersionPrefix>0.13.0</VersionPrefix>
<Authors>filipw</Authors>
<TargetFramework>netstandard1.6</TargetFramework>
<AssemblyName>Dotnet.Script.Core</AssemblyName>
Expand All @@ -19,11 +19,20 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Dotnet.Script.NuGetMetadataResolver" Version="2.0.3" />
<None Remove="Templates\csproj.template" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Templates\csproj.template" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="2.3.1" />
<PackageReference Include="Microsoft.DotNet.ProjectModel" Version="1.0.0-rc3-003121" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="1.1.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.3.1" />
<PackageReference Include="System.Reflection.Metadata" Version="1.4.2" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
</ItemGroup>

Expand Down
15 changes: 15 additions & 0 deletions src/Dotnet.Script.Core/Internal/RuntimeHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Runtime.InteropServices;

namespace Dotnet.Script.Core.Internal
{
internal static class RuntimeHelper
{
internal static string GetPlatformIdentifier()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return "osx";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return "unix";

return "win";
}
}
}
81 changes: 81 additions & 0 deletions src/Dotnet.Script.Core/Metadata/CommandRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Diagnostics;
using System.Text;

namespace Dotnet.Script.Core.Metadata
{
/// <summary>
/// A class that is capable of running a command.
/// </summary>
public class CommandRunner
{
private readonly StringBuilder _lastStandardErrorOutput = new StringBuilder();
private readonly StringBuilder _lastProcessOutput = new StringBuilder();
private readonly ScriptLogger _logger;

/// <summary>
/// Initializes a new instance of the <see cref="CommandRunner"/> class.
/// </summary>
/// <param name="logger">The <see cref="ScriptLogger"/> used for logging.</param>
public CommandRunner(ScriptLogger logger)
{
_logger = logger;
}

/// <inheritdoc />
public string Execute(string commandPath, string arguments)
{
_lastStandardErrorOutput.Clear();

_logger.Verbose($"Executing {commandPath} {arguments}");
var startInformation = CreateProcessStartInfo(commandPath, arguments);
var process = CreateProcess(startInformation);
RunAndWait(process);
_logger.Verbose(_lastProcessOutput.ToString());
if (process.ExitCode != 0)
{
_logger.Log(_lastStandardErrorOutput.ToString());
throw new InvalidOperationException($"The command {commandPath} {arguments} failed to execute");
}
return _lastProcessOutput.ToString();
}

private static ProcessStartInfo CreateProcessStartInfo(string commandPath, string arguments)
{
var startInformation = new ProcessStartInfo(commandPath);
startInformation.CreateNoWindow = true;
startInformation.Arguments = arguments;
startInformation.RedirectStandardOutput = true;
startInformation.RedirectStandardError = true;
startInformation.UseShellExecute = false;
return startInformation;
}

private Process CreateProcess(ProcessStartInfo startInformation)
{
var process = new Process();
process.StartInfo = startInformation;
process.ErrorDataReceived += (s, a) =>
{
if (!string.IsNullOrWhiteSpace(a.Data))
{
_lastStandardErrorOutput.AppendLine(a.Data);
}

};
process.OutputDataReceived += (s, a) =>
{
_lastProcessOutput.AppendLine(a.Data);
};
return process;
}

private static void RunAndWait(Process process)
{
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit();
}
}
}
141 changes: 141 additions & 0 deletions src/Dotnet.Script.Core/Metadata/DependencyResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Dotnet.Script.Core.Internal;
using Microsoft.DotNet.PlatformAbstractions;
using Microsoft.Extensions.DependencyModel;

namespace Dotnet.Script.Core.Metadata
{
/// <summary>
/// An <see cref="IDependencyResolver"/> that resolves runtime dependencies
/// from an "csproj" file.
/// </summary>
public class DependencyResolver : IDependencyResolver
{
private readonly CommandRunner _commandRunner;

private readonly ScriptLogger _logger;

// Note: Windows only, Mac and Linux needs something else?
[DllImport("Kernel32.dll")]
private static extern IntPtr LoadLibrary(string path);

[DllImport("libdl.so")]
protected static extern IntPtr dlopen(string filename, int flags);


public DependencyResolver(CommandRunner commandRunner, ScriptLogger logger)
{
_commandRunner = commandRunner;
_logger = logger;
}

private DependencyContext ReadDependencyContext(string pathToProjectFile)
{
Restore(pathToProjectFile);

var pathToAssetsFiles = Path.Combine(Path.GetDirectoryName(pathToProjectFile), "obj", "project.assets.json");

using (FileStream fs = new FileStream(pathToAssetsFiles, FileMode.Open, FileAccess.Read))
{
using (var contextReader = new DependencyContextJsonReader())
{
return contextReader.Read(fs);
}
}
}

public IEnumerable<RuntimeDependency> GetRuntimeDependencies(string pathToProjectFile)
{
var pathToGlobalPackagesFolder = GetPathToGlobalPackagesFolder();
var runtimeDepedencies = new HashSet<RuntimeDependency>();

var context = ReadDependencyContext(pathToProjectFile);

//Note: Scripting only releates to runtime libraries.
var runtimeLibraries = context.RuntimeLibraries;

foreach (var runtimeLibrary in runtimeLibraries)
{
ProcessNativeLibraries(runtimeLibrary, pathToGlobalPackagesFolder);
ProcessRuntimeAssemblies(runtimeLibrary, pathToGlobalPackagesFolder, runtimeDepedencies);
}

return runtimeDepedencies;
}

private void ProcessRuntimeAssemblies(RuntimeLibrary runtimeLibrary, string pathToGlobalPackagesFolder,
HashSet<RuntimeDependency> runtimeDepedencies)
{

foreach (var runtimeAssemblyGroup in runtimeLibrary.RuntimeAssemblyGroups.Where(rag => IsRelevantForCurrentRuntime(rag.Runtime)))
{
foreach (var assetPath in runtimeAssemblyGroup.AssetPaths)
{
var path = Path.Combine(runtimeLibrary.Path, assetPath);
if (!path.EndsWith("_._"))
{
var fullPath = Path.Combine(pathToGlobalPackagesFolder, path);
_logger.Verbose(fullPath);
runtimeDepedencies.Add(new RuntimeDependency(runtimeLibrary.Name, fullPath));
}
}
}
}

private void ProcessNativeLibraries(RuntimeLibrary runtimeLibrary, string pathToGlobalPackagesFolder)
{
foreach (var nativeLibraryGroup in runtimeLibrary.NativeLibraryGroups.Where(nlg => IsRelevantForCurrentRuntime(nlg.Runtime)))
{
foreach (var assetPath in nativeLibraryGroup.AssetPaths)
{
var fullPath = Path.Combine(pathToGlobalPackagesFolder, runtimeLibrary.Path,
assetPath);
_logger.Verbose($"Loading native library from {fullPath}");
if (RuntimeHelper.GetPlatformIdentifier() == "win")
{
LoadLibrary(fullPath);
}
else
{
// Maybe something like this?
// https://stackoverflow.com/questions/13461989/p-invoke-to-dynamically-loaded-library-on-mono
}
}
}
}

private void Restore(string pathToProjectFile)
{
var runtimeId = RuntimeEnvironment.GetRuntimeIdentifier();
_commandRunner.Execute("dotnet", $"restore {pathToProjectFile} -r {runtimeId}");
//_commandRunner.Execute("DotNet", $"restore {pathToProjectFile}");
}

private string GetPathToGlobalPackagesFolder()
{
var result = _commandRunner.Execute("dotnet", "nuget locals global-packages -l");
var match = Regex.Match(result, @"global-packages:\s*(.*)");
var pathToGlobalPackagesFolder = match.Groups[1].Captures[0].ToString();
return pathToGlobalPackagesFolder.Replace("\r", String.Empty);
}

public bool IsRelevantForCurrentRuntime(string runtime)
{
return string.IsNullOrWhiteSpace(runtime) || runtime == GetRuntimeIdentitifer();
}

private static string GetRuntimeIdentitifer()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return "osx";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return "unix";

return "win";
}
}
}
18 changes: 18 additions & 0 deletions src/Dotnet.Script.Core/Metadata/IDependencyResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Collections.Generic;

namespace Dotnet.Script.Core.Metadata
{
/// <summary>
/// Represents a class that is capable of resolving the runtime
/// dependencies for a given project file.
/// </summary>
public interface IDependencyResolver
{
/// <summary>
/// Gets the runtime dependencies for the given <paramref name="projectFile"/>.
/// </summary>
/// <param name="projectFile">The project file for which to resolve the runtime dependencies.</param>
/// <returns></returns>
IEnumerable<RuntimeDependency> GetRuntimeDependencies(string projectFile);
}
}
62 changes: 62 additions & 0 deletions src/Dotnet.Script.Core/Metadata/LegacyDependencyResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dotnet.Script.Core.Internal;
using Microsoft.DotNet.ProjectModel;

namespace Dotnet.Script.Core.Metadata
{
/// <summary>
/// An <see cref="IDependencyResolver"/> that resolves runtime dependencies
/// from an "project.json" file.
/// </summary>
public class LegacyDependencyResolver : IDependencyResolver
{
private ScriptLogger _logger;


public LegacyDependencyResolver(ScriptLogger logger)
{
_logger = logger;
}

public IEnumerable<RuntimeDependency> GetRuntimeDependencies(string projectFile)
{
var workingDirectory = Path.GetDirectoryName(projectFile);
var runtimeContext = ProjectContext.CreateContextForEachTarget(workingDirectory).FirstOrDefault();
var projectExporter = runtimeContext.CreateExporter("release");
var runtimeIdentifier = RuntimeHelper.GetPlatformIdentifier();

var runtimeDependencies = new HashSet<RuntimeDependency>();
var projectDependencies = projectExporter.GetDependencies();
foreach (var projectDependency in projectDependencies)
{
var runtimeAssemblyGroups = projectDependency.RuntimeAssemblyGroups;

foreach (var libraryAsset in runtimeAssemblyGroups.GetDefaultAssets())
{
var runtimeAssemblyPath = libraryAsset.ResolvedPath;
_logger.Verbose($"Discovered runtime dependency for '{runtimeAssemblyPath}'");
var runtimeDependency = new RuntimeDependency(libraryAsset.Name, libraryAsset.ResolvedPath);
runtimeDependencies.Add(runtimeDependency);
}

foreach (var runtimeAssemblyGroup in runtimeAssemblyGroups)
{
if (!string.IsNullOrWhiteSpace(runtimeAssemblyGroup.Runtime) && runtimeAssemblyGroup.Runtime == runtimeIdentifier)
{
foreach (var runtimeAsset in runtimeAssemblyGroups.GetRuntimeAssets(runtimeIdentifier))
{
var runtimeAssetPath = runtimeAsset.ResolvedPath;
_logger.Verbose($"Discovered runtime asset dependency ('{runtimeIdentifier}') for '{runtimeAssetPath}'");
var runtimeDependency = new RuntimeDependency(runtimeAsset.Name, runtimeAsset.ResolvedPath);
runtimeDependencies.Add(runtimeDependency);
}
}
}
}

return runtimeDependencies;
}
}
}
29 changes: 29 additions & 0 deletions src/Dotnet.Script.Core/Metadata/RuntimeDependency.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.CodeAnalysis;

namespace Dotnet.Script.Core.Metadata
{
public class RuntimeDependency
{
public string Name { get; }
public string Path { get; }

public RuntimeDependency(string name, string path)
{
Name = name;
Path = path;
}

/// <inheritdoc />
public override int GetHashCode()
{
return Name.GetHashCode() ^ Path.GetHashCode();
}

/// <inheritdoc />
public override bool Equals(object obj)
{
var other = (RuntimeDependency)obj;
return other.Name == Name && other.Path == Path;
}
}
}
Loading