diff --git a/ln.build/pipeline/DeployCommand.cs b/ln.build/pipeline/DeployCommand.cs index 2cee705..6e5e578 100644 --- a/ln.build/pipeline/DeployCommand.cs +++ b/ln.build/pipeline/DeployCommand.cs @@ -1,6 +1,7 @@ using System; using System.IO; using ln.build.repositories; +using ln.build.semver; using ln.logging; namespace ln.build.pipeline @@ -15,7 +16,36 @@ namespace ln.build.pipeline public static void Release(Stage stage,params string[] arguments) { - stage.CommandEnvironment.Logger.Log(LogLevel.WARNING, "stage command: release not yet implemented"); + if (stage.CommandEnvironment.CIJob.Repository is Repository repository) + { + SemVersion releaseVersion = (SemVersion)stage.CommandEnvironment.Get("RELEASE_VERSION"); + if (releaseVersion != null) + { + Release release = repository.GetRelease(releaseVersion.ToString()); + if (release != null) + { + stage.CommandEnvironment.Logger.Log(LogLevel.WARNING, "source repository already has release {0}", releaseVersion); + } else + { + string releaseBody = ""; + string release_ref = stage.CommandEnvironment.Get("REPO_REF"); + if (release_ref == null) + { + stage.CommandEnvironment.Logger.Log(LogLevel.WARNING,"release: no source repository reference found. can't create release!"); + } else + { + release = repository.CreateRelease(releaseVersion, releaseBody, release_ref); + foreach (string artefact in stage.CommandEnvironment.Get("RELEASE_ARTEFACTS","").Split(':')) + { + release.CreateOrReplaceAttachment(artefact,Path.GetFileName(artefact)); + } + } + } + } + } else + { + stage.CommandEnvironment.Logger.Log(LogLevel.WARNING, "release: no repository interface attached to CIJob"); + } } diff --git a/ln.build/pipeline/DotNetCommands.cs b/ln.build/pipeline/DotNetCommands.cs index 8a784f0..5970959 100644 --- a/ln.build/pipeline/DotNetCommands.cs +++ b/ln.build/pipeline/DotNetCommands.cs @@ -36,7 +36,8 @@ namespace ln.build.commands // ToDo: Parse .sln files for referenced projects - + SemVersion highestVersion = new SemVersion(0,0,0); + foreach (string projectFileName in projectFiles) { stage.PipeLine.CommandEnvironment.Extend("DOTNET_PROJECTS", projectFileName); @@ -47,8 +48,12 @@ namespace ln.build.commands string ot = csp.GetOutputType(); stage.CommandEnvironment.Logger.Log(LogLevel.INFO, "dotnet prepare: project {0} version={1} type={2}", projectName, projectVersion, ot); + + if (projectVersion > highestVersion) + highestVersion = projectVersion; } + stage.PipeLine.CommandEnvironment.Set("RELEASE_VERSION", highestVersion.ToString()); } public static void Build(Stage stage,params string[] arguments) @@ -174,6 +179,11 @@ namespace ln.build.commands string[] nupkgList = stage.CommandEnvironment.Get("DOTNET_ARTEFACTS","").Split(':').Where((fn)=>fn.EndsWith(".nupkg")).ToArray(); string[] binaryList = stage.CommandEnvironment.Get("DOTNET_ARTEFACTS","").Split(':').Where((fn)=>!fn.EndsWith(".nupkg")).ToArray(); + foreach (string binaryArtefact in binaryList) + { + stage.PipeLine.CommandEnvironment.Extend("RELEASE_ARTEFACTS", binaryArtefact); + } + if (nupkgList.Length > 0) { string nugetSource = stage.CommandEnvironment.Get("NUGET_SOURCE"); @@ -200,6 +210,8 @@ namespace ln.build.commands { argNupkg.SetValue(nupkg); cr.Run(stage.CommandEnvironment); + + stage.PipeLine.CommandEnvironment.Extend("RELEASE_ARTEFACTS", nupkg); } } } diff --git a/ln.build/repositories/Release.cs b/ln.build/repositories/Release.cs index 601a9d2..87cb952 100644 --- a/ln.build/repositories/Release.cs +++ b/ln.build/repositories/Release.cs @@ -13,6 +13,8 @@ namespace ln.build.repositories public Boolean IsDraft {get; set; } public Boolean IsPreRelease {get; set; } + public string TargetCommit { get; set; } + public string Body { get; set; } public abstract Repository Repository { get; } diff --git a/ln.build/repositories/Repository.cs b/ln.build/repositories/Repository.cs index 8e81df7..7903d4f 100644 --- a/ln.build/repositories/Repository.cs +++ b/ln.build/repositories/Repository.cs @@ -1,5 +1,6 @@ using System; +using ln.build.semver; using ln.http; namespace ln.build.repositories @@ -14,6 +15,8 @@ namespace ln.build.repositories public abstract Release GetRelease(string tagName); public abstract Release GetRelease(int id); + public abstract Release CreateRelease(SemVersion releaseVersion, string body, string target_reference); + public abstract void CommitAndPush(string message, string[] addedPaths, string[] modifiedPaths, string[] removedPaths); diff --git a/ln.build/repositories/gitea/GiteaRelease.cs b/ln.build/repositories/gitea/GiteaRelease.cs index 7fd5e29..75322a4 100644 --- a/ln.build/repositories/gitea/GiteaRelease.cs +++ b/ln.build/repositories/gitea/GiteaRelease.cs @@ -15,11 +15,17 @@ namespace ln.build.repositories.gitea { public override Repository Repository => GiteaRepository; public GiteaRepository GiteaRepository { get; } + public GiteaRelease(GiteaRepository repository) { GiteaRepository = repository; } public GiteaRelease(GiteaRepository repository,JSONObject jsonRelease) : this(repository) + { + UpdateFromJson(jsonRelease); + } + + public void UpdateFromJson(JSONObject jsonRelease) { Id = (int)(long)jsonRelease["id"].ToNative(); Name = jsonRelease["name"].ToNative().ToString(); @@ -27,6 +33,20 @@ namespace ln.build.repositories.gitea Body = jsonRelease["body"].ToNative().ToString(); IsDraft = (bool)jsonRelease["draft"].ToNative(); IsPreRelease = (bool)jsonRelease["prerelease"].ToNative(); + TargetCommit = jsonRelease["target_commitish"].ToNative().ToString(); + } + + public JSONObject ToJson() + { + JSONObject jsonRelease = new JSONObject(); + jsonRelease.Add("name", Name); + jsonRelease.Add("tag_name", TagName); + jsonRelease.Add("target_comittish", TargetCommit); + jsonRelease.Add("prerelease",IsPreRelease); + jsonRelease.Add("draft",IsDraft); + jsonRelease.Add("body",Body); + + return jsonRelease; } public override Attachment CreateOrReplaceAttachment(string localPath, string remoteFileName) @@ -63,6 +83,7 @@ namespace ln.build.repositories.gitea public override string Name { get; set; } public override string DownloadURL { get => downloadURL; set => throw new NotImplementedException(); } + public GiteaAttachment(GiteaRelease giteaRelease) { GiteaRelease = giteaRelease; diff --git a/ln.build/repositories/gitea/GiteaRepository.cs b/ln.build/repositories/gitea/GiteaRepository.cs index b451417..67310f0 100644 --- a/ln.build/repositories/gitea/GiteaRepository.cs +++ b/ln.build/repositories/gitea/GiteaRepository.cs @@ -88,7 +88,11 @@ namespace ln.build.repositories.gitea public override Release GetRelease(string tagName) { - throw new NotImplementedException(); + if (HttpStatusCode.OK == Client.GetJson(out JSONValue jsonRelease, "repos", Owner, Name, "releases", "tags", tagName)) + { + return new GiteaRelease(this, jsonRelease as JSONObject); + } + return null; } public override Release GetRelease(int releaseId) { @@ -99,6 +103,16 @@ namespace ln.build.repositories.gitea return null; } + public override Release CreateRelease(SemVersion releaseVersion, string body, string target_reference) + { + GiteaRelease giteaRelease = new GiteaRelease(this){ TagName = releaseVersion.ToString(), Body = body, TargetCommit = target_reference }; + + if (HttpStatusCode.Created != Client.PostJson(giteaRelease.ToJson(), out JSONValue jsonRelease, "repos", Owner, Name, "releases")) + throw new Exception(string.Format("release could not be created => {0}", giteaRelease.ToJson())); + + giteaRelease.UpdateFromJson(jsonRelease as JSONObject); + return giteaRelease; + } public bool ContainsFile(string filename,string _ref) => (Client.GetJson(out JSONValue response, "repos", Owner, Name, "contents", string.Format("{0}?ref={1}",filename, _ref)) == HttpStatusCode.OK); diff --git a/ln.build/scripts/pipeline/dotnet.ln b/ln.build/scripts/pipeline/dotnet.ln index 65c24fe..20c53a4 100644 --- a/ln.build/scripts/pipeline/dotnet.ln +++ b/ln.build/scripts/pipeline/dotnet.ln @@ -38,6 +38,7 @@ "priority": 1000, "commands": [ "dotnet push", + "release", "deploy" ] } diff --git a/ln.build/semver/Version.cs b/ln.build/semver/SemVersion.cs similarity index 59% rename from ln.build/semver/Version.cs rename to ln.build/semver/SemVersion.cs index 529e151..2a4e591 100644 --- a/ln.build/semver/Version.cs +++ b/ln.build/semver/SemVersion.cs @@ -20,6 +20,7 @@ namespace ln.build.semver public bool IsPreRelease => !PreRelease?.Equals(string.Empty) ?? false; public bool IsRelease => PreRelease?.Equals(string.Empty) ?? true; + public SemVersion(int major,int minor,int patch) : this(major, minor, patch, ""){ } public SemVersion(int major,int minor,int patch,string prerelease) { Major = major; @@ -78,6 +79,62 @@ namespace ln.build.semver return version; } + public static bool operator <(SemVersion a,SemVersion b) + { + if (a.Major < b.Major) + return true; + if (a.Major > b.Major) + return false; + + if (a.Minor < b.Minor) + return true; + if (a.Minor > b.Minor) + return false; + + if (a.Patch < b.Patch) + return true; + if (a.Patch > b.Patch) + return false; + + if (a.IsPreRelease && b.IsRelease) + return true; + if (a.IsRelease && b.IsPreRelease) + return false; + + if (a.IsRelease && b.IsRelease) + return false; + + return a.PreRelease.CompareTo(b.PreRelease) > 0; + } + public static bool operator >(SemVersion b,SemVersion a) + { + if (a.Major < b.Major) + return true; + if (a.Major > b.Major) + return false; + + if (a.Minor < b.Minor) + return true; + if (a.Minor > b.Minor) + return false; + + if (a.Patch < b.Patch) + return true; + if (a.Patch > b.Patch) + return false; + + if (a.IsPreRelease && b.IsRelease) + return true; + if (a.IsRelease && b.IsPreRelease) + return false; + + if (a.IsRelease && b.IsRelease) + return false; + + return a.PreRelease.CompareTo(b.PreRelease) > 0; + } + + public static explicit operator SemVersion(string versionString) => versionString == null ? null : SemVersion.Parse(versionString); }