diff --git a/common/core/desktop/.gitignore b/common/core/desktop/.gitignore index cd78b2f40f..b9b2e49a3c 100644 --- a/common/core/desktop/.gitignore +++ b/common/core/desktop/.gitignore @@ -1,3 +1,8 @@ .vs/ -build*/ +build/ configure/ + +# These files may be copied here during Linux package builds +TIER.md +VERSION.md +shellHelperFunctions.sh diff --git a/common/core/desktop/build.bat b/common/core/desktop/build.bat index a83d33a4c3..fdcc18a7e6 100644 --- a/common/core/desktop/build.bat +++ b/common/core/desktop/build.bat @@ -1,15 +1,9 @@ @echo off - +rem **************************************************************** +rem Run build.sh, not this file directly, to build on Windows. +rem **************************************************************** setlocal enabledelayedexpansion -if "!SDKVER!"=="" ( - if "!WindowsSDKVersion!"=="" ( - set SDKVER=8.1 - ) else ( - set SDKVER=!WindowsSDKVersion:~0,-1! - ) -) - if "%1"=="" goto help if "%1"=="all" goto all if "%1"=="x86" goto build @@ -21,9 +15,14 @@ goto help rem ---------------------------------- :help -echo Usage: build-core.bat x86^|x64^|all [-c] +echo Usage: %0 x86^|x64^|all debug^|release [build] [tests] +echo or +echo Usage: %0 x86^|x64^|all -c echo -c will leave your environment configured for Visual Studio for selected platform. echo -c can be used only with x86 and x64 options +echo. +echo Otherwise, %0 is intended to be used by build.sh, not directly. +echo At least one of 'build' or 'tests' is required. goto :eof rem ---------------------------------- @@ -32,12 +31,10 @@ rem ---------------------------------- setlocal cd %KEYMAN_ROOT%\common\core\desktop -cmd /c build.bat x86 -if errorlevel 1 exit /b !errorlevel! +cmd /c build.bat x86 %2 %3 %4 %5 || exit !errorlevel! cd %KEYMAN_ROOT%\common\core\desktop -cmd /c build.bat x64 -if errorlevel 1 exit /b !errorlevel! +cmd /c build.bat x64 %2 %3 %4 %5 || exit !errorlevel! goto :eof @@ -48,7 +45,6 @@ rem ---------------------------------- if "%2"=="-c" goto :setup set ARCH=%1 -echo Building Keyman Core for Windows !ARCH! echo === Locating Visual Studio === @@ -68,27 +64,43 @@ if not exist "!VCVARSALL!" ( ) echo === Configuring VC++ === -call !VCVARSALL! !ARCH! !SDKVER! -if errorlevel 1 exit /b !errorlevel! +call !VCVARSALL! !ARCH! || exit !errorlevel! -echo === Calling meson setup === cd %KEYMAN_ROOT%\common\core\desktop -meson build-!ARCH! --werror -if errorlevel 1 exit /b !errorlevel! -echo === Building Keyman Core === -cd build-!ARCH! -if errorlevel 1 exit /b !errorlevel! +set BUILDTYPE=%2 -ninja -if errorlevel 1 exit /b !errorlevel! +if "%3" == "--static" ( + set STATIC_LIBRARY=--default-library static + shift +) else ( + set STATIC_LIBRARY= +) -echo === Running tests === -rem Run test cases immediately as they are quick -meson test --print-errorlogs -if errorlevel 1 exit /b !errorlevel! +echo "Is static build = !STATIC_LIBRARY! (%3)" -cd .. +if "%3" == "build" ( + echo === Calling meson build for Windows !ARCH! !BUILDTYPE! === + if exist build\!ARCH!\!BUILDTYPE! rd /s/q build\!ARCH!\!BUILDTYPE! + meson build\!ARCH!\!BUILDTYPE! !STATIC_LIBRARY! --buildtype !BUILDTYPE! --werror || exit !errorlevel! + + echo === Building Keyman Core for Windows !ARCH! !BUILDTYPE! === + cd build\!ARCH!\!BUILDTYPE! || exit !errorlevel! + + ninja || exit !errorlevel! + cd ..\..\.. + + shift +) + +if "%3" == "tests" ( + cd build\!ARCH!/!BUILDTYPE! || exit !errorlevel! + + echo === Running tests for Windows !ARCH! !BUILDTYPE! === + meson test --print-errorlogs || exit !errorlevel! + + cd ..\..\.. +) goto :eof @@ -102,7 +114,7 @@ endlocal for /f "usebackq tokens=*" %%i in (`..\..\..\resources\build\vswhere -latest -requires Microsoft.Component.MSBuild -find **\vcvarsall.bat`) do ( set VCVARSALL="%%i" ) -%VCVARSALL% %1 8.1 +%VCVARSALL% %1 goto :eof rem ---------------------------------- diff --git a/common/core/desktop/build.sh b/common/core/desktop/build.sh new file mode 100755 index 0000000000..13f9405e55 --- /dev/null +++ b/common/core/desktop/build.sh @@ -0,0 +1,298 @@ +#!/usr/bin/env bash + +set -e +set -u + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}")" +# NOTE: this is slightly non-standard; see longer discussion below +## END STANDARD BUILD SCRIPT INCLUDE + +# This script does not use our normal shared build-utils.sh because Linux package builds +# cannot access anything outside of the `common/core/desktop` directory. This means that: +# 1. `shellHelperFunctions.sh`, `VERSION.md` and `TIER.md` are copied here by the script +# `linux/scripts/dist.sh` for inclusion locally in Linux package builds. +# 2. `getversion.sh` and `gettier.sh` will use current folder if we can't access the +# root level `VERSION.md` and `TIER.md`. +# 3. `$SCRIPTS_DIR` is set to this folder by the package build Makefile +# `common/core/desktop/debian/rules` +SCRIPTS_DIR=${SCRIPTS_DIR:-$(dirname "$THIS_SCRIPT")/../../../resources} +. "${SCRIPTS_DIR}/shellHelperFunctions.sh" + +THIS_DIR="$(dirname "$THIS_SCRIPT")" + +pushd $THIS_DIR > /dev/null +VERSION=$(./getversion.sh) +TIER=$(./gettier.sh) +popd > /dev/null + +display_usage() { + echo "usage: build.sh [build options] [targets] [-- options to pass to c++ configure]" + echo + echo "Build options:" + echo " --debug, -d Debug build" + echo " --target, -t Target path (linux,macos only, default build/)" + echo + echo "Targets (all except install if not specified):" + echo " configure Configure libraries (linux,macos only)" + echo " build Build all libraries" + echo " build-rust Build rust libraries" + echo " build-cpp Build c++ libraries" + echo " tests Run all tests" + echo " tests-rust Run rust tests" + echo " tests-cpp Run c++ and c++/rust integration tests" + echo " install Install all libraries" + echo " install-rust Install rust libraries" + echo " install-cpp Install c++ libraries" + echo + echo "Rust libraries will be in: TARGETPATH/rust//" + echo "C++ libraries will be in: TARGETPATH///src" + echo "On Windows, will be 'x86' or 'x64'; elsewhere it is 'arch'" + exit 0 +} + +get_builder_OS + +CARGO_TARGET=--release +MESON_TARGET=release +HAS_TARGET=false +CONFIGURE=false +BUILD_RUST=false +BUILD_CPP=false +TESTS_RUST=false +TESTS_CPP=false +INSTALL_RUST=false +INSTALL_CPP=false +QUIET=false +TARGET_PATH="$THIS_DIR/build" +ADDITIONAL_ARGS= + +# Parse args +shopt -s nocasematch + +while [[ $# -gt 0 ]] ; do + key="$1" + case $key in + --debug|-d) + CARGO_TARGET= + MESON_TARGET=debug + ;; + --help|-\?) + display_usage + ;; + --target|-t) + TARGET_PATH=$(readlink -f "$2") + shift + ;; + configure) + HAS_TARGET=true + CONFIGURE=true + # meson depends on the rust build in order + # to do its configure step, for now anyway + BUILD_RUST=true + ;; + build) + HAS_TARGET=true + BUILD_RUST=true + BUILD_CPP=true + ;; + build-rust) + HAS_TARGET=true + BUILD_RUST=true + ;; + build-cpp) + HAS_TARGET=true + BUILD_CPP=true + ;; + tests) + HAS_TARGET=true + TESTS_RUST=true + TESTS_CPP=true + ;; + tests-rust) + HAS_TARGET=true + TESTS_RUST=true + ;; + tests-cpp) + HAS_TARGET=true + TESTS_CPP=true + ;; + install) + HAS_TARGET=true + INSTALL_RUST=true + INSTALL_CPP=true + ;; + install-rust) + HAS_TARGET=true + INSTALL_RUST=true + ;; + install-cpp) + HAS_TARGET=true + INSTALL_CPP=true + ;; + --) + shift + ADDITIONAL_ARGS=$@ + break + ;; + *) + fail "Invalid parameters. Use --help for help" + esac + shift +done + +if ! $HAS_TARGET; then + CONFIGURE=true + BUILD_RUST=true + BUILD_CPP=true + TESTS_RUST=true + TESTS_CPP=true +fi + +MESON_PATH="$TARGET_PATH/arch/$MESON_TARGET" + +displayInfo "" \ + "VERSION: $VERSION" \ + "TIER: $TIER" \ + "CONFIGURE: $CONFIGURE" \ + "BUILD_RUST: $BUILD_RUST" \ + "BUILD_CPP: $BUILD_CPP" \ + "TESTS_RUST: $TESTS_RUST" \ + "TESTS_CPP: $TESTS_CPP" \ + "INSTALL_RUST: $INSTALL_RUST" \ + "INSTALL_CPP: $INSTALL_CPP" \ + "CARGO_TARGET: $CARGO_TARGET" \ + "MESON_TARGET: $MESON_TARGET" \ + "TARGET_PATH: $TARGET_PATH" \ + "" + + +build_test_rust() { + local TARGETBASE="$1" + if [ -z ${2+x} ]; then local TARGET=""; else local TARGET="$2"; fi + + if [ ! -z $TARGET ]; then + local TARGET_FLAG=--target=$TARGET + else + local TARGET_FLAG= + fi + + pushd "$THIS_DIR/src/rust" >/dev/null + if $BUILD_RUST; then + echo_heading "======= Building rust library for $TARGETBASE $TARGET =======" + + # Built library path for multi-arch (Windows) vs single (*nix) + + cargo build --target-dir="$TARGET_PATH/rust/$TARGETBASE" $TARGET_FLAG $CARGO_TARGET + + # On Windows, final output path is ./build/rust///debug|release/ + # On other platforms, the final file is already in the right place (TARGET=="") + if [ ! -z $TARGET ]; then + local LIB="rust_mock_processor" + + # Library name on Windows vs *nix + [ $os_id == "win" ] && \ + local LIBNAME=$LIB.lib || \ + local LIBNAME=lib$LIB.a + + local BUILT_PATH="$TARGET_PATH/rust/$TARGETBASE/$TARGET/$MESON_TARGET" + local RUST_TARGET_PATH="$TARGET_PATH/rust/$TARGETBASE/$MESON_TARGET" + cp "$BUILT_PATH/$LIBNAME" "$RUST_TARGET_PATH/$LIBNAME" + fi + fi + + if $TESTS_RUST; then + echo_heading "======= Testing rust library for $TARGETBASE $TARGET =======" + cargo test --target-dir="$TARGET_PATH/rust/$TARGETBASE" $TARGET $CARGO_TARGET + fi + popd >/dev/null +} + +build_windows() { + # Build targets for Windows + + # Build the rust targets, both x86 and x64 + build_test_rust x86 i686-pc-windows-msvc + build_test_rust x64 x86_64-pc-windows-msvc + + # Build the meson targets, both x86 and x64 also + # We need to use a batch file here so we can get + # the Visual Studio build environment with vcvarsall.bat + + if $BUILD_CPP; then + if $TESTS_CPP; then + echo_heading "======= Building and Testing C++ library for Windows (x86, x64) =======" + cmd //C build.bat all $MESON_TARGET build tests + else + echo_heading "======= Building C++ library for Windows (x86, x64) =======" + cmd //C build.bat all $MESON_TARGET build + fi + elif $TESTS_CPP; then + echo_heading "======= Testing C++ library for Windows (x86, x64) =======" + cmd //C build.bat all $MESON_TARGET tests + fi +} + +build_linux_macos() { + + # Build rust targets + build_test_rust arch + + # Build meson targets + if $CONFIGURE; then + echo_heading "======= Configuring C++ library for $os_id =======" + pushd $THIS_DIR > /dev/null + meson $MESON_PATH --werror --buildtype $MESON_TARGET $ADDITIONAL_ARGS + popd > /dev/null + fi + + if $BUILD_CPP; then + echo_heading "======= Building C++ library for $os_id =======" + pushd $MESON_PATH > /dev/null + ninja + popd > /dev/null + fi + + if $TESTS_CPP; then + echo_heading "======= Testing C++ library for $os_id =======" + pushd $MESON_PATH > /dev/null + meson test --print-errorlogs + popd > /dev/null + fi + + if $INSTALL_RUST; then + echo_heading "======= Installing Rust libraries for $os_id =======" + # TODO + fi + + if $INSTALL_CPP; then + echo_heading "======= Installing C++ libraries for $os_id =======" + pushd $MESON_PATH > /dev/null + ninja install + popd > /dev/null + fi +} + +build_macos() { + build_linux_macos +} + +build_linux() { + build_linux_macos +} + +### + + +case $os_id in + "linux") + build_linux + ;; + "mac") + build_macos + ;; + "win") + build_windows + ;; +esac diff --git a/common/core/desktop/build_cargo.bat b/common/core/desktop/build_cargo.bat new file mode 100644 index 0000000000..b0e78b29ba --- /dev/null +++ b/common/core/desktop/build_cargo.bat @@ -0,0 +1,41 @@ +@echo off + +setlocal + +rem due to some painful cmd-in-bash-in-cmd-in-make-in-cmd interactions +rem causing path confusion for Cargo, it's easier to just call out to a +rem batch file to build our rust targets on Windows + +if "%1"=="--debug" ( + set CARGO_TARGET= + set MESON_TARGET=debug +) else ( + set CARGO_TARGET=--release + set MESON_TARGET=release +) + +call :build_test_rust x86 i686-pc-windows-msvc || exit /b !errorlevel! +call :build_test_rust x64 x86_64-pc-windows-msvc || exit /b !errorlevel! +exit /b 0 + +:build_test_rust +set TARGETBASE=%1 +set TARGET=%2 +set TARGET_FLAG=--target=%TARGET% +set TARGET_PATH=%~dp0build + +cd "%TARGET_PATH%\..\src\rust" || exit !errorlevel! +cargo build --target-dir="%TARGET_PATH%\rust\%TARGETBASE%" %TARGET_FLAG% %CARGO_TARGET% || exit !errorlevel! + +:: On Windows, final output path is ./build/rust///debug|release/ +:: On other platforms, the final file is already in the right place (TARGET=="") + +set LIB=rust_mock_processor +set LIBNAME=%LIB%.lib +set BUILT_PATH=%TARGET_PATH%\rust\%TARGETBASE%\%TARGET%\%MESON_TARGET% +set RUST_TARGET_PATH=%TARGET_PATH%\rust\%TARGETBASE%\%MESON_TARGET% + +copy "%BUILT_PATH%\%LIBNAME%" "%RUST_TARGET_PATH%\%LIBNAME%" || exit !errorlevel! + +goto :eof + diff --git a/common/core/desktop/debian/control b/common/core/desktop/debian/control index b629e3be02..37e195ecf5 100644 --- a/common/core/desktop/debian/control +++ b/common/core/desktop/debian/control @@ -10,6 +10,7 @@ Build-Depends: debhelper (>= 11), meson (>= 0.45), ninja-build, + cargo, Standards-Version: 4.5.1 Vcs-Git: https://github.com/keymanapp/keyman.git Vcs-Browser: https://github.com/keymanapp/keyman/tree/master/common/core/desktop diff --git a/common/core/desktop/debian/rules b/common/core/desktop/debian/rules index 3d056fc65d..81c9a7efac 100755 --- a/common/core/desktop/debian/rules +++ b/common/core/desktop/debian/rules @@ -1,7 +1,9 @@ #!/usr/bin/make -f export DH_VERBOSE=1 +#export DH_OPTIONS=-v export DEB_BUILD_MAINT_OPTIONS = hardening=+all +export SCRIPTS_DIR = $(shell pwd)/scripts # xenial needs this to be explicit export LC_ALL=C.UTF-8 @@ -9,5 +11,18 @@ export LC_ALL=C.UTF-8 %: dh $@ +override_dh_auto_configure: + ./build.sh configure -- --wrap-mode=nodownload --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=lib/$(DEB_TARGET_GNU_TYPE) --libexecdir=lib/$(DEB_TARGET_GNU_TYPE) + +override_dh_auto_build: + ./build.sh build + +override_dh_auto_test: + ./build.sh tests + +override_dh_auto_install: + install -d $(shell pwd)/debian/tmp + DESTDIR=$(shell pwd)/debian/tmp ./build.sh install + override_dh_missing: dh_missing --fail-missing diff --git a/common/core/desktop/doc/BUILDING.md b/common/core/desktop/doc/BUILDING.md index bb7ef01ce7..777fa4ce5e 100644 --- a/common/core/desktop/doc/BUILDING.md +++ b/common/core/desktop/doc/BUILDING.md @@ -5,96 +5,148 @@ - Python 3 - Meson build system 0.50 or later. - C++14 or later compiler (VC++ 2019 or later for Windows). +- Rust from (or `cargo` package on Linux) - lib std::fs - kmcomp (for tests) -- must be added to path -## Installing Python3 - -### Linux - -You will be able to install a python3 package in any reputable recent version -of linux using its package manager if it's not already installed. - -### Mac OS X - -You can get the official installer from the official Python site: - - ### Windows -You can get the official installer from the official Python site: +You can get the official Python installer from the official Python site: -## Installing Meson +Ensure you have Python3 correctly installed and can run the command `pip`. -Ensure you have Python3 correctly installed and can run the command `pip3`. +Then install meson: ```bash python3 -m pip install meson ``` +Note on paths for kmcomp: + +The search path can be edited through System settings / Advanced system settings +/ Environment Variables / User environment variables. + +If you have Keyman Developer installed, add `%KeymanDeveloperPath%` to your +path. Otherwise, add the path where you extracted the kmcomp archive. + +#### Installing Rust + +After downloading and running the executable from: + + +Run the command: +```bash +rustup target add i686-pc-windows-msvc +``` + +### Linux + +#### Ubuntu and Debian + +You can install `meson` and Rust through the package manager: + +```bash +sudo apt update +sudo apt install meson cargo +rustup target add i686-pc-windows-msvc +``` + +You'll also need the `kmcomp` wrapper - see below. + +#### Other Linux distributions + +You will be able to install a python3 package in any reputable recent version of +linux using its package manager if it's not already installed. + +Then install meson: + +```bash +sudo apt install python3 python3-pip +python3 -m pip install meson +``` + +Install Rust from , then: + +```bash +rustup target add i686-pc-windows-msvc +``` + +You'll also need the `kmcomp` wrapper - see below. + +#### All Linux platforms + +If you want to rebuild keyboards for tests, you need a wrapper `kmcomp` shell +script: + +```bash +#!/usr/bin/env bash +wine `dirname "$0"`/kmcomp.exe "$@" +``` + +Place this in the same folder as you extracted kmcomp.exe, and +`chmod +x kmcomp`. Add the folder to the path (e.g. +`export PATH=/path/to/kmcomp:$PATH`, which you can add to `.bashrc`). + +### macOS + +You can get the official Python installer from the official Python site: + + +Install meson and rust: + +```bash +brew install meson # if you haven't already installed via pip +curl https://sh.rustup.rs -sSf | sh +rustup target add i686-pc-windows-msvc +``` + +If you want to rebuild keyboards for tests, you'll also need WINE: + +```bash +brew tap homebrew/cask-versions +brew install --cask --no-quarantine wine-stable +``` + +And you will also need a wrapper `kmcomp` shell script: + +```bash +#!/usr/bin/env bash +wine64 `dirname "$0"`/kmcomp.exe "$@" +``` + +Place this in the same folder as you extracted kmcomp.exe, and +`chmod +x kmcomp`. Add the folder to the path (e.g. +`export PATH=/path/to/kmcomp:$PATH`, which you can add to `.bashrc`). + ## Building -### Building on Windows +### Prerequisite on Windows -For Windows, use `build.bat` -- this handles environment and x86/x64 -cross-compiles with Visual Studio 2017+. - -You may need to set `SDKVER` environement variable to the current +On Windows you may need to set `SDKVER` environment variable to the current Windows SDK version, if it cannot be automatically detected, e.g.: ```DOS set SDKVER=10.0.19041.0 ``` -To build: +You'll also need to install x86 support for Rust: ```DOS -build.bat all +rustup target add i686-pc-windows-msvc ``` -### Building on Linux, macOS +### Building on all platforms -For all other platforms, in your source directory do the following: +On all platforms, use `build.sh`. ```bash -cd desktop -meson build --werror -cd build -ninja -meson test +./build.sh --debug ``` -For a debug build, pass `--buildtype debug` to meson. - ## Note on kmcomp -kmcomp is the command-line compiler from Keyman Developer, available from or -in this repo in `/windows/src/developer/kmcomp`. The compiler is currently available as a Windows -PE executable only, but it does run under WINE. - -## Additional configuration notes - -### Windows - -The search path can be edited through System settings / Advanced system settings / -Environment Variables / User environment variables. - -If you have Keyman Developer installed, add `%KeymanDeveloperPath%` to your path. Otherwise, add -the path where you extracted the kmcomp archive. - -### Linux - -You need a wrapper `kmcomp` shell script: - -```bash -#!/bin/bash -wine `dirname "$0"`/kmcomp.exe "$@" -``` - -Place this in the same folder as you extracted kmcomp.exe, and `chmod +x kmcomp`. Add the folder -to the path (e.g. `export PATH=/path/to/kmcomp:$PATH`, which you can add to `.bashrc`) - -### macOS - -TODO +kmcomp is the command-line compiler from Keyman Developer, available from + or in this repo in +`/windows/src/developer/kmcomp`. The compiler is currently available as a +Windows PE executable only, but it does run under WINE. diff --git a/common/core/desktop/gettier.sh b/common/core/desktop/gettier.sh new file mode 100755 index 0000000000..c298092649 --- /dev/null +++ b/common/core/desktop/gettier.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# Get the tier from TIER.md +# When building from a Linux source package, `TIER.md` is in the +# same directory as this script. +TIERFILE=TIER.md +[ -f ../../../TIER.md ] && TIERFILE=../../../TIER.md +cat $TIERFILE diff --git a/common/core/desktop/getversion.sh b/common/core/desktop/getversion.sh old mode 100644 new mode 100755 diff --git a/common/core/desktop/include/keyman/keyboardprocessor.h.in b/common/core/desktop/include/keyman/keyboardprocessor.h.in index ce86adb5a0..430dc0b455 100644 --- a/common/core/desktop/include/keyman/keyboardprocessor.h.in +++ b/common/core/desktop/include/keyman/keyboardprocessor.h.in @@ -889,7 +889,8 @@ enum km_kbp_tech_value { KM_KBP_TECH_UNSPECIFIED = 0, KM_KBP_TECH_MOCK = 1 << 0, KM_KBP_TECH_KMX = 1 << 1, - KM_KBP_TECH_LDML = 1 << 2 + KM_KBP_TECH_LDML = 1 << 2, + KM_KBP_TECH_RUST_MOCK = 1 << 3 }; diff --git a/common/core/desktop/meson.build b/common/core/desktop/meson.build index 60768ca2eb..498b467722 100644 --- a/common/core/desktop/meson.build +++ b/common/core/desktop/meson.build @@ -23,6 +23,29 @@ endif py = import('python3') python = py.find_python() +# Rust library path search + +if host_machine.system() == 'windows' + if host_machine.cpu_family() == 'x86' + rust_platform = 'x86' #i686-pc-windows-msvc in rust + else + rust_platform = 'x64' #x86_64-pc-windows-msvc in rust + endif +else + if host_machine.system() == 'darwin' + # todo we need to support M1 targets as well, eventually + rust_platform = 'arch' + else #linux + # we only build for our current arch + rust_platform = 'arch' + endif +endif + +rust_path = join_paths(meson.current_build_dir(), '..', '..', 'rust', rust_platform, get_option('buildtype')) +# message('rust library path is: '+rust_path) +cc = meson.get_compiler('c') +rust_mock_processor = cc.find_library('rust_mock_processor', dirs: rust_path) + subdir('doc') subdir('include') subdir('src') diff --git a/common/core/desktop/src/km_kbp_keyboard_api.cpp b/common/core/desktop/src/km_kbp_keyboard_api.cpp index 26948e72a2..8a2ef7b3ad 100644 --- a/common/core/desktop/src/km_kbp_keyboard_api.cpp +++ b/common/core/desktop/src/km_kbp_keyboard_api.cpp @@ -15,6 +15,7 @@ #include "processor.hpp" #include "kmx/kmx_processevent.hpp" #include "mock/mock_processor.hpp" +#include "rust/rust_mock_processor.hpp" using namespace km::kbp; @@ -29,6 +30,9 @@ namespace else if (kb_path.suffix() == ".mock") { return new mock_processor(kb_path); } + else if (kb_path.suffix() == ".rust_mock") { + return new rust_mock_processor(kb_path); + } else { return new null_processor(); } diff --git a/common/core/desktop/src/kmx/kmx_context.cpp b/common/core/desktop/src/kmx/kmx_context.cpp index ee391a6036..ffea32be4c 100644 --- a/common/core/desktop/src/kmx/kmx_context.cpp +++ b/common/core/desktop/src/kmx/kmx_context.cpp @@ -107,7 +107,7 @@ void KMX_Context::Set(const KMX_WCHAR *buf) } *q = 0; - pos = (intptr_t)(q-CurContext); + pos = (int)(intptr_t)(q-CurContext); CurContext[MAXCONTEXT-1] = 0; //DebugLog("KMX_Context::Set(%s): EXIT [%d]: %s", Debug_UnicodeString(buf), pos, Debug_UnicodeString(CurContext, 1)); diff --git a/common/core/desktop/src/meson.build b/common/core/desktop/src/meson.build index 9bc326f680..15af5a494f 100644 --- a/common/core/desktop/src/meson.build +++ b/common/core/desktop/src/meson.build @@ -33,7 +33,7 @@ if compiler.get_id() == 'gcc' or compiler.get_id() == 'clang' endif if host_machine.system() == 'darwin' - warns += ['-Wno-ctor-dtor-privacy', '-Wno-non-virtual-dtor '] + warns += ['-Wno-ctor-dtor-privacy', '-Wno-non-virtual-dtor'] endif endif @@ -49,34 +49,36 @@ if compiler.get_id() == 'msvc' endif lib = library('kmnkbp0', - 'option.cpp', - 'keyboard.cpp', - 'state.cpp', - 'km_kbp_context_api.cpp', - 'km_kbp_keyboard_api.cpp', - 'km_kbp_options_api.cpp', - 'km_kbp_state_api.cpp', - 'km_kbp_processevent_api.cpp', - 'json.cpp', - 'mock/mock_processor.cpp', - 'kmx/kmx_consts.cpp', - 'kmx/kmx_processevent.cpp', - 'kmx/kmx_actions.cpp', - 'kmx/kmx_capslock.cpp', - 'kmx/kmx_context.cpp', - 'kmx/kmx_debug.cpp', - 'kmx/kmx_environment.cpp', - 'kmx/kmx_file.cpp', - 'kmx/kmx_modifiers.cpp', - 'kmx/kmx_options.cpp', - 'kmx/kmx_processor.cpp', - 'kmx/kmx_xstring.cpp', - 'utfcodec.cpp', - cpp_args: defns + warns + flags, - link_args: links, - version: lib_version, - include_directories: inc, - install: true) + 'option.cpp', + 'keyboard.cpp', + 'state.cpp', + 'km_kbp_context_api.cpp', + 'km_kbp_keyboard_api.cpp', + 'km_kbp_options_api.cpp', + 'km_kbp_state_api.cpp', + 'km_kbp_processevent_api.cpp', + 'json.cpp', + 'mock/mock_processor.cpp', + 'rust/rust_mock_processor.cpp', + 'kmx/kmx_consts.cpp', + 'kmx/kmx_processevent.cpp', + 'kmx/kmx_actions.cpp', + 'kmx/kmx_capslock.cpp', + 'kmx/kmx_context.cpp', + 'kmx/kmx_debug.cpp', + 'kmx/kmx_environment.cpp', + 'kmx/kmx_file.cpp', + 'kmx/kmx_modifiers.cpp', + 'kmx/kmx_options.cpp', + 'kmx/kmx_processor.cpp', + 'kmx/kmx_xstring.cpp', + 'utfcodec.cpp', + cpp_args: defns + warns + flags, + link_args: links, + dependencies: [rust_mock_processor], + version: lib_version, + include_directories: inc, + install: true) kmnkbp = declare_dependency(link_with: lib, include_directories: inc) diff --git a/common/core/desktop/src/rust/Cargo.lock b/common/core/desktop/src/rust/Cargo.lock new file mode 100644 index 0000000000..6e559e962d --- /dev/null +++ b/common/core/desktop/src/rust/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "rust_mock_processor" +version = "0.1.0" diff --git a/common/core/desktop/src/rust/Cargo.toml b/common/core/desktop/src/rust/Cargo.toml new file mode 100644 index 0000000000..294a63871c --- /dev/null +++ b/common/core/desktop/src/rust/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "rust_mock_processor" +version = "0.1.0" + +[lib] +name = "rust_mock_processor" +crate-type = ["staticlib"] diff --git a/common/core/desktop/src/rust/rust_mock_processor.cpp b/common/core/desktop/src/rust/rust_mock_processor.cpp new file mode 100644 index 0000000000..bbe269c0a2 --- /dev/null +++ b/common/core/desktop/src/rust/rust_mock_processor.cpp @@ -0,0 +1,79 @@ +/* + Copyright: © 2021 SIL International. + Description: This is an interface to a rust mock implementation. + TODO: Add a mechanism to trigger output of PERSIST_OPT & + RESET_OPT actions items, options support and context matching. + Authors: Marc Durdin +*/ + +#include "rust/rust_mock_processor.hpp" +#include "state.hpp" + +namespace +{ + // This is metadata about the Rust implementation + constexpr km_kbp_attr const engine_attrs = { + 256, + KM_KBP_LIB_CURRENT, + KM_KBP_LIB_AGE, + KM_KBP_LIB_REVISION, + KM_KBP_TECH_RUST_MOCK, + "SIL International" + }; + +} + +namespace km { + namespace kbp + { + rust_mock_processor::rust_mock_processor(kbp::path const & path) + : abstract_processor( + keyboard_attributes(path.stem(), u"3.145", path.parent(), { + option{KM_KBP_OPT_KEYBOARD, u"__test_point", u"not triggered"}, + })), + _options({ + {u"\x01__test_point", u"not triggered"}, + {u"\x02hello", u"-"} + }) + { + } + + char16_t const * rust_mock_processor::lookup_option(km_kbp_option_scope scope, + std::u16string const & key) const + { + // TODO: move to rust + auto i = _options.find(char16_t(scope) + key); + return i != _options.end() ? i->second.c_str() : nullptr; + } + + option rust_mock_processor::update_option(km_kbp_option_scope scope, + std::u16string const & key, + std::u16string const & value) + { + // TODO: move to rust + auto i = _options.find(char16_t(scope) + key); + if (i == _options.end()) return option(); + + i->second = value; + persisted_store()[key] = value; + return option(scope, key, i->second); + } + +extern "C" uint32_t rust_mock_process_event(uint16_t vk, uint16_t modifier); + + km_kbp_status rust_mock_processor::process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state) + { + (void)(state); + //km_kbd_rust_state rust_state(state); + return rust_mock_process_event(vk, modifier_state); + } + + km_kbp_attr const & rust_mock_processor::attributes() const { + return engine_attrs; + } + + km_kbp_status rust_mock_processor::validate() const { return KM_KBP_STATUS_OK; } + + km_kbp_status rust_null_processor::validate() const { return KM_KBP_STATUS_INVALID_ARGUMENT; } + } // namespace kbp +} // namespace km diff --git a/common/core/desktop/src/rust/rust_mock_processor.hpp b/common/core/desktop/src/rust/rust_mock_processor.hpp new file mode 100644 index 0000000000..03bbce5ab0 --- /dev/null +++ b/common/core/desktop/src/rust/rust_mock_processor.hpp @@ -0,0 +1,53 @@ +/* + Copyright: © 2021 SIL International. + Description: Internal keyboard class and adaptor class for the Rust API implementations. + Authors: Marc Durdin; starting from mock_processor.hpp +*/ + +#pragma once + +#include +#include +#include + +#include "processor.hpp" +#include "option.hpp" + +namespace km { +namespace kbp +{ + class rust_mock_processor : public abstract_processor + { + std::unordered_map _options; + + public: + rust_mock_processor(km::kbp::path const &); +// ~mock_processor() override; + + km_kbp_status process_event(km_kbp_state *state, + km_kbp_virtual_key vk, + uint16_t modifier_state) override; + + virtual km_kbp_attr const & attributes() const override; + km_kbp_status validate() const override; + + + + char16_t const * lookup_option(km_kbp_option_scope, + std::u16string const & key) const override; + option update_option(km_kbp_option_scope, + std::u16string const & key, + std::u16string const & value) override; + }; + + class rust_null_processor : public rust_mock_processor { + public: + rust_null_processor(): rust_mock_processor(path()) + { + _attributes = keyboard_attributes(u"null", u"0.0", path(), {}); + } + + km_kbp_status validate() const override; + }; +} // namespace kbp +} // namespace km diff --git a/common/core/desktop/src/rust/src/lib.rs b/common/core/desktop/src/rust/src/lib.rs new file mode 100644 index 0000000000..59ff1634e6 --- /dev/null +++ b/common/core/desktop/src/rust/src/lib.rs @@ -0,0 +1,123 @@ +/* +// An table of VKEY to character sequence mappings, one per shift state. + // An empty string inidicates no mapping. + constexpr char const table[2][256][4] = { + { + /*00-0F*/"","","","","","","","","","\t","","","","\015","","", + /*10-1F*/"","","","","","","","","","","","\033","","","","", + /*20-2F*/" ","","","","","","","","","","","","","","","", + /*30-3F*/"0","1","2","3","4","5","6","7","8","9","","","","","","", + /*40-4F*/"","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o", + /*50-5F*/"p","q","r","s","t","u","v","w","x","y","z","","","","","", + /*60-6F*/"0","1","2","3","4","5","6","7","8","9","*","+","","-",".","/", + /*70-7F*/"f1","f2","f3","f4","f5","f6","f7","f8","f9","f10","f11","f12","f13","f14","f15","f16", + /*80-8F*/"f17","f18","f19","f20","f24","","","","","","","","","","","", + /*90-9F*/"","","","","","","","","","","","","","","","", + /*A0-AF*/"","","","","","","","","","","","","","","","", + /*B0-BF*/"","","","","","","","","","",";","=",",","-",".","/", + /*C0-CF*/"`","","","","","","","","","","","","","","","", + /*D0-DF*/"","","","","","","","","","","","[","\\","]","'","", + /*E0-EF*/"","","","","","","","","","","","","","","","", + /*F0-FF*/"","","","","","","","","","","","","","","","", + }, + { + /*00-0F*/"","","","","","","","","","\t","","","","\015","","", + /*10-1F*/"","","","","","","","","","","","\033","","","","", + /*20-2F*/" ","","","","","","","","","","","","","","","", + /*30-3F*/")","!","@","#","$","%","^","&","*","(","","","","","","", + /*40-4F*/"","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O", + /*50-5F*/"P","Q","R","S","T","U","V","W","X","Y","Z","","","","","", + /*60-6F*/"","","","","","","","","","","","","","","","", + /*70-7F*/"","","","","","","","","","","","","","","","", + /*80-8F*/"","","","","","","","","","","","","","","","", + /*90-9F*/"","","","","","","","","","","","","","","","", + /*A0-AF*/"","","","","","","","","","","","","","","","", + /*B0-BF*/"","","","","","","","","","",":","+","<","_",">","?", + /*C0-CF*/"~","","","","","","","","","","","","","","","", + /*D0-DF*/"","","","","","","","","","","","{","|","}","\"","", + /*E0-EF*/"","","","","","","","","","","","","","","","", + /*F0-FF*/"","","","","","","","","","","","","","","","", + } + }; + + + assert(state); + if (!state) + return KM_KBP_STATUS_INVALID_ARGUMENT; + + try + { + // At the start of every process_event always clear the action_items + state->actions().clear(); + + switch (vk) + { + case KM_KBP_VKEY_BKSP: + state->context().pop_back(); + state->actions().push_backspace(); + break; + + case KM_KBP_VKEY_F2: + { + state->actions().push_persist( + update_option(KM_KBP_OPT_KEYBOARD, + u"__test_point", + u"F2 pressed test save.")); + break; + } + + case KM_KBP_VKEY_F4: + state->context().push_marker(KM_KBP_VKEY_QUOTE); + state->actions().push_marker(KM_KBP_VKEY_QUOTE); + break; + + default: + { + auto shift_state = bool(modifier_state & KM_KBP_MODIFIER_SHIFT); + // Only process further one of the shift states has something to output. + if (table[0][vk][0] || table[1][vk][0]) + { + auto char_seq = table[shift_state][vk]; + + for (auto c = char_seq; *c; ++c) + { + km_kbp_usv usv = *c; + state->context().push_character(usv); + state->actions().push_character(usv); + } + state->actions().commit(); + + return KM_KBP_STATUS_OK; + } + + // Both shift states output nothing, generate an alert. + state->actions().push_alert(); + break; + } + } + + state->actions().commit(); + } + catch (std::bad_alloc &) + { + state->actions().clear(); + return KM_KBP_STATUS_NO_MEM; + } + + return KM_KBP_STATUS_OK; + */ + +#[no_mangle] +pub extern "C" fn rust_mock_process_event(_vk: u16, _modifier: u16) -> u32 { + return 0; //KM_KBP_STATUS_OK +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rust_mock_process_event() { + assert_eq!(0, rust_mock_process_event(0, 0)); + } +} diff --git a/common/core/desktop/tests/meson.build b/common/core/desktop/tests/meson.build index f1f8f83e76..a8e6b427da 100644 --- a/common/core/desktop/tests/meson.build +++ b/common/core/desktop/tests/meson.build @@ -23,4 +23,5 @@ endif subdir('unit/json') subdir('unit/utftest') subdir('unit/kmnkbd') -subdir('unit/kmx') \ No newline at end of file +subdir('unit/kmx') +subdir('unit/rust_mock') \ No newline at end of file diff --git a/common/core/desktop/tests/unit/kmnkbd/meson.build b/common/core/desktop/tests/unit/kmnkbd/meson.build index 92aed00b17..08de6294f2 100644 --- a/common/core/desktop/tests/unit/kmnkbd/meson.build +++ b/common/core/desktop/tests/unit/kmnkbd/meson.build @@ -18,6 +18,7 @@ foreach t : tests cpp_args: defns, include_directories: [inc, libsrc], link_args: links, + dependencies: [rust_mock_processor], objects: lib.extract_all_objects()) test(t[0], bin) diff --git a/common/core/desktop/tests/unit/kmx/meson.build b/common/core/desktop/tests/unit/kmx/meson.build index 2c57275b86..36ea1d71c7 100644 --- a/common/core/desktop/tests/unit/kmx/meson.build +++ b/common/core/desktop/tests/unit/kmx/meson.build @@ -9,6 +9,7 @@ kmx = executable('kmx', 'kmx.cpp', cpp_args: defns, include_directories: [inc, libsrc], link_args: links, + dependencies: [rust_mock_processor], objects: lib.extract_all_objects()) copyfile = ['-c', 'import sys, shutil', 'shutil.copyfile(*sys.argv[1:])'] diff --git a/common/core/desktop/tests/unit/rust_mock/keyboard_api.cpp b/common/core/desktop/tests/unit/rust_mock/keyboard_api.cpp new file mode 100644 index 0000000000..5d5b01e047 --- /dev/null +++ b/common/core/desktop/tests/unit/rust_mock/keyboard_api.cpp @@ -0,0 +1,33 @@ +/* + Copyright: © 2021 SIL International. + Description: Tests for the Keyboard API family of functions rust_mock +*/ +#include + +#include +#include "path.hpp" + +//#include "keyboard.hpp" + +namespace +{ + km::kbp::path const test_kb_path = "/a/dummy/keyboard.rust_mock"; +} + +#define try_status(expr) \ +{auto __s = (expr); if (__s != KM_KBP_STATUS_OK) return 100*__LINE__+__s;} + +int main(int, char *[]) +{ + km_kbp_keyboard * test_kb = nullptr; + km_kbp_keyboard_attrs const * kb_attrs = nullptr; + + try_status(km_kbp_keyboard_load(test_kb_path.c_str(), &test_kb)); + try_status(km_kbp_keyboard_get_attrs(test_kb, &kb_attrs)); + if (kb_attrs->folder_path != test_kb_path.parent()) + return __LINE__; + + km_kbp_keyboard_dispose(test_kb); + + return 0; +} diff --git a/common/core/desktop/tests/unit/rust_mock/meson.build b/common/core/desktop/tests/unit/rust_mock/meson.build new file mode 100644 index 0000000000..c787cbc71b --- /dev/null +++ b/common/core/desktop/tests/unit/rust_mock/meson.build @@ -0,0 +1,20 @@ +# Copyright: © 2021 SIL International. +# Description: Cross platform build script to compile rust mock API unit tests. +# + +defns=['-DKMN_KBP_STATIC'] +tests = [ + ['rust-keyboard-api', 'keyboard_api.cpp'], + ['rust-state-api', 'state_api.cpp'] +] + +foreach t : tests + bin = executable(t[0], t[1], + cpp_args: defns, + include_directories: [inc, libsrc], + link_args: links, + dependencies: [rust_mock_processor], + objects: lib.extract_all_objects()) + + test(t[0], bin) +endforeach diff --git a/common/core/desktop/tests/unit/rust_mock/state_api.cpp b/common/core/desktop/tests/unit/rust_mock/state_api.cpp new file mode 100644 index 0000000000..fddbe5d2da --- /dev/null +++ b/common/core/desktop/tests/unit/rust_mock/state_api.cpp @@ -0,0 +1,213 @@ +/* + Copyright: © 2021 SIL International. + Description: Tests for the rust_mock interfaces. +*/ +#include +#include +#include +#include + +#include +#include "path.hpp" + +#include "state.hpp" + +#define try_status(expr) \ +{auto __s = (expr); if (__s != KM_KBP_STATUS_OK) std::exit(100*__LINE__+__s);} + +namespace +{ + std::string get_json_doc(km_kbp_state const& state) + { + size_t sz = 0; + try_status(km_kbp_state_to_json(&state, nullptr, &sz)); + std::string buf(sz-1, 0); + try_status(km_kbp_state_to_json(&state, &buf[0], &sz)); + + return buf; + } + + km_kbp_option_item test_env_opts[] = + { + {u"hello", u"world", 0}, + KM_KBP_OPTIONS_END + }; + +#if 0 +constexpr char const *doc1_expected = u8"\ +{\n\ + \"$schema\" : \"keyman/keyboardprocessor/doc/introspection.schema\",\n\ + \"keyboard\" : {\n\ + \"id\" : \"dummy\",\n\ + \"folder\" : \"\",\n\ + \"version\" : \"3.145\",\n\ + \"rules\" : []\n\ + },\n\ + \"context\" : [\n\ + \"H\",\n\ + \"e\",\n\ + \"l\",\n\ + \"l\",\n\ + \"o\",\n\ + \" \",\n\ + \"😁\",\n\ + \"S\",\n\ + \"I\",\n\ + \"L\"\n\ + ],\n\ + \"actions\" : [\n\ + { \"persist\" : { \"keyboard\" : { \"__test_point\" : \"F2 pressed test save.\" } } }\n\ + ]\n\ +}\n"; + +constexpr char const *doc2_expected = u8"\ +{\n\ + \"$schema\" : \"keyman/keyboardprocessor/doc/introspection.schema\",\n\ + \"keyboard\" : {\n\ + \"id\" : \"dummy\",\n\ + \"folder\" : \"\",\n\ + \"version\" : \"3.145\",\n\ + \"rules\" : []\n\ + },\n\ + \"context\" : [],\n\ + \"actions\" : []\n\ +}\n"; + + +constexpr km_kbp_option_item const expected_persist_opt = { + u"__test_point", + u"F2 pressed test save.", + KM_KBP_OPT_KEYBOARD +}; +#endif + +inline +bool operator==(km_kbp_option_item const & lhs, km_kbp_option_item const & rhs) +{ + return lhs.scope == rhs.scope + && std::u16string(lhs.key) == rhs.key + && std::u16string(lhs.value) == rhs.value; +} + + +bool operator==(km_kbp_action_item const & lhs, + km_kbp_action_item const & rhs) +{ + if (lhs.type != rhs.type) return false; + switch(lhs.type) + { + case KM_KBP_IT_CHAR: return lhs.character == rhs.character; + case KM_KBP_IT_MARKER: return lhs.marker == rhs.marker; + case KM_KBP_IT_PERSIST_OPT: return *lhs.option == *rhs.option; + default: break; + } + + return true; +} + +#ifdef assert +#undef assert +#endif +#define assert(expr) {if (!(expr)) return __LINE__; } +bool action_items(km_kbp_state const * state, + std::initializer_list const & expected) +{ + size_t n = 0; + auto act = km_kbp_state_action_items(state, &n); + + for (auto &rhs: expected) + if (!(*act++ == rhs)) return false; + + return true; +} + +} // namespace + +int main(int, char * []) +{ + km_kbp_keyboard * test_kb = nullptr; + km_kbp_state * test_state = nullptr, + * test_clone = nullptr; + try_status(km_kbp_keyboard_load(km::kbp::path("dummy.rust_mock").c_str(), &test_kb)); + + // Simple sanity tests. + try_status(km_kbp_state_create(test_kb, test_env_opts, &test_state)); + try_status(km_kbp_state_clone(test_state, &test_clone)); + // Check sub objects have been copied and not shared. + if (km_kbp_state_context(test_state) == km_kbp_state_context(test_clone)) + return __LINE__; + size_t n_actions = 0; + if (km_kbp_state_action_items(test_state, &n_actions) == nullptr + && n_actions != 0) + return __LINE__; + + // Lets add data and do some basic checks of options and km_kbp_context + km_kbp_context_item *citems = nullptr; + try_status(km_kbp_context_items_from_utf16(u"Hello 😁", &citems)); + try_status(km_kbp_context_set(km_kbp_state_context(test_state), citems)); + km_kbp_context_items_dispose(citems); + if(km_kbp_context_length(km_kbp_state_context(test_state)) != 7) + return __LINE__; + if(km_kbp_context_length(km_kbp_state_context(test_clone)) != 0) + return __LINE__; + + // Overwrite some data. + km_kbp_option_item new_opt[] = { + {u"hello", u"globe", KM_KBP_OPT_ENVIRONMENT}, + KM_KBP_OPTIONS_END}; + try_status(km_kbp_state_options_update(test_clone, new_opt)); + + // Test the engine + auto attrs = km_kbp_get_engine_attrs(test_state); + // Check the lib supplies our required interface. + if (attrs->current - attrs->age > KM_KBP_LIB_CURRENT + || attrs->current < KM_KBP_LIB_CURRENT) return __LINE__; + if (attrs->max_context < 16) return __LINE__; + + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_S, + KM_KBP_MODIFIER_SHIFT)); + + //TEMP + action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('S')}}}); + auto doc1 = get_json_doc(*test_state); + std::cout << doc1 << std::endl; +#if 0 + // TODO: enable these tests once we have the data propagating to Rust + assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('S')}}})); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_I, + KM_KBP_MODIFIER_SHIFT)); + assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('I')}}})); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_L, 0)); + assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('l')}}})); + + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_BKSP, 0)); + assert(action_items(test_state, {{KM_KBP_IT_BACK, {0,}, {0}}})); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_L, + KM_KBP_MODIFIER_SHIFT)); + assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('L')}}})); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F2,0)); + assert(action_items(test_state, {{KM_KBP_IT_PERSIST_OPT, {0,}, + {uintptr_t(&expected_persist_opt)}}})); + + // Test debug dump + auto doc1 = get_json_doc(*test_state), + doc2 = get_json_doc(*test_clone); + + std::cout << doc1 << std::endl; + std::cout << doc2 << std::endl; + + // These should not be equal. + if (doc1 == doc2) return __LINE__; + // These should be. + if (doc1 != doc1_expected) return __LINE__; + if (doc2 != doc2_expected) return __LINE__; +#endif + + // Destroy them + km_kbp_state_dispose(test_state); + km_kbp_state_dispose(test_clone); + km_kbp_keyboard_dispose(test_kb); + + + return 0; +} diff --git a/linux/Makefile b/linux/Makefile index e1379b4bb7..5329d79380 100644 --- a/linux/Makefile +++ b/linux/Makefile @@ -32,7 +32,7 @@ sources: ./scripts/dist.sh tmpsources: - PKG_CONFIG_PATH="../keyboardprocessor/meson-private" ./scripts/dist.sh + PKG_CONFIG_PATH="../keyboardprocessor/arch/release/meson-private" ./scripts/dist.sh origdist: ./scripts/dist.sh origdist diff --git a/linux/README.md b/linux/README.md index fa11d59741..5e016355b0 100644 --- a/linux/README.md +++ b/linux/README.md @@ -24,7 +24,7 @@ See [license information](./LICENSE.md) about licensing. libjson-glib-dev libgtk-3-dev libxml2-utils help2man python3-lxml \ python3-magic python3-numpy python3-pil python3-pip python3-qrcode \ python3-requests python3-requests-cache python3 python3-gi dconf-cli \ - dconf-editor python3-dbus + dconf-editor cargo python3-dbus ``` ## Compiling from Command Line diff --git a/linux/README.repo b/linux/README.repo index c6f28a47c7..708cda84bd 100644 --- a/linux/README.repo +++ b/linux/README.repo @@ -2,6 +2,6 @@ You need autoconf, autopoint, gettext, automake and libtool to generate the buil flex and bison are useful to rebuild lex.l and yacc.y in kmflcomp but not essential -For keyboardprocessor you need meson and ninja (meson package pulls both in) +For keyboardprocessor you need rust, meson and ninja (meson package pulls in ninja) Run `make reconf` to run them diff --git a/linux/build/agent/dependencies.config b/linux/build/agent/dependencies.config index 6925cc72b8..1a6909f30b 100644 --- a/linux/build/agent/dependencies.config +++ b/linux/build/agent/dependencies.config @@ -20,7 +20,7 @@ # any=@precise.any [common] -any=git autotools-dev build-essential dh-autoreconf libibus-1.0-dev flex bison meson python3 python3-pip +any=git autotools-dev build-essential dh-autoreconf libibus-1.0-dev flex bison meson python3 python3-pip cargo [xenial] any=libx11-dev diff --git a/linux/scripts/build.sh b/linux/scripts/build.sh index 850f07f409..f3608b0496 100755 --- a/linux/scripts/build.sh +++ b/linux/scripts/build.sh @@ -19,10 +19,9 @@ if [[ "${CONFIGUREONLY}" != "no" && "${BUILDONLY}" != "no" ]]; then fi if [[ "${BUILDONLY}" == "no" ]]; then - if [ ! -d keyboardprocessor ]; then - meson ../common/core/desktop keyboardprocessor - fi - cd keyboardprocessor + ../common/core/desktop/build.sh -t keyboardprocessor configure + + cd keyboardprocessor/arch/release echo "reconfiguring keyboardprocessor meson with prefix ${INSTALLDIR}" meson configure -Dprefix=${INSTALLDIR} && ninja reconfigure cd $BASEDIR @@ -30,9 +29,10 @@ fi if [[ "${CONFIGUREONLY}" == "no" ]]; then echo "building keyboardprocessor" - cd keyboardprocessor - ninja - cd $BASEDIR + # May 2021: For now, running tests here as well. We could move this elsewhere + # in the future if we want to split out the tests, but they run in a couple of seconds + # at present. + ../common/core/desktop/build.sh -t keyboardprocessor build tests fi for proj in kmflcomp libkmfl ibus-kmfl ibus-keyman; do @@ -48,15 +48,15 @@ for proj in kmflcomp libkmfl ibus-kmfl ibus-keyman; do if [[ "${BUILDONLY}" == "no" ]]; then echo "Configuring $proj" if [[ "${INSTALLDIR}" == "/tmp/kmfl" ]]; then # don't install ibus-kmfl or ibus-keyman into ibus - ../$proj/configure KEYMAN_PROC_CFLAGS="-I\$(top_builddir)/../keyboardprocessor/include -I\$(top_builddir)/../../common/core/desktop/include" \ + ../$proj/configure KEYMAN_PROC_CFLAGS="-I\$(top_builddir)/../keyboardprocessor/arch/release/include -I\$(top_builddir)/../../common/core/desktop/include" \ CPPFLAGS="-I\$(top_builddir)/../build-kmflcomp -I\$(top_builddir)/../build-libkmfl" \ - KEYMAN_PROC_LIBS="-L`pwd`/../build-libkmfl/src -L`pwd`/../keyboardprocessor/src -lkmnkbp0" \ + KEYMAN_PROC_LIBS="-L`pwd`/../build-libkmfl/src -L`pwd`/../keyboardprocessor/arch/release/src -lkmnkbp0" \ LDFLAGS="-L`pwd`/../build-kmflcomp/src -L`pwd`/../build-libkmfl/src" --prefix=${INSTALLDIR} --libexecdir=${INSTALLDIR}/lib/ibus else # install ibus-kmfl and ibus-keyman into ibus - ../$proj/configure KEYMAN_PROC_CFLAGS="-I\$(top_builddir)/../keyboardprocessor/include -I\$(top_builddir)/../../common/core/desktop/include" \ + ../$proj/configure KEYMAN_PROC_CFLAGS="-I\$(top_builddir)/../keyboardprocessor/arch/release/include -I\$(top_builddir)/../../common/core/desktop/include" \ CPPFLAGS="-I\$(top_builddir)/../build-kmflcomp -I\$(top_builddir)/../build-libkmfl" \ LDFLAGS="-L`pwd`/../build-kmflcomp/src -L`pwd`/../build-libkmfl/src" \ - KEYMAN_PROC_LIBS="-L`pwd`/../build-libkmfl/src -L`pwd`/../keyboardprocessor/src -lkmnkbp0" \ + KEYMAN_PROC_LIBS="-L`pwd`/../build-libkmfl/src -L`pwd`/../keyboardprocessor/arch/release/src -lkmnkbp0" \ --prefix=${INSTALLDIR} --libexecdir=${INSTALLDIR}/lib/ibus --datadir=/usr/share fi if [ -d ../$proj/include ]; then diff --git a/linux/scripts/dist.sh b/linux/scripts/dist.sh index b65f840726..36a237a705 100755 --- a/linux/scripts/dist.sh +++ b/linux/scripts/dist.sh @@ -84,6 +84,9 @@ for proj in ${extra_projects}; do kbpvers="keyman-keyboardprocessor-$VERSION" cp -a desktop $kbpvers cp ../../VERSION.md $kbpvers + cp ../../TIER.md $kbpvers + mkdir -p $kbpvers/scripts + cp ../../resources/shellHelperFunctions.sh $kbpvers/scripts tar cvzf $kbpvers.tar.gz --exclude=debian --exclude=build --exclude=.gitignore $kbpvers rm -rf $kbpvers cp $kbpvers.tar.gz ../../linux/dist diff --git a/linux/scripts/install.sh b/linux/scripts/install.sh index d9db4f4ec0..63fadbfe74 100755 --- a/linux/scripts/install.sh +++ b/linux/scripts/install.sh @@ -47,7 +47,7 @@ if [ -f "/usr/share/ibus/component/keyman.xml" ] && [ "${SUDOINSTALL}" == "yes" fi -cd keyboardprocessor +cd keyboardprocessor/arch/release ninja install cd $BASEDIR diff --git a/linux/scripts/jenkins.sh b/linux/scripts/jenkins.sh index 9d8ff8568c..a45f3f9658 100755 --- a/linux/scripts/jenkins.sh +++ b/linux/scripts/jenkins.sh @@ -42,7 +42,7 @@ checkAndInstallRequirements() for p in dh-python gir1.2-webkit2-4.0 python3-all python3-setuptools \ python3-requests python3-requests-cache python3-numpy python3-pil python3-lxml \ - python3-gi python3-magic python3-qrcode python3-dbus + python3-gi python3-magic python3-qrcode cargo build-essential python3-dbus do if ! dpkg -l | grep -q $p; then TOINSTALL="$TOINSTALL $p" diff --git a/linux/scripts/reconf.sh b/linux/scripts/reconf.sh index 8ea015955d..a30199c40b 100755 --- a/linux/scripts/reconf.sh +++ b/linux/scripts/reconf.sh @@ -54,7 +54,7 @@ for proj in ${extra_projects}; do if [ "${proj}" == "keyboardprocessor" ]; then rm -rf keyboardprocessor cp ../VERSION.md ../common/core/desktop/ - meson ../common/core/desktop keyboardprocessor + ../common/core/desktop/build.sh -t keyboardprocessor configure fi if [ "${proj}" == "keyman-config" ]; then cd keyman-config diff --git a/resources/build/trigger-definitions.inc.sh b/resources/build/trigger-definitions.inc.sh index 1610bc6bcc..380728ab37 100644 --- a/resources/build/trigger-definitions.inc.sh +++ b/resources/build/trigger-definitions.inc.sh @@ -11,8 +11,8 @@ available_platforms=(android ios linux mac web windows) watch_android='web|common/models|common/predictive-text|common/lexical-model-types|common/core/web' watch_ios='web|common/models|common/predictive-text|common/lexical-model-types|common/core/web' -watch_linux='common/engine' -watch_mac='common/engine' +watch_linux='common/core/desktop' +watch_mac='common/core/desktop' watch_web='common/models|common/predictive-text|common/lexical-model-types|common/core/web' # Windows currently builds Developer and Desktop, so we need everything from common,developer,web diff --git a/resources/shellHelperFunctions.sh b/resources/shellHelperFunctions.sh index 5d78bed831..49b296df3e 100755 --- a/resources/shellHelperFunctions.sh +++ b/resources/shellHelperFunctions.sh @@ -1,5 +1,11 @@ #!/bin/bash +# +# WARNING: this file is copied into other locations during package builds; do not +# include other files or make path assumptions when changing this file. +# See `common/core/desktop/build.sh` for more details. +# + _shf_base_dir=$(dirname "$BASH_SOURCE")/.. # Designed to determine which set of browsers should be available for local testing, @@ -254,7 +260,7 @@ set_npm_version () { if [ $# == 0 ]; then fail "set_npm_version requires a specified version." fi - + local version=$1 # We use --no-git-tag-version because our CI system controls version numbering and # already tags releases. We also want to have the version of this match the @@ -303,7 +309,7 @@ verify_npm_setup () { # Ensure the repo's base resources are installed. (This ensures the local `lerna` install is available.) npm install --no-optional - # Use lerna to ensure repo-internal dependencies are all properly linked + # Use lerna to ensure repo-internal dependencies are all properly linked # while also installing external dependencies. Also propagates lerna into # each project that can use it (once added as a dev-dependency there) # @@ -328,7 +334,7 @@ verify_npm_setup () { # npx lerna bootstrap -- --no-optional # ONLY SAFE AT REPO'S BASE (otherwise, temporarily re-downloads `lerna`!) # - or - # npm run lerna -- bootstrap -- -- --no-optional # Possible to use within sub-packages with proper setup. (See below.) - + # Note: calling the above WITHOUT changing directories to $KEYMAN_ROOT will re-download lerna. # # Instead, configuring each package like this: diff --git a/windows/src/engine/keyman32/Keyman32.vcxproj b/windows/src/engine/keyman32/Keyman32.vcxproj index ac082fe0f4..874c0e8eb8 100644 --- a/windows/src/engine/keyman32/Keyman32.vcxproj +++ b/windows/src/engine/keyman32/Keyman32.vcxproj @@ -53,6 +53,14 @@ + + $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\common\core\desktop\build\rust\x86\$(Configuration);$(LibraryPath) + $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\include;$(IncludePath) + + + $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\common\core\desktop\build\rust\x86\$(Configuration);$(LibraryPath) + $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\include;$(IncludePath) + NDEBUG;%(PreprocessorDefinitions) @@ -90,7 +98,7 @@ /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + libkmnkbp0.a;rust_mock_processor.lib;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) ./Keyman32.dll true keyman32.def @@ -152,7 +160,7 @@ /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + libkmnkbp0.a;rust_mock_processor.lib;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) ./Keyman32.dll true keyman32.def diff --git a/windows/src/engine/keyman32/Makefile b/windows/src/engine/keyman32/Makefile index b57852a6cb..6dfe14b9bf 100644 --- a/windows/src/engine/keyman32/Makefile +++ b/windows/src/engine/keyman32/Makefile @@ -4,7 +4,7 @@ !include ..\..\Defines.mak -build: version.res dirs +build: version.res dirs pull-core $(MSBUILD) keyman32.vcxproj $(MSBUILD_BUILD) $(COPY) keyman32.dll $(PROGRAM)\engine $(COPY) keyman32.pdb $(DEBUGPATH)\engine @@ -33,4 +33,17 @@ install: echo You may want to manually tweak keyman-debug-etw.man and fill in $(INSTALLPATH_KEYMANENGINE)\keyman32.dll echo and then run wevtutil im keyman-debug-etw.man to get the latest event tracing +!ifdef DEBUG +CORE_DEBUG=--debug +MESON_TARGET=debug +!else +MESON_TARGET=release +!endif + +pull-core: + cd $(ROOT)\..\common\core\desktop + build_cargo.bat $(CORE_DEBUG) + build.bat all $(MESON_TARGET) --static build tests + cd $(ROOT)\src\engine\keyman32 + !include ..\..\Target.mak diff --git a/windows/src/engine/keyman64/keyman64.vcxproj b/windows/src/engine/keyman64/keyman64.vcxproj index e4466b60b0..a753b2aaf3 100644 --- a/windows/src/engine/keyman64/keyman64.vcxproj +++ b/windows/src/engine/keyman64/keyman64.vcxproj @@ -81,6 +81,14 @@ + + $(ProjectDir)..\..\..\..\common\core\desktop\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\common\core\desktop\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(ProjectDir)..\..\..\..\common\core\desktop\build\x64\$(Configuration)\include;$(IncludePath) + + + $(ProjectDir)..\..\..\..\common\core\desktop\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\common\core\desktop\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(ProjectDir)..\..\..\..\common\core\desktop\build\x64\$(Configuration)\include;$(IncludePath) + NDEBUG;%(PreprocessorDefinitions) @@ -119,7 +127,7 @@ /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - psapi.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;libcmt.lib;%(AdditionalDependencies) + rust_mock_processor.lib;libkmnkbp0.a;psapi.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;libcmt.lib;%(AdditionalDependencies) ./Keyman64.dll true C:\Program Files\Microsoft SDKs\Windows\v7.0\Lib\x64;$(VCInstallDir)lib\amd64;$(VCInstallDir)lib;%(AdditionalLibraryDirectories) @@ -181,7 +189,7 @@ /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - psapi.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;libcmt.lib;%(AdditionalDependencies) + rust_mock_processor.lib;libkmnkbp0.a;psapi.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;libcmt.lib;%(AdditionalDependencies) ./Keyman64.dll true C:\Program Files\Microsoft SDKs\Windows\v7.0A\Lib\x64;$(VCInstallDir)lib\amd64;$(VCInstallDir)lib;%(AdditionalLibraryDirectories)