1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-26 04:00:59 +01:00

Merged trunk back in: 10154->10531.

This commit is contained in:
Wouter den Breejen 2008-02-06 23:58:00 +00:00
parent a34a198006
commit 2bf4fcb7cd
15 changed files with 250 additions and 43 deletions

View file

@ -6,6 +6,19 @@
<!--==================================================================-->
<section xml:id="ssec-relnotes-0.12"><title>Release 0.12 (TBA)</title>
<itemizedlist>
<listitem><para><command>nix-store --dump-db / --load-db</command>.</para></listitem>
</itemizedlist>
</section>
<!--==================================================================--> <!--==================================================================-->
<section xml:id="ssec-relnotes-0.11"><title>Release 0.11 (December 31, <section xml:id="ssec-relnotes-0.11"><title>Release 0.11 (December 31,

View file

@ -50,7 +50,7 @@ fi
--prefix=$nixstatepath \ --prefix=$nixstatepath \
--with-store-dir=/nix/store \ --with-store-dir=/nix/store \
--with-store-state-dir=/nix/state \ --with-store-state-dir=/nix/state \
--with-ext3cow-header=/nix/store/v95qf520d6972pshykrah1dz3z53rkmj-linux-2.6.21.7/lib/modules/2.6.21.7-default/build/include/linux/ext3cow_fs.h \ --with-ext3cow-header=/nix/store/2sm0h2xd1zsm5had53q1pvzmnsn8fy8k-linux-2.6.21/lib/modules/2.6.21-ck1-default/build/include/linux/ext3cow_fs.h \
--localstatedir=/nix/var --localstatedir=/nix/var
#Options from the nix expr #Options from the nix expr

View file

@ -1,4 +1,4 @@
svn merge -r 9751:10133 https://svn.cs.uu.nl:12443/repos/trace/nix/trunk svn merge -r 10154:10531 https://svn.cs.uu.nl:12443/repos/trace/nix/trunk
#already done: #already done:
# 8628 # 8628
@ -28,4 +28,6 @@ svn merge -r 9751:10133 https://svn.cs.uu.nl:12443/repos/trace/nix/trunk
# 9561 # 9561
# 9584 # 9584
# 9751 # 9751
# 10133 TODO # 10133
# 10154
# 10531

View file

@ -124,7 +124,7 @@ inherit { return INHERIT; }
<STRING>. return yytext[0]; /* just in case: shouldn't be reached */ <STRING>. return yytext[0]; /* just in case: shouldn't be reached */
\'\'(\ *\n)? { BEGIN(IND_STRING); return IND_STRING_OPEN; } \'\'(\ *\n)? { BEGIN(IND_STRING); return IND_STRING_OPEN; }
<IND_STRING>([^\$\']|\$[^\{\']|\'[^\'])+ { <IND_STRING>([^\$\']|\$[^\{\']|\'[^\'\$])+ {
yylval->t = makeIndStr(toATerm(yytext)); yylval->t = makeIndStr(toATerm(yytext));
return IND_STR; return IND_STR;
} }
@ -142,6 +142,10 @@ inherit { return INHERIT; }
} }
<IND_STRING>\$\{ { BEGIN(INITIAL); return DOLLAR_CURLY; } <IND_STRING>\$\{ { BEGIN(INITIAL); return DOLLAR_CURLY; }
<IND_STRING>\'\' { BEGIN(INITIAL); return IND_STRING_CLOSE; } <IND_STRING>\'\' { BEGIN(INITIAL); return IND_STRING_CLOSE; }
<IND_STRING>\' {
yylval->t = makeIndStr(toATerm("'"));
return IND_STR;
}
<IND_STRING>. return yytext[0]; /* just in case: shouldn't be reached */ <IND_STRING>. return yytext[0]; /* just in case: shouldn't be reached */
{PATH} { yylval->t = toATerm(yytext); return PATH; /* !!! alloc */ } {PATH} { yylval->t = toATerm(yytext); return PATH; /* !!! alloc */ }

View file

@ -8,6 +8,7 @@
#include "expr-to-xml.hh" #include "expr-to-xml.hh"
#include "nixexpr-ast.hh" #include "nixexpr-ast.hh"
#include "local-store.hh" #include "local-store.hh"
#include "parser.hh"
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -368,8 +369,8 @@ static Hash hashDerivationModulo(EvalState & state, Derivation drv)
} }
/* If we have a state derivation, we clear state paramters because they (sometimes) can affect the outPath: /* If we have a state derivation, we clear state paramters because they (sometimes) can affect the outPath:
* If this drv has runtime paramters: The state indentifier and thus statepath may change, but the componentPath (outPath) can stay the same * If this drv has runtime paramters: The state indentifier and statepath may change, but the componentPath (outPath) can stay the same
* If this drv doesnt have runtime paramters: The state indentifier and thus statepath may change, and thus the componentPath changes since it is build with another identifier * If this drv doesnt have runtime paramters: The state indentifier and statepath may change, but the componentPath changes since it is build with another identifier
* In both cases: Other runtime state parameters like stateDirs, synchronisation and shareState never change the out or statepath so always need to be out of the hash * In both cases: Other runtime state parameters like stateDirs, synchronisation and shareState never change the out or statepath so always need to be out of the hash
*/ */
if(isStateDrv(drv)){ if(isStateDrv(drv)){
@ -1097,6 +1098,22 @@ static Expr prim_unsafeDiscardStringContext(EvalState & state, const ATermVector
return makeStr(s, PathSet()); return makeStr(s, PathSet());
} }
/* Expression serialization/deserialization */
static Expr prim_ExprToString ( EvalState & state, const ATermVector & args)
{
return makeStr ( atPrint ( evalExpr ( state, args [ 0 ] ) ) );
}
static Expr prim_StringToExpr ( EvalState & state, const ATermVector & args)
{
string s;
PathSet l;
if (! matchStr ( evalExpr ( state, args[0] ), s, l )) {
throw EvalError("__stringToExpr needs string argument!");
}
return ATreadFromString(s.c_str());
}
/************************************************************* /*************************************************************
* Primop registration * Primop registration
@ -1124,6 +1141,10 @@ void EvalState::addPrimOps()
addPrimOp("__getEnv", 1, prim_getEnv); addPrimOp("__getEnv", 1, prim_getEnv);
addPrimOp("__trace", 2, prim_trace); addPrimOp("__trace", 2, prim_trace);
// Expr <-> String
addPrimOp("__exprToString", 1, prim_ExprToString);
addPrimOp("__stringToExpr", 1, prim_StringToExpr);
addPrimOp("relativise", 2, prim_relativise); addPrimOp("relativise", 2, prim_relativise);
// Derivations // Derivations

View file

@ -102,6 +102,7 @@ protected:
{ {
nrFailed = 0; nrFailed = 0;
exitCode = ecBusy; exitCode = ecBusy;
forceInputs = false;
} }
virtual ~Goal() virtual ~Goal()
@ -109,6 +110,8 @@ protected:
trace("goal destroyed"); trace("goal destroyed");
} }
bool forceInputs;
public: public:
virtual void work() = 0; virtual void work() = 0;
@ -143,6 +146,11 @@ public:
(important!), etc. */ (important!), etc. */
virtual void cancel() = 0; virtual void cancel() = 0;
void setForceInputs(bool x)
{
forceInputs = x;
}
protected: protected:
void amDone(ExitCode result); void amDone(ExitCode result);
}; };
@ -824,7 +832,6 @@ DerivationGoal::DerivationGoal(const Path & drvPath, Worker & worker)
trace("created"); trace("created");
} }
DerivationGoal::~DerivationGoal() DerivationGoal::~DerivationGoal()
{ {
/* Careful: we should never ever throw an exception from a /* Careful: we should never ever throw an exception from a
@ -837,7 +844,6 @@ DerivationGoal::~DerivationGoal()
} }
} }
void DerivationGoal::killChild() void DerivationGoal::killChild()
{ {
if (pid != -1) { if (pid != -1) {
@ -919,9 +925,11 @@ void DerivationGoal::haveDerivation()
/* If they are all valid, then we're done. */ /* If they are all valid, then we're done. */
if (invalidOutputs.size() == 0) { if (invalidOutputs.size() == 0) {
if(! forceInputs) {
amDone(ecSuccess); amDone(ecSuccess);
return; return;
} }
}
/* If this is a fixed-output derivation, it is possible that some /* If this is a fixed-output derivation, it is possible that some
other goal is already building the output paths. (The case other goal is already building the output paths. (The case
@ -964,9 +972,11 @@ void DerivationGoal::outputsSubstituted()
nrFailed = 0; nrFailed = 0;
if (checkPathValidity(false).size() == 0) { if (checkPathValidity(false).size() == 0) {
if (! forceInputs){
amDone(ecSuccess); amDone(ecSuccess);
return; return;
} }
}
/* Otherwise, at least one of the output paths could not be /* Otherwise, at least one of the output paths could not be
produced using a substitute. So we have to build instead. */ produced using a substitute. So we have to build instead. */
@ -974,13 +984,43 @@ void DerivationGoal::outputsSubstituted()
/* The inputs must be built before we can build this goal. */ /* The inputs must be built before we can build this goal. */
/* !!! but if possible, only install the paths that we need */ /* !!! but if possible, only install the paths that we need */
for (DerivationInputs::iterator i = drv.inputDrvs.begin(); for (DerivationInputs::iterator i = drv.inputDrvs.begin();
i != drv.inputDrvs.end(); ++i) i != drv.inputDrvs.end(); ++i){
addWaitee(worker.makeDerivationGoal(i->first)); GoalPtr newGoal = worker.makeDerivationGoal(i->first);
newGoal->setForceInputs(forceInputs);
addWaitee(newGoal);
}
for (PathSet::iterator i = drv.inputSrcs.begin(); for (PathSet::iterator i = drv.inputSrcs.begin();
i != drv.inputSrcs.end(); ++i) i != drv.inputSrcs.end(); ++i)
addWaitee(worker.makeSubstitutionGoal(*i)); addWaitee(worker.makeSubstitutionGoal(*i));
/* Actually, I do some work twice just to be on the safe side */
string s = drv.env["exportBuildReferencesGraph"];
Strings ss = tokenizeString(s);
if (ss.size() % 2 !=0)
throw BuildError(format("odd number of tokens in `exportBuildReferencesGraph': `%1%'") % s);
for (Strings::iterator i = ss.begin(); i != ss.end(); ) {
string fileName = *i++;
Path storePath=*i++;
if (!isInStore(storePath))
throw BuildError(format("`exportBuildReferencesGraph' contains a non-store path `%1%'")
% storePath);
storePath = toStorePath(storePath);
if (!store->isValidPath(storePath))
throw BuildError(format("`exportBuildReferencesGraph' contains an invalid path `%1%'")
% storePath);
/* Build-time closure should be in dependencies
* We really want just derivation, its closure
* and outputs. Looks like we should build it.
* */
GoalPtr newGoal = worker.makeDerivationGoal(storePath);
newGoal->setForceInputs(true);
addWaitee(newGoal);
}
state = &DerivationGoal::inputsRealised; state = &DerivationGoal::inputsRealised;
} }
@ -998,6 +1038,12 @@ void DerivationGoal::inputsRealised()
return; return;
} }
/* Maybe we just wanted to force build of inputs */
if (checkPathValidity(false).size() == 0) {
amDone(ecSuccess);
return;
}
/* Okay, try to build. Note that here we don't wait for a build /* Okay, try to build. Note that here we don't wait for a build
slot to become available, since we don't need one if there is a slot to become available, since we don't need one if there is a
build hook. */ build hook. */
@ -1279,7 +1325,6 @@ static string makeValidityRegistration(const PathSet & paths,
return s; return s;
} }
DerivationGoal::HookReply DerivationGoal::tryBuildHook() DerivationGoal::HookReply DerivationGoal::tryBuildHook()
{ {
if (!useBuildHook) return rpDecline; if (!useBuildHook) return rpDecline;
@ -1406,7 +1451,7 @@ DerivationGoal::HookReply DerivationGoal::tryBuildHook()
/* The `references' file has exactly the format accepted by /* The `references' file has exactly the format accepted by
`nix-store --register-validity'. */ `nix-store --register-validity'. */
writeStringToFile(referencesFN, writeStringToFile(referencesFN,
makeValidityRegistration(allInputs, true)); makeValidityRegistration(allInputs, true, false));
/* Tell the hook to proceed. */ /* Tell the hook to proceed. */
writeLine(toHook.writeSide, "okay"); writeLine(toHook.writeSide, "okay");
@ -1668,14 +1713,14 @@ void DerivationGoal::startBuilder()
/* !!! in secure Nix, the writing should be done on the /* !!! in secure Nix, the writing should be done on the
build uid for security (maybe). */ build uid for security (maybe). */
writeStringToFile(tmpDir + "/" + fileName, writeStringToFile(tmpDir + "/" + fileName,
makeValidityRegistration(refs, false)); makeValidityRegistration(refs, false, false));
} }
// The same for derivations // The same for derivations
s = drv.env["exportBuildReferencesGraph"]; s = drv.env["exportBuildReferencesGraph"];
ss = tokenizeString(s); ss = tokenizeString(s);
if (ss.size() % 2 != 0) if (ss.size() % 2 != 0)
throw BuildError(format("odd number of tokens in `exportReferencesGraph': `%1%'") % s); throw BuildError(format("odd number of tokens in `exportBuildReferencesGraph': `%1%'") % s);
for (Strings::iterator i = ss.begin(); i != ss.end(); ) { for (Strings::iterator i = ss.begin(); i != ss.end(); ) {
string fileName = *i++; string fileName = *i++;
checkStoreName(fileName); /* !!! abuse of this function */ checkStoreName(fileName); /* !!! abuse of this function */
@ -1683,11 +1728,11 @@ void DerivationGoal::startBuilder()
/* Check that the store path is valid. */ /* Check that the store path is valid. */
Path storePath = *i++; Path storePath = *i++;
if (!isInStore(storePath)) if (!isInStore(storePath))
throw BuildError(format("`exportReferencesGraph' contains a non-store path `%1%'") throw BuildError(format("`exportBuildReferencesGraph' contains a non-store path `%1%'")
% storePath); % storePath);
storePath = toStorePath(storePath); storePath = toStorePath(storePath);
if (!store->isValidPath(storePath)) if (!store->isValidPath(storePath))
throw BuildError(format("`exportReferencesGraph' contains an invalid path `%1%'") throw BuildError(format("`exportBuildReferencesGraph' contains an invalid path `%1%'")
% storePath); % storePath);
/* Write closure info to `fileName'. */ /* Write closure info to `fileName'. */
@ -1700,13 +1745,14 @@ void DerivationGoal::startBuilder()
for (DerivationOutputs::iterator k=deriv.outputs.begin(); for (DerivationOutputs::iterator k=deriv.outputs.begin();
k != deriv.outputs.end(); k++) { k != deriv.outputs.end(); k++) {
refs.insert(k->second.path); refs.insert(k->second.path);
} }
} }
} }
/* !!! in secure Nix, the writing should be done on the /* !!! in secure Nix, the writing should be done on the
build uid for security (maybe). */ build uid for security (maybe). */
writeStringToFile(tmpDir + "/" + fileName, writeStringToFile(tmpDir + "/" + fileName,
makeValidityRegistration(refs, false)); makeValidityRegistration(refs, false, false));
} }

View file

@ -246,12 +246,12 @@ LocalStore::LocalStore(bool reserveSpace)
% curSchema % nixSchemaVersion); % curSchema % nixSchemaVersion);
if (curSchema < nixSchemaVersion) { if (curSchema < nixSchemaVersion) {
if (curSchema == 0) /* new store */
curSchema = nixSchemaVersion;
if (curSchema <= 1) if (curSchema <= 1)
upgradeStore07(); throw Error("your Nix store is no longer supported");
if (curSchema == 2) if (curSchema <= 2) upgradeStore09();
upgradeStore09(); if (curSchema <= 3) upgradeStore11();
if (curSchema == 3)
upgradeStore11();
writeFile(schemaFN, (format("%1%") % nixSchemaVersion).str()); writeFile(schemaFN, (format("%1%") % nixSchemaVersion).str());
} }
} }
@ -377,6 +377,31 @@ bool LocalStore::isValidPath(const Path & path)
return isValidPathTxn(noTxn, path); return isValidPathTxn(noTxn, path);
} }
PathSet LocalStore::queryValidPaths()
{
Paths paths;
nixDB.enumTable(noTxn, dbValidPaths, paths);
return PathSet(paths.begin(), paths.end());
}
static string addPrefix(const string & prefix, const string & s)
{
return prefix + string(1, (char) 0) + s;
}
static string stripPrefix(const string & prefix, const string & s)
{
if (s.size() <= prefix.size() ||
string(s, 0, prefix.size()) != prefix ||
s[prefix.size()] != 0)
throw Error(format("string `%1%' is missing prefix `%2%'")
% s % prefix);
return string(s, prefix.size() + 1);
}
bool isValidStatePathTxn(const Transaction & txn, const Path & path) bool isValidStatePathTxn(const Transaction & txn, const Path & path)
{ {
string s; string s;

View file

@ -63,6 +63,8 @@ public:
bool isValidComponentOrStatePath(const Path & path); bool isValidComponentOrStatePath(const Path & path);
PathSet queryValidPaths();
Hash queryPathHash(const Path & path); Hash queryPathHash(const Path & path);
Path queryStatePathDrv(const Path & statePath); Path queryStatePathDrv(const Path & statePath);

View file

@ -236,6 +236,12 @@ bool RemoteStore::isValidComponentOrStatePath(const Path & path)
return reply != 0; return reply != 0;
} }
PathSet RemoteStore::queryValidPaths()
{
throw Error("not implemented");
}
bool RemoteStore::hasSubstitutes(const Path & path) bool RemoteStore::hasSubstitutes(const Path & path)
{ {
writeInt(wopHasSubstitutes, to); writeInt(wopHasSubstitutes, to);

View file

@ -31,6 +31,8 @@ public:
bool isValidComponentOrStatePath(const Path & path); bool isValidComponentOrStatePath(const Path & path);
PathSet queryValidPaths();
Hash queryPathHash(const Path & path); Hash queryPathHash(const Path & path);
Path queryStatePathDrv(const Path & statePath); Path queryStatePathDrv(const Path & statePath);

View file

@ -234,13 +234,50 @@ Path computeStorePathForText(const string & suffix, const string & s,
return makeStorePath(type, hash, suffix); return makeStorePath(type, hash, suffix);
} }
ValidPathInfo decodeValidPathInfo(std::istream & str) /* Return a string accepted by decodeValidPathInfo() that
registers the specified paths as valid. Note: it's the
responsibility of the caller to provide a closure. */
string makeValidityRegistration(const PathSet & paths,
bool showDerivers, bool showHash)
{
string s = "";
for (PathSet::iterator i = paths.begin(); i != paths.end(); ++i) {
s += *i + "\n";
if (showHash)
s += printHash(store->queryPathHash(*i)) + "\n";
Path deriver = showDerivers ? store->queryDeriver(*i) : "";
s += deriver + "\n";
PathSet references;
store->queryReferences(*i, references);
s += (format("%1%\n") % references.size()).str();
for (PathSet::iterator j = references.begin();
j != references.end(); ++j)
s += *j + "\n";
}
return s;
}
ValidPathInfo decodeValidPathInfo(std::istream & str, bool hashGiven)
{ {
ValidPathInfo info; ValidPathInfo info;
getline(str, info.path); getline(str, info.path);
if (str.eof()) { info.path = ""; return info; } if (str.eof()) { info.path = ""; return info; }
if (hashGiven) {
string s;
getline(str, s);
info.hash = parseHash(htSHA256, s);
}
getline(str, info.deriver); getline(str, info.deriver);
string s; int n; string s; int n;

View file

@ -43,6 +43,9 @@ public:
/* TODO */ /* TODO */
virtual bool isValidComponentOrStatePath(const Path & path) = 0; virtual bool isValidComponentOrStatePath(const Path & path) = 0;
/* Query the set of valid paths. */
virtual PathSet queryValidPaths() = 0;
/* Queries the hash of a valid path. */ /* Queries the hash of a valid path. */
virtual Hash queryPathHash(const Path & path) = 0; virtual Hash queryPathHash(const Path & path) = 0;
@ -339,6 +342,9 @@ extern boost::shared_ptr<StoreAPI> store;
boost::shared_ptr<StoreAPI> openStore(bool reserveSpace = true); boost::shared_ptr<StoreAPI> openStore(bool reserveSpace = true);
string makeValidityRegistration(const PathSet & paths,
bool showDerivers, bool showHash);
/* OLD TODO REMOVE /* OLD TODO REMOVE
struct ValidPathInfo struct ValidPathInfo
@ -359,7 +365,8 @@ struct ValidPathInfo
int unsigned revision; int unsigned revision;
}; };
ValidPathInfo decodeValidPathInfo(std::istream & str); ValidPathInfo decodeValidPathInfo(std::istream & str,
bool hashGiven = false);
} }

View file

@ -424,25 +424,30 @@ static void opReadLog(Strings opFlags, Strings opArgs)
} }
} }
static void opRegisterValidity(Strings opFlags, Strings opArgs) static void opDumpDB(Strings opFlags, Strings opArgs)
{ {
bool reregister = false; // !!! maybe this should be the default if (!opFlags.empty()) throw UsageError("unknown flag");
if (!opArgs.empty())
throw UsageError("no arguments expected");
PathSet validPaths = store->queryValidPaths();
/* !!! this isn't streamy; makeValidityRegistration() builds a
potentially gigantic string. */
cout << makeValidityRegistration(validPaths, true, true);
}
for (Strings::iterator i = opFlags.begin();
i != opFlags.end(); ++i)
if (*i == "--reregister") reregister = true;
else throw UsageError(format("unknown flag `%1%'") % *i);
if (!opArgs.empty()) throw UsageError("no arguments expected");
static void registerValidity(bool reregister, bool hashGiven, bool canonicalise)
{
ValidPathInfos infos; ValidPathInfos infos;
while (1) { while (1) {
ValidPathInfo info = decodeValidPathInfo(cin); ValidPathInfo info = decodeValidPathInfo(cin, hashGiven);
if (info.path == "") break; if (info.path == "") break;
if (!store->isValidPath(info.path) || reregister) { if (!store->isValidPath(info.path) || reregister) {
/* !!! races */ /* !!! races */
if (canonicalise)
canonicalisePathMetaData(info.path); canonicalisePathMetaData(info.path);
if (!hashGiven)
info.hash = hashPath(htSHA256, info.path); info.hash = hashPath(htSHA256, info.path);
infos.push_back(info); infos.push_back(info);
} }
@ -455,6 +460,32 @@ static void opRegisterValidity(Strings opFlags, Strings opArgs)
} }
static void opLoadDB(Strings opFlags, Strings opArgs)
{
if (!opFlags.empty()) throw UsageError("unknown flag");
if (!opArgs.empty())
throw UsageError("no arguments expected");
registerValidity(true, true, false);
}
static void opRegisterValidity(Strings opFlags, Strings opArgs)
{
bool reregister = false; // !!! maybe this should be the default
bool hashGiven = false;
for (Strings::iterator i = opFlags.begin();
i != opFlags.end(); ++i)
if (*i == "--reregister") reregister = true;
else if (*i == "--hash-given") hashGiven = true;
else throw UsageError(format("unknown flag `%1%'") % *i);
if (!opArgs.empty()) throw UsageError("no arguments expected");
registerValidity(reregister, hashGiven, true);
}
static void opCheckValidity(Strings opFlags, Strings opArgs) static void opCheckValidity(Strings opFlags, Strings opArgs)
{ {
bool printInvalid = false; bool printInvalid = false;
@ -704,6 +735,10 @@ void run(Strings args)
op = opQuery; op = opQuery;
else if (arg == "--read-log" || arg == "-l") else if (arg == "--read-log" || arg == "-l")
op = opReadLog; op = opReadLog;
else if (arg == "--dump-db")
op = opDumpDB;
else if (arg == "--load-db")
op = opLoadDB;
else if (arg == "--register-validity") else if (arg == "--register-validity")
op = opRegisterValidity; op = opRegisterValidity;
else if (arg == "--check-validity") else if (arg == "--check-validity")

View file

@ -1 +1 @@
Str("This is an indented multi-line string\nliteral. An amount of whitespace at\nthe start of each line matching the minimum\nindentation of all lines in the string\nliteral together will be removed. Thus,\nin this case four spaces will be\nstripped from each line, even though\n THIS LINE is indented six spaces.\n\nAlso, empty lines don't count in the\ndetermination of the indentation level (the\nprevious empty line has indentation 0, but\nit doesn't matter).\nIf the string starts with whitespace\n followed by a newline, it's stripped, but\n that's not the case here. Two spaces are\n stripped because of the \" \" at the start. \nThis line is indented\na bit further.\nAnti-quotations, like so, are\nalso allowed.\n The \\ is not special here.\n' can be followed by any character except another ', e.g. 'x'.\nLikewise for $, e.g. $$ or $varName.\nBut ' followed by ' is special, as is $ followed by {.\nIf you want them, use anti-quotations: '', ${.\n Tabs are not interpreted as whitespace (since we can't guess\n what tab settings are intended), so don't use them.\n\tThis line starts with a space and a tab, so only one\n space will be stripped from each line.\nAlso note that if the last line (just before the closing ' ')\nconsists only of whitespace, it's ignored. But here there is\nsome non-whitespace stuff, so the line isn't removed. \nThis shows a hacky way to preserve an empty line after the start.\nBut there's no reason to do so: you could just repeat the empty\nline.\n Similarly you can force an indentation level,\n in this case to 2 spaces. This works because the anti-quote\n is significant (not whitespace).\nstart on network-interfaces\n\nstart script\n\n rm -f /var/run/opengl-driver\n ln -sf 123 /var/run/opengl-driver\n\n rm -f /var/log/slim.log\n \nend script\n\nenv SLIM_CFGFILE=abc\nenv SLIM_THEMESDIR=def\nenv FONTCONFIG_FILE=/etc/fonts/fonts.conf \t\t\t\t# !!! cleanup\nenv XKB_BINDIR=foo/bin \t\t\t\t# Needed for the Xkb extension.\nenv LD_LIBRARY_PATH=libX11/lib:libXext/lib:/usr/lib/ # related to xorg-sys-opengl - needed to load libglx for (AI)GLX support (for compiz)\n\nenv XORG_DRI_DRIVER_PATH=nvidiaDrivers/X11R6/lib/modules/drivers/ \n\nexec slim/bin/slim\nEscaping of ' followed by ': ''\nEscaping of $ followed by {: ${\nAnd finally to interpret \\n etc. as in a string: \n, \r, \t.\n",[]) Str("This is an indented multi-line string\nliteral. An amount of whitespace at\nthe start of each line matching the minimum\nindentation of all lines in the string\nliteral together will be removed. Thus,\nin this case four spaces will be\nstripped from each line, even though\n THIS LINE is indented six spaces.\n\nAlso, empty lines don't count in the\ndetermination of the indentation level (the\nprevious empty line has indentation 0, but\nit doesn't matter).\nIf the string starts with whitespace\n followed by a newline, it's stripped, but\n that's not the case here. Two spaces are\n stripped because of the \" \" at the start. \nThis line is indented\na bit further.\nAnti-quotations, like so, are\nalso allowed.\n The \\ is not special here.\n' can be followed by any character except another ', e.g. 'x'.\nLikewise for $, e.g. $$ or $varName.\nBut ' followed by ' is special, as is $ followed by {.\nIf you want them, use anti-quotations: '', ${.\n Tabs are not interpreted as whitespace (since we can't guess\n what tab settings are intended), so don't use them.\n\tThis line starts with a space and a tab, so only one\n space will be stripped from each line.\nAlso note that if the last line (just before the closing ' ')\nconsists only of whitespace, it's ignored. But here there is\nsome non-whitespace stuff, so the line isn't removed. \nThis shows a hacky way to preserve an empty line after the start.\nBut there's no reason to do so: you could just repeat the empty\nline.\n Similarly you can force an indentation level,\n in this case to 2 spaces. This works because the anti-quote\n is significant (not whitespace).\nstart on network-interfaces\n\nstart script\n\n rm -f /var/run/opengl-driver\n ln -sf 123 /var/run/opengl-driver\n\n rm -f /var/log/slim.log\n \nend script\n\nenv SLIM_CFGFILE=abc\nenv SLIM_THEMESDIR=def\nenv FONTCONFIG_FILE=/etc/fonts/fonts.conf \t\t\t\t# !!! cleanup\nenv XKB_BINDIR=foo/bin \t\t\t\t# Needed for the Xkb extension.\nenv LD_LIBRARY_PATH=libX11/lib:libXext/lib:/usr/lib/ # related to xorg-sys-opengl - needed to load libglx for (AI)GLX support (for compiz)\n\nenv XORG_DRI_DRIVER_PATH=nvidiaDrivers/X11R6/lib/modules/drivers/ \n\nexec slim/bin/slim\nEscaping of ' followed by ': ''\nEscaping of $ followed by {: ${\nAnd finally to interpret \\n etc. as in a string: \n, \r, \t.\nfoo\n'bla'\nbar\n",[])

View file

@ -110,4 +110,11 @@ let
And finally to interpret \n etc. as in a string: ''\n, ''\r, ''\t. And finally to interpret \n etc. as in a string: ''\n, ''\r, ''\t.
''; '';
in s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11 + s12 + s13 + s14 # Regression test: antiquotation in '${x}' should work, but didn't.
s15 = let x = "bla"; in ''
foo
'${x}'
bar
'';
in s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11 + s12 + s13 + s14 + s15