build-env 1.0.0.0 → 1.1.0.0
raw patch · 6 files changed
+120/−92 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ BuildEnv.Script: EscapeVars :: EscapeVars
+ BuildEnv.Script: ExpandVars :: EscapeVars
+ BuildEnv.Script: data EscapeVars
+ BuildEnv.Script: instance GHC.Show.Show BuildEnv.Script.EscapeVars
- BuildEnv.Script: quoteArg :: (IsString r, Monoid r) => ScriptConfig -> String -> r
+ BuildEnv.Script: quoteArg :: (IsString r, Monoid r) => EscapeVars -> ScriptConfig -> String -> r
Files
- build-env.cabal +2/−7
- changelog.md +9/−0
- readme.md +12/−7
- src/BuildEnv/Build.hs +1/−1
- src/BuildEnv/BuildOne.hs +64/−60
- src/BuildEnv/Script.hs +32/−17
build-env.cabal view
@@ -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
changelog.md view
@@ -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.
readme.md view
@@ -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`
src/BuildEnv/Build.hs view
@@ -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.
src/BuildEnv/BuildOne.hs view
@@ -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'.
src/BuildEnv/Script.hs view
@@ -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 =