diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 16cf04331..fcc5289fe 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -59,7 +59,6 @@ "CreateGitHubRelease", "DeletePackages", "DownloadLicenses", - "GenerateGlobalSolution", "GeneratePublicApi", "GenerateTools", "Hotfix", diff --git a/.nuke/parameters.json b/.nuke/parameters.json index 589f2e3fb..4ab672ff9 100644 --- a/.nuke/parameters.json +++ b/.nuke/parameters.json @@ -1,6 +1,6 @@ { "$schema": "build.schema.json", - "Solution": "nuke-common.sln", + "Solution": "nuke-common.slnx", "SignPathSettings": { "OrganizationId": "0fdaf334-6910-41f4-83d2-e58e4cccb087", "ProjectSlug": "nuke", diff --git a/CHANGELOG.md b/CHANGELOG.md index 827478247..f375877b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [vNext] +## [10.0.0] / 2025-11-20 +- Added support for .NET 10 +- Added support for slnx solution files +- Updated dependencies +- Removed automatic PowerShell/Pwsh argument positioning + ## [9.0.4] / 2025-01-15 - Security: Fixed output filter from `ArgumentStringHandler` - Removed obsolete members @@ -1216,7 +1222,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added CLT tasks for Git - Fixed background color in console output -[vNext]: https://github.com/nuke-build/nuke/compare/9.0.4...HEAD +[vNext]: https://github.com/nuke-build/nuke/compare/10.0.0...HEAD +[10.0.0]: https://github.com/nuke-build/nuke/compare/9.0.4...10.0.0 [9.0.4]: https://github.com/nuke-build/nuke/compare/9.0.3...9.0.4 [9.0.3]: https://github.com/nuke-build/nuke/compare/9.0.2...9.0.3 [9.0.2]: https://github.com/nuke-build/nuke/compare/9.0.1...9.0.2 diff --git a/Directory.Packages.props b/Directory.Packages.props index 9eae936ab..da745fd64 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,78 +1,81 @@ - true - - - - - + + + + - - - - + + + + - - - + + + - - + + - - + + - + - - + - - - + + + - - + + - - - - - + + + + + + + + + - - - - - - + + + + + + + + + + - - - - - + + + + - - + \ No newline at end of file diff --git a/README.md b/README.md index 78f7996df..5b96888c9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +> [!IMPORTANT] +> This repository will be temporarily archived until **June 9**. + diff --git a/appveyor.yml b/appveyor.yml index 19d5c7037..9ed68d107 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -35,7 +35,7 @@ artifacts: environment: PublicNuGetApiKey: - secure: AzhHrKZGYyWnvMtPg06Q7PMJPp47dl5NxAHaE9ZB9tjIWVqmySx3F26YtVhRSPGa + secure: Fh8WVVqYaEyQutEwTfVrUTEzc287b4utnOzc77wYmOh3kyZ9wyojoncGxytOvt6M GitHubReleaseGitHubToken: secure: a5UfxXiDEere9GkCCN9TUUq2+8QHAJoeVpZudQZXdWyloZWE5xKOkqzpxdMoYDPSxrVbWxjXbk1Xe9p0OydwuGVnr/3DC//BguNeGtFddbyMWlAiX36XvD1ZGEgP+ZIN SignPathApiToken: diff --git a/build/Build.Announce.cs b/build/Build.Announce.cs index 42e93d355..56e83ff88 100644 --- a/build/Build.Announce.cs +++ b/build/Build.Announce.cs @@ -28,7 +28,7 @@ partial class Build Target Announce => _ => _ .DependsOn(ReleaseImage) .WhenSkipped(DependencyBehavior.Skip) - .TriggeredBy() + // .TriggeredBy() .OnlyWhenStatic(() => GitRepository.IsOnMasterBranch()); IEnumerable ChangelogSectionNotes => ChangelogTasks.ExtractChangelogSectionNotes(From().ChangelogFile); diff --git a/build/Build.CI.AppVeyor.cs b/build/Build.CI.AppVeyor.cs index 78b1635ee..0900d9bb7 100644 --- a/build/Build.CI.AppVeyor.cs +++ b/build/Build.CI.AppVeyor.cs @@ -34,7 +34,7 @@ SkipTags = true, InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) }, Secrets = new string[0])] -[AppVeyorSecret(nameof(PublicNuGetApiKey), "AzhHrKZGYyWnvMtPg06Q7PMJPp47dl5NxAHaE9ZB9tjIWVqmySx3F26YtVhRSPGa")] +[AppVeyorSecret(nameof(PublicNuGetApiKey), "Fh8WVVqYaEyQutEwTfVrUTEzc287b4utnOzc77wYmOh3kyZ9wyojoncGxytOvt6M")] [AppVeyorSecret(ICreateGitHubRelease.GitHubRelease + nameof(ICreateGitHubRelease.GitHubToken), "a5UfxXiDEere9GkCCN9TUUq2+8QHAJoeVpZudQZXdWyloZWE5xKOkqzpxdMoYDPSxrVbWxjXbk1Xe9p0OydwuGVnr/3DC//BguNeGtFddbyMWlAiX36XvD1ZGEgP+ZIN")] [AppVeyorSecret(ISignPackages.SignPath + nameof(ISignPackages.ApiToken), "uQTH2MxpqiqWTy7EJkjtNc43ipG17EUOQN99QsODRNgtNEcikDaP0t4ylekK/ibn")] [AppVeyorSecret(IHazTwitterCredentials.Twitter + nameof(IHazTwitterCredentials.ConsumerKey), "T61zL4r+xtyj7b0aOGYCsyixrXHooXE759T8z3M67Lw=")] diff --git a/build/Build.GlobalSolution.cs b/build/Build.GlobalSolution.cs index 4e2107570..40b356741 100644 --- a/build/Build.GlobalSolution.cs +++ b/build/Build.GlobalSolution.cs @@ -15,7 +15,6 @@ using Nuke.Common.Utilities; using Nuke.Utilities.Text.Yaml; using static Nuke.Common.ControlFlow; -using static Nuke.Common.ProjectModel.SolutionModelTasks; using static Nuke.Common.Tools.Git.GitTasks; partial class Build @@ -52,30 +51,4 @@ IEnumerable ExternalRepositories } } }); - - [UsedImplicitly] - Target GenerateGlobalSolution => _ => _ - .DependsOn(CheckoutExternalRepositories) - .Executes(() => - { - var global = CreateSolution( - solutionFile: GlobalSolution, - solutions: new[] { Solution }.Concat(ExternalSolutions), - folderNameProvider: x => x.Name.TrimStart("nuke-")); - global.Save(); - - if ((RootDirectory / $"{Solution.FileName}.DotSettings").FileExists()) - { - (RootDirectory / $"{Solution.FileName}.DotSettings").Copy( - target: RootDirectory / $"{global.FileName}.DotSettings", - policy: ExistsPolicy.FileOverwrite); - } - - if ((RootDirectory / $"{Solution.FileName}.DotSettings.user").FileExists()) - { - (RootDirectory / $"{Solution.FileName}.DotSettings.user").Copy( - target: RootDirectory / $"{global.FileName}.DotSettings.user", - policy: ExistsPolicy.FileOverwrite); - } - }); } diff --git a/build/_build.csproj b/build/_build.csproj index 1ddba534a..d607caf41 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -4,7 +4,7 @@ Exe - net8.0 + net10.0 preview CS0649;CS0169 @@ -36,7 +36,7 @@ - + @@ -55,7 +55,7 @@ - + diff --git a/global.json b/global.json index 2bc13e80a..1e7fdfa95 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.100", + "version": "10.0.100", "rollForward": "latestMinor" } } diff --git a/nuke-common.sln b/nuke-common.sln deleted file mode 100644 index 2511b83d3..000000000 --- a/nuke-common.sln +++ /dev/null @@ -1,184 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2027 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{BB6A9024-24DB-4170-A09B-02349148A78F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nuke.Common", "source\Nuke.Common\Nuke.Common.csproj", "{B6D4E654-6822-42FF-86AD-DB07BE759760}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nuke.Common.Tests", "source\Nuke.Common.Tests\Nuke.Common.Tests.csproj", "{C4CEFCC5-1387-4340-B1BE-EC421511F734}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{B43B0E45-8A89-4516-BAD3-2DA4C6660D3A}" - ProjectSection(SolutionItems) = preProject - CHANGELOG.md = CHANGELOG.md - LICENSE = LICENSE - README.md = README.md - CONTRIBUTING.md = CONTRIBUTING.md - CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nuke.Tooling.Generator", "source\Nuke.Tooling.Generator\Nuke.Tooling.Generator.csproj", "{34FBD413-AD5D-493E-BD61-ED8497D314A0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.GlobalTool", "source\Nuke.GlobalTool\Nuke.GlobalTool.csproj", "{140A4F0D-C21D-4212-BB75-EFDBDBBD5E1B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.GlobalTool.Tests", "source\Nuke.GlobalTool.Tests\Nuke.GlobalTool.Tests.csproj", "{BD416DD4-06F4-4246-B92C-B261B026B6E9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.MSBuildTasks", "source\Nuke.MSBuildTasks\Nuke.MSBuildTasks.csproj", "{EFF12255-4E26-4C8A-89DA-51CC426C1AA2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Components", "source\Nuke.Components\Nuke.Components.csproj", "{E255E1E6-2D8D-47AC-899F-CD6EEFE8CAB5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.SourceGenerators", "source\Nuke.SourceGenerators\Nuke.SourceGenerators.csproj", "{6FA754E7-04F4-4C5E-BA50-0D87028B0963}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.SourceGenerators.Tests", "source\Nuke.SourceGenerators.Tests\Nuke.SourceGenerators.Tests.csproj", "{82D47ABA-67B8-423D-97A7-979858FAAD43}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Utilities", "source\Nuke.Utilities\Nuke.Utilities.csproj", "{2AC92E4F-F934-407A-A947-FA2C5FFF8CA7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Utilities.Tests", "source\Nuke.Utilities.Tests\Nuke.Utilities.Tests.csproj", "{9B885587-0570-4FB7-9E1B-B315ED0E3194}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Utilities.IO.Globbing", "source\Nuke.Utilities.IO.Globbing\Nuke.Utilities.IO.Globbing.csproj", "{A7D20720-F7B6-48F0-A9B9-D0DBA7AAA676}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Utilities.Text.Json", "source\Nuke.Utilities.Text.Json\Nuke.Utilities.Text.Json.csproj", "{72B65E27-484A-4DC5-887B-70DE865021FF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Utilities.Net", "source\Nuke.Utilities.Net\Nuke.Utilities.Net.csproj", "{B9F75B3D-7F0F-43F9-A8AA-5D26308AF73F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Tooling", "source\Nuke.Tooling\Nuke.Tooling.csproj", "{462C4F51-D1B7-4901-9AFE-B2F1F77C5E6C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Tooling.Tests", "source\Nuke.Tooling.Tests\Nuke.Tooling.Tests.csproj", "{3EFFC43F-2CE3-4A39-8475-4E7E8096EDC7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.SolutionModel", "source\Nuke.SolutionModel\Nuke.SolutionModel.csproj", "{359EF6A3-154D-40E4-A859-980D8FCFFC4B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.SolutionModel.Tests", "source\Nuke.SolutionModel.Tests\Nuke.SolutionModel.Tests.csproj", "{E2277FA4-7C0E-444A-AE6E-E8E16AF19B22}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.ProjectModel", "source\Nuke.ProjectModel\Nuke.ProjectModel.csproj", "{B177AE84-FA77-4DAA-8466-5781501A48E1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.ProjectModel.Tests", "source\Nuke.ProjectModel.Tests\Nuke.ProjectModel.Tests.csproj", "{813B4CA1-F61D-4D32-A980-BA82CAB1433A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Build.Shared", "source\Nuke.Build.Shared\Nuke.Build.Shared.csproj", "{BABFFAD2-8049-4E22-B3EA-28D3AE2E3F78}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Build", "source\Nuke.Build\Nuke.Build.csproj", "{818AD27B-86AA-4FC4-B255-DDD26A2F577E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Build.Tests", "source\Nuke.Build.Tests\Nuke.Build.Tests.csproj", "{8E54ECBA-F77D-4243-9506-0FF2B400E11C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Utilities.Text.Yaml", "source\Nuke.Utilities.Text.Yaml\Nuke.Utilities.Text.Yaml.csproj", "{8D985032-FC74-4E7D-93F7-55BCF74755F9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuke.Utilities.IO.Compression", "source\Nuke.Utilities.IO.Compression\Nuke.Utilities.IO.Compression.csproj", "{3E330C45-DB19-4EAD-9C5D-B9D24F0254E8}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BB6A9024-24DB-4170-A09B-02349148A78F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BB6A9024-24DB-4170-A09B-02349148A78F}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {B6D4E654-6822-42FF-86AD-DB07BE759760}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6D4E654-6822-42FF-86AD-DB07BE759760}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6D4E654-6822-42FF-86AD-DB07BE759760}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6D4E654-6822-42FF-86AD-DB07BE759760}.Release|Any CPU.Build.0 = Release|Any CPU - {C4CEFCC5-1387-4340-B1BE-EC421511F734}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4CEFCC5-1387-4340-B1BE-EC421511F734}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4CEFCC5-1387-4340-B1BE-EC421511F734}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4CEFCC5-1387-4340-B1BE-EC421511F734}.Release|Any CPU.Build.0 = Release|Any CPU - {34FBD413-AD5D-493E-BD61-ED8497D314A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {34FBD413-AD5D-493E-BD61-ED8497D314A0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {34FBD413-AD5D-493E-BD61-ED8497D314A0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {34FBD413-AD5D-493E-BD61-ED8497D314A0}.Release|Any CPU.Build.0 = Release|Any CPU - {140A4F0D-C21D-4212-BB75-EFDBDBBD5E1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {140A4F0D-C21D-4212-BB75-EFDBDBBD5E1B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {140A4F0D-C21D-4212-BB75-EFDBDBBD5E1B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {140A4F0D-C21D-4212-BB75-EFDBDBBD5E1B}.Release|Any CPU.Build.0 = Release|Any CPU - {BD416DD4-06F4-4246-B92C-B261B026B6E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BD416DD4-06F4-4246-B92C-B261B026B6E9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BD416DD4-06F4-4246-B92C-B261B026B6E9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BD416DD4-06F4-4246-B92C-B261B026B6E9}.Release|Any CPU.Build.0 = Release|Any CPU - {EFF12255-4E26-4C8A-89DA-51CC426C1AA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EFF12255-4E26-4C8A-89DA-51CC426C1AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EFF12255-4E26-4C8A-89DA-51CC426C1AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EFF12255-4E26-4C8A-89DA-51CC426C1AA2}.Release|Any CPU.Build.0 = Release|Any CPU - {E255E1E6-2D8D-47AC-899F-CD6EEFE8CAB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E255E1E6-2D8D-47AC-899F-CD6EEFE8CAB5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E255E1E6-2D8D-47AC-899F-CD6EEFE8CAB5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E255E1E6-2D8D-47AC-899F-CD6EEFE8CAB5}.Release|Any CPU.Build.0 = Release|Any CPU - {6FA754E7-04F4-4C5E-BA50-0D87028B0963}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6FA754E7-04F4-4C5E-BA50-0D87028B0963}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6FA754E7-04F4-4C5E-BA50-0D87028B0963}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6FA754E7-04F4-4C5E-BA50-0D87028B0963}.Release|Any CPU.Build.0 = Release|Any CPU - {82D47ABA-67B8-423D-97A7-979858FAAD43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {82D47ABA-67B8-423D-97A7-979858FAAD43}.Debug|Any CPU.Build.0 = Debug|Any CPU - {82D47ABA-67B8-423D-97A7-979858FAAD43}.Release|Any CPU.ActiveCfg = Release|Any CPU - {82D47ABA-67B8-423D-97A7-979858FAAD43}.Release|Any CPU.Build.0 = Release|Any CPU - {2AC92E4F-F934-407A-A947-FA2C5FFF8CA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2AC92E4F-F934-407A-A947-FA2C5FFF8CA7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2AC92E4F-F934-407A-A947-FA2C5FFF8CA7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2AC92E4F-F934-407A-A947-FA2C5FFF8CA7}.Release|Any CPU.Build.0 = Release|Any CPU - {9B885587-0570-4FB7-9E1B-B315ED0E3194}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9B885587-0570-4FB7-9E1B-B315ED0E3194}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9B885587-0570-4FB7-9E1B-B315ED0E3194}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9B885587-0570-4FB7-9E1B-B315ED0E3194}.Release|Any CPU.Build.0 = Release|Any CPU - {A7D20720-F7B6-48F0-A9B9-D0DBA7AAA676}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A7D20720-F7B6-48F0-A9B9-D0DBA7AAA676}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A7D20720-F7B6-48F0-A9B9-D0DBA7AAA676}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A7D20720-F7B6-48F0-A9B9-D0DBA7AAA676}.Release|Any CPU.Build.0 = Release|Any CPU - {72B65E27-484A-4DC5-887B-70DE865021FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {72B65E27-484A-4DC5-887B-70DE865021FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {72B65E27-484A-4DC5-887B-70DE865021FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {72B65E27-484A-4DC5-887B-70DE865021FF}.Release|Any CPU.Build.0 = Release|Any CPU - {B9F75B3D-7F0F-43F9-A8AA-5D26308AF73F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B9F75B3D-7F0F-43F9-A8AA-5D26308AF73F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9F75B3D-7F0F-43F9-A8AA-5D26308AF73F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B9F75B3D-7F0F-43F9-A8AA-5D26308AF73F}.Release|Any CPU.Build.0 = Release|Any CPU - {462C4F51-D1B7-4901-9AFE-B2F1F77C5E6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {462C4F51-D1B7-4901-9AFE-B2F1F77C5E6C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {462C4F51-D1B7-4901-9AFE-B2F1F77C5E6C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {462C4F51-D1B7-4901-9AFE-B2F1F77C5E6C}.Release|Any CPU.Build.0 = Release|Any CPU - {3EFFC43F-2CE3-4A39-8475-4E7E8096EDC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3EFFC43F-2CE3-4A39-8475-4E7E8096EDC7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3EFFC43F-2CE3-4A39-8475-4E7E8096EDC7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3EFFC43F-2CE3-4A39-8475-4E7E8096EDC7}.Release|Any CPU.Build.0 = Release|Any CPU - {359EF6A3-154D-40E4-A859-980D8FCFFC4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {359EF6A3-154D-40E4-A859-980D8FCFFC4B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {359EF6A3-154D-40E4-A859-980D8FCFFC4B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {359EF6A3-154D-40E4-A859-980D8FCFFC4B}.Release|Any CPU.Build.0 = Release|Any CPU - {E2277FA4-7C0E-444A-AE6E-E8E16AF19B22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E2277FA4-7C0E-444A-AE6E-E8E16AF19B22}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E2277FA4-7C0E-444A-AE6E-E8E16AF19B22}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E2277FA4-7C0E-444A-AE6E-E8E16AF19B22}.Release|Any CPU.Build.0 = Release|Any CPU - {B177AE84-FA77-4DAA-8466-5781501A48E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B177AE84-FA77-4DAA-8466-5781501A48E1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B177AE84-FA77-4DAA-8466-5781501A48E1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B177AE84-FA77-4DAA-8466-5781501A48E1}.Release|Any CPU.Build.0 = Release|Any CPU - {813B4CA1-F61D-4D32-A980-BA82CAB1433A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {813B4CA1-F61D-4D32-A980-BA82CAB1433A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {813B4CA1-F61D-4D32-A980-BA82CAB1433A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {813B4CA1-F61D-4D32-A980-BA82CAB1433A}.Release|Any CPU.Build.0 = Release|Any CPU - {BABFFAD2-8049-4E22-B3EA-28D3AE2E3F78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BABFFAD2-8049-4E22-B3EA-28D3AE2E3F78}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BABFFAD2-8049-4E22-B3EA-28D3AE2E3F78}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BABFFAD2-8049-4E22-B3EA-28D3AE2E3F78}.Release|Any CPU.Build.0 = Release|Any CPU - {818AD27B-86AA-4FC4-B255-DDD26A2F577E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {818AD27B-86AA-4FC4-B255-DDD26A2F577E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {818AD27B-86AA-4FC4-B255-DDD26A2F577E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {818AD27B-86AA-4FC4-B255-DDD26A2F577E}.Release|Any CPU.Build.0 = Release|Any CPU - {8E54ECBA-F77D-4243-9506-0FF2B400E11C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8E54ECBA-F77D-4243-9506-0FF2B400E11C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E54ECBA-F77D-4243-9506-0FF2B400E11C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8E54ECBA-F77D-4243-9506-0FF2B400E11C}.Release|Any CPU.Build.0 = Release|Any CPU - {8D985032-FC74-4E7D-93F7-55BCF74755F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D985032-FC74-4E7D-93F7-55BCF74755F9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D985032-FC74-4E7D-93F7-55BCF74755F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D985032-FC74-4E7D-93F7-55BCF74755F9}.Release|Any CPU.Build.0 = Release|Any CPU - {3E330C45-DB19-4EAD-9C5D-B9D24F0254E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3E330C45-DB19-4EAD-9C5D-B9D24F0254E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3E330C45-DB19-4EAD-9C5D-B9D24F0254E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3E330C45-DB19-4EAD-9C5D-B9D24F0254E8}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {A2C37D4D-74E1-443B-999B-505B8C6694C9} - EndGlobalSection -EndGlobal diff --git a/nuke-common.slnx b/nuke-common.slnx new file mode 100644 index 000000000..e32b27bd2 --- /dev/null +++ b/nuke-common.slnx @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/Nuke.Build.Shared/Nuke.Build.Shared.csproj b/source/Nuke.Build.Shared/Nuke.Build.Shared.csproj index 3454d4132..0e0778b0b 100644 --- a/source/Nuke.Build.Shared/Nuke.Build.Shared.csproj +++ b/source/Nuke.Build.Shared/Nuke.Build.Shared.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net8.0 + netstandard2.0;net10.0 diff --git a/source/Nuke.Build.Tests/Nuke.Build.Tests.csproj b/source/Nuke.Build.Tests/Nuke.Build.Tests.csproj index 2fe69e68c..c7d4b6a66 100644 --- a/source/Nuke.Build.Tests/Nuke.Build.Tests.csproj +++ b/source/Nuke.Build.Tests/Nuke.Build.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 diff --git a/source/Nuke.Build/Execution/ToolRequirementService.cs b/source/Nuke.Build/Execution/ToolRequirementService.cs index 7213bfa8a..1aff44ab6 100644 --- a/source/Nuke.Build/Execution/ToolRequirementService.cs +++ b/source/Nuke.Build/Execution/ToolRequirementService.cs @@ -41,7 +41,7 @@ private static void InstallNuGetPackages(IReadOnlyCollection - net8.0 + net10.0 diff --git a/source/Nuke.Build/Nuke.Build.csproj b/source/Nuke.Build/Nuke.Build.csproj index b487ee4ae..501d77fe1 100644 --- a/source/Nuke.Build/Nuke.Build.csproj +++ b/source/Nuke.Build/Nuke.Build.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 diff --git a/source/Nuke.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=detailed-triggers_attribute=GitHubActionsAttribute.verified.txt b/source/Nuke.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=detailed-triggers_attribute=GitHubActionsAttribute.verified.txt index 697dd6e7c..def839a18 100644 --- a/source/Nuke.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=detailed-triggers_attribute=GitHubActionsAttribute.verified.txt +++ b/source/Nuke.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=detailed-triggers_attribute=GitHubActionsAttribute.verified.txt @@ -74,6 +74,12 @@ jobs: .nuke/temp ~/.nuget/packages key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }} + - uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 8.0 + 9.0 + 10.0 - name: 'Run: Test' run: ./build.cmd Test env: @@ -122,6 +128,12 @@ jobs: .nuke/temp ~/.nuget/packages key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }} + - uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 8.0 + 9.0 + 10.0 - name: 'Run: Test' run: ./build.cmd Test env: @@ -170,6 +182,12 @@ jobs: .nuke/temp ~/.nuget/packages key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }} + - uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 8.0 + 9.0 + 10.0 - name: 'Run: Test' run: ./build.cmd Test env: diff --git a/source/Nuke.Common.Tests/CI/ConfigurationGenerationTest.cs b/source/Nuke.Common.Tests/CI/ConfigurationGenerationTest.cs index c8f5c3146..bd6c283f8 100644 --- a/source/Nuke.Common.Tests/CI/ConfigurationGenerationTest.cs +++ b/source/Nuke.Common.Tests/CI/ConfigurationGenerationTest.cs @@ -165,7 +165,8 @@ public class TestBuild : NukeBuild JobConcurrencyCancelInProgress = true, JobConcurrencyGroup = "custom-job-group", EnvironmentName = "environment-name", - EnvironmentUrl = "environment-url" + EnvironmentUrl = "environment-url", + SetupDotNetVersions = new [] { "8.0", "9.0", "10.0" } } ); diff --git a/source/Nuke.Common.Tests/Nuke.Common.Tests.csproj b/source/Nuke.Common.Tests/Nuke.Common.Tests.csproj index bc97d71d7..485c89f03 100644 --- a/source/Nuke.Common.Tests/Nuke.Common.Tests.csproj +++ b/source/Nuke.Common.Tests/Nuke.Common.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 diff --git a/source/Nuke.Common/Attributes/SolutionAttribute.cs b/source/Nuke.Common/Attributes/SolutionAttribute.cs index e6cb4e80f..2d0bacd4d 100644 --- a/source/Nuke.Common/Attributes/SolutionAttribute.cs +++ b/source/Nuke.Common/Attributes/SolutionAttribute.cs @@ -28,60 +28,39 @@ namespace Nuke.Common.ProjectModel; /// [PublicAPI] [UsedImplicitly(ImplicitUseKindFlags.Assign)] -public class SolutionAttribute : ParameterAttribute +public class SolutionAttribute(string relativePath) + : ParameterAttribute(GetDescription(relativePath)) { - private readonly string _relativePath; - - public SolutionAttribute() - : this(relativePath: null) + private static string GetDescription(string relativePath) { + return "Path to a solution file that is automatically loaded." + + (relativePath != null ? $" Default is {relativePath}." : string.Empty); } - public SolutionAttribute(string relativePath) - : base("Path to a solution file that is automatically loaded." - + (relativePath != null ? $" Default is {relativePath}." : string.Empty)) + public SolutionAttribute() + : this(relativePath: null) { - _relativePath = relativePath; } public override bool List { get; set; } public bool GenerateProjects { get; set; } - public bool SuppressBuildProjectCheck { get; set; } public override object GetValue(MemberInfo member, object instance) { var solutionFile = TryGetSolutionFileFromNukeFile() ?? GetSolutionFileFromParametersFile(member); - var deserializer = typeof(SolutionSerializer).GetMethod(nameof(SolutionSerializer.DeserializeFromFile)).NotNull() + var deserializer = typeof(SolutionModelExtensions).GetMethods() + .Single(x => x.Name == nameof(SolutionModelExtensions.ReadSolution) && x.ContainsGenericParameters) .MakeGenericMethod(member.GetMemberType()); - var solution = ((Solution)deserializer.Invoke(obj: null, new object[] { solutionFile })).NotNull(); - - if (!SuppressBuildProjectCheck) - { - var buildProject = solution.AllProjects.SingleOrDefault(x => x.Directory.Equals(Build.BuildProjectDirectory)); - var buildProjectConfigurations = buildProject?.Configurations.Where(x => x.Key.Contains("Build")).ToList(); - - if (buildProject != null && buildProjectConfigurations.Any()) - { - Log.Warning( - "Solution {Solution} has active build configurations for the build project.\n" + - $"Either enable {nameof(SuppressBuildProjectCheck)} on {{Member}} or remove the following entries from the solution file:\n" + - "{Entries}", - solution, - member.GetDisplayName(), - buildProjectConfigurations.Select(x => $" - {buildProject.ProjectId.ToString("B").ToUpper()}.{x.Key} = {x.Value}").JoinNewLine()); - } - } - - return solution; + return ((Solution)deserializer.Invoke(obj: null, [solutionFile])).NotNull(); } // TODO: allow wildcard matching? [Solution("nuke-*.sln")] -- no globbing? // TODO: for just [Solution] without parameter being passed, do wildcard search? private AbsolutePath GetSolutionFileFromParametersFile(MemberInfo member) { - return _relativePath != null - ? Build.RootDirectory / _relativePath + return relativePath != null + ? Build.RootDirectory / relativePath : ParameterService.GetParameter(member).NotNull($"No solution file defined for '{member.Name}'."); } diff --git a/source/Nuke.Common/CI/GitHubActions/Configuration/GitHubActionsSetupDotNetStep.cs b/source/Nuke.Common/CI/GitHubActions/Configuration/GitHubActionsSetupDotNetStep.cs new file mode 100644 index 000000000..68840e879 --- /dev/null +++ b/source/Nuke.Common/CI/GitHubActions/Configuration/GitHubActionsSetupDotNetStep.cs @@ -0,0 +1,36 @@ +// Copyright 2025 Maintainers of NUKE. +// Distributed under the MIT License. +// https://github.com/nuke-build/nuke/blob/master/LICENSE + +using JetBrains.Annotations; +using Nuke.Common.Utilities; + +namespace Nuke.Common.CI.GitHubActions.Configuration; + +// https://github.com/actions/setup-dotnet +[PublicAPI] +public class GitHubActionsSetupDotNetStep : GitHubActionsStep +{ + public string[] Versions { get; set; } + + public override void Write(CustomFileWriter writer) + { + writer.WriteLine("- uses: actions/setup-dotnet@v5"); + + using (writer.Indent()) + { + writer.WriteLine("with:"); + using (writer.Indent()) + { + writer.WriteLine("dotnet-version: |"); + using (writer.Indent()) + { + foreach (var version in Versions) + { + writer.WriteLine(version); + } + } + } + } + } +} diff --git a/source/Nuke.Common/CI/GitHubActions/GitHubActionsAttribute.cs b/source/Nuke.Common/CI/GitHubActions/GitHubActionsAttribute.cs index 2c1f1599e..7169bdc49 100644 --- a/source/Nuke.Common/CI/GitHubActions/GitHubActionsAttribute.cs +++ b/source/Nuke.Common/CI/GitHubActions/GitHubActionsAttribute.cs @@ -86,6 +86,8 @@ public GitHubActionsAttribute( public string JobConcurrencyGroup { get; set; } public bool JobConcurrencyCancelInProgress { get; set; } + public string[] SetupDotNetVersions { get; set; } = new string[0]; + public string[] InvokedTargets { get; set; } = new string[0]; public GitHubActionsSubmodules Submodules @@ -181,6 +183,14 @@ private IEnumerable GetSteps(GitHubActionsImage image, IReadO }; } + if (SetupDotNetVersions.Any()) + { + yield return new GitHubActionsSetupDotNetStep + { + Versions = SetupDotNetVersions, + }; + } + yield return new GitHubActionsRunStep { BuildCmdPath = BuildCmdPath, diff --git a/source/Nuke.Common/Nuke.Common.csproj b/source/Nuke.Common/Nuke.Common.csproj index ffb393270..1f76afd97 100644 --- a/source/Nuke.Common/Nuke.Common.csproj +++ b/source/Nuke.Common/Nuke.Common.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 diff --git a/source/Nuke.Common/Tools/PowerShell/PowerShell.Generated.cs b/source/Nuke.Common/Tools/PowerShell/PowerShell.Generated.cs index dc11bf14d..e89334ee6 100644 --- a/source/Nuke.Common/Tools/PowerShell/PowerShell.Generated.cs +++ b/source/Nuke.Common/Tools/PowerShell/PowerShell.Generated.cs @@ -69,9 +69,9 @@ public partial class PowerShellSettings : ToolOptions /// Sets the default execution policy for the current session and saves it in the $env:PSExecutionPolicyPreference environment variable. This parameter does not change the PowerShell execution policy that is set in the registry. For information about PowerShell execution policies, including a list of valid values, see about_Execution_Policies. [Argument(Format = "-ExecutionPolicy {value}")] public string ExecutionPolicy => Get(() => ExecutionPolicy); /// If the value of File is -, the command text is read from standard input. Running powershell -File - without redirected standard input starts a regular session. This is the same as not specifying the File parameter at all.If the value of File is a file path, the script runs in the local scope (dot-sourced), so that the functions and variables that the script creates are available in the current session. Enter the script file path and any parameters. File must be the last parameter in the command. All values typed after the File parameter are interpreted as the script file path and parameters passed to that script. Parameters passed to the script are passed as literal strings, after interpretation by the current shell. For example, if you are in cmd.exe and want to pass an environment variable value, you would use the cmd.exe syntax: powershell.exe -File .\test.ps1 -TestParam %windir%.In contrast, running powershell.exe -File .\test.ps1 -TestParam $env:windir in cmd.exe results in the script receiving the literal string $env:windir because it has no special meaning to the current cmd.exe shell. The $env:windir style of environment variable reference can be used inside a Command parameter, since there it will be interpreted as PowerShell code.Similarly, if you want to execute the same command from a Batch script, you would use %~dp0 instead of .\ or $PSScriptRoot to represent the current execution directory: powershell.exe -File %~dp0test.ps1 -TestParam %windir%. If you instead used .\test.ps1, PowerShell would throw an error because it cannot find the literal path .\test.ps1.When the value of File is a file path, File must be the last parameter in the command because any characters typed after the File parameter name are interpreted as the script file path followed by the script parameters.You can include the script parameters and values in the value of the File parameter. For example: -File .\Get-Script.ps1 -Domain Central.Typically, the switch parameters of a script are either included or omitted. For example, the following command uses the All parameter of the Get-Script.ps1 script file: -File .\Get-Script.ps1 -All.In rare cases, you might need to provide a Boolean value for a parameter. It is not possible to pass an explicit boolean value for a switch parameter when running a script in this way. This limitation was removed in PowerShell 6 (pwsh.exe). - [Argument(Format = "-File {value}", Position = 1)] public string File => Get(() => File); + [Argument(Format = "-File {value}")] public string File => Get(() => File); /// Arguments passed in when using the -File option. - [Argument(Format = "{value}", Position = 2)] public IReadOnlyList FileArguments => Get>(() => FileArguments); + [Argument(Format = "{value}")] public IReadOnlyList FileArguments => Get>(() => FileArguments); /// Key-value pairs passed in when using the -File option. [Argument(Format = "-{key} {value}", Secret = false)] public IReadOnlyDictionary FileKeyValueParameters => Get>(() => FileKeyValueParameters); /// Executes the specified commands (and any parameters) as though they were typed at the PowerShell command prompt, and then exits, unless the NoExit parameter is specified.The value of Command can be -, a script block, or a string. If the value of Command is -, the command text is read from standard input.The Command parameter only accepts a script block for execution when it can recognize the value passed to Command as a ScriptBlock type. This is only possible when running powershell.exe from another PowerShell host. The ScriptBlock type may be contained in an existing variable, returned from an expression, or parsed by the PowerShell host as a literal script block enclosed in curly braces ({}), before being passed to powershell.exe. diff --git a/source/Nuke.Common/Tools/PowerShell/PowerShell.json b/source/Nuke.Common/Tools/PowerShell/PowerShell.json index a05a11b6b..a2c76708f 100644 --- a/source/Nuke.Common/Tools/PowerShell/PowerShell.json +++ b/source/Nuke.Common/Tools/PowerShell/PowerShell.json @@ -99,14 +99,12 @@ "name": "File", "type": "string", "format": "-File {value}", - "position": 1, "help": "If the value of File is -, the command text is read from standard input. Running powershell -File - without redirected standard input starts a regular session. This is the same as not specifying the File parameter at all.If the value of File is a file path, the script runs in the local scope (dot-sourced), so that the functions and variables that the script creates are available in the current session. Enter the script file path and any parameters. File must be the last parameter in the command. All values typed after the File parameter are interpreted as the script file path and parameters passed to that script. Parameters passed to the script are passed as literal strings, after interpretation by the current shell. For example, if you are in cmd.exe and want to pass an environment variable value, you would use the cmd.exe syntax: powershell.exe -File .\\test.ps1 -TestParam %windir%.In contrast, running powershell.exe -File .\\test.ps1 -TestParam $env:windir in cmd.exe results in the script receiving the literal string $env:windir because it has no special meaning to the current cmd.exe shell. The $env:windir style of environment variable reference can be used inside a Command parameter, since there it will be interpreted as PowerShell code.Similarly, if you want to execute the same command from a Batch script, you would use %~dp0 instead of .\\ or $PSScriptRoot to represent the current execution directory: powershell.exe -File %~dp0test.ps1 -TestParam %windir%. If you instead used .\\test.ps1, PowerShell would throw an error because it cannot find the literal path .\\test.ps1.When the value of File is a file path, File must be the last parameter in the command because any characters typed after the File parameter name are interpreted as the script file path followed by the script parameters.You can include the script parameters and values in the value of the File parameter. For example: -File .\\Get-Script.ps1 -Domain Central.Typically, the switch parameters of a script are either included or omitted. For example, the following command uses the All parameter of the Get-Script.ps1 script file: -File .\\Get-Script.ps1 -All.In rare cases, you might need to provide a Boolean value for a parameter. It is not possible to pass an explicit boolean value for a switch parameter when running a script in this way. This limitation was removed in PowerShell 6 (pwsh.exe)." }, { "name": "FileArguments", "type": "List", "format": "{value}", - "position": 2, "help": "Arguments passed in when using the -File option." }, { diff --git a/source/Nuke.Common/Tools/Pwsh/Pwsh.Generated.cs b/source/Nuke.Common/Tools/Pwsh/Pwsh.Generated.cs index 4f279b2a6..a03dbe6eb 100644 --- a/source/Nuke.Common/Tools/Pwsh/Pwsh.Generated.cs +++ b/source/Nuke.Common/Tools/Pwsh/Pwsh.Generated.cs @@ -73,9 +73,9 @@ public partial class PwshSettings : ToolOptions /// Sets the default execution policy for the current session and saves it in the $env:PSExecutionPolicyPreference environment variable. This parameter does not change the PowerShell execution policy that is set in the registry. This parameter only applies to Windows computers. The $env:PSExecutionPolicyPreference environment variable does not exist on non-Windows platforms. [Argument(Format = "-ExecutionPolicy {value}")] public PwshExecutionPolicy ExecutionPolicy => Get(() => ExecutionPolicy); /// If the value of File is -, the command text is read from standard input. Running pwsh -File - without redirected standard input starts a regular session. This is the same as not specifying the File parameter at all.This is the default parameter if no parameters are present but values are present in the command line. The specified script runs in the local scope ("dot-sourced"), so that the functions and variables that the script creates are available in the current session. Enter the script file path and any parameters. File must be the last parameter in the command, because all characters typed after the File parameter name are interpreted as the script file path followed by the script parameters. - [Argument(Format = "-File {value}", Position = 1)] public string File => Get(() => File); + [Argument(Format = "-File {value}")] public string File => Get(() => File); /// Arguments passed in when using the -File option. - [Argument(Format = "{value}", Position = 2)] public IReadOnlyList FileArguments => Get>(() => FileArguments); + [Argument(Format = "{value}")] public IReadOnlyList FileArguments => Get>(() => FileArguments); /// Executes the specified commands (and any parameters) as though they were typed at the PowerShell command prompt, and then exits, unless the NoExit parameter is specified.The value of Command can be -, a script block, or a string. If the value of Command is -, the command text is read from standard input.The Command parameter only accepts a script block for execution when it can recognize the value passed to Command as a ScriptBlock type. This is only possible when running pwsh from another PowerShell host. The ScriptBlock type may be contained in an existing variable, returned from an expression, or parsed by the PowerShell host as a literal script block enclosed in curly braces ({}), before being passed to pwsh. [Argument(Format = "-Command {value}")] public string Command => Get(() => Command); /// Overrides the system-wide powershell.config.json settings file for the session. By default, system-wide settings are read from the powershell.config.json in the $PSHOME directory. Note that these settings are not used by the endpoint specified by the -ConfigurationName argument. diff --git a/source/Nuke.Common/Tools/Pwsh/Pwsh.json b/source/Nuke.Common/Tools/Pwsh/Pwsh.json index 14f4e50ca..04a04e8f4 100644 --- a/source/Nuke.Common/Tools/Pwsh/Pwsh.json +++ b/source/Nuke.Common/Tools/Pwsh/Pwsh.json @@ -105,14 +105,12 @@ "name": "File", "type": "string", "format": "-File {value}", - "position": 1, "help": "If the value of File is -, the command text is read from standard input. Running pwsh -File - without redirected standard input starts a regular session. This is the same as not specifying the File parameter at all.This is the default parameter if no parameters are present but values are present in the command line. The specified script runs in the local scope (\"dot-sourced\"), so that the functions and variables that the script creates are available in the current session. Enter the script file path and any parameters. File must be the last parameter in the command, because all characters typed after the File parameter name are interpreted as the script file path followed by the script parameters." }, { "name": "FileArguments", "type": "List", "format": "{value}", - "position": 2, "help": "Arguments passed in when using the -File option." }, { diff --git a/source/Nuke.Components/Nuke.Components.csproj b/source/Nuke.Components/Nuke.Components.csproj index b8fe4c524..f6dac1fec 100644 --- a/source/Nuke.Components/Nuke.Components.csproj +++ b/source/Nuke.Components/Nuke.Components.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 diff --git a/source/Nuke.GlobalTool.Tests/Nuke.GlobalTool.Tests.csproj b/source/Nuke.GlobalTool.Tests/Nuke.GlobalTool.Tests.csproj index e1f8b45f4..3b0e891ba 100644 --- a/source/Nuke.GlobalTool.Tests/Nuke.GlobalTool.Tests.csproj +++ b/source/Nuke.GlobalTool.Tests/Nuke.GlobalTool.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 diff --git a/source/Nuke.GlobalTool/Nuke.GlobalTool.csproj b/source/Nuke.GlobalTool/Nuke.GlobalTool.csproj index a47a42bc8..99e81cb78 100644 --- a/source/Nuke.GlobalTool/Nuke.GlobalTool.csproj +++ b/source/Nuke.GlobalTool/Nuke.GlobalTool.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 LatestMajor true nuke diff --git a/source/Nuke.MSBuildTasks/Nuke.MSBuildTasks.csproj b/source/Nuke.MSBuildTasks/Nuke.MSBuildTasks.csproj index 3de69c2ec..cd7e4c306 100644 --- a/source/Nuke.MSBuildTasks/Nuke.MSBuildTasks.csproj +++ b/source/Nuke.MSBuildTasks/Nuke.MSBuildTasks.csproj @@ -2,7 +2,7 @@ false - net8.0;net472 + net10.0;net472 diff --git a/source/Nuke.ProjectModel.Tests/Nuke.ProjectModel.Tests.csproj b/source/Nuke.ProjectModel.Tests/Nuke.ProjectModel.Tests.csproj index bd84cd89c..1805945bf 100644 --- a/source/Nuke.ProjectModel.Tests/Nuke.ProjectModel.Tests.csproj +++ b/source/Nuke.ProjectModel.Tests/Nuke.ProjectModel.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 diff --git a/source/Nuke.ProjectModel.Tests/ProjectModelTest.cs b/source/Nuke.ProjectModel.Tests/ProjectModelTest.cs index 62038613c..382f9e391 100644 --- a/source/Nuke.ProjectModel.Tests/ProjectModelTest.cs +++ b/source/Nuke.ProjectModel.Tests/ProjectModelTest.cs @@ -15,33 +15,33 @@ public class ProjectModelTest { private static AbsolutePath RootDirectory => Constants.TryGetRootDirectoryFrom(EnvironmentInfo.WorkingDirectory).NotNull(); - private static AbsolutePath SolutionFile => RootDirectory / "nuke-common.sln"; + private static AbsolutePath SolutionFile => RootDirectory / "nuke-common.slnx"; [Fact] public void ProjectTest() { - var solution = SolutionModelTasks.ParseSolution(SolutionFile); + var solution = SolutionFile.ReadSolution(); var project = solution.Projects.Single(x => x.Name == "Nuke.ProjectModel"); var action = new Action(() => project.GetMSBuildProject()); action.Should().NotThrow(); - project.GetTargetFrameworks().Should().Equal("net8.0", "net9.0"); + project.GetTargetFrameworks().Should().Equal("net8.0", "net9.0", "net10.0"); project.HasPackageReference("Microsoft.Build.Locator").Should().BeTrue(); project.GetPackageReferenceVersion("Microsoft.Build.Locator").Should().Be("1.7.8"); - project.GetPackageReferenceVersion("Microsoft.Build").Should().Be("17.12.6"); + project.GetPackageReferenceVersion("Microsoft.Build").Should().Be("18.0.2"); } [Fact] public void MSBuildProjectTest() { - var solution = SolutionModelTasks.ParseSolution(SolutionFile); + var solution = SolutionFile.ReadSolution(); var project = solution.Projects.Single(x => x.Name == "Nuke.ProjectModel"); var msbuildProject = project.GetMSBuildProject(targetFramework: "net8.0"); var package = msbuildProject.GetItems("PackageVersion").FirstOrDefault(x => x.EvaluatedInclude == "Microsoft.Build"); package.Should().NotBeNull(); - package.GetMetadataValue("Version").Should().Be("17.11.4"); + package.GetMetadataValue("Version").Should().Be("17.11.48"); } } diff --git a/source/Nuke.ProjectModel/Nuke.ProjectModel.csproj b/source/Nuke.ProjectModel/Nuke.ProjectModel.csproj index 3129b27ee..4bd0b3832 100644 --- a/source/Nuke.ProjectModel/Nuke.ProjectModel.csproj +++ b/source/Nuke.ProjectModel/Nuke.ProjectModel.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0 + net8.0;net9.0;net10.0 diff --git a/source/Nuke.SolutionModel.Tests/Nuke.SolutionModel.Tests.csproj b/source/Nuke.SolutionModel.Tests/Nuke.SolutionModel.Tests.csproj index 33d16ff1f..2223161b3 100644 --- a/source/Nuke.SolutionModel.Tests/Nuke.SolutionModel.Tests.csproj +++ b/source/Nuke.SolutionModel.Tests/Nuke.SolutionModel.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 diff --git a/source/Nuke.SolutionModel.Tests/SolutionModelTest.cs b/source/Nuke.SolutionModel.Tests/SolutionModelTest.cs index ef2bee640..107852bed 100644 --- a/source/Nuke.SolutionModel.Tests/SolutionModelTest.cs +++ b/source/Nuke.SolutionModel.Tests/SolutionModelTest.cs @@ -16,19 +16,17 @@ public class SolutionModelTest { private static AbsolutePath RootDirectory => Constants.TryGetRootDirectoryFrom(EnvironmentInfo.WorkingDirectory).NotNull(); - private static AbsolutePath SolutionFile => RootDirectory / "nuke-common.sln"; + private static AbsolutePath SolutionFile => RootDirectory / "nuke-common.slnx"; [Fact] public void SolutionTest() { - var solution = SolutionModelTasks.ParseSolution(SolutionFile); + var solution = SolutionFile.ReadSolution(); solution.SolutionFolders.Select(x => x.Name).Should().BeEquivalentTo("misc"); - solution.AllProjects.Where(x => x.Is(ProjectType.CSharpProject)).Should().HaveCountGreaterOrEqualTo(9); var buildProject = solution.AllProjects.SingleOrDefault(x => x.Name == "_build"); buildProject.Should().NotBeNull(); - buildProject.Is(ProjectType.CSharpProject).Should().BeTrue(); // solution.SaveAs(solution.Path + ".bak"); } @@ -36,66 +34,8 @@ public void SolutionTest() [Fact] public void SolutionGetProjectsTest() { - var solution = SolutionModelTasks.ParseSolution(SolutionFile); + var solution = SolutionFile.ReadSolution(); solution.GetAllProjects("*.Tests").Should().HaveCountGreaterOrEqualTo(2); } - - [Fact] - public void DuplicatedProjectTest() - { - var content = """ - Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{BB6A9024-24DB-4170-A09B-02349148A78F}" - EndProject - Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{BB6A9024-24DB-4170-A09B-02349148A78F}" - EndProject - Global - EndGlobal - """.SplitLineBreaks(); - - Action action = () => SolutionSerializer.DeserializeFromContent(content); - action.Should().Throw() - .Where(x => x.Message.Contains("Solution contains duplicated project ids") && - x.Message.Contains(" - bb6a9024-24db-4170-a09b-02349148a78f")); - } - - [Fact] - public void DuplicatedSolutionConfigurationTest() - { - var content = """ - - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - EndGlobal - """.SplitLineBreaks(); - - Action action = () => SolutionSerializer.DeserializeFromContent(content); - action.Should().Throw() - .Where(x => x.Message.Contains("Solution contains duplicated SolutionConfigurationPlatforms entries") && - x.Message.Contains(" - Debug|Any CPU")); - } - - [Fact] - public void DuplicatedProjectConfigurationTest() - { - var content = """ - - Global - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BB6A9024-24DB-4170-A09B-02349148A78F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BB6A9024-24DB-4170-A09B-02349148A78F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BB6A9024-24DB-4170-A09B-02349148A78F}.Release|Any CPU.ActiveCfg = Debug|Any CPU - EndGlobalSection - EndGlobal - """.SplitLineBreaks(); - - Action action = () => SolutionSerializer.DeserializeFromContent(content); - action.Should().Throw() - .Where(x => x.Message.Contains("Solution contains duplicated ProjectConfigurationPlatforms entries") && - x.Message.Contains(" - {BB6A9024-24DB-4170-A09B-02349148A78F}.Debug|Any CPU.ActiveCfg")); - } } diff --git a/source/Nuke.SolutionModel/Model.cs b/source/Nuke.SolutionModel/Model.cs new file mode 100644 index 000000000..45859b833 --- /dev/null +++ b/source/Nuke.SolutionModel/Model.cs @@ -0,0 +1,130 @@ +// Copyright 2025 Maintainers of NUKE. +// Distributed under the MIT License. +// https://github.com/nuke-build/nuke/blob/master/LICENSE + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using JetBrains.Annotations; +using Microsoft.VisualStudio.SolutionPersistence.Model; +using Microsoft.VisualStudio.SolutionPersistence.Serializer; +using Nuke.Common.IO; +using Nuke.Common.Utilities; + +namespace Nuke.Common.ProjectModel; + +[PublicAPI] +public interface IProjectContainer +{ + IProjectContainer Parent { get; } + IReadOnlyCollection Projects { get; } + IReadOnlyCollection SolutionFolders { get; } +} + +[PublicAPI] +public static class ProjectContainerExtensions +{ + [CanBeNull] + public static SolutionFolder GetSolutionFolder(this IProjectContainer container, string name) + { + return container.SolutionFolders.SingleOrDefault(x => name.Equals(x.Name, StringComparison.Ordinal)); + } + + /// + /// Gets a project by its name. + /// + [CanBeNull] + public static Project GetProject(this IProjectContainer container, string name) + { + return container.Projects.SingleOrDefault(x => name.Equals(x.Name, StringComparison.Ordinal)); + } +} + +[PublicAPI] +public class Solution(SolutionModel model, AbsolutePath path = null) : IProjectContainer, IAbsolutePathHolder +{ + private ConcurrentDictionary wrappers { get; } = new(); + + internal T GetOrCreate(object model) + { + lock (wrappers) + { + return (T)wrappers.GetOrAdd(model, x => x switch + { + SolutionProjectModel project => new Project(project, this), + SolutionFolderModel folder => new SolutionFolder(folder, this), + _ => throw new ArgumentOutOfRangeException(nameof(x), x, null) + }); + } + } + + public SolutionModel GetModel() => model; + + [CanBeNull] public AbsolutePath Path { get; set; } = path; + [CanBeNull] public string Name => Path?.NameWithoutExtension; + [CanBeNull] public string FileName => Path?.Name; + [CanBeNull] public AbsolutePath Directory => Path?.Parent; + + public IReadOnlyCollection AllProjects => model.SolutionProjects.Select(GetOrCreate).ToList(); + public IReadOnlyCollection AllSolutionFolders => model.SolutionFolders.Select(GetOrCreate).ToList(); + + IProjectContainer IProjectContainer.Parent => null; + public IReadOnlyCollection Projects => AllProjects.Where(x => x.Parent == this).ToList(); + public IReadOnlyCollection SolutionFolders => AllSolutionFolders.Where(x => x.Parent == this).ToList(); + + public static implicit operator string(Solution solution) => solution.Path; + public static implicit operator AbsolutePath(Solution solution) => solution.Path; + + public IEnumerable GetAllProjects(string wildcardPattern) + { + wildcardPattern = $"^{wildcardPattern}$"; + var regex = new Regex(wildcardPattern + .Replace(".", "\\.") + .Replace("*", ".*")); + return AllProjects.Where(x => regex.IsMatch(x.Name)); + } + + public void Save(AbsolutePath path = null) + { + Path = (path ?? Path).NotNull(); + var serializer = SolutionSerializers.GetSerializerByMoniker(Path).NotNull(); + AsyncHelper.RunSync(() => serializer.SaveAsync(Path, model, CancellationToken.None)); + } +} + +[PublicAPI] +public class SolutionItem(SolutionItemModel model, Solution solution) +{ + public string Name => model.ActualDisplayName; + + public Solution Solution => solution; + public IProjectContainer Parent => (IProjectContainer)model.Parent?.Apply(solution.GetOrCreate) ?? solution; + + public override string ToString() => model.ActualDisplayName; +} + +[PublicAPI] +public class SolutionFolder(SolutionFolderModel model, Solution solution) : SolutionItem(model, solution), IProjectContainer +{ + public SolutionFolderModel GetModel() => model; + + public IReadOnlyCollection Projects => Solution.AllProjects.Where(x => x.Parent == this).ToList(); + public IReadOnlyCollection SolutionFolders => Solution.AllSolutionFolders.Where(x => x.Parent == this).ToList(); +} + +[PublicAPI] +public class Project(SolutionProjectModel model, Solution solution) : SolutionItem(model, solution), IAbsolutePathHolder +{ + public SolutionProjectModel GetModel() => model; + + public string RelativePath => model.FilePath; + [CanBeNull] public AbsolutePath Path => Solution.Directory.NotNull() / model.FilePath; + [CanBeNull] public string FileName => System.IO.Path.GetFileName(RelativePath); + [CanBeNull] public AbsolutePath Directory => Path?.Parent; + + public static implicit operator string(Project project) => project.Path; + public static implicit operator AbsolutePath(Project project) => project.Path; +} diff --git a/source/Nuke.SolutionModel/Nuke.SolutionModel.csproj b/source/Nuke.SolutionModel/Nuke.SolutionModel.csproj index 06503295a..efc995bfb 100644 --- a/source/Nuke.SolutionModel/Nuke.SolutionModel.csproj +++ b/source/Nuke.SolutionModel/Nuke.SolutionModel.csproj @@ -1,11 +1,15 @@ - netstandard2.0;net8.0 + netstandard2.0;net10.0 + + + + diff --git a/source/Nuke.SolutionModel/PrimitiveProject.cs b/source/Nuke.SolutionModel/PrimitiveProject.cs deleted file mode 100644 index 05be0d37f..000000000 --- a/source/Nuke.SolutionModel/PrimitiveProject.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2023 Maintainers of NUKE. -// Distributed under the MIT License. -// https://github.com/nuke-build/nuke/blob/master/LICENSE - -using System; -using System.Linq; -using JetBrains.Annotations; - -namespace Nuke.Common.ProjectModel; - -/// -/// Abstraction for and . -/// -[PublicAPI] -public abstract class PrimitiveProject -{ - protected PrimitiveProject( - Solution solution, - Guid projectId, - string name, - Guid typeId) - { - Solution = solution; - ProjectId = projectId; - Name = name; - TypeId = typeId; - } - - public Solution Solution { get; } - public Guid ProjectId { get; set; } - public string Name { get; set; } - public Guid TypeId { get; set; } - - /// - /// Returns the parent . - /// - public SolutionFolder SolutionFolder - { - get => Solution.GetSolutionFolder(this); - set => Solution.SetSolutionFolder(value, this); - } - - internal abstract string RelativePath { get; } - - public bool Is(ProjectType projectType) - { - return projectType.Guids.Any(x => x.Equals(TypeId)); - } -} \ No newline at end of file diff --git a/source/Nuke.SolutionModel/Project.cs b/source/Nuke.SolutionModel/Project.cs deleted file mode 100644 index 2082b72ff..000000000 --- a/source/Nuke.SolutionModel/Project.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2023 Maintainers of NUKE. -// Distributed under the MIT License. -// https://github.com/nuke-build/nuke/blob/master/LICENSE - -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Nuke.Common.IO; - -namespace Nuke.Common.ProjectModel; - -/// -/// Represents a project. -/// -[PublicAPI] -public class Project : PrimitiveProject, IAbsolutePathHolder -{ - private readonly Func _pathProvider; - - internal Project( - Solution solution, - Guid projectId, - string name, - Func pathProvider, - Guid typeId, - IDictionary configurations) - : base(solution, projectId, name, typeId) - { - _pathProvider = pathProvider; - Configurations = configurations; - } - - public AbsolutePath Path => _pathProvider.Invoke(); - public AbsolutePath Directory => Path.Parent; - - public IDictionary Configurations { get; } - - public static implicit operator string(Project project) - { - return project.Path; - } - - public override string ToString() - { - return Path; - } - - internal override string RelativePath => Solution.Directory.GetRelativePathTo(Path); -} diff --git a/source/Nuke.SolutionModel/ProjectType.cs b/source/Nuke.SolutionModel/ProjectType.cs deleted file mode 100644 index ed0199c58..000000000 --- a/source/Nuke.SolutionModel/ProjectType.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2023 Maintainers of NUKE. -// Distributed under the MIT License. -// https://github.com/nuke-build/nuke/blob/master/LICENSE - -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; - -namespace Nuke.Common.ProjectModel; - -/// -/// According to https://www.codeproject.com/Reference/720512/List-of-Visual-Studio-Project-Type-GUIDs. -/// -[PublicAPI] -public class ProjectType -{ - public static ProjectType SolutionFolder = new(Nuke.Common.ProjectModel.SolutionFolder.Guid); - - public static ProjectType CSharpProject = new("9A19103F-16F7-4668-BE54-9A1E7A4F7556", "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"); - public static ProjectType VBNetProject = new("F184B08F-C81C-45F6-A57F-5ABD9991F28F"); - public static ProjectType FSharpProject = new("F2A71F9B-5D33-465A-A702-920D77279786"); - public static ProjectType VisualCppProject = new("8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"); - - public static ProjectType VSTAProject = new("A860303F-1F3F-4691-B57E-529FC101A107"); - public static ProjectType VSTOProject = new("BAA0C2D2-18E2-41B9-852F-F413020CAA33"); - public static ProjectType AspNetProject = new("8BB2217D-0F2D-49D1-97BC-3654ED321F3B"); - - public static ProjectType SharePointProject = new("593B0543-81F6-4436-BA1E-4747859CAAE2", - "EC05E597-79D4-47f3-ADA0-324C4F7C7484", - "F8810EC1-6754-47FC-A15F-DFABD2E3FA90"); - - public static ProjectType SqlProject = new("00D1A9C2-B5F0-4AF3-8072-F6C62B433612"); - - public static ProjectType DockerComposeProject = new("E53339B2-1760-4266-BCC7-CA923CBCF16C"); - - public static ProjectType WindowsPhoneProject = new("76F1466A-8B6D-4E39-A767-685A06062A39", - "C089C8C0-30E0-4E22-80C0-CE093F111A43", - "DB03555F-0C8B-43BE-9FF9-57896B3C5E56"); - - public static ProjectType WcfProject = new("3D9AD99F-2412-4246-B90B-4EAA41C64699"); - public static ProjectType WpfProject = new("60DC8134-EBA5-43B8-BCC9-BB4BC16C2548"); - - public static ProjectType WwfProject = new("14822709-B5A1-4724-98CA-57A101D1B079", - "D59BE175-2ED0-4C54-BE3D-CDAA9F3214C8", - "32F31D43-81CC-4C15-9DE6-3FC5453562B6"); - - public static ProjectType WindowsStoreProject = new("BC8A1FFA-BEE3-4634-8014-F334798102B3"); - public static ProjectType XamarinProject = new("EFBA0AD7-5A72-4C68-AF49-83D382785DCF", "6BC8ED88-2882-458C-8E55-DFD12B67127B"); - - public static ProjectType XnaProject = new("6D335F3A-9D43-41b4-9D22-F6F17C4BE596", - "2DF5C3F4-5A5F-47a9-8E94-23B4456F55E2", - "D399B71A-8929-442a-A9AC-8BEC78BB2433"); - - public static ProjectType JavaScriptProject = new("54A90642-561A-4BB1-A94E-469ADEE60C69"); - - public ProjectType(params string[] guids) - : this(guids.Select(Guid.Parse).ToArray()) - { - } - - public ProjectType(params Guid[] guids) - { - Guids = guids; - } - - public Guid FirstGuid => Guids.First(); - - public IReadOnlyCollection Guids { get; } -} \ No newline at end of file diff --git a/source/Nuke.SolutionModel/Solution.cs b/source/Nuke.SolutionModel/Solution.cs deleted file mode 100644 index 20fb06873..000000000 --- a/source/Nuke.SolutionModel/Solution.cs +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2023 Maintainers of NUKE. -// Distributed under the MIT License. -// https://github.com/nuke-build/nuke/blob/master/LICENSE - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using JetBrains.Annotations; -using Nuke.Common.IO; -using Nuke.Common.Utilities; -using Nuke.Common.Utilities.Collections; - -namespace Nuke.Common.ProjectModel; - -/// -/// Represents a solution file (*.sln). -/// -[PublicAPI] -public class Solution : IAbsolutePathHolder -{ - internal List PrimitiveProjects { get; } = new(); - internal Dictionary PrimitiveProjectParents { get; } = new(); - - /// - /// File the solution was loaded from or saved to. - /// - [CanBeNull] - public AbsolutePath Path { get; internal set; } - - /// - /// Name (without *.sln) of the file the solution was loaded from or saved to. - /// - [CanBeNull] - public string Name => System.IO.Path.GetFileNameWithoutExtension(Path); - - /// - /// File name (including *.sln) of the file the solution was loaded from or saved to. - /// - [CanBeNull] - public string FileName => System.IO.Path.GetFileName(Path); - - /// - /// Directory the solution was loaded from or saved to. - /// - [CanBeNull] - public AbsolutePath Directory => Path?.Parent; - - public string[] Header { get; set; } - public IDictionary Properties { get; set; } - public IDictionary ExtensibilityGlobals { get; set; } - public IDictionary Configurations { get; set; } - - /// - /// All projects that are descendants of the solution. - /// - public IReadOnlyCollection AllProjects => PrimitiveProjects.OfType().ToList(); - - /// - /// All solution folders that are descendants of the solution. - /// - public IReadOnlyCollection AllSolutionFolders => PrimitiveProjects.OfType().ToList(); - - /// - /// All projects that are direct descendants of the solution. - /// - public IReadOnlyCollection Projects => AllProjects.Where(x => x.SolutionFolder == null).ToList(); - - /// - /// All solution folders that are direct descendants of the solution. - /// - public IReadOnlyCollection SolutionFolders => AllSolutionFolders.Where(x => x.SolutionFolder == null).ToList(); - - public static implicit operator string(Solution solution) - { - return solution.Path; - } - - public static implicit operator AbsolutePath(Solution solution) - { - return solution.Path; - } - - public override string ToString() - { - return Path ?? ""; - } - - /// - /// Gets a solution folder by its name. - /// - [CanBeNull] - public SolutionFolder GetSolutionFolder(string name) - { - return SolutionFolders.SingleOrDefault(x => name.Equals(x.Name, StringComparison.Ordinal)); - } - - /// - /// Gets a project by its name. - /// - [CanBeNull] - public Project GetProject(string name) - { - return Projects.SingleOrDefault(x => name.Equals(x.Name, StringComparison.Ordinal)); - } - - /// - /// Gets all projects matching a wildcard pattern. - /// - public IEnumerable GetAllProjects(string wildcardPattern) - { - wildcardPattern = $"^{wildcardPattern}$"; - var regex = new Regex(wildcardPattern - .Replace(".", "\\.") - .Replace("*", ".*")); - return AllProjects.Where(x => regex.IsMatch(x.Name)); - } - - /// - /// Adds a solution folder to the solution. - /// - public SolutionFolder AddSolutionFolder(string name, Guid? projectId = null, SolutionFolder solutionFolder = null) - { - // TODO: rename to parent folder - projectId ??= Guid.NewGuid(); - var project = new SolutionFolder(this, projectId.Value, name, items: new Dictionary()); - AddPrimitiveProject(project, solutionFolder); - return project; - } - - /// - /// Adds a project to the solution. - /// - public Project AddProject( - string name, - Guid typeId, - string path, - Guid? projectId = null, - IDictionary configurationPlatforms = null, - SolutionFolder solutionFolder = null) - { - // TODO: rename to parent folder - projectId ??= Guid.NewGuid(); - var project = new Project(this, projectId.Value, name, () => path, typeId, configurationPlatforms ?? new Dictionary()); - AddPrimitiveProject(project, solutionFolder); - return project; - } - - internal void AddPrimitiveProject(PrimitiveProject primitiveProject, SolutionFolder solutionFolder = null) - { - var otherProject = PrimitiveProjects.FirstOrDefault(x => x.ProjectId.Equals(primitiveProject.ProjectId)); - Assert.True(otherProject == null, - $"Cannot add '{primitiveProject.Name}' because its id '{primitiveProject.ProjectId}' is already taken by '{otherProject?.Name}'"); - - PrimitiveProjects.Add(primitiveProject); - PrimitiveProjectParents.Add(primitiveProject, solutionFolder); - } - - /// - /// Removes a solution folder from the solution. Containing items are moved to the parent folder. - /// - public IReadOnlyCollection RemoveSolutionFolder(SolutionFolder solutionFolder) - { - var children = GetNestedPrimitiveProjects(solutionFolder); - foreach (var child in children) - SetSolutionFolder(solutionFolder.SolutionFolder, child); - - PrimitiveProjects.Remove(solutionFolder); - - return children; - } - - internal IReadOnlyCollection GetNestedPrimitiveProjects(SolutionFolder solutionFolder) - { - return PrimitiveProjectParents.Where(x => x.Value == solutionFolder).Select(x => x.Key).ToList(); - } - - /// - /// Removes a project from the solution. - /// - public void RemoveProject(Project project) - { - PrimitiveProjects.Remove(project); - PrimitiveProjectParents.Remove(project); - } - - [CanBeNull] - internal SolutionFolder GetSolutionFolder(PrimitiveProject primitiveProject) - { - return PrimitiveProjectParents.TryGetValue(primitiveProject, out var parent) ? parent : null; - } - - internal void SetSolutionFolder([CanBeNull] SolutionFolder solutionFolder, PrimitiveProject primitiveProject) - { - Assert.True(solutionFolder == null || solutionFolder.Solution == primitiveProject.Solution, - "Project and solution folder must belong to the same solution"); - - PrimitiveProjectParents[primitiveProject] = solutionFolder; - } - - /// - /// Saves the solution to the specified file. - /// - public void SaveAs(AbsolutePath solutionFile) - { - Path = solutionFile; - Save(); - } - - /// - /// Saves the solution to the file it was loaded from. - /// - public void Save() - { - SolutionSerializer.Serialize(this); - } - - /// - /// Adds another solution to the current solution with an optional parent solution folder. - /// - public void AddSolution(Solution solution, SolutionFolder folder = null) - { - SolutionFolder GetParentFolder(PrimitiveProject solutionFolder) => - AllSolutionFolders.FirstOrDefault(x => x.ProjectId == solutionFolder.SolutionFolder?.ProjectId); - - IDictionary GetItems(SolutionFolder solutionFolder) - => solutionFolder.Items.Keys - .Select(x => (string) Directory.GetWinRelativePathTo(solution.Directory / x)) - .ToDictionary(x => x, x => x); - - solution.AllSolutionFolders.ForEach(x => AddSolutionFolder(x.Name, x.ProjectId, GetParentFolder(x) ?? folder)); - solution.AllSolutionFolders.ForEach(x => AllSolutionFolders.Single(y => y.ProjectId == x.ProjectId).Items = GetItems(x)); - solution.AllProjects.ForEach(x => AddProject(x.Name, x.TypeId, x.Path, x.ProjectId, x.Configurations, GetParentFolder(x) ?? folder)); - } - - /// - /// Randomizes all project IDs in the solution. - /// - public void RandomizeProjectIds() - { - AllSolutionFolders.ForEach(x => x.ProjectId = Guid.NewGuid()); - AllProjects.ForEach(x => x.ProjectId = Guid.NewGuid()); - } -} diff --git a/source/Nuke.SolutionModel/SolutionFolder.cs b/source/Nuke.SolutionModel/SolutionFolder.cs deleted file mode 100644 index 7849aafc1..000000000 --- a/source/Nuke.SolutionModel/SolutionFolder.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2023 Maintainers of NUKE. -// Distributed under the MIT License. -// https://github.com/nuke-build/nuke/blob/master/LICENSE - -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; - -namespace Nuke.Common.ProjectModel; - -/// -/// Represents a solution folder. -/// -[PublicAPI] -public class SolutionFolder : PrimitiveProject -{ - internal static readonly Guid Guid = Guid.Parse("2150E333-8FDC-42A3-9474-1A3956D46DE8"); - - public SolutionFolder( - Solution solution, - Guid projectId, - string name, - IDictionary items) - : base(solution, projectId, name, Guid) - { - Items = items; - } - - public IDictionary Items { get; set; } - - public IReadOnlyCollection SolutionFolders => Solution.AllSolutionFolders.Where(x => x.SolutionFolder == this).ToList(); - public IReadOnlyCollection Projects => Solution.AllProjects.Where(x => x.SolutionFolder == this).ToList(); - - /// - /// Gets a solution folder by its name. - /// - [CanBeNull] - public SolutionFolder GetSolutionFolder(string name) - { - return SolutionFolders.SingleOrDefault(x => name.Equals(x.Name, StringComparison.Ordinal)); - } - - /// - /// Gets a project by its name. - /// - [CanBeNull] - public Project GetProject(string name) - { - return Projects.SingleOrDefault(x => name.Equals(x.Name, StringComparison.Ordinal)); - } - - internal override string RelativePath => Name; -} diff --git a/source/Nuke.SolutionModel/SolutionModelExtensions.cs b/source/Nuke.SolutionModel/SolutionModelExtensions.cs new file mode 100644 index 000000000..230d8365d --- /dev/null +++ b/source/Nuke.SolutionModel/SolutionModelExtensions.cs @@ -0,0 +1,27 @@ +// Copyright 2025 Maintainers of NUKE. +// Distributed under the MIT License. +// https://github.com/nuke-build/nuke/blob/master/LICENSE + +using System.Threading; +using JetBrains.Annotations; +using Microsoft.VisualStudio.SolutionPersistence.Serializer; +using Nuke.Common.IO; +using Nuke.Common.Utilities; + +namespace Nuke.Common.ProjectModel; + +public static class SolutionModelExtensions +{ + public static Solution ReadSolution([NotNull] this AbsolutePath path) + { + return path.ReadSolution(); + } + + public static Solution ReadSolution([NotNull] this AbsolutePath path) + where T : Solution + { + var serializer = SolutionSerializers.GetSerializerByMoniker(path).NotNull(); + var model = AsyncHelper.RunSync(() => serializer.OpenAsync(path, CancellationToken.None)); + return typeof(T).CreateInstance(model, path); + } +} diff --git a/source/Nuke.SolutionModel/SolutionModelTasks.cs b/source/Nuke.SolutionModel/SolutionModelTasks.cs deleted file mode 100644 index 7a62a2d84..000000000 --- a/source/Nuke.SolutionModel/SolutionModelTasks.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2023 Maintainers of NUKE. -// Distributed under the MIT License. -// https://github.com/nuke-build/nuke/blob/master/LICENSE - -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Nuke.Common.IO; -using Nuke.Common.Utilities.Collections; - -namespace Nuke.Common.ProjectModel; - -[PublicAPI] -public static class SolutionModelTasks -{ - public static Solution CreateSolution(AbsolutePath solutionFile = null, params Solution[] solutions) - { - return CreateSolution(solutionFile, solutions, folderNameProvider: null); - } - - public static Solution CreateSolution( - AbsolutePath solutionFile = null, - IEnumerable solutions = null, - Func folderNameProvider = null, - bool randomizeProjectIds = true) - { - Assert.True(folderNameProvider != null || solutions != null); - - var solution = SolutionSerializer.DeserializeFromContent( - new[] - { - "Microsoft Visual Studio Solution File, Format Version 12.00", - "# Visual Studio 15", - "VisualStudioVersion = 15.0.26124.0", - "MinimumVisualStudioVersion = 15.0.26124.0" - }, - solutionFile); - - solution.Configurations = new Dictionary - { - { "Debug|Any CPU", "Debug|Any CPU" }, - { "Release|Any CPU", "Release|Any CPU" } - }; - - solutions?.ForEach(x => - { - var folder = folderNameProvider != null && folderNameProvider(x) is { } folderName - ? solution.AddSolutionFolder(folderName) - : null; - - solution.AddSolution(x, folder); - - if (randomizeProjectIds) - solution.RandomizeProjectIds(); - }); - - return solution; - } - - public static Solution ParseSolution(AbsolutePath solutionFile) - { - return SolutionSerializer.DeserializeFromFile(solutionFile); - } - - /// - /// Reads the solution (*.sln) structure from a file. - /// - public static Solution ReadSolution([NotNull] this AbsolutePath path) - { - return ParseSolution(path); - } -} diff --git a/source/Nuke.SolutionModel/SolutionSerializer.cs b/source/Nuke.SolutionModel/SolutionSerializer.cs deleted file mode 100644 index d191a4784..000000000 --- a/source/Nuke.SolutionModel/SolutionSerializer.cs +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright 2023 Maintainers of NUKE. -// Distributed under the MIT License. -// https://github.com/nuke-build/nuke/blob/master/LICENSE - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using JetBrains.Annotations; -using Nuke.Common.IO; -using Nuke.Common.Utilities; -using Nuke.Common.Utilities.Collections; - -namespace Nuke.Common.ProjectModel; - -internal static class SolutionSerializer -{ - public static T DeserializeFromFile(AbsolutePath solutionFile) - where T : Solution, new() - { - Assert.FileExists(solutionFile); - return DeserializeFromContent(File.ReadAllLines(solutionFile), solutionFile); - } - - public static T DeserializeFromContent(string[] content, AbsolutePath solutionFile = null) - where T : Solution, new() - { - var trimmedContent = content.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).ToArray(); - - var solution = new T - { - Path = solutionFile, - Header = trimmedContent.TakeWhile(x => !x.StartsWith("Project")).ToArray(), - Properties = trimmedContent.GetGlobalSection("SolutionProperties", solutionFile), - ExtensibilityGlobals = trimmedContent.GetGlobalSection("ExtensibilityGlobals", solutionFile), - Configurations = trimmedContent.GetGlobalSection("SolutionConfigurationPlatforms", solutionFile) - }; - - var configurations = (trimmedContent.GetGlobalSection("ProjectConfigurationPlatforms", solutionFile) ?? - trimmedContent.GetGlobalSection("ProjectConfiguration", solutionFile) ?? - new Dictionary()) - .Select(x => new - { - ProjectId = Guid.Parse(x.Key.Substring(startIndex: 1, length: 36)), - ProjectConfiguration = x.Key.Substring(startIndex: 39), - SolutionConfiguration = x.Value - }) - .GroupBy(x => x.ProjectId) - .ToDictionary( - x => x.Key, - x => x.ToDictionary( - y => y.ProjectConfiguration, - y => y.SolutionConfiguration)); - var primitiveProjects = GetPrimitiveProjects(solution, trimmedContent, configurations) - .ToDictionarySafe( - x => x.ProjectId, - x => x, - duplicationMessage: $"Solution {solutionFile?.ToString("s")} contains duplicated project ids".TrimToOne(" ")); - foreach (var primitiveProject in primitiveProjects.Values) - solution.AddPrimitiveProject(primitiveProject); - - var projectToSolutionFolder = trimmedContent - .GetGlobalSection("NestedProjects", solutionFile) - ?.ToDictionary(x => Guid.Parse(x.Key.Trim('{', '}')), x => Guid.Parse(x.Value.Trim('{', '}'))); - if (projectToSolutionFolder != null) - { - var solutionFolders = primitiveProjects.Values.OfType().ToList(); - foreach (var (projectGuid, solutionFolderGuid) in projectToSolutionFolder) - { - var project = primitiveProjects.GetValueOrDefault(projectGuid) - .NotNull($"Project with guid '{projectGuid}' not found."); - var solutionFolder = solutionFolders.SingleOrDefault(x => x.ProjectId == solutionFolderGuid) - .NotNull($"Solution folder with guid '{solutionFolderGuid}' not found."); - - solution.SetSolutionFolder(solutionFolder, project); - } - } - - return solution; - } - - [CanBeNull] - private static Dictionary GetGlobalSection(this string[] lines, string name, [CanBeNull] string solutionFile) - { - var sectionLines = lines - .SkipWhile(x => !Regex.IsMatch(x, $@"^\s*GlobalSection\({name}\) = \w+$")) - .Skip(count: 1) - .TakeWhile(x => !Regex.IsMatch(x, @"^\s*EndGlobalSection$")) - .Where(x => !x.StartsWith("#")) - .ToList(); - - return sectionLines.Count == 0 - ? null - : sectionLines - .Select(x => x.Split('=')) - .ToDictionarySafe( - x => x[0].Trim(), - x => x[1].Trim(), - duplicationMessage: $"Solution {solutionFile?.SingleQuote()} contains duplicated {name} entries".TrimToOne(" ")); - } - - private static IEnumerable GetPrimitiveProjects( - Solution solution, - string[] content, - IReadOnlyDictionary> configurations) - { - static string GuidPattern(string text) - => $@"\{{(?<{Regex.Escape(text)}>[0-9a-fA-F]{{8}}-[0-9a-fA-F]{{4}}-[0-9a-fA-F]{{4}}-[0-9a-fA-F]{{4}}-[0-9a-fA-F]{{12}})\}}"; - - static string TextPattern(string name) - => $@"""(?<{Regex.Escape(name)}>[^""]*)"""; - - var projectRegex = new Regex( - $@"^Project\(""{GuidPattern("typeId")}""\)\s*=\s*{TextPattern("name")},\s*{TextPattern("path")},\s*""{GuidPattern("projectId")}""$"); - - for (var i = 0; i < content.Length; i++) - { - var match = projectRegex.Match(content[i]); - if (!match.Success) - continue; - - var projectId = Guid.Parse(match.Groups["projectId"].Value); - var typeId = Guid.Parse(match.Groups["typeId"].Value); - var name = match.Groups["name"].Value; - - if (!typeId.Equals(SolutionFolder.Guid)) - { - var path = match.Groups["path"].Value; - yield return new Project( - solution, - projectId, - name, - () => Path.GetFullPath(Path.Combine(solution.Directory.NotNull(), path)), - typeId, - configurations.GetValueOrDefault(projectId) ?? new Dictionary()); - } - else - { - var items = content - .Skip(i) - .TakeWhile(x => !x.StartsWith("EndProjectSection") && !x.StartsWith("EndProject")) - .Skip(2) - .Select(x => x.Split('=')) - .ToDictionary(x => x[0].Trim(), x => x[1].Trim()); - - yield return new SolutionFolder( - solution: solution, - projectId: projectId, - name: name, - items: items); - } - } - } - - public static void Serialize(Solution solution) - { - using var fileStream = File.Create(solution.Path.NotNull("solution.Path != null")); - Serialize(solution, fileStream); - } - - public static void Serialize(Solution solution, Stream stream) - { - Assert.NotNull(solution.Path); - - using var writer = new StreamWriter(stream, Encoding.UTF8); - - void Write(string text) => writer.WriteLine(text); - - void WriteSection(string start, IDictionary dictionary, string end) - { - if (dictionary == null) - return; - - Write($"\t{start}"); - dictionary.ForEach(x => Write($"\t\t{x.Key} = {x.Value}")); - Write($"\t{end}"); - } - - static string Format(Guid guid) => $"{{{guid.ToString("D").ToUpper()}}}"; - - solution.Header.ForEach(x => writer.WriteLine(x)); - - foreach (var project in solution.PrimitiveProjects) - { - var path = (WinRelativePath) project.RelativePath; - Write($@"Project(""{Format(project.TypeId)}"") = ""{project.Name}"", ""{path}"", ""{Format(project.ProjectId)}"""); - WriteSection( - "ProjectSection(SolutionItems) = preProject", - (project as SolutionFolder)?.Items, - "EndProjectSection"); - Write("EndProject"); - } - - Write("Global"); - - WriteSection( - "GlobalSection(SolutionConfigurationPlatforms) = preSolution", - solution.Configurations, - "EndGlobalSection"); - - WriteSection( - "GlobalSection(ProjectConfigurationPlatforms) = postSolution", - solution.PrimitiveProjects - .OfType() - .Where(x => x.Configurations != null) - .SelectMany( - x => x.Configurations, - (x, p) => new { Key = $"{Format(x.ProjectId)}.{p.Key}", p.Value }) - .ToDictionary(x => x.Key, x => x.Value), - "EndGlobalSection"); - - WriteSection( - "GlobalSection(SolutionProperties) = preSolution", - solution.Properties, - "EndGlobalSection"); - - WriteSection( - "GlobalSection(NestedProjects) = preSolution", - solution.PrimitiveProjects - .Where(x => x.SolutionFolder != null) - .ToDictionary(x => $"{Format(x.ProjectId)}", x => $"{Format(x.SolutionFolder.NotNull().ProjectId)}"), - "EndGlobalSection"); - - WriteSection( - "GlobalSection(ExtensibilityGlobals) = postSolution", - solution.ExtensibilityGlobals, - "EndGlobalSection"); - - Write("EndGlobal"); - } -} diff --git a/source/Nuke.SourceGenerators.Tests/Nuke.SourceGenerators.Tests.csproj b/source/Nuke.SourceGenerators.Tests/Nuke.SourceGenerators.Tests.csproj index cdeeb0848..d14313186 100644 --- a/source/Nuke.SourceGenerators.Tests/Nuke.SourceGenerators.Tests.csproj +++ b/source/Nuke.SourceGenerators.Tests/Nuke.SourceGenerators.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 true true diff --git a/source/Nuke.SourceGenerators.Tests/StronglyTypedSolutionGeneratorTest.Test#Solution.g.verified.cs b/source/Nuke.SourceGenerators.Tests/StronglyTypedSolutionGeneratorTest.Test#Solution.g.verified.cs new file mode 100644 index 000000000..1979d15fd --- /dev/null +++ b/source/Nuke.SourceGenerators.Tests/StronglyTypedSolutionGeneratorTest.Test#Solution.g.verified.cs @@ -0,0 +1,44 @@ +//HintName: Solution.g.cs +// + +using Microsoft.VisualStudio.SolutionPersistence.Model; +using Nuke.Common.ProjectModel; +using Nuke.Common.IO; + +internal class Solution(SolutionModel model, AbsolutePath path) : Nuke.Common.ProjectModel.Solution(model, path) +{ + public Nuke.Common.ProjectModel.Project _build => this.GetProject("_build"); + public Nuke.Common.ProjectModel.Project Nuke_Build => this.GetProject("Nuke.Build"); + public Nuke.Common.ProjectModel.Project Nuke_Build_Shared => this.GetProject("Nuke.Build.Shared"); + public Nuke.Common.ProjectModel.Project Nuke_Build_Tests => this.GetProject("Nuke.Build.Tests"); + public Nuke.Common.ProjectModel.Project Nuke_Common => this.GetProject("Nuke.Common"); + public Nuke.Common.ProjectModel.Project Nuke_Common_Tests => this.GetProject("Nuke.Common.Tests"); + public Nuke.Common.ProjectModel.Project Nuke_Components => this.GetProject("Nuke.Components"); + public Nuke.Common.ProjectModel.Project Nuke_GlobalTool => this.GetProject("Nuke.GlobalTool"); + public Nuke.Common.ProjectModel.Project Nuke_GlobalTool_Tests => this.GetProject("Nuke.GlobalTool.Tests"); + public Nuke.Common.ProjectModel.Project Nuke_MSBuildTasks => this.GetProject("Nuke.MSBuildTasks"); + public Nuke.Common.ProjectModel.Project Nuke_ProjectModel => this.GetProject("Nuke.ProjectModel"); + public Nuke.Common.ProjectModel.Project Nuke_ProjectModel_Tests => this.GetProject("Nuke.ProjectModel.Tests"); + public Nuke.Common.ProjectModel.Project Nuke_SolutionModel => this.GetProject("Nuke.SolutionModel"); + public Nuke.Common.ProjectModel.Project Nuke_SolutionModel_Tests => this.GetProject("Nuke.SolutionModel.Tests"); + public Nuke.Common.ProjectModel.Project Nuke_SourceGenerators => this.GetProject("Nuke.SourceGenerators"); + public Nuke.Common.ProjectModel.Project Nuke_SourceGenerators_Tests => this.GetProject("Nuke.SourceGenerators.Tests"); + public Nuke.Common.ProjectModel.Project Nuke_Tooling => this.GetProject("Nuke.Tooling"); + public Nuke.Common.ProjectModel.Project Nuke_Tooling_Generator => this.GetProject("Nuke.Tooling.Generator"); + public Nuke.Common.ProjectModel.Project Nuke_Tooling_Tests => this.GetProject("Nuke.Tooling.Tests"); + public Nuke.Common.ProjectModel.Project Nuke_Utilities => this.GetProject("Nuke.Utilities"); + public Nuke.Common.ProjectModel.Project Nuke_Utilities_IO_Compression => this.GetProject("Nuke.Utilities.IO.Compression"); + public Nuke.Common.ProjectModel.Project Nuke_Utilities_IO_Globbing => this.GetProject("Nuke.Utilities.IO.Globbing"); + public Nuke.Common.ProjectModel.Project Nuke_Utilities_Net => this.GetProject("Nuke.Utilities.Net"); + public Nuke.Common.ProjectModel.Project Nuke_Utilities_Tests => this.GetProject("Nuke.Utilities.Tests"); + public Nuke.Common.ProjectModel.Project Nuke_Utilities_Text_Json => this.GetProject("Nuke.Utilities.Text.Json"); + public Nuke.Common.ProjectModel.Project Nuke_Utilities_Text_Yaml => this.GetProject("Nuke.Utilities.Text.Yaml"); + + public _misc misc => new(this.GetSolutionFolder("misc").GetModel(), this); + + internal class _misc(SolutionFolderModel model, Nuke.Common.ProjectModel.Solution solution) : Nuke.Common.ProjectModel.SolutionFolder(model, solution) + { + + + } +} \ No newline at end of file diff --git a/source/Nuke.SourceGenerators.Tests/StronglyTypedSolutionGeneratorTest.Test#StronglyTypedSolutionGenerator.verified.cs b/source/Nuke.SourceGenerators.Tests/StronglyTypedSolutionGeneratorTest.Test#StronglyTypedSolutionGenerator.verified.cs deleted file mode 100644 index a72c5367f..000000000 --- a/source/Nuke.SourceGenerators.Tests/StronglyTypedSolutionGeneratorTest.Test#StronglyTypedSolutionGenerator.verified.cs +++ /dev/null @@ -1,42 +0,0 @@ -//HintName: StronglyTypedSolutionGenerator.cs -/// -using Nuke.Common.ProjectModel; - -internal class Solution : Nuke.Common.ProjectModel.Solution -{ - private Nuke.Common.ProjectModel.Solution SolutionFolder => this; - public Project _build => SolutionFolder.GetProject("_build"); - public Project Nuke_Common => SolutionFolder.GetProject("Nuke.Common"); - public Project Nuke_Common_Tests => SolutionFolder.GetProject("Nuke.Common.Tests"); - public Project Nuke_Tooling_Generator => SolutionFolder.GetProject("Nuke.Tooling.Generator"); - public Project Nuke_GlobalTool => SolutionFolder.GetProject("Nuke.GlobalTool"); - public Project Nuke_GlobalTool_Tests => SolutionFolder.GetProject("Nuke.GlobalTool.Tests"); - public Project Nuke_MSBuildTasks => SolutionFolder.GetProject("Nuke.MSBuildTasks"); - public Project Nuke_Components => SolutionFolder.GetProject("Nuke.Components"); - public Project Nuke_SourceGenerators => SolutionFolder.GetProject("Nuke.SourceGenerators"); - public Project Nuke_SourceGenerators_Tests => SolutionFolder.GetProject("Nuke.SourceGenerators.Tests"); - public Project Nuke_Utilities => SolutionFolder.GetProject("Nuke.Utilities"); - public Project Nuke_Utilities_Tests => SolutionFolder.GetProject("Nuke.Utilities.Tests"); - public Project Nuke_Utilities_IO_Globbing => SolutionFolder.GetProject("Nuke.Utilities.IO.Globbing"); - public Project Nuke_Utilities_Text_Json => SolutionFolder.GetProject("Nuke.Utilities.Text.Json"); - public Project Nuke_Utilities_Net => SolutionFolder.GetProject("Nuke.Utilities.Net"); - public Project Nuke_Tooling => SolutionFolder.GetProject("Nuke.Tooling"); - public Project Nuke_Tooling_Tests => SolutionFolder.GetProject("Nuke.Tooling.Tests"); - public Project Nuke_SolutionModel => SolutionFolder.GetProject("Nuke.SolutionModel"); - public Project Nuke_SolutionModel_Tests => SolutionFolder.GetProject("Nuke.SolutionModel.Tests"); - public Project Nuke_ProjectModel => SolutionFolder.GetProject("Nuke.ProjectModel"); - public Project Nuke_ProjectModel_Tests => SolutionFolder.GetProject("Nuke.ProjectModel.Tests"); - public Project Nuke_Build_Shared => SolutionFolder.GetProject("Nuke.Build.Shared"); - public Project Nuke_Build => SolutionFolder.GetProject("Nuke.Build"); - public Project Nuke_Build_Tests => SolutionFolder.GetProject("Nuke.Build.Tests"); - public Project Nuke_Utilities_Text_Yaml => SolutionFolder.GetProject("Nuke.Utilities.Text.Yaml"); - public Project Nuke_Utilities_IO_Compression => SolutionFolder.GetProject("Nuke.Utilities.IO.Compression"); - public _misc misc => new(SolutionFolder.GetSolutionFolder("misc")); - - internal class _misc - { - private SolutionFolder SolutionFolder { get; } - - public _misc(SolutionFolder solutionFolder) => SolutionFolder = solutionFolder; - } -} \ No newline at end of file diff --git a/source/Nuke.SourceGenerators/Nuke.SourceGenerators.csproj b/source/Nuke.SourceGenerators/Nuke.SourceGenerators.csproj index 3d4e219a5..80b103a3d 100644 --- a/source/Nuke.SourceGenerators/Nuke.SourceGenerators.csproj +++ b/source/Nuke.SourceGenerators/Nuke.SourceGenerators.csproj @@ -19,14 +19,18 @@ + + + + @@ -41,8 +45,10 @@ + + diff --git a/source/Nuke.SourceGenerators/StronglyTypedSolutionGenerator.cs b/source/Nuke.SourceGenerators/StronglyTypedSolutionGenerator.cs index 04c351235..60649aafe 100644 --- a/source/Nuke.SourceGenerators/StronglyTypedSolutionGenerator.cs +++ b/source/Nuke.SourceGenerators/StronglyTypedSolutionGenerator.cs @@ -15,6 +15,8 @@ using Nuke.Common.IO; using Nuke.Common.ProjectModel; using Nuke.Common.Utilities; +using Nuke.Common.Utilities.Collections; +using Scriban; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Nuke.SourceGenerators; @@ -30,9 +32,97 @@ public void Execute(GeneratorExecutionContext context) { try { - if (GenerateSolutionSource(context.Compilation) is { Length: > 0 } generatedSource) + var allTypes = context.Compilation.Assembly.GlobalNamespace.GetAllTypes(); + var members = allTypes.SelectMany(x => x.GetMembers()) + .Where(x => x is IPropertySymbol or IFieldSymbol) + .Select(x => (Member: x, AttributeData: x.GetAttributeData("global::Nuke.Common.ProjectModel.SolutionAttribute"))) + .Where(x => x.AttributeData?.NamedArguments.SingleOrDefault(x => x.Key == "GenerateProjects").Value.Value as bool? ?? false) + .ToList(); + if (members.Count == 0) + return; + + var rootDirectory = GetRootDirectoryFrom(context.Compilation); + + foreach (var (member, attribute) in members) { - context.AddSource(nameof(StronglyTypedSolutionGenerator), generatedSource); + var solutionFile = attribute.ConstructorArguments.FirstOrDefault().Value is string { Length: > 0 } relativeSlnPath + ? rootDirectory / relativeSlnPath + : GetSolutionFileFromParametersFile(rootDirectory, member.Name); + var fancyNaming = attribute.NamedArguments.SingleOrDefault(x => x.Key == "FancyNames").Value.Value as bool? ?? false; + + var solution = solutionFile.ReadSolution(); + var hintName = member.Name + ".g.cs"; + var declaration = GetDeclaration(solution); + + // lang=csharp + context.AddSource(hintName, $""" + // + + using Microsoft.VisualStudio.SolutionPersistence.Model; + using Nuke.Common.ProjectModel; + using Nuke.Common.IO; + + {declaration} + """); + + continue; + + [CanBeNull] + string GetDeclaration(IProjectContainer container) + { + var prefix = new string('_', container.Descendants(x => x.Parent).Count() + 1); + var model = new + { + IsSolution = container is Solution, + SolutionReference = container is Solution ? "this" : "solution", + Name = GetEscapedName(container switch + { + Solution => member.Name, + SolutionFolder folder => prefix.Substring(1) + folder.Name, + _ => throw new ArgumentOutOfRangeException(nameof(container), container, null) + }), + Projects = container.Projects.OrderBy(x => x.Name).Select(x => new + { + x.Name, + EscapedName = GetEscapedName(x.Name), + }), + Folders = container.SolutionFolders.OrderBy(x => x.Name).Select(x => new + { + x.Name, + TypeName = prefix + GetEscapedName(x.Name), + EscapedName = GetEscapedName(x.Name), + }), + Declarations = container.SolutionFolders.OrderBy(x => x.Name).Select(GetDeclaration).ToArray(), + }; + + // lang=csharp + var template = Template.Parse(""" + {{~ if is_solution ~}} + internal class {{ name }}(SolutionModel model, AbsolutePath path) : Nuke.Common.ProjectModel.Solution(model, path) + {{~ else ~}} + internal class {{ name }}(SolutionFolderModel model, Nuke.Common.ProjectModel.Solution solution) : Nuke.Common.ProjectModel.SolutionFolder(model, solution) + {{~ end ~}} + { + {{~ for project in projects ~}} + public Nuke.Common.ProjectModel.Project {{ project.escaped_name }} => this.GetProject("{{ project.name }}"); + {{~ end ~}} + + {{~ for folder in folders ~}} + public {{ folder.type_name }} {{ folder.escaped_name }} => new(this.GetSolutionFolder("{{ folder.name }}").GetModel(), {{ solution_reference }}); + {{~ end ~}} + + {{~ for declaration in declarations ~}} + {{ declaration }} + {{~ end ~}} + } + """); + return template.Render(model); + + string GetEscapedName(string name) => name + // .Replace(".", fancyNaming ? "丨" : "_") + .Replace(".", fancyNaming ? "٠" : "_") + .ReplaceRegex(@"(^[\W^\d]|[\W])", _ => "_"); + } } } catch (Exception exception) @@ -47,108 +137,26 @@ public void Execute(GeneratorExecutionContext context) warningLevel: 0); context.ReportDiagnostic(diagnostic); } - } - - private string GenerateSolutionSource(Compilation compilation) - { - var allTypes = compilation.Assembly.GlobalNamespace.GetAllTypes(); - var members = allTypes.SelectMany(x => x.GetMembers()) - .Where(x => x is IPropertySymbol or IFieldSymbol) - .Select(x => (Member: x, AttributeData: x.GetAttributeData("global::Nuke.Common.ProjectModel.SolutionAttribute"))) - .Where(x => x.AttributeData?.NamedArguments.SingleOrDefault(x => x.Key == "GenerateProjects").Value.Value as bool? ?? false) - .ToList(); - - if (members.Count == 0) - return null; - var rootDirectory = GetRootDirectoryFrom(compilation); - var compilationUnit = CompilationUnit() - .AddUsings(UsingDirective(IdentifierName("Nuke.Common.ProjectModel"))); + return; - foreach (var (member, attributeData) in members) + static AbsolutePath GetSolutionFileFromParametersFile(AbsolutePath rootDirectory, string memberName) { - var solutionFile = attributeData.ConstructorArguments.FirstOrDefault().Value is string { Length: > 0 } relativeSlnPath - ? rootDirectory / relativeSlnPath - : GetSolutionFileFromParametersFile(rootDirectory, member.Name); - - var solution = SolutionSerializer.DeserializeFromFile(solutionFile); - - var classDeclaration = GetSolutionFolderDeclaration(member.Name, solution.SolutionFolders, solution.Projects, isSolution: true); - compilationUnit = compilationUnit - .AddMembers(member.ContainingType.ContainingNamespace.Equals(compilation.GlobalNamespace, SymbolEqualityComparer.Default) - ? NamespaceDeclaration(IdentifierName(member.ContainingType.ContainingNamespace.GetFullName())) - .AddMembers(classDeclaration) - : classDeclaration); + var parametersFile = Constants.GetDefaultParametersFile(rootDirectory); + Assert.FileExists(parametersFile); + var obj = JObject.Parse(File.ReadAllText(parametersFile)); + var solutionRelativePath = obj[memberName].NotNull($"Property '{memberName}' does not exist in '{parametersFile}'.").Value(); + return rootDirectory / solutionRelativePath.NotNull(); } - return compilationUnit - .WithLeadingTrivia(ParseLeadingTrivia($"/// {Environment.NewLine}")) - .NormalizeWhitespace() - .ToFullString(); - } - - [CanBeNull] - private ClassDeclarationSyntax GetSolutionFolderDeclaration( - string name, - IReadOnlyCollection solutionFolders, - IReadOnlyCollection projects, - bool isSolution = false, - int depth = 0) - { - string GetMemberName(string name) => name - .ReplaceRegex(@"(^[\W^\d]|[\W])", _ => "_") - .TrimToOne("_"); - - string GetSolutionFolderReferenceName(string name) - => $"{new string('_', depth + 1)}{GetMemberName(name)}"; - - string GetSolutionFolderTypeName(string name) - => $"{new string('_', depth)}{GetMemberName(name)}"; - - MemberDeclarationSyntax GetSolutionFolderPropertyDeclaration() - => isSolution - ? ParseMemberDeclaration($"private {typeof(Solution).FullName} SolutionFolder => this;") - : ParseMemberDeclaration("private SolutionFolder SolutionFolder { get; }"); - - MemberDeclarationSyntax GetSolutionFolderConstructorDeclaration() - => ParseMemberDeclaration($"public {GetSolutionFolderTypeName(name)}(SolutionFolder solutionFolder) => SolutionFolder = solutionFolder;"); - - MemberDeclarationSyntax GetProjectPropertyDeclaration(string name) - => ParseMemberDeclaration($@"public Project {GetMemberName(name)} => SolutionFolder.GetProject(""{name}"");"); - - MemberDeclarationSyntax GetSolutionFolderProperty(string name) - => ParseMemberDeclaration( - $@"public {GetSolutionFolderReferenceName(name)} {GetMemberName(name)} => new(SolutionFolder.GetSolutionFolder(""{name}""));"); - - return ClassDeclaration(isSolution ? name : GetSolutionFolderTypeName(name)) // TODO: check for multiple solution fields - .AddModifiers(Token(SyntaxKind.InternalKeyword)) - .When(isSolution, _ => _ - .AddBaseListTypes(SimpleBaseType(ParseTypeName(typeof(Solution).FullName.NotNull())))) - .AddMembers(GetSolutionFolderPropertyDeclaration()) - .When(!isSolution, _ => _ - .AddMembers(GetSolutionFolderConstructorDeclaration())) - .AddMembers(projects.Select(project => GetProjectPropertyDeclaration(project.Name)).ToArray()) - .AddMembers(solutionFolders.Select(x => GetSolutionFolderProperty(x.Name)).ToArray()) - .AddMembers(solutionFolders.Select(x => GetSolutionFolderDeclaration(x.Name, x.SolutionFolders, x.Projects, depth: depth + 1)) - .ToArray()); - } - - private static AbsolutePath GetSolutionFileFromParametersFile(AbsolutePath rootDirectory, string memberName) - { - var parametersFile = Constants.GetDefaultParametersFile(rootDirectory); - Assert.FileExists(parametersFile); - var jobject = JObject.Parse(File.ReadAllText(parametersFile)); - var solutionRelativePath = jobject[memberName].NotNull($"Property '{memberName}' does not exist in '{parametersFile}'.").Value(); - return rootDirectory / solutionRelativePath.NotNull(); - } - - private static AbsolutePath GetRootDirectoryFrom(Compilation compilation) - { - var syntaxPath = compilation.SyntaxTrees.First().FilePath; - var startDirectory = Path.GetDirectoryName(File.Exists(syntaxPath) - ? syntaxPath - // For testing only - : Directory.GetCurrentDirectory()); - return Constants.TryGetRootDirectoryFrom(startDirectory).NotNull(); + static AbsolutePath GetRootDirectoryFrom(Compilation compilation) + { + var syntaxPath = compilation.SyntaxTrees.First().FilePath; + var startDirectory = Path.GetDirectoryName(File.Exists(syntaxPath) + ? syntaxPath + // For testing only + : Directory.GetCurrentDirectory()); + return Constants.TryGetRootDirectoryFrom(startDirectory).NotNull(); + } } } diff --git a/source/Nuke.Tooling.Tests/Nuke.Tooling.Tests.csproj b/source/Nuke.Tooling.Tests/Nuke.Tooling.Tests.csproj index 2267c07f2..9c08ba666 100644 --- a/source/Nuke.Tooling.Tests/Nuke.Tooling.Tests.csproj +++ b/source/Nuke.Tooling.Tests/Nuke.Tooling.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 diff --git a/source/Nuke.Tooling/Nuke.Tooling.csproj b/source/Nuke.Tooling/Nuke.Tooling.csproj index 6510b6a01..782257cb3 100644 --- a/source/Nuke.Tooling/Nuke.Tooling.csproj +++ b/source/Nuke.Tooling/Nuke.Tooling.csproj @@ -1,7 +1,7 @@ - net8.0;netstandard2.0 + net10.0;netstandard2.0 diff --git a/source/Nuke.Utilities.Tests/Nuke.Utilities.Tests.csproj b/source/Nuke.Utilities.Tests/Nuke.Utilities.Tests.csproj index 29c7bc89a..0ebbfcecb 100644 --- a/source/Nuke.Utilities.Tests/Nuke.Utilities.Tests.csproj +++ b/source/Nuke.Utilities.Tests/Nuke.Utilities.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 diff --git a/source/Nuke.Utilities/Nuke.Utilities.csproj b/source/Nuke.Utilities/Nuke.Utilities.csproj index a6355511a..0f9e8b838 100644 --- a/source/Nuke.Utilities/Nuke.Utilities.csproj +++ b/source/Nuke.Utilities/Nuke.Utilities.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net8.0 + netstandard2.0;net10.0