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:
parent
8f13ce8e7a
commit
865063b77c
45 changed files with 1245 additions and 838 deletions
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
|
- synchronize
|
||||||
- ready_for_review
|
- ready_for_review
|
||||||
push:
|
push:
|
||||||
branches: [master*, release*]
|
branches:
|
||||||
|
- master*
|
||||||
|
- release*
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GIT_COMMIT: ${{ github.sha }}
|
GIT_COMMIT: ${{ github.sha }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
windows-2022:
|
windows:
|
||||||
|
name: windows-2022
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
|
|
@ -41,9 +44,7 @@ jobs:
|
||||||
run: python ./scripts/install_deps.py --only qt
|
run: python ./scripts/install_deps.py --only qt
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: python ./scripts/install_deps.py
|
||||||
pip install pyyaml
|
|
||||||
python ./scripts/install_deps.py
|
|
||||||
|
|
||||||
- name: Setup VC++ environment
|
- name: Setup VC++ environment
|
||||||
uses: ilammy/msvc-dev-cmd@v1
|
uses: ilammy/msvc-dev-cmd@v1
|
||||||
|
|
@ -53,12 +54,19 @@ jobs:
|
||||||
CMAKE_PREFIX_PATH: "${{ env.QT_BASE_DIR }}\\${{ env.QT_VERSION }}\\msvc2019_64\\"
|
CMAKE_PREFIX_PATH: "${{ env.QT_BASE_DIR }}\\${{ env.QT_VERSION }}\\msvc2019_64\\"
|
||||||
run: cmake -B build --preset=windows-release
|
run: cmake -B build --preset=windows-release
|
||||||
|
|
||||||
|
- name: Set env vars
|
||||||
|
run: python ./scripts/github_env.py
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build
|
run: cmake --build build
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: ./build/bin/unittests
|
run: ./build/bin/unittests
|
||||||
|
|
||||||
|
- name: Package
|
||||||
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
|
run: python ./scripts/package.py
|
||||||
|
|
||||||
macos:
|
macos:
|
||||||
runs-on: ${{ matrix.runtime.os }}
|
runs-on: ${{ matrix.runtime.os }}
|
||||||
timeout-minutes: ${{ matrix.runtime.timeout }}
|
timeout-minutes: ${{ matrix.runtime.timeout }}
|
||||||
|
|
@ -102,21 +110,49 @@ jobs:
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: ./scripts/install_deps.py
|
||||||
pip install pyyaml
|
|
||||||
python ./scripts/install_deps.py
|
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
env:
|
env:
|
||||||
CMAKE_OSX_DEPLOYMENT_TARGET: ${{ matrix.runtime.target }}
|
CMAKE_OSX_DEPLOYMENT_TARGET: ${{ matrix.runtime.target }}
|
||||||
run: cmake -B build --preset=macos-release -DCMAKE_PREFIX_PATH=$(brew --prefix qt@5)
|
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
|
- name: Build
|
||||||
run: cmake --build build
|
run: cmake --build build
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: ./build/bin/unittests
|
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:
|
linux:
|
||||||
runs-on: ${{ matrix.distro.runs-on }}
|
runs-on: ${{ matrix.distro.runs-on }}
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
@ -126,11 +162,6 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
distro:
|
distro:
|
||||||
- name: centos-7.6
|
|
||||||
container: symless/synergy-core:centos7.6
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
legacy-cmake: true
|
|
||||||
|
|
||||||
- name: centos-8
|
- name: centos-8
|
||||||
container: symless/synergy-core:centos8
|
container: symless/synergy-core:centos8
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -166,7 +197,6 @@ jobs:
|
||||||
|
|
||||||
- name: ubuntu-24.04
|
- name: ubuntu-24.04
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
install-deps: true
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# Use @v3 since some older Linux distro versions don't support @v4
|
# Use @v3 since some older Linux distro versions don't support @v4
|
||||||
|
|
@ -176,10 +206,11 @@ jobs:
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: ${{ matrix.distro.install-deps }}
|
run: |
|
||||||
run: python ./scripts/install_deps.py
|
${{ matrix.distro.python-deps }}
|
||||||
|
./scripts/install_deps.py
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure (modern)
|
||||||
if: ${{ !matrix.distro.legacy-cmake }}
|
if: ${{ !matrix.distro.legacy-cmake }}
|
||||||
run: cmake -B build --preset=linux-release
|
run: cmake -B build --preset=linux-release
|
||||||
|
|
||||||
|
|
@ -188,8 +219,15 @@ jobs:
|
||||||
if: ${{ matrix.distro.legacy-cmake }}
|
if: ${{ matrix.distro.legacy-cmake }}
|
||||||
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
|
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
|
|
||||||
|
- name: Set env vars
|
||||||
|
run: ./scripts/github_env.py
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build
|
run: cmake --build build
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: ./build/bin/unittests
|
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
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON ..
|
cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON ..
|
||||||
. ./version
|
|
||||||
build-wrapper-linux-x86-64 --out-dir bw-output make -j
|
build-wrapper-linux-x86-64 --out-dir bw-output make -j
|
||||||
|
|
||||||
- name: Running coverage
|
- name: Running coverage
|
||||||
|
|
|
||||||
47
.gitignore
vendored
47
.gitignore
vendored
|
|
@ -1,38 +1,15 @@
|
||||||
.vscode
|
# temp dirs created during build
|
||||||
config.h
|
/build
|
||||||
.DS_Store
|
/dist
|
||||||
*.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/
|
|
||||||
/deps
|
/deps
|
||||||
flatpak/.flatpak-builder
|
/tmp
|
||||||
flatpak/build
|
/scripts/**/*.pyc
|
||||||
flatpak/export
|
|
||||||
flatpak/synergy.desktop
|
|
||||||
flatpak/*.flatpak
|
|
||||||
*.code-workspace
|
|
||||||
*.idx
|
|
||||||
aqtinstall.log
|
aqtinstall.log
|
||||||
Brewfile.lock.json
|
Brewfile.lock.json
|
||||||
|
/.cache
|
||||||
|
|
||||||
|
# typical developer-created files
|
||||||
|
.vscode
|
||||||
|
.DS_Store
|
||||||
|
*.code-workspace
|
||||||
|
.env*
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
cmake_minimum_required (VERSION 3.5)
|
cmake_minimum_required (VERSION 3.5)
|
||||||
project (synergy-core C CXX)
|
project (synergy-core C CXX)
|
||||||
|
include (cmake/Version.cmake)
|
||||||
|
|
||||||
# use response files so that ninja can compile on windows,
|
# use response files so that ninja can compile on windows,
|
||||||
# otherwise you get an error when linking qt:
|
# otherwise you get an error when linking qt:
|
||||||
|
|
@ -59,6 +60,18 @@ else()
|
||||||
option (SYNERGY_BUSINESS "Build Business" OFF)
|
option (SYNERGY_BUSINESS "Build Business" OFF)
|
||||||
endif()
|
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_STANDARD 20)
|
||||||
set (CMAKE_CXX_EXTENSIONS OFF)
|
set (CMAKE_CXX_EXTENSIONS OFF)
|
||||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
@ -81,11 +94,6 @@ if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
add_definitions (-DNDEBUG)
|
add_definitions (-DNDEBUG)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
#
|
|
||||||
# Synergy version
|
|
||||||
#
|
|
||||||
include (cmake/Version.cmake)
|
|
||||||
|
|
||||||
# TODO: Find out why we need these, and remove them
|
# TODO: Find out why we need these, and remove them
|
||||||
if (COMMAND cmake_policy)
|
if (COMMAND cmake_policy)
|
||||||
cmake_policy (SET CMP0003 NEW)
|
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)
|
macro (configure_files srcDir destDir)
|
||||||
message (STATUS "Configuring directory ${destDir}")
|
message (STATUS "Configuring directory ${destDir}")
|
||||||
|
|
@ -415,33 +423,14 @@ macro (configure_files srcDir destDir)
|
||||||
endforeach (templateFile)
|
endforeach (templateFile)
|
||||||
endmacro (configure_files)
|
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})
|
if (${SYNERGY_BUILD_INSTALLER})
|
||||||
|
|
||||||
#
|
#
|
||||||
# macOS app Bundle
|
# macOS app bundle
|
||||||
#
|
#
|
||||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||||
set (CMAKE_INSTALL_RPATH "@loader_path/../Libraries;@loader_path/../Frameworks")
|
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_DIR ${CMAKE_BINARY_DIR}/bundle)
|
||||||
set (SYNERGY_BUNDLE_APP_DIR ${SYNERGY_BUNDLE_DIR}/Synergy.app)
|
set (SYNERGY_BUNDLE_APP_DIR ${SYNERGY_BUNDLE_DIR}/Synergy.app)
|
||||||
set (SYNERGY_BUNDLE_BINARY_DIR ${SYNERGY_BUNDLE_APP_DIR}/Contents/MacOS)
|
set (SYNERGY_BUNDLE_BINARY_DIR ${SYNERGY_BUNDLE_APP_DIR}/Contents/MacOS)
|
||||||
|
|
@ -454,14 +443,14 @@ endif()
|
||||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
|
if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
|
||||||
message (STATUS "Configuring the v1 installer")
|
message (STATUS "Configuring the v1 installer")
|
||||||
set(QT_PATH $ENV{CMAKE_PREFIX_PATH})
|
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()
|
endif()
|
||||||
|
|
||||||
#
|
#
|
||||||
# Linux installation
|
# Linux installation
|
||||||
#
|
#
|
||||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux|.*BSD|DragonFly")
|
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)
|
install(FILES res/synergy.svg DESTINATION share/icons/hicolor/scalable/apps)
|
||||||
if("${VERSION_MAJOR}" STREQUAL "2")
|
if("${VERSION_MAJOR}" STREQUAL "2")
|
||||||
install(FILES res/synergy2.desktop DESTINATION share/applications)
|
install(FILES res/synergy2.desktop DESTINATION share/applications)
|
||||||
|
|
@ -471,6 +460,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux|.*BSD|DragonFly")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
else()
|
else()
|
||||||
message (STATUS "NOT configuring the v1 installer")
|
message (STATUS "NOT configuring the installer")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory (src)
|
add_subdirectory (src)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ Enhancements:
|
||||||
- #7336 Add C++ and LLDB to VS Code recommendations
|
- #7336 Add C++ and LLDB to VS Code recommendations
|
||||||
- #7351 Use deps script to make life easier for contribs
|
- #7351 Use deps script to make life easier for contribs
|
||||||
- #7352 Combine GitHub workflows to reduce config duplication
|
- #7352 Combine GitHub workflows to reduce config duplication
|
||||||
|
- #7353 Re-implement packaging for GitHub workflows (macOS)
|
||||||
|
|
||||||
# 1.14.6
|
# 1.14.6
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,25 +59,20 @@ endif()
|
||||||
|
|
||||||
string (TIMESTAMP SYNERGY_BUILD_DATE "%Y%m%d" UTC)
|
string (TIMESTAMP SYNERGY_BUILD_DATE "%Y%m%d" UTC)
|
||||||
set (SYNERGY_SNAPSHOT_INFO "${SYNERGY_VERSION_STAGE}.${SYNERGY_REVISION}")
|
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 "${SYNERGY_VERSION_MAJOR}.${SYNERGY_VERSION_MINOR}.${SYNERGY_VERSION_PATCH}")
|
||||||
set (SYNERGY_VERSION_STRING "${SYNERGY_VERSION}-${SYNERGY_VERSION_TAG}")
|
set (SYNERGY_VERSION_LONG "${SYNERGY_VERSION}-${SYNERGY_VERSION_TAG}")
|
||||||
message (STATUS "Full Synergy version string is '" ${SYNERGY_VERSION_STRING} "'")
|
message (STATUS "Long version number: " ${SYNERGY_VERSION_LONG})
|
||||||
|
|
||||||
add_definitions (-DSYNERGY_VERSION="${SYNERGY_VERSION}")
|
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_REVISION="${SYNERGY_REVISION}")
|
||||||
add_definitions (-DSYNERGY_BUILD_DATE="${SYNERGY_BUILD_DATE}")
|
add_definitions (-DSYNERGY_BUILD_DATE="${SYNERGY_BUILD_DATE}")
|
||||||
add_definitions (-DSYNERGY_VERSION_BUILD=${SYNERGY_VERSION_BUILD})
|
add_definitions (-DSYNERGY_VERSION_BUILD=${SYNERGY_VERSION_BUILD})
|
||||||
|
|
||||||
if (SYNERGY_DEVELOPER_MODE)
|
file(WRITE ${CMAKE_BINARY_DIR}/.env.version
|
||||||
add_definitions (-DSYNERGY_DEVELOPER_MODE=1)
|
"SYNERGY_VERSION_MAJOR=${SYNERGY_VERSION_MAJOR}\n"
|
||||||
endif()
|
"SYNERGY_VERSION_MINOR=${SYNERGY_VERSION_MINOR}\n"
|
||||||
|
"SYNERGY_VERSION_PATCH=${SYNERGY_VERSION_PATCH}\n"
|
||||||
if (SYNERGY_ENTERPRISE)
|
"SYNERGY_VERSION_BUILD=${SYNERGY_VERSION_BUILD}\n"
|
||||||
add_definitions (-DSYNERGY_ENTERPRISE=1)
|
"SYNERGY_VERSION_STAGE=${SYNERGY_VERSION_STAGE}\n")
|
||||||
endif()
|
|
||||||
|
|
||||||
if (SYNERGY_BUSINESS)
|
|
||||||
add_definitions(-DSYNERGY_BUSINESS=1)
|
|
||||||
endif()
|
|
||||||
|
|
|
||||||
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
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os
|
import os, sys, argparse, traceback
|
||||||
from lib import windows, cmd_utils
|
from lib import env, cmd_utils
|
||||||
import sys
|
|
||||||
import argparse
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
config_file = "deps.yml"
|
# important: load venv before loading modules that install deps.
|
||||||
|
env.ensure_in_venv(__file__)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Entry point for the script."""
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--pause-on-exit", action="store_true", help="Useful on Windows"
|
"--pause-on-exit", action="store_true", help="Useful on Windows"
|
||||||
|
|
@ -42,171 +17,110 @@ def main():
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
error = False
|
||||||
try:
|
try:
|
||||||
deps = Dependencies(args.only)
|
deps = Dependencies(args.only)
|
||||||
deps.install()
|
deps.install()
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
error = True
|
||||||
|
|
||||||
if args.pause_on_exit:
|
if args.pause_on_exit:
|
||||||
input("Press enter to continue...")
|
input("Press enter to continue...")
|
||||||
|
|
||||||
|
if error:
|
||||||
def get_os():
|
sys.exit(1)
|
||||||
"""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}"
|
|
||||||
|
|
||||||
|
|
||||||
class Dependencies:
|
class Dependencies:
|
||||||
|
|
||||||
def __init__(self, only):
|
def __init__(self, only):
|
||||||
|
from lib.config import Config
|
||||||
|
|
||||||
self.config = Config()
|
self.config = Config()
|
||||||
self.only = only
|
self.only = only
|
||||||
|
self.ci_env = env.is_running_in_ci()
|
||||||
|
|
||||||
|
if self.ci_env:
|
||||||
|
print("CI environment detected")
|
||||||
|
|
||||||
def install(self):
|
def install(self):
|
||||||
"""Installs dependencies for the current platform."""
|
"""Installs dependencies for the current platform."""
|
||||||
|
|
||||||
os = get_os()
|
if env.is_windows():
|
||||||
if os == "windows":
|
|
||||||
self.windows()
|
self.windows()
|
||||||
elif os == "mac":
|
elif env.is_mac():
|
||||||
self.mac()
|
self.mac()
|
||||||
elif os == "linux":
|
elif env.is_linux():
|
||||||
self.linux()
|
self.linux()
|
||||||
else:
|
else:
|
||||||
raise PlatformError(f"Unsupported platform: {os}")
|
raise RuntimeError(f"Unsupported platform: {os}")
|
||||||
|
|
||||||
def windows(self):
|
def windows(self):
|
||||||
"""Installs dependencies on Windows."""
|
"""Installs dependencies on Windows."""
|
||||||
|
from lib import windows
|
||||||
|
|
||||||
if not windows.is_admin():
|
if not windows.is_admin():
|
||||||
windows.relaunch_as_admin(__file__)
|
windows.relaunch_as_admin(__file__)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
ci_env = os.environ.get("CI")
|
|
||||||
if ci_env:
|
|
||||||
print("CI environment detected")
|
|
||||||
|
|
||||||
only_qt = self.only == "qt"
|
only_qt = self.only == "qt"
|
||||||
|
|
||||||
# for ci, skip qt; we install qt separately so we can cache it.
|
# for ci, skip qt; we install qt separately so we can cache it.
|
||||||
if not ci_env or only_qt:
|
if not self.ci_env or only_qt:
|
||||||
qt = windows.WindowsQt(self.config.get_qt_config(), config_file)
|
qt = windows.WindowsQt(*self.config.get_qt_config())
|
||||||
qt_install_dir = qt.get_install_dir()
|
qt_install_dir = qt.get_install_dir()
|
||||||
if qt_install_dir:
|
if qt_install_dir:
|
||||||
print(f"Skipping Qt, already installed at: {qt_install_dir}")
|
print(f"Skipping Qt, already installed at: {qt_install_dir}")
|
||||||
else:
|
else:
|
||||||
qt.install()
|
qt.install()
|
||||||
|
|
||||||
|
if not self.ci_env:
|
||||||
|
qt.set_env_vars()
|
||||||
|
|
||||||
if only_qt:
|
if only_qt:
|
||||||
return
|
return
|
||||||
|
|
||||||
choco = windows.WindowsChoco()
|
choco = windows.WindowsChoco()
|
||||||
if ci_env:
|
if self.ci_env:
|
||||||
choco.config_ci_cache()
|
choco.config_ci_cache()
|
||||||
|
choco_config_file, remove_packages = self.config.get_choco_config()
|
||||||
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.remove_from_config(choco_config_file, remove_packages)
|
choco.remove_from_config(choco_config_file, remove_packages)
|
||||||
|
|
||||||
try:
|
command = self.config.get_deps_command()
|
||||||
command = self.config.os["command"]
|
choco.install(command, self.ci_env)
|
||||||
except KeyError:
|
|
||||||
raise YamlError(f"Nothing found in {config_file} on Windows for: command")
|
|
||||||
|
|
||||||
choco.install(command, ci_env)
|
|
||||||
|
|
||||||
def mac(self):
|
def mac(self):
|
||||||
"""Installs dependencies on macOS."""
|
"""Installs dependencies on macOS."""
|
||||||
try:
|
from lib import mac
|
||||||
command = self.config.os["command"]
|
|
||||||
except KeyError:
|
|
||||||
raise YamlError(f"Nothing found in {config_file} on Mac for: command")
|
|
||||||
|
|
||||||
|
command = self.config.get_os_deps_value("command")
|
||||||
cmd_utils.run(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):
|
def linux(self):
|
||||||
"""Installs dependencies on Linux."""
|
"""Installs dependencies on Linux."""
|
||||||
|
|
||||||
distro = get_linux_distro()
|
distro = env.get_linux_distro()
|
||||||
if not 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)
|
command = self.config.get_linux_deps_command(distro)
|
||||||
cmd_utils.run(command)
|
|
||||||
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,91 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def run(command, check=True):
|
def has_command(command):
|
||||||
"""Runs a shell command and by default asserts that the return code is 0."""
|
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
|
command_str = command
|
||||||
if isinstance(command, list):
|
if isinstance(command, list):
|
||||||
command_str = " ".join(command)
|
command_str = " ".join(command)
|
||||||
|
|
||||||
print(f"Running: {command_str}")
|
if print_cmd:
|
||||||
sys.stdout.flush()
|
print(f"Running: {command_str}")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
try:
|
if get_output:
|
||||||
subprocess.run(command, shell=True, check=check)
|
result = subprocess.run(
|
||||||
except subprocess.CalledProcessError as e:
|
command,
|
||||||
print(f"Command failed: {command_str}", file=sys.stderr)
|
shell=shell,
|
||||||
raise e
|
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
|
import xml.etree.ElementTree as ET
|
||||||
from lib import cmd_utils
|
from lib import cmd_utils
|
||||||
|
|
||||||
|
cmake_env_var = "CMAKE_PREFIX_PATH"
|
||||||
class EnvError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def relaunch_as_admin(script):
|
def relaunch_as_admin(script):
|
||||||
|
|
@ -24,6 +22,24 @@ def is_admin():
|
||||||
return False
|
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:
|
class WindowsChoco:
|
||||||
"""Chocolatey for Windows."""
|
"""Chocolatey for Windows."""
|
||||||
|
|
||||||
|
|
@ -33,6 +49,7 @@ class WindowsChoco:
|
||||||
# don't show noisy choco progress bars in ci env
|
# don't show noisy choco progress bars in ci env
|
||||||
cmd_utils.run(f"{command} --no-progress")
|
cmd_utils.run(f"{command} --no-progress")
|
||||||
else:
|
else:
|
||||||
|
cmd_utils.run("winget install chocolatey", check=False)
|
||||||
cmd_utils.run(command)
|
cmd_utils.run(command)
|
||||||
|
|
||||||
def config_ci_cache(self):
|
def config_ci_cache(self):
|
||||||
|
|
@ -48,10 +65,10 @@ class WindowsChoco:
|
||||||
else:
|
else:
|
||||||
print(f"Warning: CI environment variable {runner_temp_key} not set")
|
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."""
|
"""Removes a package from the Chocolatey configuration."""
|
||||||
|
|
||||||
tree = ET.parse(config_file)
|
tree = ET.parse(choco_config_file)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
for remove in remove_packages:
|
for remove in remove_packages:
|
||||||
for package in root.findall("package"):
|
for package in root.findall("package"):
|
||||||
|
|
@ -59,33 +76,22 @@ class WindowsChoco:
|
||||||
root.remove(package)
|
root.remove(package)
|
||||||
print(f"Removed package from choco config: {remove}")
|
print(f"Removed package from choco config: {remove}")
|
||||||
|
|
||||||
tree.write(config_file)
|
tree.write(choco_config_file)
|
||||||
|
|
||||||
|
|
||||||
class WindowsQt:
|
class WindowsQt:
|
||||||
"""Qt for Windows."""
|
"""Qt for Windows."""
|
||||||
|
|
||||||
def __init__(self, config, config_file):
|
def __init__(self, mirror_url, default_version, default_base_dir):
|
||||||
self.config = config
|
self.mirror_url = mirror_url
|
||||||
self.config_file = config_file
|
|
||||||
|
|
||||||
self.version = os.environ.get("QT_VERSION")
|
self.version = os.environ.get("QT_VERSION")
|
||||||
if not self.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}")
|
print(f"QT_VERSION not set, using: {default_version}")
|
||||||
self.version = default_version
|
self.version = default_version
|
||||||
|
|
||||||
self.base_dir = os.environ.get("QT_BASE_DIR")
|
self.base_dir = os.environ.get("QT_BASE_DIR")
|
||||||
if not self.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}")
|
print(f"QT_BASE_DIR not set, using: {default_base_dir}")
|
||||||
self.base_dir = default_base_dir
|
self.base_dir = default_base_dir
|
||||||
|
|
||||||
|
|
@ -100,17 +106,15 @@ class WindowsQt:
|
||||||
|
|
||||||
cmd_utils.run(["pip", "install", "aqtinstall"])
|
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 = ["python", "-m", "aqt", "install-qt"]
|
||||||
args.extend(["--outputdir", self.base_dir])
|
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"])
|
args.extend(["windows", "desktop", self.version, "win64_msvc2019_64"])
|
||||||
cmd_utils.run(args)
|
cmd_utils.run(args)
|
||||||
|
|
||||||
install_dir = self.get_install_dir()
|
install_dir = self.get_install_dir()
|
||||||
if not 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
|
import glob
|
||||||
from lib import windows
|
from lib import windows
|
||||||
|
|
||||||
BIN_NAME = 'synergyd'
|
BIN_NAME = "synergyd"
|
||||||
SOURCE_BIN_DIR = os.path.join('build', 'bin')
|
SOURCE_BIN_DIR = os.path.join("build", "bin")
|
||||||
TARGET_BIN_DIR = 'bin'
|
TARGET_BIN_DIR = "bin"
|
||||||
SERVICE_NOT_RUNNING_ERROR = 2
|
SERVICE_NOT_RUNNING_ERROR = 2
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Entry point for the script."""
|
"""Entry point for the script."""
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--pause-on-exit', action='store_true')
|
parser.add_argument("--pause-on-exit", action="store_true")
|
||||||
parser.add_argument('--source-bin-dir', default=SOURCE_BIN_DIR)
|
parser.add_argument("--source-bin-dir", default=SOURCE_BIN_DIR)
|
||||||
parser.add_argument('--target-bin-dir', default=TARGET_BIN_DIR)
|
parser.add_argument("--target-bin-dir", default=TARGET_BIN_DIR)
|
||||||
parser.add_argument('--source-bin-name', default=BIN_NAME)
|
parser.add_argument("--source-bin-name", default=BIN_NAME)
|
||||||
parser.add_argument('--target-bin-name', default=BIN_NAME)
|
parser.add_argument("--target-bin-name", default=BIN_NAME)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if not windows.is_admin():
|
if not windows.is_admin():
|
||||||
windows.relaunch_as_admin(__file__)
|
windows.relaunch_as_admin(__file__)
|
||||||
sys.exit()
|
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):
|
def reinstall(source_bin_dir, target_bin_dir, source_bin_name, target_bin_name):
|
||||||
"""Stops the running daemon service, copies files, and reinstalls."""
|
"""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
|
|
||||||
|
|
||||||
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')
|
target_bin_file = f"{os.path.join(target_bin_dir, target_bin_name)}.exe"
|
||||||
subprocess.run([target_bin_file, '/uninstall'], shell=True, check=True)
|
|
||||||
|
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):
|
def copy_bin_files(source_bin_dir, target_bin_dir, source_bin_name, target_bin_name):
|
||||||
|
|
||||||
if not os.path.isdir(source_bin_dir):
|
if not os.path.isdir(source_bin_dir):
|
||||||
raise Exception(f'Invalid source bin dir: {source_bin_dir}')
|
raise RuntimeError(f"Invalid source bin dir: {source_bin_dir}")
|
||||||
|
|
||||||
print(f'Persisting dir: {target_bin_dir}')
|
print(f"Persisting dir: {target_bin_dir}")
|
||||||
os.makedirs(target_bin_dir, exist_ok=True)
|
os.makedirs(target_bin_dir, exist_ok=True)
|
||||||
|
|
||||||
source_bin_glob = f'{source_bin_name}*'
|
source_bin_glob = f"{source_bin_name}*"
|
||||||
source_files = glob.glob(os.path.join(source_bin_dir, source_bin_glob))
|
source_files = glob.glob(os.path.join(source_bin_dir, source_bin_glob))
|
||||||
|
|
||||||
if not source_files:
|
if not source_files:
|
||||||
raise Exception(f'No files found in {source_bin_dir} matching {source_bin_glob}')
|
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()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -25,5 +25,5 @@ const char* kCopyright = "Copyright (C) 2012-%s Symless Ltd.\n"
|
||||||
"Copyright (C) 2002-2014 Chris Schoeneman";
|
"Copyright (C) 2002-2014 Chris Schoeneman";
|
||||||
const char* kContact = "Email: engineering@symless.com";
|
const char* kContact = "Email: engineering@symless.com";
|
||||||
const char* kWebsite = "https://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;
|
const char* kAppVersion = "Synergy " SYNERGY_VERSION;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue