diff options
Diffstat (limited to 'third_party/nix/src/libstore/derivations.cc')
-rw-r--r-- | third_party/nix/src/libstore/derivations.cc | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/third_party/nix/src/libstore/derivations.cc b/third_party/nix/src/libstore/derivations.cc new file mode 100644 index 000000000000..9c344502f386 --- /dev/null +++ b/third_party/nix/src/libstore/derivations.cc @@ -0,0 +1,520 @@ +#include "libstore/derivations.hh" + +#include <absl/strings/match.h> +#include <absl/strings/str_split.h> +#include <absl/strings/string_view.h> +#include <glog/logging.h> + +#include "libproto/worker.pb.h" +#include "libstore/fs-accessor.hh" +#include "libstore/globals.hh" +#include "libstore/store-api.hh" +#include "libstore/worker-protocol.hh" +#include "libutil/istringstream_nocopy.hh" +#include "libutil/util.hh" + +namespace nix { + +// TODO(#statusor): looks like easy absl::Status conversion +void DerivationOutput::parseHashInfo(bool& recursive, Hash& hash) const { + recursive = false; + std::string algo = hashAlgo; + + if (std::string(algo, 0, 2) == "r:") { + recursive = true; + algo = std::string(algo, 2); + } + + HashType hashType = parseHashType(algo); + if (hashType == htUnknown) { + throw Error(format("unknown hash algorithm '%1%'") % algo); + } + + auto hash_ = Hash::deserialize(this->hash, hashType); + hash = Hash::unwrap_throw(hash_); +} + +nix::proto::Derivation_DerivationOutput DerivationOutput::to_proto() const { + nix::proto::Derivation_DerivationOutput result; + result.mutable_path()->set_path(path); + result.set_hash_algo(hashAlgo); + result.set_hash(hash); + return result; +} + +BasicDerivation BasicDerivation::from_proto( + const nix::proto::Derivation* proto_derivation) { + BasicDerivation result; + result.platform = proto_derivation->platform(); + result.builder = proto_derivation->builder().path(); + + for (auto [k, v] : proto_derivation->outputs()) { + result.outputs.emplace(k, v); + } + + result.inputSrcs.insert(proto_derivation->input_sources().paths().begin(), + proto_derivation->input_sources().paths().end()); + + result.args.insert(result.args.end(), proto_derivation->args().begin(), + proto_derivation->args().end()); + + for (auto [k, v] : proto_derivation->env()) { + result.env.emplace(k, v); + } + + return result; +} + +nix::proto::Derivation BasicDerivation::to_proto() const { + nix::proto::Derivation result; + for (const auto& [key, output] : outputs) { + result.mutable_outputs()->insert({key, output.to_proto()}); + } + for (const auto& input_src : inputSrcs) { + *result.mutable_input_sources()->add_paths() = input_src; + } + result.set_platform(platform); + result.mutable_builder()->set_path(builder); + for (const auto& arg : args) { + result.add_args(arg); + } + + for (const auto& [key, value] : env) { + result.mutable_env()->insert({key, value}); + } + + return result; +} + +Path BasicDerivation::findOutput(const std::string& id) const { + auto i = outputs.find(id); + if (i == outputs.end()) { + throw Error(format("derivation has no output '%1%'") % id); + } + return i->second.path; +} + +bool BasicDerivation::isBuiltin() const { + return std::string(builder, 0, 8) == "builtin:"; +} + +Path writeDerivation(const ref<Store>& store, const Derivation& drv, + const std::string& name, RepairFlag repair) { + PathSet references; + references.insert(drv.inputSrcs.begin(), drv.inputSrcs.end()); + for (auto& i : drv.inputDrvs) { + references.insert(i.first); + } + /* Note that the outputs of a derivation are *not* references + (that can be missing (of course) and should not necessarily be + held during a garbage collection). */ + std::string suffix = name + drvExtension; + std::string contents = drv.unparse(); + return settings.readOnlyMode + ? store->computeStorePathForText(suffix, contents, references) + : store->addTextToStore(suffix, contents, references, repair); +} + +/* Read string `s' from stream `str'. */ +static void expect(std::istream& str, const std::string& s) { + char s2[s.size()]; + str.read(s2, s.size()); + if (std::string(s2, s.size()) != s) { + throw FormatError(format("expected string '%1%'") % s); + } +} + +/* Read a C-style string from stream `str'. */ +static std::string parseString(std::istream& str) { + std::string res; + expect(str, "\""); + int c; + while ((c = str.get()) != '"' && c != EOF) { + if (c == '\\') { + c = str.get(); + if (c == 'n') { + res += '\n'; + } else if (c == 'r') { + res += '\r'; + } else if (c == 't') { + res += '\t'; + } else if (c == EOF) { + throw FormatError("unexpected EOF while parsing C-style escape"); + } else { + res += static_cast<char>(c); + } + } else { + res += static_cast<char>(c); + } + } + return res; +} + +static Path parsePath(std::istream& str) { + std::string s = parseString(str); + if (s.empty() || s[0] != '/') { + throw FormatError(format("bad path '%1%' in derivation") % s); + } + return s; +} + +static bool endOfList(std::istream& str) { + if (str.peek() == ',') { + str.get(); + return false; + } + if (str.peek() == ']') { + str.get(); + return true; + } + return false; +} + +static StringSet parseStrings(std::istream& str, bool arePaths) { + StringSet res; + while (!endOfList(str)) { + res.insert(arePaths ? parsePath(str) : parseString(str)); + } + return res; +} + +Derivation parseDerivation(const std::string& s) { + Derivation drv; + istringstream_nocopy str(s); + expect(str, "Derive(["); + + /* Parse the list of outputs. */ + while (!endOfList(str)) { + DerivationOutput out; + expect(str, "("); + std::string id = parseString(str); + expect(str, ","); + out.path = parsePath(str); + expect(str, ","); + out.hashAlgo = parseString(str); + expect(str, ","); + out.hash = parseString(str); + expect(str, ")"); + drv.outputs[id] = out; + } + + /* Parse the list of input derivations. */ + expect(str, ",["); + while (!endOfList(str)) { + expect(str, "("); + Path drvPath = parsePath(str); + expect(str, ",["); + drv.inputDrvs[drvPath] = parseStrings(str, false); + expect(str, ")"); + } + + expect(str, ",["); + drv.inputSrcs = parseStrings(str, true); + expect(str, ","); + drv.platform = parseString(str); + expect(str, ","); + drv.builder = parseString(str); + + /* Parse the builder arguments. */ + expect(str, ",["); + while (!endOfList(str)) { + drv.args.push_back(parseString(str)); + } + + /* Parse the environment variables. */ + expect(str, ",["); + while (!endOfList(str)) { + expect(str, "("); + std::string name = parseString(str); + expect(str, ","); + std::string value = parseString(str); + expect(str, ")"); + drv.env[name] = value; + } + + expect(str, ")"); + return drv; +} + +Derivation readDerivation(const Path& drvPath) { + try { + return parseDerivation(readFile(drvPath)); + } catch (FormatError& e) { + throw Error(format("error parsing derivation '%1%': %2%") % drvPath % + e.msg()); + } +} + +Derivation Store::derivationFromPath(const Path& drvPath) { + assertStorePath(drvPath); + ensurePath(drvPath); + auto accessor = getFSAccessor(); + try { + return parseDerivation(accessor->readFile(drvPath)); + } catch (FormatError& e) { + throw Error(format("error parsing derivation '%1%': %2%") % drvPath % + e.msg()); + } +} + +const char* findChunk(const char* begin) { + while (*begin != 0 && *begin != '\"' && *begin != '\\' && *begin != '\n' && + *begin != '\r' && *begin != '\t') { + begin++; + } + + return begin; +} + +static void printString(std::string& res, const std::string& s) { + res += '"'; + + const char* it = s.c_str(); + while (*it != 0) { + const char* end = findChunk(it); + std::copy(it, end, std::back_inserter(res)); + + it = end; + + switch (*it) { + case '"': + case '\\': + res += "\\"; + res += *it; + break; + case '\n': + res += "\\n"; + break; + case '\r': + res += "\\r"; + break; + case '\t': + res += "\\t"; + break; + default: + continue; + } + + it++; + } + + res += '"'; +} + +template <class ForwardIterator> +static void printStrings(std::string& res, ForwardIterator i, + ForwardIterator j) { + res += '['; + bool first = true; + for (; i != j; ++i) { + if (first) { + first = false; + } else { + res += ','; + } + printString(res, *i); + } + res += ']'; +} + +std::string Derivation::unparse() const { + std::string s; + s.reserve(65536); + s += "Derive(["; + + bool first = true; + for (auto& i : outputs) { + if (first) { + first = false; + } else { + s += ','; + } + s += '('; + printString(s, i.first); + s += ','; + printString(s, i.second.path); + s += ','; + printString(s, i.second.hashAlgo); + s += ','; + printString(s, i.second.hash); + s += ')'; + } + + s += "],["; + first = true; + for (auto& i : inputDrvs) { + if (first) { + first = false; + } else { + s += ','; + } + s += '('; + printString(s, i.first); + s += ','; + printStrings(s, i.second.begin(), i.second.end()); + s += ')'; + } + + s += "],"; + printStrings(s, inputSrcs.begin(), inputSrcs.end()); + + s += ','; + printString(s, platform); + s += ','; + printString(s, builder); + s += ','; + printStrings(s, args.begin(), args.end()); + + s += ",["; + first = true; + for (auto& i : env) { + if (first) { + first = false; + } else { + s += ','; + } + s += '('; + printString(s, i.first); + s += ','; + printString(s, i.second); + s += ')'; + } + + s += "])"; + + return s; +} + +bool isDerivation(const std::string& fileName) { + return absl::EndsWith(fileName, drvExtension); +} + +bool BasicDerivation::isFixedOutput() const { + return outputs.size() == 1 && outputs.begin()->first == "out" && + !outputs.begin()->second.hash.empty(); +} + +DrvHashes drvHashes; + +/* Returns the hash of a derivation modulo fixed-output + subderivations. A fixed-output derivation is a derivation with one + output (`out') for which an expected hash and hash algorithm are + specified (using the `outputHash' and `outputHashAlgo' + attributes). We don't want changes to such derivations to + propagate upwards through the dependency graph, changing output + paths everywhere. + + For instance, if we change the url in a call to the `fetchurl' + function, we do not want to rebuild everything depending on it + (after all, (the hash of) the file being downloaded is unchanged). + So the *output paths* should not change. On the other hand, the + *derivation paths* should change to reflect the new dependency + graph. + + That's what this function does: it returns a hash which is just the + hash of the derivation ATerm, except that any input derivation + paths have been replaced by the result of a recursive call to this + function, and that for fixed-output derivations we return a hash of + its output path. */ +Hash hashDerivationModulo(Store& store, Derivation drv) { + /* Return a fixed hash for fixed-output derivations. */ + if (drv.isFixedOutput()) { + auto i = drv.outputs.begin(); + return hashString(htSHA256, "fixed:out:" + i->second.hashAlgo + ":" + + i->second.hash + ":" + i->second.path); + } + + /* For other derivations, replace the inputs paths with recursive + calls to this function.*/ + DerivationInputs inputs2; + for (auto& i : drv.inputDrvs) { + Hash h = drvHashes[i.first]; + if (!h) { + assert(store.isValidPath(i.first)); + Derivation drv2 = readDerivation(store.toRealPath(i.first)); + h = hashDerivationModulo(store, drv2); + drvHashes[i.first] = h; + } + inputs2[h.to_string(Base16, false)] = i.second; + } + drv.inputDrvs = inputs2; + + return hashString(htSHA256, drv.unparse()); +} + +DrvPathWithOutputs parseDrvPathWithOutputs(absl::string_view path) { + auto pos = path.find('!'); + if (pos == absl::string_view::npos) { + return DrvPathWithOutputs(path, std::set<std::string>()); + } + + return DrvPathWithOutputs( + path.substr(0, pos), + absl::StrSplit(path.substr(pos + 1), absl::ByChar(','), + absl::SkipEmpty())); +} + +Path makeDrvPathWithOutputs(const Path& drvPath, + const std::set<std::string>& outputs) { + return outputs.empty() ? drvPath + : drvPath + "!" + concatStringsSep(",", outputs); +} + +bool wantOutput(const std::string& output, + const std::set<std::string>& wanted) { + return wanted.empty() || wanted.find(output) != wanted.end(); +} + +PathSet BasicDerivation::outputPaths() const { + PathSet paths; + for (auto& i : outputs) { + paths.insert(i.second.path); + } + return paths; +} + +Source& readDerivation(Source& in, Store& store, BasicDerivation& drv) { + drv.outputs.clear(); + auto nr = readNum<size_t>(in); + for (size_t n = 0; n < nr; n++) { + auto name = readString(in); + DerivationOutput o; + in >> o.path >> o.hashAlgo >> o.hash; + store.assertStorePath(o.path); + drv.outputs[name] = o; + } + + drv.inputSrcs = readStorePaths<PathSet>(store, in); + in >> drv.platform >> drv.builder; + drv.args = readStrings<Strings>(in); + + nr = readNum<size_t>(in); + for (size_t n = 0; n < nr; n++) { + auto key = readString(in); + auto value = readString(in); + drv.env[key] = value; + } + + return in; +} + +Sink& operator<<(Sink& out, const BasicDerivation& drv) { + out << drv.outputs.size(); + for (auto& i : drv.outputs) { + out << i.first << i.second.path << i.second.hashAlgo << i.second.hash; + } + out << drv.inputSrcs << drv.platform << drv.builder << drv.args; + out << drv.env.size(); + for (auto& i : drv.env) { + out << i.first << i.second; + } + return out; +} + +std::string hashPlaceholder(const std::string& outputName) { + // FIXME: memoize? + return "/" + hashString(htSHA256, "nix-output:" + outputName) + .to_string(Base32, false); +} + +} // namespace nix |