Skip to content

Commit 4788e33

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

File tree

1 file changed

+77
-52
lines changed

1 file changed

+77
-52
lines changed

src/hlsBinaries.ts

Lines changed: 77 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,28 @@ 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+
// now sort and get the latest suitable version
346+
const latest = (merged === null) ? null : [...merged].filter(([k, v]) => v.some(x => x === projectGhc)).sort(([k1, v1], [k2, v2]) => comparePVP(k1, k2)).pop();
347+
348+
if (!latest) {
353349
window.showErrorMessage(noMatchingHLS);
354350
throw new Error(noMatchingHLS);
351+
} else {
352+
return latest[0];
355353
}
356354
}
357355

@@ -529,44 +527,79 @@ export function addPathToProcessPath(extraPath: string): string {
529527
return PATH.join(pathSep);
530528
}
531529

530+
async function getLatestWrapperFromGHCup(
531+
context: ExtensionContext,
532+
logger: Logger
533+
): Promise<string | null> {
534+
const hlsVersions = await callGHCup(
535+
context,
536+
logger,
537+
['list', '-t', 'hls', '-c', 'installed', '-r'],
538+
undefined,
539+
false,
540+
);
541+
const installed = hlsVersions.split(/\r?\n/).pop();
542+
if (installed) {
543+
const latestHlsVersion = installed.split(' ')[1]
544+
545+
let bin = await callGHCup(context, logger,
546+
['whereis', 'hls', `${latestHlsVersion}`],
547+
undefined,
548+
false
549+
);
550+
return bin;
551+
} else {
552+
return null;
553+
}
554+
}
555+
532556
// complements getLatestHLSfromMetadata, by checking possibly locally compiled
533557
// HLS in ghcup
534558
// If 'targetGhc' is omitted, picks the latest 'haskell-language-server-wrapper',
535559
// otherwise ensures the specified GHC is supported.
536-
async function getLatestHLSfromGHCup(
560+
async function getHLSesFromGHCup(
537561
context: ExtensionContext,
538562
storagePath: string,
539563
logger: Logger,
540-
targetGhc?: string
541-
): Promise<[string, string] | null> {
564+
): Promise<Map<string, string[]> | null> {
542565
const hlsVersions = await callGHCup(
543566
context,
544567
logger,
545568
['list', '-t', 'hls', '-c', 'installed', '-r'],
546569
undefined,
547570
false,
548571
);
549-
const latestHlsVersion = hlsVersions.split(/\r?\n/).pop()!.split(' ')[1];
550-
let bindir = await callGHCup(context, logger,
572+
573+
const bindir = await callGHCup(context, logger,
551574
['whereis', 'bindir'],
552575
undefined,
553576
false
554577
);
555578

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

563-
if (fs.existsSync(hlsBin)) {
564-
return [latestHlsVersion, hlsBin];
596+
return myMap;
565597
} else {
566-
return null;
598+
return null;
567599
}
568600
}
569601

602+
570603
/**
571604
* Given a GHC version, download at least one HLS version that can be used.
572605
* This also honours the OS architecture we are on.
@@ -577,12 +610,11 @@ async function getLatestHLSfromGHCup(
577610
* @param logger Logger for feedback
578611
* @returns
579612
*/
580-
async function getLatestHLSfromMetadata(
613+
async function getHLSesfromMetadata(
581614
context: ExtensionContext,
582615
storagePath: string,
583-
targetGhc: string,
584616
logger: Logger
585-
): Promise<string | null> {
617+
): Promise<Map<string, string[]> | null> {
586618
const metadata = await getReleaseMetadata(context, storagePath, logger);
587619
if (metadata === null) {
588620
window.showErrorMessage('Could not get release metadata');
@@ -609,23 +641,16 @@ async function getLatestHLSfromMetadata(
609641
return null;
610642
}
611643

612-
let curHls: string | null = null;
613-
614644
const map: ReleaseMetadata = new Map(Object.entries(metadata));
645+
const newMap = new Map<string, string[]>();
615646
map.forEach((value, key) => {
616647
const value_ = new Map(Object.entries(value));
617648
const archValues = new Map(Object.entries(value_.get(arch)));
618649
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-
}
650+
newMap.set(key, versions);
626651
});
627652

628-
return curHls;
653+
return newMap;
629654
}
630655

631656
/**

0 commit comments

Comments
 (0)