diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..3e5ef7d --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,28 @@ +--- +Checks: '*' +WarningsAsErrors: '*' +HeaderFilterRegex: '\/src\/' +AnalyzeTemporaryDtors: false +CheckOptions: + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-use-nullptr.NullMacros + value: 'NULL' +... + diff --git a/.travis.yml b/.travis.yml index 56e0417..a3db340 100644 --- a/.travis.yml +++ b/.travis.yml @@ -159,6 +159,7 @@ script: make clean; make gyp; fi + - ./scripts/clang-tidy.sh after_script: - if [[ ${COVERAGE:-0} == 'True' ]]; then diff --git a/scripts/clang-tidy.sh b/scripts/clang-tidy.sh new file mode 100755 index 0000000..c5b08ea --- /dev/null +++ b/scripts/clang-tidy.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -eu +set -o pipefail + +# https://clang.llvm.org/extra/clang-tidy/ + +# to speed up re-runs, only re-create environment if needed +if [[ ! -f local.env ]]; then + # automatically setup environment + ./scripts/setup.sh --config local.env +fi + +# source the environment +source local.env + +PATH_TO_CLANG_TIDY_SCRIPT="$(pwd)/mason_packages/.link/share/run-clang-tidy.py" + +# to speed up re-runs, only install clang-tidy if needed +if [[ ! -f PATH_TO_CLANG_TIDY_SCRIPT ]]; then + # The MASON_LLVM_RELEASE variable comes from `local.env` + mason install clang-tidy ${MASON_LLVM_RELEASE} + # We link the tools to make it easy to know ${PATH_TO_CLANG_TIDY_SCRIPT} + mason link clang-tidy ${MASON_LLVM_RELEASE} +fi + +# build the compile_commands.json file if it does not exist +if [[ ! -f build/compile_commands.json ]]; then + # We need to clean otherwise when we make the project + # will will not see all the compile commands + make clean + # Create the build directory to put the compile_commands in + # We do this first to ensure it is there to start writing to + # immediately (make make not create it right away) + mkdir -p build + # Run make, pipe the output to the generate_compile_commands.py + # and drop them in a place that clang-tidy will automatically find them + make | scripts/generate_compile_commands.py > build/compile_commands.json +fi + +# change into the build directory so that clang-tidy can find the files +# at the right paths (since this is where the actual build happens) +cd build +${PATH_TO_CLANG_TIDY_SCRIPT} -fix + diff --git a/scripts/generate_compile_commands.py b/scripts/generate_compile_commands.py new file mode 100755 index 0000000..4e2a6c1 --- /dev/null +++ b/scripts/generate_compile_commands.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +import sys +import json +import os +import re + +# Script to generate compile_commands.json based on Makefile output +# Works by accepting Makefile output from stdin, parsing it, and +# turning into json records. These are then printed to stdout. +# More details on the compile_commands format at: +# https://clang.llvm.org/docs/JSONCompilationDatabase.html +# +# Note: make must be run in verbose mode, e.g. V=1 make or VERBOSE=1 make +# +# Usage with node-cpp-skel: +# +# make | ./scripts/generate_compile_commands.py > build/compile_commands.json + +# These work for node-cpp-skel to detect the files being compiled +# They may need to be modified if you adapt this to another tool +matcher = re.compile('^(.*) (.+cpp)') +build_dir = os.path.join(os.getcwd()) +TOKEN_DENOTING_COMPILED_FILE='c++' + +def generate(): + compile_commands = [] + for line in sys.stdin.readlines(): + if TOKEN_DENOTING_COMPILED_FILE in line: + match = matcher.match(line) + compile_commands.append({ + "directory": build_dir, + "command": line.strip(), + "file": os.path.normpath(os.path.join(build_dir,match.group(2))) + }) + print json.dumps(compile_commands,indent=4) + +if __name__ == '__main__': + generate() \ No newline at end of file diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100755 index 0000000..cb41656 --- /dev/null +++ b/scripts/setup.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash + +set -eu +set -o pipefail + +export MASON_RELEASE="${MASON_RELEASE:-v0.14.1}" +export MASON_LLVM_RELEASE="${MASON_LLVM_RELEASE:-4.0.1}" + +PLATFORM=$(uname | tr A-Z a-z) +if [[ ${PLATFORM} == 'darwin' ]]; then + PLATFORM="osx" +fi + +MASON_URL="https://s3.amazonaws.com/mason-binaries/${PLATFORM}-$(uname -m)" + +llvm_toolchain_dir="$(pwd)/.toolchain" + +function run() { + local config=${1} + # unbreak bash shell due to rvm bug on osx: https://github.com/direnv/direnv/issues/210#issuecomment-203383459 + # this impacts any usage of scripts that are source'd (like this one) + if [[ "${TRAVIS_OS_NAME:-}" == "osx" ]]; then + echo 'shell_session_update() { :; }' > ~/.direnvrc + fi + + # + # COMPILER TOOLCHAIN + # + + # We install clang++ without the mason client for a couple reasons: + # 1) decoupling makes it viable to use a custom branch of mason that might + # modify the upstream s3 bucket in a such a way that does not give + # it access to build tools like clang++ + # 2) Allows us to short-circuit and use a global clang++ install if it + # is available to save space for local builds. + GLOBAL_CLANG="${HOME}/.mason/mason_packages/${PLATFORM}-$(uname -m)/clang++/${MASON_LLVM_RELEASE}" + GLOBAL_LLVM="${HOME}/.mason/mason_packages/${PLATFORM}-$(uname -m)/llvm/${MASON_LLVM_RELEASE}" + if [[ -d ${GLOBAL_LLVM} ]]; then + echo "Detected '${GLOBAL_LLVM}/bin/clang++', using it" + local llvm_toolchain_dir=${GLOBAL_LLVM} + elif [[ -d ${GLOBAL_CLANG} ]]; then + echo "Detected '${GLOBAL_CLANG}/bin/clang++', using it" + local llvm_toolchain_dir=${GLOBAL_CLANG} + elif [[ -d ${GLOBAL_CLANG} ]]; then + echo "Detected '${GLOBAL_CLANG}/bin/clang++', using it" + local llvm_toolchain_dir=${GLOBAL_CLANG} + elif [[ ! -d ${llvm_toolchain_dir} ]]; then + BINARY="${MASON_URL}/clang++/${MASON_LLVM_RELEASE}.tar.gz" + echo "Downloading ${BINARY}" + mkdir -p ${llvm_toolchain_dir} + curl -sSfL ${BINARY} | tar --gunzip --extract --strip-components=1 --directory=${llvm_toolchain_dir} + fi + + # + # MASON + # + + function setup_mason() { + local install_dir=${1} + local mason_release=${2} + mkdir -p ${install_dir} + curl -sSfL https://github.com/mapbox/mason/archive/${mason_release}.tar.gz | tar --gunzip --extract --strip-components=1 --directory=${install_dir} + } + + setup_mason $(pwd)/.mason ${MASON_RELEASE} + + # + # ENV SETTINGS + # + + echo "export PATH=${llvm_toolchain_dir}/bin:$(pwd)/.mason:$(pwd)/mason_packages/.link/bin:"'${PATH}' > ${config} + echo "export CXX=${llvm_toolchain_dir}/bin/clang++" >> ${config} + echo "export MASON_RELEASE=${MASON_RELEASE}" >> ${config} + echo "export MASON_LLVM_RELEASE=${MASON_LLVM_RELEASE}" >> ${config} + # https://github.com/google/sanitizers/wiki/AddressSanitizerAsDso + RT_BASE=${llvm_toolchain_dir}/lib/clang/${MASON_LLVM_RELEASE}/lib/$(uname | tr A-Z a-z)/libclang_rt + if [[ $(uname -s) == 'Darwin' ]]; then + RT_PRELOAD=${RT_BASE}.asan_osx_dynamic.dylib + else + RT_PRELOAD=${RT_BASE}.asan-x86_64.so + fi + echo "export MASON_LLVM_RT_PRELOAD=${RT_PRELOAD}" >> ${config} + SUPPRESSION_FILE="/tmp/leak_suppressions.txt" + echo "leak:__strdup" > ${SUPPRESSION_FILE} + echo "leak:v8::internal" >> ${SUPPRESSION_FILE} + echo "leak:node::CreateEnvironment" >> ${SUPPRESSION_FILE} + echo "leak:node::Init" >> ${SUPPRESSION_FILE} + echo "export ASAN_SYMBOLIZER_PATH=${llvm_toolchain_dir}/bin/llvm-symbolizer" >> ${config} + echo "export MSAN_SYMBOLIZER_PATH=${llvm_toolchain_dir}/bin/llvm-symbolizer" >> ${config} + echo "export UBSAN_OPTIONS=print_stacktrace=1" >> ${config} + echo "export LSAN_OPTIONS=suppressions=${SUPPRESSION_FILE}" >> ${config} + echo "export ASAN_OPTIONS=symbolize=1:abort_on_error=1:detect_container_overflow=1:check_initialization_order=1:detect_stack_use_after_return=1" >> ${config} + echo 'export MASON_SANITIZE="-fsanitize=address,undefined -fno-sanitize=vptr,function"' >> ${config} + echo 'export MASON_SANITIZE_CXXFLAGS="${MASON_SANITIZE} -fno-sanitize=vptr,function -fsanitize-address-use-after-scope -fno-omit-frame-pointer -fno-common"' >> ${config} + echo 'export MASON_SANITIZE_LDFLAGS="${MASON_SANITIZE}"' >> ${config} + + exit 0 +} + +function usage() { + >&2 echo "Usage" + >&2 echo "" + >&2 echo "$ ./scripts/setup.sh --config local.env" + >&2 echo "$ source local.env" + >&2 echo "" + exit 1 +} + +if [[ ! ${1:-} ]]; then + usage +fi + +# https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash +for i in "$@" +do +case $i in + --config) + if [[ ! ${2:-} ]]; then + usage + fi + shift + run $@ + ;; + -h | --help) + usage + shift + ;; + *) + usage + ;; +esac +done