1
1
import * as child_process from 'child_process' ;
2
2
import { ExecException } from 'child_process' ;
3
3
import * as fs from 'fs' ;
4
+ import { stat } from 'fs/promises' ;
4
5
import * as https from 'https' ;
5
6
import * as path from 'path' ;
6
7
import { match } from 'ts-pattern' ;
@@ -242,7 +243,7 @@ export async function findHaskellLanguageServer(
242
243
243
244
// get a preliminary hls wrapper for finding project GHC version,
244
245
// 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 =>
246
247
( e === null )
247
248
? callGHCup ( context , logger ,
248
249
[ 'install' , 'hls' ] ,
@@ -255,7 +256,7 @@ export async function findHaskellLanguageServer(
255
256
false ,
256
257
( err , stdout , _stderr , resolve , _reject ) => { err ? resolve ( '' ) : resolve ( stdout ?. trim ( ) ) ; } )
257
258
)
258
- : e [ 1 ]
259
+ : e
259
260
) ;
260
261
261
262
// now figure out the project GHC version and the latest supported HLS version
@@ -327,31 +328,24 @@ async function getLatestHLS(
327
328
wrapper === undefined
328
329
? await callAsync ( `ghc${ exeExt } ` , [ '--numeric-version' ] , storagePath , logger , undefined , false )
329
330
: 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 ) {
353
345
window . showErrorMessage ( noMatchingHLS ) ;
354
346
throw new Error ( noMatchingHLS ) ;
347
+ } else {
348
+ return latest [ 0 ] ;
355
349
}
356
350
}
357
351
@@ -529,44 +523,79 @@ export function addPathToProcessPath(extraPath: string): string {
529
523
return PATH . join ( pathSep ) ;
530
524
}
531
525
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
+
532
552
// complements getLatestHLSfromMetadata, by checking possibly locally compiled
533
553
// HLS in ghcup
534
554
// If 'targetGhc' is omitted, picks the latest 'haskell-language-server-wrapper',
535
555
// otherwise ensures the specified GHC is supported.
536
- async function getLatestHLSfromGHCup (
556
+ async function getHLSesFromGHCup (
537
557
context : ExtensionContext ,
538
558
storagePath : string ,
539
559
logger : Logger ,
540
- targetGhc ?: string
541
- ) : Promise < [ string , string ] | null > {
560
+ ) : Promise < Map < string , string [ ] > | null > {
542
561
const hlsVersions = await callGHCup (
543
562
context ,
544
563
logger ,
545
564
[ 'list' , '-t' , 'hls' , '-c' , 'installed' , '-r' ] ,
546
565
undefined ,
547
566
false ,
548
567
) ;
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 ,
551
570
[ 'whereis' , 'bindir' ] ,
552
571
undefined ,
553
572
false
554
573
) ;
555
574
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
+ } ) ;
562
591
563
- if ( fs . existsSync ( hlsBin ) ) {
564
- return [ latestHlsVersion , hlsBin ] ;
592
+ return myMap ;
565
593
} else {
566
- return null ;
594
+ return null ;
567
595
}
568
596
}
569
597
598
+
570
599
/**
571
600
* Given a GHC version, download at least one HLS version that can be used.
572
601
* This also honours the OS architecture we are on.
@@ -577,12 +606,11 @@ async function getLatestHLSfromGHCup(
577
606
* @param logger Logger for feedback
578
607
* @returns
579
608
*/
580
- async function getLatestHLSfromMetadata (
609
+ async function getHLSesfromMetadata (
581
610
context : ExtensionContext ,
582
611
storagePath : string ,
583
- targetGhc : string ,
584
612
logger : Logger
585
- ) : Promise < string | null > {
613
+ ) : Promise < Map < string , string [ ] > | null > {
586
614
const metadata = await getReleaseMetadata ( context , storagePath , logger ) ;
587
615
if ( metadata === null ) {
588
616
window . showErrorMessage ( 'Could not get release metadata' ) ;
@@ -609,23 +637,16 @@ async function getLatestHLSfromMetadata(
609
637
return null ;
610
638
}
611
639
612
- let curHls : string | null = null ;
613
-
614
640
const map : ReleaseMetadata = new Map ( Object . entries ( metadata ) ) ;
641
+ const newMap = new Map < string , string [ ] > ( ) ;
615
642
map . forEach ( ( value , key ) => {
616
643
const value_ = new Map ( Object . entries ( value ) ) ;
617
644
const archValues = new Map ( Object . entries ( value_ . get ( arch ) ) ) ;
618
645
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 ) ;
626
647
} ) ;
627
648
628
- return curHls ;
649
+ return newMap ;
629
650
}
630
651
631
652
/**
0 commit comments