package catala

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file python.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
(* This file is part of the Catala build system, a specification language for
   tax and social benefits computation rules. Copyright (C) 2020-2025 Inria,
   contributors: Denis Merigoux <denis.merigoux@inria.fr>, Emile Rolley
   <emile.rolley@tuta.io>, Louis Gesbert <louis.gesbert@inria.fr>

   Licensed under the Apache License, Version 2.0 (the "License"); you may not
   use this file except in compliance with the License. You may obtain a copy of
   the License at

   http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   License for the specific language governing permissions and limitations under
   the License. *)

open Clerk_utils
open Catala_utils

let catala_flags_python = Var.make_vector "CATALA_FLAGS_PYTHON"
let python = Var.make_vector "PYTHON"

module Spec : Sig.Spec = struct
  open Var
  open File
  module Nj = Ninja_utils

  let name = "python"
  let src_extensions = ["py"]
  let module_extensions = ["py"]
  let obj_extension = "py"
  let all_obj_extensions = ["py"]
  let stdlib_subdir = ""

  let var_defs
      ~variables
      ~autotest
      ~use_default_flags
      ~test_flags
      ~include_dirs:_ =
    let catala_flags =
      Flags.catala_backend_flags ~autotest ~use_default_flags ~test_flags
        ~accepts_closure_conversion:true
    in
    let def = Flags.def ~variables in
    [def catala_flags_python (lazy catala_flags); def python (lazy ["python3"])]

  let[@ocamlformat "disable"] rules =
    [
      Nj.rule "catala-python"
        ~command:[!!catala_exe; Nj.Expr.Word name; !!catala_flags; !!catala_flags_python;
                  Nj.Expr.Word "-o"; !!output; Nj.Expr.Word "--"; !!input]
        ~description:[Nj.Expr.Word "<catala>"; Nj.Expr.Word name; Nj.Expr.Word "⇒"; !!output];
    ]

  let build_runtime ~config:_ ~stdbase =
    let python_base = stdbase / name / "catala_runtime" in
    let python_src = !runtime / name / "src" / "catala" in
    [
      Nj.build "phony"
        ~inputs:
          [
            Nj.Expr.Word (python_base -.- "py");
            Nj.Expr.Word (python_base /../ "dates.py");
            !!catala_exe;
          ]
        ~outputs:
          [
            Nj.Expr.Word "@python/runtime/src";
            Nj.Expr.Word "@python/runtime/obj";
          ];
      Nj.build "copy"
        ~inputs:[Nj.Expr.Word (python_src / "dates.py")]
        ~outputs:[Nj.Expr.Word (python_base /../ "dates.py")];
      Nj.build "copy"
        ~inputs:[Nj.Expr.Word (python_src / "catala_runtime.py")]
        ~outputs:[Nj.Expr.Word (python_base -.- "py")];
    ]

  let catala ?vars ~is_stdlib:_ ~inputs ~implicit_in ~has_scope_tests:_ =
    Seq.return
      (Nj.build "catala-python" ?vars ~inputs ~implicit_in
         ~outputs:[Common.target ~name "py"])

  let build_object _ = Seq.empty

  let runtime_dir : File.t Lazy.t =
    lazy File.(Lazy.force Poll.runtime_dir / name / "src" / "catala")

  let write_target_def_file ~config:_ ~info:_ ~dir target =
    let open File in
    File.with_out_channel (dir / "__init__.py") (fun oc ->
        Printf.fprintf oc "__all__ = [%s]\n"
          (String.concat ", " target.Clerk_config.tmodules));
    File.with_out_channel (dir / "py.typed") ignore

  let install_target ~config ~info target =
    Common.install_target_files ~name ~stdlib_subdir ~extensions:src_extensions
      ~config ~info target.Clerk_config.tname target ~copy_in:File.copy_in;
    write_target_def_file ~config ~info
      ~dir:(config.Clerk_cli.file.global.target_dir / name / target.tname)
      target

  let install_runtime ~config =
    let open File in
    let dir = config.Clerk_cli.file.global.target_dir / name / Scan.libcatala in
    remove dir;
    ensure_dir dir;
    copy_dir ()
      ~filter:(fun f -> Filename.check_suffix f ".py" && f <> "__init__.py")
      ~src:(Lazy.force Poll.stdlib_dir / name / "src" / "catala")
      ~dst:dir

  let write_project_def ~config:_ ~info:_ = ()

  let linking_command ~build_dir ~var_bindings:_ link_deps item target =
    (* a "linked" python module is a "Module.py" folder containing the module .py
     file along with the runtime and all dependencies, plus a __init__.py
     file *)
    let open File in
    let tdir = Filename.remove_extension target in
    remove tdir;
    ensure_dir tdir;
    List.iter
      (fun it ->
        let src =
          let f = Scan.target_file_name it in
          (build_dir / dirname f / "python" / basename f) ^ ".py"
        in
        copy_in ~src ~dir:tdir)
      (link_deps item);
    copy_in ~src:(target -.- "py") ~dir:tdir;
    close_out (open_out (tdir / "__init__.py"));
    []

  let run_artifact ~config ~var_bindings ~test ~trace ?scope ?quiet src =
    let open File in
    let build_dir = config.Clerk_cli.file.global.build_dir in
    let cmd =
      let base = Filename.basename (File.remove_extension src) in
      Var.get var_bindings python
      @ ["-m"; base ^ "." ^ base]
      @ Option.to_list scope
      @ (if test && not Global.options.debug then ["--test"] else [])
      @ (if Global.options.output_format = JSON then ["--json"] else [])
      @ if trace then ["--trace"] else []
    in
    let pythonpath =
      Backend_paths.pythonpath
        [
          build_dir / Scan.libcatala / "python";
          File.dirname src;
          Option.value ~default:"" (Sys.getenv_opt "PYTHONPATH");
        ]
    in
    Message.debug "Executing artifact: 'PYTHONPATH=%s %s'..." pythonpath
      (String.concat " " cmd);
    Clerk_cli.run_command_line ~setenv:["PYTHONPATH", pythonpath] ?quiet cmd
end

include Common.Make_backend (Spec)