-
Notifications
You must be signed in to change notification settings - Fork 393
Migrate GenerateFileFromTemplate to Microsoft.DotNet.Build.Tasks.Templating #7403
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 9 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
4c2efe4
Migrate GenerateTemplateFromFile to Microsoft.DotNet.Build.Tasks.Temp…
fd680db
Update src/Microsoft.DotNet.Build.Tasks.Templating/build/Microsoft.Do…
97ffb60
Apply suggestions from code review
7d4af53
Update GenerateFileFromTemplate.cs
8d5d7ee
Update MSBuildListSplitter.cs
f911aae
Update Microsoft.DotNet.Build.Tasks.Templating.csproj
3c0c88d
Import
ceaa936
Update style and address feedback
d4ae2a3
Add another warning
f5eb4e3
Separate output property
de311ce
Add tests
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
147 changes: 147 additions & 0 deletions
147
src/Microsoft.DotNet.Build.Tasks.Templating/GenerateFileFromTemplate.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using System.Text; | ||
| using Microsoft.Build.Framework; | ||
| using Microsoft.Build.Utilities; | ||
|
|
||
| namespace Microsoft.DotNet.Build.Tasks.Templating | ||
| { | ||
| /// <summary> | ||
| /// <para> | ||
| /// Generates a new file at <see cref="OutputPath"/>. | ||
| /// </para> | ||
| /// <para> | ||
| /// The <see cref="TemplateFile"/> can define variables for substitution using <see cref="Properties"/>. | ||
| /// </para> | ||
| /// <example> | ||
| /// The input file might look like this: | ||
| /// <code> | ||
| /// 2 + 2 = ${Sum} | ||
| /// </code> | ||
| /// When the task is invoked like this, it will produce "2 + 2 = 4" | ||
| /// <code> | ||
| /// <GenerateFileFromTemplate Properties="Sum=4;OtherValue=123;" ... > | ||
| /// </code> | ||
| /// </example> | ||
| /// </summary> | ||
| public class GenerateFileFromTemplate : Task | ||
| { | ||
| /// <summary> | ||
| /// The template file using the variable syntax <c>${VarName}</c>. | ||
| /// If your template file needs to output this format, you can escape the dollar sign with a backtick e.g. <c>`${NotReplaced}</c>. | ||
| /// </summary> | ||
| [Required] | ||
| public string TemplateFile { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The destination for the generated file. | ||
| /// </summary> | ||
| [Required] | ||
| [Output] | ||
| public string OutputPath { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Key=Value pairs of values, separated by semicolons e.g. <c>Properties="Sum=4;OtherValue=123;"</c>. | ||
| /// </summary> | ||
| [Required] | ||
| public string[] Properties { get; set; } | ||
|
|
||
| public override bool Execute() | ||
| { | ||
| string outputPath = Path.GetFullPath(OutputPath.Replace('\\', '/')); | ||
|
|
||
| if (!File.Exists(TemplateFile)) | ||
| { | ||
| Log.LogError($"File {TemplateFile} does not exist"); | ||
| return false; | ||
| } | ||
|
|
||
| IDictionary<string, string> values = MSBuildListSplitter.GetNamedProperties(Properties, Log); | ||
| string template = File.ReadAllText(TemplateFile); | ||
|
|
||
| string result = Replace(template, values); | ||
| Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); | ||
| File.WriteAllText(outputPath, result); | ||
|
|
||
| return !Log.HasLoggedErrors; | ||
| } | ||
|
|
||
| public string Replace(string template, IDictionary<string, string> values) | ||
| { | ||
| StringBuilder sb = new(); | ||
|
JunTaoLuo marked this conversation as resolved.
|
||
| StringBuilder varNameSb = new(); | ||
| int line = 1; | ||
| for (int i = 0; i < template.Length; i++) | ||
| { | ||
| char templateChar = template[i]; | ||
| char nextTemplateChar = i + 1 >= template.Length | ||
| ? '\0' | ||
| : template[i + 1]; | ||
|
JunTaoLuo marked this conversation as resolved.
|
||
|
|
||
| // count lines in the template file | ||
| if (templateChar == '\n') | ||
| { | ||
| line++; | ||
| } | ||
|
|
||
| if (templateChar == '`' && (nextTemplateChar == '$' || nextTemplateChar == '`')) | ||
| { | ||
| // skip the backtick for known escape characters | ||
| i++; | ||
| sb.Append(nextTemplateChar); | ||
| continue; | ||
| } | ||
|
|
||
| if (templateChar != '$' || nextTemplateChar != '{') | ||
| { | ||
| // variables begin with ${. Moving on. | ||
| sb.Append(templateChar); | ||
| continue; | ||
| } | ||
|
|
||
| varNameSb.Clear(); | ||
| i += 2; | ||
| for (; i < template.Length; i++) | ||
| { | ||
| templateChar = template[i]; | ||
| if (templateChar != '}') | ||
| { | ||
| varNameSb.Append(templateChar); | ||
| } | ||
| else | ||
| { | ||
| // Found the end of the variable substitution | ||
| string varName = varNameSb.ToString(); | ||
| if (values.TryGetValue(varName, out string value)) | ||
| { | ||
| sb.Append(value); | ||
| } | ||
| else | ||
| { | ||
| Log.LogWarning(null, null, null, TemplateFile, | ||
| line, 0, 0, 0, | ||
| message: $"No property value is available for '{varName}'"); | ||
|
JunTaoLuo marked this conversation as resolved.
|
||
| } | ||
|
|
||
| varNameSb.Clear(); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (varNameSb.Length > 0) | ||
| { | ||
| Log.LogWarning(null, null, null, TemplateFile, | ||
| line, 0, 0, 0, | ||
| message: "Expected closing bracket for variable placeholder. No substitution will be made."); | ||
| sb.Append("${").Append(varNameSb.ToString()); | ||
| } | ||
| } | ||
|
|
||
| return sb.ToString(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
43 changes: 43 additions & 0 deletions
43
src/Microsoft.DotNet.Build.Tasks.Templating/MSBuildListSplitter.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using Microsoft.Build.Utilities; | ||
|
|
||
| namespace Microsoft.DotNet.Build.Tasks.Templating | ||
| { | ||
| internal static class MSBuildListSplitter | ||
| { | ||
| public static IDictionary<string, string> GetNamedProperties(string[] input, TaskLoggingHelper log) | ||
| { | ||
| Dictionary<string, string> values = new(StringComparer.OrdinalIgnoreCase); | ||
| if (input == null) | ||
| { | ||
| return values; | ||
| } | ||
|
|
||
| foreach (string item in input) | ||
| { | ||
| int splitIdx = item.IndexOf('='); | ||
| if (splitIdx < 0) | ||
| { | ||
| log.LogWarning($"Property: {item} does not have a valid '=' separator"); | ||
| continue; | ||
|
JunTaoLuo marked this conversation as resolved.
|
||
| } | ||
|
|
||
| string key = item.Substring(0, splitIdx).Trim(); | ||
| if (string.IsNullOrEmpty(key)) | ||
| { | ||
| log.LogWarning($"Property: {item} does not have a valid property name"); | ||
| continue; | ||
| } | ||
|
dougbu marked this conversation as resolved.
|
||
|
|
||
| string value = item.Substring(splitIdx + 1); | ||
| values[key] = value; | ||
| } | ||
|
|
||
| return values; | ||
| } | ||
| } | ||
| } | ||
29 changes: 29 additions & 0 deletions
29
src/Microsoft.DotNet.Build.Tasks.Templating/Microsoft.DotNet.Build.Tasks.Templating.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| <!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. --> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. /fyi @markwilkie four projects in this repo use a different header: <!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->They should probably be made consistent. |
||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
JunTaoLuo marked this conversation as resolved.
|
||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>netstandard2.0</TargetFramework> | ||
|
JunTaoLuo marked this conversation as resolved.
|
||
| <Description>Templating task package</Description> | ||
| <PackageTags>Arcade Build Tool Templating</PackageTags> | ||
| <IncludeSymbols>false</IncludeSymbols> | ||
| <IncludeSource>false</IncludeSource> | ||
| <IsPackable>true</IsPackable> | ||
| <BuildOutputTargetFolder>tools\</BuildOutputTargetFolder> | ||
| <DevelopmentDependency>true</DevelopmentDependency> | ||
| <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking> | ||
| <EnableDefaultNoneItems>false</EnableDefaultNoneItems> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <None Include="build/**/*.*" Pack="true"> | ||
| <PackagePath>build</PackagePath> | ||
| </None> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildFrameworkVersion)" /> | ||
| <PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCoreVersion)" /> | ||
| </ItemGroup> | ||
|
|
||
| <Import Project="$(RepoRoot)eng\BuildTask.targets" /> | ||
| </Project> | ||
10 changes: 10 additions & 0 deletions
10
...crosoft.DotNet.Build.Tasks.Templating/build/Microsoft.DotNet.Build.Tasks.Templating.props
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. --> | ||
| <Project> | ||
|
|
||
| <PropertyGroup> | ||
| <MicrosoftDotNetBuildTasksTemplatingAssembly>$(MSBuildThisFileDirectory)..\tools\netstandard2.0\Microsoft.DotNet.Build.Tasks.Templating.dll</MicrosoftDotNetBuildTasksTemplatingAssembly> | ||
| </PropertyGroup> | ||
|
|
||
| <UsingTask TaskName="Microsoft.DotNet.Build.Tasks.Templating.GenerateFileFromTemplate" AssemblyFile="$(MicrosoftDotNetBuildTasksTemplatingAssembly)" /> | ||
|
|
||
| </Project> |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This "Output" is not correct, it's an input parameter. It's not returned by this task.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we use it as output here: https://github.com/dotnet/aspnetcore/blob/52eff90fbcfca39b7eb58baad597df6a99a542b0/src/ProjectTemplates/GenerateContent.targets#L45-L52.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though I think it may be more correct to set the value to the value escaped by https://github.com/aspnet/BuildTools/blame/main/src/Internal.AspNetCore.BuildTasks/GenerateFileFromTemplate.cs#L55.
We could probably also separate it out into a separate out parameter like
ResolvedOutputPathorEscapedOutputPathbut so far we haven't run into any issues in our builds due to this being anOutputparameter so I would prefer if we don't block on this change and file followup issues to address.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This path is never set by this code. Marking it output is incorrect, since it doesn't actually output anything. We should not be checking in new, incorrect, code.