-
Couldn't load subscription status.
- Fork 723
Description
Specification. External Custom setups are an extension to the custom-setup stanza which permit a package to specify an external Haskell executable (ala #3708). The syntax is:
custom-setup
setup-tool: pkgname:exename >= 0.2 && < 0.3
The intended semantics is that this specifies to build the executable exename from pkgname (under the specified version constraints), and then use this executable as a Setup script to compile the package. If the executable name is omitted, it is assumed that the executable has the same name as the package.
A setup-tool is an executable dependency, and is thus solved in the same manner as other executable dependencies (c.f. c0a4860); its dependencies are independent from the main library, and may even be compiled with an entirely different compiler toolchain than the main library.
A non setup-tool custom-setup is simply an "anonymous" executable which (1) can be built without needing a Setup script, and (2) lives inline in the same package.
To determine the supported API of a setup-tool, we look its direct dependencies and determine what version of the Cabal library is used. It is an ERROR to not depend on the Cabal library. (A future extension might lift this restriction by allowing the setup-tool executable to directly specify what version of the Cabal library it supports, or move to a more flexible command-line driven feature test interface.)
Motivation. The primary motivation is #1493: there is no way to ask Cabal to build a custom Setup stanza using a different compiler than the one that is being used to build the main library. Treating the custom Setup as an executable dependency moves us towards solving this problem: once a setup is a component by itself, we only need to ensure executable dependencies have a compiler chosen independently from the library itself. While it is true the same effect could be had by associating with every package a host compiler as well as a target compiler, there are other benefits to decoupling target/host compilers in this way: in particular, the same solution can be used to handle build-tools (and tool-depends #3708) and compiler plugin dependencies (#2965) which also need to distinguish between target and host compiler. Allowing every item in the install plan to have a distinct compiler, depending on its role in the build, is superbly natural.
There are also two minor bonus effects:
- new-build has per-component builds for non-Custom packages, but the components of a Custom package are currently built sequentially. It would be nice to also be able to atomize these packages into separate components, but a naive implementation would require each component to rebuild the Custom component from scratch. With external custom setups, we simply treat traditional custom setups as inplace, anonymous executables that can be built in a special way.
- Custom executable packages can use all of Cabal's features (including having a Custom setup for themselves!) whereas code in a custom-setup is extremely impoverished. (Though this is not a big deal in practice as you can always just put the Custom Setup code in a library.)
Implementation. Here are work items, with dependencies between them:
-
PKGFORMAT: A new field
setupTool :: Maybe ExeDependencyneeds to be added forcustom-setup. The relevant data structure isSetupBuildInfoinCabal/Distribution/Types/SetupBuildInfo.hs. You'll have to define a newExeDependencytype modeled off ofDependency(this type can be reused fortool-depends), recordingPackageName,String(executable name) andVersionRange. Don't forget to add parser support (setupBInfoFieldDescrsinCabal/Distribution/PackageDescription/Parse.hsas well as inCabal/Distribution/PackageDescription/Parsec/FieldDescr.hsas we presently have two parser codepaths.) For completeness, it's probably a good idea to check if bothsetupDependsandsetupToolare set inCabal/Distribution/PackageDescription/Check.hs; if they are both set that is an error. -
SETUPWRAPPER:
SetupWrapperneeds a way to be told, "No, don't do anything interesting, just use this executable (supporting this Cabal interface)". We should add a new field toSetupScriptOptionswhich specifies when a specific file path of an executable to use as a setup script is known, as well as what version of the Cabal interface is understood. ThengetSetupMethodcan test if these are known, and if so just directly return them as the method to use (the stored cabal version,ExternalMethodpath to setup executable, and the unmodified options). -
DEPSOLVER (depends on PKGFORMAT): We need to teach the dependency solver how to solve for
setup-tooldependencies. The code for this will live incabal-install/Distribution/Solver/Modular/IndexConversion.hs; we actually have most of the pieces in place. Look for "-- build-tools dependencies": this demonstrates how to add executable dependencies to the solver. The key function isconvExeDep, which makes a qualified dependency on an executable. Note that you'll actually add the dependency on the setup executable toconvSetupBuildInfo. It should be fine to put the dependency underComponentSetupcomponent; we'll just have to read it out from there later. -
PLANNING (depends on DEPSOLVER): The dependency solver will solve for the executable and pass the solution to the
exe_deps0argument ofelaborateSolverToComponentsincabal-install/Distribution/Client/ProjectPlanning.hs; you can get out the setup executable dependency usingCD.setupDeps exe_deps0. Now we need to enhanceElaboratedPlanPackagewith a few more fields to track external custom setups.First, we need to record the unit id of our setup dependency, as well as the path to the executable. This can be done by getting the
ElaboratedPlanPackagefor our SolverId usingelaborateExeSolverIdin the same way thatexe_deps0is processed currently.elabOrderDependenciesneeds to be modified to ensure that we add an ordering dependency on our custom setup.Second, we need record the version of the Cabal interface our setup-tool supports. Probably the easiest way to do this is to unconditionally record in
ElaboratedConfiguredPackagethe version of a Cabal library that you depend on, if you have a direct dependency on Cabal. Then we can just read it off from theElaboratedConfiguredPackagewe get fromelaborateExeSolverId(you'll need to refactor it in the same way aselaborateLibSolverIdis factored, since it returns aConfiguredId, not theElaboratedConfiguredPackage; also, you'll need to peel it out ofElaboratedPlanPackage; there are a number of examples of this, look forPreExisting. If it helps, the pre-existing case should be impossible) -
SETUP_OPTIONS (depends on PLANNING). With all of this information in hand, we are finally ready to hook everything up. In
setupHsScriptOptionsincabal-install/Distribution/Client/ProjectPlanning.hs, we feed in our newly recorded external setup dependency path and Cabal version to the returnedSetupScriptOptions, so that your modifications toSetupWrapperkick in. Delete the TODO and pat yourself on the back. Write a test inintegration-tests. -
PARALLEL_CUSTOM (optional). It should be possible already to enable per-component parallel builds with custom setups by modifying the assignment of
eligibleincabal-install/Distribution/Client/ProjectPlanning.hs: replacing the existing code with the commented out code should work. The comment is wrong and I don't think you need PER-COMPONENT INPLACE SETUP to do this. Write a test. -
PER-COMPONENT INPLACE SETUP (optional). The big complication here is that the existing code in ProjectBuilding for building a component (
buildInplaceUnpackedPackageandbuildAndInstallUnpackedPackage) doesn't work on inplace custom setups, which don't have any setup script to build themselves. Instead, the code for building the custom setup lives in SetupWrapper.I think the easiest way to deal with this is to implement a new codepath for building custom setup (in ProjectBuilding; you'll probably test on elabPkgOrComponent to find out if it's a setup component) which just runs SetupWrapper in order to build the setup script, then we bail out and copy that setup script into the store if it's an unpacked package (use the executable path that you allocated for the "setup executable" for planning). One thing to be careful about is that
elabSetupDependencieswould have to return the COMPONENTS dependencies, if the component is a setup script. Look carefully atsetupHsScriptOptionsto make sure all the parameters are getting the correct options.
c0a4860 should provide a decent blueprint of the files you will have to touch