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

361 lines, 184 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 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"
108EOF
109
110 cat $raw_cc
111
112 main-wrapper $main_name
113 } > $cc_out
114}
115
116print-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
149wrap-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
160task() {
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
185example-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
198benchmark-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# Copied from devtools/types.sh
213
214MYPY_FLAGS='--strict --no-strict-optional'
215typecheck-files() {
216 echo "MYPY $@"
217
218 # TODO: Adjust path for mcypp/examples/modules.py
219 time MYPYPATH='.:pyext' python3 -m mypy --py2 --follow-imports=silent $MYPY_FLAGS "$@"
220}
221
222typecheck() {
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 # Similar to devtools/types.sh
235
236 local status=0
237
238 set +o errexit
239 typecheck-files $main_py > $out
240 status=$?
241 set -o errexit
242
243 if test $status != 0; then
244 echo "FAIL $main_py"
245 cat $out
246 fi
247
248 return $status
249}
250
251logs-equal() {
252 local out=$1
253 shift
254
255 mycpp/compare_pairs.py "$@" | tee $out
256}
257
258#
259# shwrap rules
260#
261
262shwrap-py() {
263 ### Part of shell template for Python executables
264
265 local main=$1
266 echo 'PYTHONPATH=$REPO_ROOT:$REPO_ROOT/vendor exec $REPO_ROOT/'$main' "$@"'
267}
268
269shwrap-mycpp() {
270 ### Part of shell template for mycpp executable
271
272 cat <<'EOF'
273MYPYPATH=$1 # e.g. $REPO_ROOT/mycpp
274out=$2
275shift 2
276
277# Modifies $PATH; do not combine
278. build/dev-shell.sh
279
280tmp=$out.tmp # avoid creating partial files
281
282MYPYPATH="$MYPYPATH" \
283 python3 mycpp/mycpp_main.py --cc-out $tmp "$@"
284status=$?
285
286mv $tmp $out
287exit $status
288EOF
289}
290
291shwrap-pea() {
292 ### Part of shell template for pea executable
293
294 cat <<'EOF'
295MYPYPATH=$1 # e.g. $REPO_ROOT/mycpp
296out=$2
297shift 2
298
299tmp=$out.tmp # avoid creating partial files
300
301PYTHONPATH="$REPO_ROOT:$TODO_MYPY_REPO" MYPYPATH="$MYPYPATH" \
302 python3 pea/pea_main.py cpp "$@" > $tmp
303status=$?
304
305mv $tmp $out
306exit $status
307EOF
308}
309
310print-shwrap() {
311 local template=$1
312 local unused=$2
313 shift 2
314
315 cat << 'EOF'
316#!/bin/sh
317REPO_ROOT=$(cd "$(dirname $0)/../.."; pwd)
318. $REPO_ROOT/build/py2.sh
319EOF
320
321 case $template in
322 (py)
323 local main=$1 # additional arg
324 shift
325 shwrap-py $main
326 ;;
327 (mycpp)
328 shwrap-mycpp
329 ;;
330 (pea)
331 shwrap-pea
332 ;;
333 (*)
334 die "Invalid template '$template'"
335 ;;
336 esac
337
338 echo
339 echo '# DEPENDS ON:'
340 for dep in "$@"; do
341 echo "# $dep"
342 done
343}
344
345write-shwrap() {
346 ### Create a shell wrapper for a Python tool
347
348 # Key point: if the Python code changes, then the C++ code should be
349 # regenerated and re-compiled
350
351 local unused=$1
352 local stub_out=$2
353
354 print-shwrap "$@" > $stub_out
355 chmod +x $stub_out
356}
357
358# sourced by devtools/bin.sh
359if test $(basename $0) = 'ninja-rules-py.sh'; then
360 "$@"
361fi