mirror of https://github.com/sbt/sbt.git
[2.x] fix: Detect alias/task key name conflicts (#8659)
Problem When a user defines an alias with a name that matches an existing task or setting key (e.g., `alias c = compile` when a custom task `c` exists), the alias silently wins and shadows the task. Solution Detect conflicts at alias creation time and fail with an error message: ``` Alias 'c' conflicts with a task or setting key of the same name. Use a different alias name to avoid ambiguity. ```
This commit is contained in:
parent
9388c140fa
commit
d633de5c3f
|
|
@ -471,19 +471,40 @@ object BasicCommands {
|
|||
}(runAlias)
|
||||
|
||||
def runAlias(s: State, args: Option[(String, Option[Option[String]])]): State =
|
||||
runAlias(s, args, _ => Set.empty)
|
||||
|
||||
def runAlias(
|
||||
s: State,
|
||||
args: Option[(String, Option[Option[String]])],
|
||||
definedKeyNames: State => Set[String]
|
||||
): State =
|
||||
args match {
|
||||
case Some(x ~ None) if !x.isEmpty => printAlias(s, x.trim); s
|
||||
case Some(name ~ Some(None)) => removeAlias(s, name.trim)
|
||||
case Some(name ~ Some(Some(value))) => addAlias(s, name.trim, value.trim)
|
||||
case Some(name ~ Some(Some(value))) => addAlias(s, name.trim, value.trim, definedKeyNames)
|
||||
case _ => printAliases(s); s
|
||||
}
|
||||
def addAlias(s: State, name: String, value: String): State =
|
||||
if Command.validID(name) then
|
||||
val removed = removeAlias(s, name)
|
||||
if value.isEmpty then removed else addAlias0(removed, name, value)
|
||||
else
|
||||
addAlias(s, name, value, _ => Set.empty)
|
||||
|
||||
def addAlias(
|
||||
s: State,
|
||||
name: String,
|
||||
value: String,
|
||||
definedKeyNames: State => Set[String]
|
||||
): State =
|
||||
if !Command.validID(name) then
|
||||
System.err.println("Invalid alias name '" + name + "'.")
|
||||
s.fail
|
||||
else if definedKeyNames(s).contains(name) then
|
||||
System.err.println(
|
||||
s"Alias '$name' conflicts with a task or setting key of the same name. " +
|
||||
"Use a different alias name to avoid ambiguity."
|
||||
)
|
||||
s.fail
|
||||
else
|
||||
val removed = removeAlias(s, name)
|
||||
if value.isEmpty then removed else addAlias0(removed, name, value)
|
||||
private def addAlias0(s: State, name: String, value: String): State =
|
||||
s.copy(definedCommands = newAlias(name, value) +: s.definedCommands)
|
||||
|
||||
|
|
|
|||
|
|
@ -305,6 +305,16 @@ import sbt.internal.util.complete.DefaultParsers.*
|
|||
object BuiltinCommands {
|
||||
def initialAttributes = AttributeMap.empty
|
||||
import BasicCommands.exit
|
||||
|
||||
def aliasWithKeyConflictCheck: Command =
|
||||
Command(AliasCommand, Help.more(AliasCommand, AliasDetailed)) { s =>
|
||||
val name = token(OpOrID.examples(aliasNames(s)*))
|
||||
val assign = token(OptSpace ~ '=' ~ OptSpace)
|
||||
val sfree = removeAliases(s)
|
||||
val to = matched(sfree.combinedParser, partial = true).failOnException | any.+.string
|
||||
OptSpace ~> (name ~ (assign ~> to.?).?).?
|
||||
}((s, args) => runAlias(s, args, Project.definedKeyNames))
|
||||
|
||||
def ConsoleCommands: Seq[Command] =
|
||||
Seq(ignore, exit, IvyConsole.command, setLogLevel, early, act, nop)
|
||||
|
||||
|
|
@ -360,8 +370,9 @@ object BuiltinCommands {
|
|||
waitCmd,
|
||||
promptChannel,
|
||||
TestCommand.testOnly,
|
||||
aliasWithKeyConflictCheck,
|
||||
) ++
|
||||
allBasicCommands ++
|
||||
allBasicCommands.filterNot(_.nameOption.contains(AliasCommand)) ++
|
||||
ContinuousCommands.value ++
|
||||
BuildServerProtocol.commands
|
||||
|
||||
|
|
|
|||
|
|
@ -237,6 +237,18 @@ trait ProjectExtra extends Scoped.Syntax:
|
|||
def isProjectLoaded(state: State): Boolean =
|
||||
(state has Keys.sessionSettings) && (state has Keys.stateBuildStructure)
|
||||
|
||||
def definedKeyNames(state: State): Set[String] =
|
||||
if !isProjectLoaded(state) then Set.empty
|
||||
else
|
||||
val keyIndex = structure(state).index.keyIndex
|
||||
val globalKeys = keyIndex.keys(None)
|
||||
val projectKeys = for
|
||||
uri <- keyIndex.buildURIs
|
||||
project <- keyIndex.projects(uri)
|
||||
key <- keyIndex.keys(Some(ProjectRef(uri, project)))
|
||||
yield key
|
||||
globalKeys ++ projectKeys
|
||||
|
||||
def extract(state: State): Extracted = {
|
||||
val se = Project.session(state)
|
||||
val st = Project.structure(state)
|
||||
|
|
@ -281,11 +293,10 @@ trait ProjectExtra extends Scoped.Syntax:
|
|||
.put(sessionSettings, session)
|
||||
.put(Keys.onUnload.key, onUnload)
|
||||
val newState = unloaded.copy(attributes = newAttrs)
|
||||
// TODO: Fix this
|
||||
onLoad(
|
||||
preOnLoad(
|
||||
updateCurrent(newState)
|
||||
) /*LogManager.setGlobalLogLevels(updateCurrent(newState), structure.data)*/
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
val c = taskKey[Unit]("A custom task named 'c'")
|
||||
c := { println("Custom task 'c' executed") }
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Test that alias creation fails when it conflicts with a task key name
|
||||
# The task 'c' is defined in build.sbt, so 'alias c = compile' should fail
|
||||
-> alias c = compile
|
||||
|
||||
# Verify the task 'c' still works
|
||||
> c
|
||||
Loading…
Reference in New Issue