diff --git a/.env.example b/.env.example
index 1c9c9b745..0501fdd9b 100644
--- a/.env.example
+++ b/.env.example
@@ -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"
diff --git a/.github/actions/dist-upload/action.yml b/.github/actions/dist-upload/action.yml
index e310192dd..15eb1ac22 100644
--- a/.github/actions/dist-upload/action.yml
+++ b/.github/actions/dist-upload/action.yml
@@ -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 }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6d14ecdef..31b696363 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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 }}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 761251ce2..761027053 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -7,7 +7,10 @@
"command": "build",
"targets": ["all"],
"preset": "${command:cmake.activeBuildPresetName}",
- "group": "build"
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ }
},
{
"label": "reinstall windows daemon",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8a2e7a26a..e28171e37 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
- 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")
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ 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)
diff --git a/ChangeLog b/ChangeLog
index f7518f441..99369c6f4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/cmake/Packaging.cmake b/cmake/Packaging.cmake
new file mode 100644
index 000000000..bd4331df3
--- /dev/null
+++ b/cmake/Packaging.cmake
@@ -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 .
+
+#
+# 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)
diff --git a/cmake/Version.cmake b/cmake/Version.cmake
index 488b2d7eb..4a05f8d38 100644
--- a/cmake/Version.cmake
+++ b/cmake/Version.cmake
@@ -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 .
-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)
-endif()
+ set (SYNERGY_VERSION $ENV{SYNERGY_VERSION})
+ string(STRIP "${SYNERGY_VERSION}" SYNERGY_VERSION)
-message (STATUS "Version number: " ${VERSION})
-add_definitions (-DSYNERGY_VERSION="${VERSION}")
+ if (NOT SYNERGY_VERSION)
+ file(READ "${CMAKE_SOURCE_DIR}/VERSION" SYNERGY_VERSION)
+ string(STRIP "${SYNERGY_VERSION}" SYNERGY_VERSION)
+ endif()
+
+ 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()
diff --git a/res/dist/macos/bundle/Synergy.app/Contents/Info.plist.in b/res/dist/macos/bundle/Synergy.app/Contents/Info.plist.in
index e681046f7..555c18d04 100644
--- a/res/dist/macos/bundle/Synergy.app/Contents/Info.plist.in
+++ b/res/dist/macos/bundle/Synergy.app/Contents/Info.plist.in
@@ -11,7 +11,6 @@
Synergy.icns
CFBundleIdentifier
synergy
-
CFBundleInfoDictionaryVersion
6.0
CFBundleName
@@ -25,7 +24,7 @@
CFBundleVersion
@SYNERGY_VERSION@
NSHumanReadableCopyright
- © 2012-2016, Symless Ltd
+ © 2012-@SYNERGY_BUILD_YEAR@ Symless Ltd
LSMinimumSystemVersion
10.9.0
diff --git a/res/dist/wix/Include.wxi.in b/res/dist/wix/Include.wxi.in
index 11bf1f13d..44d520de4 100644
--- a/res/dist/wix/Include.wxi.in
+++ b/res/dist/wix/Include.wxi.in
@@ -1,24 +1,23 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/dist/wix/Product.wxs b/res/dist/wix/Product.wxs
index 0a7b0f18d..f4bd5f861 100644
--- a/res/dist/wix/Product.wxs
+++ b/res/dist/wix/Product.wxs
@@ -38,9 +38,9 @@
= 602)]]>
-
+
-
+
common_background
@@ -90,11 +90,11 @@
-
-
+
+
-
-
+
+
@@ -112,7 +112,7 @@
-
+
@@ -142,11 +142,11 @@
-
-
+
+
-
-
+
+
diff --git a/res/win/version.rc.in b/res/win/version.rc.in
new file mode 100644
index 000000000..af7c63e2a
--- /dev/null
+++ b/res/win/version.rc.in
@@ -0,0 +1,54 @@
+/*
+Based on example from:
+https://learn.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource?redirectedfrom=MSDN
+*/
+
+#include
+#include
+
+#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
\ No newline at end of file
diff --git a/scripts/github_env.py b/scripts/github_env.py
index 77e133afb..ac7246968 100755
--- a/scripts/github_env.py
+++ b/scripts/github_env.py
@@ -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"
diff --git a/scripts/install_deps.py b/scripts/install_deps.py
index 1b5635485..40dde2e4d 100755
--- a/scripts/install_deps.py
+++ b/scripts/install_deps.py
@@ -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__":
diff --git a/scripts/lib/certificate.py b/scripts/lib/certificate.py
new file mode 100644
index 000000000..6cef50b00
--- /dev/null
+++ b/scripts/lib/certificate.py
@@ -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
diff --git a/scripts/lib/cmd_utils.py b/scripts/lib/cmd_utils.py
index 00ae2c0c8..49395a690 100644
--- a/scripts/lib/cmd_utils.py
+++ b/scripts/lib/cmd_utils.py
@@ -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,23 +68,44 @@ def run(
if print_cmd:
print(f"Running: {command_str}")
- sys.stdout.flush()
-
- if get_output:
- result = subprocess.run(
- command,
- shell=shell,
- check=check,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- text=True,
- )
else:
- result = subprocess.run(command, check=check, shell=shell)
+ print("Running command...")
+ command_str = "***"
- if print_cmd and result.returncode != 0:
+ # 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,
+ shell=shell,
+ check=check,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ )
+ 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 result.returncode != 0:
print(
f"Command exited with code {result.returncode}: {command_str}",
file=sys.stderr,
)
+
return result
diff --git a/scripts/lib/config.py b/scripts/lib/config.py
index 8848c0435..0516b061c 100644
--- a/scripts/lib/config.py
+++ b/scripts/lib/config.py
@@ -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"
diff --git a/scripts/lib/env.py b/scripts/lib/env.py
index 3218821bd..ce3fa9e84 100644
--- a/scripts/lib/env.py
+++ b/scripts/lib/env.py
@@ -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()
diff --git a/scripts/lib/mac.py b/scripts/lib/mac.py
index 968e08d49..c8c91cb25 100644
--- a/scripts/lib/mac.py
+++ b/scripts/lib/mac.py
@@ -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}")
- 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,31 +180,22 @@ 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(
- [
- 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
+
+ # WARNING: contains password, never print this command
+ cmd_utils.run(
+ [
+ notarytool_path,
+ "store-credentials",
+ "notarytool-password",
+ "--apple-id",
+ user,
+ "--team-id",
+ team_id,
+ "--password",
+ password,
+ ]
+ )
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:
diff --git a/scripts/lib/windows.py b/scripts/lib/windows.py
index d3ef629bb..2dcab5526 100644
--- a/scripts/lib/windows.py
+++ b/scripts/lib/windows.py
@@ -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:
diff --git a/scripts/package.py b/scripts/package.py
index 91cc9d107..747b492da 100755
--- a/scripts/package.py
+++ b/scripts/package.py
@@ -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)
diff --git a/scripts/windows_daemon.py b/scripts/windows_daemon.py
index 3341029d4..2bea265fe 100644
--- a/scripts/windows_daemon.py
+++ b/scripts/windows_daemon.py
@@ -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")
diff --git a/src/cmd/synergy-core/CMakeLists.txt b/src/cmd/synergy-core/CMakeLists.txt
index b8e4d6315..5ab1c488d 100644
--- a/src/cmd/synergy-core/CMakeLists.txt
+++ b/src/cmd/synergy-core/CMakeLists.txt
@@ -14,21 +14,26 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+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()
diff --git a/src/cmd/synergyc/CMakeLists.txt b/src/cmd/synergyc/CMakeLists.txt
index c01609c9d..556c9f2ea 100644
--- a/src/cmd/synergyc/CMakeLists.txt
+++ b/src/cmd/synergyc/CMakeLists.txt
@@ -14,8 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+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()
diff --git a/src/cmd/synergyd/CMakeLists.txt b/src/cmd/synergyd/CMakeLists.txt
index 240cdfd07..dcd4e108a 100644
--- a/src/cmd/synergyd/CMakeLists.txt
+++ b/src/cmd/synergyd/CMakeLists.txt
@@ -14,14 +14,21 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+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})
diff --git a/src/cmd/synergys/CMakeLists.txt b/src/cmd/synergys/CMakeLists.txt
index 47b237407..ca084ef5f 100644
--- a/src/cmd/synergys/CMakeLists.txt
+++ b/src/cmd/synergys/CMakeLists.txt
@@ -14,8 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+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()
diff --git a/src/cmd/syntool/CMakeLists.txt b/src/cmd/syntool/CMakeLists.txt
index 5f841b7e9..f6b79299b 100644
--- a/src/cmd/syntool/CMakeLists.txt
+++ b/src/cmd/syntool/CMakeLists.txt
@@ -13,15 +13,21 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+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()
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 7a100ae31..5e7aab45a 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -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}")
diff --git a/src/gui/src/AboutDialog.cpp b/src/gui/src/AboutDialog.cpp
index 9e25fafa5..d79b9a78d 100644
--- a/src/gui/src/AboutDialog.cpp
+++ b/src/gui/src/AboutDialog.cpp
@@ -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");
diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp
index 5af04a173..1524322a3 100644
--- a/src/gui/src/MainWindow.cpp
+++ b/src/gui/src/MainWindow.cpp
@@ -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
}
diff --git a/src/gui/src/VersionChecker.cpp b/src/gui/src/VersionChecker.cpp
index 4933c56a6..e60304113 100644
--- a/src/gui/src/VersionChecker.cpp
+++ b/src/gui/src/VersionChecker.cpp
@@ -24,12 +24,8 @@
#include
#include
-#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)
- emit updateFound(newestVersion);
- }
+ 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");
-}
diff --git a/src/gui/src/VersionChecker.h b/src/gui/src/VersionChecker.h
index 2048d3d8a..cce00ad22 100644
--- a/src/gui/src/VersionChecker.h
+++ b/src/gui/src/VersionChecker.h
@@ -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;
};
diff --git a/src/test/integtests/CMakeLists.txt b/src/test/integtests/CMakeLists.txt
index 9ff8027a3..e85b93111 100644
--- a/src/test/integtests/CMakeLists.txt
+++ b/src/test/integtests/CMakeLists.txt
@@ -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/
diff --git a/src/test/unittests/CMakeLists.txt b/src/test/unittests/CMakeLists.txt
index 197537f9f..00fcf6b5e 100644
--- a/src/test/unittests/CMakeLists.txt
+++ b/src/test/unittests/CMakeLists.txt
@@ -14,6 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+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})
diff --git a/test b/test
deleted file mode 100644
index e69de29bb..000000000