From c1baec7ec65eb481fefbaf625c775e2901b58bc8 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 29 Dec 2016 21:05:58 +0000 Subject: [PATCH] Independently version multiple projects --- ImageSharp.sln | 9 + build.cmd | 37 +--- build/Program.cs | 252 +++++++++++++++++++++++++++ build/Properties/launchSettings.json | 7 + build/build-inner.cmd | 0 build/build.cmd | 18 ++ build/build.xproj | 25 +++ build/project.json | 22 +++ build/reset-versions.cmd | 23 +-- build/update-versions.cmd | 41 ----- 10 files changed, 337 insertions(+), 97 deletions(-) create mode 100644 build/Program.cs create mode 100644 build/Properties/launchSettings.json create mode 100644 build/build-inner.cmd create mode 100644 build/build.cmd create mode 100644 build/build.xproj create mode 100644 build/project.json delete mode 100644 build/update-versions.cmd diff --git a/ImageSharp.sln b/ImageSharp.sln index 01854bda1..f6f202531 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -35,6 +35,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{56801022 EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ImageSharp.Drawing", "src\ImageSharp.Drawing\ImageSharp.Drawing.xproj", "{2E33181E-6E28-4662-A801-E2E7DC206029}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{E919DF0B-2607-4462-8FC0-5C98FE50F8C9}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "build", "build\build.xproj", "{575A5002-DD9F-4335-AA47-1DD87FA13645}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +61,10 @@ Global {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.Build.0 = Release|Any CPU + {575A5002-DD9F-4335-AA47-1DD87FA13645}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {575A5002-DD9F-4335-AA47-1DD87FA13645}.Debug|Any CPU.Build.0 = Debug|Any CPU + {575A5002-DD9F-4335-AA47-1DD87FA13645}.Release|Any CPU.ActiveCfg = Release|Any CPU + {575A5002-DD9F-4335-AA47-1DD87FA13645}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -66,5 +74,6 @@ Global {F836E8E6-B4D9-4208-8346-140C74678B91} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {299D8E18-102C-42DE-ADBF-79098EE706A8} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2E33181E-6E28-4662-A801-E2E7DC206029} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} + {575A5002-DD9F-4335-AA47-1DD87FA13645} = {E919DF0B-2607-4462-8FC0-5C98FE50F8C9} EndGlobalSection EndGlobal diff --git a/build.cmd b/build.cmd index 6a52e0820..e33a230bc 100644 --- a/build.cmd +++ b/build.cmd @@ -1,37 +1,2 @@ - - @echo Off -ECHO Starting build - -call build\config.cmd - -ECHO Restoring packages -for %%s in (%projects%) do dotnet restore %%s - -call build\update-versions.cmd - -set buildRoot="%~dp0" -SET cli=dotnet pack --configuration Release --output "artifacts\bin\ImageSharp" -where gitversion -if not "%errorlevel%"=="0" ( - REM gitversion was not availible lets make a local build - SET cli=%cli% --version-suffix "local-build" -) - -ECHO Building packages -for %%s in (%projects%) do %cli% %%s - -REM reset local version numbers -call build\reset-versions.cmd - -:success -ECHO successfully built project -REM exit 0 -goto end - -:failure -ECHO failed to build. -REM exit -1 -goto end - -:end +call build\build.cmd \ No newline at end of file diff --git a/build/Program.cs b/build/Program.cs new file mode 100644 index 000000000..95e91cecd --- /dev/null +++ b/build/Program.cs @@ -0,0 +1,252 @@ +using Microsoft.DotNet.ProjectModel; +using System; +using System.IO; +using System.Linq; +using NuGet.Versioning; +using System.Collections.Generic; +using LibGit2Sharp; +using Newtonsoft.Json; +using System.Text; + +namespace ConsoleApplication +{ + public class Program + { + const string fallbackTag = "CI"; + + public static void Main(string[] args) + { + var resetmode = args.Contains("reset"); + + // find the project root where glbal.json lives + var root = ProjectRootResolver.ResolveRootDirectory("."); + //lets find the repo + var repo = new LibGit2Sharp.Repository(root); + + //lets find all the project.json files in the src folder (don't care about versioning tests) + var projectFiles = Directory.EnumerateFiles(Path.Combine(root, "src"), Project.FileName, SearchOption.AllDirectories); + + //open them and convert them to source projects + var projects = projectFiles.Select(x => ProjectReader.GetProject(x)) + .Select(x => new SourceProject(x, repo.Info.WorkingDirectory)) + .ToList(); + if (resetmode) + { + if (File.Exists("build-inner.bak")) + { + File.Copy("build-inner.bak", "build-inner.cmd", true); + File.Delete("build-inner.bak"); + } + + //revert the project.json change be reverting it but skipp all the git stuff as its not needed + foreach (var p in projects) + { + if (File.Exists($"{p.FullProjectFilePath}.bak")) + { + File.Copy($"{p.FullProjectFilePath}.bak", p.FullProjectFilePath, true); + File.Delete($"{p.FullProjectFilePath}.bak"); + } + Console.WriteLine($"{p.Name} {p.Version.ToFullString()}"); + } + } + else + { + // populate the dependency chains + projects.ForEach(x => x.PopulateDependencies(projects)); + + + foreach (var p in projects) + { + foreach (var c in repo.Commits) + { + // lets apply each commit to the projects looking to see if they effect it or a + // dependency and detect if the commit was one that sent the current version + if (!p.ApplyCommit(c, repo)) + { + // if it did create the version then stop looking this one wins + break; + } + } + } + + // lets build version friendly commit + string branch = repo.Head.FriendlyName; + branch = branch.Replace("/", "-").Replace("--", "-"); + if (branch == "master") + { + branch = ""; + } + + var sb = new StringBuilder(); + foreach (var p in projects) + { + //we skip the build number and standard CI prefix on first commits + var newVersion = p.CalculateVersionNumber(branch); + File.Copy(p.FullProjectFilePath, $"{p.FullProjectFilePath}.bak", true); + dynamic projectFile = JsonConvert.DeserializeObject(File.ReadAllText(p.FullProjectFilePath)); + + projectFile.version = $"{newVersion}-*"; + File.WriteAllText(p.FullProjectFilePath, JsonConvert.SerializeObject(projectFile, Formatting.Indented)); + + Console.WriteLine($"{p.Name} {newVersion}"); + + sb.AppendLine($@"dotnet pack --configuration Release --output ""artifacts\bin\ImageSharp"" ""{p.ProjectFilePath}"""); + } + + File.Copy("build-inner.cmd", "build-inner.bak", true); + File.WriteAllText("build-inner.cmd", sb.ToString()); + } + } + + + + //find project root + public static string GetProjectRoot() + { + var path = Path.GetFullPath("."); + do + { + var jsonPath = Path.Combine(path, "global.json"); + if (File.Exists(jsonPath)) + { + return path; + } + else + { + path = Path.GetDirectoryName(path); + } + } while (!string.IsNullOrWhiteSpace(path)); + + return null; + } + + public class SourceProject + { + private readonly IEnumerable dependencies; + + public string ProjectDirectory { get; } + + public NuGetVersion Version { get; } + + public List DependentProjects { get; private set; } + public string Name { get; private set; } + public string ProjectFilePath { get; private set; } + + private int commitCount = 0; + public string FullProjectFilePath { get; private set; } + + public SourceProject(Project project, string root) + { + this.Name = project.Name; + this.ProjectDirectory = project.ProjectDirectory.Substring(root.Length); + this.ProjectFilePath = project.ProjectFilePath.Substring(root.Length); + this.FullProjectFilePath = project.ProjectFilePath; + this.Version = project.Version; + this.dependencies = project.Dependencies.Select(x => x.Name); + } + + public void PopulateDependencies(IEnumerable projects) + { + DependentProjects = projects.Where(x => dependencies.Contains(x.Name)).ToList(); + + } + + private bool MatchPath(string path) + { + if(path.StartsWith(this.ProjectDirectory, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (DependentProjects.Any()) + { + return DependentProjects.Any(x => x.MatchPath(path)); + } + return false; + } + + internal bool ApplyCommitInternal(Commit commit, TreeChanges changes, Repository repo) + { + commitCount++; + + //return false if this is a version number root + var projectFileChange = changes.Where(x => x.Path?.Equals(this.ProjectFilePath, StringComparison.OrdinalIgnoreCase) == true).FirstOrDefault(); + if(projectFileChange != null) + { + if(projectFileChange.Status == ChangeKind.Added) + { + // the version must have been set here + return false; + } + else + { + var blob = repo.Lookup(projectFileChange.Oid); + using (var s = blob.GetContentStream()) + { + var project = new ProjectReader().ReadProject(s, this.Name, this.FullProjectFilePath, null); + if(project.Version != this.Version) + { + //version changed + return false; + } + } + } + // version must have been the same lets carry on + return true; + } + + return true; + } + internal bool ApplyCommit(Commit commit, Repository repo) + { + foreach (var parent in commit.Parents) + { + var changes = repo.Diff.Compare(parent.Tree, commit.Tree); + + foreach (TreeEntryChanges change in changes) + { + if (!string.IsNullOrWhiteSpace(change.OldPath)) + { + if (MatchPath(change.OldPath)) + { + return ApplyCommitInternal(commit, changes, repo); + } + } + + if (!string.IsNullOrWhiteSpace(change.Path)) + { + if (MatchPath(change.Path)) + { + return ApplyCommitInternal(commit, changes, repo); + } + } + } + } + + return true; + } + + internal string CalculateVersionNumber(string branch) + { + var version = this.Version.ToFullString(); + if (this.commitCount == 1 && branch == "") //master only + { + if (this.Version.IsPrerelease) + { + //prerelease always needs the build counter just not on a branch name + return $"{version}-{this.commitCount:000000}"; + } + //only 1 commit (the changing one) we will skip appending suffix + return version; + } + + if (branch == "") + { + branch = fallbackTag; + } + + return $"{version}-{branch}-{this.commitCount:000000}"; + } + } + } +} diff --git a/build/Properties/launchSettings.json b/build/Properties/launchSettings.json new file mode 100644 index 000000000..b3ff6cb02 --- /dev/null +++ b/build/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "build": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/build/build-inner.cmd b/build/build-inner.cmd new file mode 100644 index 000000000..e69de29bb diff --git a/build/build.cmd b/build/build.cmd new file mode 100644 index 000000000..d5a886219 --- /dev/null +++ b/build/build.cmd @@ -0,0 +1,18 @@ +@echo Off +set buildRoot="%cd%" + +ECHO restoring packages +dotnet restore + +ECHO Updating version numbers and generating build script +cd %~dp0 +dotnet run -- update +cd %buildRoot% + +ECHO Building package +call %~dp0build-inner.cmd + +ECHO Reset version numbers +cd %~dp0 +dotnet run -- reset +cd %buildRoot% \ No newline at end of file diff --git a/build/build.xproj b/build/build.xproj new file mode 100644 index 000000000..3b85ca872 --- /dev/null +++ b/build/build.xproj @@ -0,0 +1,25 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 575a5002-dd9f-4335-aa47-1dd87fa13645 + build + .\obj + .\bin\ + v4.5.1 + + + 2.0 + + + True + + + + + + \ No newline at end of file diff --git a/build/project.json b/build/project.json new file mode 100644 index 000000000..4f13b397f --- /dev/null +++ b/build/project.json @@ -0,0 +1,22 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "debugType": "portable", + "emitEntryPoint": true + }, + "dependencies": { + "Microsoft.DotNet.ProjectModel": "1.0.0-rc3-003121", + "LibGit2Sharp": "0.23.0" + }, + "frameworks": { + "net46": { + //"dependencies": { + // "Microsoft.NETCore.App": { + // "type": "platform", + // "version": "1.0.0" + // } + //}, + //"imports": "dnxcore50" + } + } +} diff --git a/build/reset-versions.cmd b/build/reset-versions.cmd index 0be7817da..31d1d2431 100644 --- a/build/reset-versions.cmd +++ b/build/reset-versions.cmd @@ -1,25 +1,8 @@ - - @echo Off -REM include project configs -call %~dp0\config.cmd set buildRoot="%cd%" +cd %~dp0 -ECHO Reseting build version numbers -for %%s in (%projects%) do ( - cd %%s - ECHO %GitVersion_NuGetVersion% - dotnet version "1.0.0-*" - cd %buildRoot% -) - -:success -REM exit 0 -goto end - -:failure -REM exit -1 -goto end +dotnet run -- reset -:end +cd %buildRoot% \ No newline at end of file diff --git a/build/update-versions.cmd b/build/update-versions.cmd deleted file mode 100644 index 9847144b4..000000000 --- a/build/update-versions.cmd +++ /dev/null @@ -1,41 +0,0 @@ - - -@echo Off -REM include project configs -call %~dp0\config.cmd - -REM gitversion not already been set in this build -if "%GitVersion_NuGetVersion%" == "" ( - rem can I call gitversion - where gitversion - if "%errorlevel%"=="0" ( - REM call gitversion and then recall this build script with the envArgs set - ECHO calculating correct version number - - REM call this file from itself with the args set - gitversion /output buildserver /exec "%~dp0\update-versions.cmd" - - REM we looped skip to the end - goto end - ) -) - -set buildRoot="%cd%" - -ECHO Updating build version numbers -for %%s in (%projects%) do ( - cd %%s - ECHO %GitVersion_NuGetVersion% - dotnet version %GitVersion_NuGetVersion% - cd %buildRoot% -) - -:success -REM exit 0 -goto end - -:failure -REM exit -1 -goto end - -:end