mirror of
https://github.com/exo-explore/exo.git
synced 2025-12-23 22:27:50 -05:00
ci: migrate build-app to github hosted runners
This commit is contained in:
126
.github/workflows/build-app.yml
vendored
126
.github/workflows/build-app.yml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-macos-app:
|
build-macos-app:
|
||||||
runs-on: [self-hosted, XCode262_Beta]
|
runs-on: "macos-26"
|
||||||
env:
|
env:
|
||||||
SPARKLE_VERSION: 2.8.1
|
SPARKLE_VERSION: 2.8.1
|
||||||
SPARKLE_DOWNLOAD_PREFIX: ${{ secrets.SPARKLE_DOWNLOAD_PREFIX }}
|
SPARKLE_DOWNLOAD_PREFIX: ${{ secrets.SPARKLE_DOWNLOAD_PREFIX }}
|
||||||
@@ -21,6 +21,10 @@ jobs:
|
|||||||
EXO_LIBP2P_NAMESPACE: ${{ github.ref_name }}
|
EXO_LIBP2P_NAMESPACE: ${{ github.ref_name }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
# ============================================================
|
||||||
|
# Checkout and tag validation
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -29,7 +33,6 @@ jobs:
|
|||||||
- name: Derive release version from tag
|
- name: Derive release version from tag
|
||||||
run: |
|
run: |
|
||||||
VERSION="${GITHUB_REF_NAME#v}"
|
VERSION="${GITHUB_REF_NAME#v}"
|
||||||
# Detect alpha tags
|
|
||||||
if [[ "$VERSION" == *-alpha* ]]; then
|
if [[ "$VERSION" == *-alpha* ]]; then
|
||||||
echo "IS_ALPHA=true" >> $GITHUB_ENV
|
echo "IS_ALPHA=true" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
@@ -40,7 +43,7 @@ jobs:
|
|||||||
- name: Ensure tag commit is on main
|
- name: Ensure tag commit is on main
|
||||||
run: |
|
run: |
|
||||||
git fetch origin main
|
git fetch origin main
|
||||||
# Allow alpha tags on any branch, but require production tags to be on main
|
# Alpha tags can be on any branch, production tags must be on main
|
||||||
if [[ "$IS_ALPHA" == "true" ]]; then
|
if [[ "$IS_ALPHA" == "true" ]]; then
|
||||||
echo "Alpha tag detected, skipping main branch check"
|
echo "Alpha tag detected, skipping main branch check"
|
||||||
elif ! git merge-base --is-ancestor origin/main HEAD; then
|
elif ! git merge-base --is-ancestor origin/main HEAD; then
|
||||||
@@ -48,27 +51,20 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Add Homebrew to PATH
|
# ============================================================
|
||||||
run: |
|
# Install dependencies
|
||||||
if [ -f /opt/homebrew/bin/brew ]; then
|
# ============================================================
|
||||||
echo "/opt/homebrew/bin" >> $GITHUB_PATH
|
|
||||||
elif [ -f /usr/local/bin/brew ]; then
|
|
||||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check Metal toolchain is installed
|
- name: Select Xcode 26.2
|
||||||
run: |
|
run: |
|
||||||
|
sudo xcode-select -s /Applications/Xcode_26.2.app
|
||||||
if ! xcrun -f metal >/dev/null 2>&1; then
|
if ! xcrun -f metal >/dev/null 2>&1; then
|
||||||
echo "Metal toolchain is not installed. Run 'xcodebuild -downloadComponent MetalToolchain' on the runner host."
|
echo "Metal toolchain is not installed."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "Metal toolchain is installed."
|
|
||||||
|
|
||||||
- name: Install Just
|
- name: Install Homebrew packages
|
||||||
run: brew install just
|
run: brew install just awscli macmon
|
||||||
|
|
||||||
- name: Install AWS CLI
|
|
||||||
run: brew install awscli
|
|
||||||
|
|
||||||
- name: Install UV
|
- name: Install UV
|
||||||
uses: astral-sh/setup-uv@v6
|
uses: astral-sh/setup-uv@v6
|
||||||
@@ -76,17 +72,25 @@ jobs:
|
|||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: uv.lock
|
cache-dependency-glob: uv.lock
|
||||||
|
|
||||||
- name: Setup Python (UV)
|
- name: Setup Python
|
||||||
run: |
|
run: |
|
||||||
uv python install
|
uv python install
|
||||||
uv sync --locked
|
uv sync --locked
|
||||||
|
|
||||||
- name: Install macmon
|
- name: Build dashboard
|
||||||
run: brew install macmon
|
|
||||||
|
|
||||||
- name: Build PyInstaller bundle
|
|
||||||
run: |
|
run: |
|
||||||
uv run pyinstaller packaging/pyinstaller/exo.spec
|
cd dashboard
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
- name: Install Sparkle CLI
|
||||||
|
run: |
|
||||||
|
CLI_URL="${SPARKLE_CLI_URL:-https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.xz}"
|
||||||
|
echo "Downloading Sparkle CLI from: $CLI_URL"
|
||||||
|
mkdir -p /tmp/sparkle
|
||||||
|
curl --fail --location --output /tmp/sparkle.tar.xz "$CLI_URL"
|
||||||
|
tar -xJf /tmp/sparkle.tar.xz -C /tmp/sparkle --strip-components=1
|
||||||
|
echo "SPARKLE_BIN=/tmp/sparkle/bin" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Prepare code-signing keychain
|
- name: Prepare code-signing keychain
|
||||||
env:
|
env:
|
||||||
@@ -95,43 +99,47 @@ jobs:
|
|||||||
PROVISIONING_PROFILE: ${{ secrets.PROVISIONING_PROFILE }}
|
PROVISIONING_PROFILE: ${{ secrets.PROVISIONING_PROFILE }}
|
||||||
run: |
|
run: |
|
||||||
KEYCHAIN_PATH="$HOME/Library/Keychains/build.keychain-db"
|
KEYCHAIN_PATH="$HOME/Library/Keychains/build.keychain-db"
|
||||||
|
|
||||||
# Remove stale keychain from previous failed runs
|
|
||||||
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
|
|
||||||
|
|
||||||
# Create fresh keychain
|
# Create fresh keychain
|
||||||
security create-keychain -p "$MACOS_CERTIFICATE_PASSWORD" "$KEYCHAIN_PATH"
|
security create-keychain -p "$MACOS_CERTIFICATE_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
# Disable auto-lock (no timeout, no lock-on-sleep)
|
# Disable auto-lock (no timeout, no lock-on-sleep)
|
||||||
security set-keychain-settings "$KEYCHAIN_PATH"
|
security set-keychain-settings "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
# Add to search list while preserving existing keychains
|
# Add to search list while preserving existing keychains
|
||||||
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
|
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
|
||||||
|
|
||||||
# Set as default and unlock
|
# Set as default and unlock
|
||||||
security default-keychain -s "$KEYCHAIN_PATH"
|
security default-keychain -s "$KEYCHAIN_PATH"
|
||||||
security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" "$KEYCHAIN_PATH"
|
security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
# Import certificate with full access for codesign
|
# Import certificate with full access for codesign
|
||||||
echo "$MACOS_CERTIFICATE" | base64 --decode > /tmp/cert.p12
|
echo "$MACOS_CERTIFICATE" | base64 --decode > /tmp/cert.p12
|
||||||
security import /tmp/cert.p12 -k "$KEYCHAIN_PATH" -P "$MACOS_CERTIFICATE_PASSWORD" \
|
security import /tmp/cert.p12 -k "$KEYCHAIN_PATH" -P "$MACOS_CERTIFICATE_PASSWORD" \
|
||||||
-T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
-T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
||||||
rm /tmp/cert.p12
|
rm /tmp/cert.p12
|
||||||
|
|
||||||
# Allow codesign to access the key without prompting
|
# Allow codesign to access the key without prompting
|
||||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CERTIFICATE_PASSWORD" "$KEYCHAIN_PATH"
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CERTIFICATE_PASSWORD" "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
# Verify keychain is unlocked and identity is available
|
# Verify keychain is unlocked and identity is available
|
||||||
echo "Verifying signing identity..."
|
echo "Verifying signing identity..."
|
||||||
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
|
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
|
||||||
|
|
||||||
# Setup provisioning profile
|
# Setup provisioning profile
|
||||||
mkdir -p "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
|
mkdir -p "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
|
||||||
echo "$PROVISIONING_PROFILE" | base64 --decode > "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles/EXO.provisionprofile"
|
echo "$PROVISIONING_PROFILE" | base64 --decode > "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles/EXO.provisionprofile"
|
||||||
|
|
||||||
# Export keychain path for other steps
|
# Export keychain path for other steps
|
||||||
echo "BUILD_KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV
|
echo "BUILD_KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Build the bundle
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
- name: Build PyInstaller bundle
|
||||||
|
run: uv run pyinstaller packaging/pyinstaller/exo.spec
|
||||||
|
|
||||||
- name: Build Swift app
|
- name: Build Swift app
|
||||||
env:
|
env:
|
||||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||||
@@ -162,7 +170,7 @@ jobs:
|
|||||||
mkdir -p output/EXO.app/Contents/Resources
|
mkdir -p output/EXO.app/Contents/Resources
|
||||||
cp -R dist/exo output/EXO.app/Contents/Resources/exo
|
cp -R dist/exo output/EXO.app/Contents/Resources/exo
|
||||||
|
|
||||||
- name: Codesign PyInstaller runtime payload
|
- name: Codesign PyInstaller runtime
|
||||||
env:
|
env:
|
||||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
@@ -226,40 +234,39 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate Sparkle appcast
|
- name: Generate Sparkle appcast
|
||||||
env:
|
env:
|
||||||
SPARKLE_VERSION: ${{ env.SPARKLE_VERSION }}
|
|
||||||
SPARKLE_DOWNLOAD_PREFIX: ${{ env.SPARKLE_DOWNLOAD_PREFIX }}
|
SPARKLE_DOWNLOAD_PREFIX: ${{ env.SPARKLE_DOWNLOAD_PREFIX }}
|
||||||
SPARKLE_ED25519_PRIVATE: ${{ secrets.SPARKLE_ED25519_PRIVATE }}
|
SPARKLE_ED25519_PRIVATE: ${{ secrets.SPARKLE_ED25519_PRIVATE }}
|
||||||
SPARKLE_CLI_URL: ${{ secrets.SPARKLE_CLI_URL }}
|
|
||||||
IS_ALPHA: ${{ env.IS_ALPHA }}
|
IS_ALPHA: ${{ env.IS_ALPHA }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd output
|
cd output
|
||||||
DOWNLOAD_PREFIX="${SPARKLE_DOWNLOAD_PREFIX:-https://assets.exolabs.net}"
|
DOWNLOAD_PREFIX="${SPARKLE_DOWNLOAD_PREFIX:-https://assets.exolabs.net}"
|
||||||
mkdir -p sparkle
|
|
||||||
CLI_URL="${SPARKLE_CLI_URL:-}"
|
|
||||||
if [[ -z "$CLI_URL" ]]; then
|
|
||||||
CLI_URL="https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.xz"
|
|
||||||
fi
|
|
||||||
echo "Downloading Sparkle CLI from: $CLI_URL"
|
|
||||||
curl --fail --location --output sparkle.tar.xz "$CLI_URL"
|
|
||||||
tar -xJf sparkle.tar.xz -C sparkle --strip-components=1
|
|
||||||
echo "$SPARKLE_ED25519_PRIVATE" > sparkle_ed25519.key
|
echo "$SPARKLE_ED25519_PRIVATE" > sparkle_ed25519.key
|
||||||
chmod 600 sparkle_ed25519.key
|
chmod 600 sparkle_ed25519.key
|
||||||
|
|
||||||
# Add --channel alpha flag for alpha builds
|
|
||||||
CHANNEL_FLAG=""
|
CHANNEL_FLAG=""
|
||||||
if [[ "$IS_ALPHA" == "true" ]]; then
|
if [[ "$IS_ALPHA" == "true" ]]; then
|
||||||
CHANNEL_FLAG="--channel alpha"
|
CHANNEL_FLAG="--channel alpha"
|
||||||
echo "Generating appcast for alpha channel"
|
echo "Generating appcast for alpha channel"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
./sparkle/bin/generate_appcast \
|
$SPARKLE_BIN/generate_appcast \
|
||||||
--ed-key-file sparkle_ed25519.key \
|
--ed-key-file sparkle_ed25519.key \
|
||||||
--download-url-prefix "$DOWNLOAD_PREFIX" \
|
--download-url-prefix "$DOWNLOAD_PREFIX" \
|
||||||
$CHANNEL_FLAG \
|
$CHANNEL_FLAG \
|
||||||
.
|
.
|
||||||
|
|
||||||
- name: Upload Sparkle assets to S3
|
# ============================================================
|
||||||
|
# Upload artifacts
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
- name: Upload DMG
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: EXO-dmg-${{ env.RELEASE_VERSION }}
|
||||||
|
path: output/EXO-${{ env.RELEASE_VERSION }}.dmg
|
||||||
|
|
||||||
|
- name: Upload to S3
|
||||||
if: env.SPARKLE_S3_BUCKET != ''
|
if: env.SPARKLE_S3_BUCKET != ''
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
@@ -281,22 +288,3 @@ jobs:
|
|||||||
aws s3 cp "$DMG_NAME" "s3://${SPARKLE_S3_BUCKET}/${PREFIX}EXO-latest.dmg"
|
aws s3 cp "$DMG_NAME" "s3://${SPARKLE_S3_BUCKET}/${PREFIX}EXO-latest.dmg"
|
||||||
fi
|
fi
|
||||||
aws s3 cp appcast.xml "s3://${SPARKLE_S3_BUCKET}/${PREFIX}appcast.xml" --content-type application/xml --cache-control no-cache
|
aws s3 cp appcast.xml "s3://${SPARKLE_S3_BUCKET}/${PREFIX}appcast.xml" --content-type application/xml --cache-control no-cache
|
||||||
|
|
||||||
- name: Cleanup keychain
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
KEYCHAIN_PATH="$HOME/Library/Keychains/build.keychain-db"
|
|
||||||
security default-keychain -s login.keychain || true
|
|
||||||
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
|
|
||||||
|
|
||||||
- name: Upload app bundle
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: EXO-app-${{ env.RELEASE_VERSION }}
|
|
||||||
path: output/EXO.app
|
|
||||||
|
|
||||||
- name: Upload DMG
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: EXO-dmg-${{ env.RELEASE_VERSION }}
|
|
||||||
path: output/EXO-${{ env.RELEASE_VERSION }}.dmg
|
|
||||||
|
|||||||
118
packaging/pyinstaller/exo.spec
Normal file
118
packaging/pyinstaller/exo.spec
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PyInstaller.utils.hooks import collect_submodules
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path.cwd()
|
||||||
|
SOURCE_ROOT = PROJECT_ROOT / "src"
|
||||||
|
ENTRYPOINT = SOURCE_ROOT / "exo" / "__main__.py"
|
||||||
|
DASHBOARD_DIR = PROJECT_ROOT / "dashboard" / "build"
|
||||||
|
EXO_SHARED_MODELS_DIR = SOURCE_ROOT / "exo" / "shared" / "models"
|
||||||
|
|
||||||
|
if not ENTRYPOINT.is_file():
|
||||||
|
raise SystemExit(f"Unable to locate Exo entrypoint: {ENTRYPOINT}")
|
||||||
|
|
||||||
|
if not DASHBOARD_DIR.is_dir():
|
||||||
|
raise SystemExit(f"Dashboard assets are missing: {DASHBOARD_DIR}")
|
||||||
|
|
||||||
|
if not EXO_SHARED_MODELS_DIR.is_dir():
|
||||||
|
raise SystemExit(f"Shared model assets are missing: {EXO_SHARED_MODELS_DIR}")
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
|
||||||
|
def _module_directory(module_name: str) -> Path:
|
||||||
|
spec = importlib.util.find_spec(module_name)
|
||||||
|
if spec is None:
|
||||||
|
raise SystemExit(f"Module '{module_name}' is not available in the current environment.")
|
||||||
|
if spec.submodule_search_locations:
|
||||||
|
return Path(next(iter(spec.submodule_search_locations))).resolve()
|
||||||
|
if spec.origin:
|
||||||
|
return Path(spec.origin).resolve().parent
|
||||||
|
raise SystemExit(f"Unable to determine installation directory for '{module_name}'.")
|
||||||
|
|
||||||
|
|
||||||
|
MLX_PACKAGE_DIR = _module_directory("mlx")
|
||||||
|
MLX_LIB_DIR = MLX_PACKAGE_DIR / "lib"
|
||||||
|
if not MLX_LIB_DIR.is_dir():
|
||||||
|
raise SystemExit(f"mlx Metal libraries are missing: {MLX_LIB_DIR}")
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_collect(package_name: str) -> list[str]:
|
||||||
|
try:
|
||||||
|
return collect_submodules(package_name)
|
||||||
|
except ImportError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
HIDDEN_IMPORTS = sorted(
|
||||||
|
set(
|
||||||
|
collect_submodules("mlx")
|
||||||
|
+ _safe_collect("mlx_lm")
|
||||||
|
+ _safe_collect("transformers")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
DATAS: list[tuple[str, str]] = [
|
||||||
|
(str(DASHBOARD_DIR), "dashboard"),
|
||||||
|
(str(MLX_LIB_DIR), "mlx/lib"),
|
||||||
|
(str(EXO_SHARED_MODELS_DIR), "exo/shared/models"),
|
||||||
|
]
|
||||||
|
|
||||||
|
MACMON_PATH = shutil.which("macmon")
|
||||||
|
if MACMON_PATH is None:
|
||||||
|
raise SystemExit(
|
||||||
|
"macmon binary not found in PATH. "
|
||||||
|
"Install it via: brew install macmon"
|
||||||
|
)
|
||||||
|
|
||||||
|
BINARIES: list[tuple[str, str]] = [
|
||||||
|
(MACMON_PATH, "."),
|
||||||
|
]
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
[str(ENTRYPOINT)],
|
||||||
|
pathex=[str(SOURCE_ROOT)],
|
||||||
|
binaries=BINARIES,
|
||||||
|
datas=DATAS,
|
||||||
|
hiddenimports=HIDDEN_IMPORTS,
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
noarchive=False,
|
||||||
|
)
|
||||||
|
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
[],
|
||||||
|
exclude_binaries=True,
|
||||||
|
name="exo",
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=False,
|
||||||
|
console=True,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
)
|
||||||
|
coll = COLLECT(
|
||||||
|
exe,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
strip=False,
|
||||||
|
upx=False,
|
||||||
|
upx_exclude=[],
|
||||||
|
name="exo",
|
||||||
|
)
|
||||||
|
|
||||||
Reference in New Issue
Block a user