diff --git a/.ci/changed-images b/.ci/changed-images new file mode 100755 index 0000000..8370e46 --- /dev/null +++ b/.ci/changed-images @@ -0,0 +1,39 @@ +#!/bin/bash +set -euo pipefail + +PATH=".ci:$PATH" + +syntax=plain +if [[ "${1:-}" = "--matrix" ]]; then + syntax=matrix +fi + +images=() +for image in * ;do + if [[ ! -d "${image}" ]]; then + continue + fi + if ! git-changed "$image" &> /dev/null; then + continue + fi + images+=("$image") +done + +if [[ "$syntax" = plain ]]; then + for image in "${images[@]}"; do + echo "$image" + done +elif [[ "$syntax" = matrix ]]; then + output='{"image":[' + first=true + for image in "${images[@]}"; do + if [[ "$first" = true ]]; then + first=false + else + output="$output," + fi + output="$output"'"'"$image"'"' + done + output="$output]}" + echo -n "$output" +fi diff --git a/.ci/git-changed b/.ci/git-changed new file mode 100755 index 0000000..b5a30af --- /dev/null +++ b/.ci/git-changed @@ -0,0 +1,52 @@ +#!/bin/bash +set -euo pipefail + +path="${1:-$(pwd)}" +old_ref="origin/master" +new_ref="HEAD" + +if [[ "$(git rev-parse "$old_ref")" = "$(git rev-parse "$new_ref")" ]]; then + old_ref="HEAD~1" +fi + +excluded_patterns=( + '.*/README.md$' +) + +is_excluded() { + local path="$1" + for excl in "${excluded_patterns[@]}"; do + if [[ "${path}" =~ ${excl} ]]; then + return 1 + fi + done + return 0 +} + +filter_excluded() { + while read line; do + if ! is_excluded "$line"; then + echo "$line" + fi + done +} + +git_changed_files() { + local old="$1" + local new="$2" + local path="$3" + git diff --name-only "$old..$new" -- "$path" +} + +mapfile -t changed_files < <(git_changed_files "$old_ref" "$new_ref" "$path" | filter_excluded) + +if [[ ${#changed_files[@]} == 0 ]]; then + echo "Nothing changed." + exit 1 +else + echo "Changes:" + for file in "${changed_files[@]}"; do + echo " $file" + done + exit 0 +fi diff --git a/.ci/tags-to-json b/.ci/tags-to-json new file mode 100755 index 0000000..0cb80d3 --- /dev/null +++ b/.ci/tags-to-json @@ -0,0 +1,23 @@ +#!/bin/bash +set -euo pipefail + +path="$1" +tags_path="$path/tags" + +if [[ ! -f "$tags_path" ]]; then + echo "$tags_path does not exist" + exit 1 +fi + +output='"' +first=true +for tag in $(<"$tags_path"); do + if [[ "$first" = true ]]; then + first=false + output="$output$tag" + else + output="$output\n$tag" + fi +done +output="$output"'"' +echo -n "$output" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43f9768..84299ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,71 @@ on: - staging pull_request: {} jobs: + set-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v2 + with: + # Fetch everything to be able to find changed images + fetch-depth: 0 + - run: '.ci/changed-images' + - id: set-matrix + run: 'echo "::set-output name=matrix::$(.ci/changed-images --matrix)"' build: + if: github.ref != 'refs/heads/master' + needs: set-matrix + runs-on: ubuntu-latest + strategy: + matrix: ${{fromJson(needs.set-matrix.outputs.matrix)}} + fail-fast: false + steps: + - uses: actions/checkout@v2 + - id: set-tags + name: Set tags + run: echo $(<${{ matrix.image }}/tags) | sed -e 's~[[:space:]][[:space:]]*~%0A~g' + - uses: docker/setup-buildx-action@v1 + id: buildx + with: + version: latest + install: true + id: docker_build + - uses: docker/build-push-action@v2 + id: docker_build + with: + push: false + tags: ${{ steps.set-tags.outputs.tags }} + context: ${{ matrix.image }} + file: ${{ matrix.image }}/Dockerfile + - name: Image digest + run: echo ${{ steps.docker_build.outputs.digest }} + build-and-push: + if: github.ref == 'refs/heads/master' + needs: set-matrix runs-on: ubuntu-latest + strategy: + matrix: ${{fromJson(needs.set-matrix.outputs.matrix)}} + fail-fast: false steps: - - run: echo "hello world" + - uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: actions/checkout@v2 + - id: set-tags + run: 'echo "::set-output name=tags::$(.ci/tags-to-json "${{ matrix.image }}")"' + - uses: docker/setup-buildx-action@v1 + id: buildx + with: + version: latest + install: true + id: docker_build + - uses: docker/build-push-action@v2 + id: docker_build + with: + push: true + tags: ${{ steps.set-tags.outputs.tags }} + context: ${{ matrix.image }} + - name: Image digest + run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/clang-format/.dockerignore b/clang-format/.dockerignore new file mode 100644 index 0000000..484eb50 --- /dev/null +++ b/clang-format/.dockerignore @@ -0,0 +1,2 @@ +# Ignore everything +** diff --git a/clang-format/Dockerfile b/clang-format/Dockerfile new file mode 100644 index 0000000..52d3ade --- /dev/null +++ b/clang-format/Dockerfile @@ -0,0 +1,60 @@ +# +# Dockerfile for clang-format +# +# It uses a multi-stage build process. The first stage clones the llvm-project +# repository and builds a statically linked clang-format. +# The second stage gets the clang-format binary into a clean base image +# so we don't have all the overhead from build tools. +# +# - LLVM is built with GCC. Building it directly with LLVM on an Alpine system +# is a quite involved process. You don't really want to do it, but you can +# read more about it here: https://wiki.musl-libc.org/building-llvm.html +# +# - We tried to find a set of LLVM build options that avoid building unnecessary +# stuff. It can probably be optimized some more. See an introduction to CMake +# options for LLVM here: https://llvm.org/docs/CMake.html#options-and-variables +# +# This process was inspired by https://github.com/angular/clang-format +# + +FROM alpine:3.12 AS builder + +# number of parallel build jobs, it should usually be the number of CPUs, but +# that may overload the system significantly +ARG PARALLEL_JOBS=4 + +# llvm-project git tag or branch to clone +ARG LLVM_TAG=llvmorg-10.0.1 + +ENV LLVM_REPO=https://github.com/llvm/llvm-project.git + +RUN apk add --no-cache git cmake make gcc g++ binutils python3 + +RUN git clone --depth 1 --branch "${LLVM_TAG}" "${LLVM_REPO}" + +RUN set -ex ;\ + cd llvm-project ;\ + mkdir build ;\ + cd build ;\ + cmake \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DLLVM_TARGETS_TO_BUILD=X86 \ + -DLLVM_BUILD_STATIC=true \ + -DLLVM_ENABLE_ZLIB=NO \ + -DLLVM_ENABLE_FFI=NO \ + -DLLVM_BUILD_DOCS=NO \ + -DLLVM_BUILD_EXAMPLES=NO \ + -DLLVM_ENABLE_PROJECTS=clang \ + -G "Unix Makefiles" \ + ../llvm ; \ + make -j ${PARALLEL_JOBS} clang-format + +FROM alpine:3.12 +LABEL io.whalebrew.name clang-format +LABEL io.whalebrew.config.volumes '["$PWD:$PWD"]' +LABEL io.whalebrew.config.working_dir '$PWD' +COPY --from=builder /llvm-project/build/bin/clang-format /usr/local/bin/clang-format +RUN mkdir /work +WORKDIR /work +ENTRYPOINT [ "clang-format" ] +CMD [ "-h" ] diff --git a/clang-format/README.md b/clang-format/README.md new file mode 100644 index 0000000..09ac067 --- /dev/null +++ b/clang-format/README.md @@ -0,0 +1,62 @@ + +# docker-clang-format + +Docker image for [clang-format](https://clang.llvm.org/docs/ClangFormat.html). + +## Overview + +- [Usage](#usage) + * [With Docker](#with-docker) + * [With Whalebrew](#with-whalebrew) +- [Build](#build) + +## Usage + +### With Docker + +When running with Docker, you'll need to mount the current directory and change the user. Otherwise, re-formatted files will be owned by root: + +```shell +docker run \ + --rm \ + -u "$(id -u):$(id -g)" \ + -v "$(pwd):$(pwd)" \ + -w "$(pwd)" \ + hdivsecurity/clang-format:latest \ + +``` + +For example, tormat all `.c` and `.h` files in the current directory, recursively: + +```shell +docker run \ + --rm \ + -u "$(id -u):$(id -g)" \ + -v "$(pwd):$(pwd)" \ + -w "$(pwd)" \ + hdivsecurity/clang-format:latest \ + -i --style=file $(find . -name '*.c' -o -name '*.h') +``` + +### With Whalebrew + +This image supports [Whalebrew](https://github.com/whalebrew/whalebrew): + +``` +whalebrew install hdivsecurity/clang-format +clang-format -i --style=file $(find . -name '*.c' -o -name '*.h') +``` + +## Build + +The image build process takes two optional [build arguments](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg): + +* `PARALLEL_JOBS` (default: `4`): number of parallel jobs for the build, it should not be higher than the number of CPUs. Note that setting it to the number of CPUs may result in the build process hogging the system. +* `LLVM_TAG` (default: `llvmorg-10.0.1`): Git branch or tag from the [llvm-project](https://github.com/llvm/llvm-project) to use for the build. + +To build the image for clang-format 10 you would run: + +```shell +docker build --build-arg PARALLEL_JOBS=4 --build-arg LLVM_TAG=llvmorg-10.0.1 -t hdivsecurity/clang-format:10 . +``` + diff --git a/clang-format/tags b/clang-format/tags new file mode 100644 index 0000000..e25608b --- /dev/null +++ b/clang-format/tags @@ -0,0 +1,2 @@ +hdivsecurity/clang-format:latest +hdivsecurity/clang-format:10