Skip to content

Commit 1622f8d

Browse files
committed
add docker based clang tools
Also try to find homebrew clang tools or clang-tidy/format with -17 as a suffix.
1 parent 9d7b988 commit 1622f8d

11 files changed

Lines changed: 397 additions & 23 deletions

File tree

appsec/cmake/clang-format.cmake

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,32 @@
1-
find_program(CLANG_FORMAT clang-format)
2-
if(CLANG_FORMAT STREQUAL CLANG_FORMAT-NOTFOUND)
3-
message(STATUS "Cannot find clang-format, either set CLANG_FORMAT or make it discoverable")
4-
return()
1+
set(_LLVM17_FORMAT /opt/homebrew/opt/llvm@17/bin/clang-format)
2+
if(EXISTS ${_LLVM17_FORMAT})
3+
set(CLANG_FORMAT ${_LLVM17_FORMAT})
4+
message(STATUS "Using Homebrew LLVM 17 clang-format: ${CLANG_FORMAT}")
5+
else()
6+
find_program(_CF_VERSIONED clang-format-17)
7+
if(NOT _CF_VERSIONED STREQUAL _CF_VERSIONED-NOTFOUND)
8+
set(CLANG_FORMAT ${_CF_VERSIONED})
9+
else()
10+
find_program(_CF_UNVERSIONED clang-format)
11+
if(NOT _CF_UNVERSIONED STREQUAL _CF_UNVERSIONED-NOTFOUND)
12+
execute_process(
13+
COMMAND ${_CF_UNVERSIONED} --version
14+
OUTPUT_VARIABLE _CF_VERSION
15+
OUTPUT_STRIP_TRAILING_WHITESPACE
16+
ERROR_QUIET)
17+
if(_CF_VERSION MATCHES " 17\\.")
18+
set(CLANG_FORMAT ${_CF_UNVERSIONED})
19+
endif()
20+
endif()
21+
endif()
22+
if(NOT CLANG_FORMAT)
23+
set(CLANG_FORMAT ${CMAKE_CURRENT_LIST_DIR}/clang-tools/clang-format)
24+
if(NOT EXISTS ${CLANG_FORMAT})
25+
message(STATUS "Cannot find clang-format version 17, either set CLANG_FORMAT or make it discoverable")
26+
return()
27+
endif()
28+
message(STATUS "Using Docker-based clang-format wrapper: ${CLANG_FORMAT}")
29+
endif()
530
endif()
631

732
set(FILE_LIST "")

appsec/cmake/clang-tidy.cmake

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,47 @@
1-
find_program(CLANG_TIDY run-clang-tidy)
2-
if(CLANG_TIDY STREQUAL CLANG_TIDY-NOTFOUND)
3-
message(STATUS "Cannot find clang-tidy, either set CLANG_TIDY or make it discoverable")
4-
return()
1+
# Prefer a locally installed LLVM 17 run-clang-tidy (e.g. via brew install llvm@17)
2+
# over the Docker-based wrapper, since native execution avoids SDK incompatibilities.
3+
set(_LLVM17_BIN /opt/homebrew/opt/llvm@17/bin)
4+
set(_LLVM17_TIDY ${_LLVM17_BIN}/run-clang-tidy)
5+
set(CLANG_TIDY_BINARY_OPT "")
6+
if(EXISTS ${_LLVM17_TIDY})
7+
set(CLANG_TIDY ${_LLVM17_TIDY})
8+
set(CLANG_TIDY_BINARY_OPT -clang-tidy-binary ${_LLVM17_BIN}/clang-tidy)
9+
message(STATUS "Using Homebrew LLVM 17 run-clang-tidy: ${CLANG_TIDY}")
10+
else()
11+
find_program(_RCT_VERSIONED run-clang-tidy-17)
12+
if(NOT _RCT_VERSIONED STREQUAL _RCT_VERSIONED-NOTFOUND)
13+
set(CLANG_TIDY ${_RCT_VERSIONED})
14+
find_program(_CT_VERSIONED clang-tidy-17)
15+
if(NOT _CT_VERSIONED STREQUAL _CT_VERSIONED-NOTFOUND)
16+
set(CLANG_TIDY_BINARY_OPT -clang-tidy-binary ${_CT_VERSIONED})
17+
endif()
18+
else()
19+
find_program(_RCT_UNVERSIONED run-clang-tidy)
20+
if(NOT _RCT_UNVERSIONED STREQUAL _RCT_UNVERSIONED-NOTFOUND)
21+
# Verify version via co-located clang-tidy
22+
get_filename_component(_RCT_DIR ${_RCT_UNVERSIONED} DIRECTORY)
23+
find_program(_CT_COLOCATED clang-tidy HINTS ${_RCT_DIR} NO_DEFAULT_PATH)
24+
if(NOT _CT_COLOCATED STREQUAL _CT_COLOCATED-NOTFOUND)
25+
execute_process(
26+
COMMAND ${_CT_COLOCATED} --version
27+
OUTPUT_VARIABLE _CT_VERSION
28+
OUTPUT_STRIP_TRAILING_WHITESPACE
29+
ERROR_QUIET)
30+
if(_CT_VERSION MATCHES " 17\\.")
31+
set(CLANG_TIDY ${_RCT_UNVERSIONED})
32+
set(CLANG_TIDY_BINARY_OPT -clang-tidy-binary ${_CT_COLOCATED})
33+
endif()
34+
endif()
35+
endif()
36+
endif()
37+
if(NOT CLANG_TIDY)
38+
set(CLANG_TIDY ${CMAKE_CURRENT_LIST_DIR}/clang-tools/run-clang-tidy)
39+
if(NOT EXISTS ${CLANG_TIDY})
40+
message(STATUS "Cannot find clang-tidy version 17, either set CLANG_TIDY or make it discoverable")
41+
return()
42+
endif()
43+
message(STATUS "Using Docker-based run-clang-tidy wrapper: ${CLANG_TIDY}")
44+
endif()
545
endif()
646

747
set(FILE_LIST "")
@@ -20,27 +60,20 @@ if(DD_APPSEC_BUILD_EXTENSION)
2060
append_target_sources(extension)
2161
endif()
2262

23-
execute_process (
24-
COMMAND bash -c "${CLANG_TIDY} --help | grep -qs 'use-color'"
25-
RESULT_VARIABLE USE_COLOR
26-
)
27-
28-
set(COLOR_OPT "")
29-
if (USE_COLOR EQUAL 0)
30-
set(COLOR_OPT -use-color)
31-
endif()
32-
3363
set(TIDY_DEPS "")
3464
if(DD_APPSEC_BUILD_EXTENSION AND TARGET libxml2_build)
3565
list(APPEND TIDY_DEPS libxml2_build)
3666
endif()
67+
if(TARGET boost_build)
68+
list(APPEND TIDY_DEPS boost_build)
69+
endif()
3770

3871
add_custom_target(tidy
39-
COMMAND ${CLANG_TIDY} ${COLOR_OPT} -p ${CMAKE_BINARY_DIR} ${FILE_LIST}
72+
COMMAND ${CLANG_TIDY} ${CLANG_TIDY_BINARY_OPT} -use-color -p ${CMAKE_BINARY_DIR} ${FILE_LIST}
4073
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
4174
DEPENDS ${TIDY_DEPS})
4275

4376
add_custom_target(tidy_fix
44-
COMMAND ${CLANG_TIDY} ${COLOR_OPT} -fix -p ${CMAKE_BINARY_DIR} ${FILE_LIST}
77+
COMMAND ${CLANG_TIDY} ${CLANG_TIDY_BINARY_OPT} -use-color -fix -p ${CMAKE_BINARY_DIR} ${FILE_LIST}
4578
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
4679
DEPENDS ${TIDY_DEPS})
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Minimal Docker image with clang-format and clang-tidy built from LLVM source
2+
# Uses static linking for smallest possible image size
3+
# Based on Alpine Linux 3.21
4+
5+
FROM alpine:3.21 AS builder
6+
7+
RUN apk add --no-cache \
8+
build-base \
9+
cmake \
10+
ninja \
11+
python3 \
12+
git \
13+
linux-headers \
14+
wget \
15+
clang \
16+
clang-dev
17+
18+
# Download and extract LLVM source
19+
ARG LLVM_VERSION=17.0.6
20+
WORKDIR /src
21+
RUN wget -q https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/llvm-project-${LLVM_VERSION}.src.tar.xz && \
22+
tar -xf llvm-project-${LLVM_VERSION}.src.tar.xz && \
23+
mv llvm-project-${LLVM_VERSION}.src llvm-project && \
24+
rm llvm-project-${LLVM_VERSION}.src.tar.xz
25+
26+
# Configure LLVM build with minimal size optimizations
27+
WORKDIR /src/llvm-project/build
28+
RUN cmake -G Ninja ../llvm \
29+
-DCMAKE_BUILD_TYPE=MinSizeRel \
30+
-DCMAKE_INSTALL_PREFIX=/usr/local \
31+
-DCMAKE_C_COMPILER=clang \
32+
-DCMAKE_CXX_COMPILER=clang++ \
33+
-DCMAKE_CXX_STANDARD=17 \
34+
-DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" \
35+
-DLLVM_TARGETS_TO_BUILD="" \
36+
-DLLVM_INCLUDE_TESTS=OFF \
37+
-DLLVM_INCLUDE_EXAMPLES=OFF \
38+
-DLLVM_INCLUDE_BENCHMARKS=OFF \
39+
-DLLVM_INCLUDE_DOCS=OFF \
40+
-DLLVM_ENABLE_BINDINGS=OFF \
41+
-DLLVM_ENABLE_OCAMLDOC=OFF \
42+
-DLLVM_ENABLE_Z3_SOLVER=OFF \
43+
-DLLVM_ENABLE_LIBXML2=OFF \
44+
-DLLVM_ENABLE_ZLIB=OFF \
45+
-DLLVM_ENABLE_ZSTD=OFF \
46+
-DLLVM_ENABLE_TERMINFO=OFF \
47+
-DLLVM_BUILD_STATIC=ON \
48+
-DLLVM_LINK_LLVM_DYLIB=OFF \
49+
-DLLVM_BUILD_LLVM_DYLIB=OFF \
50+
-DBUILD_SHARED_LIBS=OFF \
51+
-DLLVM_STATIC_LINK_CXX_STDLIB=ON \
52+
-DCMAKE_EXE_LINKER_FLAGS="-static" \
53+
-DCLANG_ENABLE_STATIC_ANALYZER=OFF \
54+
-DCLANG_ENABLE_ARCMT=OFF \
55+
-DCLANG_BUILD_EXAMPLES=OFF
56+
57+
# Build only the required tools
58+
RUN ninja clang-format clang-tidy clang-apply-replacements
59+
60+
# Install binaries
61+
RUN ninja install-clang-format install-clang-tidy install-clang-apply-replacements install-clang-resource-headers
62+
63+
# Copy run-clang-tidy helper script
64+
RUN cp /src/llvm-project/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py /usr/local/bin/run-clang-tidy && \
65+
chmod +x /usr/local/bin/run-clang-tidy
66+
67+
# Strip binaries to reduce size
68+
RUN strip /usr/local/bin/clang-format \
69+
/usr/local/bin/clang-tidy \
70+
/usr/local/bin/clang-apply-replacements
71+
72+
# Final minimal runtime image
73+
FROM alpine:3.21
74+
75+
# Install only Python runtime for run-clang-tidy script
76+
RUN apk add --no-cache python3
77+
78+
# Copy static binaries from builder
79+
COPY --from=builder /usr/local/bin/clang-format /usr/local/bin/
80+
COPY --from=builder /usr/local/bin/clang-tidy /usr/local/bin/
81+
COPY --from=builder /usr/local/bin/clang-apply-replacements /usr/local/bin/
82+
COPY --from=builder /usr/local/bin/run-clang-tidy /usr/local/bin/
83+
84+
# Copy clang resource headers so clang-tidy uses its own headers
85+
COPY --from=builder /usr/local/lib/clang/ /usr/local/lib/clang/
86+
87+
# Verify installations
88+
RUN clang-format --version && \
89+
clang-tidy --version && \
90+
run-clang-tidy --help > /dev/null
91+
92+
WORKDIR /workspace
93+
94+
CMD ["/bin/sh"]

appsec/cmake/clang-tools/README.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Clang Tools Docker Wrappers
2+
3+
This directory contains transparent wrapper scripts for `clang-format`, `clang-tidy`, and `run-clang-tidy` that execute in Docker while providing seamless filesystem access.
4+
5+
## Overview
6+
7+
The wrapper scripts behave identically to native binaries but run in a minimal Alpine Linux container with LLVM 17.0.6 tools. This ensures consistent tooling across different development environments without requiring local installation.
8+
9+
## Building the Docker Image
10+
11+
```bash
12+
./build_local.sh
13+
```
14+
15+
This builds the `datadog/dd-appsec-php-ci:clang-tools` image (~108MB) with statically-linked binaries.
16+
17+
## Usage
18+
19+
### Adding to PATH
20+
21+
Add this directory to your PATH to use the wrappers system-wide:
22+
23+
```bash
24+
export PATH="/path/to/dd-trace-php/appsec/cmake/clang-tools:$PATH"
25+
```
26+
27+
### clang-format
28+
29+
```bash
30+
# Format code from stdin
31+
echo "int main(){return 0;}" | ./clang-format
32+
33+
# Format a file
34+
./clang-format myfile.cpp
35+
36+
# Format in-place
37+
./clang-format -i myfile.cpp
38+
39+
# Check formatting
40+
./clang-format --dry-run --Werror myfile.cpp
41+
```
42+
43+
### clang-tidy
44+
45+
```bash
46+
# Run clang-tidy on a file
47+
./clang-tidy myfile.cpp -- -Iinclude
48+
49+
# With compilation database
50+
./clang-tidy -p build myfile.cpp
51+
```
52+
53+
### run-clang-tidy
54+
55+
```bash
56+
# Run on all files in compilation database
57+
./run-clang-tidy -p build
58+
59+
# Apply fixes automatically
60+
./run-clang-tidy -p build -fix
61+
62+
# Run on specific files matching regex
63+
./run-clang-tidy -p build '.*\.cpp$'
64+
```
65+
66+
## How It Works
67+
68+
### Filesystem Sharing
69+
70+
- Mounts the **git repository root** at `/workspace` in the container
71+
- Preserves the current working directory relative to the git root
72+
- Automatically translates absolute paths to container paths
73+
- Runs with your user ID to maintain file ownership
74+
75+
### Path Translation
76+
77+
The wrappers intelligently handle paths:
78+
- **Relative paths**: Pass through unchanged
79+
- **Absolute paths within git repo**: Translated to `/workspace/...`
80+
- **Absolute paths outside git repo**: Converted to relative when possible
81+
- **Flags and options**: Pass through unchanged
82+
83+
### stdin/stdout Support
84+
85+
- Detects piped input and enables interactive mode (`-i` flag) when needed
86+
- Preserves stdin for formatting code from pipes
87+
- stdout and stderr pass through transparently
88+
89+
## Example Workflows
90+
91+
### IDE Integration
92+
93+
Configure your IDE to use these wrappers as the clang-format/clang-tidy executables. They'll work transparently with features like "format on save" or inline diagnostics.
94+
95+
### CI/CD
96+
97+
```bash
98+
# Check all C++ files are formatted
99+
find . -name "*.cpp" -o -name "*.h" | xargs ./clang-format --dry-run --Werror
100+
101+
# Run clang-tidy with fixes
102+
./run-clang-tidy -p build -fix -quiet
103+
```
104+
105+
### Git Pre-commit Hook
106+
107+
```bash
108+
#!/bin/bash
109+
# Format staged C++ files
110+
git diff --cached --name-only --diff-filter=ACM | \
111+
grep -E '\.(cpp|h)$' | \
112+
xargs ./clang-format -i
113+
```
114+
115+
## Technical Details
116+
117+
**Docker Image**: `datadog/dd-appsec-php-ci:clang-tools`
118+
- Base: Alpine Linux 3.21
119+
- LLVM Version: 17.0.6
120+
- Size: ~108MB (statically-linked binaries)
121+
- Tools: clang-format, clang-tidy, clang-apply-replacements, run-clang-tidy
122+
123+
**User Mapping**: Runs as `-u $(id -u):$(id -g)` to preserve file ownership
124+
125+
**Volume Mount**: `-v $GIT_ROOT:/workspace` for full repository access
126+
127+
## Troubleshooting
128+
129+
### "Docker image not found"
130+
131+
Build the image first:
132+
```bash
133+
./build_local.sh
134+
```
135+
136+
### "Permission denied"
137+
138+
Ensure scripts are executable:
139+
```bash
140+
chmod +x clang-format clang-tidy run-clang-tidy
141+
```
142+
143+
### Paths not resolving correctly
144+
145+
The scripts automatically find the git repository root. If you're outside a git repo, they fall back to `$PWD`.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
docker buildx build -t datadog/dd-appsec-php-ci:clang-tools .
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
3+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4+
source "$SCRIPT_DIR/common.sh"
5+
6+
if [ -t 0 ]; then
7+
docker run --rm \
8+
-v "$GIT_ROOT:/workspace" \
9+
-w "/workspace/$REL_WORK_DIR" \
10+
-u "$(id -u):$(id -g)" \
11+
"$DOCKER_IMAGE" \
12+
clang-format ${ARGS[@]+"${ARGS[@]}"}
13+
else
14+
docker run --rm -i \
15+
-v "$GIT_ROOT:/workspace" \
16+
-w "/workspace/$REL_WORK_DIR" \
17+
-u "$(id -u):$(id -g)" \
18+
"$DOCKER_IMAGE" \
19+
clang-format ${ARGS[@]+"${ARGS[@]}"}
20+
fi

0 commit comments

Comments
 (0)