Re-implement packaging for GitHub workflows (Windows) (#7360)

* Add new version env vars to example

* Remove test file

* Move CMake packaging to separate module and configure OpenSSL path for Windows

* Make VS Code CMake build task default

* Generate Microsoft-friendly 4-digit version number

* Update macOS bundle .plist with build year variable

* Use correct OpenSSL path and fixed various MSI variables

* Use correct rest/dist dir for MSI

* Add version .rc file for Windows

* Use macro instead of over-complicated version query command

* Made cmd_utils more secure by defaulting to no-shell and no-print

* Add certificate management module

* Implement packaging script on Windows

* Refactor Mac packaging script to use new cmd_utils args and new cert module

* Update ChangeLog

* Change PFX env vars and add to CI

* Use import as instead of from lib to solve resolve issue

* Allow custom certificate extensions

* Check for package version when using gdrive

* Make version number required

* Add missing shell

* Add missing gdrive value in test

* Find OpenSSL dir based on openssl binary

* Only use first OpenSSL entry

* More verbose logging

* Improve logging

* Only use env var if not empty

* Fixed wrong var

* Fixed macOS GitHub artefact name

* Change filename format to match new convention
This commit is contained in:
Nick Bolton 2024-06-28 10:35:18 +01:00 committed by GitHub
parent 05ed4ab757
commit bec38ab47b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 746 additions and 370 deletions

View file

@ -13,6 +13,12 @@
# Packaging (optional)
#
# [Windows] Base64 encoded PFX code signing certificate
# WINDOWS_PFX_CERTIFICATE="very-long-base64-encoded-string"
# [Windows] Password for the PFX code signing certificate
# WINDOWS_PFX_PASSWORD="super-secret-password"
# [macOS] Apple ID used to notarize the app
# APPLE_NOTARY_USER="example@example.com"

View file

@ -26,6 +26,12 @@ runs:
using: "composite"
steps:
- if: ${{ inputs.use_gdrive == 'true' && !inputs.package-version }}
run: |
echo "Input 'package-version' is required when uploading to Google Drive"
exit 1
shell: bash
- if: ${{ inputs.use_gdrive == 'true' }}
run: |
SHORT_VERSION=$(echo "${{ inputs.package-version }}" | cut -d'-' -f1)
@ -41,7 +47,7 @@ runs:
retention-days: 3
# Upload to Google Drive
- if: ${{ inputs.use_gdrive }}
- if: ${{ inputs.use_gdrive == 'true' }}
uses: symless/gdrive-upload@target-glob
with:
credentials: ${{ inputs.gdrive-secret-key }}

View file

@ -5,6 +5,7 @@ on:
inputs:
version:
description: Synergy version number
required: true
pull_request:
types:
- opened
@ -68,6 +69,21 @@ jobs:
- name: Package
if: ${{ !github.event.pull_request.draft }}
run: python ./scripts/package.py
env:
WINDOWS_PFX_CERTIFICATE: ${{ secrets.WINDOWS_PFX }}
WINDOWS_PFX_PASSWORD: ${{ secrets.WINDOWS_PFX_PASS }}
- name: Upload
if:
uses: ./.github/actions/dist-upload
with:
use_github: ${{ env.UPLOAD_TO_GITHUB }}
use_gdrive: ${{ env.UPLOAD_TO_GDRIVE }}
github-target-filename: "synergy-windows-x64"
gdrive-target-base-dir: "synergy1/personal"
gdrive-secret-key: ${{ secrets.GOOGLE_DRIVE_KEY }}
gdrive-parent-folder-id: ${{ secrets.GOOGLE_DRIVE_TECH_DRIVE }}
package-version: ${{ env.SYNERGY_VERSION }}
macos:
runs-on: ${{ matrix.runtime.os }}
@ -88,7 +104,7 @@ jobs:
target: "10.14"
shell: "bash"
- name: "macos-11-m1"
- name: "macos-11-arm64"
timeout: 10
os: "macos-14"
arch: arm64
@ -132,7 +148,7 @@ jobs:
with:
use_github: ${{ env.UPLOAD_TO_GITHUB }}
use_gdrive: ${{ env.UPLOAD_TO_GDRIVE }}
github-target-filename: "synergy-macos-${{ matrix.runtime.target }}"
github-target-filename: "synergy-${{ matrix.runtime.name }}"
gdrive-target-base-dir: "synergy1/personal"
gdrive-secret-key: ${{ secrets.GOOGLE_DRIVE_KEY }}
gdrive-parent-folder-id: ${{ secrets.GOOGLE_DRIVE_TECH_DRIVE }}

5
.vscode/tasks.json vendored
View file

@ -7,7 +7,10 @@
"command": "build",
"targets": ["all"],
"preset": "${command:cmake.activeBuildPresetName}",
"group": "build"
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "reinstall windows daemon",

View file

@ -1,6 +1,6 @@
# Synergy -- mouse and keyboard sharing utility
# Copyright (C) 2012-2016 Symless Ltd.
# Copyright (C) 2009 Nick Bolton
# Copyright (C) 2012-2024 Symless Ltd.
# Copyright (C) 2009-2012 Nick Bolton
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -16,7 +16,9 @@
cmake_minimum_required (VERSION 3.5)
project (synergy-core C CXX)
include (cmake/Version.cmake)
set_version()
# use response files so that ninja can compile on windows,
# otherwise you get an error when linking qt:
@ -339,7 +341,7 @@ if (UNIX)
add_definitions (-DWINAPI_XWINDOWS=1)
endif()
elseif (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
elseif (WIN32)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /D _BIND_TO_CURRENT_VCLIBS_VERSION=1")
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD /O2 /Ob2")
@ -354,6 +356,11 @@ elseif (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
/D_XKEYCHECK_H
)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/res/win/version.rc.in
${CMAKE_BINARY_DIR}/src/version.rc
@ONLY)
endif()
#
@ -376,11 +383,12 @@ if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
option(GIT_SUBMODULE "Check submodules during build" ON)
if(GIT_SUBMODULE)
message(STATUS "Submodule update")
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
execute_process(
COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMOD_RESULT)
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
RESULT_VARIABLE GIT_SUBMODULE_RESULT)
if(NOT GIT_SUBMODULE_RESULT EQUAL "0")
message(FATAL_ERROR "Git submodule update failed: ${GIT_SUBMODULE_RESULT}")
endif()
endif()
endif()
@ -389,78 +397,10 @@ endif()
# Google Test
#
if(BUILD_TESTS AND NOT EXISTS "${PROJECT_SOURCE_DIR}/ext/googletest/CMakeLists.txt")
message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.")
message(FATAL_ERROR "Git submodule for Google Test is missing")
endif()
#
# Same as the `configure_file` command but for directories recursively.
#
macro (configure_files srcDir destDir)
message (STATUS "Configuring directory ${destDir}")
make_directory (${destDir})
file (GLOB_RECURSE sourceFiles RELATIVE ${srcDir} ${srcDir}/*)
file (GLOB_RECURSE templateFiles LIST_DIRECTORIES false RELATIVE ${srcDir} ${srcDir}/*.in)
list (REMOVE_ITEM sourceFiles ${templateFiles})
foreach (sourceFile ${sourceFiles})
set (sourceFilePath ${srcDir}/${sourceFile})
if (IS_DIRECTORY ${sourceFilePath})
message (STATUS "Copying directory ${sourceFile}")
make_directory (${destDir}/${sourceFile})
else()
message (STATUS "Copying file ${sourceFile}")
configure_file (${sourceFilePath} ${destDir}/${sourceFile} COPYONLY)
endif()
endforeach (sourceFile)
foreach (templateFile ${templateFiles})
set (sourceTemplateFilePath ${srcDir}/${templateFile})
string (REGEX REPLACE "\.in$" "" templateFile ${templateFile})
message (STATUS "Configuring file ${templateFile}")
configure_file (${sourceTemplateFilePath} ${destDir}/${templateFile} @ONLY)
endforeach (templateFile)
endmacro (configure_files)
if (${SYNERGY_BUILD_INSTALLER})
#
# macOS app bundle
#
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set (CMAKE_INSTALL_RPATH "@loader_path/../Libraries;@loader_path/../Frameworks")
set (SYNERGY_BUNDLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/res/dist/macos/bundle)
set (SYNERGY_BUNDLE_DIR ${CMAKE_BINARY_DIR}/bundle)
set (SYNERGY_BUNDLE_APP_DIR ${SYNERGY_BUNDLE_DIR}/Synergy.app)
set (SYNERGY_BUNDLE_BINARY_DIR ${SYNERGY_BUNDLE_APP_DIR}/Contents/MacOS)
configure_files (${SYNERGY_BUNDLE_SOURCE_DIR} ${SYNERGY_BUNDLE_DIR})
endif()
#
# Windows installer
#
if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
message (STATUS "Configuring the v1 installer")
set(QT_PATH $ENV{CMAKE_PREFIX_PATH})
configure_files (${CMAKE_CURRENT_SOURCE_DIR}/res/dist/wix ${CMAKE_BINARY_DIR}/installer)
endif()
#
# Linux installation
#
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux|.*BSD|DragonFly")
configure_files (${CMAKE_CURRENT_SOURCE_DIR}/res/dist/rpm ${CMAKE_BINARY_DIR}/rpm)
install(FILES res/synergy.svg DESTINATION share/icons/hicolor/scalable/apps)
if("${VERSION_MAJOR}" STREQUAL "2")
install(FILES res/synergy2.desktop DESTINATION share/applications)
else()
install(FILES res/synergy.desktop DESTINATION share/applications)
endif()
endif()
else()
message (STATUS "NOT configuring the installer")
endif()
include(cmake/Packaging.cmake)
configure_packaging()
add_subdirectory (src)

View file

@ -33,8 +33,9 @@ Enhancements:
- #7336 Add C++ and LLDB to VS Code recommendations
- #7351 Use deps script to make life easier for contribs
- #7352 Combine GitHub workflows to reduce config duplication
- #7353 Re-implement packaging for GitHub workflows (macOS)
- #7354 Re-implement CI auto version increment for packaging
- #7353 Re-implement packaging for GitHub workflows (macOS)
- #7360 Re-implement packaging for GitHub workflows (Windows)
# 1.14.6

120
cmake/Packaging.cmake Normal file
View file

@ -0,0 +1,120 @@
# Synergy -- mouse and keyboard sharing utility
# Copyright (C) 2012-2024 Symless Ltd.
# Copyright (C) 2009-2012 Nick Bolton
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# If enabled, configure packaging based on OS.
#
macro(configure_packaging)
if (${SYNERGY_BUILD_INSTALLER})
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
configure_macos_packaging()
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
configure_windows_packaging()
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux|.*BSD|DragonFly")
configure_linux_packaging()
endif()
else()
message (STATUS "Not configuring installer")
endif()
endmacro()
#
# macOS app bundle
#
macro(configure_macos_packaging)
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set (CMAKE_INSTALL_RPATH "@loader_path/../Libraries;@loader_path/../Frameworks")
set (SYNERGY_BUNDLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/res/dist/macos/bundle)
set (SYNERGY_BUNDLE_DIR ${CMAKE_BINARY_DIR}/bundle)
set (SYNERGY_BUNDLE_APP_DIR ${SYNERGY_BUNDLE_DIR}/Synergy.app)
set (SYNERGY_BUNDLE_BINARY_DIR ${SYNERGY_BUNDLE_APP_DIR}/Contents/MacOS)
configure_files (${SYNERGY_BUNDLE_SOURCE_DIR} ${SYNERGY_BUNDLE_DIR})
endif()
endmacro()
function(find_openssl_dir_win32 result)
execute_process(
COMMAND where openssl
OUTPUT_VARIABLE OPENSSL_PATH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# It's possible that there are multiple OpenSSL installations on the system,
# which is the case on GitHub runners.
# For now we'll pick the first one, but that's probably not very robust.
# Maybe our choco config could install to a specific location?
string(REGEX REPLACE "\r?\n" ";" OPENSSL_PATH_LIST ${OPENSSL_PATH})
message(STATUS "Found OpenSSL binaries at: ${OPENSSL_PATH_LIST}")
list(GET OPENSSL_PATH_LIST 0 OPENSSL_FIRST_PATH)
message(STATUS "First OpenSSL binary: ${OPENSSL_FIRST_PATH}")
get_filename_component(OPENSSL_BIN_DIR ${OPENSSL_FIRST_PATH} DIRECTORY)
message(STATUS "OpenSSL bin dir: ${OPENSSL_BIN_DIR}")
get_filename_component(OPENSSL_DIR ${OPENSSL_BIN_DIR} DIRECTORY)
message(STATUS "OpenSSL install root dir: ${OPENSSL_DIR}")
set(${result} ${OPENSSL_DIR} PARENT_SCOPE)
endfunction()
#
# Windows installer
#
macro(configure_windows_packaging)
find_openssl_dir_win32(OPENSSL_PATH)
set(QT_PATH $ENV{CMAKE_PREFIX_PATH})
configure_files (${CMAKE_CURRENT_SOURCE_DIR}/res/dist/wix ${CMAKE_BINARY_DIR}/installer)
endmacro()
#
# Linux packages (including BSD and DragonFly)
#
macro(configure_linux_packaging)
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.desktop DESTINATION share/applications)
endmacro()
#
# Same as the `configure_file` command but for directories recursively.
#
macro (configure_files srcDir destDir)
message (STATUS "Configuring directory ${destDir}")
make_directory (${destDir})
file (GLOB_RECURSE sourceFiles RELATIVE ${srcDir} ${srcDir}/*)
file (GLOB_RECURSE templateFiles LIST_DIRECTORIES false RELATIVE ${srcDir} ${srcDir}/*.in)
list (REMOVE_ITEM sourceFiles ${templateFiles})
foreach (sourceFile ${sourceFiles})
set (sourceFilePath ${srcDir}/${sourceFile})
if (IS_DIRECTORY ${sourceFilePath})
message (STATUS "Copying directory ${sourceFile}")
make_directory (${destDir}/${sourceFile})
else()
message (STATUS "Copying file ${sourceFile}")
configure_file (${sourceFilePath} ${destDir}/${sourceFile} COPYONLY)
endif()
endforeach (sourceFile)
foreach (templateFile ${templateFiles})
set (sourceTemplateFilePath ${srcDir}/${templateFile})
string (REGEX REPLACE "\.in$" "" templateFile ${templateFile})
message (STATUS "Configuring file ${templateFile}")
configure_file (${sourceTemplateFilePath} ${destDir}/${templateFile} @ONLY)
endforeach (templateFile)
endmacro (configure_files)

View file

@ -1,14 +1,58 @@
# Synergy -- mouse and keyboard sharing utility
# Copyright (C) 2012-2024 Symless Ltd.
# Copyright (C) 2009-2012 Nick Bolton
#
# Gets the version number either from an env var or from the VERSION file.
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set (VERSION $ENV{SYNERGY_VERSION})
string(STRIP "${VERSION}" VERSION)
# Either get the version number from the environment or from the VERSION file.
# On Windows, we also set a special 4-digit MSI version number.
macro(set_version)
if (NOT VERSION)
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VERSION)
string(STRIP "${VERSION}" VERSION)
set (SYNERGY_VERSION $ENV{SYNERGY_VERSION})
string(STRIP "${SYNERGY_VERSION}" SYNERGY_VERSION)
if (NOT SYNERGY_VERSION)
file(READ "${CMAKE_SOURCE_DIR}/VERSION" SYNERGY_VERSION)
string(STRIP "${SYNERGY_VERSION}" SYNERGY_VERSION)
endif()
message (STATUS "Version number: " ${VERSION})
add_definitions (-DSYNERGY_VERSION="${VERSION}")
message (STATUS "Version number: " ${SYNERGY_VERSION})
add_definitions (-DSYNERGY_VERSION="${SYNERGY_VERSION}")
# Useful for copyright (e.g. in macOS bundle .plist.in and Windows version .rc file)
string(TIMESTAMP SYNERGY_BUILD_YEAR "%Y")
# MSI requires a 4-digit number and doesn't accept semver.
if (WIN32)
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" _ "${SYNERGY_VERSION}")
set(VERSION_MAJOR "${CMAKE_MATCH_1}")
set(VERSION_MINOR "${CMAKE_MATCH_2}")
set(VERSION_PATCH "${CMAKE_MATCH_3}")
# Find the revision number, which is the number after the 'r'.
string(REGEX MATCH "r([0-9]+)$" _ "${SYNERGY_VERSION}")
set(VERSION_REVISION "${CMAKE_MATCH_1}")
if (NOT VERSION_REVISION)
set(VERSION_REVISION "0")
endif()
# Dot-separated version number for MSI and Windows version .rc file.
set(SYNERGY_VERSION_MS "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_REVISION}")
message (STATUS "Version number for Microsoft (dots): " ${SYNERGY_VERSION_MS})
# CSV version number for Windows version .rc file.
set(SYNERGY_VERSION_MS_CSV "${VERSION_MAJOR},${VERSION_MINOR},${VERSION_PATCH},${VERSION_REVISION}")
message (STATUS "Version number for Microsoft (CSV): " ${SYNERGY_VERSION_MS_CSV})
endif()
endmacro()

View file

@ -11,7 +11,6 @@
<string>Synergy.icns</string>
<key>CFBundleIdentifier</key>
<string>synergy</string>
<!-- TODO: Fix this in v2.0 //-->
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@ -25,7 +24,7 @@
<key>CFBundleVersion</key>
<string>@SYNERGY_VERSION@</string>
<key>NSHumanReadableCopyright</key>
<string>© 2012-2016, Symless Ltd</string>
<string>© 2012-@SYNERGY_BUILD_YEAR@ Symless Ltd</string>
<key>LSMinimumSystemVersion</key>
<string>10.9.0</string>
</dict>

View file

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Include>
<?define Name="Synergy"?>
<?define Version="@SYNERGY_VERSION@" ?>
<?define Author="Symless Ltd" ?>
<?define BinPath="@CMAKE_RUNTIME_OUTPUT_DIRECTORY@/$(var.Configuration)" ?>
<?define Version="@SYNERGY_VERSION_MS@"?>
<?define Author="Symless"?>
<?define BinPath="@CMAKE_RUNTIME_OUTPUT_DIRECTORY@"?>
<?define ResPath="@CMAKE_CURRENT_SOURCE_DIR@/res"?>
<?define ExtPath="@CMAKE_CURRENT_SOURCE_DIR@/ext"?>
<?define DisPath="@CMAKE_CURRENT_SOURCE_DIR@/dist" ?>
<?if $(var.Platform) = "x64"?>
<?define ProgramFilesFolder="ProgramFiles64Folder"?>
<?define PlatformSimpleName="64-bit"?>
@ -19,6 +18,6 @@
<?define QtPath="@QT_PATH@"?>
<?define QtBinPath="$(var.QtPath)\bin"?>
<?define QtPluginsPath="$(var.QtPath)\plugins"?>
<?define OpenSSLPath="$(var.ExtPath)\openssl\windows\$(var.Platform)" ?>
<?define OpenSSLPath="@OPENSSL_PATH@"?>
<?define OpenSSLBinPath="$(var.OpenSSLPath)\bin"?>
</Include>

View file

@ -38,9 +38,9 @@
<Condition><![CDATA[Installed OR (VersionNT >= 602)]]></Condition>
</Component>
<?if $(var.Platform) = x64 ?>
<Merge Id="VC_Redist" SourceFile="$(var.DisPath)\wix\msm\Microsoft_VC142_CRT_x64.msm" DiskId="1" Language="0"/>
<Merge Id="VC_Redist" SourceFile="$(var.ResPath)\dist\wix\msm\Microsoft_VC142_CRT_x64.msm" DiskId="1" Language="0"/>
<?else ?>
<Merge Id="VC_Redist" SourceFile="$(var.DisPath)\wix\msm\Microsoft_VC142_CRT_x86.msm" DiskId="1" Language="0"/>
<Merge Id="VC_Redist" SourceFile="$(var.ResPath)\dist\wix\msm\Microsoft_VC142_CRT_x86.msm" DiskId="1" Language="0"/>
<?endif ?>
</DirectoryRef>
<Property Id="SynergyCommonBackground">common_background</Property>
@ -90,11 +90,11 @@
</File>
<File Source="$(var.BinPath)/syntool.exe"/>
<?if $(var.Platform) = x64 ?>
<File Source="$(var.OpenSSLBinPath)/libssl-1_1-x64.dll"/>
<File Source="$(var.OpenSSLBinPath)/libcrypto-1_1-x64.dll"/>
<File Source="$(var.OpenSSLBinPath)/libssl-3-x64.dll"/>
<File Source="$(var.OpenSSLBinPath)/libcrypto-3-x64.dll"/>
<?else ?>
<File Source="$(var.OpenSSLBinPath)/libssl-1_1.dll"/>
<File Source="$(var.OpenSSLBinPath)/libcrypto-1_1.dll"/>
<File Source="$(var.OpenSSLBinPath)/libssl-3.dll"/>
<File Source="$(var.OpenSSLBinPath)/libcrypto-3.dll"/>
<?endif ?>
</Component>
<Component Guid="BAC8149B-6287-45BF-9C27-43D71ED40214" Id="Gui">
@ -112,7 +112,7 @@
<File Source="$(var.BinPath)\Qt5Widgetsd.dll"/>
<File Source="$(var.BinPath)\libGLESv2d.dll"/>
<File Source="$(var.BinPath)\libEGLd.dll"/>
<!-- Hacky -->
<!-- HACK: Normally the C++ redistributable solves this dependency, including it can cause problems -->
<File Source="C:\Program Files (x86)\Windows Kits\10\bin\$(var.Platform)\ucrt\ucrtbased.dll"/>
<?else ?>
<File Source="$(var.BinPath)\Qt5Core.dll"/>
@ -142,11 +142,11 @@
<ComponentGroup Directory="OpenSSLDir" Id="OpenSSLComponents">
<Component Guid="92648F77-65A6-4B16-AC59-A1F37BD341B1" Id="OpenSSL">
<?if $(var.Platform) = x64 ?>
<File Id="OpenSSLDll1" Source="$(var.OpenSSLBinPath)/libcrypto-1_1-x64.dll"/>
<File Id="OpenSSLDll2" Source="$(var.OpenSSLBinPath)/libssl-1_1-x64.dll"/>
<File Id="OpenSSLDll1" Source="$(var.OpenSSLBinPath)/libcrypto-3-x64.dll"/>
<File Id="OpenSSLDll2" Source="$(var.OpenSSLBinPath)/libssl-3-x64.dll"/>
<?else ?>
<File Id="OpenSSLDll1" Source="$(var.OpenSSLBinPath)/libcrypto-1_1.dll"/>
<File Id="OpenSSLDll2" Source="$(var.OpenSSLBinPath)/libssl-1_1.dll"/>
<File Id="OpenSSLDll1" Source="$(var.OpenSSLBinPath)/libcrypto-3.dll"/>
<File Id="OpenSSLDll2" Source="$(var.OpenSSLBinPath)/libssl-3.dll"/>
<?endif ?>
<File Source="$(var.OpenSSLBinPath)/openssl.exe"/>
<File Source="$(var.ResPath)/openssl/synergy.conf"/>

54
res/win/version.rc.in Normal file
View file

@ -0,0 +1,54 @@
/*
Based on example from:
https://learn.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource?redirectedfrom=MSDN
*/
#include <windows.h>
#include <verrsrc.h>
#define VER_FILEVERSION @SYNERGY_VERSION_MS_CSV@
#define VER_FILEVERSION_STR "@SYNERGY_VERSION_MS@\0"
#define VER_PRODUCTVERSION @SYNERGY_VERSION_MS_CSV@
#define VER_PRODUCTVERSION_STR "@SYNERGY_VERSION_MS@\0"
#define VER_COMPANYNAME_STR "Symless\0"
#define VER_FILEDESCRIPTION_STR "Synergy\0"
#define VER_INTERNALNAME_STR "Synergy\0"
#define VER_LEGALCOPYRIGHT_STR "Copyright (C) Symless Ltd. @SYNERGY_BUILD_YEAR@\0"
#define VER_LEGALTRADEMARKS1_STR "All Rights Reserved\0"
#define VER_LEGALTRADEMARKS2_STR "\0"
#define VER_ORIGINALFILENAME_STR "\0"
#define VER_PRODUCTNAME_STR "Synergy\0"
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
PRODUCTVERSION VER_PRODUCTVERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS 0 // No flags
FILEOS VOS__WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", VER_COMPANYNAME_STR
VALUE "FileDescription", VER_FILEDESCRIPTION_STR
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "InternalName", VER_INTERNALNAME_STR
VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR
VALUE "LegalTrademarks1", VER_LEGALTRADEMARKS1_STR
VALUE "LegalTrademarks2", VER_LEGALTRADEMARKS2_STR
VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR
VALUE "ProductName", VER_PRODUCTNAME_STR
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END

View file

@ -1,7 +1,8 @@
#!/usr/bin/env python3
import argparse
from lib import env, github
import lib.env as env
import lib.github as github
from lib.config import Config
qt_version_key = "QT_VERSION"

View file

@ -1,7 +1,8 @@
#!/usr/bin/env python3
import os, sys, argparse, traceback
from lib import env, cmd_utils
import lib.env as env
import lib.cmd_utils as cmd_utils
def main():
@ -61,7 +62,7 @@ class Dependencies:
def windows(self):
"""Installs dependencies on Windows."""
from lib import windows
import lib.windows as windows
if not windows.is_admin():
windows.relaunch_as_admin(__file__)
@ -95,10 +96,10 @@ class Dependencies:
def mac(self):
"""Installs dependencies on macOS."""
from lib import mac
import lib.mac as mac
command = self.config.get_os_deps_value("command")
cmd_utils.run(command)
cmd_utils.run(command, shell=True, print_cmd=True)
if not self.ci_env:
mac.set_cmake_prefix_env_var(self.config.get_os_value("qt-prefix-command"))
@ -123,7 +124,7 @@ class Dependencies:
# 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)
cmd_utils.run(command, check=False, shell=True, print_cmd=True)
if __name__ == "__main__":

View file

@ -0,0 +1,40 @@
import os, base64
temp_path = "tmp/certificate"
class Certificate:
"""
Installs a certificate from a base64 string, and returns the path to the certificate.
Once the context is exited, the certificate is removed from the filesystem.
Example usage:
with Certificate(base64) as cert_path:
print(f"Certificate path: {cert_path}")
"""
def __init__(self, base64, file_ext):
self.base64 = base64
self.temp_filename = f"{temp_path}.{file_ext}"
def __enter__(self):
print(f"Decoding certificate to temporary path: {self.temp_filename}")
try:
cert_bytes = base64.b64decode(self.base64)
except Exception as e:
raise ValueError("Failed to decode certificate base64") from e
os.makedirs(os.path.dirname(self.temp_filename), exist_ok=True)
with open(self.temp_filename, "wb") as cert_file:
cert_file.write(cert_bytes)
return self.temp_filename
def __exit__(self, _exc_type, _exc_value, _traceback):
# 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: {self.temp_filename}")
os.remove(self.temp_filename)
# propagate exceptions
return False

View file

@ -37,30 +37,28 @@ def strip_continuation_sequences(command):
# 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,
check=True, # true by default to fail fast
shell=False, # false by default for security
get_output=False,
print_cmd=True,
print_cmd=False, # false by default for security
):
"""
Convenience wrapper around `subprocess.run` to:
- print the command before running it
- pipe/capture the output instead of printing it
- print the command before running it (if `print_cmd` is True)
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)
- prints list commands as a readable string on failure
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.
This is the same as `subprocess.run` in that it:
- does not use shell by default for security (shell is less secure)
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.
shell (bool): Run the command in a shell (false by default for security)
get_output (bool): Return the output of the command.
print_cmd (bool): Print the command before running it.
print_cmd (bool): Print the command before running it (false by default for security)
"""
# create string version of list command, only for debugging purposes
@ -70,8 +68,16 @@ def run(
if print_cmd:
print(f"Running: {command_str}")
sys.stdout.flush()
else:
print("Running command...")
command_str = "***"
# Flush the output to ensure the command is printed before the output of the command,
# which seems to happen in the GitHub runner logs.
sys.stdout.flush()
sys.stderr.flush()
try:
if get_output:
result = subprocess.run(
command,
@ -83,10 +89,23 @@ def run(
)
else:
result = subprocess.run(command, check=check, shell=shell)
except subprocess.CalledProcessError as e:
# Take control of how failed commands are printed:
# - if `print_cmd` is false, it will print `***` instead of the command
# - if the command was a list, the command is printed as a readable string
raise RuntimeError(
f"Command exited with code {e.returncode}: {command_str}"
) from None
except Exception:
# Take control of how failed commands are printed:
# - if `print_cmd` is false, it will print `***` instead of the command
# - if the command was a list, the command is printed as a readable string
raise RuntimeError(f"Command failed: {command_str}") from None
if print_cmd and result.returncode != 0:
if result.returncode != 0:
print(
f"Command exited with code {result.returncode}: {command_str}",
file=sys.stderr,
)
return result

View file

@ -1,4 +1,5 @@
from lib import env, cmd_utils
import lib.env as env
import lib.cmd_utils as cmd_utils
config_file = "config.yml"
root_key = "config"

View file

@ -1,5 +1,5 @@
import os, sys, subprocess, platform
from lib import env, cmd_utils
import os, sys, subprocess
import lib.cmd_utils as cmd_utils
venv_path = "build/python"
version_file = "VERSION"
@ -75,7 +75,7 @@ def in_venv():
return sys.prefix != sys.base_prefix
def ensure_in_venv(script):
def ensure_in_venv(script_file):
"""
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.
@ -89,11 +89,9 @@ def ensure_in_venv(script):
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()
print(f"Using virtual environment for {script_file}", flush=True)
python_executable = get_python_executable()
result = subprocess.run([python_executable, script] + sys.argv[1:])
result = subprocess.run([python_executable, script_file] + sys.argv[1:])
sys.exit(result.returncode)
@ -109,7 +107,11 @@ def ensure_module(module, package):
__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)
cmd_utils.run(
[sys.executable, "-m", "pip", "install", package],
shell=False,
print_cmd=True,
)
def assert_dependencies(raise_error=True):
@ -150,11 +152,23 @@ def ensure_dependencies():
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())
cmd_utils.run(
f"{sudo} apt update".strip(), check=False, shell=True, print_cmd=True
)
cmd_utils.run(
f"{sudo} apt install -y python3-pip python3-venv".strip(),
shell=True,
print_cmd=True,
)
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())
cmd_utils.run(
f"{sudo} dnf check-update".strip(), check=False, shell=True, print_cmd=True
)
cmd_utils.run(
f"{sudo} dnf install -y python3-pip python3-virtualenv".strip(),
shell=True,
print_cmd=True,
)
else:
# arch, opensuse, etc, patches welcome! :)
raise RuntimeError(f"Unable to install Python dependencies on {distro}")
@ -165,7 +179,9 @@ def get_app_version():
Returns the version either from the env var, or from the version file.
"""
if version_env_var in os.environ:
return os.environ[version_env_var]
version = os.environ[version_env_var].strip()
if version:
return version
with open(version_file, "r") as f:
return f.read().strip()

View file

@ -1,9 +1,10 @@
import os, subprocess, base64, time, json, shutil, sys
from lib import cmd_utils, env
import os, time, json
import lib.cmd_utils as cmd_utils
import lib.env as env
from lib.certificate import Certificate
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"
@ -30,18 +31,20 @@ def set_env_var(name, value):
def set_cmake_prefix_env_var(cmake_prefix_command):
result = cmd_utils.run(cmake_prefix_command, get_output=True)
result = cmd_utils.run(
cmake_prefix_command, get_output=True, shell=True, print_cmd=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")
cert_base64 = env.get_env_var("APPLE_P12_CERTIFICATE")
cert_password = env.get_env_var("APPLE_P12_PASSWORD")
build_bundle()
install_certificate(certificate, password)
install_certificate(cert_base64, cert_password)
assert_certificate_installed(codesign_id)
sign_bundle(codesign_id)
dmg_path = build_dmg(filename_base)
@ -51,13 +54,12 @@ def package(filename_base):
def build_bundle():
print("Building bundle...")
# cmake build install target should run macdeployqt
cmd_utils.run("cmake --build build --target install")
cmd_utils.run("cmake --build build --target install", shell=True, print_cmd=True)
def sign_bundle(codesign_id):
print(f"Signing bundle {app_path}...")
sys.stdout.flush()
subprocess.run(
cmd_utils.run(
[
codesign_path,
"-f",
@ -67,14 +69,16 @@ def sign_bundle(codesign_id):
"-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
"security find-identity -v -p codesigning",
get_output=True,
shell=True,
print_cmd=True,
)
if codesign_id not in installed.stdout:
@ -122,18 +126,11 @@ def install_certificate(cert_base64, cert_password):
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)
with Certificate(cert_base64, "p12") as cert_path:
print(f"Installing certificate: {cert_path}")
sys.stdout.flush()
try:
# warning: contains private key password, never print this command
subprocess.run(
# WARNING: contains private key password, never print this command
cmd_utils.run(
[
sudo_path,
security_path,
@ -148,19 +145,7 @@ def install_certificate(cert_base64, cert_password):
"-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):
@ -176,7 +161,9 @@ def notarize_package(dmg_path):
def get_xcode_path():
result = cmd_utils.run([xcode_select_path, "-p"], get_output=True, shell=False)
result = cmd_utils.run(
[xcode_select_path, "-p"], get_output=True, shell=False, print_cmd=True
)
return result.stdout.strip()
@ -193,12 +180,10 @@ class NotaryTool:
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(
# WARNING: contains password, never print this command
cmd_utils.run(
[
notarytool_path,
"store-credentials",
@ -209,15 +194,8 @@ class NotaryTool:
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...")
@ -254,6 +232,7 @@ class NotaryTool:
],
get_output=True,
shell=False,
print_cmd=True,
)
if result.stderr:
@ -274,6 +253,7 @@ class NotaryTool:
],
get_output=True,
shell=False,
print_cmd=True,
)
if result.stderr:

View file

@ -1,12 +1,19 @@
import ctypes
import sys
import os
import ctypes, sys, os, shutil
import xml.etree.ElementTree as ET
from lib import cmd_utils
import lib.cmd_utils as cmd_utils
import lib.env as env
from lib.certificate import Certificate
msbuild_cmd = "msbuild"
signtool_cmd = "signtool"
certutil_cmd = "certutil"
cmake_env_var = "CMAKE_PREFIX_PATH"
runner_temp_env_var = "RUNNER_TEMP"
qt_base_dir_env_var = "QT_BASE_DIR"
dist_dir = "dist"
build_dir = "build"
wix_solution_file = f"{build_dir}/installer/Synergy.sln"
installer_file = f"{build_dir}/installer/bin/Release/Synergy.msi"
def relaunch_as_admin(script):
@ -39,7 +46,89 @@ def set_env_var(name, 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)
cmd_utils.run(["setx", name, new_value], check=True, shell=True, print_cmd=True)
def package(filename_base):
cert_base64 = env.get_env_var("WINDOWS_PFX_CERTIFICATE")
cert_password = env.get_env_var("WINDOWS_PFX_PASSWORD")
sign_binaries(cert_base64, cert_password)
build_msi(filename_base)
sign_msi(filename_base, cert_base64, cert_password)
def assert_vs_cmd(cmd):
has_cmd = cmd_utils.has_command(cmd)
if not has_cmd:
raise RuntimeError(
f"The '{cmd}' command was not found, "
"re-run from 'Developer Command Prompt for VS'"
)
def build_msi(filename_base):
print("Building MSI installer...")
configuration = "Release"
platform = "x64"
assert_vs_cmd(msbuild_cmd)
cmd_utils.run(
[
msbuild_cmd,
wix_solution_file,
f"/p:Configuration={configuration}",
f"/p:Platform={platform}",
],
shell=True,
print_cmd=True,
)
path = get_package_path(filename_base)
print(f"Copying MSI installer to {dist_dir}")
os.makedirs(dist_dir, exist_ok=True)
shutil.copy(installer_file, path)
def get_package_path(filename_base):
return f"{dist_dir}/{filename_base}.msi"
def sign_binaries(cert_base64, cert_password):
exe_pattern = f"{build_dir}/bin/*.exe"
run_codesign(exe_pattern, cert_base64, cert_password)
def sign_msi(filename_base, cert_base64, cert_password):
path = get_package_path(filename_base)
run_codesign(path, cert_base64, cert_password)
def run_codesign(path, cert_base64, cert_password):
time_server = "http://timestamp.digicert.com"
hashing_algorithm = "SHA256"
with Certificate(cert_base64, "pfx") as cert_path:
print("Signing MSI installer...")
assert_vs_cmd(signtool_cmd)
# WARNING: contains private key password, never print this command
cmd_utils.run(
[
signtool_cmd,
"sign",
"/f",
cert_path,
"/p",
cert_password,
"/t",
time_server,
"/fd",
hashing_algorithm,
path,
],
shell=True,
)
class WindowsChoco:
@ -49,10 +138,23 @@ class WindowsChoco:
"""Installs packages using Chocolatey."""
if 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",
shell=True,
print_cmd=True,
)
else:
cmd_utils.run("winget install chocolatey", check=False)
cmd_utils.run(command)
cmd_utils.run(
"winget install chocolatey",
check=False,
shell=True,
print_cmd=True,
)
cmd_utils.run(
command,
shell=True,
print_cmd=True,
)
def config_ci_cache(self):
"""Configures Chocolatey cache for CI."""
@ -62,7 +164,11 @@ class WindowsChoco:
# sets the choco cache dir, which should match the dir in the ci cache action.
key_arg = '--name="cacheLocation"'
value_arg = f'--value="{runner_temp}/choco"'
cmd_utils.run(["choco", "config", "set", key_arg, value_arg])
cmd_utils.run(
["choco", "config", "set", key_arg, value_arg],
shell=True,
print_cmd=True,
)
else:
print(f"Warning: CI environment variable {runner_temp_env_var} not set")
@ -102,13 +208,21 @@ class WindowsQt:
def install(self):
"""Installs Qt on Windows."""
cmd_utils.run(["pip", "install", "aqtinstall"])
cmd_utils.run(
["pip", "install", "aqtinstall"],
shell=True,
print_cmd=True,
)
args = ["python", "-m", "aqt", "install-qt"]
args.extend(["--outputdir", self.base_dir])
args.extend(["--base", self.mirror_url])
args.extend(["windows", "desktop", self.version, "win64_msvc2019_64"])
cmd_utils.run(args)
cmd_utils.run(
args,
shell=True,
print_cmd=True,
)
install_dir = self.get_install_dir()
if not install_dir:

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
import platform
from lib import env
import lib.env as env
env_file = ".env"
package_filename_product = "synergy"
@ -33,16 +33,17 @@ def main():
def get_filename_base(version):
os = env.get_os()
machine = platform.machine().lower()
return f"{package_filename_product}-{version}-{os}-{machine}"
return f"{package_filename_product}-{os}-{machine}-{version}"
def windows_package(filename_base):
"""TODO: Windows packaging"""
pass
import lib.windows as windows
windows.package(filename_base)
def mac_package(filename_base):
from lib import mac
import lib.mac as mac
mac.package(filename_base)

View file

@ -3,7 +3,7 @@ import subprocess
import sys
import argparse
import glob
from lib import windows
import lib.windows as windows
BIN_NAME = "synergyd"
SOURCE_BIN_DIR = os.path.join("build", "bin")

View file

@ -14,21 +14,26 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(target synergy-core)
set(sources
synergy-core.cpp
${target}.cpp
)
if (WIN32)
list(APPEND sources synergy-core.exe.manifest)
list(APPEND sources
${target}.exe.manifest
${CMAKE_BINARY_DIR}/src/version.rc
)
endif()
add_executable(synergy-core ${sources})
target_link_libraries(synergy-core
add_executable(${target} ${sources})
target_link_libraries(${target}
arch base client common io mt net ipc platform server synlib ${libs})
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
install (TARGETS synergy-core DESTINATION ${SYNERGY_BUNDLE_BINARY_DIR})
install (TARGETS ${target} DESTINATION ${SYNERGY_BUNDLE_BINARY_DIR})
elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
install (TARGETS synergy-core DESTINATION bin)
install (TARGETS ${target} DESTINATION bin)
endif()

View file

@ -14,8 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(target synergyc)
set(sources
synergyc.cpp
${target}.cpp
)
if (WIN32)
@ -23,13 +25,14 @@ if (WIN32)
file(GLOB arch_sources "MSWindows*.cpp")
list(APPEND sources
resource.h
synergyc.ico
synergyc.rc
${target}.ico
${target}.rc
tb_error.ico
tb_idle.ico
tb_run.ico
tb_wait.ico
synergyc.exe.manifest
${target}.exe.manifest
${CMAKE_BINARY_DIR}/src/version.rc
)
elseif (APPLE)
file(GLOB arch_headers "OSX*.h")
@ -46,13 +49,13 @@ if (SYNERGY_ADD_HEADERS)
list(APPEND sources ${headers})
endif()
add_executable(synergyc ${sources})
target_link_libraries(synergyc
add_executable(${target} ${sources})
target_link_libraries(${target}
arch base client common io mt net ipc platform server synlib ${libs})
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
install (TARGETS synergyc DESTINATION ${SYNERGY_BUNDLE_BINARY_DIR})
install (TARGETS ${target} DESTINATION ${SYNERGY_BUNDLE_BINARY_DIR})
elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
install (TARGETS synergyc DESTINATION bin)
install (TARGETS ${target} DESTINATION bin)
endif()

View file

@ -14,14 +14,21 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(target synergyd)
file(GLOB headers "*.h")
file(GLOB sources "*.cpp")
if (WIN32)
add_executable (synergyd WIN32 ${sources})
add_executable (
${target}
WIN32
${sources}
${CMAKE_BINARY_DIR}/src/version.rc
)
else()
add_executable (synergyd ${sources})
add_executable (${target} ${sources})
endif()
target_link_libraries (synergyd
target_link_libraries (${target}
arch base common io ipc mt net platform synlib shared ${libs})

View file

@ -14,8 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(target synergys)
set(sources
synergys.cpp
${target}.cpp
)
if (WIN32)
@ -23,13 +25,14 @@ if (WIN32)
file(GLOB arch_sources "MSWindows*.cpp")
list(APPEND sources
resource.h
synergys.ico
synergys.rc
${target}.ico
${target}.rc
tb_error.ico
tb_idle.ico
tb_run.ico
tb_wait.ico
synergys.exe.manifest
${target}.exe.manifest
${CMAKE_BINARY_DIR}/src/version.rc
)
elseif (APPLE)
file(GLOB arch_headers "OSX*.h")
@ -46,14 +49,14 @@ if (SYNERGY_ADD_HEADERS)
list(APPEND sources ${headers})
endif()
add_executable(synergys ${sources})
target_link_libraries(synergys
add_executable(${target} ${sources})
target_link_libraries(${target}
arch base client common io mt net ipc platform server synlib ${libs})
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
install (TARGETS synergys DESTINATION ${SYNERGY_BUNDLE_BINARY_DIR})
install (TARGETS ${target} DESTINATION ${SYNERGY_BUNDLE_BINARY_DIR})
elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
install (TARGETS synergys DESTINATION bin)
install (TARGETS ${target} DESTINATION bin)
endif()

View file

@ -13,15 +13,21 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(target syntool)
file(GLOB headers "*.h")
file(GLOB sources "*.cpp")
add_executable(syntool ${sources})
target_link_libraries(syntool
if (WIN32)
list(APPEND sources ${CMAKE_BINARY_DIR}/src/version.rc)
endif()
add_executable(${target} ${sources})
target_link_libraries(${target}
synlib arch base client common io ipc mt net platform server ${libs})
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
install (TARGETS syntool DESTINATION ${SYNERGY_BUNDLE_BINARY_DIR})
install (TARGETS ${target} DESTINATION ${SYNERGY_BUNDLE_BINARY_DIR})
elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
install (TARGETS syntool DESTINATION bin)
install (TARGETS ${target} DESTINATION bin)
endif()

View file

@ -4,28 +4,28 @@ set (CMAKE_AUTORCC ON)
set (CMAKE_AUTOUIC ON)
set (CMAKE_INCLUDE_CURRENT_DIR ON)
file (GLOB LEGACY_GUI_SOURCE_FILES
file (GLOB GUI_SOURCE_FILES
src/*.cpp
src/*.h
src/validators/*
src/widgets/*
)
file (GLOB LEGACY_GUI_UI_FILES src/*.ui)
file (GLOB LEGACY_ACTIVATION_FILES src/*Activation* src/*License*)
file (GLOB LEGACY_ZEROCONF_FILES src/Zeroconf*)
file (GLOB LEGACY_LANGUAGE_FILES res/lang/*.ts)
file (GLOB GUI_UI_FILES src/*.ui)
file (GLOB ACTIVATION_FILES src/*Activation* src/*License*)
file (GLOB ZEROCONF_FILES src/Zeroconf*)
file (GLOB LANGUAGE_FILES res/lang/*.ts)
configure_file(res/Languages.qrc res/Languages.qrc)
set_source_files_properties(${LEGACY_LANGUAGE_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/res/lang")
set_source_files_properties(${LANGUAGE_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/res/lang")
qt5_add_translation(QM_FILES ${LEGACY_LANGUAGE_FILES})
qt5_add_translation(QM_FILES ${LANGUAGE_FILES})
add_custom_target(translations ALL DEPENDS ${QM_FILES})
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
file (GLOB LEGACY_GUI_MAC_SOURCE_FILES src/*.mm)
list (APPEND LEGACY_GUI_SOURCE_FILES ${LEGACY_GUI_MAC_SOURCE_FILES})
file (GLOB GUI_MAC_SOURCE_FILES src/*.mm)
list (APPEND GUI_SOURCE_FILES ${GUI_MAC_SOURCE_FILES})
endif()
# Retrieve the absolute path to qmake and then use that path to find
@ -36,21 +36,24 @@ find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}")
find_program(MACDEPLOYQT_EXECUTABLE macdeployqt HINTS "${_qt_bin_dir}")
if (SYNERGY_ENTERPRISE)
list (REMOVE_ITEM LEGACY_GUI_SOURCE_FILES ${LEGACY_ACTIVATION_FILES})
list (REMOVE_ITEM LEGACY_GUI_UI_FILES ${LEGACY_ACTIVATION_FILES})
list (REMOVE_ITEM GUI_SOURCE_FILES ${ACTIVATION_FILES})
list (REMOVE_ITEM GUI_UI_FILES ${ACTIVATION_FILES})
endif ()
list (REMOVE_ITEM LEGACY_GUI_SOURCE_FILES ${LEGACY_ZEROCONF_FILES})
list (REMOVE_ITEM GUI_SOURCE_FILES ${ZEROCONF_FILES})
if (WIN32)
set (LEGACY_GUI_RC_FILES res/win/Synergy.rc)
set (GUI_RC_FILES
res/win/Synergy.rc
${CMAKE_BINARY_DIR}/src/version.rc
)
endif()
add_executable (synergy WIN32
${LEGACY_GUI_SOURCE_FILES}
${LEGACY_GUI_UI_FILES}
${LEGACY_GUI_RC_FILES}
${GUI_SOURCE_FILES}
${GUI_UI_FILES}
${GUI_RC_FILES}
res/Synergy.qrc
${CMAKE_CURRENT_BINARY_DIR}/res/Languages.qrc
${QM_FILES}
@ -59,7 +62,7 @@ add_executable (synergy WIN32
include_directories (./src)
target_link_libraries (synergy shared)
qt5_use_modules (synergy Core Widgets Network)
target_link_libraries(synergy Qt5::Core Qt5::Widgets Qt5::Network)
target_compile_definitions (synergy PRIVATE -DSYNERGY_VERSION_STAGE="${SYNERGY_VERSION_STAGE}")
target_compile_definitions (synergy PRIVATE -DSYNERGY_REVISION="${SYNERGY_REVISION}")

View file

@ -26,8 +26,7 @@ AboutDialog::AboutDialog(MainWindow* parent, const AppConfig& config) :
setupUi(this);
m_versionChecker.setApp(parent->appPath(config.synergycName()));
QString version = m_versionChecker.getVersion();
m_pLabelSynergyVersion->setText(version);
m_pLabelSynergyVersion->setText(SYNERGY_VERSION);
QString buildDateString = QString::fromLocal8Bit(__DATE__).simplified();
QDate buildDate = QLocale("en_US").toDate(buildDateString, "MMM d yyyy");

View file

@ -188,11 +188,10 @@ MainWindow::MainWindow (AppConfig& appConfig,
updateWindowTitle();
QString lastVersion = m_AppConfig->lastVersion();
QString currentVersion = m_VersionChecker.getVersion();
if (lastVersion != currentVersion) {
m_AppConfig->setLastVersion (currentVersion);
if (lastVersion != SYNERGY_VERSION) {
m_AppConfig->setLastVersion (SYNERGY_VERSION);
#ifndef SYNERGY_ENTERPRISE
m_LicenseManager->notifyUpdate (lastVersion, currentVersion);
m_LicenseManager->notifyUpdate (lastVersion, SYNERGY_VERSION);
#endif
}

View file

@ -24,12 +24,8 @@
#include <QProcess>
#include <QLocale>
#define VERSION_REGEX "(\\d+\\.\\d+\\.\\d+-[a-z1-9]*)"
#define VERSION_REGEX_SECTIONED "(\\d+)\\.(\\d+)\\.(\\d+)-([a-z1-9]*)"
#define VERSION_SEGMENT_COUNT 4
#define VERSION_URL "https://api.symless.com/version"
VersionChecker::VersionChecker()
{
m_manager = new QNetworkAccessManager(this);
@ -46,53 +42,52 @@ VersionChecker::~VersionChecker()
void VersionChecker::checkLatest()
{
auto request = QNetworkRequest(QUrl(VERSION_URL));
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Synergy (") + getVersion() + ") " + QSysInfo::prettyProductName());
request.setRawHeader("X-Synergy-Version", getVersion().toStdString().c_str() );
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Synergy (") + SYNERGY_VERSION + ") " + QSysInfo::prettyProductName());
request.setRawHeader("X-Synergy-Version", SYNERGY_VERSION );
request.setRawHeader("X-Synergy-Language", QLocale::system().name().toStdString().c_str() );
m_manager->get(request);
}
void VersionChecker::replyFinished(QNetworkReply* reply)
{
QString newestVersion = QString(reply->readAll());
if (!newestVersion.isEmpty())
{
QString currentVersion = getVersion();
if (currentVersion != "Unknown") {
if (compareVersions(currentVersion, newestVersion) > 0)
auto newestVersion = QString(reply->readAll());
if (!newestVersion.isEmpty() && compareVersions(SYNERGY_VERSION, newestVersion) > 0) {
emit updateFound(newestVersion);
}
}
}
int VersionChecker::getStageVersion(QString stage)
int VersionChecker::getStageVersion(QString stage) const
{
const int valueStable = INT_MAX; //Stable will always be considered the highest value
const int valueRC = 2;
const int valueSnapshot = 1;
const int valueOther = 0;
const char* stableName = "stable";
const char* rcName = "rc";
const char* betaName = "beta";
//Stable should always be considered highest, followed by rc[0-9] then snapshots with everything else at the end
//HACK There is probably a much better way of doing this
if (stage == "stable")
// use max int for stable so it's always the highest value.
const int stableValue = INT_MAX;
const int rcValue = 2;
const int betaValue = 1;
const int otherValue = 0;
if (stage == stableName)
{
return valueStable;
return stableValue;
}
else if (stage.startsWith("rc") || stage.startsWith("RC"))
else if (stage.toLower().startsWith(rcName))
{
QRegExp rx("\\d*", Qt::CaseInsensitive);
if (rx.indexIn(stage) != -1)
{
//Return the RC value plus the RC version as in int
return valueRC + rx.cap(1).toInt();
// return the rc value plus the rc number (e.g. 2 + 1)
// this should be ok since stable is max int.
return rcValue + rx.cap(1).toInt();
}
}
else if (stage == "snapshot")
else if (stage == betaName)
{
return valueSnapshot;
return betaValue;
}
return valueOther;
return otherValue;
}
int VersionChecker::compareVersions(const QString& left, const QString& right)
@ -100,48 +95,34 @@ int VersionChecker::compareVersions(const QString& left, const QString& right)
if (left.compare(right) == 0)
return 0; // versions are same.
QStringList leftSplit = left.split(QRegExp("[\\.-]"));
if (leftSplit.size() != VERSION_SEGMENT_COUNT)
return 1; // assume right wins.
QStringList leftParts = left.split("-");
QStringList rightParts = right.split("-");
QStringList rightSplit = right.split(QRegExp("[\\.-]"));
if (rightSplit.size() != VERSION_SEGMENT_COUNT)
return -1; // assume left wins.
if (leftParts.size() < 1 || rightParts.size() < 1)
return 0; // versions are same.
const int leftMajor = leftSplit.at(0).toInt();
const int leftMinor = leftSplit.at(1).toInt();
const int leftRev = leftSplit.at(2).toInt();
const int leftStage = getStageVersion(leftSplit.at(3));
QString leftNumber = leftParts.at(0);
QString rightNumber = rightParts.at(0);
const int rightMajor = rightSplit.at(0).toInt();
const int rightMinor = rightSplit.at(1).toInt();
const int rightRev = rightSplit.at(2).toInt();
const int rightStage = getStageVersion(rightSplit.at(3));
QStringList leftNumberParts = left.split(".");
QStringList rightNumberParts = right.split(".");
const int leftMajor = leftNumberParts.at(0).toInt();
const int leftMinor = leftNumberParts.at(1).toInt();
const int leftPatch = leftNumberParts.at(2).toInt();
const int leftStage = leftParts.size() > 1 ? getStageVersion(leftParts.at(1)) : 0;
const int rightMajor = rightNumberParts.at(0).toInt();
const int rightMinor = rightNumberParts.at(1).toInt();
const int rightPatch = rightNumberParts.at(2).toInt();
const int rightStage = rightParts.size() > 1 ? getStageVersion(rightParts.at(1)) : 0;
const bool rightWins =
( rightMajor > leftMajor) ||
((rightMajor >= leftMajor) && (rightMinor > leftMinor)) ||
((rightMajor >= leftMajor) && (rightMinor >= leftMinor) && (rightRev > leftRev)) ||
((rightMajor >= leftMajor) && (rightMinor >= leftMinor) && (rightRev >= leftRev) && (rightStage > leftStage));
((rightMajor >= leftMajor) && (rightMinor >= leftMinor) && (rightPatch > leftPatch)) ||
((rightMajor >= leftMajor) && (rightMinor >= leftMinor) && (rightPatch >= leftPatch) && (rightStage > leftStage));
return rightWins ? 1 : -1;
}
QString VersionChecker::getVersion()
{
QProcess process;
process.start(m_app, QStringList() << "--version");
process.setReadChannel(QProcess::StandardOutput);
if (process.waitForStarted() && process.waitForFinished())
{
QRegExp rx(VERSION_REGEX,Qt::CaseInsensitive);
QString text = process.readLine();
if (rx.indexIn(text) != -1)
{
return rx.cap(1);
}
}
return tr("Unknown");
}

View file

@ -31,7 +31,6 @@ public:
VersionChecker();
virtual ~VersionChecker();
void checkLatest();
QString getVersion();
void setApp(const QString& app) { m_app = app; }
int compareVersions(const QString& left, const QString& right);
public slots:
@ -47,5 +46,5 @@ private:
* \param stage The string containing the stage version
* \return An integer representation of the stage, the higher the number the more recent the version
*/
int getStageVersion(QString stage);
int getStageVersion(QString stage) const;
};

View file

@ -53,6 +53,10 @@ if (SYNERGY_ADD_HEADERS)
list(APPEND sources ${headers})
endif()
if (WIN32)
list(APPEND sources ${CMAKE_BINARY_DIR}/src/version.rc)
endif()
include_directories(
../../
../../lib/

View file

@ -14,6 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(target unittests)
file(GLOB_RECURSE headers "*.h" "languages/*.h")
file(GLOB_RECURSE sources "*.cpp" "languages/*.cpp")
@ -60,6 +62,10 @@ if (SYNERGY_ADD_HEADERS)
list(APPEND sources ${headers})
endif()
add_executable(unittests ${sources})
target_link_libraries(unittests
if (WIN32)
list(APPEND sources ${CMAKE_BINARY_DIR}/src/version.rc)
endif()
add_executable(${target} ${sources})
target_link_libraries(${target}
arch base client server common io net platform server synlib mt ipc gtest gmock shared ${libs})

0
test
View file