From 26c748c23f0681724d2f18b3908a71b336fc18be Mon Sep 17 00:00:00 2001 From: Laszlo Nagy Date: Mon, 27 Jan 2020 19:57:12 +1100 Subject: [PATCH 001/300] implement intercept wrapper to report executions --- .gitignore | 2 + .travis.yml | 56 +- CMakeLists.txt | 50 +- cmake/AddGoogleTest.cmake | 121 ++++ cmake/DownloadProject.CMakeLists.cmake.in | 17 + cmake/DownloadProject.cmake | 164 +++++ libear/CMakeLists.txt | 38 - libear/config.h.in | 16 - libear/ear.c | 656 ------------------ shell-completion/bash/CMakeLists.txt | 15 +- source/CMakeLists.txt | 30 + {bear => source/bear}/CMakeLists.txt | 0 {bear => source/bear}/bear.py | 130 ++-- source/config.h.in | 13 + source/intercept-wrapper/CMakeLists.txt | 2 + .../intercept-c++/CMakeLists.txt | 13 + .../intercept-cc/CMakeLists.txt | 13 + source/intercept-wrapper/main.cc | 63 ++ source/intercept/CMakeLists.txt | 15 + source/intercept/main.cc | 123 ++++ source/intercept_a/CMakeLists.txt | 18 + source/intercept_a/Environment.cc | 195 ++++++ source/intercept_a/Environment.h | 89 +++ source/intercept_a/Interface.h | 62 ++ source/intercept_a/Reporter.cc | 238 +++++++ source/intercept_a/Reporter.h | 62 ++ source/intercept_a/Result.h | 186 +++++ source/intercept_a/Session.cc | 229 ++++++ source/intercept_a/Session.h | 85 +++ source/intercept_a/SystemCalls.cc | 115 +++ source/intercept_a/SystemCalls.h | 56 ++ source/intercept_a/test/CMakeLists.txt | 10 + source/intercept_a/test/EnvironmentTest.cc | 150 ++++ source/intercept_a/test/ResultTest.cc | 155 +++++ source/intercept_a/test/SessionTest.cc | 107 +++ source/libexec/CMakeLists.txt | 18 + source/libexec/lib.cc | 292 ++++++++ source/libexec_a/Array.h | 97 +++ source/libexec_a/CMakeLists.txt | 20 + source/libexec_a/DynamicLinker.cc | 44 ++ source/libexec_a/DynamicLinker.h | 41 ++ source/libexec_a/Environment.cc | 97 +++ source/libexec_a/Environment.h | 38 + source/libexec_a/Executor.h | 216 ++++++ source/libexec_a/Interface.h | 49 ++ source/libexec_a/Storage.cc | 45 ++ source/libexec_a/Storage.h | 46 ++ source/libexec_a/test/ArrayTest.cc | 83 +++ source/libexec_a/test/CMakeLists.txt | 11 + source/libexec_a/test/EnvironmentTest.cc | 99 +++ source/libexec_a/test/ExecutorTest.cc | 416 +++++++++++ source/libexec_a/test/StorageTest.cc | 52 ++ test/CMakeLists.txt | 17 +- test/functional/cases/exec_calls/main.c | 2 +- test/functional/cases/run_pep8.ft | 2 - test/functional/lit.local.cfg | 9 +- test/lit.cfg | 6 - 57 files changed, 4152 insertions(+), 842 deletions(-) create mode 100644 cmake/AddGoogleTest.cmake create mode 100644 cmake/DownloadProject.CMakeLists.cmake.in create mode 100644 cmake/DownloadProject.cmake delete mode 100644 libear/CMakeLists.txt delete mode 100644 libear/config.h.in delete mode 100644 libear/ear.c create mode 100644 source/CMakeLists.txt rename {bear => source/bear}/CMakeLists.txt (100%) rename {bear => source/bear}/bear.py (91%) create mode 100644 source/config.h.in create mode 100644 source/intercept-wrapper/CMakeLists.txt create mode 100644 source/intercept-wrapper/intercept-c++/CMakeLists.txt create mode 100644 source/intercept-wrapper/intercept-cc/CMakeLists.txt create mode 100644 source/intercept-wrapper/main.cc create mode 100644 source/intercept/CMakeLists.txt create mode 100644 source/intercept/main.cc create mode 100644 source/intercept_a/CMakeLists.txt create mode 100644 source/intercept_a/Environment.cc create mode 100644 source/intercept_a/Environment.h create mode 100644 source/intercept_a/Interface.h create mode 100644 source/intercept_a/Reporter.cc create mode 100644 source/intercept_a/Reporter.h create mode 100644 source/intercept_a/Result.h create mode 100644 source/intercept_a/Session.cc create mode 100644 source/intercept_a/Session.h create mode 100644 source/intercept_a/SystemCalls.cc create mode 100644 source/intercept_a/SystemCalls.h create mode 100644 source/intercept_a/test/CMakeLists.txt create mode 100644 source/intercept_a/test/EnvironmentTest.cc create mode 100644 source/intercept_a/test/ResultTest.cc create mode 100644 source/intercept_a/test/SessionTest.cc create mode 100644 source/libexec/CMakeLists.txt create mode 100644 source/libexec/lib.cc create mode 100644 source/libexec_a/Array.h create mode 100644 source/libexec_a/CMakeLists.txt create mode 100644 source/libexec_a/DynamicLinker.cc create mode 100644 source/libexec_a/DynamicLinker.h create mode 100644 source/libexec_a/Environment.cc create mode 100644 source/libexec_a/Environment.h create mode 100644 source/libexec_a/Executor.h create mode 100644 source/libexec_a/Interface.h create mode 100644 source/libexec_a/Storage.cc create mode 100644 source/libexec_a/Storage.h create mode 100644 source/libexec_a/test/ArrayTest.cc create mode 100644 source/libexec_a/test/CMakeLists.txt create mode 100644 source/libexec_a/test/EnvironmentTest.cc create mode 100644 source/libexec_a/test/ExecutorTest.cc create mode 100644 source/libexec_a/test/StorageTest.cc delete mode 100644 test/functional/cases/run_pep8.ft diff --git a/.gitignore b/.gitignore index e1542a73..ca8c28d2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ Output/ Cargo.lock target/ .DS_Store +cmake-build-debug/ +Output/ diff --git a/.travis.yml b/.travis.yml index c4635b3b..12843158 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,20 +4,33 @@ matrix: include: - os: linux sudo: false - dist: xenial - python: "pypy" - - os: linux - sudo: required - dist: xenial - python: 2.7 - - os: linux - sudo: false - dist: xenial + dist: bionic python: 3.6 + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-8 + env: + - CC=gcc-8 + - CXX=g++-8 - os: linux sudo: false - dist: xenial + dist: bionic python: 3.7 + addons: + apt: + sources: + - llvm-toolchain-bionic-8 + - ubuntu-toolchain-r-test + packages: + - clang-8 + - libclang-8-dev + - llvm-8-tools + env: + - CC=clang-8 + - CXX=clang++-8 - os: osx osx_image: xcode7.3 language: generic @@ -28,33 +41,46 @@ matrix: addons: apt: packages: + - python-pip + - libc6-dev - cmake - scons - gfortran - qt4-qmake - nvidia-cuda-toolkit -before_install: +before_script: + - mkdir -p "$HOME/install" "$HOME/build" - uname + - env | sort + +before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then csrutil status || true; brew update; + brew upgrade cmake; brew install scons; fi install: - pip --version - pip install --upgrade pip - - pip install lit pycodestyle + - pip install lit pex pycodestyle script: - - mkdir "$HOME/build"; cd "$HOME/build" - - cmake "$TRAVIS_BUILD_DIR" -DCPACK_GENERATOR="TGZ" + - cd "$HOME/build" + - cmake --version + - cmake "$TRAVIS_BUILD_DIR" -DCPACK_GENERATOR="TGZ;DEB" - VERBOSE=1 make all - - VERBOSE=1 make check + - VERBOSE=1 make test - VERBOSE=1 make package + - VERBOSE=1 DESTDIR="$HOME/install" make install - if [ "$TRAVIS_SUDO" == "true" ]; then sudo make install; (cd "$TRAVIS_BUILD_DIR"; git clean -fdx;); lit -v "$TRAVIS_BUILD_DIR/test"; fi + +branches: + except: + - experimental diff --git a/CMakeLists.txt b/CMakeLists.txt index ee004d67..d1e8f153 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,38 +1,42 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.9) -project(bear C) -set(BEAR_VERSION "2.4.3") - -include(GNUInstallDirs) -install(FILES COPYING README.md ChangeLog.md - DESTINATION ${CMAKE_INSTALL_DOCDIR}) +if(${CMAKE_VERSION} VERSION_LESS 3.12) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.12) +endif() -include(CheckCCompilerFlag) -check_c_compiler_flag("-std=c99" C99_SUPPORTED) -if (C99_SUPPORTED) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") +set(default_build_type "Release") +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to '${default_build_type}' as none was specified.") + set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE + STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() -add_definitions(-D_GNU_SOURCE) -list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) -if (NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Release") -endif() +project(bear + VERSION 3.0.0 + DESCRIPTION "Bear is a tool to generate compilation database for clang tooling." + LANGUAGES C CXX) -set(CMAKE_OSX_ARCHITECTURES "i386;x86_64" CACHE STRING "Rogue") +enable_testing() -set(EAR_LIB_FILE ${CMAKE_SHARED_LIBRARY_PREFIX}ear${CMAKE_SHARED_LIBRARY_SUFFIX}) -set(EAR_LIB_PATH "${CMAKE_INSTALL_LIBDIR}/bear") -set(DEFAULT_PRELOAD_FILE ${CMAKE_INSTALL_PREFIX}/${EAR_LIB_PATH}/${EAR_LIB_FILE} CACHE STRING "Default path to libear.") +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +include(AddGoogleTest) -add_subdirectory(libear) -add_subdirectory(bear) +add_subdirectory(source) add_subdirectory(test) add_subdirectory(man) add_subdirectory(shell-completion) -set(CPACK_PACKAGE_NAME "bear") +include(GNUInstallDirs) +install(FILES COPYING README.md ChangeLog.md + DESTINATION ${CMAKE_INSTALL_DOCDIR}) + +set(CPACK_PACKAGE_NAME "source/bear") set(CPACK_PACKAGE_CONTACT "László Nagy") set(CPACK_PACKAGE_VENDOR ${CPACK_PACKAGE_CONTACT}) set(CPACK_PACKAGE_VERSION ${BEAR_VERSION}) diff --git a/cmake/AddGoogleTest.cmake b/cmake/AddGoogleTest.cmake new file mode 100644 index 00000000..9c8963ee --- /dev/null +++ b/cmake/AddGoogleTest.cmake @@ -0,0 +1,121 @@ +# from https://github.com/CLIUtils/cmake +# +# Copyright (c) 2017, University of Cincinnati +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +if(CMAKE_VERSION VERSION_LESS 3.11) + set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") + + include(DownloadProject) + download_project(PROJ googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.8.0 + UPDATE_DISCONNECTED 1 + QUIET + ) + + # CMake warning suppression will not be needed in version 1.9 + set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "") + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_SOURCE_DIR} EXCLUDE_FROM_ALL) + unset(CMAKE_SUPPRESS_DEVELOPER_WARNINGS) +else() + include(FetchContent) + FetchContent_Declare(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.8.0) + FetchContent_GetProperties(googletest) + if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "") + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL) + unset(CMAKE_SUPPRESS_DEVELOPER_WARNINGS) + endif() +endif() + + +if(CMAKE_CONFIGURATION_TYPES) + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} + --force-new-ctest-process --output-on-failure + --build-config "$") +else() + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} + --force-new-ctest-process --output-on-failure) +endif() +set_target_properties(check PROPERTIES FOLDER "Scripts") + +#include_directories(${gtest_SOURCE_DIR}/include) + +# More modern way to do the last line, less messy but needs newish CMake: +# target_include_directories(gtest INTERFACE ${gtest_SOURCE_DIR}/include) + + +if(GOOGLE_TEST_INDIVIDUAL) + if(NOT CMAKE_VERSION VERSION_LESS 3.9) + include(GoogleTest) + else() + set(GOOGLE_TEST_INDIVIDUAL OFF) + endif() +endif() + +# Target must already exist +macro(add_gtest TESTNAME) + target_link_libraries(${TESTNAME} PUBLIC gtest gmock gtest_main) + + if(GOOGLE_TEST_INDIVIDUAL) + if(CMAKE_VERSION VERSION_LESS 3.10) + gtest_add_tests(TARGET ${TESTNAME} + TEST_PREFIX "${TESTNAME}." + TEST_LIST TmpTestList) + set_tests_properties(${TmpTestList} PROPERTIES FOLDER "Tests") + else() + gtest_discover_tests(${TESTNAME} + TEST_PREFIX "${TESTNAME}." + PROPERTIES FOLDER "Tests") + endif() + else() + add_test(${TESTNAME} ${TESTNAME}) + set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests") + endif() + +endmacro() + +mark_as_advanced( +gmock_build_tests +gtest_build_samples +gtest_build_tests +gtest_disable_pthreads +gtest_force_shared_crt +gtest_hide_internal_symbols +BUILD_GMOCK +BUILD_GTEST +) + +set_target_properties(gtest gtest_main gmock gmock_main + PROPERTIES FOLDER "Extern") diff --git a/cmake/DownloadProject.CMakeLists.cmake.in b/cmake/DownloadProject.CMakeLists.cmake.in new file mode 100644 index 00000000..89be4fdd --- /dev/null +++ b/cmake/DownloadProject.CMakeLists.cmake.in @@ -0,0 +1,17 @@ +# Distributed under the OSI-approved MIT License. See accompanying +# file LICENSE or https://github.com/Crascit/DownloadProject for details. + +cmake_minimum_required(VERSION 2.8.2) + +project(${DL_ARGS_PROJ}-download NONE) + +include(ExternalProject) +ExternalProject_Add(${DL_ARGS_PROJ}-download + ${DL_ARGS_UNPARSED_ARGUMENTS} + SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" + BINARY_DIR "${DL_ARGS_BINARY_DIR}" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/cmake/DownloadProject.cmake b/cmake/DownloadProject.cmake new file mode 100644 index 00000000..798c74b6 --- /dev/null +++ b/cmake/DownloadProject.cmake @@ -0,0 +1,164 @@ +# Distributed under the OSI-approved MIT License. See accompanying +# file LICENSE or https://github.com/Crascit/DownloadProject for details. +# +# MODULE: DownloadProject +# +# PROVIDES: +# download_project( PROJ projectName +# [PREFIX prefixDir] +# [DOWNLOAD_DIR downloadDir] +# [SOURCE_DIR srcDir] +# [BINARY_DIR binDir] +# [QUIET] +# ... +# ) +# +# Provides the ability to download and unpack a tarball, zip file, git repository, +# etc. at configure time (i.e. when the cmake command is run). How the downloaded +# and unpacked contents are used is up to the caller, but the motivating case is +# to download source code which can then be included directly in the build with +# add_subdirectory() after the call to download_project(). Source and build +# directories are set up with this in mind. +# +# The PROJ argument is required. The projectName value will be used to construct +# the following variables upon exit (obviously replace projectName with its actual +# value): +# +# projectName_SOURCE_DIR +# projectName_BINARY_DIR +# +# The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically +# need to be provided. They can be specified if you want the downloaded source +# and build directories to be located in a specific place. The contents of +# projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the +# locations used whether you provide SOURCE_DIR/BINARY_DIR or not. +# +# The DOWNLOAD_DIR argument does not normally need to be set. It controls the +# location of the temporary CMake build used to perform the download. +# +# The PREFIX argument can be provided to change the base location of the default +# values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments +# are provided, then PREFIX will have no effect. The default value for PREFIX is +# CMAKE_BINARY_DIR. +# +# The QUIET option can be given if you do not want to show the output associated +# with downloading the specified project. +# +# In addition to the above, any other options are passed through unmodified to +# ExternalProject_Add() to perform the actual download, patch and update steps. +# The following ExternalProject_Add() options are explicitly prohibited (they +# are reserved for use by the download_project() command): +# +# CONFIGURE_COMMAND +# BUILD_COMMAND +# INSTALL_COMMAND +# TEST_COMMAND +# +# Only those ExternalProject_Add() arguments which relate to downloading, patching +# and updating of the project sources are intended to be used. Also note that at +# least one set of download-related arguments are required. +# +# If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to +# prevent a check at the remote end for changes every time CMake is run +# after the first successful download. See the documentation of the ExternalProject +# module for more information. It is likely you will want to use this option if it +# is available to you. Note, however, that the ExternalProject implementation contains +# bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when +# using the URL download method or when specifying a SOURCE_DIR with no download +# method. Fixes for these have been created, the last of which is scheduled for +# inclusion in CMake 3.8.0. Details can be found here: +# +# https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c +# https://gitlab.kitware.com/cmake/cmake/issues/16428 +# +# If you experience build errors related to the update step, consider avoiding +# the use of UPDATE_DISCONNECTED. +# +# EXAMPLE USAGE: +# +# include(DownloadProject) +# download_project(PROJ googletest +# GIT_REPOSITORY https://github.com/google/googletest.git +# GIT_TAG master +# UPDATE_DISCONNECTED 1 +# QUIET +# ) +# +# add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) +# +#======================================================================================== + + +set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}") + +include(CMakeParseArguments) + +function(download_project) + + set(options QUIET) + set(oneValueArgs + PROJ + PREFIX + DOWNLOAD_DIR + SOURCE_DIR + BINARY_DIR + # Prevent the following from being passed through + CONFIGURE_COMMAND + BUILD_COMMAND + INSTALL_COMMAND + TEST_COMMAND + ) + set(multiValueArgs "") + + cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Hide output if requested + if (DL_ARGS_QUIET) + set(OUTPUT_QUIET "OUTPUT_QUIET") + else() + unset(OUTPUT_QUIET) + message(STATUS "Downloading/updating ${DL_ARGS_PROJ}") + endif() + + # Set up where we will put our temporary CMakeLists.txt file and also + # the base point below which the default source and binary dirs will be + if (NOT DL_ARGS_PREFIX) + set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}") + endif() + if (NOT DL_ARGS_DOWNLOAD_DIR) + set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download") + endif() + + # Ensure the caller can know where to find the source and build directories + if (NOT DL_ARGS_SOURCE_DIR) + set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src") + endif() + if (NOT DL_ARGS_BINARY_DIR) + set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build") + endif() + set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE) + set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE) + + # Create and build a separate CMake project to carry out the download. + # If we've already previously done these steps, they will not cause + # anything to be updated, so extra rebuilds of the project won't occur. + configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in" + "${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt") + execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + RESULT_VARIABLE result + ${OUTPUT_QUIET} + WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" + ) + if(result) + message(FATAL_ERROR "CMake step for ${DL_ARGS_PROJ} failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + ${OUTPUT_QUIET} + WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" + ) + if(result) + message(FATAL_ERROR "Build step for ${DL_ARGS_PROJ} failed: ${result}") + endif() + +endfunction() diff --git a/libear/CMakeLists.txt b/libear/CMakeLists.txt deleted file mode 100644 index 8dc219c1..00000000 --- a/libear/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -include(CheckFunctionExists) -include(CheckSymbolExists) -include(CheckIncludeFile) -check_function_exists(execve HAVE_EXECVE) -check_function_exists(execv HAVE_EXECV) -check_function_exists(execvpe HAVE_EXECVPE) -check_function_exists(execvp HAVE_EXECVP) -check_function_exists(execvP HAVE_EXECVP2) -check_function_exists(exect HAVE_EXECT) -check_function_exists(execl HAVE_EXECL) -check_function_exists(execlp HAVE_EXECLP) -check_function_exists(execle HAVE_EXECLE) -check_function_exists(posix_spawn HAVE_POSIX_SPAWN) -check_function_exists(posix_spawnp HAVE_POSIX_SPAWNP) -check_symbol_exists(_NSGetEnviron crt_externs.h HAVE_NSGETENVIRON) - -find_package(Threads REQUIRED) - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -add_library(ear SHARED ear.c) -target_link_libraries(ear ${CMAKE_DL_LIBS}) -if(THREADS_HAVE_PTHREAD_ARG) - set_property(TARGET ear PROPERTY COMPILE_OPTIONS "-pthread") - set_property(TARGET ear PROPERTY INTERFACE_COMPILE_OPTIONS "-pthread") -endif() -if(CMAKE_THREAD_LIBS_INIT) - target_link_libraries(ear "${CMAKE_THREAD_LIBS_INIT}") -endif() - -if(APPLE) - set(CMAKE_MACOSX_RPATH 1) - set_target_properties(ear PROPERTIES INSTALL_RPATH "@loader_path/${EAR_LIB_PATH}") -endif() - -install(TARGETS ear - LIBRARY DESTINATION ${EAR_LIB_PATH}) diff --git a/libear/config.h.in b/libear/config.h.in deleted file mode 100644 index ada28e80..00000000 --- a/libear/config.h.in +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#cmakedefine HAVE_EXECVE -#cmakedefine HAVE_EXECV -#cmakedefine HAVE_EXECVPE -#cmakedefine HAVE_EXECVP -#cmakedefine HAVE_EXECVP2 -#cmakedefine HAVE_EXECT -#cmakedefine HAVE_EXECL -#cmakedefine HAVE_EXECLP -#cmakedefine HAVE_EXECLE -#cmakedefine HAVE_POSIX_SPAWN -#cmakedefine HAVE_POSIX_SPAWNP -#cmakedefine HAVE_NSGETENVIRON - -#cmakedefine APPLE diff --git a/libear/ear.c b/libear/ear.c deleted file mode 100644 index ca15ac4a..00000000 --- a/libear/ear.c +++ /dev/null @@ -1,656 +0,0 @@ -/* Copyright (C) 2012-2019 by László Nagy - This file is part of Bear. - - Bear is a tool to generate compilation database for clang tooling. - - Bear is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Bear is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -/** - * This file implements a shared library. This library can be pre-loaded by - * the dynamic linker of the Operating System (OS). It implements a few function - * related to process creation. By pre-load this library the executed process - * uses these functions instead of those from the standard library. - * - * The idea here is to inject a logic before call the real methods. The logic is - * to dump the call into a file. To call the real method this library is doing - * the job of the dynamic linker. - * - * The only input for the log writing is about the destination directory. - * This is passed as environment variable. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined HAVE_POSIX_SPAWN || defined HAVE_POSIX_SPAWNP -#include -#endif - -#if defined HAVE_NSGETENVIRON -# include -static char **environ; -#else -extern char **environ; -#endif - -#define ENV_OUTPUT "INTERCEPT_BUILD_TARGET_DIR" -#ifdef APPLE -# define ENV_FLAT "DYLD_FORCE_FLAT_NAMESPACE" -# define ENV_PRELOAD "DYLD_INSERT_LIBRARIES" -# define ENV_SIZE 3 -#else -# define ENV_PRELOAD "LD_PRELOAD" -# define ENV_SIZE 2 -#endif - -#define STRINGIFY(x) #x -#define TOSTRING(x) STRINGIFY(x) -#define AT "libear: (" __FILE__ ":" TOSTRING(__LINE__) ") " - -#define PERROR(msg) do { perror(AT msg); } while (0) - -#define ERROR_AND_EXIT(msg) do { PERROR(msg); exit(EXIT_FAILURE); } while (0) - -#define DLSYM(TYPE_, VAR_, SYMBOL_) \ - union { \ - void *from; \ - TYPE_ to; \ - } cast; \ - if (0 == (cast.from = dlsym(RTLD_NEXT, SYMBOL_))) { \ - PERROR("dlsym"); \ - exit(EXIT_FAILURE); \ - } \ - TYPE_ const VAR_ = cast.to; - - -typedef char const * bear_env_t[ENV_SIZE]; - -static int capture_env_t(bear_env_t *env); -static void release_env_t(bear_env_t *env); -static char const **string_array_partial_update(char *const envp[], bear_env_t *env); -static char const **string_array_single_update(char const *envs[], char const *key, char const *value); -static void report_call(char const *const argv[]); -static int write_report(int fd, char const *const argv[]); -static char const **string_array_from_varargs(char const * arg, va_list *args); -static char const **string_array_copy(char const **in); -static size_t string_array_length(char const *const *in); -static void string_array_release(char const **); - - -static bear_env_t env_names = - { ENV_OUTPUT - , ENV_PRELOAD -#ifdef ENV_FLAT - , ENV_FLAT -#endif - }; - -static bear_env_t initial_env = - { 0 - , 0 -#ifdef ENV_FLAT - , 0 -#endif - }; - -static int initialized = 0; -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - -static void on_load(void) __attribute__((constructor)); -static void on_unload(void) __attribute__((destructor)); - -static int mt_safe_on_load(void); -static void mt_safe_on_unload(void); - - -#ifdef HAVE_EXECVE -static int call_execve(const char *path, char *const argv[], - char *const envp[]); -#endif -#ifdef HAVE_EXECVP -static int call_execvp(const char *file, char *const argv[]); -#endif -#ifdef HAVE_EXECVPE -static int call_execvpe(const char *file, char *const argv[], - char *const envp[]); -#endif -#ifdef HAVE_EXECVP2 -static int call_execvP(const char *file, const char *search_path, - char *const argv[]); -#endif -#ifdef HAVE_EXECT -static int call_exect(const char *path, char *const argv[], - char *const envp[]); -#endif -#ifdef HAVE_POSIX_SPAWN -static int call_posix_spawn(pid_t *restrict pid, const char *restrict path, - const posix_spawn_file_actions_t *file_actions, - const posix_spawnattr_t *restrict attrp, - char *const argv[restrict], - char *const envp[restrict]); -#endif -#ifdef HAVE_POSIX_SPAWNP -static int call_posix_spawnp(pid_t *restrict pid, const char *restrict file, - const posix_spawn_file_actions_t *file_actions, - const posix_spawnattr_t *restrict attrp, - char *const argv[restrict], - char *const envp[restrict]); -#endif - - -/* Initialization method to Captures the relevant environment variables. - */ - -static void on_load(void) { - pthread_mutex_lock(&mutex); - if (0 == initialized) - initialized = mt_safe_on_load(); - pthread_mutex_unlock(&mutex); -} - -static void on_unload(void) { - pthread_mutex_lock(&mutex); - if (0 != initialized) - mt_safe_on_unload(); - initialized = 0; - pthread_mutex_unlock(&mutex); -} - -static int mt_safe_on_load(void) { -#ifdef HAVE_NSGETENVIRON - environ = *_NSGetEnviron(); - if (0 == environ) - return 0; -#endif - // Capture current relevant environment variables - return capture_env_t(&initial_env); -} - -static void mt_safe_on_unload(void) { - release_env_t(&initial_env); -} - - -/* These are the methods we are try to hijack. - */ - -#ifdef HAVE_EXECVE -int execve(const char *path, char *const argv[], char *const envp[]) { - report_call((char const *const *)argv); - return call_execve(path, argv, envp); -} -#endif - -#ifdef HAVE_EXECV -#ifndef HAVE_EXECVE -#error can not implement execv without execve -#endif -int execv(const char *path, char *const argv[]) { - report_call((char const *const *)argv); - return call_execve(path, argv, environ); -} -#endif - -#ifdef HAVE_EXECVPE -int execvpe(const char *file, char *const argv[], char *const envp[]) { - report_call((char const *const *)argv); - return call_execvpe(file, argv, envp); -} -#endif - -#ifdef HAVE_EXECVP -int execvp(const char *file, char *const argv[]) { - report_call((char const *const *)argv); - return call_execvp(file, argv); -} -#endif - -#ifdef HAVE_EXECVP2 -int execvP(const char *file, const char *search_path, char *const argv[]) { - report_call((char const *const *)argv); - return call_execvP(file, search_path, argv); -} -#endif - -#ifdef HAVE_EXECT -int exect(const char *path, char *const argv[], char *const envp[]) { - report_call((char const *const *)argv); - return call_exect(path, argv, envp); -} -#endif - -#ifdef HAVE_EXECL -# ifndef HAVE_EXECVE -# error can not implement execl without execve -# endif -int execl(const char *path, const char *arg, ...) { - va_list args; - va_start(args, arg); - char const **argv = string_array_from_varargs(arg, &args); - va_end(args); - - report_call((char const *const *)argv); - int const result = call_execve(path, (char *const *)argv, environ); - - string_array_release(argv); - return result; -} -#endif - -#ifdef HAVE_EXECLP -# ifndef HAVE_EXECVP -# error can not implement execlp without execvp -# endif -int execlp(const char *file, const char *arg, ...) { - va_list args; - va_start(args, arg); - char const **argv = string_array_from_varargs(arg, &args); - va_end(args); - - report_call((char const *const *)argv); - int const result = call_execvp(file, (char *const *)argv); - - string_array_release(argv); - return result; -} -#endif - -#ifdef HAVE_EXECLE -# ifndef HAVE_EXECVE -# error can not implement execle without execve -# endif -// int execle(const char *path, const char *arg, ..., char * const envp[]); -int execle(const char *path, const char *arg, ...) { - va_list args; - va_start(args, arg); - char const **argv = string_array_from_varargs(arg, &args); - char const **envp = va_arg(args, char const **); - va_end(args); - - report_call((char const *const *)argv); - int const result = - call_execve(path, (char *const *)argv, (char *const *)envp); - - string_array_release(argv); - return result; -} -#endif - -#ifdef HAVE_POSIX_SPAWN -int posix_spawn(pid_t *restrict pid, const char *restrict path, - const posix_spawn_file_actions_t *file_actions, - const posix_spawnattr_t *restrict attrp, - char *const argv[restrict], char *const envp[restrict]) { - report_call((char const *const *)argv); - return call_posix_spawn(pid, path, file_actions, attrp, argv, envp); -} -#endif - -#ifdef HAVE_POSIX_SPAWNP -int posix_spawnp(pid_t *restrict pid, const char *restrict file, - const posix_spawn_file_actions_t *file_actions, - const posix_spawnattr_t *restrict attrp, - char *const argv[restrict], char *const envp[restrict]) { - report_call((char const *const *)argv); - return call_posix_spawnp(pid, file, file_actions, attrp, argv, envp); -} -#endif - -/* These are the methods which forward the call to the standard implementation. - */ - -#ifdef HAVE_EXECVE -static int call_execve(const char *path, char *const argv[], - char *const envp[]) { - typedef int (*func)(const char *, char *const *, char *const *); - - DLSYM(func, fp, "execve"); - - char const **const menvp = string_array_partial_update(envp, &initial_env); - int const result = (*fp)(path, argv, (char *const *)menvp); - string_array_release(menvp); - return result; -} -#endif - -#ifdef HAVE_EXECVPE -static int call_execvpe(const char *file, char *const argv[], - char *const envp[]) { - typedef int (*func)(const char *, char *const *, char *const *); - - DLSYM(func, fp, "execvpe"); - - char const **const menvp = string_array_partial_update(envp, &initial_env); - int const result = (*fp)(file, argv, (char *const *)menvp); - string_array_release(menvp); - return result; -} -#endif - -#ifdef HAVE_EXECVP -static int call_execvp(const char *file, char *const argv[]) { - typedef int (*func)(const char *file, char *const argv[]); - - DLSYM(func, fp, "execvp"); - - char **const original = environ; - char const **const modified = string_array_partial_update(original, &initial_env); - environ = (char **)modified; - int const result = (*fp)(file, argv); - environ = original; - string_array_release(modified); - - return result; -} -#endif - -#ifdef HAVE_EXECVP2 -static int call_execvP(const char *file, const char *search_path, - char *const argv[]) { - typedef int (*func)(const char *, const char *, char *const *); - - DLSYM(func, fp, "execvP"); - - char **const original = environ; - char const **const modified = string_array_partial_update(original, &initial_env); - environ = (char **)modified; - int const result = (*fp)(file, search_path, argv); - environ = original; - string_array_release(modified); - - return result; -} -#endif - -#ifdef HAVE_EXECT -static int call_exect(const char *path, char *const argv[], - char *const envp[]) { - typedef int (*func)(const char *, char *const *, char *const *); - - DLSYM(func, fp, "exect"); - - char const **const menvp = string_array_partial_update(envp, &initial_env); - int const result = (*fp)(path, argv, (char *const *)menvp); - string_array_release(menvp); - return result; -} -#endif - -#ifdef HAVE_POSIX_SPAWN -static int call_posix_spawn(pid_t *restrict pid, const char *restrict path, - const posix_spawn_file_actions_t *file_actions, - const posix_spawnattr_t *restrict attrp, - char *const argv[restrict], - char *const envp[restrict]) { - typedef int (*func)(pid_t *restrict, const char *restrict, - const posix_spawn_file_actions_t *, - const posix_spawnattr_t *restrict, - char *const *restrict, char *const *restrict); - - DLSYM(func, fp, "posix_spawn"); - - char const **const menvp = string_array_partial_update(envp, &initial_env); - int const result = - (*fp)(pid, path, file_actions, attrp, argv, (char *const *restrict)menvp); - string_array_release(menvp); - return result; -} -#endif - -#ifdef HAVE_POSIX_SPAWNP -static int call_posix_spawnp(pid_t *restrict pid, const char *restrict file, - const posix_spawn_file_actions_t *file_actions, - const posix_spawnattr_t *restrict attrp, - char *const argv[restrict], - char *const envp[restrict]) { - typedef int (*func)(pid_t *restrict, const char *restrict, - const posix_spawn_file_actions_t *, - const posix_spawnattr_t *restrict, - char *const *restrict, char *const *restrict); - - DLSYM(func, fp, "posix_spawnp"); - - char const **const menvp = string_array_partial_update(envp, &initial_env); - int const result = - (*fp)(pid, file, file_actions, attrp, argv, (char *const *restrict)menvp); - string_array_release(menvp); - return result; -} -#endif - -/* this method is to write log about the process creation. */ - -static void report_call(char const *const argv[]) { - if (!initialized) - return; - // Create report file name - char const * const out_dir = initial_env[0]; - size_t const path_max_length = strlen(out_dir) + 32; - char filename[path_max_length]; - if (-1 == snprintf(filename, path_max_length, "%s/execution.XXXXXX", out_dir)) - ERROR_AND_EXIT("snprintf"); - // Create report file - int fd = mkstemp((char *)&filename); - if (-1 == fd) - ERROR_AND_EXIT("mkstemp"); - // Write report file - const int finished = write_report(fd, argv); - // Close report file - if (close(fd)) - ERROR_AND_EXIT("close"); - // Remove the file if it's not done - if ((-1 == finished) && (-1 == unlink(filename))) - ERROR_AND_EXIT("unlink"); -} - -static int write_binary_string(int fd, const char *const string) { - // write type - if (-1 == write(fd, "str", 3)) { - PERROR("write type"); - return -1; - } - // write length - const uint32_t length = strlen(string); - if (-1 == write(fd, (void *) &length, sizeof(uint32_t))) { - PERROR("write length"); - return -1; - } - // write value - if (-1 == write(fd, (void *) string, length)) { - PERROR("write value"); - return -1; - } - return 0; -} - -static int write_binary_string_list(int fd, const char *const *const strings) { - // write type - if (-1 == write(fd, "lst", 3)) { - PERROR("write type"); - return -1; - } - // write length - const uint32_t length = string_array_length(strings); - if (-1 == write(fd, (void *) &length, sizeof(uint32_t))) { - PERROR("write length"); - return -1; - } - // write value - for (uint32_t idx = 0; idx < length; ++idx) { - const char *string = strings[idx]; - if (-1 == write_binary_string(fd, string)) { - PERROR("write value"); - return -1; - } - } - return 0; -} - -static int write_report(int fd, char const *const argv[]) { - const char *cwd = getcwd(NULL, 0); - if (0 == cwd) { - PERROR("getcwd"); - return -1; - } else { - if (-1 == write_binary_string(fd, cwd)) { - PERROR("cwd writing failed"); - return -1; - } - } - free((void *)cwd); - if (-1 == write_binary_string_list(fd, argv)) { - PERROR("cmd writing failed"); - return -1; - } - return 0; -} - -/* update environment assure that chilren processes will copy the desired - * behaviour */ - -static int capture_env_t(bear_env_t *env) { - for (size_t it = 0; it < ENV_SIZE; ++it) { - char const * const env_value = getenv(env_names[it]); - if (0 == env_value) { - PERROR("getenv"); - return 0; - } - - char const * const env_copy = strdup(env_value); - if (0 == env_copy) { - PERROR("strdup"); - return 0; - } - - (*env)[it] = env_copy; - } - return 1; -} - -static void release_env_t(bear_env_t *env) { - for (size_t it = 0; it < ENV_SIZE; ++it) { - free((void *)(*env)[it]); - (*env)[it] = 0; - } -} - -static char const **string_array_partial_update(char *const envp[], bear_env_t *env) { - char const **result = string_array_copy((char const **)envp); - for (size_t it = 0; it < ENV_SIZE && (*env)[it]; ++it) - result = string_array_single_update(result, env_names[it], (*env)[it]); - return result; -} - -static char const **string_array_single_update(char const *envs[], char const *key, char const * const value) { - // find the key if it's there - size_t const key_length = strlen(key); - char const **it = envs; - for (; (it) && (*it); ++it) { - if ((0 == strncmp(*it, key, key_length)) && - (strlen(*it) > key_length) && ('=' == (*it)[key_length])) - break; - } - // allocate a environment entry - size_t const value_length = strlen(value); - size_t const env_length = key_length + value_length + 2; - char *env = malloc(env_length); - if (0 == env) - ERROR_AND_EXIT("malloc"); - if (-1 == snprintf(env, env_length, "%s=%s", key, value)) - ERROR_AND_EXIT("snprintf"); - // replace or append the environment entry - if (it && *it) { - free((void *)*it); - *it = env; - return envs; - } else { - size_t const size = string_array_length(envs); - char const **result = realloc(envs, (size + 2) * sizeof(char const *)); - if (0 == result) - ERROR_AND_EXIT("realloc"); - result[size] = env; - result[size + 1] = 0; - return result; - } -} - -/* util methods to deal with string arrays. environment and process arguments - * are both represented as string arrays. */ - -static char const **string_array_from_varargs(char const *const arg, va_list *args) { - char const **result = 0; - size_t size = 0; - for (char const *it = arg; it; it = va_arg(*args, char const *)) { - result = realloc(result, (size + 1) * sizeof(char const *)); - if (0 == result) - ERROR_AND_EXIT("realloc"); - char const *copy = strdup(it); - if (0 == copy) - ERROR_AND_EXIT("strdup"); - result[size++] = copy; - } - result = realloc(result, (size + 1) * sizeof(char const *)); - if (0 == result) - ERROR_AND_EXIT("realloc"); - result[size++] = 0; - - return result; -} - -static char const **string_array_copy(char const **const in) { - size_t const size = string_array_length(in); - - char const **const result = malloc((size + 1) * sizeof(char const *)); - if (0 == result) - ERROR_AND_EXIT("malloc"); - - char const **out_it = result; - for (char const *const *in_it = in; (in_it) && (*in_it); - ++in_it, ++out_it) { - *out_it = strdup(*in_it); - if (0 == *out_it) - ERROR_AND_EXIT("strdup"); - } - *out_it = 0; - return result; -} - -static size_t string_array_length(char const *const *const in) { - size_t result = 0; - for (char const *const *it = in; (it) && (*it); ++it) - ++result; - return result; -} - -static void string_array_release(char const **in) { - for (char const *const *it = in; (it) && (*it); ++it) { - free((void *)*it); - } - free((void *)in); -} diff --git a/shell-completion/bash/CMakeLists.txt b/shell-completion/bash/CMakeLists.txt index b2d4b423..11f2ccba 100644 --- a/shell-completion/bash/CMakeLists.txt +++ b/shell-completion/bash/CMakeLists.txt @@ -1,12 +1,5 @@ -find_package(bash-completion) -if (NOT BASH_COMPLETION_FOUND) - # Fallback default dir - set(BASH_COMPLETION_COMPLETIONSDIR - "${CMAKE_INSTALL_DATADIR}/bash-completion/completions/") -endif() - -install( - FILES "bear" - DESTINATION "${BASH_COMPLETION_COMPLETIONSDIR}" +include(GNUInstallDirs) +install(FILES "bear" + DESTINATION "${CMAKE_INSTALL_DATADIR}/bash-completion/completions/" COMPONENT shell-completion - ) +) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt new file mode 100644 index 00000000..200fc00c --- /dev/null +++ b/source/CMakeLists.txt @@ -0,0 +1,30 @@ +include(CheckSymbolExists) +include(CheckIncludeFile) +check_symbol_exists(_NSGetEnviron crt_externs.h HAVE_NSGETENVIRON) +check_include_file(spawn.h HAVE_SPAWN_HEADER) + +configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) + + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +include(GNUInstallDirs) +set(EXEC_LIB_FILE ${CMAKE_SHARED_LIBRARY_PREFIX}exec${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(EXEC_LIB_PATH "${CMAKE_INSTALL_LIBDIR}/bear") +set(DEFAULT_PRELOAD_FILE ${CMAKE_INSTALL_PREFIX}/${EXEC_LIB_PATH}/${EXEC_LIB_FILE} + CACHE STRING "Default path to libexec.") +set(INTERCEPT_EXE ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBEXECDIR}/intercept + CACHE STRING "Default path to intercept") +set(INTERCEPT_CC_EXE ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBEXECDIR}/intercept-cc + CACHE STRING "Default path to intercept-cc") +set(INTERCEPT_CXX_EXE ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBEXECDIR}/intercept-c++ + CACHE STRING "Default path to intercept-c++") + + +add_subdirectory(libexec_a) +add_subdirectory(libexec) +add_subdirectory(intercept_a) +add_subdirectory(intercept) +add_subdirectory(intercept-wrapper) +add_subdirectory(bear) diff --git a/bear/CMakeLists.txt b/source/bear/CMakeLists.txt similarity index 100% rename from bear/CMakeLists.txt rename to source/bear/CMakeLists.txt diff --git a/bear/bear.py b/source/bear/bear.py similarity index 91% rename from bear/bear.py rename to source/bear/bear.py index cd5abbac..b34b0376 100644 --- a/bear/bear.py +++ b/source/bear/bear.py @@ -23,14 +23,14 @@ This implementation is using the LD_PRELOAD or DYLD_INSERT_LIBRARIES mechanisms provided by the dynamic linker. The related library is implemented -in C language and can be found under 'libear' directory. +in C language and can be found under 'libexec' directory. -The 'libear' library is capturing all child process creation and logging the +The 'libexec' library is capturing all child process creation and logging the relevant information about it into separate files in a specified directory. The input of the library is therefore the output directory which is passed as an environment variable. -This module implements the build command execution with the 'libear' library +This module implements the build command execution with the 'libexec' library and the post-processing of the output files, which will condensates into a (might be empty) compilation database. """ @@ -127,11 +127,11 @@ re.compile(r'^(pg)(f77|f90|f95|fortran)$') ) -TRACE_FILE_PREFIX = 'execution.' # same as in ear.c +TRACE_FILE_SUFFIX = '.process_start.json' # same as in ear.c C_LANG, CPLUSPLUS_LANG, FORTRAN_LANG, OTHER = range(4) -Execution = collections.namedtuple('Execution', ['cwd', 'cmd']) +Execution = collections.namedtuple('Execution', ['pid', 'cwd', 'cmd']) CompilationCommand = collections.namedtuple( 'CompilationCommand', @@ -336,8 +336,8 @@ def capture(args, tools): with temporary_directory(prefix='intercept-') as tmp_dir: # run the build command - environment = setup_environment(args, tmp_dir) - exit_code = run_build(args.build, env=environment) + command = build_command(args, tmp_dir) + exit_code = run_build(command) # read the intercepted exec calls calls = (parse_exec_trace(file) for file in exec_trace_files(tmp_dir)) safe_calls = (x for x in calls if x is not None) @@ -405,31 +405,41 @@ def compilations(exec_calls, tools): yield compilation -def setup_environment(args, destination): - # type: (argparse.Namespace, str) -> Dict[str, str] - """ Sets up the environment for the build command. +def build_command(args, tmp_dir): + # type: (argparse.Namespace, str) -> List[str] + session = [ '--session-library', args.libexec ] + command = [ '--exec-file', args.build[0], '--exec-command' ] + args.build + verbose = [ '--verbose' ] if args.verbose else [] + return [ + args.interceptor, + '--report-destination', tmp_dir + ] + session + verbose + command - In order to capture the sub-commands (executed by the build process), - it needs to prepare the environment. It's either the compiler wrappers - shall be announce as compiler or the intercepting library shall be - announced for the dynamic linker. - - :param args: command line arguments - :param destination: directory path for the execution trace files - :return: a prepared set of environment variables. """ - - environment = dict(os.environ) - environment.update({'INTERCEPT_BUILD_TARGET_DIR': destination}) - - if sys.platform == 'darwin': - environment.update({ - 'DYLD_INSERT_LIBRARIES': args.libear, - 'DYLD_FORCE_FLAT_NAMESPACE': '1' - }) - else: - environment.update({'LD_PRELOAD': args.libear}) - - return environment +# def setup_environment(args, destination): +# # type: (argparse.Namespace, str) -> Dict[str, str] +# """ Sets up the environment for the build command. +# +# In order to capture the sub-commands (executed by the build process), +# it needs to prepare the environment. It's either the compiler wrappers +# shall be announce as compiler or the intercepting library shall be +# announced for the dynamic linker. +# +# :param args: command line arguments +# :param destination: directory path for the execution trace files +# :return: a prepared set of environment variables. """ +# +# environment = dict(os.environ) +# environment.update({'INTERCEPT_BUILD_TARGET_DIR': destination}) +# +# if sys.platform == 'darwin': +# environment.update({ +# 'DYLD_INSERT_LIBRARIES': args.libear, +# 'DYLD_FORCE_FLAT_NAMESPACE': '1' +# }) +# else: +# environment.update({'LD_PRELOAD': args.libear}) +# +# return environment def parse_exec_trace(filename): @@ -442,33 +452,15 @@ def parse_exec_trace(filename): :param filename: path to an execution trace file to read from, :return: an Execution object. """ - def byte_to_int(byte): - return struct.unpack_from("=I", byte)[0] - - def parse_length(handler, expected_type): - type_bytes = handler.read(3) - if type_bytes != expected_type: - raise Exception("type not expected") - length_bytes = handler.read(4) - return byte_to_int(length_bytes) - - def parse_string(handler): - length = parse_length(handler, b'str') - value_bytes = handler.read(length) - return value_bytes.decode("utf-8") - - def parse_string_list(handler): - length = parse_length(handler, b'lst') - return [parse_string(handler) for _ in range(length)] - logging.debug('parse exec trace file: %s', filename) - with open(filename, 'rb', buffering=0) as handler: + with open(filename, 'r') as handler: try: - return Execution(cwd=parse_string(handler), - cmd=parse_string_list(handler)) + entry = json.load(handler) + return Execution(pid=entry['pid'], + cwd=entry['cwd'], + cmd=entry['cmd']) except Exception as exception: - logging.warning('parse exec trace file: %s FAILED: %s', - filename, exception) + logging.warning('parse exec trace file: %s FAILED: %s', filename, exception) return None @@ -480,7 +472,7 @@ def exec_trace_files(directory): candidates = (os.path.join(directory, file) for file in os.listdir(directory) - if file.startswith(TRACE_FILE_PREFIX)) + if file.endswith(TRACE_FILE_SUFFIX)) return sorted((f for f in filter(os.path.isfile, candidates)), key=os.path.getctime) @@ -580,11 +572,29 @@ def create_intercept_parser(): The output is not continuously updated, it's done when the build command finished. """) advanced.add_argument( - '--libear', '-l', - dest='libear', + '--libexec', '-l', + dest='libexec', default="@DEFAULT_PRELOAD_FILE@", action='store', - help="""specify libear file location.""") + help="""specify libexec file location.""") + advanced.add_argument( + '--interceptor', + dest='interceptor', + default="@INTERCEPT_EXE@", + action='store', + help="""specify the intercept binary location.""") + advanced.add_argument( + '--intercept-wrapper', + dest='intercept_wrapper_cc', + default="@INTERCEPT_CC_EXE@", + action='store', + help="""specify the C compiler wrapper location.""") + advanced.add_argument( + '--intercept-wrapper++', + dest='intercept_wrapper_cxx', + default="@INTERCEPT_CXX_EXE@", + action='store', + help="""specify the C++ compiler wrapper location.""") parser.add_argument( dest='build', nargs=argparse.REMAINDER, help="""Command to run.""") @@ -665,7 +675,7 @@ def from_db_entry(cls, entry, tools): command = shell_split(entry['command']) if 'command' in entry else \ entry['arguments'] - execution = Execution(cmd=command, cwd=entry['directory']) + execution = Execution(cmd=command, cwd=entry['directory'], pid=0) return cls.iter_from_execution(execution, tools) @classmethod diff --git a/source/config.h.in b/source/config.h.in new file mode 100644 index 00000000..276f09e0 --- /dev/null +++ b/source/config.h.in @@ -0,0 +1,13 @@ +#pragma once + +#cmakedefine HAVE_NSGETENVIRON +#cmakedefine HAVE_SPAWN_HEADER + +#cmakedefine APPLE + + +#include + +#if defined HAVE_SPAWN_HEADER +# include +#endif diff --git a/source/intercept-wrapper/CMakeLists.txt b/source/intercept-wrapper/CMakeLists.txt new file mode 100644 index 00000000..4624d7da --- /dev/null +++ b/source/intercept-wrapper/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(intercept-cc) +add_subdirectory(intercept-c++) diff --git a/source/intercept-wrapper/intercept-c++/CMakeLists.txt b/source/intercept-wrapper/intercept-c++/CMakeLists.txt new file mode 100644 index 00000000..2bb2cc2d --- /dev/null +++ b/source/intercept-wrapper/intercept-c++/CMakeLists.txt @@ -0,0 +1,13 @@ +add_executable(intercept-c++ + ../main.cc + ) + +target_link_libraries(intercept-c++ exec_a) +target_compile_features(intercept-c++ PUBLIC cxx_std_14) +target_compile_options(intercept-c++ PUBLIC -fno-exceptions -fno-rtti) +set_target_properties(intercept-c++ PROPERTIES + LINKER_LANGUAGE "C") + +include(GNUInstallDirs) +install(TARGETS intercept-c++ + RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}) \ No newline at end of file diff --git a/source/intercept-wrapper/intercept-cc/CMakeLists.txt b/source/intercept-wrapper/intercept-cc/CMakeLists.txt new file mode 100644 index 00000000..71697d07 --- /dev/null +++ b/source/intercept-wrapper/intercept-cc/CMakeLists.txt @@ -0,0 +1,13 @@ +add_executable(intercept-cc + ../main.cc + ) + +target_link_libraries(intercept-cc exec_a) +target_compile_features(intercept-cc PUBLIC cxx_std_14) +target_compile_options(intercept-cc PUBLIC -fno-exceptions -fno-rtti) +set_target_properties(intercept-cc PROPERTIES + LINKER_LANGUAGE "C") + +include(GNUInstallDirs) +install(TARGETS intercept-cc + RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}) \ No newline at end of file diff --git a/source/intercept-wrapper/main.cc b/source/intercept-wrapper/main.cc new file mode 100644 index 00000000..a51f34b2 --- /dev/null +++ b/source/intercept-wrapper/main.cc @@ -0,0 +1,63 @@ +/* Copyright (C) 2012-2017 by László Nagy + This file is part of Bear. + + Bear is a tool to generate compilation database for clang tooling. + + Bear is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bear is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include + +#include "libexec_a/Interface.h" +#include "libexec_a/Environment.h" +#include "libexec_a/Executor.h" + +namespace { + + struct LibC { + using execve_t = int (*)(const char *path, char *const argv[], char *const envp[]); + static execve_t resolve_execve() noexcept { + return &execve; + } + }; + + using Executor = ::ear::Executor; + + /// It's a C++ compiler if the name ends with "++". + bool is_cxx(const char * name) noexcept { + const size_t length = ::ear::array::length(name); + return (length > 2) && (name[length - 1] == '+') && (name[length - 2] == '+'); + } +} + + +int main(int argc, char *argv[], char *envp[]) { + if (argc <= 0) { + fprintf(stderr, "intercept-wrapper: not enough arguments.\n"); + return -1; + } + + const auto session = ::ear::environment::wrapper_session(const_cast(envp)); + if (! session.is_valid()) { + fprintf(stderr, "intercept-wrapper: not initialized.\n"); + return -1; + } + + // Replace the compiler wrapper to the real compiler. + argv[0] = const_cast((is_cxx(argv[0])) ? session.cxx : session.cc); + + const Executor executor(session); + return executor.execve(argv[0], argv, envp); +} diff --git a/source/intercept/CMakeLists.txt b/source/intercept/CMakeLists.txt new file mode 100644 index 00000000..07403789 --- /dev/null +++ b/source/intercept/CMakeLists.txt @@ -0,0 +1,15 @@ +set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +set(CMAKE_EXE_LINKER_FLAGS "-static") + +add_executable(intercept + main.cc +) + +target_link_libraries(intercept intercept_a) +target_compile_features(intercept PUBLIC cxx_std_17) +target_compile_options(intercept PUBLIC -fno-exceptions -fno-rtti) +set_target_properties(intercept PROPERTIES LINKER_LANGUAGE "C") + +include(GNUInstallDirs) +install(TARGETS intercept + RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}) \ No newline at end of file diff --git a/source/intercept/main.cc b/source/intercept/main.cc new file mode 100644 index 00000000..ab8ae1d2 --- /dev/null +++ b/source/intercept/main.cc @@ -0,0 +1,123 @@ +/* Copyright (C) 2012-2017 by László Nagy + This file is part of Bear. + + Bear is a tool to generate compilation database for clang tooling. + + Bear is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bear is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include + +#include "intercept_a/Interface.h" +#include "intercept_a/Session.h" +#include "intercept_a/Result.h" +#include "intercept_a/Environment.h" +#include "intercept_a/Reporter.h" +#include "intercept_a/SystemCalls.h" + + +namespace { + + constexpr char MESSAGE_PREFIX[] = "intercept: "; + + pear::Result spawnp(const ::pear::Execution &config, + const ::pear::EnvironmentPtr &environment) noexcept { + if ((config.search_path != nullptr) && (config.file != nullptr)) { + return pear::SystemCalls::fork_with_execvp(config.file, config.search_path, config.command, environment->data()); + } else if (config.file != nullptr) { + return pear::SystemCalls::spawnp(config.file, config.command, environment->data()); + } else { + return pear::SystemCalls::spawn(config.path, config.command, environment->data()); + } + } + + void report_start(::pear::Result const &reporter, pid_t pid, const char **cmd) noexcept { + ::pear::merge(reporter, ::pear::Event::start(pid, cmd)) + .bind([](auto tuple) { + const auto& [ rptr, eptr ] = tuple; + return rptr->send(eptr); + }) + .handle_with([](auto message) { + std::cerr << MESSAGE_PREFIX << message.what() << std::endl; + }) + .get_or_else(0); + } + + void report_exit(::pear::Result const &reporter, pid_t pid, int exit) noexcept { + ::pear::merge(reporter, ::pear::Event::stop(pid, exit)) + .bind([](auto tuple) { + const auto& [ rptr, eptr ] = tuple; + return rptr->send(eptr); + }) + .handle_with([](auto message) { + std::cerr << MESSAGE_PREFIX << message.what() << std::endl; + }) + .get_or_else(0); + } + + ::pear::EnvironmentPtr create_environment(char *original[], const ::pear::SessionPtr &session) { + auto builder = pear::Environment::Builder(const_cast(original)); + session->configure(builder); + return builder.build(); + } + + std::ostream &operator<<(std::ostream &os, char *const *values) { + os << '['; + for (char *const *it = values; *it != nullptr; ++it) { + if (it != values) { + os << ", "; + } + os << '"' << *it << '"'; + } + os << ']'; + + return os; + } + +} + +int main(int argc, char *argv[], char *envp[]) { + return ::pear::parse(argc, argv) + .map([&argv](auto arguments) { + if (arguments->context_.verbose) { + std::cerr << MESSAGE_PREFIX << argv << std::endl; + } + return arguments; + }) + .bind([&envp](auto arguments) { + auto reporter = pear::Reporter::tempfile(arguments->context_.destination); + + auto environment = create_environment(envp, arguments); + return spawnp(arguments->execution_, environment) + .template map([&arguments, &reporter](auto &pid) { + report_start(reporter, pid, arguments->execution_.command); + return pid; + }) + .template bind>([](auto pid) { + return pear::SystemCalls::wait_pid(pid) + .template map>([&pid](auto exit) { + return std::make_tuple(pid, exit); + }); + }) + .template map([&reporter](auto tuple) { + const auto& [pid, exit] = tuple; + report_exit(reporter, pid, exit); + return exit; + }); + }) + .handle_with([](auto message) { + std::cerr << MESSAGE_PREFIX << message.what() << std::endl; + }) + .get_or_else(EXIT_FAILURE); +} diff --git a/source/intercept_a/CMakeLists.txt b/source/intercept_a/CMakeLists.txt new file mode 100644 index 00000000..e9e6ac98 --- /dev/null +++ b/source/intercept_a/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(intercept_a STATIC + Environment.h + Environment.cc + Interface.h + Reporter.h + Reporter.cc + Result.h + Session.h + Session.cc + SystemCalls.h + SystemCalls.cc +) + +target_compile_features(intercept_a PUBLIC cxx_std_17) +target_compile_options(intercept_a PUBLIC -fno-exceptions -fno-rtti) +set_target_properties(intercept_a PROPERTIES LINKER_LANGUAGE "C") + +add_subdirectory(test) diff --git a/source/intercept_a/Environment.cc b/source/intercept_a/Environment.cc new file mode 100644 index 00000000..6eebb24f --- /dev/null +++ b/source/intercept_a/Environment.cc @@ -0,0 +1,195 @@ +/* Copyright (C) 2012-2017 by László Nagy + This file is part of Bear. + + Bear is a tool to generate compilation database for clang tooling. + + Bear is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bear is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "intercept_a/Environment.h" +#include "intercept_a/Interface.h" + +namespace { + + constexpr char osx_preload_key[] = "DYLD_INSERT_LIBRARIES"; + constexpr char osx_namespace_key[] = "DYLD_FORCE_FLAT_NAMESPACE"; + constexpr char glibc_preload_key[] = "LD_PRELOAD"; + constexpr char cc_key[] = "CC"; + constexpr char cxx_key[] = "CXX"; + + using env_t = std::map; + using mapper_t = std::function; + + char **to_c_array(const env_t &input) { + const size_t result_size = input.size() + 1; + const auto result = new char *[result_size]; + auto result_it = result; + for (const auto &it : input) { + const size_t entry_size = it.first.size() + it.second.size() + 2; + auto entry = new char [entry_size]; + + auto key = std::copy(it.first.begin(), it.first.end(), entry); + *key++ = '='; + auto value = std::copy(it.second.begin(), it.second.end(), key); + *value = '\0'; + + *result_it++ = entry; + } + *result_it = nullptr; + return result; + } + + env_t to_map(const char **const input) noexcept { + env_t result; + if (input == nullptr) + return result; + + for (const char **it = input; *it != nullptr; ++it) { + const auto end = *it + std::strlen(*it); + const auto sep = std::find(*it, end, '='); + const std::string key = (sep != end) ? std::string(*it, sep) : std::string(*it, end); + const std::string value = (sep != end) ? std::string(sep + 1, end) : std::string(); + result.emplace(key, value); + } + return result; + } + + std::list split(const std::string &input, const char sep) noexcept { + std::list result; + + std::string::size_type previous = 0; + do { + const std::string::size_type current = input.find(sep, previous); + result.emplace_back(input.substr(previous, current - previous)); + previous = (current != std::string::npos) ? current + 1 : current; + } while (previous != std::string::npos); + + return result; + } + + std::string merge_into_paths(const std::string ¤t, const std::string &value) noexcept { + auto paths = split(current, ':'); + if (std::find(paths.begin(), paths.end(), value) == paths.end()) { + paths.emplace_front(value); + return std::accumulate(paths.begin(), paths.end(), + std::string(), + [](std::string acc, std::string item) { + return (acc.empty()) ? item : acc + ':' + item; + }); + } else { + return current; + } + } + + void insert_or_assign(env_t &target, const char *key, const char *value) noexcept { + if (auto it = target.find(key); it != target.end()) { + it->second = std::string(value); + } else { + target.emplace(key, std::string(value)); + } + } + + void insert_or_merge(env_t &target, const char *key, const char *value, const mapper_t &merger) noexcept { + if (auto it = target.find(key); it != target.end()) { + it->second = merger(it->second, std::string(value)); + } else { + target.emplace(key, std::string(value)); + } + } + +} + +namespace pear { + + Environment::Environment(const std::map &environ) noexcept + : data_(to_c_array(environ)) + { } + + Environment::~Environment() noexcept { + for (char **it = data_; *it != nullptr; ++it) { + delete [] *it; + } + delete [] data_; + } + + const char **Environment::data() const noexcept { + return const_cast(data_); + } + + + Environment::Builder::Builder(const char **environment) noexcept + : environ_(to_map(environment)) + { } + + Environment::Builder & + Environment::Builder::add_reporter(const char *reporter) noexcept { + insert_or_assign(environ_, ::pear::env::reporter_key, reporter); + return *this; + } + + Environment::Builder & + Environment::Builder::add_destination(const char *destination) noexcept { + insert_or_assign(environ_, ::pear::env::destination_key, destination); + return *this; + } + + Environment::Builder & + Environment::Builder::add_verbose(bool verbose) noexcept { + if (verbose) { + insert_or_assign(environ_, ::pear::env::verbose_key, "1"); + } + return *this; + } + + Environment::Builder & + Environment::Builder::add_library(const char *library) noexcept { + insert_or_assign(environ_, pear::env::library_key, library); +#ifdef APPLE + insert_or_assign(environ_, osx_namespace_key, "1"); + const char *key = osx_preload_key; +#else + const char *key = glibc_preload_key; +#endif + insert_or_merge(environ_, key, library, merge_into_paths); + return *this; + } + + Environment::Builder & + Environment::Builder::add_cc_compiler(const char *compiler, const char *wrapper) noexcept { + insert_or_assign(environ_, cc_key, wrapper); + insert_or_assign(environ_, ::pear::env::cc_key, compiler); + return *this; + } + + Environment::Builder & + Environment::Builder::add_cxx_compiler(const char *compiler, const char *wrapper) noexcept { + insert_or_assign(environ_, cxx_key, wrapper); + insert_or_assign(environ_, ::pear::env::cxx_key, compiler); + return *this; + } + + EnvironmentPtr Environment::Builder::build() const noexcept { + return std::unique_ptr(new Environment(environ_)); + } + +} diff --git a/source/intercept_a/Environment.h b/source/intercept_a/Environment.h new file mode 100644 index 00000000..f21d8c03 --- /dev/null +++ b/source/intercept_a/Environment.h @@ -0,0 +1,89 @@ +#pragma once +/* Copyright (C) 2012-2017 by László Nagy + This file is part of Bear. + + Bear is a tool to generate compilation database for clang tooling. + + Bear is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bear is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include + +namespace pear { + + class Environment { + public: + class Builder; + + const char **data() const noexcept; + + public: + Environment() = delete; + + ~Environment() noexcept; + + Environment(Environment &&) noexcept = delete; + + Environment(Environment const &) = delete; + + Environment &operator=(Environment &&) noexcept = delete; + + Environment &operator=(Environment const &) = delete; + + private: + explicit Environment(const std::map &environ) noexcept; + + private: + char **const data_; + }; + + using EnvironmentPtr = std::unique_ptr; + + + class Environment::Builder { + public: + explicit Builder(const char **environment) noexcept; + + Builder &add_reporter(const char *reporter) noexcept; + + Builder &add_destination(const char *target) noexcept; + + Builder &add_verbose(bool verbose) noexcept; + + Builder &add_library(const char *library) noexcept; + + Builder &add_cc_compiler(const char *compiler, const char *wrapper) noexcept; + + Builder &add_cxx_compiler(const char *compiler, const char *wrapper) noexcept; + + EnvironmentPtr build() const noexcept; + + public: + Builder() noexcept = delete; + + ~Builder() noexcept = default; + + Builder(Builder &&) noexcept = delete; + + Builder(Builder const &) = delete; + + Builder &operator=(Builder &&) noexcept = delete; + + Builder &operator=(Builder const &) = delete; + + private: + std::map environ_; + }; +} diff --git a/source/intercept_a/Interface.h b/source/intercept_a/Interface.h new file mode 100644 index 00000000..c22b2f6d --- /dev/null +++ b/source/intercept_a/Interface.h @@ -0,0 +1,62 @@ +/* Copyright (C) 2012-2017 by László Nagy + This file is part of Bear. + + Bear is a tool to generate compilation database for clang tooling. + + Bear is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bear is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +namespace pear { + namespace flag { + + constexpr char help[] = "--help"; + constexpr char verbose[] = "--verbose"; + constexpr char destination[] = "--report-destination"; + constexpr char library[] = "--session-library"; + constexpr char wrapper_cc[] = "--session-cc-wrapper"; + constexpr char wrapper_cxx[] = "--session-c++-wrapper"; + constexpr char path[] = "--exec-path"; + constexpr char file[] = "--exec-file"; + constexpr char search_path[] = "--exec-search_path"; + constexpr char command[] = "--exec-command"; + + } + + namespace env { + + constexpr char reporter_key[] = "INTERCEPT_REPORT_COMMAND"; + constexpr char destination_key[] = "INTERCEPT_REPORT_DESTINATION"; + constexpr char verbose_key[] = "INTERCEPT_VERBOSE"; + constexpr char library_key[] = "INTERCEPT_SESSION_LIBRARY"; + constexpr char cc_key[] = "INTERCEPT_SESSION_CC"; + constexpr char cxx_key[] = "INTERCEPT_SESSION_CXX"; + + } + + struct Execution { + const char **command; + const char *path; + const char *file; + const char *search_path; + }; + + struct Context { + char const *reporter; + char const *destination; + bool verbose; + }; + +} \ No newline at end of file diff --git a/source/intercept_a/Reporter.cc b/source/intercept_a/Reporter.cc new file mode 100644 index 00000000..6722c1a1 --- /dev/null +++ b/source/intercept_a/Reporter.cc @@ -0,0 +1,238 @@ +/* Copyright (C) 2012-2017 by László Nagy + This file is part of Bear. + + Bear is a tool to generate compilation database for clang tooling. + + Bear is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bear is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "intercept_a/Reporter.h" +#include "intercept_a/Result.h" +#include "intercept_a/SystemCalls.h" + +#include +#include + +namespace { + + std::string to_json_string(const std::string &value) { + std::string result; + + char const *wsrc_it = value.c_str(); + char const *const wsrc_end = wsrc_it + value.length(); + + for (; wsrc_it != wsrc_end; ++wsrc_it) { + // Insert an escape character before control characters. + switch (*wsrc_it) { + case L'\b': + result += "\\b"; + break; + case L'\f': + result += "\\f"; + break; + case L'\n': + result += "\\n"; + break; + case L'\r': + result += "\\r"; + break; + case L'\t': + result += "\\t"; + break; + case L'"': + result += "\\\""; + break; + case L'\\': + result += "\\\\"; + break; + default: + result += char(*wsrc_it); + break; + } + } + return result; + } + + void json_string(std::ostream &os, const char *value) { + os << '"' << to_json_string(value) << '"'; + } + + void json_attribute(std::ostream &os, const char *key, const char *value) { + os << '"' << key << '"' << ':'; + json_string(os, value); + } + + void json_attribute(std::ostream &os, const char *key, const char **value) { + os << '"' << key << '"' << ':'; + os << '['; + for (const char **it = value; *it != nullptr; ++it) { + if (it != value) + os << ','; + json_string(os, *it); + } + os << ']'; + } + + void json_attribute(std::ostream &os, const char *key, const int value) { + os << '"' << key << '"' << ':'<< value; + } + + + class TimedEvent : public pear::Event { + private: + std::chrono::system_clock::time_point const when_; + + public: + TimedEvent() noexcept + : when_(std::chrono::system_clock::now()) + { } + + std::chrono::system_clock::time_point const &when() const noexcept { + return when_; + } + }; + + struct ProcessStartEvent : public TimedEvent { + pid_t child_; + pid_t supervisor_; + pid_t parent_; + std::string cwd_; + const char **cmd_; + + ProcessStartEvent(pid_t child, + pid_t supervisor, + pid_t parent, + std::string cwd, + const char **cmd) noexcept + : TimedEvent() + , child_(child) + , supervisor_(supervisor) + , parent_(parent) + , cwd_(std::move(cwd)) + , cmd_(cmd) + { } + + const char *name() const override { + return "process_start"; + } + + void to_json(std::ostream &os) const override { + os << '{'; + json_attribute(os, "pid", child_); + os << ','; + json_attribute(os, "ppid", supervisor_); + os << ','; + json_attribute(os, "pppid", parent_); + os << ','; + json_attribute(os, "cwd", cwd_.c_str()); + os << ','; + json_attribute(os, "cmd", cmd_); + os << '}'; + } + }; + + struct ProcessStopEvent : public TimedEvent { + pid_t child_; + pid_t supervisor_; + int exit_; + + ProcessStopEvent(pid_t child, + pid_t supervisor, + int exit) noexcept + : TimedEvent() + , child_(child) + , supervisor_(supervisor) + , exit_(exit) + { } + + const char *name() const override { + return "process_stop"; + } + + void to_json(std::ostream &os) const override { + os << '{'; + json_attribute(os, "pid", child_); + os << ','; + json_attribute(os, "ppid", supervisor_); + os << ','; + json_attribute(os, "exit", exit_); + os << '}'; + } + }; + + + class ReporterImpl : public pear::Reporter { + public: + explicit ReporterImpl(const char *target) noexcept; + + pear::Result send(const pear::EventPtr &event) noexcept override; + + private: + pear::Result> create_stream(const std::string &) const; + + std::string const target_; + }; + + ReporterImpl::ReporterImpl(const char *target) noexcept + : pear::Reporter() + , target_(target) + { } + + pear::Result ReporterImpl::send(const pear::EventPtr &event) noexcept { + return create_stream(event->name()) + .map([&event](auto stream) { + event->to_json(*stream); + return 0; + }); + } + + pear::Result> ReporterImpl::create_stream(const std::string &prefix) const { + return pear::SystemCalls::temp_file(target_.c_str(), ("." + prefix + ".json").c_str()); + } +} + + +namespace pear { + + Result Event::start(pid_t pid, const char **cmd) noexcept { + const Result current_pid = SystemCalls::get_pid(); + const Result parent_pid = SystemCalls::get_ppid(); + const Result working_dir = SystemCalls::get_cwd(); + return merge(current_pid, parent_pid, working_dir) + .map([&pid, &cmd](auto tuple) { + const auto& [ current, parent, cwd ] = tuple; + return EventPtr(new ProcessStartEvent(pid, current, parent, cwd, cmd)); + }); + }; + + Result Event::stop(pid_t pid, int exit) noexcept { + return SystemCalls::get_pid() + .map([&pid, &exit](auto current) { + return EventPtr(new ProcessStopEvent(pid, current, exit)); + }); + } + + Result Reporter::tempfile(char const *dir_name) noexcept { + ReporterPtr result = std::make_unique(dir_name); + return Ok(std::move(result)); + +// if (std::filesystem::is_directory(dir_name)) { +// ReporterPtr result = std::make_unique(dir_name); +// return Ok(std::move(result)); +// } else { +// const std::string message = std::string("Directory does not exists: ") + dir_name; +// return Err(std::runtime_error(message)); +// } + } +} diff --git a/source/intercept_a/Reporter.h b/source/intercept_a/Reporter.h new file mode 100644 index 00000000..db06955e --- /dev/null +++ b/source/intercept_a/Reporter.h @@ -0,0 +1,62 @@ +/* Copyright (C) 2012-2017 by László Nagy + This file is part of Bear. + + Bear is a tool to generate compilation database for clang tooling. + + Bear is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bear is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#include + +#include +#include +#include + +#include "intercept_a/Result.h" + + +namespace pear { + + class Event; + using EventPtr = std::shared_ptr; + + class Event { + public: + virtual ~Event() noexcept = default; + + virtual const char *name() const = 0; + + virtual void to_json(std::ostream &) const = 0; + + public: + static Result start(pid_t pid, const char **cmd) noexcept; + static Result stop(pid_t pid, int exit) noexcept; + }; + + + class Reporter; + using ReporterPtr = std::shared_ptr; + + class Reporter { + public: + virtual ~Reporter() noexcept = default; + + virtual Result send(const EventPtr &event) noexcept = 0; + + public: + static Result tempfile(char const *) noexcept; + }; +} diff --git a/source/intercept_a/Result.h b/source/intercept_a/Result.h new file mode 100644 index 00000000..2f299277 --- /dev/null +++ b/source/intercept_a/Result.h @@ -0,0 +1,186 @@ +/* Copyright (C) 2012-2017 by László Nagy + This file is part of Bear. + + Bear is a tool to generate compilation database for clang tooling. + + Bear is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bear is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace pear { + + namespace types { + + template + struct Ok { + explicit Ok(const T& value) + : value_(value) + { } + + explicit Ok(T&& value) noexcept + : value_(value) + { } + + T value_; + }; + + template + struct Err { + explicit Err(const E& value) + : value_(value) + { } + + explicit Err(E&& value) noexcept + : value_(value) + { } + + E value_; + }; + + } + + template::type> + types::Ok Ok(T&& val) { + return types::Ok(std::forward(val)); + } + + template::type> + types::Err Err(E&& val) { + return types::Err(std::forward(val)); + } + + + template + class Result { + public: + template + Result map(std::function const &f) const noexcept; + + template + Result bind(std::function(const T &)> const &f) const noexcept; + + const T &get_or_else(const T &value) const noexcept; + + Result const & handle_with(std::function const &f) const noexcept; + + public: + ~Result() noexcept = default; + + Result() = delete; + + Result(Result &&other) noexcept = default; + + Result(const Result &other) = delete; + + Result &operator=(Result &&other) noexcept = default; + + Result &operator=(const Result &other) = delete; + + Result(types::Ok&& ok) noexcept; // NOLINT + + Result(types::Err&& err) noexcept; // NOLINT + + private: + std::variant state_; + }; + + + template + template + Result Result::map(std::function const &f) const noexcept { + if (std::holds_alternative(state_)) + return Ok(f(std::get(state_))); + else + return Err(std::get(state_)); + } + + template + template + Result Result::bind(std::function(const T &)> const &f) const noexcept { + if (std::holds_alternative(state_)) + return f(std::get(state_)); + else + return Err(std::get(state_)); + } + + template + const T &Result::get_or_else(const T &value) const noexcept { + return (std::holds_alternative(state_)) + ? std::get(state_) + : value; + } + + template + Result const & Result::handle_with(std::function const &f) const noexcept { + if (auto error = std::get_if(&state_)) { + f(*error); + }; + return *this; + } + + template + Result::Result(types::Ok &&ok) noexcept + : state_(ok.value_) + { } + + template + Result::Result(types::Err &&err) noexcept + : state_(err.value_) + { } + + + template + Result> merge(const Result &t1, const Result &t2) { + return t1.template bind>([&t2](auto &t1_value) { + return t2.template map>([&t1_value](auto &t2_value) { + return std::make_tuple(t1_value, t2_value); + }); + }); + } + + template + Result> merge(const Result &t1, const Result &t2, const Result &t3) { + return t1.template bind>([&t2, &t3](auto &t1_value) { + return t2.template bind>([&t1_value, &t3](auto &t2_value) { + return t3.template map>([&t1_value, &t2_value](auto &t3_value) { + return std::make_tuple(t1_value, t2_value, t3_value); + }); + }); + }); + } + + template + pear::Result Err(const char *message) noexcept { + std::string result = message != nullptr ? std::string(message) : std::string(); + + const size_t buffer_length = 1024 + std::strlen(message); + char buffer[buffer_length]; + if (0 == strerror_r(errno, buffer, buffer_length)) { + result += std::string(": "); + result += std::string(buffer); + } else { + result += std::string(": unkown error."); + } + errno = ENOENT; + return ::pear::Err(std::runtime_error(result)); + }; + +} diff --git a/source/intercept_a/Session.cc b/source/intercept_a/Session.cc new file mode 100644 index 00000000..982af06f --- /dev/null +++ b/source/intercept_a/Session.cc @@ -0,0 +1,229 @@ +/* Copyright (C) 2012-2017 by László Nagy + This file is part of Bear. + + Bear is a tool to generate compilation database for clang tooling. + + Bear is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bear is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "intercept_a/Session.h" +#include "intercept_a/Interface.h" +#include "intercept_a/Result.h" + +#include +#include +#include +#include +#include +#include + +namespace { + + using Parameter = std::tuple; + using Parameters = std::map; + + constexpr char program_key[] = "program"; + + + struct Option { + const char *flag; + int arguments; + const char *help; + + bool match(const char *input) const noexcept { + return (std::strcmp(input, flag) == 0); + } + + std::optional + take(const char **const begin, const char **const end) const noexcept { + return (arguments < 0) + ? std::optional(std::make_tuple(begin, end)) + : (begin + arguments > end) + ? std::optional() + : std::optional(std::make_tuple(begin, begin + arguments)); + } + + std::string format_option_line() const noexcept { + const size_t flag_size = std::strlen(flag); + + std::string result; + result += spaces(2); + result += flag; + result += (flag_size > 22) + ? "\n" + spaces(15) + : spaces(23 - flag_size); + result += std::string(help) + "\n"; + return result; + } + + static std::string spaces(size_t num) noexcept { + std::string result; + for (; num > 0; --num) + result += ' '; + return result; + } + }; + + + class Parser { + public: + Parser(std::initializer_list