diff --git a/.autover/changes/8a5204e4-83ec-45ac-8c75-27512ae9cdf8.json b/.autover/changes/8a5204e4-83ec-45ac-8c75-27512ae9cdf8.json new file mode 100644 index 00000000..7f8d57f5 --- /dev/null +++ b/.autover/changes/8a5204e4-83ec-45ac-8c75-27512ae9cdf8.json @@ -0,0 +1,12 @@ +{ + "Projects": [ + { + "Name": "Amazon.Lambda.Tools", + "Type": "Patch", + "ChangelogMessages": [ + "Update logic for finding project properties to use MSBuild instead of parsing the project file directly. See https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2022 for more details.", + "Fix 'SelfContained' property reading logic to actually read the value from the properties. Previously we were only checking that the value could be parsed and not reading the actual value." + ] + } + ] +} \ No newline at end of file diff --git a/aws-extensions-for-dotnet-cli.sln b/aws-extensions-for-dotnet-cli.sln index c3e5653e..28335904 100644 --- a/aws-extensions-for-dotnet-cli.sln +++ b/aws-extensions-for-dotnet-cli.sln @@ -67,6 +67,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestNativeAotNet8WebApp", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestIntegerFunction", "testapps\TestIntegerFunction\TestIntegerFunction.csproj", "{D7F1DFA4-066B-469C-B04C-DF032CF152C1}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestFunctionBuildProps", "testapps\TestFunctionBuildProps\TestFunctionBuildProps\TestFunctionBuildProps.csproj", "{AFA71B8E-F0AA-4704-8C4E-C11130F82B13}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -169,6 +171,10 @@ Global {D7F1DFA4-066B-469C-B04C-DF032CF152C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {D7F1DFA4-066B-469C-B04C-DF032CF152C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {D7F1DFA4-066B-469C-B04C-DF032CF152C1}.Release|Any CPU.Build.0 = Release|Any CPU + {AFA71B8E-F0AA-4704-8C4E-C11130F82B13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFA71B8E-F0AA-4704-8C4E-C11130F82B13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFA71B8E-F0AA-4704-8C4E-C11130F82B13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFA71B8E-F0AA-4704-8C4E-C11130F82B13}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -200,6 +206,7 @@ Global {AD31D053-97C5-4262-B187-EC42BFD51A9F} = {BB3CF729-8213-4DDD-85AE-A5E7754F3944} {69FFA03C-D29F-40E0-9E7F-572D5E10AF77} = {BB3CF729-8213-4DDD-85AE-A5E7754F3944} {D7F1DFA4-066B-469C-B04C-DF032CF152C1} = {BB3CF729-8213-4DDD-85AE-A5E7754F3944} + {AFA71B8E-F0AA-4704-8C4E-C11130F82B13} = {BB3CF729-8213-4DDD-85AE-A5E7754F3944} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DBFC70D6-49A2-40A1-AB08-5D9504AB7112} diff --git a/src/Amazon.Common.DotNetCli.Tools/Utilities.cs b/src/Amazon.Common.DotNetCli.Tools/Utilities.cs index 6daa6c88..f067728e 100644 --- a/src/Amazon.Common.DotNetCli.Tools/Utilities.cs +++ b/src/Amazon.Common.DotNetCli.Tools/Utilities.cs @@ -18,6 +18,7 @@ using System.Text.RegularExpressions; using System.Collections; using System.Xml; +using System.Text.Json; namespace Amazon.Common.DotNetCli.Tools { @@ -205,72 +206,136 @@ public static string DeterminePublishLocation(string workingDirectory, string pr return path; } - public static string LookupTargetFrameworkFromProjectFile(string projectLocation) - { - var projectFile = FindProjectFileInDirectory(projectLocation); - var xdoc = XDocument.Load(projectFile); - - var element = xdoc.XPathSelectElement("//PropertyGroup/TargetFramework"); - return element?.Value; - } - - /// - /// Retrieve the `OutputType` property of a given project + // + /// Looks up specified properties from a project. /// - /// Path of the project - /// The value of the `OutputType` property - public static string LookupOutputTypeFromProjectFile(string projectLocation) + /// The location of the project file. + /// The names of the properties to look up. + /// A dictionary of property names and their values. + public static Dictionary LookupProjectProperties(string projectLocation, params string[] propertyNames) { - var process = new Process(); - var output = string.Empty; - var msbuildProcessFailed = false; - try + var projectFile = FindProjectFileInDirectory(projectLocation); + var properties = new Dictionary(); + var arguments = new List { - process.StartInfo = new ProcessStartInfo() + "msbuild", + projectFile, + "-nologo", + $"--getProperty:{string.Join(',', propertyNames)}" + }; + + var process = new Process + { + StartInfo = new ProcessStartInfo { FileName = "dotnet", - Arguments = $"msbuild {projectLocation} -getProperty:OutputType", + Arguments = string.Join(" ", arguments), RedirectStandardOutput = true, + RedirectStandardError = true, UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden - }; - + CreateNoWindow = true + } + }; + try + { process.Start(); - output = process.StandardOutput.ReadToEnd(); - var hasExited = process.WaitForExit(5000); + string output = process.StandardOutput.ReadToEnd().Trim(); + string error = process.StandardError.ReadToEnd(); + process.WaitForExit(5000); - // If it hasn't completed in the specified timeout, stop the process and give up - if (!hasExited) + if (process.ExitCode == 0) { - process.Kill(); - msbuildProcessFailed = true; + if (propertyNames.Length == 1) + { + // If only one property was requested, the output is the direct value + properties[propertyNames[0]] = output; + } + else + { + // Multiple properties were requested, so we expect JSON output + using JsonDocument doc = JsonDocument.Parse(output); + JsonElement root = doc.RootElement; + JsonElement propertiesElement = root.GetProperty("Properties"); + + foreach (var property in propertyNames) + { + if (propertiesElement.TryGetProperty(property, out JsonElement propertyValue)) + { + properties[property] = propertyValue.GetString(); + } + } + } } - - // If it has completed but unsuccessfully, give up - if (process.ExitCode != 0) + else { - msbuildProcessFailed = true; + // Fallback to XML parsing + properties = LookupProjectPropertiesFromXml(projectFile, propertyNames); } } catch (Exception) { - // swallow any exceptions related to `dotnet msbuild` - msbuildProcessFailed = true; + // Fallback to XML parsing + properties = LookupProjectPropertiesFromXml(projectFile, propertyNames); } - if (msbuildProcessFailed) + return properties; + } + + + private static Dictionary LookupProjectPropertiesFromXml(string projectFile, string[] propertyNames) + { + var properties = new Dictionary(); + try { - var projectFile = FindProjectFileInDirectory(projectLocation); var xdoc = XDocument.Load(projectFile); - var element = xdoc.XPathSelectElement("//PropertyGroup/OutputType"); - output = element?.Value; + foreach (var propertyName in propertyNames) + { + var element = xdoc.XPathSelectElement($"//PropertyGroup/{propertyName}"); + if (element != null && !string.IsNullOrWhiteSpace(element.Value)) + { + properties[propertyName] = element.Value; + } + } + } + catch (Exception) + { + } + return properties; + } + + /// + /// Looks up the target framework from a project file. + /// + /// The location of the project file. + /// The target framework of the project. + public static string LookupTargetFrameworkFromProjectFile(string projectLocation) + { + var properties = LookupProjectProperties(projectLocation, "TargetFramework", "TargetFrameworks"); + if (properties.TryGetValue("TargetFramework", out var targetFramework) && !string.IsNullOrEmpty(targetFramework)) + { + return targetFramework; } + if (properties.TryGetValue("TargetFrameworks", out var targetFrameworks) && !string.IsNullOrEmpty(targetFrameworks)) + { + var frameworks = targetFrameworks.Split(';'); + if (frameworks.Length > 1 ){ + return null; + } + return frameworks[0]; + } + return null; + } - return - string.IsNullOrEmpty(output) ? - null : - output.Trim(); + /// + /// Retrieve the `OutputType` property of a given project + /// + /// Path of the project + /// The value of the `OutputType` property + public static string LookupOutputTypeFromProjectFile(string projectLocation) + { + var properties = LookupProjectProperties(projectLocation, "OutputType"); + return properties.TryGetValue("OutputType", out var outputType) ? outputType.Trim() : null; } public static bool LookPublishAotFlag(string projectLocation, string msBuildParameters) @@ -288,20 +353,15 @@ public static bool LookPublishAotFlag(string projectLocation, string msBuildPara } } - // If the property wasn't provided in msBuildParameters, fall back to searching project file - var projectFile = FindProjectFileInDirectory(projectLocation); - - var xdoc = XDocument.Load(projectFile); - - var element = xdoc.XPathSelectElement("//PropertyGroup/PublishAot"); - - if (bool.TryParse(element?.Value, out bool result)) + var properties = LookupProjectProperties(projectLocation, "PublishAot"); + if (properties.TryGetValue("PublishAot", out var publishAot)) { - return result; + return bool.TryParse(publishAot, out var result) && result; } - return false; } + + public static bool HasExplicitSelfContainedFlag(string projectLocation, string msBuildParameters) { if (msBuildParameters != null && msBuildParameters.IndexOf("--self-contained", StringComparison.InvariantCultureIgnoreCase) != -1) @@ -309,22 +369,16 @@ public static bool HasExplicitSelfContainedFlag(string projectLocation, string m return true; } - // If the property wasn't provided in msBuildParameters, fall back to searching project file - var projectFile = FindProjectFileInDirectory(projectLocation); - - var xdoc = XDocument.Load(projectFile); - - var element = xdoc.XPathSelectElement("//PropertyGroup/SelfContained"); - - if (bool.TryParse(element?.Value, out _)) + var properties = LookupProjectProperties(projectLocation, "SelfContained"); + if (properties.TryGetValue("SelfContained", out var selfContained)) { - return true; + return bool.TryParse(selfContained, out var isSelfContained) && isSelfContained; } return false; } - public static string FindProjectFileInDirectory(string directory) + private static string FindProjectFileInDirectory(string directory) { if (File.Exists(directory)) return directory; diff --git a/test/Amazon.Common.DotNetCli.Tools.Test/UtilitiesTests.cs b/test/Amazon.Common.DotNetCli.Tools.Test/UtilitiesTests.cs index 320c4cb0..09f21616 100644 --- a/test/Amazon.Common.DotNetCli.Tools.Test/UtilitiesTests.cs +++ b/test/Amazon.Common.DotNetCli.Tools.Test/UtilitiesTests.cs @@ -13,6 +13,7 @@ public class UtilitiesTests [InlineData("../../../../../testapps/TestFunction", "net6.0")] [InlineData("../../../../../testapps/ServerlessWithYamlFunction", "net6.0")] [InlineData("../../../../../testapps/TestBeanstalkWebApp", "netcoreapp3.1")] + [InlineData("../../../../../testapps/TestFunctionBuildProps/TestFunctionBuildProps", "net6.0")] public void CheckFramework(string projectPath, string expectedFramework) { var assembly = this.GetType().GetTypeInfo().Assembly; diff --git a/testapps/TestFunctionBuildProps/Directory.Build.props b/testapps/TestFunctionBuildProps/Directory.Build.props new file mode 100644 index 00000000..81e7fd0f --- /dev/null +++ b/testapps/TestFunctionBuildProps/Directory.Build.props @@ -0,0 +1,5 @@ + + + net6.0 + + diff --git a/testapps/TestFunctionBuildProps/TestFunctionBuildProps/Function.cs b/testapps/TestFunctionBuildProps/TestFunctionBuildProps/Function.cs new file mode 100644 index 00000000..f4783136 --- /dev/null +++ b/testapps/TestFunctionBuildProps/TestFunctionBuildProps/Function.cs @@ -0,0 +1,14 @@ +using Amazon.Lambda.Core; + +// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. +[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] + +namespace TestFunctionBuildProps; + +public class Function +{ + public string FunctionHandler(string input, ILambdaContext context) + { + return input.ToUpper(); + } +} diff --git a/testapps/TestFunctionBuildProps/TestFunctionBuildProps/TestFunctionBuildProps.csproj b/testapps/TestFunctionBuildProps/TestFunctionBuildProps/TestFunctionBuildProps.csproj new file mode 100644 index 00000000..db9ca90e --- /dev/null +++ b/testapps/TestFunctionBuildProps/TestFunctionBuildProps/TestFunctionBuildProps.csproj @@ -0,0 +1,16 @@ + + + enable + enable + true + Lambda + + true + + true + + + + + + \ No newline at end of file diff --git a/testapps/TestFunctionBuildProps/TestFunctionBuildProps/aws-lambda-tools-defaults.json b/testapps/TestFunctionBuildProps/TestFunctionBuildProps/aws-lambda-tools-defaults.json new file mode 100644 index 00000000..a5932b1c --- /dev/null +++ b/testapps/TestFunctionBuildProps/TestFunctionBuildProps/aws-lambda-tools-defaults.json @@ -0,0 +1,16 @@ +{ + "Information": [ + "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", + "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", + "dotnet lambda help", + "All the command line options for the Lambda command can be specified in this file." + ], + "profile": "default", + "region": "us-west-2", + "configuration": "Release", + "function-architecture": "x86_64", + "function-runtime": "dotnet8", + "function-memory-size": 512, + "function-timeout": 30, + "function-handler": "TestFunctionBuildProps::TestFunctionBuildProps.Function::FunctionHandler" +} \ No newline at end of file