From 79e17f84af788967db676ecbbdddfa9d3f58c680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Sat, 17 Dec 2016 15:06:14 +0000 Subject: [PATCH] Revive the sbt plugin (#610) * Revert "Remove SBT plugin, see #597. (#598)" This reverts commit 308f393ac71cbb290bc8166c9f82bed59b1f3a17. * Revert "Don't run scripted tests on travis" This reverts commit 983b04372b76fd116c1b3767ac78012367412f44. * Implement sbt plugin as a thin wrapper around CLI. * Use sbt-buildinfo so that I can use sbt-scalafmt * Polish sbt pluging and documentation. * Fix git diff bug. * Re-enable scoverage plugin to stop flaky tests. I have no idea why this seems to fix the flaky tests. --- .drone.yml | 21 +++++- .travis.yml | 5 -- bin/publish.sh | 2 +- bin/sonatypePublish.sh | 2 +- bin/testAll.sh | 4 +- bin/travisPublish.sh | 2 +- build.sbt | 61 +++++++++++++++-- cli/src/main/scala/org/scalafmt/cli/Cli.scala | 28 +++----- .../scala/org/scalafmt/cli/CliArgParser.scala | 16 ++++- .../scala/org/scalafmt/cli/CliOptions.scala | 4 +- .../scala/org/scalafmt/cli/Scalafmt210.scala | 6 +- .../test/scala/org/scalafmt/cli/CliTest.scala | 16 +++-- .../scala/org/scalafmt/cli/FakeGitOps.scala | 1 + .../main/scala/org/scalafmt/Versions.scala | 12 ---- .../org/scalafmt/config/FilterMatcher.scala | 26 +++++++ .../org/scalafmt/config/ProjectFiles.scala | 9 +-- project/build.sbt | 3 - project/plugins.sbt | 12 +--- readme/Installation.scalatex | 58 ++++++++++++++++ .../org/scalafmt/sbt/ScalafmtPlugin.scala | 54 +++++++++++++++ .../scalafmt-sbt/helloworld/.gitignore | 23 +++++++ .../scalafmt-sbt/helloworld/.scalafmt.conf | 4 ++ .../scalafmt-sbt/helloworld/build.sbt | 68 +++++++++++++++++++ .../helloworld/p1/src/main/scala/Test.scala | 6 ++ .../p1/src/test/scala/MainTest.scala | 6 ++ .../helloworld/p2/src/main/scala/Test.scala | 6 ++ .../p2/src/test/scala/MainTest.scala | 6 ++ .../helloworld/p3/src/main/scala/Test.scala | 6 ++ .../p3/src/test/scala/MainTest.scala | 6 ++ .../helloworld/project/build.properties | 1 + .../helloworld/project/plugins.sbt | 3 + .../target/src_managed/Generated.scala | 1 + .../src/sbt-test/scalafmt-sbt/helloworld/test | 12 ++++ .../main/scala/org/scalafmt/util/GitOps.scala | 44 +++++++++--- 34 files changed, 446 insertions(+), 88 deletions(-) delete mode 100644 core/src/main/scala/org/scalafmt/Versions.scala create mode 100644 core/src/main/scala/org/scalafmt/config/FilterMatcher.scala delete mode 100644 project/build.sbt create mode 100644 scalafmtSbt/src/main/scala/org/scalafmt/sbt/ScalafmtPlugin.scala create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/.gitignore create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/.scalafmt.conf create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/build.sbt create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p1/src/main/scala/Test.scala create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p1/src/test/scala/MainTest.scala create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p2/src/main/scala/Test.scala create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p2/src/test/scala/MainTest.scala create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p3/src/main/scala/Test.scala create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p3/src/test/scala/MainTest.scala create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/project/build.properties create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/project/plugins.sbt create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/target/src_managed/Generated.scala create mode 100644 scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/test diff --git a/.drone.yml b/.drone.yml index b3dacd40ec..af78d053fb 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,9 +2,24 @@ build: image: olafurpg/scalafix:0.0.1 commands: - export JVM_OPTS="-Xms4000m -Xmx55g -XX:MaxPermSize=2000m -Xss4m -XX:ReservedCodeCacheSize=1024m -XX:+TieredCompilation -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC" - - pip install --user codecov + # configuring ivy.home doesn't seem to work. Maybe related: + # https://github.com/sbt/sbt/issues/1894 + # After 10+ experiments I've given up on trying to use sbt.ivy.yhome and + # copy the files myself instead, as recommended here: + # http://readme.drone.io/usage/caching/ + - test -d /drone/.sbt && cp -a /drone/.sbt /root + - rm -rf /drone/.sbt + + - test -d /drone/.ivy2 && cp -a /drone/.ivy2 /root + - rm -rf /drone/.ivy2 + - ./bin/testAll.sh - # I can't be bothered to hide this token. -# - $HOME/.local/bin/codecov -t 5f2117aa-0a01-4cf1-8bf7-631a62ccb47a + - cp -a /root/.ivy2 /drone + - cp -a /root/.sbt /drone +cache: + mount: + - /drone/.sbt + - /drone/.ivy2 + - /drone/cache diff --git a/.travis.yml b/.travis.yml index acce917c62..32893baaa5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,13 +9,8 @@ git: submodules: false scala: - 2.11.8 -before_install: -- pip install --user codecov script: - "./bin/testAll.sh" -#after_success: -#- "./bin/travisPublish.sh" -#- codecov notifications: email: - olafurpg@gmail.com diff --git a/bin/publish.sh b/bin/publish.sh index a33ceeeb17..0f4c3dc191 100755 --- a/bin/publish.sh +++ b/bin/publish.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -version=$(sed -n -e 's/.*val nightly = "\(.*\)"/\1/p' core/src/main/scala/org/scalafmt/Versions.scala) +version=$(sed -n -e 's/.*val nightly = "\(.*\)"/\1/p' core/target/scala-2.11/src_managed/main/sbt-buildinfo/Versions.scala) tag="v${version}" tarfile="cli/target/scalafmt.tar.gz" current_branch=$(git rev-parse --abbrev-ref HEAD) diff --git a/bin/sonatypePublish.sh b/bin/sonatypePublish.sh index f9744d5556..def2758107 100644 --- a/bin/sonatypePublish.sh +++ b/bin/sonatypePublish.sh @@ -2,7 +2,7 @@ set -e if [ $TRAVIS_SECURE_ENV_VARS = "true" ]; then echo "Publishing snapshot..." # Assert that nighly is set to snapshot - grep "nightly.*SNAPSHOT" core/src/main/scala/org/scalafmt/Versions.scala + grep "nightly.*SNAPSHOT" core/target/scala-2.11/src_managed/main/sbt-buildinfo/Versions.scala sbt publishSigned else echo "Skipping publish" diff --git a/bin/testAll.sh b/bin/testAll.sh index 0f17bbf790..2bb1c2650e 100755 --- a/bin/testAll.sh +++ b/bin/testAll.sh @@ -1,9 +1,7 @@ #!/bin/bash set -e -#sbt clean coverage test sbt clean test sbt "core/test:runMain org.scalafmt.FormatExperimentApp" -./bin/issue492.sh -#sbt coverageAggregate +sbt "; publishLocal ; scripted" diff --git a/bin/travisPublish.sh b/bin/travisPublish.sh index c327152268..62e3349c3e 100755 --- a/bin/travisPublish.sh +++ b/bin/travisPublish.sh @@ -16,7 +16,7 @@ if [[ ${TRAVIS_SECURE_ENV_VARS} == "true" && ${TRAVIS_BRANCH} == "master" ]]; th echo "Publishing snapshot..." setupDeployKey # only publish if nightly is a snapshot. - if grep "nightly.*SNAPSHOT" core/src/main/scala/org/scalafmt/Versions.scala; then + if grep "nightly.*SNAPSHOT" core/target/scala-2.11/src_managed/main/sbt-buildinfo/Versions.scala; then echo "Publishing snapshot" sbt publish # snapshot else diff --git a/build.sbt b/build.sbt index 58b92c356c..203da8d047 100644 --- a/build.sbt +++ b/build.sbt @@ -1,10 +1,11 @@ import scoverage.ScoverageSbtPlugin.ScoverageKeys.coverageHighlighting +// The version number used in docs. +def latestStableVersion: String = "0.4.10" lazy val buildSettings = Seq( organization := "com.geirsson", - // See core/src/main/scala/org/scalafmt/Versions.scala - version := org.scalafmt.Versions.nightly, - scalaVersion := org.scalafmt.Versions.scala, + version := "0.4.10-SNAPSHOT", + scalaVersion := "2.11.8", updateOptions := updateOptions.value.withCachedResolution(true) ) @@ -86,6 +87,20 @@ lazy val noPublish = Seq( publishLocal := {} ) +lazy val buildInfoSettings: Seq[Def.Setting[_]] = Seq( + buildInfoKeys := Seq[BuildInfoKey]( + name, + version, + "nightly" -> version.value, + "stable" -> latestStableVersion, + "scala" -> scalaVersion.value, + scalaVersion, + sbtVersion + ), + buildInfoPackage := "org.scalafmt", + buildInfoObject := "Versions" +) + lazy val allSettings = commonSettings ++ buildSettings ++ publishSettings lazy val root = project @@ -108,14 +123,16 @@ lazy val root = project utils, core, metaconfig, - readme + readme, + scalafmtSbt ) .dependsOn(core) lazy val core = project - .settings(allSettings) .settings( + allSettings, metaMacroSettings, + buildInfoSettings, moduleName := "scalafmt-core", libraryDependencies ++= Seq( "com.lihaoyi" %% "sourcecode" % "0.1.2", @@ -135,6 +152,7 @@ lazy val core = project "org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full) ) .dependsOn(metaconfig, utils) + .enablePlugins(BuildInfoPlugin) lazy val cliJvmOptions = Seq( "-Xss4m" @@ -161,10 +179,9 @@ lazy val cli = project lazy val bootstrap = project .settings( allSettings, + buildInfoSettings, // crossScalaVersions := Seq("2.10.6", "2.11.8"), moduleName := "scalafmt-bootstrap", - sources in Compile += - baseDirectory.value / "../core/src/main/scala/org/scalafmt/Versions.scala", libraryDependencies ++= Seq( "com.martiansoftware" % "nailgun-server" % "0.9.1", "io.get-coursier" %% "coursier" % "1.0.0-M14", @@ -173,6 +190,36 @@ lazy val bootstrap = project ) ) .dependsOn(utils) + .enablePlugins(BuildInfoPlugin) + +lazy val scalafmtSbt = project + .settings( + allSettings, + buildInfoSettings, + ScriptedPlugin.scriptedSettings, + scripted := scripted + .dependsOn( + publishLocal in cli, + publishLocal in core, + publishLocal in metaconfig, + publishLocal in utils + ) + .evaluated, + sbtPlugin := true, + coverageHighlighting := false, + scalaVersion := "2.10.5", + // In convention of sbt plugins, the module is sbt-scalafmt instead of scalafmt-sbt. + moduleName := "sbt-scalafmt", + scriptedLaunchOpts := Seq( + "-Dplugin.version=" + version.value, + // .jvmopts is ignored, simulate here + "-XX:MaxPermSize=256m", + "-Xmx2g", + "-Xss2m" + ), + scriptedBufferLog := false + ) + .enablePlugins(BuildInfoPlugin) lazy val benchmarks = project .settings(moduleName := "scalafmt-benchmarks") diff --git a/cli/src/main/scala/org/scalafmt/cli/Cli.scala b/cli/src/main/scala/org/scalafmt/cli/Cli.scala index 66a5c2704a..3e1faacded 100644 --- a/cli/src/main/scala/org/scalafmt/cli/Cli.scala +++ b/cli/src/main/scala/org/scalafmt/cli/Cli.scala @@ -19,7 +19,9 @@ import org.scalafmt.config.ScalafmtConfig import org.scalafmt.util.FileOps import org.scalafmt.util.LogLevel import com.martiansoftware.nailgun.NGContext +import org.scalafmt.config.FilterMatcher import org.scalafmt.util.AbsoluteFile +import org.scalafmt.util.logger object Cli { def nailMain(nGContext: NGContext): Unit = { @@ -77,13 +79,6 @@ object Cli { CliArgParser.scoptParser.parse(args, init).map(CliOptions.auto(init)) } - private def mkRegexp(filters: Seq[String]): Regex = - filters match { - case Nil => "$a".r // will never match anything - case head :: Nil => head.r - case _ => filters.mkString("(", "|", ")").r - } - def canFormat(path: AbsoluteFile): Boolean = canFormat(path.path) @@ -102,22 +97,20 @@ object Cli { /** Returns file paths defined via options.{customFiles,customExclude} */ def getFilesFromCliOptions(options: CliOptions): Seq[AbsoluteFile] = { - val exclude = mkRegexp(options.customExcludes) + val exclude = FilterMatcher.mkRegexp(options.customExcludes) expandCustomFiles(options.common.workingDirectory, options.customFiles) .filter(x => exclude.findFirstIn(x.path).isEmpty) } + private def getFilesFromDiff(options: CliOptions): Seq[AbsoluteFile] = { + val branch = options.diff.get + options.gitOps.diff(branch).filter(options.config.project.matcher.matches) + } + /** Returns file paths defined via options.project */ private def getFilesFromProject(options: CliOptions): Seq[AbsoluteFile] = { val project = options.config.project - val include = mkRegexp(project.includeFilters) - val exclude = mkRegexp(project.excludeFilters) - - def matches(file: AbsoluteFile): Boolean = { - val path = file.path - include.findFirstIn(path).isDefined && - exclude.findFirstIn(path).isEmpty - } + val matcher = project.matcher val projectFiles: Seq[AbsoluteFile] = ( if (project.git) { options.gitOps.lsTree @@ -130,7 +123,7 @@ object Cli { options.common.workingDirectory, AbsoluteFile.fromFiles(project.files.map(new File(_)), options.common.workingDirectory)) - (customFiles ++ projectFiles).filter(matches) + (customFiles ++ projectFiles).filter(matcher.matches) } private def getInputMethods(options: CliOptions): Seq[InputMethod] = { @@ -139,6 +132,7 @@ object Cli { } else { val projectFiles: Seq[AbsoluteFile] = if (options.customFiles.nonEmpty) getFilesFromCliOptions(options) + else if (options.diff.isDefined) getFilesFromDiff(options) else getFilesFromProject(options) projectFiles.map(InputMethod.FileContents.apply) } diff --git a/cli/src/main/scala/org/scalafmt/cli/CliArgParser.scala b/cli/src/main/scala/org/scalafmt/cli/CliArgParser.scala index 6f958084cb..53bea76ee8 100644 --- a/cli/src/main/scala/org/scalafmt/cli/CliArgParser.scala +++ b/cli/src/main/scala/org/scalafmt/cli/CliArgParser.scala @@ -18,19 +18,23 @@ object CliArgParser { @GitCommit val gitCommit: String = ??? @BuildTime val buildTimeMs: Long = ??? - val usageExamples = + val usageExamples: String = """|scalafmt # Format all files in the current project, with config determined: | # 1. .scalafmt.conf inside root directory of current git repo - | # IF the following setting is enabled: project.git = true | # 2. .scalafmt.conf file in current directory | # 3. default style | # from .scalafmt.conf in the root directory, if the file exists. |scalafmt --test # same as without --test, except | # 1. throws an exception on the first mis-formatted file | # 2. does not write to any files. + |scalafmt --diff # same as without --diff, except only format files + | # in git diff against master branch. + |scalafmt --diff --test # same as --test, except only for edited files + | # in git diff against master branch. + |scalafmt --diff-branch 2.x # same as --diff, except against branch 2.x + |scalafmt --diff-branch 2.x --test # same as --diff |scalafmt --stdin # read from stdin and print to stdout |scalafmt --stdin --assume-filename foo.sbt # required to format .sbt files - | |scalafmt -f Code.scala # print formatted contents to stdout. |scalafmt -i -f Code1.scala,A.scala # write formatted contents to file. |scalafmt -i -f . --exclude target # format all files in directory excluding target @@ -101,6 +105,12 @@ object CliArgParser { c.copy(migrate = Some(AbsoluteFile.fromFile(file, c.common.workingDirectory)))) .text("""migrate .scalafmt CLI style configuration to hocon style configuration in .scalafmt.conf""") + opt[Unit]("diff") + .action((_, c) => c.copy(diff = Some("master"))) + .text("If set, only format edited files in git diff against master.") + opt[String]("diff-branch") + .action((branch, c) => c.copy(diff = Some(branch))) + .text("If set, only format edited files in git diff against provided branch.") opt[Unit]("build-info") .action({ case (_, c) => diff --git a/cli/src/main/scala/org/scalafmt/cli/CliOptions.scala b/cli/src/main/scala/org/scalafmt/cli/CliOptions.scala index 0ed85b97a8..33a8cbce23 100644 --- a/cli/src/main/scala/org/scalafmt/cli/CliOptions.scala +++ b/cli/src/main/scala/org/scalafmt/cli/CliOptions.scala @@ -33,7 +33,7 @@ object CliOptions { val style: Option[ScalafmtConfig] = if (init.config != parsed.config) { Option(parsed.config) } else { - tryGit(parsed).orElse(tryCurrentDirectory(parsed)) + tryCurrentDirectory(parsed).orElse(tryGit(parsed)) } val inplace = !parsed.testing && ( @@ -61,7 +61,6 @@ object CliOptions { case Left(e) => throw e } } - if parsedConfig.project.git } yield parsedConfig } @@ -89,6 +88,7 @@ case class CliOptions( inPlace: Boolean = false, testing: Boolean = false, stdIn: Boolean = false, + diff: Option[String] = None, assumeFilename: String = "stdin.scala", // used when read from stdin migrate: Option[AbsoluteFile] = None, common: CommonOptions = CommonOptions(), diff --git a/cli/src/main/scala/org/scalafmt/cli/Scalafmt210.scala b/cli/src/main/scala/org/scalafmt/cli/Scalafmt210.scala index 883e117c3a..71cd057600 100644 --- a/cli/src/main/scala/org/scalafmt/cli/Scalafmt210.scala +++ b/cli/src/main/scala/org/scalafmt/cli/Scalafmt210.scala @@ -1,5 +1,7 @@ package org.scalafmt.cli +import scala.util.matching.Regex + import java.io.File import org.scalafmt.Error.InvalidScalafmtConfiguration @@ -15,8 +17,10 @@ import org.scalafmt.util.LoggerOps._ * plugin. */ class Scalafmt210 { - val oldConfig = "--".r + val oldConfig: Regex = "--".r + def main(args: Array[String]): Unit = Cli.main(args) + // The rest is for scalafmtIncremental. def format(code: String, configFile: String, filename: String): String = { StyleCache.getStyleForFileOrError(configFile) match { case Left(throwable) => { diff --git a/cli/src/test/scala/org/scalafmt/cli/CliTest.scala b/cli/src/test/scala/org/scalafmt/cli/CliTest.scala index 8a5492776d..c2768d6178 100644 --- a/cli/src/test/scala/org/scalafmt/cli/CliTest.scala +++ b/cli/src/test/scala/org/scalafmt/cli/CliTest.scala @@ -261,13 +261,15 @@ class CliTest extends FunSuite with DiffAssertions { |object A { } |""".stripMargin - val init: CliOptions = getMockOptions(input) - val config = Cli.getConfig(Array.empty[String], init).get - Cli.run(config) - val obtained = dir2string(input) - assertNoDiff(obtained, expected) - val configTest = Cli.getConfig(Array("--test"), init).get - Cli.run(configTest) + Seq(Array.empty[String], Array("--diff")).foreach { args => + val init: CliOptions = getMockOptions(input) + val config = Cli.getConfig(args, init).get + Cli.run(config) + val obtained = dir2string(input) + assertNoDiff(obtained, expected) + val configTest = Cli.getConfig(Array("--test"), init).get + Cli.run(configTest) + } } test("config is read even from nested dir") { diff --git a/cli/src/test/scala/org/scalafmt/cli/FakeGitOps.scala b/cli/src/test/scala/org/scalafmt/cli/FakeGitOps.scala index 8deff2eb8b..f20eeaeece 100644 --- a/cli/src/test/scala/org/scalafmt/cli/FakeGitOps.scala +++ b/cli/src/test/scala/org/scalafmt/cli/FakeGitOps.scala @@ -10,4 +10,5 @@ import org.scalafmt.util.logger class FakeGitOps(root: AbsoluteFile) extends GitOps { override def lsTree: Vector[AbsoluteFile] = FileOps.listFiles(root) override def rootDir: Option[AbsoluteFile] = Some(root) + override def diff(branch: String): Seq[AbsoluteFile] = lsTree } diff --git a/core/src/main/scala/org/scalafmt/Versions.scala b/core/src/main/scala/org/scalafmt/Versions.scala deleted file mode 100644 index 75b45d95d5..0000000000 --- a/core/src/main/scala/org/scalafmt/Versions.scala +++ /dev/null @@ -1,12 +0,0 @@ -package org.scalafmt - -/** - * Single source of truth for version number. - */ -object Versions { - // Nightly, used in CLI, build.sbt, etc. - val nightly = "0.4.10" - // Stable, used in official user docs. - val stable = "0.4.10" - val scala = "2.11.8" -} diff --git a/core/src/main/scala/org/scalafmt/config/FilterMatcher.scala b/core/src/main/scala/org/scalafmt/config/FilterMatcher.scala new file mode 100644 index 0000000000..d75ee30e95 --- /dev/null +++ b/core/src/main/scala/org/scalafmt/config/FilterMatcher.scala @@ -0,0 +1,26 @@ +package org.scalafmt.config + +import scala.util.matching.Regex + +import org.scalafmt.util.AbsoluteFile + +case class FilterMatcher(include: Regex, exclude: Regex) { + def matches(file: AbsoluteFile): Boolean = matches(file.path) + def matches(input: String): Boolean = + include.findFirstIn(input).isDefined && + exclude.findFirstIn(input).isEmpty +} + +object FilterMatcher { + val matchEverything = new FilterMatcher(".*".r, mkRegexp(Nil)) + + def mkRegexp(filters: Seq[String]): Regex = + filters match { + case Nil => "$a".r // will never match anything + case head :: Nil => head.r + case _ => filters.mkString("(", "|", ")").r + } + + def apply(includes: Seq[String], excludes: Seq[String]): FilterMatcher = + new FilterMatcher(mkRegexp(includes), mkRegexp(excludes)) +} diff --git a/core/src/main/scala/org/scalafmt/config/ProjectFiles.scala b/core/src/main/scala/org/scalafmt/config/ProjectFiles.scala index 95ec72d05d..771efde4a2 100644 --- a/core/src/main/scala/org/scalafmt/config/ProjectFiles.scala +++ b/core/src/main/scala/org/scalafmt/config/ProjectFiles.scala @@ -1,13 +1,14 @@ package org.scalafmt.config -import scala.util.matching.Regex - import metaconfig.ConfigReader @ConfigReader case class ProjectFiles( git: Boolean = false, files: Seq[String] = Nil, - includeFilters: Seq[String] = Seq(".*"), + includeFilters: Seq[String] = Seq(".*\\.scala", ".*\\.sbt"), excludeFilters: Seq[String] = Nil -) +) { + lazy val matcher: FilterMatcher = + FilterMatcher(includeFilters, excludeFilters) +} diff --git a/project/build.sbt b/project/build.sbt deleted file mode 100644 index d560b26642..0000000000 --- a/project/build.sbt +++ /dev/null @@ -1,3 +0,0 @@ -sources in Compile += { - baseDirectory.value / "../core/src/main/scala/org/scalafmt/Versions.scala" -} diff --git a/project/plugins.sbt b/project/plugins.sbt index f0624ff071..7b9091b89e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,21 +1,13 @@ resolvers += Classpaths.sbtPluginReleases -resolvers += Resolver.url( - "dancingrobot84-bintray", - url("http://dl.bintray.com/dancingrobot84/sbt-plugins/") -)(Resolver.ivyStylePatterns) - -// I would love to enable this plugin but it one point I had disabled almost all -// of the warts. -// addSbtPlugin("org.wartremover" % "sbt-wartremover" % "1.1.1") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.6.1") addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-M14") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.0.1") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.15") addSbtPlugin("com.lihaoyi" % "scalatex-sbt-plugin" % "0.3.5") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.1") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") -addSbtPlugin("com.dancingrobot84" % "sbt-idea-plugin" % "0.4.2") addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.8.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.0.1") libraryDependencies += "org.scala-sbt" % "scripted-plugin" % sbtVersion.value diff --git a/readme/Installation.scalatex b/readme/Installation.scalatex index 81f6dcb43b..d594d44a90 100644 --- a/readme/Installation.scalatex +++ b/readme/Installation.scalatex @@ -155,6 +155,64 @@ org.scalafmt.cli.CliArgParser.buildInfo + "\n" + org.scalafmt.cli.CliArgParser.scoptParser.usage) + @sect{SBT} + @p + @note. If you rely on the SBT plugin, please consider becoming a maintainer! + @p + The sbt plugin is just a thin wrapper around the command line interface. + + @hl.scala + addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "@org.scalafmt.Versions.stable") + + @ul + @li + @lnk("Here is an example repository using sbt plugin.", + "https://github.com/olafurpg/sbt-scalafmt-example") + @li + Run @code("sbt scalafmt") to format all files in the project. + You can add pass any command line flags to @code{scalafmt}. + See @sect.ref{--help} for more details. + Example, + @hl.scala + sbt "scalafmt --help" + scalafmt x.y.z + Usage: ... + @li + There are three handy aliases for the @code{scalafmt} command. + @ul + @li + @code{scalafmtTest -> scalafmt --test} + @li + @code{scalafmtDiff -> scalafmt --diff} + @li + @code{scalafmtDiffTest -> scalafmt --diff --test} + @li + The plugin will automatically pick up your @sect.ref{Configuration} in + @code{.scalafmt.conf}. + @p + The configuration must be defined in .scalafmt.conf in the root directory + of your project. Why? Because integrations like IntelliJ rely on the + file existing there. + @p + If you want to define the configuration somehow differently, you can + use some trick like this in @code{build.sbt} or from within an + sbt plugin settings + + @hl.scala + // define setting key to write configuration to .scalafmt.conf + SettingKey[Unit]("scalafmtGenerateConfig") := + IO.write( // writes to file once when build is loaded + file(".scalafmt.conf"), + """style = IntelliJ + |# Your configuration here + """.stripMargin.getBytes("UTF-8") + ) + @p + There is no setting to reformat on compile, please consider contributing it + if you would like to use that feature! + Until then, I encourage you to find another way to incorporate scalafmt + into your workflow. + @sect{Vim} @ul @li diff --git a/scalafmtSbt/src/main/scala/org/scalafmt/sbt/ScalafmtPlugin.scala b/scalafmtSbt/src/main/scala/org/scalafmt/sbt/ScalafmtPlugin.scala new file mode 100644 index 0000000000..de7e898019 --- /dev/null +++ b/scalafmtSbt/src/main/scala/org/scalafmt/sbt/ScalafmtPlugin.scala @@ -0,0 +1,54 @@ +package org.scalafmt.sbt + +import sbt.Keys._ +import sbt._ +import sbt.plugins.JvmPlugin + +object ScalafmtPlugin extends AutoPlugin { + private def baseCmd = "scalafmt-stub/runMain org.scalafmt.cli.Cli " + private def cmd(args: String*)(s: State): State = + s"$baseCmd ${args.mkString(" ")}" :: s + object autoImport { + + lazy val scalafmt: Command = + Command.args("scalafmt", "run the scalafmt command line interface.") { + case (s, args) => s"$baseCmd ${args.mkString(" ")}" :: s + } + // These are not strictly necessary, since they can be run with the + // scalafmt command. However, they're convenient to avoid the need for + // parentheses when running `sbt "scalafmt --test"`. + lazy val scalafmtTest: Command = + Command.command("scalafmtTest")(cmd("--test")) + lazy val scalafmtDiff: Command = + Command.command("scalafmtDiff")(cmd("--diff")) + lazy val scalafmtDiffTest: Command = + Command.command("scalafmtDiffTest")(cmd("--diff", "--test")) + } + import autoImport._ + lazy val scalafmtStub: Project = Project( + id = "scalafmt-stub", + base = file("project/scalafmt") + ).settings( + scalaVersion := "2.11.8", + libraryDependencies += + "com.geirsson" %% "scalafmt-cli" % _root_.org.scalafmt.Versions.nightly + ) + override def globalSettings: Seq[Def.Setting[_]] = Seq( + commands ++= { + if (sbtVersion.value < "0.13.13") { + // can't do streams.value.log.warn since streams is a task and + // settings like `commands` can't depend on tasks. + val warn = s"[${scala.Console.YELLOW}warn${scala.Console.RESET}]" + System.err.println( + s"$warn sbt-scalafmt requires sbt.version=0.13.13 or higher. " + + s"Please upgrade sbt to expose the `scalafmt` command.") + Nil + } else { + Seq(scalafmt, scalafmtTest, scalafmtDiff, scalafmtDiffTest) + } + } + ) + override def extraProjects: Seq[Project] = Seq(scalafmtStub) + override def trigger: PluginTrigger = allRequirements + override def requires = JvmPlugin +} diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/.gitignore b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/.gitignore new file mode 100644 index 0000000000..0064e95172 --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/.gitignore @@ -0,0 +1,23 @@ +*.class +*.log + +# sbt specific +.cache +.history +.lib/ +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ + +# Scala-IDE specific +.scala_dependencies +.worksheet + +.idea + +# ENSIME specific +.ensime_cache/ +.ensime diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/.scalafmt.conf b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/.scalafmt.conf new file mode 100644 index 0000000000..9e75a6c9f5 --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/.scalafmt.conf @@ -0,0 +1,4 @@ +style = IntelliJ +project.excludeFilters = [ + target/ +] diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/build.sbt b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/build.sbt new file mode 100644 index 0000000000..f2c022cc20 --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/build.sbt @@ -0,0 +1,68 @@ +import java.io.File + +lazy val root = project + .in(file(".")) + .aggregate( + p1, + p2, + p3 + ) + +lazy val p1 = project.settings(scalaVersion := "2.10.5") +lazy val p2 = project.settings(scalaVersion := "2.11.8") +lazy val p3 = project.settings(scalaVersion := "2.12.1") + +def assertContentsEqual(file: File, expected: String): Unit = { + val obtained = + scala.io.Source.fromFile(file).getLines().mkString("\n") + + if (obtained.trim != expected.trim) { + val msg = + s"""File: $file + |Obtained output: + |$obtained + |Expected: + |$expected + |""".stripMargin + System.err.println(msg) + throw new Exception(msg) + } +} + +TaskKey[Unit]("setupGitRepo") := { + import sys.process._ + Seq("git", "init").!! + Seq("git", "config", "user.email", "a@a.is").!! + Seq("git", "config", "user.name", "a").!! + Seq("git", "add", ".").!! + Seq("git", "commit", "-m", "wip").!! +} + +TaskKey[Unit]("check") := { + (1 to 3).foreach { i => + val expected = + """ + |object Test { + | foo( + | a, // comment + | b + | ) + |} + """.stripMargin + val expected2 = expected.replaceFirst("Test", "MainTest") + assertContentsEqual(file(s"p$i/src/main/scala/Test.scala"), expected) + assertContentsEqual(file(s"p$i/src/test/scala/MainTest.scala"), expected2) + } + assertContentsEqual( + new File("project/plugins.sbt"), + """ + |addSbtPlugin( + | "com.geirsson" % "sbt-scalafmt" % System.getProperty("plugin.version") + |) + """.stripMargin + ) + assertContentsEqual( + new File("target/src_managed/Generated.scala"), + """object DontFormatMe { println(1) }""" + ) +} diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p1/src/main/scala/Test.scala b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p1/src/main/scala/Test.scala new file mode 100644 index 0000000000..a96ec3f1fa --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p1/src/main/scala/Test.scala @@ -0,0 +1,6 @@ +object +Test +{ + foo(a, // comment + b) +} diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p1/src/test/scala/MainTest.scala b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p1/src/test/scala/MainTest.scala new file mode 100644 index 0000000000..7d4e15448b --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p1/src/test/scala/MainTest.scala @@ -0,0 +1,6 @@ +object +MainTest +{ + foo(a, // comment + b) +} diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p2/src/main/scala/Test.scala b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p2/src/main/scala/Test.scala new file mode 100644 index 0000000000..a96ec3f1fa --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p2/src/main/scala/Test.scala @@ -0,0 +1,6 @@ +object +Test +{ + foo(a, // comment + b) +} diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p2/src/test/scala/MainTest.scala b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p2/src/test/scala/MainTest.scala new file mode 100644 index 0000000000..7d4e15448b --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p2/src/test/scala/MainTest.scala @@ -0,0 +1,6 @@ +object +MainTest +{ + foo(a, // comment + b) +} diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p3/src/main/scala/Test.scala b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p3/src/main/scala/Test.scala new file mode 100644 index 0000000000..a96ec3f1fa --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p3/src/main/scala/Test.scala @@ -0,0 +1,6 @@ +object +Test +{ + foo(a, // comment + b) +} diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p3/src/test/scala/MainTest.scala b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p3/src/test/scala/MainTest.scala new file mode 100644 index 0000000000..7d4e15448b --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/p3/src/test/scala/MainTest.scala @@ -0,0 +1,6 @@ +object +MainTest +{ + foo(a, // comment + b) +} diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/project/build.properties b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/project/build.properties new file mode 100644 index 0000000000..27e88aa115 --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.13 diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/project/plugins.sbt b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/project/plugins.sbt new file mode 100644 index 0000000000..87eecfd5e1 --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/project/plugins.sbt @@ -0,0 +1,3 @@ +addSbtPlugin ( + "com.geirsson" % "sbt-scalafmt" % System.getProperty("plugin.version")) + diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/target/src_managed/Generated.scala b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/target/src_managed/Generated.scala new file mode 100644 index 0000000000..e018c2d95e --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/target/src_managed/Generated.scala @@ -0,0 +1 @@ +object DontFormatMe { println(1) } diff --git a/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/test b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/test new file mode 100644 index 0000000000..392519cf36 --- /dev/null +++ b/scalafmtSbt/src/sbt-test/scalafmt-sbt/helloworld/test @@ -0,0 +1,12 @@ +> setupGitRepo +> scalafmt +> scalafmtTest +> check +> scalafmtDiff +> scalafmtDiffTest +> check +> scalafmt --diff-branch master +> scalafmt --diff +> scalafmt --diff --test +> check + diff --git a/utils/src/main/scala/org/scalafmt/util/GitOps.scala b/utils/src/main/scala/org/scalafmt/util/GitOps.scala index a09b6b6a2e..8490a7a87d 100644 --- a/utils/src/main/scala/org/scalafmt/util/GitOps.scala +++ b/utils/src/main/scala/org/scalafmt/util/GitOps.scala @@ -2,10 +2,12 @@ package org.scalafmt.util import scala.sys.process.ProcessLogger import scala.util.Try +import scala.util.control.NonFatal import java.io.File trait GitOps { + def diff(branch: String): Seq[AbsoluteFile] def lsTree: Seq[AbsoluteFile] def rootDir: Option[AbsoluteFile] } @@ -15,20 +17,29 @@ object GitOps { } class GitOpsImpl(workingDirectory: AbsoluteFile) extends GitOps { - val swallowStderr = ProcessLogger(_ => Unit, _ => Unit) - val baseCommand = Seq("git") def exec(cmd: Seq[String]): String = { - sys.process - .Process(cmd, workingDirectory.jfile) - .!!(swallowStderr) - .trim + val lastError = new StringBuilder + val swallowStderr = ProcessLogger(_ => Unit, err => lastError.append(err)) + try { + sys.process + .Process(cmd, workingDirectory.jfile) + .!!(swallowStderr) + .trim + } catch { + case NonFatal(e) => + throw new IllegalStateException( + s"Failed to run command ${cmd.mkString(" ")}. " + + s"Error: ${lastError.toString()}", + e) + } } override def lsTree: Seq[AbsoluteFile] = Try { exec( - baseCommand ++ Seq( + Seq( + "git", "ls-tree", "-r", "HEAD", @@ -41,9 +52,26 @@ class GitOpsImpl(workingDirectory: AbsoluteFile) extends GitOps { override def rootDir: Option[AbsoluteFile] = Try { - val cmd = baseCommand ++ Seq("rev-parse", "--show-toplevel") + val cmd = Seq( + "git", + "rev-parse", + "--show-toplevel" + ) val result = AbsoluteFile.fromFile(new File(exec(cmd)), workingDirectory) require(result.jfile.isDirectory) result }.toOption + + override def diff(branch: String): Seq[AbsoluteFile] = { + exec( + Seq( + "git", + "diff", + "--name-only", + "HEAD", + branch + )).lines.map { x => + AbsoluteFile.fromFile(new File(x), workingDirectory) + }.toSeq + } }