diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..f5ed9efc4 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,25 @@ +version: "{build}" +pull_requests: + do_not_increment_build_number: true +shallow_clone: true + +cache: ..\openclonk-cache + +configuration: RelWithDebInfo +platform: + - Win32 + - x64 + +install: + - ps: "& $env:APPVEYOR_BUILD_FOLDER\\tools\\ci\\appv-Install.ps1" +before_build: + - ps: "& $env:APPVEYOR_BUILD_FOLDER\\tools\\ci\\appv-BeforeBuild.ps1" +build: + project: ../openclonk-build/openclonk.sln + verbosity: minimal +after_build: + - ps: "& $env:APPVEYOR_BUILD_FOLDER\\tools\\ci\\appv-AfterBuild.ps1" +test_script: + - ps: "& $env:APPVEYOR_BUILD_FOLDER\\tools\\ci\\appv-Test.ps1" + +deploy: off diff --git a/tools/ci/appv-AfterBuild.ps1 b/tools/ci/appv-AfterBuild.ps1 new file mode 100644 index 000000000..6667a82e8 --- /dev/null +++ b/tools/ci/appv-AfterBuild.ps1 @@ -0,0 +1,118 @@ +pushd $env:BUILD_TARGET_FOLDER +trap {popd} + +[void]([System.Reflection.Assembly]::LoadWithPartialName('Microsoft.Build')) +$projects = New-Object Microsoft.Build.Evaluation.ProjectCollection +$projects.SetGlobalProperty('Configuration', $env:CONFIGURATION) + +function Resolve-CanonicalPath { + param([string]$Path) + Add-Type -TypeDefinition @' +using System; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +public static class ResolveCanonicalPath { + [DllImport("kernel32.dll", EntryPoint="CreateFileW", CharSet=CharSet.Unicode, SetLastError=true)] + public static extern SafeFileHandle CreateFile( + string lpFileName, + int dwDesiredAccess, + int dwShareMode, + IntPtr securityAttributes, + int dwCreationDisposition, + int dwFlagsAndAttributes, + IntPtr hTemplateFile); + [DllImport("kernel32.dll", EntryPoint="GetFinalPathNameByHandleW", CharSet=CharSet.Unicode, SetLastError=true)] + public static extern int GetFinalPathNameByHandle( + IntPtr hFile, + [In, Out] System.Text.StringBuilder lpszFilePath, + int cchFilePath, + int dwFlags); + } +'@ + # Ask the file system to provide us with the canonical path, which includes + # proper capitalization + $fh = [ResolveCanonicalPath]::CreateFile($Path, 0, 2, [System.IntPtr]::Zero, 3, 0x2000000, [System.IntPtr]::Zero) + try { + $real_path = New-Object System.Text.StringBuilder 512 + $rp_len = [ResolveCanonicalPath]::GetFinalPathNameByHandle($fh.DangerousGetHandle(), $real_path, $real_path.Capacity, 0) + if ($rp_len -lt 0) { + throw [System.ComponentModel.Win32Exception]::new([System.Runtime.InteropServices.Marshal]::GetLastWin32Error()) + } + $real_path = $real_path.ToString() + if ($real_path.StartsWith('\\?\')) { + return $real_path.Substring(4) + } + return $real_path + } finally { + $fh.Close() + } +} + +function Add-SourceServerData { + param([string]$pdb) + + $srctool = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\srcsrv\srctool.exe" + $pdbstr = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\srcsrv\pdbstr.exe" + + $temp_name = New-TemporaryFile + try { + $temp = New-Object System.IO.StreamWriter $temp_name.OpenWrite() + $temp.WriteLine(@" +SRCSRV: ini ------------------------------------------------ +VERSION=2 +VERCTRL=http +SRCSRV: variables ------------------------------------------ +DEPOT=https://raw.githubusercontent.com/$env:APPVEYOR_REPO_NAME/%COMMIT%/ +COMMIT=$env:APPVEYOR_REPO_COMMIT +SRCSRVTRG=%DEPOT%%var2% +SRCSRV: source files --------------------------------------- +"@) + & $srctool -r $pdb | %{ + Resolve-CanonicalPath $_ + } | ?{ + # Filter everything outside of the source path + $_.StartsWith($env:APPVEYOR_BUILD_FOLDER, [System.StringComparison]::OrdinalIgnoreCase) + } | ?{ + # Filter everything that doesn't exist + Test-Path $_ + } | %{ + # Strip source folder prefix for building an URL + $path = $_.Substring($env:APPVEYOR_BUILD_FOLDER.Length) + $path = $path.TrimStart(@([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar)) + $path = $path.Replace('\','/') + $temp.WriteLine("$_*$path") + } + $temp.WriteLine('SRCSRV: end ------------------------------------------------') + $temp.Close() + # Write source info to PDB + & $pdbstr -w -p:$pdb -i:$($temp_name.FullName) -s:srcsrv + } finally { + $temp_name.Delete() + } +} + +if (-not $env:APPVEYOR) { + function Push-AppveyorArtifact { + param([string]$Path) + "Uploading $Path.... (dry run)" + } +} + +Get-Item *.vcxproj | %{ + $p = $projects.LoadProject($_.FullName) + if ($p.GetPropertyValue('ConfigurationType') -eq 'Application') { + # For all executable files + $binary = $p.GetPropertyValue('TargetPath') + if (Test-Path $binary) { + # Upload the executable itself as an artifact + Push-AppveyorArtifact $binary + $pdb = $p.ItemDefinitions['Link'].GetMetadataValue('ProgramDataBaseFile') + if (Test-Path $pdb) { + # If we generated a .pdb file, add source server information + Add-SourceServerData $pdb + Push-AppveyorArtifact $pdb + } + } + } +} diff --git a/tools/ci/appv-BeforeBuild.ps1 b/tools/ci/appv-BeforeBuild.ps1 new file mode 100644 index 000000000..fcd6c4ae8 --- /dev/null +++ b/tools/ci/appv-BeforeBuild.ps1 @@ -0,0 +1,31 @@ +$ErrorActionPreference='Stop' + +switch ($env:PLATFORM) { + 'Win32' { + $cmake_generator = 'Visual Studio 14 2015' + $qt = 'C:\Qt\5.7\msvc2015' + } + 'x64' { + $cmake_generator = 'Visual Studio 14 2015 Win64' + $qt = 'C:\Qt\5.7\msvc2015_64' + } +} + +pushd $env:BUILD_TARGET_FOLDER +try { + $(cmake --version)[0] + $ErrorActionPreference='SilentlyContinue' + cmake -G $cmake_generator -DCMAKE_PREFIX_PATH:PATH="$env:BUILD_DEPS_FOLDER\$env:PLATFORM;$qt_path" $env:APPVEYOR_BUILD_FOLDER 2>&1 + $ErrorActionPreference='Stop' + if ($LASTEXITCODE -ne 0) { + if (Test-Path CMakeFiles\CMakeOutput.log) { + Push-AppveyorArtifact CMakeFiles\CMakeOutput.log + } + if (Test-Path CMakeFiles\CMakeError.log) { + Push-AppveyorArtifact CMakeFiles\CMakeError.log + } + throw "CMake invocation failed with code $LASTEXITCODE" + } +} finally { + popd +} diff --git a/tools/ci/appv-Install.ps1 b/tools/ci/appv-Install.ps1 new file mode 100644 index 000000000..70eddd17c --- /dev/null +++ b/tools/ci/appv-Install.ps1 @@ -0,0 +1,99 @@ +$ErrorActionPreference='Stop' + +if (-not $env:APPVEYOR) { + function Set-AppveyorBuildVariable { + param([string]$Name, [string]$Value) + Set-Content -Path "env:$Name" -Value $Value + } +} + +# Calculate build paths. +$source_path = $env:APPVEYOR_BUILD_FOLDER +if ($source_path -eq $null) { + $source_path = $PWD.Path +} +$build_path = $env:BUILD_TARGET_FOLDER +$slug = [System.IO.Path]::GetFileName($source_path) +$prefix = [System.IO.Path]::GetDirectoryName($source_path) +if ($build_path -eq $null) { + $build_path = [System.IO.Path]::Combine($prefix, "${slug}-build") + Set-AppveyorBuildVariable -Name BUILD_TARGET_FOLDER -Value $build_path +} +$deps_path = $env:BUILD_DEPS_FOLDER +if ($deps_path -eq $null) { + $deps_path = [System.IO.Path]::Combine($prefix, "${slug}-deps") + Set-AppveyorBuildVariable -Name BUILD_DEPS_FOLDER -Value $deps_path +} +$cache_path = $env:BUILD_CACHE_FOLDER +if ($cache_path -eq $null) { + $cache_path = [System.IO.Path]::Combine($prefix, "${slug}-cache") + Set-AppveyorBuildVariable -Name BUILD_CACHE_FOLDER -Value $cache_path +} + +# Create folders we'll use +if (-not (Test-Path $build_path)) { + [void](mkdir $build_path) +} +if (-not (Test-Path $deps_path)) { + [void](mkdir $deps_path) +} +if (-not (Test-Path $cache_path)) { + [void](mkdir $cache_path) +} + +Write-Host "Source path: $source_path" +Write-Host "Building in: $build_path" +if ($build_path -eq $source_path) { + Write-Host 'Build type: in-tree (deprecated)' +} else { + Write-Host 'Build type: out-of-tree (recommended)' +} + +function Update-BuildCache { + param( + [Parameter(Mandatory=$true)] + [Uri] + $Url, + [string] + $File = $Url.Segments[-1], + [bool] + $Force = $false + ) + $local_file = $File + if (-not [System.IO.Path]::IsPathRooted($local_file)) { + $local_file = [System.IO.Path]::Combine($cache_path, $local_file) + } + Write-Host -NoNewline "Updating $([System.IO.Path]::GetFileName($local_file))... " + $fi = New-Object System.IO.FileInfo $local_file + [System.Net.HttpWebRequest]$req = [System.Net.WebRequest]::CreateHttp($Url) + $req.Method = 'GET' + # No need to do error handling because System.IO.FileInfo will return a + # date in the past if the file does not exist + $req.IfModifiedSince = $fi.LastWriteTime + try { + [System.Net.HttpWebResponse]$resp = $req.GetResponse() + $target_stream = $fi.Create() + $resp.GetResponseStream().CopyTo($target_stream) + $target_stream.Dispose() + $fi.LastWriteTime = $resp.LastModified + Write-Host 'done' + } catch [System.Net.WebException] { + if ($_.Exception.Response.StatusCode -eq [System.Net.HttpStatusCode]::NotModified) { + Write-Host 'unchanged' + return + } + Write-Host 'failed!' + throw $_.Exception + } +} + +# Update dependencies +pushd $deps_path +try { + # If you want to use the build dependencies for yourself, please note that + # this archive is a tarbomb, i.e. it doesn't contain a single root directory + Update-BuildCache 'https://autobuild.openclonk.org/static/binaries/appveyor-deps.tar.xz' + cmake -E tar xJ $cache_path\appveyor-deps.tar.xz +} finally { + popd +} \ No newline at end of file diff --git a/tools/ci/appv-Test.ps1 b/tools/ci/appv-Test.ps1 new file mode 100644 index 000000000..ee174b15c --- /dev/null +++ b/tools/ci/appv-Test.ps1 @@ -0,0 +1,18 @@ +pushd $env:BUILD_TARGET_FOLDER +trap {popd} + +[void]([System.Reflection.Assembly]::LoadWithPartialName('Microsoft.Build')) +$projects = New-Object Microsoft.Build.Evaluation.ProjectCollection +$projects.SetGlobalProperty('Configuration', $env:CONFIGURATION) + +Get-Item tests\*.vcxproj | %{ + $p = $projects.LoadProject($_.FullName) + if ($p.GetPropertyValue('ConfigurationType') -eq 'Application') { + $binary = $p.GetPropertyValue('TargetPath') + if (Test-Path $binary) { + & $binary "--gtest_output=xml:$binary.xml" + $client = New-Object System.Net.WebClient + $client.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", "$binary.xml") + } + } +}