diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index 482612f6f..d7da62d90 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -83,7 +83,8 @@ $HelpCommand List("-" + elem, "--" + elem) } (s.startsWith(EarlyCommand + "(") && s.endsWith(")")) || - (levelOptions contains s) + (levelOptions contains s) || + (s.startsWith("-" + AddPluginSbtFileCommand) || s.startsWith("--" + AddPluginSbtFileCommand)) } val EarlyCommand = "early" @@ -96,6 +97,14 @@ $HelpCommand The order is preserved between all early commands, so `sbt "early(a)" "early(b)"` executes `a` and `b` in order. """ + def addPluginSbtFileHelp = { + val brief = + (s"--$AddPluginSbtFileCommand=", "Adds the given *.sbt file to the plugin build.") + Help(brief) + } + + val AddPluginSbtFileCommand = "addPluginSbtFile" + def ReadCommand = "<" def ReadFiles = " file1 file2 ..." def ReadDetailed = diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index eee6311ac..896cd9fe1 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -66,15 +66,42 @@ object BasicCommands { private[this] def levelParser: Parser[String] = Iterator(Level.Debug, Level.Info, Level.Warn, Level.Error) map (l => token(l.toString)) reduce (_ | _) + private[this] def addPluginSbtFileParser: Parser[File] = { + token(AddPluginSbtFileCommand) ~> (":" | "=" | Space) ~> (StringBasic).examples( + "/some/extra.sbt" + ) map { + new File(_) + } + } + + private[this] def addPluginSbtFileStringParser: Parser[String] = { + token( + token(AddPluginSbtFileCommand) ~ (":" | "=" | Space) ~ (StringBasic) + .examples("/some/extra.sbt") map { + case s1 ~ s2 ~ s3 => s1 + s2 + s3 + } + ) + } + private[this] def earlyParser: State => Parser[String] = (s: State) => { val p1 = token(EarlyCommand + "(") flatMap (_ => otherCommandParser(s) <~ token(")")) - val p2 = token("-") flatMap (_ => levelParser) - val p3 = token("--") flatMap (_ => levelParser) + val p2 = (token("-") | token("--")) flatMap (_ => levelParser) + val p3 = (token("-") | token("--")) flatMap (_ => addPluginSbtFileStringParser) p1 | p2 | p3 } private[this] def earlyHelp = Help(EarlyCommand, EarlyCommandBrief, EarlyCommandDetailed) + /** + * Adds additional *.sbt to the plugin build. + * This must be combined with early command as: --addPluginSbtFile=/tmp/extra.sbt + */ + def addPluginSbtFile: Command = Command.arb(_ => addPluginSbtFileParser, addPluginSbtFileHelp) { + (s, extraSbtFile) => + val extraFiles = s.get(BasicKeys.extraMetaSbtFiles).toList.flatten + s.put(BasicKeys.extraMetaSbtFiles, extraFiles :+ extraSbtFile) + } + def help: Command = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser) def helpParser(s: State): Parser[() => State] = { diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index d1fc63f7a..9d34c27db 100644 --- a/main-command/src/main/scala/sbt/BasicKeys.scala +++ b/main-command/src/main/scala/sbt/BasicKeys.scala @@ -20,6 +20,13 @@ object BasicKeys { "The location where command line history is persisted.", 40 ) + + val extraMetaSbtFiles = AttributeKey[Seq[File]]( + "extraMetaSbtFile", + "Additional plugin.sbt files.", + 10000 + ) + val shellPrompt = AttributeKey[State => String]( "shell-prompt", "The function that constructs the command prompt from the current build state.", diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index d7dd60054..0e592984d 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -216,6 +216,7 @@ object BuiltinCommands { setLogLevel, plugin, plugins, + addPluginSbtFile, writeSbtVersion, notifyUsersAboutShell, shell, diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index c496a7f32..b0d8b53dc 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -714,7 +714,8 @@ private[sbt] object Load { createRoot, uri, config.pluginManagement.context, - Nil + Nil, + s.get(BasicKeys.extraMetaSbtFiles).getOrElse(Nil) ) val loadedProjectsRaw = timed("Load.loadUnit: loadedProjectsRaw", log) { loadProjects(initialProjects, !hasRootAlreadyDefined) @@ -844,7 +845,8 @@ private[sbt] object Load { makeOrDiscoverRoot: Boolean, buildUri: URI, context: PluginManagement.Context, - generatedConfigClassFiles: Seq[File] + generatedConfigClassFiles: Seq[File], + extraSbtFiles: Seq[File] ): LoadedProjects = /*timed(s"Load.loadTransitive(${ newProjects.map(_.id) })", log)*/ { // load all relevant configuration files (.sbt, as .scala already exists at this point) @@ -869,7 +871,11 @@ private[sbt] object Load { val autoPlugins: Seq[AutoPlugin] = try plugins.detected.deducePluginsFromProject(p1, log) catch { case e: AutoPluginException => throw translateAutoPluginException(e, p) } - val p2 = this.resolveProject(p1, autoPlugins, plugins, injectSettings, memoSettings, log) + val extra = + if (context.globalPluginProject) extraSbtFiles + else Nil + val p2 = + this.resolveProject(p1, autoPlugins, plugins, injectSettings, memoSettings, extra, log) val projectLevelExtra = if (expand) autoPlugins flatMap { _.derivedProjects(p2) map { _.setProjectOrigin(ProjectOrigin.DerivedProject) } @@ -888,7 +894,8 @@ private[sbt] object Load { (root, rest, files, generated) case DiscoveredProjects(None, rest, files, generated) => (p, rest, files, generated) } - val (finalRoot, projectLevelExtra) = finalizeProject(root, files, true) + val (finalRoot, projectLevelExtra) = + finalizeProject(root, files, true) (finalRoot, discovered ++ projectLevelExtra, generated) } @@ -910,11 +917,16 @@ private[sbt] object Load { false, buildUri, context, - generated ++ generatedConfigClassFiles + generated ++ generatedConfigClassFiles, + Nil ) case Nil if makeOrDiscoverRoot => log.debug(s"[Loading] Scanning directory ${buildBase}") - discover(AddSettings.defaultSbtFiles, buildBase) match { + val auto = + if (context.globalPluginProject) + AddSettings.seq(AddSettings.defaultSbtFiles, AddSettings.sbtFiles(extraSbtFiles: _*)) + else AddSettings.defaultSbtFiles + discover(auto, buildBase) match { case DiscoveredProjects(Some(root), discovered, files, generated) => log.debug( s"[Loading] Found root project ${root.id} w/ remaining ${discovered.map(_.id).mkString(",")}" @@ -935,7 +947,8 @@ private[sbt] object Load { false, buildUri, context, - generated ++ generatedConfigClassFiles + generated ++ generatedConfigClassFiles, + Nil ) // Here we need to create a root project... case DiscoveredProjects(None, discovered, files, generated) => @@ -953,6 +966,7 @@ private[sbt] object Load { false, buildUri, context, + Nil, Nil ) val otherGenerated = otherProjects.generatedConfigClassFiles @@ -1014,6 +1028,7 @@ private[sbt] object Load { * @param globalUserSettings All the settings contributed from the ~/.sbt/ directory * @param memoSettings A recording of all loaded files (our files should reside in there). We should need not load any * sbt file to resolve a project. + * @param extraSbtFiles Extra *.sbt files. * @param log A logger to report auto-plugin issues to. */ private[sbt] def resolveProject( @@ -1022,6 +1037,7 @@ private[sbt] object Load { loadedPlugins: LoadedPlugins, globalUserSettings: InjectSettings, memoSettings: mutable.Map[File, LoadedSbtFile], + extraSbtFiles: Seq[File], log: Logger ): Project = timed(s"Load.resolveProject(${p.id})", log) { @@ -1060,7 +1076,17 @@ private[sbt] object Load { b ++ expandSettings(add) } } - expandSettings(AddSettings.allDefaults) + val auto = + if (extraSbtFiles.nonEmpty) + AddSettings.seq( + AddSettings.autoPlugins, + AddSettings.buildScalaFiles, + AddSettings.userSettings, + AddSettings.defaultSbtFiles, + AddSettings.sbtFiles(extraSbtFiles: _*), + ) + else AddSettings.allDefaults + expandSettings(auto) } // Finally, a project we can use in buildStructure. p.copy(settings = allSettings) diff --git a/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala b/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala index b60121458..a971c818a 100644 --- a/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala +++ b/main/src/test/scala/sbt/internal/server/SettingQueryTest.scala @@ -142,6 +142,7 @@ object SettingQueryTest extends org.specs2.mutable.Specification { loadedPlugins, injectSettings, fileToLoadedSbtFileMap, + Nil, state.log ) } diff --git a/notes/1.2.0/add-plugin.md b/notes/1.2.0/add-plugin.md new file mode 100644 index 000000000..913b3309b --- /dev/null +++ b/notes/1.2.0/add-plugin.md @@ -0,0 +1,14 @@ +Adds `--addPluginSbtFile=` command, which adds the given .sbt file to the plugin build. +Using this mechanism editors or IDEs can start a build with required plugin. + +``` +$ cat /tmp/extra.sbt +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.7") + +$ sbt --addPluginSbtFile=/tmp/extra.sbt +... +sbt:helloworld> plugins +In file:/xxxx/hellotest/ + ... + sbtassembly.AssemblyPlugin: enabled in root +```