diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1031e5c..be5a07c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,40 +1,166 @@ -name: CI +# This file was automatically generated by sbt-github-actions using the +# githubWorkflowGenerate task. You should add and commit this file to +# your git repository. It goes without saying that you shouldn't edit +# this file by hand! Instead, if you wish to make changes, you should +# change your sbt build configuration to revise the workflow description +# to meet your needs, then regenerate this file. + +name: Continuous Integration on: - push: pull_request: + branches: ['**', '!update/**', '!pr/**'] + push: + branches: ['**', '!update/**', '!pr/**'] + tags: [v*] + +env: + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + SONATYPE_CREDENTIAL_HOST: ${{ secrets.SONATYPE_CREDENTIAL_HOST }} + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + PGP_SECRET: ${{ secrets.PGP_SECRET }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: build: - runs-on: ubuntu-latest - + name: Build and Test strategy: matrix: - java-version: [8, 11, 17] + os: [ubuntu-latest] + scala: [2.12.18] + java: [temurin@8] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout current branch (full) + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Download Java (temurin@8) + id: download-java-temurin-8 + if: matrix.java == 'temurin@8' + uses: typelevel/download-java@v2 + with: + distribution: temurin + java-version: 8 + + - name: Setup Java (temurin@8) + if: matrix.java == 'temurin@8' + uses: actions/setup-java@v3 + with: + distribution: jdkfile + java-version: 8 + jdkFile: ${{ steps.download-java-temurin-8.outputs.jdkFile }} + + - name: Cache sbt + uses: actions/cache@v3 + with: + path: | + ~/.sbt + ~/.ivy2/cache + ~/.coursier/cache/v1 + ~/.cache/coursier/v1 + ~/AppData/Local/Coursier/Cache/v1 + ~/Library/Caches/Coursier/v1 + key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} + + - name: Check that workflows are up to date + run: sbt githubWorkflowCheck + + - name: Check headers and formatting + if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-latest' + run: sbt '++ ${{ matrix.scala }}' headerCheckAll scalafmtCheckAll 'project /' scalafmtSbtCheck + + - name: Test + run: sbt '++ ${{ matrix.scala }}' test + + - name: Check binary compatibility + if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-latest' + run: sbt '++ ${{ matrix.scala }}' mimaReportBinaryIssues + + - name: Generate API documentation + if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-latest' + run: sbt '++ ${{ matrix.scala }}' doc + - name: Make target directories + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') + run: mkdir -p plugin/target scalafix/rules/target target/sbt-tpolecat-scalafix-aggregate/target scalafix/output/target scalafix/tests/target target scalafix/input/target project/target + + - name: Compress target directories + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') + run: tar cf targets.tar plugin/target scalafix/rules/target target/sbt-tpolecat-scalafix-aggregate/target scalafix/output/target scalafix/tests/target target scalafix/input/target project/target + + - name: Upload target directories + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') + uses: actions/upload-artifact@v3 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }} + path: targets.tar + + publish: + name: Publish Artifacts + needs: [build] + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') + strategy: + matrix: + os: [ubuntu-latest] + java: [temurin@8] + runs-on: ${{ matrix.os }} steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Download Java - id: download-java - uses: typelevel/download-java@v1 - with: - distribution: temurin - java-version: ${{ matrix.java-version }} - - - name: Setup Java - uses: actions/setup-java@v2 - with: - distribution: jdkfile - java-version: ${{ matrix.java-version }} - jdkFile: ${{ steps.download-java.outputs.jdkFile }} - - - name: Scalafmt - run: sbt scalafmtCheckAll scalafmtSbtCheck - - - name: Run tests - run: | - sbt headerCheck - sbt test - sbt mimaReportBinaryIssues + - name: Checkout current branch (full) + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Download Java (temurin@8) + id: download-java-temurin-8 + if: matrix.java == 'temurin@8' + uses: typelevel/download-java@v2 + with: + distribution: temurin + java-version: 8 + + - name: Setup Java (temurin@8) + if: matrix.java == 'temurin@8' + uses: actions/setup-java@v3 + with: + distribution: jdkfile + java-version: 8 + jdkFile: ${{ steps.download-java-temurin-8.outputs.jdkFile }} + + - name: Cache sbt + uses: actions/cache@v3 + with: + path: | + ~/.sbt + ~/.ivy2/cache + ~/.coursier/cache/v1 + ~/.cache/coursier/v1 + ~/AppData/Local/Coursier/Cache/v1 + ~/Library/Caches/Coursier/v1 + key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} + + - name: Download target directories (2.12.18) + uses: actions/download-artifact@v3 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.18 + + - name: Inflate target directories (2.12.18) + run: | + tar xf targets.tar + rm targets.tar + + - name: Import signing key + if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == '' + run: echo $PGP_SECRET | base64 -di | gpg --import + + - name: Import signing key and strip passphrase + if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE != '' + run: | + echo "$PGP_SECRET" | base64 -di > /tmp/signing-key.gpg + echo "$PGP_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --import /tmp/signing-key.gpg + (echo "$PGP_PASSPHRASE"; echo; echo) | gpg --command-fd 0 --pinentry-mode loopback --change-passphrase $(gpg --list-secret-keys --with-colons 2> /dev/null | grep '^sec:' | cut --delimiter ':' --fields 5 | tail -n 1) + + - name: Publish + run: sbt tlCiRelease diff --git a/.github/workflows/clean.yml b/.github/workflows/clean.yml new file mode 100644 index 0000000..547aaa4 --- /dev/null +++ b/.github/workflows/clean.yml @@ -0,0 +1,59 @@ +# This file was automatically generated by sbt-github-actions using the +# githubWorkflowGenerate task. You should add and commit this file to +# your git repository. It goes without saying that you shouldn't edit +# this file by hand! Instead, if you wish to make changes, you should +# change your sbt build configuration to revise the workflow description +# to meet your needs, then regenerate this file. + +name: Clean + +on: push + +jobs: + delete-artifacts: + name: Delete Artifacts + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Delete artifacts + run: | + # Customize those three lines with your repository and credentials: + REPO=${GITHUB_API_URL}/repos/${{ github.repository }} + + # A shortcut to call GitHub API. + ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } + + # A temporary file which receives HTTP response headers. + TMPFILE=/tmp/tmp.$$ + + # An associative array, key: artifact name, value: number of artifacts of that name. + declare -A ARTCOUNT + + # Process all artifacts on this repository, loop on returned "pages". + URL=$REPO/actions/artifacts + while [[ -n "$URL" ]]; do + + # Get current page, get response headers in a temporary file. + JSON=$(ghapi --dump-header $TMPFILE "$URL") + + # Get URL of next page. Will be empty if we are at the last page. + URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') + rm -f $TMPFILE + + # Number of artifacts on this page: + COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) + + # Loop on all artifacts on this page. + for ((i=0; $i < $COUNT; i++)); do + + # Get name of artifact and count instances of this name. + name=$(jq <<<$JSON -r ".artifacts[$i].name?") + ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) + + id=$(jq <<<$JSON -r ".artifacts[$i].id?") + size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) + printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size + ghapi -X DELETE $REPO/actions/artifacts/$id + done + done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 8764e70..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Release - -on: - push: - branches: [master, main] - tags: ["*"] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Download Java - id: download-java - uses: typelevel/download-java@v1 - with: - distribution: temurin - java-version: 8 - - - name: Setup Java - uses: actions/setup-java@v2 - with: - distribution: jdkfile - java-version: 8 - jdkFile: ${{ steps.download-java.outputs.jdkFile }} - - - name: Set up GPG - run: | - mkdir ~/.gnupg && chmod 700 ~/.gnupg - echo use-agent >> ~/.gnupg/gpg.conf - echo pinentry-mode loopback >> ~/.gnupg/gpg.conf - echo allow-loopback-pinentry >> ~/.gnupg/gpg-agent.conf - chmod 600 ~/.gnupg/* - echo RELOADAGENT | gpg-connect-agent - echo $PGP_SECRET | base64 --decode | gpg --import --no-tty --batch --yes - env: - PGP_SECRET: ${{secrets.PGP_SECRET}} - - - name: Run tests - run: | - sbt headerCheck - sbt test - sbt mimaReportBinaryIssues - - - name: Publish release - run: | - export GPG_TTY=$(tty) - sbt ci-release - env: - CI_RELEASE: 'publishSigned' - CI_SNAPSHOT_RELEASE: 'publish' - PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} - PGP_SECRET: ${{ secrets.PGP_SECRET }} - SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} - SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} diff --git a/CHANGELOG.md b/CHANGELOG.md index a7e45c2..f88c14c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Early Semantic Versioning](https://docs.scala-lang.org/overviews/core/binary-compatibility-for-library-authors.html#recommended-versioning-scheme) in addition to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.0] + +### Changed + +* The plugin artifact is now published under the `org.typelevel` group ID. An artifact migration has been added to Scala Steward, so Scala Steward users should not have to update the artifact group ID manually. + +* The project now makes use of the [scalac-options](https://github.com/typelevel/scalac-options) library, so symbols relating to scalac options and their availability on differing Scala versions have been moved to the package `org.typelevel.scalacoptions`. + +* All other symbols provided by this plugin have been moved to the `org.typelevel.sbt.tpolecat` package. + +* A Scalafix migration is available, and can be applied using the `github:` scheme, e.g. + + ```bash + $ scalafix github:typelevel/sbt-tpolecat/v0_5?sha=v0.5.0 + ``` + + or via the `dependency:` scheme, e.g. + + ```bash + $ scalafix dependency:v0_5@org.typelevel::sbt-tpolecat-scalafix:0.5.0 + ``` + ## [0.4.4] ### Added @@ -146,7 +168,8 @@ Please do not use this release - GitHub accepted a tag push but not its correspo * The `validFor` function that was previously exported via this plugin's `autoImport`. -[Unreleased]: https://github.com/typelevel/sbt-tpolecat/compare/v0.4.4...HEAD +[Unreleased]: https://github.com/typelevel/sbt-tpolecat/compare/v0.5.0...HEAD +[0.5.0]: https://github.com/typelevel/sbt-tpolecat/compare/v0.4.4...v0.5.0 [0.4.4]: https://github.com/typelevel/sbt-tpolecat/compare/v0.4.3...v0.4.4 [0.4.3]: https://github.com/typelevel/sbt-tpolecat/compare/v0.4.2...v0.4.3 [0.4.2]: https://github.com/typelevel/sbt-tpolecat/compare/v0.4.1...v0.4.2 diff --git a/README.md b/README.md index d3fc2b6..93f0378 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ As of version 0.1.11, it also supports setting options for Scala 3.x. Add the following to your project's `project/plugins.sbt`: ```scala -addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.4.4") +addSbtPlugin("org.typelevel" % "sbt-tpolecat" % "0.4.4") ``` Once you are using this plugin we recommend that you don't manipulate the `scalacOptions` key directly. diff --git a/build.sbt b/build.sbt index 1229c5c..ef70a9b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,29 +1,16 @@ import com.typesafe.tools.mima.core._ +ThisBuild / tlBaseVersion := "0.4" + ThisBuild / organization := "org.typelevel" ThisBuild / organizationName := "Typelevel" ThisBuild / startYear := Some(2022) -ThisBuild / licenses += ("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0.html")) -ThisBuild / scmInfo := Some( - ScmInfo( - url("https://github.com/typelevel/sbt-tpolecat"), - "scm:git:git@github.com:typelevel/sbt-tpolecat.git" - ) -) -ThisBuild / developers := List( - Developer( - "DavidGregory084", - "David Gregory", - "davidgregory084@gmail.com", - url("https://github.com/DavidGregory084") - ) -) +ThisBuild / licenses := Seq(License.Apache2) -ThisBuild / sonatypeCredentialHost := "s01.oss.sonatype.org" -ThisBuild / sonatypeRepository := "https://s01.oss.sonatype.org/service/local" - -ThisBuild / homepage := scmInfo.value.map(_.browseUrl) +ThisBuild / developers ++= List( + tlGitHubDev("DavidGregory084", "David Gregory") +) ThisBuild / semanticdbEnabled := true ThisBuild / semanticdbVersion := scalafixSemanticdb.revision @@ -32,21 +19,10 @@ ThisBuild / versionScheme := Some(VersionScheme.EarlySemVer) lazy val `sbt-tpolecat` = project .in(file(".")) + .enablePlugins(NoPublishPlugin) .aggregate( `sbt-tpolecat-plugin`, - `sbt-tpolecat-scalafix`.rules, - `sbt-tpolecat-scalafix`.input, - `sbt-tpolecat-scalafix`.tests - /* TODO: change individual Scalafix project dependencies to `sbt-tpolecat-scalafix`.all when the package rename is in main; - * the Scalafix `output` project will not compile until the package renaming is done. - */ - ) - .settings( - publish := {}, - publishLocal := {}, - publishArtifact := false, - publish / skip := true, - mimaReportBinaryIssues := {} + `sbt-tpolecat-scalafix`.all ) lazy val `sbt-tpolecat-plugin` = project @@ -88,4 +64,6 @@ lazy val `sbt-tpolecat-scalafix` = scalafixProject("sbt-tpolecat") ) ) .inputSettings(addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.4.0")) + .inputConfigure(_.enablePlugins(SbtPlugin)) .outputConfigure(_.dependsOn(`sbt-tpolecat-plugin`)) + .outputConfigure(_.enablePlugins(SbtPlugin)) diff --git a/project/plugins.sbt b/project/plugins.sbt index c825035..816d5f2 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,11 +1,3 @@ -addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.10.0") - -addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") - -addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") - -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") - -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.11.0") - -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.3") +addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.4.22") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.11.0") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") diff --git a/scalafix/input/src/main/scala/org/typelevel/fix/v0_5Tests.scala b/scalafix/input/src/main/scala/org/typelevel/fix/v0_5Tests.scala index 8841577..783cf32 100644 --- a/scalafix/input/src/main/scala/org/typelevel/fix/v0_5Tests.scala +++ b/scalafix/input/src/main/scala/org/typelevel/fix/v0_5Tests.scala @@ -1,11 +1,15 @@ /* rule = v0_5 */ +// format: off import io.github.davidgregory084.TpolecatPlugin import io.github.davidgregory084.{ScalaVersion => Version} import io.github.davidgregory084.{DevMode => Dev} +import io.github.davidgregory084.TpolecatPlugin.autoImport._ +import io.github.davidgregory084.TpolecatPlugin.autoImport.{ tpolecatCiModeEnvVar, scalacOptionsFor } import io.github.davidgregory084._ import _root_.io.github.davidgregory084.ScalacOption import _root_.io.github.davidgregory084._ +// format: on object Tests diff --git a/scalafix/output/src/main/scala/org/typelevel/fix/v0_5Tests.scala b/scalafix/output/src/main/scala/org/typelevel/fix/v0_5Tests.scala index 54a78f1..0bdb2d9 100644 --- a/scalafix/output/src/main/scala/org/typelevel/fix/v0_5Tests.scala +++ b/scalafix/output/src/main/scala/org/typelevel/fix/v0_5Tests.scala @@ -1,4 +1,7 @@ +// format: off import org.typelevel.sbt.tpolecat.{ DevMode => Dev, TpolecatPlugin, _ } +import org.typelevel.sbt.tpolecat.TpolecatPlugin.autoImport.{ scalacOptionsFor, tpolecatCiModeEnvVar, _ } import org.typelevel.scalacoptions.{ ScalaVersion => Version, ScalacOption, _ } +// format: on object Tests diff --git a/scalafix/rules/src/main/scala/org/typelevel/fix/v0_5.scala b/scalafix/rules/src/main/scala/fix/v0_5.scala similarity index 78% rename from scalafix/rules/src/main/scala/org/typelevel/fix/v0_5.scala rename to scalafix/rules/src/main/scala/fix/v0_5.scala index 2e2b048..eeefc05 100644 --- a/scalafix/rules/src/main/scala/org/typelevel/fix/v0_5.scala +++ b/scalafix/rules/src/main/scala/fix/v0_5.scala @@ -40,6 +40,10 @@ class v0_5 extends SemanticRule("v0_5") { val ScalacOptionsSym = JavaMajorVersionSym + ScalacOptionSym + ScalaVersionSym + // autoImport contents + val TpolecatPluginAutoImportSym = + SymbolMatcher.exact("io/github/davidgregory084/TpolecatPlugin.autoImport.") + private def makeSelector(packages: String*): Term.Ref = packages.tail.foldLeft(Term.Name(packages.head): Term.Ref) { case (selector, pkg) => Term.Select(selector, Term.Name(pkg)) @@ -50,6 +54,14 @@ class v0_5 extends SemanticRule("v0_5") { Importer(makeSelector("org", "typelevel", "sbt", "tpolecat"), List(importee)) ) + private def makeAutoImportObjectImport(importee: Importee) = + Patch.addGlobalImport( + Importer( + makeSelector("org", "typelevel", "sbt", "tpolecat", "TpolecatPlugin", "autoImport"), + List(importee) + ) + ) + private def makeScalacOptionsImport(importee: Importee) = Patch.addGlobalImport( Importer(makeSelector("org", "typelevel", "scalacoptions"), List(importee)) @@ -72,5 +84,14 @@ class v0_5 extends SemanticRule("v0_5") { makeScalacOptionsImport(importee) + Patch.removeImportee(importee) }.asPatch + case importer @ Importer(ref, importees) if TpolecatPluginAutoImportSym.matches(ref) => + importees.collect { + case importee @ Importee.Name(name) => + makeAutoImportObjectImport(importee) + Patch.removeImportee(importee) + case rename @ Importee.Rename(name, _) => + makeAutoImportObjectImport(rename) + Patch.removeImportee(rename) + case importee @ Importee.Wildcard() => + makeAutoImportObjectImport(importee) + Patch.removeImportee(importee) + }.asPatch }.asPatch } diff --git a/scalafix/tests/src/test/scala/org/typelevel/fix/RuleSuite.scala b/scalafix/tests/src/test/scala/org/typelevel/fix/RuleSuite.scala index 2d92849..5231846 100644 --- a/scalafix/tests/src/test/scala/org/typelevel/fix/RuleSuite.scala +++ b/scalafix/tests/src/test/scala/org/typelevel/fix/RuleSuite.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.typelevel.fix import scalafix.testkit._