diff --git a/.editorconfig b/.editorconfig index da701fb2dd..9e981b3144 100644 --- a/.editorconfig +++ b/.editorconfig @@ -57,5 +57,8 @@ dotnet_diagnostic.IDE0090.severity = none # Allow namespaces to be independent of folder names dotnet_diagnostic.IDE0130.severity = none +# Allow file-scoped namespaces +dotnet_diagnostic.IDE0160.severity = none + # Who cares if it's a JSON formatted string, in a test? dotnet_diagnostic.JSON002.severity = none diff --git a/.gitattributes b/.gitattributes index 833392b63c..98bb8e21db 100644 --- a/.gitattributes +++ b/.gitattributes @@ -40,7 +40,7 @@ # Build files *.cake text *.ps1 text -build text eol=lf +*.sh text eol=lf Dockerfile text eol=lf # Other diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bd77c7ce40..f199f67520 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,12 +22,12 @@ jobs: uses: actions/cache@v4 with: path: _build/tools - key: build-tools-${{ hashFiles('build', 'build.ps1', 'build.cake') }} + key: build-tools-${{ hashFiles('build.sh', 'build.ps1', 'build/*') }} - name: Restore cache for _build/cake uses: actions/cache@v4 with: path: _build/cake - key: build-cake-${{ hashFiles('build.cake') }} + key: build-cake-${{ hashFiles('build/*') }} - name: Restore cache for _build/lib/nuget uses: actions/cache@v4 with: @@ -36,7 +36,7 @@ jobs: ~/.nuget/packages key: nuget-oldref-modules-${{ hashFiles('**/packages.config') }}-${{ hashFiles('**/*.csproj') }} - name: Build ckan.exe and netkan.exe - run: ./build --configuration=${{ inputs.configuration }} + run: ./build.sh --configuration=${{ inputs.configuration }} - name: Upload repack artifact id: upload-repack-artifact uses: actions/upload-artifact@v4 @@ -52,7 +52,7 @@ jobs: retention-days: 1 - name: Bundle assets for signing if: inputs.configuration == 'Release' - run: ./build Prepare-SignPath --configuration=${{ inputs.configuration }} --exclusive + run: ./build.sh Prepare-SignPath --configuration=${{ inputs.configuration }} --exclusive - name: Upload unsigned artifact id: upload-unsigned-artifact if: inputs.configuration == 'Release' diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1e21510dae..8d18277df0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -104,7 +104,7 @@ jobs: name: Release-repack-unsigned path: _build/repack/ - name: Build dmg - run: ./build osx --configuration=Release --exclusive + run: ./build.sh osx --configuration=Release --exclusive - name: Push dmg to S3 run: aws s3 cp _build/osx/CKAN.dmg s3://${AWS_S3_BUCKET} --follow-symlinks @@ -137,7 +137,7 @@ jobs: - name: Build deb env: CODENAME: nightly - run: ./build deb --configuration=Release --exclusive + run: ./build.sh deb --configuration=Release --exclusive - name: Import GPG key env: DEBIAN_PRIVATE_KEY: ${{ secrets.DEBIAN_PRIVATE_KEY }} @@ -149,7 +149,7 @@ jobs: env: CODENAME: nightly DEBIAN_PRIVATE_KEY: ${{ secrets.DEBIAN_PRIVATE_KEY }} - run: ./build deb-sign --configuration=Release --exclusive + run: ./build.sh deb-sign --configuration=Release --exclusive if: ${{ env.DEBIAN_PRIVATE_KEY }} - name: Push deb to S3 run: aws s3 sync _build/deb/apt-repo-root s3://${AWS_S3_BUCKET}/deb --follow-symlinks @@ -194,7 +194,7 @@ jobs: mkdir -p _build/repack/Release cp _build/signed/ckan.exe _build/repack/Release - name: Build rpm - run: ./build rpm --configuration=Release --exclusive + run: ./build.sh rpm --configuration=Release --exclusive - name: Import GPG key env: DEBIAN_PRIVATE_KEY: ${{ secrets.DEBIAN_PRIVATE_KEY }} @@ -206,7 +206,7 @@ jobs: env: CODENAME: nightly DEBIAN_PRIVATE_KEY: ${{ secrets.DEBIAN_PRIVATE_KEY }} - run: ./build rpm-repo --configuration=Release --exclusive + run: ./build.sh rpm-repo --configuration=Release --exclusive if: ${{ env.DEBIAN_PRIVATE_KEY }} - name: Push nightly PRM repo to S3 run: aws s3 sync _build/rpm/repo s3://${AWS_S3_BUCKET}/rpm/nightly --follow-symlinks diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 53a888048c..b30801a0cd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,7 +45,7 @@ jobs: name: Release-repack-unsigned path: _build/repack/ - name: Build dmg - run: ./build osx --configuration=Release --exclusive + run: ./build.sh osx --configuration=Release --exclusive - name: Upload OSX release asset run: gh release upload ${{ github.event.release.tag_name }} _build/osx/CKAN.dmg env: @@ -79,7 +79,7 @@ jobs: - name: Build deb env: CODENAME: stable - run: ./build deb --configuration=Release --exclusive + run: ./build.sh deb --configuration=Release --exclusive - name: Import GPG key env: DEBIAN_PRIVATE_KEY: ${{ secrets.DEBIAN_PRIVATE_KEY }} @@ -91,7 +91,7 @@ jobs: env: CODENAME: stable DEBIAN_PRIVATE_KEY: ${{ secrets.DEBIAN_PRIVATE_KEY }} - run: ./build deb-sign --configuration=Release --exclusive + run: ./build.sh deb-sign --configuration=Release --exclusive if: ${{ env.DEBIAN_PRIVATE_KEY }} - name: Push deb to S3 run: aws s3 sync _build/deb/apt-repo-root s3://${AWS_S3_BUCKET}/deb --follow-symlinks @@ -130,7 +130,7 @@ jobs: VERSION=$(echo "${{ github.event.release.tag_name }}" | tr -d v) echo "RPM_VERSION=${VERSION}.$(date +'%g%j')" >> $GITHUB_ENV - name: Build rpm - run: ./build rpm --configuration=Release --exclusive + run: ./build.sh rpm --configuration=Release --exclusive - name: Import GPG key env: DEBIAN_PRIVATE_KEY: ${{ secrets.DEBIAN_PRIVATE_KEY }} @@ -141,7 +141,7 @@ jobs: env: CODENAME: stable DEBIAN_PRIVATE_KEY: ${{ secrets.DEBIAN_PRIVATE_KEY }} - run: ./build rpm-repo --configuration=Release --exclusive + run: ./build.sh rpm-repo --configuration=Release --exclusive if: ${{ env.DEBIAN_PRIVATE_KEY }} - name: Push stable RPM repo to S3 run: aws s3 sync _build/rpm/repo s3://${AWS_S3_BUCKET}/rpm/stable --follow-symlinks diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2c08d738a5..8e558f359b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,7 @@ jobs: name: Debug-repack-unsigned path: _build/repack/ - name: Run tests - run: xvfb-run ./build test+only --configuration=Debug --where="Category!=FlakyNetwork" + run: xvfb-run ./build.sh test+only --configuration=Debug --where="Category!=FlakyNetwork" # notify: # needs: diff --git a/.gitignore b/.gitignore index 3e7b8a28d7..ce61512ae9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /_build/ /.vs/ -/tools +/build/tools test-results *.userprefs *.csproj.user @@ -52,3 +52,69 @@ bld/ /.venv/ quotes.txt.dat + +### macOS template +# General +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Rider template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser diff --git a/.idea/.idea.CKAN/.idea/indexLayout.xml b/.idea/.idea.CKAN/.idea/indexLayout.xml new file mode 100644 index 0000000000..7b08163ceb --- /dev/null +++ b/.idea/.idea.CKAN/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.CKAN/.idea/projectSettingsUpdater.xml b/.idea/.idea.CKAN/.idea/projectSettingsUpdater.xml new file mode 100644 index 0000000000..64af657f5c --- /dev/null +++ b/.idea/.idea.CKAN/.idea/projectSettingsUpdater.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.CKAN/.idea/runConfigurations/Run_Build__Debug_.xml b/.idea/.idea.CKAN/.idea/runConfigurations/Run_Build__Debug_.xml new file mode 100644 index 0000000000..97e0d127f2 --- /dev/null +++ b/.idea/.idea.CKAN/.idea/runConfigurations/Run_Build__Debug_.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.CKAN/.idea/vcs.xml b/.idea/.idea.CKAN/.idea/vcs.xml new file mode 100644 index 0000000000..94a25f7f4c --- /dev/null +++ b/.idea/.idea.CKAN/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AutoUpdate/CKAN-autoupdate.csproj b/AutoUpdate/CKAN-autoupdate.csproj index 68385ade6b..944d7b6d15 100644 --- a/AutoUpdate/CKAN-autoupdate.csproj +++ b/AutoUpdate/CKAN-autoupdate.csproj @@ -65,7 +65,7 @@ - diff --git a/CKAN.sln b/CKAN.sln index 864ee92723..abe66284d4 100644 --- a/CKAN.sln +++ b/CKAN.sln @@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CKAN-netkan", "Netkan\CKAN- EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{4F41255E-8BC1-465B-82D5-1C5665BC099A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build", "build\Build.csproj", "{B3D22D01-132E-4D78-BA9E-FAC2AD02F2E1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/Cmdline/CKAN-cmdline.csproj b/Cmdline/CKAN-cmdline.csproj index dbec83401b..231b4b2910 100644 --- a/Cmdline/CKAN-cmdline.csproj +++ b/Cmdline/CKAN-cmdline.csproj @@ -93,7 +93,7 @@ - diff --git a/ConsoleUI/CKAN-ConsoleUI.csproj b/ConsoleUI/CKAN-ConsoleUI.csproj index 56918eb4fc..afbeca5630 100644 --- a/ConsoleUI/CKAN-ConsoleUI.csproj +++ b/ConsoleUI/CKAN-ConsoleUI.csproj @@ -83,7 +83,7 @@ - diff --git a/Core/CKAN-core.csproj b/Core/CKAN-core.csproj index ff516cd54d..574815698b 100644 --- a/Core/CKAN-core.csproj +++ b/Core/CKAN-core.csproj @@ -101,7 +101,7 @@ - diff --git a/Dockerfile b/Dockerfile index bf97b87a91..20ad1dd2eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -56,7 +56,7 @@ COPY . /source WORKDIR /source ARG config ENV config ${config:-Release} -RUN ./build --configuration=${config} +RUN ./build.sh --configuration=${config} RUN mkdir /build RUN cp _build/repack/${config}/ckan.exe /build/ckan.exe ENTRYPOINT ["/root/entrypoint.sh"] diff --git a/GUI/CKAN-GUI.csproj b/GUI/CKAN-GUI.csproj index aed179e0ad..f0092df8ee 100644 --- a/GUI/CKAN-GUI.csproj +++ b/GUI/CKAN-GUI.csproj @@ -111,7 +111,7 @@ - diff --git a/Netkan/CKAN-netkan.csproj b/Netkan/CKAN-netkan.csproj index ef0a2801aa..5a14b33450 100644 --- a/Netkan/CKAN-netkan.csproj +++ b/Netkan/CKAN-netkan.csproj @@ -71,7 +71,7 @@ - diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 8ed9286771..34687a8352 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -97,7 +97,7 @@ - diff --git a/build b/build deleted file mode 100755 index 71aec17d04..0000000000 --- a/build +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/sh -set -e - -arg0="" -remainingArgs="" - -if [ $# -gt 0 ]; then - arg0="$1" -fi - -if [ $# -gt 1 ]; then - skippedFirst=false - for i in "$@"; do - if $skippedFirst; then - remainingArgs="$remainingArgs $i" - else - skippedFirst=true - fi - done -fi - -nugetVersion="6.8.0" -useExperimental=false -rootDir=$(dirname $0) -scriptFile="$rootDir/build.cake" -buildDir="$rootDir/_build" -toolsDir="$buildDir/tools" -nugetExe="$toolsDir/NuGet/$nugetVersion/nuget.exe" - -nugetDir="$(dirname $nugetExe)" -if [ ! -d "$nugetDir" ]; then - mkdir -p "$nugetDir" -fi - -if [ ! -f "$nugetExe" ]; then - curl -L "https://dist.nuget.org/win-x86-commandline/v${nugetVersion}/nuget.exe" --output "$nugetExe" -fi - -# dotnet tool install idiotically returns failure if already installed, -# so we have to ignore all errors from it -# https://github.com/dotnet/sdk/issues/9500 -set +e -dotnet tool install --global Cake.Tool -set -e - -cakeArgs="" - -if [ -n "$arg0" ]; then - case $arg0 in - -*) - cakeArgs="$cakeArgs $arg0" - ;; - *) - cakeArgs="$cakeArgs --target=$arg0" - ;; - esac -fi - -if $useExperimental; then - cakeArgs="$cakeArgs --experimental" -fi - -export PATH="$PATH:$HOME/.dotnet/tools" -dotnet cake --verbosity Minimal "$scriptFile" $cakeArgs $remainingArgs -exit $? diff --git a/build.cake b/build.cake deleted file mode 100644 index f935a96b5e..0000000000 --- a/build.cake +++ /dev/null @@ -1,524 +0,0 @@ -#addin "nuget:?package=Cake.SemVer&version=4.0.0" -#addin "nuget:?package=semver&version=2.3.0" -#addin nuget:?package=Cake.Git&version=3.0.0 -#tool "nuget:?package=ILRepack&version=2.0.27" -#tool "nuget:?package=NUnit.ConsoleRunner&version=3.16.3" - -using System.Text.RegularExpressions; -using Semver; - -var buildNetFramework = "net48"; - -var target = Argument("target", "Default"); -var configuration = Argument("configuration", "Debug"); -var solution = Argument("solution", "CKAN.sln"); - -var rootDirectory = Context.Environment.WorkingDirectory; -var coreProj = rootDirectory.Combine("Core") - .CombineWithFilePath("CKAN-core.csproj") - .FullPath; -var buildDirectory = rootDirectory.Combine("_build"); -var nugetDirectory = buildDirectory.Combine("lib") - .Combine("nuget"); -var outDirectory = buildDirectory.Combine("out"); -var nupkgFile = outDirectory.Combine("CKAN") - .Combine(configuration) - .Combine("bin") - .CombineWithFilePath($"CKAN.{GetVersion(false)}.nupkg"); -var repackDirectory = buildDirectory.Combine("repack"); -var ckanFile = repackDirectory.Combine(configuration) - .CombineWithFilePath("ckan.exe"); -var updaterFile = repackDirectory.Combine(configuration) - .CombineWithFilePath("AutoUpdater.exe"); -var netkanFile = repackDirectory.Combine(configuration) - .CombineWithFilePath("netkan.exe"); - -Task("Default") - .Description("Build ckan.exe and netkan.exe") - .IsDependentOn("Ckan") - .IsDependentOn("Netkan"); - -Task("Debug") - .Description("Build ckan.exe and netkan.exe in Debug configuration") - .IsDependentOn("Default"); - -Task("Release") - .Description("Build ckan.exe and netkan.exe in Release configuration") - .IsDependentOn("Default"); - -Task("Ckan") - .Description("Build only ckan.exe") - .IsDependentOn("Repack-Ckan"); - -Task("Netkan") - .Description("Build only netkan.exe") - .IsDependentOn("Repack-Netkan"); - -Task("osx") - .Description("Build the macOS(OSX) dmg package.") - .IsDependentOn("Ckan") - .Does(() => MakeIn("macosx")); - -Task("osx-clean") - .Description("Clean the output directory of the macOS(OSX) package.") - .Does(() => MakeIn("macosx", "clean")); - -Task("deb") - .Description("Build the deb package for Debian-based distros.") - .IsDependentOn("Ckan") - .Does(() => MakeIn("debian")); - -Task("deb-sign") - .Description("Build the deb package for Debian-based distros.") - .IsDependentOn("Ckan") - .Does(() => MakeIn("debian", "sign")); - -Task("deb-test") - .Description("Test the deb packaging.") - .IsDependentOn("deb") - .Does(() => MakeIn("debian", "test")); - -Task("deb-clean") - .Description("Clean the deb output directory.") - .Does(() => MakeIn("debian", "clean")); - -Task("rpm") - .Description("Build the rpm package for RPM-based distros.") - .IsDependentOn("Ckan") - .Does(() => MakeIn("rpm")); - -Task("rpm-repo") - .Description("Build the rpm repository for RPM-based distros.") - .IsDependentOn("Ckan") - .Does(() => MakeIn("rpm", "repo")); - -Task("rpm-test") - .Description("Test the rpm packaging.") - .IsDependentOn("Ckan") - .Does(() => MakeIn("rpm", "test")); - -Task("rpm-clean") - .Description("Clean the rpm package output directory.") - .Does(() => MakeIn("rpm", "clean")); - -private void MakeIn(string dir, string args = null) -{ - int exitCode = StartProcess("make", new ProcessSettings { - WorkingDirectory = dir, - Arguments = args, - EnvironmentVariables = new Dictionary { { "CONFIGURATION", configuration } } - }); - if (exitCode != 0) - { - throw new Exception("Make failed with exit code: " + exitCode); - } -} - -Task("Restore") - .Description("Intermediate - Download dependencies") - .Does(() => - { - DotNetRestore(solution, new DotNetRestoreSettings - { - PackagesDirectory = nugetDirectory, - EnvironmentVariables = new Dictionary { { "Configuration", configuration } }, - }); - }); - -Task("Build") - .Description("Intermediate - Build everything") - .IsDependentOn("Restore") - .IsDependentOn("Generate-GlobalAssemblyVersionInfo") - .Does(() => - { - // dotnet build won't let us compile WinForms on non-Windows, - // fall back to mono - if (IsRunningOnWindows()) - { - DotNetBuild(solution, new DotNetBuildSettings - { - Configuration = configuration, - NoRestore = true, - }); - } - else - { - // Use dotnet to build the Core DLL to get the nupkg - // (only created if all TargetFrameworks are built together) - DotNetBuild(coreProj, new DotNetBuildSettings - { - Configuration = configuration, - }); - // Use Mono to build for net48 since dotnet can't use WinForms on Linux - MSBuild(solution, - settings => settings.SetConfiguration(configuration) - .SetMaxCpuCount(0) - .WithProperty("TargetFramework", - buildNetFramework)); - // Use dotnet to build the stuff Mono can't build - DotNetBuild(solution, new DotNetBuildSettings - { - Configuration = "NoGUI", - Framework = "net8.0", - }); - } - }); - -Task("Generate-GlobalAssemblyVersionInfo") - .Description("Intermediate - Calculate the version strings for the assembly.") - .Does(() => -{ - var metaDirectory = buildDirectory.Combine("meta"); - CreateDirectory(metaDirectory); - - var version = GetVersion(); - - CreateAssemblyInfo(metaDirectory.CombineWithFilePath("GlobalAssemblyVersionInfo.cs"), new AssemblyInfoSettings - { - Version = string.Format("{0}.{1}", version.Major, version.Minor), - FileVersion = string.IsNullOrEmpty(version.Metadata) - ? string.Format("{0}.{1}.{2}", - version.Major, version.Minor, version.Patch) - : string.Format("{0}.{1}.{2}.{3}", - version.Major, version.Minor, version.Patch, - version.Metadata), - InformationalVersion = version.ToString() - }); -}); - -Task("Repack-Ckan") - .Description("Intermediate - Merge all the separate DLLs and EXEs to a single executable.") - .IsDependentOn("Build") - .Does(() => -{ - CreateDirectory(repackDirectory.Combine(configuration)); - var cmdLineBinDirectory = outDirectory.Combine("CKAN-CmdLine") - .Combine(configuration) - .Combine("bin") - .Combine(buildNetFramework); - var assemblyPaths = GetFiles(string.Format("{0}/*.dll", cmdLineBinDirectory)); - assemblyPaths.Add(cmdLineBinDirectory.CombineWithFilePath("CKAN-GUI.exe")); - assemblyPaths.Add(cmdLineBinDirectory.CombineWithFilePath("CKAN-ConsoleUI.exe")); - assemblyPaths.Add(GetFiles(string.Format( - "{0}/*/*.resources.dll", - outDirectory.Combine("CKAN-CmdLine") - .Combine(configuration) - .Combine("bin") - .Combine(buildNetFramework)))); - // Need facade to instantiate types from netstandard2.0 DLLs on Mono - assemblyPaths.Add(FacadesDirectory().CombineWithFilePath("netstandard.dll")); - var ckanLogFile = repackDirectory.Combine(configuration) - .CombineWithFilePath("ckan.log"); - ReportRepacking(ckanFile, ckanLogFile); - ILRepack( - ckanFile, - cmdLineBinDirectory.CombineWithFilePath("CKAN-CmdLine.exe"), - assemblyPaths, - new ILRepackSettings - { - Libs = new List { cmdLineBinDirectory }, - TargetPlatform = TargetPlatformVersion.v4, - Parallel = true, - Verbose = false, - SetupProcessSettings = RepackSilently, - Log = ckanLogFile.FullPath, - }); - - var autoupdateBinDirectory = outDirectory.Combine("CKAN-AutoUpdateHelper") - .Combine(configuration) - .Combine("bin") - .Combine(buildNetFramework); - var updaterLogFile = repackDirectory.Combine(configuration) - .CombineWithFilePath("AutoUpdater.log"); - ReportRepacking(updaterFile, updaterLogFile); - ILRepack( - updaterFile, - autoupdateBinDirectory.CombineWithFilePath("CKAN-AutoUpdateHelper.exe"), - GetFiles(string.Format("{0}/*/*.resources.dll", - autoupdateBinDirectory)), - new ILRepackSettings - { - Libs = new List { autoupdateBinDirectory }, - TargetPlatform = TargetPlatformVersion.v4, - Parallel = true, - Verbose = false, - SetupProcessSettings = RepackSilently, - Log = updaterLogFile.FullPath, - }); - - CopyFile(ckanFile, buildDirectory.CombineWithFilePath(ckanFile.GetFilename())); -}); - -Task("Repack-Netkan") - .Description("Intermediate - Merge all the separate DLLs and EXEs to a single executable.") - .IsDependentOn("Build") - .Does(() => -{ - CreateDirectory(repackDirectory.Combine(configuration)); - var netkanBinDirectory = outDirectory.Combine("CKAN-NetKAN") - .Combine(configuration) - .Combine("bin") - .Combine(buildNetFramework); - var netkanLogFile = repackDirectory.Combine(configuration) - .CombineWithFilePath("netkan.log"); - var assemblyPaths = GetFiles(string.Format("{0}/*.dll", netkanBinDirectory)); - // Need facade to instantiate types from netstandard2.0 DLLs on Mono - assemblyPaths.Add(FacadesDirectory().CombineWithFilePath("netstandard.dll")); - ReportRepacking(netkanFile, netkanLogFile); - ILRepack( - netkanFile, - netkanBinDirectory.CombineWithFilePath("CKAN-NetKAN.exe"), - assemblyPaths, - new ILRepackSettings - { - Libs = new List { netkanBinDirectory }, - TargetPlatform = TargetPlatformVersion.v4, - Parallel = true, - Verbose = false, - SetupProcessSettings = RepackSilently, - Log = netkanLogFile.FullPath, - } - ); - - CopyFile(netkanFile, buildDirectory.CombineWithFilePath(netkanFile.GetFilename())); -}); - -Task("Prepare-SignPath") - .Description("Create a folder with all artifacts to be signed") - .IsDependentOn("Repack-Ckan") - .Does(() => -{ - var targetDir = buildDirectory.Combine("signpath") - .Combine(configuration); - CreateDirectory(targetDir); - CopyFile(ckanFile, targetDir.CombineWithFilePath(ckanFile.GetFilename())); - CopyFile(updaterFile, targetDir.CombineWithFilePath(updaterFile.GetFilename())); - CopyFile(nupkgFile, targetDir.CombineWithFilePath(nupkgFile.GetFilename())); -}); - -private void ReportRepacking(FilePath target, FilePath log) -{ - // ILRepack is extremly noisy by default and has no options to - // make it quieter other than shutting it up completely. - // - using (NormalVerbosity()) - { - Information("Repacking {0}, logging details to {1}...", - rootDirectory.GetRelativePath(target), - rootDirectory.GetRelativePath(log)); - } -} - -private void RepackSilently(ProcessSettings settings) - => settings.SetRedirectStandardOutput(true) - .SetRedirectedStandardOutputHandler(s => "") - .SetRedirectStandardError(true) - .SetRedirectedStandardErrorHandler(s => ""); - -Task("Test") - .Description("Run tests after compilation.") - .IsDependentOn("Default") - .IsDependentOn("Test+Only"); - -Task("Test+Only") - .Description("Run tests without compiling.") - .IsDependentOn("Test-Executables+Only") - .IsDependentOn("Test-UnitTests+Only"); - -Task("Test-Executables+Only") - .Description("Intermediate - Test executables without compiling.") - .IsDependentOn("Test-CkanExecutable+Only") - .IsDependentOn("Test-NetkanExecutable+Only"); - -Task("Test-CkanExecutable+Only") - .Description("Intermediate - Test ckan.exe without compiling.") - .Does(() => -{ - var output = RunExecutable(ckanFile, "version").FirstOrDefault(); - if (output != string.Format("v{0}", GetVersion())) - { - throw new Exception($"ckan.exe smoke test failed: {output}"); - } -}); - -Task("Test-NetkanExecutable+Only") - .Description("Intermediate - Test netkan.exe without compiling.") - .Does(() => -{ - var output = RunExecutable(netkanFile, "--version").FirstOrDefault(); - if (output != string.Format("v{0}", GetVersion())) - { - throw new Exception($"netkan.exe smoke test failed: {output}"); - } -}); - -Task("Test-UnitTests+Only") - .Description("Intermediate - Run unit tests without compiling.") - .Does(() => -{ - var where = Argument("where", null); - var nunitOutputDirectory = buildDirectory.Combine("test") - .Combine("nunit"); - CreateDirectory(nunitOutputDirectory); - // Only Mono's msbuild can handle WinForms on Linux, - // but dotnet build can handle multi-targeting on Windows - if (IsRunningOnWindows()) - { - DotNetTest(solution, new DotNetTestSettings - { - Configuration = configuration, - NoRestore = true, - NoBuild = true, - NoLogo = true, - Filter = where, - ResultsDirectory = nunitOutputDirectory, - Verbosity = DotNetVerbosity.Minimal, - }); - } - else - { - DotNetTest(solution, new DotNetTestSettings - { - Configuration = "NoGUI", - Framework = "net8.0", - NoRestore = true, - NoBuild = true, - NoLogo = true, - Filter = where, - ResultsDirectory = nunitOutputDirectory, - Verbosity = DotNetVerbosity.Minimal, - }); - var testFile = outDirectory.Combine("CKAN.Tests") - .Combine(configuration) - .Combine("bin") - .Combine(buildNetFramework) - .CombineWithFilePath("CKAN.Tests.dll"); - NUnit3(testFile.FullPath, new NUnit3Settings - { - Configuration = configuration, - Where = where, - Work = nunitOutputDirectory, - NoHeader = true, - // Work around System.Runtime.Remoting.RemotingException : Tcp transport error. - Process = NUnit3ProcessOption.InProcess, - }); - } -}); - -Task("Version") - .Description("Print the current CKAN version.") - .Does(() => -{ - using (NormalVerbosity()) - { - Information(GetVersion().ToString()); - } -}); - -Setup(context => -{ - var argConfiguration = Argument("configuration", null); - - if (string.Equals(target, "Release", StringComparison.OrdinalIgnoreCase)) - { - if (argConfiguration != null) - Warning($"Ignoring configuration argument: '{argConfiguration}'"); - - configuration = "Release"; - } - else if (string.Equals(target, "Debug", StringComparison.OrdinalIgnoreCase)) - { - if (argConfiguration != null) - Warning($"Ignoring configuration argument: '{argConfiguration}'"); - - configuration = "Debug"; - } -}); - -Teardown(context => -{ - var quote = GetQuote(rootDirectory.CombineWithFilePath("quotes.txt")); - - if (quote != null) - { - using (NormalVerbosity()) - { - Information(quote); - } - } -}); - -RunTarget(target); - -private DirectoryPath FacadesDirectory() - => IsRunningOnWindows() - ? Context.Environment.GetSpecialPath(SpecialPath.ProgramFilesX86) - .Combine("Reference Assemblies") - .Combine("Microsoft") - .Combine("Framework") - .Combine(".NETFramework") - .Combine("v4.8") - .Combine("Facades") - : new DirectoryPath("/usr").Combine("lib") - .Combine("mono") - .Combine("4.8-api") - .Combine("Facades"); - -private Semver.SemVersion GetVersion(bool withBuild = true) -{ - var pattern = new Regex(@"^\s*##\s+v(?\S+)\s?.*$"); - var rootDirectory = Context.Environment.WorkingDirectory; - - var versionMatch = System.IO.File - .ReadAllLines(rootDirectory.CombineWithFilePath("CHANGELOG.md").FullPath) - .Select(i => pattern.Match(i)) - .FirstOrDefault(i => i.Success); - - var version = ParseSemVer(versionMatch.Groups["version"].Value); - - if (withBuild && DirectoryExists(rootDirectory.Combine(".git"))) - { - var commitDate = GitLogTip(rootDirectory).Committer.When; - version = CreateSemVer(version.Major, - version.Minor, - version.Patch, - version.Prerelease, - commitDate.ToString("yy") + commitDate.DayOfYear.ToString("000")); - } - - return version; -} - -private IEnumerable RunExecutable(FilePath executable, string arguments) -{ - IEnumerable output; - var exitCode = StartProcess( - executable, - new ProcessSettings { Arguments = arguments, RedirectStandardOutput = true }, - out output - ); - - if (exitCode == 0) - { - return output; - } - else - { - throw new Exception("Process failed with exit code: " + exitCode); - } -} - -private string GetQuote(FilePath file) -{ - if (FileExists(file)) - { - var quotes = System.IO.File - .ReadAllText(file.FullPath) - .Split(new [] { '%' }, StringSplitOptions.RemoveEmptyEntries); - - if (quotes.Length > 0) - return quotes[(new Random()).Next(0, quotes.Length)]; - } - - return null; -} diff --git a/build.ps1 b/build.ps1 index 22c19dca6a..e88c3d240d 100644 --- a/build.ps1 +++ b/build.ps1 @@ -17,28 +17,10 @@ if (($PSVersionTable.PSVersion -lt $minPSVer)) { } # Globals -$NugetVersion = "6.8.0" -$UseExperimental = $false $RootDir = "${PSScriptRoot}" -$ScriptFile = "${RootDir}/build.cake" +$ScriptFile = "${RootDir}/build/Build.csproj" $BuildDir = "${RootDir}/_build" $ToolsDir = "${BuildDir}/tools" -$NugetExe = "${ToolsDir}/NuGet/${NugetVersion}/nuget.exe" - -# Download NuGet -$NugetDir = Split-Path "$NugetExe" -Parent -if (!(Test-Path "$NugetDir")) { - mkdir $nugetDir > $null -} - -if (!(Test-Path "$NugetExe")) { - # Enable TLS1.2 for WebClient - [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls13 - (New-Object System.Net.WebClient).DownloadFile("https://dist.nuget.org/win-x86-commandline/v${NugetVersion}/nuget.exe", $NugetExe) -} - -# Install build packages -dotnet tool install --global Cake.Tool # Build args $cakeArgs = @() @@ -51,10 +33,6 @@ if ($Arg0) { } } -if ($UseExperimental) { - $cakeArgs += "--experimental" -} - # Run Cake -dotnet cake --verbosity Minimal "${ScriptFile}" ${cakeArgs} ${RemainingArgs} -exit $LASTEXITCODE +dotnet run --project "${ScriptFile}" -- ${cakeArgs} ${RemainingArgs} +exit $LASTEXITCODE \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000000..9d944e5286 --- /dev/null +++ b/build.sh @@ -0,0 +1,41 @@ +#!/bin/sh +set -e + +arg0="" +remainingArgs="" + +if [ $# -gt 0 ]; then + arg0="$1" +fi + +if [ $# -gt 1 ]; then + skippedFirst=false + for i in "$@"; do + if $skippedFirst; then + remainingArgs="$remainingArgs $i" + else + skippedFirst=true + fi + done +fi + +rootDir=$(dirname "$0") +scriptFile="$rootDir/build/Build.csproj" + +cakeArgs="" + +if [ -n "$arg0" ]; then + case $arg0 in + -*) + cakeArgs="$cakeArgs $arg0" + ;; + *) + cakeArgs="$cakeArgs --target=$arg0" + ;; + esac +fi + +export PATH="$PATH:$HOME/.dotnet/tools" +# shellcheck disable=SC2086 +dotnet run --project "$scriptFile" -- $cakeArgs $remainingArgs +exit $? diff --git a/build/Build.csproj b/build/Build.csproj new file mode 100644 index 0000000000..da050b5902 --- /dev/null +++ b/build/Build.csproj @@ -0,0 +1,26 @@ + + + Exe + net8.0 + $(MSBuildProjectDirectory) + Build + ..\_build\out\$(AssemblyName)\$(Configuration)\bin\ + ..\_build\out\$(AssemblyName)\VSCodeIDE\bin\ + ..\_build\out\$(AssemblyName)\$(Configuration)\obj\ + ..\_build\out\$(AssemblyName)\VSCodeIDE\obj\ + false + 12 + enable + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/build/BuildContext.cs b/build/BuildContext.cs new file mode 100644 index 0000000000..ad7c8bdfb0 --- /dev/null +++ b/build/BuildContext.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +using Cake.Common; +using Cake.Common.Diagnostics; +using Cake.Common.IO; +using Cake.Core; +using Cake.Core.IO; +using Cake.Frosting; +using Cake.Git; + +namespace Build; + +public partial class BuildContext : FrostingContext +{ + public string BuildNetFramework { get; } = "net48"; + + public string Target { get; } + + // Named to avoid conflict with ICakeContext.Configuration + public string? BuildConfiguration { get; set; } + public string Solution { get; } + + public BuildPaths Paths { get; } + + public BuildContext(ICakeContext context) + : base(context) + { + var rootDir = context.Environment.WorkingDirectory.GetParent(); + + Target = context.Argument("target", "Default"); + BuildConfiguration = context.Argument("configuration", null); + Solution = context.Argument("solution", rootDir.CombineWithFilePath("CKAN.sln").FullPath); + + if (string.Equals(Target, "Release", StringComparison.OrdinalIgnoreCase)) + { + if (BuildConfiguration != null) + { + context.Warning($"Ignoring configuration argument: '{BuildConfiguration}'"); + } + + BuildConfiguration = "Release"; + } + else if (string.Equals(Target, "Debug", StringComparison.OrdinalIgnoreCase)) + { + if (BuildConfiguration != null) + { + context.Warning($"Ignoring configuration argument: '{BuildConfiguration}'"); + } + + BuildConfiguration = "Debug"; + } + + BuildConfiguration ??= "Debug"; + Paths = new BuildPaths(rootDir, BuildConfiguration, GetVersion(false)); + } + + public SemVersion GetVersion(bool withBuild = true) + { + var rootDirectory = Environment.WorkingDirectory.GetParent(); + + var versionMatch = System.IO.File + .ReadAllLines(rootDirectory.CombineWithFilePath("CHANGELOG.md").FullPath) + .Select(i => VersionRegex().Match(i)) + .First(i => i.Success); + + if (!SemVersion.TryParse(versionMatch.Groups["version"].Value, out var version)) + { + throw new Exception("Could not parse version from CHANGELOG.md"); + } + + if (withBuild && this.DirectoryExists(rootDirectory.Combine(".git"))) + { + var commitDate = this.GitLogTip(rootDirectory).Committer.When; + version = new SemVersion(version.Major, + version.Minor, + version.Patch, + version.PreRelease, + commitDate.ToString("yy") + commitDate.DayOfYear.ToString("000")); + } + + return version; + } + + [GeneratedRegex(@"^\s*##\s+v(?\S+)\s?.*$")] + private static partial Regex VersionRegex(); + + public DirectoryPath FacadesDirectory() + { + if (this.IsRunningOnWindows()) + { + return Environment.GetSpecialPath(SpecialPath.ProgramFilesX86) + .Combine("Reference Assemblies") + .Combine("Microsoft") + .Combine("Framework") + .Combine(".NETFramework") + .Combine("v4.8") + .Combine("Facades"); + } + + DirectoryPath monoLib; + if (this.IsRunningOnMacOs()) + { + monoLib = new DirectoryPath("/Library") + .Combine("Frameworks") + .Combine("Mono.framework") + .Combine("Versions") + .Combine("Current") + .Combine("lib"); + } + else + { + monoLib = new DirectoryPath("/usr").Combine("lib"); + } + + return monoLib + .Combine("mono") + .Combine("4.8-api") + .Combine("Facades"); + } + + public void ReportRepacking(FilePath target, FilePath log) + { + // ILRepack is extremely noisy by default and has no options to + // make it quieter other than shutting it up completely. + // + using (this.NormalVerbosity()) + { + this.Information("Repacking {0}, logging details to {1}...", + Paths.RootDirectory.GetRelativePath(target), + Paths.RootDirectory.GetRelativePath(log)); + } + } + + public static void RepackSilently(ProcessSettings settings) + => settings.SetRedirectStandardOutput(true) + .SetRedirectedStandardOutputHandler(s => "") + .SetRedirectStandardError(true) + .SetRedirectedStandardErrorHandler(s => ""); + + public string? GetQuote(FilePath file) + { + if (!this.FileExists(file)) + { + return null; + } + + var quotes = System.IO.File + .ReadAllText(file.FullPath) + .Split("%", StringSplitOptions.RemoveEmptyEntries); + + return quotes.Length > 0 ? quotes[new Random().Next(quotes.Length)] : null; + } + + public IEnumerable RunExecutable(FilePath executable, string arguments) + { + var exitCode = this.StartProcess( + executable, + new ProcessSettings { Arguments = arguments, RedirectStandardOutput = true }, + out IEnumerable output + ); + + if (exitCode != 0) + { + throw new Exception("Process failed with exit code: " + exitCode); + } + + return output; + } +} diff --git a/build/BuildLifetime.cs b/build/BuildLifetime.cs new file mode 100644 index 0000000000..f8520c7e09 --- /dev/null +++ b/build/BuildLifetime.cs @@ -0,0 +1,26 @@ +using Cake.Common.Diagnostics; +using Cake.Core; +using Cake.Frosting; + +namespace Build; + +public class BuildLifetime : FrostingLifetime +{ + public override void Setup(BuildContext context, ISetupContext info) + { + } + + public override void Teardown(BuildContext context, ITeardownContext info) + { + var quote = context.GetQuote(context.Paths.RootDirectory.CombineWithFilePath("quotes.txt")); + if (quote == null) + { + return; + } + + using (context.NormalVerbosity()) + { + context.Information(quote); + } + } +} diff --git a/build/BuildPaths.cs b/build/BuildPaths.cs new file mode 100644 index 0000000000..6cda99aa82 --- /dev/null +++ b/build/BuildPaths.cs @@ -0,0 +1,43 @@ +using Cake.Common; +using Cake.Core.IO; + +namespace Build; + +public class BuildPaths +{ + public DirectoryPath RootDirectory { get; init; } + public FilePath CoreProject { get; } + public DirectoryPath BuildDirectory { get; } + public DirectoryPath NugetDirectory { get; } + public DirectoryPath OutDirectory { get; } + public FilePath NupkgFile { get; } + public DirectoryPath RepackDirectory { get; } + public FilePath CkanFile { get; } + public FilePath UpdaterFile { get; } + public FilePath NetkanFile { get; } + + public BuildPaths(DirectoryPath rootDirectory, string configuration, SemVersion version) + { + RootDirectory = rootDirectory; + CoreProject = rootDirectory.Combine("Core") + .CombineWithFilePath("CKAN-core.csproj"); + BuildDirectory = rootDirectory.Combine("_build"); + NugetDirectory = BuildDirectory.Combine("lib").Combine("nuget"); + OutDirectory = BuildDirectory.Combine("out"); + NupkgFile = OutDirectory + .Combine("CKAN") + .Combine(configuration) + .Combine("bin") + .CombineWithFilePath($"CKAN.{version}.nupkg"); + RepackDirectory = BuildDirectory.Combine("repack"); + CkanFile = RepackDirectory + .Combine(configuration) + .CombineWithFilePath("ckan.exe"); + UpdaterFile = RepackDirectory + .Combine(configuration) + .CombineWithFilePath("AutoUpdater.exe"); + NetkanFile = RepackDirectory + .Combine(configuration) + .CombineWithFilePath("netkan.exe"); + } +} \ No newline at end of file diff --git a/build/MakeTasks.cs b/build/MakeTasks.cs new file mode 100644 index 0000000000..b9237134fe --- /dev/null +++ b/build/MakeTasks.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; + +using Cake.Common; +using Cake.Core.IO; +using Cake.Frosting; + +namespace Build; + +[TaskName("osx")] +[TaskDescription("Build the macOS(OSX) dmg package.")] +[IsDependentOn(typeof(CkanTask))] +public sealed class OsxTask() : MakeTask("macosx"); + +[TaskName("osx-clean")] +[TaskDescription("Clean the output directory of the macOS(OSX) package.")] +public sealed class OsxCleanTask() : MakeTask("macosx", "clean"); + +[TaskName("deb")] +[TaskDescription("Build the deb package for Debian-based distros.")] +[IsDependentOn(typeof(CkanTask))] +public sealed class DebTask() : MakeTask("debian"); + +[TaskName("deb-sign")] +[TaskDescription("Build the deb package for Debian-based distros.")] +[IsDependentOn(typeof(DebTask))] +public sealed class DebSignTask() : MakeTask("debian", "sign"); + +[TaskName("deb-test")] +[TaskDescription("Test the deb packaging.")] +[IsDependentOn(typeof(DebTask))] +public sealed class DebTestTask() : MakeTask("debian", "test"); + +[TaskName("deb-clean")] +[TaskDescription("Clean the deb output directory.")] +public sealed class DebCleanTask() : MakeTask("debian", "clean"); + +[TaskName("rpm")] +[TaskDescription("Build the rpm package for RPM-based distros.")] +[IsDependentOn(typeof(CkanTask))] +public sealed class RpmTask() : MakeTask("rpm"); + +[TaskName("rpm-repo")] +[TaskDescription("Build the rpm repository for RPM-based distros.")] +[IsDependentOn(typeof(CkanTask))] +public sealed class RpmRepoTask() : MakeTask("rpm", "repo"); + +[TaskName("rpm-test")] +[TaskDescription("Test the rpm packaging.")] +public sealed class RpmTestTask() : MakeTask("rpm", "test"); + +[TaskName("rpm-clean")] +[TaskDescription("Clean the rpm package output directory.")] +public sealed class RpmCleanTask() : MakeTask("rpm", "clean"); + +public abstract class MakeTask(string location, ProcessArgumentBuilder? args = null) : FrostingTask +{ + private string Location { get; } = location; + private ProcessArgumentBuilder Args { get; } = args ?? ""; + + public override void Run(BuildContext context) + { + var exitCode = context.StartProcess("make", new ProcessSettings() { + WorkingDirectory = Location, + Arguments = Args, + EnvironmentVariables = new Dictionary { { "CONFIGURATION", context.BuildConfiguration } } + }); + if (exitCode != 0) + { + throw new Exception("Make failed with exit code: " + exitCode); + } + } +} diff --git a/build/Program.cs b/build/Program.cs new file mode 100644 index 0000000000..2737aaf1db --- /dev/null +++ b/build/Program.cs @@ -0,0 +1,383 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Cake.Common; +using Cake.Common.Diagnostics; +using Cake.Common.IO; +using Cake.Common.Solution.Project.Properties; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.Build; +using Cake.Common.Tools.DotNet.Restore; +using Cake.Common.Tools.DotNet.Test; +using Cake.Common.Tools.ILMerge; +using Cake.Common.Tools.ILRepack; +using Cake.Common.Tools.MSBuild; +using Cake.Common.Tools.NUnit; +using Cake.Core.IO; +using Cake.Frosting; + +namespace Build; + +public static class Program +{ + public static int Main(string[] args) + { + return new CakeHost() + .ConfigureServices(services => + { + services.UseToolPath(new DirectoryPath(Environment.CurrentDirectory) + .GetParent() + .Combine("_build") + .Combine("tools")); + }) + .InstallTool(new Uri("nuget:?package=ILRepack&version=2.0.27")) + .InstallTool(new Uri("nuget:?package=NUnit.ConsoleRunner&version=3.16.3")) + .UseContext() + .UseLifetime() + .Run(args); + } +} + +[TaskName("Default")] +[TaskDescription("Build ckan.exe and netkan.exe")] +[IsDependentOn(typeof(CkanTask))] +[IsDependentOn(typeof(NetkanTask))] +public sealed class DefaultTask : FrostingTask; + +[TaskName("Debug")] +[TaskDescription("Build ckan.exe and netkan.exe in Debug configuration")] +[IsDependentOn(typeof(DefaultTask))] +public sealed class DebugTask : FrostingTask; + +[TaskName("Release")] +[TaskDescription("Build ckan.exe and netkan.exe in Release configuration")] +[IsDependentOn(typeof(DefaultTask))] +public sealed class ReleaseTask : FrostingTask; + +[TaskName("Netkan")] +[TaskDescription("Build only netkan.exe")] +[IsDependentOn(typeof(RepackNetkanTask))] +public sealed class NetkanTask : FrostingTask; + +[TaskName("Ckan")] +[TaskDescription("Build only ckan.exe")] +[IsDependentOn(typeof(RepackCkanTask))] +public sealed class CkanTask : FrostingTask; + +[TaskName("Restore")] +[TaskDescription("Intermediate - Download dependencies")] +public sealed class RestoreTask : FrostingTask +{ + public override void Run(BuildContext context) + { + context.DotNetRestore(new DotNetRestoreSettings + { + WorkingDirectory = context.Paths.RootDirectory, + PackagesDirectory = context.Paths.NugetDirectory, + EnvironmentVariables = new Dictionary { { "Configuration", context.BuildConfiguration } } + }); + } +} + +[TaskName("Generate-GlobalAssemblyVersionInfo")] +[TaskDescription("Intermediate - Calculate the version strings for the assembly.")] +public sealed class GenerateGlobalAssemblyVersionInfoTask : FrostingTask +{ + public override void Run(BuildContext context) + { + var metaDirectory = context.Paths.BuildDirectory.Combine("meta"); + context.CreateDirectory(metaDirectory); + + var version = context.GetVersion(); + + context.CreateAssemblyInfo( + metaDirectory.CombineWithFilePath("GlobalAssemblyVersionInfo.cs"), new AssemblyInfoSettings + { + Version = $"{version.Major}.{version.Minor}", + FileVersion = version.HasMeta + ? $"{version.Major}.{version.Minor}.{version.Patch}.{version.Meta}" + : $"{version.Major}.{version.Minor}.{version.Patch}", + InformationalVersion = version.ToString() + }); + } +} + +[TaskName("Build")] +[TaskDescription("Intermediate - Build everything")] +[IsDependentOn(typeof(RestoreTask))] +[IsDependentOn(typeof(GenerateGlobalAssemblyVersionInfoTask))] +public sealed class BuildTask : FrostingTask +{ + public override void Run(BuildContext context) + { + // dotnet build won't let us compile WinForms on non-Windows, + // fall back to mono + if (context.IsRunningOnWindows()) + { + context.DotNetBuild(context.Solution, new DotNetBuildSettings + { + Configuration = context.BuildConfiguration, + NoRestore = true, + }); + } + else + { + // Use dotnet to build the Core DLL to get the nupkg + // (only created if all TargetFrameworks are built together) + context.DotNetBuild(context.Paths.CoreProject.FullPath, new DotNetBuildSettings + { + Configuration = context.BuildConfiguration, + }); + + // Use Mono to build for net48 since dotnet can't use WinForms on Linux + context.MSBuild(context.Solution, new MSBuildSettings + { + Configuration = context.BuildConfiguration, + MaxCpuCount = 0, + Properties = { { "TargetFramework", [context.BuildNetFramework] } }, + }); + // Use dotnet to build the stuff Mono can't build + context.DotNetBuild(context.Solution, new DotNetBuildSettings + { + Configuration = "NoGUI", + Framework = "net8.0", + }); + } + } +} + +[TaskName("Repack-Ckan")] +[TaskDescription("Intermediate - Merge all the separate DLLs and EXEs to a single executable.")] +[IsDependentOn(typeof(BuildTask))] +public sealed class RepackCkanTask : FrostingTask +{ + public override void Run(BuildContext context) + { + context.CreateDirectory(context.Paths.RepackDirectory.Combine(context.BuildConfiguration)); + + var cmdLineBinDirectory = context.Paths.OutDirectory.Combine("CKAN-CmdLine") + .Combine(context.BuildConfiguration) + .Combine("bin") + .Combine(context.BuildNetFramework); + var assemblyPaths = context.GetFiles($"{cmdLineBinDirectory}/*.dll"); + assemblyPaths.Add(cmdLineBinDirectory.CombineWithFilePath("CKAN-GUI.exe")); + assemblyPaths.Add(cmdLineBinDirectory.CombineWithFilePath("CKAN-ConsoleUI.exe")); + var cmdlinePath = context.Paths.OutDirectory.Combine("CKAN-CmdLine") + .Combine(context.BuildConfiguration) + .Combine("bin") + .Combine(context.BuildNetFramework); + assemblyPaths.Add(context.GetFiles($"{cmdlinePath}/*/*.resources.dll")); + // Need facade to instantiate types from netstandard2.0 DLLs on Mono + assemblyPaths.Add(context.FacadesDirectory().CombineWithFilePath("netstandard.dll")); + var ckanLogFile = context.Paths.RepackDirectory.Combine(context.BuildConfiguration) + .CombineWithFilePath("ckan.log"); + context.ReportRepacking(context.Paths.CkanFile, ckanLogFile); + context.ILRepack( + context.Paths.CkanFile, + cmdLineBinDirectory.CombineWithFilePath("CKAN-CmdLine.exe"), + assemblyPaths, + new ILRepackSettings + { + Libs = [cmdLineBinDirectory], + TargetPlatform = TargetPlatformVersion.v4, + Parallel = true, + Verbose = false, + SetupProcessSettings = BuildContext.RepackSilently, + Log = ckanLogFile.FullPath, + }); + + var autoupdateBinDirectory = context.Paths.OutDirectory.Combine("CKAN-AutoUpdateHelper") + .Combine(context.BuildConfiguration) + .Combine("bin") + .Combine(context.BuildNetFramework); + var updaterLogFile = context.Paths.RepackDirectory.Combine(context.BuildConfiguration) + .CombineWithFilePath("AutoUpdater.log"); + context.ReportRepacking(context.Paths.UpdaterFile, updaterLogFile); + context.ILRepack( + context.Paths.UpdaterFile, + autoupdateBinDirectory.CombineWithFilePath("CKAN-AutoUpdateHelper.exe"), + context.GetFiles($"{autoupdateBinDirectory}/*/*.resources.dll"), + new ILRepackSettings + { + Libs = [autoupdateBinDirectory], + TargetPlatform = TargetPlatformVersion.v4, + Parallel = true, + Verbose = false, + SetupProcessSettings = BuildContext.RepackSilently, + Log = updaterLogFile.FullPath, + }); + + context.CopyFile(context.Paths.CkanFile, + context.Paths.BuildDirectory.CombineWithFilePath(context.Paths.CkanFile.GetFilename())); + } +} + +[TaskName("Repack-Netkan")] +[TaskDescription("Intermediate - Merge all the separate DLLs and EXEs to a single executable.")] +[IsDependentOn(typeof(BuildTask))] +public sealed class RepackNetkanTask : FrostingTask +{ + public override void Run(BuildContext context) + { + context.CreateDirectory(context.Paths.RepackDirectory.Combine(context.BuildConfiguration)); + var netkanBinDirectory = context.Paths.OutDirectory.Combine("CKAN-NetKAN") + .Combine(context.BuildConfiguration) + .Combine("bin") + .Combine(context.BuildNetFramework); + var netkanLogFile = context.Paths.RepackDirectory.Combine(context.BuildConfiguration) + .CombineWithFilePath("netkan.log"); + var assemblyPaths = context.GetFiles($"{netkanBinDirectory}/*.dll"); + // Need facade to instantiate types from netstandard2.0 DLLs on Mono + assemblyPaths.Add(context.FacadesDirectory().CombineWithFilePath("netstandard.dll")); + context.ReportRepacking(context.Paths.NetkanFile, netkanLogFile); + context.ILRepack( + context.Paths.NetkanFile, + netkanBinDirectory.CombineWithFilePath("CKAN-NetKAN.exe"), + assemblyPaths, + new ILRepackSettings + { + Libs = [netkanBinDirectory], + TargetPlatform = TargetPlatformVersion.v4, + Parallel = true, + Verbose = false, + SetupProcessSettings = BuildContext.RepackSilently, + Log = netkanLogFile.FullPath, + } + ); + + context.CopyFile(context.Paths.NetkanFile, + context.Paths.BuildDirectory.CombineWithFilePath(context.Paths.NetkanFile.GetFilename())); + } +} + +[TaskName("Prepare-SignPath")] +[TaskDescription("Create a folder with all artifacts to be signed")] +[IsDependentOn(typeof(RepackCkanTask))] +public sealed class PrepareSignPathTask : FrostingTask +{ + public override void Run(BuildContext context) + { + var targetDir = context.Paths.BuildDirectory.Combine("signpath") + .Combine(context.BuildConfiguration); + context.CreateDirectory(targetDir); + context.CopyFile(context.Paths.CkanFile, targetDir.CombineWithFilePath(context.Paths.CkanFile.GetFilename())); + context.CopyFile(context.Paths.UpdaterFile, targetDir.CombineWithFilePath(context.Paths.UpdaterFile.GetFilename())); + context.CopyFile(context.Paths.NupkgFile, targetDir.CombineWithFilePath(context.Paths.NupkgFile.GetFilename())); + } +} + +[TaskName("Test")] +[TaskDescription("Run tests after compilation.")] +[IsDependentOn(typeof(DefaultTask))] +[IsDependentOn(typeof(TestOnlyTask))] +public sealed class TestTask : FrostingTask; + +[TaskName("Test+Only")] +[TaskDescription("Run tests without compiling.")] +[IsDependentOn(typeof(TestExecutablesOnlyTask))] +[IsDependentOn(typeof(TestUnitTestsOnlyTask))] +public sealed class TestOnlyTask : FrostingTask; + +[TaskName("Test-Executables+Only")] +[TaskDescription("Intermediate - Test executables without compiling.")] +[IsDependentOn(typeof(TestCkanExecutableOnlyTask))] +[IsDependentOn(typeof(TestNetkanExecutableOnlyTask))] +public sealed class TestExecutablesOnlyTask : FrostingTask; + +[TaskName("Test-CkanExecutable+Only")] +[TaskDescription("Intermediate - Test ckan.exe without compiling.")] +public sealed class TestCkanExecutableOnlyTask : FrostingTask +{ + public override void Run(BuildContext context) + { + var output = context.RunExecutable(context.Paths.CkanFile, "version").FirstOrDefault(); + if (output != $"v{context.GetVersion()}") + { + throw new Exception($"ckan.exe smoke test failed: {output}"); + } + } +} + +[TaskName("Test-NetkanExecutable+Only")] +[TaskDescription("Intermediate - Test netkan.exe without compiling.")] +public sealed class TestNetkanExecutableOnlyTask : FrostingTask +{ + public override void Run(BuildContext context) + { + var output = context.RunExecutable(context.Paths.NetkanFile, "--version").FirstOrDefault(); + if (output != $"v{context.GetVersion()}") + { + throw new Exception($"netkan.exe smoke test failed: {output}"); + } + } +} + +[TaskName("Test-UnitTests+Only")] +[TaskDescription("Intermediate - Run unit tests without compiling.")] +public sealed class TestUnitTestsOnlyTask : FrostingTask +{ + public override void Run(BuildContext context) + { + var where = context.Argument("where", null); + var nunitOutputDirectory = context.Paths.BuildDirectory.Combine("test") + .Combine("nunit"); + context.CreateDirectory(nunitOutputDirectory); + // Only Mono's msbuild can handle WinForms on Linux, + // but dotnet build can handle multi-targeting on Windows + if (context.IsRunningOnWindows()) + { + context.DotNetTest(context.Solution, new DotNetTestSettings + { + Configuration = context.BuildConfiguration, + NoRestore = true, + NoBuild = true, + NoLogo = true, + Filter = where, + ResultsDirectory = nunitOutputDirectory, + Verbosity = DotNetVerbosity.Minimal, + }); + } + else + { + context.DotNetTest(context.Solution, new DotNetTestSettings + { + Configuration = "NoGUI", + Framework = "net8.0", + NoRestore = true, + NoBuild = true, + NoLogo = true, + Filter = where, + ResultsDirectory = nunitOutputDirectory, + Verbosity = DotNetVerbosity.Minimal, + }); + var testFile = context.Paths.OutDirectory.Combine("CKAN.Tests") + .Combine(context.BuildConfiguration) + .Combine("bin") + .Combine(context.BuildNetFramework) + .CombineWithFilePath("CKAN.Tests.dll"); + context.NUnit3(testFile.FullPath, new NUnit3Settings + { + Configuration = context.BuildConfiguration, + Where = where, + Work = nunitOutputDirectory, + NoHeader = true, + // Work around System.Runtime.Remoting.RemotingException : Tcp transport error. + Process = NUnit3ProcessOption.InProcess, + }); + } + } +} + +[TaskName("Version")] +[TaskDescription("Print the current CKAN version.")] +public sealed class VersionTask : FrostingTask +{ + public override void Run(BuildContext context) + { + using (context.NormalVerbosity()) + { + context.Information(context.GetVersion().ToString()); + } + } +} diff --git a/cake.config b/cake.config deleted file mode 100644 index abdda5f965..0000000000 --- a/cake.config +++ /dev/null @@ -1,4 +0,0 @@ -[Paths] -Tools=./_build/tools -Addins=./_build/cake/addins -Modules=./_build/cake/modules diff --git a/debian/Makefile b/debian/Makefile index 53fd225937..f32285bf99 100644 --- a/debian/Makefile +++ b/debian/Makefile @@ -95,7 +95,7 @@ $(EXEDEST): $(EXESRC) umask 0022 && cp $< $@ $(EXESRC): - cd .. && ./build --configuration=$(CONFIGURATION) + cd .. && ./build.sh --configuration=$(CONFIGURATION) $(CONTROLDEST): $(CONTROLSRC) $(CHANGELOGSRC) umask 0022 && mkdir -p $(shell dirname $@) diff --git a/doc/building.md b/doc/building.md index f2c76a7044..56ad870ad7 100644 --- a/doc/building.md +++ b/doc/building.md @@ -9,7 +9,7 @@ how the CKAN build system currently works and how changes to it should be made. ```plain > git clone https://github.com/KSP-CKAN/CKAN > cd CKAN -> ./build +> ./build.sh > _build/ckan.exe version v1.23.45-dev+abcdef > _build/netkan.exe --version @@ -24,7 +24,7 @@ The following are the minimum requirements to build CKAN and NetKAN from source: - A POSIX compliant shell interpreter at `/bin/sh` - [Mono](http://www.mono-project.com/) >= 4.0.0 -- [.NET 7](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) +- [.NET 8](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) SDK - [cURL](https://curl.haxx.se/) accessible in PATH ### Windows systems @@ -32,7 +32,7 @@ The following are the minimum requirements to build CKAN and NetKAN from source: - PowerShell - MSBuild (usually by installing [Visual Studio](https://www.visualstudio.com/vs/community/)) - [.NET Framework >= 4.8.0](https://dotnet.microsoft.com/en-us/download/dotnet-framework) -- [.NET 7](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) +- [.NET 8](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) ## Build directory @@ -47,10 +47,6 @@ that are output outside this directory should be considered a bug. The `_build` directory contains several subdirectories for different purposes. Nothing should be output to the `_build` directory itself, instead use one of the existing or create a new subdirectory. -- `_build/cake`: Used to store packages used by Cake. Use of this directory by Cake is controlled by the `cake.config` - file in the top-level directory. - - `_build/cake/addins`: Used to store [Cake addins](http://cakebuild.net/addins). - - `_build/cake/modules`: Used to store Cake modules. - `_build/lib`: Used to store packages that are *automatically* resolved by a package manager. - `_build/lib/nuget`: Used to store packages that are automatically resolved by NuGet. - `_build/meta`: Used to store source code that is dynamically generated by the build system. This is primarily used to @@ -67,32 +63,7 @@ directory itself, instead use one of the existing or create a new subdirectory. - `_build/repack`: Used to store the output of [ILRepack](https://github.com/gluck/il-repack). - `_build/test`: Used to store the output of test runners. - `_build/test/nunit`: Used to store the output of the NUnit test runner. -- `_build/tools`: Used to store tools downloaded by the bootstraper or Cake. - -## Bootstrapper Scripts - -The primary entry point to the CKAN build system are the two bootstraper scripts in the top level directory `build` and -`build.ps1`. Developers use these scripts for all build related operations. - -`build` is POSIX shell script for use on Unix-like systems (BSD, Linux, macOS, etc.). `build` is executed by `/bin/sh` -which is expected to be a POSIX compatible shell. It is quite intentionally **not** a bash shell script and as such -contains no [bashisms](https://en.wiktionary.org/wiki/bashism). It can therefore be used on systems where `/bin/sh` is -not bash but instead an alternative shell interpreter like dash. Any changes to this script should be careful not to -introduce such bashisms that would break this compatibility. - -`build.ps1` is a PowerShell script for use on Windows systems. Users on Windows *must* use PowerShell 3.0 or later; - the old `cmd.exe` interpreter is not supported. - -Both of these scripts are used to "bootstrap" the build environment. They function identically and as such we will -treat them as a singular script (`./build`) for all platforms. The basic operation of the bootstrap scripts are to: - -1. Download [NuGet](https://www.nuget.org/) -2. Install `Cake.Tool` for `dotnet` -3. Parse the command line arguments -4. Execute `dotnet cake` with the appropriate command line arguments -5. Quit with the exit code generated by Cake - -The specifics of each operations are given in the following sections. +- `_build/tools`: Used to store tools downloaded by Cake. ## NuGet @@ -100,7 +71,7 @@ NuGet is the standard .NET package manager. Since nearly every useful .NET tool it can be used to bootstrap the rest of the build system. Specifically the bootstrapper scripts are used to download NuGet, which Cake then uses to install `Cake.SemVer`, `semver`, `Cake.Docker`, `ILRepack`, and `NUnit.ConsoleRunner`. -Installation of project dependencies is handled by `dotnet restore`. +Installation of project dependencies is handled by `dotnet restore`, which is automatically invoked by the build script. ## Cake @@ -109,25 +80,24 @@ After Cake is installed by NuGet, the bootstrapper scripts then parse the comman The bootstrapper scripts use the following command line: ```plain -./build [] [...] +./build.sh [] [...] ``` Which is then used to execute Cake with the following command line: ```plain -cake.exe build.cake [--target=] [...] +dotnet run --project ./build/Build.csproj -- [-t ] [...] ``` -The first argument is the Cake script to execute. This is always `build.cake` and does not ever need to be changed. The +The first argument is the Cake project to execute. This is always `build` and does not ever need to be changed. The second argument is the `--target` argument which tells Cake which task in `build.cake` to execute. If not provided it defaults to the (appropriately named) `Default` task. Finally, the bootstrapper scripts will pass any additional arguments to Cake as is. ### `build.cake` -`build.cake` is the Cake script which does most of the heavy lifting for the build. Cake scripts are C#-like files -which are dynamically compiled by the Roslyn compiler. Pretty much all the same rules as standard C# apply with the -exception of Cake-specific [preprocessor directives](http://cakebuild.net/docs/fundamentals/preprocessor-directives). +The `build` folder is the Cake Frosting project which does most of the heavy lifting for the build. Cake Frosting +projects are normal command-line C# projects, so all the same rules as standard C# apply. The CKAN build script is fairly simply and self-explanatory. Some conventions of note: @@ -161,7 +131,7 @@ The purpose of each defined task is as follows: #### Helper Methods -`build.cake` also contains a couple of helper methods. +The build script also contains a couple of helper methods. - `GetVersion()`: Read the most recent version number from `CHANGELOG.md` and and attach a short version (12-character) of the current git commit hash if available. The output is a @@ -213,8 +183,8 @@ push. GitHub's operation is pretty simple: - A bunch of packages are installed to make the build work. - Some commands are executed to simulate a graphical environment for testing. -- The solution is built using `./build --configuration=$BUILD_CONFIGURATION` -- The solution is tested using `./build test+only --configuration=$BUILD_CONFIGURATION --where="Category!=FlakyNetwork"` +- The solution is built using `./build.sh --configuration=$BUILD_CONFIGURATION` +- The solution is tested using `./build.sh test+only --configuration=$BUILD_CONFIGURATION --where="Category!=FlakyNetwork"` - The `--where="Category!=FlakyNetwork"` argument is used to not execute tests that rely upon a network connection and could thus behave nondeterministically (frustrating behavior for a CI system). - The built executables (`ckan.exe` and `netkan.exe`) are uploaded to Amazon S3 if the following conditions are met: diff --git a/macosx/Makefile b/macosx/Makefile index cc85209021..ad975f59a5 100644 --- a/macosx/Makefile +++ b/macosx/Makefile @@ -27,7 +27,7 @@ $(EXEDEST): $(EXESRC) ln $< $@ $(EXESRC): - cd .. && ./build --configuration=Release + cd .. && ./build.sh --configuration=Release $(SCRIPTDEST): $(SCRIPTSRC) mkdir -p $(shell dirname $@) diff --git a/rpm/Makefile b/rpm/Makefile index ab6022145a..c6f8e46fc9 100644 --- a/rpm/Makefile +++ b/rpm/Makefile @@ -51,7 +51,7 @@ $(REPORPM): $(RPM) $(REPOFILE) --addsign $@ $(EXESRC): - cd .. && ./build --configuration=$(CONFIGURATION) + cd .. && ./build.sh --configuration=$(CONFIGURATION) clean: rm -r "$(TOPDIR)"