Files
richard-churchman 20caa92619 Implemented a refactor of Inline Script functionality:
* Updated documentation for the revised Inline Script approach, aligning with context, strategy, and pipeline patterns. Inline Scripts now use a transient instance model; dependencies are assumed to exist in the context, and state is not supported.
* Added a syntax tree helper parser for VB.net and C# in the data layer, enhancing queries for request XPath fields and Inline Script properties in the UI. The syntax tree helper is a Roslyn-based parser for Inline Scripts that extracts public properties without a full compile, enabling property access in the UI for rule writing and abstraction rules.
* Inline Script properties fully supported in parser, completions for coder and builder, and abstraction rule grouping keys, with search keys synchronized via the `SearchKey` attribute.
* Refactored Inline Script methdology to be a transient instance class, merging payload values from public properties via reflection, removing the need to manually add properties.
* Added migrations to include a language identifier for Inline Scripts.
* Inline Scripts now fully support C# and Visual Basic (VB.net).
* Removed `ClassName` and `MethodName` from the `EntityAnalysisInlineScript` table, now derived from the IInlineScript interface.
* Updated Activation Rule Library to fall back to Inline Script properties if Latitude and Longitude are not specified in the Request XPath.
* Moved Inline Script execution to asynchronous methods to avoid blocking IO during potential remote calls; updated reflection helpers accordingly.
* Renamed variables to better match POCO conventions, as came across them.
* Updated assembly loading for Inline Scripts to include all assemblies in the Jube.App context.
* Optimized Inline Script synchronization to reduce unnecessary reflection during invocation.
* Created Jube.Sandbox for testing and deploying Inline Scripts outside the main application.
* Updated Inline Script OTP demo code new Inline Script Interface signature.
2026-01-11 16:14:54 +02:00

196 lines
7.5 KiB
C#

/* Copyright (C) 2022-present Jube Holdings Limited.
*
* This file is part of Jube™ software.
*
* Jube™ is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* Jube™ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License along with Jube™. If not,
* see <https://www.gnu.org/licenses/>.
*/
namespace Jube.Parser.Compiler
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using log4net;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.VisualBasic;
using LanguageVersion=Microsoft.CodeAnalysis.VisualBasic.LanguageVersion;
public class Compile
{
public enum Language
{
Vb,
CSharp
}
public bool Success;
public Assembly CompiledAssembly { get; set; }
// ReSharper disable once MemberCanBePrivate.Global
public IEnumerable<Diagnostic> Errors { get; set; }
public void CompileCode(string code, ILog log, string[] refs, Language language)
{
var assemblyGuid = Guid.NewGuid().ToString();
if (log.IsInfoEnabled)
{
log.Info("Roslyn Compilation in VB.net: Is about to compile the code " + code +
" with the assembly GUID of " + assemblyGuid + ".");
}
var peStream = new MemoryStream();
EmitResult result = null;
if (language == Language.Vb)
{
var compilation = VisualBasicCompilationConfig(code, log, refs, assemblyGuid);
if (log.IsInfoEnabled)
{
log.Info(
"Roslyn Compilation in VB.net: Has configured the compiler and will now proceed to compile the code.");
}
result = compilation.Emit(peStream);
}
else if (language == Language.CSharp)
{
var compilation = CSharpCompilationConfig(code, log, refs, assemblyGuid);
if (log.IsInfoEnabled)
{
log.Info(
"Roslyn Compilation in VB.net: Has configured the compiler and will now proceed to compile the code.");
}
result = compilation.Emit(peStream);
}
else
{
if (log.IsInfoEnabled)
{
log.Info(
"Roslyn Compilation in VB.net: Invalid language.");
}
}
if (log.IsInfoEnabled)
{
log.Info(
"Roslyn Compilation in VB.net: Code compilation process has concluded. Will now inspect any errors.");
}
if (result is { Success: false })
{
var failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);
IEnumerable<Diagnostic> diagnostics = failures as Diagnostic[] ?? failures.ToArray();
Errors = diagnostics;
Success = false;
}
else
{
Success = true;
HandleCompile(log, peStream);
}
}
private void HandleCompile(ILog log, Stream peStream)
{
peStream.Position = 0;
if (log.IsInfoEnabled)
{
log.Info("Roslyn Compilation in VB.net: Is about to load the assembly from a stream of " +
peStream.Length + ".");
}
var assemblyLoadContext = new SimpleUnloadableAssemblyLoadContext();
CompiledAssembly = assemblyLoadContext.LoadFromStream(peStream);
if (log.IsInfoEnabled)
{
log.Info(
"Roslyn Compilation in VB.net: Loaded compiled assembly. Will now proceed to unload the assembly context.");
}
assemblyLoadContext.Unload();
if (log.IsInfoEnabled)
{
log.Info("Roslyn Compilation in VB.net: Unloaded assembly context.");
}
}
private static Compilation VisualBasicCompilationConfig(string code, ILog log,
IReadOnlyList<string> refs,
string assemblyGuid)
{
var parseOptions = VisualBasicParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest);
var compileOptions = new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release, embedVbCoreRuntime: true);
var references = MetadataReferences(log, refs);
var compilation = VisualBasicCompilation.Create(assemblyGuid)
.AddSyntaxTrees(VisualBasicSyntaxTree.ParseText(code, parseOptions))
.WithReferences(references).WithOptions(compileOptions);
return compilation;
}
private static Compilation CSharpCompilationConfig(string code, ILog log,
IReadOnlyList<string> refs,
string assemblyGuid)
{
var parseOptions = CSharpParseOptions.Default.WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.Latest);
var compileOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release);
var references = MetadataReferences(log, refs);
var compilation = CSharpCompilation.Create(assemblyGuid)
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(code, parseOptions))
.WithReferences(references).WithOptions(compileOptions);
return compilation;
}
private static MetadataReference[] MetadataReferences(ILog log, IReadOnlyList<string> refs)
{
var references = new MetadataReference[refs.Count + 3];
int i;
for (i = 0; i < refs.Count; i++)
{
references[i] = MetadataReference.CreateFromFile(refs[i]);
if (log.IsInfoEnabled)
{
log.Info("Roslyn Compilation in VB.net: Included custom reference " + refs[i] +
". Will now add the mandated reference.");
}
}
var directoryForDll = Path.GetDirectoryName(typeof(object).Assembly.Location);
references[i] = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); //Dummy
references[i + 1] = MetadataReference.CreateFromFile(Path.Join(directoryForDll, "System.Runtime.dll"));//Dummy
references[i + 2] = MetadataReference.CreateFromFile(Path.Join(directoryForDll, "netstandard.dll")); //Dummy
if (log.IsInfoEnabled)
{
log.Info("Roslyn Compilation in VB.net: Included mandated reference " + references[i] +
". Will now configure the compiler.");
}
return references;
}
}
}