blob: eb64351f0746a78f62691b0b32487a7f7378aa49 [file] [edit]
import * as child_process from "child_process";
import * as fs from "fs";
import { SetupSdlError } from "./util";
export class GitHubRelease {
name: string;
prerelease: boolean;
tag: string;
time: number;
constructor(name: string, prerelease: boolean, tag: string, time: number) {
this.name = name;
this.prerelease = prerelease;
this.tag = tag;
this.time = time;
}
static fetch_all(repo: string): GitHubRelease[] {
const buffer = child_process.execSync(`gh release list -R ${repo} -L 1000`);
return GitHubRelease.from_gh_output(buffer.toString());
}
static from_gh_output(text: string): GitHubRelease[] {
return text.trim().split("\n").map(line_str => {
const line_parts = line_str.split("\t");
return new GitHubRelease(
line_parts[0],
line_parts[1].toLowerCase() == "pre-release",
line_parts[2],
Date.parse(line_parts[3]),
);
});
}
}
export class SdlVersion {
major: number;
minor: number;
patch: number;
constructor(
version: string | { major: number; minor: number; patch: number }
) {
if (typeof version == "string") {
const v_list = version.split(".");
if (v_list.length == 0 || v_list.length > 3) {
throw new SetupSdlError(
`Cannot convert version (${version}) to MAJOR.MINOR.PATCH`
);
}
this.major = Number(v_list[0]);
if (v_list.length > 0) {
this.minor = Number(v_list[1]);
} else {
this.minor = 0;
}
if (v_list.length > 1) {
this.patch = Number(v_list[2]);
} else {
this.patch = 0;
}
} else {
this.major = version.major;
this.minor = version.minor;
this.patch = version.patch;
}
if (isNaN(this.major) || isNaN(this.minor) || isNaN(this.patch)) {
throw new SetupSdlError(
`Cannot convert version (${version}) to MAJOR.MINOR.PATCH`
);
}
}
compare(other: SdlVersion): number {
if (this.major > other.major) {
return -1;
}
if (other.major > this.major) {
return 1;
}
if (this.minor > other.minor) {
return -1;
}
if (other.minor > this.minor) {
return 1;
}
if (this.patch > other.patch) {
return -1;
}
if (other.patch > this.patch) {
return 1;
}
return 0;
}
equals(other: SdlVersion): boolean {
return this.compare(other) == 0;
}
toString(): string {
return `${this.major}.${this.minor}.${this.patch}`;
}
static detect_sdl_version_from_source_tree(path: string): SdlVersion {
const sdl3_SDL_version_h_path = `${path}/include/SDL3/SDL_version.h`;
if (fs.existsSync(sdl3_SDL_version_h_path)) {
return this.extract_sdl_version_from_SDL_version_h(
sdl3_SDL_version_h_path
);
}
const sdl2_SDL_version_h_path = `${path}/include/SDL_version.h`;
if (fs.existsSync(sdl2_SDL_version_h_path)) {
return this.extract_sdl_version_from_SDL_version_h(
sdl2_SDL_version_h_path
);
}
throw new SetupSdlError(
`Could not find a SDL_version.h in the source tree (${path})`
);
}
static detect_sdl_version_from_install_prefix(path: string): SdlVersion {
const sdl3_SDL_version_h_path = `${path}/include/SDL3/SDL_version.h`;
if (fs.existsSync(sdl3_SDL_version_h_path)) {
return this.extract_sdl_version_from_SDL_version_h(
sdl3_SDL_version_h_path
);
}
const sdl2_SDL_version_h_path = `${path}/include/SDL2/SDL_version.h`;
if (fs.existsSync(sdl2_SDL_version_h_path)) {
return this.extract_sdl_version_from_SDL_version_h(
sdl2_SDL_version_h_path
);
}
throw new SetupSdlError(
`Could not find a SDL_version.h in the prefix (${path})`
);
}
static extract_sdl_version_from_SDL_version_h(
SDL_version_h_path: string
): SdlVersion {
const SDL_version_h = fs.readFileSync(SDL_version_h_path, "utf8");
const match_major = SDL_version_h.match(
/#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)/
);
if (!match_major) {
throw new SdlVersion(
`Unable to extract major SDL version from ${SDL_version_h_path}`
);
}
const major_version = Number(match_major[1]);
const match_minor = SDL_version_h.match(
/#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)/
);
if (!match_minor) {
throw new SdlVersion(
`Unable to extract minor SDL version from ${SDL_version_h_path}`
);
}
const minor_version = Number(match_minor[1]);
const match_patch = SDL_version_h.match(
/#define[ \t]+SDL_PATCHLEVEL[ \t]+([0-9]+)/
);
if (!match_patch) {
throw new SdlVersion(
`Unable to extract patch SDL version from ${SDL_version_h_path}`
);
}
const patch_version = Number(match_patch[1]);
return new SdlVersion({
major: major_version,
minor: minor_version,
patch: patch_version,
});
}
}
export enum SdlReleaseType {
Any = "Any",
Head = "Head",
Latest = "Latest",
Exact = "Exact",
}
export class SdlReleaseDb {
releases: SdlRelease[];
constructor(releases: SdlRelease[]) {
this.releases = releases;
}
find(
version: SdlVersion,
prerelease: boolean,
type: SdlReleaseType
): SdlRelease | null {
for (const release of this.releases) {
// Skip if a pre-release has not been requested
if (release.prerelease != null && !prerelease) {
continue;
}
if (type == SdlReleaseType.Exact) {
if (release.version.equals(version)) {
return release;
}
}
if (type == SdlReleaseType.Latest || type == SdlReleaseType.Any) {
if (release.version.major == version.major) {
return release;
}
}
}
return null;
}
static create(github_releases: GitHubRelease[]): SdlReleaseDb {
const R = new RegExp("(release-|prerelease-)?([0-9.]+)(-RC([0-9]+))?");
const releases = github_releases.map(gh_release => {
const m = gh_release.tag.match(R);
if (m == null) {
throw new SetupSdlError(`Invalid tag: ${gh_release.tag}`);
}
let prerelease: number | null = null;
if (m[1] != null && m[1] != "release-") {
prerelease = 1;
} else if (m[3] != null && m[4] != null) {
prerelease = Number(m[4]) + 1;
}
const version = m[2];
return new SdlRelease(new SdlVersion(version), prerelease, gh_release.tag);
});
releases.sort((release1, release2) => {
return release1.compare(release2);
});
return new SdlReleaseDb(releases);
}
}
export class SdlRelease {
version: SdlVersion;
prerelease: number | null;
tag: string;
constructor(version: SdlVersion, prerelease: number | null, tag: string) {
this.version = version;
this.prerelease = prerelease;
this.tag = tag;
}
compare(other: SdlRelease): number {
const cmp = this.version.compare(other.version);
if (cmp != 0) {
return cmp;
}
if (this.prerelease != null && other.prerelease != null) {
return Number(other.prerelease) - Number(this.prerelease);
}
if (this.prerelease == null && other.prerelease == null) {
return 0;
}
if (this.prerelease != null) {
return 1;
}
return -1;
}
equals(other: SdlRelease): boolean {
return this.compare(other) == 0;
}
toString(): string {
return `<SDLRelease:version=${this.version} prerelease=${this.prerelease} tag=${this.tag}>`;
}
}
export function parse_requested_sdl_version(
version_request: string
): { version: SdlVersion; type: SdlReleaseType } | null {
const ANY_SUFFIX = "-any";
const HEAD_SUFFIX = "-head";
const LATEST_SUFFIX = "-latest";
let version: SdlVersion;
let version_type: SdlReleaseType;
version_request = version_request.toLowerCase();
if (version_request.startsWith("sdl")) {
version_request = version_request.substring(3);
}
try {
if (version_request.endsWith(ANY_SUFFIX)) {
version_type = SdlReleaseType.Any;
const version_str = version_request.substring(
0,
version_request.length - ANY_SUFFIX.length
);
version = new SdlVersion({
major: Number(version_str),
minor: 0,
patch: 0,
});
} else if (version_request.endsWith(HEAD_SUFFIX)) {
version_type = SdlReleaseType.Head;
const version_str = version_request.substring(
0,
version_request.length - HEAD_SUFFIX.length
);
version = new SdlVersion({
major: Number(version_str),
minor: 0,
patch: 0,
});
} else if (version_request.endsWith(LATEST_SUFFIX)) {
version_type = SdlReleaseType.Latest;
const version_str = version_request.substring(
0,
version_request.length - LATEST_SUFFIX.length
);
version = new SdlVersion({
major: Number(version_str),
minor: 0,
patch: 0,
});
} else {
version_type = SdlReleaseType.Exact;
const version_str = version_request;
version = new SdlVersion(version_str);
}
return { version: version, type: version_type };
} catch (e) {
return null;
}
}