Skip to content

Commit

Permalink
handle version resolution for tagged releases without network connection
Browse files Browse the repository at this point in the history
  • Loading branch information
Techatrix committed Sep 7, 2024
1 parent fac8171 commit 5c27257
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 47 deletions.
2 changes: 1 addition & 1 deletion src/versionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export class VersionManager {
);
}

/** Returns all installed versions */
/** Returns all locally installed versions */
public async query(): Promise<semver.SemVer[]> {
const available: semver.SemVer[] = [];
const prefix = `${getZigOSName()}-${getZigArchName()}`;
Expand Down
104 changes: 67 additions & 37 deletions src/zigSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path from "path";

import semver from "semver";

import { ZigVersion, getHostZigName, getLatestTaggedZigVersion, getVersionIndex } from "./zigUtil";
import { ZigVersion, getHostZigName, getVersionIndex } from "./zigUtil";
import { VersionManager } from "./versionManager";
import { ZigProvider } from "./zigProvider";

Expand All @@ -17,17 +17,28 @@ export let zigProvider: ZigProvider;
async function installZig(context: vscode.ExtensionContext) {
const wantedZig = await getWantedZigVersion(
context,
Object.values(WantedZigVersionSources) as WantedZigVersionSources[],
Object.values(WantedZigVersionSource) as WantedZigVersionSource[],
);
if (!wantedZig) {
await vscode.workspace.getConfiguration("zig").update("path", undefined, true);
zigProvider.set(null);
return;
}

if (wantedZig.source === WantedZigVersionSources.workspaceBuildZigZon) {
// The `minimum_zig_version` in `build.zig.zon` may not be available as a binary. Try to search for the next available one.
wantedZig.version = (await findClosestSatisfyingZigVersion(wantedZig.version)) ?? wantedZig.version;
// The `minimum_zig_version` in `build.zig.zon` may not be available as a binary. Try to search for the next available one.
if (wantedZig.source === WantedZigVersionSource.workspaceBuildZigZon) {
const resolvedVersion = await findClosestSatisfyingZigVersion(context, wantedZig.version);
if (resolvedVersion) {
wantedZig.version = resolvedVersion;
} else if (wantedZig.version.prerelease.length !== 0) {
// Would be nice if we could forward the error message from `findClosestSatisfyingZigVersion`.
void vscode.window.showErrorMessage(
`Failed to find a suitable Zig version that fits the 'minimum_zig_version': Zig ${wantedZig.version.toString()}`,
);
return;
} else {
// No need to report an error if the wanted Zig version is tagged release.
}
}

try {
Expand All @@ -43,32 +54,40 @@ async function installZig(context: vscode.ExtensionContext) {
} else {
void vscode.window.showErrorMessage(`Failed to install Zig ${wantedZig.version.toString()}!`);
}
return null;
return;
}
}

/** Converts a zig version to the next largest that is provided by `https://ziglang.org/builds` */
async function findClosestSatisfyingZigVersion(version: semver.SemVer): Promise<semver.SemVer | null> {
// TODO This should work even if there is no internet connection
async function findClosestSatisfyingZigVersion(
context: vscode.ExtensionContext,
version: semver.SemVer,
): Promise<semver.SemVer | null> {
const cacheKey = `zig-satisfying-version-${version.raw}`;

if (version.prerelease.length === 0) {
// We can't just return `version` because `0.12.0` should return `0.12.1`.
try {
const zigVersions = await getVersions();
const versions = zigVersions.map((item) => item.version);
return semver.maxSatisfying(versions, `^${version.toString()}`) ?? version;
} catch {
// TODO Store a mapping in the `globalState`
return version;
try {
if (version.prerelease.length === 0) {
// We can't just return `version` because `0.12.0` should return `0.12.1`.
const availableVersions = (await getVersions()).map((item) => item.version);
const selectedVersion = semver.maxSatisfying(availableVersions, `^${version.toString()}`);
await context.globalState.update(cacheKey, selectedVersion ?? undefined);
return selectedVersion;
}
} else {

// TODO we need a way to query all available prebuilt binaries that are provided by `https://ziglang.org/builds`.
// The `index.json` only provides tagged releses and the latest master.
// The `index.json` only provides tagged releases and the latest master.
// Possible candidate: https://raw.githubusercontent.com/mitchellh/zig-overlay/main/sources.json
return null;
} catch {
const selectedVersion = context.globalState.get<string | null>(cacheKey, null);
return selectedVersion ? new semver.SemVer(selectedVersion) : null;
}
return version;
}

/**
* Returns a sorted list of all versions that are provided by [index.json](https://ziglang.org/download/index.json).
* Throws an exception when no network connection is available.
*/
async function getVersions(): Promise<ZigVersion[]> {
const hostName = getHostZigName();
const indexJson = await getVersionIndex();
Expand Down Expand Up @@ -98,6 +117,7 @@ async function getVersions(): Promise<ZigVersion[]> {
`no pre-built Zig is available for your system '${hostName}', you can build it yourself using https://github.com/ziglang/zig-bootstrap`,
);
}
result.sort((lhs, rhs) => semver.compare(rhs.version, lhs.version));
return result;
}

Expand Down Expand Up @@ -138,6 +158,8 @@ async function selectVersionAndInstall(context: vscode.ExtensionContext) {
void vscode.window.showErrorMessage(`Failed to query available Zig version!`);
}
return;
} else {
// Only show the locally installed versions
}
}

Expand All @@ -151,9 +173,9 @@ async function selectVersionAndInstall(context: vscode.ExtensionContext) {
];

const workspaceZig = await getWantedZigVersion(context, [
WantedZigVersionSources.workspaceZigVersionFile,
WantedZigVersionSources.workspaceBuildZigZon,
WantedZigVersionSources.zigVersionConfigOption,
WantedZigVersionSource.workspaceZigVersionFile,
WantedZigVersionSource.workspaceBuildZigZon,
WantedZigVersionSource.zigVersionConfigOption,
]);
if (workspaceZig !== null) {
const alreadyInstalled = offlineVersions.some((item) => semver.eq(item.version, workspaceZig.version));
Expand Down Expand Up @@ -211,7 +233,7 @@ async function selectVersionAndInstall(context: vscode.ExtensionContext) {
}

/** The order of these enums defines the default order in which these sources are executed. */
enum WantedZigVersionSources {
enum WantedZigVersionSource {
workspaceState = "workspace-state",
/** `.zigversion` */
workspaceZigVersionFile = ".zigversion",
Expand All @@ -223,13 +245,13 @@ enum WantedZigVersionSources {
}

/** Try to resolve the (workspace-specific) Zig version. */
async function getWantedZigVersion<T extends WantedZigVersionSources>(
async function getWantedZigVersion(
context: vscode.ExtensionContext,
/** List of "sources" that should are applied in the given order to resolve the wanted Zig version */
sources: T[],
sources: WantedZigVersionSource[],
): Promise<{
version: semver.SemVer;
source: T;
source: WantedZigVersionSource;
} | null> {
let workspace: vscode.WorkspaceFolder | null = null;
// Supporting multiple workspaces is significantly more complex so we just look for the first workspace.
Expand All @@ -242,20 +264,20 @@ async function getWantedZigVersion<T extends WantedZigVersionSources>(

try {
switch (source) {
case WantedZigVersionSources.workspaceState:
case WantedZigVersionSource.workspaceState:
// TODO test how `workspaceState` behaviour outside of a workspace
const wantedZigVersion = context.workspaceState.get<string>("zig-version");
result = wantedZigVersion ? new semver.SemVer(wantedZigVersion) : null;
break;
case WantedZigVersionSources.workspaceZigVersionFile:
case WantedZigVersionSource.workspaceZigVersionFile:
if (workspace) {
const zigVersionString = await vscode.workspace.fs.readFile(
vscode.Uri.joinPath(workspace.uri, ".zigversion"),
);
result = semver.parse(zigVersionString.toString().trim());
}
break;
case WantedZigVersionSources.workspaceBuildZigZon:
case WantedZigVersionSource.workspaceBuildZigZon:
if (workspace) {
const manifest = await vscode.workspace.fs.readFile(
vscode.Uri.joinPath(workspace.uri, "build.zig.zon"),
Expand All @@ -267,7 +289,7 @@ async function getWantedZigVersion<T extends WantedZigVersionSources>(
}
}
break;
case WantedZigVersionSources.zigVersionConfigOption:
case WantedZigVersionSource.zigVersionConfigOption:
const versionString = vscode.workspace.getConfiguration("zig").get<string>("version");
if (versionString) {
result = semver.parse(versionString);
Expand All @@ -278,14 +300,22 @@ async function getWantedZigVersion<T extends WantedZigVersionSources>(
}
}
break;
case WantedZigVersionSources.latestTagged:
// TODO This should work even if there is no internet connection
result = await getLatestTaggedZigVersion();
case WantedZigVersionSource.latestTagged:
const cacheKey = "zig-latest-tagged";
try {
const zigVersion = await getVersions();
const latestTagged = zigVersion.find((item) => item.version.prerelease.length === 0);
result = latestTagged?.version ?? null;
await context.globalState.update(cacheKey, latestTagged?.version.raw);
} catch {
const latestTagged = context.globalState.get<string | null>(cacheKey, null);
if (latestTagged) {
result = new semver.SemVer(latestTagged);
}
}
break;
}
} catch {
continue;
}
} catch {}

if (!result) continue;

Expand Down
11 changes: 2 additions & 9 deletions src/zigUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,8 @@ export type VersionIndex = Record<
Record<string, undefined | { tarball: string; shasum: string; size: string }>
>;

export async function getVersionIndex(): Promise<VersionIndex | null> {
/** Throws an exception when no network connection is available. */
export async function getVersionIndex(): Promise<VersionIndex> {
const DOWNLOAD_INDEX = "https://ziglang.org/download/index.json";
return (await axios.get<VersionIndex>(DOWNLOAD_INDEX, {})).data;
}

export async function getLatestTaggedZigVersion(): Promise<semver.SemVer | null> {
const index = await getVersionIndex();
if (!index) return null;
const versions = Object.keys(index);
if (versions.length <= 2) return null;
return new semver.SemVer(versions[1]);
}

0 comments on commit 5c27257

Please sign in to comment.