diff --git a/.gitignore b/.gitignore index 0a7eb1e4f8cebe191c5871ee8ff4938ae6cff574..a0129822f5ee66e6b514ed2da2bb3835a22e757e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ bin obj +.vs/ *.suo *.user _ReSharper.* @@ -14,8 +15,8 @@ artifacts StyleCop.Cache node_modules *.snk -.build .nuget .r .deps -global.json \ No newline at end of file +global.json +korebuild-lock.txt diff --git a/build.ps1 b/build.ps1 index d982c8fed0e132604a660a155b4be1a861b80e3f..d5eb4d5cf2799baf5807d24996744d87edcbbeb4 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,66 +1,177 @@ -$ErrorActionPreference = "Stop" - -function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $retries) -{ - while($true) - { - try - { - Invoke-WebRequest $url -OutFile $downloadLocation - break - } - catch - { - $exceptionMessage = $_.Exception.Message - Write-Host "Failed to download '$url': $exceptionMessage" - if ($retries -gt 0) { - $retries-- - Write-Host "Waiting 10 seconds before retrying. Retries left: $retries" - Start-Sleep -Seconds 10 +#!/usr/bin/env powershell +#requires -version 4 + +<# +.SYNOPSIS +Build this repository + +.DESCRIPTION +Downloads korebuild if required. Then builds the repository. + +.PARAMETER Path +The folder to build. Defaults to the folder containing this script. + +.PARAMETER Channel +The channel of KoreBuild to download. Overrides the value from the config file. + +.PARAMETER DotNetHome +The directory where .NET Core tools will be stored. + +.PARAMETER ToolsSource +The base url where build tools can be downloaded. Overrides the value from the config file. + +.PARAMETER Update +Updates KoreBuild to the latest version even if a lock file is present. + +.PARAMETER ConfigFile +The path to the configuration file that stores values. Defaults to version.xml. + +.PARAMETER MSBuildArgs +Arguments to be passed to MSBuild + +.NOTES +This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be. +When the lockfile is not present, KoreBuild will create one using latest available version from $Channel. + +The $ConfigFile is expected to be an XML file. It is optional, and the configuration values in it are optional as well. + +.EXAMPLE +Example config file: +```xml +<!-- version.xml --> +<Project> + <PropertyGroup> + <KoreBuildChannel>dev</KoreBuildChannel> + <KoreBuildToolsSource>https://aspnetcore.blob.core.windows.net/buildtools</KoreBuildToolsSource> + </PropertyGroup> +</Project> +``` +#> +[CmdletBinding(PositionalBinding = $false)] +param( + [string]$Path = $PSScriptRoot, + [Alias('c')] + [string]$Channel, + [Alias('d')] + [string]$DotNetHome, + [Alias('s')] + [string]$ToolsSource, + [Alias('u')] + [switch]$Update, + [string]$ConfigFile = (Join-Path $PSScriptRoot 'version.xml'), + [Parameter(ValueFromRemainingArguments = $true)] + [string[]]$MSBuildArgs +) + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +# +# Functions +# + +function Get-KoreBuild { + + $lockFile = Join-Path $Path 'korebuild-lock.txt' + + if (!(Test-Path $lockFile) -or $Update) { + Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile + } + $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 + if (!$version) { + Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'" + } + $version = $version.TrimStart('version:').Trim() + $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version) + + if (!(Test-Path $korebuildPath)) { + Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version" + New-Item -ItemType Directory -Path $korebuildPath | Out-Null + $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip" + + try { + $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" + Get-RemoteFile $remotePath $tmpfile + if (Get-Command -Name 'Expand-Archive' -ErrorAction Ignore) { + # Use built-in commands where possible as they are cross-plat compatible + Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath } - else - { - $exception = $_.Exception - throw $exception + else { + # Fallback to old approach for old installations of PowerShell + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath) } } + catch { + Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore + throw + } + finally { + Remove-Item $tmpfile -ErrorAction Ignore + } } + + return $korebuildPath +} + +function Join-Paths([string]$path, [string[]]$childPaths) { + $childPaths | ForEach-Object { $path = Join-Path $path $_ } + return $path } -cd $PSScriptRoot +function Get-RemoteFile([string]$RemotePath, [string]$LocalPath) { + if ($RemotePath -notlike 'http*') { + Copy-Item $RemotePath $LocalPath + return + } + + $retries = 10 + while ($retries -gt 0) { + $retries -= 1 + try { + Invoke-WebRequest -UseBasicParsing -Uri $RemotePath -OutFile $LocalPath + return + } + catch { + Write-Verbose "Request failed. $retries retries remaining" + } + } -$repoFolder = $PSScriptRoot -$env:REPO_FOLDER = $repoFolder -$koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip" -if ($env:KOREBUILD_ZIP) -{ - $koreBuildZip=$env:KOREBUILD_ZIP + Write-Error "Download failed: '$RemotePath'." } -$buildFolder = ".build" -$buildFile="$buildFolder\KoreBuild.ps1" +# +# Main +# -if (!(Test-Path $buildFolder)) { - Write-Host "Downloading KoreBuild from $koreBuildZip" +# Load configuration or set defaults - $tempFolder=$env:TEMP + "\KoreBuild-" + [guid]::NewGuid() - New-Item -Path "$tempFolder" -Type directory | Out-Null +if (Test-Path $ConfigFile) { + [xml] $config = Get-Content $ConfigFile + if (!($Channel)) { [string] $Channel = Select-Xml -Xml $config -XPath '/Project/PropertyGroup/KoreBuildChannel' } + if (!($ToolsSource)) { [string] $ToolsSource = Select-Xml -Xml $config -XPath '/Project/PropertyGroup/KoreBuildToolsSource' } +} - $localZipFile="$tempFolder\korebuild.zip" +if (!$DotNetHome) { + $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } ` + elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} ` + elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}` + else { Join-Path $PSScriptRoot '.dotnet'} +} - DownloadWithRetry -url $koreBuildZip -downloadLocation $localZipFile -retries 6 +if (!$Channel) { $Channel = 'dev' } +if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } - Add-Type -AssemblyName System.IO.Compression.FileSystem - [System.IO.Compression.ZipFile]::ExtractToDirectory($localZipFile, $tempFolder) +# Execute - New-Item -Path "$buildFolder" -Type directory | Out-Null - copy-item "$tempFolder\**\build\*" $buildFolder -Recurse +$korebuildPath = Get-KoreBuild +Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') - # Cleanup - if (Test-Path $tempFolder) { - Remove-Item -Recurse -Force $tempFolder - } +try { + Install-Tools $ToolsSource $DotNetHome + Invoke-RepositoryBuild $Path @MSBuildArgs +} +finally { + Remove-Module 'KoreBuild' -ErrorAction Ignore } - -&"$buildFile" @args diff --git a/build.sh b/build.sh index b0bcadb579286546bcd6be3ab4ae4a571132a7c1..d102c8f9054b59492c34ed9d5e3c15a7182c0f6a 100755 --- a/build.sh +++ b/build.sh @@ -1,46 +1,195 @@ #!/usr/bin/env bash -repoFolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -cd $repoFolder -koreBuildZip="https://github.com/aspnet/KoreBuild/archive/dev.zip" -if [ ! -z $KOREBUILD_ZIP ]; then - koreBuildZip=$KOREBUILD_ZIP -fi +set -euo pipefail + +# +# variables +# -buildFolder=".build" -buildFile="$buildFolder/KoreBuild.sh" +RESET="\033[0m" +RED="\033[0;31m" +MAGENTA="\033[0;95m" +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +[ -z "${DOTNET_HOME:-}"] && DOTNET_HOME="$HOME/.dotnet" +config_file="$DIR/version.xml" +verbose=false +update=false +repo_path="$DIR" +channel='' +tools_source='' -if test ! -d $buildFolder; then - echo "Downloading KoreBuild from $koreBuildZip" +# +# Functions +# +__usage() { + echo "Usage: $(basename ${BASH_SOURCE[0]}) [options] [[--] <MSBUILD_ARG>...]" + echo "" + echo "Arguments:" + echo " <MSBUILD_ARG>... Arguments passed to MSBuild. Variable number of arguments allowed." + echo "" + echo "Options:" + echo " --verbose Show verbose output." + echo " -c|--channel <CHANNEL> The channel of KoreBuild to download. Overrides the value from the config file.." + echo " --config-file <FILE> TThe path to the configuration file that stores values. Defaults to version.xml." + echo " -d|--dotnet-home <DIR> The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet." + echo " --path <PATH> The directory to build. Defaults to the directory containing the script." + echo " -s|--tools-source <URL> The base url where build tools can be downloaded. Overrides the value from the config file." + echo " -u|--update Update to the latest KoreBuild even if the lock file is present." + echo "" + echo "Description:" + echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be." + echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel." - tempFolder="/tmp/KoreBuild-$(uuidgen)" - mkdir $tempFolder + if [[ "${1:-}" != '--no-exit' ]]; then + exit 2 + fi +} + +get_korebuild() { + local lock_file="$repo_path/korebuild-lock.txt" + if [ ! -f $lock_file ] || [ "$update" = true ]; then + __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" $lock_file + fi + local version="$(grep 'version:*' -m 1 $lock_file)" + if [[ "$version" == '' ]]; then + __error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'" + return 1 + fi + version="$(echo ${version#version:} | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version" - localZipFile="$tempFolder/korebuild.zip" + { + if [ ! -d "$korebuild_path" ]; then + mkdir -p "$korebuild_path" + local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip" + tmpfile="$(mktemp)" + echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}" + if __get_remote_file $remote_path $tmpfile; then + unzip -q -d "$korebuild_path" $tmpfile + fi + rm $tmpfile || true + fi - retries=6 - until (wget -O $localZipFile $koreBuildZip 2>/dev/null || curl -o $localZipFile --location $koreBuildZip 2>/dev/null) - do - echo "Failed to download '$koreBuildZip'" - if [ "$retries" -le 0 ]; then - exit 1 + source "$korebuild_path/KoreBuild.sh" + } || { + if [ -d "$korebuild_path" ]; then + echo "Cleaning up after failed installation" + rm -rf "$korebuild_path" || true fi - retries=$((retries - 1)) - echo "Waiting 10 seconds before retrying. Retries left: $retries" - sleep 10s - done + return 1 + } +} + +__error() { + echo -e "${RED}$@${RESET}" 1>&2 +} - unzip -q -d $tempFolder $localZipFile +__machine_has() { + hash "$1" > /dev/null 2>&1 + return $? +} - mkdir $buildFolder - cp -r $tempFolder/**/build/** $buildFolder +__get_remote_file() { + local remote_path=$1 + local local_path=$2 + + if [[ "$remote_path" != 'http'* ]]; then + cp $remote_path $local_path + return 0 + fi - chmod +x $buildFile + failed=false + if __machine_has curl ; then + curl --retry 10 -sSL -f --create-dirs -o $local_path $remote_path || failed=true + elif __machine_has wget; then + wget --tries 10 -O $local_path $remote_path || failed=true + else + failed=true + fi - # Cleanup - if test -d $tempFolder; then - rm -rf $tempFolder + if [ "$failed" = true ]; then + __error "Download failed: $remote_path" 1>&2 + return 1 fi +} + +__read_dom () { local IFS=\> ; read -d \< ENTITY CONTENT ;} + +# +# main +# + +while [[ $# > 0 ]]; do + case $1 in + -\?|-h|--help) + __usage --no-exit + exit 0 + ;; + -c|--channel|-Channel) + shift + channel=${1:-} + [ -z "$channel" ] && __usage + ;; + --config-file|-ConfigFile) + shift + config_file="${1:-}" + [ -z "$config_file" ] && __usage + ;; + -d|--dotnet-home|-DotNetHome) + shift + DOTNET_HOME=${1:-} + [ -z "$DOTNET_HOME" ] && __usage + ;; + --path|-Path) + shift + repo_path="${1:-}" + [ -z "$repo_path" ] && __usage + ;; + -s|--tools-source|-ToolsSource) + shift + tools_source="${1:-}" + [ -z "$tools_source" ] && __usage + ;; + -u|--update|-Update) + update=true + ;; + --verbose|-Verbose) + verbose=true + ;; + --) + shift + break + ;; + *) + break + ;; + esac + shift +done + +if ! __machine_has unzip; then + __error 'Missing required command: unzip' + exit 1 fi -$buildFile -r $repoFolder "$@" +if ! __machine_has curl && ! __machine_has wget; then + __error 'Missing required command. Either wget or curl is required.' + exit 1 +fi + +if [ -f $config_file ]; then + comment=false + while __read_dom; do + if [ "$comment" = true ]; then [[ $CONTENT == *'-->'* ]] && comment=false ; continue; fi + if [[ $ENTITY == '!--'* ]]; then comment=true; continue; fi + if [ -z "$channel" ] && [[ $ENTITY == "KoreBuildChannel" ]]; then channel=$CONTENT; fi + if [ -z "$tools_source" ] && [[ $ENTITY == "KoreBuildToolsSource" ]]; then tools_source=$CONTENT; fi + done < $config_file +fi + +[ -z "$channel" ] && channel='dev' +[ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools' + +get_korebuild +install_tools "$tools_source" "$DOTNET_HOME" +invoke_repository_build "$repo_path" $@ diff --git a/build/RepositoryBuild.targets b/build/RepositoryBuild.targets index 5099e9801d60076a52febeb1bfd5f8098a2a98b6..aabf59367f4e1852a89be176320e1dd627a14452 100644 --- a/build/RepositoryBuild.targets +++ b/build/RepositoryBuild.targets @@ -42,20 +42,14 @@ <Message Text="============ Building $(RepositoryToBuild) ============" Importance="High" /> - <!-- Copy Korebuild folder to individual repos to avoid downloading it again and again and also to - prevent from taking in newer Korebuild changes when Universe is building midway --> - <ItemGroup> - <_KorebuildItems Include="$(RepositoryRoot).build\**\*.*" /> - </ItemGroup> - - <Message Text="Copying Korebuild from Universe to repository $(BuildRepositoryRoot)"/> - <Copy SourceFiles="@(_KorebuildItems)" DestinationFolder="$(BuildRepositoryRoot).build\%(RecursiveDir)" SkipUnchangedFiles="true" UseHardlinksIfPossible="true" /> + <!-- Copy Korebuild lock file to individual repos to align version if the repo doesn't already have one --> + <Message Text="Copying KoreBuild lockfile from Universe to repository $(BuildRepositoryRoot)"/> + <Copy SourceFiles="$(RepositoryRoot)korebuild-lock.txt" DestinationFolder="$(BuildRepositoryRoot)" /> <Exec - Command="./$(_BuildScriptToExecute) $(BuildArguments)" - EnvironmentVariables="KOREBUILD_SKIP_RUNTIME_INSTALL=1" + Command="./$(_BuildScriptToExecute) -Path $(BuildRepositoryRoot) $(BuildArguments)" IgnoreStandardErrorWarningFormat="true" - WorkingDirectory="$(BuildRepositoryRoot)" /> + WorkingDirectory="$(RepositoryRoot)" /> <ItemGroup> <RepositoryArtifacts Include="$(RepositoryArtifactsBuildDirectory)*" /> @@ -75,8 +69,10 @@ <Message Text="Publishing the following packages to the volatile feed: @(RepositoryNupkgs -> '%(Filename)%(Extension)', ', ')" Condition="'$(PublishPackages)'=='true' AND '@(RepositoryNupkgs)' != ''" /> - <Exec - Command="$(DotNetPath) $(PackagePublisherNetCoreApp) -d $(RepositoryArtifactsBuildDirectory) -f $(NuGetPublishVolatileFeed)" + <PushNuGetPackages + Packages="@(RepositoryNupkgs)" + Feed="$(NuGetPublishVolatileFeed)" + ApiKey="$(APIKey)" Condition="'$(PublishPackages)'=='true' AND '@(RepositoryNupkgs)' != ''" /> <Message Text="============ Done building $(RepositoryToBuild) ============" Importance="High" /> diff --git a/build/repo.targets b/build/repo.targets index df2b9e68b72cfeee1d529a26978af95acde1541f..6e3e9ebd9b1ce57f82fcc7c3ab8eef0b39e44802 100644 --- a/build/repo.targets +++ b/build/repo.targets @@ -23,10 +23,6 @@ <BuildDependsOn>$(BuildDependsOn);CloneRepositories;BuildRepositories</BuildDependsOn> </PropertyGroup> - <ItemGroup> - <PackageReference Include="PackagePublisher" Version="2.0.0-*" /> - </ItemGroup> - <Import Project="$(_RepositoryListToImport)" /> <Target Name="CleanUniverseArtifacts"> @@ -111,7 +107,7 @@ </Target> <Target Name="BuildRepositories" - DependsOnTargets="_PrepareRepositories;_FindDotNetPath;_CreateRepositoriesListWithCommits;_UpdateNuGetConfig;_GenerateBuildGraph;_BuildRepositories" /> + DependsOnTargets="_PrepareRepositories;_CreateRepositoriesListWithCommits;_UpdateNuGetConfig;_GenerateBuildGraph;_BuildRepositories" /> <Target Name="_PrepareRestoreGraphSpecs" DependsOnTargets="_PrepareRepositories"> <ItemGroup> diff --git a/version.xml b/version.xml new file mode 100644 index 0000000000000000000000000000000000000000..3c05022b7d7d810d34304bafeec2a7b6ec1ef4bc --- /dev/null +++ b/version.xml @@ -0,0 +1,8 @@ +<!-- This file may be overwritten by automation. --> +<Project> + <PropertyGroup> + <KoreBuildChannel>dev</KoreBuildChannel> + <VersionPrefix>2.1.0</VersionPrefix> + <VersionSuffix>preview1</VersionSuffix> + </PropertyGroup> +</Project>