| 1 | #!/usr/bin/env bash
|
| 2 | #
|
| 3 | # Actions invoked by build.ninja, which is generated by ./NINJA-config.sh.
|
| 4 | #
|
| 5 | # It's distributed with the tarball and invoked by _build/oils.sh, so
|
| 6 | # it's written in a /bin/sh style.
|
| 7 | #
|
| 8 | # And some non-Ninja wrappers.
|
| 9 | #
|
| 10 | # Usage:
|
| 11 | # build/ninja-rules-cpp.sh <function name>
|
| 12 | #
|
| 13 | # Env variables:
|
| 14 | # BASE_CXXFLAGS= default flags passed to all compiler invocations
|
| 15 | # CXXFLAGS= additional flags
|
| 16 | # OILS_CXX_VERBOSE=1 show compiler command lines
|
| 17 | # TIME_TSV_OUT=file compile_one and link output rows to this TSV file
|
| 18 |
|
| 19 | set -o nounset
|
| 20 | set -o errexit
|
| 21 | # For /bin/sh portability
|
| 22 | #eval 'set -o pipefail'
|
| 23 |
|
| 24 | REPO_ROOT=$(cd "$(dirname $0)/.."; pwd)
|
| 25 |
|
| 26 | . build/common.sh # for $BASE_CXXFLAGS
|
| 27 | . build/dev-shell.sh # python2 in $PATH
|
| 28 |
|
| 29 | # for HAVE_READLINE, READLINE_DIR, and STRIP_FLAGS
|
| 30 | if ! . _build/detected-config.sh; then
|
| 31 | die "Can't find _build/detected-config.sh. Run './configure'"
|
| 32 | fi
|
| 33 |
|
| 34 | line_count() {
|
| 35 | local out=$1
|
| 36 | shift # rest are inputs
|
| 37 | wc -l "$@" | sort -n | tee $out
|
| 38 | }
|
| 39 |
|
| 40 | #
|
| 41 | # Mutable GLOBALS
|
| 42 | #
|
| 43 |
|
| 44 | cxx='' # compiler
|
| 45 | flags='' # compile flags
|
| 46 | link_flags='' # link flags
|
| 47 |
|
| 48 | #
|
| 49 | # Functions to set them
|
| 50 | #
|
| 51 |
|
| 52 | setglobal_cxx() {
|
| 53 | local compiler=$1
|
| 54 |
|
| 55 | case $compiler in
|
| 56 | clang) cxx=$CLANGXX ;;
|
| 57 | # Note: we could get rid of this "alias", and use 'c++' everywhere
|
| 58 | cxx) cxx='c++' ;;
|
| 59 |
|
| 60 | # e.g. could be cosmoc++
|
| 61 | *) cxx=$compiler ;;
|
| 62 | esac
|
| 63 | }
|
| 64 |
|
| 65 | setglobal_compile_flags() {
|
| 66 | ### Set flags based on $variant $more_cxx_flags and $dotd
|
| 67 | local variant=$1
|
| 68 | local more_cxx_flags=$2 # e.g. NINJA_subgraph.py sets -D CPP_UNIT_TEST
|
| 69 | local dotd=${3:-}
|
| 70 |
|
| 71 | # flags from Ninja/shell respected
|
| 72 | flags="$BASE_CXXFLAGS -I $REPO_ROOT $more_cxx_flags"
|
| 73 |
|
| 74 | # flags needed to strip unused symbols
|
| 75 | # TODO: should these go in BASE_CXXFLAGS?
|
| 76 | #
|
| 77 | # https://stackoverflow.com/questions/6687630/how-to-remove-unused-c-c-symbols-with-gcc-and-ld
|
| 78 | flags="$flags -fdata-sections -ffunction-sections"
|
| 79 | # Note: -ftlo doesn't do anything for size?
|
| 80 |
|
| 81 | case $variant in
|
| 82 | *+bumpleak|*+bumproot)
|
| 83 | ;;
|
| 84 | *+bigint)
|
| 85 | flags="$flags -D MARK_SWEEP -D BIGINT"
|
| 86 | ;;
|
| 87 | *)
|
| 88 | flags="$flags -D MARK_SWEEP"
|
| 89 | ;;
|
| 90 | esac
|
| 91 |
|
| 92 | # First half of variant: what affects ALL translation units
|
| 93 |
|
| 94 | case $variant in
|
| 95 | dbg*)
|
| 96 | flags="$flags -O0 -g"
|
| 97 | ;;
|
| 98 |
|
| 99 | asan*)
|
| 100 | # CLEAN_PROCESS_EXIT avoids spurious memory leaks
|
| 101 | flags="$flags -O0 -g -fsanitize=address -D CLEAN_PROCESS_EXIT"
|
| 102 | ;;
|
| 103 |
|
| 104 | tsan*)
|
| 105 | flags="$flags -O0 -g -fsanitize=thread"
|
| 106 | ;;
|
| 107 |
|
| 108 | ubsan*)
|
| 109 | # Extra flag to make it fatal
|
| 110 | # https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
|
| 111 |
|
| 112 | flags="$flags -O0 -g -fsanitize=undefined -fno-sanitize-recover=null"
|
| 113 | ;;
|
| 114 |
|
| 115 | opt*)
|
| 116 | flags="$flags -O2 -g -D OPTIMIZED"
|
| 117 | ;;
|
| 118 |
|
| 119 | coverage*)
|
| 120 | # source-based coverage is more precise than say sanitizer-based
|
| 121 | # https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
|
| 122 | flags="$flags -O0 -g -fprofile-instr-generate -fcoverage-mapping"
|
| 123 | ;;
|
| 124 |
|
| 125 | uftrace*)
|
| 126 | # -O0 creates a A LOT more data. But sometimes we want to see the
|
| 127 | # structure of the code.
|
| 128 | # NewStr(), OverAllocatedStr(), StrFromC() etc. are not inlined
|
| 129 | # Ditto vector::size(), std::forward, len(), etc.
|
| 130 |
|
| 131 | local opt='-O0'
|
| 132 | #local opt='-O2'
|
| 133 | flags="$flags $opt -g -pg"
|
| 134 | ;;
|
| 135 |
|
| 136 | *)
|
| 137 | die "Invalid variant $variant"
|
| 138 | ;;
|
| 139 | esac
|
| 140 |
|
| 141 | # for cxx-dbg32, cxx-opt32+bumpleak, etc.
|
| 142 | case $variant in
|
| 143 | *32*)
|
| 144 | flags="$flags -m32"
|
| 145 | ;;
|
| 146 | esac
|
| 147 |
|
| 148 | # OPTIONAL second half of variant: for the application
|
| 149 |
|
| 150 | case $variant in
|
| 151 | *+gcalways)
|
| 152 | flags="$flags -D GC_ALWAYS"
|
| 153 | ;;
|
| 154 |
|
| 155 | *+tcmalloc)
|
| 156 | flags="$flags -D TCMALLOC"
|
| 157 | ;;
|
| 158 |
|
| 159 | *+bumpleak)
|
| 160 | flags="$flags -D BUMP_LEAK"
|
| 161 | ;;
|
| 162 | *+bumproot)
|
| 163 | flags="$flags -D BUMP_LEAK -D BUMP_ROOT"
|
| 164 | ;;
|
| 165 |
|
| 166 | *+bumpsmall)
|
| 167 | # the pool allocator should approximate opt+bumpsmall (which doesn't support GC)
|
| 168 | flags="$flags -D BUMP_ROOT -D BUMP_SMALL -D NO_POOL_ALLOC"
|
| 169 | ;;
|
| 170 |
|
| 171 | *+nopool)
|
| 172 | flags="$flags -D NO_POOL_ALLOC"
|
| 173 | ;;
|
| 174 | esac
|
| 175 |
|
| 176 | # HAVE_READLINE is from ./configure
|
| 177 | # $FLAG_without_readline is from _build/oils.sh - this uses DYNAMIC SCOPE!
|
| 178 | if test -n "$HAVE_READLINE" && test -z "${FLAG_without_readline:-}"; then
|
| 179 | flags="$flags -D HAVE_READLINE"
|
| 180 | fi
|
| 181 | if test -n "$READLINE_DIR"; then
|
| 182 | flags="$flags -I${READLINE_DIR}/include"
|
| 183 | fi
|
| 184 |
|
| 185 | # Flags from env
|
| 186 | # Similar to
|
| 187 | # - GNU make - https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html
|
| 188 | # CXXFLAGS "Extra flags to give to the C++ compiler"
|
| 189 | # - CMake - https://cmake.org/cmake/help/latest/envvar/CXXFLAGS.html
|
| 190 | # "Add default compilation flags to be used when compiling CXX (C++) files."
|
| 191 |
|
| 192 | local env_flags=${CXXFLAGS:-}
|
| 193 | if test -n "$env_flags"; then
|
| 194 | flags="$flags $env_flags"
|
| 195 | fi
|
| 196 |
|
| 197 | # https://ninja-build.org/manual.html#ref_headers
|
| 198 | if test -n "$dotd"; then
|
| 199 | flags="$flags -MD -MF $dotd"
|
| 200 | fi
|
| 201 | }
|
| 202 |
|
| 203 | setglobal_link_flags() {
|
| 204 | local variant=$1
|
| 205 | local more_link_flags=${2:-} # from NINJA_subgraph.py, e.g. Souffle datalog
|
| 206 |
|
| 207 | link_flags="$more_link_flags" # initialize
|
| 208 |
|
| 209 | # Linker flags based on build variant
|
| 210 | local variant_flags=''
|
| 211 | case $variant in
|
| 212 | # Must REPEAT these flags, otherwise we lose sanitizers / coverage
|
| 213 | asan*)
|
| 214 | variant_flags='-fsanitize=address'
|
| 215 | ;;
|
| 216 |
|
| 217 | tcmalloc)
|
| 218 | # Need to tell the dynamic loader where to find tcmalloc
|
| 219 | variant_flags='-ltcmalloc -Wl,-rpath,/usr/local/lib'
|
| 220 | ;;
|
| 221 |
|
| 222 | tsan)
|
| 223 | variant_flags='-fsanitize=thread'
|
| 224 | ;;
|
| 225 | ubsan*)
|
| 226 | variant_flags='-fsanitize=undefined'
|
| 227 | ;;
|
| 228 | coverage*)
|
| 229 | variant_flags='-fprofile-instr-generate -fcoverage-mapping'
|
| 230 | ;;
|
| 231 | esac
|
| 232 |
|
| 233 | if test -n "$variant_flags"; then
|
| 234 | link_flags="$link_flags $variant_flags"
|
| 235 | fi
|
| 236 |
|
| 237 | # More build variant flags, and GNU readline flags
|
| 238 | case $variant in
|
| 239 | # TODO: 32-bit variants can't handle -l readline right now.
|
| 240 | *32*)
|
| 241 | link_flags="$link_flags -m32"
|
| 242 | ;;
|
| 243 |
|
| 244 | *)
|
| 245 | # $FLAG_without_readline is from _build/oils.sh - this uses DYNAMIC SCOPE!
|
| 246 | if test -n "$HAVE_READLINE" && test -z "${FLAG_without_readline:-}"; then
|
| 247 | link_flags="$link_flags -lreadline"
|
| 248 | fi
|
| 249 | if test -n "$READLINE_DIR"; then
|
| 250 | link_flags="$link_flags -L${READLINE_DIR}/lib"
|
| 251 | fi
|
| 252 | ;;
|
| 253 | esac
|
| 254 |
|
| 255 | # Detected by ./configure
|
| 256 | if test -n "${STRIP_FLAGS:-}"; then
|
| 257 | link_flags="$link_flags -Wl,$STRIP_FLAGS"
|
| 258 | fi
|
| 259 |
|
| 260 | # Set by user/packager
|
| 261 | local env_flags=${LDFLAGS:-}
|
| 262 | if test -n "$env_flags"; then
|
| 263 | link_flags="$link_flags $env_flags"
|
| 264 | fi
|
| 265 | }
|
| 266 |
|
| 267 | compile_one() {
|
| 268 | ### Compile one translation unit. Invoked by build.ninja
|
| 269 |
|
| 270 | local compiler=$1
|
| 271 | local variant=$2
|
| 272 | local more_cxx_flags=$3
|
| 273 | local in=$4
|
| 274 | local out=$5
|
| 275 | local dotd=${6:-} # optional .d file
|
| 276 |
|
| 277 | setglobal_compile_flags "$variant" "$more_cxx_flags" "$dotd"
|
| 278 |
|
| 279 | case $out in
|
| 280 | _build/preprocessed/*)
|
| 281 | flags="$flags -E"
|
| 282 | ;;
|
| 283 |
|
| 284 | # DISABLE spew for mycpp-generated code. mycpp/pea could flag this at the
|
| 285 | # PYTHON level, rather than doing it at the C++ level.
|
| 286 | _build/obj/*/_gen/bin/oils_for_unix.mycpp*.o)
|
| 287 | flags="$flags -Wno-unused-variable -Wno-unused-but-set-variable"
|
| 288 | ;;
|
| 289 | esac
|
| 290 |
|
| 291 | if test "$compiler" = 'clang'; then
|
| 292 | # 2024-08 - Clang needs -stdlib=libc++ for some reason
|
| 293 | # https://stackoverflow.com/questions/26333823/clang-doesnt-see-basic-headers
|
| 294 | # https://stackoverflow.com/questions/19774778/when-is-it-necessary-to-use-the-flag-stdlib-libstdc
|
| 295 |
|
| 296 | # But don't do it for clang-coverage* builds, because the CI machine
|
| 297 | # doesn't like it? This makes it fail on the release machine - sigh
|
| 298 | case $variant in
|
| 299 | coverage*) ;; # include coverage+bumpleak
|
| 300 | *) flags="$flags -stdlib=libc++" ;;
|
| 301 | esac
|
| 302 |
|
| 303 | # TODO: exactly when is -fPIC needed? Clang needs it sometimes?
|
| 304 | if test $variant != 'opt'; then
|
| 305 | flags="$flags -fPIC"
|
| 306 | fi
|
| 307 | fi
|
| 308 |
|
| 309 | # this flag is only valid in Clang, doesn't work in continuous build
|
| 310 | if test "$compiler" = 'clang'; then
|
| 311 | flags="$flags -ferror-limit=10"
|
| 312 | fi
|
| 313 |
|
| 314 | setglobal_cxx $compiler
|
| 315 |
|
| 316 | # The command we want to run. Not using arrays because this is POSIX shell
|
| 317 | set -- "$cxx" $flags -o "$out" -c "$in"
|
| 318 |
|
| 319 | if test -n "${OILS_CXX_VERBOSE:-}"; then
|
| 320 | echo '__' "$@" >&2
|
| 321 | fi
|
| 322 |
|
| 323 | # Maybe add timing prefix
|
| 324 | if test -n "${TIME_TSV_OUT:-}"; then
|
| 325 | set -- \
|
| 326 | benchmarks/time_.py --tsv \
|
| 327 | --out "$TIME_TSV_OUT" --append \
|
| 328 | --rusage --field compile_one --field $out -- \
|
| 329 | "$@"
|
| 330 | else
|
| 331 | # Show timing info on the most expensive translation unit
|
| 332 | case $in in
|
| 333 | # exclude *.mycpp-main.cc
|
| 334 | */oils_for_unix.mycpp.*|*/oils_for_unix.mycpp-souffle.*)
|
| 335 | # Must have external 'time', and it must have -f
|
| 336 | # Notes: OS X has no time -f
|
| 337 | # busybox time supports -f but not --format.
|
| 338 | case ${BASH_VERSION:-} in
|
| 339 | 3*)
|
| 340 | # bash 3.2.x on macos has a bizarre bug that leads to weird
|
| 341 | # interactions between errexit and `command time -f` when this
|
| 342 | # function gets run as a background job (like it does in the
|
| 343 | # tarball build script). Don't bother timing in this case.
|
| 344 | ;;
|
| 345 | *)
|
| 346 | # Workaround for mksh bug on issue #2310 - use 'env' instead of 'time'
|
| 347 | if env time -f '%e %M' true 2>/dev/null; then
|
| 348 | set -- \
|
| 349 | env time -f "$out { elapsed: %e, max_RSS: %M }" -- \
|
| 350 | "$@"
|
| 351 | fi
|
| 352 | ;;
|
| 353 | esac
|
| 354 | ;;
|
| 355 | esac
|
| 356 | fi
|
| 357 |
|
| 358 | # Run the command
|
| 359 | "$@"
|
| 360 | }
|
| 361 |
|
| 362 | link() {
|
| 363 | ### Link a binary. Invoked by build.ninja
|
| 364 |
|
| 365 | local compiler=$1
|
| 366 | local variant=$2
|
| 367 | local more_link_flags=$3 # Used by Souffle datalog rules
|
| 368 | local out=$4
|
| 369 | shift 4
|
| 370 | # rest are inputs
|
| 371 |
|
| 372 | setglobal_link_flags "$variant" "$more_link_flags"
|
| 373 |
|
| 374 | setglobal_cxx "$compiler"
|
| 375 |
|
| 376 | if test "$compiler" = 'clang'; then
|
| 377 | case $variant in
|
| 378 | coverage*) ;; # include coverage+bumpleak
|
| 379 | *) link_flags="$link_flags -stdlib=libc++"
|
| 380 | esac
|
| 381 | fi
|
| 382 |
|
| 383 | # The command we want to run. Not using arrays because this is POSIX shell
|
| 384 | #
|
| 385 | # IMPORTANT: Flags like -ltcmalloc have to come AFTER objects! Weird but
|
| 386 | # true.
|
| 387 | set -- "$cxx" -o "$out" "$@" $link_flags
|
| 388 |
|
| 389 | if test -n "${OILS_CXX_VERBOSE:-}"; then
|
| 390 | echo '__' "$@" >&2
|
| 391 | fi
|
| 392 |
|
| 393 | # Maybe add timing prefix
|
| 394 | if test -n "${TIME_TSV_OUT:-}"; then
|
| 395 | set -- \
|
| 396 | benchmarks/time_.py --tsv \
|
| 397 | --out "$TIME_TSV_OUT" --append \
|
| 398 | --rusage --field link --field $out -- \
|
| 399 | "$@"
|
| 400 | fi
|
| 401 |
|
| 402 | # Run the command
|
| 403 | "$@"
|
| 404 | }
|
| 405 |
|
| 406 | strip_() {
|
| 407 | ### Invoked by ninja
|
| 408 |
|
| 409 | local in=$1
|
| 410 | local stripped=$2
|
| 411 | local symbols=${3:-}
|
| 412 |
|
| 413 | strip -o $stripped $in
|
| 414 |
|
| 415 | if test -n "$symbols"; then
|
| 416 | objcopy --only-keep-debug $in $symbols
|
| 417 | objcopy --add-gnu-debuglink=$symbols $stripped
|
| 418 | fi
|
| 419 | }
|
| 420 |
|
| 421 | symlink() {
|
| 422 | local out=$1
|
| 423 | local symlink_val=$2
|
| 424 |
|
| 425 | ln -s -f -v "$symlink_val" "$out"
|
| 426 | }
|
| 427 |
|
| 428 | # test/cpp-unit.sh sources this
|
| 429 | if test $(basename $0) = 'ninja-rules-cpp.sh'; then
|
| 430 | "$@"
|
| 431 | fi
|