-
Notifications
You must be signed in to change notification settings - Fork 90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider user installed HLSes (e.g. via ghcup compile) #543
Conversation
We don't do random fallbacks anymore. If serverExecutablePath is set, it must exist. We only check PATH if no other setting is enabled.
if (e instanceof MissingToolError) { | ||
const link = e.installLink(); | ||
if (link) { | ||
if (await window.showErrorMessage(e.message, `Install ${e.tool}`)) { | ||
env.openExternal(link); | ||
} | ||
} else { | ||
await window.showErrorMessage(e.message); | ||
} | ||
} else if (e instanceof Error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here at the top-level of extension init we catch all promise errors and also restore the MissingToolError
with link popup.
*/ | ||
function findHLSinPATH(context: ExtensionContext, logger: Logger, folder?: WorkspaceFolder): string | null { | ||
// try 'serverExecutablePath' setting | ||
function findServerExecutable(context: ExtensionContext, logger: Logger, folder?: WorkspaceFolder): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we separate findHLSinPATH
and findServerExecutable
...
src/hlsBinaries.ts
Outdated
// permissively check if we have HLS installed | ||
// this is just to avoid a popup | ||
let wrapper = await callGHCup(context, logger, | ||
['whereis', 'hls'], | ||
undefined, | ||
false, | ||
(err, stdout, _stderr, resolve, _reject) => { err ? resolve('') : resolve(stdout?.trim()); } | ||
); | ||
if (wrapper === '') { | ||
// install recommended HLS... even if this doesn't support the project GHC, because | ||
// we need a HLS to find the correct project GHC in the first place | ||
await callGHCup(context, logger, | ||
// we manage HLS, make sure ghcup is installed/available | ||
await getGHCup(context, logger); | ||
|
||
// get a preliminary hls wrapper for finding project GHC version, | ||
// later we may install a different HLS that supports the given GHC | ||
let wrapper = await getLatestHLSfromGHCup(context, storagePath, logger).then(e => | ||
(e === null) | ||
? callGHCup(context, logger, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's some considerable cleanup... now we pick a proper hls-wrapper executable (e.g. a compiled one) for the first pass (the one that just discovers project GHC), not just the recommended
... then the next pass will possibly downgrade the wrapper to match the project GHC.
src/hlsBinaries.ts
Outdated
@@ -346,10 +384,15 @@ export async function getProjectGHCVersion( | |||
logger.error(`stdout: ${stdout}`); | |||
} | |||
// Error message emitted by HLS-wrapper | |||
const regex = /Cradle requires (.+) but couldn't find it/; | |||
const regex = /Cradle requires (.+) but couldn't find it|The program \'(.+)\' version .* is required but the version of.*could.*not be determined|Cannot find the program \'(.+)\'\. User-specified/; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the regex that will cause MissingToolError
and matches on various outputs from the wrapper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, this is a work-around until haskell/haskell-language-server#2591 lands?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess
src/hlsBinaries.ts
Outdated
context: ExtensionContext, | ||
storagePath: string, | ||
logger: Logger, | ||
targetGhc?: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
targetGhc
is undefined for the first pass, where we just want the hls wrapper and don't care about hls exe.
src/hlsBinaries.ts
Outdated
throw new Error(noMatchingHLS); | ||
} else { | ||
const latestMetadataHls = await getLatestHLSfromMetadata(context, storagePath, projectGhc, logger); | ||
const latestGhcupHls = await getLatestHLSfromGHCup(context, storagePath, logger, projectGhc).then(e => e === null ? null : e[0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's where we get a possibly self-compiled already installed HLS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worthy not: we only consider the latest self-compiled...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is another edge case: if you compile HLS 1.6.1.0 manually instead of via bindist, the supported GHC versions differ from the metadata.
I'm not sure that's worth to support explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worthy not: we only consider the latest self-compiled...
What does that mean? If I have compiled version 1.6.1.0, and then I compile version 1.5.0.0, does that mean version 1.5.0.0 is used (since it is the most recent compiled HLS version), or that no matter what, we only use 1.6.1.0 and if there is no version 1.6.1.0, we would not use the self compiled version 1.5.0.0 either? Does this take into account what the latest supported HLS version relative to a GHC version is? E.g. when 8.10.4 supports only up to 1.4.0.0, and I compile the version 1.5.0.0 for it (because that's possible in theory) would the extension pick that up correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If 1.6.1.0 is the latest properly released one and you have 1.7.0.0 and 1.8.0.0 compiled, the extension will only bother looking at 1.8.0.0.
There are a number of complicated interactions here, e.g. it's possible that you compiled 1.6.1.0 as well and it doesn't support the project GHC, because it's only built for one GHC (unlike the bindist).
So we have a Map of HLS ver -> GHC versions
.
A way out here would be this:
- Map A: gather all ghcup-installed HLSes and their corresponding supported GHC version, regardless of the HLS metadata file
- Map B: gather HLS metadata
- merge both this way:
- if a HLS version exists both in A and B, only consider GHC versions from A (because
ghcup install hls <ver>
would not overwrite the already existing one... and the existing one might have less supported GHC versions) - if a HLS version exists only in A, pick A
- if a HLS version exists only in B, pick B
- if a HLS version exists both in A and B, only consider GHC versions from A (because
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm starting to think this functionality belongs into ghcup. It is non-trivial.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is non-trivial. Adding it to ghcup directly sounds like a nice idea!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, haven't had a chance to test everything out, though
src/hlsBinaries.ts
Outdated
@@ -346,10 +384,15 @@ export async function getProjectGHCVersion( | |||
logger.error(`stdout: ${stdout}`); | |||
} | |||
// Error message emitted by HLS-wrapper | |||
const regex = /Cradle requires (.+) but couldn't find it/; | |||
const regex = /Cradle requires (.+) but couldn't find it|The program \'(.+)\' version .* is required but the version of.*could.*not be determined|Cannot find the program \'(.+)\'\. User-specified/; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, this is a work-around until haskell/haskell-language-server#2591 lands?
src/hlsBinaries.ts
Outdated
throw new Error(noMatchingHLS); | ||
} else { | ||
const latestMetadataHls = await getLatestHLSfromMetadata(context, storagePath, projectGhc, logger); | ||
const latestGhcupHls = await getLatestHLSfromGHCup(context, storagePath, logger, projectGhc).then(e => e === null ? null : e[0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worthy not: we only consider the latest self-compiled...
What does that mean? If I have compiled version 1.6.1.0, and then I compile version 1.5.0.0, does that mean version 1.5.0.0 is used (since it is the most recent compiled HLS version), or that no matter what, we only use 1.6.1.0 and if there is no version 1.6.1.0, we would not use the self compiled version 1.5.0.0 either? Does this take into account what the latest supported HLS version relative to a GHC version is? E.g. when 8.10.4 supports only up to 1.4.0.0, and I compile the version 1.5.0.0 for it (because that's possible in theory) would the extension pick that up correctly?
src/hlsBinaries.ts
Outdated
// first we get supported GHC versions from available HLS bindists (whether installed or not) | ||
const metadataMap = await getHLSesfromMetadata(context, storagePath, logger); | ||
// then we get supported GHC versions from currently installed HLS versions | ||
const ghcupMap = await getHLSesFromGHCup(context, storagePath, logger); | ||
// since installed HLS versions may support a different set of GHC versions than the bindists | ||
// (e.g. because the user ran 'ghcup compile hls'), we need to merge both maps, preferring | ||
// values from already installed HLSes | ||
const merged = (metadataMap === null) | ||
? ghcupMap | ||
: ((ghcupMap === null) | ||
? null | ||
: (new Map<string, string[]>([...metadataMap, ...ghcupMap]))); // right-biased | ||
|
||
if (!merged) { | ||
window.showErrorMessage(noMatchingHLS); | ||
throw new Error(noMatchingHLS); | ||
} else { | ||
return latestMetadataHls; | ||
// now sort and get the latest suitable version | ||
const latest = [...merged].filter(([k, v]) => v.some(x => x === projectGhc)).sort(([k1, v1], [k2, v2]) => comparePVP(k1, k2)).pop(); | ||
if (latest) { | ||
return latest[0]; | ||
|
||
} else { | ||
window.showErrorMessage(noMatchingHLS); | ||
throw new Error(noMatchingHLS); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is the juice... we merge the maps.
And correctly merge all those maps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the changes are good as-is, but we need to make sure the documentation clearly explains all the possible user interactions.
E.g. the options system-ghcup
, internal-ghcup
and PATH
are quite unclear to absolute beginners.
Fixes #542