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

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