{"id":26075977,"url":"https://github.com/lightbend-labs/scala-sculpt","last_synced_at":"2025-04-06T05:15:27.819Z","repository":{"id":28493162,"uuid":"47437631","full_name":"lightbend-labs/scala-sculpt","owner":"lightbend-labs","description":"Dependency extraction for Scala codebases, to aid in modularizing","archived":false,"fork":false,"pushed_at":"2025-03-25T01:00:24.000Z","size":392,"stargazers_count":118,"open_issues_count":7,"forks_count":22,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-03-30T04:08:27.413Z","etag":null,"topics":["compiler-plugin","dependencies","modularization","scala"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lightbend-labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-12-05T01:44:33.000Z","updated_at":"2025-03-25T01:00:29.000Z","dependencies_parsed_at":"2023-02-13T04:01:01.733Z","dependency_job_id":"aab558d8-4953-4a5e-bc1d-dc15f3db2f66","html_url":"https://github.com/lightbend-labs/scala-sculpt","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend-labs%2Fscala-sculpt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend-labs%2Fscala-sculpt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend-labs%2Fscala-sculpt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend-labs%2Fscala-sculpt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lightbend-labs","download_url":"https://codeload.github.com/lightbend-labs/scala-sculpt/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247436285,"owners_count":20938533,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler-plugin","dependencies","modularization","scala"],"created_at":"2025-03-09T01:41:32.022Z","updated_at":"2025-04-06T05:15:27.640Z","avatar_url":"https://github.com/lightbend-labs.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sculpt: dependency graph extraction for Scala 2\n\nSculpt is a compiler plugin for analyzing the dependency structure of\nScala 2.13 source code.\n\n## Project status\n\nThis is **unfinished**, **unmaintained** software.  We are releasing\nit as open source as a public service with the hopes the code will be\nuseful to someone.\n\nSculpt is NOT supported under the Akka subscription.\n\n## What is it for?\n\nThe data generated by the plugin should be useful for all sorts of\nrefactoring efforts, including carving a monolithic codebase into\nindependent subprojects.\n\nThe plugin analyzes source code, not generated bytecode. The analysis\ncode is based on code from the incremental compiler in sbt and zinc.\nTherefore, the plugin should be an accurate source of information for\ndevelopers looking to reduce dependencies in order to reduce\nincremental compile times.\n\n## Building the plugin from source\n\n`sbt assembly` will create `target/scala-2.13/scala-sculpt_2.13-0.1.4.jar`.\n(The JAR is a fat JAR that bundles its dependency on spray-json.)\n\n## Using the plugin\n\nYou can use the compiled plugin with the Scala compiler as follows.\n\nSupposing you have `scala-sculpt_2.13-0.1.4.jar` in your current working directory,\n\nThen you can do e.g.:\n\n    scalac -Xplugin:scala-sculpt_2.13-0.1.4.jar \\\n      -Xplugin-require:sculpt \\\n      -P:sculpt:out=dep.json \\\n      Dep.scala\n\n## Sample input and output\n\nAssuming `Dep.scala` contains this source code:\n\n    object Dep1 { val x = 42; val y = Dep2.z }\n    object Dep2 { val z = Dep1.x }\n\nthen the command line shown above will generate this `dep.json` file:\n\n    [\n      {\"sym\": [\"o:Dep1\"], \"extends\": [\"pkt:scala\", \"tp:AnyRef\"]},\n      {\"sym\": [\"o:Dep1\", \"def:\u003cinit\u003e\"], \"uses\": [\"o:Dep1\"]},\n      {\"sym\": [\"o:Dep1\", \"def:\u003cinit\u003e\"], \"uses\": [\"pkt:java\", \"pkt:lang\", \"cl:Object\", \"def:\u003cinit\u003e\"]},\n      {\"sym\": [\"o:Dep1\", \"def:x\"], \"uses\": [\"o:Dep1\", \"t:x\"]},\n      {\"sym\": [\"o:Dep1\", \"def:x\"], \"uses\": [\"pkt:scala\", \"cl:Int\"]},\n      {\"sym\": [\"o:Dep1\", \"def:y\"], \"uses\": [\"o:Dep1\", \"t:y\"]},\n      {\"sym\": [\"o:Dep1\", \"def:y\"], \"uses\": [\"pkt:scala\", \"cl:Int\"]},\n      {\"sym\": [\"o:Dep1\", \"t:x\"], \"uses\": [\"pkt:scala\", \"cl:Int\"]},\n      {\"sym\": [\"o:Dep1\", \"t:y\"], \"uses\": [\"o:Dep2\", \"def:z\"]},\n      {\"sym\": [\"o:Dep1\", \"t:y\"], \"uses\": [\"ov:Dep2\"]},\n      {\"sym\": [\"o:Dep1\", \"t:y\"], \"uses\": [\"pkt:scala\", \"cl:Int\"]},\n      {\"sym\": [\"o:Dep2\"], \"extends\": [\"pkt:scala\", \"tp:AnyRef\"]},\n      {\"sym\": [\"o:Dep2\", \"def:\u003cinit\u003e\"], \"uses\": [\"o:Dep2\"]},\n      {\"sym\": [\"o:Dep2\", \"def:\u003cinit\u003e\"], \"uses\": [\"pkt:java\", \"pkt:lang\", \"cl:Object\", \"def:\u003cinit\u003e\"]},\n      {\"sym\": [\"o:Dep2\", \"def:z\"], \"uses\": [\"o:Dep2\", \"t:z\"]},\n      {\"sym\": [\"o:Dep2\", \"def:z\"], \"uses\": [\"pkt:scala\", \"cl:Int\"]},\n      {\"sym\": [\"o:Dep2\", \"t:z\"], \"uses\": [\"o:Dep1\", \"def:x\"]},\n      {\"sym\": [\"o:Dep2\", \"t:z\"], \"uses\": [\"ov:Dep1\"]},\n      {\"sym\": [\"o:Dep2\", \"t:z\"], \"uses\": [\"pkt:scala\", \"cl:Int\"]}\n    ]\n\nEach line in the JSON file represents an edge between two symbols in a\ndependency graph.\n\nThe edges are of two types, `extends` and `uses`.\n\nEach symbol is represented in the JSON as an array of strings, where\neach string represents a part of the symbol's fully qualified name.\n\nSo for example, in the above source code, we see that `Dep1` extends\n`scala.AnyRef`:\n\n    {\"sym\": [\"o:Dep1\"], \"extends\": [\"pkt:scala\", \"tp:AnyRef\"]},\n\nAnd we see that `Dep1` uses `scala.Int` in three places:\n\n    {\"sym\": [\"o:Dep1\", \"def:x\"], \"uses\": [\"pkt:scala\", \"cl:Int\"]},\n    {\"sym\": [\"o:Dep1\", \"def:y\"], \"uses\": [\"pkt:scala\", \"cl:Int\"]},\n    {\"sym\": [\"o:Dep1\", \"t:x\"], \"uses\": [\"pkt:scala\", \"cl:Int\"]},\n\nfrom this we see that `scala.Int` is used as the return type of\n`Dep1.x` and `Dep1.y`, and as the inferred type of the body of\n`Dep1.y`.\n\nFor brevity, the following abbreviations are used in the JSON output:\n\n### Terms\n\nabbreviation | meaning\n-------------|--------\nov           | object\ndef          | def\nvar          | var\nmac          | macro\npk           | package\nt            | other term\n\n### Types\n\nabbreviation | meaning\n-------------|--------\ntr           | trait\npkt          | package\no            | object\ncl           | class\ntp           | other type\n\n### Other\n\nThe name of a constructor is always `\u003cinit\u003e`.\n\n## Running in \"class mode\"\n\nThe dependency information produced by the default mode is extremely\nfine-grained; it goes all the way down to the level of individual\nmethods.\n\nIf you prefer an aggregated higher-level summary, you can run Sculpt\nin \"class mode\" by adding `-P:sculpt:mode=class`. So e.g. a complete\ninvocation would look like:\n\n    scalac -Xplugin:scala-sculpt_2.13-0.1.4.jar \\\n      -Xplugin-require:sculpt \\\n      -P:sculpt:out=classes.json \\\n      -P:sculpt:mode=class \\\n      Dep.scala\n\non the same source code used in the example above, this command line\ngenerates this `classes.json` file:\n\n    [\n      {\"sym\": [\"o:Dep1\"], \"uses\": [\"o:Dep2\"]},\n      {\"sym\": [\"o:Dep1\"], \"uses\": [\"pkt:java\", \"pkt:lang\", \"cl:Object\"]},\n      {\"sym\": [\"o:Dep1\"], \"uses\": [\"pkt:scala\", \"cl:Int\"]},\n      {\"sym\": [\"o:Dep1\"], \"uses\": [\"pkt:scala\", \"tp:AnyRef\"]},\n      {\"sym\": [\"o:Dep2\"], \"uses\": [\"o:Dep1\"]},\n      {\"sym\": [\"o:Dep2\"], \"uses\": [\"pkt:java\", \"pkt:lang\", \"cl:Object\"]},\n      {\"sym\": [\"o:Dep2\"], \"uses\": [\"pkt:scala\", \"cl:Int\"]},\n      {\"sym\": [\"o:Dep2\"], \"uses\": [\"pkt:scala\", \"tp:AnyRef\"]}\n    ]\n\nNote that all of the nodes are top-level classes, traits, objects, or\ntype aliases, and all of the edges are of type \"uses\".\n\n`-P:sculpt:mode=class` is provided as a convenience, but it isn't\nstrictly needed, in that if you have already run Sculpt in default\nmode, you can convert detailed dependencies to class-level\ndependencies in the course of an interactive session.  This\nis demonstrated in the sample interactive session below.\n\n## Graphs represented as case classes\n\nThe same JAR that contains the plugin also contains a suite of case\nclasses for representing the same information in the JSON files as\nScala objects.\n\nWe provide a `load` method for parsing a JSON file into instances\nof these case classes, and a `save` method for writing the instances\nback out to JSON.\n\nThese classes provide a possible starting point for graph analysis and\nmanipulation, e.g. in the REPL.\n\n### Sample interactive session\n\nNow in a Scala REPL with the same JARs on the classpath:\n\n    scala -classpath scala-sculpt_2.13-0.1.4.jar\n\nIf we load `dep.json` as follows, we'll see the following graph:\n\n    scala\u003e import com.lightbend.tools.sculpt.cmd._\n    import com.lightbend.tools.sculpt.cmd._\n\n    scala\u003e load(\"dep.json\")\n    res0: com.lightbend.tools.sculpt.model.Graph = Graph 'dep.json': 15 nodes, 19 edges\n\n    scala\u003e println(res0.fullString)\n    Graph 'dep.json': 15 nodes, 19 edges\n    Nodes:\n      - o:Dep1\n      - pkt:scala.tp:AnyRef\n      ...\n    Edges:\n      - o:Dep1 -[Extends]-\u003e pkt:scala.tp:AnyRef\n      - o:Dep1.def:\u003cinit\u003e -[Uses]-\u003e o:Dep1\n      ...\n\n#### Converting to class-level dependencies\n\nIf we're interested in class-level dependencies only, we can\ncall `load` with `classMode = true` in order to aggregate the\ndependencies after loading:\n\n    scala\u003e load(\"dep.json\", classMode = true)\n    res2: com.lightbend.tools.sculpt.model.Graph = Graph 'dep.json': 7 nodes, 10 edges\n\n#### Cycles and layers reports\n\nWhen untangling dependencies, circular dependencies are always\nespecially problematic. We can identify these, list their contents,\nsort them by the total number of classes in the cycle, or print\nthem grouped into layers according to their dependency structure.\n\nThe cycles and layers reports operate on class-level dependencies\nonly, so you must either run the plugin in \"class mode\", or convert\nfrom default mode to class mode at load time:\n\nContinuing the running example, here's a cycles report:\n\n    scala\u003e import com.lightbend.tools.sculpt.model.Cycles\n\n    scala\u003e println(Cycles.cyclesString(res2.nodes))\n    [2] o:Dep1 o:Dep2\n\nThe report shows that the codebase contains a single cycle of size 2,\nbecause `Dep1` and `Dep2` mutually reference each other.  (\"Cycles\" of\na single node are omitted.)\n\nAnd here's the layers report for the same code:\n\n    scala\u003e println(res2.layersString)\n    layers =\n      \"\"\"|[1] o:Dep1 o:Dep2\n         |[0] cl:java.lang.Object\n         |[0] cl:scala.Int\n         |[0] tp:scala.AnyRef\n\nThe numbers are layer numbers, defined as follows:\n\n* layer 0: classes with no dependencies\n* layer 1: classes with only layer 0 dependencies\n* layer 2: classes with only layer 0 and 1 dependencies\n* ...\n\nNote that some concepts of layered architectures require that layer n\naccesses only layer n - 1 and not any lower layers; we are not making\nthat assumption here.\n\nHere's an example portion of a cycle report for a larger sample codebase:\n\n    [8] tr:api.Agent tr:api.AgentSet tr:api.Link tr:api.Observer tr:api.Patch tr:api.TrailDrawerInterface tr:api.Turtle tr:api.World\n    [5] cl:workspace.AbstractWorkspace cl:workspace.DefaultFileManager cl:workspace.Evaluator o:workspace.AbstractWorkspaceTraits o:workspace.Benchmarker\n    [4] cl:agent.HorizCylinder cl:agent.Torus cl:agent.VertCylinder o:agent.Topology\n    [3] cl:agent.AgentSet cl:agent.ArrayAgentSet o:agent.AgentSet\n\n(The numbers are cycle sizes.)\n\nAnd here's part of the layer report for the same codebase:\n\n    [14] o:org.nlogo.headless.Main\n    [14] o:org.nlogo.headless.Shell\n    [13] o:org.nlogo.compile.middle.FrontMiddleBridge\n    [13] o:org.nlogo.headless.HeadlessWorkspace\n    [13] o:org.nlogo.mirror.ModelRunIO\n    [12] o:org.nlogo.compile.back.BackEnd\n    [12] o:org.nlogo.compile.middle.MiddleEnd\n\nshowing just the topmost layers of the application.\n\n#### Modifying the graph\n\nWe can explore the effect of removing edges from the graph using `removePaths`:\n\n    scala\u003e res0.removePaths(\"Dep2\", \"java.lang\")\n\n    scala\u003e println(res0.fullString)\n    Graph 'dep.json': 9 nodes, 8 edges\n    Nodes:\n      - o:Dep1\n      - pkt:scala.tp:AnyRef\n      - o:Dep1.def:\u003cinit\u003e\n      - o:Dep1.def:x\n      - o:Dep1.t:x\n      - pkt:scala.cl:Int\n      - o:Dep1.def:y\n      - o:Dep1.t:y\n      - ov:Dep1\n    Edges:\n      - o:Dep1 -[Extends]-\u003e pkt:scala.tp:AnyRef\n      - o:Dep1.def:\u003cinit\u003e -[Uses]-\u003e o:Dep1\n      - o:Dep1.def:x -[Uses]-\u003e o:Dep1.t:x\n      - o:Dep1.def:x -[Uses]-\u003e pkt:scala.cl:Int\n      - o:Dep1.def:y -[Uses]-\u003e o:Dep1.t:y\n      - o:Dep1.def:y -[Uses]-\u003e pkt:scala.cl:Int\n      - o:Dep1.t:x -[Uses]-\u003e pkt:scala.cl:Int\n      - o:Dep1.t:y -[Uses]-\u003e pkt:scala.cl:Int\n\nSaving the graph back to a JSON model and loading it again:\n\n    scala\u003e save(res0, \"dep2.json\")\n\n    scala\u003e load(\"dep2.json\")\n    res5: com.lightbend.tools.sculpt.model.Graph = Graph 'dep2.json': 8 nodes, 8 edges\n\n    scala\u003e println(res5.fullString)\n    Graph 'dep2.json': 8 nodes, 8 edges\n    Nodes:\n      - o:Dep1\n      - pkt:scala.tp:AnyRef\n      - o:Dep1.def:\u003cinit\u003e\n      - o:Dep1.def:x\n      - o:Dep1.t:x\n      - pkt:scala.cl:Int\n      - o:Dep1.def:y\n      - o:Dep1.t:y\n    Edges:\n      - o:Dep1 -[Extends]-\u003e pkt:scala.tp:AnyRef\n      - o:Dep1.def:\u003cinit\u003e -[Uses]-\u003e o:Dep1\n      - o:Dep1.def:x -[Uses]-\u003e o:Dep1.t:x\n      - o:Dep1.def:x -[Uses]-\u003e pkt:scala.cl:Int\n      - o:Dep1.def:y -[Uses]-\u003e o:Dep1.t:y\n      - o:Dep1.def:y -[Uses]-\u003e pkt:scala.cl:Int\n      - o:Dep1.t:x -[Uses]-\u003e pkt:scala.cl:Int\n      - o:Dep1.t:y -[Uses]-\u003e pkt:scala.cl:Int\n\n## Future work\n\nPossible future directions include:\n\n* aggregation of dependency data at higher \"zoom levels\" (per-package, per-source-file)\n* user interface (perhaps via IDE integration)\n* automatic identification of problematic dependencies\n* “what-if” analyses exploring the effect of proposed code changes\n* offer a means of declaring and enforcing desired architectural constraints (allowed and forbidden dependencies)\n\nThere are tickets on some of these at https://github.com/lightbend-labs/scala-sculpt/issues .\n\n## Similar/related work\n\n* https://github.com/matanster/extractor\n* https://github.com/lihaoyi/acyclic\n* https://www.jetbrains.com/help/idea/dsm-analysis.html\n* http://classycle.sourceforge.net\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightbend-labs%2Fscala-sculpt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flightbend-labs%2Fscala-sculpt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightbend-labs%2Fscala-sculpt/lists"}