/* ======================================================================== * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Linq; using System.Text; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; namespace Opc.Ua.SourceGeneration { /// /// Generates the stack core /// internal sealed class StackGeneration { /// /// Generate stack /// public StackGeneration( SourceProductionContext context, CompilationOptions compilationOptions, StackGenerationOptions options) { m_context = context; m_options = options; m_compilationOptions = compilationOptions; m_telemetry = SourceGeneratorTelemetry.Create(m_context); } /// /// Generate all stack files /// public void Emit(CancellationToken cancellationToken) { if (!CheckCompilationOptions(out StackGenerationType generationType)) { return; } using var fileSystem = new VirtualFileSystem(); try { // Generate files into the virtual file system Generators.GenerateStack( generationType, fileSystem, string.Empty, m_telemetry, new GeneratorOptions { Cancellation = cancellationToken, Exclusions = m_options.Exclude, OptimizeForCompileSpeed = m_compilationOptions.OptimizationLevel == OptimizationLevel.Debug, // The standard NodeSet "Models" pass runs in the Opc.Ua // (Types-only) assembly which does not reference // ISessionClient/ObjectTypeClient. Proxies are emitted // by the Stack pass (Opc.Ua.Core) instead. OmitObjectTypeProxies = generationType == StackGenerationType.Models, // Neither pass references Opc.Ua.Server.Fluent. The // typed-accessor extensions emitted by // FluentBuilderGenerator delegate into the Server-side // INodeBuilder surface, so they must be omitted // here. Server-side consumers that ship per-namespace // accessors emit them from their own model packages // (the default opt-out is honoured by setting // ModelSourceGeneratorOmitFluentApi=true on the // assembly's csproj when needed). OmitFluentApi = true, UseTypeDefinitionModellingRules = true }); // Collect all generated cs files and produce them into the compilation foreach (string file in fileSystem.CreatedFiles .Where(f => f.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))) { string content = Encoding.UTF8.GetString(fileSystem.Get(file)); m_context.AddSource(file, content); } } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { // A cancelled run is not a generator failure: let the driver // see the cancellation instead of caching an error diagnostic. throw; } catch (Exception ex) { m_context.ReportDiagnostic( Diagnostic.Create( SourceGenerator.Exception, Location.None, ex.Message, ex.ToString())); } } /// /// Tests the compilation options are valid /// /// private bool CheckCompilationOptions(out StackGenerationType type) { type = StackGenerationType.None; // A generator must never fail a compilation it was not meant for. // The stack generator is loaded into every compilation that // references it transitively - a BenchmarkDotNet host project // being the case that surfaced this - so an absent opt-in means // "not my compilation", not "misconfigured". if (string.IsNullOrEmpty(m_options.Mode)) { return false; } if (!m_compilationOptions.IsCSharp13OrLater) { m_context.ReportDiagnostic( Diagnostic.Create( SourceGenerator.GenericError, Location.None, "Opc UA stack is too old. Minimum required language version is CSharp 13.")); return false; } if (!Enum.TryParse(m_options.Mode, ignoreCase: true, out type) || type == StackGenerationType.None) { type = StackGenerationType.None; m_context.ReportDiagnostic( Diagnostic.Create( SourceGenerator.GenericError, Location.None, $"'{m_options.Mode}' is not a valid {SourceGenerator.Name}Mode. " + $"Use one of {StackGenerationType.Stack}, " + $"{StackGenerationType.Models} or {StackGenerationType.All}.")); return false; } return true; } private readonly SourceProductionContext m_context; private readonly StackGenerationOptions m_options; private readonly ITelemetryContext m_telemetry; private readonly CompilationOptions m_compilationOptions; } }