Merge pull request #1 from sbt/develop

Pull latest changes
This commit is contained in:
Ashley Mercer 2021-04-02 12:34:58 +01:00 committed by GitHub
commit 255f9c5553
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2095 changed files with 94997 additions and 15114 deletions

162
.appveyor.yml Normal file
View File

@ -0,0 +1,162 @@
image:
- MacOS
- Visual Studio 2015
- Visual Studio 2019
- Ubuntu
build: off
init:
- git config --global core.autocrlf input
for:
-
matrix:
only:
- image: Ubuntu
branches:
only:
- build-graal
artifacts:
- path: client/target/bin/sbtn
name: sbtn
install:
- curl -sL https://github.com/sbt/sbt/releases/download/v1.3.10/sbt-1.3.10.tgz > ~/sbt-bin.tgz
- mkdir ~/sbt
- tar -xf ~/sbt-bin.tgz --directory ~/sbt
- curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.11.0/install.sh | bash && . ~/.jabba/jabba.sh
- jabba install adopt@1.8.0-275
- jabba use adopt@1.8.0-275
- curl -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java8-linux-amd64-20.1.0.tar.gz > graalvm.tar.gz
- tar -xf graalvm.tar.gz
- export PATH="~/sbt/sbt/bin:$PATH"
- export PATH="$PATH:~/.jabba/jdk/adopt@1.8.0-275/bin"
- export JAVA_HOME="~/.jabba/jdk/adopt@1.8.0-275"
test_script:
- export PATH="$PATH:~/.jabba/jdk/adopt@1.8.0-275/bin"
- export PATH="$PATH:graalvm-ce-java8-20.1.0/bin"
- gu install native-image
- sbt "-Dsbt.io.virtual=false" "-Dsbt.native-image=$(pwd)/graalvm-ce-java8-20.1.0/bin/native-image" "sbtClientProj/buildNativeThinClient"
-
matrix:
only:
- image: MacOS
branches:
only:
- build-graal
artifacts:
- path: client/target/bin/sbtn
name: mac-native-sbt-client
install:
- curl -sL https://github.com/sbt/sbt/releases/download/v1.3.10/sbt-1.3.10.tgz > ~/sbt-bin.tgz
- mkdir ~/sbt
- tar -xf ~/sbt-bin.tgz --directory ~/sbt
- curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.11.0/install.sh | bash && . ~/.jabba/jabba.sh
- jabba install adopt@1.8.0-222
- jabba use adopt@1.8.0-222
- curl -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java8-darwin-amd64-20.1.0.tar.gz > graalvm.tar.gz
- tar -xf graalvm.tar.gz
- export PATH="~/sbt/sbt/bin:$PATH"
- export PATH="$PATH:~/.jabba/jdk/adopt@1.8.0-222/bin"
- export JAVA_HOME="~/.jabba/jdk/adopt@1.8.0-222"
test_script:
- export PATH="$PATH:~/.jabba/jdk/adopt@1.8.0-222/Contents/Home/bin"
- export PATH="$PATH:graalvm-ce-java8-20.1.0/Contents/Home/bin"
- gu install native-image
- sbt "-Dsbt.io.virtual=false" "-Dsbt.native-image=$(pwd)/graalvm-ce-java8-20.1.0/Contents/Home/bin/native-image" "sbtClientProj/buildNativeThinClient"
-
matrix:
only:
- image: Visual Studio 2015
branches:
only:
- build-graal
artifacts:
- path: client\target\bin\sbtn.exe
name: sbtn.exe
install:
- cinst adoptopenjdk8 -params 'installdir=C:\\jdk8'
- SET CI=true
#- choco install windows-sdk-7.1 kb2519277
- call "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd"
- ps: |
Add-Type -AssemblyName System.IO.Compression.FileSystem
if (!(Test-Path -Path "C:\sbt" )) {
(new-object System.Net.WebClient).DownloadFile(
'https://github.com/sbt/sbt/releases/download/v1.3.10/sbt-1.3.10.zip',
'C:\sbt-bin.zip'
)
[System.IO.Compression.ZipFile]::ExtractToDirectory("C:\sbt-bin.zip", "C:\sbt")
}
if (!(Test-Path -Path "C:\graalvm-ce-java8-20.2.0-dev" )) {
(new-object System.Net.WebClient).DownloadFile(
'https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java8-windows-amd64-20.1.0.zip',
'C:\graalvm-ce-java8-20.1.0.zip'
)
[System.IO.Compression.ZipFile]::ExtractToDirectory("C:\graalvm-ce-java8-20.1.0.zip", "C:\")
}
if (!(Test-Path -Path "C:\zulu-jdk7" )) {
(new-object System.Net.WebClient).DownloadFile(
'https://cdn.azul.com/zulu/bin/zulu7.38.0.11-ca-jdk7.0.262-win_x64.zip',
'C:\zulu-jdk7.zip'
)
[System.IO.Compression.ZipFile]::ExtractToDirectory("C:\zulu-jdk7.zip", "C:\")
}
- SET PATH=C:\graalvm-ce-java8-20.1.0\bin;%PATH%
- SET PATH=C:\sbt\sbt\bin;%PATH%
- SET JAVA_HOME=C:\jdk8
- gu install native-image
- rm .sbtopts
cache:
- '%USERPROFILE%\.ivy2\cache'
- '%LOCALAPPDATA%\Coursier\Cache\v1'
- '%USERPROFILE%\.sbt'
test_script:
- sbt "-Dsbt.io.virtual=false" "-Dsbt.native-image=C:\graalvm-ce-java8-20.1.0\bin\native-image.cmd" "sbtClientProj/buildNativeThinClient"
-
matrix:
only:
- image: Visual Studio 2019
branches:
except:
- build-graal
install:
- cinst adoptopenjdk8 -params 'installdir=C:\\jdk8'
- SET JAVA_HOME=C:\jdk8
- SET PATH=C:\jdk8\bin;%PATH%
- SET CI=true
- ps: |
Add-Type -AssemblyName System.IO.Compression.FileSystem
if (!(Test-Path -Path "C:\sbt" )) {
(new-object System.Net.WebClient).DownloadFile(
'https://github.com/sbt/sbt/releases/download/v1.3.10/sbt-1.3.10.zip',
'C:\sbt-bin.zip'
)
[System.IO.Compression.ZipFile]::ExtractToDirectory("C:\sbt-bin.zip", "C:\sbt")
}
- SET PATH=C:\sbt\sbt\bin;%PATH%
- SET SBT_OPTS=-Xmx4g -Dsbt.supershell=never -Dfile.encoding=UTF8
- rm .sbtopts
cache:
- '%USERPROFILE%\.ivy2\cache'
- '%LOCALAPPDATA%\Coursier\Cache\v1'
- '%USERPROFILE%\.sbt'
test_script:
# The server tests often fail in CI when run together so just run a single test to ensure
# that the thin client works on windows
- sbt "-Dsbt.io.virtual=false" "scripted actions/* reporter/source-mapper classloader-cache/* nio/* watch/*" "serverTestProj/testOnly testpkg.ClientTest"

11
.gitattributes vendored
View File

@ -1,7 +1,4 @@
# Set default behaviour, in case users don't have core.autocrlf set.
* text=auto
# Explicitly declare text files we want to always be normalized and converted
# to native line endings on checkout.
*.scala text
*.java text
# Exclude contraband generated files from diff (by default - you can see it if you want)
**/contraband-scala/**/* -diff merge=ours
**/contraband-scala/**/* linguist-generated=true
**/contraband-scala/**/* diff

22
.github/ISSUE_TEMPLATE/---bug-report.md vendored Normal file
View File

@ -0,0 +1,22 @@
---
name: "\U0001F41B Bug report"
about: Create a report to help us improve
title: ''
labels: Bug
assignees: ''
---
## steps
<!-- Describe exact steps to reproduce your problems on our computer, including sbt version and build.sbt -->
## problem
<!-- Next, describe the problem, or what you think is the problem. -->
## expectation
<!-- Describe what you think should've happened. -->
## notes

View File

@ -0,0 +1,10 @@
---
name: "\U0001F389 Feature request"
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
Please use https://discuss.lightbend.com/c/tooling including a specific user story instead of posting them to the issue tracker.

10
.github/ISSUE_TEMPLATE/--question.md vendored Normal file
View File

@ -0,0 +1,10 @@
---
name: "❓ Question"
about: Please use https://stackoverflow.com/questions/tagged/sbt for questions
title: ''
labels: ''
assignees: ''
---
Please use https://stackoverflow.com/questions/tagged/sbt for questions instead of posting them to the issue tracker.

6
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

108
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,108 @@
name: CI
on:
pull_request:
push:
jobs:
test:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
java: 11
jobtype: 1
- os: ubuntu-latest
java: 11
jobtype: 2
- os: ubuntu-latest
java: 11
jobtype: 3
- os: ubuntu-latest
java: 11
jobtype: 4
- os: ubuntu-latest
java: 11
jobtype: 5
- os: ubuntu-latest
java: 8
jobtype: 6
runs-on: ${{ matrix.os }}
env:
JAVA_OPTS: -Xms800M -Xmx2G -Xss6M -XX:ReservedCodeCacheSize=128M -server -Dsbt.io.virtual=false -Dfile.encoding=UTF-8
SCALA_212: 2.12.13
SCALA_213: 2.13.3
UTIL_TESTS: utilCache/test;utilControl/test;utilInterface/test;utilLogging/test;utilPosition/test;utilRelation/test;utilScripted/test;utilTracking/test
SBT_LOCAL: false
steps:
- name: Checkout sbt/sbt
uses: actions/checkout@v2
- name: Checkout sbt/io
uses: actions/checkout@v2
with:
repository: sbt/io
ref: develop
path: io
- name: Checkout sbt/librarymanagement
uses: actions/checkout@v2
with:
repository: sbt/librarymanagement
ref: develop
path: librarymanagement
- name: Checkout sbt/zinc
uses: actions/checkout@v2
with:
repository: sbt/zinc
ref: develop
path: zinc
- name: Setup
uses: olafurpg/setup-scala@v10
with:
java-version: "adopt@1.${{ matrix.java }}"
- name: Coursier cache
uses: coursier/cache-action@v6
- name: Cache sbt
uses: actions/cache@v2.1.3
with:
path: ~/.sbt
key: ${{ runner.os }}-sbt-cache-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}
- name: Build and test
run: |
rm -rf "$HOME/.sbt/scripted/" || true
case ${{ matrix.jobtype }} in
1)
sbt -v "mimaReportBinaryIssues ; javafmtCheck ; Test / javafmtCheck; scalafmtCheckAll ; scalafmtSbtCheck; serverTestProj/scalafmtCheckAll; headerCheck ;test:headerCheck ;whitesourceOnPush ;test:compile; publishLocal; test; serverTestProj/test; doc; $UTIL_TESTS; ++$SCALA_213; $UTIL_TESTS"
;;
2)
sbt -v "scripted actions/* apiinfo/* compiler-project/* ivy-deps-management/* reporter/* tests/* watch/* classloader-cache/* package/*"
;;
3)
sbt -v "dependencyTreeProj/publishLocal; scripted dependency-graph/* dependency-management/* plugins/* project-load/* java/* run/* nio/*"
;;
4)
sbt -v "repoOverrideTest:scripted dependency-management/*; scripted source-dependencies/* project/*"
;;
5)
sbt -v "++$SCALA_213!; test;"
;;
6)
# build from fresh IO, LM, and Zinc
BUILD_VERSION="1.5.0-SNAPSHOT"
cd io
sbt -v -Dsbt.build.version=${BUILD_VERSION} +publishLocal
cd ../
sbt -Dsbtlm.path=$HOME/work/sbt/sbt/librarymanagement -Dsbtzinc.path=$HOME/work/sbt/sbt/zinc -Dsbt.build.version=$BUILD_VERSION -Dsbt.build.fatal=false "+lowerUtils/publishLocal; {librarymanagement}/publishLocal; {zinc}/publishLocal; upperModules/publishLocal"
rm -r $(find $HOME/.sbt/boot -name "*-SNAPSHOT") || true
sbt -v -Dsbt.version=$BUILD_VERSION "++$SCALA_213; $UTIL_TESTS; ++$SCALA_212; $UTIL_TESTS; scripted actions/* source-dependencies/*1of3 dependency-management/*1of4 java/*"
;;
*)
echo unknown jobtype
exit 1
esac
rm -rf "$HOME/.sbt/scripted/" || true
rm -rf "$HOME/.ivy2/local" || true
rm -r $(find $HOME/.sbt/boot -name "*-SNAPSHOT") || true
find $HOME/Library/Caches/Coursier/v1 -name "ivydata-*.properties" -delete || true
find $HOME/.ivy2/cache -name "ivydata-*.properties" -delete || true
find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete || true
find $HOME/.sbt -name "*.lock" -delete || true

63
.github/workflows/nightly.yml vendored Normal file
View File

@ -0,0 +1,63 @@
name: Nightly
on:
schedule:
# 08:00 UTC = 03:00 EST
- cron: '0 8 * * *'
jobs:
deploy:
strategy:
matrix:
include:
- os: ubuntu-latest
java: 8
runs-on: ${{ matrix.os }}
env:
JAVA_OPTS: -Xms800M -Xmx800M -Xss6M -XX:ReservedCodeCacheSize=128M -server -Dsbt.io.virtual=false -Dfile.encoding=UTF-8
steps:
- name: Checkout sbt/sbt
uses: actions/checkout@v2
- name: Checkout sbt/io
uses: actions/checkout@v2
with:
repository: sbt/io
ref: develop
path: io
- name: Checkout sbt/librarymanagement
uses: actions/checkout@v2
with:
repository: sbt/librarymanagement
ref: develop
path: librarymanagement
- name: Checkout sbt/zinc
uses: actions/checkout@v2
with:
repository: sbt/zinc
ref: develop
path: zinc
- name: Setup
uses: olafurpg/setup-scala@v10
with:
java-version: "adopt@1.${{ matrix.java }}"
- name: Coursier cache
uses: coursier/cache-action@v6
- name: Build and deploy
run: |
# build from fresh IO, LM, and Zinc
TIMESTAMP=$(TZ=UTC date +%Y%m%dT%H%M%S)
export BUILD_VERSION="1.5.0-bin-${TIMESTAMP}"
cd io
sbt -v +publish
cd ../
sbt -Dsbtlm.path=$HOME/work/sbt/sbt/librarymanagement -Dsbtzinc.path=$HOME/work/sbt/sbt/zinc -Dsbt.build.fatal=false "+lowerUtils/publish; {librarymanagement}/publish; {zinc}/publish; upperModules/publish; bundledLauncherProj/publish"
rm -rf "$HOME/.ivy2/local" || true
rm -r $(find $HOME/.sbt/boot -name "*-SNAPSHOT") || true
find $HOME/Library/Caches/Coursier/v1 -name "ivydata-*.properties" -delete || true
find $HOME/.ivy2/cache -name "ivydata-*.properties" -delete || true
find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete || true
find $HOME/.sbt -name "*.lock" -delete || true
env:
BINTRAY_USER: ${{ secrets.BINTRAY_USER }}
BINTRAY_PASS: ${{ secrets.BINTRAY_PASS }}
BINTRAY_REPOSITORY: maven-snapshots
BINTRAY_PACKAGE: sbt

12
.gitignore vendored
View File

@ -1,2 +1,14 @@
target/
__pycache__
out
node_modules
vscode-sbt-scala/client/server
npm-debug.log
*.vsix
!sbt/src/server-test/completions/target
.big
.idea
.bloop
.metals
.bsp/
metals.sbt

7
.mailmap Normal file
View File

@ -0,0 +1,7 @@
Andrea Peruffo <andrea.peruffo1982@gmail.com>
Ethan Atkins <github@ethanatkins.com>
Ethan Atkins <github@ethanatkins.com> <ethan.atkins@gmail.com>
Eugene Yokota (eed3si9n) <eed3si9n@gmail.com>
Jorge Vicente Cantero <jorgevc@fastmail.es>
Kenji Yoshida (xuwei-k) <6b656e6a69@gmail.com>
Yasuhiro Tatsuno <mogami@exoego.net>

1
.sbtopts Normal file
View File

@ -0,0 +1 @@
-J-Xmx2G

23
.scalafmt.conf Normal file
View File

@ -0,0 +1,23 @@
version = 2.3.2
edition = 2019-10
maxColumn = 100
project.git = true
project.excludeFilters = [ "\\Wsbt-test\\W", "\\Winput_sources\\W", "\\Wcontraband-scala\\W" ]
lineEndings = preserve
# https://docs.scala-lang.org/style/scaladoc.html recommends the JavaDoc style.
# scala/scala is written that way too https://github.com/scala/scala/blob/v2.12.2/src/library/scala/Predef.scala
docstrings = JavaDoc
# This also seems more idiomatic to include whitespace in import x.{ yyy }
spaces.inImportCurlyBraces = true
# This is more idiomatic Scala.
# https://docs.scala-lang.org/style/indentation.html#methods-with-numerous-arguments
align.openParenCallSite = false
align.openParenDefnSite = false
# For better code clarity
danglingParentheses = true
trailingCommas = preserve

View File

@ -1,51 +0,0 @@
# Use Docker-based container (instead of OpenVZ)
sudo: false
cache:
directories:
- $HOME/.ivy2/cache
- $HOME/.sbt/boot
language: scala
jdk:
- oraclejdk8
matrix:
fast_finish: true
env:
matrix:
- SBT_CMD=";test:compile;scalariformCheck"
- SBT_CMD="mimaReportBinaryIssues"
- SBT_CMD="safeUnitTests"
- SBT_CMD="otherUnitTests"
- SBT_CMD="scripted actions/*"
- SBT_CMD="scripted apiinfo/*"
- SBT_CMD="scripted compiler-project/*"
- SBT_CMD="scripted dependency-management/*1of2"
- SBT_CMD="scripted dependency-management/*2of2"
- SBT_CMD="scripted ivy-deps-management/*"
- SBT_CMD="scripted java/*"
- SBT_CMD="scripted package/*"
- SBT_CMD="scripted project/*1of2"
- SBT_CMD="scripted project/*2of2"
- SBT_CMD="scripted reporter/*"
- SBT_CMD="scripted run/*"
- SBT_CMD="scripted source-dependencies/*1of3"
- SBT_CMD="scripted source-dependencies/*2of3"
- SBT_CMD="scripted source-dependencies/*3of3"
- SBT_CMD="scripted tests/*"
- SBT_CMD="scripted project-load/*"
- SBT_CMD="repoOverrideTest:scripted dependency-management/*"
notifications:
email:
- sbt-dev-bot@googlegroups.com
script:
- sbt -J-XX:ReservedCodeCacheSize=128m "$SBT_CMD"
before_cache:
- find $HOME/.ivy2 -name "ivydata-*.properties" -print -delete
- find $HOME/.sbt -name "*.lock" -print -delete

View File

@ -1,57 +1,72 @@
[StackOverflow]: http://stackoverflow.com/tags/sbt
[ask]: https://stackoverflow.com/questions/ask?tags=sbt
[Setup]: http://www.scala-sbt.org/release/docs/Getting-Started/Setup
[StackOverflow]: https://stackoverflow.com/tags/sbt
[Setup]: https://www.scala-sbt.org/release/docs/Getting-Started/Setup
[Issues]: https://github.com/sbt/sbt/issues
[sbt-dev]: https://groups.google.com/d/forum/sbt-dev
[subscriptions]: http://typesafe.com/how/subscription
[sbt-contrib]: https://gitter.im/sbt/sbt-contrib
[327]: https://github.com/sbt/sbt/issues/327
[documentation]: https://github.com/sbt/website
Contributing
============
(For support, see [SUPPORT](./SUPPORT.md))
There are lots of ways to contribute to sbt ecosystem depending on your interests and skill level.
- Help someone at work or online fix their build problem.
- Answer StackOverflow questions.
- Ask StackOverflow questions.
- Create plugins that extend sbt's features.
- Maintain and update [documentation].
- Garden the issue tracker.
- Report issues.
- Patch the core (send pull requests to code).
- On-ramp other contributors.
Issues and Pull Requests
========================
------------------------
When you find a bug in sbt we want to hear about it. Your bug reports play an important part in making sbt more reliable and usable.
Effective bug reports are more likely to be fixed. These guidelines explain how to write such reports and pull requests.
Preliminaries
--------------
Please open a GitHub issue when you are 90% sure it's an actual bug.
If you have an enhancement idea, or a general discussion, bring it up to [sbt-contrib].
### Notes about Documentation
Documentation fixes and contributions are as much welcome as to patching the core. Visit [sbt/website][documentation] to learn about how to contribute.
### Preliminaries
- Make sure your sbt version is up to date.
- Search [StackOverflow] and [Issues] to see whether your bug has already been reported.
- Open one case for each problem.
- Proceed to the next steps for details.
Where to get help and/or file a bug report
------------------------------------------
sbt project uses GitHub Issues as a publicly visible todo list. Please open a GitHub issue only when asked to do so.
- If you need help with sbt, please [ask] on StackOverflow with the tag "sbt" and the name of the sbt plugin if any.
- If you run into an issue, have an enhancement idea, or a general discussion, bring it up to [sbt-dev] Google Group first.
- If you need a faster response time, consider one of the [Typesafe subscriptions][subscriptions].
What to report
--------------
### What to report
The developers need three things from you: **steps**, **problems**, and **expectations**.
### Steps
The most important thing to remember about bug reporting is to clearly distinguish facts and opinions.
The most important thing to remember about bug reporting is to clearly distinguish facts and opinions. What we need first is **the exact steps to reproduce your problems on our computers**. This is called *reproduction steps*, which is often shortened to "repro steps" or "steps." Describe your method of running sbt. Provide `build.sbt` that caused the problem and the version of sbt or Scala that was used. Provide sample Scala code if it's to do with incremental compilation. If possible, minimize the problem to reduce non-essential factors.
#### Steps
What we need first is **the exact steps to reproduce your problems on our computers**. This is called *reproduction steps*, which is often shortened to "repro steps" or "steps." Describe your method of running sbt. Provide `build.sbt` that caused the problem and the version of sbt or Scala that was used. Provide sample Scala code if it's to do with incremental compilation. If possible, minimize the problem to reduce non-essential factors.
Repro steps are the most important part of a bug report. If we cannot reproduce the problem in one way or the other, the problem can't be fixed. Telling us the error messages is not enough.
### Problems
#### Problems
Next, describe the problems, or what *you think* is the problem. It might be "obvious" to you that it's a problem, but it could actually be an intentional behavior for some backward compatibility etc. For compilation errors, include the stack trace. The more raw info the better.
### Expectations
#### Expectations
Same as the problems. Describe what *you think* should've happened.
### Notes
#### Notes
Add an optional notes section to describe your analysis.
Add any optional notes section to describe your analysis.
### Subject
@ -61,7 +76,7 @@ The subject of the bug report doesn't matter. A more descriptive subject is cert
If possible, please format code or console outputs.
On Github it's:
On GitHub it's:
```scala
name := "foo"
@ -81,14 +96,11 @@ Finally, thank you for taking the time to report a problem.
Pull Requests
-------------
### Branch to work against
Whether implementing a new feature, fixing a bug, or modifying documentation, please work against the latest development branch (currently, 0.13).
See below for instructions on building sbt from source.
See below for the branch to work against.
### Adding notes
All pull requests are required to include a "Notes" file which documents the change. This file should reside in the
Most pull requests should include a "Notes" file which documents the change. This file should reside in the
directory:
<sbt root>
@ -111,92 +123,38 @@ Make sure you document each commit and squash them appropriately. You can use th
* Scala's documentation on [Git Hygiene](https://github.com/scala/scala/tree/v2.12.0-M3#git-hygiene)
* Play's documentation on [Working with Git](https://www.playframework.com/documentation/2.4.4/WorkingWithGit#Squashing-commits)
Documentation
-------------
Documentation fixes and contributions are as much welcome as to the source code itself. Visit [the website project](https://github.com/sbt/website) to learn about how to contribute.
Build from source
=================
1. Install the current stable binary release of sbt (see [Setup]), which will be used to build sbt from source.
2. Get the source code.
$ git clone git://github.com/sbt/sbt.git
$ cd sbt
3. The default branch is the development branch [0.13](https://github.com/sbt/sbt/tree/0.13), which contains the latest code for the next major sbt release. To build a specific release or commit, switch to the associated tag. The tag for the latest stable release is [v0.13.9](https://github.com/sbt/sbt/tree/v0.13.9):
$ git checkout v0.13.9
Note that sbt is always built with the previous stable release. For example, the [0.13](https://github.com/sbt/sbt/tree/0.13) branch is built with 0.13.9 and the [v0.13.9](https://github.com/sbt/sbt/tree/v0.13.9) tag is built with 0.13.8.
4. To build the launcher and publish all components locally,
$ sbt
> publishLocal
5. To use this locally built version of sbt, copy your stable `~/bin/sbt` script to `~/bin/xsbt` and change it to use the launcher jar at `<sbt>/launch/target/sbt-launch.jar`.
Directory `target` is removed by `clean` command. Second solution is using the artifact stored in the local ivy repository.
The launcher is located in:
$HOME/.ivy2/local/org.scala-sbt/sbt-launch/0.13.9/jars/sbt-launch.jar
for v0.13.9 tag, or in:
$HOME/.ivy2/local/org.scala-sbt/sbt-launch/0.13.10-SNAPSHOT/jars/sbt-launch.jar
for the development branch.
## Modifying sbt
1. When developing sbt itself, run `compile` when checking compilation only.
2. To use your modified version of sbt in a project locally, run `publishLocal`.
3. After each `publishLocal`, clean the `~/.sbt/boot/` directory. Alternatively, if sbt is running and the launcher hasn't changed, run `reboot full` to have sbt do this for you.
4. If a project has `project/build.properties` defined, either delete the file or change `sbt.version` to `0.13.10-SNAPSHOT`.
## Diagnosing build failures
Globally included plugins can interfere building `sbt`; if you are getting errors building sbt, try disabling all globally included plugins and try again.
Running Tests
=============
sbt has an extensive test suite of Unit tests and Integration tests!
Unit / Functional tests
-----------------------
Various functional and unit tests are defined throughout the
project. To run all of them, run `sbt test`. You can run a single test
suite with `sbt testOnly`
Integration tests
-----------------
Scripted integration tests reside in `sbt/src/sbt-test` and are
written using the same testing infrastructure sbt plugin authors can
use to test their own plugins with sbt. You can read more about this
style of tests [here](http://eed3si9n.com/testing-sbt-plugins).
See [DEVELOPING](./DEVELOPING.md)
You can run the integration tests with the `sbt scripted` sbt
command. To run a single test, such as the test in
`sbt/src/sbt-test/project/global-plugin`, simply run:
Profiling sbt
-------------
sbt "scripted project/global-plugin"
See [PROFILING](./PROFILING.md)
Please note that these tests run PAINFULLY slow if the version set in
`build.sbt` is set to SNAPSHOT, as every time the scripted test boots
up a test instance of sbt, remote mirrors are scanned for possible
updates. It is recommended that you set the version suffix to
`-devel`, as in `0.13.10-devel`.
Other notes for maintainers
---------------------------
Building Documentation
======================
### Publishing VS Code Extensions
The scala-sbt.org site documentation is a separate project [website](https://github.com/sbt/website). Follow [the steps in the README](https://github.com/sbt/website#scala-sbtorg) to generate the documentation.
Reference https://code.visualstudio.com/docs/extensions/publish-extension
```
$ sbt
> vscodePlugin/compile
> exit
cd vscode-sbt-scala/client
# update version number in vscode-sbt-scala/client/package.json
$ vsce package
$ vsce publish
```
## Signing the CLA
Contributing to sbt requires you or your employer to sign the
[Lightbend Contributor License Agreement](https://www.lightbend.com/contribute/cla).
To make it easier to respect our license agreements, we have added an sbt task
that takes care of adding the LICENSE headers to new files. Run `headerCreate`
and sbt will put a copyright notice into it.

154
DEVELOPING.md Normal file
View File

@ -0,0 +1,154 @@
Developer guide
===============
### Branch to work against
sbt uses two branches for development:
- Development branch: `develop` (this is also called "master")
- Stable branch: `1.$MINOR.x`, where `$MINOR` is current minor version (e.g. `1.1.x` during 1.1.x series)
### Instruction to build just sbt
If the change you are making is contained in sbt/sbt, you could publishLocal on sbt/sbt:
```
$ sbt
sbt:sbtRoot> publishLocal
```
### Instruction to build all modules from source
1. Install the current stable binary release of sbt (see [Setup]), which will be used to build sbt from source.
2. Get the source code.
```
$ mkdir sbt-modules
$ cd sbt-modules
$ for i in sbt io librarymanagement zinc; do \
git clone https://github.com/sbt/$i.git && (cd $i; git checkout -b develop origin/develop)
done
$ cd sbt
$ ./sbt-allsources.sh
```
3. To build and publish all components locally,
```
$ ./sbt-allsources.sh
sbt:sbtRoot> publishLocalAllModule
```
### Using the locally built sbt
The `publishLocal` above will build and publish version `1.$MINOR.$PATCH-SNAPSHOT` (e.g. 1.1.2-SNAPSHOT) to your local ivy repository.
To use the locally built sbt, set the version in `build.properties` file in your project to `1.$MINOR.$PATCH-SNAPSHOT` then launch `sbt` (this can be the `sbt` launcher installed in your machine).
```
$ cd $YOUR_OWN_PROJECT
$ sbt
> compile
```
### Nightly builds
The latest development versions are available as nightly builds on sbt-maven-snapshots (<https://repo.scala-sbt.org/scalasbt/maven-snapshots>) repo, which is a redirect proxy whose underlying repository is subject to change it could be Bintray, Linux box, etc.
To use a nightly build:
1. Find out a version from [/org/scala-sbt/sbt/](https://repo.scala-sbt.org/scalasbt/maven-snapshots/org/scala-sbt/sbt/).
2. Put the version, for example `sbt.version=1.5.0-bin-20201121T081131` in `project/build.properties`.
sbt launcher will resolve the specified sbt core artifacts. Because of the aforementioned redirection, this resolution is going to be very slow for the first time you run sbt, and then it should be ok for subsequent runs.
Unless you're debugging the `sbt` script or the launcher JAR, you should be able to use any recent stable version of sbt installation as the launcher following the [Setup][Setup] instructions first.
If you're overriding the repositories via `~/.sbt/repositories`, make sure that there's a following entry:
```
[repositories]
...
sbt-maven-snapshots: https://repo.scala-sbt.org/scalasbt/maven-snapshots/, bootOnly
```
### Clearing out boot and local cache
When you run a locally built sbt, the JAR artifacts will be now cached under `$HOME/.sbt/boot/scala-2.12.6/org.scala-sbt/sbt/1.$MINOR.$PATCH-SNAPSHOT` directory. To clear this out run: `reboot dev` command from sbt's session of your test application.
One drawback of `-SNAPSHOT` version is that it's slow to resolve as it tries to hit all the resolvers. You can workaround that by using a version name like `1.$MINOR.$PATCH-LOCAL1`. A non-SNAPSHOT artifacts will now be cached under `$HOME/.ivy/cache/` directory, so you need to clear that out using [sbt-dirty-money](https://github.com/sbt/sbt-dirty-money)'s `cleanCache` task.
### Running sbt "from source" - `sbtOn`
In addition to locally publishing a build of sbt, there is an alternative, experimental launcher within sbt/sbt
to be able to run sbt "from source", that is to compile sbt and run it from its resulting classfiles rather than
from published jar files.
Such a launcher is available within sbt/sbt's build through a custom `sbtOn` command that takes as its first
argument the directory on which you want to run sbt, and the remaining arguments are passed _to_ that sbt
instance. For example:
I have setup a minimal sbt build in the directory `/s/t`, to run sbt on that directory I call:
```bash
> sbtOn /s/t
[info] Packaging /d/sbt/scripted/sbt/target/scala-2.12/scripted-sbt_2.12-1.2.0-SNAPSHOT.jar ...
[info] Done packaging.
[info] Running (fork) sbt.RunFromSourceMain /s/t
Listening for transport dt_socket at address: 5005
[info] Loading settings from idea.sbt,global-plugins.sbt ...
[info] Loading global plugins from /Users/dnw/.dotfiles/.sbt/1.0/plugins
[info] Loading project definition from /s/t/project
[info] Set current project to t (in build file:/s/t/)
[info] sbt server started at local:///Users/dnw/.sbt/1.0/server/ce9baa494c7598e4d59b/sock
> show baseDirectory
[info] /s/t
> exit
[info] shutting down sbt server
[success] Total time: 19 s, completed 25-Apr-2018 15:04:58
```
Please note that this alternative launcher does _not_ have feature parity with sbt/launcher. (Meta)
contributions welcome! :-D
### Diagnosing build failures
Globally included plugins can interfere building `sbt`; if you are getting errors building sbt, try disabling all globally included plugins and try again.
### Running Tests
sbt has a suite of unit tests and integration tests, also known as scripted tests.
#### Unit / Functional tests
Various functional and unit tests are defined throughout the
project. To run all of them, run `sbt test`. You can run a single test
suite with `sbt testOnly`
#### Integration tests
Scripted integration tests reside in `sbt/src/sbt-test` and are
written using the same testing infrastructure sbt plugin authors can
use to test their own plugins with sbt. You can read more about this
style of tests [here](https://www.scala-sbt.org/1.0/docs/Testing-sbt-plugins).
You can run the integration tests with the `sbt scripted` sbt
command. To run a single test, such as the test in
`sbt/src/sbt-test/project/global-plugin`, simply run:
sbt "scripted project/global-plugin"
### Random tidbits
#### Import statements
You'd need alternative DSL import since you can't rely on sbt package object.
```scala
// for slash syntax
import sbt.SlashSyntax0._
// for IO
import sbt.io.syntax._
```

View File

@ -1,16 +0,0 @@
(See the guidelines for contributing, linked above)
## steps
## problem
## expectation
## notes

222
LICENSE
View File

@ -1,25 +1,203 @@
Copyright (c) 2008-2014 Typesafe Inc, Mark Harrah, Grzegorz Kossakowski, Josh Suereth, Indrajit Raychaudhuri, Eugene Yokota, and other contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) 2011 - 2018, Lightbend, Inc.
Copyright (c) 2008 - 2010, Mark Harrah
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,21 +0,0 @@
Migration notes
===============
- Build definition is based on Scala 2.11.8
- Build.scala style builds are gone. Use multi-project `build.sbt`.
- `Project(...)` constructor is restricted down to two parameters. Use `project` instead.
- `sbt.Plugin` is also gone. Use auto plugins.
- The incremental compiler, called Zinc, uses class-based name hashing.
- Zinc drops support for Scala 2.8.x and 2.9.x.
- Removed the pre-0.13.7 *.sbt file parser (previously available under `-Dsbt.parser.simple=true`)
- Removed old, hyphen-separated key names (use `publishLocal` instead of `publish-local`)
- Removes no-longer-documented old operators `<<=`, `<+=`, and `<++=`.
- Renames early command feature from `--<command>` to `early(<command>)`.
- Log options `-error`, `-warn`, `-info`, `-debug` are added as shorthand for `"early(error)"` etc.
#### Additional import required
Implicit conversions are moved to `sbt.syntax`. Add the following imports to auto plugins
or `project/*.scala`.
import sbt._, syntax._, Keys._

6
NOTICE
View File

@ -1,6 +1,7 @@
sbt
Copyright (c) 2008-2014 Typesafe Inc, Mark Harrah, Grzegorz Kossakowski, Josh Suereth, Indrajit Raychaudhuri, Eugene Yokota, and other contributors.
Licensed under BSD-style license (see LICENSE)
Copyright 2011 - 2017, Lightbend, Inc.
Copyright 2008 - 2010, Mark Harrah
Licensed under Apache v2 license (see LICENSE)
Portions based on code from the Scala compiler. Portions of the Scala
library are distributed with the launcher.
@ -33,4 +34,3 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

153
PROFILING.md Normal file
View File

@ -0,0 +1,153 @@
Profiling sbt
-------------
There are several ways to profile sbt. The new hotness in profiling is FlameGraph.
You first collect stack trace samples, and then it is processed into svg graph.
See:
- [Using FlameGraphs To Illuminate The JVM by Nitsan Wakart](https://www.youtube.com/watch?v=ugRrFdda_JQ)
- [USENIX ATC '17: Visualizing Performance with Flame Graphs](https://www.youtube.com/watch?v=D53T1Ejig1Q)
### jvm-profiling-tools/async-profiler
The first one I recommend is async-profiler. This is available for macOS and Linux,
and works fairly well.
1. Download the installer from https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v1.2
2. Make symbolic link to `build/` and `profiler.sh` to `$HOME/bin`, assuming you have PATH to `$HOME/bin`:
`ln -s ~/Applications/async-profiler/profiler.sh $HOME/bin/profiler.sh`
`ln -s ~/Applications/async-profiler/build $HOME/bin/build`
Next, close all Java applications and anything that may affect the profiling, and run sbt in one terminal:
```
$ sbt exit
```
In another terminal, run:
```
$ jps
92746 sbt-launch.jar
92780 Jps
```
This tells you the process ID of sbt. In this case, it's 92746. While it's running, run
```
$ profiler.sh -d 60 <process id>
Started [cpu] profiling
--- Execution profile ---
Total samples: 31602
Non-Java: 3239 (10.25%)
GC active: 46 (0.15%)
Unknown (native): 14667 (46.41%)
Not walkable (native): 3 (0.01%)
Unknown (Java): 433 (1.37%)
Not walkable (Java): 8 (0.03%)
Thread exit: 1 (0.00%)
Deopt: 9 (0.03%)
Frame buffer usage: 55.658%
Total: 1932000000 (6.11%) samples: 1932
[ 0] java.lang.ClassLoader$NativeLibrary.load
[ 1] java.lang.ClassLoader.loadLibrary0
[ 2] java.lang.ClassLoader.loadLibrary
[ 3] java.lang.Runtime.loadLibrary0
[ 4] java.lang.System.loadLibrary
....
```
This should show a bunch of stacktraces that are useful.
To visualize this as a flamegraph, run:
```
$ profiler.sh -d 60 -f /tmp/flamegraph.svg <process id>
```
This should produce `/tmp/flamegraph.svg` at the end.
![flamegraph](project/flamegraph_svg.png)
See https://gist.github.com/eed3si9n/82d43acc95a002876d357bd8ad5f40d5
### running sbt with standby
One of the tricky things you come across while profiling is figuring out the process ID,
while wnating to profile the beginning of the application.
For this purpose, we've added `sbt.launcher.standby` JVM flag.
In the next version of sbt, you should be able to run:
```
$ sbt -J-Dsbt.launcher.standby=20s exit
```
This will count down for 20s before doing anything else.
### jvm-profiling-tools/perf-map-agent
If you want to try the mixed flamegraph, you can try perf-map-agent.
This uses `dtrace` on macOS and `perf` on Linux.
You first have to compile https://github.com/jvm-profiling-tools/perf-map-agent.
For macOS, here to how to export `JAVA_HOME` before running `cmake .`:
```
$ export JAVA_HOME=$(/usr/libexec/java_home)
$ cmake .
-- The C compiler identification is AppleClang 9.0.0.9000039
-- The CXX compiler identification is AppleClang 9.0.0.9000039
...
$ make
```
In addition, you have to git clone https://github.com/brendangregg/FlameGraph
In a fresh termimal, run sbt with `-XX:+PreserveFramePointer` flag:
```
$ sbt -J-Dsbt.launcher.standby=20s -J-XX:+PreserveFramePointer exit
```
In the terminal that you will run the perf-map:
```
$ cd quicktest/
$ export JAVA_HOME=$(/usr/libexec/java_home)
$ export FLAMEGRAPH_DIR=$HOME/work/FlameGraph
$ jps
94592 Jps
94549 sbt-launch.jar
$ $HOME/work/perf-map-agent/bin/dtrace-java-flames 94549
dtrace: system integrity protection is on, some features will not be available
dtrace: description 'profile-99 ' matched 2 probes
Flame graph SVG written to DTRACE_FLAME_OUTPUT='/Users/xxx/work/quicktest/flamegraph-94549.svg'.
```
This would produce better flamegraph in theory, but the output looks too messy for `sbt exit` case.
See https://gist.github.com/eed3si9n/b5856ff3d987655513380d1a551aa0df
This might be because it assumes that the operations are already JITed.
### ktoso/sbt-jmh
https://github.com/ktoso/sbt-jmh
Due to JIT warmup etc, benchmarking is difficult. JMH runs the same tests multiple times to
remove these effects and comes closer to measuring the performance of your code.
There's also an integration with jvm-profiling-tools/async-profiler, apparently.
### VisualVM
I'd also mention traditional JVM profiling tool. Since VisualVM is opensource,
I'll mention this one: https://visualvm.github.io/
1. First VisualVM.
2. Start sbt from a terminal.
3. You should see `xsbt.boot.Boot` under Local.
4. Open it, and select either sampler or profiler, and hit CPU button at the point when you want to start.
If you are familiar with YourKit, it also works similarly.

View File

@ -1 +0,0 @@
(See the guidelines for contributing, linked above)

View File

@ -1,13 +1,16 @@
[![Build Status](https://travis-ci.com/sbt/sbt.svg?branch=develop)](https://travis-ci.com/github/sbt/sbt)
[![Latest version](https://img.shields.io/github/tag/sbt/sbt.svg)](https://index.scala-lang.org/sbt/sbt)
[![Gitter Chat](https://badges.gitter.im/sbt/sbt.svg)](https://gitter.im/sbt/sbt)
[sbt/sbt-zero-seven]: https://github.com/sbt/sbt-zero-seven
[CONTRIBUTING]: CONTRIBUTING.md
[Setup]: http://www.scala-sbt.org/release/docs/Getting-Started/Setup
[FAQ]: http://www.scala-sbt.org/release/docs/Faq.html
[Setup]: https://www.scala-sbt.org/release/docs/Getting-Started/Setup
[FAQ]: https://www.scala-sbt.org/release/docs/Faq.html
[sbt-dev]: https://groups.google.com/d/forum/sbt-dev
[searching]: http://stackoverflow.com/tags/sbt
[searching]: https://stackoverflow.com/tags/sbt
[asking]: https://stackoverflow.com/questions/ask?tags=sbt
[LICENSE]: LICENSE
[sbt/io]: https://github.com/sbt/io
[sbt/util]: https://github.com/sbt/util
[sbt/librarymanagement]: https://github.com/sbt/librarymanagement
[sbt/zinc]: https://github.com/sbt/zinc
[sbt/sbt]: https://github.com/sbt/sbt
@ -17,16 +20,15 @@ sbt
sbt is a build tool for Scala, Java, and more.
For general documentation, see http://www.scala-sbt.org/.
For general documentation, see https://www.scala-sbt.org/.
sbt 1.0.x
sbt 1.x
---------
This is the 1.0.x series of sbt. The source code of sbt is split across
several Github repositories, including this one.
This is the 1.x series of sbt. The source code of sbt is split across
several GitHub repositories, including this one.
- [sbt/io][sbt/io] hosts `sbt.io` module.
- [sbt/util][sbt/util] hosts a collection of internally used modules.
- [sbt/librarymanagement][sbt/librarymanagement] hosts `sbt.librarymanagement` module that wraps Ivy.
- [sbt/zinc][sbt/zinc] hosts Zinc, an incremental compiler for Scala.
- [sbt/sbt][sbt/sbt], this repository hosts modules that implements the build tool.
@ -42,7 +44,7 @@ Issues and Pull Requests
Please read [CONTRIBUTING] carefully before opening a GitHub Issue.
The short version: try [searching] or [asking] on StackOverflow and [sbt-dev].
The short version: try [searching] or [asking] on StackOverflow.
license
-------

20
SUPPORT.md Normal file
View File

@ -0,0 +1,20 @@
[ask]: https://stackoverflow.com/questions/ask?tags=sbt
[Lightbend]: https://www.lightbend.com/
[subscriptions]: https://www.lightbend.com/platform/subscription
[gitter]: https://gitter.im/sbt/sbt
Support
=======
[Lightbend] sponsors sbt and encourages contributions from the active community. Enterprises can adopt it for mission critical systems with confidence because Lightbend stands behind sbt with commercial support and services.
For community support please [ask] on StackOverflow with the tag "sbt" (and the name of the sbt plugin(s) if any).
- State the problem or question clearly and provide enough context. Code examples and `build.sbt` are often useful when appropriately edited.
- There's also [Gitter sbt/sbt room][gitter], but Stackoverflow is recommended so others can benefit from the answers.
For professional support, for instance if you need faster response times, [Lightbend], the maintainer of Scala compiler and sbt, provides:
- [Lightbend Subscriptions][subscriptions], which includes Expert Support
- Training
- Consulting

1693
build.sbt

File diff suppressed because it is too large Load Diff

5
client/completions/_sbtn Executable file
View File

@ -0,0 +1,5 @@
#compdef sbtn
COMPLETE="--completions=${words[@]}"
COMPLETIONS=($(sbtn --no-tab ${COMPLETE}))
_alternative 'arguments:custom arg:($COMPLETIONS)'

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
_do_sbtn_completions() {
COMPREPLY=($(sbtn "--completions=${COMP_LINE}"))
}
complete -F _do_sbtn_completions sbtn

4
client/completions/sbtn.fish Executable file
View File

@ -0,0 +1,4 @@
function __sbtcomp
sbtn --completions="$argv"
end
complete --command sbtn -f --arguments '(__sbtcomp (commandline -cp))'

View File

@ -0,0 +1,10 @@
$scriptblock = {
param($commandName, $line, $position)
$len = $line.ToString().length
$spaces = " " * ($position - $len)
$arg="--completions=$line$spaces"
& 'sbtn.exe' @('--no-tab', '--no-stderr', $arg)
}
Set-Alias -Name sbtn -Value sbtn.exe
Register-ArgumentCompleter -CommandName sbtn.exe -ScriptBlock $scriptBlock
Register-ArgumentCompleter -CommandName sbtn -ScriptBlock $scriptBlock

View File

@ -0,0 +1,26 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.client;
import sbt.internal.client.NetworkClient;
import java.nio.file.Paths;
import org.fusesource.jansi.AnsiConsole;
public class Client {
public static void main(final String[] args) {
boolean isWin = System.getProperty("os.name").toLowerCase().startsWith("win");
try {
if (isWin) AnsiConsole.systemInstall();
NetworkClient.main(args);
} catch (final Throwable t) {
t.printStackTrace();
} finally {
if (isWin) AnsiConsole.systemUninstall();
}
}
}

View File

@ -0,0 +1,18 @@
[
{
"name":"java.lang.ProcessBuilder",
"methods":[{"name":"redirectInput","parameterTypes":["java.lang.ProcessBuilder$Redirect"] }]
},
{
"name":"java.lang.ProcessBuilder$Redirect",
"fields":[{"name":"INHERIT"}]
},
{
"name":"java.lang.System",
"methods":[{"name":"console","parameterTypes":[] }]
},
{
"name":"jline.UnixTerminal",
"methods":[{"name":"<init>","parameterTypes":[] }]
}
]

View File

@ -0,0 +1,22 @@
{
"resources":[
{"pattern":"jline/console/completer/CandidateListCompletionHandler.properties"},
{"pattern":"org/jline/utils/ansi.caps"},
{"pattern":"org/jline/utils/capabilities.txt"},
{"pattern":"org/jline/utils/colors.txt"},
{"pattern":"org/jline/utils/dumb-color.caps"},
{"pattern":"org/jline/utils/xterm.caps"},
{"pattern":"org/jline/utils/xterm-256color.caps"},
{"pattern":"org/jline/utils/windows-256color.caps"},
{"pattern":"org/jline/utils/screen-256color.caps"},
{"pattern":"org/jline/utils/windows.caps"},
{"pattern":"org/jline/utils/windows-conemu.caps"},
{"pattern":"org/jline/utils/dumb.caps"},
{"pattern":"org/jline/utils/windows-vtp.caps"},
{"pattern":"org/jline/utils/screen.caps"},
{"pattern":"library.properties"},
{"pattern":"darwin/x86_64/libsbtipcsocket.dylib"},
{"pattern":"linux/x86_64/libsbtipcsocket.so"},
{"pattern":"win32/x86_64/sbtipcsocket.dll"}
]
}

View File

@ -0,0 +1,322 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package appmacro
import scala.reflect._
import macros._
import ContextUtil.{ DynamicDependencyError, DynamicReferenceError }
object ContextUtil {
final val DynamicDependencyError = "Illegal dynamic dependency"
final val DynamicReferenceError = "Illegal dynamic reference"
/**
* Constructs an object with utility methods for operating in the provided macro context `c`.
* Callers should explicitly specify the type parameter as `c.type` in order to preserve the path dependent types.
*/
def apply[C <: blackbox.Context with Singleton](c: C): ContextUtil[C] = new ContextUtil(c: C)
/**
* Helper for implementing a no-argument macro that is introduced via an implicit.
* This method removes the implicit conversion and evaluates the function `f` on the target of the conversion.
*
* Given `myImplicitConversion(someValue).extensionMethod`, where `extensionMethod` is a macro that uses this
* method, the result of this method is `f(<Tree of someValue>)`.
*/
def selectMacroImpl[T: c.WeakTypeTag](
c: blackbox.Context
)(f: (c.Expr[Any], c.Position) => c.Expr[T]): c.Expr[T] = {
import c.universe._
c.macroApplication match {
case s @ Select(Apply(_, t :: Nil), _) => f(c.Expr[Any](t), s.pos)
case a @ Apply(_, t :: Nil) => f(c.Expr[Any](t), a.pos)
case x => unexpectedTree(x)
}
}
def unexpectedTree[C <: blackbox.Context](tree: C#Tree): Nothing =
sys.error("Unexpected macro application tree (" + tree.getClass + "): " + tree)
}
/**
* Utility methods for macros. Several methods assume that the context's universe is a full compiler
* (`scala.tools.nsc.Global`).
* This is not thread safe due to the underlying Context and related data structures not being thread safe.
* Use `ContextUtil[c.type](c)` to construct.
*/
final class ContextUtil[C <: blackbox.Context](val ctx: C) {
import ctx.universe.{ Apply => ApplyTree, _ }
import internal.decorators._
val powerContext = ctx.asInstanceOf[reflect.macros.runtime.Context]
val global: powerContext.universe.type = powerContext.universe
def callsiteTyper: global.analyzer.Typer = powerContext.callsiteTyper
val initialOwner: Symbol = callsiteTyper.context.owner.asInstanceOf[ctx.universe.Symbol]
lazy val alistType = ctx.typeOf[AList[KList]]
lazy val alist: Symbol = alistType.typeSymbol.companion
lazy val alistTC: Type = alistType.typeConstructor
/** Modifiers for a local val.*/
lazy val localModifiers = Modifiers(NoFlags)
def getPos(sym: Symbol) = if (sym eq null) NoPosition else sym.pos
/**
* Constructs a unique term name with the given prefix within this Context.
* (The current implementation uses Context.freshName, which increments
*/
def freshTermName(prefix: String) = TermName(ctx.freshName("$" + prefix))
/**
* Constructs a new, synthetic, local ValDef Type `tpe`, a unique name,
* Position `pos`, an empty implementation (no rhs), and owned by `owner`.
*/
def freshValDef(tpe: Type, pos: Position, owner: Symbol): ValDef = {
val SYNTHETIC = (1 << 21).toLong.asInstanceOf[FlagSet]
val sym = owner.newTermSymbol(freshTermName("q"), pos, SYNTHETIC)
setInfo(sym, tpe)
val vd = internal.valDef(sym, EmptyTree)
vd.setPos(pos)
vd
}
lazy val parameterModifiers = Modifiers(Flag.PARAM)
/**
* Collects all definitions in the tree for use in checkReferences.
* This excludes definitions in wrapped expressions because checkReferences won't allow nested dereferencing anyway.
*/
def collectDefs(
tree: Tree,
isWrapper: (String, Type, Tree) => Boolean
): collection.Set[Symbol] = {
val defs = new collection.mutable.HashSet[Symbol]
// adds the symbols for all non-Ident subtrees to `defs`.
val process = new Traverser {
override def traverse(t: Tree) = t match {
case _: Ident => ()
case ApplyTree(TypeApply(Select(_, nme), tpe :: Nil), qual :: Nil)
if isWrapper(nme.decodedName.toString, tpe.tpe, qual) =>
()
case tree =>
if (tree.symbol ne null) {
defs += tree.symbol
()
}
super.traverse(tree)
}
}
process.traverse(tree)
defs
}
/**
* A reference is illegal if it is to an M instance defined within the scope of the macro call.
* As an approximation, disallow referenced to any local definitions `defs`.
*/
def illegalReference(defs: collection.Set[Symbol], sym: Symbol, mType: Type): Boolean =
sym != null && sym != NoSymbol && defs.contains(sym) && {
sym match {
case m: MethodSymbol => m.returnType.erasure <:< mType
case _ => sym.typeSignature <:< mType
}
}
/**
* A reference is illegal if it is to an M instance defined within the scope of the macro call.
* As an approximation, disallow referenced to any local definitions `defs`.
*/
def illegalReference(defs: collection.Set[Symbol], sym: Symbol): Boolean =
illegalReference(defs, sym, weakTypeOf[Any])
type PropertyChecker = (String, Type, Tree) => Boolean
/**
* A function that checks the provided tree for illegal references to M instances defined in the
* expression passed to the macro and for illegal dereferencing of M instances.
*/
def checkReferences(
defs: collection.Set[Symbol],
isWrapper: PropertyChecker,
mType: Type
): Tree => Unit = {
case s @ ApplyTree(TypeApply(Select(_, nme), tpe :: Nil), qual :: Nil) =>
if (isWrapper(nme.decodedName.toString, tpe.tpe, qual)) {
ctx.error(s.pos, DynamicDependencyError)
}
case id @ Ident(name) if illegalReference(defs, id.symbol, mType) =>
ctx.error(id.pos, DynamicReferenceError + ": " + name)
case _ => ()
}
@deprecated("Use that variant that specifies the M instance types to exclude", since = "1.3.0")
/**
* A function that checks the provided tree for illegal references to M instances defined in the
* expression passed to the macro and for illegal dereferencing of M instances.
*/
def checkReferences(defs: collection.Set[Symbol], isWrapper: PropertyChecker): Tree => Unit =
checkReferences(defs, isWrapper, weakTypeOf[Any])
/** Constructs a ValDef with a parameter modifier, a unique name, with the provided Type and with an empty rhs. */
def freshMethodParameter(tpe: Type): ValDef =
ValDef(parameterModifiers, freshTermName("p"), TypeTree(tpe), EmptyTree)
/** Constructs a ValDef with local modifiers and a unique name. */
def localValDef(tpt: Tree, rhs: Tree): ValDef =
ValDef(localModifiers, freshTermName("q"), tpt, rhs)
/** Constructs a tuple value of the right TupleN type from the provided inputs.*/
def mkTuple(args: List[Tree]): Tree =
global.gen.mkTuple(args.asInstanceOf[List[global.Tree]]).asInstanceOf[ctx.universe.Tree]
def setSymbol[_Tree](t: _Tree, sym: Symbol): Unit = {
t.asInstanceOf[global.Tree].setSymbol(sym.asInstanceOf[global.Symbol])
()
}
def setInfo(sym: Symbol, tpe: Type): Unit = {
sym.asInstanceOf[global.Symbol].setInfo(tpe.asInstanceOf[global.Type])
()
}
/** Creates a new, synthetic type variable with the specified `owner`. */
def newTypeVariable(owner: Symbol, prefix: String = "T0"): TypeSymbol =
owner
.asInstanceOf[global.Symbol]
.newSyntheticTypeParam(prefix, 0L)
.asInstanceOf[ctx.universe.TypeSymbol]
/** The type representing the type constructor `[X] X` */
lazy val idTC: Type = {
val tvar = newTypeVariable(NoSymbol)
internal.polyType(tvar :: Nil, refVar(tvar))
}
/** A Type that references the given type variable. */
def refVar(variable: TypeSymbol): Type = variable.toTypeConstructor
/** Constructs a new, synthetic type variable that is a type constructor. For example, in type Y[L[x]], L is such a type variable. */
def newTCVariable(owner: Symbol): TypeSymbol = {
val tc = newTypeVariable(owner)
val arg = newTypeVariable(tc, "x");
tc.setInfo(internal.polyType(arg :: Nil, emptyTypeBounds))
tc
}
/** >: Nothing <: Any */
def emptyTypeBounds: TypeBounds =
internal.typeBounds(definitions.NothingClass.toType, definitions.AnyClass.toType)
/** Creates a new anonymous function symbol with Position `pos`. */
def functionSymbol(pos: Position): Symbol =
callsiteTyper.context.owner
.newAnonymousFunctionValue(pos.asInstanceOf[global.Position])
.asInstanceOf[ctx.universe.Symbol]
def functionType(args: List[Type], result: Type): Type = {
val tpe = global.definitions
.functionType(args.asInstanceOf[List[global.Type]], result.asInstanceOf[global.Type])
tpe.asInstanceOf[Type]
}
/** Create a Tree that references the `val` represented by `vd`, copying attributes from `replaced`. */
def refVal(replaced: Tree, vd: ValDef): Tree =
treeCopy.Ident(replaced, vd.name).setSymbol(vd.symbol)
/** Creates a Function tree using `functionSym` as the Symbol and changing `initialOwner` to `functionSym` in `body`.*/
def createFunction(params: List[ValDef], body: Tree, functionSym: Symbol): Tree = {
changeOwner(body, initialOwner, functionSym)
val f = Function(params, body)
setSymbol(f, functionSym)
f
}
def changeOwner(tree: Tree, prev: Symbol, next: Symbol): Unit =
new ChangeOwnerAndModuleClassTraverser(
prev.asInstanceOf[global.Symbol],
next.asInstanceOf[global.Symbol]
).traverse(tree.asInstanceOf[global.Tree])
// Workaround copied from scala/async:can be removed once https://github.com/scala/scala/pull/3179 is merged.
private[this] class ChangeOwnerAndModuleClassTraverser(
oldowner: global.Symbol,
newowner: global.Symbol
) extends global.ChangeOwnerTraverser(oldowner, newowner) {
override def traverse(tree: global.Tree): Unit = {
tree match {
case _: global.DefTree => change(tree.symbol.moduleClass)
case _ =>
}
super.traverse(tree)
}
}
/** Returns the Symbol that references the statically accessible singleton `i`. */
def singleton[T <: AnyRef with Singleton](i: T)(implicit it: ctx.TypeTag[i.type]): Symbol =
it.tpe match {
case SingleType(_, sym) if !sym.isFreeTerm && sym.isStatic => sym
case x => sys.error("Instance must be static (was " + x + ").")
}
def select(t: Tree, name: String): Tree = Select(t, TermName(name))
/** Returns the symbol for the non-private method named `name` for the class/module `obj`. */
def method(obj: Symbol, name: String): Symbol = {
val ts: Type = obj.typeSignature
val m: global.Symbol = ts.asInstanceOf[global.Type].nonPrivateMember(global.newTermName(name))
m.asInstanceOf[Symbol]
}
/**
* Returns a Type representing the type constructor tcp.<name>. For example, given
* `object Demo { type M[x] = List[x] }`, the call `extractTC(Demo, "M")` will return a type representing
* the type constructor `[x] List[x]`.
*/
def extractTC(tcp: AnyRef with Singleton, name: String)(
implicit it: ctx.TypeTag[tcp.type]
): ctx.Type = {
val itTpe = it.tpe.asInstanceOf[global.Type]
val m = itTpe.nonPrivateMember(global.newTypeName(name))
val tc = itTpe.memberInfo(m).asInstanceOf[ctx.universe.Type]
assert(tc != NoType && tc.takesTypeArgs, "Invalid type constructor: " + tc)
tc
}
/**
* Substitutes wrappers in tree `t` with the result of `subWrapper`.
* A wrapper is a Tree of the form `f[T](v)` for which isWrapper(<Tree of f>, <Underlying Type>, <qual>.target) returns true.
* Typically, `f` is a `Select` or `Ident`.
* The wrapper is replaced with the result of `subWrapper(<Type of T>, <Tree of v>, <wrapper Tree>)`
*/
def transformWrappers(
t: Tree,
subWrapper: (String, Type, Tree, Tree) => Converted[ctx.type]
): Tree = {
// the main tree transformer that replaces calls to InputWrapper.wrap(x) with
// plain Idents that reference the actual input value
object appTransformer extends Transformer {
override def transform(tree: Tree): Tree =
tree match {
case ApplyTree(TypeApply(Select(_, nme), targ :: Nil), qual :: Nil) =>
subWrapper(nme.decodedName.toString, targ.tpe, qual, tree) match {
case Converted.Success(t, finalTx) =>
changeOwner(qual, currentOwner, initialOwner) // Fixes https://github.com/sbt/sbt/issues/1150
finalTx(t)
case Converted.Failure(p, m) => ctx.abort(p, m)
case _: Converted.NotApplicable[_] => super.transform(tree)
}
case _ => super.transform(tree)
}
}
appTransformer.atOwner(initialOwner) {
appTransformer.transform(t)
}
}
}

View File

@ -0,0 +1,51 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package appmacro
import scala.reflect._
import macros._
import Types.idFun
abstract class Convert {
def apply[T: c.WeakTypeTag](c: blackbox.Context)(nme: String, in: c.Tree): Converted[c.type]
def asPredicate(c: blackbox.Context): (String, c.Type, c.Tree) => Boolean =
(n, tpe, tree) => {
val tag = c.WeakTypeTag(tpe)
apply(c)(n, tree)(tag).isSuccess
}
}
sealed trait Converted[C <: blackbox.Context with Singleton] {
def isSuccess: Boolean
def transform(f: C#Tree => C#Tree): Converted[C]
}
object Converted {
def NotApplicable[C <: blackbox.Context with Singleton] = new NotApplicable[C]
final case class Failure[C <: blackbox.Context with Singleton](
position: C#Position,
message: String
) extends Converted[C] {
def isSuccess = false
def transform(f: C#Tree => C#Tree): Converted[C] = new Failure(position, message)
}
final class NotApplicable[C <: blackbox.Context with Singleton] extends Converted[C] {
def isSuccess = false
def transform(f: C#Tree => C#Tree): Converted[C] = this
}
final case class Success[C <: blackbox.Context with Singleton](
tree: C#Tree,
finalTransform: C#Tree => C#Tree
) extends Converted[C] {
def isSuccess = true
def transform(f: C#Tree => C#Tree): Converted[C] = Success(f(tree), finalTransform)
}
object Success {
def apply[C <: blackbox.Context with Singleton](tree: C#Tree): Success[C] =
Success(tree, idFun)
}
}

View File

@ -0,0 +1,230 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package appmacro
import sbt.internal.util.Classes.Applicative
import sbt.internal.util.Types.Id
/**
* The separate hierarchy from Applicative/Monad is for two reasons.
*
* 1. The type constructor is represented as an abstract type because a TypeTag cannot represent a type constructor directly.
* 2. The applicative interface is uncurried.
*/
trait Instance {
type M[x]
def app[K[L[x]], Z](in: K[M], f: K[Id] => Z)(implicit a: AList[K]): M[Z]
def map[S, T](in: M[S], f: S => T): M[T]
def pure[T](t: () => T): M[T]
}
trait MonadInstance extends Instance {
def flatten[T](in: M[M[T]]): M[T]
}
import scala.reflect.macros._
object Instance {
type Aux[M0[_]] = Instance { type M[x] = M0[x] }
type Aux2[M0[_], N[_]] = Instance { type M[x] = M0[N[x]] }
final val ApplyName = "app"
final val FlattenName = "flatten"
final val PureName = "pure"
final val MapName = "map"
final val InstanceTCName = "M"
final class Input[U <: Universe with Singleton](
val tpe: U#Type,
val expr: U#Tree,
val local: U#ValDef
)
trait Transform[C <: blackbox.Context with Singleton, N[_]] {
def apply(in: C#Tree): C#Tree
}
def idTransform[C <: blackbox.Context with Singleton]: Transform[C, Id] = in => in
/**
* Implementation of a macro that provides a direct syntax for applicative functors and monads.
* It is intended to be used in conjunction with another macro that conditions the inputs.
*
* This method processes the Tree `t` to find inputs of the form `wrap[T]( input )`
* This form is typically constructed by another macro that pretends to be able to get a value of type `T`
* from a value convertible to `M[T]`. This `wrap(input)` form has two main purposes.
* First, it identifies the inputs that should be transformed.
* Second, it allows the input trees to be wrapped for later conversion into the appropriate `M[T]` type by `convert`.
* This wrapping is necessary because applying the first macro must preserve the original type,
* but it is useful to delay conversion until the outer, second macro is called. The `wrap` method accomplishes this by
* allowing the original `Tree` and `Type` to be hidden behind the raw `T` type. This method will remove the call to `wrap`
* so that it is not actually called at runtime.
*
* Each `input` in each expression of the form `wrap[T]( input )` is transformed by `convert`.
* This transformation converts the input Tree to a Tree of type `M[T]`.
* The original wrapped expression `wrap(input)` is replaced by a reference to a new local `val x: T`, where `x` is a fresh name.
* These converted inputs are passed to `builder` as well as the list of these synthetic `ValDef`s.
* The `TupleBuilder` instance constructs a tuple (Tree) from the inputs and defines the right hand side of the vals
* that unpacks the tuple containing the results of the inputs.
*
* The constructed tuple of inputs and the code that unpacks the results of the inputs are then passed to the `i`,
* which is an implementation of `Instance` that is statically accessible.
* An Instance defines a applicative functor associated with a specific type constructor and, if it implements MonadInstance as well, a monad.
* Typically, it will be either a top-level module or a stable member of a top-level module (such as a val or a nested module).
* The `with Singleton` part of the type verifies some cases at macro compilation time,
* while the full check for static accessibility is done at macro expansion time.
* Note: Ideally, the types would verify that `i: MonadInstance` when `t.isRight`.
* With the various dependent types involved, this is not worth it.
*
* The `t` argument is the argument of the macro that will be transformed as described above.
* If the macro that calls this method is for a multi-input map (app followed by map),
* `t` should be the argument wrapped in Left.
* If this is for multi-input flatMap (app followed by flatMap),
* this should be the argument wrapped in Right.
*/
def contImpl[T, N[_]](
c: blackbox.Context,
i: Instance with Singleton,
convert: Convert,
builder: TupleBuilder,
linter: LinterDSL
)(
t: Either[c.Expr[T], c.Expr[i.M[T]]],
inner: Transform[c.type, N]
)(
implicit tt: c.WeakTypeTag[T],
nt: c.WeakTypeTag[N[T]],
it: c.TypeTag[i.type]
): c.Expr[i.M[N[T]]] = {
import c.universe.{ Apply => ApplyTree, _ }
val util = ContextUtil[c.type](c)
val mTC: Type = util.extractTC(i, InstanceTCName)
val mttpe: Type = appliedType(mTC, nt.tpe :: Nil).dealias
// the tree for the macro argument
val (tree, treeType) = t match {
case Left(l) => (l.tree, nt.tpe.dealias)
case Right(r) => (r.tree, mttpe)
}
// the Symbol for the anonymous function passed to the appropriate Instance.map/flatMap/pure method
// this Symbol needs to be known up front so that it can be used as the owner of synthetic vals
val functionSym = util.functionSymbol(tree.pos)
val instanceSym = util.singleton(i)
// A Tree that references the statically accessible Instance that provides the actual implementations of map, flatMap, ...
val instance = Ident(instanceSym)
val isWrapper: (String, Type, Tree) => Boolean = convert.asPredicate(c)
// Local definitions `defs` in the macro. This is used to ensure references are to M instances defined outside of the macro call.
// Also `refCount` is the number of references, which is used to create the private, synthetic method containing the body
val defs = util.collectDefs(tree, isWrapper)
val checkQual: Tree => Unit = util.checkReferences(defs, isWrapper, mttpe.erasure)
type In = Input[c.universe.type]
var inputs = List[In]()
// transforms the original tree into calls to the Instance functions pure, map, ...,
// resulting in a value of type M[T]
def makeApp(body: Tree): Tree =
inputs match {
case Nil => pure(body)
case x :: Nil => single(body, x)
case xs => arbArity(body, xs)
}
// no inputs, so construct M[T] via Instance.pure or pure+flatten
def pure(body: Tree): Tree = {
val typeApplied = TypeApply(util.select(instance, PureName), TypeTree(treeType) :: Nil)
val f = util.createFunction(Nil, body, functionSym)
val p = ApplyTree(typeApplied, f :: Nil)
if (t.isLeft) p else flatten(p)
}
// m should have type M[M[T]]
// the returned Tree will have type M[T]
def flatten(m: Tree): Tree = {
val typedFlatten = TypeApply(util.select(instance, FlattenName), TypeTree(tt.tpe) :: Nil)
ApplyTree(typedFlatten, m :: Nil)
}
// calls Instance.map or flatmap directly, skipping the intermediate Instance.app that is unnecessary for a single input
def single(body: Tree, input: In): Tree = {
val variable = input.local
val param =
treeCopy.ValDef(variable, util.parameterModifiers, variable.name, variable.tpt, EmptyTree)
val typeApplied =
TypeApply(util.select(instance, MapName), variable.tpt :: (TypeTree(treeType): Tree) :: Nil)
val f = util.createFunction(param :: Nil, body, functionSym)
val mapped = ApplyTree(typeApplied, input.expr :: f :: Nil)
if (t.isLeft) mapped else flatten(mapped)
}
// calls Instance.app to get the values for all inputs and then calls Instance.map or flatMap to evaluate the body
def arbArity(body: Tree, inputs: List[In]): Tree = {
val result = builder.make(c)(mTC, inputs)
val param = util.freshMethodParameter(appliedType(result.representationC, util.idTC :: Nil))
val bindings = result.extract(param)
val f = util.createFunction(param :: Nil, Block(bindings, body), functionSym)
val ttt = TypeTree(treeType)
val typedApp =
TypeApply(util.select(instance, ApplyName), TypeTree(result.representationC) :: ttt :: Nil)
val app =
ApplyTree(ApplyTree(typedApp, result.input :: f :: Nil), result.alistInstance :: Nil)
if (t.isLeft) app else flatten(app)
}
// Called when transforming the tree to add an input.
// For `qual` of type M[A], and a `selection` qual.value,
// the call is addType(Type A, Tree qual)
// The result is a Tree representing a reference to
// the bound value of the input.
def addType(tpe: Type, qual: Tree, selection: Tree): Tree = {
qual.foreach(checkQual)
val vd = util.freshValDef(tpe, qual.pos, functionSym)
inputs ::= new Input(tpe, qual, vd)
util.refVal(selection, vd)
}
def sub(name: String, tpe: Type, qual: Tree, replace: Tree): Converted[c.type] = {
val tag = c.WeakTypeTag[T](tpe)
convert[T](c)(name, qual)(tag) transform { tree =>
addType(tpe, tree, replace)
}
}
// applies the transformation
linter.runLinter(c)(tree)
val tx = util.transformWrappers(tree, (n, tpe, t, replace) => sub(n, tpe, t, replace))
// resetting attributes must be: a) local b) done here and not wider or else there are obscure errors
val tr = makeApp(inner(tx))
val noWarn = q"""($tr: @_root_.scala.annotation.nowarn("cat=other-pure-statement"))"""
c.Expr[i.M[N[T]]](noWarn)
}
import Types._
implicit def applicativeInstance[A[_]](implicit ap: Applicative[A]): Instance.Aux[A] =
new Instance {
type M[x] = A[x]
def app[K[L[x]], Z](in: K[A], f: K[Id] => Z)(implicit a: AList[K]) = a.apply[A, Z](in, f)
def map[S, T](in: A[S], f: S => T) = ap.map(f, in)
def pure[S](s: () => S): M[S] = ap.pure(s())
}
def compose[A[_], B[_]](implicit a: Aux[A], b: Aux[B]): Instance.Aux2[A, B] =
new Composed[A, B](a, b)
// made a public, named, unsealed class because of trouble with macros and inference when the Instance is not an object
class Composed[A[_], B[_]](a: Aux[A], b: Aux[B]) extends Instance {
type M[x] = A[B[x]]
def pure[S](s: () => S): A[B[S]] = a.pure(() => b.pure(s))
def map[S, T](in: M[S], f: S => T): M[T] = a.map(in, (bv: B[S]) => b.map(bv, f))
def app[K[L[x]], Z](in: K[M], f: K[Id] => Z)(implicit alist: AList[K]): A[B[Z]] = {
val g: K[B] => B[Z] = in => b.app[K, Z](in, f)
a.app[AList.SplitK[K, B]#l, B[Z]](in, g)(AList.asplit(alist))
}
}
}

View File

@ -0,0 +1,85 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package appmacro
import scala.reflect._
import macros._
/** A `TupleBuilder` that uses a KList as the tuple representation.*/
object KListBuilder extends TupleBuilder {
def make(
c: blackbox.Context
)(mt: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] =
new BuilderResult[c.type] {
val ctx: c.type = c
val util = ContextUtil[c.type](c)
import c.universe.{ Apply => ApplyTree, _ }
import util._
val knilType = c.typeOf[KNil]
val knil = Ident(knilType.typeSymbol.companion)
val kconsTpe = c.typeOf[KCons[Int, KNil, List]]
val kcons = kconsTpe.typeSymbol.companion
val mTC: Type = mt.asInstanceOf[c.universe.Type]
val kconsTC: Type = kconsTpe.typeConstructor
/** This is the L in the type function [L[x]] ... */
val tcVariable: TypeSymbol = newTCVariable(util.initialOwner)
/** Instantiates KCons[h, t <: KList[L], L], where L is the type constructor variable */
def kconsType(h: Type, t: Type): Type =
appliedType(kconsTC, h :: t :: refVar(tcVariable) :: Nil)
def bindKList(prev: ValDef, revBindings: List[ValDef], params: List[ValDef]): List[ValDef] =
params match {
case (x @ ValDef(mods, name, tpt, _)) :: xs =>
val rhs = select(Ident(prev.name), "head")
val head = treeCopy.ValDef(x, mods, name, tpt, rhs)
util.setSymbol(head, x.symbol)
val tail = localValDef(TypeTree(), select(Ident(prev.name), "tail"))
val base = head :: revBindings
bindKList(tail, if (xs.isEmpty) base else tail :: base, xs)
case Nil => revBindings.reverse
}
private[this] def makeKList(
revInputs: Inputs[c.universe.type],
klist: Tree,
klistType: Type
): Tree =
revInputs match {
case in :: tail =>
val next = ApplyTree(
TypeApply(
Ident(kcons),
TypeTree(in.tpe) :: TypeTree(klistType) :: TypeTree(mTC) :: Nil
),
in.expr :: klist :: Nil
)
makeKList(tail, next, appliedType(kconsTC, in.tpe :: klistType :: mTC :: Nil))
case Nil => klist
}
/** The input trees combined in a KList */
val klist = makeKList(inputs.reverse, knil, knilType)
/**
* The input types combined in a KList type. The main concern is tracking the heterogeneous types.
* The type constructor is tcVariable, so that it can be applied to [X] X or M later.
* When applied to `M`, this type gives the type of the `input` KList.
*/
val klistType: Type = inputs.foldRight(knilType)((in, klist) => kconsType(in.tpe, klist))
val representationC = internal.polyType(tcVariable :: Nil, klistType)
val input = klist
val alistInstance: ctx.universe.Tree =
TypeApply(select(Ident(alist), "klist"), TypeTree(representationC) :: Nil)
def extract(param: ValDef) = bindKList(param, Nil, inputs.map(_.local))
}
}

View File

@ -0,0 +1,20 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util.appmacro
import scala.reflect.macros.blackbox
trait LinterDSL {
def runLinter(ctx: blackbox.Context)(tree: ctx.Tree): Unit
}
object LinterDSL {
object Empty extends LinterDSL {
override def runLinter(ctx: blackbox.Context)(tree: ctx.Tree): Unit = ()
}
}

View File

@ -0,0 +1,27 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package appmacro
import scala.reflect._
import macros._
/**
* A builder that uses `TupleN` as the representation for small numbers of inputs (up to `TupleNBuilder.MaxInputs`)
* and `KList` for larger numbers of inputs. This builder cannot handle fewer than 2 inputs.
*/
object MixedBuilder extends TupleBuilder {
def make(
c: blackbox.Context
)(mt: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] = {
val delegate =
if (inputs.size > TupleNBuilder.MaxInputs) (KListBuilder: TupleBuilder)
else (TupleNBuilder: TupleBuilder)
delegate.make(c)(mt, inputs)
}
}

View File

@ -0,0 +1,27 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util.appmacro
import scala.reflect.macros.blackbox
object StringTypeTag {
def impl[A: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
val tpe = weakTypeOf[A]
def typeToString(tpe: Type): String = tpe match {
case TypeRef(_, sym, args) if args.nonEmpty =>
val typeCon = tpe.typeSymbol.fullName
val typeArgs = args map typeToString
s"""$typeCon[${typeArgs.mkString(",")}]"""
case _ => tpe.toString
}
val key = Literal(Constant(typeToString(tpe)))
q"new sbt.internal.util.StringTypeTag[$tpe]($key)"
}
}

View File

@ -0,0 +1,64 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package appmacro
import scala.reflect._
import macros._
/**
* A `TupleBuilder` abstracts the work of constructing a tuple data structure such as a `TupleN` or `KList`
* and extracting values from it. The `Instance` macro implementation will (roughly) traverse the tree of its argument
* and ultimately obtain a list of expressions with type `M[T]` for different types `T`.
* The macro constructs an `Input` value for each of these expressions that contains the `Type` for `T`,
* the `Tree` for the expression, and a `ValDef` that will hold the value for the input.
*
* `TupleBuilder.apply` is provided with the list of `Input`s and is expected to provide three values in the returned BuilderResult.
* First, it returns the constructed tuple data structure Tree in `input`.
* Next, it provides the type constructor `representationC` that, when applied to M, gives the type of tuple data structure.
* For example, a builder that constructs a `Tuple3` for inputs `M[Int]`, `M[Boolean]`, and `M[String]`
* would provide a Type representing `[L[x]] (L[Int], L[Boolean], L[String])`. The `input` method
* would return a value whose type is that type constructor applied to M, or `(M[Int], M[Boolean], M[String])`.
*
* Finally, the `extract` method provides a list of vals that extract information from the applied input.
* The type of the applied input is the type constructor applied to `Id` (`[X] X`).
* The returned list of ValDefs should be the ValDefs from `inputs`, but with non-empty right-hand sides.
*/
trait TupleBuilder {
/** A convenience alias for a list of inputs (associated with a Universe of type U). */
type Inputs[U <: Universe with Singleton] = List[Instance.Input[U]]
/** Constructs a one-time use Builder for Context `c` and type constructor `tcType`. */
def make(
c: blackbox.Context
)(tcType: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type]
}
trait BuilderResult[C <: blackbox.Context with Singleton] {
val ctx: C
import ctx.universe._
/**
* Represents the higher-order type constructor `[L[x]] ...` where `...` is the
* type of the data structure containing the added expressions,
* except that it is abstracted over the type constructor applied to each heterogeneous part of the type .
*/
def representationC: PolyType
/** The instance of AList for the input. For a `representationC` of `[L[x]]`, this `Tree` should have a `Type` of `AList[L]`*/
def alistInstance: Tree
/** Returns the completed value containing all expressions added to the builder. */
def input: Tree
/* The list of definitions that extract values from a value of type `$representationC[Id]`.
* The returned value should be identical to the `ValDef`s provided to the `TupleBuilder.make` method but with
* non-empty right hand sides. Each `ValDef` may refer to `param` and previous `ValDef`s in the list.*/
def extract(param: ValDef): List[ValDef]
}

View File

@ -0,0 +1,67 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package appmacro
import scala.tools.nsc.Global
import scala.reflect._
import macros._
/**
* A builder that uses a TupleN as the tuple representation.
* It is limited to tuples of size 2 to `MaxInputs`.
*/
object TupleNBuilder extends TupleBuilder {
/** The largest number of inputs that this builder can handle. */
final val MaxInputs = 11
final val TupleMethodName = "tuple"
def make(
c: blackbox.Context
)(mt: c.Type, inputs: Inputs[c.universe.type]): BuilderResult[c.type] =
new BuilderResult[c.type] {
val util = ContextUtil[c.type](c)
import c.universe._
import util._
val global: Global = c.universe.asInstanceOf[Global]
val ctx: c.type = c
val representationC: PolyType = {
val tcVariable: Symbol = newTCVariable(util.initialOwner)
val tupleTypeArgs = inputs.map(
in => internal.typeRef(NoPrefix, tcVariable, in.tpe :: Nil).asInstanceOf[global.Type]
)
val tuple = global.definitions.tupleType(tupleTypeArgs)
internal.polyType(tcVariable :: Nil, tuple.asInstanceOf[Type])
}
val input: Tree = mkTuple(inputs.map(_.expr))
val alistInstance: Tree = {
val selectTree = select(Ident(alist), TupleMethodName + inputs.size.toString)
TypeApply(selectTree, inputs.map(in => TypeTree(in.tpe)))
}
def extract(param: ValDef): List[ValDef] = bindTuple(param, Nil, inputs.map(_.local), 1)
def bindTuple(
param: ValDef,
revBindings: List[ValDef],
params: List[ValDef],
i: Int
): List[ValDef] =
params match {
case (x @ ValDef(mods, name, tpt, _)) :: xs =>
val rhs = select(Ident(param.name), "_" + i.toString)
val newVal = treeCopy.ValDef(x, mods, name, tpt, rhs)
util.setSymbol(newVal, x.symbol)
bindTuple(param, newVal :: revBindings, xs, i + 1)
case Nil => revBindings.reverse
}
}
}

View File

@ -0,0 +1,26 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
package plugins
object DependencyTreePlugin extends AutoPlugin {
object autoImport extends DependencyTreeKeys
override def trigger = AllRequirements
override def requires = MiniDependencyTreePlugin
val configurations = Vector(Compile, Test, IntegrationTest, Runtime, Provided, Optional)
// MiniDependencyTreePlugin provides baseBasicReportingSettings for Compile and Test
override def projectSettings: Seq[Def.Setting[_]] =
((configurations diff Vector(Compile, Test)) flatMap { config =>
inConfig(config)(DependencyTreeSettings.baseBasicReportingSettings)
}) ++
(configurations flatMap { config =>
inConfig(config)(DependencyTreeSettings.baseFullReportingSettings)
})
}

View File

@ -0,0 +1,4 @@
sbt: Collection Component
Copyright 2011 - 2017, Lightbend, Inc.
Copyright 2008 - 2010, Mark Harrah
Licensed under BSD-3-Clause license (see LICENSE)

View File

@ -0,0 +1,13 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import scala.collection.parallel.ParSeq
private[util] object Par {
def apply[R](s: Seq[R]): ParSeq[R] = s.par
}

View File

@ -0,0 +1,17 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import scala.collection.JavaConverters._
private[util] class WrappedMap[K, V](val jmap: java.util.Map[K, V]) extends Map[K, V] {
def +[V1 >: V](kv: (K, V1)): scala.collection.immutable.Map[K, V1] =
jmap.asScala.toMap + kv
def -(key: K): scala.collection.immutable.Map[K, V] = jmap.asScala.toMap - key
def get(key: K): Option[V] = Option(jmap.get(key))
def iterator: Iterator[(K, V)] = jmap.asScala.iterator
}

View File

@ -0,0 +1,14 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import scala.collection.parallel.CollectionConverters._
import scala.collection.parallel.ParSeq
private[util] object Par {
def apply[R](s: Seq[R]): ParSeq[R] = s.par
}

View File

@ -0,0 +1,18 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import scala.collection.JavaConverters._
private[util] class WrappedMap[K, V](val jmap: java.util.Map[K, V]) extends Map[K, V] {
def removed(key: K): scala.collection.immutable.Map[K, V] = jmap.asScala.toMap.removed(key)
def updated[V1 >: V](key: K, value: V1): scala.collection.immutable.Map[K, V1] =
jmap.asScala.toMap.updated(key, value)
def get(key: K): Option[V] = Option(jmap.get(key))
def iterator: Iterator[(K, V)] = jmap.asScala.iterator
}

View File

@ -0,0 +1,389 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import Classes.Applicative
import Types._
/**
* An abstraction over a higher-order type constructor `K[x[y]]` with the purpose of abstracting
* over heterogeneous sequences like `KList` and `TupleN` with elements with a common type
* constructor as well as homogeneous sequences `Seq[M[T]]`.
*/
trait AList[K[L[x]]] {
def transform[M[_], N[_]](value: K[M], f: M ~> N): K[N]
def traverse[M[_], N[_], P[_]](value: K[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[K[P]]
def foldr[M[_], A](value: K[M], f: (M[_], A) => A, init: A): A
def toList[M[_]](value: K[M]): List[M[_]] = foldr[M, List[M[_]]](value, _ :: _, Nil)
def apply[M[_], C](value: K[M], f: K[Id] => C)(implicit a: Applicative[M]): M[C] =
a.map(f, traverse[M, M, Id](value, idK[M])(a))
}
object AList {
type Empty = AList[ConstK[Unit]#l]
/** AList for Unit, which represents a sequence that is always empty.*/
val empty: Empty = new Empty {
def transform[M[_], N[_]](in: Unit, f: M ~> N) = ()
def foldr[M[_], T](in: Unit, f: (M[_], T) => T, init: T) = init
override def apply[M[_], C](in: Unit, f: Unit => C)(implicit app: Applicative[M]): M[C] =
app.pure(f(()))
def traverse[M[_], N[_], P[_]](in: Unit, f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[Unit] = np.pure(())
}
type SeqList[T] = AList[λ[L[x] => List[L[T]]]]
/** AList for a homogeneous sequence. */
def seq[T]: SeqList[T] = new SeqList[T] {
def transform[M[_], N[_]](s: List[M[T]], f: M ~> N) = s.map(f.fn[T])
def foldr[M[_], A](s: List[M[T]], f: (M[_], A) => A, init: A): A =
s.reverse.foldLeft(init)((t, m) => f(m, t))
override def apply[M[_], C](s: List[M[T]], f: List[T] => C)(
implicit ap: Applicative[M]
): M[C] = {
def loop[V](in: List[M[T]], g: List[T] => V): M[V] =
in match {
case Nil => ap.pure(g(Nil))
case x :: xs =>
val h = (ts: List[T]) => (t: T) => g(t :: ts)
ap.apply(loop(xs, h), x)
}
loop(s, f)
}
def traverse[M[_], N[_], P[_]](s: List[M[T]], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[List[P[T]]] = ???
}
/** AList for the arbitrary arity data structure KList. */
def klist[KL[M[_]] <: KList.Aux[M, KL]]: AList[KL] = new AList[KL] {
def transform[M[_], N[_]](k: KL[M], f: M ~> N) = k.transform(f)
def foldr[M[_], T](k: KL[M], f: (M[_], T) => T, init: T): T = k.foldr(f, init)
override def apply[M[_], C](k: KL[M], f: KL[Id] => C)(implicit app: Applicative[M]): M[C] =
k.apply(f)(app)
def traverse[M[_], N[_], P[_]](k: KL[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[KL[P]] = k.traverse[N, P](f)(np)
override def toList[M[_]](k: KL[M]) = k.toList
}
type Single[A] = AList[λ[L[x] => L[A]]]
/** AList for a single value. */
def single[A]: Single[A] = new Single[A] {
def transform[M[_], N[_]](a: M[A], f: M ~> N) = f(a)
def foldr[M[_], T](a: M[A], f: (M[_], T) => T, init: T): T = f(a, init)
def traverse[M[_], N[_], P[_]](a: M[A], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[P[A]] = f(a)
}
/** Example: calling `AList.SplitK[K, Task]#l` returns the type lambda `A[x] => K[A[Task[x]]`. */
sealed trait SplitK[K[L[x]], B[x]] { type l[A[x]] = K[(A B)#l] }
type ASplit[K[L[x]], B[x]] = AList[SplitK[K, B]#l]
/** AList that operates on the outer type constructor `A` of a composition `[x] A[B[x]]` for type constructors `A` and `B`. */
def asplit[K[L[x]], B[x]](base: AList[K]): ASplit[K, B] = new ASplit[K, B] {
type Split[L[x]] = K[(L B)#l]
def transform[M[_], N[_]](value: Split[M], f: M ~> N): Split[N] =
base.transform[(M B)#l, (N B)#l](value, nestCon[M, N, B](f))
def traverse[M[_], N[_], P[_]](value: Split[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[Split[P]] = {
val g = nestCon[M, (N P)#l, B](f)
base.traverse[(M B)#l, N, (P B)#l](value, g)(np)
}
def foldr[M[_], A](value: Split[M], f: (M[_], A) => A, init: A): A =
base.foldr[(M B)#l, A](value, f, init)
}
// TODO: auto-generate
sealed trait T2K[A, B] { type l[L[x]] = (L[A], L[B]) }
type T2List[A, B] = AList[T2K[A, B]#l]
def tuple2[A, B]: T2List[A, B] = new T2List[A, B] {
type T2[M[_]] = (M[A], M[B])
def transform[M[_], N[_]](t: T2[M], f: M ~> N): T2[N] = (f(t._1), f(t._2))
def foldr[M[_], T](t: T2[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, init))
def traverse[M[_], N[_], P[_]](t: T2[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[T2[P]] = {
val g = (Tuple2.apply[P[A], P[B]] _).curried
np.apply(np.map(g, f(t._1)), f(t._2))
}
}
sealed trait T3K[A, B, C] { type l[L[x]] = (L[A], L[B], L[C]) }
type T3List[A, B, C] = AList[T3K[A, B, C]#l]
def tuple3[A, B, C]: T3List[A, B, C] = new T3List[A, B, C] {
type T3[M[_]] = (M[A], M[B], M[C])
def transform[M[_], N[_]](t: T3[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3))
def foldr[M[_], T](t: T3[M], f: (M[_], T) => T, init: T): T = f(t._1, f(t._2, f(t._3, init)))
def traverse[M[_], N[_], P[_]](t: T3[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[T3[P]] = {
val g = (Tuple3.apply[P[A], P[B], P[C]] _).curried
np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3))
}
}
sealed trait T4K[A, B, C, D] { type l[L[x]] = (L[A], L[B], L[C], L[D]) }
type T4List[A, B, C, D] = AList[T4K[A, B, C, D]#l]
def tuple4[A, B, C, D]: T4List[A, B, C, D] = new T4List[A, B, C, D] {
type T4[M[_]] = (M[A], M[B], M[C], M[D])
def transform[M[_], N[_]](t: T4[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3), f(t._4))
def foldr[M[_], T](t: T4[M], f: (M[_], T) => T, init: T): T =
f(t._1, f(t._2, f(t._3, f(t._4, init))))
def traverse[M[_], N[_], P[_]](t: T4[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[T4[P]] = {
val g = (Tuple4.apply[P[A], P[B], P[C], P[D]] _).curried
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4))
}
}
sealed trait T5K[A, B, C, D, E] { type l[L[x]] = (L[A], L[B], L[C], L[D], L[E]) }
type T5List[A, B, C, D, E] = AList[T5K[A, B, C, D, E]#l]
def tuple5[A, B, C, D, E]: T5List[A, B, C, D, E] = new T5List[A, B, C, D, E] {
type T5[M[_]] = (M[A], M[B], M[C], M[D], M[E])
def transform[M[_], N[_]](t: T5[M], f: M ~> N) = (f(t._1), f(t._2), f(t._3), f(t._4), f(t._5))
def foldr[M[_], T](t: T5[M], f: (M[_], T) => T, init: T): T =
f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, init)))))
def traverse[M[_], N[_], P[_]](t: T5[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[T5[P]] = {
val g = (Tuple5.apply[P[A], P[B], P[C], P[D], P[E]] _).curried
np.apply(np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)), f(t._5))
}
}
sealed trait T6K[A, B, C, D, E, F] { type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F]) }
type T6List[A, B, C, D, E, F] = AList[T6K[A, B, C, D, E, F]#l]
def tuple6[A, B, C, D, E, F]: T6List[A, B, C, D, E, F] = new T6List[A, B, C, D, E, F] {
type T6[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F])
def transform[M[_], N[_]](t: T6[M], f: M ~> N) =
(f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6))
def foldr[M[_], T](t: T6[M], f: (M[_], T) => T, init: T): T =
f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, init))))))
def traverse[M[_], N[_], P[_]](t: T6[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[T6[P]] = {
val g = (Tuple6.apply[P[A], P[B], P[C], P[D], P[E], P[F]] _).curried
np.apply(
np.apply(
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)),
f(t._5)
),
f(t._6)
)
}
}
sealed trait T7K[A, B, C, D, E, F, G] {
type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G])
}
type T7List[A, B, C, D, E, F, G] = AList[T7K[A, B, C, D, E, F, G]#l]
def tuple7[A, B, C, D, E, F, G]: T7List[A, B, C, D, E, F, G] = new T7List[A, B, C, D, E, F, G] {
type T7[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G])
def transform[M[_], N[_]](t: T7[M], f: M ~> N) =
(f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7))
def foldr[M[_], T](t: T7[M], f: (M[_], T) => T, init: T): T =
f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, init)))))))
def traverse[M[_], N[_], P[_]](t: T7[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[T7[P]] = {
val g = (Tuple7.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G]] _).curried
np.apply(
np.apply(
np.apply(
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)),
f(t._5)
),
f(t._6)
),
f(t._7)
)
}
}
sealed trait T8K[A, B, C, D, E, F, G, H] {
type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H])
}
type T8List[A, B, C, D, E, F, G, H] = AList[T8K[A, B, C, D, E, F, G, H]#l]
def tuple8[A, B, C, D, E, F, G, H]: T8List[A, B, C, D, E, F, G, H] =
new T8List[A, B, C, D, E, F, G, H] {
type T8[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H])
def transform[M[_], N[_]](t: T8[M], f: M ~> N) =
(f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8))
def foldr[M[_], T](t: T8[M], f: (M[_], T) => T, init: T): T =
f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, init))))))))
def traverse[M[_], N[_], P[_]](t: T8[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[T8[P]] = {
val g = (Tuple8.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H]] _).curried
np.apply(
np.apply(
np.apply(
np.apply(
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)),
f(t._5)
),
f(t._6)
),
f(t._7)
),
f(t._8)
)
}
}
sealed trait T9K[A, B, C, D, E, F, G, H, I] {
type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H], L[I])
}
type T9List[A, B, C, D, E, F, G, H, I] = AList[T9K[A, B, C, D, E, F, G, H, I]#l]
def tuple9[A, B, C, D, E, F, G, H, I]: T9List[A, B, C, D, E, F, G, H, I] =
new T9List[A, B, C, D, E, F, G, H, I] {
type T9[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H], M[I])
def transform[M[_], N[_]](t: T9[M], f: M ~> N) =
(f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8), f(t._9))
def foldr[M[_], T](t: T9[M], f: (M[_], T) => T, init: T): T =
f(t._1, f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, f(t._9, init)))))))))
def traverse[M[_], N[_], P[_]](t: T9[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[T9[P]] = {
val g = (Tuple9.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H], P[I]] _).curried
np.apply(
np.apply(
np.apply(
np.apply(
np.apply(
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)),
f(t._5)
),
f(t._6)
),
f(t._7)
),
f(t._8)
),
f(t._9)
)
}
}
sealed trait T10K[A, B, C, D, E, F, G, H, I, J] {
type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H], L[I], L[J])
}
type T10List[A, B, C, D, E, F, G, H, I, J] = AList[T10K[A, B, C, D, E, F, G, H, I, J]#l]
def tuple10[A, B, C, D, E, F, G, H, I, J]: T10List[A, B, C, D, E, F, G, H, I, J] =
new T10List[A, B, C, D, E, F, G, H, I, J] {
type T10[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H], M[I], M[J])
def transform[M[_], N[_]](t: T10[M], f: M ~> N) =
(f(t._1), f(t._2), f(t._3), f(t._4), f(t._5), f(t._6), f(t._7), f(t._8), f(t._9), f(t._10))
def foldr[M[_], T](t: T10[M], f: (M[_], T) => T, init: T): T =
f(
t._1,
f(t._2, f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, f(t._9, f(t._10, init)))))))))
)
def traverse[M[_], N[_], P[_]](t: T10[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[T10[P]] = {
val g =
(Tuple10.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H], P[I], P[J]] _).curried
np.apply(
np.apply(
np.apply(
np.apply(
np.apply(
np.apply(
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)),
f(t._5)
),
f(t._6)
),
f(t._7)
),
f(t._8)
),
f(t._9)
),
f(t._10)
)
}
}
sealed trait T11K[A, B, C, D, E, F, G, H, I, J, K] {
type l[L[x]] = (L[A], L[B], L[C], L[D], L[E], L[F], L[G], L[H], L[I], L[J], L[K])
}
type T11List[A, B, C, D, E, F, G, H, I, J, K] = AList[T11K[A, B, C, D, E, F, G, H, I, J, K]#l]
def tuple11[A, B, C, D, E, F, G, H, I, J, K]: T11List[A, B, C, D, E, F, G, H, I, J, K] =
new T11List[A, B, C, D, E, F, G, H, I, J, K] {
type T11[M[_]] = (M[A], M[B], M[C], M[D], M[E], M[F], M[G], M[H], M[I], M[J], M[K])
def transform[M[_], N[_]](t: T11[M], f: M ~> N) =
(
f(t._1),
f(t._2),
f(t._3),
f(t._4),
f(t._5),
f(t._6),
f(t._7),
f(t._8),
f(t._9),
f(t._10),
f(t._11)
)
def foldr[M[_], T](t: T11[M], f: (M[_], T) => T, init: T): T =
f(
t._1,
f(
t._2,
f(t._3, f(t._4, f(t._5, f(t._6, f(t._7, f(t._8, f(t._9, f(t._10, f(t._11, init)))))))))
)
)
def traverse[M[_], N[_], P[_]](t: T11[M], f: M ~> (N P)#l)(
implicit np: Applicative[N]
): N[T11[P]] = {
val g = (Tuple11
.apply[P[A], P[B], P[C], P[D], P[E], P[F], P[G], P[H], P[I], P[J], P[K]] _).curried
np.apply(
np.apply(
np.apply(
np.apply(
np.apply(
np.apply(
np.apply(
np.apply(np.apply(np.apply(np.map(g, f(t._1)), f(t._2)), f(t._3)), f(t._4)),
f(t._5)
),
f(t._6)
),
f(t._7)
),
f(t._8)
),
f(t._9)
),
f(t._10)
),
f(t._11)
)
}
}
}

View File

@ -0,0 +1,278 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import Types._
import scala.reflect.Manifest
import sbt.util.OptJsonWriter
// T must be invariant to work properly.
// Because it is sealed and the only instances go through AttributeKey.apply,
// a single AttributeKey instance cannot conform to AttributeKey[T] for different Ts
/**
* A key in an [[AttributeMap]] that constrains its associated value to be of type `T`.
* The key is uniquely defined by its `label` and type `T`, represented at runtime by `manifest`.
*/
sealed trait AttributeKey[T] {
/** The runtime evidence for `T`. */
def manifest: Manifest[T]
/** The label is the identifier for the key and is camelCase by convention. */
def label: String
/** An optional, brief description of the key. */
def description: Option[String]
/**
* In environments that support delegation, looking up this key when it has no associated value
* will delegate to the values associated with these keys.
* The delegation proceeds in order the keys are returned here.
*/
def extend: Seq[AttributeKey[_]]
/**
* Specifies whether this key is a local, anonymous key (`true`) or not (`false`).
* This is typically only used for programmatic, intermediate keys that should not be referenced outside of a specific scope.
*/
def isLocal: Boolean
/** Identifies the relative importance of a key among other keys.*/
def rank: Int
def optJsonWriter: OptJsonWriter[T]
}
private[sbt] abstract class SharedAttributeKey[T] extends AttributeKey[T] {
override final def toString = label
override final def hashCode = label.hashCode
override final def equals(o: Any) =
(this eq o.asInstanceOf[AnyRef]) || (o match {
case a: SharedAttributeKey[t] => a.label == this.label && a.manifest == this.manifest
case _ => false
})
final def isLocal: Boolean = false
}
object AttributeKey {
def apply[T: Manifest: OptJsonWriter](name: String): AttributeKey[T] =
make(name, None, Nil, Int.MaxValue)
def apply[T: Manifest: OptJsonWriter](name: String, rank: Int): AttributeKey[T] =
make(name, None, Nil, rank)
def apply[T: Manifest: OptJsonWriter](name: String, description: String): AttributeKey[T] =
apply(name, description, Nil)
def apply[T: Manifest: OptJsonWriter](
name: String,
description: String,
rank: Int
): AttributeKey[T] =
apply(name, description, Nil, rank)
def apply[T: Manifest: OptJsonWriter](
name: String,
description: String,
extend: Seq[AttributeKey[_]]
): AttributeKey[T] =
apply(name, description, extend, Int.MaxValue)
def apply[T: Manifest: OptJsonWriter](
name: String,
description: String,
extend: Seq[AttributeKey[_]],
rank: Int
): AttributeKey[T] =
make(name, Some(description), extend, rank)
private[sbt] def copyWithRank[T](a: AttributeKey[T], rank: Int): AttributeKey[T] =
make(a.label, a.description, a.extend, rank)(a.manifest, a.optJsonWriter)
private[this] def make[T](
name: String,
description0: Option[String],
extend0: Seq[AttributeKey[_]],
rank0: Int
)(implicit mf: Manifest[T], ojw: OptJsonWriter[T]): AttributeKey[T] =
new SharedAttributeKey[T] {
require(
name.headOption.exists(_.isLower),
s"A named attribute key must start with a lowercase letter: $name"
)
def manifest = mf
val label = Util.hyphenToCamel(name)
def description = description0
def extend = extend0
def rank = rank0
def optJsonWriter = ojw
}
private[sbt] def local[T](implicit mf: Manifest[T], ojw: OptJsonWriter[T]): AttributeKey[T] =
new AttributeKey[T] {
def manifest = mf
def label = LocalLabel
def description = None
def extend = Nil
override def toString = label
def isLocal: Boolean = true
def rank = Int.MaxValue
val optJsonWriter = ojw
}
private[sbt] final val LocalLabel = "$" + "local"
}
/**
* An immutable map where a key is the tuple `(String,T)` for a fixed type `T` and can only be associated with values of type `T`.
* It is therefore possible for this map to contain mappings for keys with the same label but different types.
* Excluding this possibility is the responsibility of the client if desired.
*/
trait AttributeMap {
/**
* Gets the value of type `T` associated with the key `k`.
* If a key with the same label but different type is defined, this method will fail.
*/
def apply[T](k: AttributeKey[T]): T
/**
* Gets the value of type `T` associated with the key `k` or `None` if no value is associated.
* If a key with the same label but a different type is defined, this method will return `None`.
*/
def get[T](k: AttributeKey[T]): Option[T]
/**
* Returns this map without the mapping for `k`.
* This method will not remove a mapping for a key with the same label but a different type.
*/
def remove[T](k: AttributeKey[T]): AttributeMap
/**
* Returns true if this map contains a mapping for `k`.
* If a key with the same label but a different type is defined in this map, this method will return `false`.
*/
def contains[T](k: AttributeKey[T]): Boolean
/**
* Adds the mapping `k -> value` to this map, replacing any existing mapping for `k`.
* Any mappings for keys with the same label but different types are unaffected.
*/
def put[T](k: AttributeKey[T], value: T): AttributeMap
/** All keys with defined mappings. There may be multiple keys with the same `label`, but different types. */
def keys: Iterable[AttributeKey[_]]
/** Adds the mappings in `o` to this map, with mappings in `o` taking precedence over existing mappings.*/
def ++(o: Iterable[AttributeEntry[_]]): AttributeMap
/** Combines the mappings in `o` with the mappings in this map, with mappings in `o` taking precedence over existing mappings.*/
def ++(o: AttributeMap): AttributeMap
/** All mappings in this map. The [[AttributeEntry]] type preserves the typesafety of mappings, although the specific types are unknown.*/
def entries: Iterable[AttributeEntry[_]]
/** `true` if there are no mappings in this map, `false` if there are. */
def isEmpty: Boolean
/**
* Adds the mapping `k -> opt.get` if opt is Some.
* Otherwise, it returns this map without the mapping for `k`.
*/
private[sbt] def setCond[T](k: AttributeKey[T], opt: Option[T]): AttributeMap
}
object AttributeMap {
/** An [[AttributeMap]] without any mappings. */
val empty: AttributeMap = new BasicAttributeMap(Map.empty)
/** Constructs an [[AttributeMap]] containing the given `entries`. */
def apply(entries: Iterable[AttributeEntry[_]]): AttributeMap = empty ++ entries
/** Constructs an [[AttributeMap]] containing the given `entries`.*/
def apply(entries: AttributeEntry[_]*): AttributeMap = empty ++ entries
/** Presents an `AttributeMap` as a natural transformation. */
implicit def toNatTrans(map: AttributeMap): AttributeKey ~> Id = λ[AttributeKey ~> Id](map(_))
}
private class BasicAttributeMap(private val backing: Map[AttributeKey[_], Any])
extends AttributeMap {
def isEmpty: Boolean = backing.isEmpty
def apply[T](k: AttributeKey[T]) = backing(k).asInstanceOf[T]
def get[T](k: AttributeKey[T]) = backing.get(k).asInstanceOf[Option[T]]
def remove[T](k: AttributeKey[T]): AttributeMap = new BasicAttributeMap(backing - k)
def contains[T](k: AttributeKey[T]) = backing.contains(k)
def put[T](k: AttributeKey[T], value: T): AttributeMap =
new BasicAttributeMap(backing.updated(k, value: Any))
def keys: Iterable[AttributeKey[_]] = backing.keys
def ++(o: Iterable[AttributeEntry[_]]): AttributeMap =
new BasicAttributeMap(o.foldLeft(backing)((b, e) => b.updated(e.key, e.value: Any)))
def ++(o: AttributeMap): AttributeMap = o match {
case bam: BasicAttributeMap =>
new BasicAttributeMap(Map(backing.toSeq ++ bam.backing.toSeq: _*))
case _ => o ++ this
}
def entries: Iterable[AttributeEntry[_]] =
backing.collect {
case (k: AttributeKey[kt], v) => AttributeEntry(k, v.asInstanceOf[kt])
}
private[sbt] def setCond[T](k: AttributeKey[T], opt: Option[T]): AttributeMap =
opt match {
case Some(v) => put(k, v)
case None => remove(k)
}
override def toString = entries.mkString("(", ", ", ")")
}
// type inference required less generality
/** A map entry where `key` is constrained to only be associated with a fixed value of type `T`. */
final case class AttributeEntry[T](key: AttributeKey[T], value: T) {
override def toString = key.label + ": " + value
}
/** Associates a `metadata` map with `data`. */
final case class Attributed[D](data: D)(val metadata: AttributeMap) {
/** Retrieves the associated value of `key` from the metadata. */
def get[T](key: AttributeKey[T]): Option[T] = metadata.get(key)
/** Defines a mapping `key -> value` in the metadata. */
def put[T](key: AttributeKey[T], value: T): Attributed[D] =
Attributed(data)(metadata.put(key, value))
/** Transforms the data by applying `f`. */
def map[T](f: D => T): Attributed[T] = Attributed(f(data))(metadata)
}
object Attributed {
/** Extracts the underlying data from the sequence `in`. */
def data[T](in: Seq[Attributed[T]]): Seq[T] = in.map(_.data)
/** Associates empty metadata maps with each entry of `in`.*/
def blankSeq[T](in: Seq[T]): Seq[Attributed[T]] = in map blank
/** Associates an empty metadata map with `data`. */
def blank[T](data: T): Attributed[T] = Attributed(data)(AttributeMap.empty)
}

View File

@ -0,0 +1,42 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
object Classes {
trait Applicative[M[_]] {
def apply[S, T](f: M[S => T], v: M[S]): M[T]
def pure[S](s: => S): M[S]
def map[S, T](f: S => T, v: M[S]): M[T]
}
trait Selective[M[_]] extends Applicative[M] {
def select[A, B](fab: M[Either[A, B]])(fn: M[A => B]): M[B]
}
trait Monad[M[_]] extends Applicative[M] {
def flatten[T](m: M[M[T]]): M[T]
}
implicit val optionMonad: Monad[Option] = new Monad[Option] {
def apply[S, T](f: Option[S => T], v: Option[S]) = (f, v) match {
case (Some(fv), Some(vv)) => Some(fv(vv))
case _ => None
}
def pure[S](s: => S) = Some(s)
def map[S, T](f: S => T, v: Option[S]) = v map f
def flatten[T](m: Option[Option[T]]): Option[T] = m.flatten
}
implicit val listMonad: Monad[List] = new Monad[List] {
def apply[S, T](f: List[S => T], v: List[S]) = for (fv <- f; vv <- v) yield fv(vv)
def pure[S](s: => S) = s :: Nil
def map[S, T](f: S => T, v: List[S]) = v map f
def flatten[T](m: List[List[T]]): List[T] = m.flatten
}
}

View File

@ -0,0 +1,142 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
trait Dag[Node <: Dag[Node]] { self: Node =>
def dependencies: Iterable[Node]
def topologicalSort = Dag.topologicalSort(self)(_.dependencies)
}
object Dag {
import scala.collection.{ mutable, JavaConverters }
import JavaConverters.asScalaSetConverter
def topologicalSort[T](root: T)(dependencies: T => Iterable[T]): List[T] =
topologicalSort(root :: Nil)(dependencies)
def topologicalSort[T](nodes: Iterable[T])(dependencies: T => Iterable[T]): List[T] = {
val discovered = new mutable.HashSet[T]
val finished = (new java.util.LinkedHashSet[T]).asScala
def visitAll(nodes: Iterable[T]) = nodes foreach visit
def visit(node: T): Unit = {
if (!discovered(node)) {
discovered(node) = true;
try {
visitAll(dependencies(node));
} catch { case c: Cyclic => throw node :: c }
finished += node
()
} else if (!finished(node))
throw new Cyclic(node)
}
visitAll(nodes)
finished.toList
}
// doesn't check for cycles
def topologicalSortUnchecked[T](node: T)(dependencies: T => Iterable[T]): List[T] =
topologicalSortUnchecked(node :: Nil)(dependencies)
def topologicalSortUnchecked[T](nodes: Iterable[T])(dependencies: T => Iterable[T]): List[T] = {
val discovered = new mutable.HashSet[T]
var finished: List[T] = Nil
def visitAll(nodes: Iterable[T]) = nodes foreach visit
def visit(node: T): Unit = {
if (!discovered(node)) {
discovered(node) = true
visitAll(dependencies(node))
finished ::= node
}
}
visitAll(nodes);
finished;
}
final class Cyclic(val value: Any, val all: List[Any], val complete: Boolean)
extends Exception(
"Cyclic reference involving " +
(if (complete) all.mkString("\n ", "\n ", "") else value)
) {
def this(value: Any) = this(value, value :: Nil, false)
override def toString = getMessage
def ::(a: Any): Cyclic =
if (complete)
this
else if (a == value)
new Cyclic(value, all, true)
else
new Cyclic(value, a :: all, false)
}
/** A directed graph with edges labeled positive or negative. */
private[sbt] trait DirectedSignedGraph[Node] {
/**
* Directed edge type that tracks the sign and target (head) vertex.
* The sign can be obtained via [[isNegative]] and the target vertex via [[head]].
*/
type Arrow
/** List of initial nodes. */
def nodes: List[Arrow]
/** Outgoing edges for `n`. */
def dependencies(n: Node): List[Arrow]
/** `true` if the edge `a` is "negative", false if it is "positive". */
def isNegative(a: Arrow): Boolean
/** The target of the directed edge `a`. */
def head(a: Arrow): Node
}
/**
* Traverses a directed graph defined by `graph` looking for a cycle that includes a "negative" edge.
* The directed edges are weighted by the caller as "positive" or "negative".
* If a cycle containing a "negative" edge is detected, its member edges are returned in order.
* Otherwise, the empty list is returned.
*/
private[sbt] def findNegativeCycle[Node](graph: DirectedSignedGraph[Node]): List[graph.Arrow] = {
import graph._
val finished = new mutable.HashSet[Node]
val visited = new mutable.HashSet[Node]
def visit(edges: List[Arrow], stack: List[Arrow]): List[Arrow] = edges match {
case Nil => Nil
case edge :: tail =>
val node = head(edge)
if (!visited(node)) {
visited += node
visit(dependencies(node), edge :: stack) match {
case Nil =>
finished += node
visit(tail, stack)
case cycle => cycle
}
} else if (!finished(node)) {
// cycle. If a negative edge is involved, it is an error.
val between = edge :: stack.takeWhile(f => head(f) != node)
if (between exists isNegative)
between
else
visit(tail, stack)
} else
visit(tail, stack)
}
visit(graph.nodes, Nil)
}
}

View File

@ -0,0 +1,40 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import Types._
/**
* A minimal heterogeneous list type. For background, see
* https://apocalisp.wordpress.com/2010/07/06/type-level-programming-in-scala-part-6a-heterogeneous-list basics/
*/
sealed trait HList {
type Wrap[M[_]] <: HList
}
sealed trait HNil extends HList {
type Wrap[M[_]] = HNil
def :+:[G](g: G): G :+: HNil = HCons(g, this)
override def toString = "HNil"
}
object HNil extends HNil
final case class HCons[H, T <: HList](head: H, tail: T) extends HList {
type Wrap[M[_]] = M[H] :+: T#Wrap[M]
def :+:[G](g: G): G :+: H :+: T = HCons(g, this)
override def toString = head + " :+: " + tail.toString
}
object HList {
// contains no type information: not even A
implicit def fromList[A](list: Traversable[A]): HList =
list.foldLeft(HNil: HList)((hl, v) => HCons(v, hl))
}

View File

@ -0,0 +1,85 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
package internal
package util
import sjsonnew._
import Types.:+:
trait HListFormats {
implicit val lnilFormat1: JsonFormat[HNil] = forHNil(HNil: HNil)
implicit val lnilFormat2: JsonFormat[HNil.type] = forHNil(HNil)
private def forHNil[A <: HNil](hnil: A): JsonFormat[A] = new JsonFormat[A] {
def write[J](x: A, builder: Builder[J]): Unit = {
builder.beginArray()
builder.endArray()
}
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): A = jsOpt match {
case None => hnil
case Some(js) => unbuilder.beginArray(js); unbuilder.endArray(); hnil
}
}
implicit def hconsFormat[H, T <: HList](
implicit hf: JsonFormat[H],
tf: HListJF[T]
): JsonFormat[H :+: T] =
new JsonFormat[H :+: T] {
def write[J](hcons: H :+: T, builder: Builder[J]) = {
builder.beginArray()
hf.write(hcons.head, builder)
tf.write(hcons.tail, builder)
builder.endArray()
}
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]) = jsOpt match {
case None => HCons(hf.read(None, unbuilder), tf.read(None, unbuilder))
case Some(js) =>
unbuilder.beginArray(js)
val hcons =
HCons(hf.read(Some(unbuilder.nextElement), unbuilder), tf.read(Some(js), unbuilder))
unbuilder.endArray()
hcons
}
}
trait HListJF[A <: HList] {
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): A
def write[J](obj: A, builder: Builder[J]): Unit
}
implicit def hconsHListJF[H, T <: HList](
implicit hf: JsonFormat[H],
tf: HListJF[T]
): HListJF[H :+: T] =
new HListJF[H :+: T] {
def write[J](hcons: H :+: T, builder: Builder[J]) = {
hf.write(hcons.head, builder)
tf.write(hcons.tail, builder)
}
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]) = jsOpt match {
case None => HCons(hf.read(None, unbuilder), tf.read(None, unbuilder))
case Some(js) =>
HCons(hf.read(Some(unbuilder.nextElement), unbuilder), tf.read(Some(js), unbuilder))
}
}
implicit val lnilHListJF1: HListJF[HNil] = hnilHListJF(HNil: HNil)
implicit val lnilHListJF2: HListJF[HNil.type] = hnilHListJF(HNil)
implicit def hnilHListJF[A <: HNil](hnil: A): HListJF[A] = new HListJF[A] {
def write[J](hcons: A, builder: Builder[J]) = ()
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]) = hnil
}
}
object HListFormats extends HListFormats

View File

@ -0,0 +1,60 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import scala.collection.JavaConverters._
/** A mutable set interface that uses object identity to test for set membership.*/
trait IDSet[T] {
def apply(t: T): Boolean
def contains(t: T): Boolean
def +=(t: T): Unit
def ++=(t: Iterable[T]): Unit
def -=(t: T): Boolean
def all: collection.Iterable[T]
def toList: List[T]
def isEmpty: Boolean
def foreach(f: T => Unit): Unit
def process[S](t: T)(ifSeen: S)(ifNew: => S): S
}
object IDSet {
implicit def toTraversable[T]: IDSet[T] => Traversable[T] = _.all
def apply[T](values: T*): IDSet[T] = fromIterable(values)
def apply[T](values: Iterable[T]): IDSet[T] = fromIterable(values)
private def fromIterable[T](values: Iterable[T]): IDSet[T] = {
val s = create[T]
s ++= values
s
}
def create[T]: IDSet[T] = new IDSet[T] {
private[this] val backing = new java.util.IdentityHashMap[T, AnyRef]
private[this] val Dummy: AnyRef = ""
def apply(t: T) = contains(t)
def contains(t: T) = backing.containsKey(t)
def foreach(f: T => Unit) = all foreach f
def +=(t: T) = { backing.put(t, Dummy); () }
def ++=(t: Iterable[T]) = t foreach +=
def -=(t: T) = if (backing.remove(t) eq null) false else true
def all = backing.keySet.asScala
def toList = all.toList
def isEmpty = backing.isEmpty
def process[S](t: T)(ifSeen: S)(ifNew: => S) =
if (contains(t)) ifSeen
else {
this += t; ifNew
}
override def toString = backing.toString
}
}

View File

@ -0,0 +1,230 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import java.lang.Runnable
import java.util.concurrent.{ atomic, Executor, LinkedBlockingQueue }
import atomic.{ AtomicBoolean, AtomicInteger }
import Types.{ ConstK, Id }
object EvaluationState extends Enumeration {
val New, Blocked, Ready, Calling, Evaluated = Value
}
abstract class EvaluateSettings[ScopeType] {
protected val init: Init[ScopeType]
import init._
protected def executor: Executor
protected def compiledSettings: Seq[Compiled[_]]
import EvaluationState.{ Value => EvaluationState, _ }
private[this] val complete = new LinkedBlockingQueue[Option[Throwable]]
private[this] val static = PMap.empty[ScopedKey, INode]
private[this] val allScopes: Set[ScopeType] = compiledSettings.map(_.key.scope).toSet
private[this] def getStatic[T](key: ScopedKey[T]): INode[T] =
static get key getOrElse sys.error("Illegal reference to key " + key)
private[this] val transform: Initialize ~> INode = λ[Initialize ~> INode] {
case k: Keyed[s, A1$] @unchecked => single(getStatic(k.scopedKey), k.transform)
case a: Apply[k, A1$] @unchecked =>
new MixedNode[k, A1$](
a.alist.transform[Initialize, INode](a.inputs, transform),
a.f,
a.alist
)
case b: Bind[s, A1$] @unchecked => new BindNode[s, A1$](transform(b.in), x => transform(b.f(x)))
case v: Value[A1$] @unchecked => constant(v.value)
case v: ValidationCapture[A1$] @unchecked => strictConstant(v.key: A1$)
case t: TransformCapture => strictConstant(t.f: A1$)
case o: Optional[s, A1$] @unchecked =>
o.a match {
case None => constant(() => o.f(None))
case Some(i) => single[s, A1$](transform(i), x => o.f(Some(x)))
}
case x if x == StaticScopes =>
strictConstant(allScopes.asInstanceOf[A1$]) // can't convince scalac that StaticScopes => T == Set[Scope]
}
private[this] lazy val roots: Seq[INode[_]] = compiledSettings flatMap { cs =>
(cs.settings map { s =>
val t = transform(s.init)
static(s.key) = t
t
}): Seq[INode[_]]
}
private[this] val running = new AtomicInteger
private[this] val cancel = new AtomicBoolean(false)
def run(implicit delegates: ScopeType => Seq[ScopeType]): Settings[ScopeType] = {
assert(running.get() == 0, "Already running")
startWork()
roots.foreach(_.registerIfNew())
workComplete()
complete.take() foreach { ex =>
cancel.set(true)
throw ex
}
getResults(delegates)
}
private[this] def getResults(implicit delegates: ScopeType => Seq[ScopeType]) =
static.toTypedSeq.foldLeft(empty) {
case (ss, static.TPair(key, node)) =>
if (key.key.isLocal) ss else ss.set(key.scope, key.key, node.get)
}
private[this] val getValue = λ[INode ~> Id](_.get)
private[this] def submitEvaluate(node: INode[_]) = submit(node.evaluate())
private[this] def submitCallComplete[T](node: BindNode[_, T], value: T) =
submit(node.callComplete(value))
private[this] def submit(work: => Unit): Unit = {
startWork()
executor.execute(new Runnable { def run = if (!cancel.get()) run0(work) })
}
private[this] def run0(work: => Unit): Unit = {
try {
work
} catch { case e: Throwable => complete.put(Some(e)) }
workComplete()
}
private[this] def startWork(): Unit = { running.incrementAndGet(); () }
private[this] def workComplete(): Unit =
if (running.decrementAndGet() == 0)
complete.put(None)
private[this] sealed abstract class INode[T] {
private[this] var state: EvaluationState = New
private[this] var value: T = _
private[this] val blocking = new collection.mutable.ListBuffer[INode[_]]
private[this] var blockedOn: Int = 0
private[this] val calledBy = new collection.mutable.ListBuffer[BindNode[_, T]]
override def toString =
getClass.getName + " (state=" + state + ",blockedOn=" + blockedOn + ",calledBy=" + calledBy.size + ",blocking=" + blocking.size + "): " +
keyString
private[this] def keyString =
(static.toSeq.flatMap {
case (key, value) =>
if (value eq this) init.showFullKey.show(key) :: Nil else List.empty[String]
}).headOption getOrElse "non-static"
final def get: T = synchronized {
assert(value != null, toString + " not evaluated")
value
}
final def doneOrBlock(from: INode[_]): Boolean = synchronized {
val ready = state == Evaluated
if (!ready) {
blocking += from
()
}
registerIfNew()
ready
}
final def isDone: Boolean = synchronized { state == Evaluated }
final def isNew: Boolean = synchronized { state == New }
final def isCalling: Boolean = synchronized { state == Calling }
final def registerIfNew(): Unit = synchronized { if (state == New) register() }
private[this] def register(): Unit = {
assert(state == New, "Already registered and: " + toString)
val deps = dependsOn
blockedOn = deps.size - deps.count(_.doneOrBlock(this))
if (blockedOn == 0)
schedule()
else
state = Blocked
}
final def schedule(): Unit = synchronized {
assert(state == New || state == Blocked, "Invalid state for schedule() call: " + toString)
state = Ready
submitEvaluate(this)
}
final def unblocked(): Unit = synchronized {
assert(state == Blocked, "Invalid state for unblocked() call: " + toString)
blockedOn -= 1
assert(blockedOn >= 0, "Negative blockedOn: " + blockedOn + " for " + toString)
if (blockedOn == 0) schedule()
}
final def evaluate(): Unit = synchronized { evaluate0() }
protected final def makeCall(source: BindNode[_, T], target: INode[T]): Unit = {
assert(state == Ready, "Invalid state for call to makeCall: " + toString)
state = Calling
target.call(source)
}
protected final def setValue(v: T): Unit = {
assert(
state != Evaluated,
"Already evaluated (trying to set value to " + v + "): " + toString
)
if (v == null) sys.error("Setting value cannot be null: " + keyString)
value = v
state = Evaluated
blocking foreach { _.unblocked() }
blocking.clear()
calledBy foreach { node =>
submitCallComplete(node, value)
}
calledBy.clear()
}
final def call(by: BindNode[_, T]): Unit = synchronized {
registerIfNew()
state match {
case Evaluated => submitCallComplete(by, value)
case _ =>
calledBy += by
()
}
}
protected def dependsOn: Seq[INode[_]]
protected def evaluate0(): Unit
}
private[this] def strictConstant[T](v: T): INode[T] = constant(() => v)
private[this] def constant[T](f: () => T): INode[T] =
new MixedNode[ConstK[Unit]#l, T]((), _ => f(), AList.empty)
private[this] def single[S, T](in: INode[S], f: S => T): INode[T] =
new MixedNode[λ[L[x] => L[S]], T](in, f, AList.single[S])
private[this] final class BindNode[S, T](in: INode[S], f: S => INode[T]) extends INode[T] {
protected def dependsOn = in :: Nil
protected def evaluate0(): Unit = makeCall(this, f(in.get))
def callComplete(value: T): Unit = synchronized {
assert(isCalling, "Invalid state for callComplete(" + value + "): " + toString)
setValue(value)
}
}
private[this] final class MixedNode[K[L[x]], T](in: K[INode], f: K[Id] => T, alist: AList[K])
extends INode[T] {
protected def dependsOn = alist.toList(in)
protected def evaluate0(): Unit = setValue(f(alist.transform(in, getValue)))
}
}

View File

@ -0,0 +1,70 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import Types._
import Classes.Applicative
/** A higher-kinded heterogeneous list of elements that share the same type constructor `M[_]`. */
sealed trait KList[+M[_]] {
type Transform[N[_]] <: KList[N]
/** Apply the natural transformation `f` to each element. */
def transform[N[_]](f: M ~> N): Transform[N]
/** Folds this list using a function that operates on the homogeneous type of the elements of this list. */
def foldr[B](f: (M[_], B) => B, init: B): B
/** Applies `f` to the elements of this list in the applicative functor defined by `ap`. */
def apply[N[x] >: M[x], Z](f: Transform[Id] => Z)(implicit ap: Applicative[N]): N[Z]
/** Equivalent to `transform(f) . apply(x => x)`, this is the essence of the iterator at the level of natural transformations.*/
def traverse[N[_], P[_]](f: M ~> (N P)#l)(implicit np: Applicative[N]): N[Transform[P]]
/** Discards the heterogeneous type information and constructs a plain List from this KList's elements. */
def toList: List[M[_]]
}
object KList {
type Aux[+M[_], Transform0[N[_]]] = KList[M] { type Transform[N[_]] = Transform0[N] }
}
final case class KCons[H, +T <: KList[M], +M[_]](head: M[H], tail: T) extends KList[M] {
final type Transform[N[_]] = KCons[H, tail.Transform[N], N]
def transform[N[_]](f: M ~> N) = KCons(f(head), tail.transform(f))
def toList: List[M[_]] = head :: tail.toList
def apply[N[x] >: M[x], Z](f: Transform[Id] => Z)(implicit ap: Applicative[N]): N[Z] = {
val g = (t: tail.Transform[Id]) => (h: H) => f(KCons[H, tail.Transform[Id], Id](h, t))
ap.apply(tail.apply[N, H => Z](g), head)
}
def traverse[N[_], P[_]](f: M ~> (N P)#l)(implicit np: Applicative[N]): N[Transform[P]] = {
val tt: N[tail.Transform[P]] = tail.traverse[N, P](f)
val g = (t: tail.Transform[P]) => (h: P[H]) => KCons(h, t)
np.apply(np.map(g, tt), f(head))
}
def :^:[A, N[x] >: M[x]](h: N[A]) = KCons(h, this)
override def foldr[B](f: (M[_], B) => B, init: B): B = f(head, tail.foldr(f, init))
}
sealed abstract class KNil extends KList[NothingK] {
final type Transform[N[_]] = KNil
final def transform[N[_]](f: NothingK ~> N): Transform[N] = KNil
final def foldr[B](f: (NothingK[_], B) => B, init: B): B = init
final def toList = Nil
final def apply[N[x], Z](f: KNil => Z)(implicit ap: Applicative[N]): N[Z] = ap.pure(f(KNil))
final def traverse[N[_], P[_]](f: NothingK ~> (N P)#l)(implicit np: Applicative[N]): N[KNil] =
np.pure(KNil: KNil)
}
case object KNil extends KNil {
def :^:[M[_], H](h: M[H]): KCons[H, KNil, M] = KCons(h, this)
}

View File

@ -0,0 +1,129 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import collection.mutable
trait RMap[K[_], V[_]] {
def apply[T](k: K[T]): V[T]
def get[T](k: K[T]): Option[V[T]]
def contains[T](k: K[T]): Boolean
def toSeq: Seq[(K[_], V[_])]
def toTypedSeq: Seq[TPair[_]] = toSeq.map {
case (k: K[t], v) => TPair[t](k, v.asInstanceOf[V[t]])
}
def keys: Iterable[K[_]]
def values: Iterable[V[_]]
def isEmpty: Boolean
sealed case class TPair[T](key: K[T], value: V[T])
}
trait IMap[K[_], V[_]] extends (K ~> V) with RMap[K, V] {
def put[T](k: K[T], v: V[T]): IMap[K, V]
def remove[T](k: K[T]): IMap[K, V]
def mapValue[T](k: K[T], init: V[T], f: V[T] => V[T]): IMap[K, V]
def mapValues[V2[_]](f: V ~> V2): IMap[K, V2]
def mapSeparate[VL[_], VR[_]](f: V ~> λ[T => Either[VL[T], VR[T]]]): (IMap[K, VL], IMap[K, VR])
}
trait PMap[K[_], V[_]] extends (K ~> V) with RMap[K, V] {
def update[T](k: K[T], v: V[T]): Unit
def remove[T](k: K[T]): Option[V[T]]
def getOrUpdate[T](k: K[T], make: => V[T]): V[T]
def mapValue[T](k: K[T], init: V[T], f: V[T] => V[T]): V[T]
}
object PMap {
implicit def toFunction[K[_], V[_]](map: PMap[K, V]): K[_] => V[_] = k => map(k)
def empty[K[_], V[_]]: PMap[K, V] = new DelegatingPMap[K, V](new mutable.HashMap)
}
object IMap {
/**
* Only suitable for K that is invariant in its type parameter.
* Option and List keys are not suitable, for example,
* because None &lt;:&lt; Option[String] and None &lt;: Option[Int].
*/
def empty[K[_], V[_]]: IMap[K, V] = new IMap0[K, V](Map.empty)
private[sbt] def fromJMap[K[_], V[_]](map: java.util.Map[K[_], V[_]]): IMap[K, V] =
new IMap0[K, V](new WrappedMap(map))
private[sbt] class IMap0[K[_], V[_]](val backing: Map[K[_], V[_]])
extends AbstractRMap[K, V]
with IMap[K, V] {
def get[T](k: K[T]): Option[V[T]] = (backing get k).asInstanceOf[Option[V[T]]]
def put[T](k: K[T], v: V[T]) = new IMap0[K, V](backing.updated(k, v))
def remove[T](k: K[T]) = new IMap0[K, V](backing - k)
def mapValue[T](k: K[T], init: V[T], f: V[T] => V[T]) =
put(k, f(this get k getOrElse init))
def mapValues[V2[_]](f: V ~> V2) =
new IMap0[K, V2](Map(backing.iterator.map { case (k, v) => k -> f(v) }.toArray: _*))
def mapSeparate[VL[_], VR[_]](f: V ~> λ[T => Either[VL[T], VR[T]]]) = {
val left = new java.util.concurrent.ConcurrentHashMap[K[_], VL[_]]
val right = new java.util.concurrent.ConcurrentHashMap[K[_], VR[_]]
Par(backing.toVector).foreach {
case (k, v) =>
f(v) match {
case Left(l) => left.put(k, l)
case Right(r) => right.put(k, r)
}
}
(new IMap0[K, VL](new WrappedMap(left)), new IMap0[K, VR](new WrappedMap(right)))
}
def toSeq = backing.toSeq
def keys = backing.keys
def values = backing.values
def isEmpty = backing.isEmpty
override def toString = backing.toString
}
}
abstract class AbstractRMap[K[_], V[_]] extends RMap[K, V] {
def apply[T](k: K[T]): V[T] = get(k).get
def contains[T](k: K[T]): Boolean = get(k).isDefined
}
/**
* Only suitable for K that is invariant in its type parameter.
* Option and List keys are not suitable, for example,
* because None &lt;:&lt; Option[String] and None &lt;: Option[Int].
*/
class DelegatingPMap[K[_], V[_]](backing: mutable.Map[K[_], V[_]])
extends AbstractRMap[K, V]
with PMap[K, V] {
def get[T](k: K[T]): Option[V[T]] = cast[T](backing.get(k))
def update[T](k: K[T], v: V[T]): Unit = { backing(k) = v }
def remove[T](k: K[T]) = cast(backing.remove(k))
def getOrUpdate[T](k: K[T], make: => V[T]) = cast[T](backing.getOrElseUpdate(k, make))
def mapValue[T](k: K[T], init: V[T], f: V[T] => V[T]): V[T] = {
val v = f(this get k getOrElse init)
update(k, v)
v
}
def toSeq = backing.toSeq
def keys = backing.keys
def values = backing.values
def isEmpty = backing.isEmpty
private[this] def cast[T](v: V[_]): V[T] = v.asInstanceOf[V[T]]
private[this] def cast[T](o: Option[V[_]]): Option[V[T]] = o map cast[T]
override def toString = backing.toString
}

View File

@ -0,0 +1,966 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import Types._
import sbt.util.Show
import Util.{ nil, nilSeq }
sealed trait Settings[ScopeType] {
def data: Map[ScopeType, AttributeMap]
def keys(scope: ScopeType): Set[AttributeKey[_]]
def scopes: Set[ScopeType]
def definingScope(scope: ScopeType, key: AttributeKey[_]): Option[ScopeType]
def allKeys[T](f: (ScopeType, AttributeKey[_]) => T): Seq[T]
def get[T](scope: ScopeType, key: AttributeKey[T]): Option[T]
def getDirect[T](scope: ScopeType, key: AttributeKey[T]): Option[T]
def set[T](scope: ScopeType, key: AttributeKey[T], value: T): Settings[ScopeType]
}
private final class Settings0[ScopeType](
val data: Map[ScopeType, AttributeMap],
val delegates: ScopeType => Seq[ScopeType]
) extends Settings[ScopeType] {
def scopes: Set[ScopeType] = data.keySet
def keys(scope: ScopeType) = data(scope).keys.toSet
def allKeys[T](f: (ScopeType, AttributeKey[_]) => T): Seq[T] =
data.flatMap { case (scope, map) => map.keys.map(k => f(scope, k)) }.toSeq
def get[T](scope: ScopeType, key: AttributeKey[T]): Option[T] =
delegates(scope).flatMap(sc => getDirect(sc, key)).headOption
def definingScope(scope: ScopeType, key: AttributeKey[_]): Option[ScopeType] =
delegates(scope).find(sc => getDirect(sc, key).isDefined)
def getDirect[T](scope: ScopeType, key: AttributeKey[T]): Option[T] =
(data get scope).flatMap(_ get key)
def set[T](scope: ScopeType, key: AttributeKey[T], value: T): Settings[ScopeType] = {
val map = data getOrElse (scope, AttributeMap.empty)
val newData = data.updated(scope, map.put(key, value))
new Settings0(newData, delegates)
}
}
// delegates should contain the input Scope as the first entry
// this trait is intended to be mixed into an object
trait Init[ScopeType] {
/** The Show instance used when a detailed String needs to be generated.
* It is typically used when no context is available.
*/
def showFullKey: Show[ScopedKey[_]]
sealed case class ScopedKey[T](scope: ScopeType, key: AttributeKey[T])
extends KeyedInitialize[T] {
def scopedKey = this
}
type SettingSeq[T] = Seq[Setting[T]]
type ScopedMap = IMap[ScopedKey, SettingSeq]
type CompiledMap = Map[ScopedKey[_], Compiled[_]]
type MapScoped = ScopedKey ~> ScopedKey
type ValidatedRef[T] = Either[Undefined, ScopedKey[T]]
type ValidatedInit[T] = Either[Seq[Undefined], Initialize[T]]
type ValidateRef = ScopedKey ~> ValidatedRef
type ScopeLocal = ScopedKey[_] => Seq[Setting[_]]
type MapConstant = ScopedKey ~> Option
private[sbt] abstract class ValidateKeyRef {
def apply[T](key: ScopedKey[T], selfRefOk: Boolean): ValidatedRef[T]
}
/**
* The result of this initialization is the composition of applied transformations.
* This can be useful when dealing with dynamic Initialize values.
*/
lazy val capturedTransformations: Initialize[Initialize ~> Initialize] =
new TransformCapture(idK[Initialize])
def setting[T](
key: ScopedKey[T],
init: Initialize[T],
pos: SourcePosition = NoPosition
): Setting[T] = new Setting[T](key, init, pos)
def valueStrict[T](value: T): Initialize[T] = pure(() => value)
def value[T](value: => T): Initialize[T] = pure(value _)
def pure[T](value: () => T): Initialize[T] = new Value(value)
def optional[T, U](i: Initialize[T])(f: Option[T] => U): Initialize[U] = new Optional(Some(i), f)
def update[T](key: ScopedKey[T])(f: T => T): Setting[T] =
setting[T](key, map(key)(f), NoPosition)
def bind[S, T](in: Initialize[S])(f: S => Initialize[T]): Initialize[T] = new Bind(f, in)
def map[S, T](in: Initialize[S])(f: S => T): Initialize[T] =
new Apply[λ[L[x] => L[S]], T](f, in, AList.single[S])
def app[K[L[x]], T](inputs: K[Initialize])(f: K[Id] => T)(
implicit alist: AList[K]
): Initialize[T] = new Apply[K, T](f, inputs, alist)
def uniform[S, T](inputs: Seq[Initialize[S]])(f: Seq[S] => T): Initialize[T] =
new Apply[λ[L[x] => List[L[S]]], T](f, inputs.toList, AList.seq[S])
/**
* The result of this initialization is the validated `key`.
* No dependency is introduced on `key`. If `selfRefOk` is true, validation will not fail if the key is referenced by a definition of `key`.
* That is, key := f(validated(key).value) is allowed only if `selfRefOk == true`.
*/
private[sbt] final def validated[T](
key: ScopedKey[T],
selfRefOk: Boolean
): ValidationCapture[T] =
new ValidationCapture(key, selfRefOk)
/**
* Constructs a derived setting that will be automatically defined in every scope where one of its dependencies
* is explicitly defined and the where the scope matches `filter`.
* A setting initialized with dynamic dependencies is only allowed if `allowDynamic` is true.
* Only the static dependencies are tracked, however. Dependencies on previous values do not introduce a derived setting either.
*/
final def derive[T](
s: Setting[T],
allowDynamic: Boolean = false,
filter: ScopeType => Boolean = const(true),
trigger: AttributeKey[_] => Boolean = const(true),
default: Boolean = false
): Setting[T] = {
deriveAllowed(s, allowDynamic) foreach sys.error
val d = new DerivedSetting[T](s.key, s.init, s.pos, filter, trigger)
if (default) d.default() else d
}
def deriveAllowed[T](s: Setting[T], allowDynamic: Boolean): Option[String] = s.init match {
case _: Bind[_, _] if !allowDynamic => Some("Cannot derive from dynamic dependencies.")
case _ => None
}
// id is used for equality
private[sbt] final def defaultSetting[T](s: Setting[T]): Setting[T] = s.default()
private[sbt] def defaultSettings(ss: Seq[Setting[_]]): Seq[Setting[_]] =
ss.map(s => defaultSetting(s))
private[this] final val nextID = new java.util.concurrent.atomic.AtomicLong
private[this] final def nextDefaultID(): Long = nextID.incrementAndGet()
def empty(implicit delegates: ScopeType => Seq[ScopeType]): Settings[ScopeType] =
new Settings0(Map.empty, delegates)
def asTransform(s: Settings[ScopeType]): ScopedKey ~> Id = λ[ScopedKey ~> Id](k => getValue(s, k))
def getValue[T](s: Settings[ScopeType], k: ScopedKey[T]) =
s.get(k.scope, k.key) getOrElse (throw new InvalidReference(k))
def asFunction[T](s: Settings[ScopeType]): ScopedKey[T] => T = k => getValue(s, k)
def mapScope(f: ScopeType => ScopeType): MapScoped = new MapScoped {
def apply[T](k: ScopedKey[T]): ScopedKey[T] = k.copy(scope = f(k.scope))
}
private final class InvalidReference(val key: ScopedKey[_])
extends RuntimeException(
"Internal settings error: invalid reference to " + showFullKey.show(key)
)
private[this] def applyDefaults(ss: Seq[Setting[_]]): Seq[Setting[_]] = {
val result = new java.util.LinkedHashSet[Setting[_]]
val others = new java.util.ArrayList[Setting[_]]
ss.foreach {
case u: DefaultSetting[_] => result.add(u)
case r => others.add(r)
}
result.addAll(others)
import scala.collection.JavaConverters._
result.asScala.toVector
}
def compiled(init: Seq[Setting[_]], actual: Boolean = true)(
implicit delegates: ScopeType => Seq[ScopeType],
scopeLocal: ScopeLocal,
display: Show[ScopedKey[_]]
): CompiledMap = {
val initDefaults = applyDefaults(init)
// inject derived settings into scopes where their dependencies are directly defined
// and prepend per-scope settings
val derived = deriveAndLocal(initDefaults, mkDelegates(delegates))
// group by Scope/Key, dropping dead initializations
val sMap: ScopedMap = grouped(derived)
// delegate references to undefined values according to 'delegates'
val dMap: ScopedMap =
if (actual) delegate(sMap)(delegates, display) else sMap
// merge Seq[Setting[_]] into Compiled
compile(dMap)
}
@deprecated("Use makeWithCompiledMap", "1.4.0")
def make(init: Seq[Setting[_]])(
implicit delegates: ScopeType => Seq[ScopeType],
scopeLocal: ScopeLocal,
display: Show[ScopedKey[_]]
): Settings[ScopeType] = makeWithCompiledMap(init)._2
def makeWithCompiledMap(init: Seq[Setting[_]])(
implicit delegates: ScopeType => Seq[ScopeType],
scopeLocal: ScopeLocal,
display: Show[ScopedKey[_]]
): (CompiledMap, Settings[ScopeType]) = {
val cMap = compiled(init)(delegates, scopeLocal, display)
// order the initializations. cyclic references are detected here.
val ordered: Seq[Compiled[_]] = sort(cMap)
// evaluation: apply the initializations.
try {
(cMap, applyInits(ordered))
} catch {
case rru: RuntimeUndefined =>
throw Uninitialized(cMap.keys.toSeq, delegates, rru.undefined, true)
}
}
def sort(cMap: CompiledMap): Seq[Compiled[_]] =
Dag.topologicalSort(cMap.values)(_.dependencies.map(cMap))
def compile(sMap: ScopedMap): CompiledMap = sMap match {
case m: IMap.IMap0[ScopedKey, SettingSeq] @unchecked =>
Par(m.backing.toVector)
.map {
case (k, ss) =>
val deps = ss.flatMap(_.dependencies).toSet
(
k,
new Compiled(k.asInstanceOf[ScopedKey[Any]], deps, ss.asInstanceOf[SettingSeq[Any]])
)
}
.toVector
.toMap
case _ =>
sMap.toTypedSeq.map {
case sMap.TPair(k, ss) =>
val deps = ss.flatMap(_.dependencies)
(k, new Compiled(k, deps, ss))
}.toMap
}
def grouped(init: Seq[Setting[_]]): ScopedMap = {
val result = new java.util.HashMap[ScopedKey[_], Seq[Setting[_]]]
init.foreach { s =>
result.putIfAbsent(s.key, Vector(s)) match {
case null =>
case ss => result.put(s.key, if (s.definitive) Vector(s) else ss :+ s)
}
}
IMap.fromJMap[ScopedKey, SettingSeq](
result.asInstanceOf[java.util.Map[ScopedKey[_], SettingSeq[_]]]
)
}
def add[T](m: ScopedMap, s: Setting[T]): ScopedMap =
m.mapValue[T](s.key, Vector.empty[Setting[T]], ss => append(ss, s))
def append[T](ss: Seq[Setting[T]], s: Setting[T]): Seq[Setting[T]] =
if (s.definitive) Vector(s) else ss :+ s
def addLocal(init: Seq[Setting[_]])(implicit scopeLocal: ScopeLocal): Seq[Setting[_]] =
Par(init).map(_.dependencies flatMap scopeLocal).toVector.flatten ++ init
def delegate(sMap: ScopedMap)(
implicit delegates: ScopeType => Seq[ScopeType],
display: Show[ScopedKey[_]]
): ScopedMap = {
def refMap(ref: Setting[_], isFirst: Boolean) = new ValidateKeyRef {
def apply[T](k: ScopedKey[T], selfRefOk: Boolean) =
delegateForKey(sMap, k, delegates(k.scope), ref, selfRefOk || !isFirst)
}
import scala.collection.JavaConverters._
val undefined = new java.util.ArrayList[Undefined]
val result = new java.util.concurrent.ConcurrentHashMap[ScopedKey[_], Any]
val backing = sMap.toSeq
Par(backing).foreach {
case (key, settings) =>
val valid = new java.util.ArrayList[Setting[_]]
val undefs = new java.util.ArrayList[Undefined]
def validate(s: Setting[_], first: Boolean): Unit = {
s.validateKeyReferenced(refMap(s, first)) match {
case Right(v) => valid.add(v); ()
case Left(us) => us.foreach(u => undefs.add(u))
}
}
settings.headOption match {
case Some(s) =>
validate(s, true)
settings.tail.foreach(validate(_, false))
case _ =>
}
if (undefs.isEmpty) result.put(key, valid.asScala.toVector)
else undefined.addAll(undefs)
}
if (undefined.isEmpty)
IMap.fromJMap[ScopedKey, SettingSeq](
result.asInstanceOf[java.util.Map[ScopedKey[_], SettingSeq[_]]]
)
else
throw Uninitialized(sMap.keys.toSeq, delegates, undefined.asScala.toList, false)
}
private[this] def delegateForKey[T](
sMap: ScopedMap,
k: ScopedKey[T],
scopes: Seq[ScopeType],
ref: Setting[_],
selfRefOk: Boolean
): Either[Undefined, ScopedKey[T]] = {
val skeys = scopes.iterator.map(x => ScopedKey(x, k.key))
val definedAt = skeys.find(sk => (selfRefOk || ref.key != sk) && (sMap contains sk))
definedAt.toRight(Undefined(ref, k))
}
private[this] def applyInits(ordered: Seq[Compiled[_]])(
implicit delegates: ScopeType => Seq[ScopeType]
): Settings[ScopeType] = {
val x =
java.util.concurrent.Executors.newFixedThreadPool(Runtime.getRuntime.availableProcessors)
try {
val eval: EvaluateSettings[ScopeType] = new EvaluateSettings[ScopeType] {
override val init: Init.this.type = Init.this
def compiledSettings = ordered
def executor = x
}
eval.run
} finally {
x.shutdown()
}
}
def showUndefined(
u: Undefined,
validKeys: Seq[ScopedKey[_]],
delegates: ScopeType => Seq[ScopeType]
)(
implicit display: Show[ScopedKey[_]]
): String = {
val guessed = guessIntendedScope(validKeys, delegates, u.referencedKey)
val derived = u.defining.isDerived
val refString = display.show(u.defining.key)
val sourceString = if (derived) "" else parenPosString(u.defining)
val guessedString =
if (derived) ""
else guessed.map(g => "\n Did you mean " + display.show(g) + " ?").toList.mkString
val derivedString =
if (derived) ", which is a derived setting that needs this key to be defined in this scope."
else ""
display.show(u.referencedKey) + " from " + refString + sourceString + derivedString + guessedString
}
private[this] def parenPosString(s: Setting[_]): String =
s.positionString match { case None => ""; case Some(s) => " (" + s + ")" }
def guessIntendedScope(
validKeys: Seq[ScopedKey[_]],
delegates: ScopeType => Seq[ScopeType],
key: ScopedKey[_]
): Option[ScopedKey[_]] = {
val distances = validKeys.flatMap { validKey =>
refinedDistance(delegates, validKey, key).map(dist => (dist, validKey))
}
distances.sortBy(_._1).map(_._2).headOption
}
def refinedDistance(
delegates: ScopeType => Seq[ScopeType],
a: ScopedKey[_],
b: ScopedKey[_]
): Option[Int] =
if (a.key != b.key || a == b) None
else {
val dist = delegates(a.scope).indexOf(b.scope)
if (dist < 0) None else Some(dist)
}
final class Uninitialized(val undefined: Seq[Undefined], override val toString: String)
extends Exception(toString)
final class Undefined private[sbt] (val defining: Setting[_], val referencedKey: ScopedKey[_])
final class RuntimeUndefined(val undefined: Seq[Undefined])
extends RuntimeException("References to undefined settings at runtime.") {
override def getMessage =
super.getMessage + undefined.map { u =>
"\n" + u.referencedKey + " referenced from " + u.defining
}.mkString
}
def Undefined(defining: Setting[_], referencedKey: ScopedKey[_]): Undefined =
new Undefined(defining, referencedKey)
def Uninitialized(
validKeys: Seq[ScopedKey[_]],
delegates: ScopeType => Seq[ScopeType],
keys: Seq[Undefined],
runtime: Boolean
)(implicit display: Show[ScopedKey[_]]): Uninitialized = {
assert(keys.nonEmpty)
val suffix = if (keys.length > 1) "s" else ""
val prefix = if (runtime) "Runtime reference" else "Reference"
val keysString =
keys.map(u => showUndefined(u, validKeys, delegates)).mkString("\n\n ", "\n\n ", "")
new Uninitialized(
keys,
prefix + suffix + " to undefined setting" + suffix + ": " + keysString + "\n "
)
}
final class Compiled[T](
val key: ScopedKey[T],
val dependencies: Iterable[ScopedKey[_]],
val settings: Seq[Setting[T]]
) {
override def toString = showFullKey.show(key)
}
final class Flattened(val key: ScopedKey[_], val dependencies: Iterable[ScopedKey[_]])
def flattenLocals(compiled: CompiledMap): Map[ScopedKey[_], Flattened] = {
val locals = compiled flatMap {
case (key, comp) =>
if (key.key.isLocal) Seq(comp)
else nilSeq[Compiled[_]]
}
val ordered = Dag.topologicalSort(locals)(
_.dependencies.flatMap(
dep =>
if (dep.key.isLocal) Seq[Compiled[_]](compiled(dep))
else nilSeq[Compiled[_]]
)
)
def flatten(
cmap: Map[ScopedKey[_], Flattened],
key: ScopedKey[_],
deps: Iterable[ScopedKey[_]]
): Flattened =
new Flattened(
key,
deps.flatMap(
dep => if (dep.key.isLocal) cmap(dep).dependencies else Seq[ScopedKey[_]](dep).toIterable
)
)
val empty = Map.empty[ScopedKey[_], Flattened]
val flattenedLocals = ordered.foldLeft(empty) { (cmap, c) =>
cmap.updated(c.key, flatten(cmap, c.key, c.dependencies))
}
compiled flatMap {
case (key, comp) =>
if (key.key.isLocal) nilSeq[(ScopedKey[_], Flattened)]
else
Seq[(ScopedKey[_], Flattened)]((key, flatten(flattenedLocals, key, comp.dependencies)))
}
}
def definedAtString(settings: Seq[Setting[_]]): String = {
val posDefined = settings.flatMap(_.positionString.toList)
if (posDefined.nonEmpty) {
val header =
if (posDefined.size == settings.size) "defined at:"
else
"some of the defining occurrences:"
header + (posDefined.distinct mkString ("\n\t", "\n\t", "\n"))
} else ""
}
/**
* The intersect method was calling Seq.contains which is very slow compared
* to converting the Seq to a Set and calling contains on the Set. This
* private trait abstracts out the two ways that Seq[ScopeType] was actually
* used, `contains` and `exists`. In mkDelegates, we can create and cache
* instances of Delegates so that we don't have to repeatedly convert the
* same Seq to Set. On a 2020 16" macbook pro, creating the compiled map
* for the sbt project is roughly 2 seconds faster after this change
* (about 3.5 seconds before compared to about 1.5 seconds after)
*
*/
private trait Delegates {
def contains(s: ScopeType): Boolean
def exists(f: ScopeType => Boolean): Boolean
}
private[this] def mkDelegates(delegates: ScopeType => Seq[ScopeType]): ScopeType => Delegates = {
val delegateMap = new java.util.concurrent.ConcurrentHashMap[ScopeType, Delegates]
s =>
delegateMap.get(s) match {
case null =>
val seq = delegates(s)
val set = seq.toSet
val d = new Delegates {
override def contains(s: ScopeType): Boolean = set.contains(s)
override def exists(f: ScopeType => Boolean): Boolean = seq.exists(f)
}
delegateMap.put(s, d)
d
case d => d
}
}
/**
* Intersects two scopes, returning the more specific one if they intersect, or None otherwise.
*/
private[sbt] def intersect(s1: ScopeType, s2: ScopeType)(
implicit delegates: ScopeType => Seq[ScopeType]
): Option[ScopeType] = intersectDelegates(s1, s2, mkDelegates(delegates))
/**
* Intersects two scopes, returning the more specific one if they intersect, or None otherwise.
*/
private def intersectDelegates(
s1: ScopeType,
s2: ScopeType,
delegates: ScopeType => Delegates
): Option[ScopeType] =
if (delegates(s1).contains(s2)) Some(s1) // s1 is more specific
else if (delegates(s2).contains(s1)) Some(s2) // s2 is more specific
else None
private[this] def deriveAndLocal(init: Seq[Setting[_]], delegates: ScopeType => Delegates)(
implicit scopeLocal: ScopeLocal
): Seq[Setting[_]] = {
import collection.mutable
final class Derived(val setting: DerivedSetting[_]) {
val dependencies = setting.dependencies.map(_.key)
def triggeredBy = dependencies.filter(setting.trigger)
val inScopes = new mutable.HashSet[ScopeType]
val outputs = new mutable.ListBuffer[Setting[_]]
}
final class Deriveds(val key: AttributeKey[_], val settings: mutable.ListBuffer[Derived]) {
def dependencies = settings.flatMap(_.dependencies)
// This is mainly for use in the cyclic reference error message
override def toString =
s"Derived settings for ${key.label}, ${definedAtString(settings.map(_.setting).toSeq)}"
}
// separate `derived` settings from normal settings (`defs`)
val (derived, rawDefs) =
Util.separate[Setting[_], Derived, Setting[_]](init) {
case d: DerivedSetting[_] => Left(new Derived(d)); case s => Right(s)
}
val defs = addLocal(rawDefs)(scopeLocal)
// group derived settings by the key they define
val derivsByDef = new mutable.HashMap[AttributeKey[_], Deriveds]
for (s <- derived) {
val key = s.setting.key.key
derivsByDef.getOrElseUpdate(key, new Deriveds(key, new mutable.ListBuffer)).settings += s
}
// index derived settings by triggering key. This maps a key to the list of settings potentially derived from it.
val derivedBy = new mutable.HashMap[AttributeKey[_], mutable.ListBuffer[Derived]]
for (s <- derived; d <- s.triggeredBy)
derivedBy.getOrElseUpdate(d, new mutable.ListBuffer) += s
// Map a DerivedSetting[_] to the `Derived` struct wrapping it. Used to ultimately replace a DerivedSetting with
// the `Setting`s that were actually derived from it: `Derived.outputs`
val derivedToStruct: Map[DerivedSetting[_], Derived] = (derived map { s =>
s.setting -> s
}).toMap
// set of defined scoped keys, used to ensure a derived setting is only added if all dependencies are present
val defined = new mutable.HashSet[ScopedKey[_]]
def addDefs(ss: Seq[Setting[_]]): Unit = { for (s <- ss) defined += s.key }
addDefs(defs)
// true iff the scoped key is in `defined`, taking delegation into account
def isDefined(key: AttributeKey[_], scope: ScopeType) =
delegates(scope).exists(s => defined.contains(ScopedKey(s, key)))
// true iff all dependencies of derived setting `d` have a value (potentially via delegation) in `scope`
def allDepsDefined(d: Derived, scope: ScopeType, local: Set[AttributeKey[_]]): Boolean =
d.dependencies.forall(dep => local(dep) || isDefined(dep, scope))
// Returns the list of injectable derived settings and their local settings for `sk`.
// The settings are to be injected under `outputScope` = whichever scope is more specific of:
// * the dependency's (`sk`) scope
// * the DerivedSetting's scope in which it has been declared, `definingScope`
// provided that these two scopes intersect.
// A derived setting is injectable if:
// 1. it has not been previously injected into outputScope
// 2. it applies to outputScope (as determined by its `filter`)
// 3. all of its dependencies are defined for outputScope (allowing for delegation)
// This needs to handle local settings because a derived setting wouldn't be injected if it's local setting didn't exist yet.
val deriveFor = (sk: ScopedKey[_]) => {
val derivedForKey: List[Derived] = derivedBy.get(sk.key).toList.flatten
val scope = sk.scope
def localAndDerived(d: Derived): Seq[Setting[_]] = {
def definingScope = d.setting.key.scope
val outputScope = intersectDelegates(scope, definingScope, delegates)
outputScope collect {
case s if !d.inScopes.contains(s) && d.setting.filter(s) =>
val local = d.dependencies.flatMap(dep => scopeLocal(ScopedKey(s, dep)))
if (allDepsDefined(d, s, local.map(_.key.key).toSet)) {
d.inScopes.add(s)
val out = local :+ d.setting.setScope(s)
d.outputs ++= out
out
} else
nilSeq
} getOrElse nilSeq
}
derivedForKey.flatMap(localAndDerived)
}
val processed = new mutable.HashSet[ScopedKey[_]]
// derives settings, transitively so that a derived setting can trigger another
def process(rem: List[Setting[_]]): Unit = rem match {
case s :: ss =>
val sk = s.key
val ds = if (processed.add(sk)) deriveFor(sk) else nil
addDefs(ds)
process(ds ::: ss)
case Nil =>
}
process(defs.toList)
// Take all the original defs and DerivedSettings along with locals, replace each DerivedSetting with the actual
// settings that were derived.
val allDefs = addLocal(init)(scopeLocal)
allDefs.flatMap {
case d: DerivedSetting[_] => (derivedToStruct get d map (_.outputs)).toSeq.flatten
case s => s :: nil
}
}
/** Abstractly defines a value of type `T`.
*
* Specifically it defines a node in a task graph,
* where the `dependencies` represents dependent nodes,
* and `evaluate` represents the calculation based on the existing body of knowledge.
*
* @tparam T the type of the value this defines.
*/
sealed trait Initialize[T] {
def dependencies: Seq[ScopedKey[_]]
def apply[S](g: T => S): Initialize[S]
private[sbt] def mapReferenced(g: MapScoped): Initialize[T]
private[sbt] def mapConstant(g: MapConstant): Initialize[T]
private[sbt] def validateReferenced(g: ValidateRef): ValidatedInit[T] =
validateKeyReferenced(new ValidateKeyRef {
def apply[B](key: ScopedKey[B], selfRefOk: Boolean) = g(key)
})
private[sbt] def validateKeyReferenced(g: ValidateKeyRef): ValidatedInit[T]
def evaluate(map: Settings[ScopeType]): T
def zip[S](o: Initialize[S]): Initialize[(T, S)] = zipTupled(o)(idFun)
def zipWith[S, U](o: Initialize[S])(f: (T, S) => U): Initialize[U] = zipTupled(o)(f.tupled)
private[this] def zipTupled[S, U](o: Initialize[S])(f: ((T, S)) => U): Initialize[U] =
new Apply[λ[L[x] => (L[T], L[S])], U](f, (this, o), AList.tuple2[T, S])
/** A fold on the static attributes of this and nested Initializes. */
private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S
}
object Initialize {
implicit def joinInitialize[T](s: Seq[Initialize[T]]): JoinInitSeq[T] = new JoinInitSeq(s)
final class JoinInitSeq[T](s: Seq[Initialize[T]]) {
def joinWith[S](f: Seq[T] => S): Initialize[S] = uniform(s)(f)
def join: Initialize[Seq[T]] = uniform(s)(idFun)
}
def join[T](inits: Seq[Initialize[T]]): Initialize[Seq[T]] = uniform(inits)(idFun)
def joinAny[M[_]](inits: Seq[Initialize[M[T]] forSome { type T }]): Initialize[Seq[M[_]]] =
join(inits.asInstanceOf[Seq[Initialize[M[_]]]])
}
object SettingsDefinition {
implicit def unwrapSettingsDefinition(d: SettingsDefinition): Seq[Setting[_]] = d.settings
implicit def wrapSettingsDefinition(ss: Seq[Setting[_]]): SettingsDefinition =
new SettingList(ss)
}
sealed trait SettingsDefinition {
def settings: Seq[Setting[_]]
}
final class SettingList(val settings: Seq[Setting[_]]) extends SettingsDefinition
sealed class Setting[T] private[Init] (
val key: ScopedKey[T],
val init: Initialize[T],
val pos: SourcePosition
) extends SettingsDefinition {
def settings = this :: Nil
def definitive: Boolean = !init.dependencies.contains(key)
def dependencies: Seq[ScopedKey[_]] =
remove(init.dependencies.asInstanceOf[Seq[ScopedKey[T]]], key)
def mapReferenced(g: MapScoped): Setting[T] = make(key, init mapReferenced g, pos)
def validateReferenced(g: ValidateRef): Either[Seq[Undefined], Setting[T]] =
(init validateReferenced g).right.map(newI => make(key, newI, pos))
private[sbt] def validateKeyReferenced(g: ValidateKeyRef): Either[Seq[Undefined], Setting[T]] =
(init validateKeyReferenced g).right.map(newI => make(key, newI, pos))
def mapKey(g: MapScoped): Setting[T] = make(g(key), init, pos)
def mapInit(f: (ScopedKey[T], T) => T): Setting[T] = make(key, init(t => f(key, t)), pos)
def mapConstant(g: MapConstant): Setting[T] = make(key, init mapConstant g, pos)
def withPos(pos: SourcePosition) = make(key, init, pos)
def positionString: Option[String] = pos match {
case pos: FilePosition => Some(pos.path + ":" + pos.startLine)
case NoPosition => None
}
private[sbt] def mapInitialize(f: Initialize[T] => Initialize[T]): Setting[T] =
make(key, f(init), pos)
override def toString = "setting(" + key + ") at " + pos
protected[this] def make[B](
key: ScopedKey[B],
init: Initialize[B],
pos: SourcePosition
): Setting[B] = new Setting[B](key, init, pos)
protected[sbt] def isDerived: Boolean = false
private[sbt] def setScope(s: ScopeType): Setting[T] =
make(key.copy(scope = s), init.mapReferenced(mapScope(const(s))), pos)
/** Turn this setting into a `DefaultSetting` if it's not already, otherwise returns `this` */
private[sbt] def default(id: => Long = nextDefaultID()): DefaultSetting[T] =
DefaultSetting(key, init, pos, id)
}
private[Init] sealed class DerivedSetting[T](
sk: ScopedKey[T],
i: Initialize[T],
p: SourcePosition,
val filter: ScopeType => Boolean,
val trigger: AttributeKey[_] => Boolean
) extends Setting[T](sk, i, p) {
override def make[B](key: ScopedKey[B], init: Initialize[B], pos: SourcePosition): Setting[B] =
new DerivedSetting[B](key, init, pos, filter, trigger)
protected[sbt] override def isDerived: Boolean = true
override def default(_id: => Long): DefaultSetting[T] =
new DerivedSetting[T](sk, i, p, filter, trigger) with DefaultSetting[T] { val id = _id }
override def toString = "derived " + super.toString
}
// Only keep the first occurrence of this setting and move it to the front so that it has lower precedence than non-defaults.
// This is intended for internal sbt use only, where alternatives like Plugin.globalSettings are not available.
private[Init] sealed trait DefaultSetting[T] extends Setting[T] {
val id: Long
override def make[B](key: ScopedKey[B], init: Initialize[B], pos: SourcePosition): Setting[B] =
super.make(key, init, pos) default id
override final def hashCode = id.hashCode
override final def equals(o: Any): Boolean = o match {
case d: DefaultSetting[_] => d.id == id; case _ => false
}
override def toString = s"default($id) " + super.toString
override def default(id: => Long) = this
}
object DefaultSetting {
def apply[T](sk: ScopedKey[T], i: Initialize[T], p: SourcePosition, _id: Long) =
new Setting[T](sk, i, p) with DefaultSetting[T] { val id = _id }
}
private[this] def handleUndefined[T](vr: ValidatedInit[T]): Initialize[T] = vr match {
case Left(undefs) => throw new RuntimeUndefined(undefs)
case Right(x) => x
}
private[this] lazy val getValidated = λ[ValidatedInit ~> Initialize](handleUndefined(_))
// mainly for reducing generated class count
private[this] def validateKeyReferencedT(g: ValidateKeyRef) =
λ[Initialize ~> ValidatedInit](_ validateKeyReferenced g)
private[this] def mapReferencedT(g: MapScoped) = λ[Initialize ~> Initialize](_ mapReferenced g)
private[this] def mapConstantT(g: MapConstant) = λ[Initialize ~> Initialize](_ mapConstant g)
private[this] def evaluateT(g: Settings[ScopeType]) = λ[Initialize ~> Id](_ evaluate g)
private[this] def deps(ls: Seq[Initialize[_]]): Seq[ScopedKey[_]] = ls.flatMap(_.dependencies)
/** An `Initialize[T]` associated with a `ScopedKey[S]`.
* @tparam S the type of the associated `ScopedKey`
* @tparam T the type of the value this `Initialize` defines.
*/
sealed trait Keyed[S, T] extends Initialize[T] {
def scopedKey: ScopedKey[S]
def transform: S => T
final def dependencies = scopedKey :: Nil
final def apply[Z](g: T => Z): Initialize[Z] = new GetValue(scopedKey, g compose transform)
final def evaluate(ss: Settings[ScopeType]): T = transform(getValue(ss, scopedKey))
final def mapReferenced(g: MapScoped): Initialize[T] = new GetValue(g(scopedKey), transform)
private[sbt] final def validateKeyReferenced(g: ValidateKeyRef): ValidatedInit[T] =
g(scopedKey, false) match {
case Left(un) => Left(un :: Nil)
case Right(nk) => Right(new GetValue(nk, transform))
}
final def mapConstant(g: MapConstant): Initialize[T] = g(scopedKey) match {
case None => this
case Some(const) => new Value(() => transform(const))
}
private[sbt] def processAttributes[B](init: B)(f: (B, AttributeMap) => B): B = init
}
private[this] final class GetValue[S, T](val scopedKey: ScopedKey[S], val transform: S => T)
extends Keyed[S, T]
/** A `Keyed` where the type of the value and the associated `ScopedKey` are the same.
* @tparam T the type of both the value this `Initialize` defines and the type of the associated `ScopedKey`.
*/
trait KeyedInitialize[T] extends Keyed[T, T] {
final val transform = idFun[T]
}
private[sbt] final class TransformCapture(val f: Initialize ~> Initialize)
extends Initialize[Initialize ~> Initialize] {
def dependencies = Nil
def apply[Z](g2: (Initialize ~> Initialize) => Z): Initialize[Z] = map(this)(g2)
def evaluate(ss: Settings[ScopeType]): Initialize ~> Initialize = f
def mapReferenced(g: MapScoped) = new TransformCapture(mapReferencedT(g) f)
def mapConstant(g: MapConstant) = new TransformCapture(mapConstantT(g) f)
def validateKeyReferenced(g: ValidateKeyRef) =
Right(new TransformCapture(getValidated validateKeyReferencedT(g) f))
private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init
}
private[sbt] final class ValidationCapture[T](val key: ScopedKey[T], val selfRefOk: Boolean)
extends Initialize[ScopedKey[T]] {
def dependencies = Nil
def apply[Z](g2: ScopedKey[T] => Z): Initialize[Z] = map(this)(g2)
def evaluate(ss: Settings[ScopeType]) = key
def mapReferenced(g: MapScoped) = new ValidationCapture(g(key), selfRefOk)
def mapConstant(g: MapConstant) = this
def validateKeyReferenced(g: ValidateKeyRef) = g(key, selfRefOk) match {
case Left(un) => Left(un :: Nil)
case Right(k) => Right(new ValidationCapture(k, selfRefOk))
}
private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init
}
private[sbt] final class Bind[S, T](val f: S => Initialize[T], val in: Initialize[S])
extends Initialize[T] {
def dependencies = in.dependencies
def apply[Z](g: T => Z): Initialize[Z] = new Bind[S, Z](s => f(s)(g), in)
def evaluate(ss: Settings[ScopeType]): T = f(in evaluate ss) evaluate ss
def mapReferenced(g: MapScoped) = new Bind[S, T](s => f(s) mapReferenced g, in mapReferenced g)
def validateKeyReferenced(g: ValidateKeyRef) = (in validateKeyReferenced g).right.map {
validIn =>
new Bind[S, T](s => handleUndefined(f(s) validateKeyReferenced g), validIn)
}
def mapConstant(g: MapConstant) = new Bind[S, T](s => f(s) mapConstant g, in mapConstant g)
private[sbt] def processAttributes[B](init: B)(f: (B, AttributeMap) => B): B =
in.processAttributes(init)(f)
}
private[sbt] final class Optional[S, T](val a: Option[Initialize[S]], val f: Option[S] => T)
extends Initialize[T] {
def dependencies = deps(a.toList)
def apply[Z](g: T => Z): Initialize[Z] = new Optional[S, Z](a, g compose f)
def mapReferenced(g: MapScoped) = new Optional(a map mapReferencedT(g).fn, f)
def validateKeyReferenced(g: ValidateKeyRef) = a match {
case None => Right(this)
case Some(i) => Right(new Optional(i.validateKeyReferenced(g).right.toOption, f))
}
def mapConstant(g: MapConstant): Initialize[T] = new Optional(a map mapConstantT(g).fn, f)
def evaluate(ss: Settings[ScopeType]): T = f(a.flatMap(i => trapBadRef(evaluateT(ss)(i))))
// proper solution is for evaluate to be deprecated or for external use only and a new internal method returning Either be used
private[this] def trapBadRef[A](run: => A): Option[A] =
try Some(run)
catch { case _: InvalidReference => None }
private[sbt] def processAttributes[B](init: B)(f: (B, AttributeMap) => B): B = a match {
case None => init
case Some(i) => i.processAttributes(init)(f)
}
}
private[sbt] final class Value[T](val value: () => T) extends Initialize[T] {
def dependencies = Nil
def mapReferenced(g: MapScoped) = this
def validateKeyReferenced(g: ValidateKeyRef) = Right(this)
def apply[S](g: T => S) = new Value[S](() => g(value()))
def mapConstant(g: MapConstant) = this
def evaluate(map: Settings[ScopeType]): T = value()
private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init
}
private[sbt] final object StaticScopes extends Initialize[Set[ScopeType]] {
def dependencies = Nil
def mapReferenced(g: MapScoped) = this
def validateKeyReferenced(g: ValidateKeyRef) = Right(this)
def apply[S](g: Set[ScopeType] => S) = map(this)(g)
def mapConstant(g: MapConstant) = this
def evaluate(map: Settings[ScopeType]) = map.scopes
private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init
}
private[sbt] final class Apply[K[L[x]], T](
val f: K[Id] => T,
val inputs: K[Initialize],
val alist: AList[K]
) extends Initialize[T] {
def dependencies = deps(alist.toList(inputs))
def mapReferenced(g: MapScoped) = mapInputs(mapReferencedT(g))
def apply[S](g: T => S) = new Apply(g compose f, inputs, alist)
def mapConstant(g: MapConstant) = mapInputs(mapConstantT(g))
def mapInputs(g: Initialize ~> Initialize): Initialize[T] =
new Apply(f, alist.transform(inputs, g), alist)
def evaluate(ss: Settings[ScopeType]) = f(alist.transform(inputs, evaluateT(ss)))
def validateKeyReferenced(g: ValidateKeyRef) = {
val tx = alist.transform(inputs, validateKeyReferencedT(g))
val undefs = alist.toList(tx).flatMap(_.left.toSeq.flatten)
val get = λ[ValidatedInit ~> Initialize](_.right.get)
if (undefs.isEmpty) Right(new Apply(f, alist.transform(tx, get), alist)) else Left(undefs)
}
private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S =
alist.toList(inputs).foldLeft(init) { (v, i) =>
i.processAttributes(v)(f)
}
}
private def remove[T](s: Seq[T], v: T) = s filterNot (_ == v)
}

View File

@ -0,0 +1,97 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import sun.misc.{ Signal, SignalHandler }
object Signals {
val CONT = "CONT"
val INT = "INT"
def withHandler[T](handler: () => Unit, signal: String = INT)(action: () => T): T = {
val result =
try {
val signals = new Signals0
signals.withHandler(signal, handler, action)
} catch { case _: LinkageError => Right(action()): Either[Throwable, T] }
result match {
case Left(e) => throw e
case Right(v) => v
}
}
/** Helper interface so we can expose internals of signal-isms to others. */
sealed trait Registration {
def remove(): Unit
}
/**
* Register a signal handler that can be removed later.
* NOTE: Does not stack with other signal handlers!!!!
*/
def register(handler: () => Unit, signal: String = INT): Registration =
// TODO - Maybe we can just ignore things if not is-supported.
if (supported(signal)) {
val intSignal = new Signal(signal)
val newHandler = new SignalHandler {
def handle(sig: Signal): Unit = { handler() }
}
val oldHandler = Signal.handle(intSignal, newHandler)
new UnregisterNewHandler(intSignal, oldHandler)
} else {
// TODO - Maybe we should just throw an exception if we don't support signals...
NullUnregisterNewHandler
}
def supported(signal: String): Boolean =
try {
val signals = new Signals0
signals.supported(signal)
} catch { case _: LinkageError => false }
}
private class UnregisterNewHandler(intSignal: Signal, oldHandler: SignalHandler)
extends Signals.Registration {
override def remove(): Unit = {
Signal.handle(intSignal, oldHandler)
()
}
}
private object NullUnregisterNewHandler extends Signals.Registration {
override def remove(): Unit = ()
}
// Must only be referenced using a
// try { } catch { case _: LinkageError => ... }
// block to
private final class Signals0 {
def supported(signal: String): Boolean = {
import sun.misc.Signal
try {
new Signal(signal); true
} catch { case _: IllegalArgumentException => false }
}
// returns a LinkageError in `action` as Left(t) in order to avoid it being
// incorrectly swallowed as missing Signal/SignalHandler
def withHandler[T](signal: String, handler: () => Unit, action: () => T): Either[Throwable, T] = {
import sun.misc.{ Signal, SignalHandler }
val intSignal = new Signal(signal)
val newHandler = new SignalHandler {
def handle(sig: Signal): Unit = { handler() }
}
val oldHandler = Signal.handle(intSignal, newHandler)
try Right(action())
catch { case e: LinkageError => Left(e) } finally {
Signal.handle(intSignal, oldHandler); ()
}
}
}

View File

@ -0,0 +1,70 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
trait TypeFunctions {
import TypeFunctions._
type Id[X] = X
type NothingK[X] = Nothing
sealed trait Const[A] { type Apply[B] = A }
sealed trait ConstK[A] { type l[L[x]] = A }
sealed trait Compose[A[_], B[_]] { type Apply[T] = A[B[T]] }
sealed trait [A[_], B[_]] { type l[T] = A[B[T]] }
private type AnyLeft[T] = Left[T, Nothing]
private type AnyRight[T] = Right[Nothing, T]
final val left: Id ~> Left[*, Nothing] =
λ[Id ~> AnyLeft](Left(_)).setToString("TypeFunctions.left")
final val right: Id ~> Right[Nothing, *] =
λ[Id ~> AnyRight](Right(_)).setToString("TypeFunctions.right")
final val some: Id ~> Some[*] = λ[Id ~> Some](Some(_)).setToString("TypeFunctions.some")
final def idFun[T]: T => T = ((t: T) => t).setToString("TypeFunctions.id")
final def const[A, B](b: B): A => B = ((_: A) => b).setToString(s"TypeFunctions.const($b)")
final def idK[M[_]]: M ~> M = λ[M ~> M](m => m).setToString("TypeFunctions.idK")
def nestCon[M[_], N[_], G[_]](f: M ~> N): (M G)#l ~> (N G)#l =
f.asInstanceOf[(M G)#l ~> (N G)#l] // implemented with a cast to avoid extra object+method call.
// castless version:
// λ[(M G)#l ~> (N G)#l](f(_))
type Endo[T] = T => T
type ~>|[A[_], B[_]] = A ~> Compose[Option, B]#Apply
}
object TypeFunctions extends TypeFunctions {
private implicit class Ops[T[_], R[_]](val underlying: T ~> R) extends AnyVal {
def setToString(string: String): T ~> R = new (T ~> R) {
override def apply[U](a: T[U]): R[U] = underlying(a)
override def toString: String = string
override def equals(o: Any): Boolean = underlying.equals(o)
override def hashCode: Int = underlying.hashCode
}
}
private implicit class FunctionOps[A, B](val f: A => B) extends AnyVal {
def setToString(string: String): A => B = new (A => B) {
override def apply(a: A): B = f(a)
override def toString: String = string
override def equals(o: Any): Boolean = f.equals(o)
override def hashCode: Int = f.hashCode
}
}
}
trait ~>[-A[_], +B[_]] { outer =>
def apply[T](a: A[T]): B[T]
// directly on ~> because of type inference limitations
final def [C[_]](g: C ~> A): C ~> B = λ[C ~> B](c => outer.apply(g(c)))
final def [C, D](g: C => D)(implicit ev: D <:< A[D]): C => B[D] = i => apply(ev(g(i)))
final def fn[T]: A[T] => B[T] = (t: A[T]) => apply[T](t)
}
object ~> {
import TypeFunctions._
val Id: Id ~> Id = idK[Id]
implicit def tcIdEquals: Id ~> Id = Id
}

View File

@ -0,0 +1,16 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
object Types extends Types
trait Types extends TypeFunctions {
val :^: = KCons
type :+:[H, T <: HList] = HCons[H, T]
val :+: = HCons
}

View File

@ -0,0 +1,79 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import java.util.Locale
import scala.reflect.macros.blackbox
import scala.language.experimental.macros
object Util {
def makeList[T](size: Int, value: T): List[T] = List.fill(size)(value)
def separateE[A, B](ps: Seq[Either[A, B]]): (Seq[A], Seq[B]) =
separate(ps)(Types.idFun)
def separate[T, A, B](ps: Seq[T])(f: T => Either[A, B]): (Seq[A], Seq[B]) = {
val (a, b) = ps.foldLeft((Nil: Seq[A], Nil: Seq[B]))((xs, y) => prependEither(xs, f(y)))
(a.reverse, b.reverse)
}
def prependEither[A, B](acc: (Seq[A], Seq[B]), next: Either[A, B]): (Seq[A], Seq[B]) =
next match {
case Left(l) => (l +: acc._1, acc._2)
case Right(r) => (acc._1, r +: acc._2)
}
def pairID[A, B] = (a: A, b: B) => (a, b)
private[this] lazy val Hyphen = """-(\p{javaLowerCase})""".r
def hasHyphen(s: String): Boolean = s.indexOf('-') >= 0
def hyphenToCamel(s: String): String =
if (hasHyphen(s)) Hyphen.replaceAllIn(s, _.group(1).toUpperCase(Locale.ENGLISH)) else s
private[this] lazy val Camel = """(\p{javaLowerCase})(\p{javaUpperCase})""".r
def camelToHyphen(s: String): String =
Camel.replaceAllIn(s, m => m.group(1) + "-" + m.group(2).toLowerCase(Locale.ENGLISH))
def quoteIfKeyword(s: String): String = if (ScalaKeywords.values(s)) '`' + s + '`' else s
def ignoreResult[T](f: => T): Unit = macro Macro.ignore
lazy val isMac: Boolean =
System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("mac")
lazy val isWindows: Boolean =
System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows")
lazy val isCygwin: Boolean = {
val os = Option(System.getenv("OSTYPE"))
os match {
case Some(x) => x.toLowerCase(Locale.ENGLISH).contains("cygwin")
case _ => false
}
}
lazy val isNonCygwinWindows: Boolean = isWindows && !isCygwin
lazy val isCygwinWindows: Boolean = isWindows && isCygwin
lazy val isEmacs: Boolean = Option(System.getenv("INSIDE_EMACS")).isDefined
def nil[A]: List[A] = List.empty[A]
def nilSeq[A]: Seq[A] = Seq.empty[A]
def none[A]: Option[A] = (None: Option[A])
implicit class AnyOps[A](private val value: A) extends AnyVal {
def some: Option[A] = (Some(value): Option[A])
}
class Macro(val c: blackbox.Context) {
def ignore(f: c.Tree): c.Expr[Unit] = c.universe.reify({ c.Expr[Any](f).splice; () })
}
}

View File

@ -0,0 +1,29 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.util
import sjsonnew.JsonWriter
sealed trait OptJsonWriter[A]
final case class NoJsonWriter[A]() extends OptJsonWriter[A]
final case class SomeJsonWriter[A](value: JsonWriter[A]) extends OptJsonWriter[A]
trait OptJsonWriter0 {
implicit def fallback[A]: NoJsonWriter[A] = NoJsonWriter()
}
object OptJsonWriter extends OptJsonWriter0 {
implicit def lift[A](implicit z: JsonWriter[A]): SomeJsonWriter[A] = SomeJsonWriter(z)
trait StrictMode0 {
implicit def conflictingFallback1[A]: NoJsonWriter[A] = NoJsonWriter()
implicit def conflictingFallback2[A]: NoJsonWriter[A] = NoJsonWriter()
}
object StrictMode extends StrictMode0 {
implicit def lift[A](implicit z: JsonWriter[A]): SomeJsonWriter[A] = SomeJsonWriter(z)
}
}

View File

@ -0,0 +1,17 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.util
trait Show[A] {
def show(a: A): String
}
object Show {
def apply[A](f: A => String): Show[A] = a => f(a)
def fromToString[A]: Show[A] = _.toString
}

View File

@ -0,0 +1,59 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import org.scalacheck._
import Prop._
import scala.collection.mutable.HashSet
object DagSpecification extends Properties("Dag") {
property("No repeated nodes") = forAll { (dag: TestDag) =>
isSet(dag.topologicalSort)
}
property("Sort contains node") = forAll { (dag: TestDag) =>
dag.topologicalSort.contains(dag)
}
property("Dependencies precede node") = forAll { (dag: TestDag) =>
dependenciesPrecedeNodes(dag.topologicalSort)
}
implicit lazy val arbTestDag: Arbitrary[TestDag] = Arbitrary(Gen.sized(dagGen))
private def dagGen(nodeCount: Int): Gen[TestDag] = {
val nodes = new HashSet[TestDag]
def nonterminalGen(p: Gen.Parameters): Gen[TestDag] = {
val seed = rng.Seed.random()
for {
i <- 0 until nodeCount
nextDeps <- Gen.someOf(nodes).apply(p, seed)
} nodes += new TestDag(i, nextDeps)
for (nextDeps <- Gen.someOf(nodes)) yield new TestDag(nodeCount, nextDeps)
}
Gen.parameterized(nonterminalGen)
}
private def isSet[T](c: Seq[T]) = Set(c: _*).size == c.size
private def dependenciesPrecedeNodes(sort: List[TestDag]) = {
val seen = new HashSet[TestDag]
def iterate(remaining: List[TestDag]): Boolean = {
remaining match {
case Nil => true
case node :: tail =>
if (node.dependencies.forall(seen.contains) && !seen.contains(node)) {
seen += node
iterate(tail)
} else
false
}
}
iterate(sort)
}
}
class TestDag(id: Int, val dependencies: Iterable[TestDag]) extends Dag[TestDag] {
override def toString = id + "->" + dependencies.mkString("[", ",", "]")
}

View File

@ -0,0 +1,35 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
package internal
package util
import sjsonnew.shaded.scalajson.ast.unsafe._
import sjsonnew._, BasicJsonProtocol._, support.scalajson.unsafe._
import HListFormats._
class HListFormatSpec extends UnitSpec {
val quux = 23 :+: "quux" :+: true :+: HNil
it should "round trip quux" in assertRoundTrip(quux)
it should "round trip hnil" in assertRoundTrip(HNil)
it should "have a flat structure for quux" in assertJsonString(quux, """[23,"quux",true]""")
it should "have a flat structure for hnil" in assertJsonString(HNil, "[]")
def assertRoundTrip[A: JsonWriter: JsonReader](x: A) = {
val jsonString: String = toJsonString(x)
val jValue: JValue = Parser.parseUnsafe(jsonString)
val y: A = Converter.fromJson[A](jValue).get
assert(x === y)
}
def assertJsonString[A: JsonWriter](x: A, s: String) = assert(toJsonString(x) === s)
def toJsonString[A: JsonWriter](x: A): String = CompactPrinter(Converter.toJson(x).get)
}

View File

@ -0,0 +1,39 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import org.scalacheck._
import Prop._
object KeyTest extends Properties("AttributeKey") {
property("equality") = {
compare(AttributeKey[Int]("test"), AttributeKey[Int]("test"), true) &&
compare(AttributeKey[Int]("test"), AttributeKey[Int]("test", "description"), true) &&
compare(AttributeKey[Int]("test", "a"), AttributeKey[Int]("test", "b"), true) &&
compare(AttributeKey[Int]("test"), AttributeKey[Int]("tests"), false) &&
compare(AttributeKey[Int]("test"), AttributeKey[Double]("test"), false) &&
compare(AttributeKey[java.lang.Integer]("test"), AttributeKey[Int]("test"), false) &&
compare(AttributeKey[Map[Int, String]]("test"), AttributeKey[Map[Int, String]]("test"), true) &&
compare(AttributeKey[Map[Int, String]]("test"), AttributeKey[Map[Int, _]]("test"), false)
}
def compare(a: AttributeKey[_], b: AttributeKey[_], same: Boolean) =
("a.label: " + a.label) |:
("a.manifest: " + a.manifest) |:
("b.label: " + b.label) |:
("b.manifest: " + b.manifest) |:
("expected equal? " + same) |:
compare0(a, b, same)
def compare0(a: AttributeKey[_], b: AttributeKey[_], same: Boolean) =
if (same) {
("equality" |: (a == b)) &&
("hash" |: (a.hashCode == b.hashCode))
} else
("equality" |: (a != b))
}

View File

@ -0,0 +1,22 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import Types._
// compilation test
object PMapTest {
val mp = new DelegatingPMap[Some, Id](new collection.mutable.HashMap)
mp(Some("asdf")) = "a"
mp(Some(3)) = 9
val x = Some(3) :^: Some("asdf") :^: KNil
val y = x.transform[Id](mp)
assert(y.head == 9)
assert(y.tail.head == "a")
assert(y.tail.tail == KNil)
}

View File

@ -0,0 +1,97 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import sbt.util.Show
/** Define our settings system */
// A basic scope indexed by an integer.
final case class Scope(nestIndex: Int, idAtIndex: Int = 0)
// Extend the Init trait.
// (It is done this way because the Scope type parameter is used everywhere in Init.
// Lots of type constructors would become binary, which as you may know requires lots of type lambdas
// when you want a type function with only one parameter.
// That would be a general pain.)
case class SettingsExample() extends Init[Scope] {
// Provides a way of showing a Scope+AttributeKey[_]
val showFullKey: Show[ScopedKey[_]] = Show[ScopedKey[_]]((key: ScopedKey[_]) => {
s"${key.scope.nestIndex}(${key.scope.idAtIndex})/${key.key.label}"
})
// A sample delegation function that delegates to a Scope with a lower index.
val delegates: Scope => Seq[Scope] = {
case s @ Scope(index, proj) =>
s +: (if (index <= 0) Nil
else {
(if (proj > 0) List(Scope(index)) else Nil) ++: delegates(Scope(index - 1))
})
}
// Not using this feature in this example.
val scopeLocal: ScopeLocal = _ => Nil
// These three functions + a scope (here, Scope) are sufficient for defining our settings system.
}
/** Usage Example **/
case class SettingsUsage(val settingsExample: SettingsExample) {
import settingsExample._
// Define some keys
val a = AttributeKey[Int]("a")
val b = AttributeKey[Int]("b")
// Scope these keys
val a3 = ScopedKey(Scope(3), a)
val a4 = ScopedKey(Scope(4), a)
val a5 = ScopedKey(Scope(5), a)
val b4 = ScopedKey(Scope(4), b)
// Define some settings
val mySettings: Seq[Setting[_]] = Seq(
setting(a3, value(3)),
setting(b4, map(a4)(_ * 3)),
update(a5)(_ + 1)
)
// "compiles" and applies the settings.
// This can be split into multiple steps to access intermediate results if desired.
// The 'inspect' command operates on the output of 'compile', for example.
val applied: Settings[Scope] =
makeWithCompiledMap(mySettings)(delegates, scopeLocal, showFullKey)._2
// Show results.
/* for(i <- 0 to 5; k <- Seq(a, b)) {
println( k.label + i + " = " + applied.get( Scope(i), k) )
}*/
/**
* Output:
* For the None results, we never defined the value and there was no value to delegate to.
* For a3, we explicitly defined it to be 3.
* a4 wasn't defined, so it delegates to a3 according to our delegates function.
* b4 gets the value for a4 (which delegates to a3, so it is 3) and multiplies by 3
* a5 is defined as the previous value of a5 + 1 and
* since no previous value of a5 was defined, it delegates to a4, resulting in 3+1=4.
* b5 isn't defined explicitly, so it delegates to b4 and is therefore equal to 9 as well
* a0 = None
* b0 = None
* a1 = None
* b1 = None
* a2 = None
* b2 = None
* a3 = Some(3)
* b3 = None
* a4 = Some(3)
* b4 = Some(9)
* a5 = Some(4)
* b5 = Some(9)
*/
}

View File

@ -0,0 +1,219 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import org.scalacheck._, Prop._
object SettingsTest extends Properties("settings") {
val settingsExample: SettingsExample = SettingsExample()
import settingsExample._
val settingsUsage = SettingsUsage(settingsExample)
import settingsUsage._
import scala.reflect.Manifest
final val ChainMax = 5000
lazy val chainLengthGen = Gen.choose(1, ChainMax)
property("Basic settings test") = secure(all(tests: _*))
property("Basic chain") = forAll(chainLengthGen) { (i: Int) =>
val abs = math.abs(i)
singleIntTest(chain(abs, value(0)), abs)
}
property("Basic bind chain") = forAll(chainLengthGen) { (i: Int) =>
val abs = math.abs(i)
singleIntTest(chainBind(value(abs)), 0)
}
property("Allows references to completed settings") = forAllNoShrink(30) { allowedReference }
final def allowedReference(intermediate: Int): Prop = {
val top = value(intermediate)
def iterate(init: Initialize[Int]): Initialize[Int] =
bind(init) { t =>
if (t <= 0)
top
else
iterate(value(t - 1))
}
evaluate(setting(chk, iterate(top)) :: Nil); true
}
property("Derived setting chain depending on (prev derived, normal setting)") =
forAllNoShrink(Gen.choose(1, 100).label("numSettings")) { derivedSettings }
final def derivedSettings(nr: Int): Prop = {
val genScopedKeys = {
// We wan
// t to generate lists of keys that DO NOT inclue the "ch" key we use to check things.
val attrKeys = mkAttrKeys[Int](nr).filter(_.forall(_.label != "ch"))
attrKeys map (_ map (ak => ScopedKey(Scope(0), ak)))
}.label("scopedKeys").filter(_.nonEmpty)
forAll(genScopedKeys) { scopedKeys =>
try {
// Note; It's evil to grab last IF you haven't verified the set can't be empty.
val last = scopedKeys.last
val derivedSettings: Seq[Setting[Int]] = (
for {
List(scoped0, scoped1) <- chk :: scopedKeys sliding 2
nextInit = if (scoped0 == chk) chk
else
(scoped0 zipWith chk) { (p, _) =>
p + 1
}
} yield derive(setting(scoped1, nextInit))
).toSeq
{
// Note: This causes a cycle refernec error, quite frequently.
checkKey(last, Some(nr - 1), evaluate(setting(chk, value(0)) +: derivedSettings)) :| "Not derived?"
} && {
checkKey(last, None, evaluate(derivedSettings)) :| "Should not be derived"
}
} catch {
case t: Throwable =>
// TODO - For debugging only.
t.printStackTrace(System.err)
throw t
}
}
}
private def mkAttrKeys[T](nr: Int)(implicit mf: Manifest[T]): Gen[List[AttributeKey[T]]] = {
import Gen._
val nonEmptyAlphaStr =
nonEmptyListOf(alphaChar)
.map({ xs: List[Char] =>
val s = xs.mkString
s.take(1).toLowerCase + s.drop(1)
})
.suchThat(_.forall(_.isLetter))
(for {
list <- Gen.listOfN(nr, nonEmptyAlphaStr) suchThat (l => l.size == l.distinct.size)
item <- list
} yield AttributeKey[T](item)).label(s"mkAttrKeys($nr)")
}
property("Derived setting(s) replace DerivedSetting in the Seq[Setting[_]]") =
derivedKeepsPosition
final def derivedKeepsPosition: Prop = {
val a: ScopedKey[Int] = ScopedKey(Scope(0), AttributeKey[Int]("a"))
val b: ScopedKey[Int] = ScopedKey(Scope(0), AttributeKey[Int]("b"))
val prop1 = {
val settings: Seq[Setting[_]] = Seq(
setting(a, value(3)),
setting(b, value(6)),
derive(setting(b, a)),
setting(a, value(5)),
setting(b, value(8))
)
val ev = evaluate(settings)
checkKey(a, Some(5), ev) && checkKey(b, Some(8), ev)
}
val prop2 = {
val settings: Seq[Setting[Int]] = Seq(
setting(a, value(3)),
setting(b, value(6)),
derive(setting(b, a)),
setting(a, value(5))
)
val ev = evaluate(settings)
checkKey(a, Some(5), ev) && checkKey(b, Some(5), ev)
}
prop1 && prop2
}
property(
"DerivedSetting in ThisBuild scopes derived settings under projects thus allowing safe +="
) = forAllNoShrink(Gen.choose(1, 100)) { derivedSettingsScope }
final def derivedSettingsScope(nrProjects: Int): Prop = {
forAll(mkAttrKeys[Int](2)) {
case List(key, derivedKey) =>
val projectKeys = for { proj <- 1 to nrProjects } yield ScopedKey(Scope(1, proj), key)
val projectDerivedKeys = for { proj <- 1 to nrProjects } yield ScopedKey(
Scope(1, proj),
derivedKey
)
val globalKey = ScopedKey(Scope(0), key)
val globalDerivedKey = ScopedKey(Scope(0), derivedKey)
// Each project defines an initial value, but the update is defined in globalKey.
// However, the derived Settings that come from this should be scoped in each project.
val settings: Seq[Setting[_]] =
derive(setting(globalDerivedKey, settingsExample.map(globalKey)(_ + 1))) +: projectKeys
.map(pk => setting(pk, value(0)))
val ev = evaluate(settings)
// Also check that the key has no value at the "global" scope
val props = for { pk <- projectDerivedKeys } yield checkKey(pk, Some(1), ev)
checkKey(globalDerivedKey, None, ev) && Prop.all(props: _*)
}
}
// Circular (dynamic) references currently loop infinitely.
// This is the expected behavior (detecting dynamic cycles is expensive),
// but it may be necessary to provide an option to detect them (with a performance hit)
// This would test that cycle detection.
// property("Catches circular references") = forAll(chainLengthGen) { checkCircularReferences _ }
final def checkCircularReferences(intermediate: Int): Prop = {
val ccr = new CCR(intermediate)
try {
evaluate(setting(chk, ccr.top) :: Nil); false
} catch {
case _: java.lang.Exception => true
}
}
def tests =
for (i <- 0 to 5; k <- Seq(a, b)) yield {
val expected = expectedValues(2 * i + (if (k == a) 0 else 1))
checkKey[Int](ScopedKey(Scope(i), k), expected, applied)
}
lazy val expectedValues = None :: None :: None :: None :: None :: None :: Some(3) :: None ::
Some(3) :: Some(9) :: Some(4) :: Some(9) :: Nil
lazy val ch = AttributeKey[Int]("ch")
lazy val chk = ScopedKey(Scope(0), ch)
def chain(i: Int, prev: Initialize[Int]): Initialize[Int] =
if (i <= 0) prev else chain(i - 1, prev(_ + 1))
def chainBind(prev: Initialize[Int]): Initialize[Int] =
bind(prev) { v =>
if (v <= 0) prev else chainBind(value(v - 1))
}
def singleIntTest(i: Initialize[Int], expected: Int) = {
val eval = evaluate(setting(chk, i) :: Nil)
checkKey(chk, Some(expected), eval)
}
def checkKey[T](key: ScopedKey[T], expected: Option[T], settings: Settings[Scope]) = {
val value = settings.get(key.scope, key.key)
("Key: " + key) |:
("Value: " + value) |:
("Expected: " + expected) |:
(value == expected)
}
def evaluate(settings: Seq[Setting[_]]): Settings[Scope] =
try {
makeWithCompiledMap(settings)(delegates, scopeLocal, showFullKey)._2
} catch {
case e: Throwable => e.printStackTrace(); throw e
}
}
// This setup is a workaround for module synchronization issues
final class CCR(intermediate: Int) {
import SettingsTest.settingsExample._
lazy val top = iterate(value(intermediate))
def iterate(init: Initialize[Int]): Initialize[Int] =
bind(init) { t =>
if (t <= 0)
top
else
iterate(value(t - 1))
}
}

View File

@ -0,0 +1,12 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import org.scalatest._
abstract class UnitSpec extends FlatSpec with Matchers

View File

@ -0,0 +1,4 @@
sbt: Completion Component
Copyright 2011 - 2017, Lightbend, Inc.
Copyright 2008 - 2010, Mark Harrah
Licensed under BSD-3-Clause license (see LICENSE)

View File

@ -0,0 +1,370 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import java.io._
import java.util.{ List => JList }
import jline.console.ConsoleReader
import jline.console.history.{ FileHistory, MemoryHistory }
import org.jline.reader.{
Candidate,
Completer,
EndOfFileException,
LineReader => JLineReader,
LineReaderBuilder,
ParsedLine,
UserInterruptException,
}
import org.jline.utils.ClosedException
import sbt.internal.util.complete.Parser
import sbt.io.syntax._
import scala.annotation.tailrec
import scala.concurrent.duration._
import scala.util.control.NonFatal
import java.nio.channels.ClosedByInterruptException
import java.net.MalformedURLException
import org.jline.builtins.InputRC
trait LineReader extends AutoCloseable {
def readLine(prompt: String, mask: Option[Char] = None): Option[String]
override def close(): Unit = {}
}
object LineReader {
val HandleCONT =
!java.lang.Boolean.getBoolean("sbt.disable.cont") && Signals.supported(Signals.CONT)
val MaxHistorySize = 500
private def completer(parser: Parser[_]): Completer = new Completer {
def complete(lr: JLineReader, pl: ParsedLine, candidates: JList[Candidate]): Unit = {
Parser.completions(parser, pl.line(), 10).get.foreach { c =>
/*
* For commands like `~` that delegate parsing to another parser, the `~` may be
* excluded from the completion result. For example,
* ~testOnly <TAB>
* might return results like
* 'testOnly ;'
* 'testOnly com.foo.FooSpec'
* ...
* If we use the raw display, JLine will reject the completions because they are
* missing the leading `~`. To workaround this, we append to the result to the
* line provided the line does not end with " ". This fixes the missing `~` in
* the prefix problem. We also need to split the line on space and take the
* last token and append to that otherwise the completion will double print
* the prefix, so that `testOnly com<Tab>` might expand to something like:
* `testOnly testOnly\ com.foo.FooSpec` instead of `testOnly com.foo.FooSpec`.
*/
if (c.append.nonEmpty) {
val cand = pl.line() match {
case line if line.endsWith(" ") => c.append
case line => line.split(" ").last + c.append
}
// https://github.com/jline/jline3/blob/9a4971868e4bdd29a36e454de01f54d3cd6071e0/reader/src/main/java/org/jline/reader/Candidate.java#L123-L131
// "If the candidate is complete and is selected, a space separator will be added."
val complete = false
candidates.add(new Candidate(cand, cand, null, null, null, null, complete))
}
}
}
}
private[this] def inputrcFileUrl(): Option[URL] = {
// keep jline2 compatibility
// https://github.com/jline/jline2/blob/12b98d94589e3bd6a6/src/main/java/jline/console/ConsoleReader.java#L291-L306
sys.props
.get("jline.inputrc")
.flatMap { path =>
try {
Some(url(path))
} catch {
case _: MalformedURLException =>
Some(file(path).toURI.toURL)
}
}
.orElse {
sys.props.get("user.home").map { home =>
val f = file(home) / ".inputrc"
(if (f.isFile) f else file("/etc/inputrc")).toURI.toURL
}
}
}
// cache on memory.
private[this] lazy val inputrcFileContents: Option[Array[Byte]] =
inputrcFileUrl().map(in => sbt.io.IO.readBytes(in.openStream()))
def createReader(
historyPath: Option[File],
parser: Parser[_],
terminal: Terminal,
): LineReader = {
// We may want to consider insourcing LineReader.java from jline. We don't otherwise
// directly need jline3 for sbt.
new LineReader {
override def readLine(prompt: String, mask: Option[Char]): Option[String] = {
val term = JLine3(terminal)
val reader = LineReaderBuilder.builder().terminal(term).completer(completer(parser)).build()
try {
inputrcFileContents.foreach { bytes =>
InputRC.configure(
reader,
new ByteArrayInputStream(bytes)
)
}
} catch {
case NonFatal(_) =>
// ignore
}
historyPath.foreach(f => reader.setVariable(JLineReader.HISTORY_FILE, f))
val signalRegistration = terminal match {
case _: Terminal.ConsoleTerminal => Some(Signals.register(() => terminal.write(-1)))
case _ => None
}
try terminal.withRawInput {
Option(mask.map(reader.readLine(prompt, _)).getOrElse(reader.readLine(prompt)))
} catch {
case e: EndOfFileException =>
if (terminal == Terminal.console && System.console == null) None
else Some("exit")
case _: IOError | _: ClosedException => Some("exit")
case _: UserInterruptException | _: ClosedByInterruptException |
_: UncheckedIOException =>
throw new InterruptedException
} finally {
signalRegistration.foreach(_.remove())
terminal.prompt.reset()
term.close()
}
}
}
}
def createJLine2Reader(
historyPath: Option[File],
terminal: Terminal,
prompt: Prompt = Prompt.Running,
): ConsoleReader = {
val cr = Terminal.createReader(terminal, prompt)
cr.setExpandEvents(false) // https://issues.scala-lang.org/browse/SI-7650
cr.setBellEnabled(false)
val h = historyPath match {
case None => new MemoryHistory
case Some(file) => new FileHistory(file): MemoryHistory
}
h.setMaxSize(MaxHistorySize)
cr.setHistory(h)
cr.setHistoryEnabled(true)
cr
}
def simple(terminal: Terminal): LineReader = new SimpleReader(None, HandleCONT, terminal)
def simple(
historyPath: Option[File],
handleCONT: Boolean = HandleCONT,
injectThreadSleep: Boolean = false
): LineReader = new SimpleReader(historyPath, handleCONT, injectThreadSleep)
}
abstract class JLine extends LineReader {
protected[this] def handleCONT: Boolean
protected[this] def reader: ConsoleReader
@deprecated("For binary compatibility only", "1.4.0")
protected[this] def injectThreadSleep: Boolean = false
@deprecated("For binary compatibility only", "1.4.0")
protected[this] lazy val in: InputStream = Terminal.wrappedSystemIn
override def readLine(prompt: String, mask: Option[Char] = None): Option[String] =
try {
unsynchronizedReadLine(prompt, mask)
} catch {
case _: InterruptedException =>
// println("readLine: InterruptedException")
Option("")
}
private[this] def unsynchronizedReadLine(prompt: String, mask: Option[Char]): Option[String] =
readLineWithHistory(prompt, mask) map { x =>
x.trim
}
private[this] def readLineWithHistory(prompt: String, mask: Option[Char]): Option[String] =
reader.getHistory match {
case fh: FileHistory =>
try readLineDirect(prompt, mask)
finally fh.flush()
case _ => readLineDirect(prompt, mask)
}
private[this] def readLineDirect(prompt: String, mask: Option[Char]): Option[String] =
if (handleCONT)
Signals.withHandler(() => resume(), signal = Signals.CONT)(
() => readLineDirectRaw(prompt, mask)
)
else
readLineDirectRaw(prompt, mask)
private[this] def readLineDirectRaw(prompt: String, mask: Option[Char]): Option[String] = {
val newprompt = handleMultilinePrompt(prompt)
mask match {
case Some(m) => Option(reader.readLine(newprompt, m))
case None => Option(reader.readLine(newprompt))
}
}
private[this] def handleMultilinePrompt(prompt: String): String = {
val lines0 = """\r?\n""".r.split(prompt)
lines0.length match {
case 0 | 1 => handleProgress(prompt)
case _ =>
val lines = lines0.toList map handleProgress
// Workaround for regression jline/jline2#205
reader.getOutput.write(lines.init.mkString("\n") + "\n")
lines.last
}
}
private[this] def handleProgress(prompt: String): String = {
import ConsoleAppender._
if (showProgress) s"$DeleteLine" + prompt
else prompt
}
private[this] def resume(): Unit = {
Terminal.reset()
reader.drawLine()
reader.flush()
}
}
@deprecated("Use LineReader apis", "1.4.0")
private[sbt] object JLine {
@deprecated("For binary compatibility only", "1.4.0")
protected[this] val originalIn = new FileInputStream(FileDescriptor.in)
@deprecated("Handled by Terminal.fixTerminalProperty", "1.4.0")
private[sbt] def fixTerminalProperty(): Unit = ()
@deprecated("For binary compatibility only", "1.4.0")
private[sbt] def makeInputStream(injectThreadSleep: Boolean): InputStream =
if (injectThreadSleep) new InputStreamWrapper(originalIn, 2.milliseconds)
else originalIn
// When calling this, ensure that enableEcho has been or will be called.
// TerminalFactory.get will initialize the terminal to disable echo.
@deprecated("Don't use jline.Terminal directly", "1.4.0")
private[sbt] def terminal: jline.Terminal = Terminal.deprecatedTeminal
/**
* For accessing the JLine Terminal object.
* This ensures synchronized access as well as re-enabling echo after getting the Terminal.
*/
@deprecated(
"Don't use jline.Terminal directly. Use Terminal.get.withCanonicalIn instead.",
"1.4.0"
)
def usingTerminal[T](f: jline.Terminal => T): T = f(Terminal.get.toJLine)
@deprecated("unused", "1.4.0")
def createReader(): ConsoleReader = createReader(None, Terminal.wrappedSystemIn)
@deprecated("Use LineReader.createReader", "1.4.0")
def createReader(historyPath: Option[File], in: InputStream): ConsoleReader = {
val cr = Terminal.createReader(Terminal.console, Prompt.Running)
cr.setExpandEvents(false) // https://issues.scala-lang.org/browse/SI-7650
cr.setBellEnabled(false)
val h = historyPath match {
case None => new MemoryHistory
case Some(file) => new FileHistory(file): MemoryHistory
}
h.setMaxSize(MaxHistorySize)
cr.setHistory(h)
cr
}
@deprecated("Avoid referencing JLine directly.", "1.4.0")
def withJLine[T](action: => T): T = Terminal.get.withRawInput(action)
@deprecated("Use LineReader.simple instead", "1.4.0")
def simple(
historyPath: Option[File],
handleCONT: Boolean = LineReader.HandleCONT,
injectThreadSleep: Boolean = false
): SimpleReader = new SimpleReader(historyPath, handleCONT, injectThreadSleep)
@deprecated("Use LineReader.MaxHistorySize", "1.4.0")
val MaxHistorySize = LineReader.MaxHistorySize
@deprecated("Use LineReader.HandleCONT", "1.4.0")
val HandleCONT = LineReader.HandleCONT
}
@deprecated("For binary compatibility only", "1.4.0")
private[sbt] class InputStreamWrapper(is: InputStream, val poll: Duration)
extends FilterInputStream(is) {
@tailrec final override def read(): Int =
if (is.available() != 0) is.read()
else {
Thread.sleep(poll.toMillis)
read()
}
@tailrec final override def read(b: Array[Byte]): Int =
if (is.available() != 0) is.read(b)
else {
Thread.sleep(poll.toMillis)
read(b)
}
@tailrec final override def read(b: Array[Byte], off: Int, len: Int): Int =
if (is.available() != 0) is.read(b, off, len)
else {
Thread.sleep(poll.toMillis)
read(b, off, len)
}
}
final class FullReader(
historyPath: Option[File],
complete: Parser[_],
val handleCONT: Boolean,
terminal: Terminal
) extends JLine {
@deprecated("Use the constructor with no injectThreadSleep parameter", "1.4.0")
def this(
historyPath: Option[File],
complete: Parser[_],
handleCONT: Boolean = LineReader.HandleCONT,
injectThreadSleep: Boolean = false
) =
this(
historyPath,
complete,
handleCONT,
Terminal.console
)
protected[this] val reader: ConsoleReader = {
val cr = LineReader.createJLine2Reader(historyPath, terminal)
sbt.internal.util.complete.JLineCompletion.installCustomCompletor(cr, complete)
cr
}
}
class SimpleReader private[sbt] (
historyPath: Option[File],
val handleCONT: Boolean,
terminal: Terminal
) extends JLine {
def this(historyPath: Option[File], handleCONT: Boolean, injectThreadSleep: Boolean) =
this(historyPath, handleCONT, Terminal.console)
protected[this] lazy val reader: ConsoleReader =
LineReader.createJLine2Reader(historyPath, terminal)
}
object SimpleReader extends SimpleReader(None, LineReader.HandleCONT, false) {
def apply(terminal: Terminal): SimpleReader =
new SimpleReader(None, LineReader.HandleCONT, terminal)
}

View File

@ -0,0 +1,173 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
/**
* Represents a set of completions.
* It exists instead of implicitly defined operations on top of Set[Completion]
* for laziness.
*/
sealed trait Completions {
def get: Set[Completion]
final def x(o: Completions): Completions = flatMap(_ x o)
final def ++(o: Completions): Completions = Completions(get ++ o.get)
final def +:(o: Completion): Completions = Completions(get + o)
final def filter(f: Completion => Boolean): Completions = Completions(get filter f)
final def filterS(f: String => Boolean): Completions = filter(c => f(c.append))
override def toString = get.mkString("Completions(", ",", ")")
final def flatMap(f: Completion => Completions): Completions =
Completions(get.flatMap(c => f(c).get))
final def map(f: Completion => Completion): Completions = Completions(get map f)
override final def hashCode = get.hashCode
override final def equals(o: Any) = o match {
case c: Completions => get == c.get; case _ => false
}
}
object Completions {
/** Returns a lazy Completions instance using the provided Completion Set. */
def apply(cs: => Set[Completion]): Completions = new Completions {
lazy val get = cs
}
/** Returns a strict Completions instance using the provided Completion Set. */
def strict(cs: Set[Completion]): Completions = apply(cs)
/**
* No suggested completions, not even the empty Completion.
* This typically represents invalid input.
*/
val nil: Completions = strict(Set.empty)
/**
* Only includes an empty Suggestion.
* This typically represents valid input that either has no completions or accepts no further input.
*/
val empty: Completions = strict(Set.empty + Completion.empty)
/** Returns a strict Completions instance containing only the provided Completion.*/
def single(c: Completion): Completions = strict(Set.empty + c)
}
/**
* Represents a completion.
* The abstract members `display` and `append` are best explained with an example.
*
* Assuming space-delimited tokens, processing this:
* am is are w<TAB>
* could produce these Completions:
* Completion { display = "was"; append = "as" }
* Completion { display = "were"; append = "ere" }
* to suggest the tokens "was" and "were".
*
* In this way, two pieces of information are preserved:
* 1) what needs to be appended to the current input if a completion is selected
* 2) the full token being completed, which is useful for presenting a user with choices to select
*/
sealed trait Completion {
/** The proposed suffix to append to the existing input to complete the last token in the input.*/
def append: String
/** The string to present to the user to represent the full token being suggested.*/
def display: String
/** True if this Completion is suggesting the empty string.*/
def isEmpty: Boolean
/** Appends the completions in `o` with the completions in this Completion.*/
def ++(o: Completion): Completion = Completion.concat(this, o)
final def x(o: Completions): Completions =
if (Completion evaluatesRight this) o.map(this ++ _) else Completions.strict(Set.empty + this)
override final lazy val hashCode = Completion.hashCode(this)
override final def equals(o: Any) = o match {
case c: Completion => Completion.equal(this, c); case _ => false
}
}
final class DisplayOnly(val display: String) extends Completion {
def isEmpty = display.isEmpty
def append = ""
override def toString = "{" + display + "}"
}
final class Token(val display: String, val append: String) extends Completion {
def isEmpty = display.isEmpty && append.isEmpty
override final def toString = "[" + display + "]++" + append
}
final class Suggestion(val append: String) extends Completion {
def isEmpty = append.isEmpty
def display = append
override def toString = append
}
object Completion {
def concat(a: Completion, b: Completion): Completion =
(a, b) match {
case (as: Suggestion, bs: Suggestion) => suggestion(as.append + bs.append)
case (at: Token, _) if at.append.isEmpty => b
case _ if a.isEmpty => b
case _ => a
}
def evaluatesRight(a: Completion): Boolean =
a match {
case _: Suggestion => true
case at: Token if at.append.isEmpty => true
case _ => a.isEmpty
}
def equal(a: Completion, b: Completion): Boolean =
(a, b) match {
case (as: Suggestion, bs: Suggestion) => as.append == bs.append
case (ad: DisplayOnly, bd: DisplayOnly) => ad.display == bd.display
case (at: Token, bt: Token) => at.display == bt.display && at.append == bt.append
case _ => false
}
def hashCode(a: Completion): Int =
a match {
case as: Suggestion => (0, as.append).hashCode
case ad: DisplayOnly => (1, ad.display).hashCode
case at: Token => (2, at.display, at.append).hashCode
}
val empty: Completion = suggestion("")
def single(c: Char): Completion = suggestion(c.toString)
def displayOnly(value: String): Completion = new DisplayOnly(value)
def token(prepend: String, append: String): Completion =
new Token(prepend + append, append)
/** @since 0.12.1 */
def tokenDisplay(append: String, display: String): Completion = new Token(display, append)
def suggestion(value: String): Completion = new Suggestion(value)
@deprecated("No longer used. for binary compatibility", "1.1.0")
private[complete] def displayOnly(value: => String): Completion = new DisplayOnly(value)
@deprecated("No longer used. for binary compatibility", "1.1.0")
private[complete] def token(prepend: => String, append: => String): Completion =
new Token(prepend + append, append)
@deprecated("No longer used. for binary compatibility", "1.1.0")
private[complete] def suggestion(value: => String): Completion = new Suggestion(value)
}

View File

@ -0,0 +1,60 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
import java.lang.Character.{ toLowerCase => lower }
/** @author Paul Phillips */
object EditDistance {
/**
* Translated from the java version at
* http://www.merriampark.com/ld.htm
* which is declared to be public domain.
*/
def levenshtein(
s: String,
t: String,
insertCost: Int = 1,
deleteCost: Int = 1,
subCost: Int = 1,
transposeCost: Int = 1,
matchCost: Int = 0,
caseCost: Int = 1,
transpositions: Boolean = false
): Int = {
val _ = transposeCost
val n = s.length
val m = t.length
if (n == 0) return m
if (m == 0) return n
val d = Array.ofDim[Int](n + 1, m + 1)
0 to n foreach (x => d(x)(0) = x)
0 to m foreach (x => d(0)(x) = x)
for (i <- 1 to n; s_i = s(i - 1); j <- 1 to m) {
val t_j = t(j - 1)
val cost = if (s_i == t_j) matchCost else if (lower(s_i) == lower(t_j)) caseCost else subCost
val c1 = d(i - 1)(j) + deleteCost
val c2 = d(i)(j - 1) + insertCost
val c3 = d(i - 1)(j - 1) + cost
d(i)(j) = c1 min c2 min c3
if (transpositions) {
if (i > 1 && j > 1 && s(i - 1) == t(j - 2) && s(i - 2) == t(j - 1))
d(i)(j) = d(i)(j) min (d(i - 2)(j - 2) + cost)
}
}
d(n)(m)
}
}

View File

@ -0,0 +1,75 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
import java.io.File
import sbt.io.IO
/**
* These sources of examples are used in parsers for user input completion. An example of such a source is the
* [[sbt.internal.util.complete.FileExamples]] class, which provides a list of suggested files to the user as they press the
* TAB key in the console.
*/
trait ExampleSource {
/**
* @return a (possibly lazy) list of completion example strings. These strings are continuations of user's input. The
* user's input is incremented with calls to [[withAddedPrefix]].
*/
def apply(): Iterable[String]
/**
* @param addedPrefix a string that just typed in by the user.
* @return a new source of only those examples that start with the string typed by the user so far (with addition of
* the just added prefix).
*/
def withAddedPrefix(addedPrefix: String): ExampleSource
}
/**
* A convenience example source that wraps any collection of strings into a source of examples.
* @param examples the examples that will be displayed to the user when they press the TAB key.
*/
sealed case class FixedSetExamples(examples: Iterable[String]) extends ExampleSource {
override def withAddedPrefix(addedPrefix: String): ExampleSource =
FixedSetExamples(examplesWithRemovedPrefix(addedPrefix))
override def apply(): Iterable[String] = examples
private def examplesWithRemovedPrefix(prefix: String) = examples.collect {
case example if example startsWith prefix => example substring prefix.length
}
}
/**
* Provides path completion examples based on files in the base directory.
* @param base the directory within which this class will search for completion examples.
* @param prefix the part of the path already written by the user.
*/
class FileExamples(base: File, prefix: String = "") extends ExampleSource {
override def apply(): Stream[String] = files(base).map(_ substring prefix.length)
override def withAddedPrefix(addedPrefix: String): FileExamples =
new FileExamples(base, prefix + addedPrefix)
protected def files(directory: File): Stream[String] = {
val childPaths = IO.listFiles(directory).toStream
val prefixedDirectChildPaths = childPaths map { IO.relativize(base, _).get } filter {
_ startsWith prefix
}
val dirsToRecurseInto = childPaths filter { _.isDirectory } map { IO.relativize(base, _).get } filter {
dirStartsWithPrefix
}
prefixedDirectChildPaths append dirsToRecurseInto.flatMap(dir => files(new File(base, dir)))
}
private def dirStartsWithPrefix(relativizedPath: String): Boolean =
(relativizedPath startsWith prefix) || (prefix startsWith relativizedPath)
}

View File

@ -0,0 +1,63 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
import History.number
import java.io.File
final class History private (val lines: IndexedSeq[String], val path: Option[File]) {
private def reversed = lines.reverse
def all: Seq[String] = lines
def size = lines.length
def !! : Option[String] = !-(1)
def apply(i: Int): Option[String] =
if (0 <= i && i < size) Some(lines(i))
else {
sys.error("Invalid history index: " + i)
}
def !(i: Int): Option[String] = apply(i)
def !(s: String): Option[String] =
number(s) match {
case Some(n) => if (n < 0) !-(-n) else apply(n)
case None => nonEmpty(s) { reversed.find(_.startsWith(s)) }
}
def !-(n: Int): Option[String] = apply(size - n - 1)
def !?(s: String): Option[String] = nonEmpty(s) { reversed.drop(1).find(_.contains(s)) }
private def nonEmpty[T](s: String)(act: => Option[T]): Option[T] =
if (s.isEmpty)
sys.error("No action specified to history command")
else
act
def list(historySize: Int, show: Int): Seq[String] =
lines.toList
.drop(scala.math.max(0, lines.size - historySize))
.zipWithIndex
.map { case (line, number) => " " + number + " " + line }
.takeRight(show max 1)
}
object History {
def apply(lines: Seq[String], path: Option[File], error: String => Unit): History =
new History(lines.toIndexedSeq, path)
def apply(lines: Seq[String], path: Option[File]): History =
new History(lines.toIndexedSeq, path)
def number(s: String): Option[Int] =
try {
Some(s.toInt)
} catch { case _: NumberFormatException => None }
}

View File

@ -0,0 +1,88 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
import sbt.io.IO
import Util.{ AnyOps, nil }
object HistoryCommands {
val Start = "!"
// second characters
val Contains = "?"
val Last = "!"
val ListCommands = ":"
def ContainsFull = h(Contains)
def LastFull = h(Last)
def ListFull = h(ListCommands)
def ListN = ListFull + "n"
def ContainsString = ContainsFull + "string"
def StartsWithString = Start + "string"
def Previous = Start + "-n"
def Nth = Start + "n"
private def h(s: String) = Start + s
def plainCommands = Seq(ListFull, Start, LastFull, ContainsFull)
def descriptions = Seq(
LastFull -> "Execute the last command again",
ListFull -> "Show all previous commands",
ListN -> "Show the last n commands",
Nth -> ("Execute the command with index n, as shown by the " + ListFull + " command"),
Previous -> "Execute the nth command before this one",
StartsWithString -> "Execute the most recent command starting with 'string'",
ContainsString -> "Execute the most recent command containing 'string'"
)
def helpString =
"History commands:\n " + (descriptions
.map { case (c, d) => c + " " + d })
.mkString("\n ")
def printHelp(): Unit = println(helpString)
def printHistory(history: complete.History, historySize: Int, show: Int): Unit =
history.list(historySize, show).foreach(println)
import DefaultParsers._
val MaxLines = 500
lazy val num = token(NatBasic, "<integer>")
lazy val last = Last ^^^ { execute(_.!!) }
lazy val list = ListCommands ~> (num ?? Int.MaxValue) map { show => (h: History) =>
{ printHistory(h, MaxLines, show); nil[String].some }
}
lazy val execStr = flag('?') ~ token(any.+.string, "<string>") map {
case (contains, str) =>
execute(h => if (contains) h !? str else h ! str)
}
lazy val execInt = flag('-') ~ num map {
case (neg, value) =>
execute(h => if (neg) h !- value else h ! value)
}
lazy val help = success((h: History) => { printHelp(); nil[String].some })
def execute(f: History => Option[String]): History => Option[List[String]] = (h: History) => {
val command = f(h).filterNot(_.startsWith(Start))
val lines = h.lines.toArray
command.foreach(lines(lines.length - 1) = _)
h.path foreach { h =>
IO.writeLines(h, lines)
}
command.toList.some
}
val actionParser: Parser[complete.History => Option[List[String]]] =
Start ~> (help | last | execInt | list | execStr) // execStr must come last
}

View File

@ -0,0 +1,184 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
import jline.console.ConsoleReader
import jline.console.completer.{ Completer, CompletionHandler }
import scala.annotation.tailrec
import scala.collection.JavaConverters._
object JLineCompletion {
def installCustomCompletor(reader: ConsoleReader, parser: Parser[_]): Unit =
installCustomCompletor(reader)(parserAsCompletor(parser))
def installCustomCompletor(reader: ConsoleReader)(
complete: (String, Int) => (Seq[String], Seq[String])
): Unit =
installCustomCompletor(customCompletor(complete), reader)
def installCustomCompletor(
complete: (ConsoleReader, Int) => Boolean,
reader: ConsoleReader
): Unit = {
reader.removeCompleter(DummyCompletor)
reader.addCompleter(DummyCompletor)
reader.setCompletionHandler(new CustomHandler(complete))
}
private[this] final class CustomHandler(completeImpl: (ConsoleReader, Int) => Boolean)
extends CompletionHandler {
private[this] var previous: Option[(String, Int)] = None
private[this] var level: Int = 1
override def complete(
reader: ConsoleReader,
candidates: java.util.List[CharSequence],
position: Int
) = {
val current = Some(bufferSnapshot(reader))
level = if (current == previous) level + 1 else 1
previous = current
try completeImpl(reader, level)
catch {
case e: Exception =>
reader.print("\nException occurred while determining completions.")
e.printStackTrace()
false
}
}
}
// always provides dummy completions so that the custom completion handler gets called
// (ConsoleReader doesn't call the handler if there aren't any completions)
// the custom handler will then throw away the candidates and call the custom function
private[this] final object DummyCompletor extends Completer {
override def complete(
buffer: String,
cursor: Int,
candidates: java.util.List[CharSequence]
): Int = {
candidates.asInstanceOf[java.util.List[String]] add "dummy"
0
}
}
def parserAsCompletor(p: Parser[_]): (String, Int) => (Seq[String], Seq[String]) =
(str, level) => convertCompletions(Parser.completions(p, str, level))
def convertCompletions(c: Completions): (Seq[String], Seq[String]) = {
val cs = c.get
if (cs.isEmpty)
(Nil, "{invalid input}" :: Nil)
else
convertCompletions(cs)
}
def convertCompletions(cs: Set[Completion]): (Seq[String], Seq[String]) = {
val (insert, display) =
cs.foldLeft((Set.empty[String], Set.empty[String])) {
case (t @ (insert, display), comp) =>
if (comp.isEmpty) t
else (appendNonEmpty(insert, comp.append), appendNonEmpty(display, comp.display))
}
(insert.toSeq, display.toSeq.sorted)
}
def appendNonEmpty(set: Set[String], add: String) = if (add.trim.isEmpty) set else set + add
def customCompletor(
f: (String, Int) => (Seq[String], Seq[String])
): (ConsoleReader, Int) => Boolean =
(reader, level) => {
val success = complete(beforeCursor(reader), string => f(string, level), reader)
reader.flush()
success
}
def bufferSnapshot(reader: ConsoleReader): (String, Int) = {
val b = reader.getCursorBuffer
(b.buffer.toString, b.cursor)
}
def beforeCursor(reader: ConsoleReader): String = {
val b = reader.getCursorBuffer
b.buffer.substring(0, b.cursor)
}
// returns false if there was nothing to insert and nothing to display
def complete(
beforeCursor: String,
completions: String => (Seq[String], Seq[String]),
reader: ConsoleReader
): Boolean = {
val (insert, display) = completions(beforeCursor)
val common = commonPrefix(insert)
if (common.isEmpty)
if (display.isEmpty)
()
else
showCompletions(display, reader)
else
appendCompletion(common, reader)
!(common.isEmpty && display.isEmpty)
}
def appendCompletion(common: String, reader: ConsoleReader): Unit = {
reader.getCursorBuffer.write(common)
reader.redrawLine()
}
/**
* `display` is assumed to be the exact strings requested to be displayed.
* In particular, duplicates should have been removed already.
*/
def showCompletions(display: Seq[String], reader: ConsoleReader): Unit = {
printCompletions(display, reader)
reader.drawLine()
}
def printCompletions(cs: Seq[String], reader: ConsoleReader): Unit = {
val print = shouldPrint(cs, reader)
reader.println()
if (print) printLinesAndColumns(cs, reader)
}
def printLinesAndColumns(cs: Seq[String], reader: ConsoleReader): Unit = {
val (lines, columns) = cs partition hasNewline
for (line <- lines) {
reader.print(line)
if (line.charAt(line.length - 1) != '\n')
reader.println()
}
reader.printColumns(columns.map(_.trim).asJava)
}
def hasNewline(s: String): Boolean = s.indexOf('\n') >= 0
def shouldPrint(cs: Seq[String], reader: ConsoleReader): Boolean = {
val size = cs.size
(size <= reader.getAutoprintThreshold) ||
confirm("Display all %d possibilities? (y or n) ".format(size), 'y', 'n', reader)
}
def confirm(prompt: String, trueC: Char, falseC: Char, reader: ConsoleReader): Boolean = {
reader.println()
reader.print(prompt)
reader.flush()
reader.readCharacter(trueC, falseC) == trueC
}
def commonPrefix(s: Seq[String]): String = if (s.isEmpty) "" else s reduceLeft commonPrefix
def commonPrefix(a: String, b: String): String = {
val len = scala.math.min(a.length, b.length)
@tailrec def loop(i: Int): Int = if (i >= len) len else if (a(i) != b(i)) i else loop(i + 1)
a.substring(0, loop(0))
}
}

View File

@ -0,0 +1,988 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
import Parser._
import sbt.internal.util.Types.{ left, right, some }
import sbt.internal.util.Util.{ makeList, separate }
/**
* A String parser that provides semi-automatic tab completion.
* A successful parse results in a value of type `T`.
* The methods in this trait are what must be implemented to define a new Parser implementation, but are not typically useful for common usage.
* Instead, most useful methods for combining smaller parsers into larger parsers are implicitly added by the [[RichParser]] type.
*/
trait Parser[+T] {
def derive(i: Char): Parser[T]
def resultEmpty: Result[T]
def result: Option[T]
def completions(level: Int): Completions
def failure: Option[Failure]
def isTokenStart = false
def ifValid[S](p: => Parser[S]): Parser[S]
def valid: Boolean
}
sealed trait RichParser[A] {
/** Apply the original Parser and then apply `next` (in order). The result of both is provides as a pair. */
def ~[B](next: Parser[B]): Parser[(A, B)]
/** Apply the original Parser one or more times and provide the non-empty sequence of results.*/
def + : Parser[Seq[A]]
/** Apply the original Parser zero or more times and provide the (potentially empty) sequence of results.*/
def * : Parser[Seq[A]]
/** Apply the original Parser zero or one times, returning None if it was applied zero times or the result wrapped in Some if it was applied once.*/
def ? : Parser[Option[A]]
/** Apply either the original Parser or `b`.*/
def |[B >: A](b: Parser[B]): Parser[B]
/** Apply either the original Parser or `b`.*/
def ||[B](b: Parser[B]): Parser[Either[A, B]]
/** Apply the original Parser to the input and then apply `f` to the result.*/
def map[B](f: A => B): Parser[B]
/**
* Returns the original parser. This is useful for converting literals to Parsers.
* For example, `'c'.id` or `"asdf".id`
*/
def id: Parser[A]
/** Apply the original Parser, but provide `value` as the result if it succeeds. */
def ^^^[B](value: B): Parser[B]
/** Apply the original Parser, but provide `alt` as the result if it fails.*/
def ??[B >: A](alt: B): Parser[B]
/**
* Produces a Parser that applies the original Parser and then applies `next` (in order), discarding the result of `next`.
* (The arrow point in the direction of the retained result.)
*/
def <~[B](b: Parser[B]): Parser[A]
/**
* Produces a Parser that applies the original Parser and then applies `next` (in order), discarding the result of the original parser.
* (The arrow point in the direction of the retained result.)
*/
def ~>[B](b: Parser[B]): Parser[B]
/** Uses the specified message if the original Parser fails.*/
def !!!(msg: String): Parser[A]
/**
* If an exception is thrown by the original Parser,
* capture it and fail locally instead of allowing the exception to propagate up and terminate parsing.
*/
def failOnException: Parser[A]
/**
* Apply the original parser, but only succeed if `o` also succeeds.
* Note that `o` does not need to consume the same amount of input to satisfy this condition.
*/
def &(o: Parser[_]): Parser[A]
/** Explicitly defines the completions for the original Parser.*/
def examples(s: String*): Parser[A]
/** Explicitly defines the completions for the original Parser.*/
def examples(s: Set[String], check: Boolean = false): Parser[A]
/**
* @param exampleSource the source of examples when displaying completions to the user.
* @param maxNumberOfExamples limits the number of examples that the source of examples should return. This can
* prevent lengthy pauses and avoids bad interactive user experience.
* @param removeInvalidExamples indicates whether completion examples should be checked for validity (against the
* given parser). Invalid examples will be filtered out and only valid suggestions will
* be displayed.
* @return a new parser with a new source of completions.
*/
def examples(
exampleSource: ExampleSource,
maxNumberOfExamples: Int,
removeInvalidExamples: Boolean
): Parser[A]
/**
* @param exampleSource the source of examples when displaying completions to the user.
* @return a new parser with a new source of completions. It displays at most 25 completion examples and does not
* remove invalid examples.
*/
def examples(exampleSource: ExampleSource): Parser[A] =
examples(exampleSource, maxNumberOfExamples = 25, removeInvalidExamples = false)
/** Converts a Parser returning a Char sequence to a Parser returning a String.*/
def string(implicit ev: A <:< Seq[Char]): Parser[String]
/**
* Produces a Parser that filters the original parser.
* If 'f' is not true when applied to the output of the original parser, the Parser returned by this method fails.
* The failure message is constructed by applying `msg` to the String that was successfully parsed by the original parser.
*/
def filter(f: A => Boolean, msg: String => String): Parser[A]
/** Applies the original parser, applies `f` to the result to get the next parser, and applies that parser and uses its result for the overall result. */
def flatMap[B](f: A => Parser[B]): Parser[B]
}
/** Contains Parser implementation helper methods not typically needed for using parsers. */
object Parser extends ParserMain {
sealed abstract class Result[+T] {
def isFailure: Boolean
def isValid: Boolean
def errors: Seq[String]
def or[B >: T](b: => Result[B]): Result[B]
def either[B](b: => Result[B]): Result[Either[T, B]]
def map[B](f: T => B): Result[B]
def flatMap[B](f: T => Result[B]): Result[B]
def &&(b: => Result[_]): Result[T]
def filter(f: T => Boolean, msg: => String): Result[T]
def seq[B](b: => Result[B]): Result[(T, B)] = app(b)((m, n) => (m, n))
def app[B, C](b: => Result[B])(f: (T, B) => C): Result[C]
def toEither: Either[() => Seq[String], T]
}
final case class Value[+T](value: T) extends Result[T] {
def isFailure = false
def isValid: Boolean = true
def errors = Nil
def app[B, C](b: => Result[B])(f: (T, B) => C): Result[C] = b match {
case fail: Failure => fail
case Value(bv) => Value(f(value, bv))
}
def &&(b: => Result[_]): Result[T] = b match { case f: Failure => f; case _ => this }
def or[B >: T](b: => Result[B]): Result[B] = this
def either[B](b: => Result[B]): Result[Either[T, B]] = Value(Left(value))
def map[B](f: T => B): Result[B] = Value(f(value))
def flatMap[B](f: T => Result[B]): Result[B] = f(value)
def filter(f: T => Boolean, msg: => String): Result[T] = if (f(value)) this else mkFailure(msg)
def toEither = Right(value)
}
final class Failure private[sbt] (mkErrors: => Seq[String], val definitive: Boolean)
extends Result[Nothing] {
lazy val errors: Seq[String] = mkErrors
def isFailure = true
def isValid = false
def map[B](f: Nothing => B) = this
def flatMap[B](f: Nothing => Result[B]) = this
def or[B](b: => Result[B]): Result[B] = b match {
case v: Value[B] => v
case f: Failure => if (definitive) this else this ++ f
}
def either[B](b: => Result[B]): Result[Either[Nothing, B]] = b match {
case Value(v) => Value(Right(v))
case f: Failure => if (definitive) this else this ++ f
}
def filter(f: Nothing => Boolean, msg: => String) = this
def app[B, C](b: => Result[B])(f: (Nothing, B) => C): Result[C] = this
def &&(b: => Result[_]) = this
def toEither = Left(() => errors)
private[sbt] def ++(f: Failure) = mkFailures(errors ++ f.errors)
}
def mkFailures(errors: => Seq[String], definitive: Boolean = false): Failure =
new Failure(errors.distinct, definitive)
def mkFailure(error: => String, definitive: Boolean = false): Failure =
new Failure(error :: Nil, definitive)
def tuple[A, B](a: Option[A], b: Option[B]): Option[(A, B)] =
(a, b) match { case (Some(av), Some(bv)) => Some((av, bv)); case _ => None }
def mapParser[A, B](a: Parser[A], f: A => B): Parser[B] =
a.ifValid {
a.result match {
case Some(av) => success(f(av))
case None =>
a match {
case m: MapParser[_, A] => m.map(f)
case _ => new MapParser(a, f)
}
}
}
def bindParser[A, B](a: Parser[A], f: A => Parser[B]): Parser[B] =
a.ifValid {
a.result match {
case Some(av) => f(av)
case None => new BindParser(a, f)
}
}
def filterParser[T](
a: Parser[T],
f: T => Boolean,
seen: String,
msg: String => String
): Parser[T] =
a.ifValid {
a.result match {
case Some(av) if f(av) => success(av)
case _ => new Filter(a, f, seen, msg)
}
}
def seqParser[A, B](a: Parser[A], b: Parser[B]): Parser[(A, B)] =
a.ifValid {
b.ifValid {
(a.result, b.result) match {
case (Some(av), Some(bv)) => success((av, bv))
case (Some(av), None) => b map (bv => (av, bv))
case (None, Some(bv)) => a map (av => (av, bv))
case (None, None) => new SeqParser(a, b)
}
}
}
def choiceParser[A, B](a: Parser[A], b: Parser[B]): Parser[Either[A, B]] =
if (a.valid)
if (b.valid) new HetParser(a, b) else a.map(left.fn)
else
b.map(right.fn)
def opt[T](a: Parser[T]): Parser[Option[T]] =
if (a.valid) new Optional(a) else success(None)
def onFailure[T](delegate: Parser[T], msg: String): Parser[T] =
if (delegate.valid) new OnFailure(delegate, msg) else failure(msg)
def trapAndFail[T](delegate: Parser[T]): Parser[T] =
delegate.ifValid(new TrapAndFail(delegate))
def zeroOrMore[T](p: Parser[T]): Parser[Seq[T]] = repeat(p, 0, Infinite)
def oneOrMore[T](p: Parser[T]): Parser[Seq[T]] = repeat(p, 1, Infinite)
def repeat[T](p: Parser[T], min: Int = 0, max: UpperBound = Infinite): Parser[Seq[T]] =
repeat(None, p, min, max, Nil)
private[complete] def repeat[T](
partial: Option[Parser[T]],
repeated: Parser[T],
min: Int,
max: UpperBound,
revAcc: List[T]
): Parser[Seq[T]] = {
assume(min >= 0, "Minimum must be greater than or equal to zero (was " + min + ")")
assume(
max >= min,
"Minimum must be less than or equal to maximum (min: " + min + ", max: " + max + ")"
)
def checkRepeated(invalidButOptional: => Parser[Seq[T]]): Parser[Seq[T]] =
repeated match {
case _: Invalid if min == 0 => invalidButOptional
case i: Invalid => i
case _ =>
repeated.result match {
case Some(value) =>
success(revAcc reverse_::: value :: Nil) // revAcc should be Nil here
case None =>
if (max.isZero) success(revAcc.reverse)
else new Repeat(partial, repeated, min, max, revAcc)
}
}
partial match {
case Some(part) =>
part.ifValid {
part.result match {
case Some(value) => repeat(None, repeated, min, max, value :: revAcc)
case None => checkRepeated(part.map(lv => (lv :: revAcc).reverse))
}
}
case None => checkRepeated(success(Nil))
}
}
def and[T](a: Parser[T], b: Parser[_]): Parser[T] = a.ifValid(b.ifValid(new And(a, b)))
}
trait ParserMain {
/** Provides combinators for Parsers.*/
implicit def richParser[A](a: Parser[A]): RichParser[A] = new RichParser[A] {
def ~[B](b: Parser[B]) = seqParser(a, b)
def ||[B](b: Parser[B]) = choiceParser(a, b)
def |[B >: A](b: Parser[B]) = homParser[B](a, b)
def ? = opt(a)
def * = zeroOrMore(a)
def + = oneOrMore(a)
def map[B](f: A => B) = mapParser(a, f)
def id = a
def ^^^[B](value: B): Parser[B] = a map (_ => value)
def ??[B >: A](alt: B): Parser[B] = a.? map { x =>
x.getOrElse[B](alt)
}
def <~[B](b: Parser[B]): Parser[A] = (a ~ b) map { case av ~ _ => av }
def ~>[B](b: Parser[B]): Parser[B] = (a ~ b) map { case _ ~ bv => bv }
def !!!(msg: String): Parser[A] = onFailure(a, msg)
def failOnException: Parser[A] = trapAndFail(a)
def &(o: Parser[_]) = and(a, o)
def examples(s: String*): Parser[A] = examples(s.toSet)
def examples(s: Set[String], check: Boolean = false): Parser[A] =
examples(new FixedSetExamples(s), s.size, check)
def examples(
s: ExampleSource,
maxNumberOfExamples: Int,
removeInvalidExamples: Boolean
): Parser[A] =
Parser.examples(a, s, maxNumberOfExamples, removeInvalidExamples)
def filter(f: A => Boolean, msg: String => String): Parser[A] = filterParser(a, f, "", msg)
def string(implicit ev: A <:< Seq[Char]): Parser[String] = map(_.mkString)
def flatMap[B](f: A => Parser[B]) = bindParser(a, f)
}
implicit def literalRichCharParser(c: Char): RichParser[Char] = richParser(c)
implicit def literalRichStringParser(s: String): RichParser[String] = richParser(s)
/**
* Construct a parser that is valid, but has no valid result. This is used as a way
* to provide a definitive Failure when a parser doesn't match empty input. For example,
* in `softFailure(...) | p`, if `p` doesn't match the empty sequence, the failure will come
* from the Parser constructed by the `softFailure` method.
*/
private[sbt] def softFailure(msg: => String, definitive: Boolean = false): Parser[Nothing] =
SoftInvalid(mkFailures(msg :: Nil, definitive))
/**
* Defines a parser that always fails on any input with messages `msgs`.
* If `definitive` is `true`, any failures by later alternatives are discarded.
*/
def invalid(msgs: => Seq[String], definitive: Boolean = false): Parser[Nothing] =
Invalid(mkFailures(msgs, definitive))
/**
* Defines a parser that always fails on any input with message `msg`.
* If `definitive` is `true`, any failures by later alternatives are discarded.
*/
def failure(msg: => String, definitive: Boolean = false): Parser[Nothing] =
invalid(msg :: Nil, definitive)
/** Defines a parser that always succeeds on empty input with the result `value`.*/
def success[T](value: T): Parser[T] = new ValidParser[T] {
override def result = Some(value)
def resultEmpty = Value(value)
def derive(c: Char) = Parser.failure("Expected end of input.")
def completions(level: Int) = Completions.empty
override def toString = "success(" + value + ")"
}
/** Presents a Char range as a Parser. A single Char is parsed only if it is in the given range.*/
implicit def range(r: collection.immutable.NumericRange[Char]): Parser[Char] = {
val label = r.map(_.toString).toString
range(r, label)
}
/** Presents a Char range as a Parser. A single Char is parsed only if it is in the given range.*/
def range(r: collection.immutable.NumericRange[Char], label: String): Parser[Char] =
charClass(r contains _, label).examples(r.map(_.toString): _*)
/** Defines a Parser that parses a single character only if it is contained in `legal`.*/
def chars(legal: String): Parser[Char] = {
val set = legal.toSet
charClass(set, "character in '" + legal + "'") examples (set.map(_.toString))
}
/**
* Defines a Parser that parses a single character only if the predicate `f` returns true for that character.
* If this parser fails, `label` is used as the failure message.
*/
def charClass(f: Char => Boolean, label: String = "<unspecified>"): Parser[Char] =
new CharacterClass(f, label)
/** Presents a single Char `ch` as a Parser that only parses that exact character. */
implicit def literal(ch: Char): Parser[Char] = new ValidParser[Char] {
def result = None
def resultEmpty = mkFailure("Expected '" + ch + "'")
def derive(c: Char) = if (c == ch) success(ch) else new Invalid(resultEmpty)
def completions(level: Int) = Completions.single(Completion.suggestion(ch.toString))
override def toString = "'" + ch + "'"
}
/** Presents a literal String `s` as a Parser that only parses that exact text and provides it as the result.*/
implicit def literal(s: String): Parser[String] = stringLiteral(s, 0)
/** See [[unapply]]. */
object ~ {
/** Convenience for destructuring a tuple that mirrors the `~` combinator.*/
def unapply[A, B](t: (A, B)): Some[(A, B)] = Some(t)
}
/** Parses input `str` using `parser`. If successful, the result is provided wrapped in `Right`. If unsuccessful, an error message is provided in `Left`.*/
def parse[T](str: String, parser: Parser[T]): Either[String, T] =
Parser.result(parser, str).left.map { failures =>
val (msgs, pos) = failures()
ProcessError(str, msgs, pos)
}
/**
* Convenience method to use when developing a parser.
* `parser` is applied to the input `str`.
* If `completions` is true, the available completions for the input are displayed.
* Otherwise, the result of parsing is printed using the result's `toString` method.
* If parsing fails, the error message is displayed.
*
* See also [[sampleParse]] and [[sampleCompletions]].
*/
def sample(str: String, parser: Parser[_], completions: Boolean = false): Unit =
if (completions) sampleCompletions(str, parser) else sampleParse(str, parser)
/**
* Convenience method to use when developing a parser.
* `parser` is applied to the input `str` and the result of parsing is printed using the result's `toString` method.
* If parsing fails, the error message is displayed.
*/
def sampleParse(str: String, parser: Parser[_]): Unit =
parse(str, parser) match {
case Left(msg) => println(msg)
case Right(v) => println(v)
}
/**
* Convenience method to use when developing a parser.
* `parser` is applied to the input `str` and the available completions are displayed on separate lines.
* If parsing fails, the error message is displayed.
*/
def sampleCompletions(str: String, parser: Parser[_], level: Int = 1): Unit =
Parser.completions(parser, str, level).get foreach println
// intended to be temporary pending proper error feedback
def result[T](p: Parser[T], s: String): Either[() => (Seq[String], Int), T] = {
def loop(i: Int, a: Parser[T]): Either[() => (Seq[String], Int), T] =
a match {
case Invalid(f) => Left(() => (f.errors, i))
case _ =>
val ci = i + 1
if (ci >= s.length)
a.resultEmpty.toEither.left.map { msgs0 => () =>
val msgs = msgs0()
val nonEmpty = if (msgs.isEmpty) Seq("Unexpected end of input") else msgs
(nonEmpty, ci)
} else
loop(ci, a derive s(ci))
}
loop(-1, p)
}
/** Applies parser `p` to input `s`. */
def apply[T](p: Parser[T])(s: String): Parser[T] =
s.foldLeft(p)(derive1)
/** Applies parser `p` to a single character of input. */
def derive1[T](p: Parser[T], c: Char): Parser[T] =
if (p.valid) p.derive(c) else p
/**
* Applies parser `p` to input `s` and returns the completions at verbosity `level`.
* The interpretation of `level` is up to parser definitions, but 0 is the default by convention,
* with increasing positive numbers corresponding to increasing verbosity. Typically no more than
* a few levels are defined.
*/
def completions(p: Parser[_], s: String, level: Int): Completions =
// The x Completions.empty removes any trailing token completions where append.isEmpty
apply(p)(s).completions(level) x Completions.empty
def examples[A](a: Parser[A], completions: Set[String], check: Boolean = false): Parser[A] =
examples(a, new FixedSetExamples(completions), completions.size, check)
/**
* @param a the parser to decorate with a source of examples. All validation and parsing is delegated to this parser,
* only [[Parser.completions]] is modified.
* @param completions the source of examples when displaying completions to the user.
* @param maxNumberOfExamples limits the number of examples that the source of examples should return. This can
* prevent lengthy pauses and avoids bad interactive user experience.
* @param removeInvalidExamples indicates whether completion examples should be checked for validity (against the given parser). An
* exception is thrown if the example source contains no valid completion suggestions.
* @tparam A the type of values that are returned by the parser.
* @return
*/
def examples[A](
a: Parser[A],
completions: ExampleSource,
maxNumberOfExamples: Int,
removeInvalidExamples: Boolean
): Parser[A] =
if (a.valid) {
a.result match {
case Some(av) => success(av)
case None =>
new ParserWithExamples(a, completions, maxNumberOfExamples, removeInvalidExamples)
}
} else a
def matched(
t: Parser[_],
seen: Vector[Char] = Vector.empty,
partial: Boolean = false
): Parser[String] =
t match {
case i: Invalid => if (partial && seen.nonEmpty) success(seen.mkString) else i
case _ =>
if (t.result.isEmpty)
new MatchedString(t, seen, partial)
else
success(seen.mkString)
}
/**
* Establishes delegate parser `t` as a single token of tab completion.
* When tab completion of part of this token is requested, the completions provided by the delegate `t` or a later derivative are appended to
* the prefix String already seen by this parser.
*/
def token[T](t: Parser[T]): Parser[T] = token(t, TokenCompletions.default)
/**
* Establishes delegate parser `t` as a single token of tab completion.
* When tab completion of part of this token is requested, no completions are returned if `hide` returns true for the current tab completion level.
* Otherwise, the completions provided by the delegate `t` or a later derivative are appended to the prefix String already seen by this parser.
*/
def token[T](t: Parser[T], hide: Int => Boolean): Parser[T] =
token(t, TokenCompletions.default.hideWhen(hide))
/**
* Establishes delegate parser `t` as a single token of tab completion.
* When tab completion of part of this token is requested, `description` is displayed for suggestions and no completions are ever performed.
*/
def token[T](t: Parser[T], description: String): Parser[T] =
token(t, TokenCompletions.displayOnly(description))
/**
* Establishes delegate parser `t` as a single token of tab completion.
* When tab completion of part of this token is requested, `display` is used as the printed suggestion, but the completions from the delegate
* parser `t` are used to complete if unambiguous.
*/
def tokenDisplay[T](t: Parser[T], display: String): Parser[T] =
token(t, TokenCompletions.overrideDisplay(display))
def token[T](t: Parser[T], complete: TokenCompletions): Parser[T] =
mkToken(t, "", complete)
private[sbt] def mkToken[T](t: Parser[T], seen: String, complete: TokenCompletions): Parser[T] =
if (t.valid && !t.isTokenStart)
if (t.result.isEmpty) new TokenStart(t, seen, complete) else t
else
t
def homParser[A](a: Parser[A], b: Parser[A]): Parser[A] = (a, b) match {
case (Invalid(af), Invalid(bf)) => Invalid(af ++ bf)
case (Invalid(_), bv) => bv
case (av, Invalid(_)) => av
case (_, _) => new HomParser(a, b)
}
def not(p: Parser[_], failMessage: String): Parser[Unit] = p.result match {
case None => new Not(p, failMessage)
case Some(_) => failure(failMessage)
}
def oneOf[T](p: Seq[Parser[T]]): Parser[T] = p.reduceLeft(_ | _)
def seq[T](p: Seq[Parser[T]]): Parser[Seq[T]] = seq0(p, Nil)
def seq0[T](p: Seq[Parser[T]], errors: => Seq[String]): Parser[Seq[T]] = {
val (newErrors, valid) = separate(p) {
case Invalid(f) => Left(f.errors _): Either[() => Seq[String], Parser[T]]
case ok => Right(ok): Either[() => Seq[String], Parser[T]]
}
def combinedErrors = errors ++ newErrors.flatMap(_())
if (valid.isEmpty) invalid(combinedErrors) else new ParserSeq(valid, combinedErrors)
}
def stringLiteral(s: String, start: Int): Parser[String] = {
val len = s.length
if (len == 0) sys.error("String literal cannot be empty")
else if (start >= len) success(s)
else new StringLiteral(s, start)
}
}
sealed trait ValidParser[T] extends Parser[T] {
final def valid = true
final def failure = None
final def ifValid[S](p: => Parser[S]): Parser[S] = p
}
private final case class Invalid(fail: Failure) extends Parser[Nothing] {
def failure = Some(fail)
def result = None
def resultEmpty = fail
def derive(c: Char) = sys.error("Invalid.")
def completions(level: Int) = Completions.nil
override def toString = fail.errors.mkString("; ")
def valid = false
def ifValid[S](p: => Parser[S]): Parser[S] = this
}
private final case class SoftInvalid(fail: Failure) extends ValidParser[Nothing] {
def result = None
def resultEmpty = fail
def derive(c: Char) = Invalid(fail)
def completions(level: Int) = Completions.nil
override def toString = fail.errors.mkString("; ")
}
private final class TrapAndFail[A](a: Parser[A]) extends ValidParser[A] {
def result =
try {
a.result
} catch { case _: Exception => None }
def resultEmpty =
try {
a.resultEmpty
} catch { case e: Exception => fail(e) }
def derive(c: Char) =
try {
trapAndFail(a derive c)
} catch {
case e: Exception => Invalid(fail(e))
}
def completions(level: Int) =
try {
a.completions(level)
} catch {
case _: Exception => Completions.nil
}
override def toString = "trap(" + a + ")"
override def isTokenStart = a.isTokenStart
private[this] def fail(e: Exception): Failure = mkFailure(e.toString)
}
private final class OnFailure[A](a: Parser[A], message: String) extends ValidParser[A] {
def result = a.result
def resultEmpty = a.resultEmpty match {
case _: Failure => mkFailure(message); case v: Value[A] => v
}
def derive(c: Char) = onFailure(a derive c, message)
def completions(level: Int) = a.completions(level)
override def toString = "(" + a + " !!! \"" + message + "\" )"
override def isTokenStart = a.isTokenStart
}
private final class SeqParser[A, B](a: Parser[A], b: Parser[B]) extends ValidParser[(A, B)] {
lazy val result = tuple(a.result, b.result)
lazy val resultEmpty = a.resultEmpty seq b.resultEmpty
def derive(c: Char) = {
val common = a.derive(c) ~ b
a.resultEmpty match {
case Value(av) => common | b.derive(c).map(br => (av, br))
case _: Failure => common
}
}
def completions(level: Int) = a.completions(level) x b.completions(level)
override def toString = "(" + a + " ~ " + b + ")"
}
private final class HomParser[A](a: Parser[A], b: Parser[A]) extends ValidParser[A] {
lazy val result = tuple(a.result, b.result) map (_._1)
def derive(c: Char) = (a derive c) | (b derive c)
lazy val resultEmpty = a.resultEmpty or b.resultEmpty
def completions(level: Int) = a.completions(level) ++ b.completions(level)
override def toString = "(" + a + " | " + b + ")"
}
private final class HetParser[A, B](a: Parser[A], b: Parser[B]) extends ValidParser[Either[A, B]] {
lazy val result = tuple(a.result, b.result) map { case (a, _) => Left(a) }
def derive(c: Char) = (a derive c) || (b derive c)
lazy val resultEmpty = a.resultEmpty either b.resultEmpty
def completions(level: Int) = a.completions(level) ++ b.completions(level)
override def toString = "(" + a + " || " + b + ")"
}
private final class ParserSeq[T](a: Seq[Parser[T]], errors: => Seq[String])
extends ValidParser[Seq[T]] {
assert(a.nonEmpty)
lazy val resultEmpty: Result[Seq[T]] = {
val res = a.map(_.resultEmpty)
val (failures, values) = separate(res)(_.toEither)
// if(failures.isEmpty) Value(values) else mkFailures(failures.flatMap(_()) ++ errors)
if (values.nonEmpty) Value(values) else mkFailures(failures.flatMap(_()) ++ errors)
}
def result = {
val success = a.flatMap(_.result)
if (success.length == a.length) Some(success) else None
}
def completions(level: Int) = a.map(_.completions(level)).reduceLeft(_ ++ _)
def derive(c: Char) = seq0(a.map(_ derive c), errors)
override def toString = "seq(" + a + ")"
}
private final class BindParser[A, B](a: Parser[A], f: A => Parser[B]) extends ValidParser[B] {
lazy val result = a.result flatMap (av => f(av).result)
lazy val resultEmpty = a.resultEmpty flatMap (av => f(av).resultEmpty)
def completions(level: Int) =
a.completions(level) flatMap { c =>
apply(a)(c.append).resultEmpty match {
case _: Failure => Completions.strict(Set.empty + c)
case Value(av) => c x f(av).completions(level)
}
}
def derive(c: Char) = {
val common = a derive c flatMap f
a.resultEmpty match {
case Value(av) => common | derive1(f(av), c)
case _: Failure => common
}
}
override def isTokenStart = a.isTokenStart
override def toString = "bind(" + a + ")"
}
private final class MapParser[A, B](a: Parser[A], f: A => B) extends ValidParser[B] {
lazy val result = a.result map f
lazy val resultEmpty = a.resultEmpty map f
def derive(c: Char) = (a derive c) map f
def completions(level: Int) = a.completions(level)
override def isTokenStart = a.isTokenStart
override def toString = "map(" + a + ")"
def map[C](g: B => C) = new MapParser[A, C](a, f.andThen(g))
}
private final class Filter[T](p: Parser[T], f: T => Boolean, seen: String, msg: String => String)
extends ValidParser[T] {
def filterResult(r: Result[T]) = r.filter(f, msg(seen))
lazy val result = p.result filter f
lazy val resultEmpty = filterResult(p.resultEmpty)
def derive(c: Char) = filterParser(p derive c, f, seen + c, msg)
def completions(level: Int) = p.completions(level) filterS { s =>
filterResult(apply(p)(s).resultEmpty).isValid
}
override def toString = "filter(" + p + ")"
override def isTokenStart = p.isTokenStart
}
private final class MatchedString(delegate: Parser[_], seenV: Vector[Char], partial: Boolean)
extends ValidParser[String] {
lazy val seen = seenV.mkString
def derive(c: Char) = matched(delegate derive c, seenV :+ c, partial)
def completions(level: Int) = delegate.completions(level)
def result = if (delegate.result.isDefined) Some(seen) else None
def resultEmpty = delegate.resultEmpty match {
case f: Failure if !partial => f; case _ => Value(seen)
}
override def isTokenStart = delegate.isTokenStart
override def toString = "matched(" + partial + ", " + seen + ", " + delegate + ")"
}
private final class TokenStart[T](delegate: Parser[T], seen: String, complete: TokenCompletions)
extends ValidParser[T] {
def derive(c: Char) = mkToken(delegate derive c, seen + c, complete)
def completions(level: Int) = complete match {
case dc: TokenCompletions.Delegating =>
dc.completions(seen, level, delegate.completions(level))
case fc: TokenCompletions.Fixed => fc.completions(seen, level)
}
def result = delegate.result
def resultEmpty = delegate.resultEmpty
override def isTokenStart = true
override def toString = "token('" + complete + ", " + delegate + ")"
}
private final class And[T](a: Parser[T], b: Parser[_]) extends ValidParser[T] {
lazy val result = tuple(a.result, b.result) map { _._1 }
def derive(c: Char) = (a derive c) & (b derive c)
def completions(level: Int) = a.completions(level).filterS(s => apply(b)(s).resultEmpty.isValid)
lazy val resultEmpty = a.resultEmpty && b.resultEmpty
override def toString = "(%s) && (%s)".format(a, b)
}
private final class Not(delegate: Parser[_], failMessage: String) extends ValidParser[Unit] {
def derive(c: Char) = if (delegate.valid) not(delegate derive c, failMessage) else this
def completions(level: Int) = Completions.empty
def result = None
lazy val resultEmpty = delegate.resultEmpty match {
case _: Failure => Value(())
case _: Value[_] => mkFailure(failMessage)
}
override def toString = " -(%s)".format(delegate)
}
/**
* This class wraps an existing parser (the delegate), and replaces the delegate's completions with examples from
* the given example source.
*
* This class asks the example source for a limited amount of examples (to prevent lengthy and expensive
* computations and large amounts of allocated data). It then passes these examples on to the UI.
*
* @param delegate the parser to decorate with completion examples (i.e., completion of user input).
* @param exampleSource the source from which this class will take examples (potentially filter them with the delegate
* parser), and pass them to the UI.
* @param maxNumberOfExamples the maximum number of completions to read from the example source and pass to the UI. This
* limit prevents lengthy example generation and allocation of large amounts of memory.
* @param removeInvalidExamples indicates whether to remove examples that are deemed invalid by the delegate parser.
* @tparam T the type of value produced by the parser.
*/
private final class ParserWithExamples[T](
delegate: Parser[T],
exampleSource: ExampleSource,
maxNumberOfExamples: Int,
removeInvalidExamples: Boolean
) extends ValidParser[T] {
def derive(c: Char) =
examples(
delegate derive c,
exampleSource.withAddedPrefix(c.toString),
maxNumberOfExamples,
removeInvalidExamples
)
def result = delegate.result
lazy val resultEmpty = delegate.resultEmpty
def completions(level: Int) = {
if (exampleSource().isEmpty)
if (resultEmpty.isValid) Completions.nil else Completions.empty
else {
val examplesBasedOnTheResult = filteredExamples.take(maxNumberOfExamples).toSet
Completions(examplesBasedOnTheResult.map(ex => Completion.suggestion(ex)))
}
}
override def toString = "examples(" + delegate + ", " + exampleSource().take(2).toList + ")"
private def filteredExamples: Iterable[String] = {
if (removeInvalidExamples)
exampleSource().filter(isExampleValid)
else
exampleSource()
}
private def isExampleValid(example: String): Boolean = {
apply(delegate)(example).resultEmpty.isValid
}
}
private final class StringLiteral(str: String, start: Int) extends ValidParser[String] {
assert(0 <= start && start < str.length)
def failMsg = "Expected '" + str + "'"
def resultEmpty = mkFailure(failMsg)
def result = None
def derive(c: Char) =
if (str.charAt(start) == c) stringLiteral(str, start + 1) else new Invalid(resultEmpty)
def completions(level: Int) = Completions.single(Completion.suggestion(str.substring(start)))
override def toString = '"' + str + '"'
}
private final class CharacterClass(f: Char => Boolean, label: String) extends ValidParser[Char] {
def result = None
def resultEmpty = mkFailure("Expected " + label)
def derive(c: Char) = if (f(c)) success(c) else Invalid(resultEmpty)
def completions(level: Int) = Completions.empty
override def toString = "class(" + label + ")"
}
private final class Optional[T](delegate: Parser[T]) extends ValidParser[Option[T]] {
def result = delegate.result map some.fn
def resultEmpty = Value(None)
def derive(c: Char) = (delegate derive c).map(some.fn)
def completions(level: Int) = Completion.empty +: delegate.completions(level)
override def toString = delegate.toString + "?"
}
private final class Repeat[T](
partial: Option[Parser[T]],
repeated: Parser[T],
min: Int,
max: UpperBound,
accumulatedReverse: List[T]
) extends ValidParser[Seq[T]] {
assume(0 <= min, "Minimum occurrences must be non-negative")
assume(max >= min, "Minimum occurrences must be less than the maximum occurrences")
def derive(c: Char) =
partial match {
case Some(part) =>
val partD = repeat(Some(part derive c), repeated, min, max, accumulatedReverse)
part.resultEmpty match {
case Value(pv) => partD | repeatDerive(c, pv :: accumulatedReverse)
case _: Failure => partD
}
case None => repeatDerive(c, accumulatedReverse)
}
def repeatDerive(c: Char, accRev: List[T]): Parser[Seq[T]] =
repeat(Some(repeated derive c), repeated, scala.math.max(0, min - 1), max.decrement, accRev)
def completions(level: Int) = {
def pow(comp: Completions, exp: Completions, n: Int): Completions =
if (n == 1) comp else pow(comp x exp, exp, n - 1)
val repC = repeated.completions(level)
val fin = if (min == 0) Completion.empty +: repC else pow(repC, repC, min)
partial match {
case Some(p) => p.completions(level) x fin
case None => fin
}
}
def result = None
lazy val resultEmpty: Result[Seq[T]] = {
val partialAccumulatedOption =
partial match {
case None => (Value(accumulatedReverse): Result[List[T]])
case Some(partialPattern) =>
partialPattern.resultEmpty.map(_ :: accumulatedReverse)
}
(partialAccumulatedOption app repeatedParseEmpty)((x, y) => (x reverse_::: y): Seq[T])
}
private def repeatedParseEmpty: Result[List[T]] = {
if (min == 0)
Value(Nil)
else
// forced determinism
for (value <- repeated.resultEmpty) yield makeList(min, value)
}
override def toString = "repeat(" + min + "," + max + "," + partial + "," + repeated + ")"
}

View File

@ -0,0 +1,364 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
import Parser._
import java.io.File
import java.net.URI
import java.lang.Character.{
CURRENCY_SYMBOL,
DASH_PUNCTUATION,
MATH_SYMBOL,
MODIFIER_SYMBOL,
OTHER_PUNCTUATION,
OTHER_SYMBOL,
getType
}
import scala.annotation.tailrec
import sbt.internal.util.Util.nilSeq
/** Provides standard implementations of commonly useful [[Parser]]s. */
trait Parsers {
/** Matches the end of input, providing no useful result on success. */
lazy val EOF = not(any, "Expected EOF")
/** Parses any single character and provides that character as the result. */
lazy val any: Parser[Char] = charClass(_ => true, "any character")
/** Set that contains each digit in a String representation.*/
lazy val DigitSet = Set("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")
/** Parses any single digit and provides that digit as a Char as the result.*/
lazy val Digit = charClass(_.isDigit, "digit") examples DigitSet
/** Set containing Chars for hexadecimal digits 0-9 and A-F (but not a-f). */
lazy val HexDigitSet =
Set('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
/** Parses a single hexadecimal digit (0-9, a-f, A-F). */
lazy val HexDigit = charClass(c => HexDigitSet(c.toUpper), "hex digit") examples HexDigitSet.map(
_.toString
)
/** Parses a single letter, according to Char.isLetter, into a Char. */
lazy val Letter = charClass(_.isLetter, "letter")
/** Parses a single letter, according to Char.isUpper, into a Char. */
lazy val Upper = charClass(_.isUpper, "upper")
/** Parses a single letter, according to Char.isLower, into a Char. */
lazy val Lower = charClass(_.isLower, "lower")
/** Parses the first Char in an sbt identifier, which must be a [[Letter]].*/
def IDStart = Letter
/** Parses an identifier Char other than the first character. This includes letters, digits, dash `-`, and underscore `_`.*/
lazy val IDChar = charClass(isIDChar, "ID character")
/** Parses an identifier String, which must start with [[IDStart]] and contain zero or more [[IDChar]]s after that. */
lazy val ID = identifier(IDStart, IDChar)
/** Parses a single operator Char, as allowed by [[isOpChar]]. */
lazy val OpChar = charClass(isOpChar, "symbol")
/** Parses a non-empty operator String, which consists only of characters allowed by [[OpChar]]. */
lazy val Op = OpChar.+.string
/** Parses either an operator String defined by [[Op]] or a non-symbolic identifier defined by [[ID]]. */
lazy val OpOrID = ID | Op
/** Parses a single, non-symbolic Scala identifier Char. Valid characters are letters, digits, and the underscore character `_`. */
lazy val ScalaIDChar = charClass(isScalaIDChar, "Scala identifier character")
/** Parses a non-symbolic Scala-like identifier. The identifier must start with [[IDStart]] and contain zero or more [[ScalaIDChar]]s after that.*/
lazy val ScalaID = identifier(IDStart, ScalaIDChar)
/** Parses a non-symbolic Scala-like identifier. The identifier must start with [[Upper]] and contain zero or more [[ScalaIDChar]]s after that.*/
lazy val CapitalizedID = identifier(Upper, ScalaIDChar)
/** Parses a String that starts with `start` and is followed by zero or more characters parsed by `rep`.*/
def identifier(start: Parser[Char], rep: Parser[Char]): Parser[String] =
start ~ rep.* map { case x ~ xs => (x +: xs).mkString }
def opOrIDSpaced(s: String): Parser[Char] =
if (DefaultParsers.matches(ID, s))
OpChar | SpaceClass
else if (DefaultParsers.matches(Op, s))
IDChar | SpaceClass
else
any
/** Returns true if `c` an operator character. */
def isOpChar(c: Char) = !isDelimiter(c) && isOpType(getType(c))
def isOpType(cat: Int) = cat match {
case MATH_SYMBOL | OTHER_SYMBOL | DASH_PUNCTUATION | OTHER_PUNCTUATION | MODIFIER_SYMBOL |
CURRENCY_SYMBOL =>
true; case _ => false
}
/** Returns true if `c` is a dash `-`, a letter, digit, or an underscore `_`. */
def isIDChar(c: Char) = isScalaIDChar(c) || c == '-'
/** Returns true if `c` is a letter, digit, or an underscore `_`. */
def isScalaIDChar(c: Char) = c.isLetterOrDigit || c == '_'
def isDelimiter(c: Char) = c match {
case '`' | '\'' | '\"' | /*';' | */ ',' | '.' => true; case _ => false
}
/** Matches a single character that is not a whitespace character. */
lazy val NotSpaceClass = charClass(!_.isWhitespace, "non-whitespace character")
/** Matches a single whitespace character, as determined by Char.isWhitespace.*/
lazy val SpaceClass = charClass(_.isWhitespace, "whitespace character")
/** Matches a non-empty String consisting of non-whitespace characters. */
lazy val NotSpace = NotSpaceClass.+.string
/** Matches a possibly empty String consisting of non-whitespace characters. */
lazy val OptNotSpace = NotSpaceClass.*.string
/**
* Matches a non-empty String consisting of whitespace characters.
* The suggested tab completion is a single, constant space character.
*/
lazy val Space: Parser[Seq[Char]] = SpaceClass.+.examples(" ")
/**
* Matches a possibly empty String consisting of whitespace characters.
* The suggested tab completion is a single, constant space character.
*/
lazy val OptSpace = SpaceClass.*.examples(" ")
/** Parses a non-empty String that contains only valid URI characters, as defined by [[URIChar]].*/
lazy val URIClass = URIChar.+.string !!! "Invalid URI"
/** Triple-quotes, as used for verbatim quoting.*/
lazy val VerbatimDQuotes = "\"\"\""
/** Double quote character. */
lazy val DQuoteChar = '\"'
/** Backslash character. */
lazy val BackslashChar = '\\'
/** Matches a single double quote. */
lazy val DQuoteClass = charClass(_ == DQuoteChar, "double-quote character")
/** Matches any character except a double quote or whitespace. */
lazy val NotDQuoteSpaceClass =
charClass({ c: Char =>
(c != DQuoteChar) && !c.isWhitespace
}, "non-double-quote-space character")
/** Matches any character except a double quote or backslash. */
lazy val NotDQuoteBackslashClass =
charClass({ c: Char =>
(c != DQuoteChar) && (c != BackslashChar)
}, "non-double-quote-backslash character")
/** Matches a single character that is valid somewhere in a URI. */
lazy val URIChar = charClass(alphanum, "alphanum") | chars("_-!.~'()*,;:$&+=?/[]@%#")
/** Returns true if `c` is an ASCII letter or digit. */
def alphanum(c: Char) =
('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')
/**
* @param base the directory used for completion proposals (when the user presses the TAB key). Only paths under this
* directory will be proposed.
* @return the file that was parsed from the input string. The returned path may or may not exist.
*/
def fileParser(base: File): Parser[File] =
OptSpace ~> StringBasic
.examples(new FileExamples(base))
.map(new File(_))
/** Parses a port number. Currently, this accepts any integer and presents a tab completion suggestion of `<port>`. */
lazy val Port = token(IntBasic, "<port>")
/** Parses a signed integer. */
lazy val IntBasic = mapOrFail('-'.? ~ Digit.+)(Function.tupled(toInt))
/** Parses an unsigned integer. */
lazy val NatBasic = mapOrFail(Digit.+)(_.mkString.toInt)
private[this] def toInt(neg: Option[Char], digits: Seq[Char]): Int =
(neg.toSeq ++ digits).mkString.toInt
/** Parses the lower-case values `true` and `false` into their corresponding Boolean values. */
lazy val Bool = ("true" ^^^ true) | ("false" ^^^ false)
/**
* Parses a potentially quoted String value. The value may be verbatim quoted ([[StringVerbatim]]),
* quoted with interpreted escapes ([[StringEscapable]]), or unquoted ([[NotQuoted]]).
*/
lazy val StringBasic = StringVerbatim | StringEscapable | NotQuoted
/**
* Parses a verbatim quoted String value, discarding the quotes in the result. This kind of quoted text starts with triple quotes `"""`
* and ends at the next triple quotes and may contain any character in between.
*/
lazy val StringVerbatim: Parser[String] = VerbatimDQuotes ~>
any.+.string.filter(!_.contains(VerbatimDQuotes), _ => "Invalid verbatim string") <~
VerbatimDQuotes
/**
* Parses a string value, interpreting escapes and discarding the surrounding quotes in the result.
* See [[EscapeSequence]] for supported escapes.
*/
lazy val StringEscapable: Parser[String] =
(DQuoteChar ~> (NotDQuoteBackslashClass | EscapeSequence).+.string <~ DQuoteChar |
(DQuoteChar ~ DQuoteChar) ^^^ "")
/**
* Parses a size unit string. For example, `128K` parsers to `128L * 1024`, and `1.25g` parses
* to `1024L * 1024 * 1024 * 5 / 4`.
*/
lazy val Size: Parser[Long] = SizeParser.value
/**
* Parses a brace enclosed string and, if each opening brace is matched with a closing brace,
* it returns the entire string including the braces.
*
* @param open the opening character, e.g. '{'
* @param close the closing character, e.g. '}'
* @return a parser for the brace encloosed string.
*/
private[sbt] def braces(open: Char, close: Char): Parser[String] = {
val notDelim = charClass(c => c != open && c != close).*.string
def impl(): Parser[String] = {
(open ~ (notDelim ~ close).?).flatMap {
case (l, Some((content, r))) => Parser.success(l + content + r)
case (l, None) =>
((notDelim ~ impl()).map {
case (leftPrefix, nestedBraces) => leftPrefix + nestedBraces
}.+ ~ notDelim ~ close).map {
case ((nested, suffix), r) => l + nested.mkString + suffix + r
}
}
}
impl()
}
/**
* Parses a single escape sequence into the represented Char.
* Escapes start with a backslash and are followed by `u` for a [[UnicodeEscape]] or by `b`, `t`, `n`, `f`, `r`, `"`, `'`, `\` for standard escapes.
*/
lazy val EscapeSequence: Parser[Char] =
BackslashChar ~> ('b' ^^^ '\b' | 't' ^^^ '\t' | 'n' ^^^ '\n' | 'f' ^^^ '\f' | 'r' ^^^ '\r' |
'\"' ^^^ '\"' | '\'' ^^^ '\'' | '\\' ^^^ '\\' | UnicodeEscape)
/**
* Parses a single unicode escape sequence into the represented Char.
* A unicode escape begins with a backslash, followed by a `u` and 4 hexadecimal digits representing the unicode value.
*/
lazy val UnicodeEscape: Parser[Char] =
("u" ~> repeat(HexDigit, 4, 4)) map { seq =>
Integer.parseInt(seq.mkString, 16).toChar
}
/** Parses an unquoted, non-empty String value that cannot start with a double quote and cannot contain whitespace.*/
lazy val NotQuoted = (NotDQuoteSpaceClass ~ OptNotSpace) map { case (c, s) => c.toString + s }
/**
* Applies `rep` zero or more times, separated by `sep`.
* The result is the (possibly empty) sequence of results from the multiple `rep` applications. The `sep` results are discarded.
*/
def repsep[T](rep: Parser[T], sep: Parser[_]): Parser[Seq[T]] =
rep1sep(rep, sep) ?? nilSeq[T]
/**
* Applies `rep` one or more times, separated by `sep`.
* The result is the non-empty sequence of results from the multiple `rep` applications. The `sep` results are discarded.
*/
def rep1sep[T](rep: Parser[T], sep: Parser[_]): Parser[Seq[T]] =
(rep ~ (sep ~> rep).*).map { case (x ~ xs) => x +: xs }
/** Wraps the result of `p` in `Some`.*/
def some[T](p: Parser[T]): Parser[Option[T]] = p map { v =>
Some(v)
}
/**
* Applies `f` to the result of `p`, transforming any exception when evaluating
* `f` into a parse failure with the exception `toString` as the message.
*/
def mapOrFail[S, T](p: Parser[S])(f: S => T): Parser[T] =
p flatMap { s =>
try {
success(f(s))
} catch { case e: Exception => failure(e.toString) }
}
/**
* Parses a space-delimited, possibly empty sequence of arguments.
* The arguments may use quotes and escapes according to [[StringBasic]].
*/
def spaceDelimited(display: String): Parser[Seq[String]] =
(token(Space) ~> token(StringBasic, display)).* <~ SpaceClass.*
/** Applies `p` and uses `true` as the result if it succeeds and turns failure into a result of `false`. */
def flag[T](p: Parser[T]): Parser[Boolean] = (p ^^^ true) ?? false
/**
* Defines a sequence parser where the parser used for each part depends on the previously parsed values.
* `p` is applied to the (possibly empty) sequence of already parsed values to obtain the next parser to use.
* The parsers obtained in this way are separated by `sep`, whose result is discarded and only the sequence
* of values from the parsers returned by `p` is used for the result.
*/
def repeatDep[A](p: Seq[A] => Parser[A], sep: Parser[Any]): Parser[Seq[A]] = {
def loop(acc: Seq[A]): Parser[Seq[A]] = {
val next = (sep ~> p(acc)) flatMap { result =>
loop(acc :+ result)
}
next ?? acc
}
p(Vector()) flatMap { first =>
loop(Seq(first))
}
}
/** Applies String.trim to the result of `p`. */
def trimmed(p: Parser[String]) = p map { _.trim }
/** Parses a URI that is valid according to the single argument java.net.URI constructor. */
lazy val basicUri = mapOrFail(URIClass)(uri => new URI(uri))
/** Parses a URI that is valid according to the single argument java.net.URI constructor, using `ex` as tab completion examples. */
def Uri(ex: Set[URI]) = basicUri examples (ex.map(_.toString))
}
/** Provides standard [[Parser]] implementations. */
object Parsers extends Parsers
/** Provides common [[Parser]] implementations and helper methods.*/
object DefaultParsers extends Parsers with ParserMain {
/** Applies parser `p` to input `s` and returns `true` if the parse was successful. */
def matches(p: Parser[_], s: String): Boolean =
apply(p)(s).resultEmpty.isValid
/** Returns `true` if `s` parses successfully according to [[ID]].*/
def validID(s: String): Boolean = {
// Handwritten version of `matches(ID, s)` because validID turned up in profiling.
def isIdChar(c: Char): Boolean = Character.isLetterOrDigit(c) || (c == '-') || (c == '_')
@tailrec def isRestIdChar(cur: Int, s: String, length: Int): Boolean =
if (cur < length)
isIdChar(s.charAt(cur)) && isRestIdChar(cur + 1, s, length)
else
true
!s.isEmpty && Character.isLetter(s.charAt(0)) && isRestIdChar(1, s, s.length)
}
}

View File

@ -0,0 +1,38 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
object ProcessError {
def apply(command: String, msgs: Seq[String], index: Int): String = {
val (line, modIndex) = extractLine(command, index)
val point = pointerSpace(command, modIndex)
msgs.mkString("\n") + "\n" + line + "\n" + point + "^"
}
def extractLine(s: String, i: Int): (String, Int) = {
val notNewline = (c: Char) => c != '\n' && c != '\r'
val left = takeRightWhile(s.substring(0, i))(notNewline)
val right = s substring i takeWhile notNewline
(left + right, left.length)
}
def takeRightWhile(s: String)(pred: Char => Boolean): String = {
def loop(i: Int): String =
if (i < 0)
s
else if (pred(s(i)))
loop(i - 1)
else
s.substring(i + 1)
loop(s.length - 1)
}
def pointerSpace(s: String, i: Int): String =
(s take i) map { case '\t' => '\t'; case _ => ' ' } mkString ""
}

View File

@ -0,0 +1,56 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util.complete
import sbt.internal.util.complete.DefaultParsers._
private[sbt] object SizeParser {
def apply(s: String): Option[Long] = Parser.parse(s, value).toOption
private sealed trait SizeUnit
private case object Bytes extends SizeUnit
private case object KiloBytes extends SizeUnit
private case object MegaBytes extends SizeUnit
private case object GigaBytes extends SizeUnit
private def parseDouble(s: String): Parser[Either[Double, Long]] =
try Parser.success(Left(java.lang.Double.valueOf(s)))
catch { case _: NumberFormatException => Parser.failure(s"Couldn't parse $s as double.") }
private def parseLong(s: String): Parser[Either[Double, Long]] =
try Parser.success(Right(java.lang.Long.valueOf(s)))
catch { case _: NumberFormatException => Parser.failure(s"Couldn't parse $s as double.") }
private[this] val digit = charClass(_.isDigit, "digit")
private[this] val numberParser: Parser[Either[Double, Long]] =
(digit.+ ~ ('.'.examples() ~> digit.+).?).flatMap {
case (leading, Some(decimalPart)) =>
parseDouble(s"${leading.mkString}.${decimalPart.mkString}")
case (leading, _) => parseLong(leading.mkString)
}
private[this] val unitParser: Parser[SizeUnit] =
token("b" | "B" | "g" | "G" | "k" | "K" | "m" | "M").map {
case "b" | "B" => Bytes
case "g" | "G" => GigaBytes
case "k" | "K" => KiloBytes
case "m" | "M" => MegaBytes
}
private[this] def multiply(left: Either[Double, Long], right: Long): Long = left match {
case Left(d) => (d * right).toLong
case Right(l) => l * right
}
private[sbt] val value: Parser[Long] =
((numberParser <~ SpaceClass
.examples(" ", "b", "B", "g", "G", "k", "K", "m", "M")
.*) ~ unitParser.?)
.map {
case (number, unit) =>
unit match {
case None | Some(Bytes) => multiply(number, right = 1L)
case Some(KiloBytes) => multiply(number, right = 1024L)
case Some(MegaBytes) => multiply(number, right = 1024L * 1024)
case Some(GigaBytes) => multiply(number, right = 1024L * 1024 * 1024)
}
}
}

View File

@ -0,0 +1,52 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
import Completion.{ token => ctoken, tokenDisplay }
sealed trait TokenCompletions {
def hideWhen(f: Int => Boolean): TokenCompletions
}
object TokenCompletions {
private[sbt] abstract class Delegating extends TokenCompletions { outer =>
def completions(seen: String, level: Int, delegate: Completions): Completions
final def hideWhen(hide: Int => Boolean): TokenCompletions = new Delegating {
def completions(seen: String, level: Int, delegate: Completions): Completions =
if (hide(level)) Completions.nil else outer.completions(seen, level, delegate)
}
}
private[sbt] abstract class Fixed extends TokenCompletions { outer =>
def completions(seen: String, level: Int): Completions
final def hideWhen(hide: Int => Boolean): TokenCompletions = new Fixed {
def completions(seen: String, level: Int) =
if (hide(level)) Completions.nil else outer.completions(seen, level)
}
}
val default: TokenCompletions = mapDelegateCompletions((seen, level, c) => ctoken(seen, c.append))
def displayOnly(msg: String): TokenCompletions = new Fixed {
def completions(seen: String, level: Int) = Completions.single(Completion.displayOnly(msg))
}
def overrideDisplay(msg: String): TokenCompletions =
mapDelegateCompletions((seen, level, c) => tokenDisplay(display = msg, append = c.append))
def fixed(f: (String, Int) => Completions): TokenCompletions = new Fixed {
def completions(seen: String, level: Int) = f(seen, level)
}
def mapDelegateCompletions(f: (String, Int, Completion) => Completion): TokenCompletions =
new Delegating {
def completions(seen: String, level: Int, delegate: Completions) =
Completions(delegate.get.map(c => f(seen, level, c)))
}
}

View File

@ -0,0 +1,90 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
import DefaultParsers._
import TypeString._
/**
* Basic representation of types parsed from Manifest.toString.
* This can only represent the structure of parameterized types.
* All other types are represented by a TypeString with an empty `args`.
*/
private[sbt] final class TypeString(val base: String, val args: List[TypeString]) {
override def toString =
if (base.startsWith(FunctionName))
args.dropRight(1).mkString("(", ",", ")") + " => " + args.last
else if (base.startsWith(TupleName))
args.mkString("(", ",", ")")
else
cleanupTypeName(base) + (if (args.isEmpty) "" else args.mkString("[", ",", "]"))
}
private[sbt] object TypeString {
/** Makes the string representation of a type as returned by Manifest.toString more readable.*/
def cleanup(typeString: String): String =
parse(typeString, typeStringParser) match {
case Right(ts) => ts.toString
case Left(_) => typeString
}
/**
* Makes a fully qualified type name provided by Manifest.toString more readable.
* The argument should be just a name (like scala.Tuple2) and not a full type (like scala.Tuple2[Int,Boolean])
*/
def cleanupTypeName(base: String): String =
dropPrefix(base).replace('$', '.')
/**
* Removes prefixes from a fully qualified type name that are unnecessary in the presence of standard imports for an sbt setting.
* This does not use the compiler and is therefore a conservative approximation.
*/
def dropPrefix(base: String): String =
if (base.startsWith(SbtPrefix))
base.substring(SbtPrefix.length)
else if (base.startsWith(CollectionPrefix)) {
val simple = base.substring(CollectionPrefix.length)
if (ShortenCollection(simple)) simple else base
} else if (base.startsWith(ScalaPrefix))
base.substring(ScalaPrefix.length)
else if (base.startsWith(JavaPrefix))
base.substring(JavaPrefix.length)
else
TypeMap.getOrElse(base, base)
final val CollectionPrefix = "scala.collection."
final val FunctionName = "scala.Function"
final val TupleName = "scala.Tuple"
final val SbtPrefix = "sbt."
final val ScalaPrefix = "scala."
final val JavaPrefix = "java.lang."
/* scala.collection.X -> X */
val ShortenCollection = Set("Seq", "List", "Set", "Map", "Iterable")
val TypeMap = Map(
"java.io.File" -> "File",
"java.net.URL" -> "URL",
"java.net.URI" -> "URI"
)
/**
* A Parser that extracts basic structure from the string representation of a type from Manifest.toString.
* This is rudimentary and essentially only decomposes the string into names and arguments for parameterized types.
*/
lazy val typeStringParser: Parser[TypeString] = {
def isFullScalaIDChar(c: Char) = isScalaIDChar(c) || c == '.' || c == '$'
lazy val fullScalaID =
identifier(IDStart, charClass(isFullScalaIDChar, "Scala identifier character"))
lazy val tpe: Parser[TypeString] =
for (id <- fullScalaID; args <- ('[' ~> rep1sep(tpe, ',') <~ ']').?)
yield new TypeString(id, args.toList.flatten)
tpe
}
}

View File

@ -0,0 +1,64 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
sealed trait UpperBound {
/** True if and only if the given value meets this bound.*/
def >=(min: Int): Boolean
/** True if and only if this bound is one.*/
def isOne: Boolean
/** True if and only if this bound is zero.*/
def isZero: Boolean
/**
* If this bound is zero or Infinite, `decrement` returns this bound.
* Otherwise, this bound is finite and greater than zero and `decrement` returns the bound that is one less than this bound.
*/
def decrement: UpperBound
/** True if and only if this is unbounded.*/
def isInfinite: Boolean
}
/** Represents unbounded. */
case object Infinite extends UpperBound {
/** All finite numbers meet this bound. */
def >=(min: Int) = true
def isOne = false
def isZero = false
def decrement = this
def isInfinite = true
override def toString = "Infinity"
}
/**
* Represents a finite upper bound. The maximum allowed value is 'value', inclusive.
* It must positive.
*/
final case class Finite(value: Int) extends UpperBound {
assume(value >= 0, "Maximum occurrences must be nonnegative.")
def >=(min: Int) = value >= min
def isOne = value == 1
def isZero = value == 0
def decrement = Finite(scala.math.max(0, value - 1))
def isInfinite = false
override def toString = value.toString
}
object UpperBound {
implicit def intToFinite(i: Int): Finite = Finite(i)
}

View File

@ -0,0 +1,29 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
import org.scalacheck._, Gen._, Prop._
object DefaultParsersSpec extends Properties("DefaultParsers") {
import DefaultParsers.{ ID, isIDChar, matches, validID }
property("∀ s ∈ String: validID(s) == matches(ID, s)") = forAll(
(s: String) => validID(s) == matches(ID, s)
)
property("∀ s ∈ genID: matches(ID, s)") = forAll(genID)(s => matches(ID, s))
property("∀ s ∈ genID: validID(s)") = forAll(genID)(s => validID(s))
private val chars: Seq[Char] = Char.MinValue to Char.MaxValue
private val genID: Gen[String] =
for {
c <- oneOf(chars filter (_.isLetter))
cs <- listOf(oneOf(chars filter isIDChar))
} yield (c :: cs).mkString
}

View File

@ -0,0 +1,162 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
object JLineTest {
import DefaultParsers._
val one = "blue" | "green" | "black"
val two = token("color" ~> Space) ~> token(one)
val three = token("color" ~> Space) ~> token(ID.examples("blue", "green", "black"))
val four = token("color" ~> Space) ~> token(ID, "<color name>")
val num = token(NatBasic)
val five = (num ~ token("+" | "-") ~ num) <~ token('=') flatMap {
case a ~ "+" ~ b => token((a + b).toString)
case a ~ "-" ~ b => token((a - b).toString)
}
val parsers = Map("1" -> one, "2" -> two, "3" -> three, "4" -> four, "5" -> five)
def main(args: Array[String]): Unit = {
import jline.TerminalFactory
import jline.console.ConsoleReader
val reader = new ConsoleReader()
TerminalFactory.get.init
val parser = parsers(args(0))
JLineCompletion.installCustomCompletor(reader, parser)
def loop(): Unit = {
val line = reader.readLine("> ")
if (line ne null) {
println("Result: " + apply(parser)(line).resultEmpty)
loop()
}
}
loop()
}
}
import Parser._
import org.scalacheck._
object ParserTest extends Properties("Completing Parser") {
import Parsers._
import DefaultParsers.matches
val nested = (token("a1") ~ token("b2")) ~ "c3"
val nestedDisplay = (token("a1", "<a1>") ~ token("b2", "<b2>")) ~ "c3"
val spacePort = token(Space) ~> Port
def p[T](f: T): T = { println(f); f }
def checkSingle(in: String, expect: Completion)(expectDisplay: Completion = expect) =
(("token '" + in + "'") |: checkOne(in, nested, expect)) &&
(("display '" + in + "'") |: checkOne(in, nestedDisplay, expectDisplay))
def checkOne(in: String, parser: Parser[_], expect: Completion): Prop =
completions(parser, in, 1) == Completions.single(expect)
def checkAll(in: String, parser: Parser[_], expect: Completions): Prop = {
val cs = completions(parser, in, 1)
("completions: " + cs) |: ("Expected: " + expect) |: (cs == expect: Prop)
}
def checkInvalid(in: String) =
(("token '" + in + "'") |: checkInv(in, nested)) &&
(("display '" + in + "'") |: checkInv(in, nestedDisplay))
def checkInv(in: String, parser: Parser[_]): Prop = {
val cs = completions(parser, in, 1)
("completions: " + cs) |: (cs == Completions.nil: Prop)
}
property("nested tokens a") =
checkSingle("", Completion.token("", "a1"))(Completion.displayOnly("<a1>"))
property("nested tokens a1") =
checkSingle("a", Completion.token("a", "1"))(Completion.displayOnly("<a1>"))
property("nested tokens a inv") = checkInvalid("b")
property("nested tokens b") =
checkSingle("a1", Completion.token("", "b2"))(Completion.displayOnly("<b2>"))
property("nested tokens b2") =
checkSingle("a1b", Completion.token("b", "2"))(Completion.displayOnly("<b2>"))
property("nested tokens b inv") = checkInvalid("a1a")
property("nested tokens c") = checkSingle("a1b2", Completion.suggestion("c3"))()
property("nested tokens c3") = checkSingle("a1b2c", Completion.suggestion("3"))()
property("nested tokens c inv") = checkInvalid("a1b2a")
property("suggest space") = checkOne("", spacePort, Completion.token("", " "))
property("suggest port") = checkOne(" ", spacePort, Completion.displayOnly("<port>"))
property("no suggest at end") = checkOne("asdf", "asdf", Completion.suggestion(""))
property("no suggest at token end") = checkOne("asdf", token("asdf"), Completion.suggestion(""))
property("empty suggest for examples") =
checkOne("asdf", any.+.examples("asdf", "qwer"), Completion.suggestion(""))
property("empty suggest for examples token") =
checkOne("asdf", token(any.+.examples("asdf", "qwer")), Completion.suggestion(""))
val colors = Set("blue", "green", "red")
val base = (seen: Seq[String]) => token(ID examples (colors -- seen))
val sep = token(Space)
val repeat = repeatDep(base, sep)
def completionStrings(ss: Set[String]) = Completions(ss map (Completion.token("", _)))
property("repeatDep no suggestions for bad input") = checkInv(".", repeat)
property("repeatDep suggest all") = checkAll("", repeat, completionStrings(colors))
property("repeatDep suggest remaining two") = {
val first = colors.toSeq.head
checkAll(first + " ", repeat, completionStrings(colors - first))
}
property("repeatDep suggest remaining one") = {
val take = colors.toSeq.take(2)
checkAll(take.mkString("", " ", " "), repeat, completionStrings(colors -- take))
}
property("repeatDep requires at least one token") = !matches(repeat, "")
property("repeatDep accepts one token") = matches(repeat, colors.toSeq.head)
property("repeatDep accepts two tokens") = matches(repeat, colors.toSeq.take(2).mkString(" "))
}
object ParserExample {
val ws = charClass(_.isWhitespace, "whitespace").+
val notws = charClass(!_.isWhitespace, "not whitespace").+
val name = token("test")
val options = (ws ~> token("quick" | "failed" | "new")).*
val exampleSet = Set("am", "is", "are", "was", "were")
val include = (ws ~> token(
examples(notws.string, new FixedSetExamples(exampleSet), exampleSet.size, false)
)).*
val t = name ~ options ~ include
// Get completions for some different inputs
println(completions(t, "te", 1))
println(completions(t, "test ", 1))
println(completions(t, "test w", 1))
// Get the parsed result for different inputs
println(apply(t)("te").resultEmpty)
println(apply(t)("test").resultEmpty)
println(apply(t)("test w").resultEmpty)
println(apply(t)("test was were").resultEmpty)
def run(n: Int): Unit = {
val a = 'a'.id
val aq = a.?
val aqn = repeat(aq, min = n, max = n)
val an = repeat(a, min = n, max = n)
val ann = aqn ~ an
def r = apply(ann)("a" * (n * 2)).resultEmpty
println(r.isValid)
}
def run2(n: Int): Unit = {
val ab = "ab".?.*
val r = apply(ab)("a" * n).resultEmpty
println(r)
}
}

View File

@ -0,0 +1,12 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import org.scalatest._
abstract class UnitSpec extends FlatSpec with Matchers

View File

@ -0,0 +1,104 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
import java.io.File
import org.scalatest.Assertion
import sbt.io.IO
class FileExamplesTest extends UnitSpec {
"listing all files in an absolute base directory" should
"produce the entire base directory's contents" in {
withDirectoryStructure() { ds =>
ds.fileExamples().toList should contain theSameElementsAs (ds.allRelativizedPaths)
}
}
"listing files with a prefix that matches none" should "produce an empty list" in {
withDirectoryStructure(withCompletionPrefix = "z") { ds =>
ds.fileExamples().toList shouldBe empty
}
}
"listing single-character prefixed files" should "produce matching paths only" in {
withDirectoryStructure(withCompletionPrefix = "f") { ds =>
ds.fileExamples().toList should contain theSameElementsAs (ds.prefixedPathsOnly)
}
}
"listing directory-prefixed files" should "produce matching paths only" in {
withDirectoryStructure(withCompletionPrefix = "far") { ds =>
ds.fileExamples().toList should contain theSameElementsAs (ds.prefixedPathsOnly)
}
}
it should "produce sub-dir contents only when appending a file separator to the directory" in {
withDirectoryStructure(withCompletionPrefix = "far" + File.separator) { ds =>
ds.fileExamples().toList should contain theSameElementsAs (ds.prefixedPathsOnly)
}
}
"listing files with a sub-path prefix" should "produce matching paths only" in {
withDirectoryStructure(withCompletionPrefix = "far" + File.separator + "ba") { ds =>
ds.fileExamples().toList should contain theSameElementsAs (ds.prefixedPathsOnly)
}
}
"completing a full path" should "produce a list with an empty string" in {
withDirectoryStructure(withCompletionPrefix = "bazaar") { ds =>
ds.fileExamples().toList shouldEqual List("")
}
}
def withDirectoryStructure[A](withCompletionPrefix: String = "")(
thunk: DirectoryStructure => Assertion
): Assertion = {
IO.withTemporaryDirectory { tempDir =>
val ds = new DirectoryStructure(withCompletionPrefix)
ds.createSampleDirStructure(tempDir)
ds.fileExamples = new FileExamples(ds.baseDir, withCompletionPrefix)
thunk(ds)
}
}
final class DirectoryStructure(withCompletionPrefix: String) {
var fileExamples: FileExamples = _
var baseDir: File = _
var childFiles: List[File] = _
var childDirectories: List[File] = _
var nestedFiles: List[File] = _
var nestedDirectories: List[File] = _
def allRelativizedPaths: List[String] =
(childFiles ++ childDirectories ++ nestedFiles ++ nestedDirectories)
.map(IO.relativize(baseDir, _).get)
def prefixedPathsOnly: List[String] =
allRelativizedPaths
.filter(_ startsWith withCompletionPrefix)
.map(_ substring withCompletionPrefix.length)
def createSampleDirStructure(tempDir: File): Unit = {
childFiles = toChildFiles(tempDir, List("foo", "bar", "bazaar"))
childDirectories = toChildFiles(tempDir, List("moo", "far"))
nestedFiles = toChildFiles(childDirectories(1), List("farfile1", "barfile2"))
nestedDirectories = toChildFiles(childDirectories(1), List("fardir1", "bardir2"))
(childDirectories ++ nestedDirectories).map(_.mkdirs())
(childFiles ++ nestedFiles).map(_.createNewFile())
baseDir = tempDir
}
private def toChildFiles(baseDir: File, files: List[String]): List[File] =
files.map(new File(baseDir, _))
}
}

View File

@ -0,0 +1,32 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
class FixedSetExamplesTest extends UnitSpec {
"adding a prefix" should "produce a smaller set of examples with the prefix removed" in {
val _ = new Examples {
fixedSetExamples.withAddedPrefix("f")() should contain theSameElementsAs
(List("oo", "ool", "u"))
fixedSetExamples.withAddedPrefix("fo")() should contain theSameElementsAs (List("o", "ol"))
fixedSetExamples.withAddedPrefix("b")() should contain theSameElementsAs (List("ar"))
}
}
"without a prefix" should "produce the original set" in {
val _ = new Examples {
fixedSetExamples() shouldBe exampleSet
}
}
trait Examples {
val exampleSet = List("foo", "bar", "fool", "fu")
val fixedSetExamples = FixedSetExamples(exampleSet)
}
}

View File

@ -0,0 +1,117 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
package complete
import Completion._
class ParserWithExamplesTest extends UnitSpec {
"listing a limited number of completions" should
"grab only the needed number of elements from the iterable source of examples" in {
val _ = new ParserWithLazyExamples {
parserWithExamples.completions(0)
examples.size shouldEqual maxNumberOfExamples
}
}
"listing only valid completions" should
"use the delegate parser to remove invalid examples" in {
val _ = new ParserWithValidExamples {
val validCompletions = Completions(
Set(
suggestion("blue"),
suggestion("red")
)
)
parserWithExamples.completions(0) shouldEqual validCompletions
}
}
"listing valid completions in a derived parser" should
"produce only valid examples that start with the character of the derivation" in {
val _ = new ParserWithValidExamples {
val derivedCompletions = Completions(
Set(
suggestion("lue")
)
)
parserWithExamples.derive('b').completions(0) shouldEqual derivedCompletions
}
}
"listing valid and invalid completions" should
"produce the entire source of examples" in {
val _ = new parserWithAllExamples {
val completions = Completions(examples.map(suggestion(_)).toSet)
parserWithExamples.completions(0) shouldEqual completions
}
}
"listing valid and invalid completions in a derived parser" should
"produce only examples that start with the character of the derivation" in {
val _ = new parserWithAllExamples {
val derivedCompletions = Completions(
Set(
suggestion("lue"),
suggestion("lock")
)
)
parserWithExamples.derive('b').completions(0) shouldEqual derivedCompletions
}
}
class ParserWithLazyExamples
extends ParserExample(
GrowableSourceOfExamples(),
maxNumberOfExamples = 5,
removeInvalidExamples = false
)
class ParserWithValidExamples extends ParserExample(removeInvalidExamples = true)
class parserWithAllExamples extends ParserExample(removeInvalidExamples = false)
case class ParserExample(
examples: Iterable[String] = Set("blue", "yellow", "greeen", "block", "red"),
maxNumberOfExamples: Int = 25,
removeInvalidExamples: Boolean
) {
import DefaultParsers._
val colorParser = "blue" | "green" | "black" | "red"
val parserWithExamples: Parser[String] = new ParserWithExamples[String](
colorParser,
FixedSetExamples(examples),
maxNumberOfExamples,
removeInvalidExamples
)
}
case class GrowableSourceOfExamples() extends Iterable[String] {
private var numberOfIteratedElements: Int = 0
override def iterator: Iterator[String] = {
new Iterator[String] {
var currentElement = 0
override def next(): String = {
currentElement += 1
numberOfIteratedElements = Math.max(currentElement, numberOfIteratedElements)
numberOfIteratedElements.toString
}
override def hasNext: Boolean = true
}
}
override def size: Int = numberOfIteratedElements
}
}

View File

@ -0,0 +1,63 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util.complete
import org.scalatest.FlatSpec
class SizeParserSpec extends FlatSpec {
"SizeParser" should "handle raw bytes" in {
assert(Parser.parse(str = "123456", SizeParser.value) == Right(123456L))
}
it should "handle bytes" in {
assert(Parser.parse(str = "123456b", SizeParser.value) == Right(123456L))
assert(Parser.parse(str = "123456B", SizeParser.value) == Right(123456L))
assert(Parser.parse(str = "123456 b", SizeParser.value) == Right(123456L))
assert(Parser.parse(str = "123456 B", SizeParser.value) == Right(123456L))
}
it should "handle kilobytes" in {
assert(Parser.parse(str = "123456k", SizeParser.value) == Right(123456L * 1024))
assert(Parser.parse(str = "123456K", SizeParser.value) == Right(123456L * 1024))
assert(Parser.parse(str = "123456 K", SizeParser.value) == Right(123456L * 1024))
assert(Parser.parse(str = "123456 K", SizeParser.value) == Right(123456L * 1024))
}
it should "handle megabytes" in {
assert(Parser.parse(str = "123456m", SizeParser.value) == Right(123456L * 1024 * 1024))
assert(Parser.parse(str = "123456M", SizeParser.value) == Right(123456L * 1024 * 1024))
assert(Parser.parse(str = "123456 M", SizeParser.value) == Right(123456L * 1024 * 1024))
assert(Parser.parse(str = "123456 M", SizeParser.value) == Right(123456L * 1024 * 1024))
}
it should "handle gigabytes" in {
assert(Parser.parse(str = "123456g", SizeParser.value) == Right(123456L * 1024 * 1024 * 1024))
assert(Parser.parse(str = "123456G", SizeParser.value) == Right(123456L * 1024 * 1024 * 1024))
assert(Parser.parse(str = "123456 G", SizeParser.value) == Right(123456L * 1024 * 1024 * 1024))
assert(Parser.parse(str = "123456 G", SizeParser.value) == Right(123456L * 1024 * 1024 * 1024))
}
it should "handle doubles" in {
assert(Parser.parse(str = "1.25g", SizeParser.value) == Right(5L * 1024 * 1024 * 1024 / 4))
assert(Parser.parse(str = "1.25 g", SizeParser.value) == Right(5L * 1024 * 1024 * 1024 / 4))
assert(Parser.parse(str = "1.25 g", SizeParser.value) == Right(5L * 1024 * 1024 * 1024 / 4))
assert(Parser.parse(str = "1.25 G", SizeParser.value) == Right(5L * 1024 * 1024 * 1024 / 4))
}
private val expectedCompletions = Set("", "b", "B", "g", "G", "k", "K", "m", "M", " ")
it should "have completions for long" in {
val completions = Parser.completions(SizeParser.value, "123", level = 0).get.map(_.display)
assert(completions == expectedCompletions)
}
it should "have completions for long with spaces" in {
val completions = Parser.completions(SizeParser.value, "123", level = 0).get.map(_.display)
assert(completions == expectedCompletions)
}
it should "have completions for double " in {
val completions = Parser.completions(SizeParser.value, "1.25", level = 0).get.map(_.display)
assert(completions == expectedCompletions)
}
it should "have completions for double with spaces" in {
val completions = Parser.completions(SizeParser.value, "1.25 ", level = 0).get.map(_.display)
assert(completions == expectedCompletions + "")
}
}

View File

@ -0,0 +1,3 @@
Simple Build Tool: Control Component
Copyright 2009 Mark Harrah
Licensed under BSD-style license (see LICENSE)

View File

@ -0,0 +1,49 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import java.io.IOException
object ErrorHandling {
def translate[T](msg: => String)(f: => T) =
try {
f
} catch {
case e: IOException => throw new TranslatedIOException(msg + e.toString, e)
case e: Exception => throw new TranslatedException(msg + e.toString, e)
}
def wideConvert[T](f: => T): Either[Throwable, T] =
try {
Right(f)
} catch {
case ex @ (_: Exception | _: StackOverflowError) => Left(ex)
case err @ (_: ThreadDeath | _: VirtualMachineError) => throw err
case x: Throwable => Left(x)
}
def convert[T](f: => T): Either[Exception, T] =
try {
Right(f)
} catch { case e: Exception => Left(e) }
def reducedToString(e: Throwable): String =
if (e.getClass == classOf[RuntimeException]) {
val msg = e.getMessage
if (msg == null || msg.isEmpty) e.toString else msg
} else
e.toString
}
sealed class TranslatedException private[sbt] (msg: String, cause: Throwable)
extends RuntimeException(msg, cause) {
override def toString = msg
}
final class TranslatedIOException private[sbt] (msg: String, cause: IOException)
extends TranslatedException(msg, cause)

View File

@ -0,0 +1,28 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
/** Defines a function to call as sbt exits.*/
trait ExitHook {
/** Subclasses should implement this method, which is called when this hook is executed. */
def runBeforeExiting(): Unit
}
object ExitHook {
def apply(f: => Unit): ExitHook = new ExitHook { def runBeforeExiting() = f }
}
object ExitHooks {
/** Calls each registered exit hook, trapping any exceptions so that each hook is given a chance to run. */
def runExitHooks(exitHooks: Seq[ExitHook]): Seq[Throwable] =
exitHooks.flatMap(hook => ErrorHandling.wideConvert(hook.runBeforeExiting()).left.toOption)
}

View File

@ -0,0 +1,28 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
final class MessageOnlyException(override val toString: String) extends RuntimeException(toString)
/**
* A dummy exception for the top-level exception handler to know that an exception
* has been handled, but is being passed further up to indicate general failure.
*/
final class AlreadyHandledException(val underlying: Throwable) extends RuntimeException
/**
* A marker trait for a top-level exception handler to know that this exception
* doesn't make sense to display.
*/
trait UnprintableException extends Throwable
/**
* A marker trait that refines UnprintableException to indicate to a top-level exception handler
* that the code throwing this exception has already provided feedback to the user about the error condition.
*/
trait FeedbackProvidedException extends UnprintableException

View File

@ -0,0 +1,33 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import java.util.concurrent.ConcurrentHashMap
import scala.sys.process.Process
/**
* Manages forked processes created by sbt. Any process registered
* with RunningProcesses can be killed with the killAll method. In
* particular, this can be used in a signal handler to kill these
* processes when the user inputs ctrl+c.
*/
private[sbt] object RunningProcesses {
val active = ConcurrentHashMap.newKeySet[Process]
def add(process: Process): Unit = active.synchronized {
active.add(process)
()
}
def remove(process: Process): Unit = active.synchronized {
active.remove(process)
()
}
def killAll(): Unit = active.synchronized {
active.forEach(_.destroy())
active.clear()
}
}

View File

@ -0,0 +1,22 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package xsbti;
import java.util.function.Supplier;
public interface Logger {
void error(Supplier<String> msg);
void warn(Supplier<String> msg);
void info(Supplier<String> msg);
void debug(Supplier<String> msg);
void trace(Supplier<Throwable> exception);
}

View File

@ -0,0 +1,53 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package xsbti;
import java.io.File;
import java.util.Optional;
public interface Position {
Optional<Integer> line();
String lineContent();
Optional<Integer> offset();
// pointer to the column position of the error/warning
Optional<Integer> pointer();
Optional<String> pointerSpace();
Optional<String> sourcePath();
Optional<File> sourceFile();
// Default values to avoid breaking binary compatibility
default Optional<Integer> startOffset() {
return Optional.empty();
}
default Optional<Integer> endOffset() {
return Optional.empty();
}
default Optional<Integer> startLine() {
return Optional.empty();
}
default Optional<Integer> startColumn() {
return Optional.empty();
}
default Optional<Integer> endLine() {
return Optional.empty();
}
default Optional<Integer> endColumn() {
return Optional.empty();
}
}

View File

@ -0,0 +1,29 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package xsbti;
import java.util.Optional;
public interface Problem {
String category();
Severity severity();
String message();
Position position();
// Default value to avoid breaking binary compatibility
/**
* If present, the string shown to the user when displaying this Problem. Otherwise, the Problem
* will be shown in an implementation-defined way based on the values of its other fields.
*/
default Optional<String> rendered() {
return Optional.empty();
}
}

Some files were not shown because too many files have changed in this diff Show More