Add files via upload #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Python to EXE | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - '**.py' # Only trigger on Python file changes | |
| pull_request: | |
| branches: | |
| - main | |
| paths: | |
| - '**.py' # Only trigger on Python file changes | |
| workflow_dispatch: | |
| inputs: | |
| branchRef: | |
| description: 'Branch to deploy' | |
| required: true | |
| default: 'main' | |
| specificFile: | |
| description: 'Specific Python file to build (leave empty for auto-detect)' | |
| required: false | |
| # Removed concurrency configuration to allow parallel execution | |
| jobs: | |
| build: | |
| runs-on: windows-latest | |
| permissions: | |
| contents: write # This is needed to push changes to the repository | |
| steps: | |
| # Langkah 1: Checkout kode with full history | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} # This is needed for pushing changes | |
| fetch-depth: 0 # Fetch all history to get commit information | |
| # Langkah 2: Create requirements.txt first | |
| - name: Create requirements.txt | |
| run: | | |
| echo "pyinstaller" > requirements.txt | |
| echo "chardet" >> requirements.txt | |
| echo "pandas" >> requirements.txt | |
| echo "gradio" >> requirements.txt | |
| shell: powershell | |
| # Langkah 3: Setup Python - explicitly specify 3.10.x | |
| - name: Set up Python | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: '3.10' | |
| # Langkah 4: Verify Python version | |
| - name: Verify Python version | |
| run: | | |
| python --version | |
| pip --version | |
| shell: powershell | |
| # Langkah 5: Cache pip packages | |
| - name: Cache pip packages | |
| uses: actions/cache@v3 | |
| with: | |
| path: ~\AppData\Local\pip\Cache | |
| key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pip- | |
| # Langkah 6: Install dependencies directly (no venv) | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| shell: powershell | |
| # Langkah 7: Cache PyInstaller build files | |
| - name: Cache PyInstaller build files | |
| uses: actions/cache@v3 | |
| with: | |
| path: | | |
| build | |
| spec | |
| key: ${{ runner.os }}-pyinstaller-${{ hashFiles('**/*.py') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pyinstaller- | |
| # Langkah 8: Set PowerShell to use UTF-8 | |
| - name: Configure PowerShell encoding | |
| run: | | |
| [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 | |
| $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8' | |
| $PSDefaultParameterValues['*:Encoding'] = 'utf8' | |
| shell: powershell | |
| # Langkah 9: Find the Python file that triggered the action | |
| - name: Find the Python file that triggered the action | |
| id: find_python_file | |
| run: | | |
| # Check if a specific file was provided in workflow_dispatch | |
| $specific_file = "${{ github.event.inputs.specificFile }}" | |
| if ($specific_file) { | |
| echo "Using specified Python file: $specific_file" | |
| $script_path = (Get-Location).Path + "\" + $specific_file.Replace("/", "\") | |
| if (-not (Test-Path $script_path)) { | |
| echo "Warning: Specified file not found: $script_path" | |
| echo "Will try to find the file that triggered the action instead." | |
| $specific_file = "" | |
| } | |
| } | |
| if (-not $specific_file) { | |
| # For push events, get the files changed in the last commit | |
| if ("${{ github.event_name }}" -eq "push") { | |
| echo "Finding Python files changed in the last commit..." | |
| # Get the files changed in the last commit | |
| $changed_files = git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | Where-Object { $_ -match '\.py$' } | |
| if ($changed_files) { | |
| # If multiple Python files were changed, use the first one | |
| $python_file = $changed_files | Select-Object -First 1 | |
| echo "Python file changed in commit: $python_file" | |
| $script_path = (Get-Location).Path + "\" + $python_file.Replace("/", "\") | |
| } else { | |
| echo "No Python files found in the commit, falling back to most recent file..." | |
| } | |
| } | |
| # If no file was found from the commit or it's not a push event, fall back to the most recent file | |
| if (-not $script_path -or -not (Test-Path $script_path)) { | |
| echo "Finding most recent Python file..." | |
| $python_files = Get-ChildItem -Recurse -Filter "*.py" | Sort-Object LastWriteTime -Descending | |
| if ($python_files.Count -eq 0) { | |
| echo "No Python files found in the repository!" | |
| echo "Creating a sample Python file for testing..." | |
| echo "print('Hello, World!')" > sample.py | |
| $script_path = "sample.py" | |
| } else { | |
| $latest_file = $python_files | Select-Object -First 1 | |
| $script_path = $latest_file.FullName | |
| echo "Using most recent Python file: $script_path" | |
| } | |
| } | |
| } | |
| # Save the path to a file to avoid encoding issues when passing between steps | |
| $script_path | Out-File -FilePath "script_path.txt" -Encoding utf8 | |
| echo "script_file=script_path.txt" >> $env:GITHUB_OUTPUT | |
| # Save the relative path for Git operations | |
| $repo_root = (Get-Location).Path | |
| $relative_path = $script_path.Replace("$repo_root\", "").Replace("\", "/") | |
| $relative_path | Out-File -FilePath "relative_path.txt" -Encoding utf8 | |
| echo "relative_path=relative_path.txt" >> $env:GITHUB_OUTPUT | |
| shell: powershell | |
| # Langkah 10: Check and fix file encoding with improved error handling | |
| - name: Check and fix file encoding | |
| run: | | |
| # Read the script path from the file | |
| $script_path = Get-Content -Path "${{ steps.find_python_file.outputs.script_file }}" -Encoding utf8 | |
| echo "Checking encoding of Python script: $script_path" | |
| # Create a Python script to detect and fix encoding with better error handling | |
| $encoding_script = @" | |
| import sys | |
| import os | |
| import chardet | |
| def fix_encoding(file_path): | |
| # Read the file as binary | |
| with open(file_path, 'rb') as file: | |
| raw_data = file.read() | |
| # Try to detect the encoding | |
| result = chardet.detect(raw_data) | |
| encoding = result['encoding'] | |
| confidence = result['confidence'] | |
| print(f"Detected encoding: {encoding} with confidence: {confidence}") | |
| # List of encodings to try, in order of preference | |
| # Start with the detected encoding if confidence is high enough | |
| encodings_to_try = [] | |
| # Only use detected encoding if confidence is reasonable | |
| if encoding and confidence > 0.5: | |
| encodings_to_try.append(encoding) | |
| # Add common encodings as fallbacks | |
| for enc in ['utf-8', 'utf-16', 'utf-32', 'gbk', 'gb2312', 'big5', | |
| 'shift_jis', 'euc-jp', 'euc-kr', 'iso-8859-1', | |
| 'windows-1252', 'latin-1', 'cp1251']: | |
| if enc.lower() != encoding.lower(): | |
| encodings_to_try.append(enc) | |
| # Try to decode with each encoding | |
| content = None | |
| used_encoding = None | |
| for enc in encodings_to_try: | |
| try: | |
| content = raw_data.decode(enc) | |
| used_encoding = enc | |
| print(f"Successfully decoded with {enc}") | |
| break | |
| except UnicodeDecodeError: | |
| print(f"Failed to decode with {enc}") | |
| continue | |
| if content is None: | |
| print("Could not decode the file with any encoding. Using UTF-8 and ignoring errors.") | |
| content = raw_data.decode('utf-8', errors='ignore') | |
| used_encoding = 'utf-8' | |
| # Check if the file already has an encoding declaration | |
| lines = content.split('\n') | |
| has_encoding = False | |
| for i, line in enumerate(lines[:2]): | |
| if 'coding' in line and ('utf-8' in line.lower() or 'utf8' in line.lower()): | |
| has_encoding = True | |
| break | |
| # Add encoding declaration if it doesn't have one | |
| if not has_encoding: | |
| if lines[0].startswith('#!'): | |
| lines.insert(1, '# -*- coding: utf-8 -*-') | |
| else: | |
| lines.insert(0, '# -*- coding: utf-8 -*-') | |
| content = '\n'.join(lines) | |
| # Write back with UTF-8 encoding | |
| with open(file_path, 'w', encoding='utf-8') as file: | |
| file.write(content) | |
| print(f"File saved with UTF-8 encoding and proper declaration") | |
| if __name__ == '__main__': | |
| if len(sys.argv) > 1: | |
| file_path = sys.argv[1] | |
| if os.path.exists(file_path): | |
| try: | |
| fix_encoding(file_path) | |
| except Exception as e: | |
| print(f"Error processing file: {e}") | |
| # Create a simple backup by copying the file | |
| import shutil | |
| backup_path = file_path + '.bak' | |
| shutil.copy2(file_path, backup_path) | |
| print(f"Created backup at {backup_path}") | |
| # Create a simple version with just the encoding declaration | |
| with open(file_path, 'w', encoding='utf-8') as f: | |
| f.write('# -*- coding: utf-8 -*-\\n\\n') | |
| f.write('print("Hello World!")\\n') | |
| print("Created a simple replacement file with UTF-8 encoding") | |
| else: | |
| print(f"File not found: {file_path}") | |
| else: | |
| print("No file path provided") | |
| "@ | |
| # Save the encoding script | |
| $encoding_script | Out-File -FilePath "fix_encoding.py" -Encoding utf8 | |
| # Run the encoding script on the Python file | |
| python fix_encoding.py "$script_path" | |
| shell: powershell | |
| # Langkah 11: Build EXE dengan PyInstaller | |
| - name: Build EXE | |
| id: build_exe | |
| run: | | |
| # Read the script path from the file | |
| $script_path = Get-Content -Path "${{ steps.find_python_file.outputs.script_file }}" -Encoding utf8 | |
| if (-not $script_path) { | |
| echo "Error: Script path is empty!" | |
| exit 1 | |
| } | |
| echo "Building EXE for script: $script_path" | |
| # Get the directory of the script | |
| $output_dir = Split-Path -Path $script_path | |
| if (-not $output_dir) { | |
| echo "Using current directory as output directory" | |
| $output_dir = "." | |
| } | |
| echo "Output directory: $output_dir" | |
| # Extract the file name without extension for the EXE name | |
| $file_name = Split-Path -Leaf $script_path | |
| $exe_name = $file_name -replace '\.py$', '' | |
| echo "EXE name will be: $exe_name" | |
| # Create a custom PyInstaller spec file to handle encoding issues | |
| $spec_content = @" | |
| # -*- coding: utf-8 -*- | |
| import sys | |
| from PyInstaller.building.api import PYZ, EXE, COLLECT | |
| from PyInstaller.building.build_main import Analysis | |
| from PyInstaller.utils.hooks import collect_data_files | |
| # Set the correct encoding for sys.stdout and sys.stderr | |
| if hasattr(sys.stdout, 'reconfigure'): | |
| sys.stdout.reconfigure(encoding='utf-8') | |
| if hasattr(sys.stderr, 'reconfigure'): | |
| sys.stderr.reconfigure(encoding='utf-8') | |
| datas = [] | |
| datas += collect_data_files('gradio_client', include_py_files=False) | |
| datas += collect_data_files('safehttpx', include_py_files=False) | |
| datas += collect_data_files('groovy', include_py_files=False) | |
| a = Analysis( | |
| ['$($script_path -replace '\\', '\\\\')'], | |
| pathex=[], | |
| binaries=[], | |
| datas=datas, | |
| hiddenimports=[], | |
| hookspath=[], | |
| hooksconfig={}, | |
| runtime_hooks=[], | |
| excludes=[], | |
| win_no_prefer_redirects=False, | |
| win_private_assemblies=False, | |
| cipher=None, | |
| noarchive=False, | |
| ) | |
| pyz = PYZ(a.pure, a.zipped_data, cipher=None) | |
| exe = EXE( | |
| pyz, | |
| a.scripts, | |
| a.binaries, | |
| a.zipfiles, | |
| a.datas, | |
| [], | |
| name='$exe_name', | |
| debug=False, | |
| bootloader_ignore_signals=False, | |
| strip=False, | |
| upx=True, | |
| upx_exclude=[], | |
| runtime_tmpdir=None, | |
| console=True, | |
| disable_windowed_traceback=False, | |
| argv_emulation=False, | |
| target_arch=None, | |
| codesign_identity=None, | |
| entitlements_file=None, | |
| ) | |
| "@ | |
| # Save the spec file | |
| $spec_content | Out-File -FilePath "custom_build.spec" -Encoding utf8 | |
| # Build using the custom spec file | |
| pyinstaller --clean --log-level DEBUG custom_build.spec | |
| # Check if the build was successful by looking for the EXE | |
| $exe_path = "dist/$exe_name.exe" | |
| $build_success = Test-Path $exe_path | |
| # Save build status for later steps | |
| $build_success | Out-File -FilePath "build_success.txt" -Encoding utf8 | |
| echo "build_success=build_success.txt" >> $env:GITHUB_OUTPUT | |
| shell: powershell | |
| continue-on-error: true # Continue even if the build fails | |
| # Langkah 12: Delete the Python file locally | |
| - name: Delete Python file locally | |
| if: always() # This ensures the step runs even if previous steps failed | |
| run: | | |
| # Read the script path from the file | |
| $script_path = Get-Content -Path "${{ steps.find_python_file.outputs.script_file }}" -Encoding utf8 | |
| if (-not $script_path) { | |
| echo "Error: Script path is empty, cannot delete file." | |
| exit 0 # Don't fail the workflow if we can't delete | |
| } | |
| echo "Attempting to delete Python file locally: $script_path" | |
| # Create a Python script to handle file deletion (better with non-ASCII paths) | |
| $delete_script = @" | |
| import os | |
| import sys | |
| import shutil | |
| def delete_file(file_path): | |
| try: | |
| if os.path.exists(file_path): | |
| os.remove(file_path) | |
| print(f"Successfully deleted locally: {file_path}") | |
| else: | |
| print(f"File not found: {file_path}") | |
| # Check for backup file | |
| backup_path = file_path + '.bak' | |
| if os.path.exists(backup_path): | |
| os.remove(backup_path) | |
| print(f"Successfully deleted backup: {backup_path}") | |
| except Exception as e: | |
| print(f"Error deleting file: {e}") | |
| # Try alternative method if first one fails | |
| try: | |
| if os.path.exists(file_path): | |
| # Sometimes direct deletion fails with special characters | |
| # Try to rename first to a simple name | |
| temp_path = os.path.join(os.path.dirname(file_path), "temp_to_delete.py") | |
| shutil.move(file_path, temp_path) | |
| os.remove(temp_path) | |
| print(f"Successfully deleted via rename method: {file_path}") | |
| except Exception as e2: | |
| print(f"Alternative deletion also failed: {e2}") | |
| if __name__ == "__main__": | |
| if len(sys.argv) > 1: | |
| file_path = sys.argv[1] | |
| delete_file(file_path) | |
| else: | |
| print("No file path provided") | |
| "@ | |
| # Save the deletion script | |
| $delete_script | Out-File -FilePath "delete_file.py" -Encoding utf8 | |
| # Run the deletion script | |
| python delete_file.py "$script_path" | |
| shell: powershell | |
| # Langkah 13: Configure Git | |
| - name: Configure Git | |
| if: always() # This ensures the step runs even if previous steps failed | |
| run: | | |
| git config --global user.name "GitHub Actions" | |
| git config --global user.email "[email protected]" | |
| shell: powershell | |
| # Langkah 14: Commit and push the deletion with enhanced conflict resolution | |
| - name: Commit and push deletion | |
| if: always() # This ensures the step runs even if previous steps failed | |
| run: | | |
| # Read the relative path from the file | |
| $relative_path = Get-Content -Path "${{ steps.find_python_file.outputs.relative_path }}" -Encoding utf8 | |
| if (-not $relative_path) { | |
| echo "Error: Relative path is empty, cannot commit deletion." | |
| exit 0 # Don't fail the workflow if we can't commit | |
| } | |
| echo "Committing deletion of: $relative_path" | |
| # Pull the latest changes first to avoid conflicts | |
| git pull origin ${{ github.ref_name }} | |
| # Check if the file exists in Git | |
| $file_exists = git ls-files --error-unmatch $relative_path 2>$null | |
| if ($LASTEXITCODE -eq 0) { | |
| # File exists in Git, remove it | |
| git rm -f "$relative_path" | |
| git commit -m "Remove Python source file after building EXE: $relative_path" | |
| # Push with retries to handle potential race conditions | |
| $max_retries = 5 # Increased from 3 to 5 for better handling of concurrent actions | |
| $retry_count = 0 | |
| $push_success = $false | |
| while (-not $push_success -and $retry_count -lt $max_retries) { | |
| try { | |
| # Pull again before pushing to get any changes that might have happened | |
| git pull --rebase origin ${{ github.ref_name }} | |
| git push | |
| $push_success = $true | |
| echo "Successfully committed and pushed deletion of $relative_path" | |
| } catch { | |
| $retry_count++ | |
| echo "Push failed, retrying ($retry_count/$max_retries)..." | |
| # Exponential backoff: wait longer between retries | |
| Start-Sleep -Seconds (5 * $retry_count) | |
| } | |
| } | |
| if (-not $push_success) { | |
| echo "Failed to push changes after $max_retries attempts." | |
| exit 1 | |
| } | |
| } else { | |
| echo "File $relative_path is not tracked by Git, no need to commit deletion." | |
| } | |
| shell: powershell | |
| # Langkah 15: Upload EXE as artifact | |
| - name: Upload EXE as artifact | |
| if: always() # This ensures the step runs even if previous steps failed | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: exe-file | |
| path: | | |
| **/*.exe | |
| # Langkah 16: Post message with download link | |
| - name: Post download link | |
| if: always() # This ensures the step runs even if previous steps failed | |
| run: | | |
| echo "Your file has been uploaded as an artifact." | |
| echo "You can download it from: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |