hercules-ci-agent-0.8.3: hercules-ci-agent-worker/Hercules/Agent/Worker/Build/Prefetched.hs
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
-- 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 where
import qualified Data.ByteString.Char8 as C8
import Foreign (FinalizerPtr, ForeignPtr, alloca, newForeignPtr, nullPtr, peek)
import Foreign.C (peekCString)
import Hercules.CNix.Encapsulation
import Hercules.CNix.Store
import qualified Language.C.Inline.Cpp as C
import qualified Language.C.Inline.Cpp.Exceptions as C
import Protolude
C.context context
C.include "<cstring>"
C.include "<nix/config.h>"
C.include "<nix/shared.hh>"
C.include "<nix/store-api.hh>"
C.include "<nix/get-drvs.hh>"
C.include "<nix/derivations.hh>"
C.include "<nix/affinity.hh>"
C.include "<nix/globals.hh>"
C.include "<nix/fs-accessor.hh>"
C.include "<nix-compat.hh>"
C.include "<hercules-ci-cnix/store.hxx>"
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)
nullableForeignPtr :: FinalizerPtr a -> Ptr a -> IO (Maybe (ForeignPtr a))
nullableForeignPtr _ rawPtr | rawPtr == nullPtr = pure Nothing
nullableForeignPtr finalize rawPtr = Just <$> newForeignPtr finalize rawPtr
getDerivation :: Store -> StorePath -> IO (Maybe Derivation)
getDerivation (Store store) derivationPath =
nullableMoveToForeignPtrWrapper
=<< [C.throwBlock| Derivation *{
ReceiveInterrupts _;
StorePath derivationPath = *$fptr-ptr:(nix::StorePath *derivationPath);
std::list<nix::ref<nix::Store>> stores = getDefaultSubstituters();
stores.push_front(*$(refStore* store));
nix::Derivation *derivation = nullptr;
for (nix::ref<nix::Store> & currentStore : stores) {
try {
derivation = new nix::Derivation(currentStore->derivationFromPath(printPath23(*currentStore, 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.ensurePath(printPath23(store, derivationPath));
#ifdef NIX_2_4
auto derivation = store.derivationFromPath(derivationPath);
StorePathWithOutputs storePathWithOutputs { .path = derivationPath, .outputs = derivation.outputNames() };
std::vector<nix::StorePathWithOutputs> paths{storePathWithOutputs};
#else
nix::PathSet paths{printPath23(store, derivationPath)};
#endif
try {
store.buildPaths(toDerivedPaths24(paths));
status = -1;
success = true;
errorMessage = strdup("");
startTime = 0;
stopTime = 0;
}
catch (nix::Error &e) {
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)) {
#ifdef NIX_2_4
auto path = store.parseStorePath(extraInput);
#else
auto path = extraInput;
#endif
derivation->inputSrcs.insert(path);
}
nix::BuildResult result = store.buildDerivation(printPath23(store, 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;
}
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
}