From eeb818c652428638fe54fe3ffe08f277f25cc180 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Wed, 15 Feb 2023 17:22:05 -0500 Subject: [PATCH 1/8] Add readme and update schema for build targets --- .vscode/settings.json | 17 ++ .../Mono.TextTemplating.Build.props | 17 +- .../Mono.TextTemplating.Build.targets | 2 +- ...tTemplating.Build.targets.buildschema.json | 92 ++++++----- Mono.TextTemplating.Build/readme.md | 151 ++++++++++++++++++ 5 files changed, 238 insertions(+), 41 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 Mono.TextTemplating.Build/readme.md diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fd80bb6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "files.associations": { + "*.buildschema.json": "jsonc" + }, + "json.schemas": [ + { + "fileMatch": [ + "/*.buildschema.json" + ], + "url": "https://raw.githubusercontent.com/mhutch/MonoDevelop.MSBuildEditor/main/MonoDevelop.MSBuild/Schemas/buildschema.json" + } + ], + "cSpell.words": [ + "Preprocess", + "reimplementation" + ] +} diff --git a/Mono.TextTemplating.Build/Mono.TextTemplating.Build.props b/Mono.TextTemplating.Build/Mono.TextTemplating.Build.props index 1d2a0a6..b67947a 100644 --- a/Mono.TextTemplating.Build/Mono.TextTemplating.Build.props +++ b/Mono.TextTemplating.Build/Mono.TextTemplating.Build.props @@ -5,10 +5,25 @@ - + False + + False + + + False + + + False + + + False + + + False + MSBuild:TransformAll diff --git a/Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets b/Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets index 55fc490..faf7581 100644 --- a/Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets +++ b/Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets @@ -66,7 +66,7 @@ They run regardless of whether we have any items to transform, as they may want to inject items to be transformed. --> - **NOTE**: These items are processed using the `Mono.TextTemplating` T4 engine and host. Host-specific templates will not have access to the Visual Studio T4 host. + +`T4Transform` items are transformed when saving the template in Visual Studio and when explicitly invoking the `TransformTemplate` MSBuild target. The `TransformOnBuild` property can be set to transform the templates on every build by calling this target automatically. The build is incremental by default, so a template will only be transformed if its input files are newer than its output. + +`T4Preprocess` items are preprocessed into a class that can be instantiated and executed from project code. The preprocessed class is generated in the intermediate output directory and included in the build automatically, similar to a source generator. + +### Customizing the Build + +Template transformation can be customized using a range of MSBuild properties, items and metadata. + +### Properties + +| Property | Description +| -- | -- +| `TransformOnBuild` | Set this to `True` to automatically transform `T4Transform` items on build. `T4Preprocess` items are transformed on build regardless of this setting unless legacy preprocessing is enabled. +| `T4DefaultNamespace` | Sets the namespace to be used when generating T4 classes. Defaults to the project's `$(RootNamespace)`. +| `TransformOutOfDateOnly` | Setting this to `False` will disable the incremental build and force all template to be re-transformed. + +### Items + + + + + +
ItemDescription
+ +`T4Argument` + + +Pass a parameter value to the T4 templates, optionally scoped to a directive processor and/or directive. This may take one of several forms: + +* Parameter name and `Value` metadata, with optional `Processor` and/or `Directive` metadata +* Encoded `=` key-value pair +* The `!!!` format used by the CLI `t4 -a` option + +For example: + +```xml + + + + + +``` + +
+ +`DirectiveProcessor` + + + +Register a custom directive processor. This may use the same use the `!!` format as the CLI `t4 --dp` option, or separate `Class` and `Assembly` metadata. + +```xml + + + + +``` + +
+ +`T4ReferencePath` + + + +Adds a search directory for resolving assembly references in `T4Transform` templates. Affects `<#@assembly#>` directives and calls to the host's `ResolveAssemblyReference(...)` method. + +
+ +`T4IncludePath` + + + +Adds a search directory for resolving `<#@include#>` directives in `T4Transform` and `T4Preprocess` templates. For `T4Transform` items, this also affects calls to the host's `LoadIncludeText(...)` method. + +
+ +`T4AssemblyReference` + + + +Additional assemblies to be referenced when processing `T4Transform` items. May be a absolute path, or relative to the project or the `T4ReferencePath` directories. + +
+ +### Item Metadata + +The `T4Transform` and `T4Preprocess` items have optional metadata that can be used to control the path used for the generated output. + +| Metadata | Description +| -- | -- +| `OutputFilePath`| Overrides the output folder +| `OutputFileName`| Overrides the output file name + +### CLI Properties + +There also are several properties that are intended to be passed in when invoking the target on the CLI and should not be used in project/targets files: + +| Property | Description +| -- | -- +| `TransformFile` | Semicolon-separated list of template filenames. If `TransformFile` is set when invoking the `TextTransform` target explicitly, then only the templates specified by it will be transformed. +| `T4Arguments` | Semicolon-separated list of `T4Argument` items. + +For example: + +```bash +dotnet msbuild -t:TransformTemplates -p:TransformFile=Foo.cs -p:T4Arguments="Foo=1;Bar=2" +``` + +## Target Extensibility + +The `TransformTemplatesCore` target is provided as an extension points for custom targets that require running before or after template processing. This target runs when automatic or explicit transformation takes place, whereas the `TransformTemplates` target only runs when invoked explicitly. + +The outputs of the transformed and preprocessed templates are available as `GeneratedTemplateOutput` and `PreprocessedTemplateOutput` items respectively, and assemblies referenced by preprocessed templates are available as `T4RequiredAssemblies` items. + + These items are only available after the transform targets have run, so are not available in the MSBuild project at evaluation time. To access them in custom targets, use `AfterTargets="TransformTemplatesCore"` to order your target after template transformation. + +For example: + +```xml + + + + + +``` + +## Compatibility + +The following properties, items and metadata are provided for partial backwards compatibility with the Visual Studio [Microsoft.TextTemplating](https://learn.microsoft.com/en-us/visualstudio/modeling/code-generation-in-a-build-process) MSBuild targets. + +| Kind | Name | Description +| -- | -- |-- +| Item | `T4ParameterValues` | Equivalent to `T4Argument` item +| Property | `UseLegacyT4Preprocessing` | Place preprocessed templates beside the template files instead of dynamically injecting them into the build +| Property | `IncludeFolders` | Equivalent to `T4IncludePath` items +| Property | `PreprocessTemplateDefaultNamespace` | Equivalent to `T4DefaultNamespace` property +| Property | `AfterTransform` | List of targets to run before template transformation. Use `BeforeTargets="TransformTemplatesCore"` instead. +| Property | `AfterTransform` | List of targets to run after template transformation. Use `AfterTargets="TransformTemplatesCore"` instead. +| Metadata | `DirectiveProcessor.Codebase` | Equivalent to `Assembly` metadata \ No newline at end of file From e79515f83df4c0aef05c6eff75ba648eebb717f2 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Wed, 15 Feb 2023 19:08:19 -0500 Subject: [PATCH 2/8] Use build-term resx generator --- .../Messages.Designer.cs | 289 ------------------ .../Mono.TextTemplating.Build.csproj | 15 +- 2 files changed, 13 insertions(+), 291 deletions(-) delete mode 100644 Mono.TextTemplating.Build/Messages.Designer.cs diff --git a/Mono.TextTemplating.Build/Messages.Designer.cs b/Mono.TextTemplating.Build/Messages.Designer.cs deleted file mode 100644 index ff23bfb..0000000 --- a/Mono.TextTemplating.Build/Messages.Designer.cs +++ /dev/null @@ -1,289 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Mono.TextTemplating.Build { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Mono.TextTemplating.Build.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to T4 build state format has changed. All T4 files will be reprocessed.. - /// - internal static string BuildStateFormatChanged { - get { - return ResourceManager.GetString("BuildStateFormatChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to load T4 build state. All T4 files will be reprocessed.. - /// - internal static string BuildStateLoadFailed { - get { - return ResourceManager.GetString("BuildStateLoadFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to save T4 build state. All T4 files will be reprocessed on next run.. - /// - internal static string BuildStateSaveFailed { - get { - return ResourceManager.GetString("BuildStateSaveFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Directive processor '{0}' could not be split into 'name!class!assembly' components. - /// - internal static string DirectiveProcessorDoesNotHaveThreeValues { - get { - return ResourceManager.GetString("DirectiveProcessorDoesNotHaveThreeValues", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Directive processor '{0}' is missing component '{1}'. - /// - internal static string DirectiveProcessorMissingComponent { - get { - return ResourceManager.GetString("DirectiveProcessorMissingComponent", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Directive processor '{0}' has no 'Assembly' metadata. - /// - internal static string DirectiveProcessorNoAssembly { - get { - return ResourceManager.GetString("DirectiveProcessorNoAssembly", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal exception. Please report at https://github.com/mono/t4. - ///{0}. - /// - internal static string InternalException { - get { - return ResourceManager.GetString("InternalException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Loaded state file '{0}'. - /// - internal static string LoadedStateFile { - get { - return ResourceManager.GetString("LoadedStateFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Template parameter '{0}' has neither 'Value' metadata nor a value encoded in the name. - /// - internal static string ParameterNoValue { - get { - return ResourceManager.GetString("ParameterNoValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to T4 engine encountered a recoverable internal error. Although it should not affect output, please report it as it may affect reliability and performance.. - /// - internal static string RecoverableInternalError { - get { - return ResourceManager.GetString("RecoverableInternalError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating all templates: default namespace has changed. - /// - internal static string RegeneratingAllDefaultNamespaceChanged { - get { - return ResourceManager.GetString("RegeneratingAllDefaultNamespaceChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating all templates: directive processors have changed. - /// - internal static string RegeneratingAllDirectiveProcessorsChanged { - get { - return ResourceManager.GetString("RegeneratingAllDirectiveProcessorsChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating all templates: include paths have changed. - /// - internal static string RegeneratingAllIncludePathsChanged { - get { - return ResourceManager.GetString("RegeneratingAllIncludePathsChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating all templates: intermediate directory has changed. - /// - internal static string RegeneratingAllIntermediateDirChanged { - get { - return ResourceManager.GetString("RegeneratingAllIntermediateDirChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating all templates: parameters have have changed. - /// - internal static string RegeneratingAllParametersChanged { - get { - return ResourceManager.GetString("RegeneratingAllParametersChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating all templates: reference paths have changed. - /// - internal static string RegeneratingAllReferencePathsChanged { - get { - return ResourceManager.GetString("RegeneratingAllReferencePathsChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating preprocessed template '{0}': output file '{1}' was not found. - /// - internal static string RegeneratingPreprocessedOutputFileMissing { - get { - return ResourceManager.GetString("RegeneratingPreprocessedOutputFileMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating preprocessed template '{0}': output file '{1}' is older than dependency '{2}'. - /// - internal static string RegeneratingPreprocessedOutputFileOlderThanDependency { - get { - return ResourceManager.GetString("RegeneratingPreprocessedOutputFileOlderThanDependency", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating preprocessed template '{0}': output file '{1}' is older than template file. - /// - internal static string RegeneratingPreprocessedOutputFileOlderThanTemplate { - get { - return ResourceManager.GetString("RegeneratingPreprocessedOutputFileOlderThanTemplate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating transform template '{0}': output file '{1}' was not found. - /// - internal static string RegeneratingTransformMissingOutputFile { - get { - return ResourceManager.GetString("RegeneratingTransformMissingOutputFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating transform template '{0}': output file '{1}' is older than dependency '{2}'. - /// - internal static string RegeneratingTransformOutputFileOlderThanDependency { - get { - return ResourceManager.GetString("RegeneratingTransformOutputFileOlderThanDependency", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating transform template '{0}': output file '{1}' is older than template file. - /// - internal static string RegeneratingTransformOutputFileOlderThanTemplate { - get { - return ResourceManager.GetString("RegeneratingTransformOutputFileOlderThanTemplate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Regenerating transform templates: assembly references have changed. - /// - internal static string RegeneratingTransformsAsmRefsChanged { - get { - return ResourceManager.GetString("RegeneratingTransformsAsmRefsChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Skipping preprocessed template '{0}': output '{1}' is up to date. - /// - internal static string SkippingPreprocessedOutputUpToDate { - get { - return ResourceManager.GetString("SkippingPreprocessedOutputUpToDate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Skipping transform template '{0}': output '{1}' is up to date. - /// - internal static string SkippingTransformUpToDate { - get { - return ResourceManager.GetString("SkippingTransformUpToDate", resourceCulture); - } - } - } -} diff --git a/Mono.TextTemplating.Build/Mono.TextTemplating.Build.csproj b/Mono.TextTemplating.Build/Mono.TextTemplating.Build.csproj index 7a7344c..ab1cf0b 100644 --- a/Mono.TextTemplating.Build/Mono.TextTemplating.Build.csproj +++ b/Mono.TextTemplating.Build/Mono.TextTemplating.Build.csproj @@ -38,8 +38,19 @@ + + + PrepareResources;$(CompileDependsOn) + + - - + From ddfdc83ff5bc272ec99cb456feba2ac3f869ab6f Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Wed, 15 Feb 2023 20:04:04 -0500 Subject: [PATCH 3/8] Make encoded vs explicit metadata consistent Explicit MSBuild metadata should always override encoded metadata --- Mono.TextTemplating.Build/Messages.resx | 12 ++-- Mono.TextTemplating.Build/TextTransform.cs | 84 ++++++++++++---------- 2 files changed, 53 insertions(+), 43 deletions(-) diff --git a/Mono.TextTemplating.Build/Messages.resx b/Mono.TextTemplating.Build/Messages.resx index a1d6685..14e8328 100644 --- a/Mono.TextTemplating.Build/Messages.resx +++ b/Mono.TextTemplating.Build/Messages.resx @@ -127,19 +127,19 @@ Failed to save T4 build state. All T4 files will be reprocessed on next run. - Directive processor '{0}' could not be split into 'name!class!assembly' components - - - Directive processor '{0}' is missing component '{1}' + Directive processor '{0}' has encoded form with incorrect number of components Directive processor '{0}' has no 'Assembly' metadata + + Directive processor '{0}' has no 'Class' metadata + Loaded state file '{0}' - - Template parameter '{0}' has neither 'Value' metadata nor a value encoded in the name + + Template argument '{0}' has no 'Value' metadata T4 engine encountered a recoverable internal error. Although it should not affect output, please report it as it may affect reliability and performance. diff --git a/Mono.TextTemplating.Build/TextTransform.cs b/Mono.TextTemplating.Build/TextTransform.cs index d004d13..9ff0261 100644 --- a/Mono.TextTemplating.Build/TextTransform.cs +++ b/Mono.TextTemplating.Build/TextTransform.cs @@ -140,10 +140,7 @@ public override bool Execute () } //TODO - //IntermediateDirectory //RequiredAssemblies - //GeneratedTemplates - //PreprocessedTemplates //settings.Debug //settings.Log //metadata to override output name, class name and namespace @@ -167,16 +164,27 @@ bool AddParameters (TemplateBuildState buildState) foreach (var par in ParameterValues) { string paramName = par.ItemSpec; + string processorName, directiveName, paramVal; - string paramVal = par.GetMetadata ("Value"); - string processorName, directiveName; + if (TemplateGenerator.TryParseParameter (paramName, out processorName, out directiveName, out string parsedName, out paramVal)) { + paramName = parsedName; + } + + // metadata overrides encoded values. todo: warn when this happens? + if (par.GetMetadata ("Value") is string valueMetadata) { + paramVal = valueMetadata; + } + + if (par.GetMetadata ("Processor") is string processorMetadata) { + processorName = processorMetadata; + } - if (!string.IsNullOrEmpty (paramVal)) { - processorName = par.GetMetadata ("Processor"); - directiveName = par.GetMetadata ("Directive"); + if (par.GetMetadata ("Directive") is string directiveMetadata) { + directiveName = directiveMetadata; } - else if (!TemplateGenerator.TryParseParameter (paramName, out processorName, out directiveName, out paramName, out paramVal)) { - Log.LogErrorFromResources (nameof(Messages.ParameterNoValue), par); + + if(paramVal is null) { + Log.LogWarningFromResources (nameof(Messages.ArgumentNoValue), par); success = false; continue; } @@ -205,44 +213,46 @@ bool AddDirectiveProcessors (TemplateBuildState buildState) foreach (var dirItem in DirectiveProcessors) { var name = dirItem.ItemSpec; - var className = dirItem.GetMetadata ("Class"); + string className = null, assembly = null; - if (className != null) { - var assembly = dirItem.GetMetadata ("Assembly") ?? dirItem.GetMetadata ("Codebase"); - if (string.IsNullOrEmpty (assembly)) { - Log.LogErrorFromResources (nameof(Messages.DirectiveProcessorNoAssembly), name); - hasErrors = true; + if (name.IndexOf('!') > -1) { + var split = name.Split ('!'); + if (split.Length != 3) { + Log.LogErrorFromResources (nameof(Messages.DirectiveProcessorDoesNotHaveThreeValues), name); + return false; } + //empty values for these are fine; they may get set through metadata + name = split[0]; + className = split[1]; + assembly = split[2]; + } - buildState.DirectiveProcessors.Add (new TemplateBuildState.DirectiveProcessor { - Name = name, - Class = className, - Assembly = assembly - }); - continue; + if (dirItem.GetMetadata ("Class") is string classMetadata) { + className = classMetadata; + } + + if (dirItem.GetMetadata ("Codebase") is string codebaseMetadata) { + assembly = codebaseMetadata; + } + + if (dirItem.GetMetadata ("Assembly") is string assemblyMetadata) { + assembly = assemblyMetadata; } - var split = name.Split ('!'); - if (split.Length != 3) { - Log.LogErrorFromResources (nameof(Messages.DirectiveProcessorDoesNotHaveThreeValues), name); + if (string.IsNullOrEmpty (className)) { + Log.LogErrorFromResources (nameof(Messages.DirectiveProcessorNoClass), name); hasErrors = true; - continue; } - for (int i = 0; i < 3; i++) { - string s = split[i]; - if (string.IsNullOrEmpty (s)) { - string kind = i == 0 ? "name" : (i == 1 ? "class" : "assembly"); - Log.LogErrorFromResources (nameof(Messages.DirectiveProcessorMissingComponent), kind, name); - hasErrors = true; - continue; - } + if (string.IsNullOrEmpty (assembly)) { + Log.LogErrorFromResources (nameof(Messages.DirectiveProcessorNoAssembly), name); + hasErrors = true; } buildState.DirectiveProcessors.Add (new TemplateBuildState.DirectiveProcessor { - Name = split[0], - Class = split[1], - Assembly = split[2] + Name = name, + Class = className, + Assembly = assembly }); } From 7f8bd580701dd86f6d15212a693ecd878878cac7 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Wed, 15 Feb 2023 20:05:09 -0500 Subject: [PATCH 4/8] Reformat the package readme to make nuget happy --- Mono.TextTemplating.Build/TextTransform.cs | 4 ++ Mono.TextTemplating.Build/readme.md | 83 ++++++++-------------- 2 files changed, 33 insertions(+), 54 deletions(-) diff --git a/Mono.TextTemplating.Build/TextTransform.cs b/Mono.TextTemplating.Build/TextTransform.cs index 9ff0261..6137d12 100644 --- a/Mono.TextTemplating.Build/TextTransform.cs +++ b/Mono.TextTemplating.Build/TextTransform.cs @@ -99,6 +99,7 @@ public override bool Execute () string inputFile = ppt.ItemSpec; string outputFile; if (UseLegacyPreprocessingMode) { + //TODO: OutputFilePath, OutputFileName outputFile = Path.ChangeExtension (inputFile, ".cs"); } else { //FIXME: this could cause collisions. generate a path based on relative path and link metadata @@ -114,6 +115,9 @@ public override bool Execute () if (TransformTemplates != null) { buildState.TransformTemplates = new List (); foreach (var tt in TransformTemplates) { + //TODO: OutputFilePath, OutputFileName + //var outputFilePathMetadata = tt.GetMetadata("OutputFilePath"); + //var outputFileNameMetadata = tt.GetMetadata("OutputFileName"); string inputFile = tt.ItemSpec; string outputFile = Path.ChangeExtension (inputFile, ".txt"); buildState.TransformTemplates.Add (new TemplateBuildState.TransformTemplate { diff --git a/Mono.TextTemplating.Build/readme.md b/Mono.TextTemplating.Build/readme.md index 8004867..6a7304e 100644 --- a/Mono.TextTemplating.Build/readme.md +++ b/Mono.TextTemplating.Build/readme.md @@ -28,80 +28,54 @@ Template transformation can be customized using a range of MSBuild properties, i ### Items - - - - -
ItemDescription
- -`T4Argument` +| Item | Description +| -- | -- +| `T4Argument` | Pass a parameter value to the T4 templates, optionally scoped to a directive processor and/or directive. This may use `Value` metadata, with optional `Processor` and/or `Directive` metadata, or it may encode the value and processor into the `Include` with the `=` or `!!!` formats used by the CLI `t4 -a` option. +| `DirectiveProcessor` | Register a custom directive processor by name. The `Class` and `Assembly` may be provided as metadata, or encoded into the `Include` with the `!!` format used by the CLI `t4 --dp` option. +| `T4ReferencePath` | Adds a search directory for resolving assembly references in `T4Transform` templates. Affects `<#@assembly#>` directives and calls to the host's `ResolveAssemblyReference(...)` method. +|`T4IncludePath` | Adds a search directory for resolving `<#@include#>` directives in `T4Transform` and `T4Preprocess` templates. For `T4Transform` items, this also affects calls to the host's `LoadIncludeText(...)` method. +| `T4AssemblyReference` | Additional assemblies to be referenced when processing `T4Transform` items. May be a absolute path, or relative to the project or the `T4ReferencePath` directories. - -Pass a parameter value to the T4 templates, optionally scoped to a directive processor and/or directive. This may take one of several forms: +Both `T4Argument` and `DirectiveProcessor` items support either setting metadata via MSBuild metadata or encoding it into the `Include` in the same format supported by the options of the `t4` CLI tool. If both encoded metadata and MSBuild metadata are present, the MSBuild metadata takes precedence. -* Parameter name and `Value` metadata, with optional `Processor` and/or `Directive` metadata -* Encoded `=` key-value pair -* The `!!!` format used by the CLI `t4 -a` option +> **NOTE:** Encoded metadata is supported for convenience, and to support parsing `T4Argument` items from the `T4Arguments` property. However, using MSBuild metadata is preferred when possible as it allows simplified manipulation of the items. -For example: +For example, the following `T4Argument` items are equivalent: ```xml - - - + + ``` -
- -`DirectiveProcessor` - - - -Register a custom directive processor. This may use the same use the `!!` format as the CLI `t4 --dp` option, or separate `Class` and `Assembly` metadata. +As are the following `T4Argument` items: ```xml - - + + + ``` -
- -`T4ReferencePath` - - - -Adds a search directory for resolving assembly references in `T4Transform` templates. Affects `<#@assembly#>` directives and calls to the host's `ResolveAssemblyReference(...)` method. - -
+Similarly, the following `DirectiveProcessor` items are equivalent: -`T4IncludePath` - - - -Adds a search directory for resolving `<#@include#>` directives in `T4Transform` and `T4Preprocess` templates. For `T4Transform` items, this also affects calls to the host's `LoadIncludeText(...)` method. - -
- -`T4AssemblyReference` - - - -Additional assemblies to be referenced when processing `T4Transform` items. May be a absolute path, or relative to the project or the `T4ReferencePath` directories. - -
+```xml + + + + +``` ### Item Metadata -The `T4Transform` and `T4Preprocess` items have optional metadata that can be used to control the path used for the generated output. +`T4Transform` items have optional metadata that can be used to control the path used for the generated output. These can also be used for `T4Preprocess` items when using legacy preprocessing. | Metadata | Description | -- | -- -| `OutputFilePath`| Overrides the output folder -| `OutputFileName`| Overrides the output file name +| `OutputDirectory`| Set an output directory for the file generated by the template. If this is not set, it defaults to the directory containing the template file. It is evaluated relative to the project directory, not relative to the template file. If the directory does not exist, it wil; be created. +| `OutputFileName`| Set a filename to be used for the template output instead of deriving one from the template filename. If this is set, it will be the exact name used for the generated file. Any `<#@extension..#>` directive present in the template file will be ignored, and no other extension will be added. This filename may include directory components, and is evaluated relative to the template directory, which defaults to the directory containing the template file. ### CLI Properties @@ -136,7 +110,7 @@ For example:
``` -## Compatibility +## Legacy Compatibility The following properties, items and metadata are provided for partial backwards compatibility with the Visual Studio [Microsoft.TextTemplating](https://learn.microsoft.com/en-us/visualstudio/modeling/code-generation-in-a-build-process) MSBuild targets. @@ -148,4 +122,5 @@ The following properties, items and metadata are provided for partial backwards | Property | `PreprocessTemplateDefaultNamespace` | Equivalent to `T4DefaultNamespace` property | Property | `AfterTransform` | List of targets to run before template transformation. Use `BeforeTargets="TransformTemplatesCore"` instead. | Property | `AfterTransform` | List of targets to run after template transformation. Use `AfterTargets="TransformTemplatesCore"` instead. -| Metadata | `DirectiveProcessor.Codebase` | Equivalent to `Assembly` metadata \ No newline at end of file +| Metadata | `DirectiveProcessor.Codebase` | Equivalent to `Assembly` metadata +| Metadata | `T4Transform.OutputFilePath` | Equivalent to `OutputDirectory` metadata From d56e59ea0d0882e3bfe26fc85406bd9a8bb33a08 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Wed, 14 Jun 2023 18:40:57 -0400 Subject: [PATCH 5/8] Fix refs to renamed target --- Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets b/Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets index faf7581..1316dba 100644 --- a/Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets +++ b/Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets @@ -27,18 +27,18 @@ + DependsOnTargets="_SetExplicitTransformProperties;TransformTemplatesCore" /> + DependsOnTargets="_SetTransformOnBuildProperties;TransformTemplatesCore" /> From 79742bbee59a4ce5000bff93bd2063eac8c3dcc2 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Mon, 2 Oct 2023 00:10:14 -0400 Subject: [PATCH 6/8] Update the MSBuild schema --- ...xtTemplating.Build.targets.buildschema.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets.buildschema.json b/Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets.buildschema.json index 8e22fb9..eee551f 100644 --- a/Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets.buildschema.json +++ b/Mono.TextTemplating.Build/Mono.TextTemplating.Build.targets.buildschema.json @@ -1,8 +1,5 @@ { "license": "Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.", - "intellisenseImports": [ - "$(MSBuildBinPath)\\Microsoft.Common.CurrentVersion.targets" - ], "items": { "T4ReferencePath": { "description": "Additional folders to search for T4 references", @@ -21,10 +18,12 @@ } }, "T4Argument": { - "description": "T4 key-value property to pass to T4 templates", - "includeDescription": "T4 property keys", + "description": "T4 template argument, optionally scoped to a directive and/or directive processor. The value, processor and directive may be provided using the `Value, `Processor` and `Directive metadata, or encoded into the `Include` with the syntax `name=value`, `directive!name!value` or `processor!directive!name!value`.", + "includeDescription": "T4 argument name", "metadata": { - "Value": "The T4 property value" + "Value": "The T4 argument value. Overrides any value encoded in the `Include`.", + "Processor": "The T4 directive processor to use for this argument. Overrides any value encoded in the `Include`.", + "Directive": "The T4 directive to use for this argument. Overrides any value encoded in the `Include`." } }, "T4Transform": { @@ -36,7 +35,8 @@ "type": "file" }, "DirectiveProcessor": { - "description": "A T4 directive processor", + "description": "A T4 directive processor. The fully qualified class name and assembly path may be provided in `Class` and `Assembly` metadata, or encoded in `Include` with the syntax `name!class!assembly`.", + "includeDescription": "T4 directive processor name", "metadata": { "Class": "Fully qualified name of the processor class", "Assembly": { @@ -100,13 +100,13 @@ "BeforeTransform": { "description": "Targets to run before the T4 Transform target", "type": "target-name", - "deprecationMessage": "Use `BeforeTargets=\"BeforeTransform\" for target ordering`", + "deprecationMessage": "Use `BeforeTargets=\"TransformTemplatesCore\" for target ordering`", "isList": true }, "AfterTransform": { "description": "Targets to run after the T4 Transform target", "type": "target-name", - "deprecationMessage": "Use `AfterTargets=\"AfterTransform\" for target ordering`", + "deprecationMessage": "Use `AfterTargets=\"TransformTemplatesCore\" for target ordering`", "isList": true }, "IncludeFolders": { From 5d13d8bcbf9b4856626b8b1e35060e5fb6b91ff4 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Mon, 2 Oct 2023 00:20:34 -0400 Subject: [PATCH 7/8] Add LICENSE.txt to packages We use PackageLicenseExpression, but all the actual text into the packages as well. Fixes #157. --- Directory.Build.props | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Directory.Build.props b/Directory.Build.props index f7580d1..7162a68 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -19,6 +19,10 @@ + + + + true From 72b685da0fee98f7341ab4861acac8f09ff35131 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Mon, 2 Oct 2023 00:52:22 -0400 Subject: [PATCH 8/8] Fix empty item metadata overwriting encoded value --- Mono.TextTemplating.Build/TextTransform.cs | 31 ++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/Mono.TextTemplating.Build/TextTransform.cs b/Mono.TextTemplating.Build/TextTransform.cs index 6137d12..65e5e9e 100644 --- a/Mono.TextTemplating.Build/TextTransform.cs +++ b/Mono.TextTemplating.Build/TextTransform.cs @@ -116,8 +116,8 @@ public override bool Execute () buildState.TransformTemplates = new List (); foreach (var tt in TransformTemplates) { //TODO: OutputFilePath, OutputFileName - //var outputFilePathMetadata = tt.GetMetadata("OutputFilePath"); - //var outputFileNameMetadata = tt.GetMetadata("OutputFileName"); + //var outputFilePathMetadata = tt.TryGetMetadata("OutputFilePath"); + //var outputFileNameMetadata = tt.TryGetMetadata("OutputFileName"); string inputFile = tt.ItemSpec; string outputFile = Path.ChangeExtension (inputFile, ".txt"); buildState.TransformTemplates.Add (new TemplateBuildState.TransformTemplate { @@ -175,15 +175,15 @@ bool AddParameters (TemplateBuildState buildState) } // metadata overrides encoded values. todo: warn when this happens? - if (par.GetMetadata ("Value") is string valueMetadata) { + if (par.TryGetMetadata ("Value", out string valueMetadata)) { paramVal = valueMetadata; } - if (par.GetMetadata ("Processor") is string processorMetadata) { + if (par.TryGetMetadata ("Processor", out string processorMetadata)) { processorName = processorMetadata; } - if (par.GetMetadata ("Directive") is string directiveMetadata) { + if (par.TryGetMetadata ("Directive", out string directiveMetadata)) { directiveName = directiveMetadata; } @@ -231,15 +231,15 @@ bool AddDirectiveProcessors (TemplateBuildState buildState) assembly = split[2]; } - if (dirItem.GetMetadata ("Class") is string classMetadata) { + if (dirItem.TryGetMetadata ("Class", out string classMetadata)) { className = classMetadata; } - if (dirItem.GetMetadata ("Codebase") is string codebaseMetadata) { + if (dirItem.TryGetMetadata ("Codebase", out string codebaseMetadata)) { assembly = codebaseMetadata; } - if (dirItem.GetMetadata ("Assembly") is string assemblyMetadata) { + if (dirItem.TryGetMetadata ("Assembly", out string assemblyMetadata)) { assembly = assemblyMetadata; } @@ -311,4 +311,19 @@ void SaveBuildState (TemplateBuildState buildState, string filePath, MessagePack } } } + + static class TaskItemExtensions + { + public static bool TryGetMetadata (this ITaskItem item, string name, out string value) + { + var potentialValue = item.GetMetadata (name); + if (potentialValue?.Length > 0) { + value = potentialValue; + return true; + } + + value = null; + return false; + } + } }