diff --git a/.github/workflows/bump-up-version.yml b/.github/workflows/bump-up-version.yml index 0eaffe8b2..86bd55b6e 100644 --- a/.github/workflows/bump-up-version.yml +++ b/.github/workflows/bump-up-version.yml @@ -42,4 +42,13 @@ jobs: tags: true - name: Push to nuget - run: dotnet nuget push ./build/outputs/nuget/*nupkg -k ${{ secrets.NUGETTOKEN }} + run: dotnet nuget push ./build/outputs/nuget/HandyControl.*nupkg -k ${{ secrets.NUGETTOKEN }} + + - name: Create github release + uses: softprops/action-gh-release@v2 + with: + # For maximum compatibility, we choose the .net40 + files: ./build/outputs/installer/net40/* + body_path: ./build/outputs/CHANGELOG.md + token: ${{ secrets.GITHUB_TOKEN }} + prerelease: ${{ inputs.pre_release }} diff --git a/build/build.cake b/build/build.cake index 88f878f8b..82a624161 100644 --- a/build/build.cake +++ b/build/build.cake @@ -14,6 +14,7 @@ const string TagPrefix = "v"; const string RepositoryFolder = ".."; const string LibNuspecTemplateFilePath = "lib.nuspec.template"; const string LangNuspecTemplateFilePath = "lang.nuspec.template"; +const string InstallerNuspecTemplateFilePath = "installer.nuspec.template"; const string ConfigFilePath = "build.config.xml"; var target = Argument("target", "build"); @@ -25,25 +26,34 @@ var email = Argument("email", "836904362@qq.com"); var libVersion = ""; var nugetVersion = ""; var nugetFolder = ""; +var installerFolder = ""; var year = $"{DateTime.Now.Year}"; var copyright = $"Copyright © HandyOrg {HandyControlBirthYear}-{year}"; var libNuspecFilePath = ""; var langNuspecFilePath = ""; +var installerNuspecFilePath = ""; +var squirrelExePath = ""; var gitRootPath = GitFindRootFromPath(MakeAbsolute(new DirectoryPath("."))).FullPath; var propsFilePath = Combine(gitRootPath, "src/Directory.Build.Props"); var licenseFilePath = Combine(gitRootPath, "LICENSE"); var resxFileFolder = Combine(gitRootPath, "src/Shared/HandyControl_Shared/Properties/Langs"); +var iconFilePath = Combine(gitRootPath, "src/Shared/HandyControlDemo_Shared/Resources/Img/icon.ico"); var buildConfig = new BuildConfig(); Setup(context => { buildConfig = LoadBuildConfig(); + nugetFolder = Combine(buildConfig.OutputsFolder, "nuget"); + installerFolder = Combine(buildConfig.OutputsFolder, "installer"); + libNuspecFilePath = Combine(nugetFolder, GetFileNameWithoutExtension(LibNuspecTemplateFilePath)); langNuspecFilePath = Combine(nugetFolder, LangNuspecTemplateFilePath); + installerNuspecFilePath = Combine(nugetFolder, InstallerNuspecTemplateFilePath); CleanDirectory(buildConfig.OutputsFolder); CreateDirectory(nugetFolder); + CreateDirectory(installerFolder); context.Information($"preReleasePhase: {preReleasePhase}"); context.Information($"preRelease: {preRelease}"); @@ -53,6 +63,9 @@ Setup(context => Copy(LibNuspecTemplateFilePath, libNuspecFilePath, true); Copy(LangNuspecTemplateFilePath, langNuspecFilePath, true); + Copy(InstallerNuspecTemplateFilePath, installerNuspecFilePath, true); + + DownloadSquirrelTools(); }); #region TASKS @@ -83,6 +96,7 @@ Task("update version") { ReplaceFileText(libNuspecFilePath, "version", nugetVersion); ReplaceFileText(langNuspecFilePath, "version", nugetVersion); + ReplaceFileText(installerNuspecFilePath, "version", nugetVersion); } }); @@ -95,6 +109,7 @@ Task("update copyright") ReplaceFileText(libNuspecFilePath, "year", year); ReplaceFileText(langNuspecFilePath, "year", year); + ReplaceFileText(installerNuspecFilePath, "year", year); }); Task("commit files") @@ -110,6 +125,14 @@ Task("create tag") GitTag(gitRootPath, $"{TagPrefix}{nugetVersion}"); }); + +Task("generate change log") + .Does(() => +{ + // TODO + WriteAllText(Combine(buildConfig.OutputsFolder, "CHANGELOG.md"), ""); +}); + Task("update nuget sha") .Does(() => { @@ -117,6 +140,7 @@ Task("update nuget sha") ReplaceFileText(libNuspecFilePath, "commit", lastCommit.Sha); ReplaceFileText(langNuspecFilePath, "commit", lastCommit.Sha); + ReplaceFileText(installerNuspecFilePath, "commit", lastCommit.Sha); }); Task("add nuget dependencies") @@ -146,28 +170,20 @@ Task("add nuget files") foreach (BuildTask task in buildConfig.BuildTasks) { - var frameworkFolder = $@"lib\{task.OutputsFolder}"; + var libFolder = $@"lib\{task.OutputsFolder}"; if (!IsFramework(task.Framework)) { - AddFile(libDocument, $@"{frameworkFolder}\HandyControl.deps.json"); + AddFile(libDocument, $@"{libFolder}\HandyControl.deps.json"); } - AddFile(libDocument, $@"{frameworkFolder}\HandyControl.dll"); - AddFile(libDocument, $@"{frameworkFolder}\HandyControl.pdb"); - AddFile(libDocument, $@"{frameworkFolder}\HandyControl.xml"); - AddFile(langDocument, $@"{frameworkFolder}\{{lang}}\HandyControl.resources.dll"); + AddFile(libDocument, $@"{libFolder}\HandyControl.dll"); + AddFile(libDocument, $@"{libFolder}\HandyControl.pdb"); + AddFile(libDocument, $@"{libFolder}\HandyControl.xml"); + AddFile(langDocument, $@"{libFolder}\{{lang}}\HandyControl.resources.dll"); } SaveXmlDocument(libDocument, libNuspecFilePath, indentation: 2); SaveXmlDocument(langDocument, langNuspecFilePath, indentation: 2); - - void AddFile(XmlDocument document, string filePath) - { - var fileItem = document.CreateElement("file"); - fileItem.SetAttribute("src", $@"..\{filePath}"); - fileItem.SetAttribute("target", filePath); - document.DocumentElement.SelectSingleNode("//files").AppendChild(fileItem); - } }); Task("build lib") @@ -203,15 +219,20 @@ Task("build demo") continue; } + var dotNetBuildSettings = new DotNetBuildSettings + { + Configuration = task.Configuration, + Framework = task.Framework, + OutputDirectory = $"{buildConfig.OutputsFolder}/demo/{task.OutputsFolder}", + // remove pdb files + ArgumentCustomization = args => args + .Append("/p:DebugType=none") + }; + DotNetBuild ( $"{gitRootPath}/src/{task.Domain}/HandyControlDemo_{task.Domain}/HandyControlDemo_{task.Domain}.csproj", - new DotNetBuildSettings - { - Configuration = task.Configuration, - Framework = task.Framework, - OutputDirectory = $"{buildConfig.OutputsFolder}/demo/{task.OutputsFolder}", - } + dotNetBuildSettings ); } }); @@ -228,22 +249,68 @@ Task("create lang nuspec files") } }); +Task("create installer nuspec files") + .Does(() => +{ + string templateContent = ReadAllText(installerNuspecFilePath, Encoding.UTF8); + + foreach (var task in buildConfig.BuildTasks) + { + string installerFilePath = Combine(nugetFolder, $"installer.{task.Framework.Replace(".", "")}.nuspec"); + WriteAllText(installerFilePath, templateContent.Replace("{framework}", task.Framework.Replace(".", ""))); + + XmlDocument installerDocument = LoadXmlDocument(installerFilePath); + var demoFolder = $@"demo\{task.OutputsFolder}"; + // Squirrel is expecting a single lib / net45 directory provided regardless of whether your app is a net45 application. + AddFile(installerDocument, $@"{demoFolder}\**\*", @"lib\net45"); + SaveXmlDocument(installerDocument, installerFilePath, indentation: 2); + } +}); + Task("pack nuspec files") .Does(() => { string nugetExePath = Context.Tools.Resolve("nuget.exe").FullPath; StartProcess(nugetExePath, $"pack {nugetFolder}/lib.nuspec -Symbols -SymbolPackageFormat snupkg -OutputDirectory {nugetFolder}"); - foreach (string nuspecFilePath in EnumerateFiles(nugetFolder, "lang.*.nuspec")) + + foreach (string nuspecFilePath in GetAllLangNuspecFilePaths()) + { + StartProcess(nugetExePath, $"pack {nuspecFilePath} -OutputDirectory {nugetFolder}"); + } + + foreach (string nuspecFilePath in GetAllInstallerNuspecFilePaths()) { StartProcess(nugetExePath, $"pack {nuspecFilePath} -OutputDirectory {nugetFolder}"); } }); +Task("create demo installers") + .Does(() => +{ + if (!FileExists(squirrelExePath)) + { + throw new Exception("Squirrel.exe not found."); + } + + foreach (var task in buildConfig.BuildTasks) + { + Information(task.Framework); + + string releaseDir = Combine(installerFolder, task.OutputsFolder); + CreateDirectory(releaseDir); + + string installerFilePath = Combine(nugetFolder, $"HandyControlDemo-{task.Framework.Replace(".", "")}.{nugetVersion}.nupkg"); + var cmd = $"--releasify {installerFilePath} --releaseDir {releaseDir} --setupIcon {iconFilePath} --icon {iconFilePath} --no-msi --no-delta"; + StartProcess(squirrelExePath, cmd); + } +}); + Task("publish") .IsDependentOn("update license") .IsDependentOn("update version") .IsDependentOn("update copyright") .IsDependentOn("commit files") + .IsDependentOn("generate change log") .IsDependentOn("create tag") .IsDependentOn("update nuget sha") .IsDependentOn("add nuget dependencies") @@ -251,11 +318,15 @@ Task("publish") .IsDependentOn("build lib") .IsDependentOn("build demo") .IsDependentOn("create lang nuspec files") - .IsDependentOn("pack nuspec files"); + .IsDependentOn("create installer nuspec files") + .IsDependentOn("pack nuspec files") + .IsDependentOn("create demo installers") + ; Task("build") .IsDependentOn("build lib") - .IsDependentOn("build demo"); + .IsDependentOn("build demo") + ; RunTarget(target); @@ -287,6 +358,14 @@ private void SaveXmlDocument(XmlDocument document, string xmlFilePath, int inden } } +public void AddFile(XmlDocument document, string filePath, string target = "") +{ + var fileItem = document.CreateElement("file"); + fileItem.SetAttribute("src", $@"..\{filePath}"); + fileItem.SetAttribute("target", target == "" ? filePath : target); + document.DocumentElement.SelectSingleNode("//files").AppendChild(fileItem); +} + private void ReplaceFileText(string filePath, string key, string value) { WriteAllText( @@ -350,6 +429,10 @@ private IEnumerable GetAllLangs() } } +private IEnumerable GetAllInstallerNuspecFilePaths() => EnumerateFiles(nugetFolder, "installer.*.nuspec"); + +private IEnumerable GetAllLangNuspecFilePaths() => EnumerateFiles(nugetFolder, "lang.*.nuspec"); + private BuildConfig LoadBuildConfig() { var buildConfig = new BuildConfig(); @@ -383,6 +466,18 @@ private BuildConfig LoadBuildConfig() return buildConfig; } +private void DownloadSquirrelTools() +{ + const string url = "https://www.nuget.org/api/v2/package/squirrel.windows/2.0.1"; + + var nupkgFilePath = Combine("tools", "squirrel.windows.2.0.1.nupkg"); + var toolFolderPath = Combine("tools", "squirrel.windows.2.0.1"); + + DownloadFile(url, nupkgFilePath); + Unzip(nupkgFilePath, toolFolderPath, true); + squirrelExePath = Combine(toolFolderPath, "tools", "Squirrel.exe"); +} + #endregion #region INNER TYPES diff --git a/build/build.config.xml b/build/build.config.xml index d0090c1ec..62ed211a9 100644 --- a/build/build.config.xml +++ b/build/build.config.xml @@ -2,7 +2,7 @@ outputs - + diff --git a/build/installer.nuspec.template b/build/installer.nuspec.template new file mode 100644 index 000000000..8d597d00d --- /dev/null +++ b/build/installer.nuspec.template @@ -0,0 +1,24 @@ + + + + HandyControlDemo-{framework} + {version} + HandyControlDemo-{framework} + HandyOrg + HandyOrg + true + LICENSE + icon.png + https://github.com/HandyOrg/HandyControl + Contains some simple and commonly used WPF controls + Changes are detailed at https://github.com/HandyOrg/HandyControl/releases + Copyright © HandyOrg 2018-{year} + WPF C# Control + + + + + + + + diff --git a/build/lang.nuspec.template b/build/lang.nuspec.template index 9b9fdba9e..cb960faec 100644 --- a/build/lang.nuspec.template +++ b/build/lang.nuspec.template @@ -3,7 +3,7 @@ HandyControl.Lang.{lang} {version} - HandyOrg + HandyControl.Lang.{lang} HandyOrg HandyOrg true diff --git a/build/lib.nuspec.template b/build/lib.nuspec.template index 318fac479..1f24c486d 100644 --- a/build/lib.nuspec.template +++ b/build/lib.nuspec.template @@ -3,7 +3,7 @@ HandyControl {version} - HandyOrg + HandyControl HandyOrg HandyOrg true diff --git a/build/myget.nuspec.template b/build/myget.nuspec.template index 91ac1389f..9a83dfb27 100644 --- a/build/myget.nuspec.template +++ b/build/myget.nuspec.template @@ -3,7 +3,7 @@ HandyControl {version} - HandyOrg + HandyControl HandyOrg HandyOrg true diff --git a/src/Shared/HandyControlDemo_Shared/App.xaml.cs b/src/Shared/HandyControlDemo_Shared/App.xaml.cs index 54db7684a..5a326a71b 100644 --- a/src/Shared/HandyControlDemo_Shared/App.xaml.cs +++ b/src/Shared/HandyControlDemo_Shared/App.xaml.cs @@ -8,6 +8,7 @@ using System.Runtime; #endif using System.Threading; +using System.Threading.Tasks; using System.Windows; using HandyControl.Data; using HandyControl.Tools; @@ -26,63 +27,19 @@ public partial class App public App() { -#if !NET40 - var cachePath = $"{AppDomain.CurrentDomain.BaseDirectory}Cache"; - if (!Directory.Exists(cachePath)) - { - Directory.CreateDirectory(cachePath); - } - ProfileOptimization.SetProfileRoot(cachePath); - ProfileOptimization.StartProfile("Profile"); -#endif + EnsureProfileOptimization(); } protected override void OnStartup(StartupEventArgs e) { - AppMutex = new Mutex(true, "HandyControlDemo", out var createdNew); - - if (!createdNew) - { - var current = Process.GetCurrentProcess(); - - foreach (var process in Process.GetProcessesByName(current.ProcessName)) - { - if (process.Id != current.Id) - { - Win32Helper.SetForegroundWindow(process.MainWindowHandle); - break; - } - } - Shutdown(); - } - else - { - var splashScreen = new SplashScreen("Resources/Img/Cover.png"); - splashScreen.Show(true); - - base.OnStartup(e); + EnsureSingleton(); + OpenSplashScreen(); - UpdateRegistry(); + base.OnStartup(e); - ShutdownMode = ShutdownMode.OnMainWindowClose; - GlobalData.Init(); - ConfigHelper.Instance.SetLang(GlobalData.Config.Lang); - LangProvider.Culture = new CultureInfo(GlobalData.Config.Lang); - - if (GlobalData.Config.Skin != SkinType.Default) - { - UpdateSkin(GlobalData.Config.Skin); - } - - ConfigHelper.Instance.SetWindowDefaultStyle(); - ConfigHelper.Instance.SetNavigationWindowDefaultStyle(); - -#if NET40 - ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072; -#else - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; -#endif - } + //UpdateRegistry(); + ApplyConfiguration(); + UpdateApp(); } protected override void OnExit(ExitEventArgs e) @@ -112,7 +69,71 @@ internal void UpdateSkin(SkinType skin) Current.MainWindow?.OnApplyTemplate(); } - private void UpdateRegistry() + private void ApplyConfiguration() + { + ShutdownMode = ShutdownMode.OnMainWindowClose; + GlobalData.Init(); + ConfigHelper.Instance.SetLang(GlobalData.Config.Lang); + LangProvider.Culture = new CultureInfo(GlobalData.Config.Lang); + + if (GlobalData.Config.Skin != SkinType.Default) + { + UpdateSkin(GlobalData.Config.Skin); + } + + ConfigHelper.Instance.SetWindowDefaultStyle(); + ConfigHelper.Instance.SetNavigationWindowDefaultStyle(); + +#if NET40 + ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072; +#else + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; +#endif + } + + private static void OpenSplashScreen() + { + var splashScreen = new SplashScreen("Resources/Img/Cover.png"); + splashScreen.Show(true); + } + + private static void EnsureProfileOptimization() + { +#if !NET40 + var cachePath = $"{AppDomain.CurrentDomain.BaseDirectory}Cache"; + if (!Directory.Exists(cachePath)) + { + Directory.CreateDirectory(cachePath); + } + ProfileOptimization.SetProfileRoot(cachePath); + ProfileOptimization.StartProfile("Profile"); +#endif + } + + private void EnsureSingleton() + { + AppMutex = new Mutex(true, "HandyControlDemo", out var createdNew); + + if (createdNew) + { + return; + } + + var current = Process.GetCurrentProcess(); + + foreach (var process in Process.GetProcessesByName(current.ProcessName)) + { + if (process.Id != current.Id) + { + Win32Helper.SetForegroundWindow(process.MainWindowHandle); + break; + } + } + + Shutdown(); + } + + private static void UpdateRegistry() { var processModule = Process.GetCurrentProcess().MainModule; if (processModule != null) @@ -136,4 +157,24 @@ private void UpdateRegistry() } } } + + private static void UpdateApp() + { + const string api = "https://github.com/handyorg/handycontrol/releases/latest"; + + try + { + var mainDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\')); + var updateExePath = Path.Combine(mainDirectory, "Update.exe"); + + if (File.Exists(updateExePath)) + { + Task.Factory.StartNew(() => Process.Start(updateExePath, $"--update={api}")); + } + } + catch + { + // ignored + } + } }