Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions frontend/build-python.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env node

// Cross-platform Python build script
const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');

console.log('🚀 Starting Python build process...');

// Get the directory where this script is located
const scriptDir = __dirname;
const externalsDir = path.join(scriptDir, 'externals', 'python');

// Function to run shell commands
function runCommand(command, options = {}) {
try {
console.log(`Running: ${command}`);
execSync(command, {
stdio: 'inherit',
cwd: options.cwd || scriptDir,
shell: true,
...options
});
} catch (error) {
console.error(`❌ Command failed: ${command}`);
throw error;
}
}

// Function to check if executable exists
function executableExists(componentName) {
const executablePathDir = path.join(externalsDir, componentName, 'dist', componentName, componentName);
const executablePathFile = path.join(externalsDir, componentName, 'dist', componentName);

return fs.existsSync(executablePathDir) || fs.existsSync(executablePathFile);
}

// Function to build Python executable
function buildPythonExecutable(componentName) {
console.log(`\n--- Building: ${componentName} ---`);

const componentDir = path.join(externalsDir, componentName);

if (!fs.existsSync(componentDir)) {
console.error(`❌ Error: Directory not found at ${componentDir}`);
return false;
}

console.log(`📂 Navigating to ${componentDir}`);

// Check if executable already exists
if (executableExists(componentName)) {
console.log(`✅ Executable for ${componentName} already exists. Skipping build.`);
return true;
}

console.log(`🔎 Executable not found for ${componentName}. Proceeding with build...`);

try {
// Create virtual environment
console.log('🐍 Creating Python virtual environment...');
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
runCommand(`${pythonCmd} -m venv venv`, { cwd: componentDir });

// Activate virtual environment and install dependencies
console.log('🐍 Activating virtual environment...');
const pipCmd = process.platform === 'win32' ? 'venv\\Scripts\\pip' : 'venv/bin/pip3';

// Install requirements if exists
const requirementsPath = path.join(componentDir, 'requirements.txt');
if (fs.existsSync(requirementsPath)) {
console.log('📦 Installing dependencies from requirements.txt...');
runCommand(`${pipCmd} install -r requirements.txt`, { cwd: componentDir });
} else {
console.log(`⚠️ Warning: requirements.txt not found in ${componentDir}.`);
}

// Install PyInstaller
console.log('📦 Installing PyInstaller...');
runCommand(`${pipCmd} install pyinstaller`, { cwd: componentDir });

// Build with PyInstaller
const specPath = path.join(componentDir, `${componentName}.spec`);
if (fs.existsSync(specPath)) {
console.log(`🛠️ Building executable with PyInstaller (${componentName}.spec)...`);
const pyinstallerCmd = process.platform === 'win32' ? 'venv\\Scripts\\pyinstaller' : 'venv/bin/pyinstaller';
runCommand(`${pyinstallerCmd} ${componentName}.spec`, { cwd: componentDir });
} else {
console.error(`❌ Error: ${componentName}.spec not found. Cannot build.`);
return false;
}

// Verify build output
console.log('🔎 Verifying build output...');
if (executableExists(componentName)) {
console.log(`✅ Successfully built ${componentName}.`);
return true;
} else {
console.error(`❌ Error: Build verification failed. Executable not found in 'dist/' directory after build.`);
return false;
}

} catch (error) {
console.error(`❌ Error building ${componentName}:`, error.message);
return false;
}
}

// Build all components
const components = ['window_inspector', 'window_capture'];
let allSuccess = true;

// Skip macOS-specific tools on Windows
if (process.platform === 'win32') {
console.log('⚠️ Skipping macOS-specific Python tools on Windows platform');
console.log(' These tools (window_inspector, window_capture) are designed for macOS only');
console.log(' and use the Quartz framework which is not available on Windows.');
console.log('✅ Build process completed (skipped macOS tools)');
process.exit(0);
}

for (const component of components) {
if (!buildPythonExecutable(component)) {
allSuccess = false;
}
}

if (allSuccess) {
console.log('\n🎉 All Python executables have been built successfully!');
process.exit(0);
} else {
console.log('\n❌ Some Python executables failed to build.');
process.exit(1);
}
6 changes: 3 additions & 3 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "npm run typecheck:node && npm run typecheck:web",
"start": "electron-vite preview",
"build:externals": "chmod +x build-python.sh",
"build:externals": "node -e \"if(process.platform!=='win32')require('child_process').execSync('chmod +x build-python.sh')\"",
"dev": "npm run copy-backend && npm run build && electron-vite dev",
"build": "npm run build:externals && ./build-python.sh && npm run typecheck && rm -rf ./dist && electron-vite build",
"build": "npm run build:externals && node build-python.js && npm run typecheck && node -e \"if(process.platform==='win32'){require('fs').rmSync('./dist',{recursive:true,force:true})}else{require('child_process').execSync('rm -rf ./dist')}\" && electron-vite build",
"postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win",
Expand Down Expand Up @@ -142,4 +142,4 @@
"esbuild"
]
}
}
}
2 changes: 1 addition & 1 deletion frontend/scripts/copy-prebuilt-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ console.log('📦 Copying pre-built backend executable...')
// Setup paths
const backendDir = path.join(__dirname, '..', 'backend')
const sourceDir = path.join(__dirname, '..', '..')
const executableName = 'main'
const executableName = process.platform === 'win32' ? 'main.exe' : 'main'
const sourceExecutablePath = path.join(sourceDir, 'dist', executableName)
const destExecutablePath = path.join(backendDir, executableName)

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/main/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ async function startBackendServer(mainWindow: BrowserWindow) {

return new Promise((resolve, reject) => {
try {
const executableName = 'main'
const executableName = process.platform === 'win32' ? 'main.exe' : 'main'

let actualResourcesPath = getResourcesPath()
logToBackendFile(`Initial resources path: ${actualResourcesPath}`)
Expand Down