-
Notifications
You must be signed in to change notification settings - Fork 103
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
Propagate section-specific data in conditionals. #175
Conversation
Fixes #116 |
241b683
to
e907efd
Compare
I force-pushed an update to my branch that fixes a couple issues with rendering. I have tried it on our real-world use cases, and they work as expected. |
We might consider offering a warning when For example:
will generate:
Note that it includes |
@scott-fleischman Hey, on a first glance this looks reasonable. I will do a proper review when I have time, but it may take a while. |
src/Hpack/Config.hs
Outdated
|
||
instance FromJSON ThenElse where | ||
instance (HasFieldNames a, FromJSON a) => FromJSON (ThenElse a) where | ||
parseJSON = genericParseJSON_ | ||
|
||
data Empty = Empty |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can Empty
be removed now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty
is not needed in the library code. It is still used in tests, mainly for the ToJSON
and HasFieldNames
instances. I'll move Empty
to the test Helper
module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, makes sense.
Late here already, I'll try to continue the review tomorrow (no promise).
11c970b
to
e22dcdd
Compare
@scott-fleischman If I specify e.g. I'm puzzled whether we want to parametrize
Any thoughts? |
I'll give that approach a try and see how it goes. |
e22dcdd
to
197dc27
Compare
@sol I force-pushed an update that uses The code builds, but the tests aren't passing yet. There are a few bugs in the unknown fields code still. But mainly I wanted to get your feedback whether this is the direction you would like this implementation to go. The I kind of prefer the older way due to its simplicity. This way makes the |
I will also have to rebase my commit(s) in order to fix the conflicts. Just let me know whether you want Thanks! |
@scott-fleischman Sorry for the late response again. I think we should reject e.g. If you are ok with this approach, please go ahead and rebase. |
197dc27
to
a828818
Compare
@sol I finally got the motivation to finish this PR, rebasing off the latest master and fixing the broken tests. Let me know what you think! |
I basically just started from the |
Personally having two type parameters for One consideration is whether we want to add a type synonym for |
I tested the code against our real-world use case and it works great. |
@sol as you questioned the validity of fields like The current code in this PR reflects both of these (omits top-level package-specific fields in conditionals, but supports library and executable-specific fields in conditionals. |
@sol At this point, what are your thoughts on this PR and/or the issue it's related to? |
@scott-fleischman I'm very much sorry for being unresponsive on this. The reason is lack of cycles on my side and that this PR is somewhat biggish. I'll be busy tomorrow and for the weekend, but I'll try to get back at this early next week (no promise). |
@scott-fleischman I ended up doing some significant refactoring first, then implementing this on top of that based on your code. I'll add some comments to this PR on what I changed. Can you please test if what is on |
data FlatThen a = FlatThen { | ||
_flatThenCondition :: String | ||
, _flatThenBody :: CaptureUnknownFields (Section a a) | ||
} deriving (Eq, Show, Generic) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What we do here with FlatThen
is essentially saying: On this level we want to accept all the fields of Condition
and all the fields of Section
. I added a Product
data type that allows us to solve this kind of situation generically. So instead of FlatThen
we can have Product (Section a a) Condition
now.
, sectionBuildTools :: Dependencies | ||
} deriving (Eq, Show, Functor, Foldable, Traversable) | ||
} deriving (Show) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that in the parsed result we actually don't need two type parameters (we only have Section Library Library
and Section Executable Executable
). The second type parameter is only needed during parsing.
As a second observation: We have types that represent the syntactical structure of package.yaml
that we use for parsing the actual YAML. We then map those syntactic types to types that represent a Package
semantically (e.g. ExecutableConfig
is mapped to Executable
).
For Section
the syntactic type is CommonOptions
, but here we break the strict separation between syntactic and semantic types. We use Section
during parsing the actual YAML as well.
What I did is: Remove the FromJSON
instance for Section
and only use CommonOptions
during parsing.
@@ -416,12 +442,12 @@ data Library = Library { | |||
} deriving (Eq, Show) | |||
|
|||
data Executable = Executable { | |||
executableName :: String | |||
, executableMain :: FilePath | |||
executableName :: Maybe String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
executableName
can never be Nothing
and is never valid inside a conditional. So what we want here is a type that represents this properly. There are probably several ways to achieve this, what I did is:
Change [Section Executable]
to Map String (Section Executable)
in Package
and remove executableName
from Executable
. So what we have now is a mapping from executable names to executable sections.
validateExecutable :: Section Executable Executable -> Either String () | ||
validateExecutable ex = case executableMain . sectionData $ ex of | ||
Nothing -> Left "Missing main" | ||
Just _ -> Right () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I tested, Cabal
does not require main
top-level, e.g. the following is a valid executable section:
executable foo
if os(windows)
buildable: False
else
hs-source-dirs: src, driver
main-is: Main.hs
So just omitting validateExecutable
is fine I guess.
where | ||
inferModules :: IO [String] | ||
inferModules = filterMain . (++ [pathsModule]) . concat <$> mapM (getModules dir) sectionSourceDirs | ||
|
||
pathsModule = pathsModuleFromPackageName packageName_ | ||
|
||
filterMain :: [String] -> [String] | ||
filterMain = maybe id (filter . (/=)) (toModule $ splitDirectories executableSectionMain) | ||
filterMain = maybe (const []) (\m -> maybe id (filter . (/=)) (toModule $ splitDirectories m)) executableSectionMain |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think const []
needs to be id
instead (if there is no main
we don't filter anything).
|
||
(mainSrcFile, ghcOptions) = parseMain executableSectionMain | ||
fromExecutableSectionNamed :: ExecutableSection -> Executable | ||
fromExecutableSectionNamed (ExecutableSection main other) = Executable Nothing main (fromMaybeList other) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually infer other-modules
inside conditional too now (provided the user specified any source-dirs
). Any reason we wouldn't want that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that inferring other-modules
by default in conditionals will make the default behavior not work very well, which unfortunately goes against one of the values of hpack--if you use the defaults things "just work".
The main issue is that the in a conditional, usually you want to exclude one or more other-modules
that will break the build otherwise. That's why I think the preferred way to use conditionals is to manually specify other-modules
to avoid getting inferred modules that break that build.
I will see if I can put together some examples that clarify it.
@sol thanks for looking at the PR. It's disappointing there's no record of my changes in the project, but I am glad the issue has a resolution, and with cleaner code than I had. |
I'm very much sorry for that. I tried to retain your authorship but with the amount of refactoring on |
I couldn't come up with any example myself, where not inferring For libraries, inferring |
This is a working approach to allowing section-specific properties in conditionals.
The main goal is to allow library-specific properties in the body of conditionals. For example:
Implementation details:
Effectively I propagated the type parameter for a
Section
throughout all of the conditional-related code.This changes the existing behavior in a few instances.
github
within a conditional for the package (top-level properties). One previous test assumed that was invalid, and I changed the test to allow that case.executableSectionMain
inExecutableSection
now needs to beMaybe FilePath
instead ofFilePath
. This is so that you don't have to specifymain
in every conditional in an executable section (which would break existing code). Note that this pushes the validation thatmain
is supplied to a different area in the codebase. The new validation code works but could be improved.I added a
FlatThen
as an analogous type toThenElse
which handles parsing the flatcondition
property with its sibling section properties.Note of caution: it is recommended to always set
other-modules
(andexposed-modules
in thelibrary
section) at the root level when using one or both properties within a conditional. Else the recursive file selection behavior will likely conflict with what the conditional is trying to achieve. Note that in the code, the conditionals do not expand emptyexposed-modules
andother-modules
. That is only done at the first level of thelibrary
and executable sections.