1<#
2.Synopsis
3    Uploads from a VSTS release build layout to python.org
4.Description
5    Given the downloaded/extracted build artifact from a release
6    build run on python.visualstudio.com, this script uploads
7    the files to the correct locations.
8.Parameter build
9    The location on disk of the extracted build artifact.
10.Parameter user
11    The username to use when logging into the host.
12.Parameter server
13    The host or PuTTY session name.
14.Parameter target
15    The subdirectory on the host to copy files to.
16.Parameter tests
17    The path to run download tests in.
18.Parameter doc_htmlhelp
19    Optional path besides -build to locate CHM files.
20.Parameter embed
21    Optional path besides -build to locate ZIP files.
22.Parameter skipupload
23    Skip uploading
24.Parameter skippurge
25    Skip purging the CDN
26.Parameter skiptest
27    Skip the download tests
28.Parameter skiphash
29    Skip displaying hashes
30#>
31param(
32    [Parameter(Mandatory=$true)][string]$build,
33    [Parameter(Mandatory=$true)][string]$user,
34    [string]$server="python-downloads",
35    [string]$target="/srv/www.python.org/ftp/python",
36    [string]$tests=${env:TEMP},
37    [string]$doc_htmlhelp=$null,
38    [string]$embed=$null,
39    [switch]$skipupload,
40    [switch]$skippurge,
41    [switch]$skiptest,
42    [switch]$skiphash
43)
44
45if (-not $build) { throw "-build option is required" }
46if (-not $user) { throw "-user option is required" }
47
48$tools = $script:MyInvocation.MyCommand.Path | Split-Path -parent;
49
50if (-not ((Test-Path "$build\win32\python-*.exe") -or (Test-Path "$build\amd64\python-*.exe"))) {
51    throw "-build argument does not look like a 'build' directory"
52}
53
54function find-putty-tool {
55    param ([string]$n)
56    $t = gcm $n -EA 0
57    if (-not $t) { $t = gcm ".\$n" -EA 0 }
58    if (-not $t) { $t = gcm "${env:ProgramFiles}\PuTTY\$n" -EA 0 }
59    if (-not $t) { $t = gcm "${env:ProgramFiles(x86)}\PuTTY\$n" -EA 0 }
60    if (-not $t) { throw "Unable to locate $n.exe. Please put it on $PATH" }
61    return gi $t.Path
62}
63
64$p = gci -r "$build\python-*.exe" | `
65    ?{ $_.Name -match '^python-(\d+\.\d+\.\d+)((a|b|rc)\d+)?-.+' } | `
66    select -first 1 | `
67    %{ $Matches[1], $Matches[2] }
68
69"Uploading version $($p[0]) $($p[1])"
70"  from: $build"
71"    to: $($server):$target/$($p[0])"
72""
73
74if (-not $skipupload) {
75    # Upload files to the server
76    $pscp = find-putty-tool "pscp"
77    $plink = find-putty-tool "plink"
78
79    "Upload using $pscp and $plink"
80    ""
81
82    if ($doc_htmlhelp) {
83        $chm = gci -EA 0 $doc_htmlhelp\python*.chm, $doc_htmlhelp\python*.chm.asc
84    } else {
85        $chm = gci -EA 0 $build\python*.chm, $build\python*.chm.asc
86    }
87
88    $d = "$target/$($p[0])/"
89    & $plink -batch $user@$server mkdir $d
90    & $plink -batch $user@$server chgrp downloads $d
91    & $plink -batch $user@$server chmod o+rx $d
92    if ($chm) {
93        & $pscp -batch $chm.FullName "$user@${server}:$d"
94        if (-not $?) { throw "Failed to upload $chm" }
95    }
96
97    $dirs = gci "$build" -Directory
98    if ($embed) {
99        $dirs = ($dirs, (gi $embed)) | %{ $_ }
100    }
101
102    foreach ($a in $dirs) {
103        "Uploading files from $($a.FullName)"
104        pushd "$($a.FullName)"
105        $exe = gci *.exe, *.exe.asc, *.zip, *.zip.asc
106        $msi = gci *.msi, *.msi.asc, *.msu, *.msu.asc
107        popd
108
109        if ($exe) {
110            & $pscp -batch $exe.FullName "$user@${server}:$d"
111            if (-not $?) { throw "Failed to upload $exe" }
112        }
113
114        if ($msi) {
115            $sd = "$d$($a.Name)$($p[1])/"
116            & $plink -batch $user@$server mkdir $sd
117            & $plink -batch $user@$server chgrp downloads $sd
118            & $plink -batch $user@$server chmod o+rx $sd
119            & $pscp -batch $msi.FullName "$user@${server}:$sd"
120            if (-not $?) { throw "Failed to upload $msi" }
121            & $plink -batch $user@$server chgrp downloads $sd*
122            & $plink -batch $user@$server chmod g-x,o+r $sd*
123        }
124    }
125
126    & $plink -batch $user@$server chgrp downloads $d*
127    & $plink -batch $user@$server chmod g-x,o+r $d*
128    & $pscp -ls "$user@${server}:$d"
129}
130
131if (-not $skippurge) {
132    # Run a CDN purge
133    py $tools\purge.py "$($p[0])$($p[1])"
134}
135
136if (-not $skiptest) {
137    # Use each web installer to produce a layout. This will download
138    # each referenced file and validate their signatures/hashes.
139    gci "$build\*-webinstall.exe" -r -File | %{
140        $d = mkdir "$tests\$($_.BaseName)" -Force
141        gci $d -r -File | del
142        $ic = copy $_ $d -PassThru
143        "Checking layout for $($ic.Name)"
144        Start-Process -wait $ic "/passive", "/layout", "$d\layout", "/log", "$d\log\install.log"
145        if (-not $?) {
146            Write-Error "Failed to validate layout of $($inst.Name)"
147        }
148    }
149}
150
151if (-not $skiphash) {
152    # Display MD5 hash and size of each downloadable file
153    pushd $build
154    $files = gci python*.chm, *\*.exe, *\*.zip
155    if ($doc_htmlhelp) {
156        cd $doc_htmlhelp
157        $files = ($files, (gci python*.chm)) | %{ $_ }
158    }
159    if ($embed) {
160        cd $embed
161        $files = ($files, (gci *.zip)) | %{ $_ }
162    }
163    popd
164
165    $hashes = $files | `
166        Sort-Object Name | `
167        Format-Table Name, @{Label="MD5"; Expression={(Get-FileHash $_ -Algorithm MD5).Hash}}, Length -AutoSize | `
168        Out-String -Width 4096
169    $hashes | clip
170    $hashes
171}
172