import gradio as gr import os import subprocess import sys import shutil import uuid import platform import time from pathlib import Path import tempfile import glob def ensure_dir(dir_path): """Ensure directory exists""" Path(dir_path).mkdir(parents=True, exist_ok=True) def check_dependencies(): """Check if required dependencies are available""" missing_deps = [] # Check for patchelf (usually available in HF Spaces) result = subprocess.run(["which", "patchelf"], capture_output=True) if result.returncode != 0: missing_deps.append("patchelf") # Check for gcc (usually available in HF Spaces) result = subprocess.run(["which", "gcc"], capture_output=True) if result.returncode != 0: missing_deps.append("gcc") return missing_deps def check_static_libpython(): """Check if static libpython is available""" try: # Try to find static libpython result = subprocess.run( [sys.executable, "-c", "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))"], capture_output=True, text=True ) if result.returncode == 0: libdir = result.stdout.strip() # Look for libpython.a files static_libs = glob.glob(os.path.join(libdir, "libpython*.a")) return len(static_libs) > 0 except: pass return False def get_current_python_version(): """Get the current Python version for compatibility notes""" return f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" def get_nuitka_version(): """Get the current Nuitka version to handle different command line options""" try: result = subprocess.run([sys.executable, "-m", "nuitka", "--version"], capture_output=True, text=True) if result.returncode == 0: version_line = result.stdout.strip().split('\n')[0] # Extract version number from output like "Nuitka 2.5.0" version = version_line.split()[-1] return version return "unknown" except: return "unknown" def install_system_packages(packages_content, progress=gr.Progress()): """Note about system packages in HF Spaces - they cannot be installed""" if not packages_content.strip(): return "No system packages specified." # In HF Spaces, we can't install system packages packages_list = [line.strip() for line in packages_content.strip().split('\n') if line.strip() and not line.strip().startswith('#')] if packages_list: return f"❌ System packages cannot be installed in Hugging Face Spaces:\n{', '.join(packages_list)}\n\nℹ️ HF Spaces runs in a containerized environment without sudo access.\nThese packages need to be pre-installed in the Docker image or available as Python packages." return "No system packages specified." def find_compiled_binary(output_dir, output_filename): """Find the compiled binary, checking different possible paths""" # Try direct path first direct_path = os.path.join(output_dir, output_filename) if os.path.exists(direct_path): return direct_path # Try in .dist folder (standalone builds) dist_path = os.path.join(output_dir, "user_script.dist", output_filename) if os.path.exists(dist_path): return dist_path # Try using glob to find any executable patterns = [ os.path.join(output_dir, "**", output_filename), os.path.join(output_dir, "**", "user_script"), os.path.join(output_dir, "**", "*.bin"), os.path.join(output_dir, "**", "*.exe") ] for pattern in patterns: matches = glob.glob(pattern, recursive=True) if matches: return matches[0] return None def compile_with_nuitka(code, requirements, packages, compilation_mode, output_extension, progress=gr.Progress()): """Compile Python code with Nuitka""" progress(0, desc="Starting compilation process...") # Check Nuitka version nuitka_version = get_nuitka_version() # Check if static libpython is available has_static_libpython = check_static_libpython() # Check dependencies first missing_deps = check_dependencies() # Create unique ID for this compilation job_id = str(uuid.uuid4()) base_dir = os.path.join(os.getcwd(), "user_code") job_dir = os.path.join(base_dir, job_id) output_dir = os.path.join(os.getcwd(), "compiled_output", job_id) # Create directories ensure_dir(job_dir) ensure_dir(output_dir) progress(0.1, desc="Processing packages...") # Handle system packages (just log them, can't install in HF Spaces) packages_result = install_system_packages(packages, progress) # Write code to a Python file script_path = os.path.join(job_dir, "user_script.py") with open(script_path, "w") as f: f.write(code) # Handle requirements install_result = "No Python requirements specified." if requirements.strip(): req_path = os.path.join(job_dir, "requirements.txt") with open(req_path, "w") as f: f.write(requirements) try: progress(0.2, desc="Installing Python requirements...") install_process = subprocess.run( [sys.executable, "-m", "pip", "install", "--no-cache-dir", "-r", req_path], capture_output=True, text=True ) if install_process.returncode == 0: install_result = "✅ Python requirements installed successfully." else: install_result = f"⚠️ Installation completed with warnings. Return code: {install_process.returncode}\n{install_process.stderr}" except Exception as e: install_result = f"❌ Error installing requirements: {str(e)}" return install_result, None, f"Error: {str(e)}", False # Compilation try: progress(0.3, desc="Starting compilation...") # Build compilation command based on mode if compilation_mode == "Maximum Compatibility (Recommended)": cmd = [ sys.executable, "-m", "nuitka", "--standalone", "--onefile", # Single portable file "--show-progress", "--remove-output", "--follow-imports", "--assume-yes-for-downloads", # Auto-download missing dependencies "--python-flag=no_site", # Reduce dependencies "--python-flag=no_warnings", # Reduce warnings script_path, f"--output-dir={output_dir}" ] # Add static linking if available if has_static_libpython: cmd.append("--static-libpython=yes") mode_name = "Maximum Compatibility Binary" elif compilation_mode == "Portable Binary": cmd = [ sys.executable, "-m", "nuitka", "--show-progress", "--remove-output", "--assume-yes-for-downloads", "--python-flag=no_site", "--python-flag=no_warnings", script_path, f"--output-dir={output_dir}" ] mode_name = "Portable Non-Standalone" else: # Standalone Binary cmd = [ sys.executable, "-m", "nuitka", "--standalone", "--onefile", "--show-progress", "--remove-output", "--assume-yes-for-downloads", "--python-flag=no_site", "--python-flag=no_warnings", script_path, f"--output-dir={output_dir}" ] mode_name = "Standalone Binary" # Run compilation process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True ) # Progress tracking compile_output = "" line_count = 0 # Real-time progress display for line in iter(process.stdout.readline, ''): compile_output += line line_count += 1 # Update progress progress_val = 0.3 + (min(line_count / 200, 0.69) * 0.7) if "INFO:" in line: progress(progress_val, desc=f"Compiling: {line.strip()[:50]}...") else: progress(progress_val, desc="Compiling...") process.wait() progress(0.9, desc="Finalizing...") # Find the compiled binary output_filename = f"user_script{output_extension}" binary_path = find_compiled_binary(output_dir, output_filename) # If not found with expected extension, try finding any executable if not binary_path: # Try common executable patterns for onefile patterns = [ os.path.join(output_dir, "user_script"), os.path.join(output_dir, "user_script.bin"), os.path.join(output_dir, "**", "user_script"), os.path.join(output_dir, "**", "*.bin"), ] for pattern in patterns: matches = glob.glob(pattern, recursive=True) if matches: binary_path = matches[0] break if process.returncode == 0 and binary_path: # Check if it's really a binary file try: file_process = subprocess.run(["file", binary_path], capture_output=True, text=True) binary_info = file_process.stdout except: binary_info = "Binary file (unable to get detailed info)" # Check linking type try: ldd_process = subprocess.run(["ldd", binary_path], capture_output=True, text=True) if "not a dynamic executable" in ldd_process.stderr or "statically linked" in ldd_process.stdout: linking_info = "✅ Statically linked - fully portable!" else: # Check what dynamic libraries are required if ldd_process.returncode == 0: libs = ldd_process.stdout.count("=>") linking_info = f"🔗 Dynamically linked ({libs} libraries) - designed for maximum compatibility" else: linking_info = "ℹ️ Compiled binary - should work on compatible systems" except: linking_info = "ℹ️ Compiled binary created successfully" # Rename to desired extension if output_extension in ['.bin', '.sh'] and not binary_path.endswith(output_extension): new_binary_path = binary_path + output_extension shutil.move(binary_path, new_binary_path) binary_path = new_binary_path # Make executable os.chmod(binary_path, 0o755) # Current Python version info current_python = get_current_python_version() # Build the result summary string static_status = "Yes" if has_static_libpython else "No" file_size = os.path.getsize(binary_path) / 1024 binary_basename = os.path.basename(binary_path) # Create result summary without f-string to avoid syntax issues result_summary = f"""# ✅ Compilation Successful! ## Compilation Details: - **Mode**: {mode_name} - **Nuitka Version**: {nuitka_version} - **Exit Code**: {process.returncode} - **Output Path**: {binary_path} - **File Size**: {file_size:.2f} KB - **Compiled with Python**: {current_python} - **Static Libpython Available**: {static_status} - **Linking**: {linking_info} ## Environment Results: **System Packages**: {packages_result} **Python Requirements**: {install_result} ## Binary Information: {binary_info} ## 🚀 Portability Notes: - This binary was compiled with maximum compatibility settings - Using --onefile for single-file distribution - Added --assume-yes-for-downloads for automatic dependency resolution - Used --python-flag=no_site to reduce system dependencies - Should work on most compatible Linux systems ## 📋 Usage Instructions: ```bash chmod +x {binary_basename} ./{binary_basename} ``` ## ⚠️ HF Spaces Notice: This binary was compiled in a Hugging Face Spaces environment. For best results: 1. Download the binary to your local system 2. Make it executable: `chmod +x filename` 3. Run it in a compatible Linux environment""" progress(1.0, desc="Compilation successful!") return result_summary, binary_path, compile_output, True else: error_summary = f"""# ❌ Compilation Failed ## Error Details: - **Exit Code**: {process.returncode} - **Mode Attempted**: {mode_name} ## Environment Results: **System Packages**: {packages_result} **Python Requirements**: {install_result} ## Possible Solutions: 1. Check your code for syntax errors 2. Ensure all imports are available 3. Try a different compilation mode 4. Review the compilation logs below ## Missing Dependencies: {', '.join(missing_deps) if missing_deps else 'None detected'}""" return error_summary, None, compile_output, False except Exception as e: error_summary = f"""# ❌ Compilation Error ## Error: {str(e)} ## Environment Results: **System Packages**: {packages_result} **Python Requirements**: {install_result}""" return error_summary, None, f"Error: {str(e)}", False def run_compiled_binary(binary_path): """Run the compiled binary and return the output""" if not binary_path or not os.path.exists(binary_path): return "❌ No binary available to run." try: # Make the binary executable os.chmod(binary_path, 0o755) # Run the binary with timeout process = subprocess.run( [binary_path], capture_output=True, text=True, timeout=30, # Increased timeout for HF Spaces cwd=os.path.dirname(binary_path) # Run in binary's directory ) output = "" if process.stdout: output += f"## [STDOUT]\n```\n{process.stdout}\n```\n" if process.stderr: output += f"## [STDERR]\n```\n{process.stderr}\n```\n" if process.returncode != 0: output += f"## [EXIT CODE]\n{process.returncode}\n" if not output: output = "✅ Binary executed successfully with no output." else: output = "## 🧪 Execution Results\n" + output return output except subprocess.TimeoutExpired: return "⏱️ **Execution timed out after 30 seconds.**\n\nThis might indicate:\n- The program is waiting for input\n- An infinite loop\n- Long-running computation" except Exception as e: return f"❌ **Error running the binary:**\n\n```\n{str(e)}\n```" # Create Gradio interface with gr.Blocks(title="Nuitka Python Compiler for HF Spaces", theme=gr.themes.Soft()) as app: gr.Markdown("# 🚀 Nuitka Python Compiler (HF Spaces Edition)") gr.Markdown("Convert your Python code into portable executables using Nuitka, optimized for Hugging Face Spaces.") # Check environment status has_static = check_static_libpython() missing_deps = check_dependencies() if has_static: gr.Markdown("🎯 **Static Libpython Available!** Maximum portability enabled.") else: gr.Markdown("🔧 **Using alternative portable options.** Static libpython not available.") if missing_deps: gr.Markdown(f"⚠️ **Missing dependencies:** {', '.join(missing_deps)}") else: gr.Markdown("✅ **All required dependencies available!**") # HF Spaces specific notice gr.Markdown(""" > ℹ️ **Running in Hugging Face Spaces**: System packages cannot be installed. > All required dependencies should be pre-installed in the environment. """) with gr.Tabs(): with gr.TabItem("🔧 Compiler"): with gr.Row(): with gr.Column(scale=2): code_input = gr.Code( value="""# Your Python code here print('Hello from compiled Python!') print('This is a smart-compiled binary!') # This will work with automatic compatibility detection import os, sys print(f'Running from: {os.getcwd()}') print(f'Python executable: {sys.executable}') print('Compilation was optimized for your environment!') # Simple example with user input name = input('What is your name? ') print(f'Hello, {name}!')""", language="python", label="Your Python Code", lines=20 ) with gr.Column(scale=1): with gr.Tabs(): with gr.TabItem("Python Requirements"): requirements_input = gr.Textbox( placeholder="""# Add your Python dependencies here # Example: # numpy==1.24.0 # pandas==2.0.0 # requests>=2.28.0 # matplotlib # pillow""", lines=8, label="requirements.txt content" ) with gr.TabItem("System Packages"): gr.Markdown("⚠️ **System packages cannot be installed in HF Spaces**") packages_input = gr.Textbox( placeholder="""# System packages (for reference only) # These cannot be installed in HF Spaces # build-essential # libssl-dev # ffmpeg""", lines=8, label="packages.txt content (Reference Only)", interactive=True ) # Fixed dropdown choices compilation_mode = gr.Dropdown( choices=[ "Maximum Compatibility (Recommended)", "Portable Binary", "Standalone Binary" ], value="Maximum Compatibility (Recommended)", label="Compilation Mode" ) output_extension = gr.Dropdown( choices=[".bin", ".sh"], value=".bin", label="Output File Extension" ) gr.Markdown(f"📍 **Compiling with Python {get_current_python_version()}**") if check_static_libpython(): gr.Markdown("🔗 **Static libpython will be used!**") else: gr.Markdown("🔧 **Using portable compilation flags**") compile_btn = gr.Button("🚀 Compile with Nuitka", variant="primary") # Results section with gr.Column(visible=False) as results_section: with gr.Accordion("📊 Compilation Results", open=True): result_summary = gr.Markdown() with gr.Accordion("📜 Compilation Logs", open=False): compile_logs = gr.Textbox(label="Detailed Compilation Output", lines=15) download_file = gr.File(label="📥 Download Compiled Binary") # Test run section with gr.Row(): run_btn = gr.Button("🧪 Test Run Binary", variant="secondary") run_output = gr.Markdown(label="Execution Output") # Variables to store state current_binary_path = gr.State(None) compilation_success = gr.State(False) def handle_compilation(code, requirements, packages, mode, extension, progress=gr.Progress()): summary, binary_path, logs, success = compile_with_nuitka( code, requirements, packages, mode, extension, progress ) if success and binary_path: # Create download file download_filename = f"compiled_program{extension}" download_path = os.path.join(os.path.dirname(binary_path), download_filename) shutil.copy2(binary_path, download_path) return ( gr.update(visible=True), # results_section gr.update(value=summary), # result_summary gr.update(value=logs), # compile_logs gr.update(value=download_path), # download_file binary_path, # current_binary_path state True # compilation_success state ) else: return ( gr.update(visible=True), # results_section gr.update(value=summary), # result_summary gr.update(value=logs), # compile_logs gr.update(visible=False), # download_file None, # current_binary_path state False # compilation_success state ) def handle_run(binary_path): if binary_path: output = run_compiled_binary(binary_path) return gr.update(value=output) else: return gr.update(value="❌ No binary available to run.") compile_btn.click( handle_compilation, inputs=[code_input, requirements_input, packages_input, compilation_mode, output_extension], outputs=[results_section, result_summary, compile_logs, download_file, current_binary_path, compilation_success] ) run_btn.click( handle_run, inputs=[current_binary_path], outputs=[run_output] ) with gr.TabItem("📖 How to Use"): gr.Markdown(""" ## 🎯 Smart Compilation in HF Spaces **Automatic Environment Detection** This app automatically detects your Python environment and chooses the best compilation strategy: - Uses static libpython if available (maximum portability) - Falls back to highly portable alternatives if not - Automatically handles missing dependencies - Optimized specifically for Hugging Face Spaces environment ## 📋 Usage Instructions ### 1. Write Your Code - Enter your Python code in the code editor - Add any Python package requirements in the requirements tab ### 2. Choose Compilation Mode - **Maximum Compatibility**: Best for most use cases (recommended) - **Portable Binary**: Smaller size, may need Python runtime - **Standalone Binary**: Includes all dependencies (larger) ### 3. Compile - Click "Compile with Nuitka" - Wait for the compilation to complete - Download the resulting binary ### 4. Run Your Binary ```bash # On Linux (including WSL) chmod +x compiled_program.bin ./compiled_program.bin # In WSL, copy to Linux filesystem first cp /mnt/c/Users/username/Downloads/compiled_program.bin ~/ cd ~ chmod +x compiled_program.bin ./compiled_program.bin ``` ## ⚠️ HF Spaces Limitations - **System packages cannot be installed** (no apt-get access) - All required system dependencies must be pre-installed - Compilation happens in a containerized environment - Binary execution may have resource limits ## 📊 Compilation Modes Comparison | Mode | Size | Portability | Speed | Best For | |------|------|-------------|--------|----------| | Maximum Compatibility | Medium | Highest | Fast | Distribution | | Portable Binary | Small | High | Fastest | Quick testing | | Standalone Binary | Large | Highest | Medium | Isolated deployment | """) with gr.TabItem("ℹ️ About"): gr.Markdown(f""" ## 🧠 Smart Compilation Technology **How it works:** 1. **Environment Detection**: Checks if static libpython is available 2. **Adaptive Options**: Uses the best available compilation flags 3. **Fallback Strategy**: Ensures compilation succeeds even without static linking 4. **Automatic Dependencies**: Resolves missing dependencies automatically 5. **HF Spaces Optimization**: Adapted for Hugging Face Spaces environment This approach maximizes compatibility across different Python environments while working within HF Spaces constraints. ## ✅ What This Solves **Problems addressed:** - ✅ Static libpython not available error - ✅ Python version mismatches - ✅ HF Spaces environment limitations - ✅ WSL compatibility issues - ✅ Dependency resolution - ✅ Cross-environment portability ## ☁️ Current Environment Status ``` Platform: {platform.platform()} Architecture: {platform.architecture()[0]} Machine: {platform.machine()} Python Version: {get_current_python_version()} Nuitka Version: {get_nuitka_version()} Static Libpython: {'✅ Available' if check_static_libpython() else '❌ Not Available'} Environment: Hugging Face Spaces ``` ## 📋 Best Practices **Recommendations:** 1. ✅ Always use "Maximum Compatibility" mode 2. ✅ Test with simple scripts first 3. ✅ Copy binaries to Linux filesystem in WSL 4. ✅ Let the app automatically choose the best settings 5. ✅ Check the compilation details for specific optimizations used 6. ✅ Avoid system packages that require installation ## 🔧 Troubleshooting **Common Issues:** - **Import errors**: Check that all packages are in requirements.txt - **Binary won't run**: Ensure you're on a compatible Linux system - **Permission denied**: Run `chmod +x filename` before execution - **Missing libraries**: Try "Maximum Compatibility" mode ## 🚀 Future Improvements - Support for more compilation targets - Better error messages and diagnostics - Automatic dependency detection - Cross-platform compilation support """) gr.Markdown("---") gr.Markdown("🤖 Created by Claude 3.7 Sonnet | 🚀 Powered by Nuitka with Smart Compilation | ☁️ Optimized for HF Spaces") if __name__ == "__main__": # Create necessary directories on startup ensure_dir("user_code") ensure_dir("compiled_output") app.launch()