DNXでC#以外の言語をコンパイルしてみよう
目次
DNXの可能性
DNX で C# 以外の言語を使えるのかと思って調べていたところ、 F# を動作させるためのライブラリが存在していることを発見しました。
(試してみましたが、 F# コンパイラが内部で NullReferenceException 起こしてうまくいかなかったです。)
この作者のブログには、 DNX で F# を動かしてみての感想などいろいろ書いてあるので参考にどうぞ。
で、これの何がイケてるかというと、コンパイラ部分も NuGet から取得できるということです。つまり DNX さえあればコンパイラをインストールすることなく、その場でコンパイルできてしまうんですね。夢が広がる。
よろしいならば実装(インプリメント)だ
というわけで何か試すのにいい言語はないかなぁと考えたところで、 .NET で動くほかの言語は Nemerle くらいしか触ったことなかったので Nemerle をコンパイルできるようにしてみました。
完成品
rc1-final ブランチは DNX 1.0.0-rc1-final、 rc2-16177 ブランチは 1.0.0-rc2-16177 に対応しています。
NuGet パッケージは MyGet にあげてあります。
https://www.myget.org/F/azyobuzin/api/v3/index.json
をパッケージソースに追加して、 DNX 1.0.0-rc1-final ならば 1.2.0-* を、 1.0.0-rc2-16177 ならば 1.1.0-* を使用してください。
なお、 DNX の CLR 版にしか対応していないので、 CoreCLR 版から呼び出すと NullReferenceException で落ちます。 Nemerle コンパイラがポータブルじゃないからしかたないね。
IProjectCompiler を実装する
DNX は Microsoft.Dnx.Compilation.IProjectCompiler
*1 を実装したクラスを使ってプロジェクトをコンパイルします。これは C# の場合でも同様ですので、 C# での実装を見てみましょう。
dnx/RoslynProjectCompiler.cs at 1.0.0-rc1 · aspnet/dnx · GitHub
雛形としてはこのようになります。
using System; using System.Collections.Generic; using Microsoft.Dnx.Compilation; namespace YourNamespace { public class YourProjectCompiler : IProjectCompiler { public IMetadataProjectReference CompileProject( CompilationProjectContext projectContext, Func<LibraryExport> referenceResolver, Func<IList<ResourceDescriptor>> resourcesResolver) { return /* Compile */ ; } } }
RoslynProjectCompiler
では、コンストラクタで大量のインターフェイスを受け取っていますが、これは依存性注入で自動的にサービスが引数に与えられます。
CompileProject
メソッドで実際にコンパイルを行います。しかし、これは意味解析までを行うという意味です。というのも、ここで返す IMetadataProjectReference
には EmitAssembly(string outputPath)
や EmitReferenceAssembly(Stream stream)
といったメソッドがあるので、アセンブリの出力はこれらのメソッドが呼ばれた時に行われるのがベストです。(でも Nemerle コンパイラの中身までいじるのが面倒だったので、 NemerleDnxProvider ではファイルに出力して、読み込んだりコピーしたりするようにしました)
参照アセンブリは referenceResolver()
で、マニフェストリソースは resourcesResolver()
で取得できます。その他の情報は projectContext
から取得してください。
なお、参照のデータは IMetadataReference
なので、他のインターフェイスにキャストしないとデータを引き出せません。
dnx/MetadataReferenceExtensions.cs at 1.0.0-rc1 · aspnet/dnx · GitHub
IMetadataProjectReference を実装する
Microsoft.Dnx.Compilation.IMetadataProjectReference
は CompileProject
の戻り値の型です。このインターフェイスは意味解析の結果を表します。
C# ではこのように実装されています。
dnx/RoslynProjectReference.cs at 1.0.0-rc1 · aspnet/dnx · GitHub
雛形としてはこのようになります。
using System; using System.Collections.Generic; using System.IO; using System.Reflection; using Microsoft.Dnx.Compilation; using Microsoft.Extensions.PlatformAbstractions; namespace YourNamespace { public class YourProjectReference : IMetadataProjectReference { // 意味解析のエラー情報 private readonly DiagnosticResult diagnostics; public string Name { get; } // projectContext.Target.Name public string ProjectPath { get; } // projectContext.ProjectFilePath // dnu build で呼ばれます。 public DiagnosticResult EmitAssembly(string outputPath) { Directory.CreateDirectory(outputPath); // DLL ファイルを出力 return new DiagnosticResult(success: true, diagnostics: /* エラーリスト */); } // 他のプロジェクトから参照されたときに呼ばれます。 public void EmitReferenceAssembly(Stream stream) { if (!this.diagnostics.Success) // YourCompilationException は ICompilationException を実装するといいかも throw new YourCompilationException(this.diagnostics.Diagnostics); // stream にアセンブリを出力する } public DiagnosticResult GetDiagnostics() => this.diagnostics; public IList<ISourceReference> GetSources() { // コンパイルに使用したソースコードを返します。 // コンパイル時に使った ISourceReference を保持しておいて返すか // または new Microsoft.Dnx.Compilation.SourceFileReference(string path) で作成できます。 } // dnx command から実行したときに呼ばれます。 public Assembly Load(AssemblyName assemblyName, IAssemblyLoadContext loadContext) { if (!this.diagnostics.Success) throw new YourCompilationException(this.diagnostics.Diagnostics); // return loadContext.LoadFile(string path); // return loadContext.LoadStream(Stream assemblyStream, Stream assemblySymbols); } } }
アセンブリ出力をここで行う場合は、C# の実装を参考にうまいことファイルやメモリにアセンブリを出力してください。すでにファイルに出力してある場合は、ファイルのコピーなどが主な仕事になります。
使ってみよう
ProjectCompiler が完成したら、他のプロジェクトから使用してみましょう。
作成したライブラリを参照に加え、 project.json に使用するコンパイラを記述します。
"dependencies": { "YourLanguage": { "version": "1.0.0-*", "type": "build" } }, "compiler": { "name": "言語名", "compilerAssembly": "YourLanguage", "compilerType": "YourNamespace.YourProjectCompiler" }, "compile": "./**/*.extension"
これで準備は完了しました。 dnu build なり dnx command なりでテストしてみましょう。