Spaces:
Sleeping
Sleeping
File size: 8,008 Bytes
1ea9c72 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
#!/usr/bin/env python
"""
Async LaTeX compilation handler
Works efficiently on Linux/HF Spaces with forking
Falls back to sequential on Windows
"""
import os
import sys
import subprocess
import platform
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from pathlib import Path
import time
def is_linux():
"""Check if running on Linux/Unix"""
return platform.system() in ['Linux', 'Darwin']
def compile_latex_file(tex_path, output_dir=None, timeout=30):
"""
Compile a single LaTeX file to PDF
Args:
tex_path: Path to .tex file
output_dir: Output directory (default: same as tex file)
timeout: Compilation timeout in seconds
Returns:
tuple: (success: bool, pdf_path: str or None, error_msg: str or None)
"""
tex_path = Path(tex_path)
if not tex_path.exists():
return False, None, f"File not found: {tex_path}"
output_dir = output_dir or tex_path.parent
pdf_path = output_dir / tex_path.with_suffix('.pdf').name
# Remove old PDF if exists
if pdf_path.exists():
try:
pdf_path.unlink()
except:
pass
# Compile command
cmd = [
'pdflatex',
'-interaction=nonstopmode',
'-halt-on-error',
f'-output-directory={output_dir}',
str(tex_path)
]
try:
# Run compilation
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout,
cwd=str(tex_path.parent)
)
# Check if PDF was created
if pdf_path.exists():
return True, str(pdf_path), None
else:
# Extract error from log
error_msg = "Compilation failed"
if result.stdout:
lines = result.stdout.split('\n')
for i, line in enumerate(lines):
if 'Error' in line or '!' in line[:2]:
error_msg = '\n'.join(lines[i:i+5])
break
return False, None, error_msg
except subprocess.TimeoutExpired:
return False, None, f"Timeout after {timeout} seconds"
except FileNotFoundError:
return False, None, "pdflatex not found - install texlive"
except Exception as e:
return False, None, str(e)
def compile_latex_batch(tex_files, output_dir=None, max_workers=4, timeout=30):
"""
Compile multiple LaTeX files in parallel
Args:
tex_files: List of .tex file paths
output_dir: Output directory for PDFs
max_workers: Number of parallel workers
timeout: Timeout per file
Returns:
dict: {tex_path: (success, pdf_path, error_msg)}
"""
results = {}
if not tex_files:
return results
# Use ProcessPoolExecutor on Linux for true parallelism
# Use ThreadPoolExecutor on Windows (less efficient but works)
if is_linux():
executor_class = ProcessPoolExecutor
print(f"Using process-based parallelism ({max_workers} workers)")
else:
executor_class = ThreadPoolExecutor
print(f"Using thread-based parallelism ({max_workers} workers)")
with executor_class(max_workers=max_workers) as executor:
# Submit all compilation tasks
futures = {
executor.submit(compile_latex_file, tex_file, output_dir, timeout): tex_file
for tex_file in tex_files
}
# Collect results as they complete
for future in futures:
tex_file = futures[future]
try:
success, pdf_path, error = future.result(timeout=timeout+5)
results[tex_file] = (success, pdf_path, error)
if success:
print(f" ✓ Compiled: {Path(tex_file).name}")
else:
print(f" ✗ Failed: {Path(tex_file).name}")
except Exception as e:
results[tex_file] = (False, None, str(e))
print(f" ✗ Error: {Path(tex_file).name}: {e}")
return results
def compile_latex_async(tex_path, output_dir=None, callback=None):
"""
Compile LaTeX file asynchronously (fire-and-forget)
Args:
tex_path: Path to .tex file
output_dir: Output directory
callback: Optional callback function(success, pdf_path, error)
"""
if is_linux():
# On Linux, fork a subprocess
pid = os.fork()
if pid == 0:
# Child process
try:
success, pdf_path, error = compile_latex_file(tex_path, output_dir)
if callback:
callback(success, pdf_path, error)
finally:
os._exit(0)
else:
# Parent process continues immediately
print(f" → Compiling {Path(tex_path).name} in background (PID: {pid})")
else:
# On Windows, use threading
from threading import Thread
def compile_thread():
success, pdf_path, error = compile_latex_file(tex_path, output_dir)
if callback:
callback(success, pdf_path, error)
thread = Thread(target=compile_thread, daemon=True)
thread.start()
print(f" → Compiling {Path(tex_path).name} in background thread")
def check_latex_available():
"""Check if pdflatex is available"""
try:
result = subprocess.run(
['pdflatex', '--version'],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
# Extract version
for line in result.stdout.split('\n'):
if 'TeX' in line:
print(f"LaTeX available: {line.strip()}")
return True
return False
except:
return False
# Integration with universal_validator.py
def setup_async_latex_compilation():
"""
Setup async LaTeX compilation for the validator
Returns a function that can be used to compile LaTeX files
"""
if not check_latex_available():
print("Warning: LaTeX not available, PDF compilation disabled")
return None
def compile_reconciliation(tex_path):
"""Compile reconciliation document asynchronously"""
compile_latex_async(
tex_path,
callback=lambda s, p, e: print(f" [PDF] {'Success' if s else 'Failed'}: {Path(tex_path).name}")
)
return compile_reconciliation
if __name__ == "__main__":
# Test the compiler
import tempfile
print("Testing LaTeX compilation...")
print(f"Platform: {platform.system()}")
print(f"Async support: {'Yes' if is_linux() else 'Limited (Windows)'}")
if check_latex_available():
# Create a test document
with tempfile.NamedTemporaryFile(mode='w', suffix='.tex', delete=False) as f:
f.write(r"""\documentclass{article}
\begin{document}
\title{Test Document}
\author{Validator}
\maketitle
This is a test: $x^2 + y^2 = z^2$
\end{document}""")
test_file = f.name
print(f"\nCompiling test file: {test_file}")
success, pdf_path, error = compile_latex_file(test_file)
if success:
print(f"✓ Success! PDF created: {pdf_path}")
print(f" Size: {os.path.getsize(pdf_path)} bytes")
else:
print(f"✗ Failed: {error}")
# Clean up
try:
os.unlink(test_file)
if pdf_path and os.path.exists(pdf_path):
os.unlink(pdf_path)
except:
pass
else:
print("✗ LaTeX not installed")
print(" On Linux: apt-get install texlive-latex-base")
print(" On Windows: Install MiKTeX")
print(" On macOS: brew install --cask mactex") |