Skip to content

Commit f996155

Browse files
hasufellfendor
authored andcommitted
Allow to use system ghcup
1 parent bd87282 commit f996155

File tree

2 files changed

+59
-28
lines changed

2 files changed

+59
-28
lines changed

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@
173173
"default": false,
174174
"description": "Whether to ignore haskell-language-server on PATH"
175175
},
176+
"haskell.useSystemGHCup": {
177+
"scope": "resource",
178+
"type": "boolean",
179+
"default": false,
180+
"description": "Whether to use the system ghcup or an internal one for installing HLS."
181+
},
176182
"haskell.checkProject": {
177183
"scope": "resource",
178184
"type": "boolean",
@@ -432,6 +438,7 @@
432438
"js-yaml": "^3.13.1",
433439
"lodash-es": "^4.17.15",
434440
"lru-cache": "^4.1.5",
441+
"request": "^2.88.2",
435442
"request-promise-native": "^1.0.8",
436443
"ts-pattern": "^3.3.5",
437444
"vscode-languageclient": "^7.0.0",

src/hlsBinaries.ts

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -168,19 +168,26 @@ export async function downloadHaskellLanguageServer(
168168
const storagePath: string = await getStoragePath(context);
169169
logger.info(`Using ${storagePath} to store downloaded binaries`);
170170

171+
const ghcupBinDir = await callGHCup(context, logger, ['whereis', 'bindir'], undefined, false);
172+
171173
if (!fs.existsSync(storagePath)) {
172174
fs.mkdirSync(storagePath);
173175
}
174176

175177
const localWrapper = ['haskell-language-server-wrapper'].find(executableExists);
176-
const downloadedWrapper = path.join(storagePath, process.platform === 'win32' ? 'ghcup' : '.ghcup', 'bin', `haskell-language-server-wrapper${exeExt}`);
177178
let wrapper: string | undefined;
178179
if (localWrapper) {
179180
// first try PATH
180181
wrapper = localWrapper;
181-
} else if (executableExists(downloadedWrapper)) {
182-
// then try internal ghcup
183-
wrapper = downloadedWrapper;
182+
} else {
183+
// then try ghcup
184+
const ghcupHlsWrapper = path.join(
185+
ghcupBinDir,
186+
`haskell-language-server-wrapper${exeExt}`
187+
);
188+
if (executableExists(ghcupHlsWrapper)) {
189+
wrapper = ghcupHlsWrapper;
190+
}
184191
}
185192

186193
const updateBehaviour = workspace.getConfiguration('haskell').get('updateBehavior') as UpdateBehaviour;
@@ -207,30 +214,36 @@ export async function downloadHaskellLanguageServer(
207214
throw new Error('No version of HLS installed or found and installation was denied, giving up...');
208215
}
209216
}
210-
await callGHCup(
211-
context,
212-
logger,
213-
['install', 'hls', installableHls],
217+
// we use this command to both install a HLS, but also create a nice
218+
// isolated symlinked dir with only the given HLS in place, so
219+
// this works for installing and setting
220+
const symHLSPath = path.join(storagePath, 'hls', installableHls);
221+
await callGHCup(context, logger,
222+
['run', '--hls', installableHls, '-b', symHLSPath, '-i'],
214223
`Installing HLS ${installableHls}`,
215-
true,
224+
true
216225
);
217-
await callGHCup(context, logger, ['set', 'hls', installableHls], undefined, false);
218-
return downloadedWrapper;
226+
return path.join(symHLSPath, `haskell-language-server-wrapper${exeExt}`);
219227
} else {
220228
// version of active hls wrapper
221229
const setVersion = await callAsync(wrapper, ['--numeric-version'], storagePath, logger);
222230

231+
// is the currently set hls wrapper matching the required version?
232+
const activeOk = comparePVP(setVersion, installableHls);
233+
234+
// do we need a downgrade?
223235
const downgrade: boolean = comparePVP(latestHlsVersion, installableHls) > 0;
224236

225-
const projectHlsWrapper = path.join(
226-
storagePath,
227-
process.platform === 'win32' ? 'ghcup' : '.ghcup',
228-
'bin',
229-
`haskell-language-server-wrapper-${installableHls}${exeExt}`
230-
);
231-
const needInstall = !executableExists(projectHlsWrapper);
237+
if (activeOk !== 0) {
238+
// Maybe there is a versioned wrapper from ghcup,
239+
// indicating we don't need to install, just set this one as active.
240+
// We check for this so that we can potentially have less annoying popups.
241+
const projectHlsWrapper = path.join(
242+
path.dirname(wrapper),
243+
`haskell-language-server-wrapper-${installableHls}${exeExt}`
244+
);
245+
const needInstall = !executableExists(projectHlsWrapper);
232246

233-
if (comparePVP(setVersion, installableHls) !== 0) {
234247
// only update if the user wants to
235248
if (updateBehaviour === 'never-check') {
236249
logger.warn(
@@ -287,10 +300,15 @@ async function callGHCup(
287300
cancellable?: boolean
288301
): Promise<string> {
289302
const storagePath: string = await getStoragePath(context);
290-
const ghcup = path.join(storagePath, `ghcup${exeExt}`);
291-
return await callAsync(ghcup, ['--no-verbose'].concat(args), storagePath, logger, title, cancellable, {
292-
GHCUP_INSTALL_BASE_PREFIX: storagePath,
293-
});
303+
const systemGHCup = workspace.getConfiguration('haskell').get('useSystemGHCup') as boolean;
304+
if (systemGHCup) {
305+
return await callAsync('ghcup', ['--no-verbose'].concat(args), storagePath, logger, title, cancellable);
306+
} else {
307+
const ghcup = path.join(storagePath, `ghcup${exeExt}`);
308+
return await callAsync(ghcup, ['--no-verbose'].concat(args), storagePath, logger, title, cancellable, {
309+
GHCUP_INSTALL_BASE_PREFIX: storagePath,
310+
});
311+
}
294312
}
295313

296314
async function getLatestSuitableHLS(
@@ -304,7 +322,7 @@ async function getLatestSuitableHLS(
304322
// get latest hls version
305323
const hlsVersions = await callGHCup(
306324
context,
307-
logger,
325+
logger,
308326
['list', '-t', 'hls', '-c', 'available', '-r'],
309327
undefined,
310328
false,
@@ -387,9 +405,15 @@ export async function getProjectGHCVersion(
387405

388406
/**
389407
* Downloads the latest ghcup binary.
390-
* Returns null if it can't find any for the given architecture/platform.
408+
* Returns undefined if it can't find any for the given architecture/platform.
391409
*/
392-
export async function downloadGHCup(context: ExtensionContext, logger: Logger): Promise<string | null> {
410+
export async function downloadGHCup(context: ExtensionContext, logger: Logger): Promise<string | undefined> {
411+
const systemGHCup = workspace.getConfiguration('haskell').get('useSystemGHCup') as boolean;
412+
if (systemGHCup) {
413+
const localGHCup = ['ghcup'].find(executableExists);
414+
return localGHCup
415+
}
416+
393417
logger.info('Checking for ghcup installation');
394418

395419
const storagePath: string = await getStoragePath(context);
@@ -415,7 +439,7 @@ export async function downloadGHCup(context: ExtensionContext, logger: Logger):
415439
.otherwise((_) => null);
416440
if (plat === null) {
417441
window.showErrorMessage(`Couldn't find any pre-built ghcup binary for ${process.platform}`);
418-
return null;
442+
return undefined;
419443
}
420444
const arch = match(process.arch)
421445
.with('arm', (_) => 'armv7')
@@ -425,7 +449,7 @@ export async function downloadGHCup(context: ExtensionContext, logger: Logger):
425449
.otherwise((_) => null);
426450
if (arch === null) {
427451
window.showErrorMessage(`Couldn't find any pre-built ghcup binary for ${process.arch}`);
428-
return null;
452+
return undefined;
429453
}
430454
const dlUri = `https://downloads.haskell.org/~ghcup/${arch}-${plat}-ghcup${exeExt}`;
431455
const title = `Downloading ${dlUri}`;

0 commit comments

Comments
 (0)