mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 06:14:59 +00:00
343 lines
9.4 KiB
TypeScript
343 lines
9.4 KiB
TypeScript
import { execSync, spawn } from 'child_process';
|
||
import * as fs from 'fs';
|
||
import * as path from 'path';
|
||
import * as os from 'os';
|
||
|
||
const COLORS = {
|
||
reset: '\x1b[0m',
|
||
bright: '\x1b[1m',
|
||
red: '\x1b[31m',
|
||
green: '\x1b[32m',
|
||
yellow: '\x1b[33m',
|
||
blue: '\x1b[34m',
|
||
magenta: '\x1b[35m',
|
||
cyan: '\x1b[36m',
|
||
} as const;
|
||
|
||
interface ExecResult {
|
||
success: boolean;
|
||
output?: string;
|
||
error?: string;
|
||
}
|
||
|
||
interface ExecOptions {
|
||
cwd?: string;
|
||
silent?: boolean;
|
||
args?: string[];
|
||
}
|
||
|
||
const log = {
|
||
info: (msg: string) => console.log(`${COLORS.blue}ℹ${COLORS.reset} ${msg}`),
|
||
success: (msg: string) => console.log(`${COLORS.green}✓${COLORS.reset} ${msg}`),
|
||
warn: (msg: string) => console.log(`${COLORS.yellow}⚠${COLORS.reset} ${msg}`),
|
||
error: (msg: string) => console.log(`${COLORS.red}✗${COLORS.reset} ${msg}`),
|
||
section: (msg: string) => console.log(`\n${COLORS.bright}${COLORS.cyan}${msg}${COLORS.reset}\n`),
|
||
};
|
||
|
||
export class CppBuildAssistant {
|
||
private projectRoot: string;
|
||
private cppDir: string;
|
||
private buildDir: string;
|
||
|
||
constructor(projectRoot?: string) {
|
||
this.projectRoot = projectRoot || path.join(__dirname, '..');
|
||
this.cppDir = path.join(this.projectRoot, 'cpp');
|
||
this.buildDir = path.join(this.cppDir, 'build');
|
||
}
|
||
|
||
private exec(command: string, options: ExecOptions = {}): ExecResult {
|
||
try {
|
||
const result = execSync(command, {
|
||
cwd: options.cwd || this.cppDir,
|
||
encoding: 'utf-8',
|
||
stdio: options.silent ? 'pipe' : 'inherit',
|
||
});
|
||
return { success: true, output: result as string };
|
||
} catch (error: any) {
|
||
return { success: false, error: error.message, output: error.stdout };
|
||
}
|
||
}
|
||
|
||
private checkCommand(command: string, name: string): boolean {
|
||
try {
|
||
execSync(`${command} --version`, { stdio: 'pipe' });
|
||
log.success(`${name} is installed`);
|
||
return true;
|
||
} catch {
|
||
log.error(`${name} is NOT installed`);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
checkDependencies(): boolean {
|
||
log.section('Checking Dependencies');
|
||
|
||
const deps = [
|
||
{ cmd: 'cmake', name: 'CMake' },
|
||
{ cmd: 'conan', name: 'Conan' },
|
||
{ cmd: 'ninja', name: 'Ninja' },
|
||
{ cmd: 'g++', name: 'GCC' },
|
||
];
|
||
|
||
const results = deps.map(({ cmd, name }) => ({
|
||
name,
|
||
installed: this.checkCommand(cmd, name),
|
||
}));
|
||
|
||
const allInstalled = results.every((r) => r.installed);
|
||
|
||
if (!allInstalled) {
|
||
log.warn('\nSome dependencies are missing. Install them:');
|
||
|
||
if (os.platform() === 'darwin') {
|
||
log.info(' brew install cmake conan ninja gcc');
|
||
} else if (os.platform() === 'linux') {
|
||
log.info(' sudo apt-get install cmake ninja-build g++');
|
||
log.info(' pip install conan');
|
||
} else if (os.platform() === 'win32') {
|
||
log.info(' choco install cmake conan ninja');
|
||
}
|
||
}
|
||
|
||
return allInstalled;
|
||
}
|
||
|
||
createConanfile(): boolean {
|
||
log.section('Checking Conanfile');
|
||
|
||
const conanfilePath = path.join(this.cppDir, 'conanfile.txt');
|
||
|
||
if (fs.existsSync(conanfilePath)) {
|
||
log.success('conanfile.txt exists');
|
||
return true;
|
||
}
|
||
|
||
log.info('Creating conanfile.txt...');
|
||
|
||
const conanfileContent = `[requires]
|
||
sqlite3/3.45.0
|
||
fmt/10.2.1
|
||
spdlog/1.13.0
|
||
nlohmann_json/3.11.3
|
||
|
||
[generators]
|
||
CMakeDeps
|
||
CMakeToolchain
|
||
|
||
[options]
|
||
sqlite3:shared=False
|
||
|
||
[layout]
|
||
cmake_layout
|
||
`;
|
||
|
||
fs.writeFileSync(conanfilePath, conanfileContent);
|
||
log.success('Created conanfile.txt');
|
||
return true;
|
||
}
|
||
|
||
installConanDeps(): boolean {
|
||
log.section('Installing Conan Dependencies');
|
||
|
||
const conanfilePath = path.join(this.cppDir, 'conanfile.txt');
|
||
if (!fs.existsSync(conanfilePath)) {
|
||
log.error('conanfile.txt not found');
|
||
return false;
|
||
}
|
||
|
||
log.info('Running conan install...');
|
||
|
||
const buildType = process.env.CMAKE_BUILD_TYPE || 'Release';
|
||
const result = this.exec(
|
||
`conan install . --output-folder=build --build=missing -s build_type=${buildType}`
|
||
);
|
||
|
||
if (!result.success) {
|
||
log.error('Conan install failed');
|
||
return false;
|
||
}
|
||
|
||
log.success('Conan dependencies installed');
|
||
return true;
|
||
}
|
||
|
||
configureCMake(buildType: 'Debug' | 'Release' = 'Release'): boolean {
|
||
log.section('Configuring CMake with Ninja');
|
||
|
||
if (!fs.existsSync(this.buildDir)) {
|
||
fs.mkdirSync(this.buildDir, { recursive: true });
|
||
}
|
||
|
||
log.info(`Build type: ${buildType}`);
|
||
|
||
const toolchainPath = path.join(this.buildDir, 'conan_toolchain.cmake');
|
||
const cmakeArgs = [
|
||
'-G Ninja',
|
||
`-DCMAKE_BUILD_TYPE=${buildType}`,
|
||
'-DCMAKE_EXPORT_COMPILE_COMMANDS=ON',
|
||
];
|
||
|
||
if (fs.existsSync(toolchainPath)) {
|
||
cmakeArgs.push(`-DCMAKE_TOOLCHAIN_FILE=${toolchainPath}`);
|
||
log.info('Using Conan toolchain');
|
||
}
|
||
|
||
const result = this.exec(`cmake -B build ${cmakeArgs.join(' ')} .`);
|
||
|
||
if (!result.success) {
|
||
log.error('CMake configuration failed');
|
||
return false;
|
||
}
|
||
|
||
log.success('CMake configured successfully');
|
||
return true;
|
||
}
|
||
|
||
build(target = 'all', jobs = os.cpus().length): boolean {
|
||
log.section('Building with Ninja');
|
||
|
||
log.info(`Building target: ${target}`);
|
||
log.info(`Using ${jobs} parallel jobs`);
|
||
|
||
const result = this.exec(`cmake --build build --target ${target} -j ${jobs}`);
|
||
|
||
if (!result.success) {
|
||
log.error('Build failed');
|
||
return false;
|
||
}
|
||
|
||
log.success('Build completed successfully');
|
||
return true;
|
||
}
|
||
|
||
test(): boolean {
|
||
log.section('Running Tests');
|
||
|
||
const result = this.exec('ctest --test-dir build --output-on-failure');
|
||
|
||
if (!result.success) {
|
||
log.error('Tests failed');
|
||
return false;
|
||
}
|
||
|
||
log.success('All tests passed');
|
||
return true;
|
||
}
|
||
|
||
clean(): boolean {
|
||
log.section('Cleaning Build Artifacts');
|
||
|
||
if (fs.existsSync(this.buildDir)) {
|
||
fs.rmSync(this.buildDir, { recursive: true, force: true });
|
||
log.success('Build directory removed');
|
||
} else {
|
||
log.info('Build directory does not exist');
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
async run(args: string[]): Promise<boolean> {
|
||
const command = args[0] || 'help';
|
||
const options = args.slice(1);
|
||
|
||
const buildType = options.includes('--debug') ? 'Debug' : 'Release';
|
||
const jobsArg = options.find((o) => o.startsWith('--jobs='));
|
||
const jobs = jobsArg ? parseInt(jobsArg.split('=')[1]) : os.cpus().length;
|
||
|
||
switch (command) {
|
||
case 'check':
|
||
return this.checkDependencies();
|
||
|
||
case 'init':
|
||
return this.createConanfile();
|
||
|
||
case 'install':
|
||
if (!this.checkDependencies()) return false;
|
||
return this.installConanDeps();
|
||
|
||
case 'configure':
|
||
if (!this.checkDependencies()) return false;
|
||
return this.configureCMake(buildType as 'Debug' | 'Release');
|
||
|
||
case 'build':
|
||
if (!this.checkDependencies()) return false;
|
||
const target = options.find((o) => !o.startsWith('--')) || 'all';
|
||
return this.build(target, jobs);
|
||
|
||
case 'test':
|
||
return this.test();
|
||
|
||
case 'clean':
|
||
return this.clean();
|
||
|
||
case 'rebuild':
|
||
this.clean();
|
||
if (!this.checkDependencies()) return false;
|
||
if (!this.configureCMake(buildType as 'Debug' | 'Release')) return false;
|
||
return this.build('all', jobs);
|
||
|
||
case 'full':
|
||
log.section('Full Build Workflow');
|
||
if (!this.checkDependencies()) return false;
|
||
if (!this.createConanfile()) return false;
|
||
if (!this.installConanDeps()) return false;
|
||
if (!this.configureCMake(buildType as 'Debug' | 'Release')) return false;
|
||
return this.build('all', jobs);
|
||
|
||
case 'help':
|
||
default:
|
||
this.showHelp();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
private showHelp(): void {
|
||
console.log(`
|
||
${COLORS.bright}C++ Build Assistant${COLORS.reset} - Conan + Ninja Build Helper
|
||
|
||
${COLORS.cyan}USAGE:${COLORS.reset}
|
||
npm run cpp:build [command] [options]
|
||
|
||
${COLORS.cyan}COMMANDS:${COLORS.reset}
|
||
${COLORS.green}check${COLORS.reset} Check if all dependencies are installed
|
||
${COLORS.green}init${COLORS.reset} Initialize project (create conanfile if missing)
|
||
${COLORS.green}install${COLORS.reset} Install Conan dependencies
|
||
${COLORS.green}configure${COLORS.reset} Configure CMake with Ninja generator
|
||
${COLORS.green}build${COLORS.reset} [target] Build the project (default: all)
|
||
${COLORS.green}test${COLORS.reset} Run tests with CTest
|
||
${COLORS.green}clean${COLORS.reset} Remove build artifacts
|
||
${COLORS.green}rebuild${COLORS.reset} Clean and rebuild
|
||
${COLORS.green}full${COLORS.reset} Full workflow: check → install → configure → build
|
||
${COLORS.green}help${COLORS.reset} Show this help message
|
||
|
||
${COLORS.cyan}OPTIONS:${COLORS.reset}
|
||
--debug Use Debug build type
|
||
--release Use Release build type (default)
|
||
--jobs=N Number of parallel build jobs (default: CPU count)
|
||
|
||
${COLORS.cyan}EXAMPLES:${COLORS.reset}
|
||
npm run cpp:build check
|
||
npm run cpp:build full
|
||
npm run cpp:build build dbal_daemon
|
||
npm run cpp:build build -- --debug
|
||
npm run cpp:build test
|
||
`);
|
||
}
|
||
}
|
||
|
||
if (require.main === module) {
|
||
const assistant = new CppBuildAssistant(path.join(__dirname, '..'));
|
||
const args = process.argv.slice(2);
|
||
|
||
assistant
|
||
.run(args)
|
||
.then((success) => {
|
||
process.exit(success ? 0 : 1);
|
||
})
|
||
.catch((error) => {
|
||
log.error(error.message || error);
|
||
process.exit(1);
|
||
});
|
||
}
|