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