aboutsummaryrefslogtreecommitdiff
path: root/libbutl/builtin.mxx
blob: a99d6f41644ac36b8e6be5c88de8d86994f5cfa8 (plain)
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// file      : libbutl/builtin.mxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#ifndef __cpp_modules_ts
#pragma once
#endif

// C includes.

#ifndef __cpp_lib_modules_ts
#include <map>
#include <mutex>
#include <string>
#include <vector>
#include <thread>
#include <chrono>
#include <memory>             // unique_ptr
#include <cstddef>            // size_t
#include <utility>            // move()
#include <cstdint>            // uint8_t
#include <functional>
#include <condition_variable>
#endif

// Other includes.

#ifdef __cpp_modules_ts
export module butl.builtin;
#ifdef __cpp_lib_modules_ts
import std.core;
import std.threading;
#endif
import butl.path;
import butl.fdstream;
import butl.timestamp;
#else
#include <libbutl/path.mxx>
#include <libbutl/fdstream.mxx>
#include <libbutl/timestamp.mxx>
#endif

#include <libbutl/export.hxx>

LIBBUTL_MODEXPORT namespace butl
{
  // A process/thread-like object representing a running builtin.
  //
  // For now, instead of allocating the result storage dynamically, we expect
  // it to be provided by the caller (allocating it dynamically would be
  // wasteful for synchronous builtins).
  //
  class LIBBUTL_SYMEXPORT builtin
  {
  public:
    // Wait for the builtin to complete and return its exit code. This
    // function can be called multiple times.
    //
    std::uint8_t
    wait ();

    // Return the same result as wait() if the builtin has already completed
    // and nullopt otherwise.
    //
    optional<std::uint8_t>
    try_wait ();

    // Wait for the builtin to complete for up to the specified time duration.
    // Return the same result as wait() if the builtin has completed in this
    // timeframe and nullopt otherwise.
    //
    template <typename R, typename P>
    optional<std::uint8_t>
    timed_wait (const std::chrono::duration<R, P>&);

    ~builtin () {if (state_ != nullptr) state_->thread.join ();}

  public:
    struct async_state
    {
      bool finished = false;
      std::mutex mutex;
      std::condition_variable condv;
      std::thread thread;

      // Note that we can't use std::function as an argument type to get rid
      // of the template since std::function can only be instantiated with a
      // copy-constructible function and that's too restrictive for us (won't
      // be able to capture auto_fd by value in a lambda, etc).
      //
      template <typename F>
      explicit
      async_state (F);
    };

    builtin (std::uint8_t& r, std::unique_ptr<async_state>&& s = nullptr)
        : result_ (r), state_ (move (s)) {}

    builtin (builtin&&) = default;

  private:
    std::uint8_t& result_;
    std::unique_ptr<async_state> state_;
  };

  // Builtin execution callbacks that can be used for checking/handling the
  // filesystem entries being acted upon (enforcing that they are sub-entries
  // of some "working" directory, registering cleanups for new entries, etc)
  // and for providing custom implementations for some functions used by
  // builtins.
  //
  // Note that the filesystem paths passed to the callbacks are absolute and
  // normalized with directories distinguished from non-directories based on
  // the lexical representation (presence of the trailing directory separator;
  // use path::to_directory() to check).
  //
  // Also note that builtins catch any exceptions that may be thrown by the
  // callbacks and, if that's the case, issue diagnostics and exit with the
  // non-zero status.
  //
  struct builtin_callbacks
  {
    // If specified, called before (pre is true) and after (pre is false) a
    // new filesystem entry is created or an existing one is re-created or
    // updated.
    //
    using create_hook = void (const path&, bool pre);

    std::function<create_hook> create;

    // If specified, called before (pre is true) and after (pre is false) a
    // filesystem entry is moved. The force argument is true if the builtin is
    // executed with the --force option.
    //
    using move_hook = void (const path& from,
                            const path& to,
                            bool force,
                            bool pre);

    std::function<move_hook> move;

    // If specified, called before (pre is true) and after (pre is false) a
    // filesystem entry is removed. The force argument is true if the builtin
    // is executed with the --force option.
    //
    using remove_hook = void (const path&, bool force, bool pre);

    std::function<remove_hook> remove;

    // If specified, called on encountering an unknown option passing the
    // argument list and the position of the option in question. Return the
    // number of parsed arguments.
    //
    using parse_option_function =
      std::size_t (const std::vector<std::string>&, std::size_t);

    std::function<parse_option_function> parse_option;

    // If specified, called by the sleep builtin instead of the default
    // implementation.
    //
    using sleep_function = void (const duration&);

    std::function<sleep_function> sleep;

    explicit
    builtin_callbacks (std::function<create_hook>           c = {},
                       std::function<move_hook>             m = {},
                       std::function<remove_hook>           r = {},
                       std::function<parse_option_function> p = {},
                       std::function<sleep_function>        s = {})
        : create (std::move (c)),
          move (std::move (m)),
          remove (std::move (r)),
          parse_option (std::move (p)),
          sleep (std::move (s)) {}

    explicit
    builtin_callbacks (std::function<sleep_function> sl)
        : sleep (std::move (sl)) {}
  };

  // Start a builtin command. Use the current process' standard streams for
  // the unopened in, out, and err file descriptors. Use the process' current
  // working directory unless an alternative is specified. Throw
  // std::system_error on failure.
  //
  // Note that unlike argc/argv, args don't include the program name.
  //
  using builtin_function = builtin (std::uint8_t& result,
                                    const std::vector<std::string>& args,
                                    auto_fd in, auto_fd out, auto_fd err,
                                    const dir_path& cwd,
                                    const builtin_callbacks&);

  // Builtin function and weight.
  //
  // The weight between 0 and 2 reflects the builtin's contribution to the
  // containing script semantics with 0 being the lowest/ignore. Current
  // mapping is as follows:
  //
  // 0 - non-contributing (true, false)
  // 1 - non-creative     (rm, rmdir, sleep, test)
  // 2 - creative         (any builtin that may produce output)
  //
  // If the function is NULL, then the builtin has an external implementation
  // and should be executed by running the program with this name.
  //
  struct builtin_info
  {
    builtin_function* function;
    uint8_t           weight;
  };

  class builtin_map: public std::map<std::string, builtin_info>
  {
  public:
    using base = std::map<std::string, builtin_info>;
    using base::base;

    // Return NULL if not a builtin.
    //
    const builtin_info*
    find (const std::string&) const;
  };

  // Asynchronously run a function as if it was a builtin. The function must
  // have the std::uint8_t() signature and not throw exceptions.
  //
  // Note that using std::function as an argument type would be too
  // restrictive (see above).
  //
  template <typename F>
  builtin
  pseudo_builtin (std::uint8_t&, F);

  LIBBUTL_SYMEXPORT extern const builtin_map builtins;
}

#include <libbutl/builtin.ixx>