From 805199acc9df70b040fc136ec63d91ce38f92b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C3=87A=C4=9EDA=C5=9E?= Date: Fri, 28 Aug 2020 17:25:09 +0300 Subject: [PATCH] created initial build command --- .../Volo.Abp.Cli.Core.csproj | 1 + .../Volo/Abp/Cli/AbpCliCoreModule.cs | 1 + .../Cli/Build/DefaultChangedProjectFinder.cs | 188 ++++++++++++++++++ .../Cli/Build/DefaultDotNetProjectBuilder.cs | 72 +++++++ .../Abp/Cli/Build/DotNetProjectBuildConfig.cs | 9 + .../Abp/Cli/Build/DotNetProjectBuildStatus.cs | 9 + .../Build/DotNetProjectDependencyFiller.cs | 93 +++++++++ .../Volo/Abp/Cli/Build/DotNetProjectInfo.cs | 22 ++ .../Volo/Abp/Cli/Build/GitRepository.cs | 91 +++++++++ .../Abp/Cli/Build/GitRepositoryBuildStatus.cs | 88 ++++++++ .../Volo/Abp/Cli/Build/IBuildArgsReader.cs | 69 +++++++ .../Abp/Cli/Build/IChangedProjectFinder.cs | 9 + .../Abp/Cli/Build/IDotNetProjectBuilder.cs | 9 + .../Build/IDotNetProjectDependencyFiller.cs | 9 + .../Cli/Build/IRepositoryBuildStatusStore.cs | 58 ++++++ .../Volo/Abp/Cli/Commands/BuildCommand.cs | 180 +++++++++++++++++ 16 files changed, 908 insertions(+) create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultChangedProjectFinder.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultDotNetProjectBuilder.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildConfig.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildStatus.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectDependencyFiller.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfo.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepository.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepositoryBuildStatus.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IBuildArgsReader.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IChangedProjectFinder.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectBuilder.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectDependencyFiller.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IRepositoryBuildStatusStore.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs diff --git a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj index 40eecfd06a..2dfd66f4ab 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj +++ b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj @@ -22,6 +22,7 @@ + diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index 0244d46c47..3de12b7fca 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -34,6 +34,7 @@ namespace Volo.Abp.Cli options.Commands["switch-to-stable"] = typeof(SwitchToStableCommand); options.Commands["switch-to-nightly"] = typeof(SwitchToNightlyCommand); options.Commands["translate"] = typeof(TranslateCommand); + options.Commands["build"] = typeof(BuildCommand); }); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultChangedProjectFinder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultChangedProjectFinder.cs new file mode 100644 index 0000000000..2a663ed319 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultChangedProjectFinder.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using LibGit2Sharp; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Build +{ + public class DefaultChangedProjectFinder : IChangedProjectFinder, ITransientDependency + { + private readonly IRepositoryBuildStatusStore _repositoryBuildStatusStore; + + public DefaultChangedProjectFinder(IRepositoryBuildStatusStore repositoryBuildStatusStore) + { + _repositoryBuildStatusStore = repositoryBuildStatusStore; + } + + public List Find(DotNetProjectBuildConfig buildConfig) + { + if (buildConfig.SlFilePath.IsNullOrEmpty()) + { + return FindByRepository(buildConfig.GitRepository); + } + + return FindBySlnFile(buildConfig.GitRepository, buildConfig.SlFilePath); + } + + private List FindByRepository(GitRepository gitRepository) + { + var changedProjectList = new List(); + var gitRepositoryBuildStatus = _repositoryBuildStatusStore.Get(gitRepository); + + FindChangedFiles(gitRepository, gitRepositoryBuildStatus, changedProjectList); + + return changedProjectList; + } + + private List FindBySlnFile(GitRepository gitRepository, string slnFilePath) + { + var slFine = new FileInfo(slnFilePath); + var changedProjectList = new List(); + var csProjFiles = slFine.Directory.GetFiles( + "*.csproj", + SearchOption.AllDirectories + ).Select(e => e.FullName) + .ToList(); + + foreach (var csProjFile in csProjFiles) + { + AddDependingProjectsToList(gitRepository, csProjFile, changedProjectList); + } + + return changedProjectList; + } + + private void AddDependingProjectsToList( + GitRepository gitRepository, + string csProjFilePath, + List changedProjectList) + { + var repositoryName = gitRepository.FindRepositoryOf(csProjFilePath); + var project = new DotNetProjectInfo(repositoryName, csProjFilePath); + if (changedProjectList.Any(e => e.CsProjPath == csProjFilePath)) + { + return; + } + + changedProjectList.Add(project); + + AddProjectDependencies(gitRepository, project, changedProjectList); + } + + private void AddProjectDependencies(GitRepository gitRepository, DotNetProjectInfo project, List changedProjectList) + { + var projectNode = XElement.Load(project.CsProjPath); + var referenceNodes = projectNode.Descendants("ItemGroup").Descendants("ProjectReference"); + + foreach (var referenceNode in referenceNodes) + { + if (referenceNode.Attribute("Include") == null) + { + continue; + } + + var relativePath = referenceNode.Attribute("Include").Value; + var file = new FileInfo(project.CsProjPath); + var referenceProjectInfo = new FileInfo(Path.Combine(file.Directory.FullName, relativePath)); + + if (changedProjectList.Any(e => e.CsProjPath == referenceProjectInfo.FullName)) + { + continue; + } + + var repositoryName = gitRepository.FindRepositoryOf(referenceProjectInfo.FullName); + var referenceProject = new DotNetProjectInfo(repositoryName, referenceProjectInfo.FullName); + changedProjectList.Add(referenceProject); + + AddProjectDependencies(gitRepository, referenceProject, changedProjectList); + } + } + + private void FindChangedFiles( + GitRepository repository, + GitRepositoryBuildStatus repositoryBuildStatus, + List changedProjectList) + { + if (repositoryBuildStatus.CommitId.IsNullOrEmpty()) + { + AddAllCsProjFiles(repository, changedProjectList); + } + else + { + AddChangedCsProjFiles(repository, changedProjectList, repositoryBuildStatus); + } + + if (repository.DependingRepositories.Any()) + { + foreach (var dependingRepository in repository.DependingRepositories) + { + var dependingRepositoryBuildStatus = repositoryBuildStatus.GetChild(dependingRepository.Name); + FindChangedFiles(dependingRepository, dependingRepositoryBuildStatus, changedProjectList); + } + } + } + + private void AddAllCsProjFiles(GitRepository repository, List changedFiles) + { + var allCsProjFiles = Directory.GetFiles( + repository.RootPath, + "*.csproj", + SearchOption.AllDirectories + ); + + foreach (var file in allCsProjFiles) + { + changedFiles.Add( + new DotNetProjectInfo(repository.Name, Path.Combine(repository.RootPath, file)) + ); + } + } + + private void AddChangedCsProjFiles( + GitRepository repository, + List changedFiles, + GitRepositoryBuildStatus status) + { + using (var repo = new Repository(string.Concat(repository.RootPath, @"\.git"))) + { + var firstCommit = status.CommitId.IsNullOrEmpty() + ? null + : repo.Lookup(status.CommitId); + var repoDifferences = repo.Diff.Compare(firstCommit?.Tree, repo.Head.Tip.Tree); + + var files = repoDifferences + .Where(e => e.Path.EndsWith(".cs") || e.Path.EndsWith(".csproj")) + .Where(e => e.Status != ChangeKind.Deleted) + .Select(e => e) + .ToList(); + + foreach (var file in files) + { + var csProjPath = Path.Combine(repository.RootPath, file.Path); + if (status.SucceedProjects.Any(p => p.CsProjPath == csProjPath && p.CommitId == "1")) + { + continue; + } + + changedFiles.Add( + new DotNetProjectInfo(repository.Name, csProjPath) + ); + } + + if (!repository.DependingRepositories.Any()) + { + return; + } + + foreach (var subRepository in repository.DependingRepositories) + { + var subRepositoryBuildStatus = status.GetChild(subRepository.Name); + FindChangedFiles(subRepository, subRepositoryBuildStatus, changedFiles); + } + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultDotNetProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultDotNetProjectBuilder.cs new file mode 100644 index 0000000000..eea3d85c9f --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultDotNetProjectBuilder.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Build +{ + public class DefaultDotNetProjectBuilder : IDotNetProjectBuilder, ITransientDependency + { + public List Build(List projects, int maxParallelBuildCount, string arguments) + { + var builtProjects = new ConcurrentBag(); + + try + { + foreach (var project in projects) + { + if (builtProjects.Contains(project.CsProjPath)) + { + continue; + } + + BuildInternal(project, arguments, builtProjects); + + if (!project.Dependencies.Any()) + { + continue; + } + + // TODO: use parallel foreach here. + foreach (var projectDependency in project.Dependencies) + { + if (builtProjects.Contains(project.CsProjPath)) + { + continue; + } + + BuildInternal(projectDependency, arguments, builtProjects); + } + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + + return builtProjects.ToList(); + } + + private void BuildInternal(DotNetProjectInfo project, string arguments, ConcurrentBag builtProjects) + { + Console.WriteLine("Building...: dotnet build " + project.CsProjPath + " " + arguments); + + CmdHelper.RunCmdAndGetOutput( + "dotnet build " + project.CsProjPath + " " + arguments, + out int buildStatus + ); + + if (buildStatus == 0) + { + builtProjects.Add(project.CsProjPath); + } + else + { + // TODO: throw exception and stop build process. + Console.WriteLine("Build failed for :" + project.CsProjPath); + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildConfig.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildConfig.cs new file mode 100644 index 0000000000..efaa1c6075 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildConfig.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Cli.Build +{ + public class DotNetProjectBuildConfig + { + public string SlFilePath { get; set; } + + public GitRepository GitRepository { get; set; } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildStatus.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildStatus.cs new file mode 100644 index 0000000000..10092777c2 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildStatus.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Cli.Build +{ + public class DotNetProjectBuildStatus + { + public string CsProjPath { get; set; } + + public string CommitId { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectDependencyFiller.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectDependencyFiller.cs new file mode 100644 index 0000000000..27fea096ea --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectDependencyFiller.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Build +{ + public class DotNetProjectDependencyFiller : IDotNetProjectDependencyFiller, ITransientDependency + { + public ILogger Logger { get; set; } + + public void Fill(List projects) + { + foreach (var project in projects) + { + FillProjectDependencies(project); + } + } + + private void FillProjectDependencies(DotNetProjectInfo project) + { + var projectNode = XElement.Load(project.CsProjPath); + var referenceNodes = projectNode.Descendants("ItemGroup").Descendants("ProjectReference"); + + foreach (var referenceNode in referenceNodes) + { + if (referenceNode.Attribute("Include") == null) + { + continue; + } + + var relativePath = referenceNode.Attribute("Include").Value; + var file = new FileInfo(project.CsProjPath); + var referenceProjectInfo = new FileInfo(Path.Combine(file.Directory.FullName, relativePath)); + + var referenceProject = new DotNetProjectInfo(project.RepositoryName, referenceProjectInfo.FullName); + + project.Dependencies.Add(referenceProject); + } + } + + private void GetProjectsOfRepository(GitRepository repository, List projectPaths) + { + Logger.LogInformation("repo name:" + repository.Name); + Logger.LogInformation("directoryPath:" + repository.RootPath); + + GetProjectsOfADirectory(repository.RootPath, projectPaths, new List()); + + if (!repository.DependingRepositories.Any()) + { + return; + } + + foreach (var dependantRepository in repository.DependingRepositories) + { + GetProjectsOfRepository(dependantRepository, projectPaths); + } + } + + private void GetProjectsOfADirectory( + string directoryPath, + List projectPaths, + List ignoredDirectories) + { + Logger.LogInformation("directoryPath:" + directoryPath); + + var files = Directory.GetFiles(directoryPath, "*.csproj", SearchOption.AllDirectories) + .Select(f => f) + .ToList(); + + // foreach (var ignoredDirectory in ignoredDirectories) + // { + // files = files.Where(e => !e.StartsWith(Path.Combine(directoryPath, ignoredDirectory))) + // .ToList(); + // } + + projectPaths.AddRange(files); + + // foreach (var projectFile in files) + // { + // var projectNode = XElement.Load(projectFile); + // if (projectNode.Attribute("Sdk") == null) + // { + // continue; + // } + // + // projectPaths.Add(projectFile); + // } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfo.cs new file mode 100644 index 0000000000..0bc431d3a0 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfo.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + [Serializable] + public class DotNetProjectInfo + { + public string RepositoryName { get; set; } + + public string CsProjPath { get; set; } + + public List Dependencies { get; set; } + + public DotNetProjectInfo(string repositoryName, string csProjPath) + { + RepositoryName = repositoryName; + CsProjPath = csProjPath; + Dependencies = new List(); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepository.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepository.cs new file mode 100644 index 0000000000..6af55c8ed2 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepository.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + /// + /// Represents a source code repository + /// + public class GitRepository + { + /// + /// Name of the repository + /// + public string Name { get; set; } + + /// + /// Branch of the repository + /// + public string BranchName { get; set; } + + /// + /// Root path of the repository which contains .git folder + /// + public string RootPath { get; set; } + + public List DependingRepositories { get; set; } + + public GitRepository(string name, string branchName, string rootPath) + { + Name = name; + BranchName = branchName; + RootPath = rootPath; + DependingRepositories = new List(); + } + + public string GetUniqueName() + { + var name = Name + "_" + BranchName; + foreach (var dependingRepository in DependingRepositories) + { + AddToUniqueName(dependingRepository, name); + } + + return name.ToMd5(); + } + + private void AddToUniqueName(GitRepository gitRepository, string name) + { + name += "_" + gitRepository.Name + "_" + gitRepository.BranchName; + + foreach (var dependingRepository in gitRepository.DependingRepositories) + { + AddToUniqueName(dependingRepository, name); + } + } + + public string FindRepositoryOf(string csProjFilePath) + { + if (csProjFilePath.StartsWith(RootPath)) + { + return Name; + } + + foreach (var dependingRepository in DependingRepositories) + { + var name = FindRepositoryOfInternal(dependingRepository, csProjFilePath); + if (!string.IsNullOrEmpty(name)) + { + return name; + } + } + + return null; + } + + private string FindRepositoryOfInternal(GitRepository repository, string csProjFilePath) + { + if (csProjFilePath.StartsWith(repository.RootPath)) + { + return repository.Name; + } + + foreach (var dependingRepository in repository.DependingRepositories) + { + return FindRepositoryOfInternal(dependingRepository, csProjFilePath); + } + + return null; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepositoryBuildStatus.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepositoryBuildStatus.cs new file mode 100644 index 0000000000..751a205b01 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepositoryBuildStatus.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + public class GitRepositoryBuildStatus + { + /// + /// Name of the repository + /// + public string Name { get; set; } + + /// + /// Branch of the repository + /// + public string BranchName { get; set; } + + /// + /// Last succeeded commitId of the repository + /// + public string CommitId { get; set; } + + /// + /// Build succeeded projects of this repository + /// + public List SucceedProjects { get; set; } + + /// + /// Build status of depending repositories + /// + public List DependingRepositories { get; set; } + + public GitRepositoryBuildStatus(string name, string branchName) + { + Name = name; + BranchName = branchName; + SucceedProjects = new List(); + DependingRepositories = new List(); + } + + public GitRepositoryBuildStatus GetChild(string repositoryName) + { + foreach (var dependingRepository in DependingRepositories) + { + return GetChildInternal(dependingRepository, repositoryName); + } + + return null; + } + + private GitRepositoryBuildStatus GetChildInternal(GitRepositoryBuildStatus repositoryBuildStatus, + string repositoryName) + { + if (repositoryBuildStatus.Name == repositoryName) + { + return repositoryBuildStatus; + } + + foreach (var dependingRepository in repositoryBuildStatus.DependingRepositories) + { + return GetChildInternal(dependingRepository, repositoryName); + } + + return null; + } + + public string GetUniqueName() + { + var name = Name + "_" + BranchName; + foreach (var dependingRepository in DependingRepositories) + { + AddToUniqueName(dependingRepository, name); + } + + return name.ToMd5(); + } + + private void AddToUniqueName(GitRepositoryBuildStatus gitRepository, string name) + { + name += "_" + gitRepository.Name + "_" + gitRepository.BranchName; + + foreach (var dependingRepository in gitRepository.DependingRepositories) + { + AddToUniqueName(dependingRepository, name); + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IBuildArgsReader.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IBuildArgsReader.cs new file mode 100644 index 0000000000..7402cd7450 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IBuildArgsReader.cs @@ -0,0 +1,69 @@ +using System; +using System.IO; +using System.Linq; +using LibGit2Sharp; +using Volo.Abp.Json; + +namespace Volo.Abp.Cli.Build +{ + public interface IDotNetProjectBuildConfigReader + { + DotNetProjectBuildConfig Read(string directoryPath); + } + + public class FileSystemDotNetProjectBuildConfigReader : IDotNetProjectBuildConfigReader + { + private IJsonSerializer _jsonSerializer; + + public FileSystemDotNetProjectBuildConfigReader(IJsonSerializer jsonSerializer) + { + _jsonSerializer = jsonSerializer; + } + + public DotNetProjectBuildConfig Read(string directoryPath) + { + var buildArgs = new DotNetProjectBuildConfig(); + var solutionFiles = Directory.GetFiles(directoryPath, "*.sln", SearchOption.TopDirectoryOnly); + if (solutionFiles.Length == 1) + { + buildArgs.SlFilePath = solutionFiles.First(); + } + else + { + Console.WriteLine( + "There are more than 1 solution (*.sln) file in the directory. Searching for build-config.json file." + ); + } + + var configFiles = Directory.GetFiles(directoryPath, "abp-build-config.json", SearchOption.TopDirectoryOnly); + if (configFiles.Length == 1) + { + var configFile = configFiles.First(); + buildArgs.GitRepository = _jsonSerializer.Deserialize(configFile); + } + else + { + Console.WriteLine( + "There are more than 1 config (abp-build-config.json) file in the directory!" + ); + } + + SetBranchNames(buildArgs.GitRepository); + + return buildArgs; + } + + private void SetBranchNames(GitRepository gitRepository) + { + using (var repo = new Repository(string.Concat(gitRepository.RootPath, @"\.git"))) + { + gitRepository.BranchName = repo.Head.FriendlyName; + } + + foreach (var dependingRepository in gitRepository.DependingRepositories) + { + SetBranchNames(dependingRepository); + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IChangedProjectFinder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IChangedProjectFinder.cs new file mode 100644 index 0000000000..b381a073b4 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IChangedProjectFinder.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + public interface IChangedProjectFinder + { + List Find(DotNetProjectBuildConfig buildConfig); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectBuilder.cs new file mode 100644 index 0000000000..46227bb285 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectBuilder.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + public interface IDotNetProjectBuilder + { + List Build(List projects, int maxParallelBuildCount, string arguments); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectDependencyFiller.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectDependencyFiller.cs new file mode 100644 index 0000000000..cc1ba65bc7 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectDependencyFiller.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + public interface IDotNetProjectDependencyFiller + { + void Fill(List projects); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IRepositoryBuildStatusStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IRepositoryBuildStatusStore.cs new file mode 100644 index 0000000000..793d3715f7 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IRepositoryBuildStatusStore.cs @@ -0,0 +1,58 @@ +using System.IO; +using Newtonsoft.Json; +using Volo.Abp.DependencyInjection; +using Volo.Abp.IO; + +namespace Volo.Abp.Cli.Build +{ + public interface IRepositoryBuildStatusStore + { + GitRepositoryBuildStatus Get(GitRepository repository); + + void Set(GitRepositoryBuildStatus status); + } + + public class FileSystemRepositoryBuildStatusStore : IRepositoryBuildStatusStore, ITransientDependency + { + // TODO: change this ? + private string BaseBuildStatusStorePath = @"C:\Users\ismai\.abp\build"; + + public GitRepositoryBuildStatus Get(GitRepository repository) + { + if (!Directory.Exists(BaseBuildStatusStorePath)) + { + Directory.CreateDirectory(BaseBuildStatusStorePath); + } + + var buildStatusFile = Path.Combine(BaseBuildStatusStorePath, repository.GetUniqueName()) + ".json"; + + if (!File.Exists(buildStatusFile)) + { + // TODO: this is wrong + return new GitRepositoryBuildStatus(repository.Name, repository.BranchName); + } + + var buildStatusText = File.ReadAllText(buildStatusFile); + return JsonConvert.DeserializeObject(buildStatusText); + } + + public void Set(GitRepositoryBuildStatus status) + { + if (!Directory.Exists(BaseBuildStatusStorePath)) + { + Directory.CreateDirectory(BaseBuildStatusStorePath); + } + + var buildStatusFile = Path.Combine(BaseBuildStatusStorePath, status.GetUniqueName()) + ".json"; + if (File.Exists(buildStatusFile)) + { + FileHelper.DeleteIfExists(buildStatusFile); + } + + using (var file = File.CreateText(buildStatusFile)) + { + new JsonSerializer {Formatting = Formatting.Indented}.Serialize(file, status); + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs new file mode 100644 index 0000000000..56400c7c51 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using LibGit2Sharp; +using Microsoft.Extensions.Logging; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.Build; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class BuildCommand : IConsoleCommand, ITransientDependency + { + public ILogger Logger { get; set; } + + public IDotNetProjectDependencyFiller DotNetProjectDependencyFiller { get; set; } + + public IChangedProjectFinder ChangedProjectFinder { get; set; } + + public IDotNetProjectBuilder DotNetProjectBuilder { get; set; } + + public IRepositoryBuildStatusStore RepositoryBuildStatusStore { get; set; } + + public IDotNetProjectBuildConfigReader DotNetProjectBuildConfigReader { get; set; } + + public Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + var sw = new Stopwatch(); + sw.Start(); + + var buildConfig = DotNetProjectBuildConfigReader.Read(Directory.GetCurrentDirectory()); + + var maxParallelBuild = commandLineArgs.Options.GetOrNull( + Options.MaxParallelBuild.Short, + Options.MaxParallelBuild.Long + ); + + var dotnetBuildArguments = commandLineArgs.Options.GetOrNull(Options.DotnetBuildArguments.Short, + Options.DotnetBuildArguments.Long); + + var changedProjectFiles = ChangedProjectFinder.Find(buildConfig); + + DotNetProjectDependencyFiller.Fill(changedProjectFiles); + + var sortedProjects = changedProjectFiles.SortByDependencies(p => p.Dependencies); + + var buildSucceededProjects = DotNetProjectBuilder.Build( + sortedProjects, + string.IsNullOrEmpty(maxParallelBuild) ? 1 : Convert.ToInt32(maxParallelBuild), + dotnetBuildArguments ?? "" + ); + + var buildStatus = GenerateBuildStatus(buildConfig.GitRepository, changedProjectFiles, buildSucceededProjects); + RepositoryBuildStatusStore.Set(buildStatus); + + sw.Stop(); + Console.WriteLine("Build operation is completed in " + sw.ElapsedMilliseconds + " (ms)"); + + return Task.CompletedTask; + } + + private GitRepositoryBuildStatus GenerateBuildStatus( + GitRepository gitRepository, + List changedProjects, + List buildSucceededProjects) + { + using (var repo = new Repository(string.Concat(gitRepository.RootPath, @"\.git"))) + { + var lastCommitId = repo.Head.Tip.Id.ToString(); + var status = new GitRepositoryBuildStatus( + gitRepository.Name, + repo.Head.FriendlyName + ) + { + CommitId = lastCommitId + }; + + status.SucceedProjects = changedProjects.Where(p => + p.RepositoryName == gitRepository.Name && + buildSucceededProjects.Contains(p.CsProjPath) + ) + .Select(e => new DotNetProjectBuildStatus() + { + CsProjPath = e.CsProjPath, + CommitId = lastCommitId + }).ToList(); + + foreach (var dependingRepository in gitRepository.DependingRepositories) + { + GenerateBuildStatusInternal(dependingRepository, changedProjects, buildSucceededProjects, status); + } + + return status; + } + } + + private void GenerateBuildStatusInternal( + GitRepository gitRepository, + List changedProjects, + List buildSucceededProjects, + GitRepositoryBuildStatus status) + { + using (var repo = new Repository(string.Concat(gitRepository.RootPath, @"\.git"))) + { + var lastCommitId = repo.Head.Tip.Id.ToString(); + var dependingRepositoryStatus = new GitRepositoryBuildStatus( + gitRepository.Name, + repo.Head.FriendlyName + ) + { + CommitId = lastCommitId + }; + + dependingRepositoryStatus.SucceedProjects = changedProjects.Where(p => + p.RepositoryName == gitRepository.Name && + buildSucceededProjects.Contains(p.CsProjPath) + ) + .Select(e => new DotNetProjectBuildStatus() + { + CsProjPath = e.CsProjPath, + CommitId = lastCommitId + }).ToList(); + + foreach (var dependingRepository in gitRepository.DependingRepositories) + { + GenerateBuildStatusInternal( + dependingRepository, + changedProjects, + buildSucceededProjects, + dependingRepositoryStatus + ); + } + + status.DependingRepositories.Add(dependingRepositoryStatus); + } + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(""); + sb.AppendLine(" abp build [options]"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine(""); + // TODO: Explain extra parameters + sb.AppendLine(""); + sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); + + return sb.ToString(); + } + + public string GetShortDescription() + { + return "Builds a dotnet repository and dependent repositories or a solution."; + } + + public static class Options + { + public static class MaxParallelBuild + { + public const string Short = "m"; + public const string Long = "max-paralel-builds"; + } + + public static class DotnetBuildArguments + { + public const string Short = "d"; + public const string Long = "dotnet-build-arguments"; + } + } + } +}