Skip to content
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

Shorten Dep serialization format in common cases #3490

Merged
merged 7 commits into from
Sep 9, 2024

Conversation

lihaoyi
Copy link
Member

@lihaoyi lihaoyi commented Sep 9, 2024

Fixes #3417

We do a best-effort approach where we try to reverse the Dep.parse logic, and at the end do a == check to see if the round tripped Dep is identical to the original. There's some overhead with the round trip comparison - effectively for each write-read of a Dep we add an additional read - but this isn't a hot code path so it shouldn't be a big deal. In exchange we get a pretty good guarantee of correctness that later on when someone round-trips the same Dep off of disk, they get the same result. We do the same for BoundDep and re-use most of the Dep logic

The sophistication of our def parse and def unparse logic can be extended over time, but this should work well for 99% of dependencies which are simple without fancy exclusions, publication, configuration, optional, transitive, etc.

Added some assertions to ResolveDepsTests to check the round tripping for common scenarios, both simplified and not

Before:

lihaoyi mill$ ./mill show main.api.ivyDeps
[
  {
    "dep": {
      "module": {
        "organization": {
          "value": "com.lihaoyi"
        },
        "name": {
          "value": "os-lib"
        },
        "attributes": {}
      },
      "version": "0.10.7-M1",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "cross": {
      "$type": "mill.scalalib.CrossVersion.Binary",
      "platformed": false
    },
    "force": false
  },
  {
    "dep": {
      "module": {
        "organization": {
          "value": "com.lihaoyi"
        },
        "name": {
          "value": "upickle"
        },
        "attributes": {}
      },
      "version": "3.3.1",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "cross": {
      "$type": "mill.scalalib.CrossVersion.Binary",
      "platformed": false
    },
    "force": false
  },
  {
    "dep": {
      "module": {
        "organization": {
          "value": "com.lihaoyi"
        },
        "name": {
          "value": "pprint"
        },
        "attributes": {}
      },
      "version": "0.9.0",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "cross": {
      "$type": "mill.scalalib.CrossVersion.Binary",
      "platformed": false
    },
    "force": false
  },
  {
    "dep": {
      "module": {
        "organization": {
          "value": "com.lihaoyi"
        },
        "name": {
          "value": "fansi"
        },
        "attributes": {}
      },
      "version": "0.5.0",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "cross": {
      "$type": "mill.scalalib.CrossVersion.Binary",
      "platformed": false
    },
    "force": false
  },
  {
    "dep": {
      "module": {
        "organization": {
          "value": "org.scala-sbt"
        },
        "name": {
          "value": "test-interface"
        },
        "attributes": {}
      },
      "version": "1.0",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "cross": {
      "$type": "mill.scalalib.CrossVersion.Constant",
      "value": "",
      "platformed": false
    },
    "force": false
  }
]
lihaoyi mill$ ./mill show main.api.transitiveIvyDeps
[
  {
    "dep": {
      "module": {
        "organization": {
          "value": "com.lihaoyi"
        },
        "name": {
          "value": "os-lib_2.13"
        },
        "attributes": {}
      },
      "version": "0.10.7-M1",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "force": false
  },
  {
    "dep": {
      "module": {
        "organization": {
          "value": "com.lihaoyi"
        },
        "name": {
          "value": "upickle_2.13"
        },
        "attributes": {}
      },
      "version": "3.3.1",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "force": false
  },
  {
    "dep": {
      "module": {
        "organization": {
          "value": "com.lihaoyi"
        },
        "name": {
          "value": "pprint_2.13"
        },
        "attributes": {}
      },
      "version": "0.9.0",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "force": false
  },
  {
    "dep": {
      "module": {
        "organization": {
          "value": "com.lihaoyi"
        },
        "name": {
          "value": "fansi_2.13"
        },
        "attributes": {}
      },
      "version": "0.5.0",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "force": false
  },
  {
    "dep": {
      "module": {
        "organization": {
          "value": "org.scala-sbt"
        },
        "name": {
          "value": "test-interface"
        },
        "attributes": {}
      },
      "version": "1.0",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "force": false
  },
  {
    "dep": {
      "module": {
        "organization": {
          "value": "org.scala-lang"
        },
        "name": {
          "value": "scala-library"
        },
        "attributes": {}
      },
      "version": "2.13.14",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "force": true
  },
  {
    "dep": {
      "module": {
        "organization": {
          "value": "com.lihaoyi"
        },
        "name": {
          "value": "mill-moduledefs_2.13"
        },
        "attributes": {}
      },
      "version": "0.11.0-M2",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "force": false
  },
  {
    "dep": {
      "module": {
        "organization": {
          "value": "com.kohlschutter.junixsocket"
        },
        "name": {
          "value": "junixsocket-core"
        },
        "attributes": {}
      },
      "version": "2.10.0",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "force": false
  }
]

After:

lihaoyi mill$ ./mill show main.api.ivyDeps
[
  "com.lihaoyi::os-lib:0.10.7-M1",
  "com.lihaoyi::upickle:3.3.1",
  "com.lihaoyi::pprint:0.9.0",
  "com.lihaoyi::fansi:0.5.0",
  "org.scala-sbt:test-interface:1.0"
]

lihaoyi mill$ ./mill show main.api.transitiveIvyDeps
[
  "com.lihaoyi:os-lib_2.13:0.10.7-M1",
  "com.lihaoyi:upickle_2.13:3.3.1",
  "com.lihaoyi:pprint_2.13:0.9.0",
  "com.lihaoyi:fansi_2.13:0.5.0",
  "org.scala-sbt:test-interface:1.0",
  {
    "dep": {
      "module": {
        "organization": {
          "value": "org.scala-lang"
        },
        "name": {
          "value": "scala-library"
        },
        "attributes": {}
      },
      "version": "2.13.14",
      "configuration": {
        "value": "default(compile)"
      },
      "minimizedExclusions": {
        "data": "coursier.core.MinimizedExclusions.ExcludeNone"
      },
      "publication": {
        "name": "",
        "type": {
          "value": ""
        },
        "ext": {
          "value": ""
        },
        "classifier": {
          "value": ""
        }
      },
      "optional": false,
      "transitive": true
    },
    "force": true
  },
  "com.lihaoyi:mill-moduledefs_2.13:0.11.0-M2",
  "com.kohlschutter.junixsocket:junixsocket-core:2.10.0"
]

Note that org.scala-lang:scala-library is still using the old verbose serialization, due to .forceVersion() which makes it unable to be parsed/unparsed into a naked string literal as it needs a .forceVersion() call. We could make the shorthand serialization smarter to deal with this in future, but for now this is already a huge improvement over the status quo

@lihaoyi lihaoyi marked this pull request as ready for review September 9, 2024 05:52
@lihaoyi
Copy link
Member Author

lihaoyi commented Sep 9, 2024

Part of the reason that the coursier.Dependency serialization is so verbose is that Coursier does not use Scala language default parameter values for its optional parameters, and instead uses Java-style telescoping to preserve binary compatibility.

This works fine in Scala code, but as uPickle relies on Scala default parameter values to elide unnecessary fields, it is unable to recognize the telescoping apply methods as default values, and thus fully serializes everything including the default values.

If Coursier data types provided default values to apply, the serialization of scala-library above would be a lot shorter:

 {
    "dep": {
      "module": {
        "organization": {
          "value": "org.scala-lang"
        },
        "name": {
          "value": "scala-library"
        }
      },
      "version": "2.13.14"
    },
    "force": true
  },

@lihaoyi lihaoyi merged commit f582b7e into com-lihaoyi:main Sep 9, 2024
23 checks passed
@lefou lefou added this to the 0.12.0-RC1 milestone Sep 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add simpler mill.scalalib.Dep serialization for common cases
2 participants