diff --git a/.clang-format-ignore b/.clang-format-ignore index 0ca3deac91..b4bf4fe789 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -2,6 +2,7 @@ ./android/tunnel/* ./android/src/com/wireguard/* ./src/hacl-star/* +./src/platforms/windows/golang-msvc-types.h ./extension/app/json.hpp ./glean/org/mozilla/Glean/glean.js ./glean/org/mozilla/Glean/glean.lib.js diff --git a/.github/l10n/check_l10n_issues.py b/.github/l10n/check_l10n_issues.py index 9e88995256..3749451a91 100644 --- a/.github/l10n/check_l10n_issues.py +++ b/.github/l10n/check_l10n_issues.py @@ -12,7 +12,7 @@ vpn_root_folder = os.path.join(script_folder, os.pardir, os.pardir) # Extract all strings in the .ts file -srcFile = os.path.join(vpn_root_folder, "src", "src.pro") +srcFile = os.path.join(vpn_root_folder, "translations", "generated", "dummy.pro") os.system(f"lupdate {srcFile} -ts") # Load the exceptions file. String IDs can be added to each category to @@ -21,7 +21,7 @@ exceptions = json.load(f) # Load the .ts file -ts_file = os.path.join(vpn_root_folder, "translations", "en", "mozillavpn_en.ts") +ts_file = os.path.join(vpn_root_folder, "translations", "generated", "mozillavpn_en.ts") tree = etree.parse(ts_file) root = tree.getroot() diff --git a/.github/workflows/functional_tests.yaml b/.github/workflows/functional_tests.yaml index 52e86f3900..2b436db072 100644 --- a/.github/workflows/functional_tests.yaml +++ b/.github/workflows/functional_tests.yaml @@ -60,15 +60,10 @@ jobs: run: | pip3 install -r requirements.txt - python3 scripts/utils/import_languages.py - python3 scripts/utils/generate_glean.py - - # Delete unit tests, so we can get to testing faster - sed -i '/tests\/unit/d' mozillavpn.pro - qmake6 CONFIG+=DUMMY QMAKE_CXXFLAGS+=--coverage QMAKE_LFLAGS+=--coverage CONFIG+=debug CONFIG+=inspector QT+=svg - make -j$(nproc) - cp ./src/mozillavpn build/ - cp -r ./src/.obj build/ + mkdir -p build/cmake + cmake -S $(pwd) -B build/cmake -DBUILD_DUMMY=ON \ + -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_EXE_LINKER_FLAGS=--coverage + cmake --build build/cmake -j$(nproc) - name: Generate tasklist id: testGen @@ -116,7 +111,7 @@ jobs: - name: Check build shell: bash - run: ./build/mozillavpn -v + run: ./build/cmake/src/mozillavpn -v - name: Launching API proxy shell: bash @@ -139,15 +134,15 @@ jobs: ACCOUNT_PASSWORD: ${{ secrets.ACCOUNT_PASSWORD }} ARTIFACT_DIR: ${{ runner.temp }}/artifacts MVPN_API_BASE_URL: http://localhost:5000 - MVPN_BIN: ./build/mozillavpn + MVPN_BIN: ./build/cmake/src/mozillavpn - name: Generating grcov reports id: generateGrcov continue-on-error: true # Ignore GRCOV parsing errors, see github.com/mozilla/grcov/issues/570 timeout-minutes: 1 # Give GRCOV a short timeout in case it hangs after a panic run: | - cp build/.obj/*.gcno src/.obj - ./build/bin/grcov src/.obj -s src -t lcov --branch --ignore-not-existing \ + ./build/bin/grcov build/cmake/src/CMakeFiles/mozillavpn.dir \ + -s src -t lcov --branch --ignore-not-existing \ -o ${{runner.temp}}/artifacts/functional_lcov.info - name: Upload coverage to Codecov diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml index 8f5820a97c..66768cb434 100644 --- a/.github/workflows/linux.yaml +++ b/.github/workflows/linux.yaml @@ -21,25 +21,6 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 - - name: Generate build version - shell: bash - env: - GITREF: ${{github.ref}} - run: | - VERSION=$(grep ':VERSION' version.pri | cut -d= -f2 | tr -d '[:space:]') - if [[ "$GITREF" =~ ^refs/pull/([0-9]+)/merge ]]; then - BUILDREV="~pr${BASH_REMATCH[1]}" - elif [[ "$GITREF" =~ ^refs/heads/releases/([0-9][^/]*) ]]; then - git fetch --unshallow - BUILDREV="~rc$(git rev-list --count --first-parent origin/main..HEAD)" - VERSION=${BASH_REMATCH[1]} - else - BUILDREV="~github${{github.run_number}}" - fi - - # Force the upstream package version - sed -i "s/:VERSION.*/:VERSION = ${VERSION}${BUILDREV}/" version.pri - - name: Install source dependencies shell: bash run: | @@ -48,9 +29,11 @@ jobs: - name: Build source bundle shell: bash - run: ./scripts/linux/script.sh --source + env: + GITREF: ${{github.ref}} + run: ./scripts/linux/script.sh --source --gitref ${GITREF} - - name: Uploading + - name: Upload source bundle uses: actions/upload-artifact@v2 with: name: Sources diff --git a/.github/workflows/macos-build.yaml b/.github/workflows/macos-build.yaml index 4eeb41f4c0..637a251a58 100644 --- a/.github/workflows/macos-build.yaml +++ b/.github/workflows/macos-build.yaml @@ -34,7 +34,7 @@ jobs: shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git clone https://github.com/mozilla-mobile/qt_static_macos + git clone https://github.com/mozilla-mobile/qt_static_macos --depth 1 cd qt_static_macos cat qt6* > qt_static.tar.gz tar xf qt_static.tar.gz @@ -192,7 +192,7 @@ jobs: shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git clone https://github.com/mozilla-mobile/qt_static_macos + git clone https://github.com/mozilla-mobile/qt_static_macos --depth 1 cd qt_static_macos cat qt6* > qt_static.tar.gz tar xf qt_static.tar.gz diff --git a/.github/workflows/ppa-automation.yaml b/.github/workflows/ppa-automation.yaml index 15ba3f7a85..2ee0b9b9ab 100644 --- a/.github/workflows/ppa-automation.yaml +++ b/.github/workflows/ppa-automation.yaml @@ -20,27 +20,6 @@ jobs: with: fetch-depth: 0 - - name: Generate release version - id: gen-version - shell: bash - env: - GITREF: ${{github.ref}} - run: | - if [[ "$GITREF" =~ ^refs/heads/releases/([0-9][^/]*) ]]; then - BUILDREV="~rc$(git rev-list --count --first-parent origin/main..HEAD)" - VERSION=$(echo $GITREF | cut -d'/' -f4) - elif [[ "$GITREF" =~ ^refs/tags/v([0-9a-z.]+) ]]; then - BUILDREV="" - VERSION=${BASH_REMATCH[1]} - else - BUILDREV="~nightly$(date +%Y%m%d)-${{github.run_number}}" - VERSION=$(grep ':VERSION' version.pri | cut -d= -f2 | tr -d '[:space:]') - if [[ ! -z $(git rev-list --after="25 hours" ${{github.sha}}) ]]; then - echo "::set-output name=nightly-changes::true" - fi - fi - sed -i "s/:VERSION.*/:VERSION = ${VERSION}${BUILDREV}/" version.pri - - name: Install dependencies shell: bash run: | @@ -48,11 +27,13 @@ jobs: pip3 install -r requirements.txt - name: Build source bundle + id: gen-source shell: bash env: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} GNUPGHOME: ${{ runner.temp }}/gnupg-data + GITREF: ${{github.ref}} run: | mkdir -m700 $GNUPGHOME echo "allow-preset-passphrase" > $GNUPGHOME/gpg-agent.conf @@ -63,7 +44,13 @@ jobs: KEYGRIP=$(gpg --with-colons --with-keygrip --list-keys | grep -m1 '^grp:' | tr -d [grp:]) echo "$GPG_PASSWORD" | /lib/gnupg2/gpg-preset-passphrase --preset $KEYGRIP - ./scripts/linux/script.sh --sign-key $KEYID --source + ./scripts/linux/script.sh --sign-key $KEYID --source --gitref ${GITREF} + + if [[ "$GITREF" == "refs/heads/main" ]]; then + if [[ ! -z $(git rev-list --after="25 hours" ${{github.sha}}) ]]; then + echo "::set-output name=nightly-changes::true" + fi + fi - name: Uploading sources uses: actions/upload-artifact@v2 @@ -84,7 +71,7 @@ jobs: - name: Push to Launchpad Nightly PPA shell: bash - if: ${{ steps.gen-version.outputs.nightly-changes == 'true' }} + if: ${{ steps.gen-source.outputs.nightly-changes == 'true' }} env: PPA_URL: ppa:okirby/mozilla-vpn-nightly GNUPGHOME: ${{ runner.temp }}/gnupg-data diff --git a/.github/workflows/test_lottie.yaml b/.github/workflows/test_lottie.yaml index fcf9be3d14..7d06cdd4eb 100644 --- a/.github/workflows/test_lottie.yaml +++ b/.github/workflows/test_lottie.yaml @@ -49,7 +49,7 @@ jobs: shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git clone https://github.com/mozilla-mobile/qt_static_macos + git clone https://github.com/mozilla-mobile/qt_static_macos --depth 1 cd qt_static_macos cat qt6* > qt_static.tar.gz tar xf qt_static.tar.gz @@ -88,7 +88,7 @@ jobs: shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git clone https://github.com/mozilla-mobile/qt_static_windows + git clone https://github.com/mozilla-mobile/qt_static_windows --depth 1 cd qt_static_windows cat qt6* > qt_static.tar.bz2 tar xf qt_static.tar.bz2 diff --git a/.github/workflows/test_qml.yaml b/.github/workflows/test_qml.yaml index 75151bc186..1b72eea959 100644 --- a/.github/workflows/test_qml.yaml +++ b/.github/workflows/test_qml.yaml @@ -21,16 +21,17 @@ jobs: env: QTVERSION: 6.2.4 steps: - - name: Install Linux packages + - name: Clone repository + uses: actions/checkout@v2 + + - name: Install dependencies run: | sudo apt update sudo apt install git libgl-dev libegl-dev libpolkit-gobject-1-dev clang-10 liboath-dev python3 -y + python3 -m pip install -r requirements.txt python3 -m pip install aqtinstall aqt install-qt --outputdir /opt linux desktop $QTVERSION gcc_64 -m all - - name: Clone repository - uses: actions/checkout@v2 - - name: Checkout submodules shell: bash run: | @@ -38,28 +39,27 @@ jobs: git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - - name: Generating glean samples - shell: bash - run: | - pip3 install -r requirements.txt - python3 scripts/utils/generate_glean.py - - - name: Importing translation files - shell: bash - run: | - export PATH=/opt/$QTVERSION/gcc_64/bin:$PATH - python3 scripts/utils/import_languages.py - - name: Install Grcov shell: bash run: | cargo install grcov + + - name: Building tests + shell: bash + run: | + mkdir -p build + cmake -S . -B $(pwd)/build -DBUILD_TESTING=ON \ + -DCMAKE_PREFIX_PATH=/opt/$QTVERSION/gcc_64/lib/cmake/ \ + -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_EXE_LINKER_FLAGS=--coverage + cmake --build $(pwd)/build --target qml_tests - name: Running tests shell: bash run: | - export PATH=/opt/$QTVERSION/gcc_64/bin:$PATH - xvfb-run -a ./scripts/tests/qml_tests.sh --grcov qml_lcov.info + $(pwd)/build/tests/qml/qml_tests -platform offscreen + grcov $(pwd)/build/tests/qml/CMakeFiles/qml_tests.dir \ + -s $(pwd)/build/tests/qml -t lcov --branch --ignore-not-existing \ + > ${{github.workspace}}/qml_lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 @@ -82,7 +82,7 @@ jobs: shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git clone https://github.com/mozilla-mobile/qt_static_macos + git clone https://github.com/mozilla-mobile/qt_static_macos --depth 1 cd qt_static_macos cat qt6* > qt_static.tar.gz tar xf qt_static.tar.gz @@ -147,7 +147,7 @@ jobs: shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git clone https://github.com/mozilla-mobile/qt_static_windows + git clone https://github.com/mozilla-mobile/qt_static_windows --depth 1 cd qt_static_windows cat qt6* > qt_static.tar.bz2 tar xf qt_static.tar.bz2 @@ -165,20 +165,19 @@ jobs: - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.1 - - name: Install glean depedencies + - name: Install depedencies shell: bash run: | pip3 install -r requirements.txt - python3 scripts/utils/generate_glean.py - - name: Importing translation files + - name: Building tests shell: bash run: | export PATH=/c/MozillaVPNBuild/bin:$PATH - python3 scripts/utils/import_languages.py -m + mkdir -p build + cmake -S . -B $(pwd)/build -DBUILD_TESTING=ON + cmake --build $(pwd)/build --config Release --target qml_tests - name: Running tests shell: bash - run: | - export PATH=/c/MozillaVPNBuild/bin:$PATH - ./scripts/tests/qml_tests.sh + run: build/tests/qml/Release/qml_tests.exe diff --git a/.github/workflows/test_unit.yaml b/.github/workflows/test_unit.yaml index b21d423fc7..f8183abf4c 100644 --- a/.github/workflows/test_unit.yaml +++ b/.github/workflows/test_unit.yaml @@ -21,16 +21,17 @@ jobs: QTVERSION: 6.2.4 name: Run Unit tests on Linux steps: - - name: Install Linux packages + - name: Clone repository + uses: actions/checkout@v2 + + - name: Install dependences run: | sudo apt update sudo apt install git libgl-dev libegl-dev libpolkit-gobject-1-dev clang-10 liboath-dev python3 -y + python3 -m pip install -r requirements.txt python3 -m pip install aqtinstall aqt install-qt --outputdir /opt linux desktop $QTVERSION gcc_64 -m all - - name: Clone repository - uses: actions/checkout@v2 - - name: Checkout submodules shell: bash run: | @@ -38,34 +39,28 @@ jobs: git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - - name: Generating glean samples - shell: bash - run: | - pip3 install -r requirements.txt - python3 scripts/utils/generate_glean.py - - - name: Importing translation files - shell: bash - run: | - export PATH=/opt/$QTVERSION/gcc_64/bin:$PATH - python3 scripts/utils/import_languages.py - - - name: Installing the oathtool - shell: bash - run: | - pip3 install oathtool - python3 -m oathtool.generate-script - - name: Install Grcov shell: bash run: | cargo install grcov + + - name: Building tests + shell: bash + run: | + mkdir -p build + cmake -S . -B $(pwd)/build -DBUILD_TESTING=ON \ + -DCMAKE_PREFIX_PATH=/opt/$QTVERSION/gcc_64/lib/cmake/ \ + -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_EXE_LINKER_FLAGS=--coverage + cmake --build $(pwd)/build --target build_tests - name: Running tests shell: bash + working-directory: ./build run: | - export PATH=/opt/$QTVERSION/gcc_64/bin:$PATH - MVPN_OATHTOOL=./oathtool ./scripts/tests/unit_tests.sh --grcov unit_lcov.info + ctest --output-on-failure + grcov $(pwd)/tests/unit/CMakeFiles/unit_tests.dir \ + -s $(pwd)/tests/unit -t lcov --branch --ignore-not-existing \ + > ${{github.workspace}}/unit_lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 @@ -95,7 +90,7 @@ jobs: shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git clone https://github.com/mozilla-mobile/qt_static_macos + git clone https://github.com/mozilla-mobile/qt_static_macos --depth 1 cd qt_static_macos cat qt6* > qt_static.tar.gz tar xf qt_static.tar.gz @@ -119,19 +114,11 @@ jobs: run: | cargo install grcov - - name: Installing the oathtool - shell: bash - run: | - pip3 install oathtool - python3 -m oathtool.generate-script - # python3 by default... - sed -i '' "s/python/python3/g" oathtool - - name: Running tests shell: bash run: | export PATH=/opt/qt6/bin:$PATH - MVPN_OATHTOOL=./oathtool ./scripts/tests/unit_tests.sh --grcov unit_lcov.info + ./scripts/tests/unit_tests.sh --grcov unit_lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 @@ -161,7 +148,7 @@ jobs: shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git clone https://github.com/mozilla-mobile/qt_static_windows + git clone https://github.com/mozilla-mobile/qt_static_windows --depth 1 cd qt_static_windows cat qt6* > qt_static.tar.bz2 tar xf qt_static.tar.bz2 @@ -179,26 +166,21 @@ jobs: - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.1 - - name: Install glean depedencies + - name: Install depedencies shell: bash run: | pip3 install -r requirements.txt - python3 scripts/utils/generate_glean.py - - name: Importing translation files + - name: Building tests shell: bash run: | export PATH=/c/MozillaVPNBuild/bin:$PATH - python3 scripts/utils/import_languages.py -m - - - name: Installing the oathtool - shell: bash - run: | - pip3 install oathtool - python3 -m oathtool.generate-script + mkdir -p build + cmake -S . -B $(pwd)/build -DBUILD_TESTING=ON + cmake --build $(pwd)/build --config Release --target build_tests - name: Running tests shell: bash + working-directory: ./build run: | - export PATH=/c/MozillaVPNBuild/bin:$PATH - MVPN_OATHTOOL=oathtool ./scripts/tests/unit_tests.sh + ctest --build-config Release --output-on-failure diff --git a/.github/workflows/wasm.yaml b/.github/workflows/wasm.yaml index aea291a9b8..76b01b33e3 100644 --- a/.github/workflows/wasm.yaml +++ b/.github/workflows/wasm.yaml @@ -48,6 +48,7 @@ jobs: mv /tmp/wasm.prf /opt/$QTVERSION/wasm_32/mkspecs/features/wasm/wasm.prf ln -s /opt/$QTVERSION/gcc_64/bin/lconvert /opt/$QTVERSION/wasm_32/bin/ ln -s /opt/$QTVERSION/gcc_64/bin/lupdate /opt/$QTVERSION/wasm_32/bin/ + ln -s /opt/$QTVERSION/gcc_64/bin/lrelease /opt/$QTVERSION/wasm_32/bin/ - name: Install python dependencies shell: bash diff --git a/.github/workflows/windows-build.yaml b/.github/workflows/windows-build.yaml index 3896c6dbc0..645f5aab85 100644 --- a/.github/workflows/windows-build.yaml +++ b/.github/workflows/windows-build.yaml @@ -18,6 +18,8 @@ jobs: windows: name: Windows build runs-on: windows-latest + env: + BUILDCONFIG: RelWithDebInfo steps: - name: Clone repository @@ -34,7 +36,7 @@ jobs: shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git clone https://github.com/mozilla-mobile/qt_static_windows + git clone https://github.com/mozilla-mobile/qt_static_windows --depth 1 cd qt_static_windows cat qt6* > qt_static.tar.bz2 tar xf qt_static.tar.bz2 @@ -54,41 +56,35 @@ jobs: - name: Adding msbuild to PATH uses: ilammy/msvc-dev-cmd@v1 - - name: Compilation script + - name: Build VPN client shell: bash run: | export PATH=/c/MozillaVPNBuild/bin:$PATH - ./scripts/windows/compile.bat + mkdir -p build + cmake -S . -B $(pwd)/build + cmake --build $(pwd)/build --config ${BUILDCONFIG} + cmake --build $(pwd)/build --config ${BUILDCONFIG} --target msi - name: Create the zip package for signature shell: bash run: | - mkdir unsigned - cp /c/MozillaVPNBuild/SSL/bin/libssl-1_1-x64.dll unsigned - cp /c/MozillaVPNBuild/SSL/bin/libcrypto-1_1-x64.dll unsigned - cp /c/MozillaVPNBuild/*.msm unsigned - cp windows/tunnel/x64/tunnel.dll unsigned - cp windows/tunnel/.deps/wintun/bin/amd64/wintun.dll unsigned - cp balrog/x64/balrog.dll unsigned - cp windows/split-tunnel/mullvad-split-tunnel.cat unsigned - cp windows/split-tunnel/mullvad-split-tunnel.inf unsigned - cp windows/split-tunnel/mullvad-split-tunnel.sys unsigned - cp windows/split-tunnel/WdfCoinstaller01011.dll unsigned - cp extension/manifests/windows/mozillavpn.json unsigned - cp *.exe unsigned + mkdir -p unsigned + cmake --install $(pwd)/build --prefix $(pwd)/unsigned --config ${BUILDCONFIG} - - name: Upload app + - name: Upload installer uses: actions/upload-artifact@v2 with: name: build - path: windows/installer/x64/MozillaVPN.msi + path: build/windows/installer/MozillaVPN.msi if-no-files-found: error + - name: Upload Symbols uses: actions/upload-artifact@v2 with: name: win_symbols - path: MozillaVPN.pdb + path: unsigned/Mozilla VPN.pdb if-no-files-found: error + - name: Upload unsigned app uses: actions/upload-artifact@v2 with: diff --git a/.gitignore b/.gitignore index edadac2dc2..237a4cbda6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,19 +10,21 @@ Makefile.* src/mozillavpn src/mozillavpn.app src/mozillavpn.framework +src/version.h mozillavpn_wg mozillavpn.qmltypes mozillavpn_metatypes.json mozillavpn_qmltyperegistrations.cpp -balrog/balrog.a -balrog/balrog.h +balrog/balrog-api.a +balrog/balrog-api.h +balrog/vendor/ qrc_qml.cpp .qmake.stash dbus_adaptor.* dbus_interface.* mozillavpn-daemon *_plugin_import.cpp -linux/netfilter/netfilter.[ah] +src/netfilter.[ah] linux/netfilter/vendor/ .qm .tmp @@ -31,6 +33,16 @@ linux/netfilter/vendor/ __pycache__ lottie.mjs +# CMake build artifacts +cmake_install.cmake +CMakeCache.txt +CMakeFiles/ +lottie/lottie_autogen/ +lottie/liblottie.a +nebula/nebula_autogen/ +nebula/libnebula.a +src/mozillavpn_autogen/ + # Node (for functional tests) node_modules @@ -55,9 +67,13 @@ xcode.xconfig *.ts tests/auth/tests +tests/auth/auth_tests +tests/auth/auth_tests_autogen/ tests/unit/tests +tests/unit/tests_autogen/ tests/nativemessaging/tests tests/qml/qml_tests +tests/qml/qml_tests_autogen/ tests/qml/target_wrapper.sh build !taskcluster/ci/build @@ -86,6 +102,9 @@ vscode-cpptools/* MozillaVPN.vcxproj* *.autosave +# Qt Creator +CMakeLists.txt.user + #Windows .deps lib*.dll @@ -105,8 +124,11 @@ mozillavpnnp.exe mozillavpnnp.vcxproj* windows/installer/x64/ windows/tunnel/x64/ -windows/split-tunnel/* -!windows/split-tunnel/get.ps1 +windows/split-tunnel/*.cat +windows/split-tunnel/*.inf +windows/split-tunnel/*.sys +windows/split-tunnel/*.dll +windows/split-tunnel/.status #Rust .cargo_home/ @@ -124,6 +146,8 @@ mozillavpnnp.exe mozillavpnnp.vcxproj* # glean +/glean/libglean.a +/glean/glean_autogen/ /glean/telemetry/* !/glean/telemetry/.keepme android/src/org/mozilla/firefox/vpn/glean/GleanEvents.kt @@ -132,12 +156,12 @@ android/src/org/mozilla/firefox/vpn/glean/GleanEvents.kt /qt/ # Language files +/translations/libtranslations.a +/translations/translations_autogen/ translations/*/*.ts translations/*/*.qm translations/*/locversion.plist -translations/*.pri -translations/generated/*.cpp -translations/generated/*.h +translations/generated/ macos/pkg/Resources/*.lproj # Adjust SDK Files @@ -157,5 +181,6 @@ tools/inspector/node_modules # Bridge app extension/bridge/target +extension/bridge/vendor extension/bridge/.cargo_home extension/bridge/.cargo diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..124a0dc926 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,89 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +cmake_minimum_required(VERSION 3.16) + +option(BUILD_DUMMY "Build for the dummy platform" OFF) + +project(mozillavpn VERSION 2.9.0 LANGUAGES C CXX + DESCRIPTION "A fast, secure and easy to use VPN. Built by the makers of Firefox." + HOMEPAGE_URL "https://vpn.mozilla.org" +) + +message("Configuring for ${CMAKE_GENERATOR}") +if(NOT (DEFINED CMAKE_CONFIGURATION_TYPES OR "${CMAKE_BUILD_TYPE}")) + ## Ensure the build type is set for single-config generators. + set(CMAKE_BUILD_TYPE "Debug" CACHE PATH "default build type" FORCE) + message("Setting build type ${CMAKE_BUILD_TYPE}") +endif() + +## Toolchain Setup +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.17) + cmake_policy(SET CMP0099 OLD) +endif() + +if(MSVC) + include(src/cmake/msvc.cmake) +endif() + +## Some workarounds for windows build quirks +if(WIN32) + ## CMake v3.20 has problems with race conditions in dependency generation. + ## See: https://gitlab.kitware.com/cmake/cmake/-/issues/22014 + if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_EQUAL 3.20) + cmake_policy(SET CMP0116 OLD) + endif() + + ## CMake also has trouble finding OpenSSL libraries on Windows, and may + ## need some help. + if(EXISTS "C:/MozillaVPNBuild/SSL" AND NOT DEFINED OPENSSL_ROOT_DIR) + set(OPENSSL_ROOT_DIR "C:/MozillaVPNBuild/SSL") + find_package(OpenSSL REQUIRED) + endif() + + if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/unsigned" + CACHE PATH "default install path" FORCE) + endif() +endif() + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +find_package(QT NAMES Qt6 COMPONENTS Core) +message("Using Qt version ${QT_VERSION}") +add_definitions(-DQT_DEPRECATED_WARNINGS) +add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050F00) + +# VPN Client build targets +add_subdirectory(src) +add_subdirectory(glean) +add_subdirectory(lottie) +add_subdirectory(nebula) +add_subdirectory(translations) + +# Testing build targets +option(BUILD_TESTING "Build the testing tree." OFF) +include(CTest) +add_custom_target(build_tests) +set_target_properties(build_tests PROPERTIES EXCLUDE_FROM_ALL $>) +add_subdirectory(tests/auth) +add_subdirectory(tests/nativemessaging) +add_subdirectory(tests/unit) +add_subdirectory(tests/qml) + +# Web Extension build targets +if(NOT CMAKE_CROSSCOMPILING) + add_subdirectory(extension) +endif() + +# Extra platform stuff +if(WIN32) + add_subdirectory(windows/installer) + add_subdirectory(windows/split-tunnel) + add_subdirectory(windows/tunnel) +endif() diff --git a/README.md b/README.md index 003fec69d3..376d8dbcfb 100644 --- a/README.md +++ b/README.md @@ -101,46 +101,37 @@ what we wrote before, you also need the following dependencies: - wireguard-tools >= 1.0.20200513 - resolvconf >= 1.82 - golang >= 1.13 +- cmake >= 3.16 -2. Instrument the glean events: -```bash -./scripts/utils/generate_glean.py -``` - -3. Import the languages and generate the translation strings: -```bash -./scripts/utils/import_languages.py -``` - -4. **Optional**: In case you want to change the shaders, you must regenerate +2. **Optional**: In case you want to change the shaders, you must regenerate them: ```bash ./scripts/utils/bake_shaders.sh ``` -5. Finally, we are able to configure the whole project using `qmake`. Usually, -`qmake` is already in your path, but if it's not, add the Qt6 installation path -in your `PATH` env variable. +3. Create a build directory, and configure the project for building using `cmake`. ```bash -qmake # for local dev builds use `qmake CONFIG+=debug` +mkdir build && cmake -S . -B build ``` -If you prefer to not install at /usr or /etc, you can specify alternate -prefixes. Using no prefixes is equivalent to: + +If you are using a build of Qt that was not installed by your operating system, +you may need to tell `cmake` where it is located by specifying the `CMAKE_PREFIX_PATH` +during configuration: ```bash -qmake USRPATH=/usr ETCPATH=/etc +mkdir build && cmake -S . -B build -DCMAKE_PREFIX_PATH=/lib/cmake/ ``` -6. Compile the source code: +4. Compile the source code: ```bash -make -j8 # replace 8 with the number of cores. Or use: make -j$(nproc) +cmake --build build -j$(nproc) ``` -7. Installation: +5. Installation: ```bash -sudo make install +sudo cmake -B build install ``` -8. After the installation, you can run the app simply running: +6. After the installation, you can run the app simply running: ```bash mozillavpn ``` diff --git a/balrog/api.go b/balrog/balrog-api.go similarity index 95% rename from balrog/api.go rename to balrog/balrog-api.go index 6be643c325..dd1d358a27 100644 --- a/balrog/api.go +++ b/balrog/balrog-api.go @@ -6,7 +6,7 @@ package main // #include // #include -// static void callLogger(void *func, int level, const char *msg) +// static inline void callLogger(void *func, int level, const char *msg) // { // ((void(*)(int, const char *))func)(level, msg); // } diff --git a/extension/CMakeLists.txt b/extension/CMakeLists.txt new file mode 100644 index 0000000000..aae66de481 --- /dev/null +++ b/extension/CMakeLists.txt @@ -0,0 +1,38 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +## Custom target to build the web-extension bridge with Rust/Cargo +add_custom_target(cargo_mozillavpnnp ALL + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bridge + COMMAND ${CMAKE_COMMAND} -E env CARGO_HOME=${CMAKE_CURRENT_BINARY_DIR}/.cargo_home + cargo build --release --target-dir ${CMAKE_CURRENT_BINARY_DIR}/bridge/target +) +set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_CURRENT_BINARY_DIR}/bridge/target) + +## Wrap the build artifacts as a CMake target. +add_executable(mozillavpnnp IMPORTED GLOBAL) +add_dependencies(mozillavpnnp cargo_mozillavpnnp) +set_property(TARGET mozillavpnnp PROPERTY + IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/bridge/target/release/mozillavpnnp${CMAKE_EXECUTABLE_SUFFIX}) + +## Handle installation by platform. +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if(NOT DEFINED WEBEXT_INSTALL_LIBDIR) + set(WEBEXT_INSTALL_LIBDIR ${CMAKE_INSTALL_LIBDIR}) + endif() + + install(PROGRAMS $ DESTINATION ${WEBEXT_INSTALL_LIBDIR}/mozillavpn) + + configure_file(manifests/linux/mozillavpn.json.in manifests/linux/mozillavpn.json) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifests/linux/mozillavpn.json + DESTINATION ${WEBEXT_INSTALL_LIBDIR}/mozilla/native-messaging-hosts) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifests/linux/mozillavpn.json + DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/opt/chrome/native-messaging-hosts) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifests/linux/mozillavpn.json + DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/chromium/native-messaging-hosts) +elseif(WIN32) + install(PROGRAMS $ DESTINATION .) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/manifests/windows/mozillavpn.json + DESTINATION .) +endif() diff --git a/extension/manifests/linux/mozillavpn.json b/extension/manifests/linux/mozillavpn.json.in similarity index 76% rename from extension/manifests/linux/mozillavpn.json rename to extension/manifests/linux/mozillavpn.json.in index db77d77209..2472cda158 100644 --- a/extension/manifests/linux/mozillavpn.json +++ b/extension/manifests/linux/mozillavpn.json.in @@ -1,7 +1,7 @@ { "name": "mozillavpn", "description": "A fast, secure and easy to use VPN. Built by the makers of Firefox. ", - "path": "/usr/lib/mozillavpn/mozillavpnnp", + "path": "/@WEBEXT_INSTALL_LIBDIR@/mozillavpn/mozillavpnnp", "type": "stdio", "allowed_extensions": [ "vpn@mozilla.org", "@testpilot-containers" ] } diff --git a/glean/CMakeLists.txt b/glean/CMakeLists.txt new file mode 100644 index 0000000000..58d6ee26a9 --- /dev/null +++ b/glean/CMakeLists.txt @@ -0,0 +1,35 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +add_library(glean STATIC) + +find_package(Qt6 REQUIRED COMPONENTS Core Qml Sql) +target_link_libraries(glean PRIVATE Qt6::Core Qt6::Qml Qt6::Sql) +target_include_directories(glean PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(glean PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) + +get_filename_component(MVPN_SCRIPT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../scripts ABSOLUTE) + +target_sources(glean PRIVATE + glean.cpp + glean.h + glean.qrc + telemetry/telemetry.qrc + telemetry/gleansample.h +) + +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/telemetry) +add_custom_command( + OUTPUT telemetry/gleansample.h telemetry/qmldir telemetry/pings.js telemetry/sample.js telemetry/telemetry.qrc + MAIN_DEPENDENCY metrics.yaml + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + DEPENDS pings.yaml + COMMAND python3 ${MVPN_SCRIPT_DIR}/utils/generate_glean.py +) + +# Statically link to the SQLite driver, if it exists. +if(TARGET Qt6::QSQLiteDriverPlugin) + target_link_libraries(glean PUBLIC Qt6::QSQLiteDriverPlugin) + qt_import_plugins(glean INCLUDE Qt6::QSQLiteDriverPlugin) +endif() diff --git a/glean/glean.cpp b/glean/glean.cpp index 9b3c6de6a8..0f702a74bc 100644 --- a/glean/glean.cpp +++ b/glean/glean.cpp @@ -7,4 +7,10 @@ constexpr auto QRC_ROOT = "qrc:///glean/"; -void Glean::Initialize(QQmlEngine* engine) { engine->addImportPath(QRC_ROOT); } +void Glean::Initialize(QQmlEngine* engine) { +#ifndef BUILD_QMAKE + Q_INIT_RESOURCE(glean); + Q_INIT_RESOURCE(telemetry); +#endif + engine->addImportPath(QRC_ROOT); +} diff --git a/glean/glean.pri b/glean/glean.pri index f9c7270d5d..944ce9c770 100644 --- a/glean/glean.pri +++ b/glean/glean.pri @@ -8,16 +8,15 @@ exists($$PWD/telemetry/gleansample.h) { HEADERS += $$PWD/telemetry/gleansample.h + RESOURCES += $$PWD/telemetry/telemetry.qrc } else{ error(Glean generated files are missing. Please run `python3 ./scripts/utils/generate_glean.py`) } -SOURCES += \ - $$PWD/glean.cpp - -HEADERS += \ - $$PWD/glean.h +INCLUDEPATH += $$PWD +SOURCES += $$PWD/glean.cpp +HEADERS += $$PWD/glean.h !wasm { message(Include QSQlite plugin) diff --git a/glean/glean.qrc b/glean/glean.qrc index f4016f0b21..49baa57ba7 100644 --- a/glean/glean.qrc +++ b/glean/glean.qrc @@ -1,8 +1,5 @@ - telemetry/pings.js - telemetry/sample.js - telemetry/qmldir org/mozilla/Glean/glean.js org/mozilla/Glean/glean.lib.js org/mozilla/Glean/qmldir diff --git a/glean/telemetry/.keepme b/glean/telemetry/.keepme deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/linux/debian/control.qt6 b/linux/debian/control.qt6 index c779a39125..99ef0bf0c9 100644 --- a/linux/debian/control.qt6 +++ b/linux/debian/control.qt6 @@ -4,6 +4,7 @@ Priority: optional Maintainer: mozilla Build-Depends: debhelper (>= 9.20160709), cdbs, + cmake (>= 3.16~) | cmake-mozilla (>= 3.16~), quilt, flex, golang (>=2:1.13~) | golang-1.13, diff --git a/linux/debian/rules.qt6 b/linux/debian/rules.qt6 index 66cae25f6a..28e4ae917e 100755 --- a/linux/debian/rules.qt6 +++ b/linux/debian/rules.qt6 @@ -15,8 +15,7 @@ endif dh $@ --with=systemd --warn-missing override_dh_auto_configure: - python3 scripts/utils/import_languages.py - qmake6 CONFIG-=debug CONFIG+=release CONFIG-=debug_and_release BUILD_ID=$(DEB_VERSION) + dh_auto_configure -- -DBUILD_ID=$(DEB_VERSION) -DWEBEXT_INSTALL_LIBDIR=lib override_dh_installdocs: diff --git a/lottie/CMakeLists.txt b/lottie/CMakeLists.txt new file mode 100644 index 0000000000..10c6cfaf5f --- /dev/null +++ b/lottie/CMakeLists.txt @@ -0,0 +1,33 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +add_library(lottie STATIC) + +find_package(Qt6 REQUIRED COMPONENTS Core Qml) +target_link_libraries(lottie PRIVATE Qt6::Core Qt6::Qml) +target_include_directories(lottie PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/lib) + +target_sources(lottie PRIVATE + lib/lottie.cpp + lib/lottie.h + lib/lottieprivate.cpp + lib/lottieprivate.h + lib/lottieprivatedocument.cpp + lib/lottieprivatedocument.h + lib/lottieprivatenavigator.cpp + lib/lottieprivatenavigator.h + lib/lottieprivatewindow.cpp + lib/lottieprivatewindow.h + lib/lottiestatus.h + lib/lottie.qrc +) + +## This writes into the source directory to generate lottie.mjs. Which is frowned-upon. :`( +add_custom_command( + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/lib/lottie/lottie.mjs + MAIN_DEPENDENCY lib/lottie/lottie_wrap.js.template + DEPENDS lib/lottie/lottie.min.js + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib/lottie + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/lib/lottie/lottie.mjs.cmake +) diff --git a/lottie/lib/lottie.cpp b/lottie/lib/lottie.cpp index 7685cb8edd..2a24456fbb 100644 --- a/lottie/lib/lottie.cpp +++ b/lottie/lib/lottie.cpp @@ -8,6 +8,7 @@ constexpr auto QRC_ROOT = "qrc:///lottie/"; void Lottie::initialize(QQmlEngine* engine, const QString& userAgent) { + Q_INIT_RESOURCE(lottie); engine->addImportPath(QRC_ROOT); LottiePrivate::initialize(engine, userAgent); } diff --git a/lottie/lib/lottie/lottie.mjs.cmake b/lottie/lib/lottie/lottie.mjs.cmake new file mode 100644 index 0000000000..fe7f78b2ad --- /dev/null +++ b/lottie/lib/lottie/lottie.mjs.cmake @@ -0,0 +1,7 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Read and re-generate lottie.mjs +file(READ ${CMAKE_CURRENT_LIST_DIR}/lottie.min.js LOTTIEJS) +configure_file(${CMAKE_CURRENT_LIST_DIR}/lottie_wrap.js.template lottie.mjs) diff --git a/lottie/lib/lottie/lottie_wrap.js.template b/lottie/lib/lottie/lottie_wrap.js.template index f3fee95f21..011ab78c05 100644 --- a/lottie/lib/lottie/lottie_wrap.js.template +++ b/lottie/lib/lottie/lottie_wrap.js.template @@ -8,7 +8,7 @@ export function initialize(window, document, navigator) { (function() { try { - __LOTTIE__ + @LOTTIEJS@ } catch (e) { console.log(e); } diff --git a/lottie/lottie.pri b/lottie/lottie.pri index 2b3932ad66..aa425b4f6b 100644 --- a/lottie/lottie.pri +++ b/lottie/lottie.pri @@ -18,7 +18,7 @@ HEADERS += $$PWD/lib/lottie.h \ # wrap lottie in a single file before using it as resource. LOTTIEWRAP = $$cat($$PWD/lib/lottie/lottie_wrap.js.template, blob) LOTTIEJS = $$cat($$PWD/lib/lottie/lottie.min.js, blob) -LOTTIERC = $$replace(LOTTIEWRAP, __LOTTIE__, $$LOTTIEJS) +LOTTIERC = $$replace(LOTTIEWRAP, @LOTTIEJS@, $$LOTTIEJS) write_file($$PWD/lib/lottie/lottie.mjs, LOTTIERC) RESOURCES += $$PWD/lib/lottie.qrc diff --git a/macos/app/Info.plist.in b/macos/app/Info.plist.in new file mode 100644 index 0000000000..36337816e0 --- /dev/null +++ b/macos/app/Info.plist.in @@ -0,0 +1,52 @@ + + + + + CFBundleAllowMixedLocalizations + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIconName + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + CFBundleURLTypes + + + CFBundleURLSchemes + mozilla-vpn + + CFBundleURLName + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + + + ITSAppUsesNonExemptEncryption + + LSApplicationCategoryType + public.app-category.utilities + LSMinimumSystemVersion + 10.14 + LSMultipleInstancesProhibited + + NSPrincipalClass + NSApplication + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/nebula/CMakeLists.txt b/nebula/CMakeLists.txt new file mode 100644 index 0000000000..8578333bb4 --- /dev/null +++ b/nebula/CMakeLists.txt @@ -0,0 +1,19 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +add_library(nebula STATIC) + +find_package(Qt6 REQUIRED COMPONENTS Qml Quick) +target_link_libraries(nebula PRIVATE Qt6::Quick) +target_include_directories(nebula PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_sources(nebula PRIVATE + nebula.cpp + nebula.h + ui/components.qrc + ui/themes.qrc + ui/nebula_resources.qrc + ui/compatQt6.qrc + ui/resourcesQt6.qrc +) diff --git a/nebula/nebula.cpp b/nebula/nebula.cpp index ad62081ce7..9d9cb71594 100644 --- a/nebula/nebula.cpp +++ b/nebula/nebula.cpp @@ -6,4 +6,18 @@ constexpr auto QRC_ROOT = "qrc:///nebula/"; -void Nebula::Initialize(QQmlEngine* engine) { engine->addImportPath(QRC_ROOT); } +void Nebula::Initialize(QQmlEngine* engine) { +#ifndef BUILD_QMAKE + Q_INIT_RESOURCE(components); + Q_INIT_RESOURCE(themes); + Q_INIT_RESOURCE(nebula_resources); +# if QT_VERSION >= 0x060000 + Q_INIT_RESOURCE(compatQt6); + Q_INIT_RESOURCE(resourcesQt6); +# else + Q_INIT_RESOURCE(compatQt5); +# endif +#endif + + engine->addImportPath(QRC_ROOT); +} diff --git a/nebula/nebula.pri b/nebula/nebula.pri index d412a080e7..6362b428ae 100644 --- a/nebula/nebula.pri +++ b/nebula/nebula.pri @@ -12,6 +12,7 @@ SOURCES += \ HEADERS += \ $$PWD/nebula.h +INCLUDEPATH += $$PWD RESOURCES += $$PWD/ui/components.qrc RESOURCES += $$PWD/ui/themes.qrc RESOURCES += $$PWD/ui/nebula_resources.qrc diff --git a/requirements.txt b/requirements.txt index 59a613e919..0b7b60b7a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ glean_parser lxml PyYAML jsonschema +oathtool diff --git a/scripts/android/package.sh b/scripts/android/package.sh index 14073ada01..b51dbe769f 100755 --- a/scripts/android/package.sh +++ b/scripts/android/package.sh @@ -120,7 +120,7 @@ print Y "Importing translation files..." python3 scripts/utils/import_languages.py || die "Failed to import languages" print Y "Generating glean samples..." -python3 scripts/utils/generate_glean.py || die "Failed to generate glean samples" +python3 scripts/utils/generate_glean.py -j "android/src/" || die "Failed to generate glean samples" print Y "Copy and patch Adjust SDK..." rm -rf "android/src/com/adjust" || die "Failed to remove the adjust folder" diff --git a/scripts/linux/script.sh b/scripts/linux/script.sh index 6e474d3e15..d36ba0e940 100755 --- a/scripts/linux/script.sh +++ b/scripts/linux/script.sh @@ -8,6 +8,7 @@ REVISION=1 RELEASE= +GITREF= QTVERSION="qt6" SOURCEONLY=N PPA_URL= @@ -24,6 +25,7 @@ helpFunction() { print N "" print N "Build options:" print N " -r, --release DIST Build packages for distribution DIST" + print N " -g, --gitref REF Generated version suffix from REF" print N " -v, --version REV Set package revision to REV" print N " --beineri Build using Stephan Binner's Qt5.15 PPA" print N " --qt5 Build using Qt5 packages" @@ -54,6 +56,11 @@ while [[ $# -gt 0 ]]; do shift shift ;; + -g | --gitref) + GITREF="$2" + shift + shift + ;; -v | --version) REVISION="$2" shift @@ -108,9 +115,31 @@ if [ -z "$RELEASE" ]; then fi printn Y "Computing the version... " -SHORTVERSION=$(cat version.pri | grep VERSION | grep defined | cut -d= -f2 | tr -d \ ) -WORKDIR=mozillavpn-$SHORTVERSION -print G "$SHORTVERSION" +# To explain this ugly pile of regex: +# 1. The grep statement matches a cmake project(...) directive. +# 2. awk breaks it into tokens separated by whitespace. +# 3. print whatever token we find after "VERSION" +SHORTVERSION=$(grep -zoE 'project\s*\([^\(\)]*\)' CMakeLists.txt | + awk '{ for (x=1;x" +# - Release tags force the version to match the tag. +# - Release branches are suffixed with "~rc<# of commits since main>" +# - The main branch sets a nightly date code. +if [[ "$GITREF" =~ ^refs/pull/([0-9]+)/merge ]]; then + SHORTVERSION="${SHORTVERSION}~pr${BASH_REMATCH[1]}" +elif [[ "$GITREF" =~ ^refs/tags/v([0-9a-z.]+) ]]; then + SHORTVERSION=${BASH_REMATCH[1]} +elif [[ "$GITREF" =~ ^refs/heads/releases/([0-9][^/]*) ]]; then + git fetch --unshallow + RCVERSION="~rc$(git rev-list --count --first-parent origin/main..HEAD)" + SHORTVERSION="${BASH_REMATCH[1]}${RCVERSION}" +elif [[ "$GITREF" == "refs/heads/main" ]]; then + SHOFTVERSION="${SHORTVERSION}~nightly$(date +%Y%m%d)" +fi +WORKDIR=mozillavpn-${SHORTVERSION} +print G "${SHORTVERSION}" rm -rf .tmp || die "Failed to remove the temporary directory" mkdir .tmp || die "Failed to create the temporary directory" @@ -124,10 +153,15 @@ print G "done." print G "Creating the orig tarball" printn N "Creating the working directory... " -cd .tmp -mkdir $WORKDIR || die "Failed" -rsync -a --exclude='.*' .. $WORKDIR || die "Failed" +[ -e "CMakeCache.txt" ] && die "Source directory is dirty, refusing to build packages" +RSYNC_EXCLUDE_DIRS= +for CACHEFILE in $(find . -maxdepth 2 -name 'CMakeCache.txt' -printf '%P\n'); do + RSYNC_EXCLUDE_DIRS="${RSYNC_EXCLUDE_DIRS} --exclude=$(dirname ${CACHEFILE})" +done +mkdir -p .tmp/${WORKDIR} || die "Failed" +rsync -a --exclude='.*' ${RSYNC_EXCLUDE_DIRS} . .tmp/${WORKDIR} || die "Failed" print G "done." +cd .tmp print Y "Generating glean samples..." (cd $WORKDIR && python3 scripts/utils/generate_glean.py) || die "Failed to generate glean samples" diff --git a/scripts/macos/import_pkg_resources.py b/scripts/macos/import_pkg_resources.py index cc4d100268..4f9c0d994b 100644 --- a/scripts/macos/import_pkg_resources.py +++ b/scripts/macos/import_pkg_resources.py @@ -90,12 +90,12 @@ def translate(root, locale): # For each translation, lets see if it's completed. If yes, let's create the # corresponding `Resources` folder. -for translation in os.listdir('translations'): - translationDir = os.path.join('translations', translation) +for translation in os.listdir(os.path.join('translations', 'generated')): + translationDir = os.path.join('translations', 'generated', translation) if not os.path.isdir(translationDir): continue - translationFile = os.path.join(translationDir, f"mozillavpn_{translation}.ts") + translationFile = os.path.join('translations', 'generated', f"mozillavpn_{translation}.ts") if not os.path.isfile(translationFile): continue @@ -120,7 +120,7 @@ def translate(root, locale): translate(root, translation) # Finally, the English translation. -translationFile = os.path.join('translations', 'en', 'mozillavpn_en.ts') +translationFile = os.path.join('translations', 'generated', 'mozillavpn_en.ts') if not os.path.isfile(translationFile): exit(f"Translation '{translationFile}' is missing! Have you imported the languages?") diff --git a/scripts/macos/utils/xcode_patcher.rb b/scripts/macos/utils/xcode_patcher.rb index 4632aa5d14..2a5a9cbbbf 100644 --- a/scripts/macos/utils/xcode_patcher.rb +++ b/scripts/macos/utils/xcode_patcher.rb @@ -6,6 +6,7 @@ class XCodeprojPatcher attr :project + attr :qt_preprocess attr :target_main attr :target_extension @@ -35,6 +36,7 @@ def run(file, shortVersion, fullVersion, platform, networkExtension, configHash, def open_project(file) @project = Xcodeproj::Project.open(file) + @qt_preprocess = @project.targets.find { |target| target.to_s == 'Qt Preprocess' } die 'Failed to open the project file: ' + file if @project.nil? end @@ -300,19 +302,7 @@ def setup_target_extension(shortVersion, fullVersion, configHash) framework_ref = frameworks_group.new_file('NetworkExtension.framework') frameworks_build_phase.add_file_reference(framework_ref) - # This fails: @target_main.add_dependency @target_extension - container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) - container_proxy.container_portal = @project.root_object.uuid - container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] - container_proxy.remote_global_id_string = @target_extension.uuid - container_proxy.remote_info = @target_extension.name - - dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) - dependency.name = @target_extension.name - dependency.target = @target_main - dependency.target_proxy = container_proxy - - @target_main.dependencies << dependency + setup_target_dependency @target_main, @target_extension copy_appex = @target_main.new_copy_files_build_phase copy_appex.name = 'Copy Network-Extension plugin' @@ -373,19 +363,8 @@ def setup_target_loginitem(shortVersion, fullVersion, configHash) @target_loginitem.add_file_references([file]) } - # This fails: @target_main.add_dependency @target_loginitem - container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) - container_proxy.container_portal = @project.root_object.uuid - container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] - container_proxy.remote_global_id_string = @target_loginitem.uuid - container_proxy.remote_info = @target_loginitem.name - - dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) - dependency.name = @target_loginitem.name - dependency.target = @target_main - dependency.target_proxy = container_proxy - - @target_main.dependencies << dependency + setup_target_dependency @target_loginitem, @qt_preprocess + setup_target_dependency @target_main, @target_loginitem copy_app = @target_main.new_copy_files_build_phase copy_app.name = 'Copy LoginItem' @@ -396,6 +375,22 @@ def setup_target_loginitem(shortVersion, fullVersion, configHash) app_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] } end + # This works around target.add_dependency failing by creating a PBXContainerItemProxy. + def setup_target_dependency(target, depends) + container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) + container_proxy.container_portal = @project.root_object.uuid + container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] + container_proxy.remote_global_id_string = depends.uuid + container_proxy.remote_info = depends.name + + target_dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) + target_dependency.name = depends.name + target_dependency.target = target + target_dependency.target_proxy = container_proxy + + target.dependencies << target_dependency + end + def die(msg) print $msg exit 1 diff --git a/scripts/tests/list_test_classes.py b/scripts/tests/list_test_classes.py new file mode 100755 index 0000000000..5215da835e --- /dev/null +++ b/scripts/tests/list_test_classes.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import argparse +import os + +## Parse header files and output a list of test class declarations. +## This whole script could be an awk one-liner, but we can't be sure +## that awk exists on Windows, so we will do it in python instead. +parser = argparse.ArgumentParser(description='List test class declarations') +parser.add_argument('files', metavar='FILE', type=str, nargs='+', + help='C++ header files to parse for test classes') +parser.add_argument('-p', '--parent', metavar='NAME', type=str, + default='QObject', + help='Parent class name to match') +args = parser.parse_args() + +## Parse a header file looking for class declarations +def parseFile(filename, parent): + with open(filename) as fp: + for line in fp: + tokens = line.split(':') + if len(tokens) != 2: + continue + + ## Look for class delcarations + declare = tokens[0].split() + if len(declare) < 2: + continue + if declare[0] != 'class': + continue + + ## Look for classes that inherit from the parent. + inherit = tokens[1].replace(',', ' ').split() + if inherit[-1] != '{': + continue + if not parent in inherit: + continue + + ## We found a match, print it! + print(declare[1]) + +for filename in args.files: + parseFile(filename, args.parent) diff --git a/scripts/tests/unit_tests.sh b/scripts/tests/unit_tests.sh index 2aa86c864b..b38158e079 100755 --- a/scripts/tests/unit_tests.sh +++ b/scripts/tests/unit_tests.sh @@ -41,15 +41,6 @@ if ! [ -d "src" ] || ! [ -d "tests" ]; then die "This script must be executed at the root of the repository." fi -if ! [[ "$MVPN_OATHTOOL" ]]; then - printn Y "MVPN_OATHTOOL env not set. Let's generate the oathtool... " - (cd /tmp && python3 -m oathtool.generate-script) || die - MVPN_OATHTOOL=/tmp/oathtool - print G "Done." -fi - -[ -f "$MVPN_OATHTOOL" ] || die - if [[ "$OSTYPE" == "linux-gnu"* ]]; then print N "Configure for linux" . scripts/linux/utils/commons.sh diff --git a/scripts/utils/generate_glean.py b/scripts/utils/generate_glean.py index 341888c369..7d9a4edfc1 100755 --- a/scripts/utils/generate_glean.py +++ b/scripts/utils/generate_glean.py @@ -7,17 +7,51 @@ import os.path import yaml import subprocess +import argparse + +parser = argparse.ArgumentParser( + description='Generate Glean telemetry definitions from YAML sources') +parser.add_argument('-o', '--output', metavar='DIR', type=str, action='store', + default=os.path.join(os.getcwd(), 'glean', 'telemetry'), + help='Output directory for generated files') +parser.add_argument('-j', '--javadir', metavar='DIR', type=str, action='store', + help='Output directory for Java/Kotlin files') +args = parser.parse_args() + + +def camelize(string): + output = '' + first = True + for chunk in string.split('_'): + if first: + output += chunk + first = False + else: + output += chunk[0].upper() + output += chunk[1:] + return output -print("Parsing the metrics.yaml file...") -if not os.path.isfile('glean/metrics.yaml'): - print('Unable to find glean/metrics.yaml') - exit(1) -yaml_file = open("glean/metrics.yaml", 'r', encoding='utf-8') +## Find the input Yaml files relative to the script location. +rootpath = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) +metrics = os.path.join(rootpath, 'glean', 'metrics.yaml') +pings = os.path.join(rootpath, 'glean', 'pings.yaml') + +yaml_file = open(metrics, 'r', encoding='utf-8') yaml_content = yaml.safe_load(yaml_file) + +print("Parsing the metrics.yaml file...") +if not os.path.isfile(metrics): + print(f'Unable to find {metrics}') + exit(1) + +## Create the output directory if it doesn't already exist. +os.makedirs(args.output, exist_ok=True) + + print("Generating the C++ header...") -output = open("glean/telemetry/gleansample.h", "w") +output = open(os.path.join(args.output, "gleansample.h"), "w") output.write("""/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @@ -27,18 +61,6 @@ namespace GleanSample { """) -def camelize(string): - output = '' - first = True - for chunk in string.split('_'): - if first: - output += chunk - first = False - else: - output += chunk[0].upper() - output += chunk[1:] - return output - for key in yaml_content['sample']: sampleName = camelize(key) output.write(f"constexpr const char* {sampleName} = \"{sampleName}\";\n") @@ -49,11 +71,27 @@ def camelize(string): output.close(); -print("Generating the Kotlin enum...") -output = open("android/src/org/mozilla/firefox/vpn/glean/GleanEvents.kt", "w") -output.write("""/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +print("Generating the QRC file...") +output = open(os.path.join(args.output, "telemetry.qrc"), "w") +output.write(""" + + + pings.js + sample.js + qmldir + + +""") +output.close() + +if args.javadir is not None: + print("Generating the Kotlin enum...") + androiddir = os.path.join(args.javadir, 'org', 'mozilla', 'firefox', 'vpn', 'glean') + os.makedirs(androiddir, exist_ok=True) + output = open(os.path.join(androiddir, "GleanEvents.kt"), "w") + output.write("""/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // AUTOGENERATED! DO NOT EDIT!! package org.mozilla.firefox.vpn.glean @@ -63,34 +101,22 @@ def camelize(string): @SuppressLint("Unused") enum class GleanEvent(val key: String) { """) -def camelize(string): - output = '' - first = True - for chunk in string.split('_'): - if first: - output += chunk - first = False - else: - output += chunk[0].upper() - output += chunk[1:] - return output + for key in yaml_content['sample']: + sampleName = camelize(key) + output.write(f"\t {sampleName}(\"{sampleName}\"),\n") -for key in yaml_content['sample']: - sampleName = camelize(key) - output.write(f"\t {sampleName}(\"{sampleName}\"),\n") - -output.write(""" -} // GleanSample -"""); -output.close(); + output.write(""" + } // GleanSample + """) + output.close() print("Generating the JS modules...") try: - subprocess.call([sys.executable, "-m", "glean_parser", "translate", "glean/metrics.yaml", "glean/pings.yaml", - "-f", "javascript", "-o", "glean/telemetry", "--option", "platform=qt", - "--option", "version=0.30"]) + subprocess.call([sys.executable, "-m", "glean_parser", "translate", metrics, pings, + "-f", "javascript", "-o", args.output, "--option", "platform=qt", + "--option", "version=0.30"]) except Exception as e: - print("glean_parser failed. Is it installed? Try with:\n\tpip3 install -r requirements.txt --user"); - print(e); - exit(1) + print("glean_parser failed. Is it installed? Try with:\n\tpip3 install -r requirements.txt --user") + print(e) + exit(1) diff --git a/scripts/utils/generate_strings.py b/scripts/utils/generate_strings.py index 02d3781712..936451da12 100755 --- a/scripts/utils/generate_strings.py +++ b/scripts/utils/generate_strings.py @@ -7,8 +7,7 @@ import os import yaml import json - -string_ids = [] +import argparse comment_types = { "text": f"Standard text in a guide block", @@ -45,17 +44,12 @@ def construct_mapping(self, node, deep=False): mapping.append(key) return super().construct_mapping(node, deep) +def parseTranslationStrings(yamlfile): + if not os.path.isfile(yamlfile): + exit(f"Unable to find {yamlfile}") -def parseTranslationStrings(): - translations_path = os.path.abspath( - os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, "translations") - ) - yaml_path = os.path.join(translations_path, "strings.yaml") - - if not os.path.isfile(yaml_path): - exit("Unable to find translations/strings.yaml") - - with open(yaml_path, "r", encoding="utf-8") as yaml_file: + yaml_strings = {} + with open(yamlfile, "r", encoding="utf-8") as yaml_file: # Enforce a new line at the end of the file last_line = yaml_file.readlines()[-1] if last_line == last_line.rstrip(): @@ -64,327 +58,235 @@ def parseTranslationStrings(): # Reset position after reading the whole content yaml_file.seek(0) yaml_content = yaml.load(yaml_file, UniqueKeyLoader) + if yaml_content is None: + return yaml_strings - if yaml_content is not None: - if type(yaml_content) is not dict: - exit("The yaml file must contain collections only") + if type(yaml_content) is not dict: + exit(f"The {yamlfile} file must contain collections only") - for category in yaml_content: - for key in yaml_content[category]: - string_id = f"vpn.{category}.{key}" - obj = yaml_content[category][key] - value = [] - comments = [] + for category in yaml_content: + for key in yaml_content[category]: + string_id = f"vpn.{category}.{key}" + obj = yaml_content[category][key] + value = [] + comments = [] - if type(obj) is str: - if len(obj) == 0: - stop(string_id) - value = [obj] - - elif type(obj) is dict: - if not ("value" in obj): - exit( - f"The key {string_id} must contain a `value` string or an array of strings" - ) + if type(obj) is str: + if len(obj) == 0: + stop(string_id) + value = [obj] - if type(obj["value"]) is str: - value = [obj["value"]] + elif type(obj) is dict: + if not ("value" in obj): + exit( + f"The key {string_id} must contain a `value` string or an array of strings" + ) - elif type(obj["value"]) is list: - for x in range(0, len(obj["value"])): - value.append(obj["value"][x]) + if type(obj["value"]) is str: + value = [obj["value"]] - else: - exit( - f"The value of {string_id} must be a string or an array of strings" - ) + elif type(obj["value"]) is list: + for x in range(0, len(obj["value"])): + value.append(obj["value"][x]) - if "comment" in obj: - if type(obj["comment"]) is str: - comments = [obj["comment"]] + else: + exit( + f"The value of {string_id} must be a string or an array of strings" + ) - elif type(obj["comment"]) is list: - for x in range(0, len(obj["comment"])): - comments.append(obj["comment"][x]) + if "comment" in obj: + if type(obj["comment"]) is str: + comments = [obj["comment"]] - else: - exit( - f"The comment of {string_id} must be a string or an array of strings" - ) + elif type(obj["comment"]) is list: + for x in range(0, len(obj["comment"])): + comments.append(obj["comment"][x]) - if len(value) == 0: - stop(string_id) + else: + exit( + f"The comment of {string_id} must be a string or an array of strings" + ) - else: + if len(value) == 0: stop(string_id) - string_ids.append( - { - "enum_id": pascalize(f"{category}_{key}"), - "string_id": string_id, - "value": value, - "comments": comments, - } - ) - - -def parseGuideStrings(): - guides_path = os.path.abspath( - os.path.join( - os.path.dirname(__file__), os.pardir, os.pardir, "src", "ui", "guides" - ) - ) - - guide_ids = [] + else: + stop(string_id) - for guide_filename in os.listdir(guides_path): - if not guide_filename.endswith(".json"): + yaml_strings[pascalize(f"{category}_{key}")] = { + "string_id": string_id, + "value": value, + "comments": comments, + } + + return yaml_strings + +## Parse the strings from a guide block and append them to the output dictionary. +def parseGuideBlock(block, guide_id, output, filename): + if not "id" in block: + exit(f"Guide {filename} does not have an id for one of the blocks") + if not "type" in block: + exit(f"Guide {filename} does not have a type for block id {block['id']}") + if not "content" in block: + exit(f"Guide {filename} does not have a content for block id {block['id']}") + + block_id = block['id'] + block_enum = pascalize(f"guide_{guide_id}_block_{block_id}") + block_default_comment = comment_types.get(block['type'], "") + if block_enum in output: + exit(f"Duplicate block enum {block_enum} when parsing {filename}") + + if not isinstance(block["content"], list): + output[block_enum] = { + "string_id": f"guide.{guide_id}.block.{block_id}", + "value": [block["content"]], + "comments": [block.get("comment", block_default_comment)], + } + return + + for subblock in block["content"]: + if not "id" in subblock: + exit(f"Guide {filename} does not have an id for one of the subblocks of block {block_id}") + if not "content" in subblock: + exit(f"Guide file {filename} does not have a content for subblock id {subblock['id']}") + + subblock_id = subblock['id'] + subblock_enum = pascalize(f"guide_{guide_id}_block_{block_id}_{subblock_id}") + if subblock_enum in output: + exit(f"Duplicate sub-block enum {subblock_enum} when parsing {filename}") + + output[subblock_enum] = { + "string_id": f"guide.{guide_id}.block.{block_id}.{subblock_id}", + "value": [subblock["content"]], + "comments": [subblock.get("comment", block_default_comment)], + } + +## Parse the strings from a JSON guide and return them as a dictionary. +def parseGuideJson(guide_json, filename): + guide_strings = {} + + if not "id" in guide_json: + exit(f"Guide {filename} does not have an id") + if not "title" in guide_json: + exit(f"Guide {filename} does not have a title") + if not "subtitle" in guide_json: + exit(f"Guide {filename} does not have a subtitle") + if not "blocks" in guide_json: + exit(f"Guide {filename} does not have a blocks") + + guide_id = guide_json['id'] + title_enum = pascalize(f"guide_{guide_id}_title") + guide_strings[title_enum] = { + "string_id": f"guide.{guide_id}.title", + "value": [guide_json["title"]], + "comments": [guide_json.get("title_comment", "Title for a guide view")], + } + subtitle_enum = pascalize(f"guide_{guide_id}_subtitle") + guide_strings[subtitle_enum] = { + "string_id": f"guide.{guide_id}.subtitle", + "value": [guide_json["subtitle"]], + "comments": [guide_json.get("subtitle_comment", "Subtitle for a guide view")], + } + + for block in guide_json["blocks"]: + parseGuideBlock(block, guide_id, guide_strings, filename) + + return guide_strings + +## Parse a directory of JSON guides, returning their combined strings as a dictionary +def parseGuideStrings(guidepath): + guide_strings = {} + for filename in os.listdir(guidepath): + if not filename.endswith(".json"): continue - with open( - os.path.join(guides_path, guide_filename), "r", encoding="utf-8" - ) as guide_file: - guide_json = json.load(guide_file) - if not "id" in guide_json: - exit(f"Guide file {guide_filename} does not have an id") - - if not "title" in guide_json: - exit(f"Guide file {guide_filename} does not have a title") - - enum_title_id = pascalize(f"guide_{guide_json['id']}_title") - if enum_title_id in guide_ids: - exit(f"Duplicate id {enum_title_id} when parsing {guide_filename}") - guide_ids.append(enum_title_id) - - title_comment = "Title for a guide view" - if "title_comment" in guide_json: - title_comment = guide_json["title_comment"] - - string_ids.append( - { - "enum_id": enum_title_id, - "string_id": f"guide.{guide_json['id']}.title", - "value": [guide_json["title"]], - "comments": [title_comment], - } - ) - - if not "subtitle" in guide_json: - exit(f"Guide file {guide_filename} does not have a subtitle") - - enum_subtitle_id = pascalize(f"guide_{guide_json['id']}_subtitle") - if enum_subtitle_id in guide_ids: - exit(f"Duplicate id {enum_subtitle_id} when parsing {guide_filename}") - guide_ids.append(enum_subtitle_id) - - subtitle_comment = "Subtitle for a guide view" - if "subtitle_comment" in guide_json: - subtitle_comment = guide_json["subtitle_comment"] - - string_ids.append( - { - "enum_id": enum_subtitle_id, - "string_id": f"guide.{guide_json['id']}.subtitle", - "value": [guide_json["subtitle"]], - "comments": [subtitle_comment], - } - ) - - if not "blocks" in guide_json: - exit(f"Guide file {guide_filename} does not have a blocks") - for block in guide_json["blocks"]: - if not "id" in block: - exit( - f"Guide file {guide_filename} does not have an id for one of the blocks" - ) - if not "type" in block: - exit( - f"Guide file {guide_filename} does not have a type for block id {block['id']}" - ) - if not "content" in block: - exit( - f"Guide file {guide_filename} does not have a content for block id {block['id']}" - ) - enum_id = pascalize(f"guide_{guide_json['id']}_block_{block['id']}") - if enum_id in guide_ids: - exit( - f"Duplicate id {enum_id} when parsing {guide_filename} - block {block['id']}" - ) - guide_ids.append(enum_id) - - if isinstance(block["content"], list): - for subblock in block["content"]: - if not "id" in subblock: - exit( - f"Guide file {guide_filename} does not have an id for one of the subblocks of block {block['id']}" - ) - if not "content" in subblock: - exit( - f"Guide file {guide_filename} does not have a content for subblock id {subblock['id']}" - ) - enum_id = pascalize( - f"guide_{guide_json['id']}_block_{block['id']}_{subblock['id']}" - ) - if enum_id in guide_ids: - exit( - f"Duplicate id {enum_id} when parsing {guide_filename} - subblock {subblock['id']}" - ) - guide_ids.append(enum_id) - - comment = comment_types.get(block["type"], "") - if "comment" in subblock: - comment = subblock["comment"] - - string_ids.append( - { - "enum_id": enum_id, - "string_id": f"guide.{guide_json['id']}.block.{block['id']}.{subblock['id']}", - "value": [subblock["content"]], - "comments": [comment], - } - ) - else: - comment = comment_types.get(block["type"], "") - if "comment" in block: - comment = block["comment"] - - string_ids.append( - { - "enum_id": enum_id, - "string_id": f"guide.{guide_json['id']}.block.{block['id']}", - "value": [block["content"]], - "comments": [comment], - } - ) - - -def parseTutorialStrings(): - tutorials_path = os.path.abspath( - os.path.join( - os.path.dirname(__file__), os.pardir, os.pardir, "src", "ui", "tutorials" - ) - ) - - tutorial_ids = [] - for tutorial_filename in os.listdir(tutorials_path): - if not tutorial_filename.endswith(".json"): + with open(os.path.join(guidepath, filename), "r", encoding="utf-8") as fp: + guide_json = json.load(fp) + + for key, value in parseGuideJson(guide_json, filename).items(): + if key in guide_strings: + exit(f"Duplicate enum {key} when parsing {filename}") + guide_strings[key] = value + + return guide_strings + +## Parse the strings from a JSON tutorial and return them as a dictionary. +def parseTutorialJson(tutorial_json, filename): + tutorial_strings = {} + + if not "id" in tutorial_json: + exit(f"Tutorial {filename} does not have an id") + if not "title" in tutorial_json: + exit(f"Tutorial {filename} does not have a title") + if not "subtitle" in tutorial_json: + exit(f"Tutorial {filename} does not have a subtitle") + if not "completion_message" in tutorial_json: + exit(f"Tutorial {filename} does not have a completion message") + if not "steps" in tutorial_json: + exit(f"Tutorial {filename} does not have steps") + + tutorial_id = tutorial_json['id'] + title_enum = pascalize(f"tutorial_{tutorial_id}_title") + tutorial_strings[title_enum] = { + "string_id": f"tutorial.{tutorial_id}.title", + "value": [tutorial_json["title"]], + "comments": [tutorial_json.get("title_comment", "Title for a tutorial view")], + } + + subtitle_enum = pascalize(f"tutorial_{tutorial_id}_subtitle") + tutorial_strings[subtitle_enum] = { + "string_id": f"tutorial.{tutorial_id}.subtitle", + "value": [tutorial_json["subtitle"]], + "comments": [tutorial_json.get("subtitle_comment", "Subtitle for a tutorial view")], + } + + completion_enum = pascalize(f"tutorial_{tutorial_id}_completion_message") + tutorial_strings[completion_enum] = { + "string_id": f"tutorial.{tutorial_id}.completion_message", + "value": [tutorial_json["completion_message"]], + "comments": [tutorial_json.get("completion_message_comment", "Completion message for a tutorial view")], + } + + for step in tutorial_json["steps"]: + if not "id" in step: + exit(f"Tutorial {filename} does not have an id for one of the steps") + if not "tooltip" in step: + exit(f"Tutorial {filename} does not have a tooltip for step id {step['id']}") + + step_id = step['id'] + step_enum = pascalize(f"tutorial_{tutorial_id}_step_{step_id}") + if step_enum in tutorial_strings: + exit(f"Duplicate step enum {step_enum} when parsing {filename}") + + tutorial_strings[step_enum] = { + "string_id": f"tutorial.{tutorial_id}.step.{step_id}", + "value": [step["tooltip"]], + "comments": [step.get("comment", "A tutorial step tooltip")], + } + + return tutorial_strings + +## Parse a directory of JSON tutorials, returning their combined strings as a dictionary +def parseTutorialStrings(tutorialpath): + tutorial_strings = {} + for filename in os.listdir(tutorialpath): + if not filename.endswith(".json"): continue - with open( - os.path.join(tutorials_path, tutorial_filename), "r", encoding="utf-8" - ) as tutorial_file: - tutorial_json = json.load(tutorial_file) - if not "id" in tutorial_json: - exit(f"Tutorial file {tutorial_filename} does not have an id") - if not "title" in tutorial_json: - exit(f"Tutorial file {tutorial_filename} does not have a title") - - enum_title_id = pascalize(f"tutorial_{tutorial_json['id']}_title") - if enum_title_id in tutorial_ids: - exit(f"Duplicate id {enum_title_id} when parsing {tutorial_filename}") - tutorial_ids.append(enum_title_id) - - title_comment = "Title for a tutorial view" - if "title_comment" in tutorial_json: - title_comment = tutorial_json["title_comment"] - - string_ids.append( - { - "enum_id": enum_title_id, - "string_id": f"tutorial.{tutorial_json['id']}.title", - "value": [tutorial_json["title"]], - "comments": [title_comment], - } - ) - - if not "subtitle" in tutorial_json: - exit(f"Tutorial file {tutorial_filename} does not have a subtitle") - - enum_subtitle_id = pascalize(f"tutorial_{tutorial_json['id']}_subtitle") - if enum_subtitle_id in tutorial_ids: - exit(f"Duplicate id {enum_subtitle_id} when parsing {tutorial_filename}") - tutorial_ids.append(enum_subtitle_id) - - subtitle_comment = "Subtitle for a tutorial view" - if "subtitle_comment" in tutorial_json: - subtitle_comment = tutorial_json["subtitle_comment"] - - string_ids.append( - { - "enum_id": enum_subtitle_id, - "string_id": f"tutorial.{tutorial_json['id']}.subtitle", - "value": [tutorial_json["subtitle"]], - "comments": [subtitle_comment], - } - ) - - if not "completion_message" in tutorial_json: - exit( - f"Tutorial file {tutorial_filename} does not have a completion message" - ) - enum_completion_message_id = pascalize( - f"tutorial_{tutorial_json['id']}_completion_message" - ) - if enum_completion_message_id in tutorial_ids: - exit( - f"Duplicate id {enum_completion_message_id} when parsing {tutorial_filename}" - ) - tutorial_ids.append(enum_completion_message_id) - - completion_message_comment = "Completion message for a tutorial view" - if "completion_message_comment" in tutorial_json: - completion_message_comment = tutorial_json["completion_message_comment"] - - string_ids.append( - { - "enum_id": enum_completion_message_id, - "string_id": f"tutorial.{tutorial_json['id']}.completion_message", - "value": [tutorial_json["completion_message"]], - "comments": [completion_message_comment], - } - ) + with open(os.path.join(tutorialpath, filename), "r", encoding="utf-8") as fp: + tutorial_json = json.load(fp) - if not "steps" in tutorial_json: - exit(f"Tutorial file {tutorial_filename} does not have a steps") - for step in tutorial_json["steps"]: - if not "id" in step: - exit( - f"Tutorial file {tutorial_filename} does not have an id for one of the steps" - ) - if not "tooltip" in step: - exit( - f"Tutorial file {tutorial_filename} does not have a tooltip for step id {step['id']}" - ) - enum_id = pascalize(f"tutorial_{tutorial_json['id']}_step_{step['id']}") - if enum_id in tutorial_ids: - exit( - f"Duplicate id {enum_id} when parsing {tutorial_filename} - step {step['id']}" - ) - tutorial_ids.append(enum_id) - - comment = "A tutorial step tooltip" - if "comment" in step: - comment = step["comment"] - - string_ids.append( - { - "enum_id": enum_id, - "string_id": f"tutorial.{tutorial_json['id']}.step.{step['id']}", - "value": [step["tooltip"]], - "comments": [comment], - } - ) + for key, value in parseTutorialJson(tutorial_json, filename).items(): + if key in tutorial_strings: + exit(f"Duplicate enum {key} when parsing {filename}") + tutorial_strings[key] = value + return tutorial_strings -def writeOutputFiles(): - translations_path = os.path.abspath( - os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, "translations") - ) - with open( - os.path.join(translations_path, "generated", "l18nstrings.h"), - "w", - encoding="utf-8", - ) as output: +## Render a dictionary of strings into the l18nstrings module. +def generateStrings(strings, outdir): + os.makedirs(outdir, exist_ok=True) + with open(os.path.join(outdir, "l18nstrings.h"), "w", encoding="utf-8") as output: output.write( """/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -407,17 +309,18 @@ class L18nStrings final : public QQmlPropertyMap { """ ) - for string in string_ids: - output.write(f" {string['enum_id']},\n") + for key in strings: + output.write(f" {key},\n") output.write( """ __Last, }; static L18nStrings* instance(); + static void initialize(); explicit L18nStrings(QObject* parent); - ~L18nStrings(); + ~L18nStrings() = default; void retranslate(); @@ -431,11 +334,7 @@ class L18nStrings final : public QQmlPropertyMap { """ ) - with open( - os.path.join(translations_path, "generated", "l18nstrings_p.cpp"), - "w", - encoding="utf-8", - ) as output: + with open(os.path.join(outdir, "l18nstrings_p.cpp"), "w", encoding="utf-8") as output: output.write( """/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -457,37 +356,59 @@ def serialize(string): ret = "\\n".join(string) return ret.replace('"', '\\"') - for string in string_ids: - output.write(f" //% \"{serialize(string['value'])}\"\n") - for comment in string["comments"]: + for key, data in strings.items(): + output.write(f" //% \"{serialize(data['value'])}\"\n") + for comment in data["comments"]: output.write(f" //: {comment}\n") - output.write(f" QT_TRID_NOOP(\"{string['string_id']}\"),\n\n") + output.write(f" QT_TRID_NOOP(\"{data['string_id']}\"),\n\n") # This is done to make windows compiler happy - if len(string_ids) == 0: + if len(strings) == 0: output.write(f' "vpn.dummy.ignore",\n\n') output.write( """ }; -void L18nStrings::retranslate() { """ ) - for string in string_ids: - output.write( - f" insert(\"{string['enum_id']}\", qtTrId(_ids[{string['enum_id']}]));\n" - ) + # Generate the retranslate() method. + output.write("void L18nStrings::retranslate() {\n") + for key in strings: + output.write(f" insert(\"{key}\", qtTrId(_ids[{key}]));\n") output.write("}") -def generateStrings(): - parseTranslationStrings() - parseGuideStrings() - parseTutorialStrings() - writeOutputFiles() - - if __name__ == "__main__": - generateStrings() + ## Parse arguments to locate the input and output files. + parser = argparse.ArgumentParser( + description='Generate internationaliation strings database from a YAML source') + parser.add_argument('source', metavar='SOURCE', type=str, action='store', nargs='?', + help='YAML strings file to process') + parser.add_argument('-o', '--output', metavar='DIR', type=str, action='store', + help='Output directory for generated files') + parser.add_argument('-g', '--guides', metavar='DIR', type=str, action='store', + help='Parse JSON guides from DIR') + parser.add_argument('-t', '--tutorials', metavar='DIR', type=str, action='store', + help='Parse JSON tutorials from DIR') + args = parser.parse_args() + + ## If no source was provided, find it relative to this script file. + if args.source is None: + rootpath = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) + args.source = os.path.join('translations', 'strings.yaml') + + ## If no output directory was provided, use the current directory. + if args.output is None: + args.output = os.getcwd() + + ## Parse the inputs for their sweet juicy strings. + strings = parseTranslationStrings(args.source) + if args.guides: + strings.update(parseGuideStrings(args.guides)) + if args.tutorials: + strings.update(parseTutorialStrings(args.tutorials)) + + ## Render the strings into generated content. + generateStrings(strings, args.output) diff --git a/scripts/utils/import_languages.py b/scripts/utils/import_languages.py index 280e4054db..695d9fcfa8 100755 --- a/scripts/utils/import_languages.py +++ b/scripts/utils/import_languages.py @@ -8,8 +8,8 @@ import os import sys import shutil -import generate_strings import atexit +import subprocess # Use the project root as the working directory prevdir = os.getcwd() @@ -29,11 +29,14 @@ help='The QT binary path. If not set, we try to guess.') args = parser.parse_args() -def title(a, b): - print(f"\033[96m\033[1m{a}\033[0m: \033[97m{b}\033[0m") +stepnum = 1 +def title(text): + global stepnum + print(f"\033[96m\033[1mStep {stepnum}\033[0m: \033[97m{text}\033[0m") + stepnum = stepnum+1 # Step 0 -title("Step 0", "Find the Qt localization tools...") +title("Find the Qt localization tools...") def qtquery(qmake, propname): try: qtquery = os.popen(f'{qmake} -query {propname}') @@ -63,6 +66,7 @@ def qtquery(qmake, propname): lupdate = os.path.join(qtbinpath, 'lupdate') lconvert = os.path.join(qtbinpath, 'lconvert') +lrelease = os.path.join(qtbinpath, 'lrelease') # Step 0 # Let's update the i18n repo @@ -73,7 +77,7 @@ def qtquery(qmake, propname): # Go through the i18n repo, check each XLIFF file and take # note which locale is complete above the minimum threshold. # Adds path of .xliff and .ts to l10n_files. -title("Step 1", "Validate the XLIFF file...") +title("Validate the XLIFF file...") l10n_files = [] for locale in os.listdir('i18n'): # Skip non folders @@ -92,7 +96,7 @@ def qtquery(qmake, propname): print(f'OK\t- en added (reference locale)') l10n_files.append({ 'locale': 'en', - 'ts': os.path.join('translations', f'en/mozillavpn_en.ts'), + 'ts': os.path.join('translations', 'generated', 'mozillavpn_en.ts'), 'xliff': xliff_path }) continue @@ -118,17 +122,16 @@ def qtquery(qmake, propname): print(f'OK\t- {locale} added ({round(completeness*100, 2)}% translated)') l10n_files.append({ 'locale': locale, - 'ts': os.path.join('translations', f'{locale}/mozillavpn_{locale}.ts'), + 'ts': os.path.join('translations', 'generated', f'mozillavpn_{locale}.ts'), 'xliff': xliff_path }) # Step 2 -title("Step 2", "Create folders and localization files for the languages...") +title("Create folders and localization files for the languages...") for file in l10n_files: - dirname = os.path.dirname(file['ts']) - if not os.path.exists(dirname): - os.makedirs(dirname) - locversion = os.path.join(dirname, 'locversion.plist') + locdirectory = os.path.join('translations', 'generated', file['locale']) + os.makedirs(locdirectory, exist_ok=True) + locversion = os.path.join(locdirectory, f'locversion.plist') with open(locversion, 'w') as locversion_file: locversion_file.write(f""" """) -# Step 3 -title("Step 3", "Write PRI file to import the locales that are ready...") -with open('translations/translations.pri', 'w') as pri_file: - output = [] - output.append('TRANSLATIONS += \\ ') +with open(os.path.join('translations', 'generated', 'macos.pri'), 'w') as macospri: + macospri.write('### AUTOGENERATED! DO NOT EDIT!! ###\n') for file in l10n_files: - output.append(f"../{file['ts']} \\ ") - output.append('\n\n##End') - - if args.ismacos: - for file in l10n_files: - output.append(f"LANGUAGES_FILES_{file['locale']}.files += ../translations/{file['locale']}/locversion.plist") - output.append(f"LANGUAGES_FILES_{file['locale']}.path = Contents/Resources/{file['locale']}.lproj") - output.append(f"QMAKE_BUNDLE_DATA += LANGUAGES_FILES_{file['locale']}") + macospri.write(f"LANGUAGES_FILES_{file['locale']}.files += $$PWD/{file['locale']}/locversion.plist\n") + macospri.write(f"LANGUAGES_FILES_{file['locale']}.path = Contents/Resources/{file['locale']}.lproj\n") + macospri.write(f"QMAKE_BUNDLE_DATA += LANGUAGES_FILES_{file['locale']}\n\n") - pri_file.write('\n'.join(output)) +# Step 3 +title("Write resource file to import the locales that are ready...") +with open('translations/generated/translations.qrc', 'w') as qrcfile: + qrcfile.write('\n') + qrcfile.write('\n') + qrcfile.write(' \n') + for file in l10n_files: + qrcfile.write(f' mozillavpn_{file["locale"]}.qm\n') + qrcfile.write(' \n') + qrcfile.write('\n') # Step 4 -title("Step 4", "Generate the Js/C++ string definitions...") -generate_strings.generateStrings() +title("Generate the Js/C++ string definitions...") +try: + subprocess.call([sys.executable, os.path.join('scripts', 'utils', 'generate_strings.py'), + '-g', os.path.join('src', 'ui', 'guides'), + '-t', os.path.join('src', 'ui', 'tutorials'), + '-o', os.path.join('translations', 'generated'), + os.path.join('translations', 'strings.yaml')]) +except Exception as e: + print("generate_strings.py failed. Try with:\n\tpip3 install -r requirements.txt --user") + print(e) + exit(1) + +# Build a dummy project to glob together everything that might contain strings. +title("Scanning for new strings...") +def scan_sources(projfile, dirpath): + projfile.write(f"HEADERS += $$files({dirpath}/*.h, true)\n") + projfile.write(f"SOURCES += $$files({dirpath}/*.cpp, true)\n") + projfile.write(f"RESOURCES += $$files({dirpath}/*.qrc, true)\n\n") + +with open('translations/generated/dummy.pro', 'w') as dummyproj: + dummyproj.write('### AUTOGENERATED! DO NOT EDIT!! ###\n') + dummyproj.write(f"HEADERS += l18nstrings.h\n") + dummyproj.write(f"SOURCES += l18nstrings_p.cpp\n") + dummyproj.write(f"SOURCES += ../l18nstrings.cpp\n\n") + for l10n_file in l10n_files: + dummyproj.write(f"TRANSLATIONS += {os.path.basename(l10n_file['ts'])}\n") + + dummyproj.write("\n") + scan_sources(dummyproj, '../../src') + scan_sources(dummyproj, '../../nebula') # Step 5 -title("Step 5", "Generate new ts files...") -os.system(f"{lupdate} src/src.pro") - -# Step 6 -title("Step 5", "Now merge translations into the files...") +title("Generate translation resources...") +for l10n_file in l10n_files: + os.system(f"{lconvert} -if xlf -i {l10n_file['xliff']} -o {l10n_file['ts']}") +os.system(f"{lupdate} translations/generated/dummy.pro") for l10n_file in l10n_files: - os.system(f"{lconvert} -i {l10n_file['ts']} -if xlf -i {l10n_file['xliff']} -o {l10n_file['ts']}") + os.system(f"{lrelease} -idbased {l10n_file['ts']}") print(f'Imported {len(l10n_files)} locales') diff --git a/scripts/utils/make_template.py b/scripts/utils/make_template.py new file mode 100755 index 0000000000..e41520ce71 --- /dev/null +++ b/scripts/utils/make_template.py @@ -0,0 +1,74 @@ +#! /usr/bin/env python3 +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import sys +import argparse + +# Parse arguments to determine what to do. +parser = argparse.ArgumentParser( + description='Generate a file from a template') +parser.add_argument('template', metavar='TEMPLATE', type=str, action='store', + help='Template file to process') +parser.add_argument('-o', '--output', metavar='FILENAME', type=str, action='store', + help='Output file to write') +parser.add_argument('-k', '--keyword', metavar='KEY=VALUE', type=str, action='append', default=[], + help='Keyword to replace, and the value to replace it with') +parser.add_argument('-f', '--keyfile', metavar='KEY=FILE', type=str, action='append', default=[], + help='Keyword to replace, and the file to source its value from') +args = parser.parse_args() + +# Build up a dictionary of keywords and their replacement values +keywords = {} +for keyval in args.keyword: + kvsplit = keyval.split("=", 1) + if len(kvsplit) != 2: + print('Unable to parse KEY=VALUE from: ' + keyval) + sys.exit(1) + keywords[kvsplit[0]] = kvsplit[1] + +for keyfile in args.keyfile: + kfsplit = keyfile.split("=", 1) + if len(kfsplit) != 2: + print('Unable to parse KEY=FILE from: ' + keyfile) + sys.exit(1) + with open(kfsplit[1]) as fp: + keywords[kfsplit[0]] = fp.read() + +# Scan through the string for each of the keywords, replacing them +# as they are found, while taking care not to apply transformations +# to any already-transformed text. +def transform(text): + start = 0 + while start < len(text): + # Find the next matching keyword, if any. + matchIdx = -1 + matchKey = "" + for key in keywords: + x = text.find(key, start) + if (matchIdx < 0) or (x < matchIdx): + matchIdx = x + matchKey = key + + # If there are no matches, we can return. + if matchIdx < 0: + return text + + # Substitute the keyword and adjust the start. + value = keywords[matchKey] + start = matchIdx + len(value) + text = text[0:matchIdx] + value + text[matchIdx+len(matchKey):] + +# Open the output file +if args.output is None: + fout = sys.stdout +else: + fout = open(args.output, "w") + +# Read through the input file and apply variable substitutions. +with open(args.template) as fin: + fout.write(transform(fin.read())) + +fout.flush() +fout.close() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000000..a272a22a8c --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,56 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if(NOT DEFINED BUILD_ID) + string(TIMESTAMP BUILD_ID "${mozillavpn_VERSION_MAJOR}.%Y%m%d%H%M") + message("Generated BUILD_ID: ${BUILD_ID}") +endif() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/hacl-star) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/hacl-star/kremlin) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/hacl-star/kremlin/minimal) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +find_package(Qt6 REQUIRED COMPONENTS Widgets Gui Test) +find_package(Qt6 REQUIRED COMPONENTS Qml Quick) +find_package(Qt6 REQUIRED COMPONENTS NetworkAuth) +find_package(Qt6 REQUIRED COMPONENTS WebSockets) + +qt_add_executable(mozillavpn MANUAL_FINALIZATION) + +target_link_libraries(mozillavpn PRIVATE + Qt6::NetworkAuth + Qt6::Quick + Qt6::Test + Qt6::WebSockets + Qt6::Widgets +) + +target_link_libraries(mozillavpn PRIVATE glean lottie nebula translations) + +include(cmake/sources.cmake) + +if(${BUILD_DUMMY}) + set(MVPN_PLATFORM_NAME "dummy") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(MVPN_PLATFORM_NAME "linux") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + set(MVPN_PLATFORM_NAME "windows") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(MVPN_PLATFORM_NAME "macos") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android") + set(MVPN_PLATFORM_NAME "android") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "iOS") + set(MVPN_PLATFORM_NAME "ios") +endif() + +add_compile_definitions("$<$:MVPN_DEBUG>") +add_compile_definitions("MVPN_$") +include(cmake/${MVPN_PLATFORM_NAME}.cmake) + +add_subdirectory(crashreporter) +target_link_libraries(mozillavpn PRIVATE crashreporter) + +qt_finalize_target(mozillavpn) \ No newline at end of file diff --git a/src/authenticationinapp/authenticationinapp.cpp b/src/authenticationinapp/authenticationinapp.cpp index 2ffd2ede37..975dc24f6a 100644 --- a/src/authenticationinapp/authenticationinapp.cpp +++ b/src/authenticationinapp/authenticationinapp.cpp @@ -9,7 +9,7 @@ #include "leakdetector.h" #include "mozillavpn.h" -#include "../../glean/telemetry/gleansample.h" +#include "telemetry/gleansample.h" #include #include diff --git a/src/authenticationinapp/authenticationinappsession.cpp b/src/authenticationinapp/authenticationinappsession.cpp index 24e47f9d4a..02962b9726 100644 --- a/src/authenticationinapp/authenticationinappsession.cpp +++ b/src/authenticationinapp/authenticationinappsession.cpp @@ -13,7 +13,7 @@ #include "mozillavpn.h" #include "networkrequest.h" -#include "../../glean/telemetry/gleansample.h" +#include "telemetry/gleansample.h" #include #include diff --git a/src/cmake/dummy.cmake b/src/cmake/dummy.cmake new file mode 100644 index 0000000000..0ccb35131d --- /dev/null +++ b/src/cmake/dummy.cmake @@ -0,0 +1,9 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +target_sources(mozillavpn PRIVATE + platforms/dummy/dummycontroller.cpp + platforms/dummy/dummycontroller.h + platforms/dummy/dummycryptosettings.cpp +) diff --git a/src/cmake/golang.cmake b/src/cmake/golang.cmake new file mode 100644 index 0000000000..63858c1c83 --- /dev/null +++ b/src/cmake/golang.cmake @@ -0,0 +1,46 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +function(add_go_library TARGET SOURCE) + get_filename_component(SRC_NAME ${SOURCE} NAME) + get_filename_component(DIR_NAME ${SOURCE} DIRECTORY) + + file(GLOB_RECURSE SRC_DEPS ${DIR_NAME}/*.go) + string(REGEX REPLACE "[.]go$" ".h" HEADER_NAME ${SRC_NAME}) + if(WIN32) + string(REGEX REPLACE "[.]go$" ".lib" ARCHIVE_NAME ${SRC_NAME}) + else() + string(REGEX REPLACE "[.]go$" ".a" ARCHIVE_NAME ${SRC_NAME}) + endif() + + target_sources(${TARGET} PRIVATE ${HEADER_NAME}) + set_source_files_properties(${HEADER_NAME} PROPERTIES GENERATED 1) + + if(IS_DIRECTORY $ENV{HOME}) + execute_process(COMMAND go env GOCACHE OUTPUT_VARIABLE GOCACHE OUTPUT_STRIP_TRAILING_WHITESPACE) + else() + set(GOCACHE ${CMAKE_BINARY_DIR}/go-cache) + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_BINARY_DIR}/go-cache) + endif() + set(GOFLAGS -buildmode=c-archive -v) + if(IS_DIRECTORY ${DIR_NAME}/vendor) + set(GOFLAGS ${GOFLAGS} -mod vendor) + endif() + + if(MSVC AND NOT (MSVC_VERSION LESS 1900)) + # prevent error LNK2019: unresolved external symbol fprintf referenced in function ... + target_sources(${TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/platforms/windows/golang-msvc-fixup.cpp) + endif() + + get_filename_component(DIR_ABSOLUTE ${DIR_NAME} ABSOLUTE) + add_custom_command( + OUTPUT ${ARCHIVE_NAME} ${HEADER_NAME} + WORKING_DIRECTORY ${DIR_ABSOLUTE} + MAIN_DEPENDENCY ${SOURCE} + DEPENDS ${SRC_DEPS} ${DIR_NAME}/go.mod + COMMAND ${CMAKE_COMMAND} -E env GOCACHE=${GOCACHE} + go build ${GOFLAGS} -o ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME} ${SRC_NAME} + ) + target_link_libraries(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}) +endfunction(add_go_library) diff --git a/src/cmake/linux.cmake b/src/cmake/linux.cmake new file mode 100644 index 0000000000..43642c0f74 --- /dev/null +++ b/src/cmake/linux.cmake @@ -0,0 +1,119 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +find_package(Qt6 REQUIRED COMPONENTS DBus) +target_link_libraries(mozillavpn PRIVATE Qt6::DBus) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(polkit REQUIRED IMPORTED_TARGET polkit-gobject-1) +target_link_libraries(mozillavpn PRIVATE PkgConfig::polkit) + +# Linux platform source files +target_sources(mozillavpn PRIVATE + eventlistener.cpp + eventlistener.h + platforms/linux/backendlogsobserver.cpp + platforms/linux/backendlogsobserver.h + platforms/linux/dbusclient.cpp + platforms/linux/dbusclient.h + platforms/linux/linuxappimageprovider.cpp + platforms/linux/linuxappimageprovider.h + platforms/linux/linuxapplistprovider.cpp + platforms/linux/linuxapplistprovider.h + platforms/linux/linuxcontroller.cpp + platforms/linux/linuxcontroller.h + platforms/linux/linuxcryptosettings.cpp + platforms/linux/linuxdependencies.cpp + platforms/linux/linuxdependencies.h + platforms/linux/linuxnetworkwatcher.cpp + platforms/linux/linuxnetworkwatcher.h + platforms/linux/linuxnetworkwatcherworker.cpp + platforms/linux/linuxnetworkwatcherworker.h + platforms/linux/linuxpingsender.cpp + platforms/linux/linuxpingsender.h + platforms/linux/linuxsystemtraynotificationhandler.cpp + platforms/linux/linuxsystemtraynotificationhandler.h +) + +# Linux daemon source files +target_sources(mozillavpn PRIVATE + ../3rdparty/wireguard-tools/contrib/embeddable-wg-library/wireguard.c + ../3rdparty/wireguard-tools/contrib/embeddable-wg-library/wireguard.h + daemon/daemon.cpp + daemon/daemon.h + daemon/dnsutils.h + daemon/interfaceconfig.h + daemon/iputils.h + daemon/wireguardutils.h + platforms/linux/daemon/apptracker.cpp + platforms/linux/daemon/apptracker.h + platforms/linux/daemon/dbusservice.cpp + platforms/linux/daemon/dbusservice.h + platforms/linux/daemon/dbustypeslinux.h + platforms/linux/daemon/dnsutilslinux.cpp + platforms/linux/daemon/dnsutilslinux.h + platforms/linux/daemon/iputilslinux.cpp + platforms/linux/daemon/iputilslinux.h + platforms/linux/daemon/linuxdaemon.cpp + platforms/linux/daemon/pidtracker.cpp + platforms/linux/daemon/pidtracker.h + platforms/linux/daemon/polkithelper.cpp + platforms/linux/daemon/polkithelper.h + platforms/linux/daemon/wireguardutilslinux.cpp + platforms/linux/daemon/wireguardutilslinux.h +) + +add_definitions(-DPROTOCOL_VERSION=\"1\") + +set(DBUS_GENERATED_SOURCES) +qt_add_dbus_interface(DBUS_GENERATED_SOURCES platforms/linux/daemon/org.mozilla.vpn.dbus.xml dbus_interface) +qt_add_dbus_adaptor(DBUS_GENERATED_SOURCES + platforms/linux/daemon/org.mozilla.vpn.dbus.xml + platforms/linux/daemon/dbusservice.h + "" + dbus_adaptor) +target_sources(mozillavpn PRIVATE ${DBUS_GENERATED_SOURCES}) + +include(cmake/golang.cmake) +add_go_library(mozillavpn ../linux/netfilter/netfilter.go) + +include(GNUInstallDirs) +install(FILES ../linux/extra/MozillaVPN.desktop + DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) + +install(FILES ../linux/extra/MozillaVPN-startup.desktop + DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/xdg/autostart) + +install(FILES ../linux/extra/icons/16x16/mozillavpn.png + DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/16x16/apps) + +install(FILES ../linux/extra/icons/32x32/mozillavpn.png + DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/32x32/apps) + +install(FILES ../linux/extra/icons/48x48/mozillavpn.png + DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps) + +add_definitions(-DMVPN_ICON_PATH=\"${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps/mozillavpn.png\") +install(FILES ../linux/extra/icons/64x64/mozillavpn.png + DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps) + +install(FILES ../linux/extra/icons/128x128/mozillavpn.png + DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps) + +install(FILES platforms/linux/daemon/org.mozilla.vpn.policy + DESTINATION ${CMAKE_INSTALL_DATADIR}/polkit-1/actions) + +install(FILES platforms/linux/daemon/org.mozilla.vpn.conf + DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/system.d) + +install(FILES platforms/linux/daemon/org.mozilla.vpn.dbus.service + DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/system-services) + +## This is only really needed when building from source. Otherwise, we +## expect the Distro's packaging magic to sort this out. +pkg_check_modules(SYSTEMD systemd) +if("${SYSTEMD_FOUND}" EQUAL 1) + pkg_get_variable(SYSTEMD_UNIT_DIR systemd systemdsystemunitdir) + install(FILES ../linux/mozillavpn.service DESTINATION ${SYSTEMD_UNIT_DIR}) +endif() diff --git a/src/cmake/macos.cmake b/src/cmake/macos.cmake new file mode 100644 index 0000000000..2ef8674cfc --- /dev/null +++ b/src/cmake/macos.cmake @@ -0,0 +1,180 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +enable_language(OBJC) +enable_language(OBJCXX) + +set_target_properties(mozillavpn PROPERTIES OUTPUT_NAME "Mozilla VPN") + +# Configure the application bundle Info.plist +set_target_properties(mozillavpn PROPERTIES + MACOSX_BUNDLE ON + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../macos/app/Info.plist.in + MACOSX_BUNDLE_BUNDLE_NAME "Mozilla VPN" + MACOSX_BUNDLE_BUNDLE_VERSION "${BUILD_ID}" + MACOSX_BUNDLE_COPYRIGHT "MPL-2.0" + MACOSX_BUNDLE_GUI_IDENTIFIER "org.mozilla.macos.FirefoxVPN" + MACOSX_BUNDLE_ICON_FILE "AppIcon" + MACOSX_BUNDLE_INFO_STRING "Mozilla VPN" + MACOSX_BUNDLE_LONG_VERSION_STRING "${CMAKE_PROJECT_VERSION}-${BUILD_ID}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION}" +) + +#isEmpty(MVPN_DEVELOPMENT_TEAM) { +# MVPN_DEVELOPMENT_TEAM = "43AQ936H96" +#} +# +#isEmpty(MVPN_GROUP_ID_MACOS) { +# MVPN_GROUP_ID_MACOS = "group.org.mozilla.macos.Guardian" +#} +# +#isEmpty(MVPN_APP_ID_PREFIX) { +# QMAKE_TARGET_BUNDLE_PREFIX = org.mozilla.macos +#} else { +# QMAKE_TARGET_BUNDLE_PREFIX = $$MVPN_APP_ID_PREFIX +#} +# +#QMAKE_BUNDLE = "FirefoxVPN" + +# For the loginitem +find_library(FW_SYSTEMCONFIG SystemConfiguration) +find_library(FW_SERVICEMGMT ServiceManagement) +find_library(FW_SECURITY Security) +find_library(FW_COREWLAN CoreWLAN) + +target_link_libraries(mozillavpn PRIVATE ${FW_SYSTEMCONFIG}) +target_link_libraries(mozillavpn PRIVATE ${FW_SERVICEMGMT}) +target_link_libraries(mozillavpn PRIVATE ${FW_SECURITY}) +target_link_libraries(mozillavpn PRIVATE ${FW_COREWLAN}) + +qt6_import_qml_plugins(mozillavpn) + +# MacOS platform source files +target_sources(mozillavpn PRIVATE + daemon/daemon.cpp + daemon/daemon.h + daemon/daemonlocalserver.cpp + daemon/daemonlocalserver.h + daemon/daemonlocalserverconnection.cpp + daemon/daemonlocalserverconnection.h + daemon/dnsutils.h + daemon/interfaceconfig.h + daemon/iputils.h + daemon/wireguardutils.h + localsocketcontroller.cpp + localsocketcontroller.h + platforms/macos/daemon/dnsutilsmacos.cpp + platforms/macos/daemon/dnsutilsmacos.h + platforms/macos/daemon/iputilsmacos.cpp + platforms/macos/daemon/iputilsmacos.h + platforms/macos/daemon/macosdaemon.cpp + platforms/macos/daemon/macosdaemon.h + platforms/macos/daemon/macosdaemonserver.cpp + platforms/macos/daemon/macosdaemonserver.h + platforms/macos/daemon/macosroutemonitor.cpp + platforms/macos/daemon/macosroutemonitor.h + platforms/macos/daemon/wireguardutilsmacos.cpp + platforms/macos/daemon/wireguardutilsmacos.h + platforms/macos/macosauthenticationlistener.cpp + platforms/macos/macosauthenticationlistener.h + platforms/macos/macosmenubar.cpp + platforms/macos/macosmenubar.h + platforms/macos/macospingsender.cpp + platforms/macos/macospingsender.h + platforms/macos/macosstartatbootwatcher.cpp + platforms/macos/macosstartatbootwatcher.h + wgquickprocess.cpp + wgquickprocess.h + platforms/macos/macoscryptosettings.mm + platforms/macos/macosnetworkwatcher.mm + platforms/macos/macosnetworkwatcher.h + platforms/macos/macosutils.mm + platforms/macos/macosutils.h +) + +include(cmake/golang.cmake) + +# Enable Balrog for update support. +add_definitions(-DMVPN_BALROG) +add_go_library(mozillavpn ../balrog/balrog-api.go) +target_sources(mozillavpn PRIVATE + update/balrog.cpp + update/balrog.h +) + +## A helper to copy files into the application bundle +function(add_bundle_file SOURCE) + add_custom_command(TARGET mozillavpn POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Bundling ${SOURCE}" + COMMAND ${CMAKE_COMMAND} -E copy ${SOURCE} $/Resources/utils) +endfunction(add_bundle_file) + + +# Build the Wireguard Go tunnel +# FIXME: this builds in the source directory. +get_filename_component(WIREGUARD_GO_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/wireguard-go ABSOLUTE) +file(GLOB_RECURSE WIREGUARD_GO_DEPS ${WIREGUARD_GO_DIR}/*.go) +add_custom_command( + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/wireguard-go/wireguard-go + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/wireguard-go + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/wireguard-go/main.go + DEPENDS ${WIREGUARD_GO_DEPS} + COMMAND make +) +add_bundle_file(${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/wireguard-go/wireguard-go) + +# Install the native messaging extensions into the bundle. +add_dependencies(mozillavpn mozillavpnnp) +add_bundle_file($) +add_bundle_file(${CMAKE_CURRENT_SOURCE_DIR}/../extension/manifests/macos/mozillavpn.json) + +#QMAKE_ASSET_CATALOGS_APP_ICON = "AppIcon" +#QMAKE_ASSET_CATALOGS = $$PWD/../../../macos/app/Images.xcassets +# +#LD_RUNPATH_SEARCH_PATHS.name = "LD_RUNPATH_SEARCH_PATHS" +#LD_RUNPATH_SEARCH_PATHS.value = '"$(inherited) @executable_path/../Frameworks"' +#QMAKE_MAC_XCODE_SETTINGS += LD_RUNPATH_SEARCH_PATHS +# +#SWIFT_VERSION.name = "SWIFT_VERSION" +#SWIFT_VERSION.value = "5.0" +#QMAKE_MAC_XCODE_SETTINGS += SWIFT_VERSION +# +#CLANG_ENABLE_MODULES.name = "CLANG_ENABLE_MODULES" +#CLANG_ENABLE_MODULES.value = 'YES' +#QMAKE_MAC_XCODE_SETTINGS += CLANG_ENABLE_MODULES +# +#MARKETING_VERSION.name = "MARKETING_VERSION" +#MARKETING_VERSION.value = $$VERSION +#QMAKE_MAC_XCODE_SETTINGS += MARKETING_VERSION +# +#CODE_SIGN_ENTITLEMENTS.name = "CODE_SIGN_ENTITLEMENTS" +#CODE_SIGN_ENTITLEMENTS.value = $$PWD/../../../macos/app/app.entitlements +#QMAKE_MAC_XCODE_SETTINGS += CODE_SIGN_ENTITLEMENTS +# +#CODE_SIGN_IDENTITY.name = "CODE_SIGN_IDENTITY" +#CODE_SIGN_IDENTITY.value = 'Apple Development' +#QMAKE_MAC_XCODE_SETTINGS += CODE_SIGN_IDENTITY +# +#DEVELOPMENT_TEAM.name = "DEVELOPMENT_TEAM" +#DEVELOPMENT_TEAM.value = "$$MVPN_DEVELOPMENT_TEAM" +#QMAKE_MAC_XCODE_SETTINGS += DEVELOPMENT_TEAM +# +#GROUP_ID_MACOS.name = "GROUP_ID_MACOS" +#GROUP_ID_MACOS.value = "$$MVPN_GROUP_ID_MACOS" +#QMAKE_MAC_XCODE_SETTINGS += GROUP_ID_MACOS +# +#MVPN_APP_ID_MACOS = "$${QMAKE_TARGET_BUNDLE_PREFIX}.$${QMAKE_BUNDLE}" +# +#APP_ID_MACOS.name = "APP_ID_MACOS" +#APP_ID_MACOS.value = "$$MVPN_APP_ID_MACOS" +#QMAKE_MAC_XCODE_SETTINGS += APP_ID_MACOS +# +#SWIFT_OPTIMIZATION_LEVEL.name = "SWIFT_OPTIMIZATION_LEVEL" +#SWIFT_OPTIMIZATION_LEVEL.value = "-Onone" +#QMAKE_MAC_XCODE_SETTINGS += SWIFT_OPTIMIZATION_LEVEL +# +#GCC_PREPROCESSOR_DEFINITIONS.name = "GCC_PREPROCESSOR_DEFINITIONS" +#GCC_PREPROCESSOR_DEFINITIONS.value = 'GROUP_ID=\"$${MVPN_DEVELOPMENT_TEAM}.$${MVPN_APP_ID_MACOS}\"' +#QMAKE_MAC_XCODE_SETTINGS += GCC_PREPROCESSOR_DEFINITIONS +# \ No newline at end of file diff --git a/src/cmake/msvc.cmake b/src/cmake/msvc.cmake new file mode 100644 index 0000000000..fbcf2911a9 --- /dev/null +++ b/src/cmake/msvc.cmake @@ -0,0 +1,5 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +add_compile_options(/MP /Zc:preprocessor) diff --git a/src/cmake/sources.cmake b/src/cmake/sources.cmake new file mode 100644 index 0000000000..677524a1cb --- /dev/null +++ b/src/cmake/sources.cmake @@ -0,0 +1,329 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Generated version header file +configure_file(version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) +target_sources(mozillavpn PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/version.h) + +# VPN Client source files +target_sources(mozillavpn PRIVATE + appimageprovider.h + applistprovider.h + apppermission.cpp + apppermission.h + authenticationlistener.cpp + authenticationlistener.h + authenticationinapp/authenticationinapp.cpp + authenticationinapp/authenticationinapp.h + authenticationinapp/authenticationinapplistener.cpp + authenticationinapp/authenticationinapplistener.h + authenticationinapp/authenticationinappsession.cpp + authenticationinapp/authenticationinappsession.h + authenticationinapp/incrementaldecoder.cpp + authenticationinapp/incrementaldecoder.h + captiveportal/captiveportal.cpp + captiveportal/captiveportal.h + captiveportal/captiveportaldetection.cpp + captiveportal/captiveportaldetection.h + captiveportal/captiveportaldetectionimpl.cpp + captiveportal/captiveportaldetectionimpl.h + captiveportal/captiveportalmonitor.cpp + captiveportal/captiveportalmonitor.h + captiveportal/captiveportalnotifier.cpp + captiveportal/captiveportalnotifier.h + captiveportal/captiveportalrequest.cpp + captiveportal/captiveportalrequest.h + captiveportal/captiveportalrequesttask.cpp + captiveportal/captiveportalrequesttask.h + closeeventhandler.cpp + closeeventhandler.h + collator.cpp + collator.h + command.cpp + command.h + commandlineparser.cpp + commandlineparser.h + commands/commandactivate.cpp + commands/commandactivate.h + commands/commanddeactivate.cpp + commands/commanddeactivate.h + commands/commanddevice.cpp + commands/commanddevice.h + commands/commandlogin.cpp + commands/commandlogin.h + commands/commandlogout.cpp + commands/commandlogout.h + commands/commandselect.cpp + commands/commandselect.h + commands/commandservers.cpp + commands/commandservers.h + commands/commandstatus.cpp + commands/commandstatus.h + commands/commandui.cpp + commands/commandui.h + connectionbenchmark/benchmarktask.cpp + connectionbenchmark/benchmarktask.h + connectionbenchmark/benchmarktaskdownload.cpp + connectionbenchmark/benchmarktaskdownload.h + connectionbenchmark/benchmarktaskping.cpp + connectionbenchmark/benchmarktaskping.h + connectionbenchmark/benchmarktasksentinel.h + connectionbenchmark/connectionbenchmark.cpp + connectionbenchmark/connectionbenchmark.h + connectionhealth.cpp + connectionhealth.h + constants.cpp + constants.h + controller.cpp + controller.h + controllerimpl.h + cryptosettings.cpp + cryptosettings.h + curve25519.cpp + curve25519.h + dnshelper.cpp + dnshelper.h + dnspingsender.cpp + dnspingsender.h + errorhandler.cpp + errorhandler.h + featurelist.cpp + featurelist.h + features/featureaccountdeletion.h + features/featureappreview.h + features/featurecaptiveportal.h + features/featureconnectioninfo.h + features/featurecustomdns.h + features/featureinappaccountcreate.h + features/featureinappauth.h + features/featureinapppurchase.h + features/featurelocalareaaccess.h + features/featuremultiaccountcontainers.h + features/featuremultihop.h + features/featurenotificationcontrol.h + features/featuresharelogs.h + features/featuresplittunnel.h + features/featurestartonboot.h + features/featureunsecurednetworknotification.h + features/featureserverunavailablenotification.h + filterproxymodel.cpp + filterproxymodel.h + fontloader.cpp + fontloader.h + hacl-star/Hacl_Chacha20.c + hacl-star/Hacl_Chacha20Poly1305_32.c + hacl-star/Hacl_Curve25519_51.c + hacl-star/Hacl_Poly1305_32.c + hawkauth.cpp + hawkauth.h + hkdf.cpp + hkdf.h + iaphandler.cpp + iaphandler.h + imageproviderfactory.cpp + imageproviderfactory.h + inspector/inspectorhandler.cpp + inspector/inspectorhandler.h + inspector/inspectoritempicker.cpp + inspector/inspectoritempicker.h + inspector/inspectorutils.cpp + inspector/inspectorutils.h + inspector/inspectorwebsocketconnection.cpp + inspector/inspectorwebsocketconnection.h + inspector/inspectorwebsocketserver.cpp + inspector/inspectorwebsocketserver.h + ipaddress.cpp + ipaddress.h + ipaddresslookup.cpp + ipaddresslookup.h + itempicker.cpp + itempicker.h + leakdetector.cpp + leakdetector.h + localizer.cpp + localizer.h + logger.cpp + logger.h + loghandler.cpp + loghandler.h + logoutobserver.cpp + logoutobserver.h + main.cpp + models/device.cpp + models/device.h + models/devicemodel.cpp + models/devicemodel.h + models/feature.cpp + models/feature.h + models/feedbackcategorymodel.cpp + models/feedbackcategorymodel.h + models/guide.cpp + models/guide.h + models/guideblock.cpp + models/guideblock.h + models/guidemodel.cpp + models/guidemodel.h + models/helpmodel.cpp + models/helpmodel.h + models/keys.cpp + models/keys.h + models/licensemodel.cpp + models/licensemodel.h + models/server.cpp + models/server.h + models/servercity.cpp + models/servercity.h + models/servercountry.cpp + models/servercountry.h + models/servercountrymodel.cpp + models/servercountrymodel.h + models/serverdata.cpp + models/serverdata.h + models/supportcategorymodel.cpp + models/supportcategorymodel.h + models/survey.cpp + models/survey.h + models/surveymodel.cpp + models/surveymodel.h + models/tutorial.cpp + models/tutorial.h + models/tutorialstep.cpp + models/tutorialstep.h + models/tutorialstepbefore.cpp + models/tutorialstepbefore.h + models/tutorialstepnext.cpp + models/tutorialstepnext.h + models/tutorialmodel.cpp + models/tutorialmodel.h + models/user.cpp + models/user.h + models/whatsnewmodel.cpp + models/whatsnewmodel.h + mozillavpn.cpp + mozillavpn.h + networkmanager.cpp + networkmanager.h + networkrequest.cpp + networkrequest.h + networkwatcher.cpp + networkwatcher.h + networkwatcherimpl.h + notificationhandler.cpp + notificationhandler.h + pinghelper.cpp + pinghelper.h + pingsender.cpp + pingsender.h + pingsenderfactory.cpp + pingsenderfactory.h + platforms/dummy/dummyapplistprovider.cpp + platforms/dummy/dummyapplistprovider.h + platforms/dummy/dummyiaphandler.cpp + platforms/dummy/dummyiaphandler.h + platforms/dummy/dummynetworkwatcher.cpp + platforms/dummy/dummynetworkwatcher.h + platforms/dummy/dummypingsender.cpp + platforms/dummy/dummypingsender.h + qmlengineholder.cpp + qmlengineholder.h + releasemonitor.cpp + releasemonitor.h + rfc/rfc1112.cpp + rfc/rfc1112.h + rfc/rfc1918.cpp + rfc/rfc1918.h + rfc/rfc4193.cpp + rfc/rfc4193.h + rfc/rfc4291.cpp + rfc/rfc4291.h + rfc/rfc5735.cpp + rfc/rfc5735.h + serveri18n.cpp + serveri18n.h + settingsholder.cpp + settingsholder.h + simplenetworkmanager.cpp + simplenetworkmanager.h + statusicon.cpp + statusicon.h + task.h + taskscheduler.cpp + taskscheduler.h + tasks/account/taskaccount.cpp + tasks/account/taskaccount.h + tasks/adddevice/taskadddevice.cpp + tasks/adddevice/taskadddevice.h + tasks/authenticate/taskauthenticate.cpp + tasks/authenticate/taskauthenticate.h + tasks/captiveportallookup/taskcaptiveportallookup.cpp + tasks/captiveportallookup/taskcaptiveportallookup.h + tasks/deleteaccount/taskdeleteaccount.cpp + tasks/deleteaccount/taskdeleteaccount.h + tasks/getfeaturelist/taskgetfeaturelist.cpp + tasks/getfeaturelist/taskgetfeaturelist.h + tasks/controlleraction/taskcontrolleraction.cpp + tasks/controlleraction/taskcontrolleraction.h + tasks/createsupportticket/taskcreatesupportticket.cpp + tasks/createsupportticket/taskcreatesupportticket.h + tasks/function/taskfunction.cpp + tasks/function/taskfunction.h + tasks/group/taskgroup.cpp + tasks/group/taskgroup.h + tasks/heartbeat/taskheartbeat.cpp + tasks/heartbeat/taskheartbeat.h + tasks/ipfinder/taskipfinder.cpp + tasks/ipfinder/taskipfinder.h + tasks/products/taskproducts.cpp + tasks/products/taskproducts.h + tasks/release/taskrelease.cpp + tasks/release/taskrelease.h + tasks/removedevice/taskremovedevice.cpp + tasks/removedevice/taskremovedevice.h + tasks/sendfeedback/tasksendfeedback.cpp + tasks/sendfeedback/tasksendfeedback.h + tasks/servers/taskservers.cpp + tasks/servers/taskservers.h + tasks/surveydata/tasksurveydata.cpp + tasks/surveydata/tasksurveydata.h + telemetry.cpp + telemetry.h + theme.cpp + theme.h + timersingleshot.cpp + timersingleshot.h + update/updater.cpp + update/updater.h + update/versionapi.cpp + update/versionapi.h + urlopener.cpp + urlopener.h +) + +# VPN Client UI resources +target_sources(mozillavpn PRIVATE + ui/guides.qrc + ui/license.qrc + ui/resources.qrc + ui/tutorials.qrc + ui/ui.qrc + resources/certs/certs.qrc +) + +# Signal handling for unix platforms +if(UNIX) + target_sources(mozillavpn PRIVATE + signalhandler.cpp + signalhandler.h + ) +endif() + +# Sources for desktop platforms. +if(NOT CMAKE_CROSSCOMPILING) + target_sources(mozillavpn PRIVATE + systemtraynotificationhandler.cpp + systemtraynotificationhandler.h + tasks/authenticate/desktopauthenticationlistener.cpp + tasks/authenticate/desktopauthenticationlistener.h + ) +endif() \ No newline at end of file diff --git a/src/cmake/windows.cmake b/src/cmake/windows.cmake new file mode 100644 index 0000000000..a801b8fd7c --- /dev/null +++ b/src/cmake/windows.cmake @@ -0,0 +1,119 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +add_definitions(-DWIN32_LEAN_AND_MEAN) + +set_target_properties(mozillavpn PROPERTIES + OUTPUT_NAME "Mozilla VPN" + VERSION ${CMAKE_PROJECT_VERSION} + WIN32_EXECUTABLE ON +) + +# Generate the Windows version resource file. +configure_file(../windows/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +target_sources(mozillavpn PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/version.rc) + +# Windows platform source files +target_sources(mozillavpn PRIVATE + commands/commandcrashreporter.cpp + commands/commandcrashreporter.h + daemon/daemon.cpp + daemon/daemon.h + daemon/daemonlocalserver.cpp + daemon/daemonlocalserver.h + daemon/daemonlocalserverconnection.cpp + daemon/daemonlocalserverconnection.h + daemon/dnsutils.h + daemon/interfaceconfig.h + daemon/iputils.h + daemon/wireguardutils.h + eventlistener.cpp + eventlistener.h + localsocketcontroller.cpp + localsocketcontroller.h + platforms/windows/windowsapplistprovider.cpp + platforms/windows/windowsapplistprovider.h + platforms/windows/windowsappimageprovider.cpp + platforms/windows/windowsappimageprovider.h + platforms/windows/daemon/dnsutilswindows.cpp + platforms/windows/daemon/dnsutilswindows.h + platforms/windows/daemon/windowsdaemon.cpp + platforms/windows/daemon/windowsdaemon.h + platforms/windows/daemon/windowsdaemonserver.cpp + platforms/windows/daemon/windowsdaemonserver.h + platforms/windows/daemon/windowsdaemontunnel.cpp + platforms/windows/daemon/windowsdaemontunnel.h + platforms/windows/daemon/windowsroutemonitor.cpp + platforms/windows/daemon/windowsroutemonitor.h + platforms/windows/daemon/windowstunnellogger.cpp + platforms/windows/daemon/windowstunnellogger.h + platforms/windows/daemon/windowstunnelservice.cpp + platforms/windows/daemon/windowstunnelservice.h + platforms/windows/daemon/wireguardutilswindows.cpp + platforms/windows/daemon/wireguardutilswindows.h + platforms/windows/daemon/windowsfirewall.cpp + platforms/windows/daemon/windowsfirewall.h + platforms/windows/daemon/windowssplittunnel.cpp + platforms/windows/daemon/windowssplittunnel.h + platforms/windows/windowsservicemanager.cpp + platforms/windows/windowsservicemanager.h + platforms/windows/windowscommons.cpp + platforms/windows/windowscommons.h + platforms/windows/windowscryptosettings.cpp + platforms/windows/windowsnetworkwatcher.cpp + platforms/windows/windowsnetworkwatcher.h + platforms/windows/windowspingsender.cpp + platforms/windows/windowspingsender.h + platforms/windows/windowsstartatbootwatcher.cpp + platforms/windows/windowsstartatbootwatcher.h + wgquickprocess.cpp + wgquickprocess.h +) + +# Windows Qt6 UI workaround resources +if(${QT_VERSION} VERSION_GREATER_EQUAL 6.3.0) + message(WARNING "Remove the Qt6 windows hack!") +else() + target_sources(mozillavpn PRIVATE ui/qt6winhack.qrc) +endif() + +include(cmake/golang.cmake) + +# Enable Balrog for update support. +add_definitions(-DMVPN_BALROG) +add_go_library(mozillavpn ../balrog/balrog-api.go) +target_sources(mozillavpn PRIVATE + update/balrog.cpp + update/balrog.h +) + +install(TARGETS mozillavpn DESTINATION .) +install(FILES $ DESTINATION . OPTIONAL) + +## Deploy Qt runtime dependencies during installation. +get_target_property(QT_QMLLINT_EXECUTABLE Qt6::qmllint LOCATION) +get_filename_component(QT_TOOL_PATH ${QT_QMLLINT_EXECUTABLE} PATH) +find_program(QT_WINDEPLOY_EXECUTABLE + NAMES windeployqt + PATHS ${QT_TOOL_PATH} + NO_DEFAULT_PATH) +set(WINDEPLOYQT_FLAGS "--verbose 1 --no-translations --compiler-runtime --dir . --plugindir plugins") +install(CODE "execute_process(COMMAND \"${QT_WINDEPLOY_EXECUTABLE}\" \"$\" ${WINDEPLOYQT_FLAGS} WORKING_DIRECTORY \${CMAKE_INSTALL_PREFIX})") + +# Use the merge module that comes with our version of Visual Studio +cmake_path(CONVERT "$ENV{VCToolsRedistDir}" TO_CMAKE_PATH_LIST VC_TOOLS_REDIST_PATH) +install(FILES ${VC_TOOLS_REDIST_PATH}/MergeModules/Microsoft_VC${MSVC_TOOLSET_VERSION}_CRT_x64.msm + DESTINATION . RENAME Microsoft_CRT_x64.msm) + +install(FILES ui/resources/logo.ico DESTINATION .) + +## TODO: Are these still needed? It's not clear. +#libssl.files = $$PWD/../../../libssl-1_1-x64.dll +#libssl.path = $$PWD/../../../unsigned/ +#INSTALLS += libssl +# +#libcrypto.files = $$PWD/../../../libcrypto-1_1-x64.dll +#libcrypto.path = $$PWD/../../../unsigned/ +#INSTALLS += libcrypto +# diff --git a/src/command.cpp b/src/command.cpp index e4df8d68ce..31e01af1ad 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -118,13 +118,13 @@ int Command::runCommandLineApp(std::function&& a_callback) { FeatureList::instance()->initialize(); qInstallMessageHandler(LogHandler::messageQTHandler); - logger.info() << "MozillaVPN" << APP_VERSION; + logger.info() << "MozillaVPN" << Constants::versionString(); logger.info() << "User-Agent:" << NetworkManager::userAgent(); QCoreApplication app(CommandLineParser::argc(), CommandLineParser::argv()); QCoreApplication::setApplicationName("Mozilla VPN"); - QCoreApplication::setApplicationVersion(APP_VERSION); + QCoreApplication::setApplicationVersion(Constants::versionString()); Localizer localizer; SimpleNetworkManager snm; @@ -146,13 +146,13 @@ int Command::runGuiApp(std::function&& a_callback) { qInstallMessageHandler(LogHandler::messageQTHandler); - logger.info() << "MozillaVPN" << APP_VERSION; + logger.info() << "MozillaVPN" << Constants::versionString(); logger.info() << "User-Agent:" << NetworkManager::userAgent(); QApplication app(CommandLineParser::argc(), CommandLineParser::argv()); QCoreApplication::setApplicationName("Mozilla VPN"); - QCoreApplication::setApplicationVersion(APP_VERSION); + QCoreApplication::setApplicationVersion(Constants::versionString()); Localizer localizer; SimpleNetworkManager snm; @@ -181,7 +181,7 @@ int Command::runQmlApp(std::function&& a_callback) { qInstallMessageHandler(LogHandler::messageQTHandler); - logger.info() << "MozillaVPN" << APP_VERSION; + logger.info() << "MozillaVPN" << Constants::versionString(); logger.info() << "User-Agent:" << NetworkManager::userAgent(); #ifdef MVPN_WINDOWS @@ -210,7 +210,7 @@ int Command::runQmlApp(std::function&& a_callback) { QApplication app(CommandLineParser::argc(), CommandLineParser::argv()); QCoreApplication::setApplicationName("Mozilla VPN"); - QCoreApplication::setApplicationVersion(APP_VERSION); + QCoreApplication::setApplicationVersion(Constants::versionString()); Localizer localizer; diff --git a/src/commandlineparser.cpp b/src/commandlineparser.cpp index 84f893a013..4037f1cd02 100644 --- a/src/commandlineparser.cpp +++ b/src/commandlineparser.cpp @@ -5,6 +5,7 @@ #include "commandlineparser.h" #include "command.h" #include "commands/commandui.h" +#include "constants.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" @@ -70,7 +71,7 @@ int CommandLineParser::parse(int argc, char* argv[]) { if (versionOption.m_set) { QTextStream stream(stdout); - stream << argv[0] << " " << APP_VERSION << Qt::endl; + stream << argv[0] << " " << Constants::versionString() << Qt::endl; return 0; } diff --git a/src/commands/commandui.cpp b/src/commands/commandui.cpp index 6e8e60e03e..6930670713 100644 --- a/src/commands/commandui.cpp +++ b/src/commands/commandui.cpp @@ -191,6 +191,7 @@ int CommandUI::run(QStringList& tokens) { Glean::Initialize(engine); Lottie::initialize(engine, QString(NetworkManager::userAgent())); Nebula::Initialize(engine); + L18nStrings::initialize(); MozillaVPN vpn; vpn.setStartMinimized(minimizedOption.m_set); diff --git a/src/constants.cpp b/src/constants.cpp index b3c9861273..dff6f9cfe7 100644 --- a/src/constants.cpp +++ b/src/constants.cpp @@ -4,6 +4,9 @@ #include "constants.h" #include "settingsholder.h" +#ifndef BUILD_QMAKE +# include "version.h" +#endif #include #include @@ -24,3 +27,7 @@ void Constants::setStaging() { s_stagingServerAddress = SettingsHolder::instance()->stagingServerAddress(); Q_ASSERT(!s_stagingServerAddress.isEmpty()); } + +QString Constants::versionString() { return QStringLiteral(APP_VERSION); } + +QString Constants::buildNumber() { return QStringLiteral(BUILD_ID); } diff --git a/src/constants.h b/src/constants.h index 30d605293d..ebdfd96bd0 100644 --- a/src/constants.h +++ b/src/constants.h @@ -16,6 +16,10 @@ bool inProduction(); const QString& getStagingServerAddress(); void setStaging(); +// Project version and build strings. +QString versionString(); +QString buildNumber(); + // Number of msecs for the captive-portal block alert. constexpr uint32_t CAPTIVE_PORTAL_ALERT_MSEC = 4000; diff --git a/src/crashreporter/CMakeLists.txt b/src/crashreporter/CMakeLists.txt new file mode 100644 index 0000000000..c7403a76e3 --- /dev/null +++ b/src/crashreporter/CMakeLists.txt @@ -0,0 +1,54 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +add_library(crashreporter STATIC) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) +find_package(Qt6 REQUIRED COMPONENTS Qml Quick) +target_include_directories(crashreporter PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(crashreporter PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Qml + Qt6::Quick +) +target_link_libraries(crashreporter PRIVATE nebula translations) + +# Crash reporter source files +target_sources(crashreporter PRIVATE + crashclient.cpp + crashclient.h + crashreporter.cpp + crashreporter.h + crashreporterapp.cpp + crashreporterapp.h + crashreporterfactory.cpp + crashreporterfactory.h + crashserverclient.cpp + crashserverclient.h + crashserverclientfactory.cpp + crashserverclientfactory.h + crashui.cpp + crashui.h + crashuploader.cpp + crashuploader.h +) + +# Crash reporter UI resources +target_sources(crashreporter PRIVATE + crash_resources.qrc + crashui.qrc +) + +# Windows Crash reporter sources +if(WIN32) + target_sources(crashreporter PRIVATE + platforms/windows/wincrashreporter.cpp + platforms/windows/windowscrashclient.cpp + platforms/windows/wincrashreporter.h + platforms/windows/windowscrashclient.h + ) +endif() diff --git a/src/crashreporter/crashuploader.cpp b/src/crashreporter/crashuploader.cpp index b112521727..f9b131730f 100644 --- a/src/crashreporter/crashuploader.cpp +++ b/src/crashreporter/crashuploader.cpp @@ -82,7 +82,7 @@ void CrashUploader::startRequest(const QString& file) { QHttpPart versionPart; versionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"Version\""); - versionPart.setBody(APP_VERSION); + versionPart.setBody(Constants::versionString().toUtf8()); multipart->append(versionPart); multipart->append(namePart); multipart->append(formPart); diff --git a/src/inspector/inspectorhandler.cpp b/src/inspector/inspectorhandler.cpp index a33a8fc0e7..55506ac7fc 100644 --- a/src/inspector/inspectorhandler.cpp +++ b/src/inspector/inspectorhandler.cpp @@ -947,7 +947,7 @@ bool InspectorHandler::mockFreeTrial() { return s_mockFreeTrial; } // static QString InspectorHandler::appVersionForUpdate() { if (s_updateVersion.isEmpty()) { - return APP_VERSION; + return Constants::versionString(); } return s_updateVersion; diff --git a/src/localizer.cpp b/src/localizer.cpp index 3a0fd34069..3e4a97d3f1 100644 --- a/src/localizer.cpp +++ b/src/localizer.cpp @@ -214,7 +214,10 @@ QHash Localizer::roleNames() const { return roles; } -int Localizer::rowCount(const QModelIndex&) const { +int Localizer::rowCount(const QModelIndex& index) const { + if (!index.isValid()) { + return 0; + } return m_languages.count(); } diff --git a/src/models/device.cpp b/src/models/device.cpp index 5f2ba4ddfe..5a244cc001 100644 --- a/src/models/device.cpp +++ b/src/models/device.cpp @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "device.h" +#include "constants.h" #include "keys.h" #include "leakdetector.h" @@ -71,8 +72,8 @@ QString Device::currentDeviceReport() { out << "SDK Version -> " << AndroidUtils::GetSDKVersion() << Qt::endl; #endif - out << "APP Version -> " << APP_VERSION << Qt::endl; - out << "Build ID -> " << BUILD_ID << Qt::endl; + out << "APP Version -> " << Constants::versionString() << Qt::endl; + out << "Build ID -> " << Constants::buildNumber() << Qt::endl; out << "Device ID -> " << uniqueDeviceId() << Qt::endl; #ifdef MVPN_WINDOWS diff --git a/src/models/feature.cpp b/src/models/feature.cpp index b7db07cc19..9f952529e7 100644 --- a/src/models/feature.cpp +++ b/src/models/feature.cpp @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "feature.h" +#include "constants.h" #include "l18nstrings.h" #include "logger.h" #include "settingsholder.h" @@ -43,7 +44,7 @@ Feature::Feature(const QString& id, const QString& name, bool isMajor, s_features->insert(m_id, this); auto releaseVersion = VersionApi::stripMinor(aReleaseVersion); - auto currentVersion = VersionApi::stripMinor(APP_VERSION); + auto currentVersion = VersionApi::stripMinor(Constants::versionString()); auto cmp = VersionApi::compareVersions(releaseVersion, currentVersion); if (cmp == -1) { diff --git a/src/mozillavpn.cpp b/src/mozillavpn.cpp index 1fd5c6b441..6001e3093b 100644 --- a/src/mozillavpn.cpp +++ b/src/mozillavpn.cpp @@ -676,7 +676,7 @@ void MozillaVPN::deviceAdded(const QString& deviceName, settingsHolder->setPublicKey(publicKey); m_private->m_keys.storeKeys(privateKey, publicKey); - settingsHolder->setDeviceKeyVersion(APP_VERSION); + settingsHolder->setDeviceKeyVersion(Constants::versionString()); } void MozillaVPN::deviceRemoved(const QString& publicKey) { diff --git a/src/mozillavpn.h b/src/mozillavpn.h index ed43b86fad..77097ad988 100644 --- a/src/mozillavpn.h +++ b/src/mozillavpn.h @@ -271,8 +271,8 @@ class MozillaVPN final : public QObject { void silentSwitch(); - static QString versionString() { return QString(APP_VERSION); } - static QString buildNumber() { return QString(BUILD_ID); } + static QString versionString() { return Constants::versionString(); } + static QString buildNumber() { return Constants::buildNumber(); } static QString osVersion() { #ifdef MVPN_WINDOWS return WindowsCommons::WindowsVersion(); diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp index 9609d0a109..b2e4ea35b9 100644 --- a/src/networkmanager.cpp +++ b/src/networkmanager.cpp @@ -82,7 +82,8 @@ QByteArray NetworkManager::userAgent() { #endif QTextStream out(&userAgent); - out << "MozillaVPN/" << APP_VERSION << " (" << flags.join("; ") << ")"; + out << "MozillaVPN/" << Constants::versionString() << " (" + << flags.join("; ") << ")"; } return userAgent; diff --git a/src/platforms/linux/daemon/dbusservice.h b/src/platforms/linux/daemon/dbusservice.h index a9000c585f..41d6eddf35 100644 --- a/src/platforms/linux/daemon/dbusservice.h +++ b/src/platforms/linux/daemon/dbusservice.h @@ -35,6 +35,7 @@ class DBusService final : public Daemon { QString version(); QString getLogs(); + void cleanupLogs() { cleanLogs(); } QString runningApps(); bool firewallApp(const QString& appName, const QString& state); diff --git a/src/platforms/linux/daemon/wireguardutilslinux.cpp b/src/platforms/linux/daemon/wireguardutilslinux.cpp index 521cfb615d..2d2a126501 100644 --- a/src/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/src/platforms/linux/daemon/wireguardutilslinux.cpp @@ -28,7 +28,7 @@ extern "C" { #endif #include "../../3rdparty/wireguard-tools/contrib/embeddable-wg-library/wireguard.h" -#include "../../linux/netfilter/netfilter.h" +#include "netfilter.h" #if defined(__cplusplus) } #endif diff --git a/src/platforms/macos/daemon/macosdaemonserver.cpp b/src/platforms/macos/daemon/macosdaemonserver.cpp index f0bd33d6e4..5eac350bf8 100644 --- a/src/platforms/macos/daemon/macosdaemonserver.cpp +++ b/src/platforms/macos/daemon/macosdaemonserver.cpp @@ -4,6 +4,7 @@ #include "macosdaemonserver.h" #include "commandlineparser.h" +#include "constants.h" #include "daemon/daemonlocalserver.h" #include "leakdetector.h" #include "logger.h" @@ -31,7 +32,7 @@ int MacOSDaemonServer::run(QStringList& tokens) { QCoreApplication app(CommandLineParser::argc(), CommandLineParser::argv()); QCoreApplication::setApplicationName("Mozilla VPN Daemon"); - QCoreApplication::setApplicationVersion(APP_VERSION); + QCoreApplication::setApplicationVersion(Constants::versionString()); if (tokens.length() > 1) { QList options; diff --git a/src/platforms/windows/daemon/windowsdaemonserver.cpp b/src/platforms/windows/daemon/windowsdaemonserver.cpp index db51540761..8894f809df 100644 --- a/src/platforms/windows/daemon/windowsdaemonserver.cpp +++ b/src/platforms/windows/daemon/windowsdaemonserver.cpp @@ -4,6 +4,7 @@ #include "windowsdaemonserver.h" #include "commandlineparser.h" +#include "constants.h" #include "daemon/daemonlocalserver.h" #include "leakdetector.h" #include "logger.h" @@ -62,7 +63,7 @@ int WindowsDaemonServer::run(QStringList& tokens) { QCoreApplication app(CommandLineParser::argc(), CommandLineParser::argv()); QCoreApplication::setApplicationName("Mozilla VPN Daemon"); - QCoreApplication::setApplicationVersion(APP_VERSION); + QCoreApplication::setApplicationVersion(Constants::versionString()); if (tokens.length() > 1) { QList options; diff --git a/src/platforms/windows/daemon/windowsdaemontunnel.cpp b/src/platforms/windows/daemon/windowsdaemontunnel.cpp index 8b9acaff0c..1fb67e3626 100644 --- a/src/platforms/windows/daemon/windowsdaemontunnel.cpp +++ b/src/platforms/windows/daemon/windowsdaemontunnel.cpp @@ -4,6 +4,7 @@ #include "windowsdaemontunnel.h" #include "commandlineparser.h" +#include "constants.h" #include "leakdetector.h" #include "logger.h" #include "platforms/windows/windowscommons.h" @@ -34,7 +35,7 @@ int WindowsDaemonTunnel::run(QStringList& tokens) { QCoreApplication app(CommandLineParser::argc(), CommandLineParser::argv()); QCoreApplication::setApplicationName("Mozilla VPN Tunnel"); - QCoreApplication::setApplicationVersion(APP_VERSION); + QCoreApplication::setApplicationVersion(Constants::versionString()); if (tokens.length() != 2) { logger.error() << "Expected 1 parameter only: the config file."; diff --git a/src/platforms/windows/golang-msvc-fixup.cpp b/src/platforms/windows/golang-msvc-fixup.cpp new file mode 100644 index 0000000000..18b478d4d3 --- /dev/null +++ b/src/platforms/windows/golang-msvc-fixup.cpp @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#pragma comment(lib, "legacy_stdio_definitions.lib") + +extern "C" { +FILE __iob_func[3] = {*stdin, *stdout, *stderr}; +} diff --git a/src/platforms/windows/golang-msvc-types.h b/src/platforms/windows/golang-msvc-types.h new file mode 100644 index 0000000000..f08775f8f3 --- /dev/null +++ b/src/platforms/windows/golang-msvc-types.h @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file was generated by `go build -buildmode=c-archve example.go` and +// tweaked to make MSVC happy. Otherwise, only minimal changes have been +// made to keep its original structure intact. + +#ifndef GO_CGO_EXPORT_PROLOGUE_H +#define GO_CGO_EXPORT_PROLOGUE_H + +#ifndef GO_CGO_GOSTRING_TYPEDEF +typedef struct { const char *p; ptrdiff_t n; } _GoString_; +#endif + +#endif + +#ifndef GO_CGO_PROLOGUE_H +#define GO_CGO_PROLOGUE_H + +typedef signed char GoInt8; +typedef unsigned char GoUint8; +typedef short GoInt16; +typedef unsigned short GoUint16; +typedef int GoInt32; +typedef unsigned int GoUint32; +typedef long long GoInt64; +typedef unsigned long long GoUint64; +typedef GoInt64 GoInt; +typedef GoUint64 GoUint; +typedef size_t GoUintptr; +typedef float GoFloat32; +typedef double GoFloat64; + +// Complex types are not supported by MSVC compilers. +//typedef _Fcomplex GoComplex64; +//typedef _Dcomplex GoComplex128; + +/* + static assertion to make sure the file is being used on architecture + at least with matching size of GoInt. +*/ +typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; + +#ifndef GO_CGO_GOSTRING_TYPEDEF +typedef _GoString_ GoString; +#endif +typedef void *GoMap; +typedef void *GoChan; +typedef struct { void *t; void *v; } GoInterface; +typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; + +#endif diff --git a/src/qmake/balrog.pri b/src/qmake/balrog.pri index 48b5baeb0d..20dd6ba749 100644 --- a/src/qmake/balrog.pri +++ b/src/qmake/balrog.pri @@ -11,6 +11,7 @@ macos|win* { # TODO: remove this if-stmt !win* { - GO_MODULES = $$PWD/../../balrog/api.go + INCLUDEPATH += $$PWD/../../balrog + GO_MODULES = $$PWD/../../balrog/balrog-api.go } } diff --git a/src/qmake/golang.pri b/src/qmake/golang.pri index 5abeeb5fd9..c3606e61c9 100644 --- a/src/qmake/golang.pri +++ b/src/qmake/golang.pri @@ -20,21 +20,20 @@ godeps.CONFIG = no_link ## Build the Go module into a callable C library golibs.input = GO_MODULES -golibs.output = ${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.a +golibs.output = ${QMAKE_FILE_IN_PATH}/${QMAKE_FILE_BASE}.a golibs.commands = @echo Building ${QMAKE_FILE_IN} \ && cd ${QMAKE_FILE_PATH} \ && GOCACHE=$${GOCACHE} go build -buildmode=c-archive $${GOFLAGS} -v -o ${QMAKE_FILE_BASE}.a ${QMAKE_FILE_BASE}.go -golibs.clean = ${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.a +golibs.clean = ${QMAKE_FILE_OUT} golibs.variable_out = LIBS golibs.depends += ${QMAKE_FILE_PATH}/vendor/modules.txt -golibs.CONFIG += target_predeps ## Dummy compiler for the library header file gohdr.input = GO_MODULES -gohdr.output = ${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.h +gohdr.output = ${QMAKE_FILE_IN_PATH}/${QMAKE_FILE_BASE}.h gohdr.commands = $$escape_expand(\\n) # force creation of rule -gohdr.clean = ${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.h -gohdr.depends += ${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.a +gohdr.clean = ${QMAKE_FILE_OUT} +gohdr.depends += ${QMAKE_FILE_IN_PATH}/${QMAKE_FILE_BASE}.a gohdr.variable_out = HEADERS ## Add Go compiler tools to qmake diff --git a/src/qmake/includes_and_defines.pri b/src/qmake/includes_and_defines.pri index 990b0c86fa..1e33c1d9bd 100644 --- a/src/qmake/includes_and_defines.pri +++ b/src/qmake/includes_and_defines.pri @@ -5,6 +5,7 @@ include($$PWD/../../version.pri) DEFINES += APP_VERSION=\\\"$$VERSION\\\" DEFINES += BUILD_ID=\\\"$$BUILD_ID\\\" +DEFINES += BUILD_QMAKE !isEmpty(MVPN_EXTRA_USERAGENT) { DEFINES += MVPN_EXTRA_USERAGENT=\\\"$$MVPN_EXTRA_USERAGENT\\\" @@ -17,10 +18,6 @@ INCLUDEPATH += \ hacl-star \ hacl-star/kremlin \ hacl-star/kremlin/minimal \ - ../translations/generated \ - ../glean \ - ../lottie/lib \ - ../nebula + ../lottie/lib -INCLUDEPATH += ../lottie/lib DEPENDPATH += $${INCLUDEPATH} diff --git a/src/qmake/platforms/linux.pri b/src/qmake/platforms/linux.pri index 06f943a8f3..3741cee06c 100644 --- a/src/qmake/platforms/linux.pri +++ b/src/qmake/platforms/linux.pri @@ -89,6 +89,7 @@ isEmpty(ETCPATH) { DBUS_ADAPTORS += platforms/linux/daemon/org.mozilla.vpn.dbus.xml DBUS_INTERFACES = platforms/linux/daemon/org.mozilla.vpn.dbus.xml +INCLUDEPATH += $$PWD/../../../linux/netfilter GO_MODULES = $$PWD/../../../linux/netfilter/netfilter.go target.path = $${USRPATH}/bin @@ -139,28 +140,28 @@ systemd_service.files = $$PWD/../../../linux/mozillavpn.service systemd_service.path = $${USRPATH}/lib/systemd/system INSTALLS += systemd_service -ORIG_MOZILLAVPN_JSON = $$PWD/../../../extension/manifests/linux/mozillavpn.json -manifestFile.input = ORIG_MOZILLAVPN_JSON -manifestFile.output = $${OBJECTS_DIR}/mozillavpn.json -manifestFile.commands = @python3 -c \'with open(\"$$ORIG_MOZILLAVPN_JSON\") as fin, open(\"$$manifestFile.output\", \"w\") as fout: print(\"\".join(fin).replace(\"/usr/lib/\", \"$${LIBPATH}/\"), end=\"\", file=fout)\' +WEBEXT_MANIFEST_TEMPLATE = $$PWD/../../../extension/manifests/linux/mozillavpn.json.in +WEBEXT_INSTALL_LIBDIR = $$system(echo "$${LIBPATH}" | sed -e 's/^[/]*//') ## Strip leading slashes +manifestFile.input = WEBEXT_MANIFEST_TEMPLATE +manifestFile.output = $${OBJECTS_DIR}/${QMAKE_FILE_IN_BASE} +manifestFile.commands = @echo Building ${QMAKE_FILE_OUT} && \ + python3 $$PWD/../../../scripts/utils/make_template.py ${QMAKE_FILE_IN} \ + -o ${QMAKE_FILE_OUT} -k @WEBEXT_INSTALL_LIBDIR@=$${WEBEXT_INSTALL_LIBDIR} manifestFile.CONFIG = target_predeps no_link QMAKE_EXTRA_COMPILERS += manifestFile -manifestFirefox.files = $$manifestFile.output +manifestFirefox.files = $${OBJECTS_DIR}/mozillavpn.json manifestFirefox.path = $${LIBPATH}/mozilla/native-messaging-hosts -manifestFirefox.depends = $$manifestFile.output manifestFirefox.CONFIG = no_check_exist INSTALLS += manifestFirefox -manifestChrome.files = $$manifestFile.output +manifestChrome.files = $${OBJECTS_DIR}/mozillavpn.json manifestChrome.path = $${ETCPATH}/opt/chrome/native-messaging-hosts -manifestChrome.depends = $$manifestFile.output manifestChrome.CONFIG = no_check_exist INSTALLS += manifestChrome -manifestChromium.files = $$manifestFile.output +manifestChromium.files = $${OBJECTS_DIR}/mozillavpn.json manifestChromium.path = $${ETCPATH}/chromium/native-messaging-hosts -manifestChromium.depends = $$manifestFile.output manifestChromium.CONFIG = no_check_exist INSTALLS += manifestChromium diff --git a/src/qmake/sources.pri b/src/qmake/sources.pri index da4126e161..06be916340 100644 --- a/src/qmake/sources.pri +++ b/src/qmake/sources.pri @@ -60,7 +60,6 @@ SOURCES += \ ipaddress.cpp \ ipaddresslookup.cpp \ itempicker.cpp \ - l18nstringsimpl.cpp \ leakdetector.cpp \ localizer.cpp \ logger.cpp \ diff --git a/src/qmake/translations.pri b/src/qmake/translations.pri deleted file mode 100644 index 09d8589cd7..0000000000 --- a/src/qmake/translations.pri +++ /dev/null @@ -1,35 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# This file contains anything related to the translations and the strings generation - -# TODO: move the string generation here. -exists($$PWD/../../translations/generated/l18nstrings.h) { - SOURCES += $$PWD/../../translations/generated/l18nstrings_p.cpp - HEADERS += $$PWD/../../translations/generated/l18nstrings.h -} else { - error("No l18nstrings.h. Have you generated the strings?") -} - -# TODO: move the language import here -exists($$PWD/../../translations/translations.pri) { - include($$PWD/../../translations/translations.pri) -} else { - message(Languages were not imported - using fallback English) - TRANSLATIONS += \ - $$PWD/../../translations/en/mozillavpn_en.ts - - ts.commands += lupdate $$PWD -no-obsolete -ts $$PWD/../../translations/en/mozillavpn_en.ts - ts.CONFIG += no_check_exist - ts.output = $$PWD/../../translations/en/mozillavpn_en.ts - ts.input = $$PWD/.. - QMAKE_EXTRA_TARGETS += ts - PRE_TARGETDEPS += ts -} - -QMAKE_LRELEASE_FLAGS += -idbased -CONFIG += lrelease -CONFIG += embed_translations - -RESOURCES += $$PWD/../../translations/servers.qrc diff --git a/src/src.pro b/src/src.pro index 6dddb7c71f..22d6bde54c 100644 --- a/src/src.pro +++ b/src/src.pro @@ -10,18 +10,21 @@ TEMPLATE = app include($$PWD/qmake/balrog.pri) -include($$PWD/qmake/ccache.pri) include($$PWD/qmake/debug.pri) -include($$PWD/qmake/golang.pri) include($$PWD/qmake/includes_and_defines.pri) include($$PWD/qmake/qt.pri) -include($$PWD/qmake/translations.pri) include($$PWD/qmake/webextension.pri) include($$PWD/../glean/glean.pri) include($$PWD/../nebula/nebula.pri) include($$PWD/../lottie/lottie.pri) +include($$PWD/../translations/translations.pri) include($$PWD/crashreporter/crashreporter.pri) +unix { + include($$PWD/qmake/ccache.pri) + include($$PWD/qmake/golang.pri) +} + # Cross-platform entries go in here: include($$PWD/qmake/sources.pri) diff --git a/src/telemetry.cpp b/src/telemetry.cpp index 1477b36df3..7b19bb2558 100644 --- a/src/telemetry.cpp +++ b/src/telemetry.cpp @@ -7,7 +7,7 @@ #include "logger.h" #include "mozillavpn.h" -#include "../../glean/telemetry/gleansample.h" +#include "telemetry/gleansample.h" constexpr int CONNECTION_STABILITY_MSEC = 45000; diff --git a/src/update/balrog.cpp b/src/update/balrog.cpp index 8c41c13f53..d9b4941faa 100644 --- a/src/update/balrog.cpp +++ b/src/update/balrog.cpp @@ -19,36 +19,24 @@ #include #include -typedef struct { - const char* p; - size_t n; -} gostring_t; - -typedef void (*logFunc)(int level, const char* msg); - +// Terrible hacking for Windows #if defined(MVPN_WINDOWS) # include "windows.h" # include "platforms/windows/windowscommons.h" +# include "platforms/windows/golang-msvc-types.h" +#endif -constexpr const char* BALROG_WINDOWS_UA = "WINNT_x86_64"; - -typedef void BalrogSetLogger(logFunc func); -typedef unsigned char BalrogValidate(gostring_t x5uData, gostring_t updateData, - gostring_t signature, gostring_t rootHash, - gostring_t leafCertSubject); - -#elif defined(MVPN_MACOS) -# define EXPORT __attribute__((visibility("default"))) - +// Import balrog C/Go library (unless we are building on Windows with qmake) +#if !(defined(MVPN_WINDOWS) && defined(BUILD_QMAKE)) extern "C" { -EXPORT void balrogSetLogger(logFunc func); -EXPORT unsigned char balrogValidate(gostring_t x5uData, gostring_t updateData, - gostring_t signature, gostring_t rootHash, - gostring_t leafCertSubject); +# include "balrog-api.h" } +#endif +#if defined(MVPN_WINDOWS) +constexpr const char* BALROG_WINDOWS_UA = "WINNT_x86_64"; +#elif defined(MVPN_MACOS) constexpr const char* BALROG_MACOS_UA = "Darwin_x86"; - #else # error Platform not supported yet #endif @@ -189,17 +177,20 @@ bool Balrog::checkSignature(Task* task, const QByteArray& x5uData, bool Balrog::validateSignature(const QByteArray& x5uData, const QByteArray& updateData, const QByteArray& signatureBlob) { -#if defined(MVPN_WINDOWS) +#if defined(MVPN_WINDOWS) && defined(BUILD_QMAKE) + typedef void BalrogSetLogger(GoUintptr func); + typedef GoUint8 BalrogValidate(GoString x5uData, GoString updateData, + GoString signature, GoString rootHash, + GoString leafCertSubject); + static HMODULE balrogDll = nullptr; static BalrogSetLogger* balrogSetLogger = nullptr; static BalrogValidate* balrogValidate = nullptr; if (!balrogDll) { - // This process will be used by the wireguard tunnel. No need to call - // FreeLibrary. balrogDll = LoadLibrary(TEXT("balrog.dll")); if (!balrogDll) { - WindowsCommons::windowsLog("Failed to load tunnel.dll"); + WindowsCommons::windowsLog("Failed to load balrog.dll"); return false; } } @@ -223,27 +214,23 @@ bool Balrog::validateSignature(const QByteArray& x5uData, } #endif - balrogSetLogger(balrogLogger); + balrogSetLogger((GoUintptr)balrogLogger); QByteArray x5uDataCopy = x5uData; - gostring_t x5uDataGo{x5uDataCopy.constData(), (size_t)x5uDataCopy.length()}; + GoString x5uDataGo{x5uDataCopy.constData(), x5uDataCopy.length()}; QByteArray signatureCopy = signatureBlob; - gostring_t signatureGo{signatureCopy.constData(), - (size_t)signatureCopy.length()}; + GoString signatureGo{signatureCopy.constData(), signatureCopy.length()}; QByteArray updateDataCopy = updateData; - gostring_t updateDataGo{updateDataCopy.constData(), - (size_t)updateDataCopy.length()}; + GoString updateDataGo{updateDataCopy.constData(), updateDataCopy.length()}; QByteArray rootHashCopy = Constants::balrogRootCertFingerprint(); rootHashCopy = rootHashCopy.toUpper(); - gostring_t rootHashGo{rootHashCopy.constData(), - (size_t)rootHashCopy.length()}; + GoString rootHashGo{rootHashCopy.constData(), rootHashCopy.length()}; QByteArray certSubjectCopy = BALROG_CERT_SUBJECT_CN; - gostring_t certSubjectGo{certSubjectCopy.constData(), - (size_t)certSubjectCopy.length()}; + GoString certSubjectGo{certSubjectCopy.constData(), certSubjectCopy.length()}; unsigned char verify = balrogValidate(x5uDataGo, updateDataGo, signatureGo, rootHashGo, certSubjectGo); diff --git a/src/update/updater.cpp b/src/update/updater.cpp index 82aea55ec6..a1efd89c6a 100644 --- a/src/update/updater.cpp +++ b/src/update/updater.cpp @@ -54,5 +54,5 @@ QString Updater::appVersion() { if (!Constants::inProduction()) { return InspectorHandler::appVersionForUpdate(); } - return APP_VERSION; + return Constants::versionString(); } diff --git a/src/version.h.in b/src/version.h.in new file mode 100644 index 0000000000..5ef22439e3 --- /dev/null +++ b/src/version.h.in @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef VERSION_H +#define VERSION_H + +#ifndef APP_VERSION +#define APP_VERSION "@CMAKE_PROJECT_VERSION@" +#endif + +#ifndef BUILD_ID +#define BUILD_ID "@BUILD_ID@" +#endif + +#endif // VERSION_H diff --git a/tests/auth/CMakeLists.txt b/tests/auth/CMakeLists.txt new file mode 100644 index 0000000000..78d81271cd --- /dev/null +++ b/tests/auth/CMakeLists.txt @@ -0,0 +1,127 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +add_definitions(-DUNIT_TEST) +add_definitions(-DMVPN_DEBUG) +add_definitions(-DMVPN_DUMMY) + +find_package(Qt6 REQUIRED COMPONENTS Core Test) +find_package(Qt6 REQUIRED COMPONENTS Network NetworkAuth) +find_package(Qt6 REQUIRED COMPONENTS Gui Widgets) +find_package(Qt6 REQUIRED COMPONENTS Qml Quick) + +get_filename_component(MVPN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../src ABSOLUTE) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${MVPN_SOURCE_DIR}) +include_directories(${MVPN_SOURCE_DIR}/hacl-star) +include_directories(${MVPN_SOURCE_DIR}/hacl-star/kremlin) +include_directories(${MVPN_SOURCE_DIR}/hacl-star/kremlin/minimal) + +qt_add_executable(auth_tests EXCLUDE_FROM_ALL) +add_dependencies(build_tests auth_tests) + +target_link_libraries(auth_tests PRIVATE + Qt6::Core + Qt6::Test + Qt6::Network + Qt6::NetworkAuth + Qt6::Gui + Qt6::Widgets + Qt6::Qml + Qt6::Quick +) + +target_link_libraries(auth_tests PRIVATE glean translations) + +# VPN Client source files +target_sources(auth_tests PRIVATE + ${MVPN_SOURCE_DIR}/authenticationinapp/authenticationinapp.cpp + ${MVPN_SOURCE_DIR}/authenticationinapp/authenticationinapp.h + ${MVPN_SOURCE_DIR}/authenticationinapp/authenticationinapplistener.cpp + ${MVPN_SOURCE_DIR}/authenticationinapp/authenticationinapplistener.h + ${MVPN_SOURCE_DIR}/authenticationinapp/authenticationinappsession.cpp + ${MVPN_SOURCE_DIR}/authenticationinapp/authenticationinappsession.h + ${MVPN_SOURCE_DIR}/authenticationinapp/incrementaldecoder.cpp + ${MVPN_SOURCE_DIR}/authenticationinapp/incrementaldecoder.h + ${MVPN_SOURCE_DIR}/authenticationlistener.cpp + ${MVPN_SOURCE_DIR}/authenticationlistener.h + ${MVPN_SOURCE_DIR}/constants.cpp + ${MVPN_SOURCE_DIR}/constants.h + ${MVPN_SOURCE_DIR}/errorhandler.cpp + ${MVPN_SOURCE_DIR}/errorhandler.h + ${MVPN_SOURCE_DIR}/featurelist.cpp + ${MVPN_SOURCE_DIR}/featurelist.h + ${MVPN_SOURCE_DIR}/hawkauth.cpp + ${MVPN_SOURCE_DIR}/hawkauth.h + ${MVPN_SOURCE_DIR}/hkdf.cpp + ${MVPN_SOURCE_DIR}/hkdf.h + ${MVPN_SOURCE_DIR}/inspector/inspectorhandler.h + ${MVPN_SOURCE_DIR}/ipaddress.cpp + ${MVPN_SOURCE_DIR}/ipaddress.h + ${MVPN_SOURCE_DIR}/leakdetector.cpp + ${MVPN_SOURCE_DIR}/leakdetector.h + ${MVPN_SOURCE_DIR}/logger.cpp + ${MVPN_SOURCE_DIR}/logger.h + ${MVPN_SOURCE_DIR}/loghandler.cpp + ${MVPN_SOURCE_DIR}/loghandler.h + ${MVPN_SOURCE_DIR}/models/feature.cpp + ${MVPN_SOURCE_DIR}/models/feature.h + ${MVPN_SOURCE_DIR}/models/server.cpp + ${MVPN_SOURCE_DIR}/models/server.h + ${MVPN_SOURCE_DIR}/mozillavpn.h + ${MVPN_SOURCE_DIR}/networkmanager.cpp + ${MVPN_SOURCE_DIR}/networkmanager.h + ${MVPN_SOURCE_DIR}/networkrequest.cpp + ${MVPN_SOURCE_DIR}/networkrequest.h + ${MVPN_SOURCE_DIR}/rfc/rfc1918.cpp + ${MVPN_SOURCE_DIR}/rfc/rfc1918.h + ${MVPN_SOURCE_DIR}/rfc/rfc4193.cpp + ${MVPN_SOURCE_DIR}/rfc/rfc4193.h + ${MVPN_SOURCE_DIR}/rfc/rfc4291.cpp + ${MVPN_SOURCE_DIR}/rfc/rfc4291.h + ${MVPN_SOURCE_DIR}/rfc/rfc5735.cpp + ${MVPN_SOURCE_DIR}/rfc/rfc5735.h + ${MVPN_SOURCE_DIR}/settingsholder.cpp + ${MVPN_SOURCE_DIR}/settingsholder.h + ${MVPN_SOURCE_DIR}/simplenetworkmanager.cpp + ${MVPN_SOURCE_DIR}/simplenetworkmanager.h + ${MVPN_SOURCE_DIR}/task.h + ${MVPN_SOURCE_DIR}/tasks/authenticate/desktopauthenticationlistener.cpp + ${MVPN_SOURCE_DIR}/tasks/authenticate/desktopauthenticationlistener.h + ${MVPN_SOURCE_DIR}/tasks/authenticate/taskauthenticate.cpp + ${MVPN_SOURCE_DIR}/tasks/authenticate/taskauthenticate.h + ${MVPN_SOURCE_DIR}/tasks/deleteaccount/taskdeleteaccount.cpp + ${MVPN_SOURCE_DIR}/tasks/deleteaccount/taskdeleteaccount.h + ${MVPN_SOURCE_DIR}/tasks/function/taskfunction.cpp + ${MVPN_SOURCE_DIR}/tasks/function/taskfunction.h + ${MVPN_SOURCE_DIR}/update/updater.cpp + ${MVPN_SOURCE_DIR}/update/updater.h + ${MVPN_SOURCE_DIR}/update/versionapi.cpp + ${MVPN_SOURCE_DIR}/update/versionapi.h + ${MVPN_SOURCE_DIR}/urlopener.cpp + ${MVPN_SOURCE_DIR}/urlopener.h +) + +# Auth test mock resources +target_sources(auth_tests PRIVATE + auth.qrc +) + +# Auth test source files +target_sources(auth_tests PRIVATE + main.cpp + version.h + testemailvalidation.cpp + testemailvalidation.h + testpasswordvalidation.cpp + testpasswordvalidation.h + testsignupandin.cpp + testsignupandin.h + mocmozillavpn.cpp + ../unit/mocinspectorhandler.cpp +) + +## Add the tests to be run. +add_test(NAME auth_tests COMMAND auth_tests) diff --git a/tests/auth/auth.pro b/tests/auth/auth.pro index 4f172d8e89..3d32c1c9e3 100644 --- a/tests/auth/auth.pro +++ b/tests/auth/auth.pro @@ -16,6 +16,7 @@ macos { DEFINES += APP_VERSION=\\\"1234\\\" DEFINES += BUILD_ID=\\\"1234\\\" +DEFINES += BUILD_QMAKE DEFINES += QT_DEPRECATED_WARNINGS DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050F00 @@ -33,8 +34,7 @@ INCLUDEPATH += \ ../../src \ ../../src/hacl-star \ ../../src/hacl-star/kremlin \ - ../../src/hacl-star/kremlin/minimal \ - ../../translations/generated + ../../src/hacl-star/kremlin/minimal HEADERS += \ ../../src/authenticationinapp/authenticationinapp.h \ @@ -89,7 +89,6 @@ SOURCES += \ ../../src/hawkauth.cpp \ ../../src/hkdf.cpp \ ../../src/ipaddress.cpp \ - ../../src/l18nstringsimpl.cpp \ ../../src/leakdetector.cpp \ ../../src/logger.cpp \ ../../src/loghandler.cpp \ @@ -115,12 +114,8 @@ SOURCES += \ testpasswordvalidation.cpp \ testsignupandin.cpp -exists($$PWD/../../translations/generated/l18nstrings.h) { - SOURCES += $$PWD/../../translations/generated/l18nstrings_p.cpp - HEADERS += $$PWD/../../translations/generated/l18nstrings.h -} else { - error("No l18nstrings.h. Have you generated the strings?") -} +include($$PWD/../../glean/glean.pri) +include($$PWD/../../translations/translations.pri) win* { QMAKE_CXXFLAGS += -MP -Zc:preprocessor diff --git a/tests/auth/main.cpp b/tests/auth/main.cpp index a746e28a56..edeba823f6 100644 --- a/tests/auth/main.cpp +++ b/tests/auth/main.cpp @@ -18,16 +18,6 @@ #include int main(int argc, char* argv[]) { - QProcessEnvironment pe = QProcessEnvironment::systemEnvironment(); - if (!pe.contains("MVPN_OATHTOOL")) { - qDebug() - << "MVPN_OATHTOOL env not set. Please set it to a valid oathtool app. " - "If you need to install it, please use the following instructions:"; - qDebug() - << "\tpip3 install oathtool\n\tpython3 -m oathtool.generate-script"; - return 1; - } - #ifdef MVPN_DEBUG LeakDetector leakDetector; Q_UNUSED(leakDetector); diff --git a/tests/auth/testsignupandin.cpp b/tests/auth/testsignupandin.cpp index 1eaac5a42e..96ba29aff2 100644 --- a/tests/auth/testsignupandin.cpp +++ b/tests/auth/testsignupandin.cpp @@ -447,18 +447,17 @@ void TestSignUpAndIn::waitForTotpCodes() { aia->state(), AuthenticationInApp::StateVerificationSessionByTotpNeeded); - QProcessEnvironment pe = QProcessEnvironment::systemEnvironment(); - QVERIFY(pe.contains("MVPN_OATHTOOL")); - QString oathtool = pe.value("MVPN_OATHTOOL"); - QString otp; { QProcess process; - process.start(oathtool, QStringList{m_totpSecret}); + process.start("python3", + QStringList{"-m", "oathtool", m_totpSecret}); QVERIFY(process.waitForStarted()); process.closeWriteChannel(); QVERIFY(process.waitForFinished()); + QCOMPARE(process.exitStatus(), QProcess::NormalExit); + QCOMPARE(process.exitCode(), 0); otp = process.readAll().trimmed(); } diff --git a/tests/auth/version.h b/tests/auth/version.h new file mode 100644 index 0000000000..f5117a00a7 --- /dev/null +++ b/tests/auth/version.h @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef VERSION_H +#define VERSION_H + +// Dummy versions used for authentication testing +#define APP_VERSION "1234" +#define BUILD_ID "1234" + +#endif // VERSION_H diff --git a/tests/nativemessaging/CMakeLists.txt b/tests/nativemessaging/CMakeLists.txt new file mode 100644 index 0000000000..56a03cb70b --- /dev/null +++ b/tests/nativemessaging/CMakeLists.txt @@ -0,0 +1,30 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +find_package(Qt6 REQUIRED COMPONENTS Core Network Test) + +qt_add_executable(nativemessaging_tests EXCLUDE_FROM_ALL) +add_dependencies(build_tests nativemessaging_tests) + +target_link_libraries(nativemessaging_tests PRIVATE + Qt6::Core + Qt6::Network + Qt6::Test +) + +# Native messaging test sources +target_sources(nativemessaging_tests PRIVATE + main.cpp + helper.cpp + helper.h + helperserver.cpp + helperserver.h + testbridge.cpp + testbridge.h + testnoop.cpp + testnoop.h +) + +add_dependencies(nativemessaging_tests mozillavpnnp) +add_test(NAME nativemessaging_tests COMMAND nativemessaging_tests $) diff --git a/tests/qml/CMakeLists.txt b/tests/qml/CMakeLists.txt new file mode 100644 index 0000000000..6c4b9968b2 --- /dev/null +++ b/tests/qml/CMakeLists.txt @@ -0,0 +1,95 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +add_definitions(-DQUICK_TEST_SOURCE_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}\") +add_definitions(-DUNIT_TEST) +add_definitions(-DMVPN_DUMMY) + +find_package(Qt6 REQUIRED COMPONENTS Gui Widgets) +find_package(Qt6 REQUIRED COMPONENTS Qml Quick) +find_package(Qt6 REQUIRED COMPONENTS QuickTest) + +get_filename_component(MVPN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../src ABSOLUTE) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${MVPN_SOURCE_DIR}) + +qt_add_executable(qml_tests EXCLUDE_FROM_ALL) +add_dependencies(build_tests qml_tests) + +target_link_libraries(qml_tests PRIVATE + Qt6::Gui + Qt6::Widgets + Qt6::Qml + Qt6::Quick + Qt6::QuickTest +) + +target_link_libraries(qml_tests PRIVATE glean lottie nebula translations) + +# VPN Client source files +target_sources(qml_tests PRIVATE + ${MVPN_SOURCE_DIR}/closeeventhandler.cpp + ${MVPN_SOURCE_DIR}/closeeventhandler.h + ${MVPN_SOURCE_DIR}/constants.cpp + ${MVPN_SOURCE_DIR}/constants.h + ${MVPN_SOURCE_DIR}/featurelist.cpp + ${MVPN_SOURCE_DIR}/featurelist.h + ${MVPN_SOURCE_DIR}/hawkauth.cpp + ${MVPN_SOURCE_DIR}/hawkauth.h + ${MVPN_SOURCE_DIR}/hkdf.cpp + ${MVPN_SOURCE_DIR}/hkdf.h + ${MVPN_SOURCE_DIR}/inspector/inspectorhandler.h + ${MVPN_SOURCE_DIR}/logger.cpp + ${MVPN_SOURCE_DIR}/logger.h + ${MVPN_SOURCE_DIR}/loghandler.cpp + ${MVPN_SOURCE_DIR}/loghandler.h + ${MVPN_SOURCE_DIR}/models/feature.cpp + ${MVPN_SOURCE_DIR}/models/feature.h + ${MVPN_SOURCE_DIR}/models/server.cpp + ${MVPN_SOURCE_DIR}/models/whatsnewmodel.cpp + ${MVPN_SOURCE_DIR}/models/whatsnewmodel.h + ${MVPN_SOURCE_DIR}/mozillavpn.h + ${MVPN_SOURCE_DIR}/networkmanager.cpp + ${MVPN_SOURCE_DIR}/networkmanager.h + ${MVPN_SOURCE_DIR}/networkrequest.cpp + ${MVPN_SOURCE_DIR}/networkrequest.h + ${MVPN_SOURCE_DIR}/settingsholder.cpp + ${MVPN_SOURCE_DIR}/settingsholder.h + ${MVPN_SOURCE_DIR}/theme.cpp + ${MVPN_SOURCE_DIR}/theme.h + ${MVPN_SOURCE_DIR}/update/updater.cpp + ${MVPN_SOURCE_DIR}/update/updater.h + ${MVPN_SOURCE_DIR}/update/versionapi.cpp + ${MVPN_SOURCE_DIR}/update/versionapi.h +) + +# VPN Client UI resources +target_sources(qml_tests PRIVATE + ${MVPN_SOURCE_DIR}/ui/ui.qrc +) + +# QML test source files +target_sources(qml_tests PRIVATE + helper.cpp + helper.h + main.cpp + version.h + mocmozillavpn.cpp + ../unit/mocinspectorhandler.cpp +) + +if(WIN32) + if(${Qt6_VERSION} VERSION_GREATER_EQUAL 6.3.0) + message(WARNING "Remove the Qt6 windows hack!") + else() + target_sources(qml_tests PRIVATE qt6winhack.qrc) + endif() +endif() + +## Add the tests to be run. +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + list(APPEND QML_TEST_ARGS -platform offscreen) +endif() +add_test(NAME qml_tests COMMAND qml_tests ${QML_TEST_ARGS}) diff --git a/tests/qml/qml.pro b/tests/qml/qml.pro index 0d1b06c2ab..36f1023347 100644 --- a/tests/qml/qml.pro +++ b/tests/qml/qml.pro @@ -22,6 +22,7 @@ DEFINES += MVPN_DUMMY # Sets up app and build id which we test for in test_VPNAboutUs DEFINES += APP_VERSION=\\\"QMLTest_AppVersion\\\" DEFINES += BUILD_ID=\\\"QMLTest_BuildID\\\" +DEFINES += BUILD_QMAKE RESOURCES += \ $$PWD/../../src/ui/ui.qrc \ @@ -29,14 +30,12 @@ RESOURCES += \ INCLUDEPATH += \ . \ ../../src \ - ../../translations/generated \ - ../../glean \ - ../../lottie/lib \ - ../../nebula + ../../lottie/lib include($$PWD/../../glean/glean.pri) include($$PWD/../../lottie/lottie.pri) include($$PWD/../../nebula/nebula.pri) +include($$PWD/../../translations/translations.pri) SOURCES += \ helper.cpp \ @@ -48,7 +47,6 @@ SOURCES += \ ../../src/featurelist.cpp \ ../../src/hawkauth.cpp \ ../../src/hkdf.cpp \ - ../../src/l18nstringsimpl.cpp \ ../../src/logger.cpp \ ../../src/loghandler.cpp \ ../../src/models/feature.cpp \ @@ -81,13 +79,6 @@ HEADERS += \ ../../src/update/updater.h \ ../../src/update/versionapi.h \ -exists($$PWD/../../translations/generated/l18nstrings.h) { - SOURCES += $$PWD/../../translations/generated/l18nstrings_p.cpp - HEADERS += $$PWD/../../translations/generated/l18nstrings.h -} else { - error("No l18nstrings.h. Have you generated the strings?") -} - OBJECTS_DIR = .obj MOC_DIR = .moc RCC_DIR = .rcc diff --git a/tests/qml/version.h b/tests/qml/version.h new file mode 100644 index 0000000000..f9ebb044e2 --- /dev/null +++ b/tests/qml/version.h @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef VERSION_H +#define VERSION_H + +// Dummy versions used for authentication testing +#define APP_VERSION "QMLTest_AppVersion" +#define BUILD_ID "QMLTest_BuildID" + +#endif // VERSION_H diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt new file mode 100644 index 0000000000..5cd15f63c9 --- /dev/null +++ b/tests/unit/CMakeLists.txt @@ -0,0 +1,280 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +add_definitions(-DUNIT_TEST) +add_definitions(-DMVPN_ADJUST) + +find_package(Qt6 REQUIRED COMPONENTS Core Network Xml Test) +find_package(Qt6 REQUIRED COMPONENTS Widgets Gui) +find_package(Qt6 REQUIRED COMPONENTS Qml Quick) + +get_filename_component(MVPN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../src ABSOLUTE) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${MVPN_SOURCE_DIR}) +include_directories(${MVPN_SOURCE_DIR}/hacl-star) +include_directories(${MVPN_SOURCE_DIR}/hacl-star/kremlin) +include_directories(${MVPN_SOURCE_DIR}/hacl-star/kremlin/minimal) + +qt_add_executable(unit_tests EXCLUDE_FROM_ALL) +add_dependencies(build_tests unit_tests) + +target_link_libraries(unit_tests PRIVATE + Qt6::Core + Qt6::Xml + Qt6::Network + Qt6::Test + Qt6::Widgets + Qt6::Gui + Qt6::Qml + Qt6::Quick +) + +target_link_libraries(unit_tests PRIVATE glean lottie nebula translations) + +# VPN Client source files +target_sources(unit_tests PRIVATE + ${MVPN_SOURCE_DIR}/adjust/adjustfiltering.cpp + ${MVPN_SOURCE_DIR}/adjust/adjustfiltering.h + ${MVPN_SOURCE_DIR}/adjust/adjustproxypackagehandler.cpp + ${MVPN_SOURCE_DIR}/adjust/adjustproxypackagehandler.h + ${MVPN_SOURCE_DIR}/captiveportal/captiveportal.cpp + ${MVPN_SOURCE_DIR}/captiveportal/captiveportal.h + ${MVPN_SOURCE_DIR}/collator.cpp + ${MVPN_SOURCE_DIR}/collator.h + ${MVPN_SOURCE_DIR}/command.cpp + ${MVPN_SOURCE_DIR}/command.h + ${MVPN_SOURCE_DIR}/commandlineparser.cpp + ${MVPN_SOURCE_DIR}/commandlineparser.h + ${MVPN_SOURCE_DIR}/constants.cpp + ${MVPN_SOURCE_DIR}/constants.h + ${MVPN_SOURCE_DIR}/controller.h + ${MVPN_SOURCE_DIR}/curve25519.cpp + ${MVPN_SOURCE_DIR}/curve25519.h + ${MVPN_SOURCE_DIR}/dnspingsender.cpp + ${MVPN_SOURCE_DIR}/dnspingsender.h + ${MVPN_SOURCE_DIR}/errorhandler.cpp + ${MVPN_SOURCE_DIR}/errorhandler.h + ${MVPN_SOURCE_DIR}/featurelist.cpp + ${MVPN_SOURCE_DIR}/featurelist.h + ${MVPN_SOURCE_DIR}/hacl-star/Hacl_Chacha20.c + ${MVPN_SOURCE_DIR}/hacl-star/Hacl_Chacha20Poly1305_32.c + ${MVPN_SOURCE_DIR}/hacl-star/Hacl_Curve25519_51.c + ${MVPN_SOURCE_DIR}/hacl-star/Hacl_Poly1305_32.c + ${MVPN_SOURCE_DIR}/ipaddress.cpp + ${MVPN_SOURCE_DIR}/ipaddress.h + ${MVPN_SOURCE_DIR}/ipaddresslookup.cpp + ${MVPN_SOURCE_DIR}/ipaddresslookup.h + ${MVPN_SOURCE_DIR}/itempicker.cpp + ${MVPN_SOURCE_DIR}/itempicker.h + ${MVPN_SOURCE_DIR}/inspector/inspectorhandler.h + ${MVPN_SOURCE_DIR}/inspector/inspectorutils.cpp + ${MVPN_SOURCE_DIR}/inspector/inspectorutils.h + ${MVPN_SOURCE_DIR}/leakdetector.cpp + ${MVPN_SOURCE_DIR}/leakdetector.h + ${MVPN_SOURCE_DIR}/localizer.cpp + ${MVPN_SOURCE_DIR}/localizer.h + ${MVPN_SOURCE_DIR}/logger.cpp + ${MVPN_SOURCE_DIR}/logger.h + ${MVPN_SOURCE_DIR}/loghandler.cpp + ${MVPN_SOURCE_DIR}/loghandler.h + ${MVPN_SOURCE_DIR}/models/device.cpp + ${MVPN_SOURCE_DIR}/models/device.h + ${MVPN_SOURCE_DIR}/models/devicemodel.cpp + ${MVPN_SOURCE_DIR}/models/devicemodel.h + ${MVPN_SOURCE_DIR}/models/feature.cpp + ${MVPN_SOURCE_DIR}/models/feature.h + ${MVPN_SOURCE_DIR}/models/feedbackcategorymodel.cpp + ${MVPN_SOURCE_DIR}/models/feedbackcategorymodel.h + ${MVPN_SOURCE_DIR}/models/guide.cpp + ${MVPN_SOURCE_DIR}/models/guide.h + ${MVPN_SOURCE_DIR}/models/guideblock.cpp + ${MVPN_SOURCE_DIR}/models/guideblock.h + ${MVPN_SOURCE_DIR}/models/guidemodel.cpp + ${MVPN_SOURCE_DIR}/models/guidemodel.h + ${MVPN_SOURCE_DIR}/models/helpmodel.cpp + ${MVPN_SOURCE_DIR}/models/helpmodel.h + ${MVPN_SOURCE_DIR}/models/keys.cpp + ${MVPN_SOURCE_DIR}/models/keys.h + ${MVPN_SOURCE_DIR}/models/licensemodel.cpp + ${MVPN_SOURCE_DIR}/models/licensemodel.h + ${MVPN_SOURCE_DIR}/models/server.cpp + ${MVPN_SOURCE_DIR}/models/server.h + ${MVPN_SOURCE_DIR}/models/servercity.cpp + ${MVPN_SOURCE_DIR}/models/servercity.h + ${MVPN_SOURCE_DIR}/models/servercountry.cpp + ${MVPN_SOURCE_DIR}/models/servercountry.h + ${MVPN_SOURCE_DIR}/models/servercountrymodel.cpp + ${MVPN_SOURCE_DIR}/models/servercountrymodel.h + ${MVPN_SOURCE_DIR}/models/serverdata.cpp + ${MVPN_SOURCE_DIR}/models/serverdata.h + ${MVPN_SOURCE_DIR}/models/supportcategorymodel.cpp + ${MVPN_SOURCE_DIR}/models/supportcategorymodel.h + ${MVPN_SOURCE_DIR}/models/survey.cpp + ${MVPN_SOURCE_DIR}/models/survey.h + ${MVPN_SOURCE_DIR}/models/surveymodel.cpp + ${MVPN_SOURCE_DIR}/models/surveymodel.h + ${MVPN_SOURCE_DIR}/models/tutorial.cpp + ${MVPN_SOURCE_DIR}/models/tutorial.h + ${MVPN_SOURCE_DIR}/models/tutorialmodel.cpp + ${MVPN_SOURCE_DIR}/models/tutorialmodel.h + ${MVPN_SOURCE_DIR}/models/tutorialstep.cpp + ${MVPN_SOURCE_DIR}/models/tutorialstep.h + ${MVPN_SOURCE_DIR}/models/tutorialstepbefore.cpp + ${MVPN_SOURCE_DIR}/models/tutorialstepbefore.h + ${MVPN_SOURCE_DIR}/models/tutorialstepnext.cpp + ${MVPN_SOURCE_DIR}/models/tutorialstepnext.h + ${MVPN_SOURCE_DIR}/models/user.cpp + ${MVPN_SOURCE_DIR}/models/user.h + ${MVPN_SOURCE_DIR}/models/whatsnewmodel.cpp + ${MVPN_SOURCE_DIR}/models/whatsnewmodel.h + ${MVPN_SOURCE_DIR}/mozillavpn.h + ${MVPN_SOURCE_DIR}/networkmanager.cpp + ${MVPN_SOURCE_DIR}/networkmanager.h + ${MVPN_SOURCE_DIR}/networkrequest.h + ${MVPN_SOURCE_DIR}/networkwatcher.cpp + ${MVPN_SOURCE_DIR}/networkwatcher.h + ${MVPN_SOURCE_DIR}/networkwatcherimpl.h + ${MVPN_SOURCE_DIR}/pinghelper.cpp + ${MVPN_SOURCE_DIR}/pinghelper.h + ${MVPN_SOURCE_DIR}/pingsender.h + ${MVPN_SOURCE_DIR}/pingsenderfactory.cpp + ${MVPN_SOURCE_DIR}/pingsenderfactory.h + ${MVPN_SOURCE_DIR}/platforms/android/androiddatamigration.cpp + ${MVPN_SOURCE_DIR}/platforms/android/androiddatamigration.h + ${MVPN_SOURCE_DIR}/platforms/android/androidsharedprefs.cpp + ${MVPN_SOURCE_DIR}/platforms/android/androidsharedprefs.h + ${MVPN_SOURCE_DIR}/platforms/dummy/dummynetworkwatcher.cpp + ${MVPN_SOURCE_DIR}/platforms/dummy/dummynetworkwatcher.h + ${MVPN_SOURCE_DIR}/platforms/dummy/dummypingsender.cpp + ${MVPN_SOURCE_DIR}/platforms/dummy/dummypingsender.h + ${MVPN_SOURCE_DIR}/qmlengineholder.cpp + ${MVPN_SOURCE_DIR}/qmlengineholder.h + ${MVPN_SOURCE_DIR}/releasemonitor.cpp + ${MVPN_SOURCE_DIR}/releasemonitor.h + ${MVPN_SOURCE_DIR}/rfc/rfc1918.cpp + ${MVPN_SOURCE_DIR}/rfc/rfc1918.h + ${MVPN_SOURCE_DIR}/rfc/rfc4193.cpp + ${MVPN_SOURCE_DIR}/rfc/rfc4193.h + ${MVPN_SOURCE_DIR}/rfc/rfc4291.cpp + ${MVPN_SOURCE_DIR}/rfc/rfc4291.h + ${MVPN_SOURCE_DIR}/rfc/rfc5735.cpp + ${MVPN_SOURCE_DIR}/rfc/rfc5735.h + ${MVPN_SOURCE_DIR}/serveri18n.cpp + ${MVPN_SOURCE_DIR}/serveri18n.h + ${MVPN_SOURCE_DIR}/settingsholder.cpp + ${MVPN_SOURCE_DIR}/settingsholder.h + ${MVPN_SOURCE_DIR}/simplenetworkmanager.cpp + ${MVPN_SOURCE_DIR}/simplenetworkmanager.h + ${MVPN_SOURCE_DIR}/statusicon.cpp + ${MVPN_SOURCE_DIR}/statusicon.h + ${MVPN_SOURCE_DIR}/task.h + ${MVPN_SOURCE_DIR}/tasks/account/taskaccount.cpp + ${MVPN_SOURCE_DIR}/tasks/account/taskaccount.h + ${MVPN_SOURCE_DIR}/tasks/adddevice/taskadddevice.cpp + ${MVPN_SOURCE_DIR}/tasks/adddevice/taskadddevice.h + ${MVPN_SOURCE_DIR}/tasks/ipfinder/taskipfinder.cpp + ${MVPN_SOURCE_DIR}/tasks/ipfinder/taskipfinder.h + ${MVPN_SOURCE_DIR}/tasks/function/taskfunction.cpp + ${MVPN_SOURCE_DIR}/tasks/function/taskfunction.h + ${MVPN_SOURCE_DIR}/tasks/release/taskrelease.cpp + ${MVPN_SOURCE_DIR}/tasks/release/taskrelease.h + ${MVPN_SOURCE_DIR}/tasks/servers/taskservers.cpp + ${MVPN_SOURCE_DIR}/tasks/servers/taskservers.h + ${MVPN_SOURCE_DIR}/taskscheduler.cpp + ${MVPN_SOURCE_DIR}/taskscheduler.h + ${MVPN_SOURCE_DIR}/theme.cpp + ${MVPN_SOURCE_DIR}/theme.h + ${MVPN_SOURCE_DIR}/timersingleshot.cpp + ${MVPN_SOURCE_DIR}/timersingleshot.h + ${MVPN_SOURCE_DIR}/update/updater.cpp + ${MVPN_SOURCE_DIR}/update/updater.h + ${MVPN_SOURCE_DIR}/update/versionapi.cpp + ${MVPN_SOURCE_DIR}/update/versionapi.h + ${MVPN_SOURCE_DIR}/urlopener.cpp + ${MVPN_SOURCE_DIR}/urlopener.h +) + +# VPN Client UI resources +target_sources(unit_tests PRIVATE + ${MVPN_SOURCE_DIR}/ui/license.qrc +) + +# Unit test source files +target_sources(unit_tests PRIVATE + main.cpp + moccontroller.cpp + mocinspectorhandler.cpp + mocmozillavpn.cpp + mocnetworkrequest.cpp + helper.h + testadjust.cpp + testadjust.h + testandroidmigration.cpp + testandroidmigration.h + testcommandlineparser.cpp + testcommandlineparser.h + testfeature.cpp + testfeature.h + testguide.cpp + testguide.h + testipaddress.cpp + testipaddress.h + testipaddresslookup.cpp + testipaddresslookup.h + testipfinder.cpp + testipfinder.h + testlicense.cpp + testlicense.h + testlocalizer.cpp + testlocalizer.h + testlogger.cpp + testlogger.h + testmodels.cpp + testmodels.h + testmozillavpnh.cpp + testmozillavpnh.h + testnetworkmanager.cpp + testnetworkmanager.h + testreleasemonitor.cpp + testreleasemonitor.h + testserveri18n.cpp + testserveri18n.h + teststatusicon.cpp + teststatusicon.h + testtasks.cpp + testtasks.h + testthemes.cpp + testthemes.h + testtimersingleshot.cpp + testtimersingleshot.h + testtutorial.cpp + testtutorial.h + version.h +) + +# Unit test mock resources +target_sources(unit_tests PRIVATE + guides/guides.qrc + servers/servers.qrc + themes/themes.qrc + tutorials/tutorials.qrc +) + +## Add the tests to be run, one for each test class. +get_target_property(UTEST_SOURCES unit_tests SOURCES) +list(FILTER UTEST_SOURCES INCLUDE REGEX "test.*.h$") +foreach(filename ${UTEST_SOURCES}) + execute_process( + OUTPUT_VARIABLE UTEST_CLASS_LIST + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/../../scripts/tests/list_test_classes.py -p TestHelper ${filename} + ) + + foreach(UTEST_CLASS ${UTEST_CLASS_LIST}) + add_test(NAME ${UTEST_CLASS} COMMAND unit_tests ${UTEST_CLASS}) + set_property(TEST ${UTEST_CLASS} PROPERTY ENVIRONMENT LANG="en" LANGUAGE="en") + endforeach() +endforeach() diff --git a/tests/unit/helper.h b/tests/unit/helper.h index 05f0093c28..f98c36a008 100644 --- a/tests/unit/helper.h +++ b/tests/unit/helper.h @@ -38,6 +38,8 @@ class TestHelper : public QObject { static Controller::State controllerState; static QVector testList; + + static QObject* findTest(const QString& name); }; #endif // HELPER_H diff --git a/tests/unit/main.cpp b/tests/unit/main.cpp index 5a05746642..69a5d76622 100644 --- a/tests/unit/main.cpp +++ b/tests/unit/main.cpp @@ -7,12 +7,24 @@ #include "../../src/settingsholder.h" #include "constants.h" #include "helper.h" +#include "l18nstrings.h" QVector TestHelper::networkConfig; MozillaVPN::State TestHelper::vpnState = MozillaVPN::StateInitialize; Controller::State TestHelper::controllerState = Controller::StateInitializing; QVector TestHelper::testList; +QObject* TestHelper::findTest(const QString& name) { + for (QObject* obj : TestHelper::testList) { + const QMetaObject* meta = obj->metaObject(); + if (meta->className() == name) { + return obj; + } + } + + return nullptr; +} + TestHelper::TestHelper() { testList.append(this); } int main(int argc, char* argv[]) { @@ -25,16 +37,40 @@ int main(int argc, char* argv[]) { Constants::setStaging(); } + QProcessEnvironment pe = QProcessEnvironment::systemEnvironment(); + pe.insert("LANG", "en"); + pe.insert("LANGUAGE", "en"); + QCoreApplication a(argc, argv); int failures = 0; + L18nStrings::initialize(); LogHandler::enableDebug(); - for (QObject* obj : TestHelper::testList) { - int result = QTest::qExec(obj); - if (result != 0) { - ++failures; + // If arguments were passed, then run a subset of tests. + QStringList args = a.arguments(); + if (args.count() > 1) { + args.removeFirst(); + for (const QString& x : args) { + QObject* obj = TestHelper::findTest(x); + if (obj == nullptr) { + qWarning() << "No such test found:" << x; + ++failures; + continue; + } + int result = QTest::qExec(obj); + if (result != 0) { + ++failures; + } + } + } else { + // Otherwise, run all the tests. + for (QObject* obj : TestHelper::testList) { + int result = QTest::qExec(obj); + if (result != 0) { + ++failures; + } } } diff --git a/tests/unit/testguide.cpp b/tests/unit/testguide.cpp index 929394b888..bf5876a557 100644 --- a/tests/unit/testguide.cpp +++ b/tests/unit/testguide.cpp @@ -312,13 +312,14 @@ void TestGuide::conditions_data() { } void TestGuide::conditions() { + SettingsHolder settingsHolder; + FeatureList::instance()->initialize(); + QFETCH(QJsonObject, conditions); QFETCH(bool, result); QFETCH(QString, settingKey); QFETCH(QVariant, settingValue); - SettingsHolder settingsHolder; - if (!settingKey.isEmpty()) { settingsHolder.setRawSetting(settingKey, settingValue); } diff --git a/tests/unit/testlocalizer.cpp b/tests/unit/testlocalizer.cpp index 62dd83aa44..df671ff4ef 100644 --- a/tests/unit/testlocalizer.cpp +++ b/tests/unit/testlocalizer.cpp @@ -12,8 +12,6 @@ void TestLocalizer::basic() { Localizer l; QCOMPARE(Localizer::instance(), &l); - l.initialize(); - QHash rn = l.roleNames(); QCOMPARE(rn.count(), 3); QCOMPARE(rn[Localizer::LanguageRole], "language"); @@ -27,7 +25,6 @@ void TestLocalizer::basic() { void TestLocalizer::systemLanguage() { SettingsHolder settings; Localizer l; - l.initialize(); l.setCode(""); QCOMPARE(l.code(), ""); diff --git a/tests/unit/unit.pro b/tests/unit/unit.pro index c49d4dbcdf..6cfb3e5a8f 100644 --- a/tests/unit/unit.pro +++ b/tests/unit/unit.pro @@ -11,6 +11,7 @@ QT += widgets DEFINES += APP_VERSION=\\\"1234\\\" DEFINES += BUILD_ID=\\\"1234\\\" +DEFINES += BUILD_QMAKE CONFIG += c++1z @@ -31,13 +32,14 @@ INCLUDEPATH += \ ../../src \ ../../src/hacl-star \ ../../src/hacl-star/kremlin \ - ../../src/hacl-star/kremlin/minimal \ - ../../translations/generated \ - ../../glean \ - ../../nebula + ../../src/hacl-star/kremlin/minimal include($$PWD/../../glean/glean.pri) include($$PWD/../../nebula/nebula.pri) +include($$PWD/../../translations/translations.pri) + +# Remove resouce files that we intend to mock out +RESOURCES ~= 's/.*servers.qrc//g' HEADERS += \ ../../src/adjust/adjustfiltering.h \ @@ -164,7 +166,6 @@ SOURCES += \ ../../src/ipaddresslookup.cpp \ ../../src/itempicker.cpp \ ../../src/inspector/inspectorutils.cpp \ - ../../src/l18nstringsimpl.cpp \ ../../src/leakdetector.cpp \ ../../src/localizer.cpp \ ../../src/logger.cpp \ @@ -251,13 +252,6 @@ SOURCES += \ testtimersingleshot.cpp \ testtutorial.cpp -exists($$PWD/../../translations/generated/l18nstrings.h) { - SOURCES += $$PWD/../../translations/generated/l18nstrings_p.cpp - HEADERS += $$PWD/../../translations/generated/l18nstrings.h -} else { - error("No l18nstrings.h. Have you generated the strings?") -} - # Platform-specific: Linux linux { # QMAKE_CXXFLAGS *= -Werror diff --git a/tests/unit/version.h b/tests/unit/version.h new file mode 100644 index 0000000000..af7082fe4c --- /dev/null +++ b/tests/unit/version.h @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef VERSION_H +#define VERSION_H + +// Dummy versions used for unit testing +#define APP_VERSION "1234" +#define BUILD_ID "1234" + +#endif // VERSION_H diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt new file mode 100644 index 0000000000..0d72acad1e --- /dev/null +++ b/translations/CMakeLists.txt @@ -0,0 +1,103 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +add_library(translations STATIC) + +find_package(Qt6 REQUIRED COMPONENTS Core Qml) +target_link_libraries(translations PRIVATE Qt6::Core Qt6::Qml) + +get_filename_component(MVPN_SCRIPT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../scripts ABSOLUTE) +get_filename_component(GENERATED_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated ABSOLUTE) +file(MAKE_DIRECTORY ${GENERATED_DIR}) +target_include_directories(translations PUBLIC ${GENERATED_DIR}) + +target_sources(translations PRIVATE + ${GENERATED_DIR}/l18nstrings_p.cpp + ${GENERATED_DIR}/l18nstrings.h + ${GENERATED_DIR}/translations.qrc + l18nstrings.cpp + servers.qrc +) + +## Generate the string database (language agnostic) +add_custom_command( + OUTPUT ${GENERATED_DIR}/l18nstrings_p.cpp ${GENERATED_DIR}/l18nstrings.h + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/strings.yaml + COMMAND python3 ${MVPN_SCRIPT_DIR}/utils/generate_strings.py -o ${GENERATED_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/strings.yaml +) + +## Lookup the path to the Qt linguist tools +## CMake support for the LinquistTools component appears to be broken, +## so instead we will workaround it by searching the path where other +## tools can be found. +get_target_property(QT_QMLLINT_EXECUTABLE Qt6::qmllint LOCATION) +get_filename_component(QT_TOOL_PATH ${QT_QMLLINT_EXECUTABLE} PATH) +find_program(QT_LCONVERT_EXECUTABLE + NAMES lconvert-qt6 lconvert lconvert-qt5 + PATHS ${QT_TOOL_PATH} + NO_DEFAULT_PATH) +find_program(QT_LUPDATE_EXECUTABLE + NAMES lupdate-qt6 lupdate lupdate-qt5 + PATHS ${QT_TOOL_PATH} + NO_DEFAULT_PATH) +find_program(QT_LRELEASE_EXECUTABLE + NAMES lrelease-qt6 lrelease lrelease-qt5 + PATHS ${QT_TOOL_PATH} + NO_DEFAULT_PATH) + +## Build the list of supported locales +set(I18N_LOCALES) +get_filename_component(I18N_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../i18n ABSOLUTE) +file(GLOB I18N_LISTING LIST_DIRECTORIES true RELATIVE ${I18N_DIR} ${I18N_DIR}/*) +list(FILTER I18N_LISTING EXCLUDE REGEX "^\\..+") +foreach(entry ${I18N_LISTING}) + if(IS_DIRECTORY ${I18N_DIR}/${entry}) + list(APPEND I18N_LOCALES ${entry}) + endif() +endforeach() + +## Generate translation files for each locale present +file(WRITE ${GENERATED_DIR}/translations.qrc "\n \n") +foreach(LOCALE ${I18N_LOCALES}) + file(MAKE_DIRECTORY ${LOCALE}) + file(APPEND ${GENERATED_DIR}/translations.qrc " mozillavpn_${LOCALE}.qm\n") + + add_custom_command( + OUTPUT ${LOCALE}/locversion.plist + MAIN_DEPENDENCY locversion.plist.in + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${MVPN_SCRIPT_DIR}/utils/make_template.py -k __LOCALE__=${LOCALE} -o ${LOCALE}/locversion.plist locversion.plist.in + ) + + add_custom_command( + OUTPUT ${GENERATED_DIR}/mozillavpn_${LOCALE}.ts + MAIN_DEPENDENCY ${I18N_DIR}/${LOCALE}/mozillavpn.xliff + COMMAND ${QT_LCONVERT_EXECUTABLE} -verbose -if xlf -i ${I18N_DIR}/${LOCALE}/mozillavpn.xliff -o ${GENERATED_DIR}/mozillavpn_${LOCALE}.ts + ) + + add_custom_command( + OUTPUT ${GENERATED_DIR}/mozillavpn_${LOCALE}.qm + MAIN_DEPENDENCY ${GENERATED_DIR}/mozillavpn_${LOCALE}.ts + COMMAND ${QT_LRELEASE_EXECUTABLE} -verbose -idbased ${GENERATED_DIR}/mozillavpn_${LOCALE}.ts + ) +endforeach() +file(APPEND ${GENERATED_DIR}/translations.qrc " \n\n") + +## Generate a dummy translations project in case we need to update strings. +function(scan_sources PROJFILE SOURCEDIR) + file(APPEND ${PROJFILE} "HEADERS += \$\$files(${SOURCEDIR}/*.h, true)\n") + file(APPEND ${PROJFILE} "SOURCES += \$\$files(${SOURCEDIR}/*.cpp, true)\n") + file(APPEND ${PROJFILE} "RESOURCES += \$\$files(${SOURCEDIR}/*.qrc, true)\n\n") +endfunction() + +file(WRITE ${GENERATED_DIR}/dummy.pro "### AUTOGENERATED! DO NOT EDIT!! ###\n") +foreach(LOCALE ${I18N_LOCALES}) + file(APPEND ${GENERATED_DIR}/dummy.pro "TRANSLATIONS += mozillavpn_${LOCALE}.ts\n") +endforeach() +file(APPEND ${GENERATED_DIR}/dummy.pro "HEADERS += l18nstrings.h\n") +file(APPEND ${GENERATED_DIR}/dummy.pro "SOURCES += l18nstrings_p.cpp\n") +file(APPEND ${GENERATED_DIR}/dummy.pro "SOURCES += ../l18nstrings.cpp\n\n") + +scan_sources(${GENERATED_DIR}/dummy.pro ${CMAKE_SOURCE_DIR}/src) +scan_sources(${GENERATED_DIR}/dummy.pro ${CMAKE_SOURCE_DIR}/nebula) diff --git a/translations/generated/.keepme b/translations/generated/.keepme deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/l18nstringsimpl.cpp b/translations/l18nstrings.cpp similarity index 84% rename from src/l18nstringsimpl.cpp rename to translations/l18nstrings.cpp index bf56189415..943f3d5941 100644 --- a/src/l18nstringsimpl.cpp +++ b/translations/l18nstrings.cpp @@ -3,7 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "l18nstrings.h" -#include "leakdetector.h" #include @@ -20,13 +19,18 @@ L18nStrings* L18nStrings::instance() { return s_instance; } +// static +void L18nStrings::initialize() { +#ifndef BUILD_QMAKE + Q_INIT_RESOURCE(servers); + Q_INIT_RESOURCE(translations); +#endif +} + L18nStrings::L18nStrings(QObject* parent) : QQmlPropertyMap(parent) { - MVPN_COUNT_CTOR(L18nStrings); retranslate(); } -L18nStrings::~L18nStrings() { MVPN_COUNT_DTOR(L18nStrings); } - QString L18nStrings::t(L18nStrings::String string) const { Q_ASSERT(string < __Last); QString id = _ids[string]; diff --git a/translations/locversion.plist.in b/translations/locversion.plist.in new file mode 100644 index 0000000000..066a98aa1c --- /dev/null +++ b/translations/locversion.plist.in @@ -0,0 +1,15 @@ + + + + + LprojCompatibleVersion + 123 + LprojLocale + __LOCALE__ + LprojRevisionLevel + 1 + LprojVersion + 123 + + diff --git a/translations/translations.pri b/translations/translations.pri new file mode 100644 index 0000000000..e0b501e29c --- /dev/null +++ b/translations/translations.pri @@ -0,0 +1,45 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# This file contains anything related to the translations and the strings generation + +exists($$PWD/generated/translations.qrc) { + RESOURCES += $$PWD/generated/translations.qrc +} else { + error("No translations.qrc! Have you imported the languages?") +} +macos { + include($$PWD/generated/macos.pri) +} + +INCLUDEPATH += $$PWD/generated +RESOURCES += $$PWD/servers.qrc +SOURCES += $$PWD/l18nstrings.cpp + +STRING_SOURCES = $$PWD/strings.yaml + +## This is necessary to ensure MOC picks up this class. +HEADERS += $$PWD/generated/l18nstrings.h + +## Perform string generation +## FIXME: This also depends vaguely on the contents of the guide and tutorials. +## TODO: Make translations for the guides and tutorials loaded dynamically. +stringgen.input = STRING_SOURCES +stringgen.output = $$PWD/generated/l18nstrings.h +stringgen.commands = @echo Generating strings from ${QMAKE_FILE_IN} \ + && python3 $$PWD/../scripts/utils/generate_strings.py \ + -g ${QMAKE_FILE_IN_PATH}/../src/ui/guides \ + -t ${QMAKE_FILE_IN_PATH}/../src/ui/tutorials \ + -o ${QMAKE_FILE_OUT_PATH} ${QMAKE_FILE_IN} +stringgen.depends += ${QMAKE_FILE_IN} +stringgen.variable_out = HEADERS + +## Dummy rule for the private source file. +stringsrc.input = STRING_SOURCES +stringsrc.output = $$PWD/generated/l18nstrings_p.cpp +stringsrc.commands = $$escape_expand(\\n) # force creation of rule +stringsrc.depends += $$PWD/generated/l18nstrings.h +stringsrc.variable_out = SOURCES + +QMAKE_EXTRA_COMPILERS += stringgen stringsrc diff --git a/windows/installer/CMakeLists.txt b/windows/installer/CMakeLists.txt new file mode 100644 index 0000000000..11b536b88d --- /dev/null +++ b/windows/installer/CMakeLists.txt @@ -0,0 +1,27 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +set(WIX_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/.deps/bin) +set(WIX_CANDLE_FLAGS -nologo -ext WiXUtilExtension) +set(WIX_LIGHT_FLAGS -nologo -spdb -ext WixUtilExtension) +set(WIX_PLATFORM x64) + +## Download and extract Wix and friends. +file(DOWNLOAD https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip + ${CMAKE_CURRENT_BINARY_DIR}/.deps/wix-binaries.zip + EXPECTED_HASH SHA256=2c1888d5d1dba377fc7fa14444cf556963747ff9a0a289a3599cf09da03b9e2e) + +file(ARCHIVE_EXTRACT INPUT ${CMAKE_CURRENT_BINARY_DIR}/.deps/wix-binaries.zip DESTINATION ${WIX_BINARY_DIR}) + + +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/staging) +add_custom_target(msi + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/staging + COMMAND ${CMAKE_COMMAND} -E echo "Building MSI installer for $" + COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR} --prefix ${CMAKE_CURRENT_BINARY_DIR}/staging --config $ + COMMAND ${WIX_BINARY_DIR}/candle ${WIX_CANDLE_FLAGS} -dPlatform=${WIX_PLATFORM} + -out ${CMAKE_CURRENT_BINARY_DIR}/MozillaVPN.wixobj -arch x64 ${CMAKE_CURRENT_SOURCE_DIR}/MozillaVPN_cmake.wxs + COMMAND ${WIX_BINARY_DIR}/light ${WIX_LIGHT_FLAGS} -out ${CMAKE_CURRENT_BINARY_DIR}/MozillaVPN.msi ${CMAKE_CURRENT_BINARY_DIR}/MozillaVPN.wixobj +) +set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_CURRENT_BINARY_DIR}/staging) diff --git a/windows/installer/MozillaVPN_cmake.wxs b/windows/installer/MozillaVPN_cmake.wxs new file mode 100644 index 0000000000..8a685e6550 --- /dev/null +++ b/windows/installer/MozillaVPN_cmake.wxs @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NOT Installed OR WIX_UPGRADE_DETECTED + NOT Installed OR WIX_UPGRADE_DETECTED + + + + + + + + + + diff --git a/windows/split-tunnel/CMakeLists.txt b/windows/split-tunnel/CMakeLists.txt new file mode 100644 index 0000000000..e3a0e9980c --- /dev/null +++ b/windows/split-tunnel/CMakeLists.txt @@ -0,0 +1,24 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +set(COMMIT_SHA 375cfc52255ec3beb15cc0713988ea995cb7582e) +set(BASE_URI https://github.com/mullvad/mullvadvpn-app-binaries/raw/${COMMIT_SHA}/x86_64-pc-windows-msvc/split-tunnel/win10/) + +# Download the prebuilt binaries from Mullvad +file(DOWNLOAD ${BASE_URI}/mullvad-split-tunnel.cat ${CMAKE_CURRENT_BINARY_DIR}/mullvad-split-tunnel.cat + EXPECTED_HASH SHA256=385C90BE86B6C934344C6D2647887182B41D7E37803DC5EABE7E0805F6210317) +file(DOWNLOAD ${BASE_URI}/mullvad-split-tunnel.inf ${CMAKE_CURRENT_BINARY_DIR}/mullvad-split-tunnel.inf + EXPECTED_HASH SHA256=A9500015B0C93C96D6859E942CF76B6B637653F46ADDEE11BB07AF0C1EA6E879) +file(DOWNLOAD ${BASE_URI}/mullvad-split-tunnel.sys ${CMAKE_CURRENT_BINARY_DIR}/mullvad-split-tunnel.sys + EXPECTED_HASH SHA256=8BDC9FC6AA1203E444A2D6E3A564CF8DD8F7EC9DB850F0095B9D33E16B96AFF5) +file(DOWNLOAD ${BASE_URI}/WdfCoinstaller01011.dll ${CMAKE_CURRENT_BINARY_DIR}/WdfCoinstaller01011.dll + EXPECTED_HASH SHA256=B2CE2291D10834E132EC1A349B726822E8C943B47AF968E9DE592ED7AE1CFA36) + +# Install the prebuilt binaries +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/mullvad-split-tunnel.cat + ${CMAKE_CURRENT_BINARY_DIR}/mullvad-split-tunnel.inf + ${CMAKE_CURRENT_BINARY_DIR}/mullvad-split-tunnel.sys + ${CMAKE_CURRENT_BINARY_DIR}/WdfCoinstaller01011.dll + DESTINATION . +) diff --git a/windows/tunnel/CMakeLists.txt b/windows/tunnel/CMakeLists.txt new file mode 100644 index 0000000000..3ef2d88fe4 --- /dev/null +++ b/windows/tunnel/CMakeLists.txt @@ -0,0 +1,29 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +## Download and extract wintun.dll and friends. +file(DOWNLOAD https://www.wintun.net/builds/wintun-0.12.zip + ${CMAKE_CURRENT_BINARY_DIR}/.deps/wintun.zip + EXPECTED_HASH SHA256=eba90e26686ed86595ae0a6d4d3f4f022924b1758f5148a32a91c60cc6e604df) + +file(ARCHIVE_EXTRACT INPUT ${CMAKE_CURRENT_BINARY_DIR}/.deps/wintun.zip DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/.deps) + +## Build the tunnel DLL +add_custom_target(tunneldll ALL + BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/tunnel.dll ${CMAKE_CURRENT_BINARY_DIR}/tunnel.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} -E env + GOCACHE=${CMAKE_BINARY_DIR}/go-cache + GOOS=windows CGO_ENABLED=1 + CGO_CFLAGS="-O3 -Wall -Wno-unused-function -Wno-switch -std=gnu11 -DWINVER=0x0601" + CGO_LDFLAGS="-Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols -Wl,--high-entropy-va" + go build -buildmode c-shared -ldflags="-w -s" -trimpath -v -o "${CMAKE_CURRENT_BINARY_DIR}/tunnel.dll" +) +set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_BINARY_DIR}/go-cache) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/tunnel.dll + ${CMAKE_CURRENT_BINARY_DIR}/.deps/wintun/bin/${CMAKE_SYSTEM_PROCESSOR}/wintun.dll + DESTINATION . +) diff --git a/windows/version.rc.in b/windows/version.rc.in new file mode 100644 index 0000000000..468fc8fae7 --- /dev/null +++ b/windows/version.rc.in @@ -0,0 +1,52 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +#include + +#ifndef DEBUG +#define VER_DEBUG 0 +#else +#define VER_DEBUG VS_FF_DEBUG +#endif + +// See https://docs.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource +// for a list of all the neat metadata we can set in this file. +VS_VERSION_INFO VERSIONINFO +FILEVERSION @CMAKE_PROJECT_VERSION@ +PRODUCTVERSION @CMAKE_PROJECT_VERSION@ +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +FILEFLAGS (VER_DEBUG) +FILEOS VOS__WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", "Mozilla Corporation" + VALUE "FileDescription", "@CMAKE_PROJECT_DESCRIPTION@" + VALUE "FileVersion", "@CMAKE_PROJECT_VERSION@" + //VALUE "InternalName", VER_INTERNALNAME_STR + VALUE "LegalCopyright", "MPL-2.0" + //VALUE "LegalTrademarks1", VER_LEGALTRADEMARKS1_STR + //VALUE "LegalTrademarks2", VER_LEGALTRADEMARKS2_STR + //VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR + //VALUE "ProductName", VER_PRODUCTNAME_STR + VALUE "ProductVersion", "@CMAKE_PROJECT_VERSION@" + END + END + + BLOCK "VarFileInfo" + BEGIN + /* The following line should only be modified for localized versions. */ + /* It consists of any number of WORD,WORD pairs, with each pair */ + /* describing a language,codepage combination supported by the file. */ + /* */ + /* For example, a file might have values "0x409,1252" indicating that it */ + /* supports English language (0x409) in the Windows ANSI codepage (1252). */ + + VALUE "Translation", 0x409, 1252 + + END +END