From 3c9d2ff57abf828702fa8589ec89fc660e0419ef Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 22 Feb 2016 12:12:32 +0100 Subject: [PATCH 1/4] Completion for build-level keys sbt's shell provided completion only for keys that were relative to a defined project, but didn't provide completion for keys that belong to the build definition only. This commit fixes this issue by defining a new kind of `Parser` (from which completions are generated) which runs its input simultaneously on distinct parsers. We now define a parser for project-level keys and another parser for build-level keys. These two parsers are eventually combined, and we get the completions of both parsers. Fixes sbt/sbt#2460 --- main/src/main/scala/sbt/internal/Act.scala | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/main/src/main/scala/sbt/internal/Act.scala b/main/src/main/scala/sbt/internal/Act.scala index ca12939a4..f4ad46e2e 100644 --- a/main/src/main/scala/sbt/internal/Act.scala +++ b/main/src/main/scala/sbt/internal/Act.scala @@ -49,12 +49,22 @@ object Act { new ParsedKey(makeScopedKey(proj, conf, task, extra, key), mask) } - for { - rawProject <- optProjectRef(index, current) - proj = resolveProject(rawProject, current) - confAmb <- config(index configs proj) - partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false) - } yield taskKeyExtra(proj, confAmb, partialMask) + val projectKeys = + for { + rawProject <- optProjectRef(index, current) + proj = resolveProject(rawProject, current) + confAmb <- config(index configs proj) + partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false) + } yield taskKeyExtra(proj, confAmb, partialMask) + + val build = Some(BuildRef(current.build)) + val buildKeys = + for { + confAmb <- config(index configs build) + partialMask = ScopeMask(false, confAmb.isExplicit, false, false) + } yield taskKeyExtra(build, confAmb, partialMask) + + buildKeys combinedWith projectKeys map (_.flatten) } def makeScopedKey(proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]], extra: ScopeAxis[AttributeMap], key: AttributeKey[_]): ScopedKey[_] = ScopedKey(Scope(toAxis(proj, Global), toAxis(conf map ConfigKey.apply, Global), toAxis(task, Global), extra), key) From 3da38b14db4651dca5046127383e37367aa975e3 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Wed, 24 Aug 2016 11:13:08 +0200 Subject: [PATCH 2/4] Fix key selection for build level keys PR #2469 added build keys to tab completion, with the side effect of considering as available candidate in key selection, thus making sbt think that some inputs were ambiguous (e.g. `baseDirectory`): should it apply to the current project or to the build level key? This commit fixes this issue by improving the key selection: - If there's no candidate, we return the default key - If there's a single possible project level key, and zero or more build level keys, then we select the project level key. - If there are zero project level key, and a single build level key, then we select the build level key - If there are multiple candidates, sbt says that the input is ambiguous. Fixes #2707 --- main/src/main/scala/sbt/internal/Act.scala | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/internal/Act.scala b/main/src/main/scala/sbt/internal/Act.scala index f4ad46e2e..f3da90b61 100644 --- a/main/src/main/scala/sbt/internal/Act.scala +++ b/main/src/main/scala/sbt/internal/Act.scala @@ -78,11 +78,16 @@ object Act { selectFromValid(ss filter isValid(data), default) } def selectFromValid(ss: Seq[ParsedKey], default: Parser[ParsedKey])(implicit show: Show[ScopedKey[_]]): Parser[ParsedKey] = - selectByTask(selectByConfig(ss)) match { - case Seq() => default - case Seq(single) => success(single) - case multi => failure("Ambiguous keys: " + showAmbiguous(keys(multi))) + selectByTask(selectByConfig(ss)) partition isBuildKey match { + case (_, Seq(single)) => success(single) + case (Seq(single), Seq()) => success(single) + case (Seq(), Seq()) => default + case (buildKeys, projectKeys) => failure("Ambiguous keys: " + showAmbiguous(keys(buildKeys ++ projectKeys))) } + private def isBuildKey(parsed: ParsedKey): Boolean = parsed.key.scope.project match { + case Select(_: BuildReference) => true + case _ => false + } private[this] def keys(ss: Seq[ParsedKey]): Seq[ScopedKey[_]] = ss.map(_.key) def selectByConfig(ss: Seq[ParsedKey]): Seq[ParsedKey] = ss match { From cfdbce10da5928a8fa41bd4c61229bcc07966fa8 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Thu, 25 Aug 2016 15:59:43 +0200 Subject: [PATCH 3/4] Scripted test for build level keys. --- sbt/src/sbt-test/project/build-level-keys/test | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 sbt/src/sbt-test/project/build-level-keys/test diff --git a/sbt/src/sbt-test/project/build-level-keys/test b/sbt/src/sbt-test/project/build-level-keys/test new file mode 100644 index 000000000..a04b77809 --- /dev/null +++ b/sbt/src/sbt-test/project/build-level-keys/test @@ -0,0 +1,4 @@ +> baseDirectory + +> {.}/baseDirectory + From faf6120c9353e48ecb9170ba3f01198f4a4682cf Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 22 Feb 2016 12:58:30 +0100 Subject: [PATCH 4/4] Unspecified project axis means current project or its build --- main/src/test/scala/ParseKey.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/test/scala/ParseKey.scala b/main/src/test/scala/ParseKey.scala index aebf90ca6..4d1c5f3e7 100644 --- a/main/src/test/scala/ParseKey.scala +++ b/main/src/test/scala/ParseKey.scala @@ -32,7 +32,7 @@ object ParseKey extends Properties("Key parser test") { parseExpected(structure, string, expected, mask) } - property("An unspecified project axis resolves to the current project") = + property("An unspecified project axis resolves to the current project or the build of the current project") = forAllNoShrink(structureDefinedKey) { (skm: StructureKeyMask) => import skm.{ structure, key } @@ -44,7 +44,7 @@ object ParseKey extends Properties("Key parser test") { ("Current: " + structure.current) |: parse(structure, string) { case Left(err) => false - case Right(sk) => sk.scope.project == Select(structure.current) + case Right(sk) => sk.scope.project == Select(structure.current) || sk.scope.project == Select(BuildRef(structure.current.build)) } }