diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index a9957eb..0000000 --- a/.appveyor.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: 0.7.0-{build} - -image: Ubuntu1804 - -install: -- sh: sudo apt-get update -- sh: sudo apt-get install -y git cmake libpurple-dev libmxml-dev libxml2-dev libsqlite3-dev libgcrypt20-dev build-essential libglib2.0-dev libcmocka-dev --no-install-recommends -- sh: git submodule update --init --recursive - -build_script: -- sh: make - -test_script: -- sh: CMOCKA_MESSAGE_OUTPUT=XML CMOCKA_XML_FILE=build/cmocka_results_%g.xml make test --ignore-errors - -after_test: -- sh: find build/ -type f -name *.xml -exec curl -v -F "file=@$APPVEYOR_BUILD_FOLDER/{}" "https://ci.appveyor.com/api/testresults/junit/$APPVEYOR_JOB_ID" \; -- sh: bash <(curl -s https://codecov.io/bash) -g test/ -B $APPVEYOR_REPO_BRANCH -b $APPVEYOR_BUILD_VERSION - -artifacts: - - path: build/lurch.so - name: lurch-$APPVEYOR_BUILD_VERSION-$APPVEYOR_REPO_COMMIT.so diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e4de6e0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# Copyright (c) 2022 Sebastian Pipping +# Licensed under the GPL v2 or later + +version: 2 +updates: + + - package-ecosystem: "github-actions" + commit-message: + include: "scope" + prefix: "Actions" + directory: "/" + labels: + - "enhancement" + schedule: + interval: "weekly" diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml new file mode 100644 index 0000000..6003ed0 --- /dev/null +++ b/.github/workflows/linux.yml @@ -0,0 +1,107 @@ +# Copyright (c) 2022 Sebastian Pipping +# Licensed under the GPL v2 or later + +name: Build for Linux + +on: + pull_request: + push: + schedule: + - cron: '0 2 * * 5' # Every Friday at 2am + +jobs: + checks: + name: Build for Linux (shared=${{ matrix.BUILD_SHARED_LIBS }}, syslibs=${{ matrix.syslibs }}) + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + BUILD_SHARED_LIBS: ['ON', 'OFF'] + syslibs: ['ON', 'OFF'] + steps: + + - uses: actions/checkout@v4.1.4 + with: + submodules: recursive + + - name: Install build dependencies (but axc and libomemo) + run: |- + set -x + sudo apt-get update + sudo apt-get install --yes --no-install-recommends -V \ + gcovr \ + libcmocka-dev \ + libgcrypt20-dev \ + libglib2.0-dev \ + libmxml-dev \ + libpurple-dev \ + libsignal-protocol-c-dev \ + libsqlite3-dev \ + libxml2-dev \ + ninja-build + + - name: Install build dependency axc system-wide + if: matrix.syslibs == 'ON' + run: |- + set -x + cd lib/axc + cmake \ + -B build \ + -G Ninja \ + -DBUILD_SHARED_LIBS=${{ matrix.BUILD_SHARED_LIBS }} \ + -DCMAKE_INSTALL_PREFIX=/usr + ninja -v -C build all + sudo ninja -v -C build install + + - name: Install build dependency libomemo system-wide + if: matrix.syslibs == 'ON' + run: |- + set -x + cd lib/libomemo + cmake \ + -B build \ + -G Ninja \ + -DBUILD_SHARED_LIBS=${{ matrix.BUILD_SHARED_LIBS }} \ + -DCMAKE_INSTALL_PREFIX=/usr + ninja -v -C build all + sudo ninja -v -C build install + + - name: Configure + run: |- + set -x + cmake \ + -B build \ + -G Ninja \ + -DBUILD_SHARED_LIBS=${{ matrix.BUILD_SHARED_LIBS }} \ + -DLURCH_WITH_SYSTEM_AXC=${{ matrix.syslibs }} \ + -DLURCH_WITH_SYSTEM_OMEMO=${{ matrix.syslibs }} \ + -D_LURCH_WITH_COVERAGE=ON + + - name: Build + run: |- + set -x -o pipefail + ninja -v -C build all + readelf -d build/lurch.so | grep 'NEEDED\|RUNPATH' | sort + + - name: Test + run: |- + set -x + CTEST_OUTPUT_ON_FAILURE=1 ninja -C build test + ninja -C build coverage + + - name: Install + run: |- + set -x -o pipefail + + DESTDIR="${PWD}"/ROOT ninja -v -C build install + find ROOT/ -not -type d | sort | xargs ls -l + + ninja -v -C build install-home + find ~/.purple/plugins/ -not -type d | sort | xargs ls -l + + - name: Store coverage HTML report + uses: actions/upload-artifact@v4.3.3 + with: + name: lurch_coverage_shared_${{ matrix.BUILD_SHARED_LIBS }}_syslibs_${{ matrix.syslibs }}_${{ github.sha }} + path: build/coverage*.html + if-no-files-found: error diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000..1427e20 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,89 @@ +# Copyright (c) 2022 Sebastian Pipping +# Licensed under the GPL v2 or later + +name: Build for Windows + +on: + pull_request: + push: + schedule: + - cron: '0 2 * * 5' # Every Friday at 2am + +jobs: + checks: + name: Build for Windows (shared=${{ matrix.BUILD_SHARED_LIBS }}) + runs-on: windows-2019 + strategy: + fail-fast: false + matrix: + BUILD_SHARED_LIBS: ['ON', 'OFF'] + defaults: + run: + shell: msys2 {0} + steps: + + - uses: actions/checkout@v4.1.4 + with: + submodules: recursive + + - name: Install build dependencies + uses: msys2/setup-msys2@v2 + with: + install: | + cmake + mingw-w64-x86_64-cmocka + mingw-w64-x86_64-glib2 + mingw-w64-x86_64-libgcrypt + mingw-w64-x86_64-libsignal-protocol-c + mingw-w64-x86_64-libxml2 + mingw-w64-x86_64-mxml + mingw-w64-x86_64-pidgin + mingw-w64-x86_64-sqlite3 + mingw-w64-x86_64-toolchain + ninja + + - name: Configure + run: |- + set -x + cmake \ + -B build \ + -G Ninja \ + -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_SYSTEM_NAME=Windows -DWIN32=ON -DMINGW=ON \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DBUILD_SHARED_LIBS=${{ matrix.BUILD_SHARED_LIBS }} \ + -DLURCH_WITH_SYSTEM_AXC=OFF \ + -DLURCH_WITH_SYSTEM_OMEMO=OFF + + - name: Build + run: |- + set -x -o pipefail + ninja -v -C build all + objdump -p build/lurch*.dll | fgrep 'DLL Name' | sort -u | nl + + - name: Gather transitive DLL closure + run: |- + set -x -o pipefail + DLL_HOME=/mingw64/bin/ BUILD_DIR=build/ ./copy_dll_closure.sh build/lurch*.dll build/ + ls -lh build/*.dll + + - name: Test (KNOWN TO FAIL) + run: |- + set -x + CTEST_OUTPUT_ON_FAILURE=1 ninja -C build test || true # TODO fix tests + + - name: Install + run: |- + set -x -o pipefail + + DESTDIR="${PWD}"/ROOT ninja -v -C build install + find ROOT/ -not -type d | sort | xargs ls -l + + ninja -v -C build install-home + find ~/.purple/plugins/ -not -type d | sort | xargs ls -l + + - name: Store Windows binaries + uses: actions/upload-artifact@v4.3.3 + with: + name: lurch_win32bin_shared_${{ matrix.BUILD_SHARED_LIBS }}_${{ github.sha }} + path: build/*.dll + if-no-files-found: error diff --git a/CHANGELOG.md b/CHANGELOG.md index ad75546..1803b13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [UNRELEASED] +### Changed +- Migrate build system from a Makefile to CMake ([#177](https://github.com/gkdr/lurch/pull/177)) (thanks, [@hartwork](https://github.com/hartwork)!) + +### Infrastructure +- Cover Linux build by GitHub Actions CI ([#177](https://github.com/gkdr/lurch/pull/177)) (thanks, [@hartwork](https://github.com/hartwork)!) +- Cover Windows build by GitHub Actions CI using msys2 ([#177](https://github.com/gkdr/lurch/pull/177)) (thanks, [@hartwork](https://github.com/hartwork)!) + ## [0.7.0] - 2021-02-12 ### Added - This file. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..68848dd --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,353 @@ +# Copyright (c) 2022 Sebastian Pipping +# Licensed under the GPL v2 or later + +cmake_minimum_required(VERSION 3.16.3) + +project(lurch + VERSION + 0.7.0 + LANGUAGES + C +) + +include(FindPkgConfig) +include(GNUInstallDirs) + + +# +# Public configuration +# +option(BUILD_SHARED_LIBS "Build shared libraries (rather than static ones)" ON) +option(LURCH_INSTALL "Install build artifacts" ON) +option(LURCH_WITH_TESTS "Build test suite (depends on cmocka)" ON) +option(LURCH_WITH_SYSTEM_AXC "Use system-wide axc (rather than the bundled copy)" ON) +option(LURCH_WITH_SYSTEM_OMEMO "Use system-wide libomemo (rather than the bundled copy)" ON) +if(NOT _LURCH_HELP) # hide from "cmake -D_LURCH_HELP=ON -LH ." output + option(_LURCH_WARNINGS_AS_ERRORS "(Unofficial!) Turn warnings into errors" OFF) + option(_LURCH_WITH_COVERAGE "(Unofficial!) Build with coverage" OFF) +endif() + + +# +# Global CPPFLAGS and CFLAGS +# +add_compile_definitions( + _XOPEN_SOURCE=700 + _DEFAULT_SOURCE +) +add_compile_options( + -std=c99 + -Wall + -Wstrict-overflow +) + +if(_LURCH_WARNINGS_AS_ERRORS) + add_compile_options(-Werror) +endif() + +if(_LURCH_WITH_COVERAGE) + set(_LURCH_COVERAGE_FLAGS -g -O0 --coverage) + add_compile_options(${_LURCH_COVERAGE_FLAGS}) + link_libraries(${_LURCH_COVERAGE_FLAGS}) +endif() + + +# +# Build dependencies +# +function(_lurch_add_bundled name source_dir) + add_subdirectory(${source_dir}) + get_directory_property(_bundled_version DIRECTORY ${source_dir} DEFINITION PROJECT_VERSION) + message(STATUS "Checking for module '${name}'") # sending this late to workaround output ordering issues + message(STATUS " Found ${name}, version ${_bundled_version}(+) -- bundle from ${source_dir}") +endfunction() + +function(_lurch_complain_if_missing var_prefix module_name flag_name) + if(NOT ${var_prefix}_FOUND) + message(SEND_ERROR + "Required package \"${module_name}\" was not found. " + "Please either " + "(a) install the related package of your operating system or " + "(b) pass argument -D${flag_name}=OFF to CMake " + "to use the bundled copy of ${module_name}.") + endif() +endfunction() + +# NOTE: We cannot use "pkg_check_modules([..] IMPORTED_TARGET [..])" +# because we'd run into a (false positive) CMake error +# "contains relative path in its INTERFACE_INCLUDE_DIRECTORIES" +# when using "target_link_libraries([..] PkgConfig::[..])" with msys2. +if(LURCH_WITH_SYSTEM_AXC) + # NOTE: Avoiding REQUIRED just to do custom messaging + pkg_check_modules(AXC "libaxc") + _lurch_complain_if_missing(AXC "libaxc" LURCH_WITH_SYSTEM_AXC) +else() + _lurch_add_bundled(libaxc ${CMAKE_CURRENT_SOURCE_DIR}/lib/axc) + if(BUILD_SHARED_LIBS) + set(_LURCH_AXC_BINARY_GLOB ${CMAKE_CURRENT_BINARY_DIR}/lib/axc/libaxc${CMAKE_SHARED_LIBRARY_SUFFIX}*) + endif() +endif() + +if(LURCH_WITH_SYSTEM_OMEMO) + # NOTE: Avoiding REQUIRED just to do custom messaging + pkg_check_modules(OMEMO "libomemo>=0.8.0") + _lurch_complain_if_missing(OMEMO "libomemo" LURCH_WITH_SYSTEM_OMEMO) +else() + _lurch_add_bundled(libomemo ${CMAKE_CURRENT_SOURCE_DIR}/lib/libomemo) + if(BUILD_SHARED_LIBS) + set(_LURCH_OMEMO_BINARY_GLOB ${CMAKE_CURRENT_BINARY_DIR}/lib/libomemo/libomemo${CMAKE_SHARED_LIBRARY_SUFFIX}*) + endif() +endif() + +if(LURCH_WITH_TESTS) + pkg_check_modules(CMOCKA REQUIRED "cmocka") +endif() + +pkg_check_modules(GLIB REQUIRED "glib-2.0") +pkg_check_modules(GMODULE REQUIRED "gmodule-2.0") +pkg_check_modules(PURPLE REQUIRED "purple") +pkg_check_modules(SIGNAL REQUIRED "libsignal-protocol-c") +pkg_check_modules(XML REQUIRED "libxml-2.0") +pkg_get_variable(PURPLE_PLUGINDIR "purple" "plugindir") + + +# +# C Pidgin plug-in (with public symbol "purple_init_plugin") +# plus an equivalent +# internal C library for tests to link against +# +file(GLOB _LURCH_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/lurch*.[ch]) +add_library(lurch_internal STATIC ${_LURCH_SOURCES}) + +if(WIN32) + set(_LURCH_LIBRARY_TYPE MODULE) +else() + set(_LURCH_LIBRARY_TYPE SHARED) +endif() + +# NOTE: On Linux, by default CMake builds binaries with one set of RPATH +# and then sets a different value at install time. +# For "make install-home" we need a binary that already has the right +# RPATH set at build time because there is no install phase where CMake +# would change RPATH for us. If we'd use the install-time RPATH +# right away, then the tests wouldn't run, at least not with bundled +# axc or libomemo, and at least not the test_lurch_loadable one. +# So we separate those two things into two distinct binaries on Linux. +# On Windows, RPATH is not a topic and so we don't need to even build +# that extra binary on Windows. +if(NOT WIN32) + set(_LURCH_HOME_TARGET lurch_home) +ENDIF() + +foreach(_target lurch ${_LURCH_HOME_TARGET}) + add_library(${_target} ${_LURCH_LIBRARY_TYPE} ${_LURCH_SOURCES}) + set_target_properties(${_target} PROPERTIES PREFIX "") # for lurch.so not liblurch.so +endforeach() + +if(WIN32) + if(BUILD_SHARED_LIBS) + set(_LURCH_OUTPUT_NAME lurch_fully_shared) + else() + set(_LURCH_OUTPUT_NAME lurch_semi_shared) + endif() + set_target_properties(lurch PROPERTIES OUTPUT_NAME ${_LURCH_OUTPUT_NAME}) +else() + set(_LURCH_OUTPUT_NAME lurch) +endif() + +if(LURCH_INSTALL) + install(TARGETS lurch + ARCHIVE DESTINATION ${PURPLE_PLUGINDIR} + LIBRARY DESTINATION ${PURPLE_PLUGINDIR} + RUNTIME DESTINATION ${PURPLE_PLUGINDIR} + ) +endif() + +if(WIN32) + set(_LURCH_INSTALL_HOME_DEPENDS lurch) + set(_LURCH_INSTALL_HOME_OUTPUT_NAME ${_LURCH_OUTPUT_NAME}) +else() + set(_LURCH_INSTALL_HOME_DEPENDS ${_LURCH_HOME_TARGET}) + set(_LURCH_INSTALL_HOME_OUTPUT_NAME ${_LURCH_HOME_TARGET}) +endif() + +add_custom_target(install-home + COMMAND mkdir -p ~/.purple/plugins + COMMAND cp -v + ${CMAKE_CURRENT_BINARY_DIR}/${_LURCH_INSTALL_HOME_OUTPUT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX} + ${_LURCH_AXC_BINARY_GLOB} + ${_LURCH_OMEMO_BINARY_GLOB} + ~/.purple/plugins/ + DEPENDS ${_LURCH_INSTALL_HOME_DEPENDS} +) + +# NOTE: This is a 1:1 port of a potentially outdated check +# for the name of the Pidgin jabber plug-in acrosses operating systems. +# If it turns out outdated, please open bug upstream. Thank you! +if(EXISTS /etc/redhat-release) + set(_LURCH_OS "Red Hat Linux") + set(_LURCH_LIBJABBER xmpp) +elseif(EXISTS /etc/SuSE-release) + set(_LURCH_OS "openSUSE Linux") + set(_LURCH_LIBJABBER xmpp) +else() + if(WIN32) + set(_LURCH_OS "generic Windows") + else() + set(_LURCH_OS "generic Unix") + endif() + set(_LURCH_LIBJABBER jabber) +endif() +message(STATUS "Detected ${_LURCH_OS}; will link with -l${_LURCH_LIBJABBER} -L${PURPLE_PLUGINDIR} for Jabber plug-in") + +foreach(_target lurch lurch_internal ${_LURCH_HOME_TARGET}) + target_link_directories(${_target} PUBLIC ${PURPLE_PLUGINDIR}) + target_link_libraries(${_target} PRIVATE -l${_LURCH_LIBJABBER}) +endforeach() + +if(NOT WIN32) + set_target_properties(lurch PROPERTIES INSTALL_RPATH ${PURPLE_PLUGINDIR}) + set_target_properties(${_LURCH_HOME_TARGET} PROPERTIES + INSTALL_RPATH "\$ORIGIN;${PURPLE_PLUGINDIR}" + BUILD_WITH_INSTALL_RPATH ON + ) +endif() + + +# +# C test suite +# +if(LURCH_WITH_TESTS) + set(_LURCH_TEST_TARGETS test_lurch_api test_lurch_crypto test_lurch_util test_lurch_loadable) + + enable_testing() + + foreach(_target ${_LURCH_TEST_TARGETS}) + add_executable(${_target} ${CMAKE_CURRENT_SOURCE_DIR}/test/${_target}.c) + target_link_libraries(${_target} PRIVATE lurch_internal) + + if(_target STREQUAL test_lurch_loadable) + if(BUILD_SHARED_LIBS) + target_compile_options(${_target} PRIVATE ${GMODULE_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${GMODULE_LIBRARIES}) + else() + target_compile_options(${_target} PRIVATE ${GMODULE_STATIC_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${GMODULE_STATIC_LIBRARIES}) + endif() + + add_test(NAME ${_target} COMMAND ${_target} ${CMAKE_CURRENT_BINARY_DIR}/${_LURCH_OUTPUT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}) + else() + add_test(NAME ${_target} COMMAND ${_target}) + endif() + + if(BUILD_SHARED_LIBS) + target_compile_options(${_target} PRIVATE ${CMOCKA_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${CMOCKA_LIBRARIES}) + else() + target_compile_options(${_target} PRIVATE ${CMOCKA_STATIC_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${CMOCKA_STATIC_LIBRARIES}) + endif() + endforeach() + + target_link_options(test_lurch_api PRIVATE + -Wl,--wrap=purple_account_get_username + -Wl,--wrap=purple_account_get_connection + -Wl,--wrap=purple_connection_get_protocol_data + -Wl,--wrap=purple_signal_register + -Wl,--wrap=purple_signal_unregister + -Wl,--wrap=purple_signal_connect + -Wl,--wrap=purple_signal_disconnect + -Wl,--wrap=purple_find_conversation_with_account + -Wl,--wrap=jabber_pep_publish + -Wl,--wrap=jabber_chat_find_by_conv + -Wl,--wrap=jabber_iq_send + -Wl,--wrap=axc_get_device_id + -Wl,--wrap=axc_key_load_public_own + -Wl,--wrap=axc_key_load_public_addr + -Wl,--wrap=axc_session_exists_any + -Wl,--wrap=omemo_storage_user_devicelist_retrieve + -Wl,--wrap=omemo_storage_chatlist_delete + -Wl,--wrap=omemo_storage_chatlist_save + -Wl,--wrap=omemo_storage_chatlist_exists + -Wl,--wrap=omemo_storage_user_devicelist_retrieve + -Wl,--wrap=lurch_util_fp_get_printable + ) + + target_link_options(test_lurch_crypto PRIVATE + -Wl,--wrap=axc_message_encrypt_and_serialize + -Wl,--wrap=axc_session_exists_initiated + ) + + target_link_options(test_lurch_util PRIVATE + -Wl,--wrap=purple_user_dir + -Wl,--wrap=purple_prefs_get_bool + -Wl,--wrap=purple_prefs_get_int + -Wl,--wrap=purple_debug_error + -Wl,--wrap=purple_debug_info + -Wl,--wrap=purple_debug_misc + -Wl,--wrap=purple_base16_encode_chunked + ) +endif() + + +# +# External build dependencies +# +foreach(_target lurch lurch_internal ${_LURCH_HOME_TARGET} ${_LURCH_TEST_TARGETS}) + target_include_directories(${_target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/headers/jabber) + + if(LURCH_WITH_SYSTEM_AXC) + if(BUILD_SHARED_LIBS) + target_compile_options(${_target} PRIVATE ${AXC_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${AXC_LIBRARIES}) + else() + target_compile_options(${_target} PRIVATE ${AXC_STATIC_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${AXC_STATIC_LIBRARIES}) + endif() + else() + target_link_libraries(${_target} PRIVATE axc) + endif() + + if(LURCH_WITH_SYSTEM_OMEMO) + if(BUILD_SHARED_LIBS) + target_compile_options(${_target} PRIVATE ${OMEMO_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${OMEMO_LIBRARIES}) + else() + target_compile_options(${_target} PRIVATE ${OMEMO_STATIC_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${OMEMO_STATIC_LIBRARIES}) + endif() + else() + target_link_libraries(${_target} PRIVATE omemo) + endif() + + if(BUILD_SHARED_LIBS) + target_compile_options(${_target} PRIVATE ${GLIB_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${GLIB_LIBRARIES}) + + target_compile_options(${_target} PRIVATE ${PURPLE_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${PURPLE_LIBRARIES}) + + target_compile_options(${_target} PRIVATE ${XML_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${XML_LIBRARIES}) + else() + target_compile_options(${_target} PRIVATE ${GLIB_STATIC_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${GLIB_STATIC_LIBRARIES}) + + target_compile_options(${_target} PRIVATE ${PURPLE_STATIC_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${PURPLE_STATIC_LIBRARIES}) + + target_compile_options(${_target} PRIVATE ${XML_STATIC_CFLAGS}) + target_link_libraries(${_target} PRIVATE ${XML_STATIC_LIBRARIES}) + endif() +endforeach() + + +# +# Coverage reporting +# +if(_LURCH_WITH_COVERAGE) + add_custom_target(coverage + COMMAND gcovr -r ${CMAKE_CURRENT_SOURCE_DIR} --html --html-details -o coverage.html + COMMAND gcovr -r ${CMAKE_CURRENT_SOURCE_DIR} -s + ) +endif() diff --git a/Makefile b/Makefile deleted file mode 100644 index 4331906..0000000 --- a/Makefile +++ /dev/null @@ -1,242 +0,0 @@ -### toolchain -# -CC ?= gcc - -PKG_CONFIG ?= pkg-config -XML2_CONFIG ?= xml2-config -LIBGCRYPT_CONFIG ?= libgcrypt-config - -MKDIR = mkdir -MKDIR_P = mkdir -p -INSTALL = install -INSTALL_LIB = $(INSTALL) -m 755 -INSTALL_DIR = $(INSTALL) -d -m 755 -RM = rm -RM_RF = $(RM) -rf -CMAKE ?= cmake -CMAKE_FLAGS = -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS=-fPIC - -### flags -# -GLIB_CFLAGS ?= $(shell $(PKG_CONFIG) --cflags glib-2.0) -GLIB_LDFLAGS ?= $(shell $(PKG_CONFIG) --libs glib-2.0) - -LIBPURPLE_CFLAGS=$(shell $(PKG_CONFIG) --cflags purple) -PURPLE_DIR=$(shell $(PKG_CONFIG) --variable=plugindir purple) -LIBPURPLE_LDFLAGS=$(shell $(PKG_CONFIG) --cflags purple) \ - -L$(PURPLE_DIR) - -LIBOMEMO_CFLAGS = $(shell $(PKG_CONFIG) --cflags libomemo) -LIBOMEMO_LDFLAGS = $(shell $(PKG_CONFIG) --libs libomemo) - -LIBAXC_CFLAGS = $(shell $(PKG_CONFIG) --cflags libaxc) -LIBAXC_LDFLAGS = $(shell $(PKG_CONFIG) --libs libaxc) - -LIBSIGNAL_PROTOCOL_CFLAGS = $(shell $(PKG_CONFIG) --cflags libsignal-protocol-c) -LIBSIGNAL_PROTOCOL_LDFLAGS = $(shell $(PKG_CONFIG) --cflags libsignal-protocol-c) - -XML2_CFLAGS ?= $(shell $(XML2_CONFIG) --cflags) -XML2_LDFLAGS ?= $(shell $(XML2_CONFIG) --libs) - -LIBGCRYPT_LDFLAGS ?= $(shell $(LIBGCRYPT_CONFIG) --libs) - -USE_DYNAMIC_LIBS=libsignal-protocol-c libaxc libomemo -USE_DYNAMIC_LIBS:=$(shell pkg-config --exists $(USE_DYNAMIC_LIBS) && \ - echo '$(USE_DYNAMIC_LIBS)') - -PKGCFG_C=$(GLIB_CFLAGS) \ - $(LIBPURPLE_CFLAGS) \ - $(XML2_CFLAGS) - -ifneq ($(USE_DYNAMIC_LIBS),) - PKGCFG_C+=$(LIBOMEMO_CFLAGS) \ - $(LIBAXC_CFLAGS) \ - $(LIBSIGNAL_PROTOCOL_CFLAGS) -endif - - -PKGCFG_L=$(shell $(PKG_CONFIG) --libs sqlite3 mxml) \ - $(GLIB_LDFLAGS) \ - $(LIBPURPLE_LDFLAGS) \ - $(XML2_LDFLAGS) \ - $(LIBGCRYPT_LDFLAGS) - -ifneq ($(USE_DYNAMIC_LIBS),) - PKGCFG_L+=$(LIBOMEMO_LDFLAGS) \ - $(LIBAXC_LDFLAGS) \ - $(LIBSIGNAL_PROTOCOL_LDFLAGS) -endif - - -ifneq ("$(wildcard /etc/redhat-release)","") - LJABBER= -lxmpp -else -ifneq ("$(wildcard /etc/SuSE-release)","") - LJABBER= -lxmpp -else - LJABBER= -ljabber -endif -endif - -ifeq ($(USE_DYNAMIC_LIBS),) - HEADERS=-I$(HDIR)/jabber -I$(LOMEMO_SRC) -I$(AXC_SRC) -I$(AX_DIR)/src -else - HEADERS=-I$(HDIR)/jabber -endif -CFLAGS += -std=c11 -Wall -g -Wstrict-overflow $(PKGCFG_C) $(HEADERS) -PLUGIN_CPPFLAGS=-DPURPLE_PLUGINS -# -D_BSD_SOURCE can be removed once nobody uses glibc <= 2.18 any more -CPPFLAGS += -D_XOPEN_SOURCE=700 -D_BSD_SOURCE -D_DEFAULT_SOURCE -LDFLAGS += -ldl -lm $(PKGCFG_L) $(LJABBER) -Wl,-rpath,$(PURPLE_PLUGIN_DIR) -LDFLAGS_T=$(LDFLAGS) -lpurple -lcmocka - -### directories -# -PURPLE_HOME_PLUGIN_DIR=$(HOME)/.purple/plugins -PURPLE_PLUGIN_DIR = $(shell $(PKG_CONFIG) --variable=plugindir purple) - -LDIR=./lib -BDIR=./build -SDIR=./src -HDIR=./headers -TDIR=./test - -LOMEMO_DIR=$(LDIR)/libomemo -LOMEMO_SRC=$(LOMEMO_DIR)/src -LOMEMO_BUILD=$(LOMEMO_DIR)/build -LOMEMO_PATH=$(LOMEMO_BUILD)/libomemo-conversations.a - -AXC_DIR=$(LDIR)/axc -AXC_SRC=$(AXC_DIR)/src -AXC_BUILD=$(AXC_DIR)/build -AXC_PATH=$(AXC_BUILD)/libaxc-nt.a - -AX_DIR=$(AXC_DIR)/lib/libsignal-protocol-c -AX_PATH=$(AX_DIR)/build/src/libsignal-protocol-c.a - -SOURCES := $(sort $(wildcard $(SDIR)/*.c)) -OBJECTS := $(patsubst $(SDIR)/%.c, $(BDIR)/%.o, $(SOURCES)) -OBJECTS_W_COVERAGE := $(patsubst $(SDIR)/%.c, $(BDIR)/%_w_coverage.o, $(SOURCES)) -TEST_SOURCES := $(sort $(wildcard $(TDIR)/test_*.c)) -TEST_OBJECTS := $(patsubst $(TDIR)/test_%.c, $(BDIR)/test_%.o, $(TEST_SOURCES)) -TEST_TARGETS := $(patsubst $(TDIR)/test_%.c, $(BDIR)/test_%, $(TEST_SOURCES)) -ifeq ($(USE_DYNAMIC_LIBS),) - VENDOR_LIBS=$(LOMEMO_PATH) $(AXC_PATH) $(AX_PATH) -endif - -### make rules -# -all: $(BDIR)/lurch.so - -$(BDIR): - $(MKDIR_P) build - -$(AX_PATH): - cd $(AX_DIR)/ && \ - $(MKDIR_P) build && \ - cd build && \ - $(CMAKE) $(CMAKE_FLAGS) .. \ - && $(MAKE) - -$(AXC_PATH): - $(MAKE) -C "$(AXC_DIR)" build/libaxc-nt.a - -$(LOMEMO_PATH): - $(MAKE) -C "$(LOMEMO_DIR)" build/libomemo-conversations.a - -$(BDIR)/%.o: $(SDIR)/%.c | $(BDIR) - $(CC) -fPIC $(CFLAGS) $(CPPFLAGS) $(PLUGIN_CPPFLAGS) -c $(SDIR)/$*.c -o $@ - -$(BDIR)/%_w_coverage.o: $(SDIR)/%.c | $(BDIR) - $(CC) -O0 --coverage $(CFLAGS) $(CPPFLAGS) $(PLUGIN_CPPFLAGS) -c $(SDIR)/$*.c -o $@ - -$(BDIR)/test_%.o: $(TDIR)/test_%.c | $(BDIR) - $(CC) $(CFLAGS) -O0 -c $(TDIR)/test_$*.c -o $@ - -$(BDIR)/lurch.so: $(OBJECTS) $(VENDOR_LIBS) - $(CC) -fPIC -shared $(CFLAGS) $(CPPFLAGS) $(PLUGIN_CPPFLAGS) \ - $^ \ - -o $@ $(LDFLAGS) -$(BDIR)/lurch.a: $(BDIR)/lurch.o $(VENDOR_LIBS) - $(AR) rcs $@ $^ - -install: $(BDIR)/lurch.so - [ -e "$(DESTDIR)/$(PURPLE_PLUGIN_DIR)" ] || \ - $(INSTALL_DIR) "$(DESTDIR)/$(PURPLE_PLUGIN_DIR)" - $(INSTALL_LIB) "$(BDIR)/lurch.so" "$(DESTDIR)/$(PURPLE_PLUGIN_DIR)/lurch.so" - -install-home: $(BDIR)/lurch.so - [ -e "$(PURPLE_HOME_PLUGIN_DIR)" ] || \ - $(INSTALL_DIR) "$(PURPLE_HOME_PLUGIN_DIR)" - $(INSTALL_LIB) "$(BDIR)/lurch.so" "$(PURPLE_HOME_PLUGIN_DIR)/lurch.so" - - -LURCH_VERSION ?= 0.0.0 -TARBALL_DIR_NAME=lurch-$(LURCH_VERSION) -TARBALL_FILE_NAME=$(TARBALL_DIR_NAME)-src.tar.gz - -tarball: | clean-all $(BDIR) - $(MKDIR) $(TARBALL_DIR_NAME) - rsync -av --progress . $(TARBALL_DIR_NAME)/ --exclude $(TARBALL_DIR_NAME)/ --exclude-from=.gitignore - -find $(TARBALL_DIR_NAME)/ -name "*.git*" -exec rm -rf "{}" \; - tar czf $(TARBALL_FILE_NAME) $(TARBALL_DIR_NAME)/ - mv $(TARBALL_FILE_NAME) $(TARBALL_DIR_NAME)/ - mv $(TARBALL_DIR_NAME) $(BDIR)/ - -$(BDIR)/test_lurch_util: $(OBJECTS_W_COVERAGE) $(VENDOR_LIBS) $(BDIR)/test_lurch_util.o - $(CC) $(CFLAGS) $(CPPFLAGS) -O0 --coverage $^ $(PURPLE_DIR)/libjabber.so.0 -o $@ $(LDFLAGS_T) \ - -Wl,--wrap=purple_user_dir \ - -Wl,--wrap=purple_prefs_get_bool \ - -Wl,--wrap=purple_prefs_get_int \ - -Wl,--wrap=purple_debug_error \ - -Wl,--wrap=purple_debug_info \ - -Wl,--wrap=purple_debug_misc \ - -Wl,--wrap=purple_base16_encode_chunked - sh -c "set -o pipefail; $@ 2>&1 | grep -Ev ".*CRITICAL.*" | tr -s '\n'" # filter annoying and irrelevant glib output - -$(BDIR)/test_lurch_api: $(OBJECTS_W_COVERAGE) $(VENDOR_LIBS) $(BDIR)/test_lurch_api.o - $(CC) $(CFLAGS) $(CPPFLAGS) -O0 --coverage $^ $(PURPLE_DIR)/libjabber.so.0 -o $@ $(LDFLAGS_T) \ - -Wl,--wrap=purple_account_get_username \ - -Wl,--wrap=purple_account_get_connection \ - -Wl,--wrap=purple_connection_get_protocol_data \ - -Wl,--wrap=purple_signal_register \ - -Wl,--wrap=purple_signal_unregister \ - -Wl,--wrap=purple_signal_connect \ - -Wl,--wrap=purple_signal_disconnect \ - -Wl,--wrap=purple_find_conversation_with_account \ - -Wl,--wrap=jabber_pep_publish \ - -Wl,--wrap=jabber_chat_find_by_conv \ - -Wl,--wrap=jabber_iq_send \ - -Wl,--wrap=axc_get_device_id \ - -Wl,--wrap=axc_key_load_public_own \ - -Wl,--wrap=axc_key_load_public_addr \ - -Wl,--wrap=axc_session_exists_any \ - -Wl,--wrap=omemo_storage_user_devicelist_retrieve \ - -Wl,--wrap=omemo_storage_chatlist_delete \ - -Wl,--wrap=omemo_storage_chatlist_save \ - -Wl,--wrap=omemo_storage_chatlist_exists \ - -Wl,--wrap=omemo_storage_user_devicelist_retrieve \ - -Wl,--wrap=lurch_util_fp_get_printable - sh -c "set -o pipefail; $@ 2>&1 | grep -Ev ".*CRITICAL.*" | tr -s '\n'" # filter annoying and irrelevant glib output - -$(BDIR)/test_lurch_crypto: $(OBJECTS_W_COVERAGE) $(VENDOR_LIBS) $(BDIR)/test_lurch_crypto.o - $(CC) $(CFLAGS) $(CPPFLAGS) -O0 --coverage $^ $(PURPLE_DIR)/libjabber.so.0 -o $@ $(LDFLAGS_T) \ - -Wl,--wrap=axc_message_encrypt_and_serialize \ - -Wl,--wrap=axc_session_exists_initiated - sh -c "set -o pipefail; $@ 2>&1 | grep -Ev ".*CRITICAL.*" | tr -s '\n'" # filter annoying and irrelevant glib output - -test: $(OBJECTS_W_COVERAGE) $(VENDOR_LIBS) $(TEST_TARGETS) - -coverage: test - gcovr -r . --html --html-details -o build/coverage.html - gcovr -r . -s - -clean: - $(RM_RF) "$(BDIR)" - -clean-all: clean - $(MAKE) -C "$(LOMEMO_DIR)" clean - $(MAKE) -C "$(AXC_DIR)" clean-all - -.PHONY: clean clean-all install install-home tarball test coverage - diff --git a/README.md b/README.md index 99bd3eb..027de28 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ sudo dnf install git cmake libpurple-devel mxml-devel libxml2-devel libsqlite3x- git clone https://github.com/gkdr/lurch/ cd lurch git submodule update --init --recursive +cmake . make install-home ``` If you just pull a newer version (`git pull`), remember to also update the submodules as they might have changed! @@ -51,6 +52,32 @@ The last command compiles the whole thing and copies the plugin into your local The next time you start Pidgin, or another libpurple client, you should be able to activate it in the "Plugins" window. +If you would like to tweak compile time options, here's a list of the available knobs: + +```console +# rm -f CMakeCache.txt ; cmake -D_LURCH_HELP=ON -LH . 2>/dev/null | grep -B1 ':.*=' | sed 's,^--$,,' +// Build shared libraries (rather than static ones) +BUILD_SHARED_LIBS:BOOL=ON + +// Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel ... +CMAKE_BUILD_TYPE:STRING= + +// Install path prefix, prepended onto install directories. +CMAKE_INSTALL_PREFIX:PATH=/usr/local + +// Install build artifacts +LURCH_INSTALL:BOOL=ON + +// Use system-wide axc (rather than the bundled copy) +LURCH_WITH_SYSTEM_AXC:BOOL=ON + +// Use system-wide libomemo (rather than the bundled copy) +LURCH_WITH_SYSTEM_OMEMO:BOOL=ON + +// Build test suite (depends on cmocka) +LURCH_WITH_TESTS:BOOL=ON +``` + ##### 2B. OR: Install from a community repo * Arch - AUR: https://aur.archlinux.org/packages/libpurple-lurch-git/ * Fedora - COPR: https://copr.fedorainfracloud.org/coprs/treba/pidgin-lurch/ diff --git a/copy_dll_closure.sh b/copy_dll_closure.sh new file mode 100755 index 0000000..f661edf --- /dev/null +++ b/copy_dll_closure.sh @@ -0,0 +1,61 @@ +#! /usr/bin/env bash +# Copyright (c) 2022 Sebastian Pipping +# Licensed under the GPL v2 or late + +set -e -u -o pipefail + +: ${DLL_HOME:?environment variable not set but required} +: ${BUILD_DIR:?environment variable not set but required} + +direct_dependencies_of() { + local filename="$1" + objdump -p "${filename}" | fgrep 'DLL Name' | sort -u | sed 's,^.*DLL Name: ,,' +} + +copy_dll_closure() { + local filename="$1" + local target_directory="$2" + local indent="${3:-}" + if [[ -z ${indent} ]]; then + echo "[*] ${indent}$(basename "${filename}")" + fi + indent="${indent} " + success=true + for dependency in $(direct_dependencies_of "${filename}"); do + case ${dependency} in + # DLLs native to Windows (that we consider present) + ADVAPI32.dll|DNSAPI.dll|KERNEL32.dll|msvcrt.dll|ole32.dll|SHELL32.dll|USER32.dll|WS2_32.dll) + continue + ;; + # DLLs native to Pidgin (that we consider present) + libjabber.dll|libpurple.dll) + continue + ;; + esac + + if [[ -f "${target_directory}/${dependency}" ]]; then + echo "[+] ${indent}${dependency}" + continue + fi + + self_built="$(find "${BUILD_DIR}" -type f -name "${dependency}")" + if [[ -z "${self_built}" ]]; then + if [[ ! -f "${DLL_HOME}/${dependency}" ]]; then + echo "[-] ${indent}${dependency} -- MISSING" + success=false + continue + fi + cp "${DLL_HOME}/${dependency}" "${target_directory}/" + else + cp "${self_built}" "${target_directory}/" + fi + echo "[+] ${indent}${dependency} -- COPIED" + + copy_dll_closure "${target_directory}/${dependency}" "${target_directory}" "${indent}" + done + ${success} +} + +copy_dll_closure "$1" "$2" + +echo '[+] DONE.' diff --git a/lib/axc b/lib/axc index 86f0317..31f6922 160000 --- a/lib/axc +++ b/lib/axc @@ -1 +1 @@ -Subproject commit 86f03172021eae796ef4ede3e7c6a1daea1a3718 +Subproject commit 31f692274d677ad3d84ac3b12e8cf8cbeba91ed5 diff --git a/lib/libomemo b/lib/libomemo index e43cc6b..a781419 160000 --- a/lib/libomemo +++ b/lib/libomemo @@ -1 +1 @@ -Subproject commit e43cc6b7db9c76601197ab1760c8ecfaa89f6f5f +Subproject commit a7814193275309ddbf37a76e18f928cf41c55e1c diff --git a/test/test_lurch_loadable.c b/test/test_lurch_loadable.c new file mode 100644 index 0000000..0272818 --- /dev/null +++ b/test/test_lurch_loadable.c @@ -0,0 +1,70 @@ +// Copyright (c) 2022 Sebastian Pipping +// Licensed under the GPL v2 or later + +#include +#include +#include +#include + +#include + +int main(int argc, char **argv) { + if (argc < 2) { + printf("usage: %s PATH/TO/MODULE.(dll|so) [..]\n", argv[0]); + return 1; + } + + bool success = true; + size_t i = 1; + for (; i < argc; i++) { + const char *const filename = argv[i]; + // NOTE: g_module_open does not like opening plain "lurch.(so|dll)": + // it needs a path. So we turn potentially relative filenames + // into full absolute ones to avoid unncessary (and misleading) + // error "No such file or directory" and to make it work regardless. + gchar *const absolute_filename = g_canonicalize_filename(filename, NULL); + if (!absolute_filename) { + printf("[-] Could not canonicalize " + "filename %s due to error: %s (code %d)\n", + filename, strerror(errno), errno); + success = false; + continue; + } + + printf("[*] Opening module %s...\n", filename); + GModule *const module = + g_module_open(absolute_filename, G_MODULE_BIND_LOCAL); + g_free(absolute_filename); + if (!module) { + success = false; + const gchar *const error_details = g_module_error(); + printf("[-] Opening module %s has FAILED: %s\n", filename, + (char *)error_details); + continue; + } + printf("[+] Opened module %s successfully.\n", filename); + + const gchar *const symbol_name = "purple_init_plugin"; + gpointer dummy; + printf("[*] Checking module %s for symbol %s...\n", filename, + (char *)symbol_name); + if (g_module_symbol(module, symbol_name, &dummy)) { + printf("[+] Symbol %s found.\n", symbol_name); + } else { + printf("[-] Symbol %s NOT found.\n", symbol_name); + success = false; + } + + printf("[*] Closing module %s...\n", filename); + g_module_close(module); + printf("[+] Closed module %s.\n", filename); + } + + if (success) { + printf("[+] Good.\n"); + } else { + printf("[-] BAD.\n"); + } + + return success ? 0 : 1; +}