Skip to content

Commit b8b53d1

Browse files
Use MSBuild to find target framework (#348)
1 parent 21166cb commit b8b53d1

File tree

8 files changed

+188
-63
lines changed

8 files changed

+188
-63
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"Projects": [
3+
{
4+
"Name": "Amazon.Lambda.Tools",
5+
"Type": "Patch",
6+
"ChangelogMessages": [
7+
"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.",
8+
"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."
9+
]
10+
}
11+
]
12+
}

aws-extensions-for-dotnet-cli.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestNativeAotNet8WebApp", "
6767
EndProject
6868
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestIntegerFunction", "testapps\TestIntegerFunction\TestIntegerFunction.csproj", "{D7F1DFA4-066B-469C-B04C-DF032CF152C1}"
6969
EndProject
70+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestFunctionBuildProps", "testapps\TestFunctionBuildProps\TestFunctionBuildProps\TestFunctionBuildProps.csproj", "{AFA71B8E-F0AA-4704-8C4E-C11130F82B13}"
71+
EndProject
7072
Global
7173
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7274
Debug|Any CPU = Debug|Any CPU
@@ -169,6 +171,10 @@ Global
169171
{D7F1DFA4-066B-469C-B04C-DF032CF152C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
170172
{D7F1DFA4-066B-469C-B04C-DF032CF152C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
171173
{D7F1DFA4-066B-469C-B04C-DF032CF152C1}.Release|Any CPU.Build.0 = Release|Any CPU
174+
{AFA71B8E-F0AA-4704-8C4E-C11130F82B13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
175+
{AFA71B8E-F0AA-4704-8C4E-C11130F82B13}.Debug|Any CPU.Build.0 = Debug|Any CPU
176+
{AFA71B8E-F0AA-4704-8C4E-C11130F82B13}.Release|Any CPU.ActiveCfg = Release|Any CPU
177+
{AFA71B8E-F0AA-4704-8C4E-C11130F82B13}.Release|Any CPU.Build.0 = Release|Any CPU
172178
EndGlobalSection
173179
GlobalSection(SolutionProperties) = preSolution
174180
HideSolutionNode = FALSE
@@ -200,6 +206,7 @@ Global
200206
{AD31D053-97C5-4262-B187-EC42BFD51A9F} = {BB3CF729-8213-4DDD-85AE-A5E7754F3944}
201207
{69FFA03C-D29F-40E0-9E7F-572D5E10AF77} = {BB3CF729-8213-4DDD-85AE-A5E7754F3944}
202208
{D7F1DFA4-066B-469C-B04C-DF032CF152C1} = {BB3CF729-8213-4DDD-85AE-A5E7754F3944}
209+
{AFA71B8E-F0AA-4704-8C4E-C11130F82B13} = {BB3CF729-8213-4DDD-85AE-A5E7754F3944}
203210
EndGlobalSection
204211
GlobalSection(ExtensibilityGlobals) = postSolution
205212
SolutionGuid = {DBFC70D6-49A2-40A1-AB08-5D9504AB7112}

src/Amazon.Common.DotNetCli.Tools/Utilities.cs

Lines changed: 117 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using System.Text.RegularExpressions;
1919
using System.Collections;
2020
using System.Xml;
21+
using System.Text.Json;
2122

2223
namespace Amazon.Common.DotNetCli.Tools
2324
{
@@ -205,72 +206,136 @@ public static string DeterminePublishLocation(string workingDirectory, string pr
205206
return path;
206207
}
207208

208-
public static string LookupTargetFrameworkFromProjectFile(string projectLocation)
209-
{
210-
var projectFile = FindProjectFileInDirectory(projectLocation);
211209

212-
var xdoc = XDocument.Load(projectFile);
213-
214-
var element = xdoc.XPathSelectElement("//PropertyGroup/TargetFramework");
215-
return element?.Value;
216-
}
217-
218-
/// <summary>
219-
/// Retrieve the `OutputType` property of a given project
210+
// <summary>
211+
/// Looks up specified properties from a project.
220212
/// </summary>
221-
/// <param name="projectLocation">Path of the project</param>
222-
/// <returns>The value of the `OutputType` property</returns>
223-
public static string LookupOutputTypeFromProjectFile(string projectLocation)
213+
/// <param name="projectLocation">The location of the project file.</param>
214+
/// <param name="propertyNames">The names of the properties to look up.</param>
215+
/// <returns>A dictionary of property names and their values.</returns>
216+
public static Dictionary<string, string> LookupProjectProperties(string projectLocation, params string[] propertyNames)
224217
{
225-
var process = new Process();
226-
var output = string.Empty;
227-
var msbuildProcessFailed = false;
228-
try
218+
var projectFile = FindProjectFileInDirectory(projectLocation);
219+
var properties = new Dictionary<string, string>();
220+
var arguments = new List<string>
229221
{
230-
process.StartInfo = new ProcessStartInfo()
222+
"msbuild",
223+
projectFile,
224+
"-nologo",
225+
$"--getProperty:{string.Join(',', propertyNames)}"
226+
};
227+
228+
var process = new Process
229+
{
230+
StartInfo = new ProcessStartInfo
231231
{
232232
FileName = "dotnet",
233-
Arguments = $"msbuild {projectLocation} -getProperty:OutputType",
233+
Arguments = string.Join(" ", arguments),
234234
RedirectStandardOutput = true,
235+
RedirectStandardError = true,
235236
UseShellExecute = false,
236-
WindowStyle = ProcessWindowStyle.Hidden
237-
};
238-
237+
CreateNoWindow = true
238+
}
239+
};
240+
try
241+
{
239242
process.Start();
240-
output = process.StandardOutput.ReadToEnd();
241-
var hasExited = process.WaitForExit(5000);
243+
string output = process.StandardOutput.ReadToEnd().Trim();
244+
string error = process.StandardError.ReadToEnd();
245+
process.WaitForExit(5000);
242246

243-
// If it hasn't completed in the specified timeout, stop the process and give up
244-
if (!hasExited)
247+
if (process.ExitCode == 0)
245248
{
246-
process.Kill();
247-
msbuildProcessFailed = true;
249+
if (propertyNames.Length == 1)
250+
{
251+
// If only one property was requested, the output is the direct value
252+
properties[propertyNames[0]] = output;
253+
}
254+
else
255+
{
256+
// Multiple properties were requested, so we expect JSON output
257+
using JsonDocument doc = JsonDocument.Parse(output);
258+
JsonElement root = doc.RootElement;
259+
JsonElement propertiesElement = root.GetProperty("Properties");
260+
261+
foreach (var property in propertyNames)
262+
{
263+
if (propertiesElement.TryGetProperty(property, out JsonElement propertyValue))
264+
{
265+
properties[property] = propertyValue.GetString();
266+
}
267+
}
268+
}
248269
}
249-
250-
// If it has completed but unsuccessfully, give up
251-
if (process.ExitCode != 0)
270+
else
252271
{
253-
msbuildProcessFailed = true;
272+
// Fallback to XML parsing
273+
properties = LookupProjectPropertiesFromXml(projectFile, propertyNames);
254274
}
255275
}
256276
catch (Exception)
257277
{
258-
// swallow any exceptions related to `dotnet msbuild`
259-
msbuildProcessFailed = true;
278+
// Fallback to XML parsing
279+
properties = LookupProjectPropertiesFromXml(projectFile, propertyNames);
260280
}
261281

262-
if (msbuildProcessFailed)
282+
return properties;
283+
}
284+
285+
286+
private static Dictionary<string, string> LookupProjectPropertiesFromXml(string projectFile, string[] propertyNames)
287+
{
288+
var properties = new Dictionary<string, string>();
289+
try
263290
{
264-
var projectFile = FindProjectFileInDirectory(projectLocation);
265291
var xdoc = XDocument.Load(projectFile);
266-
var element = xdoc.XPathSelectElement("//PropertyGroup/OutputType");
267-
output = element?.Value;
292+
foreach (var propertyName in propertyNames)
293+
{
294+
var element = xdoc.XPathSelectElement($"//PropertyGroup/{propertyName}");
295+
if (element != null && !string.IsNullOrWhiteSpace(element.Value))
296+
{
297+
properties[propertyName] = element.Value;
298+
}
299+
}
300+
}
301+
catch (Exception)
302+
{
303+
}
304+
return properties;
305+
}
306+
307+
/// <summary>
308+
/// Looks up the target framework from a project file.
309+
/// </summary>
310+
/// <param name="projectLocation">The location of the project file.</param>
311+
/// <returns>The target framework of the project.</returns>
312+
public static string LookupTargetFrameworkFromProjectFile(string projectLocation)
313+
{
314+
var properties = LookupProjectProperties(projectLocation, "TargetFramework", "TargetFrameworks");
315+
if (properties.TryGetValue("TargetFramework", out var targetFramework) && !string.IsNullOrEmpty(targetFramework))
316+
{
317+
return targetFramework;
268318
}
319+
if (properties.TryGetValue("TargetFrameworks", out var targetFrameworks) && !string.IsNullOrEmpty(targetFrameworks))
320+
{
321+
var frameworks = targetFrameworks.Split(';');
322+
if (frameworks.Length > 1 ){
323+
return null;
324+
}
325+
return frameworks[0];
326+
}
327+
return null;
328+
}
269329

270-
return
271-
string.IsNullOrEmpty(output) ?
272-
null :
273-
output.Trim();
330+
/// <summary>
331+
/// Retrieve the `OutputType` property of a given project
332+
/// </summary>
333+
/// <param name="projectLocation">Path of the project</param>
334+
/// <returns>The value of the `OutputType` property</returns>
335+
public static string LookupOutputTypeFromProjectFile(string projectLocation)
336+
{
337+
var properties = LookupProjectProperties(projectLocation, "OutputType");
338+
return properties.TryGetValue("OutputType", out var outputType) ? outputType.Trim() : null;
274339
}
275340

276341
public static bool LookPublishAotFlag(string projectLocation, string msBuildParameters)
@@ -288,43 +353,32 @@ public static bool LookPublishAotFlag(string projectLocation, string msBuildPara
288353
}
289354
}
290355

291-
// If the property wasn't provided in msBuildParameters, fall back to searching project file
292-
var projectFile = FindProjectFileInDirectory(projectLocation);
293-
294-
var xdoc = XDocument.Load(projectFile);
295-
296-
var element = xdoc.XPathSelectElement("//PropertyGroup/PublishAot");
297-
298-
if (bool.TryParse(element?.Value, out bool result))
356+
var properties = LookupProjectProperties(projectLocation, "PublishAot");
357+
if (properties.TryGetValue("PublishAot", out var publishAot))
299358
{
300-
return result;
359+
return bool.TryParse(publishAot, out var result) && result;
301360
}
302-
303361
return false;
304362
}
363+
364+
305365
public static bool HasExplicitSelfContainedFlag(string projectLocation, string msBuildParameters)
306366
{
307367
if (msBuildParameters != null && msBuildParameters.IndexOf("--self-contained", StringComparison.InvariantCultureIgnoreCase) != -1)
308368
{
309369
return true;
310370
}
311371

312-
// If the property wasn't provided in msBuildParameters, fall back to searching project file
313-
var projectFile = FindProjectFileInDirectory(projectLocation);
314-
315-
var xdoc = XDocument.Load(projectFile);
316-
317-
var element = xdoc.XPathSelectElement("//PropertyGroup/SelfContained");
318-
319-
if (bool.TryParse(element?.Value, out _))
372+
var properties = LookupProjectProperties(projectLocation, "SelfContained");
373+
if (properties.TryGetValue("SelfContained", out var selfContained))
320374
{
321-
return true;
375+
return bool.TryParse(selfContained, out var isSelfContained) && isSelfContained;
322376
}
323377

324378
return false;
325379
}
326380

327-
public static string FindProjectFileInDirectory(string directory)
381+
private static string FindProjectFileInDirectory(string directory)
328382
{
329383
if (File.Exists(directory))
330384
return directory;

test/Amazon.Common.DotNetCli.Tools.Test/UtilitiesTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class UtilitiesTests
1313
[InlineData("../../../../../testapps/TestFunction", "net6.0")]
1414
[InlineData("../../../../../testapps/ServerlessWithYamlFunction", "net6.0")]
1515
[InlineData("../../../../../testapps/TestBeanstalkWebApp", "netcoreapp3.1")]
16+
[InlineData("../../../../../testapps/TestFunctionBuildProps/TestFunctionBuildProps", "net6.0")]
1617
public void CheckFramework(string projectPath, string expectedFramework)
1718
{
1819
var assembly = this.GetType().GetTypeInfo().Assembly;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<Project>
2+
<PropertyGroup>
3+
<TargetFramework>net6.0</TargetFramework>
4+
</PropertyGroup>
5+
</Project>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Amazon.Lambda.Core;
2+
3+
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
4+
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
5+
6+
namespace TestFunctionBuildProps;
7+
8+
public class Function
9+
{
10+
public string FunctionHandler(string input, ILambdaContext context)
11+
{
12+
return input.ToUpper();
13+
}
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<ImplicitUsings>enable</ImplicitUsings>
4+
<Nullable>enable</Nullable>
5+
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
6+
<AWSProjectType>Lambda</AWSProjectType>
7+
<!-- This property makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. -->
8+
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
9+
<!-- Generate ready to run images during publishing to improve cold start time. -->
10+
<PublishReadyToRun>true</PublishReadyToRun>
11+
</PropertyGroup>
12+
<ItemGroup>
13+
<PackageReference Include="Amazon.Lambda.Core" Version="2.4.0" />
14+
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.4" />
15+
</ItemGroup>
16+
</Project>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"Information": [
3+
"This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
4+
"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.",
5+
"dotnet lambda help",
6+
"All the command line options for the Lambda command can be specified in this file."
7+
],
8+
"profile": "default",
9+
"region": "us-west-2",
10+
"configuration": "Release",
11+
"function-architecture": "x86_64",
12+
"function-runtime": "dotnet8",
13+
"function-memory-size": 512,
14+
"function-timeout": 30,
15+
"function-handler": "TestFunctionBuildProps::TestFunctionBuildProps.Function::FunctionHandler"
16+
}

0 commit comments

Comments
 (0)