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

Allow executables to have Main modules with other names #1847

Closed
mcandre opened this Issue May 5, 2014 · 19 comments

Comments

Projects
None yet
5 participants
@mcandre

mcandre commented May 5, 2014

Originally #172. This ticket edited by @ttuegel on 2015-09-18 to include original problem description.

Query on haskell-cafe:

It seems the meaning of the -main-is switch for GHC and the Main-Is
build option for Cabal executables differ. With GHC, I can point to
any function "main" in any module, but in Cabal I must point to a
filename with precisely the module name "Main". This is tying my hands
with regard to organizing a default executable and exposing some of
its functionality as a library. Is there a way to get around this
restriction?
Concretely, I want to point Cabal's Main-Is to Program/Main.hs
which starts with
  module Program.Main where
instead of just
  module Main where

So in GHC it has this meaning:

-main-is thing
The normal rule in Haskell is that your program must supply a
main function in module Main. When testing, it is often
convenient to change which function is the "main" one, and
the -main-is flag allows you to do so. The thing can be one of:
A lower-case identifier foo. GHC assumes that the main
function is Main.foo.
    An module name A. GHC assumes that the main function
    is A.main.
    An qualified name A.foo. GHC assumes that the main
    function is A.foo.

In Cabal the main-is: field specifies the file name of the Main module.

GHC's -main-is flag is an extension to Haskell that is not supported by the other Haskell implementations.

@ttuegel ttuegel added the invalid label Feb 28, 2015

@ttuegel

This comment has been minimized.

Show comment
Hide comment
@ttuegel

ttuegel Feb 28, 2015

Member

Cabal does allow any filename for an executable.

Member

ttuegel commented Feb 28, 2015

Cabal does allow any filename for an executable.

@gbaz

This comment has been minimized.

Show comment
Hide comment
@gbaz

gbaz Sep 18, 2015

Collaborator

The problem is more subtle, and just bit someone again. The original ticket characterized it correctly, and this does not. cabal does not allow for arbitrary module names for executables -- i.e. whatever the file is named, the module must be named main.

(this was the underlying cause of the confusion here: https://www.reddit.com/r/haskell/comments/3lax6y/discussion_thread_about_stack/cv5uv0p)

Collaborator

gbaz commented Sep 18, 2015

The problem is more subtle, and just bit someone again. The original ticket characterized it correctly, and this does not. cabal does not allow for arbitrary module names for executables -- i.e. whatever the file is named, the module must be named main.

(this was the underlying cause of the confusion here: https://www.reddit.com/r/haskell/comments/3lax6y/discussion_thread_about_stack/cv5uv0p)

@ttuegel

This comment has been minimized.

Show comment
Hide comment
@ttuegel

ttuegel Sep 18, 2015

Member

@gbaz Try ghc-options: -main-is Foo. Can ghc --make alone even infer which module is Main?

Member

ttuegel commented Sep 18, 2015

@gbaz Try ghc-options: -main-is Foo. Can ghc --make alone even infer which module is Main?

@23Skidoo

This comment has been minimized.

Show comment
Hide comment
@23Skidoo
Member

23Skidoo commented Sep 18, 2015

@ttuegel +1.

@gbaz

This comment has been minimized.

Show comment
Hide comment
@gbaz

gbaz Sep 18, 2015

Collaborator

Yes, ghc --make can handle this. As the original ticket this references stated, GHC does this, and Cabal does not, and the choice was made to preserve this behavior because the GHC main-is support is GHC specific, and it was desired to support "all compilers."

At this point, I think that ship has well sailed in this regard, with regards to legacy compilers. And we can expect new compilers to provide this.

Anyway, the linked post above illustrates how this behavior can end up being quite confusing. At a minimum we should perhaps hard fail if we can't find a Main module instead of providing Warning: output was redirected with -o, but no output will be generated because there is no Main module. Not generating output seems like it should be an error to me :-)

Collaborator

gbaz commented Sep 18, 2015

Yes, ghc --make can handle this. As the original ticket this references stated, GHC does this, and Cabal does not, and the choice was made to preserve this behavior because the GHC main-is support is GHC specific, and it was desired to support "all compilers."

At this point, I think that ship has well sailed in this regard, with regards to legacy compilers. And we can expect new compilers to provide this.

Anyway, the linked post above illustrates how this behavior can end up being quite confusing. At a minimum we should perhaps hard fail if we can't find a Main module instead of providing Warning: output was redirected with -o, but no output will be generated because there is no Main module. Not generating output seems like it should be an error to me :-)

@ttuegel ttuegel reopened this Sep 18, 2015

@ttuegel ttuegel changed the title from Allow flexibility in main program filenames to Allow executables to have Main modules with other names Sep 18, 2015

@gbaz

This comment has been minimized.

Show comment
Hide comment
@gbaz

gbaz Sep 18, 2015

Collaborator

@ttuegel I'm happy to look at this if you or someone else could toss me a few pointers regarding the easiest path to tackle it.

Collaborator

gbaz commented Sep 18, 2015

@ttuegel I'm happy to look at this if you or someone else could toss me a few pointers regarding the easiest path to tackle it.

@ttuegel

This comment has been minimized.

Show comment
Hide comment
@ttuegel

ttuegel Sep 18, 2015

Member

@gbaz The main barrier to fixing this today is the fact that we (Cabal) have no idea what the module name in that file is. We could change the meaning of the main-is: field of the package description, but that would certainly break a lot of things. We could add a field main-module-is: as suggested in the original ticket, but that doesn't meet your criteria for the same reason that ghc-options: -main-is Foo doesn't. We can't even fail if there's no Main module, because we have no idea if that's the case or not! As far as I know, there is no path forward on this problem, short of rewriting Cabal to drive GHC through its API rather than the command line.

Member

ttuegel commented Sep 18, 2015

@gbaz The main barrier to fixing this today is the fact that we (Cabal) have no idea what the module name in that file is. We could change the meaning of the main-is: field of the package description, but that would certainly break a lot of things. We could add a field main-module-is: as suggested in the original ticket, but that doesn't meet your criteria for the same reason that ghc-options: -main-is Foo doesn't. We can't even fail if there's no Main module, because we have no idea if that's the case or not! As far as I know, there is no path forward on this problem, short of rewriting Cabal to drive GHC through its API rather than the command line.

@gbaz

This comment has been minimized.

Show comment
Hide comment
@gbaz

gbaz Sep 18, 2015

Collaborator

if you point me to the relevant files in the cabal codebase even, I could have a think :-)

and failing that, if there are no objections, i'll just upgrade that warning to an error.

Collaborator

gbaz commented Sep 18, 2015

if you point me to the relevant files in the cabal codebase even, I could have a think :-)

and failing that, if there are no objections, i'll just upgrade that warning to an error.

@ttuegel

This comment has been minimized.

Show comment
Hide comment
@ttuegel

ttuegel Sep 18, 2015

Member

Yes, ghc --make can handle this. As the original ticket this references stated, GHC does this, and Cabal does not, and the choice was made to preserve this behavior because the GHC main-is support is GHC specific, and it was desired to support "all compilers."

I just tried this, and no, GHC definitely cannot handle this. Observe:

Hello.hs:

module Hello where

main :: IO ()
main = putStrLn "Hello, world!"
$ ghc --make Hello.hs
[1 of 1] Compiling Hello            ( Hello.hs, Hello.o )

$ ls Hello*
Hello.hi  Hello.hs  Hello.o

On the other hand,

$ ghc --make Hello.hs -main-is Hello                                    
[1 of 1] Compiling Hello            ( Hello.hs, Hello.o ) [flags changed]
Linking Hello ...

$ ls Hello*                                                             
Hello  Hello.hi  Hello.hs  Hello.o

So, GHC cannot handle this automatically. Therefore, I don't think it's unreasonable to require the user to specify the name of module in a field such as main-module: (I think that matches exposed-modules: and other-modules: better than main-module-is:.)

and failing that, if there are no objections, i'll just upgrade that warning to an error.

I agree it should be an error and not a warning, but that produced by GHC, so you'll have to patch it there. That would be good, but it doesn't really fix the problem for users of current versions. Actually, I think it should be an error to pass -main-is Foo to GHC is the module Foo can't be found, regardless of whether -o is given or not.

Member

ttuegel commented Sep 18, 2015

Yes, ghc --make can handle this. As the original ticket this references stated, GHC does this, and Cabal does not, and the choice was made to preserve this behavior because the GHC main-is support is GHC specific, and it was desired to support "all compilers."

I just tried this, and no, GHC definitely cannot handle this. Observe:

Hello.hs:

module Hello where

main :: IO ()
main = putStrLn "Hello, world!"
$ ghc --make Hello.hs
[1 of 1] Compiling Hello            ( Hello.hs, Hello.o )

$ ls Hello*
Hello.hi  Hello.hs  Hello.o

On the other hand,

$ ghc --make Hello.hs -main-is Hello                                    
[1 of 1] Compiling Hello            ( Hello.hs, Hello.o ) [flags changed]
Linking Hello ...

$ ls Hello*                                                             
Hello  Hello.hi  Hello.hs  Hello.o

So, GHC cannot handle this automatically. Therefore, I don't think it's unreasonable to require the user to specify the name of module in a field such as main-module: (I think that matches exposed-modules: and other-modules: better than main-module-is:.)

and failing that, if there are no objections, i'll just upgrade that warning to an error.

I agree it should be an error and not a warning, but that produced by GHC, so you'll have to patch it there. That would be good, but it doesn't really fix the problem for users of current versions. Actually, I think it should be an error to pass -main-is Foo to GHC is the module Foo can't be found, regardless of whether -o is given or not.

@ttuegel

This comment has been minimized.

Show comment
Hide comment
@ttuegel

ttuegel Sep 18, 2015

Member

Relevant things to adding a main-module: field:
Distribution.PackageDescription: add a moduleName field to Executable.
Distribution.PackageDescription.Parse: add an entry to executableFieldDescrs.
Distribution.Simple.Program.GHC: add a field for -main-is option
Distribution.Simple.GHC: set the -main-is option. Does GHCJS take -main-is? Then we should set this in Distribution.Simple.GHCJS, too.

Member

ttuegel commented Sep 18, 2015

Relevant things to adding a main-module: field:
Distribution.PackageDescription: add a moduleName field to Executable.
Distribution.PackageDescription.Parse: add an entry to executableFieldDescrs.
Distribution.Simple.Program.GHC: add a field for -main-is option
Distribution.Simple.GHC: set the -main-is option. Does GHCJS take -main-is? Then we should set this in Distribution.Simple.GHCJS, too.

@gbaz

This comment has been minimized.

Show comment
Hide comment
@gbaz

gbaz Sep 18, 2015

Collaborator

oh yes, I meant ghc handles this with "main-is" and cabal does not :-)

The way it appears to do so is that it infers the module name from the path given by "main-is".

So I wonder if we couldn't just leverage this behavior in the cabal process... I'll have a look.

Collaborator

gbaz commented Sep 18, 2015

oh yes, I meant ghc handles this with "main-is" and cabal does not :-)

The way it appears to do so is that it infers the module name from the path given by "main-is".

So I wonder if we couldn't just leverage this behavior in the cabal process... I'll have a look.

@ttuegel

This comment has been minimized.

Show comment
Hide comment
@ttuegel

ttuegel Sep 18, 2015

Member

@gbaz As I just demonstrated, GHC always assumes -main-is Main unless specified otherwise. It does not do any inference or anything else.

Member

ttuegel commented Sep 18, 2015

@gbaz As I just demonstrated, GHC always assumes -main-is Main unless specified otherwise. It does not do any inference or anything else.

@gbaz

This comment has been minimized.

Show comment
Hide comment
@gbaz

gbaz Sep 18, 2015

Collaborator

Ah, I think I see the issue. Cabal's "main-is" picks out a file. GHC's "main-is" picks out a module name perhaps?

Collaborator

gbaz commented Sep 18, 2015

Ah, I think I see the issue. Cabal's "main-is" picks out a file. GHC's "main-is" picks out a module name perhaps?

@gbaz

This comment has been minimized.

Show comment
Hide comment
@gbaz

gbaz Sep 18, 2015

Collaborator

As per the manual:

The normal rule in Haskell is that your program must supply a main function in module Main. When testing, it is often convenient to change which function is the "main" one, and the -main-is flag allows you to do so. The thing can be one of:

  • A lower-case identifier foo. GHC assumes that the main function is Main.foo.
  • A module name A. GHC assumes that the main function is A.main.
  • A qualified name A.foo. GHC assumes that the main function is A.foo.

Strictly speaking, -main-is is not a link-phase flag at all; it has no effect on the link step. The flag must be specified when compiling the module containing the specified main function (e.g. module A in the latter two items above). It has no effect for other modules, and hence can safely be given to ghc --make. However, if all the modules are otherwise up to date, you may need to force recompilation both of the module where the new "main" is, and of the module where the "main" function used to be; ghc is not clever enough to figure out that they both need recompiling. You can force recompilation by removing the object file, or by using the -fforce-recomp flag.

so what I would like to see is if we generate the ghc main-is flag to pick out the module name of the file which is also specified in the "main-is" flag or something?

This is tricky because the semantics feel similar but do different things, lending to confusion.

Collaborator

gbaz commented Sep 18, 2015

As per the manual:

The normal rule in Haskell is that your program must supply a main function in module Main. When testing, it is often convenient to change which function is the "main" one, and the -main-is flag allows you to do so. The thing can be one of:

  • A lower-case identifier foo. GHC assumes that the main function is Main.foo.
  • A module name A. GHC assumes that the main function is A.main.
  • A qualified name A.foo. GHC assumes that the main function is A.foo.

Strictly speaking, -main-is is not a link-phase flag at all; it has no effect on the link step. The flag must be specified when compiling the module containing the specified main function (e.g. module A in the latter two items above). It has no effect for other modules, and hence can safely be given to ghc --make. However, if all the modules are otherwise up to date, you may need to force recompilation both of the module where the new "main" is, and of the module where the "main" function used to be; ghc is not clever enough to figure out that they both need recompiling. You can force recompilation by removing the object file, or by using the -fforce-recomp flag.

so what I would like to see is if we generate the ghc main-is flag to pick out the module name of the file which is also specified in the "main-is" flag or something?

This is tricky because the semantics feel similar but do different things, lending to confusion.

@ttuegel

This comment has been minimized.

Show comment
Hide comment
@ttuegel

ttuegel Sep 18, 2015

Member

so what I would like to see is if we generate the ghc main-is flag to pick out the module name of the file which is also specified in the "main-is" flag or something?

Cabal doesn't have access to the module name, and we can't safely infer it because that would break existing packages. Maybe you could modify the -main-is flag of GHC to accept a filename also?

Member

ttuegel commented Sep 18, 2015

so what I would like to see is if we generate the ghc main-is flag to pick out the module name of the file which is also specified in the "main-is" flag or something?

Cabal doesn't have access to the module name, and we can't safely infer it because that would break existing packages. Maybe you could modify the -main-is flag of GHC to accept a filename also?

@gbaz

This comment has been minimized.

Show comment
Hide comment
@gbaz

gbaz Sep 18, 2015

Collaborator

on the warning/error thing, ghc bug filed: https://ghc.haskell.org/trac/ghc/ticket/10895#ticket

Collaborator

gbaz commented Sep 18, 2015

on the warning/error thing, ghc bug filed: https://ghc.haskell.org/trac/ghc/ticket/10895#ticket

@ezyang

This comment has been minimized.

Show comment
Hide comment
@ezyang

ezyang Dec 22, 2015

Contributor

I think of ghc --make and filenames as a place where GHC got the interface horribly, horribly wrong. If ghc --make has been written so that a filename MUST be an executable, we wouldn't be having this discussion, because there would be no -main-is flag at the module name. (In general, ghc --make's handling of file targets is bonkers, but the ship has sailed since there are many convenient cases where you want to say ghci A.hs and mean the module named A.)

There are only two ways to fix this:

  1. Teach Cabal how to read out the module name of an hs file,
  2. Add a new mode to GHC to make it do what you want

I would much prefer (2). Cabal doesn't really have any business parsing hs files. (Well, being a build system, it kind of does, but that's a whole different kettle of fish.)

Contributor

ezyang commented Dec 22, 2015

I think of ghc --make and filenames as a place where GHC got the interface horribly, horribly wrong. If ghc --make has been written so that a filename MUST be an executable, we wouldn't be having this discussion, because there would be no -main-is flag at the module name. (In general, ghc --make's handling of file targets is bonkers, but the ship has sailed since there are many convenient cases where you want to say ghci A.hs and mean the module named A.)

There are only two ways to fix this:

  1. Teach Cabal how to read out the module name of an hs file,
  2. Add a new mode to GHC to make it do what you want

I would much prefer (2). Cabal doesn't really have any business parsing hs files. (Well, being a build system, it kind of does, but that's a whole different kettle of fish.)

@gbaz

This comment has been minimized.

Show comment
Hide comment
@gbaz

gbaz Feb 7, 2018

Collaborator

Ok, the upstream GHC issue I filed is fixed, and given ezyang's last comment here, I don't think we want to do anything fancy. (1) is a pain, and (2) seems like overkill for something that doesn't really matter. So let's just improve the docs a bit, as per the above-PR I filed, and when that's done, close this.

Collaborator

gbaz commented Feb 7, 2018

Ok, the upstream GHC issue I filed is fixed, and given ezyang's last comment here, I don't think we want to do anything fancy. (1) is a pain, and (2) seems like overkill for something that doesn't really matter. So let's just improve the docs a bit, as per the above-PR I filed, and when that's done, close this.

@23Skidoo

This comment has been minimized.

Show comment
Hide comment
@23Skidoo

23Skidoo Feb 8, 2018

Member

#5122 was merged, closing.

Member

23Skidoo commented Feb 8, 2018

#5122 was merged, closing.

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