SharpGenTools.Sdk/Tasks/SharpGenTask.cs (297 lines of code) (raw):

using System; using System.Collections.Generic; using System.ComponentModel.Design; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Xml; using SharpGen; using SharpGen.Config; using SharpGen.CppModel; using SharpGen.Generator; using SharpGen.Logging; using SharpGen.Model; using SharpGen.Parser; using SharpGen.Platform; using SharpGen.Platform.Documentation; using SharpGen.Transform; using SharpGenTools.Sdk.Documentation; using SharpGenTools.Sdk.Extensibility; using SharpGenTools.Sdk.Internal; using SdkResolver = SharpGen.Parser.SdkResolver; namespace SharpGenTools.Sdk.Tasks { public sealed class SharpGenTask : SharpGenTaskBase { // Default encoding used by MSBuild ReadLinesFromFile task private static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true); private readonly IocServiceContainer serviceContainer = new(); private readonly Ioc ioc = new(); public override bool Execute() { PrepareExecute(); serviceContainer.AddService(SharpGenLogger); serviceContainer.AddService<IDocumentationLinker, DocumentationLinker>(); serviceContainer.AddService<GlobalNamespaceProvider>(); serviceContainer.AddService(new TypeRegistry(ioc)); ioc.ConfigureServices(serviceContainer); ExtensibilityDriver.Instance.LoadExtensions(SharpGenLogger, ExtensionAssemblies.Select(x => x.ItemSpec).ToArray()); var config = new ConfigFile { Files = ConfigFiles.Select(file => file.ItemSpec).ToList(), Id = "SharpGen-MSBuild" }; try { config = LoadConfig(config); return !SharpGenLogger.HasErrors && Execute(config); } catch (CodeGenFailedException ex) { SharpGenLogger.Fatal("Internal SharpGen exception", ex); return false; } } private bool AbortExecution => SharpGenLogger.HasErrors || IsCancellationRequested; private bool Execute(ConfigFile config) { config.GetFilesWithIncludesAndExtensionHeaders( out var configsWithHeaders, out var configsWithExtensionHeaders ); CppHeaderGenerator cppHeaderGenerator = new(OutputPath, ioc); var cppHeaderGenerationResult = cppHeaderGenerator.GenerateCppHeaders(config, configsWithHeaders, configsWithExtensionHeaders); if (AbortExecution) return false; IncludeDirectoryResolver resolver = new(ioc); resolver.Configure(config); CastXmlRunner castXml = new(resolver, CastXmlExecutable.ItemSpec, CastXmlArguments, ioc) { OutputPath = OutputPath }; var macroManager = new MacroManager(castXml); var cppExtensionGenerator = new CppExtensionHeaderGenerator(); var module = config.CreateSkeletonModule(); macroManager.Parse(Path.Combine(OutputPath, config.HeaderFileName), module); cppExtensionGenerator.GenerateExtensionHeaders( config, OutputPath, module, configsWithExtensionHeaders, cppHeaderGenerationResult.UpdatedConfigs ); GenerateInputsCache( macroManager.IncludedFiles .Concat(config.ConfigFilesLoaded.Select(x => x.AbsoluteFilePath)) .Concat(configsWithExtensionHeaders.Select(x => Path.Combine(OutputPath, x.ExtensionFileName))) .Select(s => Utilities.FixFilePath(s, Utilities.EmptyFilePathBehavior.Ignore)) .Where(x => x != null) .Distinct() ); if (AbortExecution) return false; // Run the parser var parser = new CppParser(config, ioc) { OutputPath = OutputPath }; if (AbortExecution) return false; CppModule group; using (var xmlReader = castXml.Process(parser.RootConfigHeaderFileName)) { // Run the C++ parser group = parser.Run(module, xmlReader); } if (AbortExecution) return false; config.ExpandDynamicVariables(SharpGenLogger, group); var docLinker = ioc.DocumentationLinker; var globalNamespace = ioc.GlobalNamespace; NamingRulesManager namingRules = new(); foreach (var nameOverride in GlobalNamespaceOverrides) { var wellKnownName = nameOverride.ItemSpec; var overridenName = nameOverride.GetMetadata("Override"); if (string.IsNullOrEmpty(overridenName)) continue; if (Enum.TryParse(wellKnownName, out WellKnownName name)) { globalNamespace.OverrideName(name, overridenName); } else { SharpGenLogger.Warning( LoggingCodes.InvalidGlobalNamespaceOverride, "Invalid override of \"{0}\": unknown class name, ignoring the override.", wellKnownName ); } } // Run the main mapping process TransformManager transformer = new( namingRules, new ConstantManager(namingRules, ioc), ioc ); var (solution, defines) = transformer.Transform(group, config); var consumerConfig = new ConfigFile { Id = ConsumerBindMappingConfigId, IncludeProlog = {cppHeaderGenerationResult.Prologue}, Extension = new List<ExtensionBaseRule>(defines) }; var (bindings, generatedDefines) = transformer.GenerateTypeBindingsForConsumers(); consumerConfig.Bindings.AddRange(bindings); consumerConfig.Extension.AddRange(generatedDefines); consumerConfig.Mappings.AddRange( docLinker.GetAllDocLinks().Select( link => new MappingRule { DocItem = link.cppName, MappingNameFinal = link.cSharpName } ) ); GenerateConfigForConsumers(consumerConfig); if (AbortExecution) return false; var documentationCacheItemSpec = DocumentationCache.ItemSpec; Utilities.RequireAbsolutePath(documentationCacheItemSpec, nameof(DocumentationCache)); var cache = File.Exists(documentationCacheItemSpec) ? DocItemCache.Read(documentationCacheItemSpec) : new DocItemCache(); DocumentationLogger docLogger = new(SharpGenLogger) {MaxLevel = LogLevel.Warning}; var docContext = new Lazy<DocumentationContext>(() => new DocumentationContext(docLogger)); ExtensibilityDriver.Instance.DocumentModule(SharpGenLogger, cache, solution, docContext).Wait(); if (docContext.IsValueCreated) { Regex[] silencePatterns = null; var docLogLevelDefault = DocumentationFailuresAsErrors ? LogLevel.Error : LogLevel.Warning; foreach (var queryFailure in docContext.Value.Failures) { if (silencePatterns == null) { silencePatterns = new Regex[SilenceMissingDocumentationErrorIdentifierPatterns.Length]; for (var i = 0; i < silencePatterns.Length; i++) silencePatterns[i] = new Regex( SilenceMissingDocumentationErrorIdentifierPatterns[i].ItemSpec, RegexOptions.CultureInvariant ); } if (silencePatterns.Length != 0) { bool SilencePredicate(Regex x) => x.Match(queryFailure.Query).Success; if (silencePatterns.Any(SilencePredicate)) continue; } var providerName = queryFailure.FailedProviderName ?? "<null>"; var docLogLevel = queryFailure.TreatProviderFailuresAsErrors ? docLogLevelDefault : docLogLevelDefault > LogLevel.Warning ? LogLevel.Warning : docLogLevelDefault; if (queryFailure.Exceptions == null || queryFailure.Exceptions.Count <= 1) { SharpGenLogger.LogRawMessage( docLogLevel, LoggingCodes.DocumentationProviderInternalError, "Documentation provider [{0}] query for \"{1}\" failed.", queryFailure.Exceptions?.FirstOrDefault(), providerName, queryFailure.Query ); } else { var exceptionsCount = queryFailure.Exceptions.Count; for (var index = 0; index < exceptionsCount; index++) { var exception = queryFailure.Exceptions[index]; SharpGenLogger.LogRawMessage( docLogLevel, LoggingCodes.DocumentationProviderInternalError, "Documentation provider [{0}] query for \"{1}\" failed ({2}/{3}).", exception, providerName, queryFailure.Query, index + 1, exceptionsCount ); } } } } cache.WriteIfDirty(documentationCacheItemSpec); if (AbortExecution) return false; var documentationFiles = new Dictionary<string, XmlDocument>(); foreach (var file in ExternalDocumentation) { using var stream = File.OpenRead(file.ItemSpec); var xml = new XmlDocument(); xml.Load(stream); documentationFiles.Add(file.ItemSpec, xml); } PlatformDetectionType platformMask = 0; foreach (var platform in Platforms) { if (!Enum.TryParse<PlatformDetectionType>(platform.ItemSpec, out var parsedPlatform)) { SharpGenLogger.Warning( LoggingCodes.InvalidPlatformDetectionType, "The platform type {0} is an unknown platform to SharpGenTools. Falling back to Any platform detection.", platform ); platformMask = PlatformDetectionType.Any; } else { platformMask |= parsedPlatform; } } if (platformMask == 0) platformMask = PlatformDetectionType.Any; if (AbortExecution) return false; serviceContainer.AddService(new ExternalDocCommentsReader(documentationFiles)); serviceContainer.AddService<IGeneratorRegistry>( new DefaultGenerators( new GeneratorConfig { Platforms = platformMask }, ioc ) ); RoslynGenerator generator = new(); generator.Run(solution, GeneratedCodeFolder, ioc); return !SharpGenLogger.HasErrors; } private void GenerateConfigForConsumers(ConfigFile consumerConfig) { if (ConsumerBindMappingConfig == null) return; using var consumerBindMapping = File.Create(ConsumerBindMappingConfig.ItemSpec); consumerConfig.Write(consumerBindMapping); } private void GenerateInputsCache(IEnumerable<string> paths) { using var inputCacheStream = new StreamWriter(InputsCache.ItemSpec, false, DefaultEncoding); foreach (var path in paths) inputCacheStream.WriteLine(path); } private ConfigFile LoadConfig(ConfigFile config) { config = ConfigFile.Load(config, Macros, SharpGenLogger); var sdkResolver = new SdkResolver(SharpGenLogger); SharpGenLogger.Message("Resolving SDKs..."); foreach (var cfg in config.ConfigFilesLoaded) { SharpGenLogger.Message("Resolving SDK for Config {0}", cfg); foreach (var sdk in cfg.Sdks) { SharpGenLogger.Message("Resolving {0}: Version {1}", sdk.Name, sdk.Version); foreach (var directory in sdkResolver.ResolveIncludeDirsForSdk(sdk)) { SharpGenLogger.Message("Resolved include directory {0}", directory); cfg.IncludeDirs.Add(directory); } } } return config; } } }