aboutsummaryrefslogtreecommitdiff
path: root/src/libstore/build/derivation-goal.hh
blob: 6dd58afd2445fc555c075c4840b6c735fc1fa895 (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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
#pragma once
///@file

#include "notifying-counter.hh"
#include "parsed-derivations.hh"
#include "lock.hh"
#include "outputs-spec.hh"
#include "store-api.hh"
#include "pathlocks.hh"
#include "goal.hh"
#include <kj/time.h>

namespace nix {

using std::map;

struct HookInstance;

struct HookReplyBase {
    struct [[nodiscard]] Accept {
        kj::Promise<Outcome<void, Goal::WorkResult>> promise;
    };
    struct [[nodiscard]] Decline {};
    struct [[nodiscard]] Postpone {};
};

struct [[nodiscard]] HookReply
    : HookReplyBase,
      std::variant<HookReplyBase::Accept, HookReplyBase::Decline, HookReplyBase::Postpone>
{
    HookReply() = delete;
    using variant::variant;
};

/**
 * Unless we are repairing, we don't both to test validity and just assume it,
 * so the choices are `Absent` or `Valid`.
 */
enum struct PathStatus {
    Corrupt,
    Absent,
    Valid,
};

struct InitialOutputStatus {
    StorePath path;
    PathStatus status;
    /**
     * Valid in the store, and additionally non-corrupt if we are repairing
     */
    bool isValid() const {
        return status == PathStatus::Valid;
    }
    /**
     * Merely present, allowed to be corrupt
     */
    bool isPresent() const {
        return status == PathStatus::Corrupt
            || status == PathStatus::Valid;
    }
};

struct InitialOutput {
    bool wanted;
    Hash outputHash;
    std::optional<InitialOutputStatus> known = {};
};

/**
 * A goal for building some or all of the outputs of a derivation.
 */
struct DerivationGoal : public Goal
{
    struct InputStream;

    /**
      * Whether this goal has completed. Completed goals can not be
      * asked for more outputs, a new goal must be created instead.
      */
    bool isDone = false;

    /**
     * Whether to use an on-disk .drv file.
     */
    bool useDerivation;

    /** The path of the derivation. */
    StorePath drvPath;

    /**
     * The goal for the corresponding resolved derivation
     */
    std::shared_ptr<DerivationGoal> resolvedDrvGoal;

    /**
     * The specific outputs that we need to build.
     */
    OutputsSpec wantedOutputs;

    /**
     * Mapping from input derivations + output names to actual store
     * paths. This is filled in by waiteeDone() as each dependency
     * finishes, before inputsRealised() is reached.
     */
    std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;

    /**
     * See `needRestart`; just for that field.
     */
    enum struct NeedRestartForMoreOutputs {
        /**
         * The goal state machine is progressing based on the current value of
         * `wantedOutputs. No actions are needed.
         */
        OutputsUnmodifedDontNeed,
        /**
         * `wantedOutputs` has been extended, but the state machine is
         * proceeding according to its old value, so we need to restart.
         */
        OutputsAddedDoNeed,
        /**
         * The goal state machine has progressed to the point of doing a build,
         * in which case all outputs will be produced, so extensions to
         * `wantedOutputs` no longer require a restart.
         */
        BuildInProgressWillNotNeed,
    };

    /**
     * Whether additional wanted outputs have been added.
     */
    NeedRestartForMoreOutputs needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;

    bool anyHashMismatchSeen = false;
    bool anyCheckMismatchSeen = false;

    /**
     * See `retrySubstitution`; just for that field.
     */
    enum RetrySubstitution {
        /**
         * No issues have yet arose, no need to restart.
         */
        NoNeed,
        /**
         * Something failed and there is an incomplete closure. Let's retry
         * substituting.
         */
        YesNeed,
        /**
         * We are current or have already retried substitution, and whether or
         * not something goes wrong we will not retry again.
         */
        AlreadyRetried,
    };

    /**
     * Whether to retry substituting the outputs after building the
     * inputs. This is done in case of an incomplete closure.
     */
    RetrySubstitution retrySubstitution = RetrySubstitution::NoNeed;

    /**
     * The derivation stored at drvPath.
     */
    std::unique_ptr<Derivation> drv;

    std::unique_ptr<ParsedDerivation> parsedDrv;

    /**
     * The remainder is state held during the build.
     */

    /**
     * Locks on (fixed) output paths.
     */
    PathLocks outputLocks;

    /**
     * All input paths (that is, the union of FS closures of the
     * immediate input paths).
     */
    StorePathSet inputPaths;

    std::map<std::string, InitialOutput> initialOutputs;

    /**
     * Build result.
     */
    BuildResult buildResult;

    /**
     * File descriptor for the log file.
     */
    AutoCloseFD fdLogFile;
    std::shared_ptr<BufferedSink> logFileSink, logSink;

    /**
     * Number of bytes received from the builder's stdout/stderr.
     */
    unsigned long logSize;

    /**
     * The most recent log lines.
     */
    std::list<std::string> logTail;

    std::string currentLogLine;
    size_t currentLogLinePos = 0; // to handle carriage return

    std::string currentHookLine;

    /**
     * The build hook.
     */
    std::unique_ptr<HookInstance> hook;

    /**
      * Builder output is pulled from this file descriptor when not null.
      * Owned by the derivation goal or subclass, must not be reset until
      * the build has finished and no more output must be processed by us
      */
    AutoCloseFD * builderOutFD = nullptr;

    /**
     * The sort of derivation we are building.
     */
    std::optional<DerivationType> derivationType;

    BuildMode buildMode;

    NotifyingCounter<uint64_t>::Bump mcExpectedBuilds, mcRunningBuilds;

    std::unique_ptr<Activity> act;

    /**
     * Activity that denotes waiting for a lock.
     */
    std::unique_ptr<Activity> actLock;

    std::map<ActivityId, Activity> builderActivities;

    /**
     * The remote machine on which we're building.
     */
    std::string machineName;

    DerivationGoal(const StorePath & drvPath,
        const OutputsSpec & wantedOutputs, Worker & worker, bool isDependency,
        BuildMode buildMode = bmNormal);
    DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
        const OutputsSpec & wantedOutputs, Worker & worker, bool isDependency,
        BuildMode buildMode = bmNormal);
    virtual ~DerivationGoal() noexcept(false);

    WorkResult timedOut(Error && ex);

    kj::Promise<Result<WorkResult>> workImpl() noexcept override;

    /**
     * Add wanted outputs to an already existing derivation goal.
     */
    bool addWantedOutputs(const OutputsSpec & outputs);

    /**
     * The states.
     */
    kj::Promise<Result<WorkResult>> getDerivation() noexcept;
    kj::Promise<Result<WorkResult>> loadDerivation() noexcept;
    kj::Promise<Result<WorkResult>> haveDerivation() noexcept;
    kj::Promise<Result<WorkResult>> outputsSubstitutionTried() noexcept;
    kj::Promise<Result<WorkResult>> gaveUpOnSubstitution() noexcept;
    kj::Promise<Result<WorkResult>> closureRepaired() noexcept;
    kj::Promise<Result<WorkResult>> inputsRealised() noexcept;
    kj::Promise<Result<WorkResult>> tryToBuild() noexcept;
    virtual kj::Promise<Result<WorkResult>> tryLocalBuild() noexcept;
    kj::Promise<Result<WorkResult>> buildDone() noexcept;

    kj::Promise<Result<WorkResult>> resolvedFinished() noexcept;

    /**
     * Is the build hook willing to perform the build?
     */
    HookReply tryBuildHook();

    virtual int getChildStatus();

    /**
     * Check that the derivation outputs all exist and register them
     * as valid.
     */
    virtual SingleDrvOutputs registerOutputs();

    /**
     * Open a log file and a pipe to it.
     */
    Path openLogFile();

    /**
     * Sign the newly built realisation if the store allows it
     */
    virtual void signRealisation(Realisation&) {}

    /**
     * Close the log file.
     */
    void closeLogFile();

    /**
     * Close the read side of the logger pipe.
     */
    virtual void closeReadPipes();

    /**
     * Cleanup hooks for buildDone()
     */
    virtual void cleanupHookFinally();
    virtual void cleanupPreChildKill();
    virtual void cleanupPostChildKill();
    virtual bool cleanupDecideWhetherDiskFull();
    virtual void cleanupPostOutputsRegisteredModeCheck();
    virtual void cleanupPostOutputsRegisteredModeNonCheck();

protected:
    kj::TimePoint lastChildActivity = kj::minValue;

    kj::Promise<Outcome<void, WorkResult>> handleChildOutput() noexcept;
    kj::Promise<Outcome<void, WorkResult>>
    handleChildStreams(InputStream & builderIn, InputStream * hookIn) noexcept;
    kj::Promise<Outcome<void, WorkResult>> handleBuilderOutput(InputStream & in) noexcept;
    kj::Promise<Outcome<void, WorkResult>> handleHookOutput(InputStream & in) noexcept;
    kj::Promise<Outcome<void, WorkResult>> monitorForSilence() noexcept;
    WorkResult tooMuchLogs();
    void flushLine();

public:
    /**
     * Wrappers around the corresponding Store methods that first consult the
     * derivation.  This is currently needed because when there is no drv file
     * there also is no DB entry.
     */
    std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap();
    OutputPathMap queryDerivationOutputMap();

    /**
     * Update 'initialOutputs' to determine the current status of the
     * outputs of the derivation. Also returns a Boolean denoting
     * whether all outputs are valid and non-corrupt, and a
     * 'SingleDrvOutputs' structure containing the valid outputs.
     */
    std::pair<bool, SingleDrvOutputs> checkPathValidity();

    /**
     * Aborts if any output is not valid or corrupt, and otherwise
     * returns a 'SingleDrvOutputs' structure containing all outputs.
     */
    SingleDrvOutputs assertPathValidity();

    /**
     * Forcibly kill the child process, if any.
     */
    virtual void killChild();

    kj::Promise<Result<WorkResult>> repairClosure() noexcept;

    void started();

    WorkResult done(
        BuildResult::Status status,
        SingleDrvOutputs builtOutputs = {},
        std::optional<Error> ex = {});

    void waiteeDone(GoalPtr waitee) override;

    virtual bool respectsTimeouts()
    {
        return false;
    }

    StorePathSet exportReferences(const StorePathSet & storePaths);

    JobCategory jobCategory() const override {
        return JobCategory::Build;
    };
};

MakeError(NotDeterministic, BuildError);

}