Re-implement packaging for GitHub workflows (macOS) (#7353)
* Restore Azure macOS dist scripts * Move steps to workflow for testing * Always upload to GitHub * Add codesign ID * Echo codesign ID * Add cert import code * Stub file for Mac * Self-install pyyaml and choco * Auto add env var on Windows * Auto add CMAKE_PREFIX_PATH to .zshrc * Shorter var names * Append env var instead of replace * Only set env var if not CI * Improve function names and print output * Simplify Linux package command * Support continuation sequence * Add note about Windows * Remove dead doc file * Tidy up version file and move to .env format * Use Python venv for deps * Only use venv on Mac * Rename package script for all OS * Add package and dist steps, and use common upload * Remove version source * Fixed vars not available * Fixed python paths * Use RuntimeError which is sufficient * Remove dead code * Add extras command for Linux * Always install deps on Linux * Move Python deps to CI * More env bootstrapping, ugh * Forgot to return! * Simplify code * Use shell * Simplify command * Skip sudo if no sudo * Update package managers * Fixed Fedora package name * Tidy up commands * Use newer upload artifact * Strip don't trim! * Check for version file and reduce log verbosity * Remove CentOS 7.6 * Print more info about return code and log more to stderr * Install certificate on macOS * Better errors for no env var * Implement Mac signing and notary * Move dmgbuild load * Simplify notary * Rename dist files to same as dest * Fixed paths for dist * Move checked-in dist files to res (dist is meant to be a temp dir) * Fixed Mac path in CMake * Fixed dmg path * Format Python * Ignore import warnings and move function * Fixed cmake paths * Add missing env var secrets * Remove extensions from GH upload * Make deps.yml general purpose config * Add cspell config * Pass codesign ID * Use new general config file * Sign bundle on Mac * Move imports to functions * Escape chars in docs * Fixed config key accessor * Change module import order * Move file to tmp dir in workflow dir * Persist temp dir * Add tmp dir to ignore * Flush stdio before running process * Trying quotes around env values * Add codesigning certificate validation for Mac signing * Revert "Trying quotes around env values" This reverts commit 0dd741e8cd6fde21e69d4fb871e835a5f4fa1a23. * Extract codesign verify * Fixed version number * Ignore .cache dir * Fix macro name * Package name with version number and arch * Improve package function readability * Change order of vars * Testing upload to GDrive * Add missing return code * Use positional args and declare error * Use machine instead of arch and remove build from filename * Remove redundant build jobs * Replace massively over-complicated `build_version.py` script * Move version info to env module * Use version info script * Fixed: too many values to unpack * Chmod version script * Use shebang * Don't check return code on Linux * Fixed function name * Convert to GitHub specific script * Env vars must be after configure * Fixed Windows env var command * Remove && from deps command so it's not conditional * Fixed position of set env * Change order of env script * Only upload when not draft * Test * Tweak config * Fixed if condition * Don't package in draft (Windows and Linux)
This commit is contained in:
39
.github/actions/dist-upload/action.yml
vendored
Normal file
39
.github/actions/dist-upload/action.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
name: "Distribute upload"
|
||||
description: "Uploads the package from the dist dir to GitHub artifacts or Google Drive"
|
||||
inputs:
|
||||
service:
|
||||
description: "Where to upload the package (github or gdrive)"
|
||||
default: "github"
|
||||
|
||||
github-target-filename:
|
||||
description: "The filename to upload (only used by GitHub artifacts)"
|
||||
|
||||
gdrive-target-dir:
|
||||
description: "The directory to upload (only used by Google Drive)"
|
||||
|
||||
gdrive-secret-key:
|
||||
description: "The Google Drive secret key"
|
||||
|
||||
gdrive-parent-folder-id:
|
||||
description: "The Google Drive parent folder ID"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
|
||||
steps:
|
||||
# Upload to GitHub
|
||||
- if: ${{ inputs.service == 'github' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.github-target-filename }}
|
||||
path: ./dist
|
||||
retention-days: 3
|
||||
|
||||
# Upload to Google Drive
|
||||
- if: ${{ inputs.service == 'gdrive' }}
|
||||
uses: symless/gdrive-upload@target-glob
|
||||
with:
|
||||
credentials: ${{ inputs.gdrive-secret-key }}
|
||||
target: "./dist/*"
|
||||
parent_folder_id: ${{ inputs.gdrive-parent-folder-id }}
|
||||
child_folder: Snapshots/${{ inputs.gdrive-target-dir }}
|
||||
72
.github/workflows/ci.yml
vendored
72
.github/workflows/ci.yml
vendored
@ -9,13 +9,16 @@ on:
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
push:
|
||||
branches: [master*, release*]
|
||||
branches:
|
||||
- master*
|
||||
- release*
|
||||
|
||||
env:
|
||||
GIT_COMMIT: ${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
windows-2022:
|
||||
windows:
|
||||
name: windows-2022
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 20
|
||||
|
||||
@ -41,9 +44,7 @@ jobs:
|
||||
run: python ./scripts/install_deps.py --only qt
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install pyyaml
|
||||
python ./scripts/install_deps.py
|
||||
run: python ./scripts/install_deps.py
|
||||
|
||||
- name: Setup VC++ environment
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
@ -53,12 +54,19 @@ jobs:
|
||||
CMAKE_PREFIX_PATH: "${{ env.QT_BASE_DIR }}\\${{ env.QT_VERSION }}\\msvc2019_64\\"
|
||||
run: cmake -B build --preset=windows-release
|
||||
|
||||
- name: Set env vars
|
||||
run: python ./scripts/github_env.py
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ./build/bin/unittests
|
||||
|
||||
- name: Package
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
run: python ./scripts/package.py
|
||||
|
||||
macos:
|
||||
runs-on: ${{ matrix.runtime.os }}
|
||||
timeout-minutes: ${{ matrix.runtime.timeout }}
|
||||
@ -102,21 +110,49 @@ jobs:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install pyyaml
|
||||
python ./scripts/install_deps.py
|
||||
run: ./scripts/install_deps.py
|
||||
|
||||
- name: Configure
|
||||
env:
|
||||
CMAKE_OSX_DEPLOYMENT_TARGET: ${{ matrix.runtime.target }}
|
||||
run: cmake -B build --preset=macos-release -DCMAKE_PREFIX_PATH=$(brew --prefix qt@5)
|
||||
|
||||
- name: Set env vars
|
||||
run: ./scripts/github_env.py
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ./build/bin/unittests
|
||||
|
||||
- name: Package
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
run: ./scripts/package.py
|
||||
env:
|
||||
APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
|
||||
APPLE_P12_CERTIFICATE: ${{ secrets.APPLE_P12_CERTIFICATE }}
|
||||
APPLE_P12_PASSWORD: ${{ secrets.APPLE_P12_PASSWORD }}
|
||||
APPLE_NOTARY_USER: ${{ secrets.APPLE_NOTARY_USER }}
|
||||
APPLE_NOTARY_PASSWORD: ${{ secrets.APPLE_NOTARY_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Upload to GitHub
|
||||
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.draft }}
|
||||
uses: ./.github/actions/dist-upload
|
||||
with:
|
||||
service: "github"
|
||||
github-target-filename: "synergy-macos-${{ matrix.runtime.target }}"
|
||||
|
||||
- name: Upload to Google Drive
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: ./.github/actions/dist-upload
|
||||
with:
|
||||
service: "gdrive"
|
||||
gdrive-target-dir: "synergy1/v1-core-standard/${{ env.SYNERGY_VERSION }}"
|
||||
gdrive-secret-key: ${{ secrets.GOOGLE_DRIVE_KEY }}
|
||||
gdrive-parent-folder-id: ${{ secrets.GOOGLE_DRIVE_TECH_DRIVE }}
|
||||
|
||||
linux:
|
||||
runs-on: ${{ matrix.distro.runs-on }}
|
||||
timeout-minutes: 10
|
||||
@ -126,11 +162,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
distro:
|
||||
- name: centos-7.6
|
||||
container: symless/synergy-core:centos7.6
|
||||
runs-on: ubuntu-latest
|
||||
legacy-cmake: true
|
||||
|
||||
- name: centos-8
|
||||
container: symless/synergy-core:centos8
|
||||
runs-on: ubuntu-latest
|
||||
@ -166,7 +197,6 @@ jobs:
|
||||
|
||||
- name: ubuntu-24.04
|
||||
runs-on: ubuntu-24.04
|
||||
install-deps: true
|
||||
|
||||
steps:
|
||||
# Use @v3 since some older Linux distro versions don't support @v4
|
||||
@ -176,10 +206,11 @@ jobs:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Install dependencies
|
||||
if: ${{ matrix.distro.install-deps }}
|
||||
run: python ./scripts/install_deps.py
|
||||
run: |
|
||||
${{ matrix.distro.python-deps }}
|
||||
./scripts/install_deps.py
|
||||
|
||||
- name: Configure
|
||||
- name: Configure (modern)
|
||||
if: ${{ !matrix.distro.legacy-cmake }}
|
||||
run: cmake -B build --preset=linux-release
|
||||
|
||||
@ -188,8 +219,15 @@ jobs:
|
||||
if: ${{ matrix.distro.legacy-cmake }}
|
||||
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Set env vars
|
||||
run: ./scripts/github_env.py
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ./build/bin/unittests
|
||||
|
||||
- name: Package
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
run: ./scripts/package.py
|
||||
|
||||
128
.github/workflows/job-build-mac-10.13.yaml
vendored
128
.github/workflows/job-build-mac-10.13.yaml
vendored
@ -1,128 +0,0 @@
|
||||
name: "Build macOS 10.13"
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
build-mac-10:
|
||||
runs-on: "macos-latest-xlarge"
|
||||
timeout-minutes: 10
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
runtime:
|
||||
- name: "synergy"
|
||||
remote_folder: "v1-core-standard"
|
||||
enterprise: ""
|
||||
business: ""
|
||||
- name: "synergy-enterprise"
|
||||
remote_folder: "v1-core-enterprise"
|
||||
enterprise: "1"
|
||||
business: ""
|
||||
- name: "synergy-business"
|
||||
remote_folder: "v1-core-business"
|
||||
enterprise: ""
|
||||
business: "1"
|
||||
|
||||
env:
|
||||
GIT_COMMIT: ${{ github.sha }}
|
||||
SYNERGY_ENTERPRISE: ${{ matrix.runtime.enterprise }}
|
||||
SYNERGY_BUSINESS: ${{ matrix.runtime.business }}
|
||||
Qt5_DIR: /usr/local/opt/qt/5.15.2/clang_64
|
||||
OpenSSL_DIR: /usr/local/ssl
|
||||
CODESIGN_ID: "Developer ID Application: Symless Ltd (4HX897Y6GJ)"
|
||||
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Setup Environment
|
||||
run: |
|
||||
python3 -m pip install dmgbuild
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
CMAKE_PREFIX_PATH: "${{ env.Qt5_DIR }};${{ env.OpenSSL_DIR }}"
|
||||
run: |
|
||||
export PATH="$Qt5_DIR/bin:$PATH"
|
||||
python3 scripts/build_version.py
|
||||
mkdir build
|
||||
cd build
|
||||
cmake \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_CONFIGURATION_TYPES=Release ..
|
||||
. ./version
|
||||
make -j
|
||||
make install/strip
|
||||
|
||||
- name: Version Info
|
||||
id: version
|
||||
run: |
|
||||
. ./build/version
|
||||
SYNERGY_VERSION="${SYNERGY_VERSION_MAJOR}.${SYNERGY_VERSION_MINOR}.${SYNERGY_VERSION_PATCH}"
|
||||
SYNERGY_REVISION=$(git rev-parse --short=8 HEAD)
|
||||
SYNERGY_DMG_VERSION="${SYNERGY_VERSION}-${SYNERGY_VERSION_STAGE}.${SYNERGY_REVISION}"
|
||||
echo "::set-output name=SYNERGY_VERSION_MAJOR::$SYNERGY_VERSION_MAJOR"
|
||||
echo "::set-output name=SYNERGY_VERSION_MINOR::$SYNERGY_VERSION_MINOR"
|
||||
echo "::set-output name=SYNERGY_VERSION_PATCH::$SYNERGY_VERSION_PATCH"
|
||||
echo "::set-output name=SYNERGY_VERSION_STAGE::$SYNERGY_VERSION_STAGE"
|
||||
echo "::set-output name=SYNERGY_VERSION_BUILD::$SYNERGY_VERSION_BUILD"
|
||||
echo "::set-output name=SYNERGY_VERSION::$SYNERGY_VERSION"
|
||||
echo "::set-output name=SYNERGY_REVISION::$SYNERGY_REVISION"
|
||||
echo "::set-output name=SYNERGY_DMG_VERSION::$SYNERGY_DMG_VERSION"
|
||||
SYNERGY_PACKAGE_NAME=${{ matrix.runtime.name }}
|
||||
SYNERGY_REMOTE_FOLDER="${{ matrix.runtime.remote_folder }}/${SYNERGY_VERSION}/${SYNERGY_VERSION_STAGE}/b${SYNERGY_VERSION_BUILD}-${SYNERGY_REVISION}"
|
||||
SYNERGY_DMG_FILENAME="${SYNERGY_PACKAGE_NAME}_${SYNERGY_DMG_VERSION}_macos-10.13_x86-64.dmg"
|
||||
echo "SYNERGY_REMOTE_FOLDER: $SYNERGY_REMOTE_FOLDER"
|
||||
echo "::set-output name=SYNERGY_REMOTE_FOLDER::$SYNERGY_REMOTE_FOLDER"
|
||||
echo "::set-output name=SYNERGY_PACKAGE_NAME::$SYNERGY_PACKAGE_NAME"
|
||||
echo "::set-output name=SYNERGY_DMG_FILENAME::$SYNERGY_DMG_FILENAME"
|
||||
|
||||
- name: Sign applicaiton
|
||||
run: |
|
||||
export PATH="$Qt5_DIR/bin:$PATH"
|
||||
macdeployqt ${{ github.workspace }}/build/bundle/Synergy.app -codesign="$CODESIGN_ID"
|
||||
codesign -f --options runtime --deep -s "$CODESIGN_ID" ${{ github.workspace }}/build/bundle/Synergy.app
|
||||
ln -s /Applications ${{ github.workspace }}/build/bundle/Applications
|
||||
|
||||
- name: Create Installer
|
||||
env:
|
||||
SYNERGY_DMG_FILENAME: ${{ steps.version.outputs.SYNERGY_DMG_FILENAME }}
|
||||
run: |
|
||||
dmgbuild \
|
||||
-s CI/MacOS/installator_settings.py \
|
||||
-D app=${{ github.workspace }}/build/bundle/Synergy.app \
|
||||
-D background=${{ github.workspace }}/build/bundle/Synergy.app/Contents/Resources/.installer_background.tiff \
|
||||
"Synergy" \
|
||||
$SYNERGY_DMG_FILENAME
|
||||
mkdir pkg
|
||||
mv $SYNERGY_DMG_FILENAME pkg/
|
||||
cd pkg
|
||||
md5 -r $SYNERGY_DMG_FILENAME >> $SYNERGY_DMG_FILENAME.checksum.txt
|
||||
shasum $SYNERGY_DMG_FILENAME >> $SYNERGY_DMG_FILENAME.checksum.txt
|
||||
shasum -a 256 $SYNERGY_DMG_FILENAME >> $SYNERGY_DMG_FILENAME.checksum.txt
|
||||
|
||||
- name: Submit for Notarization
|
||||
env:
|
||||
ASC_USERNAME: ${{ secrets.ASC_USERNAME }}
|
||||
NOTORY_APP_PASSWORD: ${{ secrets.NOTORY_APP_PASSWORD }}
|
||||
SYNERGY_VERSION: ${{ steps.version.outputs.SYNERGY_VERSION }}
|
||||
SYNERGY_REVISION: ${{ steps.version.outputs.SYNERGY_REVISION }}
|
||||
SYNERGY_DMG_FILENAME: ${{ steps.version.outputs.SYNERGY_DMG_FILENAME }}
|
||||
run: |
|
||||
cd pkg
|
||||
../CI/MacOS/notorize.sh
|
||||
|
||||
- name: Send package to Binary Storage
|
||||
uses: garygrossgarten/github-action-scp@v0.7.3
|
||||
with:
|
||||
local: "${{ github.workspace }}/pkg/"
|
||||
remote: "${{ secrets.BINARIES_SSH_DIR }}/${{ steps.version.outputs.SYNERGY_REMOTE_FOLDER }}/"
|
||||
host: ${{ secrets.BINARIES_SSH_HOST }}
|
||||
username: ${{ secrets.BINARIES_SSH_USER }}
|
||||
privateKey: ${{ secrets.BINARIES_SSH_KEY }}
|
||||
134
.github/workflows/job-build-mac-11.yaml
vendored
134
.github/workflows/job-build-mac-11.yaml
vendored
@ -1,134 +0,0 @@
|
||||
name: "Build macOS 11"
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: "/usr/bin/arch -arch arm64e /bin/bash --noprofile --norc -eo pipefail {0}"
|
||||
|
||||
jobs:
|
||||
build-mac-11:
|
||||
runs-on: "macos-latest-xlarge"
|
||||
timeout-minutes: 10
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
runtime:
|
||||
- name: "synergy"
|
||||
remote_folder: "v1-core-standard"
|
||||
enterprise: ""
|
||||
business: ""
|
||||
- name: "synergy-enterprise"
|
||||
remote_folder: "v1-core-enterprise"
|
||||
enterprise: "1"
|
||||
business: ""
|
||||
- name: "synergy-business"
|
||||
remote_folder: "v1-core-business"
|
||||
enterprise: ""
|
||||
business: "1"
|
||||
|
||||
env:
|
||||
GIT_COMMIT: ${{ github.sha }}
|
||||
SYNERGY_ENTERPRISE: ${{ matrix.runtime.enterprise }}
|
||||
SYNERGY_BUSINESS: ${{ matrix.runtime.business }}
|
||||
CODESIGN_ID: "Developer ID Application: Symless Ltd (4HX897Y6GJ)"
|
||||
|
||||
steps:
|
||||
- name: Setup PATH
|
||||
run: echo "/opt/homebrew/bin" >> $GITHUB_PATH
|
||||
env:
|
||||
ARCH: ${{ matrix.runtime.arch }}
|
||||
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Setup Environment
|
||||
run: |
|
||||
python3 -m pip install dmgbuild
|
||||
brew bundle --file=- <<< "brew 'qt5'; brew 'openssl'"
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: Release
|
||||
run: |
|
||||
python3 scripts/build_version.py
|
||||
mkdir build
|
||||
cd build
|
||||
cmake \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=11 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH="$(brew --prefix qt5);$(brew --prefix openssl)" ..
|
||||
. ./version
|
||||
make -j
|
||||
make install/strip
|
||||
|
||||
- name: Version Info
|
||||
id: version
|
||||
run: |
|
||||
. ./build/version
|
||||
SYNERGY_VERSION="${SYNERGY_VERSION_MAJOR}.${SYNERGY_VERSION_MINOR}.${SYNERGY_VERSION_PATCH}"
|
||||
SYNERGY_REVISION=$(git rev-parse --short=8 HEAD)
|
||||
SYNERGY_DMG_VERSION="${SYNERGY_VERSION}-${SYNERGY_VERSION_STAGE}.${SYNERGY_REVISION}"
|
||||
echo "::set-output name=SYNERGY_VERSION_MAJOR::$SYNERGY_VERSION_MAJOR"
|
||||
echo "::set-output name=SYNERGY_VERSION_MINOR::$SYNERGY_VERSION_MINOR"
|
||||
echo "::set-output name=SYNERGY_VERSION_PATCH::$SYNERGY_VERSION_PATCH"
|
||||
echo "::set-output name=SYNERGY_VERSION_STAGE::$SYNERGY_VERSION_STAGE"
|
||||
echo "::set-output name=SYNERGY_VERSION_BUILD::$SYNERGY_VERSION_BUILD"
|
||||
echo "::set-output name=SYNERGY_VERSION::$SYNERGY_VERSION"
|
||||
echo "::set-output name=SYNERGY_REVISION::$SYNERGY_REVISION"
|
||||
echo "::set-output name=SYNERGY_DMG_VERSION::$SYNERGY_DMG_VERSION"
|
||||
SYNERGY_PACKAGE_NAME=${{ matrix.runtime.name }}
|
||||
SYNERGY_REMOTE_FOLDER="${{ matrix.runtime.remote_folder }}/${SYNERGY_VERSION}/${SYNERGY_VERSION_STAGE}/b${SYNERGY_VERSION_BUILD}-${SYNERGY_REVISION}"
|
||||
SYNERGY_DMG_FILENAME="${SYNERGY_PACKAGE_NAME}_${SYNERGY_DMG_VERSION}_macos-arm64.dmg"
|
||||
echo "SYNERGY_REMOTE_FOLDER: $SYNERGY_REMOTE_FOLDER"
|
||||
echo "::set-output name=SYNERGY_REMOTE_FOLDER::$SYNERGY_REMOTE_FOLDER"
|
||||
echo "::set-output name=SYNERGY_PACKAGE_NAME::$SYNERGY_PACKAGE_NAME"
|
||||
echo "::set-output name=SYNERGY_DMG_FILENAME::$SYNERGY_DMG_FILENAME"
|
||||
|
||||
- name: Sign applicaiton
|
||||
run: |
|
||||
export PATH="$(brew --prefix qt5)/bin:$PATH"
|
||||
macdeployqt ${{ github.workspace }}/build/bundle/Synergy.app -codesign="$CODESIGN_ID"
|
||||
codesign -f --options runtime --deep -s "$CODESIGN_ID" ${{ github.workspace }}/build/bundle/Synergy.app
|
||||
ln -s /Applications ${{ github.workspace }}/build/bundle/Applications
|
||||
|
||||
- name: Create Installer
|
||||
env:
|
||||
SYNERGY_DMG_FILENAME: ${{ steps.version.outputs.SYNERGY_DMG_FILENAME }}
|
||||
run: |
|
||||
dmgbuild \
|
||||
-s CI/MacOS/installator_settings.py \
|
||||
-D app=${{ github.workspace }}/build/bundle/Synergy.app \
|
||||
-D background=${{ github.workspace }}/build/bundle/Synergy.app/Contents/Resources/.installer_background.tiff \
|
||||
"Synergy" \
|
||||
$SYNERGY_DMG_FILENAME
|
||||
mkdir pkg
|
||||
mv $SYNERGY_DMG_FILENAME pkg/
|
||||
cd pkg
|
||||
md5 -r $SYNERGY_DMG_FILENAME >> $SYNERGY_DMG_FILENAME.checksum.txt
|
||||
shasum $SYNERGY_DMG_FILENAME >> $SYNERGY_DMG_FILENAME.checksum.txt
|
||||
shasum -a 256 $SYNERGY_DMG_FILENAME >> $SYNERGY_DMG_FILENAME.checksum.txt
|
||||
|
||||
- name: Submit for Notarization
|
||||
env:
|
||||
ASC_USERNAME: ${{ secrets.ASC_USERNAME }}
|
||||
NOTORY_APP_PASSWORD: ${{ secrets.NOTORY_APP_PASSWORD }}
|
||||
SYNERGY_VERSION: ${{ steps.version.outputs.SYNERGY_VERSION }}
|
||||
SYNERGY_REVISION: ${{ steps.version.outputs.SYNERGY_REVISION }}
|
||||
SYNERGY_DMG_FILENAME: ${{ steps.version.outputs.SYNERGY_DMG_FILENAME }}
|
||||
run: |
|
||||
cd pkg
|
||||
../CI/MacOS/notorize.sh
|
||||
|
||||
- name: Send package to Binary Storage
|
||||
uses: garygrossgarten/github-action-scp@v0.7.3
|
||||
with:
|
||||
local: "${{ github.workspace }}/pkg/"
|
||||
remote: "${{ secrets.BINARIES_SSH_DIR }}/${{ steps.version.outputs.SYNERGY_REMOTE_FOLDER }}/"
|
||||
host: ${{ secrets.BINARIES_SSH_HOST }}
|
||||
username: ${{ secrets.BINARIES_SSH_USER }}
|
||||
privateKey: ${{ secrets.BINARIES_SSH_KEY }}
|
||||
1
.github/workflows/sonarcloud-analysis.yml
vendored
1
.github/workflows/sonarcloud-analysis.yml
vendored
@ -49,7 +49,6 @@ jobs:
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON ..
|
||||
. ./version
|
||||
build-wrapper-linux-x86-64 --out-dir bw-output make -j
|
||||
|
||||
- name: Running coverage
|
||||
|
||||
47
.gitignore
vendored
47
.gitignore
vendored
@ -1,38 +1,15 @@
|
||||
.vscode
|
||||
config.h
|
||||
.DS_Store
|
||||
*.pyc
|
||||
*.o
|
||||
*~
|
||||
\.*.swp
|
||||
*build-gui-Desktop_Qt*
|
||||
/bin
|
||||
/lib
|
||||
/build*
|
||||
/CMakeFiles
|
||||
/ext/cryptopp562
|
||||
/ext/openssl
|
||||
/src/gui/Makefile*
|
||||
/src/gui/object_script*
|
||||
/src/gui/tmp
|
||||
/src/gui/ui_*
|
||||
src/gui/gui.pro.user*
|
||||
src/gui/.qmake.stash
|
||||
src/gui/.rnd
|
||||
src/setup/win32/synergy.suo
|
||||
/.idea
|
||||
/cmake-build-*
|
||||
/CMakeLists.txt.user
|
||||
/.vs
|
||||
/CMakeLists.txt.*
|
||||
/doxygen/
|
||||
# temp dirs created during build
|
||||
/build
|
||||
/dist
|
||||
/deps
|
||||
flatpak/.flatpak-builder
|
||||
flatpak/build
|
||||
flatpak/export
|
||||
flatpak/synergy.desktop
|
||||
flatpak/*.flatpak
|
||||
*.code-workspace
|
||||
*.idx
|
||||
/tmp
|
||||
/scripts/**/*.pyc
|
||||
aqtinstall.log
|
||||
Brewfile.lock.json
|
||||
/.cache
|
||||
|
||||
# typical developer-created files
|
||||
.vscode
|
||||
.DS_Store
|
||||
*.code-workspace
|
||||
.env*
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
cmake_minimum_required (VERSION 3.5)
|
||||
project (synergy-core C CXX)
|
||||
include (cmake/Version.cmake)
|
||||
|
||||
# use response files so that ninja can compile on windows,
|
||||
# otherwise you get an error when linking qt:
|
||||
@ -59,6 +60,18 @@ else()
|
||||
option (SYNERGY_BUSINESS "Build Business" OFF)
|
||||
endif()
|
||||
|
||||
if (SYNERGY_DEVELOPER_MODE)
|
||||
add_definitions (-DSYNERGY_DEVELOPER_MODE=1)
|
||||
endif()
|
||||
|
||||
if (SYNERGY_ENTERPRISE)
|
||||
add_definitions (-DSYNERGY_ENTERPRISE=1)
|
||||
endif()
|
||||
|
||||
if (SYNERGY_BUSINESS)
|
||||
add_definitions(-DSYNERGY_BUSINESS=1)
|
||||
endif()
|
||||
|
||||
set (CMAKE_CXX_STANDARD 20)
|
||||
set (CMAKE_CXX_EXTENSIONS OFF)
|
||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
@ -81,11 +94,6 @@ if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
add_definitions (-DNDEBUG)
|
||||
endif()
|
||||
|
||||
#
|
||||
# Synergy version
|
||||
#
|
||||
include (cmake/Version.cmake)
|
||||
|
||||
# TODO: Find out why we need these, and remove them
|
||||
if (COMMAND cmake_policy)
|
||||
cmake_policy (SET CMP0003 NEW)
|
||||
@ -386,7 +394,7 @@ endif()
|
||||
|
||||
|
||||
#
|
||||
# Configure_file... but for directories, recursively.
|
||||
# Same as the `configure_file` command but for directories recursively.
|
||||
#
|
||||
macro (configure_files srcDir destDir)
|
||||
message (STATUS "Configuring directory ${destDir}")
|
||||
@ -415,33 +423,14 @@ macro (configure_files srcDir destDir)
|
||||
endforeach (templateFile)
|
||||
endmacro (configure_files)
|
||||
|
||||
macro(generate_versionfile)
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin" OR ${CMAKE_SYSTEM_NAME} MATCHES "Linux|.*BSD|DragonFly")
|
||||
FILE(WRITE ${CMAKE_BINARY_DIR}/version
|
||||
"export SYNERGY_VERSION_MAJOR=\"${SYNERGY_VERSION_MAJOR}\"\n"
|
||||
"export SYNERGY_VERSION_MINOR=\"${SYNERGY_VERSION_MINOR}\"\n"
|
||||
"export SYNERGY_VERSION_PATCH=\"${SYNERGY_VERSION_PATCH}\"\n"
|
||||
"export SYNERGY_VERSION_BUILD=\"${SYNERGY_VERSION_BUILD}\"\n"
|
||||
"export SYNERGY_VERSION_STAGE=\"${SYNERGY_VERSION_STAGE}\"\n")
|
||||
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
|
||||
FILE(WRITE ${CMAKE_BINARY_DIR}/version.bat
|
||||
"SET SYNERGY_VERSION_MAJOR=${SYNERGY_VERSION_MAJOR}\n"
|
||||
"SET SYNERGY_VERSION_MINOR=${SYNERGY_VERSION_MINOR}\n"
|
||||
"SET SYNERGY_VERSION_PATCH=${SYNERGY_VERSION_PATCH}\n"
|
||||
"SET SYNERGY_VERSION_BUILD=${SYNERGY_VERSION_BUILD}\n"
|
||||
"SET SYNERGY_VERSION_STAGE=${SYNERGY_VERSION_STAGE}\n")
|
||||
endif()
|
||||
endmacro(generate_versionfile)
|
||||
|
||||
generate_versionfile()
|
||||
|
||||
if (${SYNERGY_BUILD_INSTALLER})
|
||||
|
||||
#
|
||||
# macOS app Bundle
|
||||
# macOS app bundle
|
||||
#
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
set (CMAKE_INSTALL_RPATH "@loader_path/../Libraries;@loader_path/../Frameworks")
|
||||
set (SYNERGY_BUNDLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/dist/macos/bundle)
|
||||
set (SYNERGY_BUNDLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/res/dist/macos/bundle)
|
||||
set (SYNERGY_BUNDLE_DIR ${CMAKE_BINARY_DIR}/bundle)
|
||||
set (SYNERGY_BUNDLE_APP_DIR ${SYNERGY_BUNDLE_DIR}/Synergy.app)
|
||||
set (SYNERGY_BUNDLE_BINARY_DIR ${SYNERGY_BUNDLE_APP_DIR}/Contents/MacOS)
|
||||
@ -454,14 +443,14 @@ endif()
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
|
||||
message (STATUS "Configuring the v1 installer")
|
||||
set(QT_PATH $ENV{CMAKE_PREFIX_PATH})
|
||||
configure_files (${CMAKE_CURRENT_SOURCE_DIR}/dist/wix ${CMAKE_BINARY_DIR}/installer)
|
||||
configure_files (${CMAKE_CURRENT_SOURCE_DIR}/res/dist/wix ${CMAKE_BINARY_DIR}/installer)
|
||||
endif()
|
||||
|
||||
#
|
||||
# Linux installation
|
||||
#
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux|.*BSD|DragonFly")
|
||||
configure_files (${CMAKE_CURRENT_SOURCE_DIR}/dist/rpm ${CMAKE_BINARY_DIR}/rpm)
|
||||
configure_files (${CMAKE_CURRENT_SOURCE_DIR}/res/dist/rpm ${CMAKE_BINARY_DIR}/rpm)
|
||||
install(FILES res/synergy.svg DESTINATION share/icons/hicolor/scalable/apps)
|
||||
if("${VERSION_MAJOR}" STREQUAL "2")
|
||||
install(FILES res/synergy2.desktop DESTINATION share/applications)
|
||||
@ -471,6 +460,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux|.*BSD|DragonFly")
|
||||
endif()
|
||||
|
||||
else()
|
||||
message (STATUS "NOT configuring the v1 installer")
|
||||
message (STATUS "NOT configuring the installer")
|
||||
endif()
|
||||
|
||||
add_subdirectory (src)
|
||||
|
||||
@ -33,6 +33,7 @@ Enhancements:
|
||||
- #7336 Add C++ and LLDB to VS Code recommendations
|
||||
- #7351 Use deps script to make life easier for contribs
|
||||
- #7352 Combine GitHub workflows to reduce config duplication
|
||||
- #7353 Re-implement packaging for GitHub workflows (macOS)
|
||||
|
||||
# 1.14.6
|
||||
|
||||
|
||||
@ -59,25 +59,20 @@ endif()
|
||||
|
||||
string (TIMESTAMP SYNERGY_BUILD_DATE "%Y%m%d" UTC)
|
||||
set (SYNERGY_SNAPSHOT_INFO "${SYNERGY_VERSION_STAGE}.${SYNERGY_REVISION}")
|
||||
set (SYNERGY_VERSION_TAG "${SYNERGY_VERSION_STAGE}.b${SYNERGY_VERSION_BUILD}-${SYNERGY_REVISION}")
|
||||
set (SYNERGY_VERSION_TAG "${SYNERGY_VERSION_STAGE}+build-${SYNERGY_VERSION_BUILD}+${SYNERGY_REVISION}")
|
||||
set (SYNERGY_VERSION "${SYNERGY_VERSION_MAJOR}.${SYNERGY_VERSION_MINOR}.${SYNERGY_VERSION_PATCH}")
|
||||
set (SYNERGY_VERSION_STRING "${SYNERGY_VERSION}-${SYNERGY_VERSION_TAG}")
|
||||
message (STATUS "Full Synergy version string is '" ${SYNERGY_VERSION_STRING} "'")
|
||||
set (SYNERGY_VERSION_LONG "${SYNERGY_VERSION}-${SYNERGY_VERSION_TAG}")
|
||||
message (STATUS "Long version number: " ${SYNERGY_VERSION_LONG})
|
||||
|
||||
add_definitions (-DSYNERGY_VERSION="${SYNERGY_VERSION}")
|
||||
add_definitions (-DSYNERGY_VERSION_STRING="${SYNERGY_VERSION_STRING}")
|
||||
add_definitions (-DSYNERGY_VERSION_LONG="${SYNERGY_VERSION_LONG}")
|
||||
add_definitions (-DSYNERGY_REVISION="${SYNERGY_REVISION}")
|
||||
add_definitions (-DSYNERGY_BUILD_DATE="${SYNERGY_BUILD_DATE}")
|
||||
add_definitions (-DSYNERGY_VERSION_BUILD=${SYNERGY_VERSION_BUILD})
|
||||
|
||||
if (SYNERGY_DEVELOPER_MODE)
|
||||
add_definitions (-DSYNERGY_DEVELOPER_MODE=1)
|
||||
endif()
|
||||
|
||||
if (SYNERGY_ENTERPRISE)
|
||||
add_definitions (-DSYNERGY_ENTERPRISE=1)
|
||||
endif()
|
||||
|
||||
if (SYNERGY_BUSINESS)
|
||||
add_definitions(-DSYNERGY_BUSINESS=1)
|
||||
endif()
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/.env.version
|
||||
"SYNERGY_VERSION_MAJOR=${SYNERGY_VERSION_MAJOR}\n"
|
||||
"SYNERGY_VERSION_MINOR=${SYNERGY_VERSION_MINOR}\n"
|
||||
"SYNERGY_VERSION_PATCH=${SYNERGY_VERSION_PATCH}\n"
|
||||
"SYNERGY_VERSION_BUILD=${SYNERGY_VERSION_BUILD}\n"
|
||||
"SYNERGY_VERSION_STAGE=${SYNERGY_VERSION_STAGE}\n")
|
||||
|
||||
65
config.yml
Normal file
65
config.yml
Normal file
@ -0,0 +1,65 @@
|
||||
config:
|
||||
windows:
|
||||
dependencies:
|
||||
command: choco install Chocolatey.config -y
|
||||
qt:
|
||||
version: 5.15.2
|
||||
mirror: https://qt.mirror.constant.com/
|
||||
install-dir: C:\Qt
|
||||
ci:
|
||||
skip:
|
||||
edit-config: Chocolatey.config
|
||||
packages:
|
||||
- cmake
|
||||
- visualstudio2022buildtools
|
||||
|
||||
mac:
|
||||
qt-prefix-command: brew --prefix qt@5
|
||||
dependencies:
|
||||
command: brew bundle --file=Brewfile
|
||||
|
||||
# arch, opensuse, etc, patches welcome! :)
|
||||
linux:
|
||||
debian: &debian
|
||||
dependencies:
|
||||
command: sudo apt-get update; sudo apt-get install -y \
|
||||
cmake \
|
||||
make \
|
||||
g++ \
|
||||
xorg-dev \
|
||||
libx11-dev \
|
||||
libxtst-dev \
|
||||
libssl-dev \
|
||||
pkg-config \
|
||||
libglib2.0-dev \
|
||||
libgdk-pixbuf-2.0-dev \
|
||||
libnotify-dev \
|
||||
libxkbfile-dev \
|
||||
qtbase5-dev \
|
||||
qttools5-dev \
|
||||
libgtk-3-dev \
|
||||
rpm
|
||||
|
||||
ubuntu:
|
||||
<<: *debian
|
||||
|
||||
fedora: &fedora
|
||||
dependencies:
|
||||
command: sudo dnf check-update; sudo dnf install -y \
|
||||
cmake \
|
||||
make \
|
||||
gcc-c++ \
|
||||
openssl-devel \
|
||||
libXtst-devel \
|
||||
glib2-devel \
|
||||
gdk-pixbuf2-devel \
|
||||
libnotify-devel \
|
||||
qt5-qtbase-devel \
|
||||
qt5-qttools-devel \
|
||||
libxkbfile-devel \
|
||||
gtk3-devel \
|
||||
rpm-build
|
||||
|
||||
centos:
|
||||
<<: *fedora
|
||||
command: sudo yum install -y
|
||||
25
cspell.json
Normal file
25
cspell.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": "0.2",
|
||||
"ignorePaths": [],
|
||||
"dictionaryDefinitions": [],
|
||||
"dictionaries": [],
|
||||
"words": [
|
||||
"aqtinstall",
|
||||
"codesign",
|
||||
"codesigning",
|
||||
"distros",
|
||||
"dmgbuild",
|
||||
"dotenv",
|
||||
"gdrive",
|
||||
"keychain",
|
||||
"Keychains",
|
||||
"macdeployqt",
|
||||
"msvc",
|
||||
"notarytool",
|
||||
"outputdir",
|
||||
"runas",
|
||||
"winget"
|
||||
],
|
||||
"ignoreWords": [],
|
||||
"import": []
|
||||
}
|
||||
96
deps.yml
96
deps.yml
@ -1,96 +0,0 @@
|
||||
dependencies:
|
||||
windows:
|
||||
command: choco install Chocolatey.config -y
|
||||
qt:
|
||||
version: 5.15.2
|
||||
mirror: https://qt.mirror.constant.com/
|
||||
install-dir: C:\Qt
|
||||
ci:
|
||||
skip:
|
||||
edit-config: Chocolatey.config
|
||||
packages:
|
||||
- cmake
|
||||
- visualstudio2022buildtools
|
||||
|
||||
mac:
|
||||
command: brew bundle --file=Brewfile
|
||||
|
||||
linux:
|
||||
debian: &debian
|
||||
command: sudo apt-get update && sudo apt-get install -y
|
||||
packages:
|
||||
- cmake
|
||||
- make
|
||||
- g++
|
||||
- xorg-dev
|
||||
- libx11-dev
|
||||
- libxtst-dev
|
||||
- libssl-dev
|
||||
- pkg-config
|
||||
- libglib2.0-dev
|
||||
- libgdk-pixbuf-2.0-dev
|
||||
- libnotify-dev
|
||||
- libxkbfile-dev
|
||||
- qtbase5-dev
|
||||
- qttools5-dev
|
||||
- libgtk-3-dev
|
||||
- rpm
|
||||
|
||||
ubuntu:
|
||||
<<: *debian
|
||||
|
||||
fedora: &fedora
|
||||
command: sudo dnf install -y
|
||||
packages:
|
||||
- cmake
|
||||
- make
|
||||
- gcc-c++
|
||||
- openssl-devel
|
||||
- libXtst-devel
|
||||
- glib2-devel
|
||||
- gdk-pixbuf2-devel
|
||||
- libnotify-devel
|
||||
- qt5-qtbase-devel
|
||||
- qt5-qttools-devel
|
||||
- libxkbfile-devel
|
||||
- gtk3-devel
|
||||
- rpm-build
|
||||
|
||||
centos:
|
||||
<<: *fedora
|
||||
command: sudo yum install -y
|
||||
|
||||
# Warning: AI generated (untested)
|
||||
arch:
|
||||
command: sudo pacman -Syu --noconfirm
|
||||
packages:
|
||||
- cmake
|
||||
- make
|
||||
- gcc
|
||||
- libx11
|
||||
- libxtst
|
||||
- openssl
|
||||
- pkg-config
|
||||
- glib2
|
||||
- gdk-pixbuf2
|
||||
- libnotify
|
||||
- libxkbfile
|
||||
- qt5-base
|
||||
- gtk3
|
||||
- rpm
|
||||
|
||||
# Warning: AI generated (untested)
|
||||
opensuse:
|
||||
command: sudo zypper install -y
|
||||
packages:
|
||||
- cmake
|
||||
- make
|
||||
- gcc-c++
|
||||
- libXtst-devel
|
||||
- glib2-devel
|
||||
- gdk-pixbuf-devel
|
||||
- libnotify-devel
|
||||
- libxkbfile-devel
|
||||
- libqt5-qtbase-devel
|
||||
- gtk3-devel
|
||||
- rpm-build
|
||||
@ -1,18 +0,0 @@
|
||||
Mac OS X Readme
|
||||
===============
|
||||
|
||||
To install on Mac OS X with the .zip distribution (first seen in 1.3.6) you must follow these steps:
|
||||
|
||||
1. Extract the zip file to any location (usually double click will do this)
|
||||
2. Open Terminal, and cd to the extracted directory (e.g. /Users/my-name/Downloads/extracted-dir/)
|
||||
3. Copy the binaries to /usr/bin using: sudo cp synergy* /usr/bin
|
||||
4. Correct the permissions and ownership: sudo chown root:wheel /usr/bin/synergy*; sudo chmod 555 /usr/bin/synergy*
|
||||
|
||||
Alternatively, you can copy the binaries as root. How to enable the root user in Mac OS X:
|
||||
http://support.apple.com/en-us/ht1528
|
||||
|
||||
Once the binaries have been copied to /usr/bin, you should follow the configuration guide:
|
||||
http://synergy2.sourceforge.net/configuration.html
|
||||
|
||||
If you have any problems, see the [[Support]] page:
|
||||
http://symless.com/help/
|
||||
150
res/dist/macos/dmgbuild/settings.py
vendored
Normal file
150
res/dist/macos/dmgbuild/settings.py
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os.path
|
||||
|
||||
# Use like this: dmgbuild -s settings.py "Test Volume" test.dmg
|
||||
# dmgbuild -s settings.py -D app=/path/to/My.app "My Application" MyApp.dmg
|
||||
|
||||
# .. Useful stuff ..............................................................
|
||||
|
||||
app = defines.get("app")
|
||||
app_basename = os.path.basename(app)
|
||||
|
||||
# .. Basics ....................................................................
|
||||
|
||||
# Volume format (see hdiutil create -help)
|
||||
format = defines.get("format", "UDBZ")
|
||||
|
||||
# Volume size
|
||||
size = defines.get("size", None)
|
||||
|
||||
# Files to include
|
||||
files = [app]
|
||||
|
||||
# Symlinks to create
|
||||
symlinks = {"Applications": "/Applications"}
|
||||
|
||||
# Volume icon
|
||||
#
|
||||
# You can either define icon, in which case that icon file will be copied to the
|
||||
# image, *or* you can define badge_icon, in which case the icon file you specify
|
||||
# will be used to badge the system's Removable Disk icon
|
||||
#
|
||||
icon = os.path.join(app, "Contents/Resources/VolumeIcon.icns")
|
||||
|
||||
# Where to put the icons
|
||||
icon_locations = {
|
||||
app_basename: (144, 190),
|
||||
"Applications": (455, 190),
|
||||
".background.tiff": (150, 450),
|
||||
".VolumeIcon.icns": (455, 450),
|
||||
}
|
||||
|
||||
# .. Window configuration ......................................................
|
||||
|
||||
# Background
|
||||
#
|
||||
# This is a STRING containing any of the following:
|
||||
#
|
||||
# #3344ff - web-style RGB color
|
||||
# #34f - web-style RGB color, short form (#34f == #3344ff)
|
||||
# rgb(1,0,0) - RGB color, each value is between 0 and 1
|
||||
# hsl(120,1,.5) - HSL (hue saturation lightness) color
|
||||
# hwb(300,0,0) - HWB (hue whiteness blackness) color
|
||||
# cmyk(0,1,0,0) - CMYK color
|
||||
# goldenrod - X11/SVG named color
|
||||
# builtin-arrow - A simple built-in background with a blue arrow
|
||||
# /foo/bar/baz.png - The path to an image file
|
||||
#
|
||||
# The hue component in hsl() and hwb() may include a unit; it defaults to
|
||||
# degrees ('deg'), but also supports radians ('rad') and gradians ('grad'
|
||||
# or 'gon').
|
||||
#
|
||||
# Other color components may be expressed either in the range 0 to 1, or
|
||||
# as percentages (e.g. 60% is equivalent to 0.6).
|
||||
background = os.path.join(app, "Contents/Resources/.background.tiff")
|
||||
|
||||
show_status_bar = False
|
||||
show_tab_view = False
|
||||
show_toolbar = False
|
||||
show_pathbar = False
|
||||
show_sidebar = False
|
||||
sidebar_width = 180
|
||||
|
||||
# Window position in ((x, y), (w, h)) format
|
||||
window_rect = ((200, 120), (620, 420))
|
||||
|
||||
# Select the default view; must be one of
|
||||
#
|
||||
# 'icon-view'
|
||||
# 'list-view'
|
||||
# 'column-view'
|
||||
# 'coverflow'
|
||||
#
|
||||
default_view = "icon-view"
|
||||
|
||||
# General view configuration
|
||||
show_icon_preview = False
|
||||
|
||||
# Set these to True to force inclusion of icon/list view settings (otherwise
|
||||
# we only include settings for the default view)
|
||||
include_icon_view_settings = "auto"
|
||||
include_list_view_settings = "auto"
|
||||
|
||||
# .. Icon view configuration ...................................................
|
||||
|
||||
arrange_by = None
|
||||
grid_offset = (0, 0)
|
||||
grid_spacing = 100
|
||||
scroll_position = (0, 0)
|
||||
label_pos = "bottom" # or 'right'
|
||||
text_size = 16
|
||||
icon_size = 100
|
||||
|
||||
# .. List view configuration ...................................................
|
||||
|
||||
# Column names are as follows:
|
||||
#
|
||||
# name
|
||||
# date-modified
|
||||
# date-created
|
||||
# date-added
|
||||
# date-last-opened
|
||||
# size
|
||||
# kind
|
||||
# label
|
||||
# version
|
||||
# comments
|
||||
#
|
||||
list_icon_size = 16
|
||||
list_text_size = 12
|
||||
list_scroll_position = (0, 0)
|
||||
list_sort_by = "name"
|
||||
list_use_relative_dates = True
|
||||
list_calculate_all_sizes = (False,)
|
||||
list_columns = ("name", "date-modified", "size", "kind", "date-added")
|
||||
list_column_widths = {
|
||||
"name": 300,
|
||||
"date-modified": 181,
|
||||
"date-created": 181,
|
||||
"date-added": 181,
|
||||
"date-last-opened": 181,
|
||||
"size": 97,
|
||||
"kind": 115,
|
||||
"label": 100,
|
||||
"version": 75,
|
||||
"comments": 300,
|
||||
}
|
||||
list_column_sort_directions = {
|
||||
"name": "ascending",
|
||||
"date-modified": "descending",
|
||||
"date-created": "descending",
|
||||
"date-added": "descending",
|
||||
"date-last-opened": "descending",
|
||||
"size": "descending",
|
||||
"kind": "ascending",
|
||||
"label": "ascending",
|
||||
"version": "ascending",
|
||||
"comments": "ascending",
|
||||
}
|
||||
@ -1,137 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
class VersionPart:
|
||||
|
||||
def __init__(self, part = ''):
|
||||
self.prefix = ''
|
||||
self.suffix = ''
|
||||
self.number = 0
|
||||
|
||||
if part:
|
||||
self.__parsePreffix(part)
|
||||
self.__parseNumber(part)
|
||||
self.__parseSuffix(part)
|
||||
|
||||
def __parsePreffix(self, part):
|
||||
if not part[0].isdigit():
|
||||
for i in part:
|
||||
if not i.isdigit():
|
||||
self.prefix += i
|
||||
else:
|
||||
break
|
||||
|
||||
def __parseNumber(self, part):
|
||||
start = len(self.prefix)
|
||||
end = part.find('-')
|
||||
if end > 0:
|
||||
self.number = int(part[start:end])
|
||||
else:
|
||||
self.number = int(part[start:])
|
||||
|
||||
def __parseSuffix(self, part):
|
||||
items = part.split('-')
|
||||
if len(items) == 2:
|
||||
self.suffix = '-' + items[1]
|
||||
|
||||
def __str__(self):
|
||||
return self.prefix + str(self.number) + self.suffix
|
||||
class Version:
|
||||
|
||||
def __init__(self, version):
|
||||
versionParts = version.split('.')
|
||||
|
||||
if len(versionParts) == 3:
|
||||
self.major = VersionPart(versionParts[0])
|
||||
self.minor = VersionPart(versionParts[1])
|
||||
self.build = VersionPart(versionParts[2])
|
||||
self.patch = VersionPart()
|
||||
self.patch.number = self.build.number
|
||||
elif len(versionParts) == 4:
|
||||
self.major = VersionPart(versionParts[0])
|
||||
self.minor = VersionPart(versionParts[1])
|
||||
self.patch = VersionPart(versionParts[2])
|
||||
self.build = VersionPart(versionParts[3])
|
||||
else:
|
||||
print('ERROR: Wrong version number')
|
||||
|
||||
def isSamePatch(self, version):
|
||||
return (self.major.number == version.major.number and
|
||||
self.minor.number == version.minor.number and
|
||||
self.patch.number == version.patch.number)
|
||||
|
||||
def __str__(self):
|
||||
result = str(self.major) + '.'
|
||||
result += str(self.minor) + '.'
|
||||
result += str(self.patch) + '.'
|
||||
result += str(self.build)
|
||||
return result
|
||||
|
||||
class VersionFile:
|
||||
def __init__(self, file):
|
||||
self.file = file
|
||||
|
||||
def setOption(self, name, value):
|
||||
fp = open(self.file, 'rt+')
|
||||
content = ''
|
||||
for line in fp:
|
||||
print(line)
|
||||
if line.find(name) != -1:
|
||||
line = 'set ('+ name + ' ' + value + ')'
|
||||
content += line
|
||||
fp.write(content)
|
||||
fp.close()
|
||||
|
||||
def findVersion(versions, cmakeVersion):
|
||||
gitVersion = Version(versions[0])
|
||||
for version in versions:
|
||||
ver = Version(version)
|
||||
if ver.isSamePatch(cmakeVersion):
|
||||
gitVersion = ver
|
||||
break
|
||||
print('INFO: Version '+ str(gitVersion) + ' has been read from git')
|
||||
return gitVersion
|
||||
|
||||
def getVesionFromGit(cmakeVersion):
|
||||
try:
|
||||
taggedRevision = subprocess.check_output(('git rev-list --tags --max-count=100').split(), universal_newlines=True)
|
||||
cmd = ('git describe --tags ' + taggedRevision).split()
|
||||
versions = subprocess.check_output(cmd, universal_newlines=True).split()
|
||||
return findVersion(versions, cmakeVersion)
|
||||
except subprocess.CalledProcessError:
|
||||
print('ERROR: Unable to get version from git')
|
||||
exit(1)
|
||||
|
||||
def updateVersionFile(number):
|
||||
fp = open('cmake/Version.cmake')
|
||||
content = fp.read()
|
||||
fp.close()
|
||||
|
||||
fp = open('cmake/Version.cmake', 'wt')
|
||||
fp.write(content.replace('set (SYNERGY_VERSION_BUILD 1)', 'set (SYNERGY_VERSION_BUILD ' + str(number) + ')'))
|
||||
fp.close()
|
||||
|
||||
def getOptionValue(source, option):
|
||||
start = source.find(option)
|
||||
if (start != -1):
|
||||
start += len(option) + 1
|
||||
end = source.find(')', start)
|
||||
return source[start : end]
|
||||
else:
|
||||
print("ERROR: Can't find option <" + option + ">");
|
||||
|
||||
def getVersionFromFile():
|
||||
fp = open('cmake/Version.cmake')
|
||||
content = fp.read()
|
||||
fp.close()
|
||||
|
||||
major = getOptionValue(content, 'SYNERGY_VERSION_MAJOR')
|
||||
minor = getOptionValue(content, 'SYNERGY_VERSION_MINOR')
|
||||
patch = getOptionValue(content, 'SYNERGY_VERSION_PATCH')
|
||||
|
||||
return Version(major + '.' + minor + '.' + patch)
|
||||
|
||||
if __name__ == '__main__':
|
||||
cmakeVersion = getVersionFromFile()
|
||||
gitVersion = getVesionFromGit(cmakeVersion)
|
||||
updateVersionFile(gitVersion.build.number)
|
||||
print('INFO: Build number is: <' + str(gitVersion) + '>')
|
||||
30
scripts/github_env.py
Executable file
30
scripts/github_env.py
Executable file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
from lib import env
|
||||
|
||||
# important: load venv before loading modules that install deps.
|
||||
env.ensure_in_venv(__file__)
|
||||
|
||||
github_key = "GITHUB_ENV"
|
||||
version_key = "SYNERGY_VERSION"
|
||||
|
||||
|
||||
def main():
|
||||
env_file = os.getenv(github_key)
|
||||
if not env_file:
|
||||
raise RuntimeError(f"Env var {github_key} not set")
|
||||
|
||||
if not os.path.exists(env_file):
|
||||
raise RuntimeError(f"File not found: {env_file}")
|
||||
|
||||
major, minor, patch, stage, _build = env.get_version_info()
|
||||
version_value = f"{major}.{minor}.{patch}-{stage}"
|
||||
|
||||
print(f"Setting env var: {version_key}={version_value}")
|
||||
with open(env_file, "a") as env_file:
|
||||
env_file.write(f"{version_key}={version_value}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,38 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
from lib import windows, cmd_utils
|
||||
import sys
|
||||
import argparse
|
||||
import traceback
|
||||
import os, sys, argparse, traceback
|
||||
from lib import env, cmd_utils
|
||||
|
||||
config_file = "deps.yml"
|
||||
|
||||
|
||||
class YamlError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PlatformError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PathError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
import yaml # type: ignore
|
||||
except ImportError:
|
||||
# this is fairly common in earlier versions of python3,
|
||||
# which is normally what you find on mac and windows.
|
||||
print("Python yaml module missing, please install: pip install pyyaml")
|
||||
sys.exit(1)
|
||||
# important: load venv before loading modules that install deps.
|
||||
env.ensure_in_venv(__file__)
|
||||
|
||||
|
||||
def main():
|
||||
"""Entry point for the script."""
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--pause-on-exit", action="store_true", help="Useful on Windows"
|
||||
@ -42,171 +17,110 @@ def main():
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
error = False
|
||||
try:
|
||||
deps = Dependencies(args.only)
|
||||
deps.install()
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
error = True
|
||||
|
||||
if args.pause_on_exit:
|
||||
input("Press enter to continue...")
|
||||
|
||||
|
||||
def get_os():
|
||||
"""Detects the operating system."""
|
||||
if sys.platform == "win32":
|
||||
return "windows"
|
||||
elif sys.platform == "darwin":
|
||||
return "mac"
|
||||
elif sys.platform.startswith("linux"):
|
||||
return "linux"
|
||||
else:
|
||||
raise PlatformError(f"Unsupported platform: {sys.platform}")
|
||||
|
||||
|
||||
def get_linux_distro():
|
||||
"""Detects the Linux distro."""
|
||||
os_file = "/etc/os-release"
|
||||
if os.path.isfile(os_file):
|
||||
with open(os_file) as f:
|
||||
for line in f:
|
||||
if line.startswith("ID="):
|
||||
return line.strip().split("=")[1].strip('"')
|
||||
return None
|
||||
|
||||
|
||||
class Config:
|
||||
"""Reads the dependencies configuration file."""
|
||||
|
||||
def __init__(self):
|
||||
with open(config_file, "r") as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
os_name = get_os()
|
||||
try:
|
||||
root = data["dependencies"]
|
||||
except KeyError:
|
||||
raise YamlError(f"Nothing found in {config_file} for: dependencies")
|
||||
|
||||
try:
|
||||
self.os = root[os_name]
|
||||
except KeyError:
|
||||
raise YamlError(f"Nothing found in {config_file} for: {os_name}")
|
||||
|
||||
def get_qt_config(self):
|
||||
try:
|
||||
return self.os["qt"]
|
||||
except KeyError:
|
||||
raise YamlError(f"Nothing found in {config_file} for: qt")
|
||||
|
||||
def get_packages_file(self):
|
||||
try:
|
||||
return self.os["packages"]
|
||||
except KeyError:
|
||||
raise YamlError(f"Nothing found in {config_file} for: packages")
|
||||
|
||||
def get_linux_package_command(self, distro):
|
||||
try:
|
||||
distro_data = self.os[distro]
|
||||
except KeyError:
|
||||
raise YamlError(f"Nothing found in {config_file} for: {distro}")
|
||||
|
||||
try:
|
||||
command_base = distro_data["command"]
|
||||
except KeyError:
|
||||
raise YamlError(f"No package command found in {config_file} for: {distro}")
|
||||
|
||||
try:
|
||||
package_data = distro_data["packages"]
|
||||
except KeyError:
|
||||
raise YamlError(f"No package list found in {config_file} for: {distro}")
|
||||
|
||||
packages = " ".join(package_data)
|
||||
return f"{command_base} {packages}"
|
||||
if error:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class Dependencies:
|
||||
|
||||
def __init__(self, only):
|
||||
from lib.config import Config
|
||||
|
||||
self.config = Config()
|
||||
self.only = only
|
||||
self.ci_env = env.is_running_in_ci()
|
||||
|
||||
if self.ci_env:
|
||||
print("CI environment detected")
|
||||
|
||||
def install(self):
|
||||
"""Installs dependencies for the current platform."""
|
||||
|
||||
os = get_os()
|
||||
if os == "windows":
|
||||
if env.is_windows():
|
||||
self.windows()
|
||||
elif os == "mac":
|
||||
elif env.is_mac():
|
||||
self.mac()
|
||||
elif os == "linux":
|
||||
elif env.is_linux():
|
||||
self.linux()
|
||||
else:
|
||||
raise PlatformError(f"Unsupported platform: {os}")
|
||||
raise RuntimeError(f"Unsupported platform: {os}")
|
||||
|
||||
def windows(self):
|
||||
"""Installs dependencies on Windows."""
|
||||
from lib import windows
|
||||
|
||||
if not windows.is_admin():
|
||||
windows.relaunch_as_admin(__file__)
|
||||
sys.exit()
|
||||
|
||||
ci_env = os.environ.get("CI")
|
||||
if ci_env:
|
||||
print("CI environment detected")
|
||||
|
||||
only_qt = self.only == "qt"
|
||||
|
||||
# for ci, skip qt; we install qt separately so we can cache it.
|
||||
if not ci_env or only_qt:
|
||||
qt = windows.WindowsQt(self.config.get_qt_config(), config_file)
|
||||
if not self.ci_env or only_qt:
|
||||
qt = windows.WindowsQt(*self.config.get_qt_config())
|
||||
qt_install_dir = qt.get_install_dir()
|
||||
if qt_install_dir:
|
||||
print(f"Skipping Qt, already installed at: {qt_install_dir}")
|
||||
else:
|
||||
qt.install()
|
||||
|
||||
if not self.ci_env:
|
||||
qt.set_env_vars()
|
||||
|
||||
if only_qt:
|
||||
return
|
||||
|
||||
choco = windows.WindowsChoco()
|
||||
if ci_env:
|
||||
if self.ci_env:
|
||||
choco.config_ci_cache()
|
||||
|
||||
try:
|
||||
ci_skip = self.config.os["ci"]["skip"]
|
||||
choco_config_file = ci_skip["edit-config"]
|
||||
remove_packages = ci_skip["packages"]
|
||||
except KeyError:
|
||||
raise YamlError(f"Bad mapping in {config_file} on Windows for: ci")
|
||||
|
||||
choco_config_file, remove_packages = self.config.get_choco_config()
|
||||
choco.remove_from_config(choco_config_file, remove_packages)
|
||||
|
||||
try:
|
||||
command = self.config.os["command"]
|
||||
except KeyError:
|
||||
raise YamlError(f"Nothing found in {config_file} on Windows for: command")
|
||||
|
||||
choco.install(command, ci_env)
|
||||
command = self.config.get_deps_command()
|
||||
choco.install(command, self.ci_env)
|
||||
|
||||
def mac(self):
|
||||
"""Installs dependencies on macOS."""
|
||||
try:
|
||||
command = self.config.os["command"]
|
||||
except KeyError:
|
||||
raise YamlError(f"Nothing found in {config_file} on Mac for: command")
|
||||
from lib import mac
|
||||
|
||||
command = self.config.get_os_deps_value("command")
|
||||
cmd_utils.run(command)
|
||||
|
||||
if not self.ci_env:
|
||||
mac.set_cmake_prefix_env_var(self.config.get_os_value("qt-prefix-command"))
|
||||
|
||||
def linux(self):
|
||||
"""Installs dependencies on Linux."""
|
||||
|
||||
distro = get_linux_distro()
|
||||
distro = env.get_linux_distro()
|
||||
if not distro:
|
||||
raise PlatformError("Unable to detect Linux distro")
|
||||
raise RuntimeError("Unable to detect Linux distro")
|
||||
|
||||
command = self.config.get_linux_package_command(distro)
|
||||
cmd_utils.run(command)
|
||||
command = self.config.get_linux_deps_command(distro)
|
||||
|
||||
has_sudo = cmd_utils.has_command("sudo")
|
||||
if "sudo" in command and not has_sudo:
|
||||
# assume we're running as root if sudo is not found (common on older distros).
|
||||
# a space char is intentionally added after "sudo" for intentionality.
|
||||
# possible limitation with stripping "sudo" is that if any packages with "sudo" in the
|
||||
# name are added to the list (probably very unlikely), this will have undefined behavior.
|
||||
print("The 'sudo' command was not found, stripping sudo from command")
|
||||
command = command.replace("sudo ", "").strip()
|
||||
|
||||
# don't check the return code, as some package managers return non-zero exit codes
|
||||
# under normal circumstances (e.g. dnf returns 100 when there are updates available).
|
||||
cmd_utils.run(command, check=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -2,18 +2,91 @@ import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def run(command, check=True):
|
||||
"""Runs a shell command and by default asserts that the return code is 0."""
|
||||
def has_command(command):
|
||||
platform = sys.platform
|
||||
if platform == "win32":
|
||||
cmd = f"where {command}"
|
||||
else:
|
||||
cmd = f"which {command}"
|
||||
try:
|
||||
subprocess.check_output(cmd, shell=True)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def strip_continuation_sequences(command):
|
||||
"""
|
||||
Remove the continuation sequences (\\) from a command.
|
||||
|
||||
To spread strings over multiple lines in YAML files, like in bash, a backslash is used at
|
||||
the end of each line as continuation character.
|
||||
When a YAML file is parsed, this becomes "\\ " (without a new line char), so this character
|
||||
sequence must be removed before running the command.
|
||||
This doesn't seem to be an issue on Windows, since the \\ path separator is rarely followed
|
||||
by a space.
|
||||
"""
|
||||
cmd_continuation = "\\ "
|
||||
|
||||
if isinstance(command, list):
|
||||
return [c.replace(cmd_continuation, "") for c in command]
|
||||
else:
|
||||
return command.replace(cmd_continuation, "")
|
||||
|
||||
|
||||
# TODO: fix bug: often when using this function, only the first arg element is sent to subprocess.run
|
||||
def run(
|
||||
command,
|
||||
check=True,
|
||||
shell=True,
|
||||
get_output=False,
|
||||
print_cmd=True,
|
||||
):
|
||||
"""
|
||||
Convenience wrapper around `subprocess.run` to:
|
||||
- print the command before running it
|
||||
- pipe/capture the output instead of printing it
|
||||
|
||||
This differs to `subprocess.run` in that by default it:
|
||||
- checks the return code by default
|
||||
- uses a shell by default (sometimes a bad idea for security)
|
||||
|
||||
Warning: This code is used by CI and prints the command before running it;
|
||||
never use this function with sensitive information such as passwords,
|
||||
unless you want the world to know.
|
||||
|
||||
Args:
|
||||
command (str or list): The command to run.
|
||||
check (bool): Raise an exception if the command fails.
|
||||
shell (bool): Run the command in a shell.
|
||||
get_output (bool): Return the output of the command.
|
||||
print_cmd (bool): Print the command before running it.
|
||||
"""
|
||||
|
||||
# create string version of list command, only for debugging purposes
|
||||
command_str = command
|
||||
if isinstance(command, list):
|
||||
command_str = " ".join(command)
|
||||
|
||||
print(f"Running: {command_str}")
|
||||
sys.stdout.flush()
|
||||
if print_cmd:
|
||||
print(f"Running: {command_str}")
|
||||
sys.stdout.flush()
|
||||
|
||||
try:
|
||||
subprocess.run(command, shell=True, check=check)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Command failed: {command_str}", file=sys.stderr)
|
||||
raise e
|
||||
if get_output:
|
||||
result = subprocess.run(
|
||||
command,
|
||||
shell=shell,
|
||||
check=check,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
else:
|
||||
result = subprocess.run(command, check=check, shell=shell)
|
||||
|
||||
if print_cmd and result.returncode != 0:
|
||||
print(
|
||||
f"Command exited with code {result.returncode}: {command_str}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return result
|
||||
|
||||
107
scripts/lib/config.py
Normal file
107
scripts/lib/config.py
Normal file
@ -0,0 +1,107 @@
|
||||
from lib import env, cmd_utils
|
||||
|
||||
env.ensure_module("yaml", "pyyaml")
|
||||
import yaml
|
||||
|
||||
config_file = "config.yml"
|
||||
deps_key = "dependencies"
|
||||
|
||||
|
||||
class ConfigError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class Config:
|
||||
"""Reads the project configuration YAML file."""
|
||||
|
||||
def __init__(self):
|
||||
with open(config_file, "r") as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
self.os_name = env.get_os()
|
||||
root_key = "config"
|
||||
try:
|
||||
root = data[root_key]
|
||||
except KeyError:
|
||||
raise ConfigError(f"Nothing found in {config_file} for: {root_key}")
|
||||
|
||||
try:
|
||||
self.os = root[self.os_name]
|
||||
except KeyError:
|
||||
raise ConfigError(f"Nothing found in {config_file} for: {self.os_name}")
|
||||
|
||||
def get_os_value(self, key):
|
||||
try:
|
||||
return self.os[key]
|
||||
except KeyError:
|
||||
raise ConfigError(
|
||||
f"Nothing found in {config_file} for: {self.os_name}:{key}"
|
||||
)
|
||||
|
||||
def get_qt_config(self):
|
||||
qt = self.get_os_deps_value("qt")
|
||||
|
||||
try:
|
||||
mirror_url = qt["mirror"]
|
||||
except KeyError:
|
||||
raise ConfigError(f"Qt mirror not set in {self.config_file}")
|
||||
|
||||
try:
|
||||
default_version = qt["version"]
|
||||
except KeyError:
|
||||
raise ConfigError(f"Qt version not set in {self.config_file}")
|
||||
|
||||
try:
|
||||
default_base_dir = qt["install-dir"]
|
||||
except KeyError:
|
||||
raise ConfigError(f"Qt install-dir not set in {self.config_file}")
|
||||
|
||||
return mirror_url, default_version, default_base_dir
|
||||
|
||||
def get_os_deps_value(self, key):
|
||||
deps = self.get_os_value(deps_key)
|
||||
try:
|
||||
return deps[key]
|
||||
except KeyError:
|
||||
raise ConfigError(
|
||||
f"Nothing found in {config_file} for: {self.os_name}:{deps_key}:{key}"
|
||||
)
|
||||
|
||||
def get_deps_command(self):
|
||||
dependencies = self.get_os_value(deps_key)
|
||||
try:
|
||||
command = dependencies["command"]
|
||||
except KeyError:
|
||||
raise ConfigError(
|
||||
f"No dependencies command found in {config_file} for: {self.os_name}"
|
||||
)
|
||||
return cmd_utils.strip_continuation_sequences(command)
|
||||
|
||||
def get_linux_deps_command(self, distro):
|
||||
distro_data = self.get_os_value(distro)
|
||||
|
||||
try:
|
||||
deps = distro_data[deps_key]
|
||||
except KeyError:
|
||||
raise ConfigError(
|
||||
f"No dependencies config found in {config_file} for: {distro}"
|
||||
)
|
||||
|
||||
try:
|
||||
command = deps["command"]
|
||||
return cmd_utils.strip_continuation_sequences(command)
|
||||
except KeyError:
|
||||
|
||||
raise ConfigError(
|
||||
f"No dependencies command found in {config_file} for: {self.os_name}:{distro}"
|
||||
)
|
||||
|
||||
def get_choco_config(self):
|
||||
ci = self.get_os_deps_value("ci")
|
||||
try:
|
||||
ci_skip = ci["skip"]
|
||||
choco_config_file = ci_skip["edit-config"]
|
||||
remove_packages = ci_skip["packages"]
|
||||
except KeyError:
|
||||
raise ConfigError(f"Bad structure in {config_file} under: ci")
|
||||
return choco_config_file, remove_packages
|
||||
163
scripts/lib/env.py
Normal file
163
scripts/lib/env.py
Normal file
@ -0,0 +1,163 @@
|
||||
import os, sys, subprocess, platform
|
||||
from lib import env, cmd_utils
|
||||
|
||||
venv_path = "build/python"
|
||||
version_env = "build/.env.version"
|
||||
|
||||
|
||||
def check_module(module):
|
||||
try:
|
||||
__import__(module)
|
||||
return True
|
||||
except ImportError:
|
||||
print(f"Python is missing {module} module", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def get_os():
|
||||
"""Detects the operating system."""
|
||||
if sys.platform == "win32":
|
||||
return "windows"
|
||||
elif sys.platform == "darwin":
|
||||
return "mac"
|
||||
elif sys.platform.startswith("linux"):
|
||||
return "linux"
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported platform: {sys.platform}")
|
||||
|
||||
|
||||
def is_windows():
|
||||
return get_os() == "windows"
|
||||
|
||||
|
||||
def is_mac():
|
||||
return get_os() == "mac"
|
||||
|
||||
|
||||
def is_linux():
|
||||
return get_os() == "linux"
|
||||
|
||||
|
||||
def is_running_in_ci():
|
||||
"""Returns True if running in a CI environment."""
|
||||
return os.environ.get("CI")
|
||||
|
||||
|
||||
def get_linux_distro():
|
||||
"""Detects the Linux distro."""
|
||||
os_file = "/etc/os-release"
|
||||
if os.path.isfile(os_file):
|
||||
with open(os_file) as f:
|
||||
for line in f:
|
||||
if line.startswith("ID="):
|
||||
return line.strip().split("=")[1].strip('"')
|
||||
return None
|
||||
|
||||
|
||||
def get_env_var(name):
|
||||
"""Returns an env var or raises an error if it is not set."""
|
||||
value = os.getenv(name)
|
||||
if not value:
|
||||
raise ValueError(f"Environment variable not set: {name}")
|
||||
return value
|
||||
|
||||
|
||||
def get_python_executable(binary="python"):
|
||||
if sys.platform == "win32":
|
||||
return os.path.join(venv_path, "Scripts", binary)
|
||||
else:
|
||||
return os.path.join(venv_path, "bin", binary)
|
||||
|
||||
|
||||
def in_venv():
|
||||
"""Returns True if the script is running in a Python virtual environment."""
|
||||
return sys.prefix != sys.base_prefix
|
||||
|
||||
|
||||
def ensure_in_venv(script):
|
||||
"""
|
||||
Ensures the script is running in a Python virtual environment (venv).
|
||||
If the script is not running in a venv, it will create one and re-run the script in the venv.
|
||||
"""
|
||||
|
||||
ensure_dependencies()
|
||||
import venv
|
||||
|
||||
if not in_venv():
|
||||
if not os.path.exists(venv_path):
|
||||
print(f"Creating virtual environment at {venv_path}")
|
||||
venv.create(venv_path, with_pip=True)
|
||||
|
||||
script_file = os.path.basename(script)
|
||||
print(f"Using virtual environment for {script_file}")
|
||||
sys.stdout.flush()
|
||||
python_executable = get_python_executable()
|
||||
result = subprocess.run([python_executable, script] + sys.argv[1:])
|
||||
sys.exit(result.returncode)
|
||||
|
||||
|
||||
# TODO: Use pyproject.toml to specify dependencies
|
||||
def ensure_module(module, package):
|
||||
"""
|
||||
Ensures that a Python module is available, and installs the package if it is not.
|
||||
"""
|
||||
|
||||
ensure_dependencies()
|
||||
|
||||
try:
|
||||
__import__(module)
|
||||
except ImportError:
|
||||
print(f"Python missing {module}, installing {package}...", file=sys.stderr)
|
||||
cmd_utils.run([sys.executable, "-m", "pip", "install", package], shell=False)
|
||||
|
||||
|
||||
def ensure_dependencies():
|
||||
"""
|
||||
Ensures that pip and venv are available, and installs them if they are not.
|
||||
This is normally only required on Linux.
|
||||
"""
|
||||
|
||||
has_pip = check_module("pip")
|
||||
has_venv = check_module("venv")
|
||||
|
||||
if has_pip and has_venv:
|
||||
return
|
||||
|
||||
print("Installing Python dependencies...")
|
||||
|
||||
os = get_os()
|
||||
if os != "linux":
|
||||
# should not be a problem, since windows and mac come with pip and venv
|
||||
raise RuntimeError(f"Unable to install Python dependencies on {os}")
|
||||
|
||||
has_sudo = cmd_utils.has_command("sudo")
|
||||
sudo = "sudo" if has_sudo else ""
|
||||
|
||||
distro = get_linux_distro()
|
||||
if distro == "ubuntu" or distro == "debian":
|
||||
cmd_utils.run(f"{sudo} apt update".strip(), check=False)
|
||||
cmd_utils.run(f"{sudo} apt install -y python3-pip python3-venv".strip())
|
||||
elif distro == "fedora" or distro == "centos":
|
||||
cmd_utils.run(f"{sudo} dnf check-update".strip(), check=False)
|
||||
cmd_utils.run(f"{sudo} dnf install -y python3-pip python3-virtualenv".strip())
|
||||
else:
|
||||
# arch, opensuse, etc, patches welcome! :)
|
||||
raise RuntimeError(f"Unable to install Python dependencies on {distro}")
|
||||
|
||||
|
||||
def get_version_info():
|
||||
env.ensure_module("dotenv", "python-dotenv")
|
||||
from dotenv import load_dotenv # type: ignore
|
||||
|
||||
if not os.path.isfile(version_env):
|
||||
raise RuntimeError(f"Version file not found: {version_env}")
|
||||
|
||||
load_dotenv(dotenv_path=version_env)
|
||||
|
||||
major = os.getenv("SYNERGY_VERSION_MAJOR")
|
||||
minor = os.getenv("SYNERGY_VERSION_MINOR")
|
||||
patch = os.getenv("SYNERGY_VERSION_PATCH")
|
||||
stage = os.getenv("SYNERGY_VERSION_STAGE")
|
||||
build = os.getenv("SYNERGY_VERSION_BUILD")
|
||||
|
||||
return major, minor, patch, stage, build
|
||||
282
scripts/lib/mac.py
Normal file
282
scripts/lib/mac.py
Normal file
@ -0,0 +1,282 @@
|
||||
import os, subprocess, base64, time, json, shutil, sys
|
||||
from lib import cmd_utils, env
|
||||
|
||||
cmake_env_var = "CMAKE_PREFIX_PATH"
|
||||
shell_rc = "~/.zshrc"
|
||||
cert_path = "tmp/codesign.p12"
|
||||
dist_dir = "dist"
|
||||
product_name = "Synergy"
|
||||
settings_file = "res/dist/macos/dmgbuild/settings.py"
|
||||
app_path = "build/bundle/Synergy.app"
|
||||
security_path = "/usr/bin/security"
|
||||
sudo_path = "/usr/bin/sudo"
|
||||
notarytool_path = "/usr/bin/notarytool"
|
||||
codesign_path = "/usr/bin/codesign"
|
||||
xcode_select_path = "/usr/bin/xcode-select"
|
||||
keychain_path = "/Library/Keychains/System.keychain"
|
||||
|
||||
|
||||
def set_env_var(name, value):
|
||||
text = f'export {name}="${name}:{value}"'
|
||||
file = os.path.expanduser(shell_rc)
|
||||
with open(file, "r") as f:
|
||||
if text in f.read():
|
||||
return
|
||||
|
||||
print(f"Setting environment variable: {name}={name}")
|
||||
with open(file, "a") as f:
|
||||
f.write(f"\n{text}")
|
||||
print(f"Appended to {shell_rc}: {text}")
|
||||
|
||||
|
||||
def set_cmake_prefix_env_var(cmake_prefix_command):
|
||||
result = cmd_utils.run(cmake_prefix_command, get_output=True)
|
||||
cmake_prefix = result.stdout.strip()
|
||||
set_env_var(cmake_env_var, cmake_prefix)
|
||||
|
||||
|
||||
def package(filename_base):
|
||||
codesign_id = env.get_env_var("APPLE_CODESIGN_ID")
|
||||
certificate = env.get_env_var("APPLE_P12_CERTIFICATE")
|
||||
password = env.get_env_var("APPLE_P12_PASSWORD")
|
||||
|
||||
build_bundle()
|
||||
install_certificate(certificate, password)
|
||||
assert_certificate_installed(codesign_id)
|
||||
sign_bundle(codesign_id)
|
||||
dmg_path = build_dmg(filename_base)
|
||||
notarize_package(dmg_path)
|
||||
|
||||
|
||||
def build_bundle():
|
||||
print("Building bundle...")
|
||||
# cmake build install target should run macdeployqt
|
||||
cmd_utils.run("cmake --build build --target install")
|
||||
|
||||
|
||||
def sign_bundle(codesign_id):
|
||||
print(f"Signing bundle {app_path}...")
|
||||
sys.stdout.flush()
|
||||
subprocess.run(
|
||||
[
|
||||
codesign_path,
|
||||
"-f",
|
||||
"--options",
|
||||
"runtime",
|
||||
"--deep",
|
||||
"-s",
|
||||
codesign_id,
|
||||
app_path,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def assert_certificate_installed(codesign_id):
|
||||
installed = cmd_utils.run(
|
||||
"security find-identity -v -p codesigning", get_output=True
|
||||
)
|
||||
|
||||
if codesign_id not in installed.stdout:
|
||||
raise RuntimeError("Code signing certificate not installed or has expired")
|
||||
|
||||
|
||||
def build_dmg(filename_base):
|
||||
env.ensure_module("dmgbuild", "dmgbuild")
|
||||
import dmgbuild # type: ignore
|
||||
|
||||
settings_file_abs = os.path.abspath(settings_file)
|
||||
app_path_abs = os.path.abspath(app_path)
|
||||
|
||||
# cwd for dmgbuild, since setting the dmg filename to a path (include the dist dir) seems to
|
||||
# make the dmg disappear and never writes to the specified path. the dmgbuild module also
|
||||
# creates a temporary file in cwd, so it makes sense to change to the dist dir.
|
||||
print(f"Changing directory to: {os.path.abspath(dist_dir)}")
|
||||
cwd = os.getcwd()
|
||||
os.makedirs(dist_dir, exist_ok=True)
|
||||
os.chdir(dist_dir)
|
||||
|
||||
try:
|
||||
dmg_filename = f"{filename_base}.dmg"
|
||||
dmg_path = os.path.join(dist_dir, dmg_filename)
|
||||
print(f"Building package {dmg_path}...")
|
||||
dmgbuild.build_dmg(
|
||||
dmg_filename,
|
||||
product_name,
|
||||
settings_file=settings_file_abs,
|
||||
defines={
|
||||
"app": app_path_abs,
|
||||
},
|
||||
)
|
||||
finally:
|
||||
print(f"Changing directory back to: {cwd}")
|
||||
os.chdir(cwd)
|
||||
|
||||
return dmg_path
|
||||
|
||||
|
||||
def install_certificate(cert_base64, cert_password):
|
||||
if not cert_base64:
|
||||
raise ValueError("Certificate base 64 not provided")
|
||||
|
||||
if not cert_password:
|
||||
raise ValueError("Certificate password not provided")
|
||||
|
||||
print(f"Decoding certificate to: {cert_path}")
|
||||
cert_bytes = base64.b64decode(cert_base64)
|
||||
os.makedirs(os.path.dirname(cert_path), exist_ok=True)
|
||||
with open(cert_path, "wb") as cert_file:
|
||||
cert_file.write(cert_bytes)
|
||||
|
||||
print(f"Installing certificate: {cert_path}")
|
||||
sys.stdout.flush()
|
||||
|
||||
try:
|
||||
# warning: contains private key password, never print this command
|
||||
subprocess.run(
|
||||
[
|
||||
sudo_path,
|
||||
security_path,
|
||||
"import",
|
||||
cert_path,
|
||||
"-k",
|
||||
keychain_path,
|
||||
"-P",
|
||||
cert_password,
|
||||
"-T",
|
||||
codesign_path,
|
||||
"-T",
|
||||
security_path,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
# important: suppress the original args with `from None` to avoid leaking the password
|
||||
raise subprocess.CalledProcessError(e.returncode, security_path) from None
|
||||
except Exception as e:
|
||||
# important: suppress the original args with `from None` to avoid leaking the password
|
||||
raise RuntimeError(f"Command failed: {security_path}") from None
|
||||
finally:
|
||||
# not strictly necessary for ci, but when run on a dev machine, it reduces the risk
|
||||
# that private keys are left on the filesystem
|
||||
print(f"Removing temporary certificate file: {cert_path}")
|
||||
os.remove(cert_path)
|
||||
|
||||
|
||||
def notarize_package(dmg_path):
|
||||
print(f"Notarizing package {dmg_path}...")
|
||||
notary_tool = NotaryTool()
|
||||
notary_tool.store_credentials(
|
||||
env.get_env_var("APPLE_NOTARY_USER"),
|
||||
env.get_env_var("APPLE_NOTARY_PASSWORD"),
|
||||
env.get_env_var("APPLE_TEAM_ID"),
|
||||
)
|
||||
|
||||
notary_tool.submit_and_wait(dmg_path)
|
||||
|
||||
|
||||
def get_xcode_path():
|
||||
result = cmd_utils.run([xcode_select_path, "-p"], get_output=True, shell=False)
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
class NotaryTool:
|
||||
"""
|
||||
Provides a wrapper around the notarytool command line tool.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.xcode_path = get_xcode_path()
|
||||
|
||||
def get_path(self):
|
||||
return f"{self.xcode_path}{notarytool_path}"
|
||||
|
||||
def store_credentials(self, user, password, team_id):
|
||||
print("Storing credentials for notary tool...")
|
||||
sys.stdout.flush()
|
||||
|
||||
notarytool_path = self.get_path()
|
||||
try:
|
||||
# warning: contains password, never print this command
|
||||
subprocess.run(
|
||||
[
|
||||
notarytool_path,
|
||||
"store-credentials",
|
||||
"notarytool-password",
|
||||
"--apple-id",
|
||||
user,
|
||||
"--team-id",
|
||||
team_id,
|
||||
"--password",
|
||||
password,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
# important: suppress the original args with `from None` to avoid leaking the password
|
||||
raise subprocess.CalledProcessError(e.returncode, notarytool_path) from None
|
||||
except Exception as e:
|
||||
# important: suppress the original args with `from None` to avoid leaking the password
|
||||
raise RuntimeError(f"Command failed: {notarytool_path}") from None
|
||||
|
||||
def submit_and_wait(self, dmg_filename):
|
||||
print("Submitting notarization request...")
|
||||
submit_result = self.run_submit_command(dmg_filename)
|
||||
request_id = submit_result["id"]
|
||||
|
||||
print(f"Notary submitted, waiting for request: {request_id}")
|
||||
start = time.time()
|
||||
wait_result = self.run_wait_command(request_id)
|
||||
status = wait_result["status"]
|
||||
|
||||
time_taken = time.time() - start
|
||||
print(f"Notary complete in {time_taken:.2f}s, status: {status}")
|
||||
if status == "Accepted":
|
||||
print("Notarization successful.")
|
||||
elif status == "Invalid" or status == "Rejected":
|
||||
raise ValueError(f"Notarization failed, status: {status}")
|
||||
else:
|
||||
raise ValueError(f"Unknown status: {status}")
|
||||
|
||||
def run_submit_command(self, dmg_filename):
|
||||
if not os.path.exists(dmg_filename):
|
||||
raise FileNotFoundError(f"File not found: {dmg_filename}")
|
||||
|
||||
result = cmd_utils.run(
|
||||
[
|
||||
self.get_path(),
|
||||
"submit",
|
||||
dmg_filename,
|
||||
"--keychain-profile",
|
||||
"notarytool-password",
|
||||
"--output-format",
|
||||
"json",
|
||||
],
|
||||
get_output=True,
|
||||
shell=False,
|
||||
)
|
||||
|
||||
if result.stderr:
|
||||
return json.loads(result.stderr)
|
||||
else:
|
||||
return json.loads(result.stdout)
|
||||
|
||||
def run_wait_command(self, request_id):
|
||||
result = cmd_utils.run(
|
||||
[
|
||||
self.get_path(),
|
||||
"wait",
|
||||
request_id,
|
||||
"--keychain-profile",
|
||||
"notarytool-password",
|
||||
"--output-format",
|
||||
"json",
|
||||
],
|
||||
get_output=True,
|
||||
shell=False,
|
||||
)
|
||||
|
||||
if result.stderr:
|
||||
return json.loads(result.stderr)
|
||||
else:
|
||||
return json.loads(result.stdout)
|
||||
@ -4,9 +4,7 @@ import os
|
||||
import xml.etree.ElementTree as ET
|
||||
from lib import cmd_utils
|
||||
|
||||
|
||||
class EnvError(Exception):
|
||||
pass
|
||||
cmake_env_var = "CMAKE_PREFIX_PATH"
|
||||
|
||||
|
||||
def relaunch_as_admin(script):
|
||||
@ -24,6 +22,24 @@ def is_admin():
|
||||
return False
|
||||
|
||||
|
||||
def set_env_var(name, value):
|
||||
"""
|
||||
Sets or updates an environment variable. Appends the value if it doesn't already exist.
|
||||
|
||||
Args:
|
||||
name (str): The name of the environment variable.
|
||||
value (str): The value of the environment variable.
|
||||
"""
|
||||
|
||||
current_value = os.getenv(name, "")
|
||||
|
||||
if value not in current_value:
|
||||
new_value = f"{current_value}{os.pathsep}{value}" if current_value else value
|
||||
os.environ[name] = new_value
|
||||
print(f"Setting environment variable: {name}={value}")
|
||||
cmd_utils.run(["setx", name, new_value], check=True)
|
||||
|
||||
|
||||
class WindowsChoco:
|
||||
"""Chocolatey for Windows."""
|
||||
|
||||
@ -33,6 +49,7 @@ class WindowsChoco:
|
||||
# don't show noisy choco progress bars in ci env
|
||||
cmd_utils.run(f"{command} --no-progress")
|
||||
else:
|
||||
cmd_utils.run("winget install chocolatey", check=False)
|
||||
cmd_utils.run(command)
|
||||
|
||||
def config_ci_cache(self):
|
||||
@ -48,10 +65,10 @@ class WindowsChoco:
|
||||
else:
|
||||
print(f"Warning: CI environment variable {runner_temp_key} not set")
|
||||
|
||||
def remove_from_config(self, config_file, remove_packages):
|
||||
def remove_from_config(self, choco_config_file, remove_packages):
|
||||
"""Removes a package from the Chocolatey configuration."""
|
||||
|
||||
tree = ET.parse(config_file)
|
||||
tree = ET.parse(choco_config_file)
|
||||
root = tree.getroot()
|
||||
for remove in remove_packages:
|
||||
for package in root.findall("package"):
|
||||
@ -59,33 +76,22 @@ class WindowsChoco:
|
||||
root.remove(package)
|
||||
print(f"Removed package from choco config: {remove}")
|
||||
|
||||
tree.write(config_file)
|
||||
tree.write(choco_config_file)
|
||||
|
||||
|
||||
class WindowsQt:
|
||||
"""Qt for Windows."""
|
||||
|
||||
def __init__(self, config, config_file):
|
||||
self.config = config
|
||||
self.config_file = config_file
|
||||
def __init__(self, mirror_url, default_version, default_base_dir):
|
||||
self.mirror_url = mirror_url
|
||||
|
||||
self.version = os.environ.get("QT_VERSION")
|
||||
if not self.version:
|
||||
try:
|
||||
default_version = config["version"]
|
||||
except KeyError:
|
||||
raise EnvError(f"Qt version not set in {config_file}")
|
||||
|
||||
print(f"QT_VERSION not set, using: {default_version}")
|
||||
self.version = default_version
|
||||
|
||||
self.base_dir = os.environ.get("QT_BASE_DIR")
|
||||
if not self.base_dir:
|
||||
try:
|
||||
default_base_dir = config["install-dir"]
|
||||
except KeyError:
|
||||
raise EnvError(f"Qt install-dir not set in {config_file}")
|
||||
|
||||
print(f"QT_BASE_DIR not set, using: {default_base_dir}")
|
||||
self.base_dir = default_base_dir
|
||||
|
||||
@ -100,17 +106,15 @@ class WindowsQt:
|
||||
|
||||
cmd_utils.run(["pip", "install", "aqtinstall"])
|
||||
|
||||
try:
|
||||
mirror_url = self.config["mirror"]
|
||||
except KeyError:
|
||||
raise EnvError(f"Qt mirror not set in {self.config_file}")
|
||||
|
||||
args = ["python", "-m", "aqt", "install-qt"]
|
||||
args.extend(["--outputdir", self.base_dir])
|
||||
args.extend(["--base", mirror_url])
|
||||
args.extend(["--base", self.mirror_url])
|
||||
args.extend(["windows", "desktop", self.version, "win64_msvc2019_64"])
|
||||
cmd_utils.run(args)
|
||||
|
||||
install_dir = self.get_install_dir()
|
||||
if not install_dir:
|
||||
raise EnvError(f"Qt not installed, path not found: {install_dir}")
|
||||
raise RuntimeError(f"Qt not installed, path not found: {install_dir}")
|
||||
|
||||
def set_env_vars(self):
|
||||
set_env_var(cmake_env_var, f"{self.get_install_dir()}\\msvc2019_64")
|
||||
|
||||
57
scripts/package.py
Executable file
57
scripts/package.py
Executable file
@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import platform
|
||||
from lib import env
|
||||
|
||||
# important: load venv before loading modules that install deps.
|
||||
env.ensure_in_venv(__file__)
|
||||
|
||||
env_file = ".env"
|
||||
package_filename_product = "synergy"
|
||||
|
||||
|
||||
def main():
|
||||
env.ensure_module("dotenv", "python-dotenv")
|
||||
from dotenv import load_dotenv # type: ignore
|
||||
|
||||
load_dotenv(dotenv_path=env_file)
|
||||
|
||||
major, minor, patch, stage, _build = env.get_version_info()
|
||||
version = f"{major}.{minor}.{patch}-{stage}"
|
||||
filename_base = get_filename_base(version)
|
||||
print(f"Package filename base: {filename_base}")
|
||||
|
||||
if env.is_windows():
|
||||
windows_package(filename_base)
|
||||
elif env.is_mac():
|
||||
mac_package(filename_base)
|
||||
elif env.is_linux():
|
||||
linux_package(filename_base)
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported platform: {env.get_os()}")
|
||||
|
||||
|
||||
def get_filename_base(version):
|
||||
os = env.get_os()
|
||||
machine = platform.machine().lower()
|
||||
return f"{package_filename_product}-{version}-{os}-{machine}"
|
||||
|
||||
|
||||
def windows_package(filename_base):
|
||||
"""TODO: Windows packaging"""
|
||||
pass
|
||||
|
||||
|
||||
def mac_package(filename_base):
|
||||
from lib import mac
|
||||
|
||||
mac.package(filename_base)
|
||||
|
||||
|
||||
def linux_package(filename_base):
|
||||
"""TODO: Linux packaging"""
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -5,79 +5,90 @@ import argparse
|
||||
import glob
|
||||
from lib import windows
|
||||
|
||||
BIN_NAME = 'synergyd'
|
||||
SOURCE_BIN_DIR = os.path.join('build', 'bin')
|
||||
TARGET_BIN_DIR = 'bin'
|
||||
BIN_NAME = "synergyd"
|
||||
SOURCE_BIN_DIR = os.path.join("build", "bin")
|
||||
TARGET_BIN_DIR = "bin"
|
||||
SERVICE_NOT_RUNNING_ERROR = 2
|
||||
|
||||
|
||||
def main():
|
||||
"""Entry point for the script."""
|
||||
"""Entry point for the script."""
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--pause-on-exit', action='store_true')
|
||||
parser.add_argument('--source-bin-dir', default=SOURCE_BIN_DIR)
|
||||
parser.add_argument('--target-bin-dir', default=TARGET_BIN_DIR)
|
||||
parser.add_argument('--source-bin-name', default=BIN_NAME)
|
||||
parser.add_argument('--target-bin-name', default=BIN_NAME)
|
||||
args = parser.parse_args()
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--pause-on-exit", action="store_true")
|
||||
parser.add_argument("--source-bin-dir", default=SOURCE_BIN_DIR)
|
||||
parser.add_argument("--target-bin-dir", default=TARGET_BIN_DIR)
|
||||
parser.add_argument("--source-bin-name", default=BIN_NAME)
|
||||
parser.add_argument("--target-bin-name", default=BIN_NAME)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not windows.is_admin():
|
||||
windows.relaunch_as_admin(__file__)
|
||||
sys.exit()
|
||||
if not windows.is_admin():
|
||||
windows.relaunch_as_admin(__file__)
|
||||
sys.exit()
|
||||
|
||||
try:
|
||||
reinstall(
|
||||
args.source_bin_dir,
|
||||
args.target_bin_dir,
|
||||
args.source_bin_name,
|
||||
args.target_bin_name,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
if args.pause_on_exit:
|
||||
input("Press enter to continue...")
|
||||
|
||||
try:
|
||||
reinstall(args.source_bin_dir, args.target_bin_dir, args.source_bin_name, args.target_bin_name)
|
||||
except Exception as e:
|
||||
print(f'Error: {e}')
|
||||
|
||||
if (args.pause_on_exit):
|
||||
input('Press enter to continue...')
|
||||
|
||||
def reinstall(source_bin_dir, target_bin_dir, source_bin_name, target_bin_name):
|
||||
"""Stops the running daemon service, copies files, and reinstalls."""
|
||||
|
||||
print('Stopping daemon service')
|
||||
try:
|
||||
subprocess.run(['net', 'stop', 'synergy'], shell=True, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
if (e.returncode == SERVICE_NOT_RUNNING_ERROR):
|
||||
print('Daemon service not running')
|
||||
else:
|
||||
raise e
|
||||
"""Stops the running daemon service, copies files, and reinstalls."""
|
||||
|
||||
copy_bin_files(source_bin_dir, target_bin_dir, source_bin_name, target_bin_name)
|
||||
print("Stopping daemon service")
|
||||
try:
|
||||
subprocess.run(["net", "stop", "synergy"], shell=True, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
if e.returncode == SERVICE_NOT_RUNNING_ERROR:
|
||||
print("Daemon service not running")
|
||||
else:
|
||||
raise e
|
||||
|
||||
target_bin_file = f'{os.path.join(target_bin_dir, target_bin_name)}.exe'
|
||||
copy_bin_files(source_bin_dir, target_bin_dir, source_bin_name, target_bin_name)
|
||||
|
||||
print('Removing old daemon service')
|
||||
subprocess.run([target_bin_file, '/uninstall'], shell=True, check=True)
|
||||
target_bin_file = f"{os.path.join(target_bin_dir, target_bin_name)}.exe"
|
||||
|
||||
print("Removing old daemon service")
|
||||
subprocess.run([target_bin_file, "/uninstall"], shell=True, check=True)
|
||||
|
||||
print("Installing daemon service")
|
||||
subprocess.run([target_bin_file, "/install"], shell=True, check=True)
|
||||
|
||||
print('Installing daemon service')
|
||||
subprocess.run([target_bin_file, '/install'], shell=True, check=True)
|
||||
|
||||
def copy_bin_files(source_bin_dir, target_bin_dir, source_bin_name, target_bin_name):
|
||||
|
||||
if not os.path.isdir(source_bin_dir):
|
||||
raise Exception(f'Invalid source bin dir: {source_bin_dir}')
|
||||
if not os.path.isdir(source_bin_dir):
|
||||
raise RuntimeError(f"Invalid source bin dir: {source_bin_dir}")
|
||||
|
||||
print(f'Persisting dir: {target_bin_dir}')
|
||||
os.makedirs(target_bin_dir, exist_ok=True)
|
||||
print(f"Persisting dir: {target_bin_dir}")
|
||||
os.makedirs(target_bin_dir, exist_ok=True)
|
||||
|
||||
source_bin_glob = f'{source_bin_name}*'
|
||||
source_files = glob.glob(os.path.join(source_bin_dir, source_bin_glob))
|
||||
source_bin_glob = f"{source_bin_name}*"
|
||||
source_files = glob.glob(os.path.join(source_bin_dir, source_bin_glob))
|
||||
|
||||
if not source_files:
|
||||
raise Exception(f'No files found in {source_bin_dir} matching {source_bin_glob}')
|
||||
if not source_files:
|
||||
raise RuntimeError(
|
||||
f"No files found in {source_bin_dir} matching {source_bin_glob}"
|
||||
)
|
||||
|
||||
for source_file in source_files:
|
||||
base_name = os.path.basename(source_file)
|
||||
base_name = base_name.replace(source_bin_name, target_bin_name)
|
||||
target_file = os.path.join(target_bin_dir, base_name)
|
||||
print(f"Copying {source_file} to {target_file}")
|
||||
# use the copy command; shutil.copy gives us a permission denied error.
|
||||
try:
|
||||
subprocess.run(["copy", source_file, target_file], shell=True, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Copy failed: {e}")
|
||||
|
||||
for source_file in source_files:
|
||||
base_name = os.path.basename(source_file)
|
||||
base_name = base_name.replace(source_bin_name, target_bin_name)
|
||||
target_file = os.path.join(target_bin_dir, base_name)
|
||||
print(f'Copying {source_file} to {target_file}')
|
||||
# use the copy command; shutil.copy gives us a permission denied error.
|
||||
try:
|
||||
subprocess.run(['copy', source_file, target_file], shell=True, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f'Copy failed: {e}')
|
||||
|
||||
main()
|
||||
|
||||
@ -25,5 +25,5 @@ const char* kCopyright = "Copyright (C) 2012-%s Symless Ltd.\n"
|
||||
"Copyright (C) 2002-2014 Chris Schoeneman";
|
||||
const char* kContact = "Email: engineering@symless.com";
|
||||
const char* kWebsite = "https://symless.com/";
|
||||
const char* kVersion = SYNERGY_VERSION_STRING;
|
||||
const char* kVersion = SYNERGY_VERSION_LONG;
|
||||
const char* kAppVersion = "Synergy " SYNERGY_VERSION;
|
||||
|
||||
Reference in New Issue
Block a user