Added a basic workspace implementation

- This is a port of a simple project.json workspace I wrote a while back. It's been updating to use the new APIs (which actually made it much easier to implement).
This commit is contained in:
David Fowler 2015-11-09 00:23:46 -08:00
parent 5711d1764d
commit 215b53c6ab
3 changed files with 327 additions and 0 deletions

View file

@ -0,0 +1,234 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.ProjectModel;
using NuGet.Frameworks;
namespace Microsoft.DotNet.ProjectModel.Workspaces
{
public class ProjectJsonWorkspace : Workspace
{
private Dictionary<string, AssemblyMetadata> _cache = new Dictionary<string, AssemblyMetadata>();
private readonly string[] _projectPaths;
public ProjectJsonWorkspace(string projectPath) : this(new[] { projectPath })
{
}
public ProjectJsonWorkspace(string[] projectPaths) : base(MefHostServices.DefaultHost, "Custom")
{
_projectPaths = projectPaths;
Initialize();
}
private void Initialize()
{
foreach (var projectPath in _projectPaths)
{
AddProject(projectPath);
}
}
private void AddProject(string projectPath)
{
// Get all of the specific projects (there is a project per framework)
foreach (var p in ProjectContext.CreateContextForEachFramework(projectPath))
{
AddProject(p);
}
}
private ProjectId AddProject(ProjectContext project)
{
// Create the framework specific project and add it to the workspace
var projectInfo = ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
project.ProjectFile.Name + "+" + project.TargetFramework,
project.ProjectFile.Name,
LanguageNames.CSharp,
project.ProjectFile.ProjectFilePath);
OnProjectAdded(projectInfo);
// TODO: ctor argument?
var configuration = "Debug";
var compilationOptions = project.ProjectFile.GetCompilerOptions(project.TargetFramework, configuration);
var compilationSettings = ToCompilationSettings(compilationOptions, project.TargetFramework, project.ProjectFile.ProjectDirectory);
OnParseOptionsChanged(projectInfo.Id, new CSharpParseOptions(compilationSettings.LanguageVersion, preprocessorSymbols: compilationSettings.Defines));
OnCompilationOptionsChanged(projectInfo.Id, compilationSettings.CompilationOptions);
foreach (var file in project.ProjectFile.Files.SourceFiles)
{
AddSourceFile(projectInfo, file);
}
var exporter = project.CreateExporter(configuration);
foreach (var dependency in exporter.GetDependencies())
{
var projectDependency = dependency.Library as ProjectDescription;
if (projectDependency != null)
{
var projectDependencyContext = ProjectContext.Create(projectDependency.Project.ProjectFilePath, projectDependency.Framework);
var id = AddProject(projectDependencyContext);
OnProjectReferenceAdded(projectInfo.Id, new ProjectReference(id));
}
else
{
foreach (var asset in dependency.CompilationAssemblies)
{
OnMetadataReferenceAdded(projectInfo.Id, GetMetadataReference(asset.ResolvedPath));
}
}
foreach (var file in dependency.SourceReferences)
{
AddSourceFile(projectInfo, file);
}
}
return projectInfo.Id;
}
private void AddSourceFile(ProjectInfo projectInfo, string file)
{
using (var stream = File.OpenRead(file))
{
var sourceText = SourceText.From(stream, encoding: Encoding.UTF8);
var id = DocumentId.CreateNewId(projectInfo.Id);
var version = VersionStamp.Create();
var loader = TextLoader.From(TextAndVersion.Create(sourceText, version));
OnDocumentAdded(DocumentInfo.Create(id, file, filePath: file, loader: loader));
}
}
private MetadataReference GetMetadataReference(string path)
{
AssemblyMetadata assemblyMetadata;
if (!_cache.TryGetValue(path, out assemblyMetadata))
{
using (var stream = File.OpenRead(path))
{
var moduleMetadata = ModuleMetadata.CreateFromStream(stream, PEStreamOptions.PrefetchMetadata);
assemblyMetadata = AssemblyMetadata.Create(moduleMetadata);
_cache[path] = assemblyMetadata;
}
}
return assemblyMetadata.GetReference();
}
private static CompilationSettings ToCompilationSettings(CommonCompilerOptions compilerOptions,
NuGetFramework targetFramework,
string projectDirectory)
{
var options = GetCompilationOptions(compilerOptions, projectDirectory);
// Disable 1702 until roslyn turns this off by default
options = options.WithSpecificDiagnosticOptions(new Dictionary<string, ReportDiagnostic>
{
{ "CS1701", ReportDiagnostic.Suppress }, // Binding redirects
{ "CS1702", ReportDiagnostic.Suppress },
{ "CS1705", ReportDiagnostic.Suppress }
});
AssemblyIdentityComparer assemblyIdentityComparer =
targetFramework.IsDesktop() ?
DesktopAssemblyIdentityComparer.Default :
null;
options = options.WithAssemblyIdentityComparer(assemblyIdentityComparer);
LanguageVersion languageVersion;
if (!Enum.TryParse<LanguageVersion>(value: compilerOptions.LanguageVersion,
ignoreCase: true,
result: out languageVersion))
{
languageVersion = LanguageVersion.CSharp6;
}
var settings = new CompilationSettings
{
LanguageVersion = languageVersion,
Defines = compilerOptions.Defines ?? Enumerable.Empty<string>(),
CompilationOptions = options
};
return settings;
}
private static CSharpCompilationOptions GetCompilationOptions(CommonCompilerOptions compilerOptions, string projectDirectory)
{
var outputKind = compilerOptions.EmitEntryPoint.GetValueOrDefault() ?
OutputKind.ConsoleApplication : OutputKind.DynamicallyLinkedLibrary;
var options = new CSharpCompilationOptions(outputKind);
string platformValue = compilerOptions.Platform;
bool allowUnsafe = compilerOptions.AllowUnsafe ?? false;
bool optimize = compilerOptions.Optimize ?? false;
bool warningsAsErrors = compilerOptions.WarningsAsErrors ?? false;
Platform platform;
if (!Enum.TryParse(value: platformValue, ignoreCase: true, result: out platform))
{
platform = Platform.AnyCpu;
}
options = options
.WithAllowUnsafe(allowUnsafe)
.WithPlatform(platform)
.WithGeneralDiagnosticOption(warningsAsErrors ? ReportDiagnostic.Error : ReportDiagnostic.Default)
.WithOptimizationLevel(optimize ? OptimizationLevel.Release : OptimizationLevel.Debug);
return AddSigningOptions(options, compilerOptions, projectDirectory);
}
private static CSharpCompilationOptions AddSigningOptions(CSharpCompilationOptions options, CommonCompilerOptions compilerOptions, string projectDirectory)
{
var useOssSigning = compilerOptions.UseOssSigning == true;
var keyFile = compilerOptions.KeyFile;
if (!string.IsNullOrEmpty(keyFile))
{
keyFile = Path.GetFullPath(Path.Combine(projectDirectory, compilerOptions.KeyFile));
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || useOssSigning)
{
return options.WithCryptoPublicKey(
SnkUtils.ExtractPublicKey(File.ReadAllBytes(keyFile)));
}
options = options.WithCryptoKeyFile(keyFile);
return options.WithDelaySign(compilerOptions.DelaySign);
}
return options;
}
private class CompilationSettings
{
public LanguageVersion LanguageVersion { get; set; }
public IEnumerable<string> Defines { get; set; }
public CSharpCompilationOptions CompilationOptions { get; set; }
}
}
}

View file

@ -0,0 +1,84 @@
using System;
using System.Collections.Immutable;
using System.IO;
namespace Microsoft.DotNet.ProjectModel.Workspaces
{
internal static class SnkUtils
{
const byte PUBLICKEYBLOB = 0x06;
const byte PRIVATEKEYBLOB = 0x07;
private const uint CALG_RSA_SIGN = 0x00002400;
private const uint CALG_SHA = 0x00008004;
private const uint RSA1 = 0x31415352; //"RSA1" publickeyblob
private const uint RSA2 = 0x32415352; //"RSA2" privatekeyblob
private const int VersionOffset = 1;
private const int ModulusLengthOffset = 12;
private const int ExponentOffset = 16;
private const int MagicPrivateKeyOffset = 8;
private const int MagicPublicKeyOffset = 20;
public static ImmutableArray<byte> ExtractPublicKey(byte[] snk)
{
ValidateBlob(snk);
if (snk[0] != PRIVATEKEYBLOB)
{
return ImmutableArray.Create(snk);
}
var version = snk[VersionOffset];
int modulusBitLength = ReadInt32(snk, ModulusLengthOffset);
uint exponent = (uint)ReadInt32(snk, ExponentOffset);
var modulus = new byte[modulusBitLength >> 3];
Array.Copy(snk, 20, modulus, 0, modulus.Length);
return CreatePublicKey(version, exponent, modulus);
}
private static void ValidateBlob(byte[] snk)
{
// 160 - the size of public key
if (snk.Length >= 160)
{
if (snk[0] == PRIVATEKEYBLOB && ReadInt32(snk, MagicPrivateKeyOffset) == RSA2 || // valid private key
snk[12] == PUBLICKEYBLOB && ReadInt32(snk, MagicPublicKeyOffset) == RSA1) // valid public key
{
return;
}
}
throw new InvalidOperationException("Invalid key file.");
}
private static int ReadInt32(byte[] array, int index)
{
return array[index] | array[index + 1] << 8 | array[index + 2] << 16 | array[index + 3] << 24;
}
private static ImmutableArray<byte> CreatePublicKey(byte version, uint exponent, byte[] modulus)
{
using (var ms = new MemoryStream(160))
using (var binaryWriter = new BinaryWriter(ms))
{
binaryWriter.Write(CALG_RSA_SIGN);
binaryWriter.Write(CALG_SHA);
// total size of the rest of the blob (20 - size of RSAPUBKEY)
binaryWriter.Write(modulus.Length + 20);
binaryWriter.Write(PUBLICKEYBLOB);
binaryWriter.Write(version);
binaryWriter.Write((ushort)0x00000000); // reserved
binaryWriter.Write(CALG_RSA_SIGN);
binaryWriter.Write(RSA1);
binaryWriter.Write(modulus.Length << 3);
binaryWriter.Write(exponent);
binaryWriter.Write(modulus);
return ImmutableArray.Create(ms.ToArray());
}
}
}
}

View file

@ -0,0 +1,9 @@
{
"dependencies": {
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
"Microsoft.CodeAnalysis.CSharp.Workspaces": "1.1.0-*"
},
"frameworks": {
"dnxcore50": { }
}
}