Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Directly call in-library functions to build packages #9871

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

sheaf
Copy link
Collaborator

@sheaf sheaf commented Apr 8, 2024

Template Β: This PR does not modify cabal behaviour (documentation, tests, refactoring, etc.)

This PR modifies cabal-install to directly go through the Cabal library when building packages, instead of the Setup interface (except of course for build-type: Custom packages).

Overview of changes:

  • Addition of LibraryMethod to SetupMethod(*), in which we directly invoke the Cabal configure, build, ... functions.
    • This required a bit of GADT trickery to accomodate the fact that configure returns a LocalBuildInfo which must then be passed to subsequent phases, while with the old Setup interface everything returns IO () and communication is done through the filesystem (e.g. the local build info file).
  • New library hooks-exe, provisioning:
    • CallHooksExe: the API for communicating with an external hooks executable (cabal-install depends on this)
    • HooksExe: the necessary functions for creating a hooks executable (cabal-install adds a dependency on this when compiling a package with build-type: Hooks).
    • This library depends on the new CommunicationHandle functionality from process PR #308.

(*) NB: SetupMethod/SetupWrapper is now a bit of a misnomer, because the whole aim of this PR is to no longer go through the Setup interface (and its old UserHooks incarnations such as defaultMainWithHooks ). New naming conventions welcome!
The main change is in Distribution.Client.SetupWrapper: we add a new SetupMethod (probably a misnomer at this point because we no longer go through Setup; new names for this datatype are welcome) in which we directly invoke the Cabal configure, build etc functions rather than going through Setup (or the equivalent defaultMainWithUserHooks interface).

TODO:

@sheaf sheaf marked this pull request as draft April 8, 2024 10:45
@andreabedini
Copy link
Collaborator

I tried compiling this with

source-repository-package
  type: git
  location: https://github.com/sheaf/process
  tag: 61b1de95e72acda5d773c972c7484b1a792ca9e5
  post-checkout-command: autoreconf -i

but it fails because System.Process.CommunicationHandle in process does not export useCommunicationHandle. Am I missing something?

@sheaf sheaf force-pushed the wip/cabal-install-hooks branch 2 times, most recently from 253e4d5 to d4a6532 Compare April 11, 2024 08:47
@sheaf
Copy link
Collaborator Author

sheaf commented Apr 11, 2024

I tried compiling this with

source-repository-package
  type: git
  location: https://github.com/sheaf/process
  tag: 61b1de95e72acda5d773c972c7484b1a792ca9e5
  post-checkout-command: autoreconf -i

but it fails because System.Process.CommunicationHandle in process does not export useCommunicationHandle. Am I missing something?

I've updated the PR now, but please note that this PR is still work in progress and there are quite a few issues that I have yet to resolve.

I didn't know about post-checkout-command, that's neat.

@sheaf sheaf force-pushed the wip/cabal-install-hooks branch 8 times, most recently from 17278dc to c209ca5 Compare April 17, 2024 16:42
@sheaf sheaf force-pushed the wip/cabal-install-hooks branch 5 times, most recently from 7392807 to bbc2418 Compare April 29, 2024 15:14
@sheaf sheaf force-pushed the wip/cabal-install-hooks branch 5 times, most recently from e34ac10 to 0085999 Compare May 3, 2024 12:01
@sheaf sheaf changed the title Draft: SetupHooks integration for cabal-install Draft: Directly call in-library functions to build packages May 3, 2024
@sheaf sheaf force-pushed the wip/cabal-install-hooks branch 3 times, most recently from 2469702 to c3554cd Compare May 6, 2024 13:40
@sheaf
Copy link
Collaborator Author

sheaf commented May 6, 2024

I have successfully built all of the clc-stackage repository with this patched version of cabal (I included an additional patch to ignore logging handles, to avoid using self-exec instead of in-library build method).

@sheaf sheaf force-pushed the wip/cabal-install-hooks branch 2 times, most recently from ee74d63 to 243f1c8 Compare May 7, 2024 10:51
@sheaf sheaf force-pushed the wip/cabal-install-hooks branch 7 times, most recently from ceb9782 to b24aaba Compare May 9, 2024 15:01
@sheaf sheaf changed the title Draft: Directly call in-library functions to build packages Directly call in-library functions to build packages May 9, 2024
Comment on lines +1087 to +1082
configureCompiler
:: Verbosity
-> SetupScriptOptions
-> IO (Compiler, ProgramDb, SetupScriptOptions)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #9735 I was bothered by this threaded mutable state. Removing it boilds down to splitting SetupScriptOptions in V1/V2 versions. The first has many unknown because it's made with miminal planning but the V2 version has already everything we might want to know.

&& bt == Custom =
return (packageVersion pkg, Nothing, options')
installedCabalVersion verbosity pkg _bt options' compiler progdb = do
index <- maybeGetInstalledPackages verbosity options' compiler progdb
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

installedCabalVersion is only called once (L949) and right after configureCompiler which pre-emptively calls maybeGetInstalledPackages knowing it's going to needed here.
If I got this right, this function signature can be simplified down to

installedCabalVersion
  :: Verbosity
  -> InstalledPackageIndex
  -> VersionRange
  -> PackageId
  -> BuildType
  -> IO (Version, Maybe InstalledPackageId)

which has the advantage of not dealing with SetupScriptOptions mutation's. See my attempt https://github.com/haskell/cabal/pull/9735/files#diff-6ff0f0024945e11d4f23279d3d025c146ef7b28d15e29fefe73f1dab10a80325R793-R799

@andreabedini
Copy link
Collaborator

@sheaf This is impressive. I took the liberty of making some comments about SetupWrapper. I hope you don't mind. I think this subsumes #9735, what do you think?

@sheaf
Copy link
Collaborator Author

sheaf commented May 10, 2024

@sheaf This is impressive. I took the liberty of making some comments about SetupWrapper. I hope you don't mind. I think this subsumes #9735, what do you think?

This is great, I definitely wanted your feedback about SetupWrapper. My changes don't entirely subsume yours at the moment, but do let me know how you would like to proceed.

Comment on lines +496 to +504
= do
abiOK <-
if buildType' == Hooks
then do
-- NB: compileExternalSetupMethod compiles the hooks executable.
_ <- compileExternalSetupMethod verbosity options pkg Hooks
externalHooksABI <- externalSetupHooksABI $ hooksProgFilePath (useWorkingDir options) (useDistPref options)
let internalHooksABI = hooksVersion
return $ externalHooksABI == internalHooksABI
else return True
if abiOK
then do
debug verbosity $ "Using in-library setup method with build-type " ++ show buildType'
return $ with (cabalVersion, LibraryMethod, options)
else do
debug verbosity $ "Hooks ABI mismatch; falling back to external setup method."
withExternalSetupMethod
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, in case of the Hooks build-type, this boils down to:

      _ <- compileExternalSetupMethod verbosity options pkg Hooks
      externalHooksABI <- externalSetupHooksABI $ hooksProgFilePath (useWorkingDir options) (useDistPref options)
      let internalHooksABI = hooksVersion
      let abiOk = externalHooksABI == internalHooksABI
      if abiOk
          return $ with (cabalVersion, LibraryMethod, options)
        else do
          withExternalSetupMethod

Is there an advantage of using the LibraryMethod given we had to compile an external setup anyway just to check the ABI?

Now I am wondering, is this always the case? That is, is there a case where we are sure we can use the library method without compiling the script first?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right that currently we compile both the external hooks executable and the setup executable here, even though we only need to compile the hooks executable in this code path (falling back to the setup executable when there is an ABI mismatch). I can refactor the code to avoid the compilation of the external setup executable (it makes sense to do so).

I think the existing "cabal version" logic (what's at the top of withSetupMethod) effectively guarantees that we will have matching Cabal ABI, but given that we need to compile the external hooks executable anyway it makes sense to do an additional version check as belt-and-braces (mismatched Binary instance errors can be a real nightmare to debug). I don't think there's any other work that we can avoid doing, other than not compiling the Setup executable if we don't need it; we need to compile the hooks executable when using build-type: Hooks.

BTW, there are other benefits to going through the in-library method even if we pessimistically compile the setup executable, for instance we skip re-configuring things that cabal-install has already configured.

This commit makes it so that cabal-install emits PackageDescription
parser warnings at normal verbosity or above. Previously, we used to
emit warnings at verbosity >= verbose, but now that we are going through
in-library methods this meant we could skip ever emitting some warnings,
which is not desirable.
@alt-romes alt-romes force-pushed the wip/cabal-install-hooks branch 4 times, most recently from 4705b31 to d41884b Compare October 31, 2024 16:13
sheaf and others added 6 commits October 31, 2024 16:40
This commit modifies the SetupWrapper mechanism, adding a new way of
building a package: directly calling Cabal library functions (e.g.
'build', 'configure' etc).

This currently requires a bit of GADT trickery to accomodate the fact
that configure returns a LocalBuildInfo which must then be passed to
subsequent phases, while with the old Setup interface everything returns
IO () and communication is done through the filesystem
(the local build info file).

To handle 'build-type: Hooks', this commit introduces the hooks-exe
package, which contains:

  - the hooks-exe library, used to compile a set of SetupHooks into an
    external executable,
  - the hooks-cli library, which is used by cabal-install to communicate
    with an external hooks executable.

This package depends on the new `CommunicationHandle` functionality from
haskell/process#308.
The `hooks-exe` package enables `SetupHooks` values to be converted into
a `Setup.hs` executable which can be executed independently of Cabal.
The `Setup.hs` executable wrapping `SetupHooks` is quite important to
preserve the interface used by other tools when packages migrate to
`Hooks` from `Custom`.

Even though `hooks-exe` is an internal dependency required by the `Setup.hs`
wrapper around `SetupHooks`, it is a dependency nonetheless.

Given the internal nature of `hooks-exe`, we don't want to impose on our
users the obligation to add a dependency on `hooks-exe` in their
setup-depends field. Instead, we want `hooks-exe` to be implicitly added
to the setup dependencies. This commit does that exactly.
This commit updates the bootstrap plans to account for the new
local hooks-exe package, which cabal-install depends on.
This commit contains testsuite changes to account for the change in
how cabal-install calls Cabal.

The most common change is that we emit warnings at a slightly different
point during compilation.
This SetupMethod is no longer used; it is replaced by the InLibrary
SetupMethod which directly calls Cabal library functions instead of
going through defaultMain.
This moves some functions to the top-level and threads through the
required arguments. This helps to separate concerns a bit more, as
opposed to having all functions defined in one big blob.
@alt-romes alt-romes force-pushed the wip/cabal-install-hooks branch 2 times, most recently from 157cbb5 to 24de4e0 Compare November 5, 2024 11:48
`reusingGHCCompilationArtifacts` assumed the existence of a build folder
where objects were written (even if empty), but with InLibrary calls
this is no longer necessarily true.

Previously, the build folder ended up always existing because the call
of `configure` through `Setup` created the folder.
However, now that we may call Cabal the library directly, the existence
of this directory is no longer guaranteed.

Easy fix: don't try to copy the build folder if it doesn't exist yet.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants