├── .sbtopts ├── .github ├── mergify.yml ├── scala-steward.conf ├── dependabot.yml └── workflows │ ├── dependency-graph.yml │ └── build-test.yml ├── project ├── build.properties ├── plugins.sbt └── OmnidocBuild.scala ├── .gitignore ├── README.md └── LICENSE /.sbtopts: -------------------------------------------------------------------------------- 1 | -J-Xms2G 2 | -J-Xmx2G 3 | -------------------------------------------------------------------------------- /.github/mergify.yml: -------------------------------------------------------------------------------- 1 | extends: .github 2 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.7 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea/ 3 | .bsp/ 4 | .sdkmanrc 5 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.1.1") 2 | -------------------------------------------------------------------------------- /.github/scala-steward.conf: -------------------------------------------------------------------------------- 1 | commits.message = "[3.0.x] ${artifactName} ${nextVersion} (was ${currentVersion})" 2 | 3 | pullRequests.grouping = [ 4 | { name = "patches", "title" = "[3.0.x] Patch updates", "filter" = [{"version" = "patch"}] } 5 | ] 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | commit-message: 8 | prefix: "[3.0.x] " 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | target-branch: "2.9.x" 14 | commit-message: 15 | prefix: "[2.9.x] " 16 | -------------------------------------------------------------------------------- /.github/workflows/dependency-graph.yml: -------------------------------------------------------------------------------- 1 | name: Dependency Graph 2 | on: 3 | push: 4 | branches: 5 | - 3.0.x 6 | 7 | concurrency: 8 | # Only run once for latest commit per ref and cancel other (previous) runs. 9 | group: dependency-graph-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | permissions: 13 | contents: write # this permission is needed to submit the dependency graph 14 | 15 | jobs: 16 | dependency-graph: 17 | name: Submit dependencies to GitHub 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v6 21 | with: 22 | fetch-depth: 0 23 | ref: ${{ inputs.ref }} 24 | - uses: sbt/setup-sbt@v1 25 | - uses: scalacenter/sbt-dependency-submission@v3 26 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | pull_request: 5 | 6 | push: 7 | branches: 8 | - 3.0.x # Check branch after merge 9 | 10 | concurrency: 11 | # Only run once for latest commit per ref and cancel other (previous) runs. 12 | group: ci-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | publish-local: 17 | name: Publish local 18 | uses: playframework/.github/.github/workflows/cmd.yml@v4 19 | with: 20 | java: 21, 17, 11 21 | scala: 2.13.x, 3.x 22 | cmd: sbt ++$MATRIX_SCALA publishLocal # Check that we can actually build and package the library 23 | 24 | finish: 25 | name: Finish 26 | if: github.event_name == 'pull_request' 27 | needs: # Should be last 28 | - "publish-local" 29 | uses: playframework/.github/.github/workflows/rtm.yml@v4 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Omnidoc 2 | 3 | [![Twitter Follow](https://img.shields.io/twitter/follow/playframework?label=follow&style=flat&logo=twitter&color=brightgreen)](https://twitter.com/playframework) 4 | [![Discord](https://img.shields.io/discord/931647755942776882?logo=discord&logoColor=white)](https://discord.gg/g5s2vtZ4Fa) 5 | [![GitHub Discussions](https://img.shields.io/github/discussions/playframework/playframework?&logo=github&color=brightgreen)](https://github.com/playframework/playframework/discussions) 6 | [![StackOverflow](https://img.shields.io/static/v1?label=stackoverflow&logo=stackoverflow&logoColor=fe7a16&color=brightgreen&message=playframework)](https://stackoverflow.com/tags/playframework) 7 | [![YouTube](https://img.shields.io/youtube/channel/views/UCRp6QDm5SDjbIuisUpxV9cg?label=watch&logo=youtube&style=flat&color=brightgreen&logoColor=ff0000)](https://www.youtube.com/channel/UCRp6QDm5SDjbIuisUpxV9cg) 8 | [![Twitch Status](https://img.shields.io/twitch/status/playframework?logo=twitch&logoColor=white&color=brightgreen&label=live%20stream)](https://www.twitch.tv/playframework) 9 | [![OpenCollective](https://img.shields.io/opencollective/all/playframework?label=financial%20contributors&logo=open-collective)](https://opencollective.com/playframework) 10 | 11 | [![Build Status](https://github.com/playframework/omnidoc/actions/workflows/build-test.yml/badge.svg)](https://github.com/playframework/omnidoc/actions/workflows/build-test.yml) 12 | [![Maven](https://img.shields.io/maven-central/v/org.playframework/play-omnidoc_2.13.svg?logo=apache-maven)](https://mvnrepository.com/artifact/org.playframework/play-omnidoc_2.13) 13 | [![Repository size](https://img.shields.io/github/repo-size/playframework/omnidoc.svg?logo=git)](https://github.com/playframework/omnidoc) 14 | [![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-blue.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org) 15 | [![Mergify Status](https://img.shields.io/endpoint.svg?url=https://api.mergify.com/v1/badges/playframework/omnidoc&style=flat)](https://mergify.com) 16 | 17 | Omnidoc is an sbt build that adds to sbt's `mappings in (Compile, packageBin)` to aggregate source code and manuals produced within the Play ecosystem and produce a single deliverable. This must not be confused with Interplay's [Omnidoc](https://github.com/playframework/interplay/blob/main/src/main/scala/interplay/Omnidoc.scala) which _simply_ adds some metadata on the `-source.jar` artifact of every Play library. 18 | 19 | The resulting deliverable includes: 20 | 21 | * manual (also referred to as `playdoc`) 22 | * javadoc 23 | * scaladoc 24 | 25 | ## How it works 26 | 27 | Omnidoc defines [5 tasks](https://github.com/playframework/omnidoc/blob/70f04533d0f881a9a7f6c1ac5ec6af1d8bb335f9/project/OmnidocBuild.scala#L88-L92) to: 28 | 29 | 1. download `-sources.jar` and `-playdoc.jar` artifacts for each dependency 30 | 2. extract the dowloaded `-sources`artifacts into `omnidoc/sources/` 31 | 3. extract the dowloaded `-playdoc`artifacts into `omnidoc/playdoc/` 32 | 4. use `omnidoc/sources/` to produce `omnidoc/javadoc/` 33 | 5. use `omnidoc/sources/` to produce `omnidoc/scaladoc/` 34 | 6. package `playdoc` into `play/docs/content` 35 | 7. package `javadoc` into `play/docs/content/api/java` 36 | 8. package `scaladoc` into `play/docs/content/api/scala` 37 | 38 | *NOTE:* all the paths above are relative to `target/scala-2.1x/` (for example `target/scala-2.12/`). 39 | 40 | ## Releasing a new version 41 | 42 | Omnidoc will be released as part of a Play Framework release. See https://github.com/playframework/.github/blob/main/RELEASING.md 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /project/OmnidocBuild.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import sbt.Artifact.SourceClassifier 3 | import sbt.Keys._ 4 | import sbt.io.Using 5 | import sbt.librarymanagement.{GetClassifiersConfiguration, GetClassifiersModule, SemanticSelector, UpdateConfiguration, VersionNumber} 6 | import sbtdynver.DynVerPlugin.autoImport._ 7 | 8 | import java.io.IOException 9 | 10 | object OmnidocBuild { 11 | 12 | val scala213 = "2.13.18" 13 | val scala3 = "3.3.7" 14 | 15 | val playOrganisation = "org.playframework" 16 | val twirlOrganisation = "org.playframework.twirl" 17 | val scalaTestPlusPlayOrganisation = "org.scalatestplus.play" 18 | val playOrganisations = Seq(playOrganisation, twirlOrganisation, scalaTestPlusPlayOrganisation) 19 | 20 | val snapshotVersionLabel = "3.0.x" 21 | 22 | val playVersion = sys.props.getOrElse("play.version", "3.0.9") 23 | val scalaTestPlusPlayVersion = sys.props.getOrElse("scalatestplus-play.version", "7.0.2") 24 | val playJsonVersion = sys.props.getOrElse("play-json.version", "3.0.6") 25 | val playSlickVersion = sys.props.getOrElse("play-slick.version", "6.2.0") 26 | val maybeTwirlVersion = sys.props.get("twirl.version") 27 | 28 | // List Play artifacts so that they can be added as dependencies 29 | // and later Omnidoc will read the javadoc artifact. 30 | // 31 | // Of course there are dependencies between these projects and 32 | // we would need to list them all here, but it is better if we 33 | // do so that we won't need to worry about the dependency tree. 34 | val playProjects = Seq( 35 | "play", 36 | "play-ahc-ws", 37 | "play-pekko-http-server", 38 | "play-pekko-http2-support", 39 | "play-cache", 40 | "play-caffeine-cache", 41 | "play-cluster-sharding", 42 | "play-configuration", 43 | "play-ehcache", 44 | "play-guice", 45 | "play-java", 46 | "play-java-cluster-sharding", 47 | "play-java-forms", 48 | "play-java-jdbc", 49 | "play-java-jpa", 50 | "play-jcache", 51 | "play-jdbc", 52 | "play-jdbc-api", 53 | "play-jdbc-evolutions", 54 | "play-joda-forms", 55 | "play-logback", 56 | "play-netty-server", 57 | "play-openid", 58 | "play-server", 59 | "play-specs2", 60 | "play-streams", 61 | "play-test", 62 | "play-ws", 63 | "play-filters-helpers", 64 | ) 65 | 66 | val excludeArtifacts = Seq( 67 | "play-build-link", 68 | "play-exceptions", 69 | "play-netty-utils", 70 | ) 71 | 72 | val playModules = Seq( 73 | scalaTestPlusPlayOrganisation %% "scalatestplus-play" % scalaTestPlusPlayVersion, 74 | playOrganisation %% "play-functional" % playJsonVersion, 75 | playOrganisation %% "play-json" % playJsonVersion, 76 | playOrganisation %% "play-slick" % playSlickVersion, 77 | playOrganisation %% "play-slick-evolutions" % playSlickVersion 78 | ) 79 | 80 | val maybeTwirlModule = maybeTwirlVersion.map(v => twirlOrganisation %% "twirl-api" % v).toSeq 81 | 82 | val externalModules = playModules ++ maybeTwirlModule 83 | 84 | val nameFilter = excludeArtifacts.foldLeft(new SimpleFilter(!_.contains("-standalone")): NameFilter)(_ - _) 85 | val organizationFilter = playOrganisations.map(new ExactFilter(_): NameFilter).reduce(_ | _) 86 | val playModuleFilter = moduleFilter(organization = organizationFilter, name = nameFilter) 87 | 88 | val Omnidoc = config("omnidoc").hide 89 | 90 | val PlaydocClassifier = "playdoc" 91 | 92 | val extractedSources = TaskKey[Seq[Extracted]]("extractedSources") 93 | val sourceUrls = TaskKey[Map[String, String]]("sourceUrls") 94 | val javadoc = TaskKey[File]("javadoc") 95 | val scaladoc = TaskKey[File]("scaladoc") 96 | val playdoc = TaskKey[File]("playdoc") 97 | 98 | // Duplicate of sbt's updateClassifiers, for 'playdoc' specific use 99 | val updatePlaydocClassifiers = taskKey[(UpdateReport, UpdateReport)]("") 100 | 101 | lazy val omnidoc = project 102 | .in(file(".")) 103 | .settings(omnidocSettings) 104 | .configs(Omnidoc) 105 | 106 | def omnidocSettings: Seq[Setting[_]] = 107 | projectSettings ++ 108 | dependencySettings ++ 109 | inConfig(Omnidoc) { 110 | updateSettings ++ 111 | extractSettings ++ 112 | compilerReporterSettings ++ 113 | scaladocSettings ++ 114 | javadocSettings ++ 115 | packageSettings 116 | } 117 | 118 | def projectSettings: Seq[Setting[_]] = Seq( 119 | name := "play-omnidoc", 120 | crossScalaVersions := Seq(scala213, scala3), 121 | scalaVersion := (Seq(scala213, scala3) 122 | .filter(v => SemanticSelector(sys.props.get("scala.version").getOrElse(scala213)).matches(VersionNumber(v))) match { 123 | case Nil => sys.error("Unable to detect scalaVersion!") 124 | case Seq(version) => version 125 | case multiple => sys.error(s"Multiple crossScalaVersions matched query '${sys.props("scala.version")}': ${multiple.mkString(", ")}") 126 | }), 127 | resolvers += Resolver.sonatypeCentralSnapshots, 128 | useCoursier := false, // so updatePlaydocClassifiers isn't empty 129 | updateSbtClassifiers / useCoursier := true, // https://github.com/sbt/sbt/issues/5263#issuecomment-626462593 130 | ThisBuild / dynverVTagPrefix := false, // Customise sbt-dynver's behaviour to make it work with tags which aren't v-prefixed 131 | Global / onLoad := (Global / onLoad).value.andThen { s => 132 | dynverAssertTagVersion.value // Sanity-check: assert that version comes from a tag (e.g. not a too-shallow clone) 133 | s // https://github.com/dwijnand/sbt-dynver/#sanity-checking-the-version 134 | }, 135 | ) 136 | 137 | def dependencySettings: Seq[Setting[_]] = Seq( 138 | ivyConfigurations += Omnidoc, 139 | libraryDependencies ++= playProjects.map(playOrganisation %% _ % playVersion % Omnidoc.name), 140 | libraryDependencies ++= externalModules.map(_ % Omnidoc.name), 141 | libraryDependencies ++= Seq( 142 | playOrganisation %% "play-docs" % playVersion, 143 | ) 144 | ) 145 | 146 | def updateSettings: Seq[Setting[_]] = Seq( 147 | transitiveClassifiers := Seq(SourceClassifier, PlaydocClassifier), 148 | updatePlaydocClassifiers := updatePlaydocClassifiersTask.value 149 | ) 150 | 151 | def extractSettings: Seq[Setting[_]] = Seq( 152 | target := crossTarget.value / "omnidoc", 153 | sources / target := target.value / "sources", 154 | extractedSources := extractSources.value, 155 | sources := extractedSources.value.map(_.dir), 156 | sourceUrls := getSourceUrls(extractedSources.value), 157 | dependencyClasspath := Classpaths.managedJars(configuration.value, classpathTypes.value, update.value), 158 | playdoc / target := target.value / "playdoc", 159 | playdoc := extractPlaydocs.value 160 | ) 161 | 162 | def scaladocSettings: Seq[Setting[_]] = Defaults.docTaskSettings(scaladoc) ++ Seq( 163 | scaladoc / sources := { 164 | val s = sources.value 165 | // Exclude a Play JSON file from the Scaladoc build because 166 | // it includes a macro from a third party library and we don't 167 | // want to deal with bringing extra libraries into Omnidoc. 168 | ((s ** "*.scala") --- (s ** "JsMacroImpl.scala")).get 169 | }, 170 | Compile / sources := (scaladoc / sources).value, // Needed for Scala 3 compilation, see comments below 171 | Compile / dependencyClasspath := dependencyClasspath.value, // Needed for Scala 3 compilation, see comments below 172 | scaladoc / target := target.value / "scaladoc", 173 | scaladoc / scalacOptions := scaladocOptions.value, 174 | scaladoc := { 175 | if (ScalaArtifacts.isScala3(scalaVersion.value)) { 176 | // Scala 3 scaladoc ("dottydoc") does not support .scala files at the moment (unless Scala 2 scaladoc 177 | // which does). It requires .tasty and/or .jar files to generate the scala doc. That means, when on Scala 3, 178 | // we need to trigger a full compilation to generate those .tasty files. 179 | // (This requires to set Compile / dependencyClasspath and Compile / sources) 180 | // See 181 | // https://github.com/lampepfl/dotty/issues/11454#issuecomment-1636872223 182 | // https://github.com/lampepfl/dotty/pull/18215 183 | (Compile / compile).value 184 | } else { 185 | // No compilation needed for Scala 2 186 | } 187 | rewriteSourceUrls(scaladoc.value, sourceUrls.value, "/src/main/scala", ".scala") 188 | } 189 | ) 190 | 191 | def javadocSettings: Seq[Setting[_]] = Defaults.docTaskSettings(javadoc) ++ Seq( 192 | (javadoc / sources) := (sources.value ** "*.java").get, 193 | (javadoc / target) := target.value / "javadoc", 194 | (javadoc / javacOptions) := javadocOptions.value 195 | ) 196 | 197 | private val compilerReporter = taskKey[xsbti.Reporter]("Experimental hook to listen (or send) compilation failure messages.") 198 | 199 | def compilerReporterSettings = Seq( 200 | compile / compilerReporter := { 201 | new sbt.internal.inc.LoggedReporter( 202 | maxErrors.value, 203 | streams.value.log, 204 | foldMappers(sourcePositionMappers.value) 205 | ) 206 | }, 207 | tastyFiles := (Compile / compile / tastyFiles).value, // not 100% sure but it seems to work 208 | bspReporter := (Compile / compile / bspReporter).value, // same 209 | ) 210 | 211 | private def foldMappers[A](mappers: Seq[A => Option[A]]): A => A = 212 | mappers.foldRight(idFun[A]) { (mapper, acc) => 213 | p: A => mapper(p).getOrElse(acc(p)) 214 | } 215 | 216 | def packageSettings: Seq[Setting[_]] = Seq( 217 | Compile / packageBin / mappings ++= { 218 | def mapped(dir: File, path: String) = dir.allPaths.pair(Path.rebase(dir, path)) 219 | mapped(playdoc.value, "play/docs/content") ++ 220 | mapped(scaladoc.value, "play/docs/content/api/scala") ++ 221 | mapped(javadoc.value, "play/docs/content/api/java") 222 | } 223 | ) 224 | 225 | /** 226 | * Custom update classifiers task that only resolves classifiers for Play modules. 227 | * Also redirects warnings to debug for any artifacts that can't be found. 228 | */ 229 | private def updatePlaydocClassifiersTask: Def.Initialize[Task[(UpdateReport, UpdateReport)]] = Def.task { 230 | val updateConfig0 = updateConfiguration.value.withMetadataDirectory(dependencyCacheDirectory.value) 231 | val updateConfig1 = updateConfig0.withArtifactFilter(updateConfig0.artifactFilter.map(_.invert)) 232 | 233 | def updateClassifiersTask0(updateConfig: UpdateConfiguration): UpdateReport = { 234 | val lm = dependencyResolution.value 235 | val omnidocReport = update.value.configuration(Omnidoc.toConfigRef) 236 | val playModules = omnidocReport.toVector.flatMap(_.allModules.filter(playModuleFilter)) 237 | val classifiers = transitiveClassifiers.value.toVector 238 | val playdocConfig = GetClassifiersConfiguration( 239 | GetClassifiersModule(projectID.value, None, playModules, Vector(Omnidoc), classifiers), 240 | Vector.empty, 241 | updateConfig, 242 | sourceArtifactTypes.value.toVector, 243 | docArtifactTypes.value.toVector, 244 | ) 245 | val uwConfig = (update / unresolvedWarningConfiguration).value 246 | lm.updateClassifiers(playdocConfig, uwConfig, Vector.empty, streams.value.log).right.get 247 | } 248 | 249 | // Current impl of Ivy resolvers in sbt or the underlying Ivy client don't support 250 | // the combination of `playdoc` classifiers with `sources` and `docs` so we 251 | // run two passes with different (complementary) filters. 252 | (updateClassifiersTask0(updateConfig1), updateClassifiersTask0(updateConfig0)) 253 | }.tag(Tags.Update, Tags.Network) 254 | 255 | /** 256 | * Redirect logging above a certain level to debug. 257 | */ 258 | def quietLogger(underlying: Logger, minimumLevel: Level.Value = Level.Info): Logger = new Logger { 259 | def log(level: Level.Value, message: => String): Unit = { 260 | if (level.id > minimumLevel.id) underlying.log(Level.Debug, s"[$level] $message") 261 | else underlying.log(level, message) 262 | } 263 | def success(message: => String): Unit = underlying.success(message) 264 | def trace(t: => Throwable): Unit = underlying.trace(t) 265 | } 266 | 267 | def extractSources = Def.task { 268 | val log = streams.value.log 269 | val targetDir = (sources / target).value 270 | val dependencies = updatePlaydocClassifiers.value._1.filter(artifactFilter(classifier = SourceClassifier)).toSeq 271 | log.info("Extracting sources...") 272 | IO.delete(targetDir) 273 | dependencies.map { case (conf, module, artifact, file) => 274 | val name = s"${module.organization}-${module.name}-${module.revision}" 275 | val dir = targetDir / name 276 | log.debug(s"Extracting $name") 277 | IO.unzip(file, dir, -"META-INF*") 278 | val sourceUrl = extractSourceUrl(file) 279 | if (sourceUrl.isEmpty) log.warn(s"Source url not found for ${module.name}") 280 | Extracted(dir, sourceUrl) 281 | } 282 | } 283 | 284 | def extractPlaydocs = Def.task { 285 | val log = streams.value.log 286 | val targetDir = (playdoc / target).value 287 | val dependencies = updatePlaydocClassifiers.value._2.matching(artifactFilter(classifier = PlaydocClassifier)) 288 | log.info("Extracting playdocs...") 289 | IO.delete(targetDir) 290 | dependencies.foreach { file => 291 | log.debug(s"Extracting $file") 292 | IO.unzip(file, targetDir, -"META-INF*") 293 | } 294 | targetDir 295 | } 296 | 297 | def scaladocOptions = Def.task { 298 | val sourcepath = (sources / target).value.getAbsolutePath 299 | val docSourceUrl = sourceUrlMarker("€{FILE_PATH}") 300 | Seq( 301 | "-sourcepath", sourcepath, 302 | "-doc-source-url", docSourceUrl 303 | ) 304 | } 305 | 306 | def javadocOptions = Def.task { 307 | val versionish = if (isSnapshot.value) snapshotVersionLabel else version.value 308 | val label = s"Play $versionish" 309 | Seq( 310 | "-windowtitle", label, 311 | // Adding a user agent when we run `javadoc` was necessary to create link docs 312 | // with Akka (at least, maybe play too) because doc.akka.io is served by Cloudflare 313 | // which blocks requests without a User-Agent header. 314 | // Not sure we need that for Pekko however. 315 | "-J-Dhttp.agent=Play-Unidoc-Javadoc", 316 | "-link", 317 | "https://docs.oracle.com/en/java/javase/11/docs/api/", 318 | "-link", 319 | "https://pekko.apache.org/japi/pekko/1.0/", 320 | "-link", 321 | "https://pekko.apache.org/japi/pekko-http/1.0/", 322 | "-notimestamp", 323 | "-exclude", "play.api:play.core", 324 | "-Xdoclint:none", 325 | "-noqualifier", 326 | "java.lang", 327 | "-encoding", 328 | "UTF-8", 329 | "-source", 330 | "11", 331 | 332 | ) 333 | } 334 | 335 | // Source linking 336 | 337 | case class Extracted(dir: File, url: Option[String]) 338 | 339 | val SourceUrlKey = "Omnidoc-Source-URL" 340 | 341 | val NoSourceUrl = "javascript:;" 342 | 343 | // first part of path is the extracted directory name, which is used as the source url mapping key 344 | val SourceUrlRegex = sourceUrlMarker("/([^/\\s]*)(/\\S*)").r 345 | 346 | def extractSourceUrl(sourceJar: File): Option[String] = { 347 | Using.jarFile(verify = false)(sourceJar) { jar => 348 | try { 349 | Option(jar.getManifest.getMainAttributes.getValue(SourceUrlKey)) 350 | } catch { 351 | case e: IOException => 352 | // JDK jar APIs refuse to attribute jar files in their exceptions 353 | // And vegemite seems to have a corrupt jar 354 | throw new IOException(s"Error reading manifest attributes from $sourceJar", e) 355 | } 356 | } 357 | } 358 | 359 | def sourceUrlMarker(path: String): String = s"http://_SOURCE;${path}_" 360 | 361 | def getSourceUrls(extracted: Seq[Extracted]): Map[String, String] = { 362 | (extracted flatMap { source => source.url map source.dir.name.-> }).toMap 363 | } 364 | 365 | def rewriteSourceUrls(baseDir: File, sourceUrls: Map[String, String], prefix: String, suffix: String): File = { 366 | val files = baseDir.allPaths.filter(!_.isDirectory).get 367 | files.foreach { file => 368 | val contents = IO.read(file) 369 | val newContents = SourceUrlRegex.replaceAllIn(contents, matched => { 370 | val key = matched.group(1) 371 | val path = matched.group(2) 372 | sourceUrls.get(key).fold(NoSourceUrl)(_ + prefix + path + suffix) 373 | }) 374 | if (newContents != contents) { 375 | IO.write(file, newContents) 376 | } 377 | } 378 | baseDir 379 | } 380 | 381 | } 382 | --------------------------------------------------------------------------------