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,28 @@ 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 ) ;
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 ) {
353
349
window . showErrorMessage ( noMatchingHLS ) ;
354
350
throw new Error ( noMatchingHLS ) ;
351
+ } else {
352
+ return latest [ 0 ] ;
355
353
}
356
354
}
357
355
@@ -529,44 +527,79 @@ export function addPathToProcessPath(extraPath: string): string {
529
527
return PATH . join ( pathSep ) ;
530
528
}
531
529
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
+
532
556
// complements getLatestHLSfromMetadata, by checking possibly locally compiled
533
557
// HLS in ghcup
534
558
// If 'targetGhc' is omitted, picks the latest 'haskell-language-server-wrapper',
535
559
// otherwise ensures the specified GHC is supported.
536
- async function getLatestHLSfromGHCup (
560
+ async function getHLSesFromGHCup (
537
561
context : ExtensionContext ,
538
562
storagePath : string ,
539
563
logger : Logger ,
540
- targetGhc ?: string
541
- ) : Promise < [ string , string ] | null > {
564
+ ) : Promise < Map < string , string [ ] > | null > {
542
565
const hlsVersions = await callGHCup (
543
566
context ,
544
567
logger ,
545
568
[ 'list' , '-t' , 'hls' , '-c' , 'installed' , '-r' ] ,
546
569
undefined ,
547
570
false ,
548
571
) ;
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 ,
551
574
[ 'whereis' , 'bindir' ] ,
552
575
undefined ,
553
576
false
554
577
) ;
555
578
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
+ } ) ;
562
595
563
- if ( fs . existsSync ( hlsBin ) ) {
564
- return [ latestHlsVersion , hlsBin ] ;
596
+ return myMap ;
565
597
} else {
566
- return null ;
598
+ return null ;
567
599
}
568
600
}
569
601
602
+
570
603
/**
571
604
* Given a GHC version, download at least one HLS version that can be used.
572
605
* This also honours the OS architecture we are on.
@@ -577,12 +610,11 @@ async function getLatestHLSfromGHCup(
577
610
* @param logger Logger for feedback
578
611
* @returns
579
612
*/
580
- async function getLatestHLSfromMetadata (
613
+ async function getHLSesfromMetadata (
581
614
context : ExtensionContext ,
582
615
storagePath : string ,
583
- targetGhc : string ,
584
616
logger : Logger
585
- ) : Promise < string | null > {
617
+ ) : Promise < Map < string , string [ ] > | null > {
586
618
const metadata = await getReleaseMetadata ( context , storagePath , logger ) ;
587
619
if ( metadata === null ) {
588
620
window . showErrorMessage ( 'Could not get release metadata' ) ;
@@ -609,23 +641,16 @@ async function getLatestHLSfromMetadata(
609
641
return null ;
610
642
}
611
643
612
- let curHls : string | null = null ;
613
-
614
644
const map : ReleaseMetadata = new Map ( Object . entries ( metadata ) ) ;
645
+ const newMap = new Map < string , string [ ] > ( ) ;
615
646
map . forEach ( ( value , key ) => {
616
647
const value_ = new Map ( Object . entries ( value ) ) ;
617
648
const archValues = new Map ( Object . entries ( value_ . get ( arch ) ) ) ;
618
649
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 ) ;
626
651
} ) ;
627
652
628
- return curHls ;
653
+ return newMap ;
629
654
}
630
655
631
656
/**
0 commit comments