Add github actions for macos #1
18 changed files with 850 additions and 29 deletions
362
.github/workflows/default.yml
vendored
Normal file
362
.github/workflows/default.yml
vendored
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: CI
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the "master" branch
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
ubuntu:
|
||||
name: Build Ubuntu Debian package
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-22.04]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10.8'
|
||||
|
||||
- name: Install prereqs
|
||||
run: |
|
||||
sudo apt install -y libegl-dev libxcb-keysyms1 libxcb-randr0 libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-shape0 libxcb-xkb1 libxcb-render-util0
|
||||
|
||||
- name: Setup Env Vars
|
||||
run: |
|
||||
echo "BUILD_DIR=build" >> $GITHUB_ENV
|
||||
echo "DIST_DIR=dist" >> $GITHUB_ENV
|
||||
FILE_LOC=$(find . -name info.py)
|
||||
export VER="$(cat $FILE_LOC | grep -w version | awk -F'"' '$0=$2')"
|
||||
export PROJ_NAME="$(cat $FILE_LOC | grep -w name | awk -F'"' '$0=$2')"
|
||||
export PROJ_MOD="$(cat $FILE_LOC | grep -w module_name | awk -F'"' '$0=$2')"
|
||||
export MAIN_FILE="$(cat $FILE_LOC | grep -w main_file | awk -F'"' '$0=$2')"
|
||||
export MAIN_MOD="$(cat $FILE_LOC | grep -w main_module | awk -F'"' '$0=$2')"
|
||||
export BUNDLE_ID="$(cat $FILE_LOC | grep -w bundle_identifier | awk -F'"' '$0=$2')"
|
||||
export EMAIL="$(cat $FILE_LOC | grep -w email | awk -F'"' '$0=$2')"
|
||||
export DESCRIPTION="$(cat $FILE_LOC | grep -w description | awk -F'"' '$0=$2')"
|
||||
echo "VER=$VER" >> $GITHUB_ENV
|
||||
echo "DESCRIPTION=$DESCRIPTION" >> $GITHUB_ENV
|
||||
echo "PROJECT_NAME=$PROJ_NAME" >> $GITHUB_ENV
|
||||
echo "PROJ_DIR=$PROJ_MOD" >> $GITHUB_ENV
|
||||
echo "PROJ_PATH=$PROJ_MOD" >> $GITHUB_ENV
|
||||
echo "PROJ_MOD=$PROJ_MOD" >> $GITHUB_ENV
|
||||
echo "MAIN_MOD=$MAIN_MOD" >> $GITHUB_ENV
|
||||
echo "MAIN_FILE=$MAIN_FILE" >> $GITHUB_ENV
|
||||
echo "BUNDLE_ID=$BUNDLE_ID" >> $GITHUB_ENV
|
||||
echo "EMAIL=$EMAIL" >> $GITHUB_ENV
|
||||
|
||||
- name: Check Python install
|
||||
run: |
|
||||
pip3 install --upgrade pip
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
which python3
|
||||
python3 --version
|
||||
which pip3
|
||||
pip3 --version
|
||||
file python3
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
pip3 install -U pip setuptools wheel certifi
|
||||
pip3 install -r requirements.txt
|
||||
pip3 install py2app
|
||||
PYINSTALLER_COMPILE_BOOTLOADER=1 MACOSX_DEPLOYMENT_TARGET=10.9 pip3 install https://github.com/pyinstaller/pyinstaller/tarball/develop --no-binary :all:
|
||||
|
||||
- name: Check Python dependencies
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
python3 -c "from PySide6 import __version__; print(__version__)"
|
||||
python3 -c "from PySide6.QtCore import __version__; print(__version__)"
|
||||
python3 -c "from PySide6.QtCore import QLibraryInfo; print(QLibraryInfo.location(QLibraryInfo.LibrariesPath))"
|
||||
python3 -c "import ssl; print(ssl)"
|
||||
python3 -c "from py2app.recipes import pyside6"
|
||||
python3 -c 'from distutils.sysconfig import get_config_var; print(get_config_var("LDLIBRARY"))'
|
||||
|
||||
- name: Setup Deb
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
python3 create_pyinstaller_file.py
|
||||
pyinstaller -w --noconfirm --hidden-import PySide6 \
|
||||
--add-data "${{ env.PROJ_DIR }}/resources:." \
|
||||
--icon "${{ env.PROJ_DIR }}/resources/icon.png" \
|
||||
--hidden-import configobj \
|
||||
--workpath "${{ env.BUILD_DIR }}" \
|
||||
--distpath "${{ env.DIST_DIR }}" \
|
||||
-n "${{ env.PROJ_MOD }}" pyinstaller.py
|
||||
mv "${{ env.DIST_DIR }}/${{ env.PROJ_MOD }}" "./${{ env.PROJ_MOD }}-tar"
|
||||
tar -czvf ${{ env.PROJ_MOD }}.tar.gz ${{ env.PROJ_MOD }}-tar/*
|
||||
ci/deb-script.sh
|
||||
|
||||
- uses: jiro4989/build-deb-action@v2
|
||||
with:
|
||||
package: ${{ env.PROJ_PATH }}-${{ env.VER }}-${{ matrix.os }}-deb
|
||||
package_root: .debpkg
|
||||
maintainer: ${{ env.EMAIL }}
|
||||
version: ${{ env.VER }} # refs/tags/v*.*.*
|
||||
arch: 'all'
|
||||
depends: 'libc6 (>= 2.2.1)'
|
||||
desc: "${{ env.DESCRIPTION }}"
|
||||
|
||||
- name: Deploy tar
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.PROJ_PATH }}_${{ env.VER }}_${{ matrix.os }}-tar
|
||||
path: |
|
||||
${{ env.PROJ_MOD }}.tar.gz
|
||||
|
||||
- name: Deploy deb
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.PROJ_PATH }}_${{ env.VER }}_${{ matrix.os }}-deb
|
||||
path: |
|
||||
./*.deb
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
${{ env.PROJ_MOD }}.tar.gz
|
||||
./*.deb
|
||||
|
||||
windows:
|
||||
name: Build for Windows
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-2019]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10.8'
|
||||
|
||||
- name: Check Python install
|
||||
run: |
|
||||
python -m venv venv
|
||||
venv\Scripts\Activate.ps1
|
||||
which python
|
||||
python --version
|
||||
python -c "import struct; print(struct.calcsize('P') * 8)"
|
||||
which pip
|
||||
pip --version
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
venv\Scripts\Activate.ps1
|
||||
pip install -U setuptools wheel pip pillow
|
||||
pip install -r requirements.txt
|
||||
pip install https://github.com/pyinstaller/pyinstaller/tarball/develop
|
||||
|
||||
- name: Check Python dependencies
|
||||
run: |
|
||||
venv\Scripts\Activate.ps1
|
||||
python -c "from PySide6 import __version__; print(__version__)"
|
||||
python -c "from PySide6.QtCore import __version__; print(__version__)"
|
||||
python -c "from PySide6.QtCore import QLibraryInfo; print(QLibraryInfo.location(QLibraryInfo.LibrariesPath))"
|
||||
|
||||
- name: Setup Env Vars
|
||||
run: |
|
||||
$FILE_LOC = (Get-ChildItem -Path .\ -Filter info.py -Recurse -ErrorAction SilentlyContinue -Force | foreach {$_.FullName})
|
||||
$VER = (findstr /b version $FILE_LOC).split('"')[1]
|
||||
echo "VER=$VER" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "BUILD_DIR=build" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "DIST_DIR=dist" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
$PROJ_NAME = (findstr /b name $FILE_LOC).split('"')[1]
|
||||
$PROJ_MOD = (findstr /b module_name $FILE_LOC).split('"')[1]
|
||||
$MAIN_FILE = (findstr /b main_file $FILE_LOC).split('"')[1]
|
||||
$MAIN_MOD = (findstr /b main_mod $FILE_LOC).split('"')[1]
|
||||
echo "PROJECT_NAME=$PROJ_NAME" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "PROJ_MOD=$PROJ_MOD" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "PROJ_DIR=$PROJ_MOD" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "MAIN_FILE=$MAIN_FILE" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "MAIN_MOD=$MAIN_MOD" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
venv\Scripts\Activate.ps1
|
||||
python create_pyinstaller_file.py
|
||||
pyinstaller -w --noconfirm --hidden-import PySide6 `
|
||||
--add-data "${{ env.PROJ_DIR }}\resources;." `
|
||||
-i ${{ env.PROJ_DIR }}\resources\icon.ico `
|
||||
--hidden-import pkg_resources `
|
||||
--workpath "${{ env.BUILD_DIR }}" `
|
||||
--distpath "${{ env.DIST_DIR }}" `
|
||||
--onedir -n "${{ env.PROJECT_NAME }}" pyinstaller.py
|
||||
Compress-Archive -Path .\${{env.DIST_DIR}}\${{env.PROJECT_NAME}} -DestinationPath ${{env.PROJECT_NAME}}-${{ env.VER }}-windows-x64.zip
|
||||
|
||||
|
||||
- name: Deploy Zip
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.PROJECT_NAME }}_${{ env.VER }}-${{ matrix.os }}-zip
|
||||
path: |
|
||||
${{ env.PROJECT_NAME }}-${{ env.VER }}-windows-x64.zip
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
${{ env.PROJECT_NAME }}-${{ env.VER }}-windows-x64.zip
|
||||
|
||||
|
||||
macos:
|
||||
name: Build for macOS
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-11, macos-12]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Homebrew
|
||||
run: |
|
||||
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
brew install create-dmg
|
||||
- name: Setup Python
|
||||
run: |
|
||||
wget https://www.python.org/ftp/python/3.10.8/python-3.10.8-macos11.pkg
|
||||
sudo installer -verbose -pkg ./python-3*.pkg -target /
|
||||
echo "/Library/Frameworks/Python.framework/Versions/3.10/bin" >> $GITHUB_PATH
|
||||
- name: Setup Env Vars
|
||||
run: |
|
||||
echo "BUILD_DIR=build" >> $GITHUB_ENV
|
||||
echo "DIST_DIR=dist" >> $GITHUB_ENV
|
||||
FILE_LOC=$(find . -name info.py)
|
||||
export VER="$(cat $FILE_LOC | grep -w version | awk -F'"' '$0=$2')"
|
||||
export PROJ_NAME="$(cat $FILE_LOC | grep -w name | awk -F'"' '$0=$2')"
|
||||
export PROJ_MOD="$(cat $FILE_LOC | grep -w module_name | awk -F'"' '$0=$2')"
|
||||
export MAIN_FILE="$(cat $FILE_LOC | grep -w main_file | awk -F'"' '$0=$2')"
|
||||
export MAIN_MOD="$(cat $FILE_LOC | grep -w main_module | awk -F'"' '$0=$2')"
|
||||
export BUNDLE_ID="$(cat $FILE_LOC | grep -w bundle_identifier | awk -F'"' '$0=$2')"
|
||||
echo "VER=$VER" >> $GITHUB_ENV
|
||||
echo "PROJECT_NAME=$PROJ_NAME" >> $GITHUB_ENV
|
||||
echo "PROJ_DIR=$PROJ_MOD" >> $GITHUB_ENV
|
||||
echo "PROJ_MOD=$PROJ_MOD" >> $GITHUB_ENV
|
||||
echo "MAIN_FILE=$MAIN_FILE" >> $GITHUB_ENV
|
||||
echo "MAIN_MOD=$MAIN_MOD" >> $GITHUB_ENV
|
||||
echo "BUNDLE_ID=$BUNDLE_ID" >> $GITHUB_ENV
|
||||
|
||||
- name: Check Python install
|
||||
run: |
|
||||
pip3 install --upgrade pip
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
which python3
|
||||
python3 --version
|
||||
which pip3
|
||||
pip3 --version
|
||||
file python3
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
pip3 install -U pip setuptools wheel pyclean
|
||||
pip3 install -r requirements.txt
|
||||
pip3 install py2app
|
||||
PYINSTALLER_COMPILE_BOOTLOADER=1 MACOSX_DEPLOYMENT_TARGET=10.9 pip3 install https://github.com/pyinstaller/pyinstaller/tarball/develop --no-binary :all:
|
||||
|
||||
- name: Install universal2 dependencies
|
||||
env:
|
||||
CFLAGS: -arch x86_64 -arch arm64
|
||||
ARCHFLAGS: -arch x86_64 -arch arm64
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
pip3 uninstall cffi -y
|
||||
pip3 install --no-binary :all: cffi
|
||||
pip3 uninstall cryptography -y
|
||||
pip3 download --platform macosx_10_10_universal2 --only-binary :all: --no-deps --dest . cryptography
|
||||
pip3 install --no-cache-dir --no-index --find-links . cryptography
|
||||
|
||||
- name: Check Python dependencies
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
python3 -c "from PySide6 import __version__; print(__version__)"
|
||||
python3 -c "from PySide6.QtCore import __version__; print(__version__)"
|
||||
python3 -c "from PySide6.QtCore import QLibraryInfo; print(QLibraryInfo.location(QLibraryInfo.LibrariesPath))"
|
||||
python3 -c "import ssl; print(ssl)"
|
||||
python3 -c "from py2app.recipes import pyside6"
|
||||
python3 -c 'from distutils.sysconfig import get_config_var; print(get_config_var("LDLIBRARY"))'
|
||||
- name: Build
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
python3 create_pyinstaller_file.py
|
||||
# py2app works better
|
||||
python3 buildPy2app.py py2app
|
||||
# pyinstaller -w --noconfirm --hidden-import PySide6 \
|
||||
# --add-data "${{ env.PROJ_DIR }}/resources:." \
|
||||
# --icon "${{ env.PROJ_DIR }}/resources/icon.icns" \
|
||||
# --target-architecture universal2 \
|
||||
# --osx-bundle-identifier "${{ env.BUNDLE_ID }}" \
|
||||
# --hidden-import pkg_resources \
|
||||
# --distpath "${{ env.BUILD_DIR }}/ProjectMac" \
|
||||
# --onefile -n "${{ env.PROJECT_NAME }}" pyinstaller.py
|
||||
mv "${{ env.DIST_DIR }}/${{ env.PROJECT_NAME }}.app" "${{ env.PROJECT_NAME }}.app"
|
||||
# Clean the directory so it's not full of python bytecode
|
||||
pyclean "${{ env.PROJECT_NAME }}.app"
|
||||
python3 ci/cleandist.py "${{ env.PROJECT_NAME }}.app"
|
||||
python3 ci/codesign.py "${{ env.PROJECT_NAME }}.app"
|
||||
zip -ry "${{ env.PROJECT_NAME }}_${{ env.VER }}-macos-universal.zip" "${{ env.PROJECT_NAME }}.app"
|
||||
|
||||
- name: Prepare for deployment
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
mkdir -p "${{ env.DIST_DIR }}/dmg"
|
||||
test -f "${{ env.PROJECT_NAME }}_${{ env.VER }}.dmg" && rm "${{ env.PROJECT_NAME }}_${{ env.VER }}.dmg"
|
||||
mv "${{ env.PROJECT_NAME }}.app" "${{ env.DIST_DIR}}/dmg/"
|
||||
create-dmg \
|
||||
--volname "${{ env.PROJECT_NAME }}-${{ env.VER }} Installer" \
|
||||
--volicon "${{ env.PROJ_DIR }}/resources/icon.icns" \
|
||||
--window-pos 200 120 \
|
||||
--window-size 800 400 \
|
||||
--icon-size 100 \
|
||||
--icon "${{ env.PROJECT_NAME }}.app" 200 190 \
|
||||
--hide-extension "${{ env.PROJECT_NAME }}.app" \
|
||||
--app-drop-link 600 185 \
|
||||
"${{ env.PROJECT_NAME }}_${{ env.VER }}-universal.dmg" \
|
||||
"${{ env.DIST_DIR }}/dmg/"
|
||||
ls -al ${{ env.DIST_DIR }}
|
||||
|
||||
- name: Deploy Zip
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.PROJECT_NAME }}_${{ env.VER }}-${{ matrix.os }}-zip
|
||||
path: |
|
||||
${{ env.PROJECT_NAME }}_${{ env.VER }}-macos-universal.zip
|
||||
|
||||
- name: Deploy DMG
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.PROJECT_NAME }}_${{ env.VER }}-${{ matrix.os}}-dmg
|
||||
path: |
|
||||
${{ env.PROJECT_NAME }}_${{ env.VER }}-universal.dmg
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
${{ env.PROJECT_NAME }}_${{ env.VER }}-universal.dmg
|
||||
${{ env.PROJECT_NAME }}_${{ env.VER }}-macos-universal.zip
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -127,3 +127,4 @@ dmypy.json
|
|||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
.DS_Store
|
||||
|
|
|
|||
|
|
@ -1,35 +1,36 @@
|
|||
from setuptools import setup
|
||||
from glob import glob
|
||||
from datetime import datetime
|
||||
from package_alias import package
|
||||
|
||||
module_name = "project"
|
||||
name = "Project"
|
||||
version = ""
|
||||
main_file = "main.py"
|
||||
info = package.info
|
||||
|
||||
# Overrides the above
|
||||
exec(open("info.py").read())
|
||||
name = info.name
|
||||
version = info.version
|
||||
bundle_identifier = info.bundle_identifier
|
||||
module_name = info.module_name
|
||||
|
||||
|
||||
APP = [f'{module_name}/{main_file}']
|
||||
APP = [f'pyinstaller.py']
|
||||
DATA_FILES = [
|
||||
('resources', glob(module_name + '/resources/*.png') + glob(module_name + '/resources/*.rtf') + glob(module_name + '/resources/*.txt')),
|
||||
('resources', glob(module_name + '/resources/*')),
|
||||
]
|
||||
OPTIONS = {
|
||||
'arch': 'universal2',
|
||||
'optimize': 2,
|
||||
'iconfile': module_name + '/resources/icon.icns',
|
||||
'extra_scripts': 'info.py',
|
||||
'includes': {'PySide6.QtCore', 'PySide6.QtUiTools', 'PySide6.QtGui', 'PySide6.QtWidgets', 'certifi', 'cffi', 'pem'},
|
||||
'excludes': {'tkinter'},
|
||||
'includes': {'PySide6.QtCore', 'PySide6.QtUiTools', 'PySide6.QtGui', 'PySide6.QtWidgets', 'certifi', },
|
||||
'excludes': {'tkinter', "unittest"},
|
||||
'qt_plugins': [
|
||||
'platforms/libqcocoa.dylib',
|
||||
'platforms/libqminimal.dylib',
|
||||
'platforms/libqoffscreen.dylib',
|
||||
'styles/libqmacstyle.dylib'
|
||||
],
|
||||
'argv_emulation': True,
|
||||
'plist': {
|
||||
'CFBundleName': name,
|
||||
'CFBundleShortVersionString': version,
|
||||
'CFBundleIdentifier': f'pl.{module_name}.{name}',
|
||||
'CFBundleIdentifier': bundle_identifier,
|
||||
'LSMinimumSystemVersion': '10.12.0',
|
||||
'NSHumanReadableCopyright': f'Copyright © {datetime.now().year} {name} All Rights Reserved',
|
||||
}
|
||||
|
|
|
|||
21
ci/appdatatemplate.xml
Normal file
21
ci/appdatatemplate.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>$BUNDLE_ID</id>
|
||||
<metadata_license>$METADATA_LICENSE</metadata_license>
|
||||
<project_license>$PROJECT_LICENSE</project_license>
|
||||
<name>$PROJ_NAME</name>
|
||||
<summary>$SUMMARY</summary>
|
||||
<description>
|
||||
<p>$DESCRIPTION</p>
|
||||
</description>
|
||||
<launchable type="desktop-id">$BUNDLE_ID.desktop</launchable>
|
||||
<url type="homepage">$HOMEPAGE</url>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>$SCREENSHOT</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<provides>
|
||||
<id>$BUNDLE_ID.desktop</id>
|
||||
</provides>
|
||||
</component>
|
||||
3
ci/bintemplate
Normal file
3
ci/bintemplate
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
/usr/bin/$PROJ_MOD-bin/$PROJ_MOD "$@"
|
||||
122
ci/cleandist.py
Normal file
122
ci/cleandist.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import sys
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
|
||||
from glob import glob
|
||||
|
||||
pyver = platform.python_version_tuple()[0] + '.' + platform.python_version_tuple()[1]
|
||||
|
||||
# Clean resources
|
||||
def clean(glob_path: str = "", to_be_kept=None, to_be_deleted=None):
|
||||
to_be_kept = to_be_kept or []
|
||||
to_be_deleted = to_be_deleted or []
|
||||
|
||||
if to_be_kept and glob_path:
|
||||
for f in glob(glob_path):
|
||||
if not any({k in f for k in to_be_kept}):
|
||||
to_be_deleted.append(f)
|
||||
|
||||
for p in to_be_deleted:
|
||||
if os.path.exists(p):
|
||||
if os.path.isdir(p):
|
||||
shutil.rmtree(p, ignore_errors=True)
|
||||
else:
|
||||
os.remove(p)
|
||||
|
||||
|
||||
def clean_resources(app_path: str):
|
||||
PATH = '{app_path}/Contents/Resources/'
|
||||
|
||||
to_be_kept = []
|
||||
to_be_deleted = []
|
||||
|
||||
clean(f'{PATH}/qt*', to_be_deleted=to_be_deleted, to_be_kept=to_be_kept)
|
||||
|
||||
|
||||
def clean_pyside6(app_path: str):
|
||||
# Clean PySide6 folder
|
||||
|
||||
PATH = f'{app_path}/Contents/Resources/lib/python{pyver}/PySide6'
|
||||
|
||||
shutil.rmtree(f'{PATH}/examples', ignore_errors=True)
|
||||
shutil.rmtree(f'{PATH}/include', ignore_errors=True)
|
||||
shutil.rmtree(f'{PATH}/Qt/libexec', ignore_errors=True)
|
||||
|
||||
to_be_kept = ['QtCore', 'QtGui', 'QtWidgets']
|
||||
to_be_deleted = []
|
||||
|
||||
for f in glob(f'{PATH}/Qt*'):
|
||||
if not any({k in f for k in to_be_kept}):
|
||||
to_be_deleted.append(f)
|
||||
|
||||
for a in glob(f'{PATH}/*.app'):
|
||||
to_be_deleted.append(a)
|
||||
|
||||
to_be_deleted.remove(f'{PATH}/Qt')
|
||||
to_be_deleted.extend([
|
||||
f'{PATH}/lupdate',
|
||||
f'{PATH}/qmllint',
|
||||
f'{PATH}/lrelease',
|
||||
f'{PATH}/qmlformat',
|
||||
f'{PATH}/qmlls',
|
||||
])
|
||||
clean(to_be_deleted=to_be_deleted)
|
||||
|
||||
|
||||
def clean_qt(app_path: str):
|
||||
# Clean PySide6/Qt folder
|
||||
|
||||
PATH = f'{app_path}/Contents/Resources/lib/python{pyver}/PySide6/Qt'
|
||||
|
||||
to_be_deleted = [f'{PATH}/qml', f'{PATH}/translations']
|
||||
|
||||
clean(to_be_deleted=to_be_deleted)
|
||||
|
||||
|
||||
def clean_lib(app_path: str):
|
||||
# Clean PySide6/Qt/lib folder
|
||||
|
||||
PATH = f'{app_path}/Contents/Resources/lib/python{pyver}/PySide6/Qt/lib'
|
||||
|
||||
to_be_kept = ['QtCore', 'QtDBus', 'QtGui', 'QtWidgets']
|
||||
to_be_deleted = [f'{PATH}/metatypes']
|
||||
|
||||
clean(f'{PATH}/Qt*', to_be_kept=to_be_kept, to_be_deleted=to_be_deleted)
|
||||
|
||||
|
||||
def clean_plugins(app_path: str):
|
||||
# Clean PySide6/Qt/plugins folder
|
||||
PATH = f'{app_path}/Contents/Resources/lib/python{pyver}/PySide6/Qt/plugins'
|
||||
|
||||
to_be_kept = ['platforms', 'styles']
|
||||
to_be_deleted = []
|
||||
|
||||
clean(f'{PATH}/*', to_be_kept=to_be_kept, to_be_deleted=to_be_deleted)
|
||||
|
||||
|
||||
def symlink_shiboken(app_path: str):
|
||||
# symlink .so from shiboken6 to PySide6 folder
|
||||
cwd = os.getcwd()
|
||||
|
||||
FROM = f'{app_path}/Contents/Resources/lib/python{pyver}/shiboken6'
|
||||
TO = f'{app_path}/Contents/Resources/lib/python{pyver}/PySide6'
|
||||
|
||||
fn = os.path.basename(glob(f'{FROM}/libshiboken6*.dylib')[0])
|
||||
|
||||
os.chdir(TO)
|
||||
os.symlink(f'../shiboken6/{fn}', f'./{fn}')
|
||||
os.chdir(cwd)
|
||||
|
||||
|
||||
def main():
|
||||
app_path = sys.argv[1]
|
||||
|
||||
clean_resources(app_path)
|
||||
clean_pyside6(app_path)
|
||||
clean_qt(app_path)
|
||||
clean_lib(app_path)
|
||||
clean_plugins(app_path)
|
||||
symlink_shiboken(app_path)
|
||||
|
||||
main()
|
||||
224
ci/codesign.py
Normal file
224
ci/codesign.py
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Generator, List, Optional
|
||||
|
||||
from macholib.MachO import MachO
|
||||
|
||||
import contextlib
|
||||
import fcntl
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from macholib.util import is_platform_file
|
||||
|
||||
def _dosign(*path):
|
||||
with reset_blocking_status():
|
||||
subprocess.check_call(
|
||||
(
|
||||
"codesign",
|
||||
"--deep",
|
||||
"--force",
|
||||
"-s",
|
||||
"-",
|
||||
"--preserve-metadata=identifier,entitlements,flags,runtime",
|
||||
"-f",
|
||||
"-vvvv",
|
||||
)
|
||||
+ path,
|
||||
)
|
||||
|
||||
def _macho_find(path):
|
||||
for basename, _dirs, files in os.walk(path):
|
||||
for fn in files:
|
||||
path = os.path.join(basename, fn)
|
||||
if is_platform_file(path):
|
||||
yield path
|
||||
|
||||
def codesign_adhoc(bundle):
|
||||
"""
|
||||
(Re)sign a bundle
|
||||
|
||||
Signing should be done "depth-first", sign
|
||||
libraries before signing the libraries/executables
|
||||
linking to them.
|
||||
|
||||
The current implementation is a crude hack,
|
||||
but is better than nothing. Signing properly requires
|
||||
performing a topological sort using dependencies.
|
||||
|
||||
"codesign" will resign the entire bundle, but only
|
||||
if partial signatures are valid.
|
||||
"""
|
||||
# try:
|
||||
# _dosign(bundle)
|
||||
# return
|
||||
# except subprocess.CalledProcessError:
|
||||
# pass
|
||||
|
||||
platfiles = list(_macho_find(bundle))
|
||||
print("sign", platfiles)
|
||||
while platfiles:
|
||||
failed = []
|
||||
for file in platfiles:
|
||||
failed = []
|
||||
try:
|
||||
_dosign(file)
|
||||
except subprocess.CalledProcessError:
|
||||
failed.append(file)
|
||||
if failed == platfiles:
|
||||
raise RuntimeError("Cannot sign bundle %r" % (bundle,))
|
||||
platfiles = failed
|
||||
|
||||
for _ in range(5):
|
||||
try:
|
||||
_dosign(bundle)
|
||||
break
|
||||
except subprocess.CalledProcessError:
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def reset_blocking_status():
|
||||
"""
|
||||
Contextmanager that resets the non-blocking status of
|
||||
the std* streams as necessary. Used with all calls of
|
||||
xcode tools, mostly because ibtool tends to set the
|
||||
std* streams to non-blocking.
|
||||
"""
|
||||
orig_nonblocking = [
|
||||
fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_NONBLOCK for fd in (0, 1, 2)
|
||||
]
|
||||
|
||||
try:
|
||||
yield
|
||||
|
||||
finally:
|
||||
for fd, is_nonblocking in zip((0, 1, 2), orig_nonblocking):
|
||||
cur = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
if is_nonblocking:
|
||||
reset = cur | os.O_NONBLOCK
|
||||
else:
|
||||
reset = cur & ~os.O_NONBLOCK
|
||||
|
||||
if cur != reset:
|
||||
print("Resetting blocking status of %s" % (fd,))
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, reset)
|
||||
|
||||
def create_symlink(folder: Path) -> None:
|
||||
"""Create the appropriate symlink in the MacOS folder
|
||||
pointing to the Resources folder.
|
||||
"""
|
||||
sibbling = Path(str(folder).replace("MacOS", ""))
|
||||
|
||||
# PyQt5/Qt/qml/QtQml/Models.2
|
||||
root = str(sibbling).partition("Contents")[2].lstrip("/")
|
||||
# ../../../../
|
||||
backward = "../" * (root.count("/") + 1)
|
||||
# ../../../../Resources/PyQt5/Qt/qml/QtQml/Models.2
|
||||
good_path = f"{backward}Resources/{root}"
|
||||
|
||||
folder.symlink_to(good_path)
|
||||
|
||||
|
||||
def fix_dll(dll: Path) -> None:
|
||||
"""Fix the DLL lookup paths to use relative ones for Qt dependencies.
|
||||
Inspiration: PyInstaller/depend/dylib.py:mac_set_relative_dylib_deps()
|
||||
Currently one header is pointing to (we are in the Resources folder):
|
||||
@loader_path/../../../../QtCore (it is referencing to the old MacOS folder)
|
||||
It will be converted to:
|
||||
@loader_path/../../../../../../MacOS/QtCore
|
||||
"""
|
||||
|
||||
def match_func(pth: str) -> Optional[str]:
|
||||
"""Callback function for MachO.rewriteLoadCommands() that is
|
||||
called on every lookup path setted in the DLL headers.
|
||||
By returning None for system libraries, it changes nothing.
|
||||
Else we return a relative path pointing to the good file
|
||||
in the MacOS folder.
|
||||
"""
|
||||
basename = os.path.basename(pth)
|
||||
if not basename.startswith("Qt"):
|
||||
return None
|
||||
return f"@loader_path{good_path}/{basename}"
|
||||
|
||||
# Resources/PyQt5/Qt/qml/QtQuick/Controls.2/Fusion
|
||||
root = str(dll.parent).partition("Contents")[2][1:]
|
||||
# /../../../../../../..
|
||||
backward = "/.." * (root.count("/") + 1)
|
||||
# /../../../../../../../MacOS
|
||||
good_path = f"{backward}/MacOS"
|
||||
|
||||
# Rewrite Mach headers with corrected @loader_path
|
||||
mach_dll = MachO(dll)
|
||||
mach_dll.rewriteLoadCommands(match_func)
|
||||
with open(mach_dll.filename, "rb+") as f:
|
||||
for header in mach_dll.headers:
|
||||
f.seek(0)
|
||||
mach_dll.write(f)
|
||||
f.seek(0, 2)
|
||||
f.flush()
|
||||
|
||||
|
||||
def find_problematic_folders(folder: Path) -> Generator[Path, None, None]:
|
||||
"""Recursively yields problematic folders (containing a dot in their name)."""
|
||||
for path in folder.iterdir():
|
||||
print(path)
|
||||
if not path.is_dir() or path.is_symlink():
|
||||
# Skip simlinks as they are allowed (even with a dot)
|
||||
continue
|
||||
if "." in path.name:
|
||||
yield path
|
||||
else:
|
||||
yield from find_problematic_folders(path)
|
||||
|
||||
|
||||
def move_contents_to_resources(folder: Path) -> Generator[Path, None, None]:
|
||||
"""Recursively move any non symlink file from a problematic folder
|
||||
to the sibbling one in Resources.
|
||||
"""
|
||||
for path in folder.iterdir():
|
||||
if path.is_symlink():
|
||||
continue
|
||||
if path.name == "qml":
|
||||
yield from move_contents_to_resources(path)
|
||||
else:
|
||||
sibbling = Path(str(path).replace("MacOS", "Resources"))
|
||||
sibbling.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(path, sibbling)
|
||||
yield sibbling
|
||||
|
||||
|
||||
def main(args: List[str]):
|
||||
"""
|
||||
Fix the application to allow codesign (NXDRIVE-1301).
|
||||
Take one or more .app as arguments: "Nuxeo Drive.app".
|
||||
To overall process will:
|
||||
- move problematic folders from MacOS to Resources
|
||||
- fix the DLLs lookup paths
|
||||
- create the appropriate symbolic link
|
||||
"""
|
||||
for app in args:
|
||||
name = os.path.basename(app)
|
||||
print(f">>> [{name}] Fixing Qt folder names")
|
||||
path = Path(app) / "Contents" / "MacOS"
|
||||
for folder in find_problematic_folders(path):
|
||||
for file in move_contents_to_resources(folder):
|
||||
try:
|
||||
fix_dll(file)
|
||||
except (ValueError, IsADirectoryError):
|
||||
continue
|
||||
shutil.rmtree(folder)
|
||||
create_symlink(folder)
|
||||
print(f" !! Fixed {folder}")
|
||||
codesign_adhoc(app)
|
||||
print(f">>> [{name}] Application fixed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
52
ci/deb-script.sh
Executable file
52
ci/deb-script.sh
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
#! /bin/bash
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
BUILD_DIR=".debpkg"
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# store repo root as variable
|
||||
REPO_ROOT=$(readlink -f $(dirname $(dirname "$0")))
|
||||
OLD_CWD=$(readlink -f .)
|
||||
FILE_LOC=$(find $REPO_ROOT -name info.py)
|
||||
|
||||
export VER="$(cat $FILE_LOC | grep -w version | awk -F'"' '$0=$2')"
|
||||
export PROJ_NAME="$(cat $FILE_LOC | grep -w name | awk -F'"' '$0=$2')"
|
||||
export PROJ_MOD="$(cat $FILE_LOC | grep -w module_name | awk -F'"' '$0=$2')"
|
||||
export MAIN_FILE="$(cat $FILE_LOC | grep -w main_file | awk -F'"' '$0=$2')"
|
||||
export BUNDLE_ID="$(cat $FILE_LOC | grep -w bundle_identifier | awk -F'"' '$0=$2')"
|
||||
export PROJECT_LICENSE="$(cat $FILE_LOC | grep -w project_license | awk -F'"' '$0=$2')"
|
||||
export SCREENSHOT="$(cat $FILE_LOC | grep -w screenshot | awk -F'"' '$0=$2')"
|
||||
export METADATA_LICENSE="$(cat $FILE_LOC | grep -w metadata_license | awk -F'"' '$0=$2')"
|
||||
export DESCRIPTION="$(cat $FILE_LOC | grep -w description | awk -F'"' '$0=$2')"
|
||||
export HOMEPAGE="$(cat $FILE_LOC | grep -w homepage | awk -F'"' '$0=$2')"
|
||||
export SUMMARY="$(cat $FILE_LOC | grep -w summary | awk -F'"' '$0=$2')"
|
||||
export CATEGORIES="$(cat $FILE_LOC | grep -w categories | awk -F'"' '$0=$2')"
|
||||
export MIMETYPE="$(cat $FILE_LOC | grep -w mimetype | awk -F'"' '$0=$2')"
|
||||
export KEYWORDS="$(cat $FILE_LOC | grep -w keywords | awk -F'"' '$0=$2')"
|
||||
export APP_TYPE="$(cat $FILE_LOC | grep -w application_type | awk -F'"' '$0=$2')"
|
||||
export MAIN_MOD="$(cat $FILE_LOC | grep -w main_module | awk -F'"' '$0=$2')"
|
||||
export EMAIL="$(cat $FILE_LOC | grep -w email | awk -F'"' '$0=$2')"
|
||||
|
||||
pushd "$BUILD_DIR"
|
||||
|
||||
# move and rename .desktop file
|
||||
cat > $PROJ_MOD.desktop <(envsubst < $REPO_ROOT/ci/projectemplate.desktop)
|
||||
|
||||
|
||||
mkdir -p $PROJ_MOD/usr/bin/$PROJ_MOD-bin
|
||||
|
||||
chmod +x $REPO_ROOT/$PROJ_MOD-tar/$PROJ_MOD
|
||||
cp -r $REPO_ROOT/$PROJ_MOD-tar/* $PROJ_MOD/usr/bin/$PROJ_MOD-bin/
|
||||
|
||||
cat > $PROJ_MOD/usr/bin/$PROJ_MOD <(envsubst < $REPO_ROOT/ci/bintemplate)
|
||||
chmod +x $PROJ_MOD/usr/bin/$PROJ_MOD
|
||||
|
||||
mkdir -p $PROJ_MOD/usr/share/applications
|
||||
mkdir -p $PROJ_MOD/usr/lib/$PROJ_MOD
|
||||
|
||||
cp $PROJ_MOD.desktop $PROJ_MOD/usr/share/applications
|
||||
chmod +x $PROJ_MOD/usr/share/applications/$PROJ_MOD.desktop
|
||||
|
||||
mkdir -p $PROJ_MOD/DEBIAN
|
||||
9
ci/projectemplate.desktop
Normal file
9
ci/projectemplate.desktop
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[Desktop Entry]
|
||||
Name=$PROJ_NAME
|
||||
Comment=$DESCRIPTION
|
||||
Exec=$PROJ_MOD %u
|
||||
Type=$APP_TYPE
|
||||
Icon=icon
|
||||
Categories=$CATEGORIES;
|
||||
MimeType=$MIME_TYPES
|
||||
Keywords=$PROJ_MOD;$KEYWORDS
|
||||
7
create_pyinstaller_file.py
Normal file
7
create_pyinstaller_file.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from package_alias import package_name, package
|
||||
|
||||
with open("pyinstaller.py", "w+") as f:
|
||||
f.writelines([
|
||||
f"from {package_name} import {package.info.main_module}\n",
|
||||
f"{package.info.main_module}.main()"
|
||||
])
|
||||
3
info.cfg
3
info.cfg
|
|
@ -1,3 +0,0 @@
|
|||
version="0.1.0"
|
||||
name="Project"
|
||||
module_name="project"
|
||||
6
info.py
6
info.py
|
|
@ -1,6 +0,0 @@
|
|||
version="0.1.0"
|
||||
name="Project"
|
||||
module_name="project"
|
||||
company_name="Project Company"
|
||||
bundle_identifier="com.projectcompany.project.Project"
|
||||
main_file="main.py"
|
||||
11
package_alias.py
Normal file
11
package_alias.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import sys
|
||||
import os
|
||||
from glob import glob
|
||||
|
||||
info_name = 'info'
|
||||
info_file = glob(os.path.join('*', f'{info_name}.py'))[0]
|
||||
package_name = info_file.split(os.path.sep)[0]
|
||||
|
||||
package = __import__(f"{package_name}", fromlist=["main", "info"])
|
||||
if package_name not in sys.modules:
|
||||
sys.modules[package_name] = package
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
from .. import info
|
||||
|
||||
version = info.version
|
||||
name = info.name
|
||||
module_name = info.module_name
|
||||
18
project/info.py
Normal file
18
project/info.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
version="0.1.0"
|
||||
name="Project"
|
||||
module_name="project"
|
||||
company_name="Project Company"
|
||||
bundle_identifier="com.projectcompany.project.Project"
|
||||
main_file="main.py"
|
||||
main_module="main"
|
||||
metadata_license="MIT"
|
||||
project_license="PRIVATE"
|
||||
email="user@example.com"
|
||||
homepage="https://example.com"
|
||||
screenshot=""
|
||||
summary="A project of your creation."
|
||||
description="A project of your creation."
|
||||
keywords="interesting;good"
|
||||
categories="CategoryA;CategoryB"
|
||||
mimetype="image/x-foo;image/y-foo"
|
||||
application_type="Application"
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
from PySide6.QtWidgets import QMainWindow, QApplication, QLabel, QVBoxLayout, QPushButton, QWidget
|
||||
from PySide6.QtGui import QIcon
|
||||
from . import name as project_name
|
||||
from . import info
|
||||
project_name = info.name
|
||||
|
||||
import sys
|
||||
|
||||
|
|
@ -22,7 +23,10 @@ class MainWindow(QMainWindow):
|
|||
|
||||
self.show()
|
||||
|
||||
if __name__ == '__main__':
|
||||
def main():
|
||||
app = QApplication(sys.argv)
|
||||
w = MainWindow()
|
||||
app.exec_()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
|||
BIN
project/resources/icon.ico
Normal file
BIN
project/resources/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
BIN
project/resources/icon.png
Normal file
BIN
project/resources/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 671 KiB |
Loading…
Add table
Add a link
Reference in a new issue