diff --git a/src/GitVersionCore.Tests/Helpers/TestFileSystem.cs b/src/GitVersionCore.Tests/Helpers/TestFileSystem.cs index 2bbed5a83c..ddfac75184 100644 --- a/src/GitVersionCore.Tests/Helpers/TestFileSystem.cs +++ b/src/GitVersionCore.Tests/Helpers/TestFileSystem.cs @@ -73,7 +73,7 @@ public void WriteAllText(string file, string fileContents, Encoding encoding) fileSystem[path] = encoding.GetBytes(fileContents); } - public IEnumerable DirectoryGetFiles(string directory, string searchPattern, SearchOption searchOption) + public IEnumerable DirectoryEnumerateFiles(string directory, string searchPattern, SearchOption searchOption) { throw new NotImplementedException(); } diff --git a/src/GitVersionCore.Tests/VersionConverters/AssemblyInfoFileUpdaterTests.cs b/src/GitVersionCore.Tests/VersionConverters/AssemblyInfoFileUpdaterTests.cs index e64de23625..a148690f64 100644 --- a/src/GitVersionCore.Tests/VersionConverters/AssemblyInfoFileUpdaterTests.cs +++ b/src/GitVersionCore.Tests/VersionConverters/AssemblyInfoFileUpdaterTests.cs @@ -139,7 +139,7 @@ public void ShouldStartSearchFromWorkingDirectory() using var assemblyInfoFileUpdater = new AssemblyInfoFileUpdater(log, fileSystem); assemblyInfoFileUpdater.Execute(variables, new AssemblyInfoContext(workingDir, false, assemblyInfoFiles.ToArray())); - fileSystem.Received().DirectoryGetFiles(Arg.Is(workingDir), Arg.Any(), Arg.Any()); + fileSystem.Received().DirectoryEnumerateFiles(Arg.Is(workingDir), Arg.Any(), Arg.Any()); } [TestCase("cs", "[assembly: AssemblyVersion(\"1.0.0.0\")]\r\n[assembly: AssemblyInformationalVersion(\"1.0.0.0\")]\r\n[assembly: AssemblyFileVersion(\"1.0.0.0\")]")] diff --git a/src/GitVersionCore.Tests/VersionConverters/ProjectFileUpdaterTests.cs b/src/GitVersionCore.Tests/VersionConverters/ProjectFileUpdaterTests.cs new file mode 100644 index 0000000000..1be9f3f599 --- /dev/null +++ b/src/GitVersionCore.Tests/VersionConverters/ProjectFileUpdaterTests.cs @@ -0,0 +1,318 @@ +using System; +using System.IO; +using System.Xml.Linq; +using GitVersion; +using GitVersion.Extensions; +using GitVersion.Logging; +using GitVersion.OutputVariables; +using GitVersion.VersionCalculation; +using GitVersion.VersionConverters.AssemblyInfo; +using GitVersionCore.Tests.Helpers; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using NUnit.Framework; +using Shouldly; + +namespace GitVersionCore.Tests +{ + [TestFixture] + [Parallelizable(ParallelScope.None)] + public class ProjectFileUpdaterTests : TestBase + { + private IVariableProvider variableProvider; + private ILog log; + private IFileSystem fileSystem; + + [SetUp] + public void Setup() + { + ShouldlyConfiguration.ShouldMatchApprovedDefaults.LocateTestMethodUsingAttribute(); + var sp = ConfigureServices(); + log = Substitute.For(); + fileSystem = sp.GetService(); + variableProvider = sp.GetService(); + } + + [TestCase(@" + + + Exe + netcoreapp3.1 + + +")] + [Category(NoMono)] + [Description(NoMonoDescription)] + public void CanUpdateProjectFileWithStandardProjectFileXml(string xml) + { + using var projectFileUpdater = new ProjectFileUpdater(log, fileSystem); + + var canUpdate = projectFileUpdater.CanUpdateProjectFile(XElement.Parse(xml)); + + canUpdate.ShouldBe(true); + } + + [TestCase(@" + + + Exe + netcoreapp3.1 + + +")] + [Category(NoMono)] + [Description(NoMonoDescription)] + public void CannotUpdateProjectFileWithIncorrectProjectSdk(string xml) + { + using var projectFileUpdater = new ProjectFileUpdater(log, fileSystem); + + var canUpdate = projectFileUpdater.CanUpdateProjectFile(XElement.Parse(xml)); + + canUpdate.ShouldBe(false); + } + + [TestCase(@" + + + Exe + netcoreapp3.1 + + +")] + [Category(NoMono)] + [Description(NoMonoDescription)] + public void CannotUpdateProjectFileWithMissingProjectSdk(string xml) + { + using var projectFileUpdater = new ProjectFileUpdater(log, fileSystem); + + var canUpdate = projectFileUpdater.CanUpdateProjectFile(XElement.Parse(xml)); + + canUpdate.ShouldBe(false); + } + + [TestCase(@" + + + Exe + netcoreapp3.1 + false + + +")] + [Category(NoMono)] + [Description(NoMonoDescription)] + public void CannotUpdateProjectFileWithoutAssemblyInfoGeneration(string xml) + { + using var projectFileUpdater = new ProjectFileUpdater(log, fileSystem); + + var canUpdate = projectFileUpdater.CanUpdateProjectFile(XElement.Parse(xml)); + + canUpdate.ShouldBe(false); + } + + [TestCase(@" + + +")] + [Category(NoMono)] + [Description(NoMonoDescription)] + public void CannotUpdateProjectFileWithoutAPropertyGroup(string xml) + { + using var projectFileUpdater = new ProjectFileUpdater(log, fileSystem); + + var canUpdate = projectFileUpdater.CanUpdateProjectFile(XElement.Parse(xml)); + + canUpdate.ShouldBe(false); + } + + [TestCase(@" + + + Exe + netcoreapp3.1 + +" + )] + [Category(NoMono)] + [Description(NoMonoDescription)] + public void UpdateProjectXmlVersionElementWithStandardXmlInsertsElement(string xml) + { + using var projectFileUpdater = new ProjectFileUpdater(log, fileSystem); + + var variables = variableProvider.GetVariablesFor(SemanticVersion.Parse("2.0.0", "v"), new TestEffectiveConfiguration(), false); + var xmlRoot = XElement.Parse(xml); + projectFileUpdater.UpdateProjectVersionElement(xmlRoot, ProjectFileUpdater.AssemblyVersionElement, variables.AssemblySemVer); + + var expectedXml = XElement.Parse(@" + + + Exe + netcoreapp3.1 + 2.0.0.0 + +"); + xmlRoot.ToString().ShouldBe(expectedXml.ToString()); + } + + [TestCase(@" + + + Exe + netcoreapp3.1 + 1.0.0.0 + +" + )] + [Category(NoMono)] + [Description(NoMonoDescription)] + public void UpdateProjectXmlVersionElementWithStandardXmlModifiesElement(string xml) + { + using var projectFileUpdater = new ProjectFileUpdater(log, fileSystem); + + var variables = variableProvider.GetVariablesFor(SemanticVersion.Parse("2.0.0", "v"), new TestEffectiveConfiguration(), false); + var xmlRoot = XElement.Parse(xml); + projectFileUpdater.UpdateProjectVersionElement(xmlRoot, ProjectFileUpdater.AssemblyVersionElement, variables.AssemblySemVer); + + var expectedXml = XElement.Parse(@" + + + Exe + netcoreapp3.1 + 2.0.0.0 + +"); + xmlRoot.ToString().ShouldBe(expectedXml.ToString()); + } + + [TestCase(@" + + + Exe + netcoreapp3.1 + 1.0.0.0 + + + 1.0.0.0 + +" + )] + [Category(NoMono)] + [Description(NoMonoDescription)] + public void UpdateProjectXmlVersionElementWithDuplicatePropertyGroupsModifiesLastElement(string xml) + { + using var projectFileUpdater = new ProjectFileUpdater(log, fileSystem); + + var variables = variableProvider.GetVariablesFor(SemanticVersion.Parse("2.0.0", "v"), new TestEffectiveConfiguration(), false); + var xmlRoot = XElement.Parse(xml); + projectFileUpdater.UpdateProjectVersionElement(xmlRoot, ProjectFileUpdater.AssemblyVersionElement, variables.AssemblySemVer); + + var expectedXml = XElement.Parse(@" + + + Exe + netcoreapp3.1 + 1.0.0.0 + + + 2.0.0.0 + +"); + xmlRoot.ToString().ShouldBe(expectedXml.ToString()); + } + + [TestCase(@" + + + Exe + netcoreapp3.1 + 1.0.0.0 + 1.0.0.0 + +" + )] + + [Category(NoMono)] + [Description(NoMonoDescription)] + public void UpdateProjectXmlVersionElementWithMultipleVersionElementsLastOneIsModified(string xml) + { + using var projectFileUpdater = new ProjectFileUpdater(log, fileSystem); + + var variables = variableProvider.GetVariablesFor(SemanticVersion.Parse("2.0.0", "v"), new TestEffectiveConfiguration(), false); + var xmlRoot = XElement.Parse(xml); + projectFileUpdater.UpdateProjectVersionElement(xmlRoot, ProjectFileUpdater.AssemblyVersionElement, variables.AssemblySemVer); + + var expectedXml = XElement.Parse(@" + + + Exe + netcoreapp3.1 + 1.0.0.0 + 2.0.0.0 + +"); + xmlRoot.ToString().ShouldBe(expectedXml.ToString()); + } + + [TestCase(@" + + + Exe + netcoreapp3.1 + +")] + [Category(NoMono)] + [Description(NoMonoDescription)] + public void UpdateProjectFileAddsVersionToFile(string xml) + { + var fileName = Path.Combine(Path.GetTempPath(), "TestProject.csproj"); + + VerifyAssemblyInfoFile(xml, fileName, AssemblyVersioningScheme.MajorMinorPatch, verify: (fs, variables) => + { + using var projectFileUpdater = new ProjectFileUpdater(log, fs); + projectFileUpdater.Execute(variables, new AssemblyInfoContext(Path.GetTempPath(), false, fileName)); + + var expectedXml = @" + + + Exe + netcoreapp3.1 + 2.3.1.0 + 2.3.1.0 + 2.3.1+3.Branch.foo.Sha.hash + +"; + var transformedXml = fs.ReadAllText(fileName); + transformedXml.ShouldBe(XElement.Parse(expectedXml).ToString()); + }); + } + + private void VerifyAssemblyInfoFile( + string projectFileContent, + string fileName, + AssemblyVersioningScheme versioningScheme = AssemblyVersioningScheme.MajorMinorPatch, + Action verify = null) + { + fileSystem = Substitute.For(); + var version = new SemanticVersion + { + BuildMetaData = new SemanticVersionBuildMetaData("versionSourceHash", 3, "foo", "hash", "shortHash", DateTimeOffset.Now), + Major = 2, + Minor = 3, + Patch = 1 + }; + + fileSystem.Exists(fileName).Returns(true); + fileSystem.ReadAllText(fileName).Returns(projectFileContent); + fileSystem.When(f => f.WriteAllText(fileName, Arg.Any())).Do(c => + { + projectFileContent = c.ArgAt(1); + fileSystem.ReadAllText(fileName).Returns(projectFileContent); + }); + + var config = new TestEffectiveConfiguration(assemblyVersioningScheme: versioningScheme); + var variables = variableProvider.GetVariablesFor(version, config, false); + + verify?.Invoke(fileSystem, variables); + } + } +} diff --git a/src/GitVersionCore/Common/IFileSystem.cs b/src/GitVersionCore/Common/IFileSystem.cs index 55dd3e0b5c..bfd32bcbd5 100644 --- a/src/GitVersionCore/Common/IFileSystem.cs +++ b/src/GitVersionCore/Common/IFileSystem.cs @@ -13,7 +13,7 @@ public interface IFileSystem string ReadAllText(string path); void WriteAllText(string file, string fileContents); void WriteAllText(string file, string fileContents, Encoding encoding); - IEnumerable DirectoryGetFiles(string directory, string searchPattern, SearchOption searchOption); + IEnumerable DirectoryEnumerateFiles(string directory, string searchPattern, SearchOption searchOption); Stream OpenWrite(string path); Stream OpenRead(string path); void CreateDirectory(string path); diff --git a/src/GitVersionCore/Core/FileSystem.cs b/src/GitVersionCore/Core/FileSystem.cs index 3f5ef24f7f..cb64fee773 100644 --- a/src/GitVersionCore/Core/FileSystem.cs +++ b/src/GitVersionCore/Core/FileSystem.cs @@ -46,9 +46,9 @@ public void WriteAllText(string file, string fileContents, Encoding encoding) File.WriteAllText(file, fileContents, encoding); } - public IEnumerable DirectoryGetFiles(string directory, string searchPattern, SearchOption searchOption) + public IEnumerable DirectoryEnumerateFiles(string directory, string searchPattern, SearchOption searchOption) { - return Directory.GetFiles(directory, searchPattern, searchOption); + return Directory.EnumerateFiles(directory, searchPattern, searchOption); } public Stream OpenWrite(string path) diff --git a/src/GitVersionCore/Core/GitVersionTool.cs b/src/GitVersionCore/Core/GitVersionTool.cs index 041f79a520..1a5cdc0dcc 100644 --- a/src/GitVersionCore/Core/GitVersionTool.cs +++ b/src/GitVersionCore/Core/GitVersionTool.cs @@ -24,6 +24,7 @@ public class GitVersionTool : IGitVersionTool private readonly IWixVersionFileUpdater wixVersionFileUpdater; private readonly IGitVersionInfoGenerator gitVersionInfoGenerator; private readonly IAssemblyInfoFileUpdater assemblyInfoFileUpdater; + private readonly IProjectFileUpdater projectFileUpdater; private readonly IOptions options; private readonly Lazy versionContext; @@ -32,7 +33,7 @@ public class GitVersionTool : IGitVersionTool public GitVersionTool(ILog log, INextVersionCalculator nextVersionCalculator, IVariableProvider variableProvider, IGitPreparer gitPreparer, IGitVersionCache gitVersionCache, IGitVersionCacheKeyFactory cacheKeyFactory, IOutputGenerator outputGenerator, IWixVersionFileUpdater wixVersionFileUpdater, IGitVersionInfoGenerator gitVersionInfoGenerator, IAssemblyInfoFileUpdater assemblyInfoFileUpdater, - IOptions options, Lazy versionContext) + IOptions options, Lazy versionContext, IProjectFileUpdater projectFileUpdater) { this.log = log ?? throw new ArgumentNullException(nameof(log)); @@ -50,6 +51,7 @@ public GitVersionTool(ILog log, INextVersionCalculator nextVersionCalculator, IV this.options = options ?? throw new ArgumentNullException(nameof(options)); this.versionContext = versionContext ?? throw new ArgumentNullException(nameof(versionContext)); + this.projectFileUpdater = projectFileUpdater; } public VersionVariables CalculateVersionVariables() @@ -92,12 +94,20 @@ public void OutputVariables(VersionVariables variables) public void UpdateAssemblyInfo(VersionVariables variables) { var gitVersionOptions = options.Value; + var assemblyInfoContext = new AssemblyInfoContext(gitVersionOptions.WorkingDirectory, gitVersionOptions.AssemblyInfo.EnsureAssemblyInfo, gitVersionOptions.AssemblyInfo.Files.ToArray()); - if (gitVersionOptions.AssemblyInfo.ShouldUpdate) + if (gitVersionOptions.AssemblyInfo.UpdateProjectFiles) + { + using (projectFileUpdater) + { + projectFileUpdater.Execute(variables, assemblyInfoContext); + } + } + else if (gitVersionOptions.AssemblyInfo.UpdateAssemblyInfo) { using (assemblyInfoFileUpdater) { - assemblyInfoFileUpdater.Execute(variables, new AssemblyInfoContext(gitVersionOptions.WorkingDirectory, gitVersionOptions.AssemblyInfo.EnsureAssemblyInfo, gitVersionOptions.AssemblyInfo.Files.ToArray())); + assemblyInfoFileUpdater.Execute(variables, assemblyInfoContext); } } } diff --git a/src/GitVersionCore/GitVersionCoreModule.cs b/src/GitVersionCore/GitVersionCoreModule.cs index 99f7c6bde3..93279c23b2 100644 --- a/src/GitVersionCore/GitVersionCoreModule.cs +++ b/src/GitVersionCore/GitVersionCoreModule.cs @@ -50,6 +50,7 @@ public void RegisterTypes(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(sp => sp.GetService().Create()); diff --git a/src/GitVersionCore/Model/AssemblyInfoData.cs b/src/GitVersionCore/Model/AssemblyInfoData.cs index ec886c0fa8..36d6f23552 100644 --- a/src/GitVersionCore/Model/AssemblyInfoData.cs +++ b/src/GitVersionCore/Model/AssemblyInfoData.cs @@ -4,7 +4,8 @@ namespace GitVersion { public class AssemblyInfoData { - public bool ShouldUpdate; + public bool UpdateAssemblyInfo; + public bool UpdateProjectFiles; public bool EnsureAssemblyInfo; public ISet Files = new HashSet(); } diff --git a/src/GitVersionCore/VersionConverters/AssemblyInfo/AssemblyInfoFileUpdater.cs b/src/GitVersionCore/VersionConverters/AssemblyInfo/AssemblyInfoFileUpdater.cs index bef1316c34..dbf5a9ee92 100644 --- a/src/GitVersionCore/VersionConverters/AssemblyInfo/AssemblyInfoFileUpdater.cs +++ b/src/GitVersionCore/VersionConverters/AssemblyInfo/AssemblyInfoFileUpdater.cs @@ -177,7 +177,7 @@ private IEnumerable GetAssemblyInfoFiles(AssemblyInfoContext context) } else { - foreach (var item in fileSystem.DirectoryGetFiles(workingDirectory, "AssemblyInfo.*", SearchOption.AllDirectories)) + foreach (var item in fileSystem.DirectoryEnumerateFiles(workingDirectory, "AssemblyInfo.*", SearchOption.AllDirectories)) { var assemblyInfoFile = new FileInfo(item); diff --git a/src/GitVersionCore/VersionConverters/AssemblyInfo/ProjectFileUpdater.cs b/src/GitVersionCore/VersionConverters/AssemblyInfo/ProjectFileUpdater.cs new file mode 100644 index 0000000000..84a9224ff7 --- /dev/null +++ b/src/GitVersionCore/VersionConverters/AssemblyInfo/ProjectFileUpdater.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using GitVersion.Logging; +using GitVersion.OutputVariables; + +namespace GitVersion.VersionConverters.AssemblyInfo +{ + public interface IProjectFileUpdater : IVersionConverter + { + } + + public class ProjectFileUpdater : IProjectFileUpdater + { + internal const string AssemblyVersionElement = "AssemblyVersion"; + internal const string FileVersionElement = "FileVersion"; + internal const string InformationalVersionElement = "InformationalVersion"; + + private readonly List restoreBackupTasks = new List(); + private readonly List cleanupBackupTasks = new List(); + + private readonly IFileSystem fileSystem; + private readonly ILog log; + + public ProjectFileUpdater(ILog log, IFileSystem fileSystem) + { + this.fileSystem = fileSystem; + this.log = log; + } + + public void Execute(VersionVariables variables, AssemblyInfoContext context) + { + if (context.EnsureAssemblyInfo) + throw new WarningException($"Configuration setting {nameof(context.EnsureAssemblyInfo)} is not valid when updating project files!"); + + var projectFilesToUpdate = GetProjectFiles(context).ToList(); + + var assemblyVersion = variables.AssemblySemVer; + var assemblyInfoVersion = variables.InformationalVersion; + var assemblyFileVersion = variables.AssemblySemFileVer; + + foreach (var projectFile in projectFilesToUpdate) + { + var localProjectFile = projectFile.FullName; + + var originalFileContents = fileSystem.ReadAllText(localProjectFile); + var fileXml = XElement.Parse(originalFileContents); + + if (!CanUpdateProjectFile(fileXml)) + { + log.Warning($"Unable to update file: {localProjectFile}"); + continue; + } + + var backupProjectFile = localProjectFile + ".bak"; + fileSystem.Copy(localProjectFile, backupProjectFile, true); + + restoreBackupTasks.Add(() => + { + if (fileSystem.Exists(localProjectFile)) + { + fileSystem.Delete(localProjectFile); + } + + fileSystem.Move(backupProjectFile, localProjectFile); + }); + + cleanupBackupTasks.Add(() => fileSystem.Delete(backupProjectFile)); + + if (!string.IsNullOrWhiteSpace(assemblyVersion)) + { + UpdateProjectVersionElement(fileXml, AssemblyVersionElement, assemblyVersion); + } + + if (!string.IsNullOrWhiteSpace(assemblyFileVersion)) + { + UpdateProjectVersionElement(fileXml, FileVersionElement, assemblyFileVersion); + } + + if (!string.IsNullOrWhiteSpace(assemblyInfoVersion)) + { + UpdateProjectVersionElement(fileXml, InformationalVersionElement, assemblyInfoVersion); + } + + var outputXmlString = fileXml.ToString(); + if (originalFileContents != outputXmlString) + { + fileSystem.WriteAllText(localProjectFile, outputXmlString); + } + } + + CommitChanges(); + } + + internal bool CanUpdateProjectFile(XElement xmlRoot) + { + if (xmlRoot.Name != "Project") + { + log.Warning($"Invalid project file specified, root element must be ."); + return false; + } + + var supportedSdk = "Microsoft.NET.Sdk"; + var sdkAttribute = xmlRoot.Attribute("Sdk"); + if (sdkAttribute == null || sdkAttribute.Value != supportedSdk) + { + log.Warning($"Specified project file Sdk ({sdkAttribute?.Value}) is not supported, please ensure the project sdk is {supportedSdk}."); + return false; + } + + var propertyGroups = xmlRoot.Descendants("PropertyGroup").ToList(); + if (!propertyGroups.Any()) + { + log.Warning("Unable to locate any elements in specified project file. Are you sure it is in a correct format?"); + return false; + } + + var lastGenerateAssemblyInfoElement = propertyGroups.SelectMany(s => s.Elements("GenerateAssemblyInfo")).LastOrDefault(); + if (lastGenerateAssemblyInfoElement != null && (bool)lastGenerateAssemblyInfoElement == false) + { + log.Warning($"Project file specifies false: versions set in this project file will not affect the output artifacts."); + return false; + } + + return true; + } + + internal void UpdateProjectVersionElement(XElement xmlRoot, string versionElement, string versionValue) + { + var propertyGroups = xmlRoot.Descendants("PropertyGroup").ToList(); + + var propertyGroupToModify = propertyGroups.LastOrDefault(l => l.Element(versionElement) != null) + ?? propertyGroups.First(); + + var versionXmlElement = propertyGroupToModify.Elements(versionElement).LastOrDefault(); + if (versionXmlElement != null) + { + versionXmlElement.Value = versionValue; + } + else + { + propertyGroupToModify.SetElementValue(versionElement, versionValue); + } + } + + public void Dispose() + { + foreach (var restoreBackup in restoreBackupTasks) + { + restoreBackup(); + } + + cleanupBackupTasks.Clear(); + restoreBackupTasks.Clear(); + } + + private void CommitChanges() + { + foreach (var cleanupBackupTask in cleanupBackupTasks) + { + cleanupBackupTask(); + } + + cleanupBackupTasks.Clear(); + restoreBackupTasks.Clear(); + } + + private IEnumerable GetProjectFiles(AssemblyInfoContext context) + { + var workingDirectory = context.WorkingDirectory; + var assemblyInfoFileNames = new HashSet(context.AssemblyInfoFiles); + + if (assemblyInfoFileNames.Any(x => !string.IsNullOrWhiteSpace(x))) + { + foreach (var item in assemblyInfoFileNames) + { + var fullPath = Path.Combine(workingDirectory, item); + + if (fileSystem.Exists(fullPath)) + { + yield return new FileInfo(fullPath); + } + else + { + log.Warning($"Specified file {fullPath} was not found and will not be updated."); + } + } + } + else + { + foreach (var item in fileSystem.DirectoryEnumerateFiles(workingDirectory, "*", SearchOption.AllDirectories).Where(IsSupportedProjectFile)) + { + var assemblyInfoFile = new FileInfo(item); + + yield return assemblyInfoFile; + } + } + } + + private bool IsSupportedProjectFile(string fileName) + { + if (string.IsNullOrEmpty(fileName)) + { + return false; + } + + return fileName.EndsWith(".csproj") || + fileName.EndsWith(".fsproj") || + fileName.EndsWith(".vbproj"); + } + } +} diff --git a/src/GitVersionExe.Tests/ArgumentParserTests.cs b/src/GitVersionExe.Tests/ArgumentParserTests.cs index 9891f57e35..3e3d2836df 100644 --- a/src/GitVersionExe.Tests/ArgumentParserTests.cs +++ b/src/GitVersionExe.Tests/ArgumentParserTests.cs @@ -295,6 +295,16 @@ public void UpdateAssemblyInfoTrue(string command) arguments.UpdateAssemblyInfo.ShouldBe(true); } + [TestCase("-updateProjectFiles assemblyInfo.csproj")] + [TestCase("-updateProjectFiles assemblyInfo.csproj")] + [TestCase("-updateProjectFiles assemblyInfo.csproj otherAssemblyInfo.fsproj")] + [TestCase("-updateProjectFiles")] + public void UpdateProjectTrue(string command) + { + var arguments = argumentParser.ParseArguments(command); + arguments.UpdateProjectFiles.ShouldBe(true); + } + [TestCase("-updateAssemblyInfo false")] [TestCase("-updateAssemblyInfo 0")] public void UpdateAssemblyInfoFalse(string command) @@ -310,6 +320,13 @@ public void CreateMulitpleAssemblyInfoProtected(string command) exception.Message.ShouldBe("Can't specify multiple assembly info files when using /ensureassemblyinfo switch, either use a single assembly info file or do not specify /ensureassemblyinfo and create assembly info files manually"); } + [TestCase("-updateProjectFiles Assembly.csproj -ensureassemblyinfo")] + public void UpdateProjectInfoWithEnsureAssemblyInfoProtected(string command) + { + var exception = Assert.Throws(() => argumentParser.ParseArguments(command)); + exception.Message.ShouldBe("Cannot specify -ensureassemblyinfo with updateprojectfiles: please ensure your project file exists before attempting to update it"); + } + [Test] public void UpdateAssemblyInfoWithFilename() { @@ -342,6 +359,24 @@ public void UpdateAssemblyInfoWithMultipleFilenames() arguments.UpdateAssemblyInfoFileName.ShouldContain(x => Path.GetFileName(x).Equals("VersionAssemblyInfo.cs")); } + [Test] + public void UpdateProjectFilesWithMultipleFilenames() + { + using var repo = new EmptyRepositoryFixture(); + + var assemblyFile1 = Path.Combine(repo.RepositoryPath, "CommonAssemblyInfo.csproj"); + using var file = File.Create(assemblyFile1); + + var assemblyFile2 = Path.Combine(repo.RepositoryPath, "VersionAssemblyInfo.csproj"); + using var file2 = File.Create(assemblyFile2); + + var arguments = argumentParser.ParseArguments($"-targetpath {repo.RepositoryPath} -updateProjectFiles CommonAssemblyInfo.csproj VersionAssemblyInfo.csproj"); + arguments.UpdateProjectFiles.ShouldBe(true); + arguments.UpdateAssemblyInfoFileName.Count.ShouldBe(2); + arguments.UpdateAssemblyInfoFileName.ShouldContain(x => Path.GetFileName(x).Equals("CommonAssemblyInfo.csproj")); + arguments.UpdateAssemblyInfoFileName.ShouldContain(x => Path.GetFileName(x).Equals("VersionAssemblyInfo.csproj")); + } + [Test] public void UpdateAssemblyInfoWithMultipleFilenamesMatchingGlobbing() { diff --git a/src/GitVersionExe/ArgumentParser.cs b/src/GitVersionExe/ArgumentParser.cs index 878b06b8a2..015e6d51a4 100644 --- a/src/GitVersionExe/ArgumentParser.cs +++ b/src/GitVersionExe/ArgumentParser.cs @@ -206,6 +206,12 @@ private static bool ParseSwitches(Arguments arguments, string name, string[] val if (ParseExecArguments(arguments, name, values, value)) return true; + if (name.IsSwitch("updateprojectfiles")) + { + ParseUpdateProjectInfo(arguments, value, values); + return true; + } + if (name.IsSwitch("updateAssemblyInfo")) { ParseUpdateAssemblyInfo(arguments, value, values); @@ -419,6 +425,11 @@ private static void ParseEnsureAssemblyInfo(Arguments arguments, string value) arguments.EnsureAssemblyInfo = false; } + if (arguments.UpdateProjectFiles) + { + throw new WarningException("Cannot specify -ensureassemblyinfo with updateprojectfiles: please ensure your project file exists before attempting to update it"); + } + if (arguments.UpdateAssemblyInfoFileName.Count > 1 && arguments.EnsureAssemblyInfo) { throw new WarningException("Can't specify multiple assembly info files when using /ensureassemblyinfo switch, either use a single assembly info file or do not specify /ensureassemblyinfo and create assembly info files manually"); @@ -515,12 +526,57 @@ private static void ParseUpdateAssemblyInfo(Arguments arguments, string value, s arguments.UpdateAssemblyInfo = true; } + if (arguments.UpdateProjectFiles) + { + throw new WarningException("Cannot specify both updateprojectfiles and updateassemblyinfo in the same run. Please rerun GitVersion with only one parameter"); + } if (arguments.UpdateAssemblyInfoFileName.Count > 1 && arguments.EnsureAssemblyInfo) { throw new WarningException("Can't specify multiple assembly info files when using -ensureassemblyinfo switch, either use a single assembly info file or do not specify -ensureassemblyinfo and create assembly info files manually"); } } + private static void ParseUpdateProjectInfo(Arguments arguments, string value, string[] values) + { + if (value.IsTrue()) + { + arguments.UpdateProjectFiles = true; + } + else if (value.IsFalse()) + { + arguments.UpdateProjectFiles = false; + } + else if (values != null && values.Length > 1) + { + arguments.UpdateProjectFiles = true; + foreach (var v in values) + { + arguments.UpdateAssemblyInfoFileName.Add(v); + } + } + else if (!value.IsSwitchArgument()) + { + arguments.UpdateProjectFiles = true; + if (value != null) + { + arguments.UpdateAssemblyInfoFileName.Add(value); + } + } + else + { + arguments.UpdateProjectFiles = true; + } + + if (arguments.UpdateAssemblyInfo) + { + throw new WarningException("Cannot specify both updateassemblyinfo and updateprojectfiles in the same run. Please rerun GitVersion with only one parameter"); + } + if (arguments.EnsureAssemblyInfo) + { + throw new WarningException("Cannot specify -ensureassemblyinfo with updateprojectfiles: please ensure your project file exists before attempting to update it"); + } + } + private static void EnsureArgumentValueCount(IReadOnlyList values) { if (values != null && values.Count > 1) diff --git a/src/GitVersionExe/Arguments.cs b/src/GitVersionExe/Arguments.cs index 9c9afd22f7..b225d08426 100644 --- a/src/GitVersionExe/Arguments.cs +++ b/src/GitVersionExe/Arguments.cs @@ -38,9 +38,10 @@ public class Arguments public ISet Output = new HashSet(); public Verbosity Verbosity = Verbosity.Normal; + public bool UpdateProjectFiles; public bool UpdateAssemblyInfo; - public ISet UpdateAssemblyInfoFileName = new HashSet(); public bool EnsureAssemblyInfo; + public ISet UpdateAssemblyInfoFileName = new HashSet(); [Obsolete] public string Proj; @@ -61,7 +62,8 @@ public GitVersionOptions ToOptions() AssemblyInfo = { - ShouldUpdate = UpdateAssemblyInfo, + UpdateProjectFiles = UpdateProjectFiles, + UpdateAssemblyInfo = UpdateAssemblyInfo, EnsureAssemblyInfo = EnsureAssemblyInfo, Files = UpdateAssemblyInfoFileName }, diff --git a/src/GitVersionExe/HelpWriter.cs b/src/GitVersionExe/HelpWriter.cs index 4dd030aa77..5a85eafa91 100644 --- a/src/GitVersionExe/HelpWriter.cs +++ b/src/GitVersionExe/HelpWriter.cs @@ -53,6 +53,9 @@ path The directory containing .git. If not defined current directory # AssemblyInfo updating /updateassemblyinfo Will recursively search for all 'AssemblyInfo.cs' files in the git repo and update them + /updateprojectfiles + Will recursively search for all project files (.csproj/.vbproj/.fsproj) files in the git repo and update them + Note: This is only compatible with the newer Sdk projects /updateassemblyinfofilename Specify name of AssemblyInfo file. Can also /updateAssemblyInfo GlobalAssemblyInfo.cs as a shorthand /ensureassemblyinfo diff --git a/src/GitVersionTask/GitVersionTaskExecutor.cs b/src/GitVersionTask/GitVersionTaskExecutor.cs index fec45bb227..bc28131aad 100644 --- a/src/GitVersionTask/GitVersionTaskExecutor.cs +++ b/src/GitVersionTask/GitVersionTaskExecutor.cs @@ -37,7 +37,7 @@ public void UpdateAssemblyInfo(UpdateAssemblyInfo task) task.AssemblyInfoTempFilePath = Path.Combine(fileWriteInfo.WorkingDirectory, fileWriteInfo.FileName); var gitVersionOptions = options.Value; - gitVersionOptions.AssemblyInfo.ShouldUpdate = true; + gitVersionOptions.AssemblyInfo.UpdateAssemblyInfo = true; gitVersionOptions.AssemblyInfo.EnsureAssemblyInfo = true; gitVersionOptions.WorkingDirectory = fileWriteInfo.WorkingDirectory; gitVersionOptions.AssemblyInfo.Files.Add(fileWriteInfo.FileName);