Skip to content

Commit 95698ce

Browse files
committed
Consider all ghcup installed/compile HLSes
And correctly merge all those maps.
1 parent 6ebfdef commit 95698ce

File tree

1 file changed

+83
-52
lines changed

1 file changed

+83
-52
lines changed

src/hlsBinaries.ts

Lines changed: 83 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as child_process from 'child_process';
22
import { ExecException } from 'child_process';
33
import * as fs from 'fs';
4+
import { stat } from 'fs/promises';
45
import * as https from 'https';
56
import * as path from 'path';
67
import { match } from 'ts-pattern';
@@ -242,7 +243,7 @@ export async function findHaskellLanguageServer(
242243

243244
// get a preliminary hls wrapper for finding project GHC version,
244245
// later we may install a different HLS that supports the given GHC
245-
let wrapper = await getLatestHLSfromGHCup(context, storagePath, logger).then(e =>
246+
let wrapper = await getLatestWrapperFromGHCup(context, logger).then(e =>
246247
(e === null)
247248
? callGHCup(context, logger,
248249
['install', 'hls'],
@@ -255,7 +256,7 @@ export async function findHaskellLanguageServer(
255256
false,
256257
(err, stdout, _stderr, resolve, _reject) => { err ? resolve('') : resolve(stdout?.trim()); })
257258
)
258-
: e[1]
259+
: e
259260
);
260261

261262
// now figure out the project GHC version and the latest supported HLS version
@@ -327,31 +328,34 @@ async function getLatestHLS(
327328
wrapper === undefined
328329
? await callAsync(`ghc${exeExt}`, ['--numeric-version'], storagePath, logger, undefined, false)
329330
: await getProjectGHCVersion(wrapper, workingDir, logger);
330-
331-
// get installable HLS that supports the project GHC version (this might not be the most recent)
332-
const latestMetadataHls = await getLatestHLSfromMetadata(context, storagePath, projectGhc, logger);
333-
const latestGhcupHls = await getLatestHLSfromGHCup(context, storagePath, logger, projectGhc).then(e => e === null ? null : e[0]);
334-
335-
if (latestMetadataHls !== null && latestGhcupHls !== null) {
336-
// both returned a result, compare versions
337-
if (comparePVP(latestMetadataHls, latestGhcupHls) >= 0) {
338-
logger.info("Picking HLS according to metadata");
339-
return latestMetadataHls;
340-
} else {
341-
logger.info("Picking a probably self compiled HLS via ghcup");
342-
return latestGhcupHls;
343-
}
344-
345-
} else if (latestMetadataHls === null && latestGhcupHls !== null) {
346-
logger.info("Picking a probably self compiled HLS via ghcup");
347-
return latestGhcupHls;
348-
} else if (latestMetadataHls !== null && latestGhcupHls === null) {
349-
logger.info("Picking HLS according to metadata");
350-
return latestMetadataHls;
351-
} else {
352-
const noMatchingHLS = `No HLS version was found for supporting GHC ${projectGhc}.`;
331+
const noMatchingHLS = `No HLS version was found for supporting GHC ${projectGhc}.`;
332+
333+
// first we get supported GHC versions from available HLS bindists (whether installed or not)
334+
const metadataMap = await getHLSesfromMetadata(context, storagePath, logger);
335+
// then we get supported GHC versions from currently installed HLS versions
336+
const ghcupMap = await getHLSesFromGHCup(context, storagePath, logger);
337+
// since installed HLS versions may support a different set of GHC versions than the bindists
338+
// (e.g. because the user ran 'ghcup compile hls'), we need to merge both maps, preferring
339+
// values from already installed HLSes
340+
const merged = (metadataMap === null)
341+
? ghcupMap
342+
: ((ghcupMap === null)
343+
? null
344+
: (new Map<string, string[]>([...metadataMap, ...ghcupMap]))); // right-biased
345+
346+
if (!merged) {
353347
window.showErrorMessage(noMatchingHLS);
354348
throw new Error(noMatchingHLS);
349+
} else {
350+
// now sort and get the latest suitable version
351+
const latest = [...merged].filter(([k, v]) => v.some(x => x === projectGhc)).sort(([k1, v1], [k2, v2]) => comparePVP(k1, k2)).pop();
352+
if (latest) {
353+
return latest[0];
354+
355+
} else {
356+
window.showErrorMessage(noMatchingHLS);
357+
throw new Error(noMatchingHLS);
358+
}
355359
}
356360
}
357361

@@ -529,44 +533,79 @@ export function addPathToProcessPath(extraPath: string): string {
529533
return PATH.join(pathSep);
530534
}
531535

536+
async function getLatestWrapperFromGHCup(
537+
context: ExtensionContext,
538+
logger: Logger
539+
): Promise<string | null> {
540+
const hlsVersions = await callGHCup(
541+
context,
542+
logger,
543+
['list', '-t', 'hls', '-c', 'installed', '-r'],
544+
undefined,
545+
false,
546+
);
547+
const installed = hlsVersions.split(/\r?\n/).pop();
548+
if (installed) {
549+
const latestHlsVersion = installed.split(' ')[1]
550+
551+
let bin = await callGHCup(context, logger,
552+
['whereis', 'hls', `${latestHlsVersion}`],
553+
undefined,
554+
false
555+
);
556+
return bin;
557+
} else {
558+
return null;
559+
}
560+
}
561+
532562
// complements getLatestHLSfromMetadata, by checking possibly locally compiled
533563
// HLS in ghcup
534564
// If 'targetGhc' is omitted, picks the latest 'haskell-language-server-wrapper',
535565
// otherwise ensures the specified GHC is supported.
536-
async function getLatestHLSfromGHCup(
566+
async function getHLSesFromGHCup(
537567
context: ExtensionContext,
538568
storagePath: string,
539569
logger: Logger,
540-
targetGhc?: string
541-
): Promise<[string, string] | null> {
570+
): Promise<Map<string, string[]> | null> {
542571
const hlsVersions = await callGHCup(
543572
context,
544573
logger,
545574
['list', '-t', 'hls', '-c', 'installed', '-r'],
546575
undefined,
547576
false,
548577
);
549-
const latestHlsVersion = hlsVersions.split(/\r?\n/).pop()!.split(' ')[1];
550-
let bindir = await callGHCup(context, logger,
578+
579+
const bindir = await callGHCup(context, logger,
551580
['whereis', 'bindir'],
552581
undefined,
553582
false
554583
);
555584

556-
let hlsBin = '';
557-
if (targetGhc) {
558-
hlsBin = path.join(bindir, `haskell-language-server-${targetGhc}~${latestHlsVersion}${exeExt}`);
559-
} else {
560-
hlsBin = path.join(bindir, `haskell-language-server-wrapper-${latestHlsVersion}${exeExt}`);
561-
}
585+
const files = fs.readdirSync(bindir).filter(async e => {
586+
return await stat(path.join(bindir, e)).then(s => s.isDirectory()).catch(() => false);
587+
});
588+
589+
590+
const installed = hlsVersions.split(/\r?\n/).map(e => e.split(' ')[1]);
591+
if (installed.length > 0) {
592+
const myMap = new Map<string, string[]>();
593+
installed.forEach(hls => {
594+
const ghcs = files.filter(f => f.endsWith(`~${hls}${exeExt}`) && f.startsWith('haskell-language-server-'))
595+
.map(f => {
596+
const rmPrefix = f.substring('haskell-language-server-'.length);
597+
return rmPrefix.substring(0, rmPrefix.length - `~${hls}${exeExt}`.length);
598+
})
599+
myMap.set(hls, ghcs);
600+
});
562601

563-
if (fs.existsSync(hlsBin)) {
564-
return [latestHlsVersion, hlsBin];
602+
return myMap;
565603
} else {
566-
return null;
604+
return null;
567605
}
568606
}
569607

608+
570609
/**
571610
* Given a GHC version, download at least one HLS version that can be used.
572611
* This also honours the OS architecture we are on.
@@ -577,12 +616,11 @@ async function getLatestHLSfromGHCup(
577616
* @param logger Logger for feedback
578617
* @returns
579618
*/
580-
async function getLatestHLSfromMetadata(
619+
async function getHLSesfromMetadata(
581620
context: ExtensionContext,
582621
storagePath: string,
583-
targetGhc: string,
584622
logger: Logger
585-
): Promise<string | null> {
623+
): Promise<Map<string, string[]> | null> {
586624
const metadata = await getReleaseMetadata(context, storagePath, logger);
587625
if (metadata === null) {
588626
window.showErrorMessage('Could not get release metadata');
@@ -609,23 +647,16 @@ async function getLatestHLSfromMetadata(
609647
return null;
610648
}
611649

612-
let curHls: string | null = null;
613-
614650
const map: ReleaseMetadata = new Map(Object.entries(metadata));
651+
const newMap = new Map<string, string[]>();
615652
map.forEach((value, key) => {
616653
const value_ = new Map(Object.entries(value));
617654
const archValues = new Map(Object.entries(value_.get(arch)));
618655
const versions: string[] = archValues.get(plat) as string[];
619-
if (versions !== undefined && versions.some((el) => el === targetGhc)) {
620-
if (curHls === null) {
621-
curHls = key;
622-
} else if (comparePVP(key, curHls) > 0) {
623-
curHls = key;
624-
}
625-
}
656+
newMap.set(key, versions);
626657
});
627658

628-
return curHls;
659+
return newMap;
629660
}
630661

631662
/**

0 commit comments

Comments
 (0)