setup-godot/src/utils.ts
Joanna May ca46d7ba46
feat: infer godot version from global.json (if specified) (#24)
* feat: infer godot version from global.json (if specified)

* fix: restore run settings
2023-03-30 19:08:11 -05:00

315 lines
8.3 KiB
TypeScript

import * as core from '@actions/core'
import * as fs from 'fs'
import normalize from 'normalize-path'
import * as os from 'os'
import * as path from 'path'
export interface Platform {
/** Godot installation filename suffix. */
godotFilenameSuffix(useDotnet: boolean): string
/**
* Returns true if the given path is most likely the Godot executable for
* the platform.
* @param basename File basename to check.
*/
isGodotExecutable(basename: string): boolean
/**
* Returns the path to the unzipped file for the platform.
* @param installationDir Installation directory.
* @param versionName Version name.
*/
getUnzippedPath(
installationDir: string,
versionName: string,
useDotnet: boolean
): string
GODOT_EXPORT_TEMPLATE_BASE_PATH: string
}
export class Linux implements Platform {
GODOT_EXPORT_TEMPLATE_BASE_PATH = path.join(
os.homedir(),
'.local/share/godot'
)
godotFilenameSuffix(useDotnet: boolean): string {
if (useDotnet) {
return '_mono_linux_x86_64'
}
return '_linux.x86_64'
}
isGodotExecutable(basename: string): boolean {
return basename.toLowerCase().endsWith('x86_64')
}
getUnzippedPath(
installationDir: string,
versionName: string,
useDotnet: boolean
): string {
return path.join(installationDir, versionName)
}
}
export class Windows implements Platform {
GODOT_EXPORT_TEMPLATE_BASE_PATH = path.normalize(
path.join(os.homedir(), '\\AppData\\Roaming\\Godot')
)
godotFilenameSuffix(useDotnet: boolean): string {
if (useDotnet) {
return '_mono_win64'
}
return '_win64.exe'
}
isGodotExecutable(basename: string): boolean {
return basename.toLowerCase().endsWith('_win64.exe')
}
getUnzippedPath(
installationDir: string,
versionName: string,
useDotnet: boolean
): string {
return path.join(installationDir, versionName)
}
}
export class MacOS implements Platform {
GODOT_EXPORT_TEMPLATE_BASE_PATH = path.join(
os.homedir(),
'/Library/Application Support/Godot/'
)
godotFilenameSuffix(useDotnet: boolean): string {
return `${useDotnet ? '_mono' : ''}_macos.universal`
}
isGodotExecutable(basename: string): boolean {
return basename.toLowerCase() === 'godot'
}
getUnzippedPath(
installationDir: string,
versionName: string,
useDotnet: boolean
): string {
return path.join(installationDir, `Godot${useDotnet ? '_mono' : ''}.app`)
}
}
/** Semantic version representation */
interface SemanticVersion {
/** Version major number */
major: string
/** Version minor number */
minor: string
/** Version patch number */
patch: string
/** Pre-release label (e.g., `beta.16`) */
label: string
}
/** Godot download url prefix. */
const GODOT_URL_PREFIX = 'https://downloads.tuxfamily.org/godotengine/'
/** Godot filename prefix. */
const GODOT_FILENAME_PREFIX = 'Godot_v'
/**
* Official semantic version regex.
* See https://semver.org
*/
const SEMANTIC_VERSION_REGEX =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
export function parseVersion(version: string): SemanticVersion {
const match = version.match(SEMANTIC_VERSION_REGEX)
if (match === null) {
throw new Error(`⛔️ Invalid version: ${version}`)
}
const major = match[1] || ''
const minor = match[2] || ''
const patch = match[3] || ''
const label = match[4] || ''
return {major, minor, patch, label}
}
/**
* Returns the Godot download url for the given version and platform.
* @param versionString Version string.
* @param platform Current platform instance.
* @param useDotnet True to use the .NET-enabled version of Godot.
* @param isTemplate True to return the url for the template
* @returns Godot binary download url.
*/
export function getGodotUrl(
versionString: string,
platform: Platform,
useDotnet: boolean,
isTemplate: boolean
): string {
const version = parseVersion(versionString)
const major = version.major
const minor = version.minor
const patch = version.patch
const label = version.label.replace('.', '')
let url = `${GODOT_URL_PREFIX + major}.${minor}`
if (patch !== '' && patch !== '0') {
url += `.${patch}`
}
url += '/'
if (label !== '') {
url += `${label}/`
}
if (useDotnet) url += `mono/`
if (!isTemplate)
return `${url}${getGodotFilename(version, platform, useDotnet)}.zip`
return `${url}${getGodotFilenameBase(version)}${
useDotnet ? '_mono' : ''
}_export_templates.tpz`
}
/**
* Returns the Godot export template local path
* @param versionString Version string.
* @param platform Current platform instance.
* @param useDotnet True to use the .NET-enabled version of Godot.
* @returns export template local path
*/
export function getExportTemplatePath(
versionString: string,
platform: Platform,
useDotnet: boolean
): string {
const version = parseVersion(versionString)
const major = version.major
const minor = version.minor
const patch = version.patch
const label = version.label.replace('.', '')
let folderName = `${major}.${minor}`
if (patch !== '' && patch !== '0') {
folderName += `.${patch}`
}
if (label !== '') {
folderName += `.${label}`
} else {
folderName += '.stable'
}
if (useDotnet) folderName += '.mono'
return normalize(
path.join(
platform.GODOT_EXPORT_TEMPLATE_BASE_PATH,
version.major === '4' ? 'export_templates' : 'templates',
folderName
)
)
}
export function getGodotFilename(
version: SemanticVersion,
platform: Platform,
useDotnet: boolean
): string {
return getGodotFilenameBase(version) + platform.godotFilenameSuffix(useDotnet)
}
export function getGodotFilenameBase(version: SemanticVersion): string {
const major = version.major
const minor = version.minor
const patch = version.patch
const label = version.label.replace('.', '')
let filename = GODOT_FILENAME_PREFIX + major
if (minor !== '') {
filename += `.${minor}`
}
if (patch !== '' && patch !== '0') {
filename += `.${patch}`
}
if (label !== '') {
filename += `-${label}`
} else {
filename += '-stable'
}
return filename
}
export function getGodotFilenameFromVersionString(
versionString: string,
platform: Platform,
useDotnet: boolean
): string {
return getGodotFilename(parseVersion(versionString), platform, useDotnet)
}
export function getPlatform(processPlatform: NodeJS.Platform): Platform {
switch (processPlatform) {
case 'linux':
core.info('🐧 Running on Linux')
return new Linux()
case 'win32':
core.info('⧉ Running on Windows')
return new Windows()
case 'darwin':
core.info('🍏 Running on macOS')
return new MacOS()
default:
throw new Error(`⛔️ Unrecognized platform: ${process.platform}`)
}
}
export async function findExecutablesRecursively(
platform: Platform,
dir: string,
indent: string
): Promise<string[]> {
core.info(`${indent}📁 ${dir}`)
let executables: string[] = []
const files = await fs.promises.readdir(dir, {withFileTypes: true})
for (const file of files) {
const filePath = path.join(dir, file.name)
if (file.isDirectory()) {
const additionalExecutables = await findExecutablesRecursively(
platform,
filePath,
`${indent} `
)
executables = executables.concat(additionalExecutables)
} else {
// Test if file is executable. GodotSharp.dll is always considered an
// executable.
let isExecutable = file.name === 'GodotSharp.dll' ? true : false
if (!isExecutable) {
if (platform instanceof Windows) {
// fs.constants.X_OK doesn't seem to work on Windows.
// Resort to checking the file extension.
if (file.name.toLowerCase().endsWith('.exe')) {
isExecutable = true
}
} else {
try {
fs.accessSync(filePath, fs.constants.X_OK)
isExecutable = true
} catch (error) {
// File is not executable.
}
}
}
if (isExecutable) {
core.info(`${indent} 🚀 ${file.name}`)
executables.push(filePath)
} else {
core.info(`${indent} 📄 ${file.name}`)
}
}
}
return executables
}