-
Notifications
You must be signed in to change notification settings - Fork 204
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
Upgrade validation compare deps #19184
Conversation
61ba239
to
a167cc3
Compare
Without explicit references to dependencies' values, the deps won't show up in directDeps. This is fine, but was a real blocker
0e5a3d8
to
5a6d7e0
Compare
I've split this PR into its three pieces (daml, canton, daml) to check that they all work - some additional work is necessary for the first change to pass, we'll remove it in the last step. |
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.
Annotated the code in a few spots to help review, as well as ask a few questions
oldPkgId2: Ref.PackageId, | ||
optOldPkg2: Option[Ast.Package], | ||
optOldPkg2: Option[(Ast.Package, PackageMap)], |
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.
We pass in a packagemap of dependencies with each package - this map only contains packageids of deps and their corresponding names and versions, so that we can check that each package's version increases correctly.
@@ -199,9 +204,9 @@ class PackageUpgradeValidator( | |||
case (Some((newPkgId1, newPkg1)), Some((oldPkgId2, oldPkg2))) => | |||
strictTypecheckUpgrades( | |||
typecheckPhase, | |||
Some((newPkgId1, newPkg1)), | |||
Some((newPkgId1, newPkg1, newPkg1.directDeps.map((x: Ref.PackageId) => (x, packageMap(x))).toMap)), |
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.
Here we generate the dependency map for a package from directDeps
which is populated during the decoding phase.
An important caveat is that if a dependency does not get used (none of its identifiers show up in the depending package), the dep will show up in the manifest file but will not show up in the directDeps
list.
However, I don't think this is much of an issue, it only makes compiler-side checks more restrictive than participant, and the workaround to make it pass on the compiler-side is not to list an unused dependency, which is good code hygiene.
If anything, we should be removing dependencies from the manifest that aren't actually used - should I create a tickt for this?
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.
As discussed on zoom, I think this will let someone sneakily upload v2 of a package (via a dep), without canton checking that v3 upgrades v2 and v2 upgrades v1.
Also as discussed, this is probably not the place to check for that and should be part of another PR.
|
||
-- | Take a package version of regex "(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*" into | ||
-- a list of integers [Integer] | ||
splitPackageVersion :: PackageVersion -> Maybe [Integer] |
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.
utils for versions and comparing them
| SecondVersionUnparseable PackageVersion | ||
deriving (Show, Eq, Ord) | ||
|
||
comparePackageVersion :: PackageVersion -> PackageVersion -> Either ComparePackageVersionError Ordering |
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.
Here we don't use the 0.0.0
convention in other code because (I assume) packages don't adhere to that convention - is that true?
@@ -694,6 +702,10 @@ instance Pretty Warning where | |||
[ "The template " <> pPrint tpl <> " has implemented interface " <> pPrint iface <> ", which is defined in a previous version of this package." | |||
, "However, it is recommended that interfaces are defined in their own package separate from their implementations." | |||
] | |||
WPastDependencyHasUnparseableVersion pkgName version -> | |||
"Dependency " <> pPrint pkgName <> " of upgrading package has a version which cannot be parsed: '" <> pPrint version <> "'" | |||
WPresentDependencyHasUnparseableVersion pkgName version -> |
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.
Backport to 2.x will need to deal with metadata being potentially missing from a dependency.
@@ -353,18 +358,18 @@ tests damlc = | |||
) | |||
let sharedDir = dir </> "shared" | |||
let sharedDar = sharedDir </> "out.dar" | |||
writeFiles sharedDir (projectFile lfVersion ("upgrades-example-" <> name <> "-dep") Nothing Nothing : sharedDepFiles) |
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 ugly mess of test code generates daml.yamls and appropriate directories from groups of test files in test-common/...
. It's messy but ultimately is just handling the different ways we might want to build a series of packages for upgrading.
tc.check() | ||
} | ||
} | ||
|
||
def typecheckUpgrades( |
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.
_ <- tryAll(upgradedModules.values, checkModule(_)) | ||
} yield () | ||
} | ||
|
||
private def checkDeps(): Try[Unit] = { |
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.
Duplicate the dependency logic from the compiler inside the participant.
private def checkDep(dep: (Ref.PackageName, Upgrading[Ref.PackageVersion])): Try[Unit] = { | ||
val (depName, depVersions) = dep | ||
failIf( | ||
depVersions.present < depVersions.past, |
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 already is a "comparable" instance for Ref.PackageVersion that does the correct thing, so we don't need any utils like we did with Haskell
import Dep | ||
|
||
self : Text | ||
self = "v1 with " <> dep |
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.
We have to actually use the dependency, otherwise it won't show up during participant validation in directDeps
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.
Thanks!
...cala/com/digitalasset/canton/platform/apiserver/services/admin/PackageUpgradeValidator.scala
Outdated
Show resolved
Hide resolved
@@ -199,9 +204,9 @@ class PackageUpgradeValidator( | |||
case (Some((newPkgId1, newPkg1)), Some((oldPkgId2, oldPkg2))) => | |||
strictTypecheckUpgrades( | |||
typecheckPhase, | |||
Some((newPkgId1, newPkg1)), | |||
Some((newPkgId1, newPkg1, newPkg1.directDeps.map((x: Ref.PackageId) => (x, packageMap(x))).toMap)), |
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.
As discussed on zoom, I think this will let someone sneakily upload v2 of a package (via a dep), without canton checking that v3 upgrades v2 and v2 upgrades v1.
Also as discussed, this is probably not the place to check for that and should be part of another PR.
val internedTypes = Work.run(decodeInternedTypes(env0, lfPackage)) | ||
val env = env0.copy(internedTypes = internedTypes) | ||
|
||
val modules = lfPackage.getModulesList.asScala.map(env.decodeModule(_)) |
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.
Is that a leftover from a refactoring that didn't happen, or some debugging maybe? It's fine as it is, but I want to make sure you weren't planning on making a change here and then forgot about 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.
I decided to move the line here, since it's side-effectful in a way that affects the return value of directDeps
in Package.build
, to prevent any problems that would occur if the lines were re-ordered.
sdk/daml-lf/validation/src/main/scala/com/daml/lf/validation/Upgrading.scala
Outdated
Show resolved
Hide resolved
sdk/daml-lf/validation/src/main/scala/com/daml/lf/validation/Upgrading.scala
Outdated
Show resolved
Hide resolved
let pad xs target | ||
| length xs < length target = xs ++ replicate (length target - length xs) 0 | ||
| otherwise = xs |
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 you can make that a bit shorter and (in my opinion) more readable:
let pad xs target = take (length target) (xs ++ repeat 0)
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.
Correct me if I'm wrong, if the target
is shorter than xs
, then won't this drop elements from xs
? We want to keep the maximum of the two.
That said, a hybrid solution would work best:
let pad xs target = take (length target `max` length xs) (xs ++ repeat 0)
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.
Yes, sorry about that!
- Change _package to literal identifier `package` - Use type synonym PackageMap where possible - Fix binding of _new and _deleted in wrong order - Refactor `splitPackageVersion` and `comparePackageVersion`
@@ -343,15 +343,15 @@ case class TypecheckUpgrades( | |||
) { | |||
import TypecheckUpgrades._ | |||
|
|||
private lazy val _package: Upgrading[Ast.Package] = packages.map(_._2) | |||
private lazy val `package`: Upgrading[Ast.Package] = packages.map(_._2) |
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.
Oh of course, I understand why the underscore now 🤦
Constructed in the aggregate by the pt1 PR, the pt2 PR, and Paul's follow up changes to the compiler. |
So if mylib-1.0.0 depends on mydep-1.0.0, then mylib-2.0.0 must depend on a mydep >= 1.0.0. If mydep has a version strictly less than 1.0.0, then the dependency downgrades while the package itself upgrades, in which case we should error out.
Will be rebased on top of #19123, we should complete a review of that firstdone