diff --git a/launcher-package/CONTRIBUTING.md b/launcher-package/CONTRIBUTING.md new file mode 100644 index 000000000..dfb55c095 --- /dev/null +++ b/launcher-package/CONTRIBUTING.md @@ -0,0 +1,28 @@ +Steps to publish +================ + +``` +$ sbt -Dsbt.build.version=1.0.3 -Dsbt.build.offline=true +> universal:publish +> debian:publish +> rpm:publish +> universal:bintrayReleaseAllStaged +> debian:releaseAllStaged +> rpm:releaseAllStaged +``` + +## Notes on batch + +### Testing if a variable is blank + +``` +if not defined _JAVACMD set _JAVACMD=java +``` + +### Testing if an argument %0 is blank + +``` +if "%~0" == "" goto echolist_end +``` + +The above would work in case `%0` contains either double quote (`"`) or whitespace. diff --git a/launcher-package/LICENSE b/launcher-package/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/launcher-package/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + 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 [yyyy] [name of copyright owner] + + 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. diff --git a/launcher-package/NOTICE b/launcher-package/NOTICE new file mode 100644 index 000000000..6b28ce942 --- /dev/null +++ b/launcher-package/NOTICE @@ -0,0 +1,45 @@ +sbt +https://www.scala-sbt.org/ +Copyright 2011 - 2019, 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. +Copyright 2002-2008 EPFL, Lausanne + +JLine is distributed with the launcher. +It is licensed under a BSD-style license + +Portions based on code from sbt launcher script +Copyright 2011 - 2012, Paul Phillips, 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: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. + +Apache Ivy, licensed under the Apache License, Version 2.0 + +This product includes software developed by +The Apache Software Foundation (http://www.apache.org/). + +Portions of Ivy were originally developed by +Jayasoft SARL (http://www.jayasoft.fr/) +and are licensed to the Apache Software Foundation under the +"Software Grant License Agreement" + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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. diff --git a/launcher-package/README.md b/launcher-package/README.md new file mode 100644 index 000000000..9551dd528 --- /dev/null +++ b/launcher-package/README.md @@ -0,0 +1,60 @@ +sbt: the launcher script +======================== + +This is a launcher script for running [sbt](https://github.com/sbt/sbt). + +Current -help output: + +```bash +Usage: sbt [options] + + -h | -help print this message + -v | -verbose this runner is chattier + -V | -version print the version of mothership sbt + -d | -debug set sbt log level to debug + -no-colors disable ANSI color codes + -sbt-create start sbt even if current directory contains no sbt project + -sbt-dir path to global settings/plugins directory (default: ~/.sbt) + -sbt-boot path to shared boot directory (default: ~/.sbt/boot in 0.11 series) + -ivy path to local Ivy repository (default: ~/.ivy2) + -mem set memory options (default: 1024, which is -Xms1024m -Xmx1024m -XX:ReservedCodeCacheSize=128m) + -no-share use all local caches; no sharing + -no-global uses global caches, but does not use global ~/.sbt directory. + -jvm-debug Turn on JVM debugging, open at the given port. + -batch Disable interactive mode + + # sbt version (default: from project/build.properties if present, else latest release) + -sbt-version use the specified version of sbt + -sbt-jar use the specified jar as the sbt launcher + + # java version (default: java from PATH, currently openjdk version "1.8.0_172") + -java-home alternate JAVA_HOME + + # jvm options and output control + JAVA_OPTS environment variable, if unset uses "" + .jvmopts if this file exists in the current directory, its contents + are appended to JAVA_OPTS + SBT_OPTS environment variable, if unset uses "" + .sbtopts if this file exists in the current directory, its contents + are prepended to the runner args + /etc/sbt/sbtopts if this file exists, it is prepended to the runner args + -Dkey=val pass -Dkey=val directly to the java runtime + -J-X pass option -X directly to the java runtime + (-J is stripped) + -S-X add -X to sbt's scalacOptions (-S is stripped) +``` + +### Native packages + +This project also includes native packages to run sbt for + +* Windows +* RedHat (rpm) +* Debian (deb) + +Locations for download to be available soon. + +### Build status + +[![Build Status](https://travis-ci.org/sbt/sbt-launcher-package.svg?branch=master)](https://travis-ci.org/sbt/sbt-launcher-package) +[![Build Status](https://ci.appveyor.com/api/projects/status/github/sbt/sbt-launcher-package?branch=master&svg=true&retina=true)](https://ci.appveyor.com/project/sbt/sbt-launcher-package) diff --git a/launcher-package/bin/coursier b/launcher-package/bin/coursier new file mode 100755 index 000000000..e808b0db0 Binary files /dev/null and b/launcher-package/bin/coursier differ diff --git a/launcher-package/bin/coursier.bat b/launcher-package/bin/coursier.bat new file mode 100644 index 000000000..87ca34b5b --- /dev/null +++ b/launcher-package/bin/coursier.bat @@ -0,0 +1,95 @@ +@echo off + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set HOME=%HOMEDRIVE%%HOMEPATH%) + +set ERROR_CODE=0 + +@REM set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" @setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +for /f %%j in ("java.exe") do ( + set JAVA_EXE="%%~$PATH:j" + goto init +) + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" ( + SET JAVA_EXE="%JAVA_HOME%\bin\java.exe" + goto init +) + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory. +echo JAVA_HOME = %JAVA_HOME% +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation +echo. +goto error + +:init +@REM Decide how to startup depending on the version of windows + +@REM -- Win98ME +if NOT "%OS%"=="Windows_NT" goto Win9xArg + +@REM -- 4NT shell +if "%@eval[2+2]" == "4" goto 4NTArgs + +@REM -- Regular WinNT shell +set CMD_LINE_ARGS=%* +goto endInit + +@REM The 4NT Shell from jp software +:4NTArgs +set CMD_LINE_ARGS=%$ +goto endInit + +:Win9xArg +@REM Slurp the command line arguments. This loop allows for an unlimited number +@REM of agruments (up to the command line limit, anyway). +set CMD_LINE_ARGS= +:Win9xApp +if %1a==a goto endInit +set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1 +shift +goto Win9xApp + +@REM Reaching here means variables are defined and arguments have been captured +:endInit + +set JAR_PATH=%~dp0\%~n0 +SET PROG_DIR=%~dp0 +SET PSEP=; + +@REM Start Java program +:runm2 +SET CMDLINE=%JAVA_EXE% -noverify %JAVA_OPTS% -Dprog.dir="%PROG_DIR:\=\\%" -jar "%JAR_PATH%" %CMD_LINE_ARGS% +%CMDLINE% +if ERRORLEVEL 1 goto error +goto end + +:error +if "%OS%"=="Windows_NT" @endlocal +set ERROR_CODE=1 + +:end +@REM set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" goto endNT + +@REM For old DOS remove the set variables from ENV - we assume they were not set +@REM before we started - at least we don't leave any baggage around +set JAVA_EXE= +set CMD_LINE_ARGS= +set CMDLINE= +set PSEP= +goto postExec + +:endNT +@endlocal + +:postExec +exit /B %ERROR_CODE% diff --git a/launcher-package/build.sbt b/launcher-package/build.sbt new file mode 100755 index 000000000..c2459f8a3 --- /dev/null +++ b/launcher-package/build.sbt @@ -0,0 +1,470 @@ +import scala.util.control.Exception.catching +import _root_.bintray.InternalBintrayKeys._ +import _root_.bintray.{BintrayRepo, Bintray} +import NativePackagerHelper._ +import com.typesafe.sbt.packager.SettingsHelper._ +import DebianConstants._ + +lazy val sbtOfflineInstall = + sys.props.getOrElse("sbt.build.offline", sys.env.getOrElse("sbt.build.offline", "true")) match { + case "true" | "1" => true + case "false" | "0" => false + case _ => false + } +lazy val sbtIncludeSbtn = + sys.props.getOrElse("sbt.build.includesbtn", sys.env.getOrElse("sbt.build.includesbtn", "true")) match { + case "true" | "1" => true + case "false" | "0" => false + case _ => false + } +lazy val sbtVersionToRelease = sys.props.getOrElse("sbt.build.version", sys.env.getOrElse("sbt.build.version", { + sys.error("-Dsbt.build.version must be set") + })) + +lazy val scala210 = "2.10.7" +lazy val scala212 = "2.12.10" +lazy val scala210Jline = "org.scala-lang" % "jline" % scala210 +lazy val jansi = { + if (sbtVersionToRelease startsWith "1.") "org.fusesource.jansi" % "jansi" % "1.12" + else "org.fusesource.jansi" % "jansi" % "1.4" +} +lazy val scala212Compiler = "org.scala-lang" % "scala-compiler" % scala212 +lazy val scala212Jline = "jline" % "jline" % "2.14.6" +// use the scala-xml version used by the compiler not the latest: https://github.com/scala/scala/blob/v2.12.10/versions.properties#L22 +lazy val scala212Xml = "org.scala-lang.modules" % "scala-xml_2.12" % "1.0.6" +lazy val sbtActual = "org.scala-sbt" % "sbt" % sbtVersionToRelease + +lazy val sbt013ExtraDeps = { + if (sbtVersionToRelease startsWith "0.13.") Seq(scala210Jline) + else Seq() +} + +lazy val isWindows: Boolean = sys.props("os.name").toLowerCase(java.util.Locale.ENGLISH).contains("windows") +lazy val isExperimental = (sbtVersionToRelease contains "RC") || (sbtVersionToRelease contains "M") +val sbtLaunchJarUrl = SettingKey[String]("sbt-launch-jar-url") +val sbtLaunchJarLocation = SettingKey[File]("sbt-launch-jar-location") +val sbtLaunchJar = TaskKey[File]("sbt-launch-jar", "Resolves SBT launch jar") +val moduleID = (organization) apply { (o) => ModuleID(o, "sbt", sbtVersionToRelease) } +val sbtnVersion = SettingKey[String]("sbtn-version") +val sbtnJarsMappings = TaskKey[Seq[(File, String)]]("sbtn-jars-mappings", "Resolves sbtn JARs") +val sbtnJarsBaseUrl = SettingKey[String]("sbtn-jars-base-url") + +lazy val bintrayDebianUrl = settingKey[String]("API point for Debian packages") +lazy val bintrayDebianExperimentalUrl = settingKey[String]("API point for Debian experimental packages") +lazy val bintrayRpmUrl = settingKey[String]("API point for RPM packages") +lazy val bintrayRpmExperimentalUrl = settingKey[String]("API point for RPM experimental packages") +lazy val bintrayGenericPackagesUrl = settingKey[String]("API point for generic packages") +lazy val bintrayTripple = settingKey[(String, String, String)]("id, url, and pattern") + +val bintrayLinuxPattern = "[module]/[revision]/[module]-[revision].[ext]" +val bintrayGenericPattern = "[module]/[revision]/[module]/[revision]/[module]-[revision].[ext]" +val bintrayReleaseAllStaged = TaskKey[Unit]("bintray-release-all-staged", "Release all staged artifacts on bintray.") +val windowsBuildId = settingKey[Int]("build id for Windows installer") +val debianBuildId = settingKey[Int]("build id for Debian") + +val exportRepoUsingCoursier = taskKey[File]("export Maven style repository") +val exportRepoCsrDirectory = settingKey[File]("") + +val x86MacPlatform = "x86_64-apple-darwin" +val x86LinuxPlatform = "x86_64-pc-linux" +val x86WindowsPlatform = "x86_64-pc-win32" +val x86MacImageName = s"sbtn-$x86MacPlatform" +val x86LinuxImageName = s"sbtn-$x86LinuxPlatform" +val x86WindowsImageName = s"sbtn-$x86WindowsPlatform.exe" + +// This build creates a SBT plugin with handy features *and* bundles the SBT script for distribution. +val root = (project in file(".")). + enablePlugins(UniversalPlugin, LinuxPlugin, DebianPlugin, RpmPlugin, WindowsPlugin, + UniversalDeployPlugin, DebianDeployPlugin, RpmDeployPlugin, WindowsDeployPlugin). + settings( + organization := "org.scala-sbt", + name := "sbt-launcher-packaging", + packageName := "sbt", + version := "0.1.0", + crossTarget := target.value, + clean := { + val _ = (clean in dist).value + clean.value + }, + credentials ++= { + (sys.env.get("BINTRAY_USER"), sys.env.get("BINTRAY_PASS")) match { + case (Some(u), Some(p)) => Seq(Credentials("Bintray API Realm", "api.bintray.com", u, p)) + case _ => Nil + } + }, + useGpg := true, + usePgpKeyHex("642AC823"), + pgpSecretRing := file(s"""${sys.props("user.home")}""") / ".ssh" / "scalasbt.key", + pgpPublicRing := file(s"""${sys.props("user.home")}""") / ".ssh" / "scalasbt.pub", + publishToSettings, + sbtLaunchJarUrl := downloadUrlForVersion(sbtVersionToRelease), + sbtLaunchJarLocation := { target.value / "sbt-launch.jar" }, + sbtLaunchJar := { + val uri = sbtLaunchJarUrl.value + val file = sbtLaunchJarLocation.value + import dispatch.classic._ + if(!file.exists) { + // oddly, some places require us to create the file before writing... + IO.touch(file) + val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(file)) + try Http(url(uri) >>> writer) + finally writer.close() + } + // TODO - GPG Trust validation. + file + }, + sbtnVersion := "1.4.7", + sbtnJarsBaseUrl := "https://github.com/sbt/sbtn-dist/releases/download", + sbtnJarsMappings := { + val baseUrl = sbtnJarsBaseUrl.value + val v = sbtnVersion.value + val macosImageTar = s"sbtn-$x86MacPlatform-$v.tar.gz" + val linuxImageTar = s"sbtn-$x86LinuxPlatform-$v.tar.gz" + val windowsImageZip = s"sbtn-$x86WindowsPlatform-$v.zip" + val t = target.value + val macosTar = t / macosImageTar + val linuxTar = t / linuxImageTar + val windowsZip = t / windowsImageZip + import dispatch.classic._ + if(!macosTar.exists && !isWindows && sbtIncludeSbtn) { + IO.touch(macosTar) + val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(macosTar)) + try Http(url(s"$baseUrl/v$v/$macosImageTar") >>> writer) + finally writer.close() + val platformDir = t / x86MacPlatform + IO.createDirectory(platformDir) + s"tar zxvf $macosTar --directory $platformDir".! + IO.move(platformDir / "sbtn", t / x86MacImageName) + } + if(!linuxTar.exists && !isWindows && sbtIncludeSbtn) { + IO.touch(linuxTar) + val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(linuxTar)) + try Http(url(s"$baseUrl/v$v/$linuxImageTar") >>> writer) + finally writer.close() + val platformDir = t / x86LinuxPlatform + IO.createDirectory(platformDir) + s"""tar zxvf $linuxTar --directory $platformDir""".! + IO.move(platformDir / "sbtn", t / x86LinuxImageName) + } + if(!windowsZip.exists && sbtIncludeSbtn) { + IO.touch(windowsZip) + val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(windowsZip)) + try Http(url(s"$baseUrl/v$v/$windowsImageZip") >>> writer) + finally writer.close() + val platformDir = t / x86WindowsPlatform + IO.unzip(windowsZip, platformDir) + IO.move(platformDir / "sbtn.exe", t / x86WindowsImageName) + } + if (!sbtIncludeSbtn) Seq() + else if (isWindows) Seq(t / x86WindowsImageName -> s"bin/$x86WindowsImageName") + else + Seq(t / x86MacImageName -> s"bin/$x86MacImageName", + t / x86LinuxImageName -> s"bin/$x86LinuxImageName", + t / x86WindowsImageName -> s"bin/$x86WindowsImageName") + }, + + // GENERAL LINUX PACKAGING STUFFS + maintainer := "Eugene Yokota ", + packageSummary := "sbt, the interactive build tool", + packageDescription := """This script provides a native way to run sbt, + a build tool for Scala and more.""", + // Here we remove the jar file and launch lib from the symlinks: + linuxPackageSymlinks := { + val links = linuxPackageSymlinks.value + for { + link <- links + if !(link.destination endsWith "sbt-launch.jar") + } yield link + }, + + // DEBIAN SPECIFIC + debianBuildId := sys.props.getOrElse("sbt.build.patch", sys.env.getOrElse("DIST_PATCHVER", "0")).toInt, + version in Debian := { + if (debianBuildId.value == 0) sbtVersionToRelease + else sbtVersionToRelease + "." + debianBuildId.value + }, + // Used to have "openjdk-8-jdk" but that doesn't work on Ubuntu 14.04 https://github.com/sbt/sbt/issues/3105 + // before that we had java6-runtime-headless" and that was pulling in JDK9 on Ubuntu 16.04 https://github.com/sbt/sbt/issues/2931 + debianPackageDependencies in Debian ++= Seq("bash (>= 3.2)"), + debianPackageRecommends in Debian += "git", + linuxPackageMappings in Debian += { + val bd = sourceDirectory.value + (packageMapping( + (bd / "debian" / "changelog") -> "/usr/share/doc/sbt/changelog.gz" + ) withUser "root" withGroup "root" withPerms "0644" gzipped) asDocs() + }, + debianChangelog in Debian := { Some(sourceDirectory.value / "debian" / "changelog") }, + addPackage(Debian, packageBin in Debian, "deb"), + debianNativeBuildOptions in Debian := Seq("-Zgzip", "-z3"), + + // RPM SPECIFIC + rpmRelease := debianBuildId.value.toString, + version in Rpm := { + val stable0 = (sbtVersionToRelease split "[^\\d]" filterNot (_.isEmpty) mkString ".") + val stable = if (rpmRelease.value == "0") stable0 + else stable0 + "." + rpmRelease.value + if (isExperimental) ((sbtVersionToRelease split "[^\\d]" filterNot (_.isEmpty)).toList match { + case List(a, b, c, d) => List(0, 99, c, d).mkString(".") + }) + else stable + }, + // remove sbtn from RPM because it complains about it being noarch + linuxPackageMappings in Rpm := { + val orig = (linuxPackageMappings in Rpm).value + val nativeMappings = sbtnJarsMappings.value + orig.map(o => o.copy(mappings = o.mappings.toList filterNot { + case (x, p) => p.contains("sbtn-x86_64") + })) + }, + rpmVendor := "lightbend", + rpmUrl := Some("http://github.com/sbt/sbt-launcher-package"), + rpmLicense := Some("BSD"), + // This is intentionally empty. java-devel could bring in JDK 9-ea on Fedora, + // and java-1.8.0-devel doesn't work on CentOS 6 and 7. + // https://github.com/sbt/sbt-launcher-package/issues/151 + // https://github.com/elastic/logstash/issues/6275#issuecomment-261359933 + rpmRequirements := Seq(), + rpmProvides := Seq("sbt"), + + // WINDOWS SPECIFIC + windowsBuildId := 0, + version in Windows := { + val bid = windowsBuildId.value + val sv = sbtVersionToRelease + (sv split "[^\\d]" filterNot (_.isEmpty)) match { + case Array(major,minor,bugfix, _*) if bid == 0 => Seq(major, minor, bugfix) mkString "." + case Array(major,minor,bugfix, _*) => Seq(major, minor, bugfix, bid.toString) mkString "." + case Array(major,minor) => Seq(major, minor, "0", bid.toString) mkString "." + case Array(major) => Seq(major, "0", "0", bid.toString) mkString "." + } + }, + maintainer in Windows := "Lightbend, Inc.", + packageSummary in Windows := "sbt " + (version in Windows).value, + packageDescription in Windows := "The interactive build tool.", + wixProductId := "ce07be71-510d-414a-92d4-dff47631848a", + wixProductUpgradeId := Hash.toHex(Hash((version in Windows).value)).take(32), + javacOptions := Seq("-source", "1.8", "-target", "1.8"), + + // Universal ZIP download install. + packageName in Universal := packageName.value, // needs to be set explicitly due to a bug in native-packager + version in Universal := sbtVersionToRelease, + + mappings in Universal := { + val t = (target in Universal).value + val prev = (mappings in Universal).value + val BinSbt = "bin" + java.io.File.separator + "sbt" + val BinBat = BinSbt + ".bat" + prev.toList map { + case (k, BinSbt) => + import java.nio.file.{Files, FileSystems} + val x = IO.read(k) + IO.write(t / "sbt", x.replaceAllLiterally( + "declare init_sbt_version=_to_be_replaced", + s"declare init_sbt_version=$sbtVersionToRelease")) + + if (FileSystems.getDefault.supportedFileAttributeViews.contains("posix")) { + val perms = Files.getPosixFilePermissions(k.toPath) + Files.setPosixFilePermissions(t / "sbt" toPath, perms) + } + + (t / "sbt", BinSbt) + case (k, BinBat) => + val x = IO.read(k) + IO.write(t / "sbt.bat", x.replaceAllLiterally( + "set init_sbt_version=_to_be_replaced", + s"set init_sbt_version=$sbtVersionToRelease")) + (t / "sbt.bat", BinBat) + case (k, v) => (k, v) + } + }, + + mappings in Universal ++= { + val launchJar = sbtLaunchJar.value + Seq( + launchJar -> "bin/sbt-launch.jar" + ) ++ sbtnJarsMappings.value + }, + mappings in Universal ++= (Def.taskDyn { + if (sbtOfflineInstall && sbtVersionToRelease.startsWith("1.")) + Def.task { + val _ = (exportRepoUsingCoursier in dist).value + directory((target in dist).value / "lib") + } + else if (sbtOfflineInstall) + Def.task { + val _ = (exportRepo in dist).value + directory((target in dist).value / "lib") + } + else Def.task { Seq[(File, String)]() } + }).value, + mappings in Universal ++= { + val base = baseDirectory.value + if (sbtVersionToRelease startsWith "0.13.") Nil + else Seq[(File, String)](base / "LICENSE" -> "LICENSE", base / "NOTICE" -> "NOTICE") + }, + + // Misccelaneous publishing stuff... + projectID in Debian := { + val m = moduleID.value + m.copy(revision = (version in Debian).value) + }, + projectID in Windows := { + val m = moduleID.value + m.copy(revision = (version in Windows).value) + }, + projectID in Rpm := { + val m = moduleID.value + m.copy(revision = (version in Rpm).value) + }, + projectID in Universal := { + val m = moduleID.value + m.copy(revision = (version in Universal).value) + } + ) + +lazy val integrationTest = (project in file("integration-test")) + .settings( + name := "integration-test", + scalaVersion := scala212, + libraryDependencies ++= Seq( + "io.monix" %% "minitest" % "2.3.2" % Test, + "com.eed3si9n.expecty" %% "expecty" % "0.11.0" % Test, + "org.scala-sbt" %% "io" % "1.3.1" % Test + ), + testFrameworks += new TestFramework("minitest.runner.Framework") + ) + +def downloadUrlForVersion(v: String) = (v split "[^\\d]" flatMap (i => catching(classOf[Exception]) opt (i.toInt))) match { + case Array(0, 11, 3, _*) => "https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.11.3-2/sbt-launch.jar" + case Array(0, 11, x, _*) if x >= 3 => "https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/"+v+"/sbt-launch.jar" + case Array(0, y, _*) if y >= 12 => "https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/"+v+"/sbt-launch.jar" + case Array(1, _, _*) if v contains ("-20") => "https://repo.scala-sbt.org/scalasbt/maven-snapshots/org/scala-sbt/sbt-launch/"+v+"/sbt-launch.jar" + case _ => "https://repo1.maven.org/maven2/org/scala-sbt/sbt-launch/"+v+"/sbt-launch-"+v+".jar" +} + +def makePublishToForConfig(config: Configuration) = { + val v = sbtVersionToRelease + + // Add the publish to and ensure global resolvers has the resolver we just configured. + inConfig(config)(Seq( + name := "sbt", + bintrayOrganization := { + // offline installation exceeds 50MB file limit for OSS organization + if (sbtOfflineInstall) Some("sbt") + else Some("sbt") + }, + bintrayRepository := bintrayTripple.value._1, + bintrayRepo := Bintray.cachedRepo(bintrayEnsureCredentials.value, + bintrayOrganization.value, + bintrayRepository.value), + bintrayPackage := "sbt", + + bintrayDebianUrl := s"https://api.bintray.com/content/${bintrayOrganization.value.get}/debian/", + bintrayDebianExperimentalUrl := s"https://api.bintray.com/content/${bintrayOrganization.value.get}/debian-experimental/", + bintrayRpmUrl := s"https://api.bintray.com/content/${bintrayOrganization.value.get}/rpm/", + bintrayRpmExperimentalUrl := s"https://api.bintray.com/content/${bintrayOrganization.value.get}/rpm-experimental/", + bintrayGenericPackagesUrl := s"https://api.bintray.com/content/${bintrayOrganization.value.get}/native-packages/", + bintrayTripple := { + config.name match { + case Debian.name if isExperimental => ("debian-experimental", bintrayDebianExperimentalUrl.value, bintrayLinuxPattern) + case Debian.name => ("debian", bintrayDebianUrl.value, bintrayLinuxPattern) + case Rpm.name if isExperimental => ("rpm-experimental", bintrayRpmExperimentalUrl.value, bintrayLinuxPattern) + case Rpm.name => ("rpm", bintrayRpmUrl.value, bintrayLinuxPattern) + case _ => ("native-packages", bintrayGenericPackagesUrl.value, bintrayGenericPattern) + } + }, + publishTo := { + val (id, url, pattern) = bintrayTripple.value + val resolver = Resolver.url(id, new URL(url))(Patterns(pattern)) + Some(resolver) + }, + bintrayReleaseAllStaged := bintrayRelease(bintrayRepo.value, bintrayPackage.value, version.value, sLog.value) + // Uncomment to release right after publishing + // publish <<= (publish, bintrayRepo, bintrayPackage, version, sLog) apply { (publish, bintrayRepo, bintrayPackage, version, sLog) => + // for { + // pub <- publish + // repo <- bintrayRepo + // } yield bintrayRelease(repo, bintrayPackage, version, sLog) + // } + )) ++ Seq( + resolvers ++= ((publishTo in config) apply (_.toSeq)).value + ) +} + +def publishToSettings = + Seq[Configuration](Debian, Universal, Windows, Rpm) flatMap makePublishToForConfig + +def bintrayRelease(repo: BintrayRepo, pkg: String, version: String, log: Logger): Unit = + repo.release(pkg, version, log) + +def downloadUrl(uri: URI, out: File): Unit = + { + import dispatch.classic._ + if(!out.exists) { + IO.touch(out) + val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(out)) + try Http(url(uri.toString) >>> writer) + finally writer.close() + } + } + +def colonName(m: ModuleID): String = s"${m.organization}:${m.name}:${m.revision}" + +lazy val dist = (project in file("dist")) + .enablePlugins(ExportRepoPlugin) + .settings( + name := "dist", + scalaVersion := { + if (sbtVersionToRelease startsWith "0.13.") scala210 + else scala212 + }, + libraryDependencies ++= Seq(sbtActual, jansi, scala212Compiler, scala212Jline, scala212Xml) ++ sbt013ExtraDeps, + exportRepo := { + val old = exportRepo.value + sbtVersionToRelease match { + case v if v.startsWith("1.") => + sys.error("sbt 1.x should use coursier") + case v if v.startsWith("0.13.") => + val outbase = exportRepoDirectory.value / "org.scala-sbt" / "compiler-interface" / v + val uribase = s"https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/compiler-interface/$v/" + downloadUrl(uri(uribase + "ivys/ivy.xml"), outbase / "ivys" / "ivy.xml") + downloadUrl(uri(uribase + "jars/compiler-interface.jar"), outbase / "jars" / "compiler-interface.jar") + downloadUrl(uri(uribase + "srcs/compiler-interface-sources.jar"), outbase / "srcs" / "compiler-interface-sources.jar") + case _ => + } + old + }, + exportRepoDirectory := target.value / "lib" / "local-preloaded", + exportRepoCsrDirectory := exportRepoDirectory.value, + exportRepoUsingCoursier := { + val outDirectory = exportRepoCsrDirectory.value + val csr = + if (isWindows) (baseDirectory in LocalRootProject).value / "bin" / "coursier.bat" + else (baseDirectory in LocalRootProject).value / "bin" / "coursier" + val cache = target.value / "coursier" + IO.delete(cache) + val v = sbtVersionToRelease + s"$csr fetch --cache $cache org.scala-sbt:sbt:$v".! + s"$csr fetch --cache $cache ${colonName(jansi)}".! + s"$csr fetch --cache $cache ${colonName(scala212Compiler)}".! + s"$csr fetch --cache $cache ${colonName(scala212Xml)}".! + val mavenCache = cache / "https" / "repo1.maven.org" / "maven2" + val compilerBridgeVer = IO.listFiles(mavenCache / "org" / "scala-sbt" / "compiler-bridge_2.12", DirectoryFilter).toList.headOption + compilerBridgeVer match { + case Some(bridgeDir) => + val bridgeVer = bridgeDir.getName + s"$csr fetch --cache $cache --sources org.scala-sbt:compiler-bridge_2.10:$bridgeVer".! + s"$csr fetch --cache $cache --sources org.scala-sbt:compiler-bridge_2.11:$bridgeVer".! + s"$csr fetch --cache $cache --sources org.scala-sbt:compiler-bridge_2.12:$bridgeVer".! + s"$csr fetch --cache $cache --sources org.scala-sbt:compiler-bridge_2.13:$bridgeVer".! + case _ => + sys.error("bridge not found") + } + IO.copyDirectory(mavenCache, outDirectory, true, true) + outDirectory + }, + conflictWarning := ConflictWarning.disable, + publish := (), + publishLocal := (), + resolvers += Resolver.typesafeIvyRepo("releases") + ) diff --git a/launcher-package/citest/Hello.scala b/launcher-package/citest/Hello.scala new file mode 100644 index 000000000..b6b4d95ba --- /dev/null +++ b/launcher-package/citest/Hello.scala @@ -0,0 +1,5 @@ +package foo + +object Hello extends App { + println("hello") +} diff --git a/launcher-package/citest/build.sbt b/launcher-package/citest/build.sbt new file mode 100644 index 000000000..cd4f0ab9f --- /dev/null +++ b/launcher-package/citest/build.sbt @@ -0,0 +1,22 @@ +lazy val check = taskKey[Unit]("") +lazy val check2 = taskKey[Unit]("") + +lazy val root = (project in file(".")) + .settings( + scalaVersion := "2.12.4", + name := "Hello", + libraryDependencies += "com.eed3si9n.verify" %% "verify" % "0.2.0" % Test, + testFrameworks += new TestFramework("verify.runner.Framework"), + check := { + val xs = IO.readLines(file("output.txt")).toVector + + println(xs) + assert(xs(0) contains "welcome to sbt") + assert(xs(1) contains "loading project definition") + assert(xs(2) contains "loading settings") + + val ys = IO.readLines(file("err.txt")).toVector.distinct + + assert(ys.isEmpty, s"there's an stderr: $ys") + } + ) diff --git a/launcher-package/citest/project/build.properties b/launcher-package/citest/project/build.properties new file mode 100644 index 000000000..0837f7a13 --- /dev/null +++ b/launcher-package/citest/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.3.13 diff --git a/launcher-package/citest/src/test/scala/HelloTest.scala b/launcher-package/citest/src/test/scala/HelloTest.scala new file mode 100644 index 000000000..863715d84 --- /dev/null +++ b/launcher-package/citest/src/test/scala/HelloTest.scala @@ -0,0 +1,7 @@ +import verify._ + +object HelloTest extends BasicTestSuite { + test("addition") { + assert(2 == 1 + 1) + } +} diff --git a/launcher-package/citest/test.bat b/launcher-package/citest/test.bat new file mode 100755 index 000000000..c35ddae49 --- /dev/null +++ b/launcher-package/citest/test.bat @@ -0,0 +1,31 @@ +@echo on + +cd "%~dp0" + +mkdir freshly-baked +unzip ..\target\universal\sbt.zip -d freshly-baked + +SETLOCAL + +"freshly-baked\sbt\bin\sbt" about + +SET JAVA_HOME=C:\jdk11 +SET PATH=C:\jdk11\bin;%PATH% +SET SBT_OPTS=-Xmx4g -Dfile.encoding=UTF8 + +"freshly-baked\sbt\bin\sbt" -Dsbt.no.format=true about + +"freshly-baked\sbt\bin\sbt" -Dsbt.no.format=true about 1> output.txt 2> err.txt + +"freshly-baked\sbt\bin\sbt" -Dsbt.no.format=true check + +"freshly-baked\sbt\bin\sbt" -Dsbt.no.format=true --numeric-version > numericVersion.txt +"freshly-baked\sbt\bin\sbt" -Dsbt.no.format=true checkNumericVersion + +"freshly-baked\sbt\bin\sbt" -Dsbt.no.format=true --script-version > scriptVersion.txt +"freshly-baked\sbt\bin\sbt" -Dsbt.no.format=true checkScriptVersion + +"freshly-baked\sbt\bin\sbt" -Dsbt.no.format=true --version > version.txt +"freshly-baked\sbt\bin\sbt" -Dsbt.no.format=true checkVersion + +ENDLOCAL diff --git a/launcher-package/citest/test.sh b/launcher-package/citest/test.sh new file mode 100755 index 000000000..3cac58991 --- /dev/null +++ b/launcher-package/citest/test.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# exit when something fails +set -e + +## https://github.com/travis-ci/travis-ci/issues/8408 +unset _JAVA_OPTIONS +unset SBT_OPTS + +java -version +## end of Java switching + +mkdir -p freshly-baked +unzip -qo ../target/universal/sbt.zip -d ./freshly-baked + +./freshly-baked/sbt/bin/sbt -Dsbt.no.format=true about +./freshly-baked/sbt/bin/sbt -Dsbt.no.format=true about 1> output.txt 2> err.txt +./freshly-baked/sbt/bin/sbt check + +./freshly-baked/sbt/bin/sbt about run -v + +./freshly-baked/sbt/bin/sbt about run + +fail() { + echo "$@" >&2 + exit 1 +} + +env HOME=./target/home1 ./freshly-baked/sbt/bin/sbt about +test -d ./target/home1/.sbt/preloaded/org/scala-sbt || fail "expected to find preloaded in ./target/home1/.sbt" + +env HOME=./target/home2 ./freshly-baked/sbt/bin/sbt -sbt-dir ./target/home2/alternate-sbt about +test -d ./target/home2/alternate-sbt/preloaded/org/scala-sbt || fail "expected to find preloaded in ./target/home2/alternate-sbt" + +env HOME=./target/home3 ./freshly-baked/sbt/bin/sbt -J-Dsbt.preloaded=./target/home3/alternate-preloaded about +test -d ./target/home3/alternate-preloaded/org/scala-sbt || fail "expected to find preloaded in ./target/home3/alternate-preloaded" + +env HOME=./target/home4 ./freshly-baked/sbt/bin/sbt -J-Dsbt.global.base=./target/home4/global-base about +test -d ./target/home4/global-base/preloaded/org/scala-sbt || fail "expected to find preloaded in ./target/home4/global-base" diff --git a/launcher-package/citest/test3/.jvmopts b/launcher-package/citest/test3/.jvmopts new file mode 100644 index 000000000..ecacefc9a --- /dev/null +++ b/launcher-package/citest/test3/.jvmopts @@ -0,0 +1,3 @@ +-XX:+CMSClassUnloadingEnabled +#-XX:ReservedCodeCacheSize=192m +#-Duser.timezone=GMT \ No newline at end of file diff --git a/launcher-package/citest/test3/build.sbt b/launcher-package/citest/test3/build.sbt new file mode 100755 index 000000000..6cba9926c --- /dev/null +++ b/launcher-package/citest/test3/build.sbt @@ -0,0 +1,52 @@ +lazy val check = taskKey[Unit]("") +lazy val checkNumericVersion = taskKey[Unit]("") +lazy val checkScriptVersion = taskKey[Unit]("") +lazy val checkVersion = taskKey[Unit]("") + +// 1.3.0, 1.3.0-M4 +lazy val versionRegEx = "\\d(\\.\\d+){2}(-\\w+)?" + +lazy val root = (project in file(".")) + .settings( + scalaVersion := "2.12.4", + name := "Hello", + check := { + val xs = IO.readLines(file("output.txt")).toVector + + println(xs) + + assert(xs(0) startsWith "[info] Loading project definition") + assert(xs(1) startsWith "[info] Loading settings from build.sbt") + assert(xs(2) startsWith "[info] Set current project to Hello") + assert(xs(3) startsWith "[info] This is sbt") + assert(xs(4) startsWith "[info] The current project") + assert(xs(5) startsWith "[info] The current project is built against Scala 2.12.4") + + val ys = IO.readLines(file("err.txt")).toVector.distinct + + assert(ys.size == 1, s"ys has more than one item: $ys") + assert(ys(0) startsWith "Java HotSpot(TM) 64-Bit Server VM warning") + }, + checkNumericVersion = { + val xs = IO.readLines(file("numericVersion.txt")).toVector + val expectedVersion = "^"+versionRegEx+"$" + + assert(xs(0).matches(expectedVersion)) + }, + checkScriptVersion = { + val xs = IO.readLines(file("scriptVersion.txt")).toVector + val expectedVersion = "^"+versionRegEx+"$" + + assert(xs(0).matches(expectedVersion)) + }, + checkVersion = { + val out = IO.readLines(file("version.txt")).toVector.mkString("\n") + + val expectedVersion = + s"""|(?m)^sbt version in this project: $versionRegEx + |sbt script version: $versionRegEx$$ + |""".stripMargin.trim.replace("\n", "\\n") + + assert(out.matches(expectedVersion)) + } + ) diff --git a/launcher-package/citest/test3/test3.bat b/launcher-package/citest/test3/test3.bat new file mode 100644 index 000000000..38b1bde4f --- /dev/null +++ b/launcher-package/citest/test3/test3.bat @@ -0,0 +1,17 @@ +@echo on + +SETLOCAL + +SET JAVA_HOME=C:\jdk11 +SET PATH=C:\jdk11\bin;%PATH% +SET SBT_OPTS=-Xmx4g -Dfile.encoding=UTF8 + +SET BASE_DIR=%CD% +SET SCRIPT_DIR=%~dp0 + +CD %SCRIPT_DIR% +"%BASE_DIR%freshly-baked\sbt\bin\sbt" about 1> output.txt 2> err.txt +"%BASE_DIR%freshly-baked\sbt\bin\sbt" check +CD %BASE_DIR% + +ENDLOCAL diff --git a/launcher-package/integration-test/bin/java b/launcher-package/integration-test/bin/java new file mode 100755 index 000000000..dc584d35b --- /dev/null +++ b/launcher-package/integration-test/bin/java @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 + +import sys + +if '--version' in sys.argv or '-version' in sys.argv: + print('openjdk version "1.8.0_212"') +else: + for arg in sys.argv[1:]: + print(repr(arg)[1:-1]) diff --git a/launcher-package/integration-test/bin/java.cmd b/launcher-package/integration-test/bin/java.cmd new file mode 100644 index 000000000..4b214dbf6 --- /dev/null +++ b/launcher-package/integration-test/bin/java.cmd @@ -0,0 +1 @@ +@python "%~dp0%~n0" %* diff --git a/launcher-package/integration-test/src/test/scala/InheritInput.scala b/launcher-package/integration-test/src/test/scala/InheritInput.scala new file mode 100644 index 000000000..d8304926d --- /dev/null +++ b/launcher-package/integration-test/src/test/scala/InheritInput.scala @@ -0,0 +1,17 @@ +package sbt.internal + +import java.lang.{ ProcessBuilder => JProcessBuilder } + +private[sbt] object InheritInput { + def apply(p: JProcessBuilder): Boolean = (redirectInput, inherit) match { + case (Some(m), Some(f)) => + m.invoke(p, f); true + case _ => false + } + + private[this] val pbClass = Class.forName("java.lang.ProcessBuilder") + private[this] val redirectClass = pbClass.getClasses find (_.getSimpleName == "Redirect") + + private[this] val redirectInput = redirectClass map (pbClass.getMethod("redirectInput", _)) + private[this] val inherit = redirectClass map (_ getField "INHERIT" get null) +} diff --git a/launcher-package/integration-test/src/test/scala/PowerAssertions.scala b/launcher-package/integration-test/src/test/scala/PowerAssertions.scala new file mode 100644 index 000000000..eb3671b76 --- /dev/null +++ b/launcher-package/integration-test/src/test/scala/PowerAssertions.scala @@ -0,0 +1,7 @@ +package example.test + +import com.eed3si9n.expecty.Expecty + +trait PowerAssertions { + lazy val assert: Expecty = new Expecty() +} diff --git a/launcher-package/integration-test/src/test/scala/Process.scala b/launcher-package/integration-test/src/test/scala/Process.scala new file mode 100644 index 000000000..40798e209 --- /dev/null +++ b/launcher-package/integration-test/src/test/scala/Process.scala @@ -0,0 +1,216 @@ +package sbt.internal + +import java.lang.{ Process => JProcess, ProcessBuilder => JProcessBuilder } +import java.io.{ Closeable, File, IOException } +import java.io.{ BufferedReader, InputStream, InputStreamReader, OutputStream, PipedInputStream, PipedOutputStream } +import java.net.URL + +trait ProcessExtra { + import Process._ + implicit def builderToProcess(builder: JProcessBuilder): ProcessBuilder = apply(builder) + implicit def fileToProcess(file: File): FilePartialBuilder = apply(file) + implicit def urlToProcess(url: URL): URLPartialBuilder = apply(url) + + implicit def buildersToProcess[T](builders: Seq[T])(implicit convert: T => SourcePartialBuilder): Seq[SourcePartialBuilder] = applySeq(builders) + + implicit def stringToProcess(command: String): ProcessBuilder = apply(command) + implicit def stringSeqToProcess(command: Seq[String]): ProcessBuilder = apply(command) +} + +/** Methods for constructing simple commands that can then be combined. */ +object Process extends ProcessExtra { + def apply(command: String): ProcessBuilder = apply(command, None) + + def apply(command: Seq[String]): ProcessBuilder = apply(command.toArray, None) + + def apply(command: String, arguments: Seq[String]): ProcessBuilder = apply(command :: arguments.toList, None) + /** create ProcessBuilder with working dir set to File and extra environment variables */ + def apply(command: String, cwd: File, extraEnv: (String, String)*): ProcessBuilder = + apply(command, Some(cwd), extraEnv: _*) + /** create ProcessBuilder with working dir set to File and extra environment variables */ + def apply(command: Seq[String], cwd: File, extraEnv: (String, String)*): ProcessBuilder = + apply(command, Some(cwd), extraEnv: _*) + /** create ProcessBuilder with working dir optionally set to File and extra environment variables */ + def apply(command: String, cwd: Option[File], extraEnv: (String, String)*): ProcessBuilder = { + apply(command.split("""\s+"""), cwd, extraEnv: _*) + // not smart to use this on windows, because CommandParser uses \ to escape ". + /*CommandParser.parse(command) match { + case Left(errorMsg) => error(errorMsg) + case Right((cmd, args)) => apply(cmd :: args, cwd, extraEnv : _*) + }*/ + } + /** create ProcessBuilder with working dir optionally set to File and extra environment variables */ + def apply(command: Seq[String], cwd: Option[File], extraEnv: (String, String)*): ProcessBuilder = { + val jpb = new JProcessBuilder(command.toArray: _*) + cwd.foreach(jpb directory _) + extraEnv.foreach { case (k, v) => jpb.environment.put(k, v) } + apply(jpb) + } + def apply(builder: JProcessBuilder): ProcessBuilder = new SimpleProcessBuilder(builder) + def apply(file: File): FilePartialBuilder = new FileBuilder(file) + def apply(url: URL): URLPartialBuilder = new URLBuilder(url) + + def applySeq[T](builders: Seq[T])(implicit convert: T => SourcePartialBuilder): Seq[SourcePartialBuilder] = builders.map(convert) + + def apply(value: Boolean): ProcessBuilder = apply(value.toString, if (value) 0 else 1) + def apply(name: String, exitValue: => Int): ProcessBuilder = new DummyProcessBuilder(name, exitValue) + + def cat(file: SourcePartialBuilder, files: SourcePartialBuilder*): ProcessBuilder = cat(file :: files.toList) + def cat(files: Seq[SourcePartialBuilder]): ProcessBuilder = + { + require(files.nonEmpty) + files.map(_.cat).reduceLeft(_ #&& _) + } +} + +trait SourcePartialBuilder extends NotNull { + /** Writes the output stream of this process to the given file. */ + def #>(f: File): ProcessBuilder = toFile(f, false) + /** Appends the output stream of this process to the given file. */ + def #>>(f: File): ProcessBuilder = toFile(f, true) + /** + * Writes the output stream of this process to the given OutputStream. The + * argument is call-by-name, so the stream is recreated, written, and closed each + * time this process is executed. + */ + def #>(out: => OutputStream): ProcessBuilder = #>(new OutputStreamBuilder(out)) + def #>(b: ProcessBuilder): ProcessBuilder = new PipedProcessBuilder(toSource, b, false, ExitCodes.firstIfNonzero) + private def toFile(f: File, append: Boolean) = #>(new FileOutput(f, append)) + def cat = toSource + protected def toSource: ProcessBuilder +} +trait SinkPartialBuilder extends NotNull { + /** Reads the given file into the input stream of this process. */ + def #<(f: File): ProcessBuilder = #<(new FileInput(f)) + /** Reads the given URL into the input stream of this process. */ + def #<(f: URL): ProcessBuilder = #<(new URLInput(f)) + /** + * Reads the given InputStream into the input stream of this process. The + * argument is call-by-name, so the stream is recreated, read, and closed each + * time this process is executed. + */ + def #<(in: => InputStream): ProcessBuilder = #<(new InputStreamBuilder(in)) + def #<(b: ProcessBuilder): ProcessBuilder = new PipedProcessBuilder(b, toSink, false, ExitCodes.firstIfNonzero) + protected def toSink: ProcessBuilder +} + +trait URLPartialBuilder extends SourcePartialBuilder +trait FilePartialBuilder extends SinkPartialBuilder with SourcePartialBuilder { + def #<<(f: File): ProcessBuilder + def #<<(u: URL): ProcessBuilder + def #<<(i: => InputStream): ProcessBuilder + def #<<(p: ProcessBuilder): ProcessBuilder +} + +/** + * Represents a process that is running or has finished running. + * It may be a compound process with several underlying native processes (such as 'a #&& b`). + */ +trait Process extends NotNull { + /** Blocks until this process exits and returns the exit code.*/ + def exitValue(): Int + /** Destroys this process. */ + def destroy(): Unit +} +/** Represents a runnable process. */ +trait ProcessBuilder extends SourcePartialBuilder with SinkPartialBuilder { + /** + * Starts the process represented by this builder, blocks until it exits, and returns the output as a String. Standard error is + * sent to the console. If the exit code is non-zero, an exception is thrown. + */ + def !! : String + /** + * Starts the process represented by this builder, blocks until it exits, and returns the output as a String. Standard error is + * sent to the provided ProcessLogger. If the exit code is non-zero, an exception is thrown. + */ + def !!(log: ProcessLogger): String + /** + * Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available + * but the process has not completed. Standard error is sent to the console. If the process exits with a non-zero value, + * the Stream will provide all lines up to termination and then throw an exception. + */ + def lines: Stream[String] + /** + * Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available + * but the process has not completed. Standard error is sent to the provided ProcessLogger. If the process exits with a non-zero value, + * the Stream will provide all lines up to termination but will not throw an exception. + */ + def lines(log: ProcessLogger): Stream[String] + /** + * Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available + * but the process has not completed. Standard error is sent to the console. If the process exits with a non-zero value, + * the Stream will provide all lines up to termination but will not throw an exception. + */ + def lines_! : Stream[String] + /** + * Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available + * but the process has not completed. Standard error is sent to the provided ProcessLogger. If the process exits with a non-zero value, + * the Stream will provide all lines up to termination but will not throw an exception. + */ + def lines_!(log: ProcessLogger): Stream[String] + /** + * Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are + * sent to the console. + */ + def ! : Int + /** + * Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are + * sent to the given ProcessLogger. + */ + def !(log: ProcessLogger): Int + /** + * Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are + * sent to the console. The newly started process reads from standard input of the current process. + */ + def !< : Int + /** + * Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are + * sent to the given ProcessLogger. The newly started process reads from standard input of the current process. + */ + def !<(log: ProcessLogger): Int + /** Starts the process represented by this builder. Standard output and error are sent to the console.*/ + def run(): Process + /** Starts the process represented by this builder. Standard output and error are sent to the given ProcessLogger.*/ + def run(log: ProcessLogger): Process + /** Starts the process represented by this builder. I/O is handled by the given ProcessIO instance.*/ + def run(io: ProcessIO): Process + /** + * Starts the process represented by this builder. Standard output and error are sent to the console. + * The newly started process reads from standard input of the current process if `connectInput` is true. + */ + def run(connectInput: Boolean): Process + /** + * Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are + * sent to the given ProcessLogger. + * The newly started process reads from standard input of the current process if `connectInput` is true. + */ + def run(log: ProcessLogger, connectInput: Boolean): Process + + def runBuffered(log: ProcessLogger, connectInput: Boolean): Process + + /** Constructs a command that runs this command first and then `other` if this command succeeds.*/ + def #&&(other: ProcessBuilder): ProcessBuilder + /** Constructs a command that runs this command first and then `other` if this command does not succeed.*/ + def #||(other: ProcessBuilder): ProcessBuilder + /** + * Constructs a command that will run this command and pipes the output to `other`. + * `other` must be a simple command. + * The exit code will be that of `other` regardless of whether this command succeeds. + */ + def #|(other: ProcessBuilder): ProcessBuilder + /** Constructs a command that will run this command and then `other`. The exit code will be the exit code of `other`.*/ + def ###(other: ProcessBuilder): ProcessBuilder + + def canPipeTo: Boolean +} +/** Each method will be called in a separate thread.*/ +final class ProcessIO(val writeInput: OutputStream => Unit, val processOutput: InputStream => Unit, val processError: InputStream => Unit, val inheritInput: JProcessBuilder => Boolean) extends NotNull { + def withOutput(process: InputStream => Unit): ProcessIO = new ProcessIO(writeInput, process, processError, inheritInput) + def withError(process: InputStream => Unit): ProcessIO = new ProcessIO(writeInput, processOutput, process, inheritInput) + def withInput(write: OutputStream => Unit): ProcessIO = new ProcessIO(write, processOutput, processError, inheritInput) +} +trait ProcessLogger { + def info(s: => String): Unit + def error(s: => String): Unit + def buffer[T](f: => T): T +} diff --git a/launcher-package/integration-test/src/test/scala/ProcessImpl.scala b/launcher-package/integration-test/src/test/scala/ProcessImpl.scala new file mode 100644 index 000000000..7c8e4bc01 --- /dev/null +++ b/launcher-package/integration-test/src/test/scala/ProcessImpl.scala @@ -0,0 +1,433 @@ +package sbt.internal + +import java.lang.{ Process => JProcess, ProcessBuilder => JProcessBuilder } +import java.io.{ BufferedReader, Closeable, InputStream, InputStreamReader, IOException, OutputStream, PrintStream } +import java.io.{ FilterInputStream, FilterOutputStream, PipedInputStream, PipedOutputStream } +import java.io.{ File, FileInputStream, FileOutputStream } +import java.net.URL + +/** Runs provided code in a new Thread and returns the Thread instance. */ +private object Spawn { + def apply(f: => Unit): Thread = apply(f, false) + def apply(f: => Unit, daemon: Boolean): Thread = + { + val thread = new Thread() { override def run() = { f } } + thread.setDaemon(daemon) + thread.start() + thread + } +} +private object Future { + def apply[T](f: => T): () => T = + { + val result = new SyncVar[Either[Throwable, T]] + def run(): Unit = + try { result.set(Right(f)) } + catch { case e: Exception => result.set(Left(e)) } + Spawn(run) + () => + result.get match { + case Right(value) => value + case Left(exception) => throw exception + } + } +} + +object BasicIO { + def apply(buffer: StringBuffer, log: Option[ProcessLogger], withIn: Boolean) = new ProcessIO(input(withIn), processFully(buffer), getErr(log), inheritInput(withIn)) + def apply(log: ProcessLogger, withIn: Boolean) = new ProcessIO(input(withIn), processInfoFully(log), processErrFully(log), inheritInput(withIn)) + + def getErr(log: Option[ProcessLogger]) = log match { case Some(lg) => processErrFully(lg); case None => toStdErr } + + private def processErrFully(log: ProcessLogger) = processFully(s => log.error(s)) + private def processInfoFully(log: ProcessLogger) = processFully(s => log.info(s)) + + def closeOut = (_: OutputStream).close() + final val BufferSize = 8192 + final val Newline = System.getProperty("line.separator") + + def close(c: java.io.Closeable) = try { c.close() } catch { case _: java.io.IOException => () } + def processFully(buffer: Appendable): InputStream => Unit = processFully(appendLine(buffer)) + def processFully(processLine: String => Unit): InputStream => Unit = + in => + { + val reader = new BufferedReader(new InputStreamReader(in)) + processLinesFully(processLine)(reader.readLine) + reader.close() + } + def processLinesFully(processLine: String => Unit)(readLine: () => String): Unit = { + def readFully(): Unit = { + val line = readLine() + if (line != null) { + processLine(line) + readFully() + } + } + readFully() + } + def connectToIn(o: OutputStream): Unit = transferFully(Uncloseable protect System.in, o) + def input(connect: Boolean): OutputStream => Unit = if (connect) connectToIn else closeOut + def standard(connectInput: Boolean): ProcessIO = standard(input(connectInput), inheritInput(connectInput)) + def standard(in: OutputStream => Unit, inheritIn: JProcessBuilder => Boolean): ProcessIO = new ProcessIO(in, toStdOut, toStdErr, inheritIn) + + def toStdErr = (in: InputStream) => transferFully(in, System.err) + def toStdOut = (in: InputStream) => transferFully(in, System.out) + + def transferFully(in: InputStream, out: OutputStream): Unit = + try { transferFullyImpl(in, out) } + catch { case _: InterruptedException => () } + + private[this] def appendLine(buffer: Appendable): String => Unit = + line => + { + buffer.append(line) + buffer.append(Newline) + } + + private[this] def transferFullyImpl(in: InputStream, out: OutputStream): Unit = { + val continueCount = 1 //if(in.isInstanceOf[PipedInputStream]) 1 else 0 + val buffer = new Array[Byte](BufferSize) + def read(): Unit = { + val byteCount = in.read(buffer) + if (byteCount >= continueCount) { + out.write(buffer, 0, byteCount) + out.flush() + read + } + } + read + in.close() + } + + def inheritInput(connect: Boolean) = { p: JProcessBuilder => if (connect) InheritInput(p) else false } +} +private[sbt] object ExitCodes { + def ignoreFirst: (Int, Int) => Int = (a, b) => b + def firstIfNonzero: (Int, Int) => Int = (a, b) => if (a != 0) a else b +} + +private[sbt] abstract class AbstractProcessBuilder extends ProcessBuilder with SinkPartialBuilder with SourcePartialBuilder { + def #&&(other: ProcessBuilder): ProcessBuilder = new AndProcessBuilder(this, other) + def #||(other: ProcessBuilder): ProcessBuilder = new OrProcessBuilder(this, other) + def #|(other: ProcessBuilder): ProcessBuilder = + { + require(other.canPipeTo, "Piping to multiple processes is not supported.") + new PipedProcessBuilder(this, other, false, exitCode = ExitCodes.ignoreFirst) + } + def ###(other: ProcessBuilder): ProcessBuilder = new SequenceProcessBuilder(this, other) + + protected def toSource = this + protected def toSink = this + + def run(): Process = run(false) + def run(connectInput: Boolean): Process = run(BasicIO.standard(connectInput)) + def run(log: ProcessLogger): Process = run(log, false) + def run(log: ProcessLogger, connectInput: Boolean): Process = run(BasicIO(log, connectInput)) + + private[this] def getString(log: Option[ProcessLogger], withIn: Boolean): String = + { + val buffer = new StringBuffer + val code = this ! BasicIO(buffer, log, withIn) + if (code == 0) buffer.toString else sys.error("Nonzero exit value: " + code) + } + def !! = getString(None, false) + def !!(log: ProcessLogger) = getString(Some(log), false) + def !!< = getString(None, true) + def !!<(log: ProcessLogger) = getString(Some(log), true) + + def lines: Stream[String] = lines(false, true, None) + def lines(log: ProcessLogger): Stream[String] = lines(false, true, Some(log)) + def lines_! : Stream[String] = lines(false, false, None) + def lines_!(log: ProcessLogger): Stream[String] = lines(false, false, Some(log)) + + private[this] def lines(withInput: Boolean, nonZeroException: Boolean, log: Option[ProcessLogger]): Stream[String] = + { + val streamed = Streamed[String](nonZeroException) + val process = run(new ProcessIO(BasicIO.input(withInput), BasicIO.processFully(streamed.process), BasicIO.getErr(log), BasicIO.inheritInput(withInput))) + Spawn { streamed.done(process.exitValue()) } + streamed.stream() + } + + def ! = run(false).exitValue() + def !< = run(true).exitValue() + def !(log: ProcessLogger) = runBuffered(log, false).exitValue() + def !<(log: ProcessLogger) = runBuffered(log, true).exitValue() + def runBuffered(log: ProcessLogger, connectInput: Boolean) = + log.buffer { run(log, connectInput) } + def !(io: ProcessIO) = run(io).exitValue() + + def canPipeTo = false +} + +private[sbt] class URLBuilder(url: URL) extends URLPartialBuilder with SourcePartialBuilder { + protected def toSource = new URLInput(url) +} +private[sbt] class FileBuilder(base: File) extends FilePartialBuilder with SinkPartialBuilder with SourcePartialBuilder { + protected def toSource = new FileInput(base) + protected def toSink = new FileOutput(base, false) + def #<<(f: File): ProcessBuilder = #<<(new FileInput(f)) + def #<<(u: URL): ProcessBuilder = #<<(new URLInput(u)) + def #<<(s: => InputStream): ProcessBuilder = #<<(new InputStreamBuilder(s)) + def #<<(b: ProcessBuilder): ProcessBuilder = new PipedProcessBuilder(b, new FileOutput(base, true), false, ExitCodes.firstIfNonzero) +} + +private abstract class BasicBuilder extends AbstractProcessBuilder { + protected[this] def checkNotThis(a: ProcessBuilder) = require(a != this, "Compound process '" + a + "' cannot contain itself.") + final def run(io: ProcessIO): Process = + { + val p = createProcess(io) + p.start() + p + } + protected[this] def createProcess(io: ProcessIO): BasicProcess +} +private abstract class BasicProcess extends Process { + def start(): Unit +} + +private abstract class CompoundProcess extends BasicProcess { + def destroy(): Unit = destroyer() + def exitValue() = getExitValue().getOrElse(sys.error("No exit code: process destroyed.")) + + def start() = getExitValue + + protected lazy val (getExitValue, destroyer) = + { + val code = new SyncVar[Option[Int]]() + code.set(None) + val thread = Spawn(code.set(runAndExitValue())) + + ( + Future { thread.join(); code.get }, + () => thread.interrupt() + ) + } + + /** Start and block until the exit value is available and then return it in Some. Return None if destroyed (use 'run')*/ + protected[this] def runAndExitValue(): Option[Int] + + protected[this] def runInterruptible[T](action: => T)(destroyImpl: => Unit): Option[T] = + { + try { Some(action) } + catch { case _: InterruptedException => destroyImpl; None } + } +} + +private abstract class SequentialProcessBuilder(a: ProcessBuilder, b: ProcessBuilder, operatorString: String) extends BasicBuilder { + checkNotThis(a) + checkNotThis(b) + override def toString = " ( " + a + " " + operatorString + " " + b + " ) " +} +private class PipedProcessBuilder(first: ProcessBuilder, second: ProcessBuilder, toError: Boolean, exitCode: (Int, Int) => Int) extends SequentialProcessBuilder(first, second, if (toError) "#|!" else "#|") { + override def createProcess(io: ProcessIO) = new PipedProcesses(first, second, io, toError, exitCode) +} +private class AndProcessBuilder(first: ProcessBuilder, second: ProcessBuilder) extends SequentialProcessBuilder(first, second, "#&&") { + override def createProcess(io: ProcessIO) = new AndProcess(first, second, io) +} +private class OrProcessBuilder(first: ProcessBuilder, second: ProcessBuilder) extends SequentialProcessBuilder(first, second, "#||") { + override def createProcess(io: ProcessIO) = new OrProcess(first, second, io) +} +private class SequenceProcessBuilder(first: ProcessBuilder, second: ProcessBuilder) extends SequentialProcessBuilder(first, second, "###") { + override def createProcess(io: ProcessIO) = new ProcessSequence(first, second, io) +} + +private class SequentialProcess(a: ProcessBuilder, b: ProcessBuilder, io: ProcessIO, evaluateSecondProcess: Int => Boolean) extends CompoundProcess { + protected[this] override def runAndExitValue() = + { + val first = a.run(io) + runInterruptible(first.exitValue)(first.destroy()) flatMap + { codeA => + if (evaluateSecondProcess(codeA)) { + val second = b.run(io) + runInterruptible(second.exitValue)(second.destroy()) + } else + Some(codeA) + } + } +} +private class AndProcess(a: ProcessBuilder, b: ProcessBuilder, io: ProcessIO) extends SequentialProcess(a, b, io, _ == 0) +private class OrProcess(a: ProcessBuilder, b: ProcessBuilder, io: ProcessIO) extends SequentialProcess(a, b, io, _ != 0) +private class ProcessSequence(a: ProcessBuilder, b: ProcessBuilder, io: ProcessIO) extends SequentialProcess(a, b, io, ignore => true) + +private class PipedProcesses(a: ProcessBuilder, b: ProcessBuilder, defaultIO: ProcessIO, toError: Boolean, exitCode: (Int, Int) => Int) extends CompoundProcess { + protected[this] override def runAndExitValue() = + { + val currentSource = new SyncVar[Option[InputStream]] + val pipeOut = new PipedOutputStream + val source = new PipeSource(currentSource, pipeOut, a.toString) + source.start() + + val pipeIn = new PipedInputStream(pipeOut) + val currentSink = new SyncVar[Option[OutputStream]] + val sink = new PipeSink(pipeIn, currentSink, b.toString) + sink.start() + + def handleOutOrError(fromOutput: InputStream) = currentSource.put(Some(fromOutput)) + + val firstIO = + if (toError) + defaultIO.withError(handleOutOrError) + else + defaultIO.withOutput(handleOutOrError) + val secondIO = defaultIO.withInput(toInput => currentSink.put(Some(toInput))) + + val second = b.run(secondIO) + val first = a.run(firstIO) + try { + runInterruptible { + val firstResult = first.exitValue + currentSource.put(None) + currentSink.put(None) + val secondResult = second.exitValue + exitCode(firstResult, secondResult) + } { + first.destroy() + second.destroy() + } + } finally { + BasicIO.close(pipeIn) + BasicIO.close(pipeOut) + } + } +} +private class PipeSource(currentSource: SyncVar[Option[InputStream]], pipe: PipedOutputStream, label: => String) extends Thread { + final override def run(): Unit = { + currentSource.get match { + case Some(source) => + try { BasicIO.transferFully(source, pipe) } + catch { case e: IOException => println("I/O error " + e.getMessage + " for process: " + label); e.printStackTrace() } + finally { + BasicIO.close(source) + currentSource.unset() + } + run() + case None => + currentSource.unset() + BasicIO.close(pipe) + } + } +} +private class PipeSink(pipe: PipedInputStream, currentSink: SyncVar[Option[OutputStream]], label: => String) extends Thread { + final override def run(): Unit = { + currentSink.get match { + case Some(sink) => + try { BasicIO.transferFully(pipe, sink) } + catch { case e: IOException => println("I/O error " + e.getMessage + " for process: " + label); e.printStackTrace() } + finally { + BasicIO.close(sink) + currentSink.unset() + } + run() + case None => + currentSink.unset() + } + } +} + +private[sbt] class DummyProcessBuilder(override val toString: String, exitValue: => Int) extends AbstractProcessBuilder { + override def run(io: ProcessIO): Process = new DummyProcess(exitValue) + override def canPipeTo = true +} +/** + * A thin wrapper around a java.lang.Process. `ioThreads` are the Threads created to do I/O. + * The implementation of `exitValue` waits until these threads die before returning. + */ +private class DummyProcess(action: => Int) extends Process { + private[this] val exitCode = Future(action) + override def exitValue() = exitCode() + override def destroy(): Unit = () +} +/** Represents a simple command without any redirection or combination. */ +private[sbt] class SimpleProcessBuilder(p: JProcessBuilder) extends AbstractProcessBuilder { + override def run(io: ProcessIO): Process = + { + import io._ + val inherited = inheritInput(p) + val process = p.start() + + // spawn threads that process the output and error streams, and also write input if not inherited. + if (!inherited) + Spawn(writeInput(process.getOutputStream)) + val outThread = Spawn(processOutput(process.getInputStream)) + val errorThread = + if (!p.redirectErrorStream) + Spawn(processError(process.getErrorStream)) :: Nil + else + Nil + new SimpleProcess(process, outThread :: errorThread) + } + override def toString = p.command.toString + override def canPipeTo = true +} + +/** + * A thin wrapper around a java.lang.Process. `outputThreads` are the Threads created to read from the + * output and error streams of the process. + * The implementation of `exitValue` wait for the process to finish and then waits until the threads reading output and error streams die before + * returning. Note that the thread that reads the input stream cannot be interrupted, see https://github.com/sbt/sbt/issues/327 and + * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4514257 + */ +private class SimpleProcess(p: JProcess, outputThreads: List[Thread]) extends Process { + override def exitValue() = + { + try { + p.waitFor() + } catch { + case _: InterruptedException => p.destroy() + } + outputThreads.foreach(_.join()) // this ensures that all output is complete before returning (waitFor does not ensure this) + p.exitValue() + } + override def destroy() = p.destroy() +} + +private[sbt] class FileOutput(file: File, append: Boolean) extends OutputStreamBuilder(new FileOutputStream(file, append), file.getAbsolutePath) +private[sbt] class URLInput(url: URL) extends InputStreamBuilder(url.openStream, url.toString) +private[sbt] class FileInput(file: File) extends InputStreamBuilder(new FileInputStream(file), file.getAbsolutePath) + +import Uncloseable.protect +private[sbt] class OutputStreamBuilder(stream: => OutputStream, label: String) extends ThreadProcessBuilder(label, _.writeInput(protect(stream))) { + def this(stream: => OutputStream) = this(stream, "") +} +private[sbt] class InputStreamBuilder(stream: => InputStream, label: String) extends ThreadProcessBuilder(label, _.processOutput(protect(stream))) { + def this(stream: => InputStream) = this(stream, "") +} + +private[sbt] abstract class ThreadProcessBuilder(override val toString: String, runImpl: ProcessIO => Unit) extends AbstractProcessBuilder { + override def run(io: ProcessIO): Process = + { + val success = new SyncVar[Boolean] + success.put(false) + new ThreadProcess(Spawn { runImpl(io); success.set(true) }, success) + } +} +private[sbt] final class ThreadProcess(thread: Thread, success: SyncVar[Boolean]) extends Process { + override def exitValue() = + { + thread.join() + if (success.get) 0 else 1 + } + override def destroy(): Unit = thread.interrupt() +} + +object Uncloseable { + def apply(in: InputStream): InputStream = new FilterInputStream(in) { override def close(): Unit = () } + def apply(out: OutputStream): OutputStream = new FilterOutputStream(out) { override def close(): Unit = () } + def protect(in: InputStream): InputStream = if (in eq System.in) Uncloseable(in) else in + def protect(out: OutputStream): OutputStream = if ((out eq System.out) || (out eq System.err)) Uncloseable(out) else out +} +private[sbt] object Streamed { + def apply[T](nonzeroException: Boolean): Streamed[T] = + { + val q = new java.util.concurrent.LinkedBlockingQueue[Either[Int, T]] + def next(): Stream[T] = + q.take match { + case Left(0) => Stream.empty + case Left(code) => if (nonzeroException) sys.error("Nonzero exit code: " + code) else Stream.empty + case Right(s) => Stream.cons(s, next) + } + new Streamed((s: T) => q.put(Right(s)), code => q.put(Left(code)), () => next()) + } +} + +private[sbt] final class Streamed[T](val process: T => Unit, val done: Int => Unit, val stream: () => Stream[T]) extends NotNull \ No newline at end of file diff --git a/launcher-package/integration-test/src/test/scala/RunnerTest.scala b/launcher-package/integration-test/src/test/scala/RunnerTest.scala new file mode 100755 index 000000000..721fff23e --- /dev/null +++ b/launcher-package/integration-test/src/test/scala/RunnerTest.scala @@ -0,0 +1,90 @@ +package example.test + +import minitest._ +import scala.sys.process._ +import java.io.File + +object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { + // 1.3.0, 1.3.0-M4 + private val versionRegEx = "\\d(\\.\\d+){2}(-\\w+)?" + + lazy val isWindows: Boolean = sys.props("os.name").toLowerCase(java.util.Locale.ENGLISH).contains("windows") + lazy val sbtScript = + if (isWindows) new File("target/universal/stage/bin/sbt.bat") + else new File("target/universal/stage/bin/sbt") + + def sbtProcess(args: String*) = sbtProcessWithOpts(args: _*)("", "") + def sbtProcessWithOpts(args: String*)(javaOpts: String, sbtOpts: String) = + sbt.internal.Process(Seq(sbtScript.getAbsolutePath) ++ args, new File("citest"), + "JAVA_OPTS" -> javaOpts, + "SBT_OPTS" -> sbtOpts) + + test("sbt runs") { + assert(sbtScript.exists) + val out = sbtProcess("compile", "-v").! + assert(out == 0) + () + } + + test("sbt -V|-version|--version should print sbtVersion") { + val out = sbtProcess("-version").!!.trim + val expectedVersion = + s"""|(?m)^sbt version in this project: $versionRegEx(\\r)? + |sbt script version: $versionRegEx$$ + |""".stripMargin.trim.replace("\n", "\\n") + assert(out.matches(expectedVersion)) + + val out2 = sbtProcess("--version").!!.trim + assert(out2.matches(expectedVersion)) + + val out3 = sbtProcess("-V").!!.trim + assert(out3.matches(expectedVersion)) + () + } + + test("sbt --numeric-version should print sbt script version") { + val out = sbtProcess("--numeric-version").!!.trim + val expectedVersion = "^"+versionRegEx+"$" + assert(out.matches(expectedVersion)) + () + } + + test("sbt --script-version should print sbtVersion") { + val out = sbtProcess("--script-version").!!.trim + val expectedVersion = "^"+versionRegEx+"$" + assert(out.matches(expectedVersion)) + () + } + + test("sbt --sbt-jar should run") { + val out = sbtProcess("compile", "-v", "--sbt-jar", "../target/universal/stage/bin/sbt-launch.jar").!!.linesIterator.toList + assert(out.contains[String]("../target/universal/stage/bin/sbt-launch.jar") || + out.contains[String]("\"../target/universal/stage/bin/sbt-launch.jar\"") + ) + () + } + + test("sbt \"testOnly *\"") { + val out = sbtProcess("testOnly *", "--no-colors", "-v").!!.linesIterator.toList + assert(out.contains[String]("[info] HelloTest")) + () + } + + /* + test("sbt --client") { + val out = sbtProcess("--client", "--no-colors", "compile").!!.linesIterator.toList + if (isWindows) { + println(out) + } else { + assert(out exists { _.contains("server was not detected") }) + } + val out2 = sbtProcess("--client", "--no-colors", "shutdown").!!.linesIterator.toList + if (isWindows) { + println(out) + } else { + assert(out2 exists { _.contains("disconnected") }) + } + () + } + */ +} diff --git a/launcher-package/integration-test/src/test/scala/ScriptTest.scala b/launcher-package/integration-test/src/test/scala/ScriptTest.scala new file mode 100644 index 000000000..1db0c50dc --- /dev/null +++ b/launcher-package/integration-test/src/test/scala/ScriptTest.scala @@ -0,0 +1,172 @@ +package example.test + +import minitest._ +import java.io.File + +object SbtScriptTest extends SimpleTestSuite with PowerAssertions { + lazy val isWindows: Boolean = sys.props("os.name").toLowerCase(java.util.Locale.ENGLISH).contains("windows") + lazy val sbtScript = + if (isWindows) new File("target/universal/stage/bin/sbt.bat") + else new File("target/universal/stage/bin/sbt") + + private val javaBinDir = new File("integration-test", "bin").getAbsolutePath + + private def makeTest( + name: String, + javaOpts: String = "", + sbtOpts: String = "", + )(args: String*)(f: List[String] => Any) = { + test(name) { + val out = sbtProcessWithOpts(args: _*)(javaOpts = javaOpts, sbtOpts = sbtOpts).!!.linesIterator.toList + f(out) + () + } + } + + def sbtProcess(args: String*) = sbtProcessWithOpts(args: _*)("", "") + def sbtProcessWithOpts(args: String*)(javaOpts: String, sbtOpts: String) = { + val path = sys.env("PATH") + sbt.internal.Process(Seq(sbtScript.getAbsolutePath) ++ args, new File("citest"), + "JAVA_OPTS" -> javaOpts, + "SBT_OPTS" -> sbtOpts, + if (isWindows) + "JAVACMD" -> new File(javaBinDir, "java.cmd").getAbsolutePath() + else + "PATH" -> (javaBinDir + File.pathSeparator + path) + ) + } + + makeTest("sbt -no-colors")("compile", "-no-colors", "-v") { out: List[String] => + assert(out.contains[String]("-Dsbt.log.noformat=true")) + } + + makeTest("sbt --no-colors")("compile", "--no-colors", "-v") { out: List[String] => + assert(out.contains[String]("-Dsbt.log.noformat=true")) + } + + makeTest("sbt --color=false")("compile", "--color=false", "-v") { out: List[String] => + assert(out.contains[String]("-Dsbt.color=false")) + } + + makeTest("sbt --no-colors in SBT_OPTS", sbtOpts = "--no-colors")("compile", "-v") { out: List[String] => + if (isWindows) cancel("Test not supported on windows") + assert(out.contains[String]("-Dsbt.log.noformat=true")) + } + + makeTest("sbt --debug-inc")("compile", "--debug-inc", "-v") { out: List[String] => + assert(out.contains[String]("-Dxsbt.inc.debug=true")) + } + + makeTest("sbt --supershell=never")("compile", "--supershell=never", "-v") { out: List[String] => + assert(out.contains[String]("-Dsbt.supershell=never")) + } + + makeTest("sbt --timings")("compile", "--timings", "-v") { out: List[String] => + assert(out.contains[String]("-Dsbt.task.timings=true")) + } + + makeTest("sbt -D arguments")("-Dsbt.supershell=false", "compile", "-v") { out: List[String] => + assert(out.contains[String]("-Dsbt.supershell=false")) + } + + makeTest("sbt --sbt-version")("--sbt-version", "1.3.13", "-v") { out: List[String] => + assert(out.contains[String]("-Dsbt.version=1.3.13")) + } + + makeTest("sbt -mem 503")("-mem", "503", "-v") { out: List[String] => + assert(out.contains[String]("-Xmx503m")) + } + + makeTest("sbt with -mem 503, -Xmx in JAVA_OPTS", javaOpts = "-Xmx1024m")("-mem", "503", "-v") { out: List[String] => + assert(out.contains[String]("-Xmx503m")) + assert(!out.contains[String]("-Xmx1024m")) + } + + makeTest("sbt with -mem 503, -Xmx in SBT_OPTS", sbtOpts = "-Xmx1024m")("-mem", "503", "-v") { out: List[String] => + assert(out.contains[String]("-Xmx503m")) + assert(!out.contains[String]("-Xmx1024m")) + } + + makeTest("sbt with -mem 503, -Xss in JAVA_OPTS", javaOpts = "-Xss6m")("-mem", "503", "-v") { out: List[String] => + assert(out.contains[String]("-Xmx503m")) + assert(!out.contains[String]("-Xss6m")) + } + + makeTest("sbt with -mem 503, -Xss in SBT_OPTS", sbtOpts = "-Xss6m")("-mem", "503", "-v") { out: List[String] => + assert(out.contains[String]("-Xmx503m")) + assert(!out.contains[String]("-Xss6m")) + } + + makeTest("sbt with -Xms2048M -Xmx2048M -Xss6M in JAVA_OPTS", javaOpts = "-Xms2048M -Xmx2048M -Xss6M")("-v") { out: List[String] => + assert(out.contains[String]("-Xms2048M")) + assert(out.contains[String]("-Xmx2048M")) + assert(out.contains[String]("-Xss6M")) + } + + makeTest("sbt with -Xms2048M -Xmx2048M -Xss6M in SBT_OPTS", sbtOpts = "-Xms2048M -Xmx2048M -Xss6M")( "-v") { out: List[String] => + assert(out.contains[String]("-Xms2048M")) + assert(out.contains[String]("-Xmx2048M")) + assert(out.contains[String]("-Xss6M")) + } + + + makeTest( + name = "sbt with -Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 in SBT_OPTS", + sbtOpts = "-Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080", + )("-v") { out: List[String] => + assert(out.contains[String]("-Dhttp.proxyHost=proxy")) + assert(out.contains[String]("-Dhttp.proxyPort=8080")) + } + + makeTest( + name = "sbt with -XX:ParallelGCThreads=16 -XX:PermSize=128M in SBT_OPTS", + sbtOpts = "-XX:ParallelGCThreads=16 -XX:PermSize=128M", + )("-v") { out: List[String] => + assert(out.contains[String]("-XX:ParallelGCThreads=16")) + assert(out.contains[String]("-XX:PermSize=128M")) + } + + makeTest("sbt with -XX:+UseG1GC -XX:+PrintGC in JAVA_OPTS", javaOpts = "-XX:+UseG1GC -XX:+PrintGC")("-v") { out: List[String] => + assert(out.contains[String]("-XX:+UseG1GC")) + assert(out.contains[String]("-XX:+PrintGC")) + assert(!out.contains[String]("-XX:+UseG1GC=-XX:+PrintGC")) + } + + makeTest("sbt with -XX:-UseG1GC -XX:-PrintGC in SBT_OPTS", sbtOpts = "-XX:+UseG1GC -XX:+PrintGC")( "-v") { out: List[String] => + assert(out.contains[String]("-XX:+UseG1GC")) + assert(out.contains[String]("-XX:+PrintGC")) + assert(!out.contains[String]("-XX:+UseG1GC=-XX:+PrintGC")) + } + + test("sbt with -debug in SBT_OPTS appears in sbt commands") { + if (isWindows) cancel("Test not supported on windows") + + val out: List[String] = sbtProcessWithOpts("compile", "-v")(javaOpts = "", sbtOpts = "-debug").!!.linesIterator.toList + // Debug argument must appear in the 'commands' section (after the sbt-launch.jar argument) to work + val sbtLaunchMatcher = """^.+sbt-launch.jar["]{0,1}$""".r + val locationOfSbtLaunchJarArg = out.zipWithIndex.collectFirst { + case (arg, index) if sbtLaunchMatcher.findFirstIn(arg).nonEmpty => index + } + + assert(locationOfSbtLaunchJarArg.nonEmpty) + + val argsAfterSbtLaunch = out.drop(locationOfSbtLaunchJarArg.get) + assert(argsAfterSbtLaunch.contains("-debug")) + () + } + + makeTest("sbt --jvm-debug ")("--jvm-debug", "12345", "-v") { out: List[String] => + assert(out.contains[String]("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=12345")) + } + + makeTest("sbt --no-share adds three system properties")("--no-share") { out: List[String] => + assert(out.contains[String]("-Dsbt.global.base=project/.sbtboot")) + assert(out.contains[String]("-Dsbt.boot.directory=project/.boot")) + assert(out.contains[String]("-Dsbt.ivy.home=project/.ivy")) + } + + makeTest("accept `--ivy` in `SBT_OPTS`", sbtOpts = "--ivy /ivy/dir")("-v") { out: List[String] => + if (isWindows) cancel("Test not supported on windows") + assert(out.contains[String]("-Dsbt.ivy.home=/ivy/dir")) + } +} diff --git a/launcher-package/integration-test/src/test/scala/SyncVar.scala b/launcher-package/integration-test/src/test/scala/SyncVar.scala new file mode 100644 index 000000000..5754e6da0 --- /dev/null +++ b/launcher-package/integration-test/src/test/scala/SyncVar.scala @@ -0,0 +1,38 @@ +package sbt.internal + +// minimal copy of scala.concurrent.SyncVar since that version deprecated put and unset +private[sbt] final class SyncVar[A] { + private[this] var isDefined: Boolean = false + private[this] var value: Option[A] = None + + /** Waits until a value is set and then gets it. Does not clear the value */ + def get: A = synchronized { + while (!isDefined) wait() + value.get + } + + /** Waits until a value is set, gets it, and finally clears the value. */ + def take(): A = synchronized { + try get finally unset() + } + + /** Sets the value, whether or not it is currently defined. */ + def set(x: A): Unit = synchronized { + isDefined = true + value = Some(x) + notifyAll() + } + + /** Sets the value, first waiting until it is undefined if it is currently defined. */ + def put(x: A): Unit = synchronized { + while (isDefined) wait() + set(x) + } + + /** Clears the value, whether or not it is current defined. */ + def unset(): Unit = synchronized { + isDefined = false + value = None + notifyAll() + } +} diff --git a/launcher-package/project/PackageSignerPlugin.scala b/launcher-package/project/PackageSignerPlugin.scala new file mode 100644 index 000000000..719aa322f --- /dev/null +++ b/launcher-package/project/PackageSignerPlugin.scala @@ -0,0 +1,60 @@ +import sbt._ +import Keys._ +import com.typesafe.sbt.SbtPgp +import com.typesafe.sbt.packager.universal.{ UniversalPlugin, UniversalDeployPlugin } +import com.typesafe.sbt.packager.debian.{ DebianPlugin, DebianDeployPlugin } +import com.typesafe.sbt.packager.rpm.{ RpmPlugin, RpmDeployPlugin } +import com.typesafe.sbt.pgp.gpgExtension + +object PackageSignerPlugin extends sbt.AutoPlugin { + override def trigger = allRequirements + override def requires = SbtPgp && UniversalDeployPlugin && DebianDeployPlugin && RpmDeployPlugin + + import com.typesafe.sbt.pgp.PgpKeys._ + import UniversalPlugin.autoImport._ + import DebianPlugin.autoImport._ + import RpmPlugin.autoImport._ + + override def projectSettings: Seq[Setting[_]] = + inConfig(Universal)(packageSignerSettings) ++ + inConfig(Debian)(packageSignerSettings) ++ + inConfig(Rpm)(packageSignerSettings) + + def subExtension(art: Artifact, ext: String): Artifact = + art.copy(extension = ext) + + def packageSignerSettings: Seq[Setting[_]] = Seq( + signedArtifacts := { + val artifacts = packagedArtifacts.value + val r = pgpSigner.value + val skipZ = (skip in pgpSigner).value + val s = streams.value + if (!skipZ) { + artifacts flatMap { case (art, f) => + Seq(art -> f, + subExtension(art, art.extension + gpgExtension) -> + r.sign(f, file(f.getAbsolutePath + gpgExtension), s)) + } + } + else artifacts + }, + publishSignedConfiguration := Classpaths.publishConfig( + signedArtifacts.value, + None, + resolverName = Classpaths.getPublishTo(publishTo.value).name, + checksums = (checksums in publish).value, + logging = ivyLoggingLevel.value, + overwrite = isSnapshot.value), + publishLocalSignedConfiguration := Classpaths.publishConfig( + signedArtifacts.value, + None, + resolverName = "local", + checksums = (checksums in publish).value, + logging = ivyLoggingLevel.value, + overwrite = isSnapshot.value), + publishSigned := Classpaths.publishTask(publishSignedConfiguration, deliver).value, + publishLocalSigned := Classpaths.publishTask(publishLocalSignedConfiguration, deliver).value + ) + +} + diff --git a/launcher-package/project/build.properties b/launcher-package/project/build.properties new file mode 100644 index 000000000..133a8f197 --- /dev/null +++ b/launcher-package/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.17 diff --git a/launcher-package/project/plugins.sbt b/launcher-package/project/plugins.sbt new file mode 100644 index 000000000..4f87d4adb --- /dev/null +++ b/launcher-package/project/plugins.sbt @@ -0,0 +1,5 @@ +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.2.0-M9") +libraryDependencies += "net.databinder" %% "dispatch-http" % "0.8.10" +addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.1") +addSbtPlugin("com.eed3si9n" % "sbt-export-repo" % "0.1.1") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.1") diff --git a/launcher-package/src/debian/changelog b/launcher-package/src/debian/changelog new file mode 100644 index 000000000..d7947decf --- /dev/null +++ b/launcher-package/src/debian/changelog @@ -0,0 +1,49 @@ +sbt (1.0.0) stable; urgency=low + + * sbt 1.0 uses Scala 2.12 for build definitions and plugins. This also requires JDK 8. + * See http://www.scala-sbt.org/1.x/docs/sbt-1.0-Release-Notes.html for the full list of changes. + + -- Eugene Yokota Fri, 11 Aug 2017 00:00:00 +0000 + +sbt (0.13.15) stable; urgency=low + + * sbt 0.13.15 removes the Maven version range when possible. + * Adds preliminary compatibility with JDK 9. Using this requires 0.13.15+ launcher. #2951 by @retronym + * Adds "local-preloaded" repository for offline installation + + -- Eugene Yokota Mon, 10 Apr 2017 00:00:00 +0000 + +sbt (0.13.13) stable; urgency=low + + * The `new` command, which helps creating new build definitions. + This is extensible via `templateResolverInfos` setting. + sbt 0.13.13 will ship with Giter8 template resolver out of the box. + #2705 by @eed3si9n + * Auto plugins can add synthetic subprojects. #2717 by @eed3si9n + * The no-longer-documented sbt 0.12 DSL `<<=`, `<+=`, and `<++=` + operators and tuple enrichment are now marked deprecated. + (These are removed in 1.0.x branch.) @eed3si9n and @dwijnand + * The `.value` method is deprecated for input tasks since `.evaluated` + is normally what's needed. + (Also removed in 1.0.x branch) #2709 by @eed3si9n + * sbt 0.13.13 will display deprecation warning for build.sbt. + #2784 by @eed3si9n + * Renames the early command `--` that was added in 0.13.1 to + `early()`. `--error` will work, but we encourage you to + migrate to single hyphen version: `-error`, `-warn`, `-info`, and + `-debug`. #2742 by @eed3si9n + + -- Dale Wijnand Fri, 28 Oct 2016 14:30:00 +0000 + +sbt (0.12.0-build-0100) + + * No need for different launcher jar files now + + -- Joshua Suereth 2012-07-2012 + +sbt (0.11.2-build-0100) + + * First debian package release + + -- Joshua Suereth 2011-11-29 + diff --git a/launcher-package/src/debian/usr/share/doc/sbt/copyright b/launcher-package/src/debian/usr/share/doc/sbt/copyright new file mode 100644 index 000000000..f05b46cfb --- /dev/null +++ b/launcher-package/src/debian/usr/share/doc/sbt/copyright @@ -0,0 +1,22 @@ + +sbt + +Copyright: Paul Phillips, Lightbend, and other contributors. + +2011-11-28 + +The home page of sbt package is at: +https://github.com/sbt/sbt-launcher-package + +The entire code base may be distributed under the terms of the BSD license, which appears immediately below. + +Copyright (c) 2011 - 2018, Paul Phillips, Lightbend, 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: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. diff --git a/launcher-package/src/linux/usr/share/doc/sbt/copyright b/launcher-package/src/linux/usr/share/doc/sbt/copyright new file mode 100644 index 000000000..f05b46cfb --- /dev/null +++ b/launcher-package/src/linux/usr/share/doc/sbt/copyright @@ -0,0 +1,22 @@ + +sbt + +Copyright: Paul Phillips, Lightbend, and other contributors. + +2011-11-28 + +The home page of sbt package is at: +https://github.com/sbt/sbt-launcher-package + +The entire code base may be distributed under the terms of the BSD license, which appears immediately below. + +Copyright (c) 2011 - 2018, Paul Phillips, Lightbend, 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: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. diff --git a/launcher-package/src/linux/usr/share/man/man1/sbt.1 b/launcher-package/src/linux/usr/share/man/man1/sbt.1 new file mode 100644 index 000000000..914a99b50 --- /dev/null +++ b/launcher-package/src/linux/usr/share/man/man1/sbt.1 @@ -0,0 +1,98 @@ +.\" Process this file with +.\" groff -man -Tascii sbt.1 +.\" +.TH SBT 1 "NOVEMBER 2011" Linux "User Manuals" +.SH NAME +sbt \- An interactive build tool for Scala, Java, and more. +.SH SYNOPSIS +.B sbt [-h] +.I +.B ... +.SH DESCRIPTION +SBT is a build tool for Scala, Java, and more. It requires Java 1.8 or later. +The current directory is assumed to be the project. +.SH OPTIONS +.IP "-h, --help" +Show help options. +.IP "-v, --verbose" +turn up the noise +.IP "-V, --version" +print sbt version information +.IP "--numeric-version" +print the numeric sbt version (sbt sbtVersion) +.IP "--script-version" +print the version of sbt script +.IP "-d, --debug" +set sbt log level to debug +.IP --no-colors +disable ANSI color codes +.IP "--color=auto|always|true|false|never" +enable or disable ANSI color codes (sbt 1.3 and above) +.IP "--supershell=auto|always|true|false|never" +enable or disable supershell (sbt 1.3 and above) +.IP --traces +generate Trace Event report on shutdown (sbt 1.3 and above) +.IP --timings +display task timings report on shutdown +.IP --sbt-create +start sbt even if current directory contains no sbt project +.IP "--sbt-dir " +path to global settings/plugins directory (default: ~/.sbt) +.IP "--sbt-boot " +path to shared boot directory (default: ~/.sbt/boot in 0.11 series) +.IP "--ivy " +path to local Ivy repository (default: ~/.ivy2) +.IP "--mem " +set memory options (default: 1024) +.IP "--no-share" +use all local caches; no sharing +.IP "--no-global" +uses global caches, but does not use global ~/.sbt directory. +.IP "--jvm-debug " +Turn on JVM debugging, open at the given port. +.IP --batch +Disable interactive mode +.SH SBT Version Options +.IP "--sbt-version " +Use the alternate system wide +.IP "--sbt-jar " +use the specified jar as the sbt launcher +.SH Java Options +.IP "--java-home " +alternate JAVA_HOME +.IP "-Dkey=val" +pass -Dkey=val directly to the java runtime +.IP -J-X +pass option -X directly to the java runtime (-J is stripped) +.IP -S-X +add -X to sbt's scalacOptions (-S is stripped) +.SH FILES +.I ~/.sbt +.RS +The user configuration directory. +.RE +.I ".jvmopts" +.RS +if this file exists in the current directory, its contents are appended +to the JAVA_OPTS. +.RE +.I ".sbtopts" +.RS +if this file exists in the current directory, its contents are prepended +to the runner args. +.RE +.I "/etc/sbt/sbtopts" +.RS +if this file exists, it is prepended to the runner args +.SH ENVIRONMENT +.IP JAVA_OPTS +If non-null a set of arguments passed to java. +.IP SBT_OPTS +environment variable, if unset uses "-Dfile.encoding=UTF-8". +.SH NOTES +In the case of duplicated or conflicting options, the order above +shows precedence: JAVA_OPTS lowest, command line options highest. +.SH EXAMPLES +Most users of this script will only have to call "sbt" on the command line. +.SH BUGS +https://github.com/sbt/sbt/issues diff --git a/launcher-package/src/universal/bin/sbt b/launcher-package/src/universal/bin/sbt new file mode 100755 index 000000000..ac198cc8b --- /dev/null +++ b/launcher-package/src/universal/bin/sbt @@ -0,0 +1,678 @@ +#!/usr/bin/env bash + +set +e +declare -a residual_args +declare -a java_args +declare -a scalac_args +declare -a sbt_commands +declare -a sbt_options +declare -a print_version +declare -a print_sbt_version +declare -a print_sbt_script_version +declare -a original_args +declare java_cmd=java +declare java_version +declare init_sbt_version=_to_be_replaced +declare sbt_default_mem=1024 +declare -r default_sbt_opts="" +declare -r default_java_opts="-Dfile.encoding=UTF-8" +declare sbt_verbose= +declare sbt_debug= +declare build_props_sbt_version= +declare use_sbtn= +declare sbtn_command="$SBTN_CMD" + +### ------------------------------- ### +### Helper methods for BASH scripts ### +### ------------------------------- ### + +# Bash reimplementation of realpath to return the absolute path +realpathish () { +( + TARGET_FILE="$1" + FIX_CYGPATH="$2" + + cd "$(dirname "$TARGET_FILE")" + TARGET_FILE=$(basename "$TARGET_FILE") + + COUNT=0 + while [ -L "$TARGET_FILE" -a $COUNT -lt 100 ] + do + TARGET_FILE=$(readlink "$TARGET_FILE") + cd "$(dirname "$TARGET_FILE")" + TARGET_FILE=$(basename "$TARGET_FILE") + COUNT=$(($COUNT + 1)) + done + + # make sure we grab the actual windows path, instead of cygwin's path. + if [[ "x$FIX_CYGPATH" != "x" ]]; then + echo "$(cygwinpath "$(pwd -P)/$TARGET_FILE")" + else + echo "$(pwd -P)/$TARGET_FILE" + fi +) +} + +# Uses uname to detect if we're in the odd cygwin environment. +is_cygwin() { + local os=$(uname -s) + case "$os" in + CYGWIN*) return 0 ;; + MINGW*) return 0 ;; + MSYS*) return 0 ;; + *) return 1 ;; + esac +} + +# TODO - Use nicer bash-isms here. +CYGWIN_FLAG=$(if is_cygwin; then echo true; else echo false; fi) + +# This can fix cygwin style /cygdrive paths so we get the +# windows style paths. +cygwinpath() { + local file="$1" + if [[ "$CYGWIN_FLAG" == "true" ]]; then #" + echo $(cygpath -w $file) + else + echo $file + fi +} + + +declare -r sbt_bin_dir="$(dirname "$(realpathish "$0")")" +declare -r sbt_home="$(dirname "$sbt_bin_dir")" + +echoerr () { + echo 1>&2 "$@" +} +vlog () { + [[ $sbt_verbose || $sbt_debug ]] && echoerr "$@" +} +dlog () { + [[ $sbt_debug ]] && echoerr "$@" +} + +jar_file () { + echo "$(cygwinpath "${sbt_home}/bin/sbt-launch.jar")" +} + +acquire_sbt_jar () { + sbt_jar="$(jar_file)" + + if [[ ! -f "$sbt_jar" ]]; then + echoerr "Could not find launcher jar: $sbt_jar" + exit 2 + fi +} + +# execRunner should be called only once to give up control to java +execRunner () { + # print the arguments one to a line, quoting any containing spaces + [[ $sbt_verbose || $sbt_debug ]] && echo "# Executing command line:" && { + for arg; do + if printf "%s\n" "$arg" | grep -q ' '; then + printf "\"%s\"\n" "$arg" + else + printf "%s\n" "$arg" + fi + done + echo "" + } + + if [[ "$CYGWIN_FLAG" == "true" ]]; then + # In cygwin we loose the ability to re-hook stty if exec is used + # https://github.com/sbt/sbt-launcher-package/issues/53 + "$@" + else + exec "$@" + fi +} + +addJava () { + dlog "[addJava] arg = '$1'" + java_args=( "${java_args[@]}" "$1" ) +} +addSbt () { + dlog "[addSbt] arg = '$1'" + sbt_commands=( "${sbt_commands[@]}" "$1" ) +} +addResidual () { + dlog "[residual] arg = '$1'" + residual_args=( "${residual_args[@]}" "$1" ) +} +addDebugger () { + addJava "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$1" +} + +addMemory () { + dlog "[addMemory] arg = '$1'" + # evict memory related options + local xs=("${java_args[@]}") + java_args=() + for i in "${xs[@]}"; do + if ! [[ "${i}" == *-Xmx* ]] && ! [[ "${i}" == *-Xms* ]] && ! [[ "${i}" == *-Xss* ]] && ! [[ "${i}" == *-XX:MaxPermSize* ]] && ! [[ "${i}" == *-XX:MaxMetaspaceSize* ]] && ! [[ "${i}" == *-XX:ReservedCodeCacheSize* ]]; then + java_args+=("${i}") + fi + done + local ys=("${sbt_options[@]}") + sbt_options=() + for i in "${ys[@]}"; do + if ! [[ "${i}" == *-Xmx* ]] && ! [[ "${i}" == *-Xms* ]] && ! [[ "${i}" == *-Xss* ]] && ! [[ "${i}" == *-XX:MaxPermSize* ]] && ! [[ "${i}" == *-XX:MaxMetaspaceSize* ]] && ! [[ "${i}" == *-XX:ReservedCodeCacheSize* ]]; then + sbt_options+=("${i}") + fi + done + # a ham-fisted attempt to move some memory settings in concert + local mem=$1 + local codecache=$(( $mem / 8 )) + (( $codecache > 128 )) || codecache=128 + (( $codecache < 512 )) || codecache=512 + local class_metadata_size=$(( $codecache * 2 )) + if [[ -z $java_version ]]; then + java_version=$(jdk_version) + fi + + addJava "-Xms${mem}m" + addJava "-Xmx${mem}m" + addJava "-Xss4M" + addJava "-XX:ReservedCodeCacheSize=${codecache}m" + (( $java_version >= 8 )) || addJava "-XX:MaxPermSize=${class_metadata_size}m" +} + +addDefaultMemory() { + # if we detect any of these settings in ${JAVA_OPTS} or ${JAVA_TOOL_OPTIONS} we need to NOT output our settings. + # The reason is the Xms/Xmx, if they don't line up, cause errors. + if [[ "${java_args[@]}" == *-Xmx* ]] || \ + [[ "${java_args[@]}" == *-Xms* ]] || \ + [[ "${java_args[@]}" == *-Xss* ]] || \ + [[ "${java_args[@]}" == *-XX:+UseCGroupMemoryLimitForHeap* ]] || \ + [[ "${java_args[@]}" == *-XX:MaxRAM* ]] || \ + [[ "${java_args[@]}" == *-XX:InitialRAMPercentage* ]] || \ + [[ "${java_args[@]}" == *-XX:MaxRAMPercentage* ]] || \ + [[ "${java_args[@]}" == *-XX:MinRAMPercentage* ]]; then + : + elif [[ "${JAVA_TOOL_OPTIONS}" == *-Xmx* ]] || \ + [[ "${JAVA_TOOL_OPTIONS}" == *-Xms* ]] || \ + [[ "${JAVA_TOOL_OPTIONS}" == *-Xss* ]] || \ + [[ "${JAVA_TOOL_OPTIONS}" == *-XX:+UseCGroupMemoryLimitForHeap* ]] || \ + [[ "${JAVA_TOOL_OPTIONS}" == *-XX:MaxRAM* ]] || \ + [[ "${JAVA_TOOL_OPTIONS}" == *-XX:InitialRAMPercentage* ]] || \ + [[ "${JAVA_TOOL_OPTIONS}" == *-XX:MaxRAMPercentage* ]] || \ + [[ "${JAVA_TOOL_OPTIONS}" == *-XX:MinRAMPercentage* ]] ; then + : + elif [[ "${sbt_options[@]}" == *-Xmx* ]] || \ + [[ "${sbt_options[@]}" == *-Xms* ]] || \ + [[ "${sbt_options[@]}" == *-Xss* ]] || \ + [[ "${sbt_options[@]}" == *-XX:+UseCGroupMemoryLimitForHeap* ]] || \ + [[ "${sbt_options[@]}" == *-XX:MaxRAM* ]] || \ + [[ "${sbt_options[@]}" == *-XX:InitialRAMPercentage* ]] || \ + [[ "${sbt_options[@]}" == *-XX:MaxRAMPercentage* ]] || \ + [[ "${sbt_options[@]}" == *-XX:MinRAMPercentage* ]] ; then + : + else + addMemory $sbt_default_mem + fi +} + +require_arg () { + local type="$1" + local opt="$2" + local arg="$3" + if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then + echo "$opt requires <$type> argument" + exit 1 + fi +} + +is_function_defined() { + declare -f "$1" > /dev/null +} + +# parses JDK version from the -version output line. +# 8 for 1.8.0_nn, 9 for 9-ea etc, and "no_java" for undetected +jdk_version() { + local result + local lines=$("$java_cmd" -Xms32M -Xmx32M -version 2>&1 | tr '\r' '\n') + local IFS=$'\n' + for line in $lines; do + if [[ (-z $result) && ($line = *"version \""*) ]] + then + local ver=$(echo $line | sed -e 's/.*version "\(.*\)"\(.*\)/\1/; 1q') + # on macOS sed doesn't support '?' + if [[ $ver = "1."* ]] + then + result=$(echo $ver | sed -e 's/1\.\([0-9]*\)\(.*\)/\1/; 1q') + else + result=$(echo $ver | sed -e 's/\([0-9]*\)\(.*\)/\1/; 1q') + fi + fi + done + if [[ -z $result ]] + then + result=no_java + fi + echo "$result" +} + +# Extracts the preloaded directory from either -Dsbt.preloaded or -Dsbt.global.base +# properties by looking at: +# - _JAVA_OPTIONS environment variable, +# - SBT_OPTS environment variable, +# - JAVA_OPTS environment variable and +# - properties set by command-line options +# in that order. The last one will be chosen such that `sbt.preloaded` is +# always preferred over `sbt.global.base`. +getPreloaded() { + local -a _java_options_array + local -a sbt_opts_array + local -a java_opts_array + read -a _java_options_array <<< "$_JAVA_OPTIONS" + read -a sbt_opts_array <<< "$SBT_OPTS" + read -a java_opts_array <<< "$JAVA_OPTS" + + local args_to_check=( + "${_java_options_array[@]}" + "${sbt_opts_array[@]}" + "${java_opts_array[@]}" + "${java_args[@]}") + local via_global_base="$HOME/.sbt/preloaded" + local via_explicit="" + + for opt in "${args_to_check[@]}"; do + if [[ "$opt" == -Dsbt.preloaded=* ]]; then + via_explicit="${opt#-Dsbt.preloaded=}" + elif [[ "$opt" == -Dsbt.global.base=* ]]; then + via_global_base="${opt#-Dsbt.global.base=}/preloaded" + fi + done + + echo "${via_explicit:-${via_global_base}}" +} + +syncPreloaded() { + local source_preloaded="$sbt_home/lib/local-preloaded/" + local target_preloaded="$(getPreloaded)" + if [[ "$init_sbt_version" == "" ]]; then + # FIXME: better $init_sbt_version detection + init_sbt_version="$(ls -1 "$source_preloaded/org/scala-sbt/sbt/")" + fi + [[ -f "$target_preloaded/org/scala-sbt/sbt/$init_sbt_version/" ]] || { + # lib/local-preloaded exists (This is optional) + [[ -d "$source_preloaded" ]] && { + command -v rsync >/dev/null 2>&1 && { + mkdir -p "$target_preloaded" + rsync --recursive --links --perms --times --ignore-existing "$source_preloaded" "$target_preloaded" || true + } + } + } +} + +# Detect that we have java installed. +checkJava() { + local required_version="$1" + # Now check to see if it's a good enough version + local good_enough="$(expr $java_version ">=" $required_version)" + if [[ "$java_version" == "" ]]; then + echo + echo "No Java Development Kit (JDK) installation was detected." + echo Please go to http://www.oracle.com/technetwork/java/javase/downloads/ and download. + echo + exit 1 + elif [[ "$good_enough" != "1" ]]; then + echo + echo "The Java Development Kit (JDK) installation you have is not up to date." + echo $script_name requires at least version $required_version+, you have + echo version $java_version + echo + echo Please go to http://www.oracle.com/technetwork/java/javase/downloads/ and download + echo a valid JDK and install before running $script_name. + echo + exit 1 + fi +} + +copyRt() { + local at_least_9="$(expr $java_version ">=" 9)" + if [[ "$at_least_9" == "1" ]]; then + # The grep for java9-rt-ext- matches the filename prefix printed in Export.java + java9_ext=$("$java_cmd" "${sbt_options[@]}" "${java_args[@]}" \ + -jar "$sbt_jar" --rt-ext-dir | grep java9-rt-ext- | tr -d '\r') + java9_rt=$(echo "$java9_ext/rt.jar") + vlog "[copyRt] java9_rt = '$java9_rt'" + if [[ ! -f "$java9_rt" ]]; then + echo copying runtime jar... + mkdir -p "$java9_ext" + "$java_cmd" \ + "${sbt_options[@]}" \ + "${java_args[@]}" \ + -jar "$sbt_jar" \ + --export-rt \ + "${java9_rt}" + fi + addJava "-Dscala.ext.dirs=${java9_ext}" + fi +} + +run() { + # Copy preloaded repo to user's preloaded directory + syncPreloaded + + # no jar? download it. + [[ -f "$sbt_jar" ]] || acquire_sbt_jar "$sbt_version" || { + # still no jar? uh-oh. + echo "Download failed. Obtain the sbt-launch.jar manually and place it at $sbt_jar" + exit 1 + } + + # TODO - java check should be configurable... + checkJava "6" + + # Java 9 support + copyRt + + # If we're in cygwin, we should use the windows config, and terminal hacks + if [[ "$CYGWIN_FLAG" == "true" ]]; then #" + stty -icanon min 1 -echo > /dev/null 2>&1 + addJava "-Djline.terminal=jline.UnixTerminal" + addJava "-Dsbt.cygwin=true" + fi + + if [[ $print_sbt_version ]]; then + execRunner "$java_cmd" -jar "$sbt_jar" "sbtVersion" | tail -1 | sed -e 's/\[info\]//g' + elif [[ $print_sbt_script_version ]]; then + echo "$init_sbt_version" + elif [[ $print_version ]]; then + execRunner "$java_cmd" -jar "$sbt_jar" "sbtVersion" | tail -1 | sed -e 's/\[info\]/sbt version in this project:/g' + echo "sbt script version: $init_sbt_version" + else + # run sbt + execRunner "$java_cmd" \ + "${java_args[@]}" \ + "${sbt_options[@]}" \ + -jar "$sbt_jar" \ + "${sbt_commands[@]}" \ + "${residual_args[@]}" + fi + + exit_code=$? + + # Clean up the terminal from cygwin hacks. + if [[ "$CYGWIN_FLAG" == "true" ]]; then #" + stty icanon echo > /dev/null 2>&1 + fi + exit $exit_code +} + +declare -ra noshare_opts=(-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy) +declare -r sbt_opts_file=".sbtopts" +declare -r build_props_file="$(pwd)/project/build.properties" +declare -r etc_sbt_opts_file="/etc/sbt/sbtopts" +# this allows /etc/sbt/sbtopts location to be changed +declare -r etc_file="${SBT_ETC_FILE:-$etc_sbt_opts_file}" +declare -r dist_sbt_opts_file="${sbt_home}/conf/sbtopts" +declare -r win_sbt_opts_file="${sbt_home}/conf/sbtconfig.txt" + +usage() { + cat < path to global settings/plugins directory (default: ~/.sbt) + --sbt-boot path to shared boot directory (default: ~/.sbt/boot in 0.11 series) + --ivy path to local Ivy repository (default: ~/.ivy2) + --mem set memory options (default: $sbt_default_mem) + --no-share use all local caches; no sharing + --no-global uses global caches, but does not use global ~/.sbt directory. + --jvm-debug Turn on JVM debugging, open at the given port. + --batch disable interactive mode + + # sbt version (default: from project/build.properties if present, else latest release) + --sbt-version use the specified version of sbt + --sbt-jar use the specified jar as the sbt launcher + + # java version (default: java from PATH, currently $(java -version 2>&1 | grep version)) + --java-home alternate JAVA_HOME + + # jvm options and output control + JAVA_OPTS environment variable, if unset uses "$default_java_opts" + .jvmopts if this file exists in the current directory, its contents + are appended to JAVA_OPTS + SBT_OPTS environment variable, if unset uses "$default_sbt_opts" + .sbtopts if this file exists in the current directory, its contents + are prepended to the runner args + /etc/sbt/sbtopts if this file exists, it is prepended to the runner args + -Dkey=val pass -Dkey=val directly to the java runtime + -J-X pass option -X directly to the java runtime + (-J is stripped) + -S-X add -X to sbt's scalacOptions (-S is stripped) + +In the case of duplicated or conflicting options, the order above +shows precedence: JAVA_OPTS lowest, command line options highest. +EOM +} + +process_my_args () { + while [[ $# -gt 0 ]]; do + case "$1" in + -batch|--batch) exec + + -sbt-create|--sbt-create) sbt_create=true && shift ;; + + new) sbt_new=true && addResidual "$1" && shift ;; + + *) addResidual "$1" && shift ;; + esac + done + + # Now, ensure sbt version is used. + [[ "${sbt_version}XXX" != "XXX" ]] && addJava "-Dsbt.version=$sbt_version" + + # Confirm a user's intent if the current directory does not look like an sbt + # top-level directory and neither the -sbt-create option nor the "new" + # command was given. + [[ -f ./build.sbt || -d ./project || -n "$sbt_create" || -n "$sbt_new" ]] || { + echo "[warn] Neither build.sbt nor a 'project' directory in the current directory: $(pwd)" + while true; do + echo 'c) continue' + echo 'q) quit' + + read -p '? ' || exit 1 + case "$REPLY" in + c|C) break ;; + q|Q) exit 1 ;; + esac + done + } +} + +## map over argument array. this is used to process both command line arguments and SBT_OPTS +map_args () { + local options=() + local commands=() + while [[ $# -gt 0 ]]; do + case "$1" in + -no-colors|--no-colors) options=( "${options[@]}" "-Dsbt.log.noformat=true" ) && shift ;; + -timings|--timings) options=( "${options[@]}" "-Dsbt.task.timings=true" "-Dsbt.task.timings.on.shutdown=true" ) && shift ;; + -traces|--traces) options=( "${options[@]}" "-Dsbt.traces=true" ) && shift ;; + --supershell=*) options=( "${options[@]}" "-Dsbt.supershell=${1:13}" ) && shift ;; + -supershell=*) options=( "${options[@]}" "-Dsbt.supershell=${1:12}" ) && shift ;; + --color=*) options=( "${options[@]}" "-Dsbt.color=${1:8}" ) && shift ;; + -color=*) options=( "${options[@]}" "-Dsbt.color=${1:7}" ) && shift ;; + -no-share|--no-share) options=( "${options[@]}" "${noshare_opts[@]}" ) && shift ;; + -no-global|--no-global) options=( "${options[@]}" "-Dsbt.global.base=$(pwd)/project/.sbtboot" ) && shift ;; + -ivy|--ivy) require_arg path "$1" "$2" && options=( "${options[@]}" "-Dsbt.ivy.home=$2" ) && shift 2 ;; + -sbt-boot|--sbt-boot) require_arg path "$1" "$2" && options=( "${options[@]}" "-Dsbt.boot.directory=$2" ) && shift 2 ;; + -sbt-dir|--sbt-dir) require_arg path "$1" "$2" && options=( "${options[@]}" "-Dsbt.global.base=$2" ) && shift 2 ;; + -debug|--debug) commands=( "${commands[@]}" "-debug" ) && shift ;; + -debug-inc|--debug-inc) options=( "${options[@]}" "-Dxsbt.inc.debug=true" ) && shift ;; + *) options=( "${options[@]}" "$1" ) && shift ;; + esac + done + declare -p options + declare -p commands +} + +process_args () { + while [[ $# -gt 0 ]]; do + case "$1" in + -h|-help|--help) usage; exit 1 ;; + -v|-verbose|--verbose) sbt_verbose=1 && shift ;; + -V|-version|--version) print_version=1 && shift ;; + --numeric-version) print_sbt_version=1 && shift ;; + --script-version) print_sbt_script_version=1 && shift ;; + -d|-debug|--debug) sbt_debug=1 && addSbt "-debug" && shift ;; + --client) use_sbtn=1 && shift ;; + --server) use_sbtn=0 && shift ;; + + -mem|--mem) require_arg integer "$1" "$2" && addMemory "$2" && shift 2 ;; + -jvm-debug|--jvm-debug) require_arg port "$1" "$2" && addDebugger $2 && shift 2 ;; + -batch|--batch) exec = 2 )) || ( (( $sbtBinaryV_1 >= 1 )) && (( $sbtBinaryV_2 >= 4 )) ); then + if [[ "$use_sbtn" == "1" ]] && [[ "$sbtn_command" != "" ]]; then + echo "true" + else + echo "false" + fi + else + echo "false" + fi +} + +runNativeClient() { + vlog "[debug] running native client" + for i in "${!original_args[@]}"; do + if [[ "${original_args[i]}" = "--client" ]]; then + unset 'original_args[i]' + fi + done + sbt_script=$0 + sbt_script=${sbt_script/ /%20} + execRunner "$sbtn_command" "--sbt-script=$sbt_script" "${original_args[@]}" +} + +original_args=("$@") + +# Here we pull in the default settings configuration. +[[ -f "$dist_sbt_opts_file" ]] && set -- $(loadConfigFile "$dist_sbt_opts_file") "$@" + +# Here we pull in the global settings configuration. +[[ -f "$etc_file" ]] && set -- $(loadConfigFile "$etc_file") "$@" + +# Pull in the project-level config file, if it exists. +[[ -f "$sbt_opts_file" ]] && set -- $(loadConfigFile "$sbt_opts_file") "$@" + +# Pull in the project-level java config, if it exists. +[[ -f ".jvmopts" ]] && export JAVA_OPTS="$JAVA_OPTS $(loadConfigFile .jvmopts)" + +# Pull in default JAVA_OPTS +[[ -z "${JAVA_OPTS// }" ]] && export JAVA_OPTS="$default_java_opts" + +[[ -f "$build_props_file" ]] && loadPropFile "$build_props_file" + +detectNativeClient + +java_args=($JAVA_OPTS) +sbt_options0=(${SBT_OPTS:-$default_sbt_opts}) +if [[ "$SBT_NATIVE_CLIENT" == "true" ]]; then + use_sbtn=1 +fi + +# Split SBT_OPTS into options/commands +miniscript=$(map_args "${sbt_options0[@]}") && eval "${miniscript/options/sbt_options}" && \ +eval "${miniscript/commands/sbt_additional_commands}" + +# Combine command line options/commands and commands from SBT_OPTS +miniscript=$(map_args "$@") && eval "${miniscript/options/cli_options}" && eval "${miniscript/commands/cli_commands}" +args1=( "${cli_options[@]}" "${cli_commands[@]}" "${sbt_additional_commands[@]}" ) + +# process the combined args, then reset "$@" to the residuals +process_args "${args1[@]}" +vlog "[sbt_options] $(declare -p sbt_options)" + +if [[ "$(isRunNativeClient)" == "true" ]]; then + set -- "${residual_args[@]}" + argumentCount=$# + runNativeClient +else + java_version="$(jdk_version)" + vlog "[process_args] java_version = '$java_version'" + addDefaultMemory + set -- "${residual_args[@]}" + argumentCount=$# + run +fi diff --git a/launcher-package/src/universal/bin/sbt.bat b/launcher-package/src/universal/bin/sbt.bat new file mode 100755 index 000000000..cda987c9e --- /dev/null +++ b/launcher-package/src/universal/bin/sbt.bat @@ -0,0 +1,981 @@ +@REM SBT launcher script +@REM +@REM Environment: +@REM JAVA_HOME - location of a JDK home dir (mandatory) +@REM SBT_OPTS - JVM options (optional) +@REM Configuration: +@REM sbtconfig.txt found in the SBT_HOME. + +@REM ZOMG! We need delayed expansion to build up CFG_OPTS later +@setlocal enabledelayedexpansion + +@echo off +set SBT_BIN_DIR=%~dp0 +if not defined SBT_HOME for %%I in ("!SBT_BIN_DIR!\..") do set "SBT_HOME=%%~fI" + +set SBT_ARGS= +set _JAVACMD= +set _SBT_OPTS= +set _JAVA_OPTS= + +set init_sbt_version=_to_be_replaced +set sbt_default_mem=1024 +set default_sbt_opts= +set default_java_opts=-Dfile.encoding=UTF-8 +set sbt_jar= +set build_props_sbt_version= +set run_native_client= + +set sbt_args_print_version= +set sbt_args_print_sbt_version= +set sbt_args_print_sbt_script_version= +set sbt_args_verbose= +set sbt_args_debug= +set sbt_args_debug_inc= +set sbt_args_batch= +set sbt_args_color= +set sbt_args_no_colors= +set sbt_args_no_global= +set sbt_args_no_share= +set sbt_args_sbt_jar= +set sbt_args_ivy= +set sbt_args_supershell= +set sbt_args_timings= +set sbt_args_traces= +set sbt_args_sbt_create= +set sbt_args_sbt_dir= +set sbt_args_sbt_version= +set sbt_args_mem= +set sbt_args_client= + +rem users can set SBT_OPTS via .sbtopts +if exist .sbtopts for /F %%A in (.sbtopts) do ( + set _sbtopts_line=%%A + if not "!_sbtopts_line:~0,1!" == "#" ( + if "!_sbtopts_line:~0,2!" == "-J" ( + set _sbtopts_line=!_sbtopts_line:~2,1000! + ) + if defined _SBT_OPTS ( + set _SBT_OPTS=!_SBT_OPTS! !_sbtopts_line! + ) else ( + set _SBT_OPTS=!_sbtopts_line! + ) + ) +) + +rem TODO: remove/deprecate sbtconfig.txt and parse the sbtopts files + +rem FIRST we load the config file of extra options. +set SBT_CONFIG=!SBT_HOME!\conf\sbtconfig.txt +set SBT_CFG_OPTS= +for /F "tokens=* eol=# usebackq delims=" %%i in ("!SBT_CONFIG!") do ( + set DO_NOT_REUSE_ME=%%i + rem ZOMG (Part #2) WE use !! here to delay the expansion of + rem SBT_CFG_OPTS, otherwise it remains "" for this loop. + set SBT_CFG_OPTS=!SBT_CFG_OPTS! !DO_NOT_REUSE_ME! +) + +rem poor man's jenv (which is not available on Windows) +if defined JAVA_HOMES ( + if exist .java-version for /F %%A in (.java-version) do ( + set JAVA_HOME=%JAVA_HOMES%\%%A + set JDK_HOME=%JAVA_HOMES%\%%A + ) +) + +if exist "project\build.properties" ( + for /F "eol=# delims== tokens=1*" %%a in (project\build.properties) do ( + if "%%a" == "sbt.version" if not "%%b" == "" ( + set build_props_sbt_version=%%b + ) + ) +) + +rem must set PATH or wrong javac is used for java projects +if defined JAVA_HOME set "PATH=%JAVA_HOME%\bin;%PATH%" + +rem We use the value of the JAVACMD environment variable if defined +if defined JAVACMD set "_JAVACMD=%JAVACMD%" + +rem remove quotes +if defined _JAVACMD set _JAVACMD=!_JAVACMD:"=! + +if not defined _JAVACMD ( + if not "%JAVA_HOME%" == "" ( + if exist "%JAVA_HOME%\bin\java.exe" set "_JAVACMD=%JAVA_HOME%\bin\java.exe" + ) +) + +if not defined _JAVACMD set _JAVACMD=java + +rem users can set JAVA_OPTS via .jvmopts (sbt-extras style) +if exist .jvmopts for /F %%A in (.jvmopts) do ( + set _jvmopts_line=%%A + if not "!_jvmopts_line:~0,1!" == "#" ( + if defined _JAVA_OPTS ( + set _JAVA_OPTS=!_JAVA_OPTS! %%A + ) else ( + set _JAVA_OPTS=%%A + ) + ) +) + +rem We use the value of the JAVA_OPTS environment variable if defined, rather than the config. +if not defined _JAVA_OPTS if defined JAVA_OPTS set _JAVA_OPTS=%JAVA_OPTS% +if not defined _JAVA_OPTS if defined default_java_opts set _JAVA_OPTS=!default_java_opts! + +rem We use the value of the SBT_OPTS environment variable if defined, rather than the config. +if not defined _SBT_OPTS if defined SBT_OPTS set _SBT_OPTS=%SBT_OPTS% +if not defined _SBT_OPTS if defined SBT_CFG_OPTS set _SBT_OPTS=!SBT_CFG_OPTS! +if not defined _SBT_OPTS if defined default_sbt_opts set _SBT_OPTS=!default_sbt_opts! + +if defined SBT_NATIVE_CLIENT ( + if "%SBT_NATIVE_CLIENT%" == "true" ( + set sbt_args_client=1 + ) +) + +:args_loop +shift + +if "%~0" == "" goto args_end +set g=%~0 + +rem make sure the sbt_args_debug gets set first incase any argument parsing uses :dlog +if "%~0" == "-d" set _debug_arg=true +if "%~0" == "--debug" set _debug_arg=true + +if defined _debug_arg ( + set _debug_arg= + set sbt_args_debug=1 + set SBT_ARGS=-debug !SBT_ARGS! + goto args_loop +) + +if "%~0" == "-h" goto usage +if "%~0" == "-help" goto usage +if "%~0" == "--help" goto usage + +if "%~0" == "-v" set _verbose_arg=true +if "%~0" == "-verbose" set _verbose_arg=true +if "%~0" == "--verbose" set _verbose_arg=true + +if defined _verbose_arg ( + set _verbose_arg= + set sbt_args_verbose=1 + goto args_loop +) + +if "%~0" == "-V" set _version_arg=true +if "%~0" == "-version" set _version_arg=true +if "%~0" == "--version" set _version_arg=true + +if defined _version_arg ( + set _version_arg= + set sbt_args_print_version=1 + goto args_loop +) + +if "%~0" == "--client" set _client_arg=true + +if defined _client_arg ( + set _client_arg= + set sbt_args_client=1 + goto args_loop +) + +if "%~0" == "-batch" set _batch_arg=true +if "%~0" == "--batch" set _batch_arg=true + +if defined _batch_arg ( + set _batch_arg= + set sbt_args_batch=1 + goto args_loop +) + +if "%~0" == "-no-colors" set _no_colors_arg=true +if "%~0" == "--no-colors" set _no_colors_arg=true + +if defined _no_colors_arg ( + set _no_colors_arg= + set sbt_args_no_colors=1 + goto args_loop +) + +if "%~0" == "-no-global" set _no_global_arg=true +if "%~0" == "--no-global" set _no_global_arg=true + +if defined _no_global_arg ( + set _no_global_arg= + set sbt_args_no_global=1 + goto args_loop +) + +if "%~0" == "-traces" set _traces_arg=true +if "%~0" == "--traces" set _traces_arg=true + +if defined _traces_arg ( + set _traces_arg= + set sbt_args_traces=1 + goto args_loop +) + +if "%~0" == "-sbt-create" set _sbt_create_arg=true +if "%~0" == "--sbt-create" set _sbt_create_arg=true + +if defined _sbt_create_arg ( + set _sbt_create_arg= + set sbt_args_sbt_create=1 + goto args_loop +) + +if "%~0" == "-sbt-dir" set _sbt_dir_arg=true +if "%~0" == "--sbt-dir" set _sbt_dir_arg=true + +if defined _sbt_dir_arg ( + set _sbt_dir_arg= + if not "%~1" == "" ( + set sbt_args_sbt_dir=%1 + shift + goto args_loop + ) else ( + echo "%~0" is missing a value + goto error + ) +) + +if "%~0" == "-sbt-boot" set _sbt_boot_arg=true +if "%~0" == "--sbt-boot" set _sbt_boot_arg=true + +if defined _sbt_boot_arg ( + set _sbt_boot_arg= + if not "%~1" == "" ( + set sbt_args_sbt_boot=%1 + shift + goto args_loop + ) else ( + echo "%~0" is missing a value + goto error + ) +) + +if "%~0" == "-sbt-jar" set _sbt_jar=true +if "%~0" == "--sbt-jar" set _sbt_jar=true + +if defined _sbt_jar ( + set _sbt_jar= + if not "%~1" == "" ( + if exist "%~1" ( + set sbt_args_sbt_jar=%1 + shift + goto args_loop + ) else ( + echo %~1 does not exist + goto error + ) + ) else ( + echo "%~0" is missing a value + goto error + ) +) + +if "%~0" == "-ivy" set _sbt_ivy_arg=true +if "%~0" == "--ivy" set _sbt_ivy_arg=true + +if defined _sbt_ivy_arg ( + set _sbt_ivy_arg= + if not "%~1" == "" ( + set sbt_args_ivy=%1 + shift + goto args_loop + ) else ( + echo "%~0" is missing a value + goto error + ) +) + +if "%~0" == "-debug-inc" set _debug_inc_arg=true +if "%~0" == "--debug-inc" set _debug_inc_arg=true + +if defined _debug_inc_arg ( + set _debug_inc_arg= + set sbt_args_debug_inc=1 + goto args_loop +) + +if "%~0" == "--sbt-version" set _sbt_version_arg=true +if "%~0" == "-sbt-version" set _sbt_version_arg=true + +if defined _sbt_version_arg ( + set _sbt_version_arg= + if not "%~1" == "" ( + set sbt_args_sbt_version=%~1 + shift + goto args_loop + ) else ( + echo "%~0" is missing a value + goto error + ) +) + +if "%~0" == "--mem" set _sbt_mem_arg=true +if "%~0" == "-mem" set _sbt_mem_arg=true + +if defined _sbt_mem_arg ( + set _sbt_mem_arg= + if not "%~1" == "" ( + set sbt_args_mem=%~1 + shift + goto args_loop + ) else ( + echo "%~0" is missing a value + goto error + ) +) + +if "%~0" == "--supershell" set _supershell_arg=true +if "%~0" == "-supershell" set _supershell_arg=true + +if defined _supershell_arg ( + set _supershell_arg= + if not "%~1" == "" ( + set sbt_args_supershell=%~1 + shift + goto args_loop + ) else ( + echo "%~0" is missing a value + goto error + ) +) + +if "%~0" == "--color" set _color_arg=true +if "%~0" == "-color" set _color_arg=true + +if defined _color_arg ( + set _color_arg= + if not "%~1" == "" ( + set sbt_args_color=%~1 + shift + goto args_loop + ) else ( + echo "%~0" is missing a value + goto error + ) + goto args_loop +) + +if "%~0" == "--no-share" set _no_share_arg=true +if "%~0" == "-no-share" set _no_share_arg=true + +if defined _no_share_arg ( + set _no_share_arg= + set sbt_args_no_share=1 + goto args_loop +) + +if "%~0" == "--timings" set _timings_arg=true +if "%~0" == "-timings" set _timings_arg=true + +if defined _timings_arg ( + set _timings_arg= + set sbt_args_timings=1 + goto args_loop +) + +if "%~0" == "--script-version" ( + set sbt_args_print_sbt_script_version=1 + goto args_loop +) + +if "%~0" == "--numeric-version" ( + set sbt_args_print_sbt_version=1 + goto args_loop +) + +if "%~0" == "-jvm-debug" set _jvm_debug_arg=true +if "%~0" == "--jvm-debug" set _jvm_debug_arg=true + +if defined _jvm_debug_arg ( + set _jvm_debug_arg= + if not "%~1" == "" ( + set /a JVM_DEBUG_PORT=%~1 2>nul >nul + if !JVM_DEBUG_PORT! EQU 0 ( + rem next argument wasn't a port, set a default and process next arg + set /A JVM_DEBUG_PORT=5005 + goto args_loop + ) else ( + shift + goto args_loop + ) + ) +) + +if "%~0" == "-java-home" set _java_home_arg=true +if "%~0" == "--java-home" set _java_home_arg=true + +if defined _java_home_arg ( + set _java_home_arg= + if not "%~1" == "" ( + if exist "%~1\bin\java.exe" ( + set "_JAVACMD=%~1\bin\java.exe" + set "JAVA_HOME=%~1" + set "JDK_HOME=%~1" + shift + goto args_loop + ) else ( + echo Directory "%~1" for JAVA_HOME is not valid + goto error + ) + ) else ( + echo Second argument for --java-home missing + goto error + ) +) + +if "%~0" == "new" ( + if not defined SBT_ARGS ( + set sbt_new=true + ) +) + +if "%g:~0,2%" == "-D" ( + rem special handling for -D since '=' gets parsed away + for /F "tokens=1 delims==" %%a in ("%g%") do ( + rem make sure it doesn't have the '=' already + if "%g%" == "%%a" ( + if not "%~1" == "" ( + call :dlog [args_loop] -D argument %~0=%~1 + set "SBT_ARGS=!SBT_ARGS! %~0=%~1" + shift + goto args_loop + ) else ( + echo %g% is missing a value + goto error + ) + ) else ( + call :dlog [args_loop] -D argument %~0 + set "SBT_ARGS=!SBT_ARGS! %~0" + goto args_loop + ) + ) +) + +if not "%g:~0,5%" == "-XX:+" if not "%g:~0,5%" == "-XX:-" if "%g:~0,3%" == "-XX" ( + rem special handling for -XX since '=' gets parsed away + for /F "tokens=1 delims==" %%a in ("%g%") do ( + rem make sure it doesn't have the '=' already + if "%g%" == "%%a" ( + if not "%~1" == "" ( + call :dlog [args_loop] -XX argument %~0=%~1 + set "SBT_ARGS=!SBT_ARGS! %~0=%~1" + shift + goto args_loop + ) else ( + echo %g% is missing a value + goto error + ) + ) else ( + call :dlog [args_loop] -XX argument %~0 + set "SBT_ARGS=!SBT_ARGS! %~0" + goto args_loop + ) + ) +) + +rem the %0 (instead of %~0) preserves original argument quoting +set SBT_ARGS=!SBT_ARGS! %0 + +goto args_loop +:args_end + +rem Confirm a user's intent if the current directory does not look like an sbt +rem top-level directory and the "new" command was not given. + +if not defined sbt_args_sbt_create if not defined sbt_args_print_version if not defined sbt_args_print_sbt_version if not defined sbt_args_print_sbt_script_version if not exist build.sbt ( + if not exist project\ ( + if not defined sbt_new ( + echo [warn] Neither build.sbt nor a 'project' directory in the current directory: "%CD%" + setlocal +:confirm + echo c^) continue + echo q^) quit + + set /P reply=^? + if /I "!reply!" == "c" ( + goto confirm_end + ) else if /I "!reply!" == "q" ( + exit /B 1 + ) + + goto confirm +:confirm_end + endlocal + ) + ) +) + +call :process + +rem avoid bootstrapping/java version check for script version + +if !sbt_args_print_sbt_script_version! equ 1 ( + echo !init_sbt_version! + goto :eof +) + +if !run_native_client! equ 1 ( + goto :runnative !SBT_ARGS! + goto :eof +) + +call :checkjava + +if defined sbt_args_sbt_jar ( + set "sbt_jar=!sbt_args_sbt_jar!" +) else ( + set "sbt_jar=!SBT_HOME!\bin\sbt-launch.jar" +) + +set sbt_jar=!sbt_jar:"=! + +call :copyrt + +if defined JVM_DEBUG_PORT ( + set _JAVA_OPTS=!_JAVA_OPTS! -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=!JVM_DEBUG_PORT! +) + +call :sync_preloaded + +call :run !SBT_ARGS! + +if ERRORLEVEL 1 goto error +goto end + +:run + +rem set arguments + +if defined sbt_args_debug_inc ( + set _SBT_OPTS=-Dxsbt.inc.debug=true !_SBT_OPTS! +) + +if defined sbt_args_no_colors ( + set _SBT_OPTS=-Dsbt.log.noformat=true !_SBT_OPTS! +) + +if defined sbt_args_no_global ( + set _SBT_OPTS=-Dsbt.global.base=project/.sbtboot !_SBT_OPTS! +) + +if defined sbt_args_no_share ( + set _SBT_OPTS=-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy !_SBT_OPTS! +) + +if defined sbt_args_supershell ( + set _SBT_OPTS=-Dsbt.supershell=!sbt_args_supershell! !_SBT_OPTS! +) + +if defined sbt_args_sbt_version ( + set _SBT_OPTS=-Dsbt.version=!sbt_args_sbt_version! !_SBT_OPTS! +) + +if defined sbt_args_sbt_dir ( + set _SBT_OPTS=-Dsbt.global.base=!sbt_args_sbt_dir! !_SBT_OPTS! +) + +if defined sbt_args_sbt_boot ( + set _SBT_OPTS=-Dsbt.boot.directory=!sbt_args_sbt_boot! !_SBT_OPTS! +) + +if defined sbt_args_ivy ( + set _SBT_OPTS=-Dsbt.ivy.home=!sbt_args_ivy! !_SBT_OPTS! +) + +if defined sbt_args_color ( + set _SBT_OPTS=-Dsbt.color=!sbt_args_color! !_SBT_OPTS! +) + +if defined sbt_args_mem ( + call :addMemory !sbt_args_mem! +) else ( + call :addDefaultMemory +) + +if defined sbt_args_timings ( + set _SBT_OPTS=-Dsbt.task.timings=true -Dsbt.task.timings.on.shutdown=true !_SBT_OPTS! +) + +if defined sbt_args_traces ( + set _SBT_OPTS=-Dsbt.traces=true !_SBT_OPTS! +) + +rem TODO: _SBT_OPTS needs to be processed as args and diffed against SBT_ARGS + +if !sbt_args_print_sbt_version! equ 1 ( + call :set_sbt_version + echo !sbt_version! + goto :eof +) + +if !sbt_args_print_version! equ 1 ( + call :set_sbt_version + echo sbt version in this project: !sbt_version! + echo sbt script version: !init_sbt_version! + goto :eof +) + +if defined sbt_args_verbose ( + echo # Executing command line: + echo "!_JAVACMD!" + if defined _JAVA_OPTS ( call :echolist !_JAVA_OPTS! ) + if defined _SBT_OPTS ( call :echolist !_SBT_OPTS! ) + echo -cp + echo "!sbt_jar!" + echo xsbt.boot.Boot + if not "%~1" == "" ( call :echolist %* ) + echo. +) + +"!_JAVACMD!" !_JAVA_OPTS! !_SBT_OPTS! -cp "!sbt_jar!" xsbt.boot.Boot %* + +goto :eof + +:runnative + +set "_SBTNCMD=!SBT_BIN_DIR!sbtn-x86_64-pc-win32.exe" + +if defined sbt_args_verbose ( + echo # running native client + if not "%~1" == "" ( call :echolist %* ) + set "SBT_ARGS=-v !SBT_ARGS!" +) + +set "SBT_SCRIPT=!SBT_BIN_DIR: =%%20!sbt.bat" +set "SBT_ARGS=--sbt-script=!SBT_SCRIPT! %SBT_ARGS%" + +rem Microsoft Visual C++ 2010 SP1 Redistributable Package (x64) is required +rem https://www.microsoft.com/en-us/download/details.aspx?id=13523 +"!_SBTNCMD!" %SBT_ARGS% + +goto :eof + +rem for expression tries to interpret files, so simply loop over %* instead +rem fixes dealing with quotes after = args: -Dscala.ext.dirs="C:\Users\First Last\.sbt\0.13\java9-rt-ext-adoptopenjdk_11_0_3" +:echolist +rem call method is in first call of %0 +shift + +if "%~0" == "" goto echolist_end +set "p=%~0" + +if "%p:~0,2%" == "-D" ( + rem special handling for -D since '=' gets parsed away + for /F "tokens=1 delims==" %%a in ("%p%") do ( + rem make sure it doesn't have the '=' already + if "%p%" == "%%a" if not "%~1" == "" ( + echo %0=%1 + shift + goto echolist + ) + ) +) + +if not "%p:~0,5%" == "-XX:+" if not "%p:~0,5%" == "-XX:-" if "%p:~0,3%" == "-XX" ( + rem special handling for -XX since '=' gets parsed away + for /F "tokens=1 delims==" %%a in ("%p%") do ( + rem make sure it doesn't have the '=' already + if "%p%" == "%%a" if not "%~1" == "" ( + echo %0=%1 + shift + goto echolist + ) + ) +) + +if "%p:~0,14%" == "-agentlib:jdwp" ( + rem special handling for --jvm-debug since '=' and ',' gets parsed away + for /F "tokens=1 delims==" %%a in ("%p%") do ( + rem make sure it doesn't have the '=' already + if "%p%" == "%%a" if not "%~1" == "" if not "%~2" == "" if not "%~3" == "" if not "%~4" == "" if not "%~5" == "" if not "%~6" == "" if not "%~7" == "" if not "%~8" == "" ( + echo %0=%1=%2,%3=%4,%5=%6,%7=%8 + shift & shift & shift & shift & shift & shift & shift & shift + goto echolist + ) + ) +) + +echo %0 +goto echolist + +:echolist_end + +exit /B 0 + +:addJava + call :dlog [addJava] arg = '%*' + set "_JAVA_OPTS=!_JAVA_OPTS! %*" +exit /B 0 + +:addMemory + call :dlog [addMemory] arg = '%*' + + rem evict memory related options + set _new_java_opts= + set _old_java_opts=!_JAVA_OPTS! +:next_java_opt + if "!_old_java_opts!" == "" goto :done_java_opt + for /F "tokens=1,*" %%g in ("!_old_java_opts!") do ( + set "p=%%g" + if not "!p:~0,4!" == "-Xmx" if not "!p:~0,4!" == "-Xms" if not "!p:~0,4!" == "-Xss" if not "!p:~0,15!" == "-XX:MaxPermSize" if not "!p:~0,20!" == "-XX:MaxMetaspaceSize" if not "!p:~0,25!" == "-XX:ReservedCodeCacheSize" ( + set _new_java_opts=!_new_java_opts! %%g + ) + set "_old_java_opts=%%h" + ) + goto :next_java_opt +:done_java_opt + set _JAVA_OPTS=!_new_java_opts! + + set _new_sbt_opts= + set _old_sbt_opts=!_SBT_OPTS! +:next_sbt_opt + if "!_old_sbt_opts!" == "" goto :done_sbt_opt + for /F "tokens=1,*" %%g in ("!_old_sbt_opts!") do ( + set "p=%%g" + if not "!p:~0,4!" == "-Xmx" if not "!p:~0,4!" == "-Xms" if not "!p:~0,4!" == "-Xss" if not "!p:~0,15!" == "-XX:MaxPermSize" if not "!p:~0,20!" == "-XX:MaxMetaspaceSize" if not "!p:~0,25!" == "-XX:ReservedCodeCacheSize" ( + set _new_sbt_opts=!_new_sbt_opts! %%g + ) + set "_old_sbt_opts=%%h" + ) + goto :next_sbt_opt +:done_sbt_opt + set _SBT_OPTS=!_new_sbt_opts! + + rem a ham-fisted attempt to move some memory settings in concert + set mem=%1 + set /a codecache=!mem! / 8 + if !codecache! GEQ 512 set /a codecache=512 + if !codecache! LEQ 128 set /a codecache=128 + + set /a class_metadata_size=!codecache! * 2 + + call :addJava -Xms!mem!m + call :addJava -Xmx!mem!m + call :addJava -Xss4M + call :addJava -XX:ReservedCodeCacheSize=!codecache!m + + if /I !JAVA_VERSION! LSS 8 ( + call :addJava -XX:MaxPermSize=!class_metadata_size!m + ) + +exit /B 0 + +:addDefaultMemory + rem if we detect any of these settings in ${JAVA_OPTS} or ${JAVA_TOOL_OPTIONS} we need to NOT output our settings. + rem The reason is the Xms/Xmx, if they don't line up, cause errors. + + set _has_memory_args= + + if defined _JAVA_OPTS for /F %%g in ("!_JAVA_OPTS!") do ( + set "p=%%g" + if "!p:~0,4!" == "-Xmx" set _has_memory_args=1 + if "!p:~0,4!" == "-Xms" set _has_memory_args=1 + if "!p:~0,4!" == "-Xss" set _has_memory_args=1 + ) + + if defined JAVA_TOOL_OPTIONS for /F %%g in ("%JAVA_TOOL_OPTIONS%") do ( + set "p=%%g" + if "!p:~0,4!" == "-Xmx" set _has_memory_args=1 + if "!p:~0,4!" == "-Xms" set _has_memory_args=1 + if "!p:~0,4!" == "-Xss" set _has_memory_args=1 + ) + + if defined _SBT_OPTS for /F %%g in ("!_SBT_OPTS!") do ( + set "p=%%g" + if "!p:~0,4!" == "-Xmx" set _has_memory_args=1 + if "!p:~0,4!" == "-Xms" set _has_memory_args=1 + if "!p:~0,4!" == "-Xss" set _has_memory_args=1 + ) + + if not defined _has_memory_args ( + call :addMemory !sbt_default_mem! + ) +exit /B 0 + +:dlog + if defined sbt_args_debug ( + echo %* 1>&2 + ) +exit /B 0 + +:process +rem Parses x out of 1.x; for example 8 out of java version 1.8.0_xx +rem Otherwise, parses the major version; 9 out of java version 9-ea +set JAVA_VERSION=0 + +for /f "tokens=3 usebackq" %%g in (`CALL "!_JAVACMD!" -Xms32M -Xmx32M -version 2^>^&1 ^| findstr /i version`) do ( + set JAVA_VERSION=%%g +) + +rem removes all quotes from JAVA_VERSION +set JAVA_VERSION=!JAVA_VERSION:"=! + +for /f "delims=.-_ tokens=1-2" %%v in ("!JAVA_VERSION!") do ( + if /I "%%v" EQU "1" ( + set JAVA_VERSION=%%w + ) else ( + set JAVA_VERSION=%%v + ) +) + +rem parse the first two segments of sbt.version and set run_native_client to +rem 1 if the user has also indicated they want to use native client. +set sbtV=!build_props_sbt_version! +set sbtBinaryV_1= +set sbtBinaryV_2= +for /F "delims=.-_ tokens=1-2" %%v in ("!sbtV!") do ( + set sbtBinaryV_1=%%v + set sbtBinaryV_2=%%w +) +set native_client_ready= +if !sbtBinaryV_1! geq 2 ( + set native_client_ready=1 +) else ( + if !sbtBinaryV_1! geq 1 ( + if !sbtBinaryV_2! geq 4 ( + set native_client_ready=1 + ) + ) +) +if !native_client_ready! equ 1 ( + if !sbt_args_client! equ 1 ( + set run_native_client=1 + ) +) +set native_client_ready= + +exit /B 0 + +:checkjava +set /a required_version=6 +if /I !JAVA_VERSION! GEQ !required_version! ( + exit /B 0 +) +echo. +echo The Java Development Kit ^(JDK^) installation you have is not up to date. +echo sbt requires at least version !required_version!+, you have +echo version "!JAVA_VERSION!" +echo. +echo Please go to http://www.oracle.com/technetwork/java/javase/downloads/ and download +echo a valid JDK and install before running sbt. +echo. +exit /B 1 + +:copyrt +if /I !JAVA_VERSION! GEQ 9 ( + "!_JAVACMD!" !_JAVA_OPTS! !_SBT_OPTS! -jar "!sbt_jar!" --rt-ext-dir > "%TEMP%.\rtext.txt" + set /p java9_ext= < "%TEMP%.\rtext.txt" + set "java9_rt=!java9_ext!\rt.jar" + + if not exist "!java9_rt!" ( + mkdir "!java9_ext!" + "!_JAVACMD!" !_JAVA_OPTS! !_SBT_OPTS! -jar "!sbt_jar!" --export-rt "!java9_rt!" + ) + set _JAVA_OPTS=!_JAVA_OPTS! -Dscala.ext.dirs="!java9_ext!" +) +exit /B 0 + +:sync_preloaded +if not defined init_sbt_version ( + rem FIXME: better !init_sbt_version! detection + FOR /F "tokens=* usebackq" %%F IN (`dir /b "!SBT_HOME!\lib\local-preloaded\org\scala-sbt\sbt" /B`) DO ( + SET init_sbt_version=%%F + ) +) + +set PRELOAD_SBT_JAR="%UserProfile%\.sbt\preloaded\org\scala-sbt\sbt\!init_sbt_version!\" +if /I !JAVA_VERSION! GEQ 8 ( + where robocopy >nul 2>nul + if %ERRORLEVEL% EQU 0 ( + if not exist !PRELOAD_SBT_JAR! ( + if exist "!SBT_HOME!\lib\local-preloaded\" ( + robocopy "!SBT_HOME!\lib\local-preloaded" "%UserProfile%\.sbt\preloaded" /E >nul 2>nul + ) + ) + ) +) +exit /B 0 + +:usage + +for /f "tokens=3 usebackq" %%g in (`CALL "!_JAVACMD!" -Xms32M -Xmx32M -version 2^>^&1 ^| findstr /i version`) do ( + set FULL_JAVA_VERSION=%%g +) + +echo. +echo Usage: %~n0 [options] +echo. +echo -h ^| --help print this message +echo -v ^| --verbose this runner is chattier +echo -V ^| --version print sbt version information +echo --numeric-version print the numeric sbt version (sbt sbtVersion) +echo --script-version print the version of sbt script +echo -d ^| --debug set sbt log level to debug +echo -debug-inc ^| --debug-inc +echo enable extra debugging for the incremental debugger +echo --no-colors disable ANSI color codes +echo --color=auto^|always^|true^|false^|never +echo enable or disable ANSI color codes ^(sbt 1.3 and above^) +echo --supershell=auto^|always^|true^|false^|never +echo enable or disable supershell ^(sbt 1.3 and above^) +echo --traces generate Trace Event report on shutdown ^(sbt 1.3 and above^) +echo --timings display task timings report on shutdown +echo --sbt-create start sbt even if current directory contains no sbt project +echo --sbt-dir ^ path to global settings/plugins directory ^(default: ~/.sbt^) +echo --sbt-boot ^ path to shared boot directory ^(default: ~/.sbt/boot in 0.11 series^) +echo --ivy ^ path to local Ivy repository ^(default: ~/.ivy2^) +echo --mem ^ set memory options ^(default: %sbt_default_mem%^) +echo --no-share use all local caches; no sharing +echo --no-global uses global caches, but does not use global ~/.sbt directory. +echo --jvm-debug ^ enable on JVM debugging, open at the given port. +rem echo --batch disable interactive mode +echo. +echo # sbt version ^(default: from project/build.properties if present, else latest release^) +echo --sbt-version ^ use the specified version of sbt +echo --sbt-jar ^ use the specified jar as the sbt launcher +echo. +echo # java version ^(default: java from PATH, currently !FULL_JAVA_VERSION!^) +echo --java-home ^ alternate JAVA_HOME +echo. +echo # jvm options and output control +echo JAVA_OPTS environment variable, if unset uses "!default_java_opts!" +echo .jvmopts if this file exists in the current directory, its contents +echo are appended to JAVA_OPTS +echo SBT_OPTS environment variable, if unset uses "!default_sbt_opts!" +echo .sbtopts if this file exists in the current directory, its contents +echo are prepended to the runner args +echo !SBT_CONFIG! +echo if this file exists, it is prepended to the runner args +echo -Dkey=val pass -Dkey=val directly to the java runtime +rem echo -J-X pass option -X directly to the java runtime +rem echo ^(-J is stripped^) +rem echo -S-X add -X to sbt's scalacOptions ^(-S is stripped^) +echo. +echo In the case of duplicated or conflicting options, the order above +echo shows precedence: JAVA_OPTS lowest, command line options highest. +echo. + +@endlocal +exit /B 1 + +:set_sbt_version +rem set project sbtVersion +for /F "usebackq tokens=2" %%G in (`CALL "!_JAVACMD!" -jar "!sbt_jar!" "sbtVersion" 2^>^&1`) do set "sbt_version=%%G" +exit /B 0 + +:error +@endlocal +exit /B 1 + +:end +@endlocal +exit /B 0 diff --git a/launcher-package/src/universal/conf/sbtconfig.txt b/launcher-package/src/universal/conf/sbtconfig.txt new file mode 100755 index 000000000..9226e252d --- /dev/null +++ b/launcher-package/src/universal/conf/sbtconfig.txt @@ -0,0 +1,14 @@ +# sbt configuration file for Windows + +# Set the java args + +#-mem 1024 was added in sbt.bat as default + +#-Xms1024m +#-Xmx1024m +#-Xss4M +#-XX:ReservedCodeCacheSize=128m + +# Set the extra sbt options + +# -Dsbt.log.format=true diff --git a/launcher-package/src/universal/conf/sbtopts b/launcher-package/src/universal/conf/sbtopts new file mode 100644 index 000000000..c6f4e7bec --- /dev/null +++ b/launcher-package/src/universal/conf/sbtopts @@ -0,0 +1,49 @@ +# ------------------------------------------------ # +# The SBT Configuration file. # +# ------------------------------------------------ # + + +# Disable ANSI color codes +# +#-no-colors + +# Starts sbt even if the current directory contains no sbt project. +# +-sbt-create + +# Path to global settings/plugins directory (default: ~/.sbt) +# +#-sbt-dir /etc/sbt + +# Path to shared boot directory (default: ~/.sbt/boot in 0.11 series) +# +#-sbt-boot ~/.sbt/boot + +# Path to local Ivy repository (default: ~/.ivy2) +# +#-ivy ~/.ivy2 + +# set memory options +# +#-mem + +# Use local caches for projects, no sharing. +# +#-no-share + +# Put SBT in offline mode. +# +#-offline + +# Sets the SBT version to use. +#-sbt-version 0.11.3 + +# Scala version (default: latest release) +# +#-scala-home +#-scala-version + +# java version (default: java from PATH, currently $(java -version |& grep version)) +# +#-java-home + diff --git a/launcher-package/src/windows/License.rtf b/launcher-package/src/windows/License.rtf new file mode 100644 index 000000000..f0b3562a1 Binary files /dev/null and b/launcher-package/src/windows/License.rtf differ diff --git a/launcher-package/src/windows/sbt b/launcher-package/src/windows/sbt new file mode 100644 index 000000000..ce24abbdd --- /dev/null +++ b/launcher-package/src/windows/sbt @@ -0,0 +1,38 @@ +#!/bin/sh +# sbt launcher script for Cygwin and MSYS +# +# Environment: +# JAVA_HOME - location of a JDK home dir (mandatory) +# SBT_OPTS - JVM options (optional) +# Configuration: +# sbtconfig.txt found in the SBT_HOME. + +if [ -z "$JAVA_HOME" ]; then +JAVA_CMD=java +else +JAVA_CMD=$JAVA_HOME/bin/java +fi + +UDIR=`dirname "$0"` +if [ -z "$MSYSTEM" ]; then + WDIR=`cygpath -alm "$UDIR"` +else + WDIR=`echo "$UDIR" | sed -e 's~^/\([^/]*\)/~\1:/~'` +fi + +if [ -z "$JAVA_OPTS" ]; then + JAVA_OPTS=$(cat "$WDIR/sbtconfig.txt" | sed -e 's/\r//g' -e 's/^#.*$//g' | sed ':a;N;$!ba;s/\n/ /g') +fi + +# TODO - this check should detect cygwin terminal, not just xterm. +if [ "_$TERM" = "_xterm" ]; then + # Let the terminal handle ANSI sequences + stty -icanon min 1 -echo > /dev/null 2>&1 + "$JAVA_CMD" $JAVA_OPTS -Djline.terminal=jline.UnixTerminal -Dsbt.cygwin=true $SBT_OPTS -jar "$WDIR/sbt-launch.jar" "$@" + SCALA_STATUS=$? + stty icanon echo > /dev/null 2>&1 + exit $SCALA_STATUS +else + # Use Jansi to intercept ANSI sequences + "$JAVA_CMD" -Dsbt.log.format=true $JAVA_OPTS $SBT_OPTS -cp "$WDIR/sbt-launch.jar" xsbt.boot.Boot "$@" +fi diff --git a/launcher-package/upload/sbt-upload.sh b/launcher-package/upload/sbt-upload.sh new file mode 100755 index 000000000..cf4b53c83 --- /dev/null +++ b/launcher-package/upload/sbt-upload.sh @@ -0,0 +1,319 @@ +#!/usr/bin/env bash + +# +# This script will upload an sbt distribution (tar/tgz/msi and +# checksump files) to IBM Cloud Object Storage with the correct +# permissions, and prepare the shortened URLs on the "piccolo.link" +# Polr server. +# + +# +# Required env vars: +# +# SBT_DEPLOY_S3KEY -- API Key for the sbt-distribution-archive bucket +# SBT_DEPLOY_S3SECRET -- Secret corresponding to the API Key +# +# POLR_USERNAME -- Username for the polr shortener +# POLR_PASSWORD -- Password for the polr shortener +# +# These environment vars can be either defined by the caller, or directly +# in the following lines, in place of the "your..." constants below. +# +SBT_DEPLOY_S3KEY="${SBT_DEPLOY_S3KEY:-yourSbtDeployS3key}" +SBT_DEPLOY_S3SECRET="${SBT_DEPLOY_S3SECRET:-yourSbtDeployS3secret}" +POLR_USERNAME="${POLR_USERNAME:-yourPolrUsername}" +POLR_PASSWORD="${POLR_PASSWORD:-yourPolrPassword}" + +# Where to find the above information: +# - for SBT_DEPLOY_S3KEY/SBT_DEPLOY_S3SECRET, you need the API credentials for the +# 'sbt-downloads' buckets on IBM's COS. Please ask @cunei for +# details. +# +# Once you have SBT_DEPLOY_S3KEY/SBT_DEPLOY_S3SECRET, you can (and should) use those +# values also in your favorite S3 GUI browser (like CyberDuck +# or DragonDisk), in order to verify that the files are correctly +# uploaded, or to perform maintenance within the storage bucket. +# +# Note: do *not* use an S3 GUI to upload files, since that will set +# the permissions incorrectly, and the CDN will be unable to access +# the files. +# +# - for POLR_USERNAME/POLR_PASSWORD, you will need the credentials +# on "piccolo.link" for the user "lightbend-tools". Ask @cunei +# for details. Note: do not create a personal account to generate +# the short URLs: they are linked to the user that creates them +# and they would not appear in the "lightbend-tools" dashboard. +# + +# Once you have set correctly the four vars above, please call +# this script as follows: +# +# If your files have this directory structure: +# +# here +# here/v0.13.15 +# here/v0.13.15/sbt-0.13.15.msi +# here/v0.13.15/sbt-0.13.15.zip.asc +# here/v0.13.15/sbt-0.13.15.tgz.asc +# here/v0.13.15/sbt-0.13.15.tgz +# here/v0.13.15/sbt-0.13.15.zip +# here/v0.13.15/sbt-0.13.15.msi.asc +# +# +# then "cd here", then: +# +# $ .../sbt-upload.sh v0.13.15 [v0.13.16 ...] +# +# Where one or more directories are specified. Each directory name +# must be the letter 'v' plus a release version. The files within +# each directory can be named arbitrarily, and live in any +# subdirectory. +# +# Please note that the script will try to store temporary cookies +# and other info in a directory called "cookies", which will be +# created in the same directory as this script. +# +#-------------------------------------------------------------- +# +set -o pipefail + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +usageAndExit() { + echo 'Usage: "'${0##*/}' [ ...]"' + echo "where the final path component of each RELEASEDIR" + echo "must be 'v' plus"' a release number, for example: "v0.13.15".' + echo 'This script will leave some cookie files in a directory' + echo 'called "cookies", in the same location as the script.' + exit 1 +} + + +if [[ "$#" < 1 ]] +then + usageAndExit +fi +RELEASE_DIRS=("${@:1}") +for RELEASE_DIR in "${RELEASE_DIRS[@]}" +do + RELEASE_TAG="$(basename "$RELEASE_DIR")" + if [[ "${RELEASE_TAG:0:1}" != 'v' || ! -d "${RELEASE_DIR}" ]] + then + usageAndExit + fi +done + + +POLR_DOMAIN_NAME="${POLR_DOMAIN_NAME:-piccolo.link}" +POLR_URL="https://$POLR_DOMAIN_NAME" +POLR_LOGIN_URL="$POLR_URL/login" +POLR_SHORTEN_URL="$POLR_URL/shorten" + +# Let's try to login into Polr + +# Prepare a work directory to store the cookies into +COOKIEDIR="${SCRIPTDIR}/cookies" +mkdir -p "${COOKIEDIR}" +HOME_COOKIES_PATH="${COOKIEDIR}/home-cookies" +LOGIN_COOKIES_PATH="${COOKIEDIR}/login-cookies" +CSRF_TOKEN_PATH="${COOKIEDIR}/csrf-token" + +# Access home page to get tokens. +RESPONSE="$( + curl -si "$POLR_URL" \ + --cookie-jar "$HOME_COOKIES_PATH" +)" +CODE="$(echo "$RESPONSE" | head -1 | awk '{print $2}')" +if [ -z "$CODE" -o "$CODE" -ne 200 ] +then + echo "ERROR: Could not load $POLR_URL; aborting." + echo -e "Response:\n$RESPONSE" + exit 1 +fi + +TOKEN="$(echo "$RESPONSE" | grep "_token" | \ + sed -E "s/.+value='([a-zA-Z0-9]+)'.+/\1/")" +if [ -z "$TOKEN" ] +then + echo "ERROR: Could not find auth token; aborting." + exit 1 +fi + +# Save CSRF token. +CSRF_TOKEN="$(echo "$RESPONSE" | grep "csrf-token" | \ + sed -E 's/.+content="([a-zA-Z0-9]+)".+/\1/')" +if [ -z "$CSRF_TOKEN" ] +then + echo "ERROR: Could not find CSRF token; aborting." + exit 1 +fi + +echo "$CSRF_TOKEN" > "$CSRF_TOKEN_PATH" + +# Login and save cookie. +RESPONSE="$( + curl -siX POST "$POLR_LOGIN_URL" \ + --cookie "$HOME_COOKIES_PATH" \ + --cookie-jar "$LOGIN_COOKIES_PATH" \ + -d "username=$POLR_USERNAME" \ + -d "password=$POLR_PASSWORD" \ + -d "_token=$TOKEN" \ + -d "login='Sign In'" +)" +CODE="$(echo "$RESPONSE" | head -1 | awk '{print $2}')" +if [ -z "$CODE" -o "$CODE" -ne 302 ] +then + echo "ERROR: Login failed; aborting." + echo -e "Response:\n$RESPONSE" + exit 1 +fi + +# +# OK! Now, let's process the files +# + +# +# Call this upload routine with: +# 1) relative path to the file from current dir. Do NOT use '..' or '.' in this path +# for example: uploadS3 "v1.1.4/sbt-1.1.4.tgz" +# assuming the file is in /v1.1.4/sbt-1.1.4.tgz +# +# The file will be placed in the appropriate S3 bucket, and will be visible +# as: https://sbt-downloads.cdnedge.bluemix.net/releases/v1.1.4/sbt-1.1.4.tgz +# +uploadS3() { + S3FILEPATH="$1" + if [[ -z "$S3FILEPATH" ]] + then + echo "Internal error! Please report." + return 1 + fi + S3BUCKET="sbt-distribution-archives" + S3HOST="s3-api.us-geo.objectstorage.softlayer.net" + S3RELATIVEPATH="/${S3BUCKET}/releases/${S3FILEPATH}" + S3CONTENTTYPE="$(file --brief --mime-type "$S3FILEPATH")" + S3NOWDATE="$(date -R)" + S3STRINGTOSIGN="PUT\n\n${S3CONTENTTYPE}\n${S3NOWDATE}\nx-amz-acl:public-read\n${S3RELATIVEPATH}" + S3SIGNATURE="$(echo -en ${S3STRINGTOSIGN} | openssl sha1 -hmac ${SBT_DEPLOY_S3SECRET} -binary | base64)" + echo "Uploading $S3FILEPATH" + curl -X PUT -T "${S3FILEPATH}" \ + -H "Host: ${S3HOST}" \ + -H "Date: ${S3NOWDATE}" \ + -H "Content-Type: ${S3CONTENTTYPE}" \ + -H "x-amz-acl: public-read" \ + -H "Authorization: AWS ${SBT_DEPLOY_S3KEY}:${S3SIGNATURE}" \ + "https://${S3HOST}${S3RELATIVEPATH}" + if [ $? -ne 0 ] + then + echo "Sorry, could not upload this file." + return 1 + fi +} + +shorten() { + # Arguments + URL="$1" + ENDING="$2" + TAGS=("${@:3}") + +# echo "Short requested: ${ENDING} -- tags: ${TAGS[@]}" +# Retrieve the CSRF token +# CSRF_TOKEN="$(cat "$CSRF_TOKEN_PATH")" + + TAG_LIST="" + if [ "${#TAGS[@]}" -gt 0 ]; then + for TAG in "${TAGS[@]}"; do # tag_list%5B%5D=$TAG + TAG_LIST="$TAG_LIST -d tag_list[]=$TAG" + done + fi + + RESPONSE="$( + curl -siX POST "$POLR_SHORTEN_URL" \ + --cookie "$LOGIN_COOKIES_PATH" \ + -H "X-CSRF-TOKEN: $CSRF_TOKEN" \ + --data-urlencode "link-url=$URL" \ + -d "custom-ending=$ENDING" \ + $TAG_LIST + )" + + CODE="$(echo "$RESPONSE" | head -n 1 | awk '{print $2}')" + if [ "$CODE" -ne 200 ] + then + echo "Could not shorten to: '$ENDING', creating a different link." + return 1 + fi + + SHORT_URL="$( + echo "$RESPONSE" | \ + egrep "value='http[s]?://$POLR_DOMAIN_NAME/" | \ + sed -E "s@.+value='(http[s]?://$POLR_DOMAIN_NAME/[-_\.0-9a-zA-Z]+)'.+@\1@" + )" + + echo "--> $SHORT_URL" | sed 's@http://@https://@' +} + +for RELEASE_DIR in "${RELEASE_DIRS[@]}" +do + RELEASE_TAG="$(basename "$RELEASE_DIR")" + cd "$(dirname "$RELEASE_DIR")" + find "$RELEASE_TAG" -type f -print | while read + do + echo + uploadS3 "$REPLY" + if [ $? -eq 0 ] + then + URL="https://sbt-downloads.cdnedge.bluemix.net/releases/${REPLY}" + echo "--> $URL" + # + # let's calculate the Polr tags. + # + # In Polr the tags can only be combined with 'or', therefore + # we need to subdivide them according to the combinations that + # might be needed. + # + # We add these tags: + # - if it is an 'asc' checksum file, "${TAG}_asc", "asc" + # - if it is a tar/tgz/tar.gz/txz/tar.xz/zip/msi/deb/rpm/dmg, "${TAG}_bin", "bin" + # - if it is another file, "${TAG}_other", "other" + # In any case, we add "${TAG}" + # + # so that: + # 1) "${TAG}_asc" + "${TAG}_bin" + "${TAG}_other" are the statistics + # for the entire "${TAG}" release + # 2) "bin" are all of the archives downloads + # 3) "bin" + "asc" + "other" are all of the downloads + # + if [[ "${REPLY}" == *.asc ]] + then + KIND="asc" + elif [[ "${REPLY}" == *.tar || \ + "${REPLY}" == *.tgz || \ + "${REPLY}" == *.tar.gz || \ + "${REPLY}" == *.txz || \ + "${REPLY}" == *.tar.xz || \ + "${REPLY}" == *.zip || \ + "${REPLY}" == *.msi || \ + "${REPLY}" == *.deb || \ + "${REPLY}" == *.rpm || \ + "${REPLY}" == *.dmg ]] + then + KIND="bin" + else + KIND="other" + fi + # First we try to shorten using just the file name. + # If that fails, we use an encoding of the entire path. + SHORT1="$(basename "$REPLY")" + shorten "$URL" "$SHORT1" "${RELEASE_TAG}_${KIND}" "${RELEASE_TAG}" "${KIND}" + if [[ $? != 0 ]] + then + SHORT2="${REPLY//\//_}" + shorten "$URL" "$SHORT2" "${RELEASE_TAG}_${KIND}" "${RELEASE_TAG}" "${KIND}" + if [[ $? != 0 ]] + then + echo "Sorry, could not create a short link." + fi + fi + fi + done +done