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

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