| | | 1 | | using System.Text.Json; |
| | | 2 | | |
| | | 3 | | namespace ZhEaIsNsAaBn.DbSeeder.Tool; |
| | | 4 | | |
| | | 5 | | /// <summary>A project's build facts, from <c>dotnet msbuild -getProperty</c> (evaluation only, no injected targets).</s |
| | 0 | 6 | | internal sealed record ProjectMetadata( string ProjectFile, string Name, string ProjectDirectory, string TargetFramework |
| | 0 | 7 | | string RuntimeConfigFile, IReadOnlyList<string> PackageFolders ) |
| | | 8 | | { |
| | | 9 | | /// <summary>The project file for <paramref name="path"/>: a .csproj, or the folder holding exactly one.</summary> |
| | | 10 | | /// <exception cref="ToolException">No project or several.</exception> |
| | | 11 | | public static string ResolveProjectFile( string? path, string currentDirectory ) |
| | | 12 | | { |
| | 0 | 13 | | var full = Path.GetFullPath( path ?? ".", currentDirectory ); |
| | | 14 | | |
| | 0 | 15 | | if ( File.Exists( full ) ) { return full; } |
| | | 16 | | |
| | 0 | 17 | | if ( !Directory.Exists( full ) ) { throw new ToolException( $"{full} doesn't exist." ); } |
| | | 18 | | |
| | 0 | 19 | | var projects = Directory.GetFiles( full, "*.csproj" ); |
| | | 20 | | |
| | 0 | 21 | | return projects.Length switch |
| | 0 | 22 | | { |
| | 0 | 23 | | 1 => projects[ 0 ], |
| | 0 | 24 | | 0 => throw new ToolException( $"No project in {full}. Use --project (and --startup-project)." ), |
| | 0 | 25 | | _ => throw new ToolException( $"More than one project in {full}: {string.Join( ", ", projects.Select( Path.G |
| | 0 | 26 | | }; |
| | | 27 | | } |
| | | 28 | | |
| | | 29 | | /// <summary>The target framework to use: <paramref name="framework"/>, else the project's only one.</summary> |
| | | 30 | | /// <exception cref="ToolException">The project has several and none was picked.</exception> |
| | | 31 | | public static async Task<string> TargetFrameworkAsync( string projectFile, string? framework, string configuration, |
| | | 32 | | { |
| | 0 | 33 | | if ( framework is not null ) { return framework; } |
| | | 34 | | |
| | 0 | 35 | | var properties = await PropertiesAsync( projectFile, null, configuration, process, "TargetFramework", "TargetFra |
| | | 36 | | |
| | 0 | 37 | | if ( properties[ "TargetFramework" ] is { Length: > 0 } single ) { return single; } |
| | | 38 | | |
| | 0 | 39 | | var frameworks = properties[ "TargetFrameworks" ].Split( ';', StringSplitOptions.RemoveEmptyEntries | StringSpli |
| | | 40 | | |
| | 0 | 41 | | return frameworks.Length == 1 |
| | 0 | 42 | | ? frameworks[ 0 ] |
| | 0 | 43 | | : throw new ToolException( $"{Path.GetFileNameWithoutExtension( projectFile )} targets {string.Join( |
| | 0 | 44 | | } |
| | | 45 | | |
| | | 46 | | /// <summary>The metadata after a build (NuGetPackageFolders comes from the restore).</summary> |
| | | 47 | | public static async Task<ProjectMetadata> ReadAsync( string projectFile, string framework, string configuration, Too |
| | | 48 | | { |
| | 0 | 49 | | var properties = await PropertiesAsync( projectFile, framework, configuration, process, "MSBuildProjectName", "P |
| | 0 | 50 | | "ProjectRuntimeConfigFilePath", "NuGetPackageFolders" ).ConfigureAwait( |
| | | 51 | | |
| | 0 | 52 | | return new ProjectMetadata( projectFile, properties[ "MSBuildProjectName" ], properties[ "ProjectDir" ], framewo |
| | 0 | 53 | | properties[ "ProjectDepsFilePath" ], properties[ "ProjectRuntimeConfigFilePath" ], |
| | 0 | 54 | | properties[ "NuGetPackageFolders" ].Split( ';', StringSplitOptions.RemoveEmptyEntrie |
| | 0 | 55 | | } |
| | | 56 | | |
| | | 57 | | |
| | | 58 | | private static async Task<Dictionary<string, string>> PropertiesAsync( string projectFile, string? framework, string |
| | | 59 | | params string[] names ) |
| | | 60 | | { |
| | 0 | 61 | | List<string> args = [ "msbuild", projectFile, "-nologo", $"-p:Configuration={configuration}", .. names.Select( n |
| | | 62 | | |
| | 0 | 63 | | if ( framework is not null ) { args.Add( $"-p:TargetFramework={framework}" ); } |
| | | 64 | | |
| | 0 | 65 | | var (code, output) = await process.CaptureAsync( args ).ConfigureAwait( false ); |
| | | 66 | | |
| | 0 | 67 | | if ( code != 0 ) { throw new ToolException( $"Reading {Path.GetFileName( projectFile )} failed:\n{output}" ); } |
| | | 68 | | |
| | | 69 | | try |
| | | 70 | | { |
| | 0 | 71 | | using var document = JsonDocument.Parse( output ); |
| | | 72 | | |
| | 0 | 73 | | return names.ToDictionary( n => n, n => document.RootElement.GetProperty( "Properties" ).GetProperty( n ).Ge |
| | | 74 | | } |
| | 0 | 75 | | catch ( JsonException ) |
| | | 76 | | { |
| | 0 | 77 | | throw new ToolException( $"Unexpected output from dotnet msbuild -getProperty (SDK 8 or later needed):\n{out |
| | | 78 | | } |
| | 0 | 79 | | } |
| | | 80 | | } |
| | | 81 | | |
| | | 82 | | /// <summary>A tool error: printed as <c>error: …</c>, exit code 2.</summary> |
| | | 83 | | internal sealed class ToolException : Exception |
| | | 84 | | { |
| | | 85 | | public ToolException( string message ) : base( message ) { } |
| | | 86 | | } |