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:
parent
5711d1764d
commit
215b53c6ab
3 changed files with 327 additions and 0 deletions
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
84
src/Microsoft.DotNet.ProjectModel.Workspace/SnkUtils.cs
Normal file
84
src/Microsoft.DotNet.ProjectModel.Workspace/SnkUtils.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
src/Microsoft.DotNet.ProjectModel.Workspace/project.json
Normal file
9
src/Microsoft.DotNet.ProjectModel.Workspace/project.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
|
||||
"Microsoft.CodeAnalysis.CSharp.Workspaces": "1.1.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnxcore50": { }
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue