OILS / build / ninja-rules-py.sh View on Github | oilshell.org

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