From a6ba9040d9bcdf0e81648e9338d8c032ef3795b9 Mon Sep 17 00:00:00 2001 From: flightlevel Date: Sat, 11 Nov 2017 17:14:14 +1100 Subject: [PATCH] Use Cake to Build (#2113) --- .gitignore | 7 +- Installer.iss | 10 +-- appveyor.yml | 54 +---------- build.cake | 230 +++++++++++++++++++++++++++++++++++++++++++++++ build.ps1 | 234 ++++++++++++++++++++++++++++++++++++++++++++++++ src/Jackett.sln | 3 +- 6 files changed, 478 insertions(+), 60 deletions(-) create mode 100644 build.cake create mode 100644 build.ps1 diff --git a/.gitignore b/.gitignore index 66b6a62a2..c715d4dd8 100644 --- a/.gitignore +++ b/.gitignore @@ -194,7 +194,8 @@ FakesAssemblies/ # Visual Studio 6 workspace options file *.opt -/Build.mono -/Build.windows -/Output +/tools +/BuildOutput +/Artifacts +/TestResults *.DS_Store diff --git a/Installer.iss b/Installer.iss index 001eb1adc..8a87a2ffb 100644 --- a/Installer.iss +++ b/Installer.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "Jackett" -#define MyAppVersion GetFileVersion("build.windows/Jackett.dll") +#define MyAppVersion GetFileVersion("BuildOutput\FullFramework\Windows\Jackett\Jackett.Common.dll") #define MyAppPublisher "Jackett Inc." #define MyAppURL "https://github.com/Jackett/Jackett" #define MyAppExeName "JackettTray.exe" @@ -22,7 +22,7 @@ AppUpdatesURL={#MyAppURL} DefaultDirName={pf}\{#MyAppName} DefaultGroupName={#MyAppName} DisableProgramGroupPage=yes -OutputBaseFilename=setup +OutputBaseFilename=Jackett.Installer.Windows SetupIconFile=src\Jackett.Console\jackett.ico UninstallDisplayIcon={commonappdata}\Jackett\JackettConsole.exe Compression=lzma @@ -37,9 +37,9 @@ Name: "windowsService"; Description: "Install as a Windows Service" Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked [Files] -Source: "build.windows\JackettTray.exe"; DestDir: "{commonappdata}\Jackett"; Flags: ignoreversion -Source: "build.windows\JackettUpdater.exe"; DestDir: "{commonappdata}\Jackett"; Flags: ignoreversion -Source: "build.windows\*"; DestDir: "{commonappdata}\Jackett"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "BuildOutput\FullFramework\Windows\Jackett\JackettTray.exe"; DestDir: "{commonappdata}\Jackett"; Flags: ignoreversion +Source: "BuildOutput\FullFramework\Windows\Jackett\JackettUpdater.exe"; DestDir: "{commonappdata}\Jackett"; Flags: ignoreversion +Source: "BuildOutput\FullFramework\Windows\Jackett\*"; DestDir: "{commonappdata}\Jackett"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] diff --git a/appveyor.yml b/appveyor.yml index 6e4ae00b1..34a53863e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,57 +19,9 @@ dotnet_csproj: assembly_version: '{version}' file_version: '{version}' informational_version: '{version}' -install: -- cmd: choco install InnoSetup -before_build: -- cmd: nuget restore src\Jackett.sln -build: - verbosity: minimal -after_build: -- cmd: >- - xcopy src\Jackett.Console\bin\Release base\ /e /y - - copy /Y src\Jackett.Service\bin\Release\JackettService.exe* %APPVEYOR_BUILD_FOLDER%\base\ - - copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe* %APPVEYOR_BUILD_FOLDER%\base\ - - copy /Y src\Jackett.Updater\bin\Release\JackettUpdater.exe* %APPVEYOR_BUILD_FOLDER%\base\ - - copy /Y Upstart.config base\Upstart.config - - copy /Y LICENSE base\LICENSE - - copy /Y README.md base\README.md - - - xcopy base build.windows\ /e /y - - xcopy base BaseBuild\Jackett\ /e /y - - - "C:\Program Files (x86)\Inno Setup 5\ISCC.exe" Installer.iss - - RENAME Output\setup.exe Jackett.Installer.Windows.exe - - MOVE Output\Jackett.Installer.Windows.exe %APPVEYOR_BUILD_FOLDER% - - - cd BaseBuild - - 7z a -tzip -r "%APPVEYOR_BUILD_FOLDER%\Jackett.Binaries.Windows.zip" "Jackett\" - - 7z a -ttar "%APPVEYOR_BUILD_FOLDER%\Jackett.Binaries.Mono.tar" "Jackett\" - - cd %APPVEYOR_BUILD_FOLDER% - - 7z a -tgzip "Jackett.Binaries.Mono.tar.gz" "Jackett.Binaries.Mono.tar" - - - appveyor PushArtifact Jackett.Installer.Windows.exe - - appveyor PushArtifact Jackett.Binaries.Windows.zip - - appveyor PushArtifact Jackett.Binaries.Mono.tar.gz +build_script: +- ps: .\build.ps1 +test: off deploy: - provider: GitHub tag: v$(appveyor_build_version) diff --git a/build.cake b/build.cake new file mode 100644 index 000000000..d88fb03f8 --- /dev/null +++ b/build.cake @@ -0,0 +1,230 @@ +#tool nuget:?package=NUnit.ConsoleRunner&version=3.7.0 + +////////////////////////////////////////////////////////////////////// +// ARGUMENTS +////////////////////////////////////////////////////////////////////// + +var target = Argument("target", "Default"); +var configuration = Argument("configuration", "Release"); + +////////////////////////////////////////////////////////////////////// +// PREPARATION +////////////////////////////////////////////////////////////////////// + +// Define directories. +var workingDir = MakeAbsolute(Directory("./")); +var artifactsDirName = "Artifacts"; +var testResultsDirName = "TestResults"; + +var windowsBuildFullFramework = "./BuildOutput/FullFramework/Windows"; +var monoBuildFullFramework = "./BuildOutput/FullFramework/Mono"; + +////////////////////////////////////////////////////////////////////// +// TASKS +////////////////////////////////////////////////////////////////////// + +Task("Info") + .Does(() => + { + Information(@"Jackett Cake build script starting..."); + Information(@"Requires InnoSetup and C:\cygwin to be present for packaging (Pre-installed on AppVeyor)"); + Information(@"Working directory is: " + workingDir); + }); + +Task("Clean") + .IsDependentOn("Info") + .Does(() => + { + CleanDirectories("./src/**/obj" + configuration); + CleanDirectories("./src/**/bin" + configuration); + CleanDirectories("./BuildOutput"); + CleanDirectories("./" + artifactsDirName); + CleanDirectories("./" + testResultsDirName); + + Information(@"Clean completed"); + }); + +Task("Restore-NuGet-Packages") + .IsDependentOn("Clean") + .Does(() => + { + NuGetRestore("./src/Jackett.sln"); + }); + +Task("Build") + .IsDependentOn("Restore-NuGet-Packages") + .Does(() => + { + MSBuild("./src/Jackett.sln", settings => settings.SetConfiguration(configuration)); + }); + +Task("Run-Unit-Tests") + .IsDependentOn("Build") + .Does(() => + { + CreateDirectory("./" + testResultsDirName); + var resultsFile = $"./{testResultsDirName}/JackettTestResult.xml"; + + NUnit3("./src/**/bin/" + configuration + "/**/*.Test.dll", new NUnit3Settings + { + Results = new[] { new NUnit3Result { FileName = resultsFile } } + }); + + if(AppVeyor.IsRunningOnAppVeyor) + { + AppVeyor.UploadTestResults(resultsFile, AppVeyorTestResultsType.NUnit3); + } + }); + +Task("Copy-Files-Full-Framework") + .IsDependentOn("Run-Unit-Tests") + .Does(() => + { + var windowsOutput = windowsBuildFullFramework + "/Jackett"; + + CopyDirectory("./src/Jackett.Console/bin/" + configuration, windowsOutput); + CopyFiles("./src/Jackett.Service/bin/" + configuration + "/JackettService.*", windowsOutput); + CopyFiles("./src/Jackett.Tray/bin/" + configuration + "/JackettTray.*", windowsOutput); + CopyFiles("./src/Jackett.Updater/bin/" + configuration + "/JackettUpdater.*", windowsOutput); + CopyFiles("./Upstart.config", windowsOutput); + CopyFiles("./LICENSE", windowsOutput); + CopyFiles("./README.md", windowsOutput); + + var monoOutput = monoBuildFullFramework + "/Jackett"; + + CopyDirectory(windowsBuildFullFramework, monoBuildFullFramework); + DeleteFiles(monoOutput + "/JackettService.*"); + DeleteFiles(monoOutput + "/JackettTray.*"); + }); + +Task("Check-Packaging-Platform") + .IsDependentOn("Copy-Files-Full-Framework") + .Does(() => + { + if (IsRunningOnWindows()) + { + CreateDirectory("./" + artifactsDirName); + Information(@"Platform is Windows"); + } + else + { + throw new Exception("Packaging is currently only implemented for a Windows environment"); + } + }); + +Task("Package-Windows-Installer-Full-Framework") + .IsDependentOn("Check-Packaging-Platform") + .Does(() => + { + InnoSetup("./Installer.iss", new InnoSetupSettings { + OutputDirectory = workingDir + "/" + artifactsDirName + }); + }); + +Task("Package-Files-Full-Framework-Windows") + .IsDependentOn("Check-Packaging-Platform") + .Does(() => + { + Zip(windowsBuildFullFramework, $"./{artifactsDirName}/Jackett.Binaries.Windows.zip"); + Information(@"Full Framework Windows Binaries Zipping Completed"); + }); + +Task("Package-Files-Full-Framework-Mono") + .IsDependentOn("Check-Packaging-Platform") + .Does(() => + { + var cygMonoBuildPath = RelativeWinPathToCygPath(monoBuildFullFramework); + var tarFileName = "Jackett.Binaries.Mono.tar"; + var tarArguments = @"-cvf " + cygMonoBuildPath + "/" + tarFileName + " -C " + cygMonoBuildPath + " Jackett --mode ='755'"; + var gzipArguments = @"-k " + cygMonoBuildPath + "/" + tarFileName; + + RunCygwinCommand("Tar", tarArguments); + RunCygwinCommand("Gzip", gzipArguments); + + MoveFile($"{monoBuildFullFramework}/{tarFileName}.gz", $"./{artifactsDirName}/{tarFileName}.gz"); + }); + +Task("Package-Full-Framework") + .IsDependentOn("Package-Windows-Installer-Full-Framework") + .IsDependentOn("Package-Files-Full-Framework-Windows") + .IsDependentOn("Package-Files-Full-Framework-Mono") + .Does(() => + { + Information(@"Full Framwork Packaging Completed"); + }); + +Task("Appveyor-Push-Artifacts") + .IsDependentOn("Package-Full-Framework") + .Does(() => + { + if (AppVeyor.IsRunningOnAppVeyor) + { + foreach (var file in GetFiles(workingDir + $"/{artifactsDirName}/*")) + { + AppVeyor.UploadArtifact(file.FullPath); + } + } + else + { + Information(@"Skipping as not running in AppVeyor Environment"); + } + }); + + +private void RunCygwinCommand(string utility, string utilityArguments) +{ + var cygwinDir = @"C:\cygwin\bin\"; + var utilityProcess = cygwinDir + utility + ".exe"; + + Information("CygWin Utility: " + utility); + Information("CygWin Directory: " + cygwinDir); + Information("Utility Location: " + utilityProcess); + Information("Utility Arguments: " + utilityArguments); + + IEnumerable redirectedStandardOutput; + IEnumerable redirectedErrorOutput; + var exitCodeWithArgument = + StartProcess( + utilityProcess, + new ProcessSettings { + Arguments = utilityArguments, + WorkingDirectory = cygwinDir, + RedirectStandardOutput = true + }, + out redirectedStandardOutput, + out redirectedErrorOutput + ); + + Information(utility + " output:" + Environment.NewLine + string.Join(Environment.NewLine, redirectedStandardOutput.ToArray())); + + // Throw exception if anything was written to the standard error. + if (redirectedErrorOutput != null && redirectedErrorOutput.Any()) + { + throw new Exception( + string.Format( + utility + " Errors ocurred: {0}", + string.Join(", ", redirectedErrorOutput))); + } + + Information(utility + " Exit code: {0}", exitCodeWithArgument); +} + +private string RelativeWinPathToCygPath(string relativePath) +{ + var cygdriveBase = "/cygdrive/" + workingDir.ToString().Replace(":", "").Replace("\\", "/"); + var cygPath = cygdriveBase + relativePath.Replace(".", ""); + return cygPath; +} + +////////////////////////////////////////////////////////////////////// +// TASK TARGETS +////////////////////////////////////////////////////////////////////// + +Task("Default") + .IsDependentOn("Appveyor-Push-Artifacts"); + +////////////////////////////////////////////////////////////////////// +// EXECUTION +////////////////////////////////////////////////////////////////////// + +RunTarget(target); diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 000000000..265bd1238 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,234 @@ +########################################################################## +# This is the Cake bootstrapper script for PowerShell. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +<# + +.SYNOPSIS +This is a Powershell script to bootstrap a Cake build. + +.DESCRIPTION +This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) +and execute your Cake build script with the parameters you provide. + +.PARAMETER Script +The build script to execute. +.PARAMETER Target +The build script target to run. +.PARAMETER Configuration +The build configuration to use. +.PARAMETER Verbosity +Specifies the amount of information to be displayed. +.PARAMETER ShowDescription +Shows description about tasks. +.PARAMETER DryRun +Performs a dry run. +.PARAMETER Experimental +Uses the nightly builds of the Roslyn script engine. +.PARAMETER Mono +Uses the Mono Compiler rather than the Roslyn script engine. +.PARAMETER SkipToolPackageRestore +Skips restoring of packages. +.PARAMETER ScriptArgs +Remaining arguments are added here. + +.LINK +https://cakebuild.net + +#> + +[CmdletBinding()] +Param( + [string]$Script = "build.cake", + [string]$Target, + [string]$Configuration, + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity, + [switch]$ShowDescription, + [Alias("WhatIf", "Noop")] + [switch]$DryRun, + [switch]$Experimental, + [switch]$Mono, + [switch]$SkipToolPackageRestore, + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$ScriptArgs +) + +[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null +function MD5HashFile([string] $filePath) +{ + if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) + { + return $null + } + + [System.IO.Stream] $file = $null; + [System.Security.Cryptography.MD5] $md5 = $null; + try + { + $md5 = [System.Security.Cryptography.MD5]::Create() + $file = [System.IO.File]::OpenRead($filePath) + return [System.BitConverter]::ToString($md5.ComputeHash($file)) + } + finally + { + if ($file -ne $null) + { + $file.Dispose() + } + } +} + +function GetProxyEnabledWebClient +{ + $wc = New-Object System.Net.WebClient + $proxy = [System.Net.WebRequest]::GetSystemWebProxy() + $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials + $wc.Proxy = $proxy + return $wc +} + +Write-Host "Preparing to run build script..." + +if(!$PSScriptRoot){ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} + +$TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$ADDINS_DIR = Join-Path $TOOLS_DIR "Addins" +$MODULES_DIR = Join-Path $TOOLS_DIR "Modules" +$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" +$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" +$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" +$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" +$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" +$ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config" +$MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config" + +# Make sure tools folder exists +if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { + Write-Verbose -Message "Creating tools directory..." + New-Item -Path $TOOLS_DIR -Type directory | out-null +} + +# Make sure that packages.config exist. +if (!(Test-Path $PACKAGES_CONFIG)) { + Write-Verbose -Message "Downloading packages.config..." + try { + $wc = GetProxyEnabledWebClient + $wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { + Throw "Could not download packages.config." + } +} + +# Try find NuGet.exe in path if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Trying to find nuget.exe in PATH..." + $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } + $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 + if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { + Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." + $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName + } +} + +# Try download NuGet.exe if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Downloading NuGet.exe..." + try { + $wc = GetProxyEnabledWebClient + $wc.DownloadFile($NUGET_URL, $NUGET_EXE) + } catch { + Throw "Could not download NuGet.exe." + } +} + +# Save nuget.exe path to environment to be available to child processed +$ENV:NUGET_EXE = $NUGET_EXE + +# Restore tools from NuGet? +if(-Not $SkipToolPackageRestore.IsPresent) { + Push-Location + Set-Location $TOOLS_DIR + + # Check for changes in packages.config and remove installed tools if true. + [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) + if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or + ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { + Write-Verbose -Message "Missing or changed package.config hash..." + Remove-Item * -Recurse -Exclude packages.config,nuget.exe + } + + Write-Verbose -Message "Restoring tools from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet tools." + } + else + { + $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" + } + Write-Verbose -Message ($NuGetOutput | out-string) + + Pop-Location +} + +# Restore addins from NuGet +if (Test-Path $ADDINS_PACKAGES_CONFIG) { + Push-Location + Set-Location $ADDINS_DIR + + Write-Verbose -Message "Restoring addins from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet addins." + } + + Write-Verbose -Message ($NuGetOutput | out-string) + + Pop-Location +} + +# Restore modules from NuGet +if (Test-Path $MODULES_PACKAGES_CONFIG) { + Push-Location + Set-Location $MODULES_DIR + + Write-Verbose -Message "Restoring modules from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet modules." + } + + Write-Verbose -Message ($NuGetOutput | out-string) + + Pop-Location +} + +# Make sure that Cake has been installed. +if (!(Test-Path $CAKE_EXE)) { + Throw "Could not find Cake.exe at $CAKE_EXE" +} + + + +# Build Cake arguments +$cakeArguments = @("$Script"); +if ($Target) { $cakeArguments += "-target=$Target" } +if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } +if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } +if ($ShowDescription) { $cakeArguments += "-showdescription" } +if ($DryRun) { $cakeArguments += "-dryrun" } +if ($Experimental) { $cakeArguments += "-experimental" } +if ($Mono) { $cakeArguments += "-mono" } +$cakeArguments += $ScriptArgs + +# Start Cake +Write-Host "Running build script..." +&$CAKE_EXE $cakeArguments +exit $LASTEXITCODE diff --git a/src/Jackett.sln b/src/Jackett.sln index b69ec1915..6a7f40ab4 100644 --- a/src/Jackett.sln +++ b/src/Jackett.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27004.2006 +VisualStudioVersion = 15.0.27004.2008 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jackett", "Jackett\Jackett.csproj", "{E636D5F8-68B4-4903-B4ED-CCFD9C9E899F}" EndProject @@ -9,6 +9,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BE7B0C8A-6144-47CD-821E-B09BA1B7BADE}" ProjectSection(SolutionItems) = preProject ..\appveyor.yml = ..\appveyor.yml + ..\build.cake = ..\build.cake ..\Installer.iss = ..\Installer.iss ..\LICENSE = ..\LICENSE ..\README.md = ..\README.md