├── .github └── workflows │ ├── ci.yml │ └── scala-steward.yml ├── .gitignore ├── .mergify.yml ├── .scala-steward.conf ├── .scalafmt.conf ├── LICENSE ├── README.md ├── browser-test-js └── src │ └── main │ └── scala │ └── com │ └── softwaremill │ └── SbtSoftwareMillBrowserTestJS.scala ├── build.sbt ├── common └── src │ ├── main │ └── scala │ │ └── com │ │ └── softwaremill │ │ └── SbtSoftwareMillCommon.scala │ └── sbt-test │ └── sbt-softwaremill │ └── simple │ ├── A.scala │ ├── build.sbt │ ├── project │ └── plugins.sbt │ └── test ├── extra └── src │ └── main │ └── scala │ └── com │ └── softwaremill │ ├── CheckUpdates.scala │ └── SbtSoftwareMillExtra.scala ├── project ├── Publish.scala ├── UpdateVersionInDocs.scala ├── build.properties ├── build.sbt └── project │ └── plugins.sbt ├── publish └── src │ └── main │ └── scala │ └── com │ └── softwaremill │ ├── Publish.scala │ ├── SbtSoftwareMillPublish.scala │ └── UpdateVersionInDocs.scala └── scripts └── decrypt_files_if_not_pr.sh /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | branches: ['**'] 5 | push: 6 | branches: ['**'] 7 | tags: [v*] 8 | jobs: 9 | ci: 10 | # run on external PRs, but not on internal PRs since those will be run by push to branch 11 | if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository 12 | runs-on: ubuntu-24.04 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | - uses: sbt/setup-sbt@v1 17 | - name: Set up JDK 18 | uses: actions/setup-java@v4 19 | with: 20 | distribution: 'temurin' 21 | cache: 'sbt' 22 | java-version: 11 23 | - name: Test 24 | run: sbt test 25 | - name: Publish 26 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v')) 27 | run: sbt ci-release 28 | env: 29 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 30 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 31 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 32 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 33 | -------------------------------------------------------------------------------- /.github/workflows/scala-steward.yml: -------------------------------------------------------------------------------- 1 | name: Scala Steward 2 | 3 | # This workflow will launch at 00:00 every day 4 | on: 5 | schedule: 6 | - cron: '0 0 * * *' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | scala-steward: 11 | runs-on: ubuntu-22.04 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | - name: Set up JDK 11 16 | uses: actions/setup-java@v3 17 | with: 18 | distribution: 'temurin' 19 | java-version: 11 20 | cache: 'sbt' 21 | - name: Launch Scala Steward 22 | uses: scala-steward-org/scala-steward-action@v2 23 | with: 24 | author-name: scala-steward 25 | author-email: scala-steward 26 | github-token: ${{ secrets.REPO_GITHUB_TOKEN }} 27 | repo-config: .scala-steward.conf 28 | ignore-opts-files: false 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.ipr 3 | *.iws 4 | .idea/ 5 | target/ 6 | *.log 7 | .DS_Store 8 | application.conf 9 | data/ 10 | .bsp 11 | .metals 12 | .vscode 13 | metals.sbt 14 | .bloop -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: delete head branch after merge 3 | conditions: [] 4 | actions: 5 | delete_head_branch: {} 6 | - name: automatic merge for softwaremill-ci pull requests affecting build.sbt 7 | conditions: 8 | - author=softwaremill-ci 9 | - check-success=ci 10 | - "#files=1" 11 | - files=build.sbt 12 | actions: 13 | merge: 14 | method: merge 15 | - name: automatic merge for softwaremill-ci pull requests affecting project build.sbt 16 | conditions: 17 | - author=softwaremill-ci 18 | - check-success=ci 19 | - "#files=1" 20 | - files=project/build.sbt 21 | actions: 22 | merge: 23 | method: merge 24 | - name: automatic merge for softwaremill-ci pull requests affecting project build.properties 25 | conditions: 26 | - author=softwaremill-ci 27 | - check-success=ci 28 | - "#files=1" 29 | - files=project/build.properties 30 | actions: 31 | merge: 32 | method: merge 33 | - name: automatic merge for softwaremill-ci pull requests affecting .scalafmt.conf 34 | conditions: 35 | - author=softwaremill-ci 36 | - check-success=ci 37 | - "#files=1" 38 | - files=.scalafmt.conf 39 | actions: 40 | merge: 41 | method: merge 42 | - name: semi-automatic merge for softwaremill-ci pull requests 43 | conditions: 44 | - author=softwaremill-ci 45 | - check-success=ci 46 | - "#approved-reviews-by>=1" 47 | actions: 48 | merge: 49 | method: merge 50 | -------------------------------------------------------------------------------- /.scala-steward.conf: -------------------------------------------------------------------------------- 1 | updates.ignore = [ 2 | {groupId = "org.scala-lang", artifactId = "scala-compiler", version = "2.12."}, 3 | {groupId = "org.scala-lang", artifactId = "scala-compiler", version = "2.13."}, 4 | {groupId = "org.scala-lang", artifactId = "scala-compiler", version = "3."} 5 | ] 6 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version=3.8.6 2 | maxColumn=140 3 | dialect=scala212 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sbt-softwaremill 2 | 3 | [![Build Status](https://travis-ci.org/softwaremill/sbt-softwaremill.svg?branch=master)](https://travis-ci.org/softwaremill/sbt-softwaremill) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.sbt-softwaremill/sbt-softwaremill-common/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.sbt-softwaremill/sbt-softwaremill-common) 5 | 6 | A sane set of common build settings. 7 | 8 | ## Usage 9 | 10 | For each project where you'd like to use the build settings, add some or all of the following your `project/plugins.sbt` 11 | file: 12 | 13 | ````scala 14 | addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % "2.0.26") 15 | addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % "2.0.26") 16 | addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "2.0.26") 17 | addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-browser-test-js" % "2.0.26") 18 | ```` 19 | 20 | Now you can add the appropriate settings in your `build.sbt`, e.g.: 21 | 22 | ````scala 23 | import com.softwaremill.SbtSoftwareMillCommon.commonSmlBuildSettings 24 | 25 | lazy val commonSettings = commonSmlBuildSettings ++ Seq( 26 | // your settings, which can override some of commonSmlBuildSettings 27 | ) 28 | ```` 29 | 30 | Each dependency provides a choice of settings: 31 | 32 | ````scala 33 | // common - compiler flags 34 | import com.softwaremill.SbtSoftwareMillCommon.commonSmlBuildSettings 35 | commonSmlBuildSettings 36 | 37 | // publish 38 | import com.softwaremill.Publish.ossPublishSettings 39 | ossPublishSettings 40 | 41 | // extra - use all or choose 42 | lazy val extraSmlBuildSettings = 43 | dependencyUpdatesSettings ++ // check dependency updates on startup (max once per 12h) 44 | dependencyCheckSettings 45 | 46 | // downloads the appropriate chrome/gecko driver for testing scala.js using scalajs-env-selenium and sets the jsenv 47 | import com.softwaremill.SbtSoftwareMillBrowserTestJS.{browserChromeTestSettings, browserGeckoTestSettings} 48 | browserChromeTestSettings 49 | browserGeckoTestSettings 50 | ```` 51 | 52 | `sbt-softwaremill-common` comes with: 53 | - [sbt-scalafmt](https://scalameta.org/scalafmt/docs/installation.html) 54 | - [sbt-tpolecat](https://github.com/DavidGregory084/sbt-tpolecat) 55 | 56 | `sbt-softwaremill-publish` comes with: 57 | - [sbt-ci-release](https://github.com/olafurpg/sbt-ci-release) 58 | 59 | `sbt-softwaremill-extra` comes with: 60 | - [sbt-updates](https://github.com/rtimush/sbt-updates) 61 | - [sbt-dependency-check](https://github.com/albuch/sbt-dependency-check) 62 | 63 | ## Sonatype setup 64 | 65 | By default, the plugins use the new `s01.oss.sonatype.org` host for releasing to Sonatype. If your project isn't yet 66 | [migrated](https://central.sonatype.org/news/20210223_new-users-on-s01/), you'll need to add the following to your 67 | root project settings: 68 | 69 | ```scala 70 | sonatypeCredentialHost := "oss.sonatype.org" 71 | ``` 72 | 73 | ## Releasing your library 74 | 75 | `sbt-softwaremill-publish` exposes a default configuration suitable for releasing open source libraries. 76 | The release process is broken into two steps: 77 | 78 | 1. *local*: `sbt release`. This sbt command prepares the next release: asks about the version, updates the version 79 | in the docs & readme, commits the changes and finally asks the user to push the changes. Your `README.md`, 80 | `docs` and `doc` directory will be parsed for `"[organization]" %(%) "artifactId" % "someVersion"` and that 81 | version value will be bumped. 82 | 2. *remote*: `sbt ci-release`. This sbt command should be run on GH actions, triggered when a new tag is pushed. It 83 | publishes the artifacts to sonatype, and invokes repository release. 84 | 85 | To setup the remote part, follow the guide on [sbt-ci-release](https://github.com/olafurpg/sbt-ci-release). You can 86 | also take a look at this project's `.github/workflows/ci.yml`. 87 | 88 | You might need to explicitly set the sonatype profile name: 89 | 90 | ```scala 91 | val commonSettings = ossPublishSettings ++ Seq( 92 | sonatypeProfileName := "com.example" 93 | ) 94 | ``` 95 | 96 | ## Releasing sbt-softwaremill 97 | 98 | sbt-softwaremill release process is setup on GH Actions. This plugin uses itself to publish binaries to oss-sonatype. 99 | 100 | ## Note for migrating from sbt-softwaremill 1.x series 101 | 102 | You should remove `version.sbt` file as it's no longer used, and it may disrupt the release process. In the 2.x series the version is deduced from git tags and the current state using [https://github.com/dwijnand/sbt-dynver](https://github.com/dwijnand/sbt-dynver). 103 | 104 | Moreover, a number of bundled plugins are removed, which aren't available for Scala3 and would cause build problems 105 | -------------------------------------------------------------------------------- /browser-test-js/src/main/scala/com/softwaremill/SbtSoftwareMillBrowserTestJS.scala: -------------------------------------------------------------------------------- 1 | package com.softwaremill 2 | 3 | import sbt.{Def, Global, Task, TaskKey, taskKey, _} 4 | import Keys._ 5 | import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.{jsEnv, scalaJSLinkerConfig} 6 | 7 | object SbtSoftwareMillBrowserTestJS { 8 | val downloadChromeDriver: TaskKey[Unit] = taskKey[Unit]( 9 | "Download chrome driver corresponding to installed google-chrome version" 10 | ) 11 | 12 | lazy val downloadGeckoDriver: TaskKey[Unit] = taskKey[Unit]( 13 | "Download gecko driver" 14 | ) 15 | 16 | lazy val geckoDriverVersion: SettingKey[String] = 17 | settingKey[String]("Gecko driver version to download") 18 | 19 | val downloadChromeDriverSettings: Seq[Def.Setting[Task[Unit]]] = Seq( 20 | Global / downloadChromeDriver := { 21 | if (java.nio.file.Files.notExists(new File("target", "chromedriver").toPath)) { 22 | println( 23 | "ChromeDriver binary file not found. Detecting google-chrome version..." 24 | ) 25 | import sys.process._ 26 | val osName = sys.props("os.name") 27 | val isMac = osName.toLowerCase.contains("mac") 28 | val isWin = osName.toLowerCase.contains("win") 29 | val chromeVersionExecutable = 30 | if (isMac) 31 | "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" 32 | else "google-chrome" 33 | val chromeVersion = Seq(chromeVersionExecutable, "--version").!!.split(' ')(2) 34 | println(s"Detected google-chrome version: $chromeVersion") 35 | val withoutLastPart = chromeVersion.split('.').dropRight(1).mkString(".") 36 | println(s"Selected release: $withoutLastPart") 37 | val latestVersion = IO.readLinesURL(new URL(s"https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_$withoutLastPart")).mkString 38 | val platformSuffix = if (isMac) { 39 | if (System.getProperty("os.arch") == "x86_64") "mac-x64" else "mac-arm64" 40 | } else if (isWin) { "win32" } else { "linux64" } 41 | println(s"Downloading chrome driver version $latestVersion for $osName") 42 | IO.unzipURL( 43 | new URL(s"https://storage.googleapis.com/chrome-for-testing-public/$latestVersion/$platformSuffix/chromedriver-$platformSuffix.zip"), 44 | new File("target") 45 | ) 46 | IO.move( 47 | new File(new File("target", s"chromedriver-$platformSuffix"), "chromedriver"), 48 | new File("target", "chromedriver")) 49 | IO.chmod("rwxrwxr-x", new File("target", "chromedriver")) 50 | } else { 51 | println("Detected chromedriver binary file, skipping downloading.") 52 | } 53 | } 54 | ) 55 | 56 | val downloadGeckoDriverSettings: Seq[Def.Setting[_]] = Seq( 57 | Global / geckoDriverVersion := "v0.28.0", 58 | Global / downloadGeckoDriver := { 59 | if (java.nio.file.Files.notExists(new File("target", "geckodriver").toPath)) { 60 | val version = (geckoDriverVersion in Global).value 61 | println(s"geckodriver binary file not found") 62 | import sys.process._ 63 | val osName = sys.props("os.name") 64 | val isMac = osName.toLowerCase.contains("mac") 65 | val isWin = osName.toLowerCase.contains("win") 66 | val platformDependentName = if (isMac) { 67 | "macos.tar.gz" 68 | } else if (isWin) { 69 | "win64.zip" 70 | } else { 71 | "linux64.tar.gz" 72 | } 73 | println(s"Downloading gecko driver version $version for $osName") 74 | val geckoDriverUrl = 75 | s"https://github.com/mozilla/geckodriver/releases/download/$version/geckodriver-$version-$platformDependentName" 76 | if (!isWin) { 77 | url(geckoDriverUrl) #> file("target/geckodriver.tar.gz") #&& 78 | "tar -xz -C target -f target/geckodriver.tar.gz" #&& 79 | "rm target/geckodriver.tar.gz" ! 80 | } else { 81 | IO.unzipURL(new URL(geckoDriverUrl), new File("target")) 82 | } 83 | IO.chmod("rwxrwxr-x", new File("target", "geckodriver")) 84 | } else { 85 | println("Detected geckodriver binary file, skipping downloading.") 86 | } 87 | } 88 | ) 89 | 90 | val browserCommonTestSetting: Seq[Def.Setting[_]] = Seq( 91 | // https://github.com/scalaz/scalaz/pull/1734#issuecomment-385627061 92 | scalaJSLinkerConfig ~= { 93 | _.withBatchMode( 94 | System.getenv("GITHUB_ACTIONS") == "true" || System.getenv( 95 | "CONTINUOUS_INTEGRATION" 96 | ) == "true" 97 | ) 98 | } 99 | ) 100 | 101 | val browserChromeTestSettings: Seq[Def.Setting[_]] = 102 | downloadChromeDriverSettings ++ browserCommonTestSetting ++ Seq( 103 | jsEnv in Test := { 104 | val debugging = false // set to true to help debugging 105 | System.setProperty("webdriver.chrome.driver", "target/chromedriver") 106 | new org.scalajs.jsenv.selenium.SeleniumJSEnv( 107 | { 108 | val options = new org.openqa.selenium.chrome.ChromeOptions() 109 | val args = Seq( 110 | "auto-open-devtools-for-tabs", // devtools needs to be open to capture network requests 111 | "no-sandbox", 112 | "allow-file-access-from-files" // change the origin header from 'null' to 'file' 113 | ) ++ (if (debugging) Seq.empty else Seq("headless")) 114 | options.addArguments(args: _*) 115 | val capabilities = 116 | org.openqa.selenium.remote.DesiredCapabilities.chrome() 117 | capabilities.setCapability( 118 | org.openqa.selenium.chrome.ChromeOptions.CAPABILITY, 119 | options 120 | ) 121 | capabilities 122 | }, 123 | org.scalajs.jsenv.selenium.SeleniumJSEnv 124 | .Config() 125 | .withKeepAlive(debugging) 126 | ) 127 | }, 128 | test in Test := (test in Test) 129 | .dependsOn(downloadChromeDriver) 130 | .value 131 | ) 132 | 133 | val browserGeckoTestSettings: Seq[Def.Setting[_]] = 134 | downloadGeckoDriverSettings ++ browserCommonTestSetting ++ Seq( 135 | jsEnv in Test := { 136 | val debugging = false // set to true to help debugging 137 | System.setProperty("webdriver.gecko.driver", "target/geckodriver") 138 | new org.scalajs.jsenv.selenium.SeleniumJSEnv( 139 | { 140 | val options = new org.openqa.selenium.firefox.FirefoxOptions() 141 | val args = (if (debugging) Seq("--devtools") else Seq("-headless")) 142 | options.addArguments(args: _*) 143 | options 144 | }, 145 | org.scalajs.jsenv.selenium.SeleniumJSEnv 146 | .Config() 147 | .withKeepAlive(debugging) 148 | ) 149 | }, 150 | test in Test := (test in Test) 151 | .dependsOn(downloadGeckoDriver) 152 | .value 153 | ) 154 | } 155 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import com.softwaremill.Publish 2 | import sbt.{addSbtPlugin, _} 3 | import Keys._ 4 | import sbtsoftwaremill.BuildInfo 5 | 6 | val commonSettings = Publish.ossPublishSettings ++ Seq( 7 | scalaVersion := "2.12.20", 8 | organization := "com.softwaremill.sbt-softwaremill", 9 | sbtVersion in Global := { 10 | scalaBinaryVersion.value match { 11 | case "2.10" => "0.13.17" 12 | case "2.12" => "1.10.2" 13 | } 14 | } 15 | ) 16 | 17 | lazy val root = project 18 | .in(file(".")) 19 | .settings(commonSettings) 20 | .settings( 21 | name := "sbt-softwaremill-root", 22 | description := "Build configuration for SBT projects" 23 | ) 24 | .settings(Publish.noPublishSettings) 25 | .aggregate(common, publish, extra, browserTestJs) 26 | 27 | lazy val common = project 28 | .in(file("common")) 29 | .enablePlugins(SbtPlugin) 30 | .settings(commonSettings) 31 | .settings( 32 | name := "sbt-softwaremill-common", 33 | description := "Build configuration for SBT projects: common", 34 | sbtPlugin := true, 35 | scriptedLaunchOpts += ("-Dplugin.version=" + version.value) 36 | ) 37 | .settings( 38 | addSbtPlugin( 39 | "org.scalameta" % "sbt-scalafmt" % BuildInfo.sbtScalafmtVersion 40 | ), 41 | addSbtPlugin("org.typelevel" % "sbt-tpolecat" % "0.5.2") 42 | ) 43 | 44 | lazy val publish = project 45 | .in(file("publish")) 46 | .enablePlugins(SbtPlugin) 47 | .settings(commonSettings) 48 | .settings( 49 | name := "sbt-softwaremill-publish", 50 | description := "Build configuration for SBT projects: publishing", 51 | sbtPlugin := true, 52 | scriptedLaunchOpts += ("-Dplugin.version=" + version.value), 53 | libraryDependencies += "com.github.pathikrit" %% "better-files" % "3.9.2" 54 | ) 55 | .settings( 56 | addSbtPlugin( 57 | "com.github.sbt" % "sbt-ci-release" % BuildInfo.sbtCiReleaseVersion 58 | ) 59 | ) 60 | 61 | lazy val extra = project 62 | .in(file("extra")) 63 | .enablePlugins(SbtPlugin) 64 | .settings(commonSettings) 65 | .settings( 66 | name := "sbt-softwaremill-extra", 67 | description := "Build configuration for SBT projects: extra", 68 | sbtPlugin := true, 69 | scriptedLaunchOpts += ("-Dplugin.version=" + version.value), 70 | libraryDependencies += "com.github.pathikrit" %% "better-files" % "3.9.2" 71 | ) 72 | .settings( 73 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4"), 74 | addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "5.1.0") 75 | ) 76 | 77 | lazy val browserTestJs = project 78 | .in(file("browser-test-js")) 79 | .enablePlugins(SbtPlugin) 80 | .settings(commonSettings) 81 | .settings( 82 | name := "sbt-softwaremill-browser-test-js", 83 | description := "Build configuration for SBT projects: browser test JS", 84 | sbtPlugin := true, 85 | scriptedLaunchOpts += ("-Dplugin.version=" + version.value) 86 | ) 87 | .settings( 88 | libraryDependencies += "org.scala-js" %% "scalajs-env-selenium" % "1.1.1", 89 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.19.0") 90 | ) 91 | -------------------------------------------------------------------------------- /common/src/main/scala/com/softwaremill/SbtSoftwareMillCommon.scala: -------------------------------------------------------------------------------- 1 | package com.softwaremill 2 | 3 | import sbt._ 4 | import Keys._ 5 | 6 | object SbtSoftwareMillCommon extends AutoPlugin { 7 | override def requires = plugins.JvmPlugin 8 | override def trigger = allRequirements 9 | 10 | lazy val commonSmlBuildSettings = Seq( 11 | libraryDependencies ++= { 12 | if (ScalaArtifacts.isScala3(scalaVersion.value)) Nil 13 | else Seq(compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.3" cross CrossVersion.full)) 14 | }, 15 | // silence transitive eviction warnings 16 | update / evictionWarningOptions := EvictionWarningOptions.empty, 17 | // use sbt-tpolecat, but without fatal warnings 18 | scalacOptions ~= (_.filterNot(Set("-Xfatal-warnings"))), 19 | // when using 2.13, fail on non-exhaustive matches 20 | scalacOptions := { 21 | val current = scalacOptions.value 22 | if (scalaVersion.value.startsWith("2.13")) 23 | current :+ "-Wconf:cat=other-match-analysis:error" 24 | else current 25 | }, 26 | // scala.js on scala3 needs an additional compiler option 27 | scalacOptions ++= { 28 | val isScalaJS = libraryDependencies.value.exists(_.organization == "org.scala-js") 29 | val isScala3 = ScalaArtifacts.isScala3(scalaVersion.value) 30 | if (isScalaJS && isScala3) Seq("-scalajs") else Seq() 31 | } 32 | ) 33 | 34 | object autoImport {} 35 | } 36 | -------------------------------------------------------------------------------- /common/src/sbt-test/sbt-softwaremill/simple/A.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | class A 4 | -------------------------------------------------------------------------------- /common/src/sbt-test/sbt-softwaremill/simple/build.sbt: -------------------------------------------------------------------------------- 1 | crossScalaVersions := Seq("2.11.12", "2.12.10", "2.13.2") 2 | 3 | scalacOptions += "-Xfatal-warnings" 4 | 5 | commonSmlBuildSettings 6 | -------------------------------------------------------------------------------- /common/src/sbt-test/sbt-softwaremill/simple/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % System.getProperty("plugin.version")) 2 | -------------------------------------------------------------------------------- /common/src/sbt-test/sbt-softwaremill/simple/test: -------------------------------------------------------------------------------- 1 | > + compile 2 | -------------------------------------------------------------------------------- /extra/src/main/scala/com/softwaremill/CheckUpdates.scala: -------------------------------------------------------------------------------- 1 | package com.softwaremill 2 | 3 | import better.files._ 4 | import sbt.State 5 | import java.io.{File => JFile} 6 | import scala.util.Try 7 | 8 | object CheckUpdates { 9 | 10 | var updatesTaskExecuted = false 11 | val tmpDir = new JFile(System.getProperty("java.io.tmpdir")).toScala 12 | 13 | def needsChecking(projectName: String): Boolean = { 14 | val file = tmpDir / (projectName + "_sml_last_update") 15 | val now = System.currentTimeMillis() 16 | val lastUpdate = Try(file.contentAsString.toLong).getOrElse(0L) 17 | if (now - lastUpdate > 12 * 3600 * 1000) { 18 | file.overwrite(now.toString) 19 | true 20 | } else false 21 | } 22 | 23 | lazy val startupTransition: String => State => State = { 24 | p: String => 25 | { s: State => 26 | if (!updatesTaskExecuted && needsChecking(p)) { 27 | updatesTaskExecuted = true 28 | "dependencyUpdates" :: s 29 | } else 30 | s 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /extra/src/main/scala/com/softwaremill/SbtSoftwareMillExtra.scala: -------------------------------------------------------------------------------- 1 | package com.softwaremill 2 | 3 | import sbt._ 4 | import Keys._ 5 | import net.vonbuchholtz.sbt.dependencycheck.DependencyCheckPlugin 6 | import java.net.URI 7 | 8 | object SbtSoftwareMillExtra extends AutoPlugin { 9 | override def requires = plugins.JvmPlugin 10 | override def trigger = allRequirements 11 | 12 | lazy val dependencyUpdatesSettings = Seq( 13 | // onLoad is scoped to Global because there's only one. 14 | onLoad in Global := { 15 | val old = (onLoad in Global).value 16 | // compose the new transition on top of the existing one 17 | // in case your plugins are using this hook. 18 | CheckUpdates.startupTransition(organization.value + "_" + name.value) compose old 19 | } 20 | ) 21 | 22 | lazy val dependencyCheckSettings = Seq( 23 | DependencyCheckPlugin.autoImport.dependencyCheckCveUrlModified := Some(URI.create("http://nvdmirror.sml.io/").toURL()), 24 | DependencyCheckPlugin.autoImport.dependencyCheckCveUrlBase := Some("http://nvdmirror.sml.io/"), 25 | DependencyCheckPlugin.autoImport.dependencyCheckAssemblyAnalyzerEnabled := Some(false), 26 | DependencyCheckPlugin.autoImport.dependencyCheckFormat := "All" 27 | ) 28 | 29 | lazy val extraSmlBuildSettings = 30 | dependencyUpdatesSettings ++ 31 | dependencyCheckSettings 32 | } 33 | -------------------------------------------------------------------------------- /project/Publish.scala: -------------------------------------------------------------------------------- 1 | ../publish/src/main/scala/com/softwaremill/Publish.scala -------------------------------------------------------------------------------- /project/UpdateVersionInDocs.scala: -------------------------------------------------------------------------------- 1 | ../publish/src/main/scala/com/softwaremill/UpdateVersionInDocs.scala -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.1 2 | -------------------------------------------------------------------------------- /project/build.sbt: -------------------------------------------------------------------------------- 1 | val sbtCiReleaseVersion = "1.11.1" 2 | val sbtScalafmtVersion = "2.5.4" 3 | 4 | lazy val root = project 5 | .in(file(".")) 6 | .enablePlugins(BuildInfoPlugin) 7 | .settings( 8 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % sbtCiReleaseVersion), 9 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % sbtScalafmtVersion), 10 | buildInfoKeys := Seq[BuildInfoKey]( 11 | "sbtCiReleaseVersion" -> sbtCiReleaseVersion, 12 | "sbtScalafmtVersion" -> sbtScalafmtVersion 13 | ), 14 | buildInfoPackage := "sbtsoftwaremill" 15 | ) 16 | -------------------------------------------------------------------------------- /project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1") 2 | -------------------------------------------------------------------------------- /publish/src/main/scala/com/softwaremill/Publish.scala: -------------------------------------------------------------------------------- 1 | package com.softwaremill 2 | 3 | import sbt.Keys._ 4 | import sbt._ 5 | import sbtdynver.DynVerPlugin.autoImport.dynverTagPrefix 6 | import java.net.URI 7 | 8 | trait Publish { 9 | lazy val ossPublishSettings = Seq( 10 | organizationHomepage := Some(url("https://softwaremill.com")), 11 | homepage := Some(url("http://softwaremill.com/open-source")), 12 | licenses := Seq( 13 | "Apache 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0") 14 | ), 15 | developers := List( 16 | Developer( 17 | id = "softwaremill", 18 | name = "SoftwareMill", 19 | email = "info@softwaremill.com", 20 | url = URI.create("https://softwaremill.com").toURL() 21 | ) 22 | ), 23 | updateDocs := UpdateVersionInDocs(sLog.value, organization.value, version.value), 24 | commands += releaseCommand 25 | ) 26 | 27 | lazy val noPublishSettings = 28 | Seq(publish := {}, publishLocal := {}, publishArtifact := false) 29 | 30 | val updateDocs = taskKey[Seq[File]]("Update docs during a release") 31 | 32 | private val releaseCommand = Command.command("release") { state => 33 | var s = state 34 | s.log.info("Current version:") 35 | s = processCommandOrThrow("version", s) 36 | val version = readNextVersion() 37 | 38 | val tagPrefix = Project.extract(s).getOpt(ThisBuild / dynverTagPrefix).getOrElse("v") 39 | val tag = tagPrefix + version 40 | 41 | s = processCommandOrThrow(s"""set ThisBuild/version := "$version"""", s) 42 | 43 | val (s2, files) = Project.extract(s).runTask(updateDocs, s) 44 | s = s2 45 | s = addFilesToGit(s, files) 46 | 47 | s.log.info(s"\nDocs updated, git status:\n") 48 | s = processCommandOrThrow(s"git status", s) 49 | s.log.info(s"\n") 50 | 51 | s = processCommandOrThrow(s"""git commit -m "Release $version"""", s) 52 | 53 | s.log.info(s"Tagging release as: $tag") 54 | s = processCommandOrThrow(s"git tag $tag", s) 55 | 56 | s = pushChanges(s) 57 | s 58 | } 59 | 60 | private def readNextVersion(): String = 61 | SimpleReader.readLine("Release version: ") match { 62 | case Some("") => sys.error("Aborting, empty version provided!") 63 | case None => sys.error("Aborting, no version provided!") 64 | case Some(input) => input 65 | } 66 | 67 | private def addFilesToGit(state: State, fs: Seq[File]): State = 68 | fs.foldLeft(state) { case (s, f) => 69 | processCommandOrThrow(s"git add ${f.getAbsolutePath}", s) 70 | } 71 | 72 | private def pushChanges(state: State): State = 73 | SimpleReader.readLine("Push changes? [y/n] ") match { 74 | case Some("y") => 75 | val state2 = processCommandOrThrow(s"git push", state) 76 | processCommandOrThrow(s"git push --tags", state2) 77 | case _ => sys.error("Aborting, not pushing changes"); state 78 | } 79 | 80 | private def processCommandOrThrow(command: String, state: State): State = 81 | Command.process(command, state, msg => throw new RuntimeException(msg)) 82 | } 83 | 84 | object Publish extends Publish 85 | -------------------------------------------------------------------------------- /publish/src/main/scala/com/softwaremill/SbtSoftwareMillPublish.scala: -------------------------------------------------------------------------------- 1 | package com.softwaremill 2 | 3 | import sbt._ 4 | 5 | object SbtSoftwareMillPublish extends AutoPlugin { 6 | override def requires = plugins.JvmPlugin 7 | override def trigger = allRequirements 8 | 9 | object autoImport {} 10 | } 11 | -------------------------------------------------------------------------------- /publish/src/main/scala/com/softwaremill/UpdateVersionInDocs.scala: -------------------------------------------------------------------------------- 1 | package com.softwaremill 2 | 3 | import sbt._ 4 | 5 | import java.util.regex.Pattern 6 | 7 | object UpdateVersionInDocs { 8 | val DefaultFilesForVersionUpdate: List[File] = 9 | List(file("README.md"), file("doc"), file("docs")) 10 | 11 | // based on https://github.com/EECOLOR/sbt-release-custom-steps/blob/master/src/main/scala/org/qirx/sbtrelease/UpdateVersionInFiles.scala 12 | def apply( 13 | log: Logger, 14 | organization: String, 15 | version: String, 16 | filesToUpdate: List[File] = DefaultFilesForVersionUpdate 17 | ): Seq[File] = { 18 | var allFiles: Seq[File] = Vector() 19 | filesToUpdate match { 20 | case Nil => 21 | log.info( 22 | "[UpdateVersionInDocs] Received empty set of files to update. Skipping updating version in docs" 23 | ) 24 | 25 | case nonEmptyFilesToUpdate => 26 | val regexStr = 27 | s""""$organization" %{1,2} "[\\w\\.-]+" % "([\\w\\.-]+)"""" 28 | val currentVersionPattern = regexStr.r 29 | 30 | def findCurrentVersion(oldFile: String): Option[String] = { 31 | currentVersionPattern.findFirstMatchIn(oldFile).map(_.group(1)) 32 | } 33 | 34 | def replaceInFile(f: File): Unit = { 35 | val oldFile = IO.read(f) 36 | findCurrentVersion(oldFile).foreach(currentVersion => { 37 | log.info( 38 | s"[UpdateVersionInDocs] Replacing $currentVersion with $version in ${f.name}" 39 | ) 40 | val newFile = oldFile.replaceAll( 41 | Pattern.quote(currentVersion), 42 | version 43 | ) 44 | IO.write(f, newFile) 45 | 46 | allFiles = allFiles :+ f 47 | }) 48 | } 49 | 50 | def replaceDocsInDirectory(d: File) { 51 | Option(d.listFiles()).foreach(_.foreach { f => 52 | if (f.isDirectory) { 53 | replaceDocsInDirectory(f) 54 | } else if (f.getName.endsWith(".rst") || f.getName.endsWith(".md")) { 55 | replaceInFile(f) 56 | } 57 | }) 58 | } 59 | 60 | nonEmptyFilesToUpdate.foreach { 61 | case file: File if !file.isDirectory && file.exists() => 62 | replaceInFile(file) 63 | case directory: File if directory.exists() => 64 | replaceDocsInDirectory(directory) 65 | case notExistingFile => 66 | log.warn( 67 | s"[UpdateVersionInDocs] ${notExistingFile.getPath} does not exist, skipping..." 68 | ) 69 | } 70 | } 71 | allFiles 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /scripts/decrypt_files_if_not_pr.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ "$TRAVIS_PULL_REQUEST" == "false" ]]; then 4 | openssl aes-256-cbc -K $encrypted_419986865484_key -iv $encrypted_419986865484_iv -in secrets.tar.enc -out secrets.tar -d 5 | tar xvf secrets.tar 6 | fi 7 | --------------------------------------------------------------------------------