Skip to content

Commit 4493e48

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

File tree

1 file changed

+73
-52
lines changed

1 file changed

+73
-52
lines changed

src/hlsBinaries.ts

Lines changed: 73 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,24 @@ 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) || new Map<string, string[]>();
335+
// then we get supported GHC versions from currently installed HLS versions
336+
const ghcupMap = await getHLSesFromGHCup(context, storagePath, logger) || new Map<string, string[]>();
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 = new Map<string, string[]>([...metadataMap, ...ghcupMap]); // right-biased
341+
// now sort and get the latest suitable version
342+
const latest = [...merged].filter(([k, v]) => v.some(x => x === projectGhc)).sort(([k1, v1], [k2, v2]) => comparePVP(k1, k2)).pop();
343+
344+
if (!latest) {
353345
window.showErrorMessage(noMatchingHLS);
354346
throw new Error(noMatchingHLS);
347+
} else {
348+
return latest[0];
355349
}
356350
}
357351

@@ -529,44 +523,79 @@ export function addPathToProcessPath(extraPath: string): string {
529523
return PATH.join(pathSep);
530524
}
531525

526+
async function getLatestWrapperFromGHCup(
527+
context: ExtensionContext,
528+
logger: Logger
529+
): Promise<string | null> {
530+
const hlsVersions = await callGHCup(
531+
context,
532+
logger,
533+
['list', '-t', 'hls', '-c', 'installed', '-r'],
534+
undefined,
535+
false,
536+
);
537+
const installed = hlsVersions.split(/\r?\n/).pop();
538+
if (installed) {
539+
const latestHlsVersion = installed.split(' ')[1]
540+
541+
let bin = await callGHCup(context, logger,
542+
['whereis', 'hls', `${latestHlsVersion}`],
543+
undefined,
544+
false
545+
);
546+
return bin;
547+
} else {
548+
return null;
549+
}
550+
}
551+
532552
// complements getLatestHLSfromMetadata, by checking possibly locally compiled
533553
// HLS in ghcup
534554
// If 'targetGhc' is omitted, picks the latest 'haskell-language-server-wrapper',
535555
// otherwise ensures the specified GHC is supported.
536-
async function getLatestHLSfromGHCup(
556+
async function getHLSesFromGHCup(
537557
context: ExtensionContext,
538558
storagePath: string,
539559
logger: Logger,
540-
targetGhc?: string
541-
): Promise<[string, string] | null> {
560+
): Promise<Map<string, string[]> | null> {
542561
const hlsVersions = await callGHCup(
543562
context,
544563
logger,
545564
['list', '-t', 'hls', '-c', 'installed', '-r'],
546565
undefined,
547566
false,
548567
);
549-
const latestHlsVersion = hlsVersions.split(/\r?\n/).pop()!.split(' ')[1];
550-
let bindir = await callGHCup(context, logger,
568+
569+
const bindir = await callGHCup(context, logger,
551570
['whereis', 'bindir'],
552571
undefined,
553572
false
554573
);
555574

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-
}
575+
const files = fs.readdirSync(bindir).filter(async e => {
576+
return await stat(path.join(bindir, e)).then(s => s.isDirectory()).catch(() => false);
577+
});
578+
579+
580+
const installed = hlsVersions.split(/\r?\n/).map(e => e.split(' ')[1]);
581+
if (installed.length > 0) {
582+
const myMap = new Map<string, string[]>();
583+
installed.forEach(hls => {
584+
const ghcs = files.filter(f => f.endsWith(`~${hls}${exeExt}`) && f.startsWith('haskell-language-server-'))
585+
.map(f => {
586+
const rmPrefix = f.substring('haskell-language-server-'.length);
587+
return rmPrefix.substring(0, rmPrefix.length - `~${hls}${exeExt}`.length);
588+
})
589+
myMap.set(hls, ghcs);
590+
});
562591

563-
if (fs.existsSync(hlsBin)) {
564-
return [latestHlsVersion, hlsBin];
592+
return myMap;
565593
} else {
566-
return null;
594+
return null;
567595
}
568596
}
569597

598+
570599
/**
571600
* Given a GHC version, download at least one HLS version that can be used.
572601
* This also honours the OS architecture we are on.
@@ -577,12 +606,11 @@ async function getLatestHLSfromGHCup(
577606
* @param logger Logger for feedback
578607
* @returns
579608
*/
580-
async function getLatestHLSfromMetadata(
609+
async function getHLSesfromMetadata(
581610
context: ExtensionContext,
582611
storagePath: string,
583-
targetGhc: string,
584612
logger: Logger
585-
): Promise<string | null> {
613+
): Promise<Map<string, string[]> | null> {
586614
const metadata = await getReleaseMetadata(context, storagePath, logger);
587615
if (metadata === null) {
588616
window.showErrorMessage('Could not get release metadata');
@@ -609,23 +637,16 @@ async function getLatestHLSfromMetadata(
609637
return null;
610638
}
611639

612-
let curHls: string | null = null;
613-
614640
const map: ReleaseMetadata = new Map(Object.entries(metadata));
641+
const newMap = new Map<string, string[]>();
615642
map.forEach((value, key) => {
616643
const value_ = new Map(Object.entries(value));
617644
const archValues = new Map(Object.entries(value_.get(arch)));
618645
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-
}
646+
newMap.set(key, versions);
626647
});
627648

628-
return curHls;
649+
return newMap;
629650
}
630651

631652
/**

0 commit comments

Comments
 (0)