diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..43e578c5 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,241 @@ +#!groovy + +/* +This is our new pipeline script to do all of the building in, of and around IW4x. + +Here's what it is supposed to do: + +- Make sure Modern Warfare 2 is installed (CI should provide the folder like a custom tool) +- Check out code from iw4x-data +- Build the IW4x client library (this code repository) +- Use iw4x.exe from the iw4x-data repository in order to build the zone files in iw4x-data +- Package the IW4x client with the newly built data files + +At this point it is done building everything, however afterwards we want the build server to +also push the newly built files to an update repository, depending on the branch we're on. + +- For "develop", release to the "iw4x-dev" branch on the repository server. +- For "master", release to the "iw4x" branch on the repository server. + +I'm looking into how the logic of pipelining works in detail before deciding on whether to +throw in the IW4x Updater and the IW4x Node binaries in as well or not. +*/ + +/* +Note that this is just a rewrite of the jobs as they are currently set up on the production +Jenkins server. This will allow every developer to tinker around with how the build process +is set up. For those who want to play around with this, here's a bit of information: + +- This is a Groovy script. Essentially Java but with less bullshit (like brackets and verbose writing). +- This gets directly translated into a Jenkins pipeline. +- If you have no idea how to handle scripts, get your hands off this file. +- If you do not use Jenkins, get your hands off this file. +- If you fuck this script up, I will kill you. +*/ + +import groovy.transform.Field + +@Field def configurations = [ + "Debug", + "DebugStatic", + "Release", + "ReleaseStatic" +] + +def useShippedPremake(f) { + def premakeHome = "${pwd()}\\tools" + + withEnv(["PATH+=${premakeHome}"], f) +} + +def getIW4xExecutable() { + step([ + $class: 'CopyArtifact', + filter: '*', + fingerprintArtifacts: true, + projectName: 'iw4x/iw4x-executable/master', + selector: [ + $class: 'TriggeredBuildSelector', + allowUpstreamDependencies: false, + fallbackToLastSuccessful: true, + upstreamFilterStrategy: 'UseGlobalSetting' + ] + ]) +} + +// This will build the IW4x client. +// We need a Windows Server with Visual Studio 2015, Premake5 and Git on it. +def doBuild(name, wsid, premakeFlags, configuration) { + node("windows") { + ws("IW4x/build/$wsid") { + checkout scm + + useShippedPremake { + def outputDir = pwd() + def msbuild = tool "Microsoft.NET MSBuild 14.0" + bat "premake5 vs2015 $premakeFlags" + bat "\"${msbuild}\" build\\iw4x.sln \"/p:OutDir=$outputDir\\\\\" \"/p:Configuration=$configuration\"" + } + + stash name: "$name", includes: "*.dll,*.pdb" + } + } +} + +// This will run the unit tests for IW4x. +// We need a Windows Server with MW2 on it. +def doUnitTests(name) { + mw2dir = tool "Modern Warfare 2" + + unstash "$name" + + // Get installed localization for correct zonefiles directory junction + def localization = readFile("$mw2dir/localization.txt").split("\r?\n")[0] + + try { + timeout(time: 180, unit: "MINUTES") { + // Set up environment + if (isUnix()) { + sh """ + mkdir -p zone + for f in main zone/dlc \"zone/$localization\"; do + ln -sfv \"$mw2dir/\$f\" \"\$f\" + done + for f in \"$mw2dir\"/*.dll \"$mw2dir\"/*.txt \"$mw2dir\"/*.bmp; do + ln -sfv \"\$f\" \"\$(basename \"\$f\")\" + done + """ + } else { + bat """ + mklink /J \"main\" \"$mw2dir\\main\" + mkdir \"zone\" + mklink /J \"zone\\dlc\" \"$mw2dir\\zone\\dlc\" + mklink /J \"zone\\$localization\" \"$mw2dir\\zone\\$localization\" + copy /y \"$mw2dir\\*.dll\" + copy /y \"$mw2dir\\*.txt\" + copy /y \"$mw2dir\\*.bmp\" + """ + } + + // Run tests + getIW4xExecutable() + if (isUnix()) { + sh "wine-wrapper iw4x.exe -tests" + } else { + bat "iw4x.exe -tests" + } + } + } finally { + // In all cases make sure to at least remove the directory junctions! + if (!isUnix()) { + bat """ + rmdir \"main\" + rmdir \"zone\\dlc\" + rmdir \"zone\\$localization\" + """ + } + deleteDir() + } +} + +// Job properties +properties([ + [$class: "GitLabConnectionProperty", gitLabConnection: "sr0"] +]) + +gitlabBuilds(builds: ["Checkout & Versioning", "Build", "Testing", "Archiving"]) { + // First though let's give this build a proper name + stage("Checkout & Versioning") { + gitlabCommitStatus("Checkout & Versioning") { + node("windows") { + checkout scm + + useShippedPremake { + def version = bat(returnStdout: true, script: '@premake5 version').split("\r?\n")[1] + + currentBuild.setDisplayName "$version (#${env.BUILD_NUMBER})" + } + } + } + } + + // For each available configuration generate a normal build and a unit test build. + stage("Build") { + gitlabCommitStatus("Build") { + def executions = [:] + for (int i = 0; i < configurations.size(); i++) + { + def configuration = configurations[i] + executions["$configuration"] = { + doBuild("IW4x $configuration", "$configuration", "", configuration) + } + executions["$configuration with unit tests"] = { + doBuild("IW4x $configuration (unit tests)", "$configuration+unittests", "--force-unit-tests", configuration) + } + } + parallel executions + } + } + + // Run unit tests on each configuration. + stage("Testing") { + gitlabCommitStatus("Testing") { + executions = [:] + for (int i = 0; i < configurations.size(); i++) { + def configuration = configurations[i] + executions["$configuration on Windows"] = { + node("windows") { + ws("IW4x/testing/$configuration") { + doUnitTests("IW4x $configuration (unit tests)") + } + } + } + executions["$configuration on Linux"] = { + node("docker && linux && amd64") { + try { + def image = null + dir("src") { + checkout scm + image = docker.build("github.com/IW4x/iw4x-client-testing-wine32", "--rm --force-rm -f jenkins/wine32.Dockerfile jenkins") + deleteDir() + } + image.inside { + doUnitTests("IW4x $configuration (unit tests)") + } + } catch (Exception e) { + if (isUnix()) { + manager.buildUnstable() + manager.addWarningBadge "$configuration unit test failed on Linux" + } else { + throw e + } + } + } + } + } + parallel executions + } + } + + // Collect all the binaries and give each configuration its own subfolder + stage("Archiving") { + gitlabCommitStatus("Archiving") { + node("windows") { // any node will do + ws("IW4x/pub") { + try { + for (int i = 0; i < configurations.size(); i++) + { + def configuration = configurations[i] + dir("$configuration") { + unstash "IW4x $configuration" + } + } + archiveArtifacts artifacts: "**/*.dll,**/*.pdb", fingerprint: true + } finally { + deleteDir() + } + } + } + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index 4d4c5409..383509be 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# IW4x: Client +# IW4x: Client ## How to compile diff --git a/jenkins/wine32.Dockerfile b/jenkins/wine32.Dockerfile new file mode 100644 index 00000000..1061c8e5 --- /dev/null +++ b/jenkins/wine32.Dockerfile @@ -0,0 +1,37 @@ +# Requires a decent modern Docker version (v1.10.x at least ideally) + +# Use semi-official Arch Linux image with fixed versioning +FROM pritunl/archlinux:2016-09-10 + +# Environment variables +ENV WINEPREFIX /wine32 +ENV WINEARCH win32 +ENV WINEDEBUG -all + +# Install Wine (32-bit) +RUN \ + echo -e "#!/bin/sh\nwine \$@\nretval=\$?\ntail --pid=\$(pidof wineserver 2>/dev/null||echo 0) -f /dev/null\nexit \$retval" > /usr/local/bin/wine-wrapper &&\ + chmod +x /usr/local/bin/wine-wrapper &&\ +\ + (\ + echo '' &&\ + echo '[multilib]' &&\ + echo 'Include = /etc/pacman.d/mirrorlist'\ + ) >> /etc/pacman.conf &&\ + pacman -Sy --noconfirm wine wget xorg-server-xvfb &&\ +\ + wine-wrapper wineboot.exe -i &&\ + wget -Ovcredist_x86.exe https://download.microsoft.com/download/d/d/9/dd9a82d0-52ef-40db-8dab-795376989c03/vcredist_x86.exe &&\ + xvfb-run sh -c 'wine-wrapper vcredist_x86.exe /q' &&\ + rm vcredist_x86.exe &&\ +\ + pacman -Rs --noconfirm xorg-server-xvfb wget &&\ +\ + find /. -name "*~" -type f -delete &&\ + rm -rf /tmp/* /var/tmp/* /usr/share/man/* /usr/share/info/* /usr/share/doc/* &&\ + pacman -Scc --noconfirm &&\ + paccache -rk0 &&\ + pacman-optimize &&\ + rm -rf /var/lib/pacman/sync/* + +USER 0 diff --git a/src/Components/Modules/BitMessage.cpp b/src/Components/Modules/BitMessage.cpp index 7a2e6cb4..76d44476 100644 --- a/src/Components/Modules/BitMessage.cpp +++ b/src/Components/Modules/BitMessage.cpp @@ -13,10 +13,10 @@ namespace Components BitMessage::BitMessage() { #ifdef DEBUG -Logger::Print("Initializing BitMessage...\n"); + Logger::Print("Initializing BitMessage...\n"); #endif // DEBUG - + BitMessage::BMClient = new BitMRC(BITMESSAGE_OBJECT_STORAGE_FILENAME, BITMESSAGE_KEYS_FILENAME); BitMessage::BMClient->init(); @@ -36,7 +36,7 @@ Logger::Print("Initializing BitMessage...\n"); BitMessage::BMClient->start(); #ifdef DEBUG - Command::Add("bm_send", [](Command::Params params) + Command::Add("bm_send", [](Command::Params params) { if (params.Length() < 3) return; @@ -70,7 +70,7 @@ Logger::Print("Initializing BitMessage...\n"); Logger::Print("Broadcast done.\n"); }); - Command::Add("bm_check_messages", [](Command::Params) + Command::Add("bm_check_messages", [](Command::Params) { while (BitMessage::BMClient->new_messages.size() > 0) { @@ -79,7 +79,7 @@ Logger::Print("Initializing BitMessage...\n"); } }); - Command::Add("bm_check_connections", [](Command::Params) + Command::Add("bm_check_connections", [](Command::Params) { std::shared_lock mlock(BitMessage::BMClient->mutex_nodes); @@ -104,7 +104,7 @@ Logger::Print("Initializing BitMessage...\n"); mlock.unlock(); }); - Command::Add("bm_check_privatekey", [](Command::Params) + Command::Add("bm_check_privatekey", [](Command::Params) { std::shared_lock mlock(BitMessage::BMClient->mutex_priv); @@ -123,7 +123,7 @@ Logger::Print("Initializing BitMessage...\n"); mlock.unlock(); }); - Command::Add("bm_check_publickey", [](Command::Params) + Command::Add("bm_check_publickey", [](Command::Params) { std::shared_lock mlock(BitMessage::BMClient->mutex_pub); @@ -140,12 +140,12 @@ Logger::Print("Initializing BitMessage...\n"); mlock.unlock(); }); - Command::Add("bm_save", [](Command::Params) + Command::Add("bm_save", [](Command::Params) { BitMessage::Save(); }); - Command::Add("bm_address_public", [](Command::Params params) + Command::Add("bm_address_public", [](Command::Params params) { if (params.Length() < 2) return; @@ -165,7 +165,7 @@ Logger::Print("Initializing BitMessage...\n"); } }); - Command::Add("bm_address_broadcast", [](Command::Params params) + Command::Add("bm_address_broadcast", [](Command::Params params) { if (params.Length() < 2) return; diff --git a/src/Components/Modules/MinidumpUpload.cpp b/src/Components/Modules/MinidumpUpload.cpp index 89da69a8..c50421fb 100644 --- a/src/Components/Modules/MinidumpUpload.cpp +++ b/src/Components/Modules/MinidumpUpload.cpp @@ -157,6 +157,7 @@ namespace Components MinidumpUpload::MinidumpUpload() { #if !defined(DEBUG) || defined(FORCE_MINIDUMP_UPLOAD) + if (Loader::PerformingUnitTests() || ZoneBuilder::IsEnabled()) return; this->uploadThread = std::thread([&]() { this->UploadQueuedMinidumps(); }); #endif } diff --git a/src/Components/Modules/Singleton.cpp b/src/Components/Modules/Singleton.cpp index 1a50fa5b..1d5ba1b1 100644 --- a/src/Components/Modules/Singleton.cpp +++ b/src/Components/Modules/Singleton.cpp @@ -19,7 +19,7 @@ namespace Components Console::FreeNativeConsole(); - if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return; + if (Loader::PerformingUnitTests() || Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return; Singleton::FirstInstance = (CreateMutexA(NULL, FALSE, "iw4x_mutex") && GetLastError() != ERROR_ALREADY_EXISTS);