diff --git a/main/command/src/main/scala/sbt/BasicCommandStrings.scala b/main/command/src/main/scala/sbt/BasicCommandStrings.scala index a5727c2ce..01d69a88d 100644 --- a/main/command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main/command/src/main/scala/sbt/BasicCommandStrings.scala @@ -74,7 +74,8 @@ object BasicCommandStrings { val levelLongOptions = Level.values.toSeq map { "--" + _ } (s.startsWith(EarlyCommand + "(") && s.endsWith(")")) || (levelShortOptions contains s) || - (levelLongOptions contains s) + (levelLongOptions contains s) || + (s.startsWith("-" + AddPluginSbtFileCommand) || s.startsWith("--" + AddPluginSbtFileCommand)) } val OldEarlyCommand = "--" @@ -87,6 +88,14 @@ object BasicCommandStrings { 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 cb4617b57..b85426cd1 100644 --- a/main/command/src/main/scala/sbt/BasicCommands.scala +++ b/main/command/src/main/scala/sbt/BasicCommands.scala @@ -22,20 +22,45 @@ object BasicCommands { def ignore = Command.command(FailureWall)(idFun) def early = Command.arb(earlyParser, earlyHelp) { (s, other) => other :: s } + private[this] def levelParser: Parser[String] = token(Level.Debug.toString) | token(Level.Info.toString) | token(Level.Warn.toString) | token(Level.Error.toString) - private[this] def earlyParser: State => Parser[String] = (s: State) => - (token(EarlyCommand + "(") flatMap { _ => - otherCommandParser(s) <~ token(")") - }) | - (token("-") flatMap { _ => - levelParser - }) | - (token(OldEarlyCommand) flatMap { _ => - levelParser - }) + + 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("-") | 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.make(HelpCommand, helpBrief, helpDetailed)(helpParser) def helpParser(s: State) = diff --git a/main/command/src/main/scala/sbt/BasicKeys.scala b/main/command/src/main/scala/sbt/BasicKeys.scala index fe4abff7d..99d3ece76 100644 --- a/main/command/src/main/scala/sbt/BasicKeys.scala +++ b/main/command/src/main/scala/sbt/BasicKeys.scala @@ -5,6 +5,13 @@ import sbt.template.TemplateResolver object BasicKeys { val historyPath = AttributeKey[Option[File]]("history", "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.", 10000) val watch = AttributeKey[Watched]("watch", "Continuous execution configuration.", 1000) private[sbt] val interactive = AttributeKey[Boolean]("interactive", "True if commands are currently being entered from an interactive environment.", 10) diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index 149c25aa6..2398b30c0 100644 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -479,7 +479,7 @@ object Load { val memoSettings = new mutable.HashMap[File, LoadedSbtFile] def loadProjects(ps: Seq[Project], createRoot: Boolean) = { - val result = loadTransitive(ps, normBase, plugs, () => eval, config.injectSettings, Nil, memoSettings, config.log, createRoot, uri, config.pluginManagement.context, Nil) + val result = loadTransitive(ps, normBase, plugs, () => eval, config.injectSettings, Nil, memoSettings, config.log, createRoot, uri, config.pluginManagement.context, Nil, s.get(BasicKeys.extraMetaSbtFiles).getOrElse(Nil)) result } val loadedProjectsRaw = timed("Load.loadUnit: loadedProjectsRaw", log) { loadProjects(initialProjects, !hasRootAlreadyDefined) } @@ -578,7 +578,8 @@ object Load { makeOrDiscoverRoot: Boolean, buildUri: URI, context: PluginManagement.Context, - generatedConfigClassFiles: Seq[File]): LoadedProjects = + 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) def discover(auto: AddSettings, base: File): DiscoveredProjects = @@ -600,7 +601,11 @@ object Load { 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)} } else Nil @@ -627,21 +632,25 @@ object Load { case Seq(next, rest @ _*) => log.debug(s"[Loading] Loading project ${next.id} @ ${next.base}") val (finished, discovered, generated) = discoverAndLoad(next) - loadTransitive(rest ++ discovered, buildBase, plugins, eval, injectSettings, acc :+ finished, memoSettings, log, false, buildUri, context, generated ++ generatedConfigClassFiles) + loadTransitive(rest ++ discovered, buildBase, plugins, eval, injectSettings, acc :+ finished, memoSettings, log, false, buildUri, context, 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(",")}") val (finalRoot, projectLevelExtra) = timed(s"Load.loadTransitive: finalizeProject($root)", log) { finalizeProject(root, files, true) } - loadTransitive(discovered ++ projectLevelExtra, buildBase, plugins, eval, injectSettings, finalRoot +: acc, memoSettings, log, false, buildUri, context, generated ++ generatedConfigClassFiles) + loadTransitive(discovered ++ projectLevelExtra, buildBase, plugins, eval, injectSettings, finalRoot +: acc, memoSettings, log, false, buildUri, context, generated ++ generatedConfigClassFiles, Nil) // Here we need to create a root project... case DiscoveredProjects(None, discovered, files, generated) => log.debug(s"[Loading] Found non-root projects ${discovered.map(_.id).mkString(",")}") // Here we do something interesting... We need to create an aggregate root project - val otherProjects = loadTransitive(discovered, buildBase, plugins, eval, injectSettings, acc, memoSettings, log, false, buildUri, context, Nil) + val otherProjects = loadTransitive(discovered, buildBase, plugins, eval, injectSettings, acc, memoSettings, log, false, buildUri, context, Nil, Nil) val otherGenerated = otherProjects.generatedConfigClassFiles val existingIds = otherProjects.projects map (_.id) val refs = existingIds map (id => ProjectRef(buildUri, id)) @@ -691,6 +700,7 @@ 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[this] def resolveProject( @@ -699,6 +709,7 @@ object Load { loadedPlugins: sbt.LoadedPlugins, globalUserSettings: InjectSettings, memoSettings: mutable.Map[File, LoadedSbtFile], + extraSbtFiles: Seq[File], log: Logger): Project = timed(s"Load.resolveProject(${p.id})", log) { import AddSettings._ @@ -740,7 +751,10 @@ object Load { case q: Sequence => (Seq.empty[Setting[_]] /: q.sequence) { (b, add) => b ++ expandSettings(add) } } timed(s"Load.resolveProject(${p.id}): expandSettings(...)", log) { - expandSettings(p.auto) + val auto = if (extraSbtFiles.nonEmpty) { + AddSettings.seq(autoPlugins, buildScalaFiles, userSettings, nonAutoPlugins, AddSettings.defaultSbtFiles, AddSettings.sbtFiles(extraSbtFiles: _*)) + } else p.auto + expandSettings(auto) } } // Finally, a project we can use in buildStructure. diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 3f1528f72..4f84f7e98 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -95,6 +95,7 @@ object BuiltinCommands { projects, project, reboot, read, history, set, sessionCommand, inspect, loadProjectImpl, loadFailed, Cross.crossBuild, Cross.switchVersion, PluginCross.pluginCross, PluginCross.pluginSwitch, setOnFailure, clearOnFailure, stashOnFailure, popOnFailure, setLogLevel, plugin, plugins, + addPluginSbtFile, writeSbtVersion, notifyUsersAboutShell, ifLast, multi, shell, continuous, eval, alias, append, last, lastGrep, export, boot, nop, call, exit, early, initialize, act) ++ compatCommands