The project management component
A GUI layer
An API proposal
Each hIDE instance manages one project, so there is sort of a singleton object for the project management. Each project has a single root of its build tree. At the moment we expect there to be at most one .cabal file in that root directory.
Setting the build tree root
ProjectManagement.setBuildTreeRoot :: FilePath -> IO ()
This operation sets the root of the build tree to the given directory. The initialisation function for the project management component should do:
pwd <- getCurrentDirectory setBuildTreeRoot pwd
Of course this assumes that hIDE was invoked with no args, if it was invoked with hIDE /path/to/project/root then we'd use that as the build tree root.
Getting at the cabal package(s)
Then there should be a function to get the list of cabal projects in the build tree. This will only do anything after the build root has been set.
ProjectManagement.getCabalPackages :: IO [CabalPackage]
The first version of this code will only assume there is at most one .cabal file and so will only return zero or one CabalPackages.
The CabalPackage object corresponds to a .cabal file. It will have a function to get the information from th file. This will re-use the data structures provided by the Cabal library.
Similarly it will provide function that perform the normal actions on cabal projects, like configure, build etc. This will probably be implemented using the Cabal library too.
Finding the package for a source file
There should also be a function to find the BuildInfo corresponding to a given source file. This is for cases like the interactive type checker. It needs to know what setting to use for any given source file. So it will use this function:
ProjectManagement.getBuildInfoForFile :: FilePath -> IO (Maybe BuildInfo)
The project management component will figure out this mapping when the build tree root is set by reading the .cabal file(s) and figuring out which source files belong to it/them.
If a source file does not belong to any cabal package (for example if the user has not created any .cabal file or if it's merely some test module that is not part of the normal project build) then of course getBuildInfoForFile will return Nothing. In this case the interactive type checker would still be able to check the file but it would use default settings, as if the user were using ghc --make with no other flags.
Note that this allows us to extend things later for the case of more than one .cabal file in a build tree. Though it does make the (not unreasonable) assumption that each source file belongs to at most one cabal package.
On top of the above API it ought to be possible to implement a function which does a partial build starting from any given source file. This is basically the same as what the interactive type checker has to do.
Updating project settings
ProjectManagement.setCabalPackage :: CabalPackage -> IO ()
This should write the .cabal file. Note that the CabalPackage object will need to know the name of the file. For a new CabalPackage the default should be taken from the package name. All listeners shuold be notified of the change in this package.
Actually perhaps the setting should not be writte immediately to the file but cached at written later when appropriate. For example the GUI editor for the project settings will generate changes live, but we don't want to write the file every single keystroke.
Notification of changes to project settings
It should be posible to get notified when the setting for a cabal package change, or when a package is created or deleted. This will take the form of registering a callback function.
We should decide if this callback is for all packages or per-package.
Components like the interactive type checking would want to be notified of changes in project setting so that can be reflected in the setting used during compilation. For example if a user adds an extra package to the build-depends field. Then the setting used for the interactive typechecking would need to be updated accordingly.
Record of a conversation between dcoutts and Lemmih about the project management API:
<Lemmih> dcoutts: So the first thing on my TODO list should be to extend the GUI interface? <dcoutts> Lemmih, no I don't think so <dcoutts> Lemmih, it'd be to design the main api <Lemmih> Main api? <dcoutts> Lemmih, bearing in mind the clients of that api, which includes the gui <dcoutts> the main project management api <dcoutts> what do other components want to know from the project management component? <dcoutts> what actions do they want to perform? <dcoutts> eg the interactive type checking needs to know what options to supply to ghc etc <dcoutts> like flags, include paths, dependent packages etc <dcoutts> Lemmih, then the gui wants to get and modify settings in the .cabal files <Lemmih> Shouldn't the project management plugin just add some menu items for starting a new project and running 'configure|build|...'? <dcoutts> Lemmih, that's the gui part of the plugin, which actually is the least important. <Lemmih> dcoutts: All other plugins just need the .cabal file info. <dcoutts> Lemmih, at the end the gui part of the project managemtn should be as simple as adding a couple menu items which then use the project management component to actually do the build/configure etc <Lemmih> dcoutts: The project manager can't really give them any more information. <dcoutts> Lemmih, any more than? <dcoutts> Lemmih, it can figure out which files belong to which .cabal files <Lemmih> dcoutts: Any more than the .cabal file. <dcoutts> given the root of the source tree <dcoutts> Lemmih, right, it gives access to the info in the .cabal file <dcoutts> Lemmih, probably including notification of when it changes. <Lemmih> Right, does it offer more than that? <dcoutts> Lemmih, and actions like configure, build etc <dcoutts> we'll have to think about if that's for individual .cabal packages or for whole collections of cabal packages in a source tree that depend on each other <dcoutts> basically we need to figure out all the requirements <Lemmih> I don't wanna handle more than one .cabal for now. <dcoutts> which are not that trivial <dcoutts> Lemmih, ok that's an ok place to start. <dcoutts> Lemmih, so we should have a function to set what we believe the root of the build tree is. <Lemmih> I wanna wait till shipments in Cabal are properly defined. <dcoutts> Lemmih, sure <dcoutts> and then we'll expect that root dir to contain one .cabal file. <dcoutts> which is what the current cabal system expects <dcoutts> we can wait for shipments or whatever they turn out to be <Lemmih> Isn't the .cabal file always at the root? <dcoutts> yes it should be <dcoutts> under the current system <dcoutts> eventually it might get extended to allow more, but not at the moment <dcoutts> Lemmih, so for example if I start up hIDE we'd set ProjectManagement.setBuildTreeRoot currentWorkingDirectory <dcoutts> which will do the right thing if we're in the root of a cabal package build tree <Lemmih> "< dcoutts> Lemmih, and actions like configure, build etc" You want the plugin to suppy hooks just like Cabal's UserHooks? <dcoutts> Lemmih, I'm not quite sure what you mean <Lemmih> *supply <dcoutts> Lemmih, I'd guess the project management would have a function that gives us a CabalPackage object <dcoutts> Lemmih, and that would have a function to invoke the configure or build etc <dcoutts> (so that leaves open the possability to have more than one CabalPackage later) <dcoutts> Lemmih, does that make any sense? <Lemmih> dcoutts: Yeah, it does. <dcoutts> what do other IDEs call packages? <dcoutts> components, projects, "solutions" <dcoutts> maybe CabalPackage will do <JKnecht> all of which are different in th MS universe. <dcoutts> Lemmih, so the CabalPackage will enable us to get at the info in the .cabal file. <dcoutts> Lemmih, we can reuse the data strucures that the Cabal library provides. <dcoutts> Lemmih, so then the type checking plugin will extract the info from that so that it has the right packages imported etc. <dcoutts> Lemmih, I think we do want to have a function which maps a file back to a CabalPackage <dcoutts> Lemmih, the reason for that is: <dcoutts> there may be no .cabal package to which the file belongs <dcoutts> in which case to compile it we use default setting, correspondibg basically to doing ghc --make <dcoutts> when we have build trees that have more than one .cabal file, we need to know which setting to use when building it, hence mapping the file to the CabalPackage <dcoutts> the mapping is constructed when we set the build root <dcoutts> by looking at the source files in the build tree and the .cabal file(s) to see which files belong to the .cabal package(s) <Lemmih> The project manager should also be affecting the file browser. <Lemmih> And we need to use the GHC api to verify that Exposed-modules + Other-modules actually cover all the needed modules.