Build / build (pull_request) Successful in 10m29s
CMake the way CLion does it (profiles and presets, targets, run and debug configurations, CTest), moved out of personal-assistant's cmake-extension and renamed from CMake Targets. - Gitea Actions: typecheck, package the .vsix, release it on a v* tag - Updates: the latest Gitea release is looked at every 10 minutes and offered for install; a private repository's token in the secret storage - cmakeHelper.toolbar: the toolbar in the status bar, the editors' title, or VS Code's title bar (workbench.html patched, offered again after VS Code or extension updates) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
134 lines
5.3 KiB
TypeScript
134 lines
5.3 KiB
TypeScript
/**
|
|
* CMake profiles, as in CLion: each is a build directory with its own build
|
|
* type, generator, toolchain, cache variables and environment. They come from
|
|
* the setting `cmakeHelper.profiles` and from the configure presets; with
|
|
* neither there's CLion's default, Debug in cmake-build-debug.
|
|
*/
|
|
import * as path from "node:path";
|
|
import * as vscode from "vscode";
|
|
import { loadConfigurePresets } from "./presets";
|
|
import { expand, onPath, settings } from "./util";
|
|
|
|
export interface ProfileSetting {
|
|
name: string;
|
|
/** false: kept, but not offered (CLion's "Enable profile"). */
|
|
enabled?: boolean;
|
|
buildType?: string;
|
|
buildDirectory?: string;
|
|
generator?: string;
|
|
toolchainFile?: string;
|
|
cacheVariables?: Record<string, string | boolean>;
|
|
environment?: Record<string, string>;
|
|
configureArgs?: string[];
|
|
buildArgs?: string[];
|
|
}
|
|
|
|
export interface Profile {
|
|
/** Unique across both kinds: `settings:<name>` or `preset:<name>`. */
|
|
key: string;
|
|
name: string;
|
|
source: "settings" | "preset";
|
|
buildType: string;
|
|
buildDir: string;
|
|
generator?: string;
|
|
env: Record<string, string>;
|
|
/** cmake's arguments for configuring, run in the source directory. */
|
|
configureArgs: string[];
|
|
buildArgs: string[];
|
|
description?: string;
|
|
}
|
|
|
|
export const BUILD_TYPES = ["Debug", "Release", "RelWithDebInfo", "MinSizeRel"];
|
|
|
|
export function profileSettings(): ProfileSetting[] {
|
|
const list = settings().get<ProfileSetting[]>("profiles") ?? [];
|
|
return list.filter((p) => p && typeof p.name === "string" && p.name.trim());
|
|
}
|
|
|
|
function defaultGenerator(): string | undefined {
|
|
const g = settings().get<string>("generator")?.trim();
|
|
if (g) return g;
|
|
// CLion's default is Ninja when there is one.
|
|
return onPath("ninja") ? "Ninja" : undefined;
|
|
}
|
|
|
|
export function loadProfiles(sourceDir: string, workspaceFolder: string): Profile[] {
|
|
const cfg = settings();
|
|
const common: string[] = [];
|
|
if (cfg.get<boolean>("exportCompileCommands", true)) common.push("-DCMAKE_EXPORT_COMPILE_COMMANDS=ON");
|
|
common.push(...(cfg.get<string[]>("configureArgs") ?? []));
|
|
const globalEnv = cfg.get<Record<string, string>>("environment") ?? {};
|
|
|
|
const out: Profile[] = [];
|
|
for (const p of profileSettings()) {
|
|
if (p.enabled === false) continue;
|
|
const buildType = p.buildType?.trim() || "Debug";
|
|
const vars = { workspaceFolder, sourceDir, profile: p.name, buildType };
|
|
const buildDir = path.resolve(sourceDir, expand(p.buildDirectory?.trim() || `cmake-build-${p.name.toLowerCase().replace(/[^\w.-]+/g, "-")}`, vars));
|
|
const generator = p.generator?.trim() || defaultGenerator();
|
|
const args = ["-S", sourceDir, "-B", buildDir];
|
|
if (generator) args.push("-G", generator);
|
|
args.push(`-DCMAKE_BUILD_TYPE=${buildType}`);
|
|
if (p.toolchainFile) args.push(`-DCMAKE_TOOLCHAIN_FILE=${path.resolve(sourceDir, expand(p.toolchainFile, vars))}`);
|
|
for (const [k, v] of Object.entries(p.cacheVariables ?? {})) {
|
|
args.push(typeof v === "boolean" ? `-D${k}:BOOL=${v ? "ON" : "OFF"}` : `-D${k}=${expand(String(v), vars)}`);
|
|
}
|
|
args.push(...common, ...(p.configureArgs ?? []).map((a) => expand(a, vars)));
|
|
out.push({
|
|
key: `settings:${p.name}`,
|
|
name: p.name,
|
|
source: "settings",
|
|
buildType,
|
|
buildDir,
|
|
generator,
|
|
env: { ...globalEnv, ...expandAll(p.environment, vars) },
|
|
configureArgs: args,
|
|
buildArgs: (p.buildArgs ?? []).map((a) => expand(a, vars)),
|
|
});
|
|
}
|
|
|
|
if (cfg.get<boolean>("usePresets", true)) {
|
|
for (const p of loadConfigurePresets(sourceDir)) {
|
|
const buildDir = p.binaryDir ?? path.join(sourceDir, "build", p.name);
|
|
const args = ["--preset", p.name];
|
|
if (!p.binaryDir) args.push("-B", buildDir);
|
|
args.push(...common);
|
|
const env: Record<string, string> = { ...globalEnv };
|
|
// The preset's environment is CMake's business when configuring; building and running need it too.
|
|
for (const [k, v] of Object.entries(p.environment)) if (typeof v === "string" && !v.includes("$")) env[k] = v;
|
|
out.push({
|
|
key: `preset:${p.name}`,
|
|
name: p.displayName || p.name,
|
|
source: "preset",
|
|
buildType: p.buildType || "Debug",
|
|
buildDir,
|
|
generator: p.generator,
|
|
env,
|
|
configureArgs: args,
|
|
buildArgs: [],
|
|
description: p.description,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (!out.length) {
|
|
const buildDir = path.join(sourceDir, "cmake-build-debug");
|
|
const generator = defaultGenerator();
|
|
const args = ["-S", sourceDir, "-B", buildDir, ...(generator ? ["-G", generator] : []), "-DCMAKE_BUILD_TYPE=Debug", ...common];
|
|
out.push({ key: "default:Debug", name: "Debug", source: "settings", buildType: "Debug", buildDir, generator, env: globalEnv, configureArgs: args, buildArgs: [] });
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function expandAll(env: Record<string, string> | undefined, vars: Parameters<typeof expand>[1]): Record<string, string> {
|
|
const out: Record<string, string> = {};
|
|
for (const [k, v] of Object.entries(env ?? {})) out[k] = expand(String(v), vars);
|
|
return out;
|
|
}
|
|
|
|
export async function removeProfile(profile: Profile): Promise<void> {
|
|
if (profile.source !== "settings") return;
|
|
const list = (settings().get<ProfileSetting[]>("profiles") ?? []).filter((p) => p.name !== profile.name);
|
|
await settings().update("profiles", list, vscode.ConfigurationTarget.Workspace);
|
|
}
|