diff --git a/build-env.cabal b/build-env.cabal
--- a/build-env.cabal
+++ b/build-env.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name:          build-env
-version:       1.0.0.0
+version:       1.1.0.0
 author:        Ben Gamari & Sam Derbyshire
 maintainer:    ben@smart-cactus.org
 license:       BSD-3-Clause
@@ -21,12 +21,7 @@
   package database.
 
   In particular, __build-env__ enables bootstrapping of Haskell packages
-  in hermetic build environments without the use of @cabal-install@ or Stack.
-
-  It can be used as a library, or as an executable.
-
-  Please refer to the [readme](https://github.com/bgamari/build-env/readme.md)
-  for more information and usage examples.
+  in hermetic build environments, without the use of @cabal-install@ or Stack.
 
 common common
 
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,14 @@
 # Changelog for `build-env`
 
+## Version 1.1.0.0 (2023-03-09)
+
+- Change the default directory structure produced by `build-env` to be more
+  predictable.
+
+- Ensure that important arguments to Cabal configure override arguments
+  passed using `--configure-arg=`, as they are essential for `build-env`
+  to function.
+
 ## Version 1.0.0.0 (2022-12-26)
 
 - First released version.
diff --git a/readme.md b/readme.md
--- a/readme.md
+++ b/readme.md
@@ -26,7 +26,7 @@
 
 ```
 $ build-env build lens -f sources -o install -j8
-$ ghci -package-db install/package.conf/ -package lens
+$ ghci -package-db install/package.conf -package lens
 ghci> :ty Control.Lens.Lens
 Control.Lens.Lens
   :: Control.Lens.Type.Lens s t a b
@@ -65,6 +65,12 @@
 Being able to separate these steps affords us some extra flexibility, as
 subsequent sections will explain.
 
+Note: it is preferable to pass a previously computed build plan when calling
+`build-env build --prefetched`: an invocation of the form `build-env build a b c --prefetched`
+will compute a new build plan, and this build plan could be different from
+the plan that was previously fetched, for example if you have run `cabal update`
+in the meantime.
+
 ## What does `build-env` do?
 
 - **plan:** `build-env` calls `cabal build --dry-run` on a dummy
@@ -84,13 +90,12 @@
 when running the `Setup` script:
 
   - `with-compiler`, `prefix` and `destdir` options supplied by the user,
-  - the default `datadir` option,
-  - `cid`, `datasubdir`, `bindir` and `builddir` options supplied by `build-env`,
+  - `cid`, `datadir`, `datasubdir`, `bindir` and `builddir` options supplied by `build-env`,
   - package flags and `dependency` arguments, obtained from the build plan,
   - `<pkg>_datadir` environment variables supplied by `build-env`.
 
-Problems will likely arise if you pass extra arguments that override any of
-these, for example by using `--configure-arg ARG` on the command line.
+These options cannot be overridden. If you absolutely need to change some of
+these, please file a bug on the issue tracker asking for this customisability.
 
 ## Hermetic builds
 
@@ -138,7 +143,7 @@
 the computation of the build plan nor which packages are fetched.
 
 You can also resume a build where it left off by passing `--resume`; this will
-avoid rebuilding any of the units that have already been registed in the
+avoid rebuilding any of the units that have already been registered in the
 package database.
 
 ## Bootstrapping
@@ -184,7 +189,7 @@
 Next, compute a build script containing references to variables:
 
 ```
-$ build-env build -p myPlan.json -f sources -o install --prefetched --configure-arg \$\{myArgs\} --script script.sh
+$ build-env build -p myPlan.json -f sources -o install --prefetched --configure-arg \$myArgs --script script.sh
 ```
 
 This will output a script which passes `$myArgs` to each `Setup configure`
diff --git a/src/BuildEnv/Build.hs b/src/BuildEnv/Build.hs
--- a/src/BuildEnv/Build.hs
+++ b/src/BuildEnv/Build.hs
@@ -347,7 +347,7 @@
 
 -- | Build a 'CabalPlan'. This will install all the packages in the plan
 -- by running their @Setup@ scripts. Libraries will be registered
--- into a local package database at @<install-dir>/package.conf@.
+-- into a local package database at @installDir/package.conf@.
 buildPlan :: Verbosity
           -> FilePath
               -- ^ Working directory.
diff --git a/src/BuildEnv/BuildOne.hs b/src/BuildEnv/BuildOne.hs
--- a/src/BuildEnv/BuildOne.hs
+++ b/src/BuildEnv/BuildOne.hs
@@ -100,17 +100,17 @@
        setupHs <- findSetupHs prepPkgDir
        return do
          scriptCfg <- askScriptConfig
-         let setupArgs = [ quoteArg scriptCfg (buildPkgDir </> setupHs)
+         let setupArgs = [ quoteArg ExpandVars scriptCfg ( buildPkgDir </> setupHs )
                          , "-o"
-                         , quoteArg scriptCfg (buildPkgDir </> "Setup")
-                         , "-package-db=" ++ quoteArg scriptCfg tempPkgDbDir
+                         , quoteArg ExpandVars scriptCfg ( buildPkgDir </> "Setup" )
+                         , "-package-db=" <> quoteArg ExpandVars scriptCfg tempPkgDbDir
                          , ghcVerbosity verbosity
                          ] ++ map unitIdArg puSetupDepends
 
              -- Specify location of binaries and data directories.
              --
              -- See the commentary around 'depDataDirs' in 'buildUnit'.
-             binDirs = [ quoteArg scriptCfg $ installDir </> "bin" </> Text.unpack ( unUnitId exeUnitId )
+             binDirs = [ quoteArg ExpandVars scriptCfg $ installDir </> "bin" </> Text.unpack ( unUnitId exeUnitId )
                        | exeUnitId <- puExeDepends ]
              setupDepDataDirs =
                dataDirs scriptCfg paths
@@ -224,48 +224,59 @@
               | flagSpecIsEmpty flags
               -> []
               | otherwise
-              -> [ "--flags=" ++ quoteArg scriptCfg ( Text.unpack (showFlagSpec flags) ) ]
+              -> [ "--flags=" <> quoteArg ExpandVars scriptCfg ( Text.unpack (showFlagSpec flags) ) ]
 
+          -- NB: make sure to update the readme after changing
+          -- the arguments that are passed here.
+
+          -- Non-essential arguments (can be overriden by the user).
+          overridableConfigureArgs =
+            [ "--libdir="     <> quoteArg EscapeVars scriptCfg ( "$prefix" </> "lib" )
+            , "--libsubdir="  <> thisUnit'sId
+            , "--dynlibdir="  <> quoteArg EscapeVars scriptCfg ( "$prefix" </> "dynlib"  </> pkgNameVer )
+            , "--libexecdir=" <> quoteArg EscapeVars scriptCfg ( "$prefix" </> "libexec" </> pkgNameVer )
+            , "--docdir="     <> quoteArg EscapeVars scriptCfg ( "$prefix" </> "doc"     </> pkgNameVer )
+            , setupVerbosity verbosity
+            ] ++ flagsArg -- Flags shouldn't really be overridden,
+                          -- but we allow it as an expert feature.
+
           -- Set a different build directory for each unit,
           -- to avoid clashes when building multiple units from the same
           -- package concurrently.
-          buildDir = quoteArg scriptCfg -- Quote to escape \ on Windows.
+          buildDir = quoteArg ExpandVars scriptCfg -- Quote to escape \ on Windows.
                    $ "temp-build" </> thisUnit'sId
 
-          -- Set a different binDir for each executable unit,
-          -- so that we can know precisely which executables have been built
-          -- for the purpose of resumable builds.
-          thisUnit'sBinDir = quoteArg scriptCfg
-                           $ prefix </> "bin" </> thisUnit'sId
-            -- NB: use prefix not installDir here, otherwise the copy command
-            -- will duplicate destDir.
-          thisUnit'sBinDirArg
-            | Exe <- cuComponentType unit
-            = [ "--bindir=" ++ thisUnit'sBinDir ]
-            | otherwise
-            = []
+          -- Arguments essential to the build; can't be overriden by the user.
+          essentialConfigureArgs =
+            [ "--exact-configuration"
+            , "--with-compiler", quoteArg ExpandVars scriptCfg ghcPath
+            , "--prefix"       , quoteArg ExpandVars scriptCfg prefix
+            , "--cid="        <> Text.unpack ( unUnitId puId )
+            , "--package-db=" <> quoteArg ExpandVars scriptCfg tempPkgDbDir
+            , "--datadir="    <> quoteArg EscapeVars scriptCfg ( "$prefix" </> "share" )
+                -- Keep datadir in sync with the 'dataDirs' function.
+            , "--datasubdir=" <> pkgNameVer
+            , "--builddir="   <> buildDir
+            , "--bindir="     <> quoteArg EscapeVars scriptCfg ( "$prefix" </> "bin" </> thisUnit'sId )
+                -- Set a different binDir for each executable unit,
+                -- so that we can know precisely which executables have been built
+                -- for the purpose of resumable builds.
+                -- Keep bindir in sync with 'binDirs' below.
+            ] ++ map ( dependencyArg plan unit ) puDepends
+              ++ [ buildTarget unit ]
 
+          configureArgs
+            =  overridableConfigureArgs
+            ++ userConfigureArgs
+            ++ essentialConfigureArgs
+
           -- Add the output binary directories to PATH, to satisfy executable
           -- dependencies during the build.
-          binDirs = [ quoteArg scriptCfg $
+          -- Keep this in sync with --bindir in essentialConfigureArgs.
+          binDirs = [ quoteArg ExpandVars scriptCfg $
                       installDir </> "bin" </> Text.unpack ( unUnitId exeUnitId )
                     | exeUnitId <- puExeDepends ]
 
-            -- NB: make sure to update the readme after changing
-            -- the arguments that are passed here.
-          configureArgs = [ "--with-compiler", quoteArg scriptCfg ghcPath
-                          , "--prefix", quoteArg scriptCfg prefix
-                          , "--cid=" ++ Text.unpack (unUnitId puId)
-                          , "--package-db=" ++ quoteArg scriptCfg tempPkgDbDir
-                          , "--exact-configuration"
-                          , "--datasubdir=" ++ pkgNameVer
-                          , "--builddir=" ++ buildDir
-                          , setupVerbosity verbosity
-                          ] ++ thisUnit'sBinDirArg
-                            ++ flagsArg
-                            ++ map ( dependencyArg plan unit ) puDepends
-                            ++ userConfigureArgs
-                          ++ [ buildTarget unit ]
           setupExe = RelPath $ runCwdExe scriptCfg "Setup" -- relative to pkgDir
 
       logMessage verbosity Verbose $
@@ -289,7 +300,7 @@
         CP { cwd          = pkgDir
            , prog         = setupExe
            , args         = [ "build"
-                            , "--builddir=" ++ buildDir
+                            , "--builddir=" <> buildDir
                             , setupVerbosity verbosity ]
            , extraPATH    = binDirs
            , extraEnvVars = depDataDirs
@@ -305,9 +316,9 @@
           CP { cwd          = pkgDir
              , prog         = setupExe
              , args         = [ "haddock"
-                              , "--builddir=" ++ buildDir
                               , setupVerbosity verbosity ]
-                                ++ userHaddockArgs
+                              ++ userHaddockArgs
+                              ++ [ "--builddir=" <> buildDir ]
              , extraPATH    = binDirs
              , extraEnvVars = depDataDirs
              , logBasePath  = logPath
@@ -321,8 +332,8 @@
         CP { cwd          = pkgDir
            , prog         = setupExe
            , args         = [ "copy", setupVerbosity verbosity
-                            , "--builddir=" ++ buildDir
-                            , "--destdir", quoteArg scriptCfg destDir ]
+                            , "--builddir=" <> buildDir
+                            , "--destdir", quoteArg ExpandVars scriptCfg destDir ]
            , extraPATH    = []
            , extraEnvVars = []
            , logBasePath  = logPath
@@ -349,10 +360,10 @@
             callProcess $
               CP { cwd          = pkgDir
                  , prog         = setupExe
-                 , args         = [ "register", setupVerbosity verbosity
-                                  , "--builddir=" ++ buildDir
-                                  , "--gen-pkg-config=" ++ pkgRegsFile
-                                  ] ++ extraSetupArgs
+                 , args         = [ "register", setupVerbosity verbosity ]
+                                  ++ extraSetupArgs
+                                  ++ [ "--builddir=" <> buildDir
+                                     , "--gen-pkg-config=" <> pkgRegsFile ]
                  , extraPATH    = []
                  , extraEnvVars = []
                  , logBasePath  = logPath
@@ -368,10 +379,10 @@
               CP { cwd          = pkgDir
                  , prog         = AbsPath ghcPkgPath
                  , args         = [ "register"
-                                  , ghcPkgVerbosity verbosity
-                                  , "--package-db", quoteArg scriptCfg pkgDbDir
-                                  , pkgRegsFile ]
+                                  , ghcPkgVerbosity verbosity ]
                                   ++ extraPkgArgs
+                                  ++ [ "--package-db", quoteArg ExpandVars scriptCfg pkgDbDir
+                                     , pkgRegsFile ]
                  , extraPATH    = []
                  , extraEnvVars = []
                  , logBasePath  = logPath
@@ -387,7 +398,7 @@
 
 -- | The argument @-package-id PKG_ID@.
 unitIdArg :: UnitId -> String
-unitIdArg (UnitId unitId) = "-package-id " ++ Text.unpack unitId
+unitIdArg (UnitId unitId) = "-package-id " <> Text.unpack unitId
 
 -- | The target to configure and build.
 buildTarget :: ConfiguredUnit -> String
@@ -401,7 +412,7 @@
 -- available, ignoring any bounds in the cabal file.
 dependencyArg :: Map UnitId PlanUnit -> ConfiguredUnit -> UnitId -> String
 dependencyArg fullPlan unitWeAreBuilding depUnitId
-  = "--dependency=" ++ Text.unpack (mkDependency pu)
+  = "--dependency=" <> Text.unpack ( mkDependency pu )
   where
     pu :: PlanUnit
     pu = lookupDependency unitWeAreBuilding depUnitId fullPlan
@@ -426,8 +437,8 @@
   = pu
   | otherwise
   = error $ "buildUnit: can't find dependency in build plan\n\
-            \package: " ++ show pkgWeAreBuilding ++ "\n\
-            \dependency: " ++ show depUnitId
+            \package: " <> show pkgWeAreBuilding <> "\n\
+            \dependency: " <> show depUnitId
 
 --------------------------------------------------------------------------------
 -- Directory structure computation helpers
@@ -556,14 +567,7 @@
          -> [ ( String, FilePath ) ]
 dataDirs scriptCfg ( BuildPaths { installDir } ) units =
     [ ( mangledPkgName puPkgName <> "_datadir"
-      , quoteArg scriptCfg $
-          dataDir </> Text.unpack (pkgNameVersion puPkgName puVersion) )
+      , quoteArg ExpandVars scriptCfg $
+          installDir </> "share" </> Text.unpack (pkgNameVersion puPkgName puVersion) )
+        -- Keep this in sync with --datadir in essentialConfigureArgs.
     | ConfiguredUnit { puPkgName, puVersion } <- units ]
-  where
-    dataDir =
-      case scriptStyle scriptCfg of
-        WinStyle   -> installDir
-        PosixStyle -> installDir </> "share"
-      -- NB: this is the default value of 'datadir'.
-      -- If we want the user to be able to specify 'datadir', we should
-      -- make it into an explicit argument, like 'prefix' & 'destdir'.
diff --git a/src/BuildEnv/Script.hs b/src/BuildEnv/Script.hs
--- a/src/BuildEnv/Script.hs
+++ b/src/BuildEnv/Script.hs
@@ -34,7 +34,7 @@
 
     -- ** Configuring build scripts
   , ScriptOutput(..), ScriptConfig(..), hostRunCfg
-  , quoteArg
+  , EscapeVars(..), quoteArg
 
   ) where
 
@@ -202,19 +202,30 @@
     , scriptStyle  = hostStyle
     , scriptTotal  = mbTotal }
 
+-- | Whether to expand or escape variables in a shell script.
+data EscapeVars
+  -- | Allow the shell to expand variables.
+  = ExpandVars
+  -- | Escape variables so that the shell doesn't expand them.
+  | EscapeVars
+  deriving stock Show
+
 -- | Quote a string, to avoid spaces causing the string
 -- to be interpreted as multiple arguments.
-q :: ( IsString r, Monoid r ) => String -> r
-q t = "\"" <> fromString (escapeArg t) <> "\""
+q :: ( IsString r, Monoid r ) => EscapeVars-> String -> r
+q escapeVars t = "\"" <> fromString ( escapeArg t ) <> "\""
   where
     escapeArg :: String -> String
     escapeArg = reverse . foldl' escape []
 
+    charsToEscape :: [ Char ]
+    charsToEscape = case escapeVars of
+      ExpandVars -> [ '\\', '\'', '"' ]
+      EscapeVars -> [ '\\', '\'', '"', '$' ]
+
     escape :: String -> Char -> String
     escape cs c
-      |  '\\' == c
-      || '\'' == c
-      || '"'  == c
+      | c `elem` charsToEscape
       = c:'\\':cs
       | otherwise
       = c:cs
@@ -224,11 +235,15 @@
 --
 -- No need to call this on the 'cwd' or 'prog' fields of 'CallProcess',
 -- as these will be quoted by the shell-script backend no matter what.
-quoteArg :: ( IsString r, Monoid r ) => ScriptConfig -> String -> r
-quoteArg ( ScriptConfig { scriptOutput } ) =
+quoteArg :: ( IsString r, Monoid r )
+         => EscapeVars
+         -> ScriptConfig
+         -> String
+         -> r
+quoteArg escapeVars ( ScriptConfig { scriptOutput } ) =
   case scriptOutput of
     Run      -> fromString
-    Shell {} -> q
+    Shell {} -> q escapeVars
 
 --------------------------------------------------------------------------------
 -- Interpretation
@@ -297,9 +312,9 @@
 stepScript :: ScriptConfig -> BuildStep -> [ Text ]
 stepScript scriptCfg = \case
   CreateDir dir ->
-    [ "mkdir -p " <> q dir ]
+    [ "mkdir -p " <> q ExpandVars dir ]
   LogMessage str ->
-    [ "echo " <> q str ]
+    [ "echo " <> q ExpandVars str ]
   ReportProgress { outputProgress } ->
     case scriptTotal scriptCfg of
       Nothing
@@ -322,7 +337,7 @@
     -- NB: we ignore the semaphore, as the build scripts we produce
     -- are inherently sequential.
     logCommand ++
-    [ "( cd " <> q cwd <> " ; \\" ]
+    [ "( cd " <> q ExpandVars cwd <> " ; \\" ]
     ++ mbUpdatePath
     ++ map mkEnvVar extraEnvVars
     ++
@@ -334,7 +349,7 @@
     , "  echo -e " <>
         "\"callProcess failed with non-zero exit code. Command:\\n" <>
         "  > " <> unquote cmd <> "\\n" <>
-        "  CWD = " <> unquote (q cwd) <> "\""
+        "  CWD = " <> unquote (q ExpandVars cwd) <> "\""
       <> logErr
     ]
     ++ progressReport
@@ -344,7 +359,7 @@
     , "fi" ]
     where
       cmd :: Text
-      cmd = q ( progPath prog ) <> " " <> Text.unwords (map Text.pack args)
+      cmd = q ExpandVars ( progPath prog ) <> " " <> Text.unwords (map Text.pack args)
         --         (1)                                         (2)
         --
         -- (1)
@@ -386,14 +401,14 @@
           Nothing      -> ( [], "", " >&2", [] )
           Just logPath ->
             let stdoutFile, stderrFile :: Text
-                stdoutFile = q ( logPath <.> "stdout" )
-                stderrFile = q ( logPath <.> "stderr" )
+                stdoutFile = q ExpandVars ( logPath <.> "stdout" )
+                stderrFile = q ExpandVars ( logPath <.> "stderr" )
             in ( [ "echo \"> " <> unquote cmd <> "\" >> " <> stdoutFile ]
                , " > " <> stdoutFile <> " 2> >( tee -a " <> stderrFile <> " >&2 )"
                   -- Write stdout to the stdout log file.
                   -- Write stderr both to the terminal and to the stderr log file.
                , " | tee -a " <> stderrFile <> " >&2"
-               , [ "  echo \"Logs are available at: " <> unquote ( q ( logPath <> ".{stdout,stderr}" ) ) <> "\"" ] )
+               , [ "  echo \"Logs are available at: " <> unquote ( q ExpandVars ( logPath <> ".{stdout,stderr}" ) ) <> "\"" ] )
 
       progressReport :: [Text]
       progressReport =
