-
Notifications
You must be signed in to change notification settings - Fork 36
Configure Gobra via JSON #895
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
base: master
Are you sure you want to change the base?
Conversation
|
@ArquintL this is marked as a draft PR but you asked for the review. Should I provide feedback on the implementation already? regarding the cli options, I would opt for one of the following:
|
|
@jcp19 This PR is in draft mode as the design choices are more like suggestions. Code-wise, I do not have any additional changes planned so I'd appreciate a code review. We currently do not have a JSON field for every existing CLI option but if necessary, we can always add more (in separate PRs) |
|
High-level questions about the design:
|
|
I think that the following design could also work:
Under this design, the (*) While thinking of how all options relate to each other, and especially, how the configs of each package relate to each other, I was confronted again with the fact that right now, we read all configs of all files, regardless of their packages. I think this is a bad idea, which leads to unexpected results (e.g., if we import a package that was verified for overflow checks, it enables overflow checks for the current package). Maybe we should consider deprecating in-file configs when we introduce the json config, except for tests. |
Sorry for the imprecision, it's only mandatory if
One can argue whether we should allow
If both files are optional? How would we identify which folder forms a module's root? By detecting
Not sure whether this is less confusing but I think that this should work. The only weird case affects |
Either that or by traversing the directory tree, starting in the current package, and moving from parent to parent until a
IIRC, you do not pass a package in the recursive mode, but rather, the project root. So, I guess we would iterate through all packages in that directory. I think we need to think about what should be the roles of the project root and module root (should they be unified somehow?). |
After additional consideration, I am happy with @ArquintL general proposal, but I would rather choose one of the behaviours I proposed above for the case when we provide more CLI flags in addition to |
jcp19
left a comment
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.
So far, I reviewed up to src/main/scala/viper/gobra/frontend/Config.scala. I will continue the review later
| resolvedConfig = resolveTo match { | ||
| case None => config | ||
| case Some(p) => config.copy(includeDirs = config.includeDirs.map( | ||
| // it's important to convert includeDir to a string first as `path` might be a ZipPath and `includeDir` might not |
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.
and
includeDirmight not
Do you mean "must not"?
| case None => config | ||
| case Some(p) => config.copy(includeDirs = config.includeDirs.map( | ||
| // it's important to convert includeDir to a string first as `path` might be a ZipPath and `includeDir` might not | ||
| includeDir => p.toAbsolutePath.resolve(includeDir.toString))) |
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.
do I see it correctly that we are only resolving the include directories here? What about the other parameters that may contain paths? (e.g., --includePackages or -i)
| object CliEnumConverter { | ||
| trait EnumCase { | ||
| def value: String | ||
| } | ||
|
|
||
| /** trait for an enum value to configure parsing and serialization of this enum */ | ||
| trait CliEnum[E <: EnumCase] { | ||
| def values: List[E] | ||
| def convert(s: String): E = values.find(_.value == s).getOrElse(Violation.violation(s"Unexpected value: $s")) | ||
| } | ||
| } | ||
|
|
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.
this looks fine to me. An alternative would be to use the builtin Enumeration type in Scala (https://www.scala-lang.org/api/2.13.15/scala/Enumeration.html) which may lead to a shorter implementation.
| // provide the generic type argument to avoid weird runtime errors even though the | ||
| // pretends to not need it! |
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.
typo here?
| // provide the generic type argument to avoid weird runtime errors even though the | |
| // pretends to not need it! | |
| // provide the generic type argument to avoid weird runtime errors even though the compiler | |
| // pretends to not need it! |
| } else { | ||
| Right(VerificationJobCfg()) | ||
| } |
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 don't understand this. In the case where we do have a file that we want to resolve, doesn't it matter at all if we found the json file or if not? In that case, I would expect that the return type of this function would be Either[Vector[VerifierError], Option[VerificationJobCfg]], and I would expect that we return Right(None) in this case
Update: I guess every every field in this VerificationJobCfg will be an optional, so nevermind what I said. Adding a comment before Right(VerificationJobCfg()) may be helpful to clarify this.
| case (_, Left(jobErrors)) => Left(jobErrors) | ||
| case (Right(moduleCfg), Right(jobCfg)) => | ||
| def mergeField[T](fieldSelector: VerificationJobCfg => Option[T]): Option[T] = | ||
| fieldSelector(jobCfg).orElse(moduleCfg.default_job_cfg.flatMap(fieldSelector(_))) |
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 we drop the (_)?
| fieldSelector(jobCfg).orElse(moduleCfg.default_job_cfg.flatMap(fieldSelector(_))) | |
| fieldSelector(jobCfg).orElse(moduleCfg.default_job_cfg.flatMap(fieldSelector)) |
| val logLevel: Level = ConfigDefaults.DefaultLogLevel | ||
| val debug: Boolean = Level.DEBUG.isGreaterOrEqual(logLevel) | ||
| BaseConfig( | ||
| gobraDirectory = ConfigDefaults.DefaultGobraDirectory, |
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.
there are a few options that always get the default value (instead of being configurable). Is there a reason for that?
| printVpr = c.job_cfg.print_vpr.getOrElse(debug), | ||
| streamErrs = !ConfigDefaults.DefaultNoStreamErrors), | ||
| backend = c.job_cfg.backend.map(_.underlying), | ||
| isolate = ConfigDefaults.DefaultIsolate, |
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.
does this mean that chopping never works if we provide a json config?
| // at this point, we do not know anymore from which config file these arguments come from. | ||
| // Thus, we do not resolve potential paths. | ||
| Config.parseCliArgs(otherArgs, None).map(Some(_)) |
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.
Things get a bit tricky here, but wouldn't it be easier to resolve all paths before merging?
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.
Maybe we want to disallow passing options that take paths in "other"
| | Mode 4 (--config): | ||
| | Instructs Gobra to read all configuration options from the provided JSON file. |
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.
shall we mention here that a module config file must exist in the directory tree?
This PR pushes @jcp19's idea further to use a JSON file to configure Gobra by integrating the parsing of the JSON file into Gobra. The accepted JSON structure follows gobrago with the following differences:
gobra-mod.jsonandgobra.json, respectivelyinstallation_cfganddefault_job_cfgotherfield, which is merged by concatenating the list of options from both JSON filesIn addition, the following design choices have been made:
--config <path>is used to configure Gobra, in which case all other CLI options are ignored (should there be any)<path>must exist and can either be one of the following:<path>towards the filesystem's root directory