diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index dd76b6d118..831d039b64 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "cake.tool": { - "version": "2.3.0", + "version": "3.0.0", "commands": [ "dotnet-cake" ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f48e8560c..fd179be432 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,7 +68,7 @@ Any new code should also have reasonable unit test coverage. you talk about a feature you would like to see (or a bug), and why it should be in Cake. * If approved through the GitHub discussions, ensure an accompanying GitHub issue is created with information and a link back to the discussion. - * Once you get a nod from one of the [Cake Team](https://github.com/cake-build?tab=members), you can start on the feature. + * Once you get a nod from one of the [Cake Team](https://github.com/orgs/cake-build/people), you can start on the feature. * Alternatively, if a feature is on the issues list with the [Up For Grabs](https://github.com/cake-build/cake/labels/up-for-grabs) label, it is open for a community member (contributor) to patch. You should comment that you are signing up for it on diff --git a/ReleaseNotes.md b/ReleaseNotes.md index aa95cb9e37..624d7c9e52 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,3 +1,23 @@ +### New in 3.1.0 (Released 2023/07/09) + +* 4122 Call multiple tasks from CLI in Frosting. +* 4092 Add support for getting the user's home directory in the Cake Environment. +* 4184 Update Autofac to 7.0.1. +* 4183 Update System.Reflection.Metadata to 7.0.2. +* 4182 Update Basic.Reference.Assemblies.Net60/Net70 to 1.4.2. +* 4181 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.6.0. +* 4170 Update NuGet.* to v6.6.1. +* 4138 Upgrade Spectre.Console to v0.46.0. +* 4109 Add PublishReadyToRun to DotNetRestoreSettings. +* 4107 DotNetPublishSettings is missing a way to set the --os option. +* 4090 Update Microsoft.CodeAnalysis.CSharp.Scripting to v4.4.0. +* 4087 Update Newtonsoft.Json to v13.0.2. +* 4086 Update Autofac to v6.5.0. +* 4085 Update NuGet.* to v6.4.0. +* 1317 CleanDirectory does not clean readonly files. +* 4095 Fix broken link to Cake Team on CONTRIBUTING.md. +* 4128 Inconsistent determination of positional Argument when using context.Arguments versus context.Argument. + ### New in 3.0.0 (Released 2022/11/08) * 4046 Add typed data context CakeTaskBuilder.Finally overload. diff --git a/build.cake b/build.cake index d1a6e286db..7da4bbc5a0 100644 --- a/build.cake +++ b/build.cake @@ -1,11 +1,10 @@ // Install addins. -#addin "nuget:https://api.nuget.org/v3/index.json?package=Cake.Twitter&version=2.0.0" -#addin "nuget:https://api.nuget.org/v3/index.json?package=Cake.Gitter&version=2.0.0" +#addin "nuget:https://api.nuget.org/v3/index.json?package=Cake.Twitter&version=3.0.0" // Install .NET Core Global tools. -#tool "dotnet:https://api.nuget.org/v3/index.json?package=GitVersion.Tool&version=5.10.3" -#tool "dotnet:https://api.nuget.org/v3/index.json?package=SignClient&version=1.3.155" -#tool "dotnet:https://api.nuget.org/v3/index.json?package=GitReleaseManager.Tool&version=0.12.1" +#tool "dotnet:https://api.nuget.org/v3/index.json?package=GitVersion.Tool&version=5.12.0" +#tool "dotnet:https://api.nuget.org/v3/index.json?package=GitReleaseManager.Tool&version=0.13.0" +#tool "dotnet:https://api.nuget.org/v3/index.json?package=sign&version=0.9.1-beta.23274.1&prerelease" // Load other scripts. #load "./build/parameters.cake" @@ -30,6 +29,11 @@ Setup(context => parameters.Version.CakeVersion, parameters.IsTagged); + if (parameters.ShouldSignPackages && !parameters.CodeSigning.HasCredentials) + { + throw new CakeException("Code signing credentials are missing."); + } + foreach(var assemblyInfo in GetFiles("./src/**/AssemblyInfo.cs")) { CreateAssemblyInfo( @@ -54,25 +58,6 @@ Teardown((context, parameters) => { TwitterSendTweet(parameters.Twitter.ConsumerKey, parameters.Twitter.ConsumerSecret, parameters.Twitter.AccessToken, parameters.Twitter.AccessTokenSecret, message); } - - if(parameters.CanPostToGitter) - { - var gitterMessage = $"@/all {message}"; - - var postMessageResult = Gitter.Chat.PostMessage( - message: gitterMessage, - messageSettings: new GitterChatMessageSettings { Token = parameters.Gitter.Token, RoomId = parameters.Gitter.RoomId} - ); - - if (postMessageResult.Ok) - { - Information("Message {0} successfully sent", postMessageResult.TimeStamp); - } - else - { - Error("Failed to send message: {0}", postMessageResult.Error); - } - } } } @@ -165,52 +150,44 @@ Task("Create-NuGet-Packages") Task("Sign-Binaries") .IsDependentOn("Create-NuGet-Packages") - .WithCriteria((context, parameters) => - (parameters.ShouldPublish && !parameters.SkipSigning) || - StringComparer.OrdinalIgnoreCase.Equals(EnvironmentVariable("SIGNING_TEST"), "True")) - .Does((context, parameters) => + .WithCriteria(static (context, parameters) => parameters.ShouldSignPackages) + .Does(static (context, parameters) => { - // Get the secret. - var secret = EnvironmentVariable("SIGNING_SECRET"); - if(string.IsNullOrWhiteSpace(secret)) { - throw new InvalidOperationException("Could not resolve signing secret."); - } - // Get the user. - var user = EnvironmentVariable("SIGNING_USER"); - if(string.IsNullOrWhiteSpace(user)) { - throw new InvalidOperationException("Could not resolve signing user."); - } - - var settings = File("./signclient.json"); - var filter = File("./signclient.filter"); - // Get the files to sign. - var files = GetFiles(string.Concat(parameters.Paths.Directories.NuGetRoot, "/", "*.nupkg")); - - foreach(var file in files) - { - Information("Signing {0}...", file.FullPath); + var files = context.GetFiles(string.Concat(parameters.Paths.Directories.NuGetRoot, "/", "*.nupkg")); + var commandSettings = new CommandSettings{ + ToolExecutableNames = new [] { "sign", "sign.exe" }, + ToolName = "sign", + ToolPath = parameters.Paths.SignClientPath.FullPath + }; + + Parallel.ForEach( + files, + file => { + context.Information("Signing {0}...", file.FullPath); // Build the argument list. var arguments = new ProcessArgumentBuilder() - .Append("sign") - .AppendSwitchQuoted("-c", MakeAbsolute(settings.Path).FullPath) - .AppendSwitchQuoted("-i", MakeAbsolute(file).FullPath) - .AppendSwitchQuoted("-f", MakeAbsolute(filter).FullPath) - .AppendSwitchQuotedSecret("-s", secret) - .AppendSwitchQuotedSecret("-r", user) - .AppendSwitchQuoted("-n", "Cake") - .AppendSwitchQuoted("-d", "Cake (C# Make) is a cross platform build automation system.") - .AppendSwitchQuoted("-u", "https://cakebuild.net"); - - // Sign the binary. - var result = StartProcess(parameters.Paths.SignClientPath.FullPath, new ProcessSettings { Arguments = arguments }); - if(result != 0) - { - // We should not recover from this. - throw new InvalidOperationException("Signing failed!"); - } - } + .Append("code") + .Append("azure-key-vault") + .AppendQuoted(file.FullPath) + .AppendSwitchQuoted("--file-list", parameters.Paths.SignFilterPath.FullPath) + .AppendSwitchQuoted("--publisher-name", "Cake") + .AppendSwitchQuoted("--description", "Cake (C# Make) is a cross platform build automation system.") + .AppendSwitchQuoted("--description-url", "https://cakebuild.net") + .AppendSwitchQuotedSecret("--azure-key-vault-tenant-id", parameters.CodeSigning.SignTenantId) + .AppendSwitchQuotedSecret("--azure-key-vault-client-id", parameters.CodeSigning.SignClientId) + .AppendSwitchQuotedSecret("--azure-key-vault-client-secret", parameters.CodeSigning.SignClientSecret) + .AppendSwitchQuotedSecret("--azure-key-vault-certificate", parameters.CodeSigning.SignKeyVaultCertificate) + .AppendSwitchQuotedSecret("--azure-key-vault-url", parameters.CodeSigning.SignKeyVaultUrl); + + context.Command( + commandSettings, + arguments + ); + + context.Information("Done signing {0}.", file.FullPath); + }); }); Task("Upload-AppVeyor-Artifacts") diff --git a/build/credentials.cake b/build/credentials.cake index 3cfdb1ffe0..451687681d 100644 --- a/build/credentials.cake +++ b/build/credentials.cake @@ -1,3 +1,35 @@ +public record CodeSigningCredentials( + string SignTenantId, + string SignClientId, + string SignClientSecret, + string SignKeyVaultCertificate, + string SignKeyVaultUrl +) +{ + public bool HasCredentials + { + get + { + return + !string.IsNullOrEmpty(SignTenantId) && + !string.IsNullOrEmpty(SignClientId) && + !string.IsNullOrEmpty(SignClientSecret) && + !string.IsNullOrEmpty(SignKeyVaultCertificate) && + !string.IsNullOrEmpty(SignKeyVaultUrl); + } + } + + public static CodeSigningCredentials GetCodeSigningCredentials(ICakeContext context) + { + return new CodeSigningCredentials( + SignTenantId: context.EnvironmentVariable("SIGN_TENANT_ID"), + SignClientId: context.EnvironmentVariable("SIGN_CLIENT_ID"), + SignClientSecret: context.EnvironmentVariable("SIGN_CLIENT_SECRET"), + SignKeyVaultCertificate: context.EnvironmentVariable("SIGN_KEYVAULT_CERTIFICATE"), + SignKeyVaultUrl: context.EnvironmentVariable("SIGN_KEYVAULT_URL")); + } +} + public record BuildCredentials(string Token) { public static BuildCredentials GetGitHubCredentials(ICakeContext context) @@ -23,14 +55,3 @@ public record TwitterCredentials( context.EnvironmentVariable("TWITTER_ACCESS_TOKEN_SECRET")); } } - -public record GitterCredentials(string Token, string RoomId) -{ - public static GitterCredentials GetGitterCredentials(ICakeContext context) - { - return new GitterCredentials( - context.EnvironmentVariable("GITTER_TOKEN"), - context.EnvironmentVariable("GITTER_ROOM_ID") - ); - } -} diff --git a/build/parameters.cake b/build/parameters.cake index 41add05c47..22d502daa0 100644 --- a/build/parameters.cake +++ b/build/parameters.cake @@ -22,13 +22,13 @@ public class BuildParameters public bool SkipSigning { get; } public BuildCredentials GitHub { get; } public TwitterCredentials Twitter { get; } - public GitterCredentials Gitter { get; } public ReleaseNotes ReleaseNotes { get; } public BuildVersion Version { get; set; } public BuildPaths Paths { get; } public BuildPackages Packages { get; } public bool PublishingError { get; set; } public DotNetMSBuildSettings MSBuildSettings { get; } + public CodeSigningCredentials CodeSigning { get; } public bool ShouldPublish { @@ -48,6 +48,8 @@ public class BuildParameters } } + + public bool ShouldSignPackages { get; } public bool CanPostToTwitter { get @@ -59,14 +61,6 @@ public class BuildParameters } } - public bool CanPostToGitter - { - get - { - return !string.IsNullOrEmpty(Gitter.Token) && !string.IsNullOrEmpty(Gitter.RoomId); - } - } - public BuildParameters (ISetupContext context) { if (context == null) @@ -89,7 +83,7 @@ public class BuildParameters IsTagged = IsBuildTagged(buildSystem); GitHub = BuildCredentials.GetGitHubCredentials(context); Twitter = TwitterCredentials.GetTwitterCredentials(context); - Gitter = GitterCredentials.GetGitterCredentials(context); + CodeSigning = CodeSigningCredentials.GetCodeSigningCredentials(context); ReleaseNotes = context.ParseReleaseNotes("./ReleaseNotes.md"); IsPublishBuild = IsPublishing(context.TargetTask.Name); IsReleaseBuild = IsReleasing(context.TargetTask.Name); @@ -129,6 +123,14 @@ public class BuildParameters { MSBuildSettings.WithProperty("TemplateVersion", Version.SemVersion); } + + + ShouldSignPackages = (!SkipSigning && ShouldPublish) + || + StringComparer.OrdinalIgnoreCase.Equals( + context.EnvironmentVariable("SIGNING_TEST"), + "True" + ); } private static bool IsBuildTagged(BuildSystem buildSystem) diff --git a/build/paths.cake b/build/paths.cake index bed3f9b0ec..f7b2ee1f22 100644 --- a/build/paths.cake +++ b/build/paths.cake @@ -1,6 +1,7 @@ public record BuildPaths( BuildDirectories Directories, - FilePath SignClientPath + FilePath SignClientPath, + FilePath SignFilterPath ) { public static BuildPaths GetPaths( @@ -38,11 +39,20 @@ public record BuildPaths( nugetRoot, integrationTestsBinTool); - var signClientPath = context.Tools.Resolve("SignClient.exe") ?? context.Tools.Resolve("SignClient") ?? throw new Exception("Failed to locate sign tool"); + var signClientPath = context.Tools.Resolve("sign.exe") + ?? context.Tools.Resolve("sign") + ?? ( + context.IsRunningOnWindows() + ? throw new Exception("Failed to locate sign tool") + : null + ); + + var signFilterPath = context.MakeAbsolute(context.File("./build/signclient.filter")); return new BuildPaths( Directories: buildDirectories, - SignClientPath: signClientPath + SignClientPath: signClientPath, + SignFilterPath: signFilterPath ); } } diff --git a/signclient.filter b/build/signclient.filter similarity index 100% rename from signclient.filter rename to build/signclient.filter diff --git a/global.json b/global.json index 2afa50f519..ecd662c276 100644 --- a/global.json +++ b/global.json @@ -3,7 +3,7 @@ "src" ], "sdk": { - "version": "7.0.100", + "version": "7.0.305", "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/signclient.json b/signclient.json deleted file mode 100644 index 3276a45de5..0000000000 --- a/signclient.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "SignClient": { - "AzureAd": { - "AADInstance": "https://login.microsoftonline.com/", - "ClientId": "c248d68a-ba6f-4aa9-8a68-71fe872063f8", - "TenantId": "16076fdc-fcc1-4a15-b1ca-32c9a255900e" - }, - "Service": { - "Url": "https://codesign.dotnetfoundation.org/", - "ResourceId": "https://SignService/3c30251f-36f3-490b-a955-520addb85001" - } - } -} \ No newline at end of file diff --git a/src/Cake.Cli/Cake.Cli.csproj b/src/Cake.Cli/Cake.Cli.csproj index 5c3a9bf711..f32bfeee7f 100644 --- a/src/Cake.Cli/Cake.Cli.csproj +++ b/src/Cake.Cli/Cake.Cli.csproj @@ -18,8 +18,8 @@ - - - + + + \ No newline at end of file diff --git a/src/Cake.Common.Tests/Cake.Common.Tests.csproj b/src/Cake.Common.Tests/Cake.Common.Tests.csproj index a8cdea4c6f..0d1b477ca8 100644 --- a/src/Cake.Common.Tests/Cake.Common.Tests.csproj +++ b/src/Cake.Common.Tests/Cake.Common.Tests.csproj @@ -16,14 +16,14 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - - + + diff --git a/src/Cake.Common.Tests/Fixtures/IO/FileSystemFixture.cs b/src/Cake.Common.Tests/Fixtures/IO/FileSystemFixture.cs index 914de8de4f..62fe44bd30 100644 --- a/src/Cake.Common.Tests/Fixtures/IO/FileSystemFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/IO/FileSystemFixture.cs @@ -47,6 +47,7 @@ private static FakeFileSystem CreateFileSystem(ICakeEnvironment environment) fileSystem.CreateFile("/Temp/HasFiles/A.txt"); fileSystem.CreateFile("/HasReadonly/Readonly.txt", FileAttributes.ReadOnly); fileSystem.CreateFile("/HasReadonly/Not-Readonly.txt"); + fileSystem.CreateFile("/HasReadonly/Hidden.txt").Hide(); return fileSystem; } } diff --git a/src/Cake.Common.Tests/Unit/IO/DirectoryAliasesTests.cs b/src/Cake.Common.Tests/Unit/IO/DirectoryAliasesTests.cs index 0598b82e88..64d043be14 100644 --- a/src/Cake.Common.Tests/Unit/IO/DirectoryAliasesTests.cs +++ b/src/Cake.Common.Tests/Unit/IO/DirectoryAliasesTests.cs @@ -146,6 +146,65 @@ public void Should_Delete_Directories_In_Provided_Directory() Assert.Empty(fixture.FileSystem.GetDirectory(directory).GetDirectories("*", SearchScope.Recursive)); } + [Fact] + public void Should_Throw_When_Deleting_Readonly_Files_In_Provided_Directory_If_Not_Forced() + { + // Given + var directory = new DirectoryPath("/HasReadonly"); + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + // When + var result = Record.Exception(() => DirectoryAliases.CleanDirectory(context, directory, new CleanDirectorySettings { Force = false })); + + // Then + AssertEx.IsExceptionWithMessage(result, "Cannot delete readonly file '/HasReadonly/Readonly.txt'."); + } + + [Fact] + public void Should_Delete_Readonly_Files_In_Provided_Directory_If_Forced() + { + // Given + var directory = new DirectoryPath("/HasReadonly"); + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + // When + DirectoryAliases.CleanDirectory(context, directory, new CleanDirectorySettings { Force = true }); + + // Then + Assert.Empty(fixture.FileSystem.GetDirectory(directory).GetDirectories("*", SearchScope.Recursive)); + } + + [Fact] + public void Should_Delete_Readonly_Files_Respecting_Predicate_In_Provided_Directory_If_Forced() + { + // Given + var directory = new DirectoryPath("/HasReadonly"); + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + Func wherePredicate = entry => !entry.Hidden; + context.FileSystem.Returns(fixture.FileSystem); + var filesNotMatchingPredicate = fixture + .FileSystem + .GetDirectory(directory) + .GetFiles("*", SearchScope.Recursive) + .Where(entry => !wherePredicate(entry)) + .ToArray(); + + // When + DirectoryAliases.CleanDirectory(context, directory, wherePredicate, new CleanDirectorySettings { Force = true }); + + // Then + Assert.Empty(fixture.FileSystem.GetDirectory(directory).GetFiles("*", SearchScope.Recursive).Where(wherePredicate)); + Assert.Equal(filesNotMatchingPredicate, fixture.FileSystem.GetDirectory(directory).GetFiles("*", SearchScope.Recursive)); + + Assert.Single(filesNotMatchingPredicate); + Assert.Equal("/HasReadonly/Hidden.txt", filesNotMatchingPredicate[0].Path.FullPath); + } + [Fact] public void Should_Delete_Directories_Respecting_Predicate_In_Provided_Directory() { @@ -325,6 +384,41 @@ public void Should_Create_Directories_If_Missing() // Then Assert.True(fixture.FileSystem.Exist((DirectoryPath)"/NonExisting")); } + + [Fact] + public void Should_Throw_When_Deleting_With_Readonly_Files_If_Not_Force() + { + // Given + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + var paths = new DirectoryPath[] { "/HasReadonly" }; + + // When + var result = Record.Exception(() => DirectoryAliases.CleanDirectories(context, paths, new CleanDirectorySettings() { Force = false })); + + // Then + Assert.IsType(result); + Assert.Equal("Cannot delete readonly file '/HasReadonly/Readonly.txt'.", result?.Message); + } + + [Fact] + public void Should_Delete_Directories_With_Readonly_Files_If_Force() + { + // Given + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + var paths = new DirectoryPath[] { "/HasReadonly" }; + + // When + DirectoryAliases.CleanDirectories(context, paths, new CleanDirectorySettings() { Force = true }); + + // Then + Assert.Empty(fixture.FileSystem.GetDirectory("/HasReadonly").GetDirectories("*", SearchScope.Recursive)); + } } public sealed class WithStrings @@ -418,6 +512,41 @@ public void Should_Create_Directories_If_Missing() // Then Assert.True(fixture.FileSystem.Exist((DirectoryPath)"/NonExisting")); } + + [Fact] + public void Should_Throw_When_Deleting_With_Readonly_Files_If_Not_Force() + { + // Given + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + var paths = new[] { "/HasReadonly" }; + + // When + var result = Record.Exception(() => DirectoryAliases.CleanDirectories(context, paths, new CleanDirectorySettings() { Force = false })); + + // Then + Assert.IsType(result); + Assert.Equal("Cannot delete readonly file '/HasReadonly/Readonly.txt'.", result?.Message); + } + + [Fact] + public void Should_Delete_Directories_With_Readonly_Files_If_Force() + { + // Given + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + var paths = new[] { "/HasReadonly" }; + + // When + DirectoryAliases.CleanDirectories(context, paths, new CleanDirectorySettings() { Force = true }); + + // Then + Assert.Empty(fixture.FileSystem.GetDirectory("/HasReadonly").GetDirectories("*", SearchScope.Recursive)); + } } } diff --git a/src/Cake.Common.Tests/Unit/ReleaseNotesParserTests.cs b/src/Cake.Common.Tests/Unit/ReleaseNotesParserTests.cs index 364f12adf5..418c142e97 100644 --- a/src/Cake.Common.Tests/Unit/ReleaseNotesParserTests.cs +++ b/src/Cake.Common.Tests/Unit/ReleaseNotesParserTests.cs @@ -79,7 +79,7 @@ public void Should_Parse_Release_Note_Text() var result = parser.Parse(content); // Then - Assert.Equal(1, result[0].Notes.Count); + Assert.Single(result[0].Notes); Assert.Equal("Line 1", result[0].Notes[0]); } @@ -94,7 +94,7 @@ public void Should_Remove_Bullets_From_Release_Note_Text() var result = parser.Parse(content); // Then - Assert.Equal(1, result[0].Notes.Count); + Assert.Single(result[0].Notes); Assert.Equal("Line 1", result[0].Notes[0]); } @@ -143,7 +143,7 @@ public void Should_Remove_Empty_Lines_From_Release_Note_Text() var result = parser.Parse(content); // Then - Assert.Equal(1, result[0].Notes.Count); + Assert.Single(result[0].Notes); Assert.Equal("Line 1", result[0].Notes[0]); } @@ -239,7 +239,7 @@ public void Should_Parse_Release_Note_Text() var result = parser.Parse(content); // Then - Assert.Equal(1, result[0].Notes.Count); + Assert.Single(result[0].Notes); Assert.Equal("Line 1", result[0].Notes[0]); } diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Publish/DotNetPublisherTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Publish/DotNetPublisherTests.cs index 4b445cd243..a051bee08c 100644 --- a/src/Cake.Common.Tests/Unit/Tools/DotNet/Publish/DotNetPublisherTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Publish/DotNetPublisherTests.cs @@ -289,6 +289,20 @@ public void Should_Add_EnableCompressionInSingleFile() // Then Assert.Equal("publish -p:EnableCompressionInSingleFile=true", result.Args); } + + [Fact] + public void Should_Add_Os() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.OS = "linux"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish --os linux", result.Args); + } } } } diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Restore/DotNetRestorerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Restore/DotNetRestorerTests.cs index d9941bd859..a86e6f1d9b 100644 --- a/src/Cake.Common.Tests/Unit/Tools/DotNet/Restore/DotNetRestorerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Restore/DotNetRestorerTests.cs @@ -216,6 +216,20 @@ public void Should_Add_ForceEvaluate() // Then Assert.Equal($@"restore --force-evaluate", result.Args); } + + [Fact] + public void Should_Add_PublishReadyToRun() + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.Settings.PublishReadyToRun = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("restore -p:PublishReadyToRun=true", result.Args); + } } } } \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildSettingsExtensionsTests.cs b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildSettingsExtensionsTests.cs index 4fe7721a1e..2d9b2c1549 100644 --- a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildSettingsExtensionsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildSettingsExtensionsTests.cs @@ -945,7 +945,7 @@ public void Should_Set_WarningsAsError() // Then Assert.True(settings.WarningsAsError); - Assert.Equal(0, settings.WarningsAsErrorCodes.Count); + Assert.Empty(settings.WarningsAsErrorCodes); } [Fact] @@ -959,7 +959,7 @@ public void Should_Add_Codes() // Then Assert.True(settings.WarningsAsError); - Assert.Equal(1, settings.WarningsAsErrorCodes.Count); + Assert.Single(settings.WarningsAsErrorCodes); Assert.Equal("12345", settings.WarningsAsErrorCodes.First()); } @@ -989,7 +989,7 @@ public void Should_Add_Codes() settings.WithWarningsAsMessage("12345"); // Then - Assert.Equal(1, settings.WarningsAsMessageCodes.Count); + Assert.Single(settings.WarningsAsMessageCodes); Assert.Equal("12345", settings.WarningsAsMessageCodes.First()); } diff --git a/src/Cake.Common/ArgumentAliases.cs b/src/Cake.Common/ArgumentAliases.cs index 42eb8bca4d..b6a2636582 100644 --- a/src/Cake.Common/ArgumentAliases.cs +++ b/src/Cake.Common/ArgumentAliases.cs @@ -78,7 +78,7 @@ public static T Argument(this ICakeContext context, string name) throw new ArgumentNullException(nameof(context)); } - var value = context.Arguments.GetArguments(name).FirstOrDefault(); + var value = context.Arguments.GetArguments(name).LastOrDefault(); if (value == null) { const string format = "Argument '{0}' was not set."; @@ -248,7 +248,7 @@ public static T Argument(this ICakeContext context, string name, T defaultVal throw new ArgumentNullException(nameof(context)); } - var value = context.Arguments.GetArguments(name)?.FirstOrDefault(); + var value = context.Arguments.GetArguments(name)?.LastOrDefault(); return value == null ? defaultValue : Convert(value); diff --git a/src/Cake.Common/IO/CleanDirectorySettings.cs b/src/Cake.Common/IO/CleanDirectorySettings.cs new file mode 100644 index 0000000000..d2d9234553 --- /dev/null +++ b/src/Cake.Common/IO/CleanDirectorySettings.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.IO +{ + /// + /// Contains settings used by CleanDirectory. + /// + public class CleanDirectorySettings + { + /// + /// Gets or sets a value indicating whether to clean read-only files if set to true. + /// + /// It is set to false by default. + /// + /// + public bool Force { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/IO/DirectoryAliases.cs b/src/Cake.Common/IO/DirectoryAliases.cs index b45fae03a3..8f6560033f 100644 --- a/src/Cake.Common/IO/DirectoryAliases.cs +++ b/src/Cake.Common/IO/DirectoryAliases.cs @@ -163,6 +163,31 @@ public static void CleanDirectories(this ICakeContext context, GlobPattern patte CleanDirectories(context, directories); } + /// + /// Cleans the directories matching the specified pattern. + /// Cleaning the directory will remove all its content but not the directory itself. + /// + /// + /// + /// CleanDirectories("./src/**/bin/debug", new CleanDirectorySettings() { Force = true }); + /// + /// + /// The context. + /// The pattern to match. + /// The clean settings. + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + public static void CleanDirectories(this ICakeContext context, GlobPattern pattern, CleanDirectorySettings settings) + { + var directories = context.GetDirectories(pattern); + if (directories.Count == 0) + { + context.Log.Verbose("The provided pattern did not match any directories."); + return; + } + CleanDirectories(context, directories, settings); + } + /// /// Cleans the directories matching the specified pattern. /// Cleaning the directory will remove all its content but not the directory itself. @@ -192,6 +217,36 @@ public static void CleanDirectories(this ICakeContext context, GlobPattern patte CleanDirectories(context, directories); } + /// + /// Cleans the directories matching the specified pattern. + /// Cleaning the directory will remove all its content but not the directory itself. + /// + /// + /// + /// Func<IFileSystemInfo, bool> exclude_node_modules = + /// fileSystemInfo=>!fileSystemInfo.Path.FullPath.EndsWith( + /// "node_modules", + /// StringComparison.OrdinalIgnoreCase); + /// CleanDirectories("./src/**/bin/debug", exclude_node_modules, new CleanDirectorySettings() { Force = true }); + /// + /// + /// The context. + /// The pattern to match. + /// The predicate used to filter directories based on file system information. + /// The clean settings. + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + public static void CleanDirectories(this ICakeContext context, GlobPattern pattern, Func predicate, CleanDirectorySettings settings) + { + var directories = context.GetDirectories(pattern, new GlobberSettings { Predicate = predicate }); + if (directories.Count == 0) + { + context.Log.Verbose("The provided pattern did not match any directories."); + return; + } + CleanDirectories(context, directories, settings); + } + /// /// Cleans the specified directories. /// Cleaning a directory will remove all its content but not the directory itself. @@ -218,6 +273,33 @@ public static void CleanDirectories(this ICakeContext context, IEnumerable + /// Cleans the specified directories. + /// Cleaning a directory will remove all its content but not the directory itself. + /// + /// + /// + /// var directoriesToClean = GetDirectories("./src/**/bin/"); + /// CleanDirectories(directoriesToClean, new CleanDirectorySettings() { Force = true }); + /// + /// + /// The context. + /// The directory paths. + /// The clean settings. + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + public static void CleanDirectories(this ICakeContext context, IEnumerable directories, CleanDirectorySettings settings) + { + if (directories == null) + { + throw new ArgumentNullException(nameof(directories)); + } + foreach (var directory in directories) + { + CleanDirectory(context, directory, settings); + } + } + /// /// Cleans the specified directories. /// Cleaning a directory will remove all its content but not the directory itself. @@ -248,6 +330,37 @@ public static void CleanDirectories(this ICakeContext context, IEnumerable + /// Cleans the specified directories. + /// Cleaning a directory will remove all its content but not the directory itself. + /// + /// + /// + /// var directoriesToClean = new []{ + /// "./src/Cake/obj", + /// "./src/Cake.Common/obj" + /// }; + /// CleanDirectories(directoriesToClean, new CleanDirectorySettings() { Force = true }); + /// + /// + /// The context. + /// The directory paths. + /// The clean settings. + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + public static void CleanDirectories(this ICakeContext context, IEnumerable directories, CleanDirectorySettings settings) + { + if (directories == null) + { + throw new ArgumentNullException(nameof(directories)); + } + var paths = directories.Select(p => new DirectoryPath(p)); + foreach (var directory in paths) + { + CleanDirectory(context, directory, settings); + } + } + /// /// Cleans the specified directory. /// @@ -265,6 +378,26 @@ public static void CleanDirectory(this ICakeContext context, DirectoryPath path) DirectoryCleaner.Clean(context, path); } + /// + /// Cleans the specified directory. + /// + /// + /// + /// CleanDirectory("./src/Cake.Common/obj", new CleanDirectorySettings { + /// Force = true + /// }); + /// + /// + /// The context. + /// The directory path. + /// The clean settings. + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + public static void CleanDirectory(this ICakeContext context, DirectoryPath path, CleanDirectorySettings settings) + { + DirectoryCleaner.Clean(context, path, settings); + } + /// /// Cleans the specified directory. /// @@ -283,6 +416,27 @@ public static void CleanDirectory(this ICakeContext context, DirectoryPath path, DirectoryCleaner.Clean(context, path, predicate); } + /// + /// Cleans the specified directory. + /// + /// + /// + /// CleanDirectory("./src/Cake.Common/obj", fileSystemInfo=>!fileSystemInfo.Hidden, new CleanDirectorySettings { + /// Force = true + /// }); + /// + /// + /// The context. + /// The directory path. + /// Predicate used to determine which files/directories should get deleted. + /// The clean settings. + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + public static void CleanDirectory(this ICakeContext context, DirectoryPath path, Func predicate, CleanDirectorySettings settings) + { + DirectoryCleaner.Clean(context, path, predicate, settings); + } + /// /// Creates the specified directory. /// diff --git a/src/Cake.Common/IO/DirectoryCleaner.cs b/src/Cake.Common/IO/DirectoryCleaner.cs index 21644f0790..5f3a084197 100644 --- a/src/Cake.Common/IO/DirectoryCleaner.cs +++ b/src/Cake.Common/IO/DirectoryCleaner.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.IO; using Cake.Core; using Cake.Core.Diagnostics; using Cake.Core.IO; @@ -13,10 +14,20 @@ internal static class DirectoryCleaner { public static void Clean(ICakeContext context, DirectoryPath path) { - Clean(context, path, null); + Clean(context, path, null, null); + } + + public static void Clean(ICakeContext context, DirectoryPath path, CleanDirectorySettings settings) + { + Clean(context, path, null, settings); } public static void Clean(ICakeContext context, DirectoryPath path, Func predicate) + { + Clean(context, path, predicate, null); + } + + public static void Clean(ICakeContext context, DirectoryPath path, Func predicate, CleanDirectorySettings settings) { if (context == null) { @@ -43,10 +54,11 @@ public static void Clean(ICakeContext context, DirectoryPath path, Func true); - CleanDirectory(root, predicate, 0); + settings = settings ?? new CleanDirectorySettings(); + CleanDirectory(root, predicate, 0, settings); } - private static bool CleanDirectory(IDirectory root, Func predicate, int level) + private static bool CleanDirectory(IDirectory root, Func predicate, int level, CleanDirectorySettings settings) { var shouldDeleteRoot = predicate(root); @@ -54,7 +66,7 @@ private static bool CleanDirectory(IDirectory root, Func var directories = root.GetDirectories("*", SearchScope.Current); foreach (var directory in directories) { - if (!CleanDirectory(directory, predicate, level + 1)) + if (!CleanDirectory(directory, predicate, level + 1, settings)) { // Since the child directory reported it shouldn't be // removed, we should not remove the current directory either. @@ -68,6 +80,12 @@ private static bool CleanDirectory(IDirectory root, Func { if (predicate(file)) { + if (settings.Force) + { + // Remove the ReadOnly attribute on file (if set) + file.Attributes &= ~FileAttributes.ReadOnly; + } + file.Delete(); } else diff --git a/src/Cake.Common/Tools/DotNet/Publish/DotNetPublishSettings.cs b/src/Cake.Common/Tools/DotNet/Publish/DotNetPublishSettings.cs index 79f9862a77..00e47aa990 100644 --- a/src/Cake.Common/Tools/DotNet/Publish/DotNetPublishSettings.cs +++ b/src/Cake.Common/Tools/DotNet/Publish/DotNetPublishSettings.cs @@ -170,6 +170,16 @@ public class DotNetPublishSettings : DotNetSettings /// public ICollection Sources { get; set; } = new List(); + /// + /// Gets or sets the target operating system (OS). + /// This is a shorthand syntax for setting the Runtime Identifier (RID), where the provided value is combined with the default RID. + /// If you use this option, don't use the -r|--runtime option. + /// + /// + /// Requires .NET 6 or newer. + /// + public string OS { get; set; } + /// /// Gets or sets additional arguments to be passed to MSBuild. /// diff --git a/src/Cake.Common/Tools/DotNet/Publish/DotNetPublisher.cs b/src/Cake.Common/Tools/DotNet/Publish/DotNetPublisher.cs index b26b7adac0..8564d44905 100644 --- a/src/Cake.Common/Tools/DotNet/Publish/DotNetPublisher.cs +++ b/src/Cake.Common/Tools/DotNet/Publish/DotNetPublisher.cs @@ -266,6 +266,13 @@ private ProcessArgumentBuilder GetArguments(string path, DotNetPublishSettings s } } + // Os + if (!string.IsNullOrEmpty(settings.OS)) + { + builder.Append("--os"); + builder.Append(settings.OS); + } + if (settings.MSBuildSettings != null) { builder.AppendMSBuildSettings(settings.MSBuildSettings, _environment); diff --git a/src/Cake.Common/Tools/DotNet/Restore/DotNetRestoreSettings.cs b/src/Cake.Common/Tools/DotNet/Restore/DotNetRestoreSettings.cs index 13c6ea43f5..733e3a1511 100644 --- a/src/Cake.Common/Tools/DotNet/Restore/DotNetRestoreSettings.cs +++ b/src/Cake.Common/Tools/DotNet/Restore/DotNetRestoreSettings.cs @@ -108,6 +108,18 @@ public class DotNetRestoreSettings : DotNetSettings /// public bool ForceEvaluate { get; set; } + /// + /// Gets or sets a value indicating whether to compile your application assemblies as ReadyToRun (R2R) format. + /// + /// + /// In .NET 6, dotnet restore followed by dotnet publish -p:PublishReadyToRun=true --no-restore will fail with the NETSDK1095 error. + /// This is because the crossgen binary is now shipped as a separate NuGet package, and so needs to be part of the restore operation for publishing to succeed. + /// + /// Supported by .NET SDK version 6.0.100 and above. + /// + /// + public bool? PublishReadyToRun { get; set; } + /// /// Gets or sets additional arguments to be passed to MSBuild. /// diff --git a/src/Cake.Common/Tools/DotNet/Restore/DotNetRestorer.cs b/src/Cake.Common/Tools/DotNet/Restore/DotNetRestorer.cs index b8d3175977..2488341c7d 100644 --- a/src/Cake.Common/Tools/DotNet/Restore/DotNetRestorer.cs +++ b/src/Cake.Common/Tools/DotNet/Restore/DotNetRestorer.cs @@ -148,12 +148,25 @@ private ProcessArgumentBuilder GetArguments(string root, DotNetRestoreSettings s builder.AppendSwitchQuoted("--lock-file-path", " ", settings.LockFilePath.MakeAbsolute(_environment).FullPath); } - // force evaluate + // Force Evaluate if (settings.ForceEvaluate) { builder.Append("--force-evaluate"); } + // Publish ReadyToRun + if (settings.PublishReadyToRun.HasValue) + { + if (settings.PublishReadyToRun.Value) + { + builder.Append("-p:PublishReadyToRun=true"); + } + else + { + builder.Append("-p:PublishReadyToRun=false"); + } + } + if (settings.MSBuildSettings != null) { builder.AppendMSBuildSettings(settings.MSBuildSettings, _environment); diff --git a/src/Cake.Core.Tests/Cake.Core.Tests.csproj b/src/Cake.Core.Tests/Cake.Core.Tests.csproj index 797d609b85..5ab212ea53 100644 --- a/src/Cake.Core.Tests/Cake.Core.Tests.csproj +++ b/src/Cake.Core.Tests/Cake.Core.Tests.csproj @@ -14,14 +14,14 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - - + + diff --git a/src/Cake.Core.Tests/Unit/Graph/CakeGraphTests.cs b/src/Cake.Core.Tests/Unit/Graph/CakeGraphTests.cs index b7f3f21c7f..9af0ffb9c3 100644 --- a/src/Cake.Core.Tests/Unit/Graph/CakeGraphTests.cs +++ b/src/Cake.Core.Tests/Unit/Graph/CakeGraphTests.cs @@ -36,7 +36,7 @@ public void Should_Add_Node_To_Graph() graph.Add("start"); // Then - Assert.Equal(1, graph.Nodes.Count); + Assert.Single(graph.Nodes); } [Fact] @@ -110,7 +110,7 @@ public void Should_Not_Create_Edge_Between_Connected_Nodes_If_An_Edge_Already_Ex graph.Connect("start", "end"); // Then - Assert.Equal(1, graph.Edges.Count); + Assert.Single(graph.Edges); } [Fact] @@ -124,7 +124,7 @@ public void Should_Not_Create_Edge_Between_Connected_Nodes_If_An_Edge_Already_Ex graph.Connect("START", "END"); // Then - Assert.Equal(1, graph.Edges.Count); + Assert.Single(graph.Edges); } [Fact] diff --git a/src/Cake.Core.Tests/Unit/Scripting/Analysis/ScriptAnalyzerTests.cs b/src/Cake.Core.Tests/Unit/Scripting/Analysis/ScriptAnalyzerTests.cs index aadc195d51..8bd68365f4 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/Analysis/ScriptAnalyzerTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/Analysis/ScriptAnalyzerTests.cs @@ -82,7 +82,7 @@ public void Should_Return_Error_If_Script_Was_Not_Found() // Then Assert.False(result.Succeeded); - Assert.Equal(1, result.Errors.Count); + Assert.Single(result.Errors); Assert.Equal("Could not find script '/Working/notfound.cake'.", result.Errors[0].Message); } @@ -115,7 +115,7 @@ public void Should_Return_Single_Assembly_Reference_Found_In_Source(string sourc var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.References.Count); + Assert.Single(result.Script.References); Assert.Equal("hello.dll", result.Script.References.ElementAt(0)); } @@ -132,7 +132,7 @@ public void Should_Return_Single_Assembly_Reference_With_Space_In_File_Name_Foun var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.References.Count); + Assert.Single(result.Script.References); Assert.Equal("hello world.dll", result.Script.References.ElementAt(0)); } @@ -165,7 +165,7 @@ public void Should_Process_Using_Directives() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Namespaces.Count); + Assert.Single(result.Script.Namespaces); Assert.Equal("Cake.Core", result.Script.Namespaces.ElementAt(0)); } @@ -180,7 +180,7 @@ public void Should_Process_Using_Alias_Directives() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.UsingAliases.Count); + Assert.Single(result.Script.UsingAliases); Assert.Equal("using Core = Cake.Core;", result.Script.UsingAliases.ElementAt(0)); } @@ -195,8 +195,8 @@ public void Should_Keep_Using_Block() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(0, result.Script.UsingAliases.Count); - Assert.Equal(0, result.Script.Namespaces.Count); + Assert.Empty(result.Script.UsingAliases); + Assert.Empty(result.Script.Namespaces); Assert.Equal(4, result.Lines.Count); Assert.Equal(result.Lines[0], "#line 1 \"/Working/script.cake\""); Assert.Equal(result.Lines[1], "using(new Temp())"); @@ -232,7 +232,7 @@ public void Should_Process_Addin_Directive_Without_Source() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Addins.Count); + Assert.Single(result.Script.Addins); Assert.Equal("nuget:?package=Hello.World", result.Script.Addins.ElementAt(0).OriginalString); } @@ -247,7 +247,7 @@ public void Should_Process_Addin_Directive_Using_Uri() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Addins.Count); + Assert.Single(result.Script.Addins); Assert.Equal("npm:?package=node", result.Script.Addins.ElementAt(0).OriginalString); } @@ -262,7 +262,7 @@ public void Should_Process_Addin_Directive_With_Source() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Addins.Count); + Assert.Single(result.Script.Addins); Assert.Equal("nuget:http://source/?package=Hello.World", result.Script.Addins.ElementAt(0).OriginalString); } @@ -277,7 +277,7 @@ public void Should_Process_Tool_Directive_Without_Source() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Tools.Count); + Assert.Single(result.Script.Tools); Assert.Equal("nuget:?package=Hello.World", result.Script.Tools.ElementAt(0).OriginalString); } @@ -292,7 +292,7 @@ public void Should_Process_Tool_Directive_With_Source() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Tools.Count); + Assert.Single(result.Script.Tools); Assert.Equal("nuget:http://source/?package=Hello.World", result.Script.Tools.ElementAt(0).OriginalString); } @@ -307,7 +307,7 @@ public void Should_Process_Tool_Directive_Using_Uri() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Tools.Count); + Assert.Single(result.Script.Tools); Assert.Equal("npm:?package=node", result.Script.Tools.ElementAt(0).OriginalString); } @@ -343,7 +343,7 @@ public void Should_Log_Error_Location() Assert.Equal("#line 1 \"/Working/script.cake\"", result.Lines[0]); Assert.Equal("// #load \"local:?pat\"", result.Lines[1]); Assert.False(result.Succeeded); - Assert.Equal(1, result.Errors.Count); + Assert.Single(result.Errors); Assert.Equal("/Working/script.cake", result.Errors[0].File.FullPath); Assert.Equal(1, result.Errors[0].Line); Assert.Equal("Query string for #load is missing parameter 'path'.", result.Errors[0].Message); @@ -364,7 +364,7 @@ public void Should_Log_Error_Location_In_Loaded_Script() // Then Assert.Equal(6, result.Lines.Count); Assert.False(result.Succeeded); - Assert.Equal(1, result.Errors.Count); + Assert.Single(result.Errors); Assert.Equal("/Working/script2.cake", result.Errors[0].File.FullPath); Assert.Equal(2, result.Errors[0].Line); Assert.Equal("Query string for #load contains more than one parameter 'path'.", result.Errors[0].Message); @@ -381,7 +381,7 @@ public void Should_Process_Using_Static_Directives() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.UsingStaticDirectives.Count); + Assert.Single(result.Script.UsingStaticDirectives); Assert.Equal("using static System.Math;", result.Script.UsingStaticDirectives.ElementAt(0)); } @@ -396,7 +396,7 @@ public void Should_Process_Define_Directives() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Defines.Count); + Assert.Single(result.Script.Defines); Assert.Equal("#define FOO", result.Script.Defines.ElementAt(0)); } } diff --git a/src/Cake.Core.Tests/Unit/Scripting/Processors/LoadDirectiveProcessorTests.cs b/src/Cake.Core.Tests/Unit/Scripting/Processors/LoadDirectiveProcessorTests.cs index f400bcd95a..6ec85b8662 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/Processors/LoadDirectiveProcessorTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/Processors/LoadDirectiveProcessorTests.cs @@ -25,7 +25,7 @@ public void Should_Process_Single_Script_Reference_Found_In_Source(string source var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Includes.Count); + Assert.Single(result.Script.Includes); Assert.Equal("/Working/utils.cake", result.Script.Includes[0].Path.FullPath); } @@ -44,7 +44,7 @@ public void Should_Process_Single_Script_Reference_With_Spaces_In_File_Name_Foun var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Includes.Count); + Assert.Single(result.Script.Includes); Assert.Equal("/Working/test/my utils.cake", result.Script.Includes[0].Path.FullPath); } @@ -63,7 +63,7 @@ public void Should_Process_Single_Script_Reference_With_Any_Extension_Found_In_S var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Includes.Count); + Assert.Single(result.Script.Includes); Assert.Equal($"/Working/{filename}", result.Script.Includes[0].Path.FullPath); } @@ -103,7 +103,7 @@ public void Should_Process_Environment_Variable_Found_In_Source(string source) var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Includes.Count); + Assert.Single(result.Script.Includes); Assert.Equal("/Working/test/utils.cake", result.Script.Includes[0].Path.FullPath); } @@ -123,7 +123,7 @@ public void Should_Process_Multiple_Environment_Variable_Found_In_Source(string var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Includes.Count); + Assert.Single(result.Script.Includes); Assert.Equal("/Working/test/scripts/utils.cake", result.Script.Includes[0].Path.FullPath); } @@ -151,7 +151,7 @@ public void Should_Ignore_Globber_Matches_With_Invalid_Extensions(string source) var result = fixture.Analyze("/Working/bootstrap.cake"); // Then - Assert.Equal(0, result.Script.Includes.Count); + Assert.Empty(result.Script.Includes); } [Theory] @@ -232,7 +232,7 @@ public void Should_Process_AbsolutePath_Script_Reference_Found_In_Source(string var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Includes.Count); + Assert.Single(result.Script.Includes); Assert.Equal("/utils.cake", result.Script.Includes[0].Path.FullPath); } @@ -251,7 +251,7 @@ public void Should_Process_RelativePath_Script_Reference_Found_In_Source(string var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Includes.Count); + Assert.Single(result.Script.Includes); Assert.Equal("/Working/test/utils.cake", result.Script.Includes[0].Path.FullPath); } @@ -270,7 +270,7 @@ public void Should_Process_WindowsAbsolutePath_Script_Reference_Found_In_Source( var result = fixture.Analyze("C:/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Includes.Count); + Assert.Single(result.Script.Includes); Assert.Equal("C:/utils.cake", result.Script.Includes[0].Path.FullPath); } @@ -289,7 +289,7 @@ public void Should_Process_WindowsRelativePath_Script_Reference_Found_In_Source( var result = fixture.Analyze("C:/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Includes.Count); + Assert.Single(result.Script.Includes); Assert.Equal("C:/Working/test/utils.cake", result.Script.Includes[0].Path.FullPath); } } diff --git a/src/Cake.Core.Tests/Unit/Scripting/ScriptAliasTests.cs b/src/Cake.Core.Tests/Unit/Scripting/ScriptAliasTests.cs index 65c1315c0f..c531b3b238 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/ScriptAliasTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/ScriptAliasTests.cs @@ -34,7 +34,7 @@ public void Should_Not_Throw_If_Method_Is_Null() var result = new ScriptAlias(method, ScriptAliasType.Method, null); // Then - Assert.Equal(0, result.Namespaces.Count); + Assert.Empty(result.Namespaces); } } } diff --git a/src/Cake.Core.Tests/Unit/Scripting/ScriptTests.cs b/src/Cake.Core.Tests/Unit/Scripting/ScriptTests.cs index 5aa6588386..53ed41b9b0 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/ScriptTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/ScriptTests.cs @@ -4,6 +4,7 @@ using Cake.Core.Scripting; using Xunit; +using static System.Array; namespace Cake.Core.Tests.Unit.Scripting { @@ -15,60 +16,60 @@ public sealed class TheConstructor public void Should_Not_Throw_If_Namespaces_Are_Null() { // Given, When - var script = new Script(null, new string[] { }, new ScriptAlias[] { }, new string[] { }, new string[] { }, new string[] { }); + var script = new Script(null, Empty(), Empty(), Empty(), Empty(), Empty()); // Then - Assert.Equal(0, script.Namespaces.Count); + Assert.Empty(script.Namespaces); } [Fact] public void Should_Not_Throw_If_Lines_Are_Null() { // Given, When - var script = new Script(new string[] { }, null, new ScriptAlias[] { }, new string[] { }, new string[] { }, new string[] { }); + var script = new Script(Empty(), null, new ScriptAlias[] { }, Empty(), Empty(), Empty()); // Then - Assert.Equal(0, script.Lines.Count); + Assert.Empty(script.Lines); } [Fact] public void Should_Not_Throw_If_Aliases_Are_Null() { // Given, When - var script = new Script(new string[] { }, new string[] { }, null, new string[] { }, new string[] { }, new string[] { }); + var script = new Script(Empty(), Empty(), null, Empty(), Empty(), Empty()); // Then - Assert.Equal(0, script.Aliases.Count); + Assert.Empty(script.Aliases); } [Fact] public void Should_Not_Throw_If_Using_Alias_Directives_Are_Null() { // Given, When - var script = new Script(new string[] { }, new string[] { }, new ScriptAlias[] { }, null, new string[] { }, new string[] { }); + var script = new Script(Empty(), Empty(), Empty(), null, Empty(), Empty()); // Then - Assert.Equal(0, script.UsingAliasDirectives.Count); + Assert.Empty(script.UsingAliasDirectives); } [Fact] public void Should_Not_Throw_If_Using_Static_Directives_Are_Null() { // Given, When - var script = new Script(new string[] { }, new string[] { }, new ScriptAlias[] { }, new string[] { }, null, new string[] { }); + var script = new Script(Empty(), Empty(), Empty(), Empty(), null, Empty()); // Then - Assert.Equal(0, script.UsingStaticDirectives.Count); + Assert.Empty(script.UsingStaticDirectives); } [Fact] public void Should_Not_Throw_If_Defines_Are_Null() { // Given, When - var script = new Script(new string[] { }, new string[] { }, new ScriptAlias[] { }, new string[] { }, new string[] { }, null); + var script = new Script(Empty(), Empty(), Empty(), Empty(), Empty(), null); // Then - Assert.Equal(0, script.Defines.Count); + Assert.Empty(script.Defines); } } } diff --git a/src/Cake.Core/CakeEnvironment.cs b/src/Cake.Core/CakeEnvironment.cs index f1ddc508d4..c2b208eaa0 100644 --- a/src/Cake.Core/CakeEnvironment.cs +++ b/src/Cake.Core/CakeEnvironment.cs @@ -20,6 +20,9 @@ public DirectoryPath WorkingDirectory set { SetWorkingDirectory(value); } } + /// + public DirectoryPath UserHomeDirectory { get; } + /// public DirectoryPath ApplicationRoot { get; } @@ -46,6 +49,9 @@ public CakeEnvironment(ICakePlatform platform, ICakeRuntime runtime) // Get the working directory. WorkingDirectory = new DirectoryPath(System.IO.Directory.GetCurrentDirectory()); + + // Get the Home directory. + UserHomeDirectory = GetSpecialPath(SpecialPath.UserProfile); } /// diff --git a/src/Cake.Core/ICakeEnvironment.cs b/src/Cake.Core/ICakeEnvironment.cs index 737c238787..db90cb9ea0 100644 --- a/src/Cake.Core/ICakeEnvironment.cs +++ b/src/Cake.Core/ICakeEnvironment.cs @@ -18,6 +18,12 @@ public interface ICakeEnvironment /// The working directory. DirectoryPath WorkingDirectory { get; set; } + /// + /// Gets the user's home directory. + /// + /// The user's home directory. + DirectoryPath UserHomeDirectory => GetSpecialPath(SpecialPath.UserProfile); + /// /// Gets the application root path. /// diff --git a/src/Cake.Core/IO/SpecialPath.cs b/src/Cake.Core/IO/SpecialPath.cs index b7c18a3f3d..bd530668fd 100644 --- a/src/Cake.Core/IO/SpecialPath.cs +++ b/src/Cake.Core/IO/SpecialPath.cs @@ -45,6 +45,11 @@ public enum SpecialPath /// /// The current user's temporary folder. /// - LocalTemp + LocalTemp, + + /// + /// The user's profile folder. + /// + UserProfile, } } \ No newline at end of file diff --git a/src/Cake.Core/Polyfill/SpecialPathHelper.cs b/src/Cake.Core/Polyfill/SpecialPathHelper.cs index 208e6a9920..7e8e1501e7 100644 --- a/src/Cake.Core/Polyfill/SpecialPathHelper.cs +++ b/src/Cake.Core/Polyfill/SpecialPathHelper.cs @@ -17,6 +17,11 @@ public static DirectoryPath GetFolderPath(ICakePlatform platform, SpecialPath pa return new DirectoryPath(System.IO.Path.GetTempPath()); } + if (path == SpecialPath.UserProfile) + { + return new DirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); + } + var result = GetXPlatFolderPath(platform, path); if (result != null) { diff --git a/src/Cake.DotNetTool.Module.Tests/Cake.DotNetTool.Module.Tests.csproj b/src/Cake.DotNetTool.Module.Tests/Cake.DotNetTool.Module.Tests.csproj index 6bafb3809d..4456c42c5c 100644 --- a/src/Cake.DotNetTool.Module.Tests/Cake.DotNetTool.Module.Tests.csproj +++ b/src/Cake.DotNetTool.Module.Tests/Cake.DotNetTool.Module.Tests.csproj @@ -16,13 +16,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - - + + diff --git a/src/Cake.Frosting.Tests/Cake.Frosting.Tests.csproj b/src/Cake.Frosting.Tests/Cake.Frosting.Tests.csproj index 7a435dbde1..ba7e795c47 100644 --- a/src/Cake.Frosting.Tests/Cake.Frosting.Tests.csproj +++ b/src/Cake.Frosting.Tests/Cake.Frosting.Tests.csproj @@ -8,15 +8,15 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/src/Cake.Frosting.Tests/CakeHostTests.cs b/src/Cake.Frosting.Tests/CakeHostTests.cs index c5a256ab19..02e330fbca 100644 --- a/src/Cake.Frosting.Tests/CakeHostTests.cs +++ b/src/Cake.Frosting.Tests/CakeHostTests.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection; using NSubstitute; using Xunit; +using Xunit.Sdk; namespace Cake.Frosting.Tests { @@ -330,5 +331,33 @@ public void Should_pass_target_within_cakeContext_arguments() .Received(1) .ExecuteAsync(Arg.Any(), Arg.Is(cc => cc.Arguments.HasArgument("target") && cc.Arguments.GetArgument("target").Equals(nameof(DummyTask)))); } + + [Theory] + [InlineData(nameof(DummyTask), nameof(DummyTask2), nameof(DummyTask3))] + [InlineData(nameof(DummyTask), nameof(DummyTask3), nameof(DummyTask2))] + [InlineData(nameof(DummyTask2), nameof(DummyTask3), nameof(DummyTask))] + [InlineData(nameof(DummyTask2), nameof(DummyTask), nameof(DummyTask3))] + [InlineData(nameof(DummyTask3), nameof(DummyTask2), nameof(DummyTask))] + [InlineData(nameof(DummyTask3), nameof(DummyTask), nameof(DummyTask2))] + public void Should_Execute_Multiple_Targets_In_Correct_Order(string task0, string task1, string task2) + { + // Given + var fixture = new CakeHostFixture(); + fixture.RegisterTask(); + fixture.RegisterTask(); + fixture.RegisterTask(); + fixture.Strategy = Substitute.For(); + + // When + fixture.Run("--target", task0, "--target", task1, "--target", task2); + + // Then + Received.InOrder(() => + { + fixture.Strategy.ExecuteAsync(Arg.Is(t => t.Name == task0), Arg.Any()); + fixture.Strategy.ExecuteAsync(Arg.Is(t => t.Name == task1), Arg.Any()); + fixture.Strategy.ExecuteAsync(Arg.Is(t => t.Name == task2), Arg.Any()); + }); + } } } diff --git a/src/Cake.Frosting.Tests/Tasks/DummyTask2.cs b/src/Cake.Frosting.Tests/Tasks/DummyTask2.cs new file mode 100644 index 0000000000..f4a9a34f19 --- /dev/null +++ b/src/Cake.Frosting.Tests/Tasks/DummyTask2.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Frosting.Tests +{ + public sealed class DummyTask2 : FrostingTask + { + public override void Run(ICakeContext context) + { + } + } +} diff --git a/src/Cake.Frosting.Tests/Tasks/DummyTask3.cs b/src/Cake.Frosting.Tests/Tasks/DummyTask3.cs new file mode 100644 index 0000000000..081c6c75c5 --- /dev/null +++ b/src/Cake.Frosting.Tests/Tasks/DummyTask3.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Frosting.Tests +{ + public sealed class DummyTask3 : FrostingTask + { + public override void Run(ICakeContext context) + { + } + } +} diff --git a/src/Cake.Frosting/CakeHost.cs b/src/Cake.Frosting/CakeHost.cs index 2591b97475..6a4ed7fdea 100644 --- a/src/Cake.Frosting/CakeHost.cs +++ b/src/Cake.Frosting/CakeHost.cs @@ -90,7 +90,7 @@ public int Run(IEnumerable args) config.ValidateExamples(); // Top level examples. - config.AddExample(new[] { string.Empty }); + config.AddExample(Array.Empty()); config.AddExample(new[] { "--verbosity", "quiet" }); config.AddExample(new[] { "--tree" }); }); diff --git a/src/Cake.Frosting/Internal/Commands/DefaultCommand.cs b/src/Cake.Frosting/Internal/Commands/DefaultCommand.cs index 6d0df33735..cce55ad5de 100644 --- a/src/Cake.Frosting/Internal/Commands/DefaultCommand.cs +++ b/src/Cake.Frosting/Internal/Commands/DefaultCommand.cs @@ -68,7 +68,7 @@ public override int Execute(CommandContext context, DefaultCommandSettings setti runner.Settings.UseExclusiveTarget(); } - runner.Run(settings.Target); + runner.Run(settings.Targets); } catch (Exception ex) { @@ -98,7 +98,11 @@ private static CakeArguments CreateCakeArguments(IRemainingArguments remainingAr { arguments[targetArgumentName] = new List(); } - arguments[targetArgumentName].Add(settings.Target); + + foreach (var target in settings.Targets) + { + arguments[targetArgumentName].Add(target); + } var argumentLookUp = arguments.SelectMany(a => a.Value, Tuple.Create).ToLookup(a => a.Item1.Key, a => a.Item2); return new CakeArguments(argumentLookUp); diff --git a/src/Cake.Frosting/Internal/Commands/DefaultCommandSettings.cs b/src/Cake.Frosting/Internal/Commands/DefaultCommandSettings.cs index ea0d6b7f64..e9a1892f40 100644 --- a/src/Cake.Frosting/Internal/Commands/DefaultCommandSettings.cs +++ b/src/Cake.Frosting/Internal/Commands/DefaultCommandSettings.cs @@ -15,7 +15,7 @@ internal sealed class DefaultCommandSettings : CommandSettings [CommandOption("--target|-t ")] [DefaultValue("Default")] [Description("Target task to invoke.")] - public string Target { get; set; } + public string[] Targets { get; set; } [CommandOption("--working|-w ")] [TypeConverter(typeof(Cli.DirectoryPathConverter))] diff --git a/src/Cake.Frosting/Internal/FrostingEngine.cs b/src/Cake.Frosting/Internal/FrostingEngine.cs index 918710a4d3..b2747e2c42 100644 --- a/src/Cake.Frosting/Internal/FrostingEngine.cs +++ b/src/Cake.Frosting/Internal/FrostingEngine.cs @@ -13,7 +13,7 @@ namespace Cake.Frosting.Internal internal interface IFrostingEngine { ExecutionSettings Settings { get; } - CakeReport Run(string target); + CakeReport Run(IEnumerable targets); } internal abstract class FrostingEngine : IFrostingEngine @@ -51,13 +51,13 @@ protected FrostingEngine( _tasks = new List(tasks ?? Array.Empty()); } - public CakeReport Run(string target) + public CakeReport Run(IEnumerable targets) { ConfigureTasks(); ConfigureLifetime(); ConfigureTaskLifetime(); - return _host.RunTarget(target); + return _host.RunTargets(targets); } private void ConfigureTaskLifetime() diff --git a/src/Cake.NuGet.Tests/Cake.NuGet.Tests.csproj b/src/Cake.NuGet.Tests/Cake.NuGet.Tests.csproj index 4c01745ff6..78ea742d85 100644 --- a/src/Cake.NuGet.Tests/Cake.NuGet.Tests.csproj +++ b/src/Cake.NuGet.Tests/Cake.NuGet.Tests.csproj @@ -15,14 +15,14 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - - + + diff --git a/src/Cake.NuGet.Tests/Unit/NuGetContentResolverTests.cs b/src/Cake.NuGet.Tests/Unit/NuGetContentResolverTests.cs index 295591712b..f44098a66a 100644 --- a/src/Cake.NuGet.Tests/Unit/NuGetContentResolverTests.cs +++ b/src/Cake.NuGet.Tests/Unit/NuGetContentResolverTests.cs @@ -29,7 +29,7 @@ public void Should_Return_Exe_Files_By_Default() var files = fixture.GetFiles(); // Then - Assert.Equal(1, files.Count); + Assert.Single(files); Assert.Equal("/Working/tools/Foo/Foo.exe", files.First().Path.FullPath); } @@ -46,7 +46,7 @@ public void Should_Return_Dll_Files_By_Default() var files = fixture.GetFiles(); // Then - Assert.Equal(1, files.Count); + Assert.Single(files); Assert.Equal("/Working/tools/Foo/Foo.dll", files.First().Path.FullPath); } @@ -87,7 +87,7 @@ public void Should_Excluded_Files_If_Specified(string package) var files = fixture.GetFiles(); // Then - Assert.Equal(0, files.Count); + Assert.Empty(files); } [Fact] @@ -148,7 +148,7 @@ public void Should_Return_Exact_Framework_If_Possible(string framework, Runtime var result = fixture.GetFiles(); // Then - Assert.Equal(1, result.Count); + Assert.Single(result); Assert.Equal($"/Working/lib/{expected}/file.dll", result.ElementAt(0).Path.FullPath); } @@ -169,7 +169,7 @@ public void Should_Return_Nearest_Compatible_Framework_If_An_Exact_Match_Is_Not_ var result = fixture.GetFiles(); // Then - Assert.Equal(1, result.Count); + Assert.Single(result); Assert.Equal($"/Working/lib/{expected}/file.dll", result.ElementAt(0).Path.FullPath); } @@ -188,7 +188,7 @@ public void Should_Return_Empty_Result_If_Any_Match_Is_Not_Possible(string frame var result = fixture.GetFiles(); // Then - Assert.Equal(0, result.Count); + Assert.Empty(result); } [Fact] @@ -206,7 +206,7 @@ public void Should_Return_Compatible_Netstandard_If_An_Exact_Match_Is_Not_Possib var result = fixture.GetFiles(); // Then - Assert.Equal(1, result.Count); + Assert.Single(result); Assert.Equal($"/Working/lib/netstandard1.6/file.dll", result.ElementAt(0).Path.FullPath); } @@ -223,7 +223,7 @@ public void Should_Return_Only_CLR_Assemblies() var result = fixture.GetFiles(); // Then - Assert.Equal(1, result.Count); + Assert.Single(result); Assert.Equal("/Working/lib/netstandard1.6/file.dll", result.ElementAt(0).Path.FullPath); } @@ -266,7 +266,7 @@ public void Should_Return_Exact_Framework_Even_Though_Files_Located_In_Root(stri var result = fixture.GetFiles(); // Then - Assert.Equal(1, result.Count); + Assert.Single(result); Assert.Equal($"/Working/lib/{expected}/file.dll", result.ElementAt(0).Path.FullPath); } @@ -285,7 +285,7 @@ public void Should_Return_From_Root_If_No_Compatible_Framework_Found(string fram var result = fixture.GetFiles(); // Then - Assert.Equal(1, result.Count); + Assert.Single(result); Assert.Equal($"/Working/file.dll", result.ElementAt(0).Path.FullPath); } @@ -330,7 +330,7 @@ public void Should_Not_Return_Ref_Assemblies(string framework, Runtime runtime) var result = fixture.GetFiles(); // Then - Assert.Equal(0, result.Count); + Assert.Empty(result); } public void Should_Return_Runtimes_Assemblies_If_CoreCLR() @@ -349,7 +349,7 @@ public void Should_Return_Runtimes_Assemblies_If_CoreCLR() var result = fixture.GetFiles(); // Then - Assert.Equal(1, result.Count); + Assert.Single(result); } public void Should_Return_Native_Runtimes_Assemblies_If_CoreCLR() @@ -367,7 +367,7 @@ public void Should_Return_Native_Runtimes_Assemblies_If_CoreCLR() var result = fixture.GetFiles(); // Then - Assert.Equal(1, result.Count); + Assert.Single(result); } } } diff --git a/src/Cake.NuGet.Tests/Unit/NuGetLoadDirectiveProviderTests.cs b/src/Cake.NuGet.Tests/Unit/NuGetLoadDirectiveProviderTests.cs index 9c300af187..53527f7a81 100644 --- a/src/Cake.NuGet.Tests/Unit/NuGetLoadDirectiveProviderTests.cs +++ b/src/Cake.NuGet.Tests/Unit/NuGetLoadDirectiveProviderTests.cs @@ -148,7 +148,7 @@ public void Should_Write_To_Log_If_No_Scripts_Were_Found() fixture.Load(); // Then - Assert.Equal(1, fixture.Log.Entries.Count); + Assert.Single(fixture.Log.Entries); Assert.Equal("No scripts found in NuGet package Cake.Recipe.", fixture.Log.Entries[0].Message); Assert.Equal(Verbosity.Minimal, fixture.Log.Entries[0].Verbosity); Assert.Equal(LogLevel.Warning, fixture.Log.Entries[0].Level); diff --git a/src/Cake.NuGet/Cake.NuGet.csproj b/src/Cake.NuGet/Cake.NuGet.csproj index c81a813ab2..cf565a1088 100644 --- a/src/Cake.NuGet/Cake.NuGet.csproj +++ b/src/Cake.NuGet/Cake.NuGet.csproj @@ -18,17 +18,17 @@ - - - - - - - + + + + + + + - + All diff --git a/src/Cake.Testing.Xunit/Cake.Testing.Xunit.csproj b/src/Cake.Testing.Xunit/Cake.Testing.Xunit.csproj index 4ca967f60b..af7d9f598f 100644 --- a/src/Cake.Testing.Xunit/Cake.Testing.Xunit.csproj +++ b/src/Cake.Testing.Xunit/Cake.Testing.Xunit.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Cake.Testing/FakeEnvironment.cs b/src/Cake.Testing/FakeEnvironment.cs index b744ed230b..85743ca495 100644 --- a/src/Cake.Testing/FakeEnvironment.cs +++ b/src/Cake.Testing/FakeEnvironment.cs @@ -23,6 +23,9 @@ public sealed class FakeEnvironment : ICakeEnvironment /// public DirectoryPath WorkingDirectory { get; set; } + /// + public DirectoryPath UserHomeDirectory { get; set; } + /// public DirectoryPath ApplicationRoot { get; set; } @@ -67,6 +70,7 @@ public static FakeEnvironment CreateUnixEnvironment(bool is64Bit = true) { var environment = new FakeEnvironment(PlatformFamily.Linux, is64Bit); environment.WorkingDirectory = new DirectoryPath("/Working"); + environment.UserHomeDirectory = new DirectoryPath("/Users/CakeUser"); environment.ApplicationRoot = "/Working/bin"; return environment; } @@ -80,6 +84,7 @@ public static FakeEnvironment CreateWindowsEnvironment(bool is64Bit = true) { var environment = new FakeEnvironment(PlatformFamily.Windows, is64Bit); environment.WorkingDirectory = new DirectoryPath("C:/Working"); + environment.UserHomeDirectory = new DirectoryPath("C:/Users/CakeUser"); environment.ApplicationRoot = "C:/Working/bin"; return environment; } diff --git a/src/Cake.Tests/Cake.Tests.csproj b/src/Cake.Tests/Cake.Tests.csproj index 4f000480bf..d530c01aba 100644 --- a/src/Cake.Tests/Cake.Tests.csproj +++ b/src/Cake.Tests/Cake.Tests.csproj @@ -10,14 +10,14 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - - + + diff --git a/src/Cake/Cake.csproj b/src/Cake/Cake.csproj index b9d9cc87ed..8704a7cfa3 100644 --- a/src/Cake/Cake.csproj +++ b/src/Cake/Cake.csproj @@ -26,11 +26,11 @@ - + - - - - + + + + \ No newline at end of file diff --git a/src/Cake/Program.cs b/src/Cake/Program.cs index abe894568d..614f9f5422 100644 --- a/src/Cake/Program.cs +++ b/src/Cake/Program.cs @@ -58,7 +58,7 @@ public async Task Run(string[] args) } // Top level examples. - config.AddExample(new[] { string.Empty }); + config.AddExample(Array.Empty()); config.AddExample(new[] { "build.cake", "--verbosity", "quiet" }); config.AddExample(new[] { "build.cake", "--tree" }); }); diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index b82b629f13..baad8e125f 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -10,7 +10,7 @@ using System.Reflection; [assembly: AssemblyProduct("Cake")] -[assembly: AssemblyVersion("3.0.0.0")] -[assembly: AssemblyFileVersion("3.0.0.0")] -[assembly: AssemblyInformationalVersion("3.0.0-beta.1+0.Branch.release-3.0.0.Sha.deaf9568275db4906a238fb28b36ede750daf8f9")] +[assembly: AssemblyVersion("3.1.0.0")] +[assembly: AssemblyFileVersion("3.1.0.0")] +[assembly: AssemblyInformationalVersion("3.1.0-beta.1+0.Branch.release-3.1.0.Sha.6a334f30ec989c44d6578a7e73843ed9f1167ca3")] [assembly: AssemblyCopyright("Copyright (c) .NET Foundation and Contributors")] diff --git a/tests/integration/Cake.Common/ArgumentAliases.cake b/tests/integration/Cake.Common/ArgumentAliases.cake index f24a999304..8f07b985ce 100644 --- a/tests/integration/Cake.Common/ArgumentAliases.cake +++ b/tests/integration/Cake.Common/ArgumentAliases.cake @@ -42,7 +42,18 @@ Task("Cake.Common.ArgumentAliases.Argument.WithDefaultValue") Assert.Equal("foo", arg); }); -Task("Cake.Common.ArgumentAliases.Argument.MultipleArguments") +Task("Cake.Common.ArgumentAliases.Argument.MultipleArguments.GetsLastValue") + .Does(() => +{ + // Given, When + var arg = Argument("multipleargs"); + + // Then + Assert.Equal("b", arg); +}); + + +Task("Cake.Common.ArgumentAliases.Arguments.MultipleArguments") .Does(() => { @@ -53,7 +64,7 @@ Task("Cake.Common.ArgumentAliases.Argument.MultipleArguments") Assert.Equal(new[] { "a", "b" }, arg); }); -Task("Cake.Common.ArgumentAliases.Argument.MultipleArguments.WithSingleDefaultValue") +Task("Cake.Common.ArgumentAliases.Arguments.MultipleArguments.WithSingleDefaultValue") .Does(() => { // Given @@ -66,7 +77,7 @@ Task("Cake.Common.ArgumentAliases.Argument.MultipleArguments.WithSingleDefaultVa Assert.Equal(expect, arg); }); -Task("Cake.Common.ArgumentAliases.Argument.MultipleArguments.WithMultipleDefaultValue") +Task("Cake.Common.ArgumentAliases.Arguments.MultipleArguments.WithMultipleDefaultValue") .Does(() => { // Given @@ -79,7 +90,7 @@ Task("Cake.Common.ArgumentAliases.Argument.MultipleArguments.WithMultipleDefault Assert.Equal(expect, arg); }); -Task("Cake.Common.ArgumentAliases.Argument.MultipleArguments.WithLazyDefaultValue") +Task("Cake.Common.ArgumentAliases.Arguments.MultipleArguments.WithLazyDefaultValue") .Does(() => { // Given @@ -145,10 +156,11 @@ Task("Cake.Common.ArgumentAliases") .IsDependentOn("Cake.Common.ArgumentAliases.HasArgument.ThatDoNotExist") .IsDependentOn("Cake.Common.ArgumentAliases.Argument") .IsDependentOn("Cake.Common.ArgumentAliases.Argument.WithDefaultValue") - .IsDependentOn("Cake.Common.ArgumentAliases.Argument.MultipleArguments") - .IsDependentOn("Cake.Common.ArgumentAliases.Argument.MultipleArguments.WithSingleDefaultValue") - .IsDependentOn("Cake.Common.ArgumentAliases.Argument.MultipleArguments.WithMultipleDefaultValue") - .IsDependentOn("Cake.Common.ArgumentAliases.Argument.MultipleArguments.WithLazyDefaultValue") + .IsDependentOn("Cake.Common.ArgumentAliases.Argument.MultipleArguments.GetsLastValue") + .IsDependentOn("Cake.Common.ArgumentAliases.Arguments.MultipleArguments") + .IsDependentOn("Cake.Common.ArgumentAliases.Arguments.MultipleArguments.WithSingleDefaultValue") + .IsDependentOn("Cake.Common.ArgumentAliases.Arguments.MultipleArguments.WithMultipleDefaultValue") + .IsDependentOn("Cake.Common.ArgumentAliases.Arguments.MultipleArguments.WithLazyDefaultValue") .IsDependentOn("Cake.Common.ArgumentAliases.Argument.DirectoryPathArgument") .IsDependentOn("Cake.Common.ArgumentAliases.Argument.FilePathArgument") .IsDependentOn("Cake.Common.ArgumentAliases.Argument.DotNetVerbosityArgument") diff --git a/tests/integration/Cake.Core/Scripting/SpectreConsole.cake b/tests/integration/Cake.Core/Scripting/SpectreConsole.cake index 8dcf832fc0..52d15d8028 100644 --- a/tests/integration/Cake.Core/Scripting/SpectreConsole.cake +++ b/tests/integration/Cake.Core/Scripting/SpectreConsole.cake @@ -5,7 +5,7 @@ Task("Cake.Core.Scripting.Spectre.Console.FigletText") { AnsiConsole.Render( new FigletText("Cake") - .LeftAligned() + .LeftJustified() .Color(Color.Red)); });