From 33ff18a38710209fee66d04010c25d6de1f4246c Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Fri, 14 Jun 2024 14:58:03 -0500 Subject: [PATCH] Update build definition to publish artifacts linked against multiple versions of Twitter dependencies --- .github/workflows/ci.yml | 158 +++++++++--------- .mergify.yml | 207 ++++++++++++++++++++++++ build.sbt | 90 ++++------- project/BaseVersion.scala | 10 -- project/FinaglePlugin.scala | 306 +++++++++++++++++++++++++++++++---- project/TwitterVersion.scala | 29 ++++ project/Version.scala | 78 +++++++++ project/build.properties | 2 +- project/plugins.sbt | 9 +- 9 files changed, 703 insertions(+), 186 deletions(-) create mode 100644 .mergify.yml delete mode 100644 project/BaseVersion.scala create mode 100644 project/TwitterVersion.scala create mode 100644 project/Version.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f93acaa..9960304 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,75 +15,61 @@ on: 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 }} + +concurrency: + group: ${{ github.workflow }} @ ${{ github.ref }} + cancel-in-progress: true + jobs: build: name: Build and Test strategy: matrix: os: [ubuntu-latest] - scala: [2.12.17, 2.13.10] + scala: [per-project-matrix] java: [temurin@8] runs-on: ${{ matrix.os }} + timeout-minutes: 60 steps: - name: Checkout current branch (full) - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Download Java (temurin@8) - id: download-java-temurin-8 - if: matrix.java == 'temurin@8' - uses: typelevel/download-java@v1 - with: - distribution: temurin - java-version: 8 - - name: Setup Java (temurin@8) + id: setup-java-temurin-8 if: matrix.java == 'temurin@8' - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - distribution: jdkfile + distribution: temurin java-version: 8 - jdkFile: ${{ steps.download-java-temurin-8.outputs.jdkFile }} + cache: sbt - - name: Cache sbt - uses: actions/cache@v2 - 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: sbt update + if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' + run: sbt +update - name: Check that workflows are up to date run: sbt githubWorkflowCheck - - run: sbt '++ ${{ matrix.scala }}' clean coverage test mimaReportBinaryIssues scalastyle scalafmtCheckAll scalafmtSbtCheck unidoc coverageReport + - run: sbt clean coverage test mimaReportBinaryIssues scalastyle scalafmtCheckAll scalafmtSbtCheck unidoc coverageReport - name: Code coverage analysis uses: codecov/codecov-action@v1 - name: Make target directories - if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p util/target target .rootFinagle/target scalafix/input/target effect3/target finagle/target .finagle-core/target scalafix/rules/target scalafix/tests/target benchmark/target project/target + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v')) + run: mkdir -p finagle/target/twitter-22.7.0-jvm-2.12 util/target/twitter-22.7.0-jvm-2.13 util/target/twitter-22.7.0-jvm-2.12 effect3/target/twitter-22.4.0-jvm-2.12 .catbird-finagle-latest/target/twitter-22.12.0-jvm-2.12 finagle/target/twitter-22.4.0-jvm-2.13 effect3/target/twitter-22.12.0-jvm-2.13 util/target/twitter-22.12.0-jvm-2.12 util/target/twitter-22.12.0-jvm-2.13 util/target/twitter-22.4.0-jvm-2.13 effect3/target/twitter-22.7.0-jvm-2.13 util/target/twitter-22.4.0-jvm-2.12 finagle/target/twitter-22.12.0-jvm-2.12 effect3/target/twitter-22.7.0-jvm-2.12 .catbird-effect3-latest/target/twitter-22.12.0-jvm-2.13 .catbird-util-latest/target/twitter-22.12.0-jvm-2.13 effect3/target/twitter-22.4.0-jvm-2.13 effect3/target/twitter-22.12.0-jvm-2.12 finagle/target/twitter-22.12.0-jvm-2.13 finagle/target/twitter-22.4.0-jvm-2.12 .catbird-util-latest/target/twitter-22.12.0-jvm-2.12 .catbird-finagle-latest/target/twitter-22.12.0-jvm-2.13 finagle/target/twitter-22.7.0-jvm-2.13 .catbird-effect3-latest/target/twitter-22.12.0-jvm-2.12 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 util/target target .rootFinagle/target scalafix/input/target effect3/target finagle/target .finagle-core/target scalafix/rules/target scalafix/tests/target benchmark/target project/target + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v')) + run: tar cf targets.tar finagle/target/twitter-22.7.0-jvm-2.12 util/target/twitter-22.7.0-jvm-2.13 util/target/twitter-22.7.0-jvm-2.12 effect3/target/twitter-22.4.0-jvm-2.12 .catbird-finagle-latest/target/twitter-22.12.0-jvm-2.12 finagle/target/twitter-22.4.0-jvm-2.13 effect3/target/twitter-22.12.0-jvm-2.13 util/target/twitter-22.12.0-jvm-2.12 util/target/twitter-22.12.0-jvm-2.13 util/target/twitter-22.4.0-jvm-2.13 effect3/target/twitter-22.7.0-jvm-2.13 util/target/twitter-22.4.0-jvm-2.12 finagle/target/twitter-22.12.0-jvm-2.12 effect3/target/twitter-22.7.0-jvm-2.12 .catbird-effect3-latest/target/twitter-22.12.0-jvm-2.13 .catbird-util-latest/target/twitter-22.12.0-jvm-2.13 effect3/target/twitter-22.4.0-jvm-2.13 effect3/target/twitter-22.12.0-jvm-2.12 finagle/target/twitter-22.12.0-jvm-2.13 finagle/target/twitter-22.4.0-jvm-2.12 .catbird-util-latest/target/twitter-22.12.0-jvm-2.12 .catbird-finagle-latest/target/twitter-22.12.0-jvm-2.13 finagle/target/twitter-22.7.0-jvm-2.13 .catbird-effect3-latest/target/twitter-22.12.0-jvm-2.12 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@v2 + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v')) + uses: actions/upload-artifact@v4 with: name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }} path: targets.tar @@ -91,77 +77,93 @@ jobs: publish: name: Publish Artifacts needs: [build] - if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v')) strategy: matrix: os: [ubuntu-latest] - scala: [2.13.10] java: [temurin@8] runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Download Java (temurin@8) - id: download-java-temurin-8 + - name: Setup Java (temurin@8) + id: setup-java-temurin-8 if: matrix.java == 'temurin@8' - uses: typelevel/download-java@v1 + uses: actions/setup-java@v4 with: distribution: temurin java-version: 8 + cache: sbt - - name: Setup Java (temurin@8) - if: matrix.java == 'temurin@8' - uses: actions/setup-java@v2 - with: - distribution: jdkfile - java-version: 8 - jdkFile: ${{ steps.download-java-temurin-8.outputs.jdkFile }} + - name: sbt update + if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' + run: sbt +update - - name: Cache sbt - uses: actions/cache@v2 - 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.17) - uses: actions/download-artifact@v2 + - name: Download target directories (per-project-matrix) + uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.17 + name: target-${{ matrix.os }}-${{ matrix.java }}-per-project-matrix - - name: Inflate target directories (2.12.17) - run: | - tar xf targets.tar - rm targets.tar - - - name: Download target directories (2.13.10) - uses: actions/download-artifact@v2 - with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.10 - - - name: Inflate target directories (2.13.10) + - name: Inflate target directories (per-project-matrix) 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 + env: + PGP_SECRET: ${{ secrets.PGP_SECRET }} + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} + run: echo $PGP_SECRET | base64 -d -i - | gpg --import - name: Import signing key and strip passphrase if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE != '' + env: + PGP_SECRET: ${{ secrets.PGP_SECRET }} + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} run: | - echo "$PGP_SECRET" | base64 -di > /tmp/signing-key.gpg + echo "$PGP_SECRET" | base64 -d -i - > /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 '++ ${{ matrix.scala }}' tlRelease + env: + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + SONATYPE_CREDENTIAL_HOST: ${{ secrets.SONATYPE_CREDENTIAL_HOST }} + run: sbt tlCiRelease + + dependency-submission: + name: Submit Dependencies + if: github.event_name != 'pull_request' + strategy: + matrix: + os: [ubuntu-latest] + java: [temurin@8] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout current branch (full) + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Java (temurin@8) + id: setup-java-temurin-8 + if: matrix.java == 'temurin@8' + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 8 + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' + run: sbt +update + + - name: Submit Dependencies + uses: scalacenter/sbt-dependency-submission@v2 + with: + configs-ignore: test scala-tool scala-doc-tool test-internal diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 0000000..07a9af8 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,207 @@ +# This file was automatically generated by sbt-typelevel-mergify using the +# mergifyGenerate 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 mergify configuration +# to meet your needs, then regenerate this file. + +pull_request_rules: +- name: merge scala-steward's PRs + conditions: + - author=scala-steward + - body~=labels:.*early-semver-patch + - status-success=Build and Test (ubuntu-latest, per-project-matrix, temurin@8) + actions: + merge: {} +- name: Label catbird-effect3-latest-twitter-22_12_0 PRs + conditions: + - files~=^.sbt/matrix/catbird-effect3-latest-twitter-22_12_0/ + actions: + label: + add: + - catbird-effect3-latest-twitter-22_12_0 + remove: [] +- name: Label catbird-effect3-latest-twitter-22_12_02_12 PRs + conditions: + - files~=^.sbt/matrix/catbird-effect3-latest-twitter-22_12_02_12/ + actions: + label: + add: + - catbird-effect3-latest-twitter-22_12_02_12 + remove: [] +- name: Label catbird-effect3-twitter-22_12_0 PRs + conditions: + - files~=^.sbt/matrix/catbird-effect3-twitter-22_12_0/ + actions: + label: + add: + - catbird-effect3-twitter-22_12_0 + remove: [] +- name: Label catbird-effect3-twitter-22_12_02_12 PRs + conditions: + - files~=^.sbt/matrix/catbird-effect3-twitter-22_12_02_12/ + actions: + label: + add: + - catbird-effect3-twitter-22_12_02_12 + remove: [] +- name: Label catbird-effect3-twitter-22_4_0 PRs + conditions: + - files~=^.sbt/matrix/catbird-effect3-twitter-22_4_0/ + actions: + label: + add: + - catbird-effect3-twitter-22_4_0 + remove: [] +- name: Label catbird-effect3-twitter-22_4_02_12 PRs + conditions: + - files~=^.sbt/matrix/catbird-effect3-twitter-22_4_02_12/ + actions: + label: + add: + - catbird-effect3-twitter-22_4_02_12 + remove: [] +- name: Label catbird-effect3-twitter-22_7_0 PRs + conditions: + - files~=^.sbt/matrix/catbird-effect3-twitter-22_7_0/ + actions: + label: + add: + - catbird-effect3-twitter-22_7_0 + remove: [] +- name: Label catbird-effect3-twitter-22_7_02_12 PRs + conditions: + - files~=^.sbt/matrix/catbird-effect3-twitter-22_7_02_12/ + actions: + label: + add: + - catbird-effect3-twitter-22_7_02_12 + remove: [] +- name: Label catbird-finagle-latest-twitter-22_12_0 PRs + conditions: + - files~=^.sbt/matrix/catbird-finagle-latest-twitter-22_12_0/ + actions: + label: + add: + - catbird-finagle-latest-twitter-22_12_0 + remove: [] +- name: Label catbird-finagle-latest-twitter-22_12_02_12 PRs + conditions: + - files~=^.sbt/matrix/catbird-finagle-latest-twitter-22_12_02_12/ + actions: + label: + add: + - catbird-finagle-latest-twitter-22_12_02_12 + remove: [] +- name: Label catbird-finagle-twitter-22_12_0 PRs + conditions: + - files~=^.sbt/matrix/catbird-finagle-twitter-22_12_0/ + actions: + label: + add: + - catbird-finagle-twitter-22_12_0 + remove: [] +- name: Label catbird-finagle-twitter-22_12_02_12 PRs + conditions: + - files~=^.sbt/matrix/catbird-finagle-twitter-22_12_02_12/ + actions: + label: + add: + - catbird-finagle-twitter-22_12_02_12 + remove: [] +- name: Label catbird-finagle-twitter-22_4_0 PRs + conditions: + - files~=^.sbt/matrix/catbird-finagle-twitter-22_4_0/ + actions: + label: + add: + - catbird-finagle-twitter-22_4_0 + remove: [] +- name: Label catbird-finagle-twitter-22_4_02_12 PRs + conditions: + - files~=^.sbt/matrix/catbird-finagle-twitter-22_4_02_12/ + actions: + label: + add: + - catbird-finagle-twitter-22_4_02_12 + remove: [] +- name: Label catbird-finagle-twitter-22_7_0 PRs + conditions: + - files~=^.sbt/matrix/catbird-finagle-twitter-22_7_0/ + actions: + label: + add: + - catbird-finagle-twitter-22_7_0 + remove: [] +- name: Label catbird-finagle-twitter-22_7_02_12 PRs + conditions: + - files~=^.sbt/matrix/catbird-finagle-twitter-22_7_02_12/ + actions: + label: + add: + - catbird-finagle-twitter-22_7_02_12 + remove: [] +- name: Label catbird-util-latest-twitter-22_12_0 PRs + conditions: + - files~=^.sbt/matrix/catbird-util-latest-twitter-22_12_0/ + actions: + label: + add: + - catbird-util-latest-twitter-22_12_0 + remove: [] +- name: Label catbird-util-latest-twitter-22_12_02_12 PRs + conditions: + - files~=^.sbt/matrix/catbird-util-latest-twitter-22_12_02_12/ + actions: + label: + add: + - catbird-util-latest-twitter-22_12_02_12 + remove: [] +- name: Label catbird-util-twitter-22_12_0 PRs + conditions: + - files~=^.sbt/matrix/catbird-util-twitter-22_12_0/ + actions: + label: + add: + - catbird-util-twitter-22_12_0 + remove: [] +- name: Label catbird-util-twitter-22_12_02_12 PRs + conditions: + - files~=^.sbt/matrix/catbird-util-twitter-22_12_02_12/ + actions: + label: + add: + - catbird-util-twitter-22_12_02_12 + remove: [] +- name: Label catbird-util-twitter-22_4_0 PRs + conditions: + - files~=^.sbt/matrix/catbird-util-twitter-22_4_0/ + actions: + label: + add: + - catbird-util-twitter-22_4_0 + remove: [] +- name: Label catbird-util-twitter-22_4_02_12 PRs + conditions: + - files~=^.sbt/matrix/catbird-util-twitter-22_4_02_12/ + actions: + label: + add: + - catbird-util-twitter-22_4_02_12 + remove: [] +- name: Label catbird-util-twitter-22_7_0 PRs + conditions: + - files~=^.sbt/matrix/catbird-util-twitter-22_7_0/ + actions: + label: + add: + - catbird-util-twitter-22_7_0 + remove: [] +- name: Label catbird-util-twitter-22_7_02_12 PRs + conditions: + - files~=^.sbt/matrix/catbird-util-twitter-22_7_02_12/ + actions: + label: + add: + - catbird-util-twitter-22_7_02_12 + remove: [] diff --git a/build.sbt b/build.sbt index 760a470..e525e2a 100644 --- a/build.sbt +++ b/build.sbt @@ -1,46 +1,32 @@ -val catsVersion = "2.9.0" - -ThisBuild / tlBaseVersion := BaseVersion(finagleVersion) -ThisBuild / tlVersionIntroduced := // test bincompat starting from the beginning of this series - List("2.12", "2.13").map(_ -> s"${tlBaseVersion.value}.0").toMap -ThisBuild / tlCiHeaderCheck := false - -// For the transition period, we publish artifacts for both cats-effect 2.x and 3.x -val catsEffectVersion = "2.5.5" -val catsEffect3Version = "3.4.3" - -ThisBuild / crossScalaVersions := Seq("2.12.17", "2.13.10") - -ThisBuild / libraryDependencySchemes ++= Seq( - // scoverage depends on scala-xml 1, but discipline-scalatest transitively pulls in scala-xml 2 - // this is normally discouraged but was recommended by one of the scoverage maintainers in the Typelevel Discord - "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always -) +lazy val `catbird-root` = (project in file(".")) + .aggregate(allProjects.map(_.project) *) + .settings( + publish / skip := true, + publishArtifact := false, + ScalaUnidoc / unidoc / unidocProjectFilter := { + val excluded = benchmark.componentProjects ++ effect3.componentProjects + // `scalafix-input`, + // `scalafix-output`, + // `scalafix-tests` + inAnyProject -- inProjects(excluded.map(_.project) *) + }, +// TODO addMappingsToSiteDir(ScalaUnidoc / packageDoc / mappings, docMappingsApiDir), + git.remoteRepo := "git@github.com:typelevel/catbird.git" + ) + .settings( + console / initialCommands := + """ + |import com.twitter.finagle._ + |import com.twitter.util._ + |import org.typelevel.catbird.finagle._ + |import org.typelevel.catbird.util._ + """.stripMargin + ) + .enablePlugins(FinaglePlugin) -Global / onChangedBuildSource := ReloadOnSourceChanges -val docMappingsApiDir = settingKey[String]("Subdirectory in site target directory for API docs") -lazy val baseSettings = Seq( - // Finagle releases monthly using a {year}.{month}.{patch} version scheme. - // The combination of year and month is effectively a major version, because - // each monthly release often contains binary-incompatible changes. - // This means we should release at least monthly as well, when Finagle does, - // but in between those monthly releases, maintain binary compatibility. - // This is effectively PVP style versioning. - // We set this at the project-level instead of ThisBuild to circumvent sbt-typelevel checks. - versionScheme := Some("pvp"), - libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % catsVersion, - "org.scalacheck" %% "scalacheck" % "1.17.0" % Test, - "org.scalatest" %% "scalatest" % "3.2.14" % Test, - "org.typelevel" %% "cats-laws" % catsVersion % Test, - "org.typelevel" %% "discipline-core" % "1.5.1" % Test, - "org.typelevel" %% "discipline-scalatest" % "2.2.0" % Test - ), - resolvers ++= Resolver.sonatypeOssRepos("snapshots"), - docMappingsApiDir := "api", - autoAPIMappings := true, +/* // disable automatic copyright header creation otherwise added via sbt-typelevel headerMappings := Map.empty @@ -125,29 +111,6 @@ lazy val benchmark = project .enablePlugins(JmhPlugin) .dependsOn(util) -ThisBuild / apiURL := Some(url("https://typelevel.org/catbird/api/")) -ThisBuild / developers += Developer("travisbrown", "Travis Brown", "", url("https://twitter.com/travisbrown")) - -ThisBuild / githubWorkflowBuild := Seq( - WorkflowStep.Sbt( - List( - "clean", - "coverage", - "test", - "mimaReportBinaryIssues", - "scalastyle", - "scalafmtCheckAll", - "scalafmtSbtCheck", - "unidoc", - "coverageReport" - ) - ), - WorkflowStep.Use( - UseRef.Public("codecov", "codecov-action", "v1"), - name = Some("Code coverage analysis") - ) -) - lazy val `scalafix-rules` = (project in file("scalafix/rules")) .settings(allSettings) .settings( @@ -194,3 +157,4 @@ lazy val `scalafix-tests` = (project in file("scalafix/tests")) ScalafixTestkitPlugin, NoPublishPlugin ) +*/ diff --git a/project/BaseVersion.scala b/project/BaseVersion.scala deleted file mode 100644 index 4a1127f..0000000 --- a/project/BaseVersion.scala +++ /dev/null @@ -1,10 +0,0 @@ -object BaseVersion { - private val versionRegex = """^(\d+)\.(\d+).*$""".r - - def apply(s: String): String = - s match { - case versionRegex(year, month) => s"$year.$month" - case _ => - throw new IllegalArgumentException(s"base version must start with {year}.{month}, but got $s") - } -} diff --git a/project/FinaglePlugin.scala b/project/FinaglePlugin.scala index 950c112..3e45a06 100644 --- a/project/FinaglePlugin.scala +++ b/project/FinaglePlugin.scala @@ -1,48 +1,292 @@ -import sbt._, Keys._ - -import com.typesafe.tools.mima.plugin.MimaKeys._ +import sbt.{Def, *} +import _root_.scalafix.sbt.ScalafixTestkitPlugin.autoImport.* +import _root_.scalafix.sbt.{ScalafixPlugin, ScalafixTestkitPlugin} +import com.typesafe.tools.mima.plugin.MimaPlugin +import com.typesafe.tools.mima.plugin.MimaPlugin.autoImport.* +import org.scalajs.jsenv.JSEnv +import org.scalajs.jsenv.nodejs.NodeJSEnv +import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.* +import org.typelevel.sbt.TypelevelMimaPlugin.autoImport.* +import org.typelevel.sbt.TypelevelSettingsPlugin +import org.typelevel.sbt.TypelevelSettingsPlugin.autoImport.* +import org.typelevel.sbt.TypelevelSonatypeCiReleasePlugin.autoImport.* +import org.typelevel.sbt.TypelevelSonatypePlugin.autoImport.* +import org.typelevel.sbt.TypelevelVersioningPlugin.autoImport.* +import org.typelevel.sbt.gha.GenerativePlugin.autoImport.* +import org.typelevel.sbt.gha.GitHubActionsPlugin.autoImport.* +import org.typelevel.sbt.mergify.MergifyPlugin +import org.typelevel.sbt.mergify.MergifyPlugin.autoImport.* +import sbt.Keys.* +import sbt.internal.ProjectMatrix +import sbt.librarymanagement.DependencyBuilders.OrganizationArtifactName +import sbtprojectmatrix.ProjectMatrixPlugin +import sbtprojectmatrix.ProjectMatrixPlugin.autoImport.* +import scalafix.sbt.ScalafixPlugin.autoImport.* +import com.typesafe.tools.mima.plugin.MimaKeys.* import com.typesafe.tools.mima.plugin.SbtMima import org.typelevel.sbt.NoPublishPlugin +import org.typelevel.sbt.TypelevelCiPlugin.autoImport.* +import pl.project13.scala.sbt.JmhPlugin +import sbt.nio.Keys.{ReloadOnSourceChanges, onChangedBuildSource} object FinaglePlugin extends AutoPlugin { - override def trigger = allRequirements + override def trigger = noTrigger + + override def requires: Plugins = + ProjectMatrixPlugin && ScalafixPlugin && MimaPlugin && MergifyPlugin && TypelevelSettingsPlugin && + WarnNonUnitStatements && JmhPlugin object autoImport { - lazy val finagleVersion = versions.head + val docMappingsApiDir = settingKey[String]("Subdirectory in site target directory for API docs") + + lazy val allProjects: Seq[Project] = + List( + util, + effect3, + finagle, + ).flatMap { pm => + if (Set("scalafix-input", "scalafix-output", "scalafix-input-dependency", "scalafix-output-dependency", "scalafix-tests").contains(pm.id)) List(pm) + else List(pm, latestVersionAlias(pm)) + } + .flatMap(_.componentProjects) + + lazy val benchmark = FinaglePlugin.benchmark + lazy val effect3 = FinaglePlugin.effect3 } - /* When a new Finagle version is released, add it to the beginning of - * this list. If `rootFinagle/mimaReportBinaryIssues` shows no issues, - * we have a chain of binary-compatible releases and the new list can - * be left as it is (i.e. with the new version at the head). If issues - * are found, then remove everything other than the new version, - * because the newest release is not binary compatible with the older - * versions it was checked against. - */ - val versions = Seq("22.12.0") - - lazy val modules = Seq( - "com.twitter" %% "finagle-core" % versions.head + import autoImport.* + + private val currentTwitterVersion = Version("22.12.0").get + + // When a new version is released, move what was previously the current version into the list of old versions. + // This plugin will automatically release a new suffixed artifact that can be used by users with bincompat issues. + // Don't forget to regenerate the GitHub Actions workflow by running the `githubWorkflowGenerate` sbt task. + private val oldVersions = List( + "22.7.0", + "22.4.0", ) + .flatMap(Version(_)) + + private val supportedVersions = (currentTwitterVersion :: oldVersions).sorted.reverse + + private val SCALA_2_13: String = "2.13.10" + private val SCALA_2_12 = "2.12.17" + private val Scala2Versions: Seq[String] = Seq(SCALA_2_13, SCALA_2_12) + private val catsEffect3Version = "3.4.3" + private val catsVersion = "2.9.0" + + private def projectMatrixForSupportedTwitterVersions(id: String, + path: String) + (s: Version => List[Setting[?]]): ProjectMatrix = + supportedVersions.foldLeft(ProjectMatrix(id, file(path)))(addTwitterCustomRow(s)) - private lazy val subprojects = modules.map { module => - Project(module.name, file(s".${module.name}")) - .enablePlugins(NoPublishPlugin) - .settings( - libraryDependencies += module, - mimaCurrentClassfiles := { - (Compile / dependencyClasspath).value.seq.map(_.data).find(_.getName.startsWith(module.name)).get + private def addTwitterCustomRow(s: Version => List[Setting[?]]) + (p: ProjectMatrix, v: Version): ProjectMatrix = + p.customRow( + scalaVersions = Scala2Versions, + axisValues = List(TwitterVersion(v), VirtualAxis.jvm), + _.settings( + s(v), + // Finagle releases monthly using a {year}.{month}.{patch} version scheme. + // The combination of year and month is effectively a major version, because + // each monthly release often contains binary-incompatible changes. + // This means we should release at least monthly as well, when Finagle does, + // but in between those monthly releases, maintain binary compatibility. + // This is effectively PVP style versioning. + // We set this at the project-level instead of ThisBuild to circumvent sbt-typelevel checks. + // TODO is this still desired? + versionScheme := Some("pvp"), + ) + ) + + private def latestVersionAlias(p: ProjectMatrix): ProjectMatrix = + ProjectMatrix(s"${p.id}-latest", file(s".${p.id}-latest")) + .customRow( + scalaVersions = Scala2Versions, + axisValues = List(TwitterVersion(currentTwitterVersion), VirtualAxis.jvm), + _.settings( + moduleName := p.id, + tlVersionIntroduced := Map("2.12" -> "1.1.0", "2.13" -> "1.1.0"), + ) + ) + .dependsOn(p) + + lazy val util = + projectMatrixForSupportedTwitterVersions("catbird-util", "util") { v => + List( + moduleName := name.value + s"-$v", + libraryDependencies ++= { + Seq( + "com.twitter" %% "util-core" % v, + ) // TODO ++ (if (scalaVersion.value.startsWith("2")) scala2CompilerPlugins else Nil) }, - mimaPreviousArtifacts := versions.tail.map { v => - module.withRevision(v) - }.toSet + Test / scalacOptions ~= { + _.filterNot(Set("-Yno-imports", "-Yno-predef")) + } ) - } + } + + lazy val effect3 = + projectMatrixForSupportedTwitterVersions("catbird-effect3", "effect3") { v => + List( + moduleName := name.value + s"-$v", + libraryDependencies ++= { + Seq( + "org.typelevel" %% "cats-effect" % catsEffect3Version, + "org.typelevel" %% "cats-effect-laws" % catsEffect3Version % Test, + "org.typelevel" %% "cats-effect-testkit" % catsEffect3Version % Test + ) // TODO ++ (if (scalaVersion.value.startsWith("2")) scala2CompilerPlugins else Nil) + }, + Test / scalacOptions ~= { + _.filterNot(Set("-Yno-imports", "-Yno-predef")) + } + ) + } + .dependsOn(util, util % "test->test") + + lazy val finagle = + projectMatrixForSupportedTwitterVersions("catbird-finagle", "finagle") { v => + List( + moduleName := name.value + s"-$v", + libraryDependencies ++= { + Seq( + "com.twitter" %% "finagle-core" % v + ) // TODO ++ (if (scalaVersion.value.startsWith("2")) scala2CompilerPlugins else Nil) + }, + Test / scalacOptions ~= { + _.filterNot(Set("-Yno-imports", "-Yno-predef")) + } + ) + } + .dependsOn(util) + + lazy val benchmark = + projectMatrixForSupportedTwitterVersions("benchmark", "benchmark") { v => + List( + moduleName := name.value + s"-$v", + libraryDependencies ++= { + Seq( + "org.scalatest" %% "scalatest" % "3.2.14" + ) // TODO ++ (if (scalaVersion.value.startsWith("2")) scala2CompilerPlugins else Nil) + }, + Test / scalacOptions ~= { + _.filterNot(Set("-Yno-imports", "-Yno-predef")) + } + ) + } + .enablePlugins(JmhPlugin) + .dependsOn(util) + + override def buildSettings: Seq[Def.Setting[?]] = Seq( + githubWorkflowScalaVersions := Seq("per-project-matrix"), + githubWorkflowBuildSbtStepPreamble := Nil, + tlBaseVersion := "22.12", + tlCiHeaderCheck := false, + + // TODO this may change as we support multiple Twitter versions + tlVersionIntroduced := // test bincompat starting from the beginning of this series + List("2.12", "2.13").map(_ -> s"${tlBaseVersion.value}.0").toMap, + + apiURL := Some(url("https://typelevel.org/catbird/api/")), + developers += Developer("travisbrown", "Travis Brown", "", url("https://twitter.com/travisbrown")), + + githubWorkflowBuild := Seq( + WorkflowStep.Sbt( + List( + "clean", + "coverage", + "test", + "mimaReportBinaryIssues", + "scalastyle", + "scalafmtCheckAll", + "scalafmtSbtCheck", + "unidoc", + "coverageReport" + ) + ), + WorkflowStep.Use( + UseRef.Public("codecov", "codecov-action", "v1"), + name = Some("Code coverage analysis") + ) + ), + + libraryDependencies ++= Seq( + "org.typelevel" %% "cats-core" % catsVersion, + "org.scalacheck" %% "scalacheck" % "1.17.0" % Test, + "org.scalatest" %% "scalatest" % "3.2.14" % Test, + "org.typelevel" %% "cats-laws" % catsVersion % Test, + "org.typelevel" %% "discipline-core" % "1.5.1" % Test, + "org.typelevel" %% "discipline-scalatest" % "2.2.0" % Test + ), + resolvers ++= Resolver.sonatypeOssRepos("snapshots"), + libraryDependencySchemes ++= Seq( + // scoverage depends on scala-xml 1, but discipline-scalatest transitively pulls in scala-xml 2 + // this is normally discouraged but was recommended by one of the scoverage maintainers in the Typelevel Discord + "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always + ), + docMappingsApiDir := "api", + autoAPIMappings := true, - lazy val rootFinagle = - project.in(file(s".rootFinagle")).enablePlugins(NoPublishPlugin).aggregate(subprojects.map(_.project): _*) + // disable automatic copyright header creation otherwise added via sbt-typelevel +// TODO headerMappings := Map.empty + ) + + // /* When a new Finagle version is released, add it to the beginning of +// * this list. If `rootFinagle/mimaReportBinaryIssues` shows no issues, +// * we have a chain of binary-compatible releases and the new list can +// * be left as it is (i.e. with the new version at the head). If issues +// * are found, then remove everything other than the new version, +// * because the newest release is not binary compatible with the older +// * versions it was checked against. +// */ +// val versions = Seq("22.12.0") +// +// lazy val modules = Seq( +// "com.twitter" %% "finagle-core" % versions.head +// ) +// +// private lazy val subprojects = modules.map { module => +// Project(module.name, file(s".${module.name}")) +// .enablePlugins(NoPublishPlugin) +// .settings( +// libraryDependencies += module, +// mimaCurrentClassfiles := { +// (Compile / dependencyClasspath).value.seq.map(_.data).find(_.getName.startsWith(module.name)).get +// }, +// mimaPreviousArtifacts := versions.tail.map { v => +// module.withRevision(v) +// }.toSet +// ) +// } +// +// lazy val rootFinagle = +// project.in(file(s".rootFinagle")).enablePlugins(NoPublishPlugin).aggregate(subprojects.map(_.project): _*) +// + + override def extraProjects: Seq[Project] = autoImport.allProjects + + override def globalSettings: Seq[Def.Setting[?]] = Seq( + onChangedBuildSource := ReloadOnSourceChanges, + ) - override lazy val extraProjects: Seq[Project] = rootFinagle +: subprojects + private implicit class OrganizationArtifactNameOps(val oan: OrganizationArtifactName) extends AnyVal { + def %(vav: Version): ModuleID = + oan % vav.toString + } +} +object WarnNonUnitStatements extends AutoPlugin { + override def trigger = allRequirements + + override def projectSettings: Seq[Def.Setting[?]] = Seq( + scalacOptions ++= { + if (scalaVersion.value.startsWith("2.13")) + Seq("-Wnonunit-statement") + else if (scalaVersion.value.startsWith("2.12")) + Seq() + else + Nil + }, + ) } diff --git a/project/TwitterVersion.scala b/project/TwitterVersion.scala new file mode 100644 index 0000000..96e7311 --- /dev/null +++ b/project/TwitterVersion.scala @@ -0,0 +1,29 @@ +import sbt.* +import sbt.Keys.* +import sbt.internal.ProjectMatrix + +case class TwitterVersion(version: Version) extends VirtualAxis.WeakAxis { + override def directorySuffix: String = + s"-twitter-$version" + + override def idSuffix: String = + s"-twitter-$version".replace('.', '_') +} + +object TwitterVersion { + def resolve[T](matrix: ProjectMatrix, + key: TaskKey[T], + ): Def.Initialize[Task[T]] = + Def.taskDyn { + val project = matrix.finder().apply(scalaVersion.value) + Def.task((project / key).value) + } + + def resolve[T](matrix: ProjectMatrix, + key: SettingKey[T] + ): Def.Initialize[T] = + Def.settingDyn { + val project = matrix.finder().apply(scalaVersion.value) + Def.setting((project / key).value) + } +} diff --git a/project/Version.scala b/project/Version.scala new file mode 100644 index 0000000..95f53b2 --- /dev/null +++ b/project/Version.scala @@ -0,0 +1,78 @@ +/* + * 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. + */ + +/* Adapted from https://github.com/typelevel/sbt-typelevel/blob/9106a7e3114d94ea1d9b9af3511e2f5e5806be17/kernel/src/main/scala/org/typelevel/sbt/kernel/Version.scala */ + +import scala.util.Try + +final case class Version(major: Int, + minor: Int, + patch: Option[Int], + prerelease: Option[String] + ) extends Ordered[Version] { + + override def toString: String = + s"$major.$minor${patch.fold("")(p => s".$p")}${prerelease.fold("")(p => s"-$p")}" + + def isPrerelease: Boolean = prerelease.nonEmpty + + def isSameSeries(that: Version): Boolean = + this.major == that.major && this.minor == that.minor + + def mustBeBinCompatWith(that: Version): Boolean = + this >= that && !that.isPrerelease && this.major == that.major && (major > 0 || this.minor == that.minor) + + def compare(that: Version): Int = { + val x = this.major.compare(that.major) + if (x != 0) return x + val y = this.minor.compare(that.minor) + if (y != 0) return y + (this.patch, that.patch) match { + case (None, None) => 0 + case (None, Some(patch)) => 1 + case (Some(patch), None) => -1 + case (Some(thisPatch), Some(thatPatch)) => + val z = thisPatch.compare(thatPatch) + if (z != 0) return z + (this.prerelease, that.prerelease) match { + case (None, None) => 0 + case (Some(_), None) => 1 + case (None, Some(_)) => -1 + case (Some(thisPrerelease), Some(thatPrerelease)) => + // TODO not great, but not everyone uses Ms and RCs + thisPrerelease.compare(thatPrerelease) + } + } + } + +} + +object Version { + private val version = """^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-(.+))?$""".r + + def apply(v: String): Option[Version] = Version.unapply(v) + + def unapply(v: String): Option[Version] = v match { + case version(major, minor, patch, prerelease) => + Try(Version(major.toInt, minor.toInt, Option(patch).map(_.toInt), Option(prerelease))).toOption + case _ => None + } + + object Tag { + def unapply(v: String): Option[Version] = + if (v.startsWith("v")) Version.unapply(v.substring(1)) else None + } +} diff --git a/project/build.properties b/project/build.properties index 6a9f038..04267b1 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.7.3 +sbt.version=1.9.9 diff --git a/project/plugins.sbt b/project/plugins.sbt index 67af0ae..12b3269 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,10 +1,13 @@ addSbtPlugin("com.github.sbt" % "sbt-unidoc" % "0.5.0") -addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.4.17") -addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") +addSbtPlugin("org.typelevel" % "sbt-typelevel-ci-release" % "0.7.1") +addSbtPlugin("org.typelevel" % "sbt-typelevel-mergify" % "0.7.1") +addSbtPlugin("org.typelevel" % "sbt-typelevel-settings" % "0.7.1") +addSbtPlugin("com.github.sbt" % "sbt-ghpages" % "0.8.0") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.6") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.4") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.12.0") +addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.10.0") ThisBuild / libraryDependencySchemes ++= Seq( "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always