From 5471587ceae189a924e30d9c22ddfb2765e447fd Mon Sep 17 00:00:00 2001 From: Frank Lichtenheld Date: Thu, 19 Mar 2026 16:24:11 +0100 Subject: [PATCH] windows-msi: Replace build.wsf and build-and-package.ps1 with CMake The JavaScript code had a limited implementation of a make system. So instead replace it by using an actual make system. File dependencies are downloaded in the configuration phase. Also replace version.m4 with a version.cmake since I could find no indication that it was used with m4 currently (maybe it was useful in the distant past, but not today). Github: Closes #1111 Signed-off-by: Frank Lichtenheld --- .github/workflows/build.yaml | 19 +- .gitignore | 2 +- release/version-and-tags.sh | 8 +- release/windows-installer-build.sh | 24 +- renovate.json | 4 +- windows-msi/CMakeLists.txt | 537 ++++++++++ windows-msi/README.rst | 126 +-- windows-msi/build-and-package-env-sample.ps1 | 1 - windows-msi/build-and-package.ps1 | 145 --- windows-msi/build.wsf | 359 ------- .../{bump-version.m4.ps1 => bump-version.ps1} | 116 +-- .../{bump-version.m4.sh => bump-version.sh} | 12 +- windows-msi/cleanup.ps1 | 75 -- windows-msi/concat_files.cmake | 13 + windows-msi/convert_to_crlf.cmake | 12 + .../doc/{doc => }/INSTALL-win32.txt.in | 0 ...EADME.txt.in => README-config-auto.txt.in} | 0 .../README.txt.in => README-config.txt.in} | 0 .../{log/README.txt.in => README-log.txt.in} | 0 windows-msi/script/Builder.js | 952 ------------------ windows-msi/script/M4Parser.js | 70 -- windows-msi/script/String.js | 61 -- windows-msi/sign-exe.bat | 14 - windows-msi/version.cmake | 56 ++ windows-msi/version.m4 | 56 -- 25 files changed, 758 insertions(+), 1904 deletions(-) create mode 100644 windows-msi/CMakeLists.txt delete mode 100644 windows-msi/build-and-package-env-sample.ps1 delete mode 100644 windows-msi/build-and-package.ps1 delete mode 100644 windows-msi/build.wsf rename windows-msi/{bump-version.m4.ps1 => bump-version.ps1} (58%) rename windows-msi/{bump-version.m4.sh => bump-version.sh} (60%) delete mode 100644 windows-msi/cleanup.ps1 create mode 100644 windows-msi/concat_files.cmake create mode 100644 windows-msi/convert_to_crlf.cmake rename windows-msi/doc/{doc => }/INSTALL-win32.txt.in (100%) rename windows-msi/doc/{config-auto/README.txt.in => README-config-auto.txt.in} (100%) rename windows-msi/doc/{config/README.txt.in => README-config.txt.in} (100%) rename windows-msi/doc/{log/README.txt.in => README-log.txt.in} (100%) delete mode 100644 windows-msi/script/Builder.js delete mode 100644 windows-msi/script/M4Parser.js delete mode 100644 windows-msi/script/String.js delete mode 100755 windows-msi/sign-exe.bat create mode 100644 windows-msi/version.cmake delete mode 100644 windows-msi/version.m4 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1d12a347d..185414e13 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -22,6 +22,7 @@ jobs: SigningKeyStore: ${{ secrets.GOOGLE_CLOUD_KMS_KEYRING }} SigningStoreKeyName: ${{ secrets.GOOGLE_CLOUD_KMS_KEY }} SigningCertificateFile: ${{ github.workspace }}/certificate.pem + ManifestTimestampRFC3161Url: http://timestamp.digicert.com VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/vcpkg_cache,readwrite name: 'openvpn-build' @@ -83,9 +84,9 @@ jobs: $BuildVersion = 10000 + [int]$env:GITHUB_RUN_NUMBER $NewProductVersion = "2.7.$BuildVersion" echo $NewProductCode $NewProductVersion - $version_m4 = (Get-Content version.m4) - $version_m4 -replace '^define\(\[PRODUCT_CODE\], \[\{(?.*)\}]\)', "define([PRODUCT_CODE], [{${NewProductCode}}])" ` - -replace '^define\(\[PRODUCT_VERSION\], \[(.*?)\]\)', "define([PRODUCT_VERSION], [${NewProductVersion}])" | Out-File -Encoding ASCII version.m4 + $version_cmake = (Get-Content version.cmake) + $version_cmake -replace '^set\(PRODUCT_CODE\s+"\{.*?\}"\)', "set(PRODUCT_CODE `"{${NewProductCode}}`")" ` + -replace '^set\(PRODUCT_VERSION\s+".*?"\)', "set(PRODUCT_VERSION `"${NewProductVersion}`")" | Out-File -Encoding ASCII version.cmake - name: Authenticate to Google Cloud id: 'auth' @@ -105,26 +106,28 @@ jobs: if: ${{ env.SigningKeyStore != '' }} run: | echo "${{ secrets.SIGNING_CERTIFICATE }}" >${{ github.workspace }}/certificate.pem - ./build-and-package.ps1 -sign -arch ${{ matrix.arch }} + cmake -B build -DOPENVPN_ARCH=${{ matrix.arch }} -DSIGN_BINARIES=ON + cmake --build build - name: Build working-directory: windows-msi if: ${{ env.SigningKeyStore == '' }} run: | - ./build-and-package.ps1 -arch ${{ matrix.arch }} + cmake -B build -DOPENVPN_ARCH=${{ matrix.arch }} + cmake --build build - name: Rename MSI run: | $commit = git -C ./src/openvpn rev-parse --short HEAD $dt = Get-Date -Format "yyyyMMddThhmm" - $orig_msi_name = (Get-Item .\windows-msi\image\*-${{ matrix.arch }}.msi).Name + $orig_msi_name = (Get-Item .\windows-msi\build\image\*-${{ matrix.arch }}.msi).Name $msi_name = $orig_msi_name -replace '^(OpenVPN-.*?)-(${{ matrix.arch }}\.msi)', "`$1+${dt}-${commit}-`$2" - mv .\windows-msi\image\$orig_msi_name .\windows-msi\image\$msi_name + mv .\windows-msi\build\image\$orig_msi_name .\windows-msi\build\image\$msi_name - name: Archive MSI uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: - path: ${{ github.workspace }}\windows-msi\image\*-${{ matrix.arch }}.msi + path: ${{ github.workspace }}\windows-msi\build\image\*-${{ matrix.arch }}.msi archive: false - name: Save vcpkg cache diff --git a/.gitignore b/.gitignore index d8112c243..93fbffaa2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ tmp *.cer *.crt release/vars.infrastructure -windows-msi/build-and-package-env.ps1 +windows-msi/build-env.bat diff --git a/release/version-and-tags.sh b/release/version-and-tags.sh index 5552fba28..292daa448 100755 --- a/release/version-and-tags.sh +++ b/release/version-and-tags.sh @@ -88,11 +88,11 @@ popd pushd "$MSI" # Update user-visible version -sed -E -i s/"define\(\[PACKAGE_VERSION\], \[(.+)\]\)"/"define\(\[PACKAGE_VERSION\], \[$BUILD_VERSION\]\)"/1 version.m4 -# if version.m4 was already updated, assume everything is fine as is +sed -E -i s/'set\(PACKAGE_VERSION "(.+)"\)'/"set(PACKAGE_VERSION \"$BUILD_VERSION\")"/1 version.cmake +# if version.cmake was already updated, assume everything is fine as is if ! git diff --exit-code; then - PRODUCT_VERSION_NEW="$PRODUCT_VERSION" ./bump-version.m4.sh - git add ./version.m4 + PRODUCT_VERSION_NEW="$PRODUCT_VERSION" ./bump-version.sh + git add ./version.cmake fi popd diff --git a/release/windows-installer-build.sh b/release/windows-installer-build.sh index 9eb6bc7af..9cf3f44ca 100755 --- a/release/windows-installer-build.sh +++ b/release/windows-installer-build.sh @@ -21,24 +21,26 @@ pushd "$TOP_DIR" . "$SCRIPT_DIR/vars" . "$SCRIPT_DIR/vars.infrastructure" -cat >build-and-package-env.ps1 <windows-msi/build-env.bat <>build-and-package-env.ps1 + echo "set VCPKG_BINARY_SOURCES=$VCPKG_CACHE" >>windows-msi/build-env.bat fi ssh $WINDOWS_MSI_BUILDHOST "gcloud auth login --quiet --cred-file=$WINDOWS_MSI_WORKDIR\..\clientLibraryConfig.json" set +x ACCESS_TOKEN=$(ssh $WINDOWS_MSI_BUILDHOST "cd $WINDOWS_MSI_WORKDIR/windows-msi && gcloud auth print-access-token") -echo "\$Env:SigningStorePass=\"$ACCESS_TOKEN\"" >>build-and-package-env.ps1 +echo "set SigningStorePass=$ACCESS_TOKEN" >>windows-msi/build-env.bat set -x -scp build-and-package-env.ps1 "$WINDOWS_MSI_BUILDHOST":"$WINDOWS_MSI_WORKDIR/windows-msi/" +scp windows-msi/build-env.bat "$WINDOWS_MSI_BUILDHOST":"$WINDOWS_MSI_WORKDIR/windows-msi/" ssh $WINDOWS_MSI_BUILDHOST git -C "$WINDOWS_MSI_WORKDIR" submodule update --init ssh $WINDOWS_MSI_BUILDHOST git -C "$WINDOWS_MSI_WORKDIR/src/openvpn" remote remove internal || true @@ -47,10 +49,10 @@ ssh $WINDOWS_MSI_BUILDHOST git -C "$WINDOWS_MSI_WORKDIR" remote remove internal ssh $WINDOWS_MSI_BUILDHOST git -C "$WINDOWS_MSI_WORKDIR" tag -d "OpenVPN-$BUILD_VERSION" || true ssh $WINDOWS_MSI_BUILDHOST git -C "$WINDOWS_MSI_WORKDIR" remote add -f --tags internal "$INTERNAL_GIT_REPO_BUILD_RO" ssh $WINDOWS_MSI_BUILDHOST git -C "$WINDOWS_MSI_WORKDIR" checkout --recurse-submodules -f "OpenVPN-$BUILD_VERSION" -ssh $WINDOWS_MSI_BUILDHOST "cd $WINDOWS_MSI_WORKDIR/windows-msi && \"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvarsall.bat\" x64 && powershell ./build-and-package.ps1 -sign" +ssh $WINDOWS_MSI_BUILDHOST "cd $WINDOWS_MSI_WORKDIR/windows-msi && \"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvarsall.bat\" x64 && build-env.bat && cmake -B build -DSIGN_BINARIES=ON && cmake --build build" mkdir -p "$OUTPUT/upload/" -scp "$WINDOWS_MSI_BUILDHOST":"$WINDOWS_MSI_WORKDIR/windows-msi/image/OpenVPN-${BUILD_VERSION}"-*.msi "$OUTPUT/upload/" +scp "$WINDOWS_MSI_BUILDHOST":"$WINDOWS_MSI_WORKDIR/windows-msi/build/image/OpenVPN-${BUILD_VERSION}"-*.msi "$OUTPUT/upload/" read -p "Upload MSIs to $SECONDARY_WEBSERVER?" # upload MSIs $SCRIPT_DIR/sign-and-push.sh diff --git a/renovate.json b/renovate.json index aa56008a9..95e5794c9 100644 --- a/renovate.json +++ b/renovate.json @@ -56,10 +56,10 @@ { "customType": "regex", "managerFilePatterns": [ - "/windows-msi/version.m4$/" + "/windows-msi/version\\.cmake$/" ], "matchStrings": [ - "datasource=(?.*?) depName=(?.*?)( versioning=(?.*?))?\\sdefine\\(\\[.*?\\],\\s*\\[(?.*?)\\]\\)\\s" + "datasource=(?.*?) depName=(?.*?)( versioning=(?.*?))?\\nset\\(\\w+\\s+\"(?.*?)\"\\)" ] } ] diff --git a/windows-msi/CMakeLists.txt b/windows-msi/CMakeLists.txt new file mode 100644 index 000000000..ae54e6e4f --- /dev/null +++ b/windows-msi/CMakeLists.txt @@ -0,0 +1,537 @@ +# openvpn-build — CMake-based Windows MSI build system +# +# Usage: +# cmake -B build -DOPENVPN_ARCH=all (or x86, amd64, arm64) +# cmake --build build +# +# Options: +# -DOPENVPN_ARCH=all|x86|amd64|arm64 Architectures to build (default: all) +# -DSIGN_BINARIES=ON Enable code signing +# -DCMAKE_PROGRAM_PATH=... Additional search paths for tools + +cmake_minimum_required(VERSION 3.20) +project(openvpn-msi NONE) + +# ============================================================ +# Options +# ============================================================ + +set(OPENVPN_ARCH "all" CACHE STRING "Architectures to build: all, x86, amd64, arm64") +set_property(CACHE OPENVPN_ARCH PROPERTY STRINGS all x86 amd64 arm64) + +option(SIGN_BINARIES "Sign binaries and MSI packages" OFF) + +# ============================================================ +# Paths +# ============================================================ + +set(MSI_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +set(ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..") +set(DEPS_DIR "${ROOT_DIR}/src") +set(BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/tmp") +set(SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/sources") +set(OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/image") + +# ============================================================ +# Version and dependency configuration +# ============================================================ + +include("${MSI_DIR}/version.cmake") + +message(STATUS "Building ${PRODUCT_NAME} ${PACKAGE_VERSION} (${PRODUCT_VERSION})") + +# ============================================================ +# Determine architectures to build +# ============================================================ + +set(_valid_archs all x86 amd64 arm64) +if(NOT OPENVPN_ARCH IN_LIST _valid_archs) + message(FATAL_ERROR "Invalid OPENVPN_ARCH '${OPENVPN_ARCH}'. Must be one of: ${_valid_archs}") +endif() + +if(OPENVPN_ARCH STREQUAL "all") + set(ARCHITECTURES x86 amd64 arm64) +else() + set(ARCHITECTURES ${OPENVPN_ARCH}) +endif() + +message(STATUS "Target architectures: ${ARCHITECTURES}") + +# ============================================================ +# Platform-specific mappings +# ============================================================ + +# OpenVPN GUI uses different directory names than the openvpn build arch +set(GUI_ARCH_x86 "x86") +set(GUI_ARCH_amd64 "x64") +set(GUI_ARCH_arm64 "arm64") + +# OpenVPN CMake preset names +set(OVPN_PRESET_x86 "win-x86-release") +set(OVPN_PRESET_amd64 "win-amd64-release") +set(OVPN_PRESET_arm64 "win-arm64-release") + +# WiX platform names +set(WIX_PLAT_x86 "x86") +set(WIX_PLAT_amd64 "x64") +set(WIX_PLAT_arm64 "arm64") + +# vcpkg triplet prefix (used in vcpkg_installed paths and vcredist) +set(VCPKG_TRIPLET_x86 "x86") +set(VCPKG_TRIPLET_amd64 "x64") +set(VCPKG_TRIPLET_arm64 "arm64") + +# OpenSSL platform suffix for DLL names +set(OPENSSL_PLAT_x86 "") +set(OPENSSL_PLAT_amd64 "-x64") +set(OPENSSL_PLAT_arm64 "-arm64") + +# TAP-Windows platform names +set(TAP_PLAT_x86 "i386") +set(TAP_PLAT_amd64 "amd64") +set(TAP_PLAT_arm64 "arm64") + +# Program Files folder for WiX +set(PROGRAM_FILES_DIR_x86 "ProgramFilesFolder") +set(PROGRAM_FILES_DIR_amd64 "ProgramFiles64Folder") +set(PROGRAM_FILES_DIR_arm64 "ProgramFiles64Folder") + +# ============================================================ +# Helper: build paths for each architecture +# ============================================================ + +macro(set_arch_paths arch) + set(_preset "${OVPN_PRESET_${arch}}") + set(_gui_arch "${GUI_ARCH_${arch}}") + set(_vcpkg_triplet "${VCPKG_TRIPLET_${arch}}") + + set(OPENVPN_BIN_${arch} "${DEPS_DIR}/openvpn/out/build/${_preset}/Release") + set(OPENVPN_MSICA_${arch} "${DEPS_DIR}/openvpn/out/build/${_preset}/src/openvpnmsica/Release") + set(OPENVPN_SERV_${arch} "${DEPS_DIR}/openvpn/out/build/${_preset}/src/openvpnserv/Release") + set(OPENVPN_TAPCTL_${arch} "${DEPS_DIR}/openvpn/out/build/${_preset}/src/tapctl/Release") + set(OPENVPN_DOC_${arch} "${DEPS_DIR}/openvpn/out/build/${_preset}/doc") + set(OPENVPN_GUI_DIR "${DEPS_DIR}/openvpn-gui") + set(OPENVPN_GUI_BIN_${arch} "${DEPS_DIR}/openvpn-gui/out/build/${_gui_arch}/Release") + set(OPENSSL_TOOLS_${arch} "${DEPS_DIR}/openvpn/out/build/${_preset}/vcpkg_installed/${_vcpkg_triplet}-windows-ovpn/tools/openssl") + set(OPENSSL_BIN_${arch} "${DEPS_DIR}/openvpn/out/build/${_preset}/vcpkg_installed/${_vcpkg_triplet}-windows-ovpn/bin") +endmacro() + +foreach(_arch ${ARCHITECTURES}) + set_arch_paths(${_arch}) +endforeach() + +# ============================================================ +# Find required tools +# ============================================================ + +# CMake (for building sub-projects) +find_program(CMAKE_EXE cmake HINTS "C:/Program Files/CMake/bin" REQUIRED) + +# WiX Toolset +if(DEFINED ENV{WIX}) + set(WIX_DIR "$ENV{WIX}") +else() + # Try common install locations + find_path(WIX_DIR NAMES "bin/candle.exe" + HINTS "C:/Program Files (x86)/WiX Toolset v3.14" + ) +endif() + +find_program(WIX_CANDLE candle HINTS "${WIX_DIR}/bin" REQUIRED) +find_program(WIX_LIGHT light HINTS "${WIX_DIR}/bin" REQUIRED) +message(STATUS "WiX candle: ${WIX_CANDLE}") +message(STATUS "WiX light: ${WIX_LIGHT}") + +# ============================================================ +# Create output directories +# ============================================================ + +file(MAKE_DIRECTORY "${BUILD_DIR}") +file(MAKE_DIRECTORY "${SOURCE_DIR}") +file(MAKE_DIRECTORY "${OUTPUT_DIR}") + +foreach(_arch ${ARCHITECTURES}) + file(MAKE_DIRECTORY "${BUILD_DIR}/${_arch}") +endforeach() + +# ============================================================ +# vcpkg bootstrap +# ============================================================ + +set(VCPKG_ROOT "${DEPS_DIR}/vcpkg") +set(VCPKG_OVERLAY_PORTS "${MSI_DIR}/vcpkg-ports") + +# Environment for sub-builds: override VCPKG_ROOT to use our vcpkg, not +# a system-installed one (e.g. the VS-bundled vcpkg). +set(VCPKG_ENV + ${CMAKE_COMMAND} -E env + "VCPKG_ROOT=${VCPKG_ROOT}" + "VCPKG_OVERLAY_PORTS=${VCPKG_OVERLAY_PORTS}" +) + +add_custom_command( + OUTPUT "${VCPKG_ROOT}/vcpkg.exe" + COMMAND cmd /c "${VCPKG_ROOT}/bootstrap-vcpkg.bat" + WORKING_DIRECTORY "${VCPKG_ROOT}" + COMMENT "Bootstrapping vcpkg" +) + +add_custom_target(vcpkg_bootstrap DEPENDS "${VCPKG_ROOT}/vcpkg.exe") + +# ============================================================ +# Build OpenVPN GUI (per architecture) +# ============================================================ + +foreach(_arch ${ARCHITECTURES}) + set(_gui_arch "${GUI_ARCH_${_arch}}") + + add_custom_target(openvpn_gui_${_arch} + COMMAND ${VCPKG_ENV} "${CMAKE_EXE}" --preset ${_gui_arch} + COMMAND ${VCPKG_ENV} "${CMAKE_EXE}" --build --preset ${_gui_arch}-release + WORKING_DIRECTORY "${DEPS_DIR}/openvpn-gui" + COMMENT "Building openvpn-gui (${_gui_arch})" + DEPENDS vcpkg_bootstrap + ) +endforeach() + +# ============================================================ +# Build OpenVPN core (per architecture) +# ============================================================ + +foreach(_arch ${ARCHITECTURES}) + set(_preset "${OVPN_PRESET_${_arch}}") + + add_custom_target(openvpn_core_${_arch} + COMMAND ${VCPKG_ENV} "${CMAKE_EXE}" --preset ${_preset} + COMMAND ${VCPKG_ENV} "${CMAKE_EXE}" --build --preset ${_preset} + WORKING_DIRECTORY "${DEPS_DIR}/openvpn" + COMMENT "Building openvpn (${_preset})" + DEPENDS vcpkg_bootstrap + ) +endforeach() + +# ============================================================ +# Sign OpenVPN binaries (optional, per architecture) +# ============================================================ + +if(SIGN_BINARIES) + foreach(_arch ${ARCHITECTURES}) + set(_sign_arch "${_arch}") + if(_arch STREQUAL "amd64") + set(_sign_arch_alt "x64") + else() + set(_sign_arch_alt "${_arch}") + endif() + + add_custom_target(sign_openvpn_${_arch} + COMMAND ${CMAKE_COMMAND} -E env + "SignArch=${_sign_arch}" + "SignArchAlt=${_sign_arch_alt}" + cmd /c "\"${MSI_DIR}/sign-openvpn.bat\"" + WORKING_DIRECTORY "${MSI_DIR}" + COMMENT "Signing openvpn binaries (${_arch})" + DEPENDS openvpn_core_${_arch} openvpn_gui_${_arch} + ) + endforeach() +endif() + +# ============================================================ +# Helper: download a file if it doesn't already exist +# ============================================================ + +function(download_file url dest) + if(NOT EXISTS "${dest}") + message(STATUS "Downloading ${url}") + file(DOWNLOAD "${url}" "${dest}" + STATUS _dl_status + ) + list(GET _dl_status 0 _dl_code) + if(NOT _dl_code EQUAL 0) + file(REMOVE "${dest}") + message(FATAL_ERROR "Failed to download ${url}: ${_dl_status}") + endif() + endif() +endfunction() + +# ============================================================ +# Download openvpnserv2.exe +# ============================================================ + +set(OPENVPNSERV2_FILE "${BUILD_DIR}/openvpnserv2.exe") +download_file( + "https://github.com/OpenVPN/openvpnserv2/releases/download/${OVPNSERV2_VERSION}/openvpnserv2-${OVPNSERV2_VERSION}-signed.exe" + "${OPENVPNSERV2_FILE}" +) + +# ============================================================ +# Download and extract Easy-RSA +# ============================================================ + +set(EASYRSA_ZIP "${SOURCE_DIR}/easyrsa-${EASYRSA_VERSION}.zip") +set(EASYRSA_DIR "${BUILD_DIR}/easyrsa-${EASYRSA_VERSION}") + +download_file( + "https://github.com/OpenVPN/easy-rsa/releases/download/v${EASYRSA_VERSION}/EasyRSA-${EASYRSA_VERSION}-win64.zip" + "${EASYRSA_ZIP}" +) + +if(NOT EXISTS "${EASYRSA_DIR}/easyrsa") + message(STATUS "Extracting Easy-RSA...") + file(ARCHIVE_EXTRACT INPUT "${EASYRSA_ZIP}" DESTINATION "${BUILD_DIR}") + # Easy-RSA zip extracts to EasyRSA-; rename to our convention + if(EXISTS "${BUILD_DIR}/EasyRSA-${EASYRSA_VERSION}" AND NOT EXISTS "${EASYRSA_DIR}") + file(RENAME "${BUILD_DIR}/EasyRSA-${EASYRSA_VERSION}" "${EASYRSA_DIR}") + endif() +endif() + +# Generate EasyRSA-Start.bat +file(WRITE "${EASYRSA_DIR}/EasyRSA-Start.bat" +"@echo OFF\r +rem Automatically set PATH to openssl.exe\r +FOR /F \"tokens=2*\" %%a IN ('REG QUERY \"HKEY_LOCAL_MACHINE\\SOFTWARE\\OpenVPN\" /v bin_dir') DO set \"PATH=%PATH%;%%b\"\r +bin\\sh.exe bin\\easyrsa-shell-init.sh %*\r +") + +# ============================================================ +# Download TAP-Windows6 and ovpn-dco (per architecture) +# ============================================================ + +foreach(_arch ${ARCHITECTURES}) + set(_tap_plat "${TAP_PLAT_${_arch}}") + + download_file( + "https://github.com/OpenVPN/tap-windows6/releases/download/${PRODUCT_TAP_WIN_VERSION}/tap-windows-${PRODUCT_TAP_WIN_VERSION}-${PRODUCT_TAP_WIN_INSTALLER_VERSION}-${_tap_plat}.msm" + "${BUILD_DIR}/${_arch}/tap-windows6.msm" + ) + + download_file( + "https://github.com/OpenVPN/ovpn-dco-win/releases/download/${PRODUCT_OVPN_DCO_VERSION}/ovpn-dco-${_arch}.msm" + "${BUILD_DIR}/${_arch}/ovpn-dco.msm" + ) +endforeach() + +# ============================================================ +# Preprocess documentation templates (@VAR@ -> value) +# ============================================================ + +# Preprocess documentation templates (@VAR@ -> value, CRLF line endings) +set(_doc_templates INSTALL-win32 README-config README-config-auto README-log) +foreach(_doc ${_doc_templates}) + configure_file( + "${MSI_DIR}/doc/${_doc}.txt.in" + "${BUILD_DIR}/${_doc}.txt" + @ONLY + NEWLINE_STYLE CRLF + ) +endforeach() + +# ============================================================ +# Generate license.txt and sample configs (per architecture) +# ============================================================ + +foreach(_arch ${ARCHITECTURES}) + set(_arch_build "${BUILD_DIR}/${_arch}") + + # license.txt: concatenation of COPYING + COPYRIGHT.GPL + bundled-licenses.txt + set(_license_parts + "${DEPS_DIR}/openvpn/COPYING" + "${DEPS_DIR}/openvpn/COPYRIGHT.GPL" + "${MSI_DIR}/doc/bundled-licenses.txt" + ) + + add_custom_command( + OUTPUT "${_arch_build}/license.txt" + COMMAND ${CMAKE_COMMAND} + "-DOUTPUT=${_arch_build}/license.txt" + "-DINPUTS=${_license_parts}" + -P "${MSI_DIR}/concat_files.cmake" + DEPENDS ${_license_parts} + COMMENT "Generating license.txt (${_arch})" + ) + + # Sample config files (copy with CRLF line endings) + foreach(_config client server) + add_custom_command( + OUTPUT "${_arch_build}/${_config}.ovpn" + COMMAND ${CMAKE_COMMAND} + -DINPUT="${DEPS_DIR}/openvpn/sample/sample-config-files/${_config}.conf" + -DOUTPUT="${_arch_build}/${_config}.ovpn" + -P "${MSI_DIR}/convert_to_crlf.cmake" + DEPENDS "${DEPS_DIR}/openvpn/sample/sample-config-files/${_config}.conf" + COMMENT "Copying ${_config}.ovpn (${_arch}) with CRLF line endings" + ) + endforeach() +endforeach() + +# ============================================================ +# WiX compilation and linking (per architecture) +# ============================================================ + +foreach(_arch ${ARCHITECTURES}) + set(_arch_build "${BUILD_DIR}/${_arch}") + set(_wix_plat "${WIX_PLAT_${_arch}}") + set(_openssl_plat "${OPENSSL_PLAT_${_arch}}") + set(_vcpkg_triplet "${VCPKG_TRIPLET_${_arch}}") + + # WiX compiler define flags + set(_wix_defines + -dPRODUCT_PUBLISHER="${PRODUCT_PUBLISHER}" + -dPRODUCT_NAME="${PRODUCT_NAME}" + -dPRODUCT_VERSION="${PRODUCT_VERSION}" + -dPACKAGE_VERSION="${PACKAGE_VERSION}" + -dPRODUCT_TAP_WIN_NAME="${PRODUCT_TAP_WIN_NAME}" + -dPRODUCT_TAP_WIN_COMPONENT_ID="${PRODUCT_TAP_WIN_COMPONENT_ID}" + -dPRODUCT_PLATFORM="${_arch}" + -dPRODUCT_CODE="${PRODUCT_CODE}" + -dUPGRADE_CODE="${UPGRADE_CODE_${_arch}}" + -dCONFIG_EXTENSION="${CONFIG_EXTENSION}" + -dPROGRAM_FILES_DIR="${PROGRAM_FILES_DIR_${_arch}}" + -dOPENSSL_PLAT="${_openssl_plat}" + ) + + # Common WiX compiler flags + set(_wix_candle_flags + -nologo + -ext WixNetFxExtension + -ext WixUtilExtension + -arch "${_wix_plat}" + ${_wix_defines} + ) + + # Compile gui.wxs + add_custom_command( + OUTPUT "${_arch_build}/gui.wixobj" + COMMAND "${WIX_CANDLE}" ${_wix_candle_flags} + -out "${_arch_build}/gui.wixobj" + "${MSI_DIR}/gui.wxs" + DEPENDS "${MSI_DIR}/gui.wxs" "${MSI_DIR}/version.cmake" + COMMENT "WiX compiling gui.wxs (${_arch})" + ) + + # Compile msi.wxs + add_custom_command( + OUTPUT "${_arch_build}/msi.wixobj" + COMMAND "${WIX_CANDLE}" ${_wix_candle_flags} + -out "${_arch_build}/msi.wixobj" + "${MSI_DIR}/msi.wxs" + DEPENDS "${MSI_DIR}/msi.wxs" "${MSI_DIR}/version.cmake" + COMMENT "WiX compiling msi.wxs (${_arch})" + ) + + # WiX linker flags + set(_wix_light_flags + -nologo + -dcl:high + -spdb + -ext WixNetFxExtension + -ext WixUtilExtension + -b "${MSI_DIR}" + -b "build=${BUILD_DIR}" + -b "openvpndoc=${OPENVPN_DOC_${_arch}}" + -b "openvpnbin=${OPENVPN_BIN_${_arch}}" + -b "openvpnmsica=${OPENVPN_MSICA_${_arch}}" + -b "openvpnserv=${OPENVPN_SERV_${_arch}}" + -b "openvpntapctl=${OPENVPN_TAPCTL_${_arch}}" + -b "openvpngui=${OPENVPN_GUI_DIR}" + -b "openvpnguibin=${OPENVPN_GUI_BIN_${_arch}}" + -b "openssltools=${OPENSSL_TOOLS_${_arch}}" + -b "opensslbin=${OPENSSL_BIN_${_arch}}" + -b "easyrsa=${EASYRSA_DIR}" + -b "openvpnserv2=${BUILD_DIR}" + ) + + # MSI output file + set(_msi_file "${OUTPUT_DIR}/${PRODUCT_NAME}-${PACKAGE_VERSION}-${_arch}.msi") + + # Per-architecture MSI target (links WiX objects into MSI) + add_custom_target(msi_${_arch} + COMMAND "${WIX_LIGHT}" ${_wix_light_flags} + -out "${_msi_file}" + "${_arch_build}/gui.wixobj" + "${_arch_build}/msi.wixobj" + DEPENDS + "${_arch_build}/gui.wixobj" + "${_arch_build}/msi.wixobj" + # Generated files + "${_arch_build}/license.txt" + "${_arch_build}/client.ovpn" + "${_arch_build}/server.ovpn" + # Downloaded files + "${_arch_build}/tap-windows6.msm" + "${_arch_build}/ovpn-dco.msm" + "${OPENVPNSERV2_FILE}" + COMMENT "WiX linking ${PRODUCT_NAME}-${PACKAGE_VERSION}-${_arch}.msi" + ) + + # Ensure binaries are built (and optionally signed) before linking + add_dependencies(msi_${_arch} openvpn_core_${_arch} openvpn_gui_${_arch}) + if(SIGN_BINARIES) + add_dependencies(msi_${_arch} sign_openvpn_${_arch}) + endif() +endforeach() + +# ============================================================ +# Sign MSI packages (optional) +# ============================================================ + +if(SIGN_BINARIES) + set(_all_msi_targets "") + foreach(_arch ${ARCHITECTURES}) + list(APPEND _all_msi_targets msi_${_arch}) + endforeach() + + add_custom_target(sign_msi + COMMAND cmd /c "${MSI_DIR}/sign-msi.bat" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + COMMENT "Signing MSI packages" + DEPENDS ${_all_msi_targets} + ) +endif() + +# ============================================================ +# Top-level targets +# ============================================================ + +# "msi" target builds all architecture MSIs +set(_all_msi_targets "") +foreach(_arch ${ARCHITECTURES}) + list(APPEND _all_msi_targets msi_${_arch}) +endforeach() + +add_custom_target(msi DEPENDS ${_all_msi_targets}) + +# Default target +if(SIGN_BINARIES) + add_custom_target(package ALL DEPENDS sign_msi) +else() + add_custom_target(package ALL DEPENDS msi) +endif() + +# ============================================================ +# Clean target +# ============================================================ + +set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES + "${BUILD_DIR}" + "${SOURCE_DIR}" + "${OUTPUT_DIR}" +) + +# ============================================================ +# Status summary +# ============================================================ + +message(STATUS "") +message(STATUS "=== OpenVPN MSI Build Configuration ===") +message(STATUS " Product: ${PRODUCT_NAME} ${PACKAGE_VERSION}") +message(STATUS " Version: ${PRODUCT_VERSION}") +message(STATUS " Architectures: ${ARCHITECTURES}") +message(STATUS " Sign binaries: ${SIGN_BINARIES}") +message(STATUS " TAP-Windows: ${PRODUCT_TAP_WIN_VERSION}") +message(STATUS " ovpn-dco: ${PRODUCT_OVPN_DCO_VERSION}") +message(STATUS " Easy-RSA: ${EASYRSA_VERSION}") +message(STATUS " openvpnserv2: ${OVPNSERV2_VERSION}") +message(STATUS " WiX candle: ${WIX_CANDLE}") +message(STATUS " WiX light: ${WIX_LIGHT}") +message(STATUS " Output: ${OUTPUT_DIR}") +message(STATUS "=======================================") diff --git a/windows-msi/README.rst b/windows-msi/README.rst index 9d6ef77a9..87af430f7 100644 --- a/windows-msi/README.rst +++ b/windows-msi/README.rst @@ -7,58 +7,56 @@ This folder contains scripts and binaries required to build and package OpenVPN Requirements for building MSI packages -------------------------------------- -1. `WiX Toolset`_ - tested with 3.14.1 -2. ``unzip.exe`` - tested with UnZip 6.00 of 20 April 2009, by Info-ZIP -3. GNU ``tar.exe`` - tested with 1.30 -4. ``gzip.exe`` - tested with 1.9 -5. ``bzip2.exe`` - tested with 1.0.6, 6-Sept-2010 +These are the tools used directly by this buildsystem. For +the dependencies of building OpenVPN itself see the next +section. -Note: ``unzip.exe``, ``tar.exe``, ``gzip.exe``, and ``bzip2.exe`` must be in -``%PATH%``. +This buildsystem only works on Windows machines, it should +work on Windows 10 and newer and Windows Server 2019 and newer. + +1. CMake 3.20 or later +2. `WiX Toolset`_ - tested with 3.14.1 Requirements for building OpenVPN and its dependencies ------------------------------------------------------ -Building and packaging OpenVPN on Windows is a fairly complex process with lots -of dependencies. If you're starting from scratch it is recommended to use the -"msibuilder" VM in `openvpn-vagrant `_. - -If using Vagrant and Virtualbox is not an option you should be able to run the -Vagrant provisioning scripts with suitable parameters on a fresh Windows 10-based system, -though only Windows Server 2019 is tested. - -In either case you will end up with a directory layout such as this: - -- openvpn-build - -- vcpkg (openssl etc.) +These tools are required for building OpenVPN and OpenVPN-GUI -- openvpn-gui - -- openvpn +1. Visual Studio build tools +2. Git +3. Powershell Core (pwsh) +4. Python 3 + docutils package +All other dependencies are built via vcpkg. Signing ------- -If you want to do code signing, you need to add your code-signing -PFX certificate into the certificate store:: - - Import-PfxCertificate -FilePath .\mycert.pfx -CertstoreLocation Cert:\Currentuser\My -Password (ConvertTo-SecureString -String "mypass" -Force -AsPlainText) +If you want to do code signing, set the following environment variables (shown +here for ``cmd.exe``) before building with ``-DSIGN_BINARIES=ON``:: -This command will print out the certificate thumbprint which you'll need to tell to -the build scripts. To do this, create a config file, ``build-and-package-env.ps1``, -next to ``build-and-package.ps1``:: + set JsignJar=path\to\jsign.jar + set SigningStoreType=GOOGLECLOUD + set SigningKeyStore=your-keyring + set SigningStoreKeyName=your-key + set SigningCertificateFile=path\to\certificate.pem + set SigningStorePass=your-access-token + set ManifestTimestampRFC3161Url=http://timestamp.digicert.com - $Env:ManifestCertificateThumbprint = "cert thumbprint" +Or for PowerShell:: -This is not required unless you call ``build-and-package.ps1`` with the ``-sign`` -option. + $env:JsignJar = "path\to\jsign.jar" + $env:SigningStoreType = "GOOGLECLOUD" + $env:SigningKeyStore = "your-keyring" + $env:SigningStoreKeyName = "your-key" + $env:SigningCertificateFile = "path\to\certificate.pem" + $env:SigningStorePass = "your-access-token" + $env:ManifestTimestampRFC3161Url = "http://timestamp.digicert.com" Building and packaging ---------------------- -First adjust ``version.m4``. It is important to increment +First adjust ``version.cmake``. It is important to increment ``PRODUCT_VERSION`` *and* ``PRODUCT_CODE`` on each release. MSI upgrading logic relies on this. You can use ``bump-version.ps1`` script for this purpose. @@ -66,60 +64,26 @@ script for this purpose. To build and package:: cd openvpn-build\windows-msi - .\build-and-package.ps1 - -If everything was set up correctly you should see three MSI packages in -``image`` subfolder, each signed and containing signed binaries. - -Cleaning up ------------ - -You can use the ``cleanup.ps1`` script to clean up temporary build files and build artefacts. -This makes it easier to create clean builds. - -build.wsf ---------- - -Note: The following explains details of the packaging process wrapped by the -``build-and-package.ps1`` script described above. + cmake -B build + cmake --build build -The ``build.wsf`` is a simple Makefile type building tool used to generate MSI -packages. It expects OpenVPN and its dependencies to be -built and in the directory layout described above. It was developed to avoid -Microsoft Visual Studio or GNU Make requirements. Refer to ``build.wsf`` for -exact usage:: +To build a single architecture or with signing:: - C:\openvpn-build\windows-msi>cscript build.wsf /? - Microsoft (R) Windows Script Host Version 5.812 - Copyright (C) Microsoft Corporation. All rights reserved. + cmake -B build -DOPENVPN_ARCH=amd64 -DSIGN_BINARIES=ON + cmake --build build - Packages OpenVPN for Windows. - Usage: build.wsf [] [/a] +If everything was set up correctly you should see MSI packages in the +``build\image`` subfolder. - Options: +Individual targets can be built directly:: - : Command to execute (default: "all") - a : Builds all targets even if output is newer than input + cmake --build build --target msi_amd64 - Commands: - all Builds MSI packages - msi Builds MSI packages - clean Cleans intermediate and output files - -Digital signing ---------------- - -The ``build.wsf`` tool does not support digital signing of MSI files -(yet). The ``sign-openvpn.bat`` and ``sign-msi.bat`` scripts handle that part. +Cleaning up +----------- -When signing MSI packages, set a signature description (``/d`` flag with -``signtool.exe`` utility). The ``msiexec.exe`` saves the MSI package under some -random name and launches an elevated process to install it. When the signature -on the MSI package contains no description, Windows displays the MSI filename -instead on the UAC prompt. Now MSI having a random filename, the UAC prompt -gets quite confusing. Therefore, we strongly encourage you to set a description -in the MSI signature accurately describing the package content. +Remove the ``build`` directory to clean all build artefacts, or use:: -Signing of ``tapctl.exe`` is mandatory as it requires elevation of privileges. + cmake --build build --target clean .. _`WiX Toolset`: http://wixtoolset.org/ diff --git a/windows-msi/build-and-package-env-sample.ps1 b/windows-msi/build-and-package-env-sample.ps1 deleted file mode 100644 index 4e4e3d63e..000000000 --- a/windows-msi/build-and-package-env-sample.ps1 +++ /dev/null @@ -1 +0,0 @@ -$Env:ManifestCertificateThumbprint = "thumbprint" diff --git a/windows-msi/build-and-package.ps1 b/windows-msi/build-and-package.ps1 deleted file mode 100644 index 0ee7fd1fb..000000000 --- a/windows-msi/build-and-package.ps1 +++ /dev/null @@ -1,145 +0,0 @@ -param( - # Must be top directory of openvpn-build checkout - [string] $topdir = "${PSScriptRoot}/..", - [string] $arch = "all", - [switch] $sign - ) - -### Preparations -if(-not($topdir)) { - Write-Host "Usage: build-and-package.ps1 [-topdir ] [-arch ] [-sign]" - exit 1 -} - -$allowed_arch = "all", "x86", "amd64", "arm64" -if (-Not($allowed_arch.Contains($arch))) -{ - Write-Host "-arch must be:" $allowed_arch - exit 1 -} - -# Convert relative path to absolute to prevent breakages below -$basedir = (Resolve-Path -Path $topdir) - -$basedir_exists = Test-Path $basedir - -if ($basedir_exists -ne $True) { - Write-Host "ERROR: directory ${basedir} does not exist!" - exit 1 -} - -# sane defaults -$Env:VCPKG_ROOT = "${basedir}\src\vcpkg" -$Env:VCPKG_OVERLAY_PORTS = "${basedir}\windows-msi\vcpkg-ports" -$Env:CMAKE = "C:\\Program Files\\CMake\\bin\\cmake.exe" -$Env:ManifestTimestampRFC3161Url = "http://timestamp.digicert.com" - -if ((Test-Path "${PSScriptRoot}/build-and-package-env.ps1") -ne $True) { - Write-Host "WARNING: configuration file (build-and-package-env.ps1) is missing" -} else { - . "${PSScriptRoot}/build-and-package-env.ps1" -} - -if ($sign -And -not($Env:SigningCertificateFile)) { - Write-Host "ERROR: signing requested but Env:SigningCertificateFile not set" - exit 1 -} - -# At the end of the build return to the directory we started from -$cwd = Get-Location - -Set-Location "$Env:VCPKG_ROOT" -& .\bootstrap-vcpkg.bat - -### Build OpenVPN-GUI -Set-Location "${basedir}\src\openvpn-gui" - -$gui_arch = @() -switch ($arch) -{ - 'all' - { - $gui_arch = "x64", "arm64", "x86" - } - 'x86' - { - $gui_arch += "x86" - } - 'amd64' - { - $gui_arch += "x64" - } - 'arm64' - { - $gui_arch += "arm64" - } -} - -$gui_arch | ForEach-Object { - $platform = $_ - Write-Host "Building openvpn-gui ${platform}" - & "$Env:CMAKE" --preset ${platform} - & "$Env:CMAKE" --build --preset ${platform}-release -} - -### Build OpenVPN -$ovpn_arch = @("amd64", "arm64", "x86") -if ($arch -ne "all") { - $ovpn_arch = @($arch) -} - -$ovpn_arch | ForEach-Object { - $platform = $_ - Write-Host "Building openvpn ${platform}" - Set-Location "${basedir}\src\openvpn" - # VCPKG_HOST_TRIPLET required to use host tools like pkgconf - & "$Env:CMAKE" --preset "win-${platform}-release" - & "$Env:CMAKE" --build --preset "win-${platform}-release" - - ### Sign binaries - if ($sign) { - Set-Location "${basedir}\windows-msi" - $Env:SignArch = $platform - if ($platform -ne "amd64") { - $Env:SignArchAlt = $platform - } else { - $Env:SignArchAlt = "x64" - } - - & .\sign-openvpn.bat - } else { - Write-Host "Skip signing binaries" - } -} - -### Build MSI -Set-Location "${basedir}\windows-msi" - -switch ($arch) -{ - 'all' - { - & cscript.exe build.wsf msi - } - 'amd64' - { - & cscript.exe build.wsf msi-amd64 - } - 'x86' - { - & cscript.exe build.wsf msi-x86 - } - 'arm64' - { - & cscript.exe build.wsf msi-arm64 - } -} - -### Sign MSI -if ($sign) { - & .\sign-msi.bat -} else { - Write-Host "Skip signing MSI" -} - -Set-Location $cwd diff --git a/windows-msi/build.wsf b/windows-msi/build.wsf deleted file mode 100644 index d4e32e71a..000000000 --- a/windows-msi/build.wsf +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - Packages OpenVPN for Windows. - - - -Commands: -all Builds MSI packages -msi Builds MSI packages -clean Cleans intermediate and output files - - - - - - - - diff --git a/windows-msi/bump-version.m4.ps1 b/windows-msi/bump-version.ps1 similarity index 58% rename from windows-msi/bump-version.m4.ps1 rename to windows-msi/bump-version.ps1 index 36ad24748..e60c0ab2d 100644 --- a/windows-msi/bump-version.m4.ps1 +++ b/windows-msi/bump-version.ps1 @@ -1,58 +1,58 @@ -# Change PRODUCT_CODE and PRODUCT_VERSION in version.m4. Primarily intended to -# be used within a CI system, not for release builds. - -<# -.Description -Get the current value of PRODUCT_VERSION in version.m4. This is needed so that we can increment it. Example: 2.5.028. -#> -Function Get-ProductVersion { - ForEach ($line in (Get-Content version.m4)) { - if ($line -match '^define\(\[PRODUCT_VERSION\], \[(.*?)\]\)') { - $ProductVersion = $Matches[1] - break - } - } - - $ProductVersion -} - -<# -.Description -Increment the last digit in PRODUCTION_VERSION. Only used internally. -#> -Function Increment-Counter { - param ( - [string]$Counter - ) - - # String off leading zeroes, if any - $Counter = $Counter -replace '^0+', '' - $IntCounter = [int]$Counter - $IntCounter += 1 - $Counter = [string]$IntCounter - - # Add back leading zeroes, if any - $Counter = $Counter.PadLeft(3, '0') - $Counter -} - -<# -.Description -Create an incremented PRODUCT_VERSION (e.g. 2.5.029). -#> -Function New-ProductVersion { - $OldProductVersion = (Get-ProductVersion) -match '(\d\.\d?)\.(\d\d\d?)' - $Version = $Matches[1] - $Counter = $Matches[2] - $Counter = Increment-Counter -Counter $Counter - "${Version}.${Counter}" -} - -# Get updated values for PRODUCT_VERSION and PRODUCT_CODE -$NewProductVersion = New-ProductVersion -$NewProductCode = (New-Guid).ToString().ToUpper() - -# Replace old values with the newly generated ones, overwriting version.m4 -$version_m4 = (Get-Content version.m4) -$version_m4 -replace '^define\(\[PRODUCT_CODE\], \[\{(?.*)\}]\)', "define([PRODUCT_CODE], [{${NewProductCode}}])" ` - -replace '^define\(\[PRODUCT_VERSION\], \[(.*?)\]\)', "define([PRODUCT_VERSION], [${NewProductVersion}])" | Out-File -Encoding ASCII version.m4 +# Change PRODUCT_CODE and PRODUCT_VERSION in version.cmake. Primarily intended +# to be used within a CI system, not for release builds. + +<# +.Description +Get the current value of PRODUCT_VERSION in version.cmake. This is needed so that we can increment it. Example: 2.5.028. +#> +Function Get-ProductVersion { + ForEach ($line in (Get-Content version.cmake)) { + if ($line -match '^set\(PRODUCT_VERSION\s+"(.*?)"\)') { + $ProductVersion = $Matches[1] + break + } + } + + $ProductVersion +} + +<# +.Description +Increment the last digit in PRODUCTION_VERSION. Only used internally. +#> +Function Increment-Counter { + param ( + [string]$Counter + ) + + # String off leading zeroes, if any + $Counter = $Counter -replace '^0+', '' + $IntCounter = [int]$Counter + $IntCounter += 1 + $Counter = [string]$IntCounter + + # Add back leading zeroes, if any + $Counter = $Counter.PadLeft(3, '0') + $Counter +} + +<# +.Description +Create an incremented PRODUCT_VERSION (e.g. 2.5.029). +#> +Function New-ProductVersion { + $OldProductVersion = (Get-ProductVersion) -match '(\d\.\d?)\.(\d\d\d?)' + $Version = $Matches[1] + $Counter = $Matches[2] + $Counter = Increment-Counter -Counter $Counter + "${Version}.${Counter}" +} + +# Get updated values for PRODUCT_VERSION and PRODUCT_CODE +$NewProductVersion = New-ProductVersion +$NewProductCode = (New-Guid).ToString().ToUpper() + +# Replace old values with the newly generated ones, overwriting version.cmake +$version_cmake = (Get-Content version.cmake) +$version_cmake -replace '^set\(PRODUCT_CODE\s+"\{.*?\}"\)', "set(PRODUCT_CODE `"{${NewProductCode}}`")" ` + -replace '^set\(PRODUCT_VERSION\s+".*?"\)', "set(PRODUCT_VERSION `"${NewProductVersion}`")" | Out-File -Encoding ASCII version.cmake diff --git a/windows-msi/bump-version.m4.sh b/windows-msi/bump-version.sh similarity index 60% rename from windows-msi/bump-version.m4.sh rename to windows-msi/bump-version.sh index 6dea98ba7..0e774483c 100755 --- a/windows-msi/bump-version.m4.sh +++ b/windows-msi/bump-version.sh @@ -1,11 +1,11 @@ #!/bin/sh # -# Change PRODUCT_CODE and PRODUCT_VERSION in version.m4 +# Change PRODUCT_CODE and PRODUCT_VERSION in version.cmake # Get current product version -PRODUCT_FULL_VERSION=`grep -E '^define\(\[PRODUCT_VERSION' version.m4|cut -d " " -f 2|tr -d '[])'` +PRODUCT_FULL_VERSION=$(grep -E '^set\(PRODUCT_VERSION ' version.cmake | sed 's/set(PRODUCT_VERSION "\(.*\)")/\1/') # Get current product code -PRODUCT_CODE=`grep -E 'define\(\[PRODUCT_CODE' version.m4|cut -d " " -f 2|tr -d '[{}])'` +PRODUCT_CODE=$(grep -E '^set\(PRODUCT_CODE ' version.cmake | sed 's/set(PRODUCT_CODE "{\(.*\)}")/\1/') # Increment product version unless specified by environment (e.g. release build) if [ -z "${PRODUCT_VERSION_NEW:-}" ]; then @@ -16,10 +16,10 @@ if [ -z "${PRODUCT_VERSION_NEW:-}" ]; then fi # Create new product code -PRODUCT_CODE_NEW=`uuidgen |tr '[:lower:]' '[:upper:]'` +PRODUCT_CODE_NEW=$(uuidgen |tr '[:lower:]' '[:upper:]') # Replace product code -sed -i s/"$PRODUCT_CODE"/"$PRODUCT_CODE_NEW"/1 version.m4 +sed -i s/"$PRODUCT_CODE"/"$PRODUCT_CODE_NEW"/1 version.cmake # Replace product version -sed -i s/"$PRODUCT_FULL_VERSION"/"$PRODUCT_VERSION_NEW"/g version.m4 +sed -i s/"$PRODUCT_FULL_VERSION"/"$PRODUCT_VERSION_NEW"/g version.cmake diff --git a/windows-msi/cleanup.ps1 b/windows-msi/cleanup.ps1 deleted file mode 100644 index 81e96d350..000000000 --- a/windows-msi/cleanup.ps1 +++ /dev/null @@ -1,75 +0,0 @@ -# Script that cleans up temporary build files and artefacts to ensure clean builds. - -param([string] $basedir) - -function Remove-IfExists { - param ( - [string]$path - ) - - if (Test-Path $path) { - Remove-Item -Recurse -Force $path - } -} - -$cwd = Get-Location - -# Check parameters -if(-not($basedir)) { - Write-Host "Usage: cleanup.ps1 -basedir " - exit 1 -} - -$basedir = (Resolve-Path -Path $basedir) -$basedir_exists = Test-Path $basedir - -# Ensure that base directory exists -if ($basedir_exists -ne $True) { - Write-Host "ERROR: directory ${basedir} does not exist!" - exit 1 -} - -# Ensure that the base directory contains the subdirectories it should contain -$subdirs = "openvpn", "openvpn-gui", "openvpn-build", "vcpkg" -$all_dirs_present = $True - -ForEach ($dir in $subdirs) { - if (-Not(Test-Path "${basedir}\${dir}")) { - Write-Host "ERROR: did not find directory `"${dir}`"" - $all_dirs_present = $False - } -} - -if ($all_dirs_present -ne $True) { - Write-Host "One or more directories this script expected to clean up were not found. Please check that `$basedir is set correctly." - exit 1 -} - -# Clean up openvpn -Set-Location "${basedir}\openvpn" -Remove-IfExists -Path Win32-Output -Remove-IfExists -Path ARM64-Output -Remove-IfExists -Path x64-Output -Remove-IfExists -Path src\openvpn\vcpkg_installed - -# Clean up openvpn-gui -Set-Location "${basedir}\openvpn-gui" -Remove-IfExists -Path out - -# Clean up openvpn-build -Set-Location "${basedir}\openvpn-build\windows-msi" -Remove-IfExists -Path image -Remove-IfExists -Path sources -Remove-IfExists -Path tmp - -# Clean up vcpkg -Set-Location "${basedir}\vcpkg" -if (Test-Path vcpkg.exe) { - & .\vcpkg.exe integrate remove -} -Set-Location $basedir -Remove-IfExists -Path vcpkg -git clone https://github.com/microsoft/vcpkg.git -Remove-IfExists -Path "${HOME}\AppData\Local\vcpkg" - -Set-Location $cwd \ No newline at end of file diff --git a/windows-msi/concat_files.cmake b/windows-msi/concat_files.cmake new file mode 100644 index 000000000..3ac17aed7 --- /dev/null +++ b/windows-msi/concat_files.cmake @@ -0,0 +1,13 @@ +# concat_files.cmake — Concatenate multiple files into one +# +# Usage: cmake -DOUTPUT=out.txt "-DINPUTS=a.txt;b.txt;c.txt" -P concat_files.cmake + +if(NOT DEFINED OUTPUT OR NOT DEFINED INPUTS) + message(FATAL_ERROR "OUTPUT and INPUTS must be defined") +endif() + +file(WRITE "${OUTPUT}" "") +foreach(_file ${INPUTS}) + file(READ "${_file}" _content) + file(APPEND "${OUTPUT}" "${_content}") +endforeach() diff --git a/windows-msi/convert_to_crlf.cmake b/windows-msi/convert_to_crlf.cmake new file mode 100644 index 000000000..0debacee4 --- /dev/null +++ b/windows-msi/convert_to_crlf.cmake @@ -0,0 +1,12 @@ +# convert_to_crlf.cmake — Copy a file converting line endings to CRLF +# +# Usage: cmake -DINPUT=in.txt -DOUTPUT=out.txt -P convert_to_crlf.cmake + +if(NOT DEFINED INPUT OR NOT DEFINED OUTPUT) + message(FATAL_ERROR "INPUT and OUTPUT must be defined") +endif() + +file(READ "${INPUT}" _contents) +string(REPLACE "\r\n" "\n" _contents "${_contents}") +string(REPLACE "\n" "\r\n" _contents "${_contents}") +file(WRITE "${OUTPUT}" "${_contents}") diff --git a/windows-msi/doc/doc/INSTALL-win32.txt.in b/windows-msi/doc/INSTALL-win32.txt.in similarity index 100% rename from windows-msi/doc/doc/INSTALL-win32.txt.in rename to windows-msi/doc/INSTALL-win32.txt.in diff --git a/windows-msi/doc/config-auto/README.txt.in b/windows-msi/doc/README-config-auto.txt.in similarity index 100% rename from windows-msi/doc/config-auto/README.txt.in rename to windows-msi/doc/README-config-auto.txt.in diff --git a/windows-msi/doc/config/README.txt.in b/windows-msi/doc/README-config.txt.in similarity index 100% rename from windows-msi/doc/config/README.txt.in rename to windows-msi/doc/README-config.txt.in diff --git a/windows-msi/doc/log/README.txt.in b/windows-msi/doc/README-log.txt.in similarity index 100% rename from windows-msi/doc/log/README.txt.in rename to windows-msi/doc/README-log.txt.in diff --git a/windows-msi/script/Builder.js b/windows-msi/script/Builder.js deleted file mode 100644 index bc55376e2..000000000 --- a/windows-msi/script/Builder.js +++ /dev/null @@ -1,952 +0,0 @@ -/* - * openvpn-build — OpenVPN packaging - * - * Copyright (C) 2018-2020 Simon Rozman - * Copyright (C) 2020-2020 Lev Stipakov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - * - * This program 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -/*@cc_on @*/ -/*@if (! @__BUILDER_JS__) @*/ -/*@set @__BUILDER_JS__ = true @*/ - - -/** - * Creates a Builder object - * - * @returns Builder object - */ -function Builder() -{ - this.wsh = WScript.CreateObject("WScript.Shell"); - this.fso = WScript.CreateObject("Scripting.FileSystemObject"); - this.env = this.wsh.Environment("Process"); - - // Temporary folder. - this.tempPath = this.env("TEMP"); - - // Detect the WiX Toolset path. - this.wixPath = this.env("WIX"); - if (!this.wixPath || this.wixPath.length == 0) { - // No WiX, no fun. - throw new Error("The WIX environment is missing or empty. Please, make sure the WiX Toolset is installed correctly."); - } - - this.wixCandleFlags = ["-nologo"]; - this.wixLightFlags = ["-nologo", "-dcl:high"]; - - this.unzipFlags = []; - this.tarFlags = []; - this.gunzipFlags = ["-d"]; - this.bunzip2Flags = ["-d"]; - - // Get the codepage Windows is using for stdin/stdout/stderr. - switch (parseInt(this.wsh.RegRead("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage\\OEMCP"), 10)) { - case 437: this.cpOEMMime = "cp437" ; break; - case 850: this.cpOEMMime = "ibm850" ; break; - case 852: this.cpOEMMime = "ibm852" ; break; - case 1250: this.cpOEMMime = "windows-1250"; break; - case 1251: this.cpOEMMime = "windows-1251"; break; - case 65001: this.cpOEMMime = "utf-8" ; break; - default : this.cpOEMMime = null; - } - - this.force = false; - this.rules = []; - this.ruleIndex = {}; - - return this; -} - - -/** - * Adds a rule to the builder - * - * @param {any} rule - */ -Builder.prototype.pushRule = function (rule) -{ - this.rules.push(rule); - for (var i in rule.outNames) { - var outFileAbsolute = this.fso.GetAbsolutePathName(rule.outNames[i]).toLowerCase(); - if (outFileAbsolute in this.ruleIndex) - throw new Error("Rule for building " + rule.outNames[i] + " is already in the ruleset."); - - this.ruleIndex[outFileAbsolute] = rule; - } -} - - -/** - * Builds given file - * - * @param outName File to build - */ -Builder.prototype.build = function (outName) -{ - var builder = this; - var stack = []; - - function build(outName) - { - var outFileAbsolute = builder.fso.GetAbsolutePathName(outName).toLowerCase(); - stack.push(outFileAbsolute); - try { - // Check stack for cycles. - for (var i = 0, n = stack.length - 1; i < n; i++) - if (outFileAbsolute == stack[i]) - throw new Error("Cyclic dependency:\n " + stack.join("\n ")); - - if (outFileAbsolute in builder.ruleIndex) { - // We found the rule to build builder file. - var rule = builder.ruleIndex[outFileAbsolute]; - - // Have we already build this rule in this session? - if (rule.timeBuilt != 0) - return builder.fso.GetFile(outName).DateLastModified; - - // Build dependencies. - var ts = 0; - for (var i in rule.inNames) { - var tsInput = build(rule.inNames[i]); - if (ts < tsInput) - ts = tsInput; - } - - // Is building required? - if (!builder.force) { - var tsOutput = rule.buildTime(builder); - if (tsOutput != 0 && ts <= tsOutput) { - rule.timeBuilt = tsOutput; - return builder.fso.GetFile(outName).DateLastModified; - } - } - - // Make sure all output folders exist. - for (var i in rule.outNames) - builder.makeDir(builder.fso.GetParentFolderName(rule.outNames[i])) - - try { - // Build! - WScript.Echo("BUILD: " + outName); - rule.build(builder); - } catch (err) { - // Clean the rule output should anything go wrong in build. - // We don't want half finished zombie output files with fresh - // timestamp lying around. - rule.clean(builder); - throw err; - } - - return builder.fso.GetFile(outName).DateLastModified; - } - - if (builder.fso.FileExists(outName)) { - // No rule found to build the file, but the file already exists. - return builder.fso.GetFile(outName).DateLastModified; - } - - throw new Error("Don't know how to build \"" + outName + "\"."); - } finally { - stack.pop(); - } - } - - build(outName); -} - - -/** - * Cleans intermmediate and output files - */ -Builder.prototype.clean = function () { - for (var i in this.rules) - this.rules[i].clean(this); -} - - -/** - * Creates folder creating all parent folders if required - * - * @param path Path to folder to create - * - * @returns true if the folder was created; false if the folder already existed. - */ -Builder.prototype.makeDir = function (path) -{ - var fso = this.fso; - - function makeDir(path) - { - if (path == "") return false; - try { - // Create folder. - fso.CreateFolder(path); - return true; - } catch (err) { - switch (err.number) { - case -2146828230: // "File already exists" - return false; - case -2146828212: // "Path not found" - // Create the parent folder. - makeDir(fso.GetParentFolderName(path)); - try { - // Create folder. - fso.CreateFolder(path); - return true; - } catch (err) { - throw new Error(err.number, "Error creating \"" + path + "\" folder: " + err.message); - } - default: - throw new Error(err.number, "Error creating \"" + path + "\" folder: " + err.message); - } - } - } - - return makeDir(this.fso.GetAbsolutePathName(path)); -} - - -/** - * Deletes folder including all files and subfolders - * - * @param path Path to folder to delete - * - * @returns true if the folder was deleted; false if the folder did not exist. - */ -Builder.prototype.removeDir = function (path) -{ - try { - // Delete folder. - this.fso.DeleteFolder(path); - return true; - } catch (err) { - switch (err.number) { - case -2146828212: // "Path not found" - return false; - default: - throw new Error(err.number, "Error deleting \"" + path + "\" folder: " + err.message); - } - } -} - - -/** - * Copies file - * - * @param inName Source file name - * @param outName Destination file name - */ -Builder.prototype.copyFile = function (inName, outName) -{ - try { - this.fso.CopyFile(inName, outName); - } catch (err) { - throw new Error(err.number, "Error copying \"" + inName + "\" to \"" + outName + "\": " + err.message); - } -} - - -/** - * Deletes a file - * - * @param fileName Name of the file to delete - * - * @returns true if the file was deleted; false otherwise. - */ -Builder.prototype.removeFile = function (fileName) -{ - try { - this.fso.DeleteFile(fileName, true); - return true; - } catch (err) { - switch (err.number) { - case -2146828235: // "File not found" (pre Windows 10) - case -2146828212: // "Path not found" (Windows 10) - return false; - default: - throw new Error(err.number, "Error deleting \"" + fileName + "\": " + err.message); - } - } -} - - -/** - * Executes the command synchronously - * - * @param cmd Command to execute - * - * @returns Command exit code - */ -Builder.prototype.exec = function (cmd) -{ - if (!Builder.prototype.__exec) { - // Initialize static data. - Builder.prototype.__exec = { - "re_cr": new RegExp("\\r", "g") - }; - } - - var result = -1; - var outputPath = BuildPath(this.tempPath, this.fso.GetTempName()); - try { - // Execute command and wait for it to finish. Redirect stdout and strerr to a temporary file. - WScript.Echo("RUN: " + cmd); - result = this.wsh.Run("\"" + _CMD(this.env("ComSpec")) + "\" /S /C \"" + cmd + " > \"" + _CMD(outputPath) + "\" 2>&1\"", 0, true); - - var dat = WScript.CreateObject("ADODB.Stream"); - var output = ""; - dat.Open(); - try { - // Load its output. - dat.Type = adTypeText; - if (this.cpOEMMime) - dat.Charset = this.cpOEMMime; - dat.LoadFromFile(outputPath); - output = (new String(dat.ReadText(adReadAll))).replace(Builder.prototype.__exec.re_cr, ""); - } finally { - dat.Close(); - } - - // Replay all output on our console. - WScript.Echo(output); - } finally { - this.removeFile(outputPath); - } - - return result; -} - - -/** - * Creates a generic build rule - * - * @param outNames Array of output files - * @param inNames Array of input files - * - * @returns Build rule - */ -function BuildRule(outNames, inNames) -{ - this.outNames = outNames; - this.inNames = inNames; - this.timeBuilt = 0; - - return this; -} - - -/** - * Blank build rule - * - * @param builder The builder object - */ -BuildRule.prototype.build = function (builder) -{ - this.timeBuilt = (new Date()).getVarDate(); -} - - -/** - * Returns the time the rule was built - * - * @param builder The builder object - * - * @returns Oldest timestamp of the output files if all exist; 0 otherwise - */ -BuildRule.prototype.buildTime = function (builder) -{ - var ts = (new Date()).getVarDate(); - for (var i in this.outNames) { - if (builder.fso.FileExists(this.outNames[i])) { - var tsOutput = builder.fso.GetFile(this.outNames[i]).DateLastModified; - if (tsOutput < ts) - ts = tsOutput; - } else - return 0; - } - return ts; -} - - -/** - * Removes all output files - * - * @param builder The builder object - */ -BuildRule.prototype.clean = function (builder) -{ - for (var i in this.outNames) - builder.removeFile(this.outNames[i]); -} - - -/** - * Creates a file copy build rule - * - * @param outNames Output file names - * @param inName Input file name - * @param depNames Additional dependencies - * - * @returns Build rule - */ -function CopyFileBuildRule(outNames, inName, depNames) -{ - BuildRule.call(this, outNames, [inName].concat(depNames)); - - return this; -} - - -/** - * Builds the rule - * - * @param builder The builder object - */ -CopyFileBuildRule.prototype.build = function (builder) -{ - for (var i in this.outNames) { - WScript.Echo("COPY: " + this.inNames[0] + " >> " + this.outNames[i]); - builder.copyFile(this.inNames[0], this.outNames[i]); - } - - BuildRule.prototype.build.call(this, builder); -} - - -/** - * Returns the time the rule was built - * - * @param builder The builder object - * - * @returns Oldest timestamp of the output files if all exist; 0 otherwise - */ -CopyFileBuildRule.prototype.buildTime = BuildRule.prototype.buildTime; - - -/** - * Removes all output files - * - * @param builder The builder object - */ -CopyFileBuildRule.prototype.clean = BuildRule.prototype.clean; - - -/** - * Creates a text conversion build rule - * - * @param outName Output .txt file name - * @param outCharset Charset to use on output (e.g. "utf-8", "windows-1251" etc.) - * @param outLineSep Line separator on output (e.g. adCRLF, adLF) - * @param inNames Input .txt file names. Files are concatenated together. - * @param inCharset Charset to expect on input (e.g. "utf-8", "windows-1251" etc.) - * @param inLineSep Line separator on input (e.g. adCRLF, adLF) - * @param depNames Additional dependencies - * - * @returns Build rule - */ -function ConvertTextBuildRule(outName, outCharset, outLineSep, inNames, inCharset, inLineSep, depNames) -{ - BuildRule.call(this, [outName], inNames.concat(depNames)); - - this.outCharset = outCharset; - this.outLineSep = outLineSep; - this.txtNames = inNames; - this.inCharset = inCharset; - this.inLineSep = inLineSep; - - return this; -} - - -/** - * Builds the rule - * - * @param builder The builder object - */ -ConvertTextBuildRule.prototype.build = function (builder) -{ - WScript.Echo("CONVERT: " + this.txtNames.join("+") + " >> " + this.outNames[0]); - var datOut = WScript.CreateObject("ADODB.Stream"); - datOut.Open(); - try { - datOut.Type = adTypeText; - datOut.Charset = this.outCharset; - datOut.LineSeparator = this.outLineSep; - - for (var i in this.txtNames) { - var datIn = WScript.CreateObject("ADODB.Stream"); - datIn.Open(); - try { - // Load input file. - datIn.Type = adTypeText; - datIn.Charset = this.inCharset; - datIn.LineSeparator = this.inLineSep; - datIn.LoadFromFile(this.txtNames[i]); - - while (!datIn.EOS) - datOut.WriteText(this.transform(builder, datIn.ReadText(adReadLine)), adWriteLine); - } finally { - datIn.Close(); - } - } - - // Persist stream to file. - datOut.SaveToFile(this.outNames[0], adSaveCreateOverWrite); - } finally { - datOut.Close(); - } - - BuildRule.prototype.build.call(this, builder); -} - - -/** - * Returns the time the rule was built - * - * @param builder The builder object - * - * @returns Oldest timestamp of the output files if all exist; 0 otherwise - */ -ConvertTextBuildRule.prototype.buildTime = BuildRule.prototype.buildTime; - - -/** - * Removes all output files - * - * @param builder The builder object - */ -ConvertTextBuildRule.prototype.clean = BuildRule.prototype.clean; - - -/** - * Transforms one line of text - * - * @param builder The builder object - * @param str Line of text to transform - * - * @returns Transformed text - */ -ConvertTextBuildRule.prototype.transform = function (builder, str) -{ - return str; -} - - -/** - * Creates a text preprocessing build rule - * - * @param outName Output .txt file name - * @param outCharset Charset to use on output (e.g. "utf-8", "windows-1251" etc.) - * @param outLineSep Line separator on output (e.g. adCRLF, adLF) - * @param inNames Input .txt.in file names. Files are concatenated together. - * @param inCharset Charset to expect on input (e.g. "utf-8", "windows-1251" etc.) - * @param inLineSep Line separator on input (e.g. adCRLF, adLF) - * @param ver M4 parser - * @param depNames Additional dependencies - * - * @returns Build rule - */ -function PreprocessBuildRule(outName, outCharset, outLineSep, inNames, inCharset, inLineSep, ver, depNames) -{ - ConvertTextBuildRule.call(this, outName, outCharset, outLineSep, inNames, inCharset, inLineSep, depNames); - - this.ver = ver; - - return this; -} - - -/** - * Builds the rule - * - * @param builder The builder object - */ -PreprocessBuildRule.prototype.build = ConvertTextBuildRule.prototype.build; - - -/** - * Returns the time the rule was built - * - * @param builder The builder object - * - * @returns Oldest timestamp of the output files if all exist; 0 otherwise - */ -PreprocessBuildRule.prototype.buildTime = BuildRule.prototype.buildTime; - - -/** - * Removes all output files - * - * @param builder The builder object - */ -PreprocessBuildRule.prototype.clean = BuildRule.prototype.clean; - - -/** - * Transforms one line of text - * - * @param builder The builder object - * @param str Line of text to transform - * - * @returns Transformed text - */ -PreprocessBuildRule.prototype.transform = function (builder, str) -{ - if (!PreprocessBuildRule.prototype.__transform) { - // Initialize static data. - PreprocessBuildRule.prototype.__transform = { - "re_param": new RegExp("@(\\w+)@", "g") - }; - } - - var dict = this.ver.define; - - str = str.replace(PreprocessBuildRule.prototype.__transform.re_param, function ($0, $1) { - return $1 in dict ? dict[$1] : "@" + $1 + "@"; - }); - - return str; -} - - -/** - * Creates a WiX compiler build rule - * - * @param outName Output .wixobj file name - * @param inName Input .wxs file name - * @param depNames Additional dependencies - * @param flags Additional WiX Candle flags - * - * @returns Build rule - */ -function WiXCompileBuildRule(outName, inName, depNames, flags) -{ - BuildRule.call(this, [outName], [inName].concat(depNames)); - - this.flags = flags; - - return this; -} - - -/** - * Builds the rule - * - * @param builder The builder object - */ -WiXCompileBuildRule.prototype.build = function (builder) -{ - // Compile .wxs file. - if (builder.exec( - "\"" + _CMD(BuildPath(builder.wixPath, "bin", "candle.exe")) + "\" " + - builder.wixCandleFlags.join(" ") + (this.flags && this.flags.length ? " " + this.flags.join(" ") : "") + - " -out \"" + _CMD(this.outNames[0]) + "\" \"" + _CMD(this.inNames[0]) + "\"") != 0) - throw new Error("WiX compiler returned non-zero."); - - BuildRule.prototype.build.call(this, builder); -} - - -/** - * Returns the time the rule was built - * - * @param builder The builder object - * - * @returns Oldest timestamp of the output files if all exist; 0 otherwise - */ -WiXCompileBuildRule.prototype.buildTime = BuildRule.prototype.buildTime; - - -/** - * Removes all output files - * - * @param builder The builder object - */ -WiXCompileBuildRule.prototype.clean = BuildRule.prototype.clean; - - -/** - * Creates a WiX linker build rule - * - * @param outName Output .msi file name - * @param inNames Input .wixobj file names - * @param depNames Additional dependencies - * @param flags Additional WiX Light flags - * - * @returns Build rule - */ -function WiXLinkBuildRule(outName, inNames, depNames, flags) -{ - BuildRule.call(this, [outName], inNames.concat(depNames)); - - this.flags = flags; - this.objNames = inNames; - - return this; -} - - -/** - * Builds the rule - * - * @param builder The builder object - */ -WiXLinkBuildRule.prototype.build = function (builder) -{ - // Link .wixobj files. - if (builder.exec( - "\"" + _CMD(BuildPath(builder.wixPath, "bin", "light.exe")) + "\" " + - builder.wixLightFlags.join(" ") + (this.flags && this.flags.length ? " " + this.flags.join(" ") : "") + - " -out \"" + this.outNames[0] + "\" \"" + this.objNames.join("\" \"") + "\"") != 0) - throw new Error("WiX linker returned non-zero."); - - BuildRule.prototype.build.call(this, builder); -} - - -/** - * Returns the time the rule was built - * - * @param builder The builder object - * - * @returns Oldest timestamp of the output files if all exist; 0 otherwise - */ -WiXLinkBuildRule.prototype.buildTime = BuildRule.prototype.buildTime; - - -/** - * Removes all output files - * - * @param builder The builder object - */ -WiXLinkBuildRule.prototype.clean = BuildRule.prototype.clean; - - -/** - * Creates a download build rule - * - * @param outName Output file name - * @param url URL to retrieve data from - * @param depNames Additional dependencies - * - * @returns Build rule - */ -function DownloadBuildRule(outName, url, depNames) -{ - BuildRule.call(this, [outName], depNames); - - this.url = url; - - return this; -} - - -/** - * Builds the rule - * - * @param builder The builder object - */ -DownloadBuildRule.prototype.build = function (builder) -{ - WScript.Echo("GET: " + this.url + " >> " + this.outNames[0]); - var req = new ActiveXObject("WinHttp.WinHttpRequest.5.1"); - req.Open("GET", this.url, false); - req.Send(); - if (req.Status == "200") { - var datOut = new ActiveXObject("ADODB.Stream"); - datOut.Open(); - try { - datOut.Type = adTypeBinary; - datOut.Write(req.ResponseBody); - datOut.SaveToFile(this.outNames[0], adSaveCreateOverWrite); - } finally { - datOut.Close(); - } - } else - throw new Error("GET " + this.url + " failed with status " + req.Status + " " + req.StatusText + "."); - - BuildRule.prototype.build.call(this, builder); -} - - -/** - * Returns the time the rule was built - * - * @param builder The builder object - * - * @returns Oldest timestamp of the output files if all exist; 0 otherwise - */ -DownloadBuildRule.prototype.buildTime = BuildRule.prototype.buildTime; - - -/** - * Removes all output files - * - * @param builder The builder object - */ -DownloadBuildRule.prototype.clean = BuildRule.prototype.clean; - - -/** - * Creates a generic archive extraction build rule - * - * @param outNames Array of output files. You may list only the important ones, however, the cleaning will take care of only those then. - * @param outDir Output directory - * @param tsName Timestamp file name. Serves as a dummy output file to mark the extraction date. This is the actual output file used to detect if the rule needs rebuilding. - * @param inName Input archive file name - * @param depNames Additional dependencies - * - * @returns Build rule - */ -function ExtractBuildRule(outNames, outDir, tsName, inName, depNames) -{ - BuildRule.call(this, [tsName].concat(outNames), [inName].concat(depNames)); - - this.outDir = outDir; - - return this; -} - - -/** - * Builds the rule - * - * @param builder The builder object - */ -ExtractBuildRule.prototype.build = function (builder) -{ - if (this.inNames[0].slice(-4).toLowerCase() == ".zip") { - // Unzip file. - if (builder.exec( - "unzip.exe " + - builder.unzipFlags.join(" ") + - " -o" + - " -d \"" + _CMD(this.outDir) + "\"" + - " \"" + _CMD(this.inNames[0]) + "\"") != 0) - throw new Error("Unzip returned non-zero."); - } else if (this.inNames[0].slice(-7).toLowerCase() == ".tar.gz" || this.inNames[0].slice(-4).toLowerCase() == ".tgz") { - // Gunzip then untar file. - if (builder.exec( - "gzip.exe " + - builder.gunzipFlags.join(" ") + - " -c" + - " \"" + _CMD(this.inNames[0]) + "\"" + - " | tar.exe " + - builder.tarFlags.join(" ") + - " -xf - -C \"" + _CMD(this.outDir) + "\"") != 0) - throw new Error("gunzip|tar returned non-zero."); - } else if (this.inNames[0].slice(-8).toLowerCase() == ".tar.bz2" || this.inNames[0].slice(-5).toLowerCase()() == ".tbz2") { - // Bunzip2 then untar file. - if (builder.exec( - "bzip2.exe " + - builder.bunzip2Flags.join(" ") + - " -c" + - " \"" + _CMD(this.inNames[0]) + "\"" + - " | tar.exe " + - builder.tarFlags.join(" ") + - " -xf - -C \"" + _CMD(this.outDir) + "\"") != 0) - throw new Error("bunzip2|tar returned non-zero."); - } - - // Create the timestamp file. - var datOut = WScript.CreateObject("ADODB.Stream"); - datOut.Open(); - try { - datOut.Type = adTypeText; - datOut.Charset = "utf-8"; - datOut.LineSeparator = adCRLF; - datOut.WriteText(this.inNames[0] + " extracted at " + (new Date()).toLocaleString(), adWriteLine); - datOut.SaveToFile(this.outNames[0], adSaveCreateOverWrite); - } finally { - datOut.Close(); - } - - BuildRule.prototype.build.call(this, builder); -} - - -/** - * Returns the time the rule was built - * - * @param builder The builder object - * - * @returns Oldest timestamp of the output files if all exist; 0 otherwise - */ -ExtractBuildRule.prototype.buildTime = function (builder) -{ - // Timestamp and all the output files must exist. - // However, it is the timestamp file that always provides the output time. - for (var i in this.outNames) - if (!builder.fso.FileExists(this.outNames[i])) return 0; - return builder.fso.GetFile(this.outNames[0]).DateLastModified; -} - - -/** - * Removes all output files - * - * @param builder The builder object - */ -ExtractBuildRule.prototype.clean = BuildRule.prototype.clean; - - -/** - * Create file build rule - * - * @param outName Output file name - * @param inLines Lines to append - * - * @returns Build rule - */ -function CreateFileBuildRule(outName, inLines, depNames) -{ - BuildRule.call(this, [outName], depNames); - this.inLines = inLines; - return this; -} - -/** - * Builds the rule - * - * @param builder The builder object - */ -CreateFileBuildRule.prototype.build = function (builder) -{ - f = builder.fso.OpenTextFile(this.outNames[0], ForWriting, true); - for (var i in this.inLines) - f.WriteLine(this.inLines[i]) - f.Close(); - BuildRule.prototype.build.call(this, builder); -} - - -/** - * Returns the time the rule was built - * - * @param builder The builder object - * - * @returns Oldest timestamp of the output files if all exist; 0 otherwise - */ -CreateFileBuildRule.prototype.buildTime = BuildRule.prototype.buildTime; - - -/** - * Removes all output files - * - * @param builder The builder object - */ -CreateFileBuildRule.prototype.clean = BuildRule.prototype.clean; - -/*@end @*/ diff --git a/windows-msi/script/M4Parser.js b/windows-msi/script/M4Parser.js deleted file mode 100644 index 081547b0d..000000000 --- a/windows-msi/script/M4Parser.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * openvpn-build — OpenVPN packaging - * - * Copyright (C) 2018-2020 Simon Rozman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - * - * This program 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -/*@cc_on @*/ -/*@if (! @__M4PARSER_JS__) @*/ -/*@set @__M4PARSER_JS__ = true @*/ - - -/** - * Creates a parser object - * - * @returns M4 parser object - */ -function M4Parser() -{ - // Initialization - this.define = new Array(); - - return this; -} - - -/** - * Parses a M4 file - * - * @param fileName M4 file name to parse - */ -M4Parser.prototype.parse = function (fileName) -{ - if (!M4Parser.prototype.__parse) { - // Initialize static data. - M4Parser.prototype.__parse = { - "re_define": new RegExp("^\\s*define\\s*\\(\\s*\\[?(\\w+)\\]?\\s*,\\s*\\[([^\\]]*)\\]\\s*\\)\\s*$") - }; - } - - // Open M4 file. - var dat = WScript.CreateObject("Scripting.FileSystemObject").OpenTextFile(fileName, ForReading); - try { - // (Re)initialize - this.define = new Array(); - - // Read M4 file line by line and parse it. - while (!dat.AtEndOfStream) { - var line = new String(dat.ReadLine()); - var m = line.match(M4Parser.prototype.__parse.re_define); - if (m) - this.define[m[1]] = m[2]; - } - } finally { - dat.Close(); - } -} - -/*@end @*/ diff --git a/windows-msi/script/String.js b/windows-msi/script/String.js deleted file mode 100644 index fd1a40df9..000000000 --- a/windows-msi/script/String.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * openvpn-build — OpenVPN packaging - * - * Copyright (C) 2018-2020 Simon Rozman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - * - * This program 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -/*@cc_on @*/ -/*@if (! @__STRING_JS__) @*/ -/*@set @__STRING_JS__ = true @*/ - -var _CMD_stat = null; -function _CMD(str) -{ - if (!_CMD_stat) { - _CMD_stat = { - "re_quot": new RegExp("\"", "g") - }; - } - - if (str == null) return null; - switch (typeof(str)) { - case "string": break; - case "undefined": return null; - default: try { str = str.toString(); } catch (err) { return null; } - } - - return str.replace(_CMD_stat.re_quot, "\"\""); -} - - -var BuildPath_stat = null; -function BuildPath(str) -{ - if (!BuildPath_stat) { - BuildPath_stat = { - "fso": WScript.CreateObject("Scripting.FileSystemObject") - }; - } - - for (var i = 1, n_arg = arguments.length; i < n_arg; i++) - str = BuildPath_stat.fso.BuildPath(str, arguments[i]); - - return str; -} - - - - -/*@end @*/ diff --git a/windows-msi/sign-exe.bat b/windows-msi/sign-exe.bat deleted file mode 100755 index 13c2bab9f..000000000 --- a/windows-msi/sign-exe.bat +++ /dev/null @@ -1,14 +0,0 @@ -@echo off - -rem This script digitally signs EXE installer. -rem -rem Set `%ManifestCertificateThumbprint%` to SHA-1 thumbprint of your code signing -rem certificate. This thumbprint is used only to locate the certificate in user -rem certificate store. The signing and timestamping will use SHA-256 hashes. -rem -rem Set `%ManifestTimestampRFC3161Url%` to URL of your code signing cerificate provider's -rem RFC3161-compliant web service. -rem -rem Run this script after `cscript build.wsf exe`. - -signtool.exe sign /sha1 "%ManifestCertificateThumbprint%" /fd sha256 /tr "%ManifestTimestampRFC3161Url%" /td sha256 image\*.exe diff --git a/windows-msi/version.cmake b/windows-msi/version.cmake new file mode 100644 index 000000000..cb30666b0 --- /dev/null +++ b/windows-msi/version.cmake @@ -0,0 +1,56 @@ +# ============================================================ +# Downloadables +# ============================================================ + +# TAP-Windows binaries +# renovate: datasource=github-releases depName=OpenVPN/tap-windows6 +set(PRODUCT_TAP_WIN_VERSION "9.27.0") +# Note: Not handled by renovate +set(PRODUCT_TAP_WIN_INSTALLER_VERSION "I0") +set(PRODUCT_TAP_WIN_COMPONENT_ID "tap0901") +set(PRODUCT_TAP_WIN_NAME "TAP-Windows") + +# ovpn-dco binaries +# renovate: datasource=github-releases depName=OpenVPN/ovpn-dco-win +set(PRODUCT_OVPN_DCO_VERSION "2.8.2") + +# OpenVPNServ2.exe binary +# renovate: datasource=github-releases depName=OpenVPN/openvpnserv2 versioning=loose +set(OVPNSERV2_VERSION "2.0.1.0") + +# Easy-RSA binaries: +# URL to .zip file containing "easy-rsa-[EASYRSA_VERSION]" folder with Easy-RSA. +# The OpenSSL binaries, which come with Easy-RSA, are not used by Openvpn-build. +# The only binaries which Openvpn-build uses from Easy-RSA, are the *nix style +# (32bit only) binaries for Windows, from easy-rsa/distro/windows/bin. +# Further details: easy-rsa/distro/windows/Licensing/mksh-Win32.txt +# renovate: datasource=github-releases depName=OpenVPN/easy-rsa +set(EASYRSA_VERSION "3.2.5") + +# ============================================================ +# MSI Provisioning +# ============================================================ + +# Define the product name and publisher. +set(PRODUCT_NAME "OpenVPN") +set(PRODUCT_PUBLISHER "OpenVPN, Inc.") + +# The package version as displayed by UI and used in filenames (no spaces, please). +set(PACKAGE_VERSION "2.8_git-I001") + +# The MSI product version in the form of n[.n[.n]] (numbers only). +# The third field is 100*openvpn bugfix release + MSI build number. +# So for the 2nd MSI build for OpenVPN 2.6.3 use 2.6.302 +set(PRODUCT_VERSION "2.8.0") + +# The MSI product code MUST change on each product release. +set(PRODUCT_CODE "{AFB9E34B-0126-474A-AF75-A7C69AB91905}") + +# The MSI upgrade codes MUST persist for all versions of the same product line. +# Please use own upgrade codes when deploying a non-official OpenVPN release. +set(UPGRADE_CODE_x86 "{1195A47B-A37A-4055-9D34-B7A691F7E97B}") +set(UPGRADE_CODE_amd64 "{461BDF86-D389-4471-BF36-99806B64C127}") +set(UPGRADE_CODE_arm64 "{1E8C4DDC-9E93-4AE2-9495-DF86821EAA3A}") + +# OpenVPN configuration file extension (e.g. conf, ovpn...) +set(CONFIG_EXTENSION "ovpn") diff --git a/windows-msi/version.m4 b/windows-msi/version.m4 deleted file mode 100644 index 806411107..000000000 --- a/windows-msi/version.m4 +++ /dev/null @@ -1,56 +0,0 @@ -dnl ============================================================ -dnl Downloadables -dnl ============================================================ - -dnl TAP-Windows binaries -dnl renovate: datasource=github-releases depName=OpenVPN/tap-windows6 -define([PRODUCT_TAP_WIN_VERSION], [9.27.0]) -dnl Note: Not handled by renovate -define([PRODUCT_TAP_WIN_INSTALLER_VERSION], [I0]) -define([PRODUCT_TAP_WIN_COMPONENT_ID], [tap0901]) -define([PRODUCT_TAP_WIN_NAME], [TAP-Windows]) - -dnl ovpn-dco binaries -dnl renovate: datasource=github-releases depName=OpenVPN/ovpn-dco-win -define([PRODUCT_OVPN_DCO_VERSION], [2.8.2]) - -dnl OpenVPNServ2.exe binary -dnl renovate: datasource=github-releases depName=OpenVPN/openvpnserv2 versioning=loose -define([OVPNSERV2_VERSION], [2.0.1.0]) - -dnl Easy-RSA binaries: -dnl URL to .zip file containing "easy-rsa-[EASYRSA_VERSION]" folder with Easy-RSA. -dnl The OpenSSL binaries, which come with Easy-RSA, are not used by Openvpn-build. -dnl The only binaries which Openvpn-build uses from Easy-RSA, are the *nix style -dnl (32bit only) binaries for Windows, from easy-rsa/distro/windows/bin. -dnl Further details: easy-rsa/distro/windows/Licensing/mksh-Win32.txt -dnl renovate: datasource=github-releases depName=OpenVPN/easy-rsa -define([EASYRSA_VERSION], [3.2.5]) - -dnl ============================================================ -dnl MSI Provisioning -dnl ============================================================ - -dnl Define the product name and publisher. -define([PRODUCT_NAME], [OpenVPN]) -define([PRODUCT_PUBLISHER], [OpenVPN, Inc.]) - -dnl The package version as displayed by UI and used in filenames (no spaces, please). -define([PACKAGE_VERSION], [2.8_git-I001]) - -dnl The MSI product version in the form of n[.n[.n]] (numbers only). -dnl The third field is 100*openvpn bugfix release + MSI build number. -dnl So for the 2nd MSI build for OpenVPN 2.6.3 use 2.6.302 -define([PRODUCT_VERSION], [2.8.0]) - -dnl The MSI product code MUST change on each product release. -define([PRODUCT_CODE], [{AFB9E34B-0126-474A-AF75-A7C69AB91905}]) - -dnl The MSI upgrade codes MUST persist for all versions of the same product line. -dnl Please use own upgrade codes when deploying a non-official OpenVPN release. -define([UPGRADE_CODE_x86], [{1195A47B-A37A-4055-9D34-B7A691F7E97B}]) -define([UPGRADE_CODE_amd64], [{461BDF86-D389-4471-BF36-99806B64C127}]) -define([UPGRADE_CODE_arm64], [{1E8C4DDC-9E93-4AE2-9495-DF86821EAA3A}]) - -dnl OpenVPN configration file extension (e.g. conf, ovpn...) -define([CONFIG_EXTENSION], [ovpn])