Files
metabuilder/dbal/shared/tools/cpp-build-assistant.cjs

354 lines
9.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
const { execSync, spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const os = require('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',
};
const log = {
info: (msg) => console.log(`${COLORS.blue}${COLORS.reset} ${msg}`),
success: (msg) => console.log(`${COLORS.green}${COLORS.reset} ${msg}`),
warn: (msg) => console.log(`${COLORS.yellow}${COLORS.reset} ${msg}`),
error: (msg) => console.log(`${COLORS.red}${COLORS.reset} ${msg}`),
section: (msg) => console.log(`\n${COLORS.bright}${COLORS.cyan}${msg}${COLORS.reset}\n`),
};
class CppBuildAssistant {
constructor(projectRoot) {
this.projectRoot = projectRoot || path.join(__dirname, '..');
this.cppDir = path.join(this.projectRoot, 'cpp');
this.buildDir = path.join(this.cppDir, 'build');
this.conanDir = path.join(this.cppDir, '.conan');
}
exec(command, options = {}) {
try {
const result = execSync(command, {
cwd: options.cwd || this.cppDir,
encoding: 'utf-8',
stdio: options.silent ? 'pipe' : 'inherit',
});
return { success: true, output: result };
} catch (error) {
return { success: false, error: error.message, output: error.stdout };
}
}
async execAsync(command, options = {}) {
return new Promise((resolve, reject) => {
const proc = spawn(command, options.args || [], {
cwd: options.cwd || this.cppDir,
shell: true,
stdio: 'inherit',
});
proc.on('close', (code) => {
if (code === 0) {
resolve({ success: true });
} else {
reject({ success: false, code });
}
});
proc.on('error', (error) => {
reject({ success: false, error: error.message });
});
});
}
checkCommand(command, name) {
try {
execSync(`${command} --version`, { stdio: 'pipe' });
log.success(`${name} is installed`);
return true;
} catch {
log.error(`${name} is NOT installed`);
return false;
}
}
checkDependencies() {
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() {
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() {
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 = 'Release') {
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');
let 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) {
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() {
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() {
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;
}
showHelp() {
console.log(`
${COLORS.bright}C++ Build Assistant${COLORS.reset} - Conan + Ninja Build Helper
${COLORS.cyan}USAGE:${COLORS.reset}
node cpp-build-assistant.js [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}
node cpp-build-assistant.js check
node cpp-build-assistant.js full
node cpp-build-assistant.js build dbal_daemon
node cpp-build-assistant.js build --debug
node cpp-build-assistant.js test
`);
}
async run(args) {
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);
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)) 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)) return false;
return this.build('all', jobs);
case 'help':
default:
this.showHelp();
return true;
}
}
}
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);
});
}
module.exports = CppBuildAssistant;