blob: 89f2805e12b0f802db72920ec1c56147990d9086 [file] [log] [blame]
import * as child_process from "child_process";
import * as crypto from "crypto";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import * as cache from "@actions/cache";
import * as core from "@actions/core";
import { SDL_GIT_URL } from "./constants";
import { configure_ninja_build_tool } from "./ninja";
import { SetupSdlError } from "./util";
import {
GitHubRelease,
SdlReleaseDb,
SdlReleaseType,
SdlVersion,
parse_requested_sdl_version,
} from "./version";
import {
export_environent_variables,
get_sdl_build_platform,
get_platform_root_directory,
SdlBuildPlatform,
} from "./platform";
async function convert_git_branch_tag_to_hash(
branch_tag: string
): Promise<string> {
const git_hash = await core.group(
`Calculating git hash of ${branch_tag}`,
async () => {
const command = `git ls-remote ${SDL_GIT_URL} ${branch_tag}`;
core.info(`Executing "${command}"`);
const output = child_process.execSync(command, {
stdio: "pipe",
encoding: "utf8",
});
const git_hash = output.split("\t")[0];
core.info(`git hash = ${git_hash}`);
return git_hash;
}
);
return git_hash;
}
async function echo_command_and_execute(command: string, directory: string) {
core.info(`Executing "${command}`);
child_process.execSync(command, { stdio: "inherit", cwd: directory });
}
async function checkout_sdl_git_hash(
branch_tag_hash: string,
directory: string
) {
fs.mkdirSync(directory, { recursive: true });
await core.group(
`Checking out ${branch_tag_hash} into ${directory}`,
async () => {
await echo_command_and_execute(`git init`, directory);
await echo_command_and_execute(
`git remote add SDL ${SDL_GIT_URL}`,
directory
);
await echo_command_and_execute(
`git fetch --depth 1 SDL ${branch_tag_hash}`,
directory
);
await echo_command_and_execute(`git checkout FETCH_HEAD`, directory);
}
);
}
function execute_child_process(
command: string,
shell: string | undefined | null
) {
core.info(`${command}`);
let final_command: string;
if (shell && shell.indexOf("{0}") >= 0) {
const cmd_file = `${os.tmpdir}/cmd.txt`;
fs.writeFileSync(cmd_file, command);
final_command = shell.replace("{0}", cmd_file);
core.info(`-> ${final_command}`);
} else {
final_command = command;
}
child_process.execSync(final_command, { stdio: "inherit" });
}
async function cmake_configure_build(args: {
source_dir: string;
build_dir: string;
package_dir: string;
build_type: string;
cmake_configure_args: string[];
shell: string;
verbose: boolean;
}) {
const configure_args = [
"cmake",
"-S",
args.source_dir,
"-B",
args.build_dir,
...args.cmake_configure_args,
];
if (core.isDebug()) {
configure_args.push("--trace-expand");
}
const build_args = [
"cmake",
"--build",
args.build_dir,
"--config",
args.build_type,
];
if (args.verbose) {
build_args.push("--verbose");
}
const install_args = [
"cmake",
"--install",
args.build_dir,
"--prefix",
args.package_dir,
"--config",
args.build_type,
];
await core.group(`Configuring SDL (CMake)`, async () => {
const configure_command = configure_args.join(" ");
execute_child_process(configure_command, args.shell);
});
await core.group(`Building SDL (CMake)`, async () => {
const build_command = build_args.join(" ");
execute_child_process(build_command, args.shell);
});
await core.group(`Installing SDL (CMake)`, async () => {
const install_command = install_args.join(" ");
execute_child_process(install_command, args.shell);
});
}
function calculate_state_hash(args: {
git_hash: string;
build_platform: SdlBuildPlatform;
shell: string;
cmake_toolchain_file: string | undefined;
}) {
const ENV_KEYS = [
"AR",
"CC",
"CXX",
"ARFLAGS",
"CFLAGS",
"CXXFLAGS",
"INCLUDES",
"LDFLAGS",
"LIB",
"LIBPATH",
"CMAKE_PREFIX_PATH",
"CMAKE_TOOLCHAIN_FILE",
"PKG_CONFIG_PATH",
];
const env_state: string[] = [];
for (const key of ENV_KEYS) {
env_state.push(`${key}=${process.env[key]}`);
}
const ACTION_KEYS = [
"build-type",
"cmake-toolchain-file",
"discriminator",
"ninja",
];
const inputs_state: string[] = [];
for (const key of ACTION_KEYS) {
const v = core.getInput(key);
inputs_state.push(`${key}=${v}`);
}
const misc_state = [
`GIT_HASH=${args.git_hash}`,
`build_platform=${args.build_platform}`,
`shell=${args.shell}`,
];
if (args.cmake_toolchain_file) {
const toolchain_contents = fs.readFileSync(args.cmake_toolchain_file, {
encoding: "utf8",
});
const cmake_toolchain_file_hash = crypto
.createHash("sha256")
.update(toolchain_contents)
.digest("hex");
misc_state.push(`cmake_toolchain_file_hash=${cmake_toolchain_file_hash}`);
}
const complete_state: string[] = [
"ENVIRONMENT",
...env_state,
"INPUTS",
...inputs_state,
"MISC",
...misc_state,
];
const state_string = complete_state.join("##");
core.debug(`state_string=${state_string}`);
return crypto.createHash("sha256").update(state_string).digest("hex");
}
function resolve_workspace_path(in_path: string): string | undefined {
if (!in_path) {
return undefined;
}
if (fs.existsSync(in_path)) {
return path.resolve(in_path);
}
const workspace_path = path.resolve(
`${process.env.GITHUB_WORKSPACE}`,
in_path
);
if (fs.existsSync(workspace_path)) {
return workspace_path;
}
return undefined;
}
function get_cmake_toolchain_path(): string | undefined {
const in_cmake_toolchain_file = core.getInput("cmake-toolchain-file");
if (in_cmake_toolchain_file) {
const resolved_cmake_toolchain_file = resolve_workspace_path(
in_cmake_toolchain_file
);
if (!resolved_cmake_toolchain_file) {
throw new SetupSdlError(
`Cannot find CMake toolchain file: ${in_cmake_toolchain_file}`
);
}
return resolved_cmake_toolchain_file;
}
const env_cmake_toolchain_file = process.env.CMAKE_TOOLCHAIN_FILE;
if (env_cmake_toolchain_file) {
const resolved_cmake_toolchain_file = resolve_workspace_path(
env_cmake_toolchain_file
);
if (!resolved_cmake_toolchain_file) {
throw new SetupSdlError(
`Cannot find CMake toolchain file: ${env_cmake_toolchain_file}`
);
}
return resolved_cmake_toolchain_file;
}
return undefined;
}
async function run() {
const SDL_BUILD_PLATFORM = get_sdl_build_platform();
core.info(`build platform=${SDL_BUILD_PLATFORM}`);
const SETUP_SDL_ROOT = get_platform_root_directory(SDL_BUILD_PLATFORM);
core.info(`root=${SETUP_SDL_ROOT}`);
const IGNORED_SHELLS = ["bash", "pwsh", "sh", "cmd", "pwsh", "powershell"];
let shell_in = core.getInput("shell");
if (IGNORED_SHELLS.indexOf(shell_in) >= 0) {
shell_in = "";
}
const SHELL = shell_in;
const REQUESTED_VERSION_TYPE = parse_requested_sdl_version(
core.getInput("version")
);
const CMAKE_BUILD_TYPE = core.getInput("build-type");
const CMAKE_BUILD_TYPES = [
"Release",
"Debug",
"MinSizeRel",
"RelWithDebInfo",
];
if (!CMAKE_BUILD_TYPES.includes(CMAKE_BUILD_TYPE)) {
throw new SetupSdlError("Invalid build-type");
}
let git_branch_hash: string;
if (REQUESTED_VERSION_TYPE == null) {
git_branch_hash = core.getInput("version");
} else {
const { version: requested_version, type: requested_type } =
REQUESTED_VERSION_TYPE;
if (requested_type == SdlReleaseType.Head) {
if (requested_version.major == 2) {
git_branch_hash = "SDL2";
} else if (requested_version.major == 3) {
git_branch_hash = "main";
} else {
throw new SetupSdlError("Invalid -head version");
}
} else {
const github_releases = GitHubRelease.fetch_all("libsdl-org/SDL");
const release_db = SdlReleaseDb.create(github_releases);
const sdl_release = release_db.find(
requested_version,
core.getBooleanInput("pre-release"),
requested_type
);
if (!sdl_release) {
throw new SetupSdlError(
`Could not find a matching SDL release for ${requested_version}`
);
}
git_branch_hash = sdl_release.tag;
}
}
const GIT_HASH: string = await convert_git_branch_tag_to_hash(
git_branch_hash
);
const CMAKE_TOOLCHAIN_FILE = get_cmake_toolchain_path();
const STATE_HASH = calculate_state_hash({
git_hash: GIT_HASH,
build_platform: SDL_BUILD_PLATFORM,
shell: SHELL,
cmake_toolchain_file: CMAKE_TOOLCHAIN_FILE,
});
const PACKAGE_DIR = `${SETUP_SDL_ROOT}/${STATE_HASH}/package`;
const CACHE_KEY = `setup-sdl-${STATE_HASH}`;
const CACHE_PATHS = [PACKAGE_DIR];
const sdl_from_cache = await core.group(
`Looking up a SDL build in the cache`,
async () => {
core.info(`setup-sdl state = ${STATE_HASH}`);
// Pass a copy of CACHE_PATHS since cache.restoreCache modifies/modified its arguments
const found_cache_key = await cache.restoreCache(
CACHE_PATHS.slice(),
CACHE_KEY
);
if (found_cache_key) {
core.info(`SDL found in the chache: key = ${found_cache_key}`);
} else {
core.info("No match found in cache. Building SDL from scratch.");
}
return !!found_cache_key;
}
);
if (!sdl_from_cache) {
const SOURCE_DIR = `${SETUP_SDL_ROOT}/${STATE_HASH}/source`;
const BUILD_DIR = `${SETUP_SDL_ROOT}/${STATE_HASH}/build`;
await checkout_sdl_git_hash(GIT_HASH, SOURCE_DIR);
const USE_NINJA = core.getBooleanInput("ninja");
if (USE_NINJA) {
await core.group(`Configuring Ninja`, async () => {
await configure_ninja_build_tool(SDL_BUILD_PLATFORM);
});
}
const cmake_configure_args = [
`-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}`,
"-DCMAKE_INSTALL_BINDIR=bin",
"-DCMAKE_INSTALL_INCLUDEDIR=include",
"-DCMAKE_INSTALL_LIBDIR=lib",
];
if (CMAKE_TOOLCHAIN_FILE) {
cmake_configure_args.push(
`-DCMAKE_TOOLCHAIN_FILE="${CMAKE_TOOLCHAIN_FILE}"`
);
}
if (USE_NINJA) {
cmake_configure_args.push("-GNinja");
}
await cmake_configure_build({
source_dir: SOURCE_DIR,
build_dir: BUILD_DIR,
package_dir: PACKAGE_DIR,
build_type: CMAKE_BUILD_TYPE,
cmake_configure_args: cmake_configure_args,
verbose: core.getBooleanInput("verbose"),
shell: SHELL,
});
await core.group("Storing SDL in the cache", async () => {
core.info(`Caching ${CACHE_PATHS}.`);
// Pass a copy of CACHE_PATHS since cache.saveCache modifies/modified its arguments
await cache.saveCache(CACHE_PATHS.slice(), CACHE_KEY);
});
}
const SDL_VERSION =
SdlVersion.detect_sdl_version_from_install_prefix(PACKAGE_DIR);
core.info(`SDL version is ${SDL_VERSION.toString()}`);
if (core.getBooleanInput("add-to-environment")) {
export_environent_variables(SDL_BUILD_PLATFORM, PACKAGE_DIR);
}
core.exportVariable(`SDL${SDL_VERSION.major}_ROOT`, PACKAGE_DIR);
core.setOutput("prefix", PACKAGE_DIR);
core.setOutput("version", SDL_VERSION.toString());
}
try {
run();
} catch (e) {
if (e instanceof Error) {
core.error(e.message);
core.setFailed(e.message);
}
throw e;
}