OILS / build / ninja-rules-py.sh View on Github | oils.pub

349 lines, 153 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# _bin/shwrap/mycpp_main respects EXTRA_MYCPP_ARGS
10# for --stack-roots-warn 16 in CI
11
12set -o nounset
13set -o pipefail
14set -o errexit
15
16REPO_ROOT=$(cd "$(dirname $0)/.."; pwd)
17
18source build/dev-shell.sh # python2 in $PATH
19#source devtools/types.sh # typecheck-files
20source $REPO_ROOT/test/tsv-lib.sh # time-tsv
21
22die() {
23 echo "$@" >& 2
24 exit 1
25}
26
27example-main-wrapper() {
28 ### Used by mycpp/examples
29 local name_namespace=${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 $name_namespace::run_benchmarks();
39 } else {
40 $name_namespace::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
70windows-main() {
71 local main_namespace=$1
72
73 cat <<EOF
74int main(int argc, char **argv) {
75 gHeap.Init();
76
77 auto* args = Alloc<List<BigStr*>>();
78 for (int i = 0; i < argc; ++i) {
79 args->append(StrFromC(argv[i]));
80 }
81
82 int status = $main_namespace::main(args);
83
84 gHeap.ProcessExit();
85
86 return status;
87}
88EOF
89}
90
91print-wrap-cc() {
92 local out=$1
93 local main_func=$2
94 local main_namespace=$3
95 local in=$4
96 local preamble_path=$5
97
98 echo "// $out - generated from Python source code"
99 echo
100
101 if test -f "$preamble_path"; then
102 echo "#include \"$preamble_path\""
103 else
104 # the default preamble, for mycpp/examples
105 echo '#include "mycpp/runtime.h"'
106 fi
107 echo
108
109 cat $in
110
111 # example-main-wrapper, main-wrapper, etc.
112 $main_func $main_namespace
113}
114
115wrap-cc() {
116 local out=$1
117
118 # $translator $main_namespace $in $preamble_path
119 print-wrap-cc "$@" > $out
120}
121
122# TODO: Move mycpp/example tasks out of Ninja since timing is not a VALUE. It
123# depends on the machine, can be done more than once, etc.
124
125task() {
126 local bin=$1 # Run this
127 local task_out=$2
128 local log_out=$3
129
130 shift 3
131 # The rest of the args are passed as flags to time-tsv
132
133 case $bin in
134 (mycpp/examples/*.py)
135 # we import mycpp.mylib
136 export PYTHONPATH="$REPO_ROOT/mycpp:$REPO_ROOT/vendor:$REPO_ROOT"
137 ;;
138 esac
139
140 case $task_out in
141 (_test/tasks/benchmark/*)
142 export BENCHMARK=1
143 ;;
144 esac
145
146 time-tsv -o $task_out --rusage "$@" --field $bin --field $task_out -- \
147 $bin >$log_out 2>&1
148}
149
150example-task() {
151 ### Run a program in the examples/ dir, either in Python or C++
152
153 local name=$1 # e.g. 'fib_iter'
154 local impl=$2 # 'Python' or 'C++'
155
156 local bin=$3 # Run this
157 local task_out=$4
158 local log_out=$5
159
160 task $bin $task_out $log_out --field $name --field $impl
161}
162
163benchmark-table() {
164 local out=$1
165 shift
166
167 # TODO: Use QTT header with types?
168 { time-tsv --print-header --rusage \
169 --field example_name --field impl \
170 --field bin --field task_out
171
172 # Concatenate task files
173 cat "$@"
174 } > $out
175}
176
177# Copied from devtools/types.sh
178
179MYPY_FLAGS='--strict --no-strict-optional'
180typecheck-files() {
181 echo "MYPY $@"
182
183 # TODO: Adjust path for mcypp/examples/modules.py
184 time MYPYPATH='.:pyext' python3 -m mypy --py2 --follow-imports=silent $MYPY_FLAGS "$@"
185}
186
187typecheck() {
188 ### Typecheck without translation
189 local main_py=$1
190 local out=$2
191 local skip_imports=${3:-}
192
193 if test -n "$skip_imports"; then
194 local more_flags='--follow-imports=silent'
195 else
196 local more_flags=''
197 fi
198
199 # Similar to devtools/types.sh
200
201 local status=0
202
203 set +o errexit
204 typecheck-files $main_py > $out
205 status=$?
206 set -o errexit
207
208 if test $status != 0; then
209 echo "FAIL $main_py"
210 cat $out
211 fi
212
213 return $status
214}
215
216logs-equal() {
217 local out=$1
218 shift
219
220 mycpp/compare_pairs.py "$@" | tee $out
221}
222
223#
224# shwrap rules
225#
226
227shwrap-py() {
228 ### Part of shell template for Python executables
229
230 local main=$1
231 echo 'PYTHONPATH=$REPO_ROOT:$REPO_ROOT/vendor exec $REPO_ROOT/'$main' "$@"'
232}
233
234shwrap-mycpp() {
235 ### Part of shell template for mycpp executable
236
237 cat <<'EOF'
238MYPYPATH=$1 # e.g. $REPO_ROOT/mycpp
239out=$2
240shift 2
241
242# Modifies $PATH; do not combine
243. build/dev-shell.sh
244
245tmp=$out.tmp # avoid creating partial files
246
247# The command we want to run
248# EXTRA_MYCPP_ARGS is for --stack-root-warn 16, in Soil CI
249set -- python3 mycpp/mycpp_main.py --cc-out $tmp ${EXTRA_MYCPP_ARGS:-} "$@"
250
251# If 'time' is on the system, add timing info. (It's not present on some
252# Debian CI images)
253if which time >/dev/null; then
254 # 'busybox time' supports -f but not --format.
255 set -- \
256 time -f 'MYCPP { elapsed: %e, max_RSS: %M }' -- \
257 "$@"
258fi
259
260MYPYPATH="$MYPYPATH" "$@"
261status=$?
262
263mv $tmp $out
264exit $status
265EOF
266}
267
268shwrap-pea() {
269 ### Part of shell template for pea executable
270
271 cat <<'EOF'
272MYPYPATH=$1 # e.g. $REPO_ROOT/mycpp
273out=$2
274shift 2
275
276tmp=$out.tmp # avoid creating partial files
277
278# copied from build/dev-shell.sh
279
280USER_WEDGE_DIR=~/wedge/oils-for-unix.org
281
282MYPY_VERSION=0.780
283MYPY_WEDGE=$USER_WEDGE_DIR/pkg/mypy/$MYPY_VERSION
284
285PY3_LIBS_VERSION=2023-03-04
286site_packages=lib/python3.10/site-packages
287PY3_LIBS_WEDGE=$USER_WEDGE_DIR/pkg/py3-libs/$PY3_LIBS_VERSION/$site_packages
288
289PYTHONPATH="$REPO_ROOT:$MYPY_WEDGE:$PY3_LIBS_WEDGE" MYPYPATH="$MYPYPATH" \
290 python3 pea/pea_main.py mycpp "$@" > $tmp
291status=$?
292
293mv $tmp $out
294exit $status
295EOF
296}
297
298print-shwrap() {
299 local template=$1
300 local unused=$2
301 shift 2
302
303 cat << 'EOF'
304#!/bin/sh
305REPO_ROOT=$(cd "$(dirname $0)/../.."; pwd)
306. $REPO_ROOT/build/py2.sh
307EOF
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
333write-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
347if test $(basename $0) = 'ninja-rules-py.sh'; then
348 "$@"
349fi