Skip to content

Commit f53033a

Browse files
committed
Decouple downloading and extracting the full SDK
Even offloading the HTTPS transport to `curl.exe` seems not to have been enough to fend off those pesky `ECONNRESET` errors. Let's offload even the unzipping to `unzip.exe`. Sadly, this means that we do not have a streaming unzipper anymore. It appears that we cannot even pipe the output from one spawned process via Node to another one: in the 64-bit case, it makes the difference between ~9m15s and ~8m, so it makes quite a difference. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 9cdab4e commit f53033a

File tree

2 files changed

+68
-37
lines changed

2 files changed

+68
-37
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"cSpell.ignoreRegExpList": [
3+
"CHERE_INVOKING",
34
"makepkg-git",
45
"mingw-w64-git",
56
"vstfs://.*"

src/downloader.ts

Lines changed: 67 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import unzipper from 'unzipper'
55
import {spawn} from 'child_process'
66
import {delimiter} from 'path'
77

8+
const gitForWindowsUsrBinPath = 'C:/Program Files/Git/usr/bin'
9+
const gitForWindowsMINGW64BinPath = 'C:/Program Files/Git/mingw64/bin'
10+
811
async function fetchJSONFromURL<T>(url: string): Promise<T> {
912
return new Promise<T>((resolve, reject) => {
1013
https
@@ -54,12 +57,11 @@ async function unzip(
5457
stripPrefix: string,
5558
outputDirectory: string,
5659
verbose: boolean | number,
57-
streamEntries?: (
58-
path: string,
59-
stream: Readable,
60+
downloader?: (
61+
_url: string,
6062
directory: string,
6163
_verbose: boolean | number
62-
) => void
64+
) => Promise<void>
6365
): Promise<void> {
6466
let progress =
6567
verbose === false
@@ -76,15 +78,19 @@ async function unzip(
7678
}
7779
}
7880
mkdirp(outputDirectory)
81+
82+
if (downloader) {
83+
// `https.get()` seems to have performance problems that cause frequent
84+
// ECONNRESET problems with larger payloads. Let's (ab-)use Git for Windows'
85+
// `curl.exe` to do the downloading for us in that case.
86+
return await downloader(url, outputDirectory, verbose)
87+
}
88+
7989
return new Promise<void>((resolve, reject) => {
80-
const handleStream = (res: Readable): void => {
90+
https.get(url, (res: Readable): void => {
8191
res
8292
.pipe(unzipper.Parse())
8393
.on('entry', entry => {
84-
if (streamEntries) {
85-
streamEntries(entry.path, entry, outputDirectory, verbose)
86-
return
87-
}
8894
if (!entry.path.startsWith(stripPrefix)) {
8995
process.stderr.write(
9096
`warning: skipping ${entry.path} because it does not start with ${stripPrefix}\n`
@@ -104,48 +110,72 @@ async function unzip(
104110
.on('error', reject)
105111
.on('finish', progress)
106112
.on('finish', resolve)
107-
}
108-
109-
if (!streamEntries) {
110-
https.get(url, handleStream)
111-
} else {
112-
// `https.get()` seems to have performance problems that cause frequent
113-
// ECONNRESET problems with larger payloads. Let's (ab-)use Git for Windows'
114-
// `curl.exe` to do the downloading for us in that case.
115-
const curl = spawn('C:/Program Files/Git/mingw64/bin/curl.exe', [url])
116-
handleStream(curl.stdout)
117-
// eslint-disable-next-line no-console
118-
curl.stderr.on('data', chunk => console.log(`${chunk}`))
119-
}
113+
})
120114
})
121115
}
122116

123117
/* We're (ab-)using Git for Windows' `tar.exe` and `xz.exe` to do the job */
124-
function unpackTarXZEntry(
125-
path: string,
126-
stream: Readable,
118+
async function unpackTarXZInZipFromURL(
119+
url: string,
127120
outputDirectory: string,
128121
verbose: boolean | number = false
129-
): void {
130-
if (path.endsWith('/')) return
131-
if (!path.endsWith('.tar.xz')) {
132-
process.stderr.write(`warning: unhandled entry: ${path}`)
133-
return
122+
): Promise<void> {
123+
const tmp = await fs.promises.mkdtemp(`${outputDirectory}/tmp`)
124+
const zipPath = `${tmp}/artifacts.zip`
125+
const curl = spawn(
126+
`${gitForWindowsMINGW64BinPath}/curl.exe`,
127+
['-o', zipPath, url],
128+
{stdio: [undefined, 'inherit', 'inherit']}
129+
)
130+
await new Promise<void>((resolve, reject) => {
131+
curl
132+
.on('close', code =>
133+
code === 0 ? resolve() : reject(new Error(`${code}`))
134+
)
135+
.on('error', e => reject(new Error(`${e}`)))
136+
})
137+
138+
const zipContents = (await unzipper.Open.file(zipPath)).files.filter(
139+
e => !e.path.endsWith('/')
140+
)
141+
if (zipContents.length !== 1) {
142+
throw new Error(
143+
`${zipPath} does not contain exactly one file (${zipContents.map(
144+
e => e.path
145+
)})`
146+
)
134147
}
135148

136-
const usrBinPath = 'C:/Program Files/Git/usr/bin'
149+
// eslint-disable-next-line no-console
150+
console.log(`unzipping ${zipPath}\n`)
137151
const tarXZ = spawn(
138-
`${usrBinPath}/tar.exe`,
139-
[verbose === true ? 'xJvf' : 'xJf', '-'],
152+
`${gitForWindowsUsrBinPath}/bash.exe`,
153+
[
154+
'-lc',
155+
`unzip -p "${zipPath}" ${zipContents[0].path} | tar ${
156+
verbose === true ? 'xJvf' : 'xJf'
157+
} -`
158+
],
140159
{
141160
cwd: outputDirectory,
142161
env: {
143-
PATH: `${usrBinPath}${delimiter}${process.env.PATH}`
162+
CHERE_INVOKING: '1',
163+
MSYSTEM: 'MINGW64',
164+
PATH: `${gitForWindowsUsrBinPath}${delimiter}${process.env.PATH}`
144165
},
145-
stdio: ['pipe', 'inherit', 'inherit']
166+
stdio: [undefined, 'inherit', 'inherit']
146167
}
147168
)
148-
stream.pipe(tarXZ.stdin)
169+
await new Promise<void>((resolve, reject) => {
170+
tarXZ.on('close', code => {
171+
if (code === 0) {
172+
resolve()
173+
} else {
174+
reject(new Error(`tar: exited with code ${code}`))
175+
}
176+
})
177+
})
178+
await fs.promises.rmdir(tmp, {recursive: true})
149179
}
150180

151181
export async function get(
@@ -221,7 +251,7 @@ export async function get(
221251
`${artifactName}/`,
222252
outputDirectory,
223253
verbose,
224-
flavor === 'full' ? unpackTarXZEntry : undefined
254+
flavor === 'full' ? unpackTarXZInZipFromURL : undefined
225255
)
226256
}
227257
return {download, id}

0 commit comments

Comments
 (0)