Skip to content

External custom setup #4047

@ezyang

Description

@ezyang

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 ExeDependency needs to be added for custom-setup. The relevant data structure is SetupBuildInfo in Cabal/Distribution/Types/SetupBuildInfo.hs. You'll have to define a new ExeDependency type modeled off of Dependency (this type can be reused for tool-depends), recording PackageName, String (executable name) and VersionRange. Don't forget to add parser support (setupBInfoFieldDescrs in Cabal/Distribution/PackageDescription/Parse.hs as well as in Cabal/Distribution/PackageDescription/Parsec/FieldDescr.hs as we presently have two parser codepaths.) For completeness, it's probably a good idea to check if both setupDepends and setupTool are set in Cabal/Distribution/PackageDescription/Check.hs; if they are both set that is an error.

  • SETUPWRAPPER: SetupWrapper needs 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 to SetupScriptOptions which 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. Then getSetupMethod can test if these are known, and if so just directly return them as the method to use (the stored cabal version, ExternalMethod path to setup executable, and the unmodified options).

  • DEPSOLVER (depends on PKGFORMAT): We need to teach the dependency solver how to solve for setup-tool dependencies. The code for this will live in cabal-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 is convExeDep, which makes a qualified dependency on an executable. Note that you'll actually add the dependency on the setup executable to convSetupBuildInfo. It should be fine to put the dependency under ComponentSetup component; 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_deps0 argument of elaborateSolverToComponents in cabal-install/Distribution/Client/ProjectPlanning.hs; you can get out the setup executable dependency using CD.setupDeps exe_deps0. Now we need to enhance ElaboratedPlanPackage with 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 ElaboratedPlanPackage for our SolverId using elaborateExeSolverId in the same way that exe_deps0 is processed currently. elabOrderDependencies needs 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 ElaboratedConfiguredPackage the 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 the ElaboratedConfiguredPackage we get from elaborateExeSolverId (you'll need to refactor it in the same way as elaborateLibSolverId is factored, since it returns a ConfiguredId, not the ElaboratedConfiguredPackage; also, you'll need to peel it out of ElaboratedPlanPackage; there are a number of examples of this, look for PreExisting. 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 setupHsScriptOptions in cabal-install/Distribution/Client/ProjectPlanning.hs, we feed in our newly recorded external setup dependency path and Cabal version to the returned SetupScriptOptions, so that your modifications to SetupWrapper kick in. Delete the TODO and pat yourself on the back. Write a test in integration-tests.

  • PARALLEL_CUSTOM (optional). It should be possible already to enable per-component parallel builds with custom setups by modifying the assignment of eligible in cabal-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 (buildInplaceUnpackedPackage and buildAndInstallUnpackedPackage) 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 elabSetupDependencies would have to return the COMPONENTS dependencies, if the component is a setup script. Look carefully at setupHsScriptOptions to make sure all the parameters are getting the correct options.

c0a4860 should provide a decent blueprint of the files you will have to touch

Metadata

Metadata

Assignees

Labels

Cabal: customcabal-install: v2-build systemAffecting v2-build and related commands that use v2-architecture (aka "nix local builds").

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions