2 # Copyright (c) the JPEG XL Project Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style
5 # license that can be found in the LICENSE file.
7 # Continuous integration helper module. This module is meant to be called from
8 # the .gitlab-ci.yml file during the continuous integration build, as well as
9 # from the command line for developers.
15 MYDIR
=$
(dirname $
(realpath
"$0"))
17 ### Environment parameters:
18 TEST_STACK_LIMIT
="${TEST_STACK_LIMIT:-256}"
19 CMAKE_BUILD_TYPE
=${CMAKE_BUILD_TYPE:-RelWithDebInfo}
20 CMAKE_PREFIX_PATH
=${CMAKE_PREFIX_PATH:-}
21 CMAKE_C_COMPILER_LAUNCHER
=${CMAKE_C_COMPILER_LAUNCHER:-}
22 CMAKE_CXX_COMPILER_LAUNCHER
=${CMAKE_CXX_COMPILER_LAUNCHER:-}
23 CMAKE_MAKE_PROGRAM
=${CMAKE_MAKE_PROGRAM:-}
24 SKIP_TEST
="${SKIP_TEST:-0}"
25 TEST_SELECTOR
="${TEST_SELECTOR:-}"
26 BUILD_TARGET
="${BUILD_TARGET:-}"
27 ENABLE_WASM_SIMD
="${ENABLE_WASM_SIMD:-0}"
28 if [[ -n "${BUILD_TARGET}" ]]; then
29 BUILD_DIR
="${BUILD_DIR:-${MYDIR}/build-${BUILD_TARGET%%-*}}"
31 BUILD_DIR
="${BUILD_DIR:-${MYDIR}/build}"
33 # Whether we should post a message in the MR when the build fails.
34 POST_MESSAGE_ON_ERROR
="${POST_MESSAGE_ON_ERROR:-1}"
36 # Set default compilers to clang if not already set
37 export CC
=${CC:-clang}
38 export CXX
=${CXX:-clang++}
40 # Time limit for the "fuzz" command in seconds (0 means no limit).
41 FUZZER_MAX_TIME
="${FUZZER_MAX_TIME:-0}"
46 if [[ "${BUILD_TARGET%%-*}" == "x86_64" ||
47 "${BUILD_TARGET%%-*}" == "i686" ]]; then
48 # Default to building all targets, even if compiler baseline is SSE4
49 HWY_BASELINE_TARGETS
=${HWY_BASELINE_TARGETS:-HWY_EMU128}
51 HWY_BASELINE_TARGETS
=${HWY_BASELINE_TARGETS:-}
54 # Convenience flag to pass both CMAKE_C_FLAGS and CMAKE_CXX_FLAGS
55 CMAKE_FLAGS
=${CMAKE_FLAGS:-}
56 CMAKE_C_FLAGS
="${CMAKE_C_FLAGS:-} ${CMAKE_FLAGS}"
57 CMAKE_CXX_FLAGS
="${CMAKE_CXX_FLAGS:-} ${CMAKE_FLAGS}"
59 CMAKE_CROSSCOMPILING_EMULATOR
=${CMAKE_CROSSCOMPILING_EMULATOR:-}
60 CMAKE_EXE_LINKER_FLAGS
=${CMAKE_EXE_LINKER_FLAGS:-}
61 CMAKE_FIND_ROOT_PATH
=${CMAKE_FIND_ROOT_PATH:-}
62 CMAKE_MODULE_LINKER_FLAGS
=${CMAKE_MODULE_LINKER_FLAGS:-}
63 CMAKE_SHARED_LINKER_FLAGS
=${CMAKE_SHARED_LINKER_FLAGS:-}
64 CMAKE_TOOLCHAIN_FILE
=${CMAKE_TOOLCHAIN_FILE:-}
66 if [[ "${ENABLE_WASM_SIMD}" -ne "0" ]]; then
67 CMAKE_CXX_FLAGS
="${CMAKE_CXX_FLAGS} -msimd128"
68 CMAKE_C_FLAGS
="${CMAKE_C_FLAGS} -msimd128"
69 CMAKE_EXE_LINKER_FLAGS
="${CMAKE_EXE_LINKER_FLAGS} -msimd128"
72 if [[ "${ENABLE_WASM_SIMD}" -eq "2" ]]; then
73 CMAKE_CXX_FLAGS
="${CMAKE_CXX_FLAGS} -DHWY_WANT_WASM2"
74 CMAKE_C_FLAGS
="${CMAKE_C_FLAGS} -DHWY_WANT_WASM2"
77 if [[ ! -z "${HWY_BASELINE_TARGETS}" ]]; then
78 CMAKE_CXX_FLAGS
="${CMAKE_CXX_FLAGS} -DHWY_BASELINE_TARGETS=${HWY_BASELINE_TARGETS}"
81 # Version inferred from the CI variables.
82 CI_COMMIT_SHA
=${CI_COMMIT_SHA:-${GITHUB_SHA:-}}
83 JPEGXL_VERSION
=${JPEGXL_VERSION:-${CI_COMMIT_SHA:0:8}}
85 # Benchmark parameters
86 STORE_IMAGES
=${STORE_IMAGES:-1}
87 BENCHMARK_CORPORA
="${MYDIR}/third_party/corpora"
89 # Local flags passed to sanitizers.
96 -fsanitize=float-cast-overflow
97 -fsanitize=float-divide-by-zero
98 -fsanitize=integer-divide-by-zero
100 -fsanitize=object-size
101 -fsanitize=pointer-overflow
103 -fsanitize=returns-nonnull-attribute
104 -fsanitize=shift-base
105 -fsanitize=shift-exponent
106 -fsanitize=unreachable
109 -fno-sanitize-recover=undefined
110 # Brunsli uses unaligned accesses to uint32_t, so alignment is just a warning.
111 -fsanitize-recover=alignment
113 # -fsanitize=function doesn't work on aarch64 and arm.
114 if [[ "${BUILD_TARGET%%-*}" != "aarch64" &&
115 "${BUILD_TARGET%%-*}" != "arm" ]]; then
120 if [[ "${BUILD_TARGET%%-*}" != "arm" ]]; then
122 -fsanitize=signed-integer-overflow
126 CLANG_TIDY_BIN
=$
(which clang-tidy-6.0 clang-tidy-7 clang-tidy-8 clang-tidy |
head -n 1)
127 # Default to "cat" if "colordiff" is not installed or if stdout is not a tty.
129 COLORDIFF_BIN
=$
(which colordiff
cat |
head -n 1)
133 FIND_BIN
=$
(which gfind
find |
head -n 1)
134 # "false" will disable wine64 when not installed. This won't allow
136 WINE_BIN
=$
(which wine64 false |
head -n 1)
138 CLANG_VERSION
="${CLANG_VERSION:-}"
139 # Detect the clang version suffix and store it in CLANG_VERSION. For example,
140 # "6.0" for clang 6 or "7" for clang 7.
141 detect_clang_version
() {
142 if [[ -n "${CLANG_VERSION}" ]]; then
145 local clang_version
=$
("${CC:-clang}" --version |
head -n1)
146 clang_version
=${clang_version#"Debian "}
147 clang_version
=${clang_version#"Ubuntu "}
149 case "${clang_version}" in
154 # Any other clang version uses just the major version number.
155 local suffix
="${clang_version#clang version }"
156 CLANG_VERSION
="${suffix%%.*}"
159 # We can't use asan or msan in the emcc case.
162 echo "Unknown clang version: ${clang_version}" >&2
167 # Temporary files cleanup hooks.
170 if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then
171 rm -fr "${CLEANUP_FILES[@]}"
178 # Always cleanup the CLEANUP_FILES.
181 # Post a message in the MR when requested with POST_MESSAGE_ON_ERROR but only
182 # if the run failed and we are not running from a MR pipeline.
183 if [[ ${retcode} -ne 0 && -n "${CI_BUILD_NAME:-}" &&
184 -n "${POST_MESSAGE_ON_ERROR}" && -z "${CI_MERGE_REQUEST_ID:-}" &&
185 "${CI_BUILD_REF_NAME}" = "master" ]]; then
186 load_mr_vars_from_commit
187 { set +xeu
; } 2>/dev
/null
188 local message
="**Run ${CI_BUILD_NAME} @ ${CI_COMMIT_SHORT_SHA} failed.**
190 Check the output of the job at ${CI_JOB_URL:-} to see if this was your problem.
191 If it was, please rollback this change or fix the problem ASAP, broken builds
192 slow down development. Check if the error already existed in the previous build
195 Pipeline: ${CI_PIPELINE_URL}
197 Previous build commit: ${CI_COMMIT_BEFORE_SHA}
199 cmd_post_mr_comment
"${message}"
203 trap 'retcode=$?; { set +x; } 2>/dev/null; on_exit ${retcode}' INT TERM EXIT
206 # These variables are populated when calling merge_request_commits().
208 # The current hash at the top of the current branch or merge request branch (if
209 # running from a merge request pipeline).
211 # The common ancestor between the current commit and the tracked branch, such
212 # as master. This includes a list
215 # Populate MR_HEAD_SHA and MR_ANCESTOR_SHA.
216 merge_request_commits
() {
217 { set +x
; } 2>/dev
/null
218 # GITHUB_SHA is the current reference being build in GitHub Actions.
219 if [[ -n "${GITHUB_SHA:-}" ]]; then
220 # GitHub normally does a checkout of a merge commit on a shallow repository
221 # by default. We want to get a bit more of the history to be able to diff
222 # changes on the Pull Request if needed. This fetches 10 more commits which
223 # should be enough given that PR normally should have 1 commit.
224 git
-C "${MYDIR}" fetch
-q origin
"${GITHUB_SHA}" --depth 10
225 MR_HEAD_SHA
="$(git rev-parse "FETCH_HEAD^
2" 2>/dev/null ||
226 echo "${GITHUB_SHA}")"
228 # CI_BUILD_REF is the reference currently being build in the CI workflow.
229 MR_HEAD_SHA
=$
(git
-C "${MYDIR}" rev-parse
-q "${CI_BUILD_REF:-HEAD}")
232 if [[ -n "${CI_MERGE_REQUEST_IID:-}" ]]; then
233 # Merge request pipeline in CI. In this case the upstream is called "origin"
234 # but it refers to the forked project that's the source of the merge
235 # request. We need to get the target of the merge request, for which we need
236 # to query that repository using our CI_JOB_TOKEN.
237 echo "machine gitlab.com login gitlab-ci-token password ${CI_JOB_TOKEN}" \
239 git
-C "${MYDIR}" fetch
"${CI_MERGE_REQUEST_PROJECT_URL}" \
240 "${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}"
241 MR_ANCESTOR_SHA
=$
(git
-C "${MYDIR}" rev-parse
-q FETCH_HEAD
)
242 elif [[ -n "${GITHUB_BASE_REF:-}" ]]; then
243 # Pull request workflow in GitHub Actions. GitHub checkout action uses
244 # "origin" as the remote for the git checkout.
245 git
-C "${MYDIR}" fetch
-q origin
"${GITHUB_BASE_REF}"
246 MR_ANCESTOR_SHA
=$
(git
-C "${MYDIR}" rev-parse
-q FETCH_HEAD
)
248 # We are in a local branch, not a merge request.
249 MR_ANCESTOR_SHA
=$
(git
-C "${MYDIR}" rev-parse
-q HEAD@
{upstream
} || true
)
252 if [[ -z "${MR_ANCESTOR_SHA}" ]]; then
253 echo "Warning, not tracking any branch, using the last commit in HEAD.">&2
254 # This prints the return value with just HEAD.
255 MR_ANCESTOR_SHA
=$
(git
-C "${MYDIR}" rev-parse
-q "${MR_HEAD_SHA}^")
257 # GitHub runs the pipeline on a merge commit, no need to look for the common
258 # ancestor in that case.
259 if [[ -z "${GITHUB_BASE_REF:-}" ]]; then
260 MR_ANCESTOR_SHA
=$
(git
-C "${MYDIR}" merge-base \
261 "${MR_ANCESTOR_SHA}" "${MR_HEAD_SHA}")
267 # Load the MR iid from the landed commit message when running not from a
268 # merge request workflow. This is useful to post back results at the merge
269 # request when running pipelines from master.
270 load_mr_vars_from_commit
() {
271 { set +x
; } 2>/dev
/null
272 if [[ -z "${CI_MERGE_REQUEST_IID:-}" ]]; then
273 local mr_iid
=$
(git rev-list
--format=%B
--max-count=1 HEAD |
274 grep -F "${CI_PROJECT_URL}" |
grep -F "/merge_requests" |
head -n 1)
275 # mr_iid contains a string like this if it matched:
276 # Part-of: <https://gitlab.com/wg1/jpeg-xlm/merge_requests/123456>
277 if [[ -n "${mr_iid}" ]]; then
278 mr_iid
=$
(echo "${mr_iid}" |
279 sed -E 's,^.*merge_requests/([0-9]+)>.*$,\1,')
280 CI_MERGE_REQUEST_IID
="${mr_iid}"
281 CI_MERGE_REQUEST_PROJECT_ID
=${CI_PROJECT_ID}
287 # Posts a comment to the current merge request.
288 cmd_post_mr_comment
() {
289 { set +x
; } 2>/dev
/null
291 if [[ -n "${BOT_TOKEN:-}" && -n "${CI_MERGE_REQUEST_IID:-}" ]]; then
292 local url
="${CI_API_V4_URL}/projects/${CI_MERGE_REQUEST_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes"
294 -H "PRIVATE-TOKEN: ${BOT_TOKEN}" \
295 --data-urlencode "body=${comment}" \
302 # Set up and export the environment variables needed by the child processes.
304 if [[ "${BUILD_TARGET}" == *mingw32
]]; then
305 # Wine needs to know the paths to the mingw dlls. These should be
307 WINEPATH
=$
("${CC:-clang}" -print-search-dirs --target="${BUILD_TARGET}" \
308 |
grep -F 'libraries: =' | cut
-f 2- -d '=' |
tr ':' ';')
309 # We also need our own libraries in the wine path.
310 local real_build_dir
=$
(realpath
"${BUILD_DIR}")
311 # Some library .dll dependencies are installed in /bin:
312 export WINEPATH
="${WINEPATH};${real_build_dir};${real_build_dir}/third_party/brotli;/usr/${BUILD_TARGET}/bin"
314 local prefix
="${BUILD_DIR}/wineprefix"
316 export WINEPREFIX
=$
(realpath
"${prefix}")
318 # Sanitizers need these variables to print and properly format the stack
320 LLVM_SYMBOLIZER
=$
("${CC:-clang}" -print-prog-name=llvm-symbolizer || true
)
321 if [[ -n "${LLVM_SYMBOLIZER}" ]]; then
322 export ASAN_SYMBOLIZER_PATH
="${LLVM_SYMBOLIZER}"
323 export MSAN_SYMBOLIZER_PATH
="${LLVM_SYMBOLIZER}"
324 export UBSAN_SYMBOLIZER_PATH
="${LLVM_SYMBOLIZER}"
331 if [[ "${STACK_SIZE:-0}" == 1 ]]; then
332 # Dump the stack size of each function in the .stack_sizes section for
334 CMAKE_C_FLAGS
+=" -fstack-size-section"
335 CMAKE_CXX_FLAGS
+=" -fstack-size-section"
339 -B"${BUILD_DIR}" -H"${MYDIR}"
340 -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}"
342 -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}"
343 -DCMAKE_C_FLAGS="${CMAKE_C_FLAGS}"
344 -DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}"
345 -DCMAKE_MODULE_LINKER_FLAGS="${CMAKE_MODULE_LINKER_FLAGS}"
346 -DCMAKE_SHARED_LINKER_FLAGS="${CMAKE_SHARED_LINKER_FLAGS}"
347 -DJPEGXL_VERSION="${JPEGXL_VERSION}"
348 -DSANITIZER="${SANITIZER}"
349 # These are not enabled by default in cmake.
350 -DJPEGXL_ENABLE_VIEWERS=ON
351 -DJPEGXL_ENABLE_PLUGINS=ON
352 -DJPEGXL_ENABLE_DEVTOOLS=ON
353 # We always use libfuzzer in the ci.sh wrapper.
354 -DJPEGXL_FUZZER_LINK_FLAGS="-fsanitize=fuzzer"
356 if [[ "${BUILD_TARGET}" != *mingw32
]]; then
358 -DJPEGXL_WARNINGS_AS_ERRORS=ON
361 if [[ -n "${BUILD_TARGET}" ]]; then
362 local system_name
="Linux"
363 if [[ "${BUILD_TARGET}" == *mingw32
]]; then
364 # When cross-compiling with mingw the target must be set to Windows and
365 # run programs with wine.
366 system_name
="Windows"
368 -DCMAKE_CROSSCOMPILING_EMULATOR="${WINE_BIN}"
369 # Normally CMake automatically defines MINGW=1 when building with the
370 # mingw compiler (x86_64-w64-mingw32-gcc) but we are normally compiling
375 # EMSCRIPTEN toolchain sets the right values itself
376 if [[ "${BUILD_TARGET}" != wasm
* ]]; then
377 # If set, BUILD_TARGET must be the target triplet such as
378 # x86_64-unknown-linux-gnu.
380 -DCMAKE_C_COMPILER_TARGET="${BUILD_TARGET}"
381 -DCMAKE_CXX_COMPILER_TARGET="${BUILD_TARGET}"
382 # Only the first element of the target triplet.
383 -DCMAKE_SYSTEM_PROCESSOR="${BUILD_TARGET%%-*}"
384 -DCMAKE_SYSTEM_NAME="${system_name}"
385 -DCMAKE_TOOLCHAIN_FILE="${CMAKE_TOOLCHAIN_FILE}"
389 # sjpeg confuses WASM SIMD with SSE.
390 -DSJPEG_ENABLE_SIMD=OFF
391 # Building shared libs is not very useful for WASM.
392 -DBUILD_SHARED_LIBS=OFF
396 # These are needed to make googletest work when cross-compiling.
397 -DCMAKE_CROSSCOMPILING=1
400 -DHAVE_GNU_POSIX_REGEX=0
401 -DHAVE_STEADY_CLOCK=0
402 -DHAVE_THREAD_SAFETY_ATTRIBUTES=0
404 if [[ -z "${CMAKE_FIND_ROOT_PATH}" ]]; then
405 # find_package() will look in this prefix for libraries.
406 CMAKE_FIND_ROOT_PATH
="/usr/${BUILD_TARGET}"
408 if [[ -z "${CMAKE_PREFIX_PATH}" ]]; then
409 CMAKE_PREFIX_PATH
="/usr/${BUILD_TARGET}"
411 # Use pkg-config for the target. If there's no pkg-config available for the
412 # target we can set the PKG_CONFIG_PATH to the appropriate path in most
413 # linux distributions.
414 local pkg_config
=$
(which "${BUILD_TARGET}-pkg-config" || true
)
415 if [[ -z "${pkg_config}" ]]; then
416 pkg_config
=$
(which pkg-config
)
417 export PKG_CONFIG_LIBDIR
="/usr/${BUILD_TARGET}/lib/pkgconfig"
419 if [[ -n "${pkg_config}" ]]; then
420 args
+=(-DPKG_CONFIG_EXECUTABLE="${pkg_config}")
423 if [[ -n "${CMAKE_CROSSCOMPILING_EMULATOR}" ]]; then
425 -DCMAKE_CROSSCOMPILING_EMULATOR="${CMAKE_CROSSCOMPILING_EMULATOR}"
428 if [[ -n "${CMAKE_FIND_ROOT_PATH}" ]]; then
430 -DCMAKE_FIND_ROOT_PATH="${CMAKE_FIND_ROOT_PATH}"
433 if [[ -n "${CMAKE_PREFIX_PATH}" ]]; then
435 -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}"
438 if [[ -n "${CMAKE_C_COMPILER_LAUNCHER}" ]]; then
440 -DCMAKE_C_COMPILER_LAUNCHER="${CMAKE_C_COMPILER_LAUNCHER}"
443 if [[ -n "${CMAKE_CXX_COMPILER_LAUNCHER}" ]]; then
445 -DCMAKE_CXX_COMPILER_LAUNCHER="${CMAKE_CXX_COMPILER_LAUNCHER}"
448 if [[ -n "${CMAKE_MAKE_PROGRAM}" ]]; then
450 -DCMAKE_MAKE_PROGRAM="${CMAKE_MAKE_PROGRAM}"
453 if [[ "${BUILD_TARGET}" == wasm
* ]]; then
454 emcmake cmake
"${args[@]}" "$@"
456 cmake
"${args[@]}" "$@"
460 cmake_build_and_test
() {
461 # gtest_discover_tests() runs the test binaries to discover the list of tests
462 # at build time, which fails under qemu.
463 ASAN_OPTIONS
=detect_leaks
=0 cmake
--build "${BUILD_DIR}" -- all doc
464 # Pack test binaries if requested.
465 if [[ "${PACK_TEST:-}" == "1" ]]; then
467 ${FIND_BIN} -name '*.cmake' -a '!' -path '*CMakeFiles*'
468 # gtest / gmock / gtest_main shared libs
469 ${FIND_BIN} lib
/ -name 'libg*.so*'
470 ${FIND_BIN} -type d
-name tests
-a '!' -path '*CMakeFiles*'
471 ) |
tar -C "${BUILD_DIR}" -cf "${BUILD_DIR}/tests.tar.xz" -T - \
472 --use-compress-program="xz --threads=$(nproc --all || echo 1) -6"
473 du
-h "${BUILD_DIR}/tests.tar.xz"
474 # Pack coverage data if also available.
475 touch "${BUILD_DIR}/gcno.sentinel"
476 (cd "${BUILD_DIR}"; echo gcno.sentinel
; ${FIND_BIN} -name '*gcno') | \
477 tar -C "${BUILD_DIR}" -cvf "${BUILD_DIR}/gcno.tar.xz" -T - \
478 --use-compress-program="xz --threads=$(nproc --all || echo 1) -6"
481 if [[ "${SKIP_TEST}" -ne "1" ]]; then
483 export UBSAN_OPTIONS
=print_stacktrace
=1
484 [[ "${TEST_STACK_LIMIT}" == "none" ]] ||
ulimit -s "${TEST_STACK_LIMIT}"
485 ctest
-j $
(nproc
--all ||
echo 1) ${TEST_SELECTOR} --output-on-failure)
489 # Configure the build to strip unused functions. This considerably reduces the
490 # output size, specially for tests which only use a small part of the whole
493 # Emscripten does tree shaking without any extra flags.
494 if [[ "${BUILD_TARGET}" == wasm
* ]]; then
497 # -ffunction-sections, -fdata-sections and -Wl,--gc-sections effectively
498 # discard all unreachable code, reducing the code size. For this to work, we
499 # need to also pass --no-export-dynamic to prevent it from exporting all the
500 # internal symbols (like functions) making them all reachable and thus not a
501 # candidate for removal.
502 CMAKE_CXX_FLAGS
+=" -ffunction-sections -fdata-sections"
503 CMAKE_C_FLAGS
+=" -ffunction-sections -fdata-sections"
504 if [[ "${OS}" == "Darwin" ]]; then
505 CMAKE_EXE_LINKER_FLAGS
+=" -dead_strip"
506 CMAKE_SHARED_LINKER_FLAGS
+=" -dead_strip"
508 CMAKE_EXE_LINKER_FLAGS
+=" -Wl,--gc-sections -Wl,--no-export-dynamic"
509 CMAKE_SHARED_LINKER_FLAGS
+=" -Wl,--gc-sections -Wl,--no-export-dynamic"
513 ### Externally visible commands
516 CMAKE_BUILD_TYPE
="Debug"
522 CMAKE_BUILD_TYPE
="Release"
529 CMAKE_BUILD_TYPE
="RelWithDebInfo"
530 CMAKE_CXX_FLAGS
+=" -DJXL_DEBUG_WARNING -DJXL_DEBUG_ON_ERROR"
536 # -O0 prohibits stack space reuse -> causes stack-overflow on dozens of tests.
537 TEST_STACK_LIMIT
="none"
539 cmd_release
-DJPEGXL_ENABLE_COVERAGE=ON
"$@"
541 if [[ "${SKIP_TEST}" -ne "1" ]]; then
542 # If we didn't run the test we also don't print a coverage report.
547 cmd_coverage_report
() {
548 LLVM_COV
=$
("${CC:-clang}" -print-prog-name=llvm-cov
)
549 local real_build_dir
=$
(realpath
"${BUILD_DIR}")
551 -r "${real_build_dir}"
552 --gcov-executable "${LLVM_COV} gcov"
553 # Only print coverage information for the libjxl directories. The rest
554 # is not part of the code under test.
556 --exclude '.*_gbench.cc'
557 --exclude '.*_test.cc'
558 --exclude '.*_testonly..*'
559 --exclude '.*_debug.*'
560 --exclude '.*test_utils..*'
561 --object-directory "${real_build_dir}"
565 cd "${real_build_dir}"
566 gcovr
"${gcovr_args[@]}" --html --html-details \
567 --output="${real_build_dir}/coverage.html"
568 gcovr
"${gcovr_args[@]}" --print-summary |
569 tee "${real_build_dir}/coverage.txt"
570 gcovr
"${gcovr_args[@]}" --xml --output="${real_build_dir}/coverage.xml"
576 # Unpack tests if needed.
577 if [[ -e "${BUILD_DIR}/tests.tar.xz" && ! -d "${BUILD_DIR}/tests" ]]; then
578 tar -C "${BUILD_DIR}" -Jxvf "${BUILD_DIR}/tests.tar.xz"
580 if [[ -e "${BUILD_DIR}/gcno.tar.xz" && ! -d "${BUILD_DIR}/gcno.sentinel" ]]; then
581 tar -C "${BUILD_DIR}" -Jxvf "${BUILD_DIR}/gcno.tar.xz"
584 export UBSAN_OPTIONS
=print_stacktrace
=1
585 [[ "${TEST_STACK_LIMIT}" == "none" ]] ||
ulimit -s "${TEST_STACK_LIMIT}"
586 ctest
-j $
(nproc
--all ||
echo 1) ${TEST_SELECTOR} --output-on-failure "$@")
592 export UBSAN_OPTIONS
=print_stacktrace
=1
594 --benchmark_counters_tabular=true \
595 --benchmark_out_format=json \
596 --benchmark_out=gbench.json
"$@"
601 CMAKE_CXX_FLAGS
+=" -fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1"
602 CMAKE_C_FLAGS
+=" -fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1"
603 cmd_asan
-DJPEGXL_ENABLE_FUZZERS=ON
"$@"
607 # Install msan if needed before changing the flags.
609 local msan_prefix
="${HOME}/.msan/${CLANG_VERSION}"
610 if [[ ! -d "${msan_prefix}" ||
-e "${msan_prefix}/lib/libc++abi.a" ]]; then
611 # Install msan libraries for this version if needed or if an older version
612 # with libc++abi was installed.
616 CMAKE_CXX_FLAGS
+=" -fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1"
617 CMAKE_C_FLAGS
+=" -fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1"
618 cmd_msan
-DJPEGXL_ENABLE_FUZZERS=ON
"$@"
623 CMAKE_C_FLAGS
+=" -DJXL_ENABLE_ASSERT=1 -g -DADDRESS_SANITIZER \
624 -fsanitize=address ${UBSAN_FLAGS[@]}"
625 CMAKE_CXX_FLAGS
+=" -DJXL_ENABLE_ASSERT=1 -g -DADDRESS_SANITIZER \
626 -fsanitize=address ${UBSAN_FLAGS[@]}"
628 cmake_configure
"$@" -DJPEGXL_ENABLE_TCMALLOC=OFF
635 -DJXL_ENABLE_ASSERT=1
641 CMAKE_C_FLAGS
+=" ${tsan_args[@]}"
642 CMAKE_CXX_FLAGS
+=" ${tsan_args[@]}"
644 CMAKE_BUILD_TYPE
="RelWithDebInfo"
645 cmake_configure
"$@" -DJPEGXL_ENABLE_TCMALLOC=OFF
652 local msan_prefix
="${HOME}/.msan/${CLANG_VERSION}"
653 if [[ ! -d "${msan_prefix}" ||
-e "${msan_prefix}/lib/libc++abi.a" ]]; then
654 # Install msan libraries for this version if needed or if an older version
655 # with libc++abi was installed.
661 -fno-omit-frame-pointer
662 -fsanitize-memory-track-origins
664 -DJXL_ENABLE_ASSERT=1
668 # Force gtest to not use the cxxbai.
669 -DGTEST_HAS_CXXABI_H_=0
671 local msan_cxx_flags
=(
674 # Some C++ sources don't use the std at all, so the -stdlib=libc++ is unused
675 # in those cases. Ignore the warning.
676 -Wno-unused-command-line-argument
679 # We include the libc++ from the msan directory instead, so we don't want
682 -cxx-isystem"${msan_prefix}/include/c++/v1"
685 local msan_linker_flags
=(
686 -L"${msan_prefix}"/lib
687 -Wl,-rpath -Wl,"${msan_prefix}"/lib
/
690 CMAKE_C_FLAGS
+=" ${msan_c_flags[@]} ${UBSAN_FLAGS[@]}"
691 CMAKE_CXX_FLAGS
+=" ${msan_cxx_flags[@]} ${UBSAN_FLAGS[@]}"
692 CMAKE_EXE_LINKER_FLAGS
+=" ${msan_linker_flags[@]}"
693 CMAKE_MODULE_LINKER_FLAGS
+=" ${msan_linker_flags[@]}"
694 CMAKE_SHARED_LINKER_FLAGS
+=" ${msan_linker_flags[@]}"
696 cmake_configure
"$@" \
697 -DCMAKE_CROSSCOMPILING=1 -DRUN_HAVE_STD_REGEX=0 -DRUN_HAVE_POSIX_REGEX=0 \
698 -DJPEGXL_ENABLE_TCMALLOC=OFF
-DJPEGXL_WARNINGS_AS_ERRORS=OFF \
699 -DCMAKE_REQUIRED_LINK_OPTIONS="${msan_linker_flags[@]}"
703 # Install libc++ libraries compiled with msan in the msan_prefix for the current
706 local tmpdir
=$
(mktemp
-d)
707 CLEANUP_FILES
+=("${tmpdir}")
708 # Detect the llvm to install:
709 export CC
="${CC:-clang}"
710 export CXX
="${CXX:-clang++}"
712 # Allow overriding the LLVM checkout.
713 local llvm_root
="${LLVM_ROOT:-}"
714 if [ -z "${llvm_root}" ]; then
715 local llvm_tag
="llvmorg-${CLANG_VERSION}.0.0"
716 case "${CLANG_VERSION}" in
718 llvm_tag
="llvmorg-6.0.1"
721 llvm_tag
="llvmorg-7.0.1"
724 local llvm_targz
="${tmpdir}/${llvm_tag}.tar.gz"
725 curl
-L --show-error -o "${llvm_targz}" \
726 "https://github.com/llvm/llvm-project/archive/${llvm_tag}.tar.gz"
727 tar -C "${tmpdir}" -zxf "${llvm_targz}"
728 llvm_root
="${tmpdir}/llvm-project-${llvm_tag}"
731 local msan_prefix
="${HOME}/.msan/${CLANG_VERSION}"
732 rm -rf "${msan_prefix}"
734 declare -A CMAKE_EXTRAS
735 CMAKE_EXTRAS
[libcxx
]="\
736 -DLIBCXX_CXX_ABI=libstdc++ \
737 -DLIBCXX_INSTALL_EXPERIMENTAL_LIBRARY=ON"
739 for project
in libcxx
; do
740 local proj_build
="${tmpdir}/build-${project}"
741 local proj_dir
="${llvm_root}/${project}"
742 mkdir
-p "${proj_build}"
743 cmake
-B"${proj_build}" -H"${proj_dir}" \
745 -DCMAKE_BUILD_TYPE=Release \
746 -DLLVM_USE_SANITIZER=Memory \
747 -DLLVM_PATH="${llvm_root}/llvm" \
748 -DLLVM_CONFIG_PATH="$(which llvm-config llvm-config-7 llvm-config-6.0 | \
750 -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}" \
751 -DCMAKE_C_FLAGS="${CMAKE_C_FLAGS}" \
752 -DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" \
753 -DCMAKE_SHARED_LINKER_FLAGS="${CMAKE_SHARED_LINKER_FLAGS}" \
754 -DCMAKE_INSTALL_PREFIX="${msan_prefix}" \
755 ${CMAKE_EXTRAS[${project}]}
756 cmake
--build "${proj_build}"
757 ninja
-C "${proj_build}" install
761 # Internal build step shared between all cmd_ossfuzz_* commands.
765 mkdir
-p "${BUILD_DIR}"
766 local real_build_dir
=$
(realpath
"${BUILD_DIR}")
768 # oss-fuzz defines three directories:
769 # * /work, with the working directory to do re-builds
770 # * /src, with the source code to build
771 # * /out, with the output directory where to copy over the built files.
772 # We use $BUILD_DIR as the /work and the script directory as the /src. The
773 # /out directory is ignored as developers are used to look for the fuzzers in
774 # $BUILD_DIR/tools/ directly.
776 if [[ "${sanitizer}" = "memory" && ! -d "${BUILD_DIR}/msan" ]]; then
777 sudo docker run
--rm -i \
778 --user $
(id
-u):$
(id
-g) \
779 -v "${real_build_dir}":/work \
780 gcr.io
/oss-fuzz-base
/msan-libs-builder \
781 bash
-c "cp -r /msan /work"
784 # Args passed to ninja. These will be evaluated as a string separated by
786 local jpegxl_extra_args
="$@"
788 sudo docker run
--rm -i \
789 -e JPEGXL_UID
=$
(id
-u) \
790 -e JPEGXL_GID
=$
(id
-g) \
791 -e FUZZING_ENGINE
="${FUZZING_ENGINE:-libfuzzer}" \
792 -e SANITIZER
="${sanitizer}" \
793 -e ARCHITECTURE
=x86_64 \
794 -e FUZZING_LANGUAGE
=c
++ \
795 -e MSAN_LIBS_PATH
="/work/msan" \
796 -e JPEGXL_EXTRA_ARGS
="${jpegxl_extra_args}" \
797 -v "${MYDIR}":/src
/libjxl \
798 -v "${MYDIR}/tools/ossfuzz-build.sh":/src
/build.sh \
799 -v "${real_build_dir}":/work \
800 gcr.io
/oss-fuzz
/libjxl
804 _cmd_ossfuzz address
"$@"
807 _cmd_ossfuzz memory
"$@"
809 cmd_ossfuzz_ubsan
() {
810 _cmd_ossfuzz undefined
"$@"
813 cmd_ossfuzz_ninja
() {
814 [[ -e "${BUILD_DIR}/build.ninja" ]]
815 local real_build_dir
=$
(realpath
"${BUILD_DIR}")
817 if [[ -e "${BUILD_DIR}/msan" ]]; then
818 echo "ossfuzz_ninja doesn't work with msan builds. Use ossfuzz_msan." >&2
822 sudo docker run
--rm -i \
823 --user $
(id
-u):$
(id
-g) \
824 -v "${MYDIR}":/src
/libjxl \
825 -v "${real_build_dir}":/work \
826 gcr.io
/oss-fuzz
/libjxl \
830 cmd_fast_benchmark
() {
831 local small_corpus_tar
="${BENCHMARK_CORPORA}/jyrki-full.tar"
832 mkdir
-p "${BENCHMARK_CORPORA}"
833 curl
--show-error -o "${small_corpus_tar}" -z "${small_corpus_tar}" \
834 "https://storage.googleapis.com/artifacts.jpegxl.appspot.com/corpora/jyrki-full.tar"
836 local tmpdir
=$
(mktemp
-d)
837 CLEANUP_FILES
+=("${tmpdir}")
838 tar -xf "${small_corpus_tar}" -C "${tmpdir}"
840 run_benchmark
"${tmpdir}" 1048576
844 local nikon_corpus_tar
="${BENCHMARK_CORPORA}/nikon-subset.tar"
845 mkdir
-p "${BENCHMARK_CORPORA}"
846 curl
--show-error -o "${nikon_corpus_tar}" -z "${nikon_corpus_tar}" \
847 "https://storage.googleapis.com/artifacts.jpegxl.appspot.com/corpora/nikon-subset.tar"
849 local tmpdir
=$
(mktemp
-d)
850 CLEANUP_FILES
+=("${tmpdir}")
851 tar -xvf "${nikon_corpus_tar}" -C "${tmpdir}"
853 local sem_id
="jpegxl_benchmark-$$"
854 local nprocs
=$
(nproc
--all ||
echo 1)
857 while IFS
= read -r filename
; do
858 # This removes the './'
859 filename
="${filename:2}"
861 if [[ "${filename:0:4}" == "srgb" ]]; then
862 mode
="RGB_D65_SRG_Rel_SRG"
863 elif [[ "${filename:0:5}" == "adobe" ]]; then
864 mode
="RGB_D65_Ado_Rel_Ado"
866 echo "Unknown image colorspace: ${filename}" >&2
869 png_filename
="${filename%.ppm}.png"
870 png_filename
=$
(echo "${png_filename}" |
tr '/' '_')
871 sem
--bg --id "${sem_id}" -j"${nprocs}" -- \
872 "${BUILD_DIR}/tools/decode_and_encode" \
873 "${tmpdir}/${filename}" "${mode}" "${tmpdir}/${png_filename}"
874 images
+=( "${png_filename}" )
875 done < <(cd "${tmpdir}"; ${FIND_BIN} .
-name '*.ppm' -type f
)
876 sem
--id "${sem_id}" --wait
878 # We need about 10 GiB per thread on these images.
879 run_benchmark
"${tmpdir}" 10485760
882 get_mem_available
() {
883 if [[ "${OS}" == "Darwin" ]]; then
884 echo $
(vm_stat |
grep -F 'Pages free:' |
awk '{print $3 * 4}')
886 echo $
(grep -F MemAvailable
: /proc
/meminfo |
awk '{print $2}')
891 local src_img_dir
="$1"
892 local mem_per_thread
="${2:-10485760}"
894 local output_dir
="${BUILD_DIR}/benchmark_results"
895 mkdir
-p "${output_dir}"
897 # The memory available at the beginning of the benchmark run in kB. The number
898 # of threads depends on the available memory, and the passed memory per
899 # thread. We also add a 2 GiB of constant memory.
900 local mem_available
="$(get_mem_available)"
901 # Check that we actually have a MemAvailable value.
902 [[ -n "${mem_available}" ]]
903 local num_threads
=$
(( (${mem_available} - 1048576) / ${mem_per_thread} ))
904 if [[ ${num_threads} -le 0 ]]; then
908 local benchmark_args
=(
909 --input "${src_img_dir}/*.png"
910 --codec=jpeg
:yuv420
:q85
,webp
:q80
,jxl
:d1
:6,jxl
:d1
:6:downsampling
=8,jxl
:d5
:6,jxl
:d5
:6:downsampling
=8,jxl
:m
:d0
:2,jxl
:m
:d0
:3,jxl
:m
:d2
:2
911 --output_dir "${output_dir}"
912 --noprofiler --show_progress
913 --num_threads="${num_threads}"
915 if [[ "${STORE_IMAGES}" == "1" ]]; then
916 benchmark_args
+=(--save_decompressed --save_compressed)
919 [[ "${TEST_STACK_LIMIT}" == "none" ]] ||
ulimit -s "${TEST_STACK_LIMIT}"
920 "${BUILD_DIR}/tools/benchmark_xl" "${benchmark_args[@]}" | \
921 tee "${output_dir}/results.txt"
923 # Check error code for benckmark_xl command. This will exit if not.
924 return ${PIPESTATUS[0]}
927 if [[ -n "${CI_BUILD_NAME:-}" ]]; then
928 { set +x
; } 2>/dev
/null
929 local message
="Results for ${CI_BUILD_NAME} @ ${CI_COMMIT_SHORT_SHA} (job ${CI_JOB_URL:-}):
931 $(cat "${output_dir}/results.txt
")
933 cmd_post_mr_comment
"${message}"
938 # Helper function to wait for the CPU temperature to cool down on ARM.
940 { set +x
; } 2>/dev
/null
941 local temp_limit
=${1:-38000}
942 if [[ -z "${THERMAL_FILE:-}" ]]; then
943 echo "Must define the THERMAL_FILE with the thermal_zoneX/temp file" \
944 "to read the temperature from. This is normally set in the runner." >&2
947 local org_temp
=$
(cat "${THERMAL_FILE}")
948 if [[ "${org_temp}" -ge "${temp_limit}" ]]; then
949 echo -n "Waiting for temp to get down from ${org_temp}... "
951 local temp
="${org_temp}"
953 while [[ "${temp}" -ge "${temp_limit}" ]]; do
955 temp
=$
(cat "${THERMAL_FILE}")
958 if [[ ${secs} -ge 5 ]]; then
962 if [[ "${org_temp}" -ge "${temp_limit}" ]]; then
963 echo "Done, temp=${temp}"
968 # Helper function to set the cpuset restriction of the current process.
970 [[ "${SKIP_CPUSET:-}" != "1" ]] ||
return 0
972 local mycpuset
=$
(cat /proc
/self
/cpuset
)
973 mycpuset
="/dev/cpuset${mycpuset}"
974 # Check that the directory exists:
975 [[ -d "${mycpuset}" ]]
976 if [[ -e "${mycpuset}/cpuset.cpus" ]]; then
977 echo "${newset}" >"${mycpuset}/cpuset.cpus"
979 echo "${newset}" >"${mycpuset}/cpus"
983 # Return the encoding/decoding speed from the Stats output.
984 _speed_from_output
() {
986 local unit
="${2:-MP/s}"
987 if [[ "${speed}" == *"${unit}"* ]]; then
988 speed
="${speed%% ${unit}*}"
995 # Run benchmarks on ARM for the big and little CPUs.
996 cmd_arm_benchmark
() {
997 # Flags used for cjxl encoder with .png inputs
998 local jxl_png_benchmarks
=(
1000 "--epf=0 --distance=1.0 --speed=cheetah"
1001 "--epf=2 --distance=1.0 --speed=cheetah"
1002 "--epf=0 --distance=8.0 --speed=cheetah"
1003 "--epf=1 --distance=8.0 --speed=cheetah"
1004 "--epf=2 --distance=8.0 --speed=cheetah"
1005 "--epf=3 --distance=8.0 --speed=cheetah"
1010 "--modular -E 0 -I 0"
1012 "--modular --responsive=1"
1013 # Near-lossless options:
1014 "--epf=0 --distance=0.3 --speed=fast"
1018 # Flags used for cjxl encoder with .jpg inputs. These should do lossless
1019 # JPEG recompression (of pixels or full jpeg).
1020 local jxl_jpeg_benchmarks
=(
1025 "testdata/jxl/flower/flower.png"
1029 "testdata/jxl/flower/flower.png.im_q85_420.jpg"
1032 if [[ "${SKIP_CPUSET:-}" == "1" ]]; then
1033 # Use a single cpu config in this case.
1034 local cpu_confs
=("?")
1036 # Otherwise the CPU config comes from the environment:
1038 "${RUNNER_CPU_LITTLE}"
1040 # The CPU description is something like 3-7, so these configurations only
1041 # take the first CPU of the group.
1042 "${RUNNER_CPU_LITTLE%%-*}"
1043 "${RUNNER_CPU_BIG%%-*}"
1045 # Check that RUNNER_CPU_ALL is defined. In the SKIP_CPUSET=1 case this will
1046 # be ignored but still evaluated when calling cmd_cpuset.
1047 [[ -n "${RUNNER_CPU_ALL}" ]]
1050 local jpg_dirname
="third_party/corpora/jpeg"
1051 mkdir
-p "${jpg_dirname}"
1052 local jpg_qualities
=( 50 80 95 )
1053 for src_img
in "${images[@]}"; do
1054 for q
in "${jpg_qualities[@]}"; do
1055 local jpeg_name
="${jpg_dirname}/"$(basename "${src_img}" .png)"-q${q}.jpg"
1056 convert
-sampling-factor 1x1
-quality "${q}" \
1057 "${src_img}" "${jpeg_name}"
1058 jpg_images
+=("${jpeg_name}")
1062 local output_dir
="${BUILD_DIR}/benchmark_results"
1063 mkdir
-p "${output_dir}"
1064 local runs_file
="${output_dir}/runs.txt"
1066 if [[ ! -e "${runs_file}" ]]; then
1067 echo -e "binary\tflags\tsrc_img\tsrc size\tsrc pixels\tcpuset\tenc size (B)\tenc speed (MP/s)\tdec speed (MP/s)\tJPG dec speed (MP/s)\tJPG dec speed (MB/s)" |
1068 tee -a "${runs_file}"
1071 mkdir
-p "${BUILD_DIR}/arm_benchmark"
1074 for src_img
in "${jpg_images[@]}" "${images[@]}"; do
1075 local src_img_hash
=$
(sha1sum "${src_img}" | cut
-f 1 -d ' ')
1076 local enc_binaries
=("${BUILD_DIR}/tools/cjxl")
1077 local src_ext
="${src_img##*.}"
1078 for enc_binary
in "${enc_binaries[@]}"; do
1079 local enc_binary_base
=$
(basename "${enc_binary}")
1081 # Select the list of flags to use for the current encoder/image pair.
1082 local img_benchmarks
1083 if [[ "${src_ext}" == "jpg" ]]; then
1084 img_benchmarks
=("${jxl_jpeg_benchmarks[@]}")
1086 img_benchmarks
=("${jxl_png_benchmarks[@]}")
1089 for flags
in "${img_benchmarks[@]}"; do
1091 local enc_file_hash
="${enc_binary_base} || $flags || ${src_img} || ${src_img_hash}"
1092 enc_file_hash
=$
(echo "${enc_file_hash}" |
sha1sum | cut
-f 1 -d ' ')
1093 local enc_file
="${BUILD_DIR}/arm_benchmark/${enc_file_hash}.jxl"
1095 for cpu_conf
in "${cpu_confs[@]}"; do
1096 cmd_cpuset
"${cpu_conf}"
1097 # nproc returns the number of active CPUs, which is given by the cpuset
1099 local num_threads
="$(nproc)"
1101 echo "Encoding with: ${enc_binary_base} img=${src_img} cpus=${cpu_conf} enc_flags=${flags}"
1103 if [[ "${flags}" == *"modular"* ]]; then
1104 # We don't benchmark encoding speed in this case.
1105 if [[ ! -f "${enc_file}" ]]; then
1106 cmd_cpuset
"${RUNNER_CPU_ALL:-}"
1107 "${enc_binary}" ${flags} "${src_img}" "${enc_file}.tmp"
1108 mv "${enc_file}.tmp" "${enc_file}"
1109 cmd_cpuset
"${cpu_conf}"
1111 enc_output
=" ?? MP/s"
1114 enc_output
=$
("${enc_binary}" ${flags} "${src_img}" "${enc_file}.tmp" \
1115 2>&1 |
tee /dev
/stderr |
grep -F "MP/s [")
1116 mv "${enc_file}.tmp" "${enc_file}"
1118 local enc_speed
=$
(_speed_from_output
"${enc_output}")
1119 local enc_size
=$
(stat
-c "%s" "${enc_file}")
1121 echo "Decoding with: img=${src_img} cpus=${cpu_conf} enc_flags=${flags}"
1125 dec_output
=$
("${BUILD_DIR}/tools/djxl" "${enc_file}" \
1126 --num_reps=5 --num_threads="${num_threads}" 2>&1 |
tee /dev
/stderr |
1127 grep -E "M[BP]/s \[")
1128 local img_size
=$
(echo "${dec_output}" | cut
-f 1 -d ',')
1129 local img_size_x
=$
(echo "${img_size}" | cut
-f 1 -d ' ')
1130 local img_size_y
=$
(echo "${img_size}" | cut
-f 3 -d ' ')
1131 local img_size_px
=$
(( ${img_size_x} * ${img_size_y} ))
1132 local dec_speed
=$
(_speed_from_output
"${dec_output}")
1134 # For JPEG lossless recompression modes (where the original is a JPEG)
1135 # decode to JPG as well.
1136 local jpeg_dec_mps_speed
=""
1137 local jpeg_dec_mbs_speed
=""
1138 if [[ "${src_ext}" == "jpg" ]]; then
1140 local dec_file
="${BUILD_DIR}/arm_benchmark/${enc_file_hash}.jpg"
1141 dec_output
=$
("${BUILD_DIR}/tools/djxl" "${enc_file}" \
1142 "${dec_file}" --num_reps=5 --num_threads="${num_threads}" 2>&1 | \
1143 tee /dev
/stderr |
grep -E "M[BP]/s \[")
1144 local jpeg_dec_mps_speed
=$
(_speed_from_output
"${dec_output}")
1145 local jpeg_dec_mbs_speed
=$
(_speed_from_output
"${dec_output}" MB
/s
)
1146 if ! cmp --quiet "${src_img}" "${dec_file}"; then
1147 # Add a start at the end to signal that the files are different.
1148 jpeg_dec_mbs_speed
+="*"
1152 # Record entry in a tab-separated file.
1153 local src_img_base
=$
(basename "${src_img}")
1154 echo -e "${enc_binary_base}\t${flags}\t${src_img_base}\t${img_size}\t${img_size_px}\t${cpu_conf}\t${enc_size}\t${enc_speed}\t${dec_speed}\t${jpeg_dec_mps_speed}\t${jpeg_dec_mbs_speed}" |
1155 tee -a "${runs_file}"
1160 cmd_cpuset
"${RUNNER_CPU_ALL:-}"
1163 if [[ -n "${CI_BUILD_NAME:-}" ]]; then
1164 load_mr_vars_from_commit
1165 { set +x
; } 2>/dev
/null
1166 local message
="Results for ${CI_BUILD_NAME} @ ${CI_COMMIT_SHORT_SHA} (job ${CI_JOB_URL:-}):
1169 $(column -t -s " " "${runs_file}")
1172 cmd_post_mr_comment
"${message}"
1177 # Generate a corpus and run the fuzzer on that corpus.
1179 local corpus_dir
=$
(realpath
"${BUILD_DIR}/fuzzer_corpus")
1180 local fuzzer_crash_dir
=$
(realpath
"${BUILD_DIR}/fuzzer_crash")
1181 mkdir
-p "${corpus_dir}" "${fuzzer_crash_dir}"
1183 "${BUILD_DIR}/tools/fuzzer_corpus" "${corpus_dir}"
1185 local nprocs
=$
(nproc
--all ||
echo 1)
1188 "tools/djxl_fuzzer" "${fuzzer_crash_dir}" "${corpus_dir}" \
1189 -max_total_time="${FUZZER_MAX_TIME}" -jobs=${nprocs} \
1190 -artifact_prefix="${fuzzer_crash_dir}/"
1194 # Runs the linter (clang-format) on the pending CLs.
1196 merge_request_commits
1197 { set +x
; } 2>/dev
/null
1198 local versions
=(${1:-6.0 7 8 9 10 11})
1199 local clang_format_bins
=("${versions[@]/#/clang-format-}" clang-format
)
1200 local tmpdir
=$
(mktemp
-d)
1201 CLEANUP_FILES
+=("${tmpdir}")
1204 local build_patch
="${tmpdir}/build_cleaner.patch"
1205 if ! "${MYDIR}/tools/build_cleaner.py" >"${build_patch}"; then
1207 echo "build_cleaner.py findings:" >&2
1208 "${COLORDIFF_BIN}" <"${build_patch}"
1209 echo "Run \`tools/build_cleaner.py --update\` to apply them" >&2
1215 for clang_format
in "${clang_format_bins[@]}"; do
1216 if ! which "${clang_format}" >/dev
/null
; then
1219 installed
+=("${clang_format}")
1220 local tmppatch
="${tmpdir}/${clang_format}.patch"
1221 # We include in this linter all the changes including the uncommitted changes
1222 # to avoid printing changes already applied.
1224 # Ignoring the error that git-clang-format outputs.
1225 git
-C "${MYDIR}" "${clang_format}" --binary "${clang_format}" \
1226 --style=file --diff "${MR_ANCESTOR_SHA}" -- >"${tmppatch}" || true
1227 { set +x
; } 2>/dev
/null
1228 if grep -E '^--- ' "${tmppatch}">/dev
/null
; then
1229 if [[ -n "${LINT_OUTPUT:-}" ]]; then
1230 cp "${tmppatch}" "${LINT_OUTPUT}"
1232 clang_patch
="${tmppatch}"
1234 echo "clang-format check OK" >&2
1239 if [[ ${#installed[@]} -eq 0 ]]; then
1240 echo "You must install clang-format for \"git clang-format\"" >&2
1244 # clang-format is installed but found problems.
1245 echo "clang-format findings:" >&2
1246 "${COLORDIFF_BIN}" < "${clang_patch}"
1248 echo "clang-format found issues in your patches from ${MR_ANCESTOR_SHA}" \
1249 "to the current patch. Run \`./ci.sh lint | patch -p1\` from the base" \
1250 "directory to apply them." >&2
1254 # Runs clang-tidy on the pending CLs. If the "all" argument is passed it runs
1255 # clang-tidy over all the source files instead.
1259 if [[ -z "${CLANG_TIDY_BIN}" ]]; then
1260 echo "ERROR: You must install clang-tidy-7 or newer to use ci.sh tidy" >&2
1265 if [[ "${what}" == "all" ]]; then
1269 merge_request_commits
1271 diff-tree
--no-commit-id --name-only -r "${MR_ANCESTOR_SHA}"
1276 # Clang-tidy needs the compilation database generated by cmake.
1277 if [[ ! -e "${BUILD_DIR}/compile_commands.json" ]]; then
1278 # Generate the build options in debug mode, since we need the debug asserts
1279 # enabled for the clang-tidy analyzer to use them.
1280 CMAKE_BUILD_TYPE
="Debug"
1282 # Build the autogen targets to generate the .h files from the .ui files.
1283 local autogen_targets
=(
1284 $
(ninja
-C "${BUILD_DIR}" -t targets |
grep -F _autogen
: |
1287 if [[ ${#autogen_targets[@]} != 0 ]]; then
1288 ninja
-C "${BUILD_DIR}" "${autogen_targets[@]}"
1293 local nprocs
=$
(nproc
--all ||
echo 1)
1295 if ! parallel
-j"${nprocs}" --keep-order -- \
1296 "${CLANG_TIDY_BIN}" -p "${BUILD_DIR}" -format-style=file -quiet "$@" {} \
1297 < <(git "${git_args[@]}" | grep -E '(\.cc|\.cpp)$') \
1298 >"${BUILD_DIR}/clang-tidy.txt
"; then
1301 { set +x; } 2>/dev/null
1302 echo "Findings statistics
:" >&2
1303 grep -E ' \[[A-Za-z\.,\-]+\]' -o "${BUILD_DIR}/clang-tidy.txt
" | sort \
1306 if [[ $ret -ne 0 ]]; then
1308 Errors found, see ${BUILD_DIR}/clang-tidy.txt for details.
1309 To automatically fix them, run:
1311 SKIP_TEST=1 ./ci.sh debug
1312 ${CLANG_TIDY_BIN} -p ${BUILD_DIR} -fix -format-style=file -quiet $@ \$(git ${git_args[@]} | grep -E '(\.cc|\.cpp)\$')
1319 # Print stats about all the packages built in ${BUILD_DIR}/debs/.
1320 cmd_debian_stats() {
1321 { set +x; } 2>/dev/null
1322 local debsdir="${BUILD_DIR}/debs
"
1324 while IFS='' read -r -d '' f; do
1325 echo "====================================================================="
1329 done < <(find "${BUILD_DIR}/debs
" -maxdepth 1 -mindepth 1 -type f \
1330 -name '*.deb' -print0)
1333 build_debian_pkg() {
1337 local debsdir="${BUILD_DIR}/debs
"
1338 local builddir="${debsdir}/${srcpkg}"
1340 # debuild doesn't have an easy way to build out of tree, so we make a copy
1341 # of with all symlinks on the first level.
1342 mkdir -p "${builddir}"
1343 for f in $(find "${srcdir}" -mindepth 1 -maxdepth 1 -printf '%P\n'); do
1344 if [[ ! -L "${builddir}/$f" ]]; then
1345 rm -f "${builddir}/$f"
1346 ln -s "${srcdir}/$f" "${builddir}/$f"
1355 cmd_debian_build() {
1356 local srcpkg="${1:-}"
1360 build_debian_pkg "${MYDIR}" "jpeg-xl
"
1363 build_debian_pkg "${MYDIR}/third_party
/highway
" "highway
"
1366 echo "ERROR
: Must pass a valid
source package name to build.
" >&2
1373 local line=$(grep -F "set(${varname} " lib/CMakeLists.txt | head -n 1)
1375 line="${line#set(${varname} }"
1380 cmd_bump_version() {
1381 local newver="${1:-}"
1383 if ! which dch >/dev/null; then
1384 echo "Run
:\n sudo apt
install debhelper
"
1388 if [[ -z "${newver}" ]]; then
1389 local major=$(get_version JPEGXL_MAJOR_VERSION)
1390 local minor=$(get_version JPEGXL_MINOR_VERSION)
1392 minor=$(( ${minor} + 1))
1394 local major="${newver%%.*}"
1395 newver="${newver#*.}"
1396 local minor="${newver%%.*}"
1397 newver="${newver#${minor}}"
1398 local patch="${newver#.}"
1399 if [[ -z "${patch}" ]]; then
1404 newver="${major}.${minor}.${patch}"
1406 echo "Bumping version to
${newver} (${major}.${minor}.${patch})"
1408 -e "s
/(set\\(JPEGXL_MAJOR_VERSION
) [0-9]+\\)/\\1 ${major})/" \
1409 -e "s
/(set\\(JPEGXL_MINOR_VERSION
) [0-9]+\\)/\\1 ${minor})/" \
1410 -e "s
/(set\\(JPEGXL_PATCH_VERSION
) [0-9]+\\)/\\1 ${patch})/" \
1411 -i lib/CMakeLists.txt
1413 -e "s
/(LIBJXL_VERSION
: )[0-9\\.
]+/\\1 ${major}.${minor}.${patch}/" \
1414 -e "s
/(LIBJXL_ABI_VERSION
: )[0-9\\.
]+/\\1 ${major}.
${minor}/" \
1415 -i .github/workflows/conformance.yml
1418 tools/build_cleaner.py --update
1420 # Mark the previous version as "unstable
".
1421 DEBCHANGE_RELEASE_HEURISTIC=log dch -M --distribution unstable --release ''
1422 DEBCHANGE_RELEASE_HEURISTIC=log dch -M \
1423 --newversion "${newver}" \
1424 "Bump JPEG XL version to
${newver}.
"
1427 # Check that the AUTHORS file contains the email of the committer.
1429 merge_request_commits
1432 readarray -t emails < <(git log --format='%ae' "${MR_ANCESTOR_SHA}..
${MR_HEAD_SHA}")
1433 readarray -t names < <(git log --format='%an' "${MR_ANCESTOR_SHA}..
${MR_HEAD_SHA}")
1434 for i in "${!names[@]}"; do
1435 echo "Checking name
'${names[$i]}' with email
'${emails[$i]}' ...
"
1436 "${MYDIR}"/tools/check_author.py "${emails[$i]}" "${names[$i]}"
1442 if [[ -z "${cmd}" ]]; then
1446 Where cmd is one of:
1447 opt Build and test a Release with symbols build.
1448 debug Build and test a Debug build (NDEBUG is not defined).
1449 release Build and test a striped Release binary without debug information.
1450 asan Build and test an ASan (AddressSanitizer) build.
1451 msan Build and test an MSan (MemorySanitizer) build. Needs to have msan
1452 c++ libs installed with msan_install first.
1453 tsan Build and test a TSan (ThreadSanitizer) build.
1454 asanfuzz Build and test an ASan (AddressSanitizer) build for fuzzing.
1455 msanfuzz Build and test an MSan (MemorySanitizer) build for fuzzing.
1456 test Run the tests build by opt, debug, release, asan or msan. Useful when
1457 building with SKIP_TEST=1.
1458 gbench Run the Google benchmark tests.
1459 fuzz Generate the fuzzer corpus and run the fuzzer on it. Useful after
1460 building with asan or msan.
1461 benchmark Run the benchmark over the default corpus.
1462 fast_benchmark Run the benchmark over the small corpus.
1464 coverage Buils and run tests with coverage support. Runs coverage_report as
1466 coverage_report Generate HTML, XML and text coverage report after a coverage
1469 lint Run the linter checks on the current commit or merge request.
1470 tidy Run clang-tidy on the current commit or merge request.
1471 authors Check that the last commit's author is listed in the AUTHORS file.
1473 msan_install Install the libc++ libraries required to build in msan mode. This
1474 needs to be done once.
1476 debian_build <srcpkg> Build the given source package.
1477 debian_stats Print stats about the built packages.
1480 ossfuzz_asan Build the local source inside oss-fuzz docker with asan.
1481 ossfuzz_msan Build the local source inside oss-fuzz docker with msan.
1482 ossfuzz_ubsan Build the local source inside oss-fuzz docker with ubsan.
1483 ossfuzz_ninja Run ninja on the BUILD_DIR inside the oss-fuzz docker. Extra
1484 parameters are passed to ninja, for example "djxl_fuzzer
" will
1485 only build that ninja target. Use for faster build iteration
1486 after one of the ossfuzz_*san commands.
1488 You can pass some optional environment variables as well:
1489 - BUILD_DIR: The output build directory (by default "$
$repo/build
")
1490 - BUILD_TARGET: The target triplet used when cross-compiling.
1491 - CMAKE_FLAGS: Convenience flag to pass both CMAKE_C_FLAGS and CMAKE_CXX_FLAGS.
1492 - CMAKE_PREFIX_PATH: Installation prefixes to be searched by the find_package.
1493 - ENABLE_WASM_SIMD=1: enable experimental SIMD in WASM build (only).
1494 - FUZZER_MAX_TIME: "fuzz
" command fuzzer running timeout in seconds.
1495 - LINT_OUTPUT: Path to the output patch from the "lint
" command.
1496 - SKIP_CPUSET=1: Skip modifying the cpuset in the arm_benchmark.
1497 - SKIP_TEST=1: Skip the test stage.
1498 - STORE_IMAGES=0: Makes the benchmark discard the computed images.
1499 - TEST_STACK_LIMIT: Stack size limit (ulimit -s) during tests, in KiB.
1500 - TEST_SELECTOR: pass additional arguments to ctest, e.g. "-R .Resample.
".
1501 - STACK_SIZE=1: Generate binaries with the .stack_sizes sections.
1503 These optional environment variables are forwarded to the cmake call as
1508 - CMAKE_C_COMPILER_LAUNCHER
1509 - CMAKE_CXX_COMPILER_LAUNCHER
1510 - CMAKE_CROSSCOMPILING_EMULATOR
1511 - CMAKE_FIND_ROOT_PATH
1512 - CMAKE_EXE_LINKER_FLAGS
1513 - CMAKE_MAKE_PROGRAM
1514 - CMAKE_MODULE_LINKER_FLAGS
1515 - CMAKE_SHARED_LINKER_FLAGS
1516 - CMAKE_TOOLCHAIN_FILE
1519 BUILD_DIR=/tmp/build $0 opt