Skip to content

Commit b324a72

Browse files
committed
Add support for #1273
1 parent c574d3d commit b324a72

File tree

2 files changed

+65
-19
lines changed

2 files changed

+65
-19
lines changed

src/ghcup.ts

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ export type GHCupConfig = {
2525
executablePath?: string;
2626
};
2727

28+
export type ToolInfo = {
29+
tool: Tool;
30+
version: string;
31+
tags: string[];
32+
};
33+
2834
export class GHCup {
2935
constructor(
3036
readonly logger: Logger,
@@ -64,27 +70,44 @@ export class GHCup {
6470
* Upgrade the `ghcup` binary unless this option was disabled by the user.
6571
*/
6672
public async upgrade(): Promise<void> {
67-
const upgrade = this.config.upgradeGHCup; // workspace.getConfiguration('haskell').get('upgradeGHCup') as boolean;
73+
const upgrade = this.config.upgradeGHCup;
6874
if (upgrade) {
6975
await this.call(['upgrade'], 'Upgrading ghcup', true);
7076
}
7177
}
7278

79+
/**
80+
* Find the `set` version of a {@link Tool} in GHCup.
81+
* If no version is set, return null.
82+
* @param tool Tool you want to know the latest version of.
83+
* @returns The latest installed or generally available version of the {@link tool}
84+
*/
85+
public async getSetVersion(tool: Tool): Promise<ToolInfo | null> {
86+
// these might be custom/stray/compiled, so we try first
87+
const installedVersions = await this.listTool(tool, 'set');
88+
const latestInstalled = installedVersions.pop();
89+
if (latestInstalled) {
90+
return latestInstalled;
91+
} else {
92+
return null;
93+
}
94+
}
95+
7396
/**
7497
* Find the latest version of a {@link Tool} that we can find in GHCup.
7598
* Prefer already installed versions, but fall back to all available versions, if there aren't any.
7699
* @param tool Tool you want to know the latest version of.
77100
* @returns The latest installed or generally available version of the {@link tool}
78101
*/
79-
public async getLatestVersion(tool: Tool): Promise<string> {
102+
public async getAnyLatestVersion(tool: Tool): Promise<ToolInfo | null> {
80103
// these might be custom/stray/compiled, so we try first
81-
const installedVersions = await this.call(['list', '-t', tool, '-c', 'installed', '-r'], undefined, false);
82-
const latestInstalled = installedVersions.split(/\r?\n/).pop();
104+
const installedVersions = await this.listTool(tool, 'installed');
105+
const latestInstalled = installedVersions.pop();
83106
if (latestInstalled) {
84-
return latestInstalled.split(/\s+/)[1];
107+
return latestInstalled;
108+
} else {
109+
return this.getLatestAvailableVersion(tool);
85110
}
86-
87-
return this.getLatestAvailableVersion(tool);
88111
}
89112

90113
/**
@@ -95,16 +118,14 @@ export class GHCup {
95118
* @param tag The tag to filter the available versions with. By default `"latest"`.
96119
* @returns The latest available version filtered by {@link tag}.
97120
*/
98-
public async getLatestAvailableVersion(tool: Tool, tag: string = 'latest'): Promise<string> {
121+
public async getLatestAvailableVersion(tool: Tool, tag: string = 'latest'): Promise<ToolInfo> {
99122
// fall back to installable versions
100-
const availableVersions = await this.call(['list', '-t', tool, '-c', 'available', '-r'], undefined, false).then(
101-
(s) => s.split(/\r?\n/),
102-
);
123+
const availableVersions = await this.listTool(tool, 'available');
103124

104-
let latestAvailable: string | null = null;
105-
availableVersions.forEach((ver) => {
106-
if (ver.split(/\s+/)[2].split(',').includes(tag)) {
107-
latestAvailable = ver.split(/\s+/)[1];
125+
let latestAvailable: ToolInfo | null = null;
126+
availableVersions.forEach((toolInfo) => {
127+
if (toolInfo.tags.includes(tag)) {
128+
latestAvailable = toolInfo;
108129
}
109130
});
110131
if (!latestAvailable) {
@@ -113,6 +134,31 @@ export class GHCup {
113134
return latestAvailable;
114135
}
115136
}
137+
138+
private async listTool(tool: Tool, category: string): Promise<ToolInfo[]> {
139+
// fall back to installable versions
140+
const availableVersions = await this.call(['list', '-t', tool, '-c', category, '-r'], undefined, false).then((s) =>
141+
s.split(/\r?\n/),
142+
);
143+
144+
return availableVersions.map((toolString) => {
145+
const toolParts = toolString.split(/\s+/);
146+
return {
147+
tool: tool,
148+
version: toolParts[1],
149+
tags: toolParts[2]?.split(',') ?? [],
150+
};
151+
});
152+
}
153+
154+
public async findLatestUserInstalledTool(tool: Tool): Promise<ToolInfo> {
155+
let toolInfo = null;
156+
toolInfo = await this.getSetVersion(tool);
157+
if (toolInfo) return toolInfo;
158+
toolInfo = await this.getAnyLatestVersion(tool);
159+
if (toolInfo) return toolInfo;
160+
throw new Error(`Unable to find a version for tool ${tool}`);
161+
}
116162
}
117163

118164
function findGHCup(logger: Logger, exePath?: string, folder?: WorkspaceFolder): string {

src/hlsBinaries.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,16 +177,16 @@ export async function findHaskellLanguageServer(
177177
// (we need HLS and cabal/stack and ghc as fallback),
178178
// later we may install a different toolchain that's more project-specific
179179
if (latestHLS === undefined) {
180-
latestHLS = await ghcup.getLatestVersion('hls');
180+
latestHLS = await ghcup.getAnyLatestVersion('hls').then((tool) => tool?.version);
181181
}
182182
if (latestCabal === undefined) {
183-
latestCabal = await ghcup.getLatestVersion('cabal');
183+
latestCabal = (await ghcup.findLatestUserInstalledTool('cabal')).version;
184184
}
185185
if (latestStack === undefined) {
186-
latestStack = await ghcup.getLatestVersion('stack');
186+
latestStack = (await ghcup.findLatestUserInstalledTool('stack')).version;
187187
}
188188
if (recGHC === undefined) {
189-
recGHC = !executableExists('ghc') ? await ghcup.getLatestAvailableVersion('ghc', 'recommended') : null;
189+
recGHC = !executableExists('ghc') ? (await ghcup.getLatestAvailableVersion('ghc', 'recommended')).version : null;
190190
}
191191

192192
// download popups

0 commit comments

Comments
 (0)