| 1 | #!/usr/bin/env bash
 | 
| 2 | #
 | 
| 3 | # Ninja rules for translating Python to C++.
 | 
| 4 | #
 | 
| 5 | # Usage:
 | 
| 6 | #   build/ninja-rules-py.sh <function name>
 | 
| 7 | #
 | 
| 8 | # Env variables:
 | 
| 9 | #   EXTRA_MYCPP_ARGS - passed to mycpp_main
 | 
| 10 | 
 | 
| 11 | set -o nounset
 | 
| 12 | set -o pipefail
 | 
| 13 | set -o errexit
 | 
| 14 | 
 | 
| 15 | REPO_ROOT=$(cd "$(dirname $0)/.."; pwd)
 | 
| 16 | 
 | 
| 17 | source build/dev-shell.sh  # python2 in $PATH
 | 
| 18 | source mycpp/common-vars.sh  # MYPY_REPO
 | 
| 19 | source $REPO_ROOT/test/tsv-lib.sh  # time-tsv
 | 
| 20 | 
 | 
| 21 | example-main-wrapper() {
 | 
| 22 |   ### Used by mycpp/examples
 | 
| 23 | 
 | 
| 24 |   local main_module=${1:-fib_iter}
 | 
| 25 | 
 | 
| 26 |   cat <<EOF
 | 
| 27 | int main(int argc, char **argv) {
 | 
| 28 |   gHeap.Init();
 | 
| 29 | 
 | 
| 30 |   char* b = getenv("BENCHMARK");
 | 
| 31 |   if (b && strlen(b)) {  // match Python's logic
 | 
| 32 |     fprintf(stderr, "Benchmarking...\\n");
 | 
| 33 |     $main_module::run_benchmarks();
 | 
| 34 |   } else {
 | 
| 35 |     $main_module::run_tests();
 | 
| 36 |   }
 | 
| 37 | 
 | 
| 38 |   gHeap.CleanProcessExit();
 | 
| 39 | }
 | 
| 40 | EOF
 | 
| 41 | }
 | 
| 42 | 
 | 
| 43 | main-wrapper() {
 | 
| 44 |   ### Used by oils-for-unix and yaks
 | 
| 45 |   local main_namespace=$1
 | 
| 46 | 
 | 
| 47 |   cat <<EOF
 | 
| 48 | int main(int argc, char **argv) {
 | 
| 49 |   mylib::InitCppOnly();  // Initializes gHeap
 | 
| 50 | 
 | 
| 51 |   auto* args = Alloc<List<BigStr*>>();
 | 
| 52 |   for (int i = 0; i < argc; ++i) {
 | 
| 53 |     args->append(StrFromC(argv[i]));
 | 
| 54 |   }
 | 
| 55 | 
 | 
| 56 |   int status = $main_namespace::main(args);
 | 
| 57 | 
 | 
| 58 |   gHeap.ProcessExit();
 | 
| 59 | 
 | 
| 60 |   return status;
 | 
| 61 | }
 | 
| 62 | EOF
 | 
| 63 | }
 | 
| 64 | 
 | 
| 65 | gen-oils-for-unix() {
 | 
| 66 |   local main_name=$1
 | 
| 67 |   local out_prefix=$2
 | 
| 68 |   local preamble=$3
 | 
| 69 |   shift 3  # rest are inputs
 | 
| 70 | 
 | 
| 71 |   # Put it in _build/tmp so it's not in the tarball
 | 
| 72 |   local tmp=_build/tmp
 | 
| 73 |   mkdir -p $tmp
 | 
| 74 | 
 | 
| 75 |   local raw_cc=$tmp/${main_name}_raw.cc
 | 
| 76 |   local cc_out=${out_prefix}.cc
 | 
| 77 | 
 | 
| 78 |   local raw_header=$tmp/${main_name}_raw.h
 | 
| 79 |   local header_out=${out_prefix}.h
 | 
| 80 | 
 | 
| 81 |   local mypypath="$REPO_ROOT:$REPO_ROOT/pyext"
 | 
| 82 | 
 | 
| 83 |   _bin/shwrap/mycpp_main $mypypath $raw_cc \
 | 
| 84 |     --header-out $raw_header \
 | 
| 85 |     ${EXTRA_MYCPP_ARGS:-} \
 | 
| 86 |     "$@"
 | 
| 87 | 
 | 
| 88 |   # oils_for_unix -> OILS_FOR_UNIX_MYCPP_H'
 | 
| 89 |   local guard=${main_name^^}_MYCPP_H
 | 
| 90 | 
 | 
| 91 |   { echo "// $main_name.mycpp.h: translated from Python by mycpp"
 | 
| 92 |     echo
 | 
| 93 |     echo "#ifndef $guard"
 | 
| 94 |     echo "#define $guard"
 | 
| 95 | 
 | 
| 96 |     cat $raw_header
 | 
| 97 | 
 | 
| 98 |     echo "#endif  // $guard"
 | 
| 99 | 
 | 
| 100 |   } > $header_out
 | 
| 101 | 
 | 
| 102 |   { cat <<EOF
 | 
| 103 | // $main_name.mycpp.cc: translated from Python by mycpp
 | 
| 104 | 
 | 
| 105 | // #include "$header_out"
 | 
| 106 | 
 | 
| 107 | #include "$preamble"
 | 
| 108 | EOF
 | 
| 109 | 
 | 
| 110 |     cat $raw_cc
 | 
| 111 | 
 | 
| 112 |     main-wrapper $main_name
 | 
| 113 |   } > $cc_out
 | 
| 114 | }
 | 
| 115 | 
 | 
| 116 | print-wrap-cc() {
 | 
| 117 |   local translator=$1
 | 
| 118 |   local main_module=$2
 | 
| 119 |   local in=$3
 | 
| 120 |   local preamble_path=$4
 | 
| 121 | 
 | 
| 122 |    echo "// examples/$main_module translated by $translator"
 | 
| 123 |    echo
 | 
| 124 | 
 | 
| 125 |    if test -f "$preamble_path"; then
 | 
| 126 |      echo "#include \"$preamble_path\""
 | 
| 127 |    fi
 | 
| 128 | 
 | 
| 129 |    cat $in
 | 
| 130 | 
 | 
| 131 |    # main() function
 | 
| 132 |    case $translator in
 | 
| 133 |      mycpp)
 | 
| 134 |        example-main-wrapper $main_module
 | 
| 135 |        ;;
 | 
| 136 |      yaks)
 | 
| 137 |        main-wrapper $main_module
 | 
| 138 |        ;;
 | 
| 139 |      pea)
 | 
| 140 |         echo '#include <stdio.h>'
 | 
| 141 |         echo 'int main() { printf("stub\n"); return 1; }'
 | 
| 142 |        ;;
 | 
| 143 |      (*)
 | 
| 144 |        die "Invalid translator $translator"
 | 
| 145 |        ;;
 | 
| 146 |    esac
 | 
| 147 | }
 | 
| 148 | 
 | 
| 149 | wrap-cc() {
 | 
| 150 |   local out=$1
 | 
| 151 |   shift
 | 
| 152 | 
 | 
| 153 |   # $translator $main_module $in $preamble_path
 | 
| 154 |   print-wrap-cc "$@" > $out
 | 
| 155 | }
 | 
| 156 | 
 | 
| 157 | # TODO: Move mycpp/example tasks out of Ninja since timing is not a VALUE.  It
 | 
| 158 | # depends on the machine, can be done more than once, etc.
 | 
| 159 | 
 | 
| 160 | task() {
 | 
| 161 |   local bin=$1  # Run this
 | 
| 162 |   local task_out=$2
 | 
| 163 |   local log_out=$3
 | 
| 164 | 
 | 
| 165 |   shift 3
 | 
| 166 |   # The rest of the args are passed as flags to time-tsv
 | 
| 167 | 
 | 
| 168 |   case $bin in
 | 
| 169 |     (mycpp/examples/*.py)
 | 
| 170 |       # we import mycpp.mylib
 | 
| 171 |       export PYTHONPATH="$REPO_ROOT/mycpp:$REPO_ROOT/vendor:$REPO_ROOT"
 | 
| 172 |       ;;
 | 
| 173 |   esac
 | 
| 174 | 
 | 
| 175 |   case $task_out in
 | 
| 176 |     (_test/tasks/benchmark/*)
 | 
| 177 |       export BENCHMARK=1
 | 
| 178 |       ;;
 | 
| 179 |   esac
 | 
| 180 | 
 | 
| 181 |   time-tsv -o $task_out --rusage "$@" --field $bin --field $task_out -- \
 | 
| 182 |     $bin >$log_out 2>&1
 | 
| 183 | }
 | 
| 184 | 
 | 
| 185 | example-task() {
 | 
| 186 |   ### Run a program in the examples/ dir, either in Python or C++
 | 
| 187 | 
 | 
| 188 |   local name=$1  # e.g. 'fib_iter'
 | 
| 189 |   local impl=$2  # 'Python' or 'C++'
 | 
| 190 | 
 | 
| 191 |   local bin=$3  # Run this
 | 
| 192 |   local task_out=$4
 | 
| 193 |   local log_out=$5
 | 
| 194 | 
 | 
| 195 |   task $bin $task_out $log_out --field $name --field $impl
 | 
| 196 | }
 | 
| 197 | 
 | 
| 198 | benchmark-table() {
 | 
| 199 |   local out=$1
 | 
| 200 |   shift
 | 
| 201 | 
 | 
| 202 |   # TODO: Use QTT header with types?
 | 
| 203 |   { time-tsv --print-header --rusage \
 | 
| 204 |       --field example_name --field impl \
 | 
| 205 |       --field bin --field task_out 
 | 
| 206 | 
 | 
| 207 |     # Concatenate task files
 | 
| 208 |     cat "$@" 
 | 
| 209 |   } > $out
 | 
| 210 | }
 | 
| 211 | 
 | 
| 212 | # TODO: No longer works.  This is called by ninja mycpp-check
 | 
| 213 | # I think it's giving strict warnings.
 | 
| 214 | mypy() {
 | 
| 215 |   ( source $MYCPP_VENV/bin/activate
 | 
| 216 |     # Don't need this since the virtualenv we created with it?
 | 
| 217 |     # source build/dev-shell.sh
 | 
| 218 |     PYTHONPATH=$MYPY_REPO python3 -m mypy "$@";
 | 
| 219 |   )
 | 
| 220 | }
 | 
| 221 | 
 | 
| 222 | typecheck() {
 | 
| 223 |   ### Typecheck without translation
 | 
| 224 |   local main_py=$1
 | 
| 225 |   local out=$2
 | 
| 226 |   local skip_imports=${3:-}
 | 
| 227 | 
 | 
| 228 |   if test -n "$skip_imports"; then
 | 
| 229 |     local more_flags='--follow-imports=silent'
 | 
| 230 |   else
 | 
| 231 |     local more_flags=''
 | 
| 232 |   fi
 | 
| 233 | 
 | 
| 234 |   # $more_flags can be empty
 | 
| 235 |   MYPYPATH="$REPO_ROOT:$REPO_ROOT/mycpp" \
 | 
| 236 |     mypy --py2 --strict $more_flags $main_py > $out
 | 
| 237 | }
 | 
| 238 | 
 | 
| 239 | logs-equal() {
 | 
| 240 |   local out=$1
 | 
| 241 |   shift
 | 
| 242 | 
 | 
| 243 |   mycpp/compare_pairs.py "$@" | tee $out
 | 
| 244 | }
 | 
| 245 | 
 | 
| 246 | #
 | 
| 247 | # shwrap rules
 | 
| 248 | #
 | 
| 249 | 
 | 
| 250 | shwrap-py() {
 | 
| 251 |   ### Part of shell template for Python executables
 | 
| 252 | 
 | 
| 253 |   local main=$1
 | 
| 254 |   echo 'PYTHONPATH=$REPO_ROOT:$REPO_ROOT/vendor exec $REPO_ROOT/'$main' "$@"'
 | 
| 255 | }
 | 
| 256 | 
 | 
| 257 | shwrap-mycpp() {
 | 
| 258 |   ### Part of shell template for mycpp executable
 | 
| 259 | 
 | 
| 260 |   cat <<'EOF'
 | 
| 261 | MYPYPATH=$1    # e.g. $REPO_ROOT/mycpp
 | 
| 262 | out=$2
 | 
| 263 | shift 2
 | 
| 264 | 
 | 
| 265 | # Modifies $PATH; do not combine
 | 
| 266 | . build/dev-shell.sh
 | 
| 267 | 
 | 
| 268 | tmp=$out.tmp  # avoid creating partial files
 | 
| 269 | 
 | 
| 270 | MYPYPATH="$MYPYPATH" \
 | 
| 271 |   python3 mycpp/mycpp_main.py --cc-out $tmp "$@"
 | 
| 272 | status=$?
 | 
| 273 | 
 | 
| 274 | mv $tmp $out
 | 
| 275 | exit $status
 | 
| 276 | EOF
 | 
| 277 | }
 | 
| 278 | 
 | 
| 279 | shwrap-pea() {
 | 
| 280 |   ### Part of shell template for pea executable
 | 
| 281 | 
 | 
| 282 |   cat <<'EOF'
 | 
| 283 | MYPYPATH=$1    # e.g. $REPO_ROOT/mycpp
 | 
| 284 | out=$2
 | 
| 285 | shift 2
 | 
| 286 | 
 | 
| 287 | tmp=$out.tmp  # avoid creating partial files
 | 
| 288 | 
 | 
| 289 | PYTHONPATH="$REPO_ROOT:$MYPY_REPO" MYPYPATH="$MYPYPATH" \
 | 
| 290 |   python3 pea/pea_main.py cpp "$@" > $tmp
 | 
| 291 | status=$?
 | 
| 292 | 
 | 
| 293 | mv $tmp $out
 | 
| 294 | exit $status
 | 
| 295 | EOF
 | 
| 296 | }
 | 
| 297 | 
 | 
| 298 | print-shwrap() {
 | 
| 299 |   local template=$1
 | 
| 300 |   local unused=$2
 | 
| 301 |   shift 2
 | 
| 302 | 
 | 
| 303 |   cat << 'EOF'
 | 
| 304 | #!/bin/sh
 | 
| 305 | REPO_ROOT=$(cd "$(dirname $0)/../.."; pwd)
 | 
| 306 | . $REPO_ROOT/build/py2.sh
 | 
| 307 | EOF
 | 
| 308 | 
 | 
| 309 |   case $template in
 | 
| 310 |     (py)
 | 
| 311 |       local main=$1  # additional arg
 | 
| 312 |       shift
 | 
| 313 |       shwrap-py $main
 | 
| 314 |       ;;
 | 
| 315 |     (mycpp)
 | 
| 316 |       shwrap-mycpp
 | 
| 317 |       ;;
 | 
| 318 |     (pea)
 | 
| 319 |       shwrap-pea
 | 
| 320 |       ;;
 | 
| 321 |     (*)
 | 
| 322 |       die "Invalid template '$template'"
 | 
| 323 |       ;;
 | 
| 324 |   esac
 | 
| 325 | 
 | 
| 326 |   echo
 | 
| 327 |   echo '# DEPENDS ON:'
 | 
| 328 |   for dep in "$@"; do
 | 
| 329 |     echo "#   $dep"
 | 
| 330 |   done
 | 
| 331 | }
 | 
| 332 | 
 | 
| 333 | write-shwrap() {
 | 
| 334 |   ### Create a shell wrapper for a Python tool
 | 
| 335 | 
 | 
| 336 |   # Key point: if the Python code changes, then the C++ code should be
 | 
| 337 |   # regenerated and re-compiled
 | 
| 338 | 
 | 
| 339 |   local unused=$1
 | 
| 340 |   local stub_out=$2
 | 
| 341 | 
 | 
| 342 |   print-shwrap "$@" > $stub_out
 | 
| 343 |   chmod +x $stub_out
 | 
| 344 | }
 | 
| 345 | 
 | 
| 346 | # sourced by devtools/bin.sh
 | 
| 347 | if test $(basename $0) = 'ninja-rules-py.sh'; then
 | 
| 348 |   "$@"
 | 
| 349 | fi
 |