aboutsummaryrefslogtreecommitdiff
path: root/src/nix/bundle.cc
blob: 8bf158d6186899efad0fb42079ec6d34d6fc29cc (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
#include "installable-flake.hh"
#include "command.hh"
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
#include "local-fs-store.hh"
#include "fs-accessor.hh"
#include "eval-inline.hh"

using namespace nix;

struct CmdBundle : InstallableCommand
{
    std::string bundler = "github:NixOS/bundlers";
    std::optional<Path> outLink;

    CmdBundle()
    {
        addFlag({
            .longName = "bundler",
            .description = fmt("Use a custom bundler instead of the default (`%s`).", bundler),
            .labels = {"flake-url"},
            .handler = {&bundler},
            .completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
                completeFlakeRef(completions, getStore(), prefix);
            }}
        });

        addFlag({
            .longName = "out-link",
            .shortName = 'o',
            .description = "Override the name of the symlink to the build result. It defaults to the base name of the app.",
            .labels = {"path"},
            .handler = {&outLink},
            .completer = completePath
        });

    }

    std::string description() override
    {
        return "bundle an application so that it works outside of the Nix store";
    }

    std::string doc() override
    {
        return
          #include "bundle.md"
          ;
    }

    Category category() override { return catSecondary; }

    // FIXME: cut&paste from CmdRun.
    Strings getDefaultFlakeAttrPaths() override
    {
        Strings res{
            "apps." + settings.thisSystem.get() + ".default",
            "defaultApp." + settings.thisSystem.get()
        };
        for (auto & s : SourceExprCommand::getDefaultFlakeAttrPaths())
            res.push_back(s);
        return res;
    }

    Strings getDefaultFlakeAttrPathPrefixes() override
    {
        Strings res{"apps." + settings.thisSystem.get() + "."};
        for (auto & s : SourceExprCommand::getDefaultFlakeAttrPathPrefixes())
            res.push_back(s);
        return res;
    }

    void run(ref<Store> store, ref<Installable> installable) override
    {
        auto evalState = getEvalState();

        auto const installableValue = InstallableValue::require(installable);

        auto val = installableValue->toValue(*evalState).first;

        auto [bundlerFlakeRef, bundlerName, extendedOutputsSpec] = parseFlakeRefWithFragmentAndExtendedOutputsSpec(bundler, absPath("."));
        const flake::LockFlags lockFlags{ .writeLockFile = false };
        InstallableFlake bundler{this,
            evalState, std::move(bundlerFlakeRef), bundlerName, std::move(extendedOutputsSpec),
            {"bundlers." + settings.thisSystem.get() + ".default",
             "defaultBundler." + settings.thisSystem.get()
            },
            {"bundlers." + settings.thisSystem.get() + "."},
            lockFlags
        };

        auto vRes = evalState->allocValue();
        evalState->callFunction(*bundler.toValue(*evalState).first, *val, *vRes, noPos);

        if (!evalState->isDerivation(*vRes))
            throw Error("the bundler '%s' does not produce a derivation", bundler.what());

        auto attr1 = vRes->attrs->get(evalState->sDrvPath);
        if (!attr1)
            throw Error("the bundler '%s' does not produce a derivation", bundler.what());

        NixStringContext context2;
        auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2, "");

        auto attr2 = vRes->attrs->get(evalState->sOutPath);
        if (!attr2)
            throw Error("the bundler '%s' does not produce a derivation", bundler.what());

        auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2, "");

        store->buildPaths({
            DerivedPath::Built {
                .drvPath = makeConstantStorePathRef(drvPath),
                .outputs = OutputsSpec::All { },
            },
        });

        auto outPathS = store->printStorePath(outPath);

        if (!outLink) {
            auto * attr = vRes->attrs->get(evalState->sName);
            if (!attr)
                throw Error("attribute 'name' missing");
            outLink = evalState->forceStringNoCtx(*attr->value, attr->pos, "");
        }

        // TODO: will crash if not a localFSStore?
        store.dynamic_pointer_cast<LocalFSStore>()->addPermRoot(outPath, absPath(*outLink));
    }
};

static auto r2 = registerCommand<CmdBundle>("bundle");