{-# LANGUAGE CPP #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} #ifdef __GHCIDE__ # define NIX_IS_AT_LEAST(mm,m,p) 1 #endif -- This implements an optimized routine to build from a remote derivation. -- It is not in the "CNix" tree because it seems to be too specific for general use. -- BuildStatus and BuildResult *can* be moved there, but I do not know of an -- easy to maintain approach to do decouple it with inline-c-cpp. Perhaps it's -- better to use an FFI generator instead? module Hercules.Agent.Worker.Build.Prefetched ( buildDerivation, Hercules.Agent.Worker.Build.Prefetched.getDerivation, BuildResult (..), ) where import Data.ByteString.Char8 qualified as C8 import Foreign (alloca, peek) import Foreign.C (peekCString) import Hercules.CNix.Encapsulation import Hercules.CNix.Store import Language.C.Inline.Cpp qualified as C import Language.C.Inline.Cpp.Exception qualified as C import Protolude C.context context C.include "" C.include "" C.include "" C.include "" C.include "" C.include "" C.include "" #if NIX_IS_AT_LEAST(2,19,0) C.include "" #else -- redundant? C.include "" #endif #if NIX_IS_AT_LEAST(2,7,0) C.include "" #endif C.include "" C.include "" C.using "namespace nix" data BuildStatus = Built | Substituted | AlreadyValid | PermanentFailure | InputRejected | OutputRejected | TransientFailure -- possibly transient | CachedFailure -- no longer used | TimedOut | MiscFailure | DependencyFailed | LogLimitExceeded | NotDeterministic | Successful -- Catch-all for unknown successful status | UnknownFailure -- Catch-all for unknown unsuccessful status deriving (Show) -- Must match the FFI boilerplate toBuildStatus :: C.CInt -> BuildStatus toBuildStatus 0 = Built toBuildStatus 1 = Substituted toBuildStatus 2 = AlreadyValid toBuildStatus 3 = PermanentFailure toBuildStatus 4 = InputRejected toBuildStatus 5 = OutputRejected toBuildStatus 6 = TransientFailure toBuildStatus 7 = CachedFailure toBuildStatus 8 = TimedOut toBuildStatus 9 = MiscFailure toBuildStatus 10 = DependencyFailed toBuildStatus 11 = LogLimitExceeded toBuildStatus 12 = NotDeterministic toBuildStatus (-1) = Successful toBuildStatus _ = UnknownFailure data BuildResult = BuildResult { isSuccess :: Bool, status :: BuildStatus, startTime :: C.CTime, stopTime :: C.CTime, errorMessage :: Text } deriving (Show) getDerivation :: Store -> StorePath -> IO (Maybe Derivation) getDerivation (Store store) derivationPath = nullableMoveToForeignPtrWrapper =<< [C.throwBlock| Derivation *{ ReceiveInterrupts _; StorePath derivationPath = *$fptr-ptr:(nix::StorePath *derivationPath); std::list> stores = getDefaultSubstituters(); stores.push_front(*$(refStore* store)); nix::Derivation *derivation = nullptr; for (nix::ref & currentStore : stores) { try { derivation = new nix::Derivation(currentStore->derivationFromPath(derivationPath)); break; } catch (nix::Interrupted &e) { throw e; } catch (nix::Error &e) { printTalkative("ignoring exception during drv lookup in %s: %s", currentStore->getUri(), e.what()); } catch (std::exception &e) { printTalkative("ignoring exception during drv lookup in %s: %s", currentStore->getUri(), e.what()); } catch (...) { // FIXME: remove this and make the "specific" catches above work on darwin printTalkative("ignoring unknown exception during drv lookup in %s: %s", currentStore->getUri()); } } return derivation; }|] -- | @buildDerivation derivationPath derivationText@ buildDerivation :: Store -> StorePath -> Derivation -> Maybe [ByteString] -> IO BuildResult buildDerivation (Store store) derivationPath derivation extraInputs = let extraInputsMerged = C8.intercalate "\n" (fromMaybe [] extraInputs) materializeDerivation = if isNothing extraInputs then 1 else 0 in alloca $ \successPtr -> alloca $ \statusPtr -> alloca $ \startTimePtr -> alloca $ \stopTimePtr -> alloca $ \errorMessagePtr -> do [C.throwBlock| void { ReceiveInterrupts _; Store &store = **$(refStore* store); bool &success = *$(bool *successPtr); int &status = *$(int *statusPtr); const char *&errorMessage = *$(const char **errorMessagePtr); time_t &startTime = *$(time_t *startTimePtr); time_t &stopTime = *$(time_t *stopTimePtr); StorePath derivationPath = *$fptr-ptr:(nix::StorePath *derivationPath); if ($(bool materializeDerivation)) { store.addTempRoot(derivationPath); store.ensurePath(derivationPath); auto derivation = store.derivationFromPath(derivationPath); StorePathWithOutputs storePathWithOutputs { .path = derivationPath, .outputs = derivation.outputNames() }; std::vector paths{storePathWithOutputs}; try { store.buildPaths(toDerivedPaths(paths)); status = -1; success = true; errorMessage = strdup(""); startTime = 0; stopTime = 0; } catch (nix::Error &e) { printError(e.msg()); status = -2; success = false; errorMessage = strdup(e.msg().c_str()); startTime = 0; stopTime = 0; } } else { nix::BasicDerivation *derivation = new BasicDerivation(*$fptr-ptr:(Derivation *derivation)); std::string extraInputsMerged($bs-ptr:extraInputsMerged, $bs-len:extraInputsMerged); std::string extraInput; std::istringstream stream(extraInputsMerged); while (std::getline(stream, extraInput)) { auto path = store.parseStorePath(extraInput); derivation->inputSrcs.insert(path); } nix::BuildResult result = store.buildDerivation(derivationPath, *derivation); switch (result.status) { case nix::BuildResult::Built: status = 0; break; case nix::BuildResult::Substituted: status = 1; break; case nix::BuildResult::AlreadyValid: status = 2; break; case nix::BuildResult::PermanentFailure: status = 3; break; case nix::BuildResult::InputRejected: status = 4; break; case nix::BuildResult::OutputRejected: status = 5; break; case nix::BuildResult::TransientFailure: // possibly transient status = 6; break; case nix::BuildResult::CachedFailure: // no longer used status = 7; break; case nix::BuildResult::TimedOut: status = 8; break; case nix::BuildResult::MiscFailure: status = 9; break; case nix::BuildResult::DependencyFailed: status = 10; break; case nix::BuildResult::LogLimitExceeded: status = 11; break; case nix::BuildResult::NotDeterministic: status = 12; break; default: status = result.success() ? -1 : -2; break; } printError(result.errorMsg); success = result.success(); errorMessage = strdup(result.errorMsg.c_str()); startTime = result.startTime; stopTime = result.stopTime; } } |] successValue <- peek successPtr statusValue <- peek statusPtr startTimeValue <- peek startTimePtr stopTimeValue <- peek stopTimePtr errorMessageValue0 <- peek errorMessagePtr errorMessageValue <- peekCString errorMessageValue0 pure $ BuildResult { isSuccess = successValue /= 0, status = toBuildStatus statusValue, startTime = startTimeValue, stopTime = stopTimeValue, errorMessage = toS errorMessageValue }