Este repositorio proporciona un ejemplo sencillo de cómo configurar varios servicios de CI, así como de integrar herramientas de análisis en estos servicios. Estas herramientas deben usarse como parte de un proceso de desarrollo de software (SDP) integral y también pueden usarse como plantilla inicial para cualquier aplicación C o C++. Se utilizan las siguientes herramientas de CI, que brindan soporte de pruebas para Windows, Cygwin, Linux y macOS
Se realizan los siguientes controles:
Los siguientes proyectos del mundo real utilizan una variedad de estas técnicas como parte de su SDP:
Aunque este repositorio se puede ejecutar en la mayoría de los sistemas, las siguientes son las plataformas compatibles y sus dependencias:
sudo apt-get install git build-essential cmake
setup-x86_64.exe -q -P git,make,gcc-core,gcc-g++,cmake
Instale los siguientes paquetes:
Para compilar e instalar este ejemplo, utilice las siguientes instrucciones:
git clone https://github.com/ainfosec/ci_helloworld.git
mkdir ci_helloworld/build
cd ci_helloworld/build
cmake ..
make
make test
git clone https://github.com/ainfosec/ci_helloworld.git
mkdir ci_helloworld/build
cd ci_helloworld/build
cmake -G "NMake Makefiles" ..
nmake
nmake test
git clone https://github.com/ainfosec/ci_helloworld.git
mkdir ci_helloworld/build
cd ci_helloworld/build
cmake -G "Visual Studio 15 2017 Win64" ..
msbuild ci_helloworld.sln
ctest
git clone https://github.com/ainfosec/ci_helloworld.git
mkdir ci_helloworld/build
cd ci_helloworld/build
cmake ..
make
make test
A continuación se proporciona una descripción de todas las herramientas de análisis que se han integrado en los servicios de CI utilizados por este proyecto, incluida una explicación de cómo funciona.
El CI está configurado para comprobar si falta documentación utilizando doxygen. A diferencia de la mayoría de las herramientas de análisis utilizadas en este proyecto, no existe un objetivo de creación para doxygen y, en su lugar, se ejecuta utilizando doxygen manualmente con el siguiente script:
- doxygen .doxygen.txt
- |
if [[ -s doxygen_warnings.txt ]]; then
echo "You must fix doxygen before submitting a pull request"
echo ""
cat doxygen_warnings.txt
exit -1
fi
Este script ejecuta doxygen contra el código fuente y cualquier advertencia se coloca en un archivo llamado doxygen_warnings.txt
. Si este archivo está vacío, significa que se aprobó el análisis de doxygen y que todo el código está documentado en función de la configuración del archivo de configuración .doxygen.txt
. Si este archivo no está vacío, la prueba falla e imprime las advertencias generadas por doxygen.
git diff --check
proporciona una forma sencilla de detectar cuándo se han registrado errores de espacios en blanco en el repositorio, así como verificar cuándo faltan nuevas líneas al final del archivo o si contienen demasiadas. Puede encontrar más información sobre este cheque aquí. Esta verificación es extremadamente útil para los desarrolladores cuando los PR contienen modificaciones no relacionadas con sus cambios específicos.
- |
if [[ -n $(git diff --check HEAD^) ]]; then
echo "You must remove whitespace before submitting a pull request"
echo ""
git diff --check HEAD^
exit -1
fi
Esta verificación simplemente ejecuta git diff --check
, que devuelve un error si la verificación falla. Si esto ocurre, la diferencia se muestra para que la vea el usuario.
El formateo del código fuente es una excelente manera de mantener una apariencia coherente con el código. El problema con el formato fuente es que, a menos que todos lo estén usando, los PR de los desarrolladores contendrán modificaciones no relacionadas con sus cambios específicos o, peor aún, intentar corregir el formato fuente periódicamente destruirá el historial de git de su repositorio cada vez que formatee la fuente. Por lo tanto, si se va a utilizar el formato fuente, se debe verificar en cada diferencia de código para garantizar que el formato sea correcto.
Para este ejemplo, usamos Astyle, pero el formato Clang también funcionará. Para admitir Astyle de una manera sencilla, proporcionamos un destino make que permite al desarrollador formatear su código fuente simplemente ejecutando make format
. Para hacer esto, primero debemos obtener Astyle:
list ( APPEND ASTYLE_CMAKE_ARGS
"-DCMAKE_INSTALL_PREFIX= ${CMAKE_BINARY_DIR} "
)
ExternalProject_Add(
astyle
GIT_REPOSITORY https://github.com/Bareflank/astyle.git
GIT_TAG v1.2
GIT_SHALLOW 1
CMAKE_ARGS ${ASTYLE_CMAKE_ARGS}
PREFIX ${CMAKE_BINARY_DIR} /astyle/ prefix
TMP_DIR ${CMAKE_BINARY_DIR} /astyle/tmp
STAMP_DIR ${CMAKE_BINARY_DIR} /astyle/stamp
DOWNLOAD_DIR ${CMAKE_BINARY_DIR} /astyle/download
SOURCE_DIR ${CMAKE_BINARY_DIR} /astyle/src
BINARY_DIR ${CMAKE_BINARY_DIR} /astyle/ build
)
Esta lógica de cmake utiliza ExternalProject_Add para descargar Astyle automáticamente, compilarlo para su plataforma e instalarlo en su directorio de compilación para que pueda ser utilizado por nuestro destino de creación personalizado. Tenga en cuenta que utilizamos nuestra propia versión parcheada de Astyle que cambia el sistema de compilación del conjunto personalizado de Makefiles de Astyle a un sistema de compilación CMake para simplificar.
list ( APPEND ASTYLE_ARGS
--style=1tbs
--lineend=linux
-- suffix =none
--pad-oper
--unpad-paren
--break-closing-brackets
--align-pointer= name
--align-reference= name
--indent-preproc-define
--indent-switches
--indent-col1-comments
--keep-one-line-statements
--keep-one-line-blocks
--pad-header
--convert-tabs
--min-conditional-indent=0
--indent=spaces=4
--close-templates
--add-brackets
--break-after-logical
${CMAKE_SOURCE_DIR} / include /*.h
${CMAKE_SOURCE_DIR} /src/*.cpp
${CMAKE_SOURCE_DIR} / test /*.cpp
)
if ( NOT WIN32 STREQUAL "1" )
add_custom_target (
format
COMMAND ${CMAKE_SOURCE_DIR} /bin/astyle ${ASTYLE_ARGS}
COMMENT "running astyle"
)
else ()
add_custom_target (
format
COMMAND ${CMAKE_SOURCE_DIR} /bin/astyle.exe ${ASTYLE_ARGS}
COMMENT "running astyle"
)
endif ()
Para crear nuestro objetivo de creación de estilo personalizado, utilizamos el código CMake anterior. Esto dirige a CMake al binario astyle resultante según la plataforma y proporciona a astyle las opciones de formato y archivos fuente específicos de este proyecto.
- cmake -DENABLE_ASTYLE=ON -DCMAKE_CXX_COMPILER="g++-6" ..
- make
- make format
- |
if [[ -n $(git diff) ]]; then
echo "You must run make format before submitting a pull request"
echo ""
git diff
exit -1
fi
Finalmente, para verificar en cada PR que un cambio de código se adhiere a nuestra configuración de Astyle, agregamos el código anterior a nuestro script Travis CI. Esto crea nuestro destino make format
y lo ejecuta para formatear el código. Si make format
formatea el código, se creará una diferencia que git diff
se puede utilizar para detectar. Si no se crea ninguna diferencia, significa que toda la fuente se adhiere a nuestra configuración de Astyle y la prueba pasa.
Clang Tidy proporciona análisis estático. La compatibilidad con esta herramienta comienza agregando lo siguiente al CMakeLists.txt:
set (CMAKE_EXPORT_COMPILE_COMMANDS ON )
Esto le indica a CMake que registre todas las instrucciones de compilación utilizadas para compilar su proyecto, incluidas las banderas y definiciones. Clang Tidy utilizará esta información para analizar estáticamente su proyecto de la misma manera que fue compilado. La ventaja de este enfoque es una mejora significativa en la precisión. La principal desventaja de este enfoque es que Clang Tidy no es bueno para analizar estáticamente archivos que no aparecen en esta base de datos de compilación (como archivos de encabezado). Por este motivo, si se desea analizar un encabezado, éste debe estar incluido en un archivo fuente que esté incluido en la base de datos de compilación.
list ( APPEND RUN_CLANG_TIDY_BIN_ARGS
-clang-tidy-binary ${CLANG_TIDY_BIN}
-header- filter =.*
-checks=clan*,cert*,misc*,perf*,cppc*,read*,mode*,-cert-err58-cpp,-misc-noexcept-move-constructor
)
add_custom_target (
tidy
COMMAND ${RUN_CLANG_TIDY_BIN} ${RUN_CLANG_TIDY_BIN_ARGS}
COMMENT "running clang tidy"
)
Finalmente, Clang Tidy recibe su propio objetivo de creación para simplificar su uso. Aquí le indicamos al script run-clang-tidy-4.0.py
qué binario clang tidy usar, así como qué comprobaciones realizar y qué archivos de encabezado incluir, que son todos. Desactivamos la prueba -cert-err58-cpp porque activa el código de catch.hpp y -misc-noexcept-move-constructor porque todavía tiene errores en 4.0. Tenga en cuenta que elegimos una versión específica de Clang Tidy, lo cual es importante porque cada nueva versión de Clang Tidy corrige errores y agrega nuevas comprobaciones, lo que genera resultados diferentes según la versión que utilice.
- cmake -DENABLE_CLANG_TIDY=ON -DCMAKE_CXX_COMPILER="g++-6" ..
- make
- make tidy > output.txt
- |
if [[ -n $(grep "warning: " output.txt) ]] || [[ -n $(grep "error: " output.txt) ]]; then
echo "You must pass the clang tidy checks before submitting a pull request"
echo ""
grep --color -E '^|warning: |error: ' output.txt
exit -1;
else
echo -e " 33[1;32mxE2x9Cx93 passed: 33[0m $1";
fi
Desde Travis CI, habilitamos Clang Tidy y volcamos su salida a un archivo. Si este archivo contiene una "advertencia" o un "error", fallamos la prueba y enviamos los problemas informados por Clang Tidy al usuario para que los solucione. Esto garantiza que cada PR haya sido verificado estáticamente.
CppCheck es otra herramienta de análisis estático.
list ( APPEND CPPCHECK_CMAKE_ARGS
"-DCMAKE_INSTALL_PREFIX= ${CMAKE_BINARY_DIR} "
)
ExternalProject_Add(
cppcheck
GIT_REPOSITORY https://github.com/danmar/cppcheck.git
GIT_TAG 1.79
GIT_SHALLOW 1
CMAKE_ARGS ${CPPCHECK_CMAKE_ARGS}
PREFIX ${CMAKE_BINARY_DIR} /external/cppcheck/ prefix
TMP_DIR ${CMAKE_BINARY_DIR} /external/cppcheck/tmp
STAMP_DIR ${CMAKE_BINARY_DIR} /external/cppcheck/stamp
DOWNLOAD_DIR ${CMAKE_BINARY_DIR} /external/cppcheck/download
SOURCE_DIR ${CMAKE_BINARY_DIR} /external/cppcheck/src
BINARY_DIR ${CMAKE_BINARY_DIR} /external/cppcheck/ build
)
La versión de CppCheck proporcionada por Ubuntu 14.04 es antigua y no es compatible con C++ 11, por lo que tomamos una versión específica de CppCheck de GitHub, lo que permite que todos los usuarios del proyecto usen la misma versión.
list ( APPEND CPPCHECK_ARGS
--enable=warning,style,performance,portability,unusedFunction
--std=c++11
--verbose
--error-exitcode=1
-- language =c++
-DMAIN=main
-I ${CMAKE_SOURCE_DIR} / include
${CMAKE_SOURCE_DIR} / include /*.h
${CMAKE_SOURCE_DIR} /src/*.cpp
${CMAKE_SOURCE_DIR} / test /*.cpp
)
add_custom_target (
check
COMMAND ${CMAKE_BINARY_DIR} /bin/cppcheck ${CPPCHECK_ARGS}
COMMENT "running cppcheck"
)
Luego agregamos un objetivo personalizado para nuestra aplicación CppCheck recién creada, diciéndole a CppCheck que habilite todas sus comprobaciones (menos advertencias pedantes) y que verifique todos nuestros archivos fuente. Tenga en cuenta que CppCheck necesita saber que MAIN=main; de lo contrario, pensará que la función principal no se ejecuta y debemos indicarle a CppCheck que falle con un código de error distinto de 0 para que Travis CI informe una prueba fallida si alguno de los los controles fallan.
- cmake -DENABLE_CPPCHECK=ON -DCMAKE_CXX_COMPILER="g++-6" ..
- make
- make check
Ejecutar la prueba Travis CI es tan simple como activar CppCheck y ejecutar el objetivo de creación personalizado.
Coverity Scan es otra herramienta de análisis estático que es muy buena para encontrar problemas estructurales difíciles de encontrar en su código. Si tiene acceso a Coverity Scan, vale la pena agregarlo a su SDP.
- os : linux
env :
- TEST="Coverity Scan"
addons :
apt :
sources :
- ubuntu-toolchain-r-test
packages :
- gcc-6
- g++-6
coverity_scan :
project :
name : " ainfosec/ci_helloworld "
description : " A simple example of how to setup a complete CI environment for C and C++ "
notification_email : [email protected]
build_command_prepend : " cmake -DCMAKE_CXX_COMPILER=g++-6 .. "
build_command : " make "
branch_pattern : master
script :
- echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-
Coverity Scan también es muy sencillo de configurar. La prueba de Travis CI anterior es un corte y pegado de su sitio web después de registrar su proyecto. Todo lo que tenemos que hacer es compilar la fuente que le indica a Coverity Scan cómo se compila el código fuente. A partir de ahí, su sitio web proporcionará un medio para excluir directorios y ver problemas con el código. En nuestro ejemplo, hacemos un escaneo de cada cambio para dominar, ya que la cantidad de cambios para dominar es pequeña, pero en proyectos grandes con muchas fusiones por día, Coverity Scan sugiere usar una rama coverity_scan específica para los escaneos. Si se hace esto, se debe configurar un escaneo nocturno tomando la rama master y empujándola a la rama coverity_scan cada noche. De esta manera, los problemas con Coverity Scan se pueden identificar rápidamente.
Codecov es una herramienta de cobertura potente pero sencilla de configurar.
if (ENABLE_COVERAGE)
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -g " )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -O0" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fprofile-arcs" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -ftest-coverage" )
set ( CMAKE_EXE_LINKER_FLAGS " ${CMAKE_EXE_LINKER_FLAGS} --coverage" )
endif ()
Para configurar la cobertura, debemos habilitar la compatibilidad con GCOV en nuestro compilador (se supone que GCC o Clang). Una vez que este soporte esté habilitado, ejecutar make test
generará estadísticas de cobertura que Codecov puede analizar para brindarle un informe de qué código se ha probado o no.
- cmake -DENABLE_COVERAGE=ON -DCMAKE_CXX_COMPILER="g++-6" ..
- make
- make test
- cd ..
- bash <(curl -s https://codecov.io/bash)
La prueba de Travis CI es tan simple como compilar y ejecutar las pruebas unitarias y luego ejecutar el script bash de Codecov. Una vez hecho esto, los resultados se podrán ver en el sitio web de Codecov.
Los monos son otra herramienta de cobertura.
if (ENABLE_COVERAGE)
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -g " )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -O0" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fprofile-arcs" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -ftest-coverage" )
set ( CMAKE_EXE_LINKER_FLAGS " ${CMAKE_EXE_LINKER_FLAGS} --coverage" )
endif ()
Al igual que Codecov, GCOV debe estar habilitado.
- pip install --user git+git://github.com/eddyxu/cpp-coveralls.git
- cmake -DENABLE_COVERAGE=ON -DCMAKE_CXX_COMPILER="g++-6" ..
- make
- make test
- cd ..
- |
coveralls --build-root build --gcov-options '-lp'
-e build/external
-e build/include
-e build/CMakeFiles/3.8.0
-e build/CMakeFiles/feature_tests.c
-e build/CMakeFiles/feature_tests.cxx
A diferencia de Codecov, Coveralls es mucho más difícil de configurar. Codecov realiza un seguimiento de qué archivos están en su repositorio de git y solo genera informes para los archivos en el repositorio, mientras que Coveralls generará informes de cobertura para todos los archivos que ve, incluidos los archivos generados por CMake. Coveralls tampoco tiene un script bash simple para informar los datos de cobertura a su servidor, sino que requiere la instalación de una herramienta externa específica de C++ para recopilar datos GCOV. Por estos motivos, tenemos que instalar cpp-coveralls y luego decirle que excluya archivos/directorios específicos que se están recopilando y que no deberían.
Los Google Sanitizers son una herramienta de análisis dinámico que se incluye en GCC y Clang/LLVM.
if (ENABLE_ASAN)
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -g" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -O1" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fuse-ld=gold" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fsanitize=address" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fsanitize=leak" )
endif ()
if (ENABLE_USAN)
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fuse-ld=gold" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fsanitize=undefined" )
endif ()
if (ENABLE_TSAN)
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fuse-ld=gold" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fsanitize=thread" )
endif ()
Cada desinfectante debe ejecutarse de forma aislada y, por lo tanto, tenemos una prueba por grupo de desinfectante. Las banderas para cada conjunto se pueden encontrar en la página GitHub de Google, así como en la documentación de uso de Clang.
- cmake -DENABLE_ASAN= ON -DCMAKE_CXX_COMPILER= "g++-6" ..
- make
- make test
Para cada prueba, activamos la verificación específica y las pruebas unitarias, y si una verificación falla, la prueba unitaria saldrá con un código de salida distinto de 0, lo que provocará que Travis CI falle la prueba. Cabe señalar que cada nueva versión de GCC y Clang viene con un mejor soporte y, por lo tanto, al igual que algunas de las otras herramientas, debes ceñirte a una versión específica.
Valgrind es otra herramienta de análisis dinámico que proporciona detección de fugas.
set (MEMORYCHECK_COMMAND_OPTIONS " ${MEMORYCHECK_COMMAND_OPTIONS} --leak-check=full" )
set (MEMORYCHECK_COMMAND_OPTIONS " ${MEMORYCHECK_COMMAND_OPTIONS} --track-fds=yes" )
set (MEMORYCHECK_COMMAND_OPTIONS " ${MEMORYCHECK_COMMAND_OPTIONS} --trace-children=yes" )
set (MEMORYCHECK_COMMAND_OPTIONS " ${MEMORYCHECK_COMMAND_OPTIONS} --error-exitcode=1" )
La forma más sencilla de ejecutar Valgrind es utilizar el soporte integrado de CMake, ya que manejará la lógica de errores por usted. Por esta razón, necesitamos decirle a CMake qué indicadores darle a Valgrind. En este caso, habilitamos todas sus comprobaciones y le decimos a Valgrind que salga con un código de salida distinto de 0, de modo que si una verificación falla, Travis CI no pasará la prueba.
- cmake -DCMAKE_CXX_COMPILER="g++-6" ..
- make
- ctest -T memcheck
Para ejecutar la prueba, todo lo que necesitamos hacer es compilar el código y ejecutar las pruebas unitarias usando ctest, habilitando el modo memcheck.
Este proyecto está bajo la licencia MIT.