Skip to content

Resolve project, package and component from anywhere. #7471

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

Open
kindaro opened this issue Jul 1, 2021 · 13 comments
Open

Resolve project, package and component from anywhere. #7471

kindaro opened this issue Jul 1, 2021 · 13 comments

Comments

@kindaro
Copy link

kindaro commented Jul 1, 2021

It would be good in many ways if Cabal resolved some project, package and component from anywhere on the file system. To name a few:

  • Annoying errors such as this would go away:
    % cabal repl
    cabal: No targets given and there is no package in the current directory. Use
    the target 'all' for all packages in the project or specify packages or
    components by name or location. See 'cabal build --help' for more details on
    target options.
    
  • A default project, package and component would provide a default environment for using ghci as a calculator. One can add their favourite dependencies and language extensions, and cabal repl would respect that.

There are two ways to accomplish this.

The strong way.

The strong way is to wire into Cabal how to resolve a project, package and component from anywhere in the file system. Properties:

  • Backwards compatibility. The assignment of a project, package and component is an extension of the partial assignment that we already have.
  • Totality. From any place in the file system, there is a unique assignment of a project, package and component.

This assumes that a default project is always there. Cabal would initialize it as needed.

The weak way.

The weak way is to make Cabal respect an environment variable pointing to a default project but otherwise behave the same. We still require backwards compatibility, but not totality. Currently, there are two ways to resolve a project, in the order of precedence:

  1. The command line option --project-file.
  2. The presence of cabal.project or *.cabal anywhere in or above the current working directory.

We add another way:

  1. The presence of an environment variable.

So, due to its lower precedence, Cabal will ignore the environment variable if already in a project or a package.

Cabal will not uniquely resolve a package or a component if there are several of them in the project pointed at, so annoying errors stay. This way only covers the calculator use case. The user is responsible for setting the project up and wiring it into Cabal via the environment variable.


We can also combine both ways: have hard wired defaulting and allow overriding the default project, package and component by an environment variable. This would allow one to swap the default project at will.

This has been previously discussed in #6481.

I can write the code.

@Mikolaj
Copy link
Member

Mikolaj commented Jul 1, 2021

To clarify: in the strong way, do you mean that if there is no other plausible choice, the default stuff is going to be chosen (and initialized, if not present yet)? I'm confused by "wire into Cabal how to resolve a project" not mentioning 'default'. The weak way is clear.

BTW, my personal preference is for the strong way. I'd specify the location of the default project in the ~/.cabal/config file and, as discussed in issues about CABAL_STORE variable, the location of ~/.cabal/config can be overridden by environment variable and that's the only variable we ever need, because everything else can be specified in the (overridden) config file.

@kindaro
Copy link
Author

kindaro commented Jul 1, 2021

Yes. I see it like this:

  • When we are in a project (that is, a project or package configuration file is found in or above the current working direcory), and it has at least one package with at least one component (can it be otherwise?), resolve into some package and component in this project.
  • Otherwise, resolve into some package and component in the global project.

So, we make a best effort to resolve into something locally sensible, and fall through onto the default project if all else fails.

@phadej
Copy link
Collaborator

phadej commented Jul 1, 2021

Otherwise, resolve into some package and component in the global project.

This isn't simple as that. cabal install pandoc - should it take global package dependencies into account or not?

In my opinion it shouldn't, i just want pandoc executable.

The implicit global package will add a new implicit thing, and ruin all the work done to try to remove implicit assumptions from cabal-install.

The less implicit stuff cabal-install uses the easier it is to understand what happens (and why or why not). even at the cost of slight inconveniences.

@kindaro
Copy link
Author

kindaro commented Jul 1, 2021

What is to stop cabal install pandoc from resolving into the pandoc package? We are not adding pandoc to the global project, do we.

My understanding is that cabal install is a convenience command that behaves exactly like a combination of cabal get, cabal build, cabal list-bin and cp. Is there more to it than that?

The way to assure quality is to verify the properties of the few core functions and then make sure that all convenience functions are nothing but combinations of the verified core.

@Mikolaj
Copy link
Member

Mikolaj commented Jul 1, 2021

@kindaro: the problem with cabal install foo being processed in a auto-created project with single package foo is that sometimes there is package foo in the legit project you are in and you want all the fancy settings from cabal.project to apply when building the package. Or the same even though the package is on hackage. I guess we'd need to decouple cabal install that takes current project into account and cabal install that constructs a dummy project with just one package. Seems messy. Any better idea?

@kindaro
Copy link
Author

kindaro commented Jul 1, 2021

Oh, I see. So cabal install has some special cases for us to deal with. I gotta think about it.

@kindaro
Copy link
Author

kindaro commented Jul 23, 2021

What if we say that cabal install is special anyway? It only ever resolves into executable components, while commands such as cabal build and cabal repl can resolve into any component. If so, then cabal install is going to need a special logic anyway, and we can special case it all the way. Is this problematic in some way?

@Mikolaj
Copy link
Member

Mikolaj commented Jul 23, 2021

From a coding point of view, I think cabal install is handled specially already, so possibly it's not a problem to decouple it from the other commands even more. Plausibly it may simplify the common code that no longer has to take cabal install into account. A prototype would demonstrate if that's the case.

However, I'm more worried about the semantics. We'd need to check what the users use and preserve it and offer them a good story, competitive to "like cabal build but with these tweaks". I still don't see how we can do better than splitting cabal install into a command that is similar to cabal build and another one that is not, assuming the two-fold split matches prevalent use cases (in-place, from hackage).

@kindaro
Copy link
Author

kindaro commented Jul 23, 2021

Mikolaj, could you please articulate the nature of the split you are mentioning? I see 2 possible readings:

A. The split between cabal install <an executable> (A₁) and cabal install --lib <a library> (A₂).
B. The split between cabal install (B₁, does something with the default component of the default package in the context of the current project) and cabal install <a component> (B₂, does something with the specified component in an appropriate context).

So, there are so far 4 scenarios to consider. I actually find all of them to be problematic in one way or another. For example, suppose I say cabal install x, where x is a package with a library in the current project. Should it write the library into the dependencies of the default package of the default project?

This unearths the realization that install is actually a binary verb: you want to be able to specify both the «what» you wish to install and the «where to», with appropriate defaulting. So for example install x into y, where x is an executable and y is a project, would mean «build x in the context of y and put the binary into the appropriate location». Possible values for «where to» would include a specific project, the default project and a fresh fake project. So, the install command has the type ⟨maybe what; maybe where to⟩ → build; something else where something else depends on the kind of the what:

  • Executable → put the binary into the appropriate location.
  • Library → wire it into the dependencies of the where to.
  • Test suite → error out.

Is this the right way to put the question forward?

@Mikolaj
Copy link
Member

Mikolaj commented Jul 23, 2021

The split I have in mind (but there may be better lines of divide; this one was in response to the problem with the auto-created default central project conflicting with the project (directory) in which cabal install is invoked) is as mentioned earlier:

I guess we'd need to decouple cabal install that takes current project into account and cabal install that constructs a dummy project with just one package.

(in-place, from hackage)

To reword the idea: it may be helpful to have cabal install-locally foo and cabal install-from-hackage foo that differ in where the foo is looked up (in one of package in current project (directory) vs on Hackage) and whether the configuration form the current project or the default central project is taken into account.

As I said, this is a messy idea, but I don't have a better solution to the conflict of the potential two conflicting project configurations.

@Mikolaj
Copy link
Member

Mikolaj commented Jul 23, 2021

Oh, and I mean only the exe-installing cabal install. The --lib is a completely different issue.

@jneira
Copy link
Member

jneira commented Jul 23, 2021

there is more sources to install, no? you can install a local (and remote?) tarball, you could install from a git repo, not sure if they will fit some of the other two

@Mikolaj
Copy link
Member

Mikolaj commented Jul 23, 2021

there is more sources to install, no? you can install a local (and remote?) tarball, you could install from a git repo, not sure if they will fit some of the other two

Right. The differentiating factor might be if you want the project config present (by chance perhaps?) in your current directory to affect the installation. That solves a few cases. I have no idea how installing from a git repo address works currently --- does it clone the repo and run cabal inside the cloned directory, in effect using the project configuration from that directory instead of from pwd? Edit: I do know the cabal.project file from Hackage archive is ignored (should it be?).

Anyway, we could even allow all kinds of targets (with sufficient disambiguation) for both ways of resolving project configuration. So, in a project directory with a package directory foo you might be able to run cabal install hackage:foo --use-local-project-config and well as cabal install ./foo --use-default-project-config and cabal install git:some-address/foo --use-downloaded-project-config and any other sensible combinations. The combinations might also involve whether other packages should be sourced locally or from Hackage (but all should be affected by single project configuration).

I don't think this is a good idea, but it demonstrates the design space we need to prune.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants