pax_global_header00006660000000000000000000000064152256102720014514gustar00rootroot0000000000000052 comment=95963d77b383c65873a804973eeb502f79e1838c sbt-launcher-ebbd426/000077500000000000000000000000001522561027200145755ustar00rootroot00000000000000sbt-launcher-ebbd426/.github/000077500000000000000000000000001522561027200161355ustar00rootroot00000000000000sbt-launcher-ebbd426/.github/ISSUE_TEMPLATE/000077500000000000000000000000001522561027200203205ustar00rootroot00000000000000sbt-launcher-ebbd426/.github/ISSUE_TEMPLATE/no-new-issues.md000066400000000000000000000004121522561027200233530ustar00rootroot00000000000000--- name: No new issues about: Use https://github.com/sbt/sbt/issues/new/choose instead title: '' labels: '' assignees: '' --- No new issues should be opened against this repo. Please use https://github.com/sbt/sbt/issues/new/choose to file an issue against sbt. sbt-launcher-ebbd426/.github/dependabot.yml000066400000000000000000000001661522561027200207700ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" sbt-launcher-ebbd426/.github/workflows/000077500000000000000000000000001522561027200201725ustar00rootroot00000000000000sbt-launcher-ebbd426/.github/workflows/ci.yml000066400000000000000000000017361522561027200213170ustar00rootroot00000000000000name: CI on: pull_request: push: jobs: test: strategy: fail-fast: false matrix: include: - os: ubuntu-latest java: 8 distribution: zulu runs-on: ${{ matrix.os }} env: # define Java options for both official sbt and sbt-extras JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 JVM_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 steps: - name: Checkout uses: actions/checkout@v7 - name: Setup JDK uses: actions/setup-java@v5 with: distribution: "${{ matrix.distribution }}" java-version: "${{ matrix.java }}" cache: sbt - name: Setup sbt uses: sbt/setup-sbt@v1 - name: Build and test run: | sbt -v "mimaReportBinaryIssues; scalafmtSbtCheck; scalafmtCheckAll; packageBin; shadedPackageBin; test;" ci-test/test.sh shell: bash sbt-launcher-ebbd426/.gitignore000066400000000000000000000000221522561027200165570ustar00rootroot00000000000000target metals.sbt sbt-launcher-ebbd426/.java-version000066400000000000000000000000041522561027200171740ustar00rootroot000000000000001.8 sbt-launcher-ebbd426/.scalafmt.conf000066400000000000000000000017411522561027200173170ustar00rootroot00000000000000version = 3.9.9 runner.dialect = scala3 maxColumn = 100 project.git = true lineEndings = preserve fileOverride { "glob:**/project/**" { runner.dialect = scala212source3 } "glob:**/*.sbt" { runner.dialect = scala212source3 } } # https://docs.scala-lang.org/style/scaladoc.html recommends the JavaDoc style. # scala/scala is written that way too https://github.com/scala/scala/blob/v2.12.2/src/library/scala/Predef.scala docstrings.style = Asterisk docstrings.wrap = false # This also seems more idiomatic to include whitespace in import x.{ yyy } spaces.inImportCurlyBraces = true # This is more idiomatic Scala. # https://docs.scala-lang.org/style/indentation.html#methods-with-numerous-arguments align.openParenCallSite = false align.openParenDefnSite = false # For better code clarity danglingParentheses.preset = true trailingCommas = preserve rewrite.scala3.convertToNewSyntax = true rewrite.scala3.newSyntax.control = true rewrite.scala3.removeOptionalBraces = yes sbt-launcher-ebbd426/LICENSE000066400000000000000000000261351522561027200156110ustar00rootroot00000000000000 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.sbt-launcher-ebbd426/NOTICE000066400000000000000000000027071522561027200155070ustar00rootroot00000000000000sbt launcher Copyright 2011 - 2019 Lightbend, Inc. Copyright 2008, 2009, 2010 Mark Harrah, David MacIver Licensed under Apache v2 license (see LICENSE) Classes from the Scala library are distributed with the launcher. Copyright 2002-2013 EPFL, Lausanne Licensed under BSD-style license (see licenses/LICENSE_Scala) Classes from Apache Ivy, licensed under the Apache License, Version 2.0 (see licenses/LICENSE_Apache) are distributed with the launcher. It requires the following notice: 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. sbt-launcher-ebbd426/README.md000066400000000000000000000041151522561027200160550ustar00rootroot00000000000000# The sbt launcher module This project is the componetized sbt launcher. It can be used to launch many Maven/Ivy deployed applications and utilities, and forms the basis of [sbt](https://github.com/sbt/sbt) and [conscript](https://github.com/foundweekends/conscript)'s launching abilities. For the full set of documentation, read: . ## Rebundling This project provides two modules for general use: 1. A library for interacting with Launcher features as a launched application, or for defining a launched Server. 2. A minimal JAR that can be used to lookup your application, using Ivy, and load/run it. This minimal JAR file is designed to be: * Less than 1MB in size * Able to launch any application on the JVM * Isolate classloaders and allow re-use of Scala library classloader for Scala applications. * Rebundled as a "wrapper" or "launcher" for your specific project. To rebundle the JAR for your project, first you'll need a launcher properties file (specified [here](https://www.scala-sbt.org/1.x/docs/Launcher-Configuration.html)). You can test your launch configuration file by running: ``` java -jar @ ``` Once you've verified your properties file is complete you can inject your launcher properties file into the "launch jar" as the file `sbt/sbt.boot.properties`. The launcher will look for this file in lieu of any command-line arguments to launch an application. If you've not done this correctly, you will see the following: ``` $ java -jar target/sbt-launch-1.0.0-SNAPSHOT.jar Error during sbt execution: Could not finder sbt launch configuration. Searched classpath for: /sbt.boot.properties0.13.7 /sbt/sbt.boot.properties0.13.7 /sbt.boot.properties0.13 /sbt/sbt.boot.properties0.13 /sbt.boot.properties /sbt/sbt.boot.properties ``` Additionally, we recommend renaming your bundled launch jar for your application (e.g. activator calls theirs "activator-launch-.jar"). ## building ``` sbt -J-XX:MaxPermSize=256M ``` # License This software is under Apache 2.0 license. sbt-launcher-ebbd426/build.sbt000066400000000000000000000203021522561027200164030ustar00rootroot00000000000000import Deps.* import Util.* import com.typesafe.tools.mima.core.*, ProblemFilters.* lazy val keepFullClasses = settingKey[Seq[String]]( "Fully qualified names of classes that proguard should preserve the non-private API of." ) Global / onChangedBuildSource := ReloadOnSourceChanges ThisBuild / dynverSonatypeSnapshots := true ThisBuild / version := { val orig = (ThisBuild / version).value if (orig.endsWith("-SNAPSHOT")) "1.4.0-SNAPSHOT" else orig } ThisBuild / description := "Standalone launcher for maven/ivy deployed projects" ThisBuild / scalaVersion := "3.7.2" ThisBuild / publishMavenStyle := true ThisBuild / crossPaths := false ThisBuild / testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1") ThisBuild / scalafmtOnCompile := !(Global / insideCI).value ThisBuild / Test / scalafmtOnCompile := !(Global / insideCI).value lazy val root = (project in file(".")) .enablePlugins(ShadingPlugin) .aggregate(launchInterfaceSub, launchSub, testSamples) .settings(javaOnly ++ Util.commonSettings("launcher") ++ Release.settings) .settings(nocomma { mimaPreviousArtifacts := Set.empty Compile / packageBin := (launchSub / Proguard / proguard).value.head Compile / packageSrc := (launchSub / Compile / packageSrc).value Compile / packageDoc := (launchSub / Compile / packageDoc).value commands += Command.command("release") { state => "clean" :: "test" :: "publishSigned" :: state } validNamespaces ++= Set("xsbt", "xsbti", "scala", "org.apache.ivy", "org.fusesource.jansi") validEntries ++= Set("LICENSE", "NOTICE", "module.properties") shadingRules ++= { Seq( "coursier", "concurrentrefhashmap", "dependency", "org.fusesource", "macrocompat", "io.github.alexarchambault.windowsansi", "com.google.common", "org.apache.commons", "org.apache.xbean", "org.codehaus", "org.iq80", "org.tukaani", "com.github.plokhotnyuk.jsoniter_scala", "scala.cli", "com.github.luben.zstd", ).map(ShadingRule.moveUnder(_, "xsbt.boot.internal.shaded")) } headerLicense := (ThisBuild / headerLicense).value }) // the launcher is published with metadata so that the scripted plugin can pull it in // being proguarded, it shouldn't ever be on a classpath with other jars, however def proguardedLauncherSettings = Seq( packageSrc / publishArtifact := false, moduleName := "sbt-launch", autoScalaLibrary := false, description := "sbt application launcher" ) def launchSettings = inConfig(Compile)(Transform.configSettings) ++ inConfig(Compile)( Transform.transSourceSettings ++ Seq( // TODO - these should be shared between sbt core + sbt-launcher... Transform.inputSourceDirectory := sourceDirectory.value / "input_sources", Transform.sourceProperties := Map("cross.package0" -> "xsbt", "cross.package1" -> "boot") ) ) // The interface JAR for projects which want to be launched by sbt. lazy val launchInterfaceSub = (project in file("launcher-interface")) .settings(javaOnly) .settings(nocomma { name := "Launcher Interface" Compile / resourceGenerators += Def.task { generateVersionFile("sbt.launcher.version.properties")( version.value, resourceManaged.value, streams.value, (Compile / compile).value ) }.taskValue description := "Interfaces for launching projects with the sbt launcher" mimaPreviousArtifacts := Set(organization.value % moduleName.value % "1.0.1") mimaBinaryIssueFilters ++= Seq( exclude[ReversedMissingMethodProblem]("xsbti.MavenRepository.allowInsecureProtocol"), exclude[ReversedMissingMethodProblem]("xsbti.IvyRepository.allowInsecureProtocol") ) exportJars := true headerLicense := (ThisBuild / headerLicense).value }) .settings(Release.settings) // the launcher. Retrieves, loads, and runs applications based on a configuration file. // TODO - move into a directory called "launcher-impl or something." lazy val launchSub = (project in file("launcher-implementation")) .enablePlugins(SbtProguard) .dependsOn(launchInterfaceSub) .settings(Util.base) .settings(launchSettings) .settings(nocomma { name := "Launcher Implementation" publish / skip := true exportJars := true libraryDependencies ++= Seq( coursier, verify % Test, sbtIo % Test, scalacheck % Test, junit % Test, ) testFrameworks += new TestFramework("verify.runner.Framework") Test / compile := { val ignore = (testSamples / publishLocal).value val ignore2 = (launchInterfaceSub / publishLocal).value (Test / compile).value } Proguard / proguardOptions ++= Seq( "-keep,allowshrinking class * { *; }", // no obfuscation "-keepattributes SourceFile,LineNumberTable,Signature,InnerClasses,EnclosingMethod", // preserve debugging information "-dontobfuscate", "-dontnote", "-dontwarn org.apache.ivy.**", "-dontwarn scala.runtime.Statics", "-ignorewarnings" ) keepFullClasses := "xsbti.**" :: Nil Proguard / proguard / javaOptions := Seq("-Xmx2G") Proguard / proguardOptions ++= keepFullClasses.value map ("-keep public class " + _ + " {\n\tpublic protected * ;\n}") Proguard / proguardInputFilter := { file => file.name match { case x if x.startsWith("scala-library") => Some(libraryFilter) case x if x.startsWith("ivy-2.3.0") => Some(ivyFilter) case x if x.startsWith("launcher-implementation") => None case x if x.startsWith("jsoniter-scala-core") => None case _ => Some(generalFilter) } } Proguard / proguardOptions += ProguardOptions.keepMain("xsbt.boot.Boot") mimaPreviousArtifacts := Set.empty headerLicense := (ThisBuild / headerLicense).value }) def generalFilter = List( "!META-INF/**", "!*.properties", "!**/*.tasty" ).mkString(",") def libraryFilter = List( "**/*.class", "!META-INF/**", "!*.properties", "!scala/util/parsing/**", "!rootdoc.txt" ).mkString(",") def ivyFilter = { def excludeString(s: List[String]) = s.map("!" + _).mkString(",") val ivyResources = "META-INF/**" :: "fr/**" :: "**/antlib.xml" :: "**/*.png" :: "org/apache/ivy/core/settings/ivyconf*.xml" :: "org/apache/ivy/core/settings/ivysettings-*.xml" :: "org/apache/ivy/plugins/resolver/packager/*" :: "**/ivy_vfs.xml" :: "org/apache/ivy/plugins/report/ivy-report-*" :: "org/apache/ivy/ant/**" :: Nil excludeString(ivyResources) } // used to test the retrieving and loading of an application: sample app is packaged and published to the local repository lazy val testSamples = (project in file("test-sample")) .dependsOn(launchInterfaceSub) .settings(Release.javaVersionCheckSettings) .settings(Util.baseScalacOptions) .settings(nocomma { name := "Launch Test" mimaFailOnNoPrevious := false publish := { () } publishSigned := { () } headerLicense := (ThisBuild / headerLicense).value }) ThisBuild / organization := "org.scala-sbt" ThisBuild / pomIncludeRepository := { x => false } ThisBuild / homepage := Some(url("https://scala-sbt.org")) ThisBuild / licenses += "Apache-2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0.txt") ThisBuild / scmInfo := Some( ScmInfo( browseUrl = url("https://github.com/sbt/launcher"), connection = "scm:git@github.com:sbt/launcher.git" ) ) ThisBuild / developers := List( Developer("eed3si9n", "Eugene Yokota", "@eed3si9n", url("https://github.com/eed3si9n")), Developer("jsuereth", "Josh Suereth", "@jsuereth", url("https://github.com/jsuereth")), Developer("dwijnand", "Dale Wijnand", "@dwijnand", url("https://github.com/dwijnand")) ) ThisBuild / publishTo := { val centralSnapshots = "https://central.sonatype.com/repository/maven-snapshots/" val v = (ThisBuild / version).value if (v.endsWith("SNAPSHOT")) Some("central-snapshots" at centralSnapshots) else localStaging.value } ThisBuild / headerLicense := Some( HeaderLicense.Custom( """|sbt |Licensed under Apache License 2.0 (see LICENSE) |""".stripMargin ) ) sbt-launcher-ebbd426/ci-test/000077500000000000000000000000001522561027200161455ustar00rootroot00000000000000sbt-launcher-ebbd426/ci-test/app0/000077500000000000000000000000001522561027200170055ustar00rootroot00000000000000sbt-launcher-ebbd426/ci-test/app0/build.sbt000066400000000000000000000000001522561027200206040ustar00rootroot00000000000000sbt-launcher-ebbd426/ci-test/app0/sbt.1.3.13.boot.properties000066400000000000000000000027511522561027200234040ustar00rootroot00000000000000[scala] version: ${sbt.scala.version-auto} [app] org: ${sbt.organization-org.scala-sbt} name: sbt version: ${sbt.version-read(sbt.version)[1.3.13]} class: ${sbt.main.class-sbt.xMain} components: xsbti,extra cross-versioned: ${sbt.cross.versioned-false} resources: ${sbt.extraClasspath-} [repositories] local local-preloaded-ivy: file:///${sbt.preloaded-${sbt.global.base-${user.home}/.sbt}/preloaded/}, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext] local-preloaded: file:///${sbt.preloaded-${sbt.global.base-${user.home}/.sbt}/preloaded/} maven-central sbt-maven-releases: https://repo.scala-sbt.org/scalasbt/maven-releases/, bootOnly sbt-maven-snapshots: https://repo.scala-sbt.org/scalasbt/maven-snapshots/, bootOnly typesafe-ivy-releases: https://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly sbt-ivy-snapshots: https://repo.scala-sbt.org/scalasbt/ivy-snapshots/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly [boot] directory: /tmp/boot0/ lock: ${sbt.boot.lock-true} [ivy] ivy-home: ${sbt.ivy.home-${user.home}/.ivy2/} checksums: ${sbt.checksums-sha1,md5} repository-override: ${sbt.repository.override-${sbt.global.base-${user.home}/.sbt}/repositories_force} override-build-repos: ${sbt.override.build.repos-false} repository-config: ${sbt.repository.config-${sbt.global.base-${user.home}/.sbt}/repositories} sbt-launcher-ebbd426/ci-test/app1/000077500000000000000000000000001522561027200170065ustar00rootroot00000000000000sbt-launcher-ebbd426/ci-test/app1/build.sbt000066400000000000000000000000001522561027200206050ustar00rootroot00000000000000sbt-launcher-ebbd426/ci-test/app1/sbt.0.13.18.boot.properties000066400000000000000000000027521522561027200234730ustar00rootroot00000000000000[scala] version: ${sbt.scala.version-auto} [app] org: ${sbt.organization-org.scala-sbt} name: sbt version: ${sbt.version-read(sbt.version)[0.13.18]} class: ${sbt.main.class-sbt.xMain} components: xsbti,extra cross-versioned: ${sbt.cross.versioned-false} resources: ${sbt.extraClasspath-} [repositories] local local-preloaded-ivy: file:///${sbt.preloaded-${sbt.global.base-${user.home}/.sbt}/preloaded/}, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext] local-preloaded: file:///${sbt.preloaded-${sbt.global.base-${user.home}/.sbt}/preloaded/} maven-central sbt-maven-releases: https://repo.scala-sbt.org/scalasbt/maven-releases/, bootOnly sbt-maven-snapshots: https://repo.scala-sbt.org/scalasbt/maven-snapshots/, bootOnly typesafe-ivy-releases: https://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly sbt-ivy-snapshots: https://repo.scala-sbt.org/scalasbt/ivy-snapshots/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly [boot] directory: /tmp/boot1/ lock: ${sbt.boot.lock-true} [ivy] ivy-home: ${sbt.ivy.home-${user.home}/.ivy2/} checksums: ${sbt.checksums-sha1,md5} repository-override: ${sbt.repository.override-${sbt.global.base-${user.home}/.sbt}/repositories_force} override-build-repos: ${sbt.override.build.repos-false} repository-config: ${sbt.repository.config-${sbt.global.base-${user.home}/.sbt}/repositories} sbt-launcher-ebbd426/ci-test/app2/000077500000000000000000000000001522561027200170075ustar00rootroot00000000000000sbt-launcher-ebbd426/ci-test/app2/build.sbt000066400000000000000000000000001522561027200206060ustar00rootroot00000000000000sbt-launcher-ebbd426/ci-test/app2/sbt.1.4.0.boot.properties000066400000000000000000000023401522561027200233150ustar00rootroot00000000000000[scala] version: ${sbt.scala.version-auto} [app] org: ${sbt.organization-org.scala-sbt} name: sbt version: ${sbt.version-read(sbt.version)[1.4.0]} class: ${sbt.main.class-sbt.xMain} components: xsbti,extra cross-versioned: ${sbt.cross.versioned-false} resources: ${sbt.extraClasspath-} [repositories] local maven-central sbt-maven-releases: https://repo.scala-sbt.org/scalasbt/maven-releases/, bootOnly sbt-maven-snapshots: https://repo.scala-sbt.org/scalasbt/maven-snapshots/, bootOnly typesafe-ivy-releases: https://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly sbt-ivy-snapshots: https://repo.scala-sbt.org/scalasbt/ivy-snapshots/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly [boot] directory: /tmp/boot0/ lock: ${sbt.boot.lock-true} [ivy] ivy-home: ${sbt.ivy.home-${user.home}/.ivy2/} checksums: ${sbt.checksums-sha1,md5} repository-override: ${sbt.repository.override-${sbt.global.base-${user.home}/.sbt}/repositories_force} override-build-repos: ${sbt.override.build.repos-false} repository-config: ${sbt.repository.config-${sbt.global.base-${user.home}/.sbt}/repositories} sbt-launcher-ebbd426/ci-test/test.sh000077500000000000000000000006671522561027200174740ustar00rootroot00000000000000#!/bin/bash -e LAUNCHER="../../launcher-implementation/target/proguard/launcher-implementation-1.4.0-SNAPSHOT-shading.jar" pushd ci-test/app0 COURSIER_CACHE=/tmp/cache/ java -jar $LAUNCHER @sbt.1.3.13.boot.properties exit popd pushd ci-test/app1 COURSIER_CACHE=/tmp/cache/ java -jar $LAUNCHER @sbt.0.13.18.boot.properties exit popd pushd ci-test/app2 COURSIER_CACHE=/tmp/cache/ java -jar $LAUNCHER @sbt.1.4.0.boot.properties exit popd sbt-launcher-ebbd426/launcher-implementation/000077500000000000000000000000001522561027200214215ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/000077500000000000000000000000001522561027200222105ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/main/000077500000000000000000000000001522561027200231345ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/main/input_sources/000077500000000000000000000000001522561027200260365ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/main/input_sources/CrossVersionUtil.scala000066400000000000000000000064221522561027200323440ustar00rootroot00000000000000package ${{cross.package0}}.${{cross.package1}} object CrossVersionUtil { val trueString = "true" val falseString = "false" val fullString = "full" val noneString = "none" val disabledString = "disabled" val binaryString = "binary" val TransitionScalaVersion = "2.10" val TransitionSbtVersion = "0.12" def isFull(s: String): Boolean = (s == trueString) || (s == fullString) def isDisabled(s: String): Boolean = (s == falseString) || (s == noneString) || (s == disabledString) def isBinary(s: String): Boolean = (s == binaryString) private[${{cross.package0}}] def isSbtApiCompatible(v: String): Boolean = sbtApiVersion(v).isDefined /** Returns sbt binary interface x.y API compatible with the given version string v. * RCs for x.y.0 are considered API compatible. * Compatibile versions include 0.12.0-1 and 0.12.0-RC1 for Some(0, 12). */ private[${{cross.package0}}] def sbtApiVersion(v: String): Option[(Int, Int)] = { val ReleaseV = """(\d+)\.(\d+)\.(\d+)(-\d+)?""".r val CandidateV = """(\d+)\.(\d+)\.(\d+)(-RC\d+)""".r val NonReleaseV = """(\d+)\.(\d+)\.(\d+)([-\w+]*)""".r v match { case ReleaseV(x, y, _, _) => Some((x.toInt, y.toInt)) case CandidateV(x, y, _, _) => Some((x.toInt, y.toInt)) case NonReleaseV(x, y, z, _) if z.toInt > 0 => Some((x.toInt, y.toInt)) case _ => None } } private[${{cross.package0}}] def isScalaApiCompatible(v: String): Boolean = scalaApiVersion(v).isDefined /** Returns Scala binary interface x.y API compatible with the given version string v. * Compatibile versions include 2.10.0-1 and 2.10.1-M1 for Some(2, 10), but not 2.10.0-RC1. */ private[${{cross.package0}}] def scalaApiVersion(v: String): Option[(Int, Int)] = { val ReleaseV = """(\d+)\.(\d+)\.(\d+)(-\d+)?""".r val BinCompatV = """(\d+)\.(\d+)\.(\d+)-bin(-.*)?""".r val NonReleaseV = """(\d+)\.(\d+)\.(\d+)(-\w+)""".r v match { case ReleaseV(x, y, _, _) => Some((x.toInt, y.toInt)) case BinCompatV(x, y, _, _) => Some((x.toInt, y.toInt)) case NonReleaseV(x, y, z, _) if z.toInt > 0 => Some((x.toInt, y.toInt)) case _ => None } } private[${{cross.package0}}] val PartialVersion = """(\d+)\.(\d+)(?:\..+)?""".r private[${{cross.package0}}] def partialVersion(s: String): Option[(Int,Int)] = s match { case PartialVersion(major, minor) => Some((major.toInt, minor.toInt)) case _ => None } def binaryScalaVersion(full: String): String = binaryVersionWithApi(full, TransitionScalaVersion)(scalaApiVersion) def binarySbtVersion(full: String): String = binaryVersionWithApi(full, TransitionSbtVersion)(sbtApiVersion) private[${{cross.package0}}] def binaryVersion(full: String, cutoff: String): String = binaryVersionWithApi(full, cutoff)(scalaApiVersion) private def isNewer(major: Int, minor: Int, minMajor: Int, minMinor: Int): Boolean = major > minMajor || (major == minMajor && minor >= minMinor) private def binaryVersionWithApi(full: String, cutoff: String)(apiVersion: String => Option[(Int,Int)]): String = { def sub(major: Int, minor: Int) = major.toString() + "." + minor (apiVersion(full), partialVersion(cutoff)) match { case (Some((major, minor)), None) => sub(major, minor) case (Some((major, minor)), Some((minMajor, minMinor))) if isNewer(major, minor, minMajor, minMinor) => sub(major, minor) case _ => full } } } sbt-launcher-ebbd426/launcher-implementation/src/main/java/000077500000000000000000000000001522561027200240555ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/main/java/xsbt/000077500000000000000000000000001522561027200250355ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/main/java/xsbt/boot/000077500000000000000000000000001522561027200260005ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/main/java/xsbt/boot/IO.java000066400000000000000000000041241522561027200271530ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.*; import java.nio.file.attribute.*; import java.util.EnumSet; import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; public class IO { public static void copyDirectory(final Path source, final Path target) throws IOException { Files.walkFileTree(source, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new FileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes sourceBasic) throws IOException { String relative = source.relativize(dir).toString(); if (!Files.exists(target.getFileSystem().getPath(relative))) Files.createDirectory(target.getFileSystem().getPath(relative)); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { String relative = source.relativize(file).toString(); Files.copy(file, target.getFileSystem().getPath(relative), COPY_ATTRIBUTES, REPLACE_EXISTING); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException e) throws IOException { throw e; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { if (e != null) throw e; return FileVisitResult.CONTINUE; } }); } } sbt-launcher-ebbd426/launcher-implementation/src/main/scala/000077500000000000000000000000001522561027200242175ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/000077500000000000000000000000001522561027200251775ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/000077500000000000000000000000001522561027200261425ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/Boot.scala000066400000000000000000000102511522561027200300510ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.nio.file.{ Path, Paths } // The entry point to the launcher object Boot: lazy val defaultGlobalBase: Path = Paths.get(sys.props("user.home"), ".sbt", "1.0") lazy val globalBase = sys.props.get("sbt.global.base").getOrElse(defaultGlobalBase.toString) def main(args: Array[String]): Unit = try standBy() val config = parseArgs(args) // If we haven't exited, we set up some hooks and launch System.clearProperty("scala.home") // avoid errors from mixing Scala versions in the same JVM System.setProperty("jline.shutdownhook", "false") // shutdown hooks cause class loader leaks System.setProperty("jline.esc.timeout", "0") // starts up a thread otherwise CheckProxy() run(config) catch // When sbt.boot.exit=false is set by an embedding application, exit() converts // sys.exit calls into this exception so the host JVM is not terminated. // See: https://github.com/sbt/sbt/issues/7540 case _: BootExit => () def standBy(): Unit = import scala.concurrent.duration.Duration val x = System.getProperty("sbt.launcher.standby") if x == null then () else val sec = Duration(x).toSeconds if sec >= 1 then (sec to 1 by -1) foreach { i => Console.err.println(s"[info] [launcher] standing by: $i") Thread.sleep(1000) } def parseArgs(args: Array[String]): LauncherArguments = @annotation.tailrec def parse( args: List[String], isLocate: Boolean, isExportRt: Boolean, remaining: List[String] ): LauncherArguments = args match case "--launcher-version" :: _ => Console.err.println( "sbt launcher version " + getClass.getPackage.getImplementationVersion ) exit(0) case "--rt-ext-dir" :: _ => var v = sys.props("java.vendor") + "_" + sys.props("java.version") v = v.replaceAll("\\W", "_").toLowerCase /* * The launch script greps for output starting with "java9-rt-ext-" so changing this * string will require changing the grep command in sbt-launch-lib.bash. */ val rtExtDir = Paths.get(globalBase, "java9-rt-ext-" + v) Console.out.println(rtExtDir.toString) exit(0) case "--locate" :: rest => parse(rest, true, isExportRt, remaining) case "--export-rt" :: rest => parse(rest, isLocate, true, remaining) case next :: rest => parse(rest, isLocate, isExportRt, next :: remaining) case Nil => new LauncherArguments(remaining.reverse, isLocate, isExportRt) parse(args.toList, false, false, Nil) // this arrangement is because Scala does not always properly optimize away // the tail recursion in a catch statement final def run(args: LauncherArguments): Unit = runImpl(args) match case Some(newArgs) => run(newArgs) case None => () private def runImpl(args: LauncherArguments): Option[LauncherArguments] = try Launch(args) map exit catch case b: BootException => errorAndExit(b.toString) case r: xsbti.RetrieveException => errorAndExit(r.getMessage) case r: xsbti.FullReload => Some(new LauncherArguments(r.arguments.toList, false, false)) case e: Throwable => e.printStackTrace errorAndExit(Pre.prefixError(e.toString)) private def errorAndExit(msg: String): Nothing = msg.linesIterator.toList foreach { line => Console.err.println("[error] [launcher] " + line) } exit(1) // Public so Find.scala can route its own System.exit calls through here. def exit(code: Int): Nothing = if exitSuppressed then throw BootExit(code) else sys.exit(code) // When an embedding application sets -Dsbt.boot.exit=false, exit() throws // BootExit instead of calling sys.exit, so Boot.main can catch it and return // normally without terminating the host JVM. private def exitSuppressed: Boolean = sys.props.get("sbt.boot.exit").exists(_.toLowerCase == "false") private case class BootExit(code: Int) extends RuntimeException(code.toString) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/BootConfiguration.scala000066400000000000000000000113471522561027200326100ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.io.File // // [.]scala-/ [baseDirectoryName] // lib/ [ScalaDirectoryName] // -/ [appDirectoryName] // // see also ProjectProperties for the set of constants that apply to the build.properties file in a project // The scala organization is used as a prefix in baseDirectoryName when a non-standard organization is used. private[boot] object BootConfiguration: // these are the Scala module identifiers to resolve/retrieve val ScalaOrg = "org.scala-lang" val CompilerModuleName = "scala-compiler" val Compiler3ModuleName = "scala3-compiler_3" val LibraryModuleName = "scala-library" val Library3ModuleName = "scala3-library_3" val JUnitName = "junit" val JAnsiVersion = "1.18" val SbtOrg = "org.scala-sbt" /** The Ivy conflict manager to use for updating. */ val ConflictManagerName = "latest-revision" /** The name of the local Ivy repository, which is used when compiling sbt from source. */ val LocalIvyName = "local" /** The pattern used for the local Ivy repository, which is used when compiling sbt from source. */ val LocalPattern = "[organisation]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]" /** The artifact pattern used for the local Ivy repository. */ def LocalArtifactPattern = LocalPattern /** The Ivy pattern used for the local Ivy repository. */ def LocalIvyPattern = LocalPattern final val FjbgPackage = "ch.epfl.lamp.fjbg." /** The class name prefix used to hide the Scala classes used by this loader from the application */ final val ScalaPackage = "scala." /** The class name prefix used to hide the Ivy classes used by this loader from the application */ final val IvyPackage = "org.apache.ivy." /** * The class name prefix used to hide the launcher classes from the application. * Note that access to xsbti classes are allowed. */ final val SbtBootPackage = "xsbt.boot." /** * The loader will check that these classes can be loaded and will assume that their presence indicates * the Scala compiler and library have been downloaded. */ val TestLoadScala2Classes = "scala.Option" :: "scala.tools.nsc.Global" :: Nil val TestLoadScala3Classes = "scala.Option" :: "dotty.tools.dotc.Driver" :: Nil val ScalaHomeProperty = "scala.home" val UpdateLogName = "update.log" val DefaultChecksums = "sha1" :: "md5" :: Nil val DefaultIvyConfiguration = "default" /** The name of the directory within the boot directory to retrieve scala to. */ val ScalaDirectoryName = "lib" /** * The Ivy pattern to use for retrieving the scala compiler and library. It is relative to the directory * containing all jars for the requested version of scala. */ val scalaRetrievePattern = ScalaDirectoryName + "/[artifact](-[classifier]).[ext]" def artifactType(classifier: String) = classifier match case "sources" => "src" case "javadoc" => "doc" case _ => "jar" /** * The Ivy pattern to use for retrieving the application and its dependencies. It is relative to the directory * containing all jars for the requested version of scala. */ def appRetrievePattern(appID: xsbti.ApplicationID) = appDirectoryName(appID, "/") + "(/[component])/[artifact]-[revision](-[classifier]).[ext]" val ScalaVersionPrefix = "scala-" /** The name of the directory to retrieve the application and its dependencies to. */ def appDirectoryName(appID: xsbti.ApplicationID, sep: String) = appID.groupID + sep + appID.name + sep + appID.version /** The name of the directory in the boot directory to put all jars for the given version of scala in. */ def baseDirectoryName(scalaOrg: String, scalaVersion: Option[String]) = scalaVersion match case None => "other" case Some(sv) => (if scalaOrg == ScalaOrg then "" else scalaOrg + ".") + ScalaVersionPrefix + sv def extractScalaVersion(dir: File): Option[String] = val name = dir.getName if name.contains(ScalaVersionPrefix) then Some(name.substring(name.lastIndexOf(ScalaVersionPrefix) + ScalaVersionPrefix.length)) else None private[boot] final class ProxyProperties( val envURL: String, val envUser: String, val envPassword: String, val sysHost: String, val sysPort: String, val sysUser: String, val sysPassword: String ) private[boot] object ProxyProperties: val http = apply("http") val https = apply("https") val ftp = apply("ftp") def apply(pre: String) = new ProxyProperties( pre + "_proxy", pre + "_proxy_user", pre + "_proxy_pass", pre + ".proxyHost", pre + ".proxyPort", pre + ".proxyUser", pre + ".proxyPassword" ) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/Cache.scala000066400000000000000000000014131522561027200301510ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.lang.ref.{ Reference, SoftReference } import java.util.HashMap final class Cache[K, X, V](create: (K, X) => V): private val delegate = new HashMap[K, Reference[V]] def apply(k: K, x: X): V = synchronized { getFromReference(k, x, delegate.get(k)) } private def getFromReference(k: K, x: X, existingRef: Reference[V]) = if existingRef eq null then newEntry(k, x) else get(k, x, existingRef.get) private def get(k: K, x: X, existing: V) = if existing == null then newEntry(k, x) else existing private def newEntry(k: K, x: X): V = val v = create(k, x) Pre.assert(v != null, "Value for key " + k + " was null") delegate.put(k, new SoftReference(v)) v sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/CheckProxy.scala000066400000000000000000000024001522561027200312220ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import Pre.* import java.net.{ MalformedURLException, URL } object CheckProxy: def apply(): Unit = import ProxyProperties.* for pp <- Seq(http, https, ftp) do setFromEnv(pp) private def setFromEnv(conf: ProxyProperties): Unit = import conf.* val proxyURL = System.getenv(envURL) if isDefined(proxyURL) && !isPropertyDefined(sysHost) && !isPropertyDefined(sysPort) then try val proxy = new URL(proxyURL) setProperty(sysHost, proxy.getHost) val port = proxy.getPort if port >= 0 then System.setProperty(sysPort, port.toString) copyEnv(envUser, sysUser) copyEnv(envPassword, sysPassword) catch case e: MalformedURLException => Console.err.println(s"[warn] [launcher] could not parse $envURL setting: ${e.toString}") private def copyEnv(envKey: String, sysKey: String): Unit = setProperty(sysKey, System.getenv(envKey)) private def setProperty(key: String, value: String): Unit = if value != null then System.setProperty(key, value) () private def isPropertyDefined(k: String) = isDefined(System.getProperty(k)) private def isDefined(s: String) = s != null && isNonEmpty(s) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/Configuration.scala000066400000000000000000000145121522561027200317610ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import Pre.* import java.io.{ File, InputStreamReader } import java.net.{ MalformedURLException, URI, URL } import java.util.regex.Pattern import scala.collection.immutable.List import scala.annotation.{ nowarn, tailrec } object ConfigurationStorageState extends Enumeration: val PropertiesFile = value("properties-file") val SerializedFile = value("serialized-file") object Configuration: import ConfigurationStorageState.* final val SysPropPrefix = "-D" @nowarn def parse(file: URL, baseDirectory: File) = Using(new InputStreamReader(file.openStream, "utf8"))((new ConfigurationParser).apply) /** * Finds the configuration location. * * Note: Configuration may be previously serialized by a launcher. */ @tailrec def find( args: List[String], baseDirectory: File ): (URL, List[String], ConfigurationStorageState.Value) = args match case head :: tail if head.startsWith("@load:") => (directConfiguration(head.substring(6), baseDirectory), tail, SerializedFile) case head :: tail if head.startsWith("@") => (directConfiguration(head.substring(1), baseDirectory), tail, PropertiesFile) case head :: tail if head.startsWith(SysPropPrefix) => setProperty(head stripPrefix SysPropPrefix) find(tail, baseDirectory) case _ => val propertyConfigured = System.getProperty("sbt.boot.properties") val url = if propertyConfigured == null then configurationOnClasspath else configurationFromFile(propertyConfigured, baseDirectory) (url, args, PropertiesFile) def setProperty(head: String): Unit = head.split("=", 2) match case Array("") => Console.err.println(s"[warn] [launcher] invalid system property '$head'") case Array(key) => sys.props += key -> "" case Array(key, value) => sys.props += key -> value case _ => () () def configurationOnClasspath: URL = val paths = resourcePaths(guessSbtVersion) paths.iterator.map(getClass.getResource).find(neNull) getOrElse (multiPartError("could not find sbt launch configuration. searched classpath for:", paths)) def directConfiguration(path: String, baseDirectory: File): URL = try new URL(path) catch case _: MalformedURLException => configurationFromFile(path, baseDirectory) def configurationFromFile(path: String, baseDirectory: File): URL = val pathURI = filePathURI(path) def resolve(against: URI): Option[URL] = val resolved = against.resolve(pathURI) // variant that accepts String doesn't properly escape (#725) val exists = try (new File(resolved)).exists catch case _: IllegalArgumentException => false if exists then Some(resolved.toURL) else None val against = resolveAgainst(baseDirectory) // use Iterators so that resolution occurs lazily, for performance val resolving = against.iterator.flatMap(e => resolve(e).toList.iterator) if !resolving.hasNext then multiPartError("could not find configuration file '" + path + "'. searched:", against) resolving.next() def multiPartError[A](firstLine: String, lines: List[A]) = Pre.error((firstLine :: lines.map(_.toString())).mkString("\n\t")) def UnspecifiedVersionPart = "Unspecified" def DefaultVersionPart = "Default" def DefaultBuildProperties = "project/build.properties" def SbtVersionProperty = "sbt.version" val ConfigurationName = "sbt.boot.properties" val JarBasePath = "/sbt/" def userConfigurationPath = "/" + ConfigurationName def defaultConfigurationPath = JarBasePath + ConfigurationName val baseResourcePaths: List[String] = userConfigurationPath :: defaultConfigurationPath :: Nil def resourcePaths(sbtVersion: Option[String]): List[String] = versionParts(sbtVersion) flatMap { part => baseResourcePaths map { base => base + part } } def fallbackParts: List[String] = "" :: Nil def versionParts(version: Option[String]): List[String] = version match case None => UnspecifiedVersionPart :: fallbackParts case Some(v) => versionParts(v) def versionParts(version: String): List[String] = val pattern = Pattern.compile("""(\d+)(\.\d+)(\.\d+)(-.*)?""") val m = pattern.matcher(version) if m.matches() then subPartsIndices flatMap { is => fullMatchOnly(is.map(m.group)) } else noMatchParts def noMatchParts: List[String] = DefaultVersionPart :: fallbackParts private def fullMatchOnly(groups: List[String]): Option[String] = if groups.forall(neNull) then Some(groups.mkString) else None private def subPartsIndices = (1 :: 2 :: 3 :: 4 :: Nil) :: (1 :: 2 :: 3 :: Nil) :: (1 :: 2 :: Nil) :: (Nil) :: Nil // the location of project/build.properties and the name of the property within that file // that configures the sbt version is configured in sbt.boot.properties. // We have to hard code them here in order to use them to determine the location of sbt.boot.properties itself def guessSbtVersion: Option[String] = val props = Pre.readProperties(new File(DefaultBuildProperties)) Option(props.getProperty(SbtVersionProperty)) def resolveAgainst(baseDirectory: File): List[URI] = directoryURI(baseDirectory) :: directoryURI(new File(System.getProperty("user.home"))) :: toDirectory(classLocation(getClass).toURI) :: Nil def classLocation(cl: Class[?]): URL = val codeSource = cl.getProtectionDomain.getCodeSource if codeSource == null then Pre.error("no class location for " + cl) else codeSource.getLocation // single-arg constructor doesn't properly escape def filePathURI(path: String): URI = if path.startsWith("file:") then new URI(path) else val f = new File(path) new URI(if f.isAbsolute then "file" else null, path, null) def directoryURI(dir: File): URI = directoryURI(dir.toURI) def directoryURI(uri: URI): URI = assert(uri.isAbsolute) val str = uri.toASCIIString val dirStr = if str.endsWith("/") then str else str + "/" (new URI(dirStr)).normalize def toDirectory(uri: URI): URI = try val file = new File(uri) val newFile = if file.isFile then file.getParentFile else file directoryURI(newFile) catch case _: Exception => uri private def neNull: AnyRef => Boolean = _ ne null sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/ConfigurationParser.scala000066400000000000000000000404121522561027200331340ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import Pre.* import ConfigurationParser.* import java.io.{ BufferedReader, File, FileInputStream, InputStreamReader, Reader, StringReader } import java.net.{ MalformedURLException, URL } import java.util.regex.{ Matcher, Pattern } import Matcher.quoteReplacement import scala.collection.immutable.List import scala.annotation.nowarn object ConfigurationParser: def trim(s: Array[String]) = s.map(_.trim).toList def ids(value: String) = trim(substituteVariables(value).split(",")).filter(isNonEmpty) private lazy val VarPattern = Pattern.compile("""\$\{([\w.]+)(\-(.*))?\}""") def substituteVariables(s: String): String = if s.indexOf('$') >= 0 then substituteVariables0(s) else s // scala.util.Regex brought in 30kB, so we code it explicitly def substituteVariables0(s: String): String = val m = VarPattern.matcher(s) val b = new StringBuffer while m.find() do val key = m.group(1) val defined = System.getProperty(key) val value = if defined ne null then defined else val default = m.group(3) if default eq null then m.group() else substituteVariables(default) m.appendReplacement(b, quoteReplacement(value)) m.appendTail(b) b.toString implicit val readIDs: String => List[String] = ids(_) class ConfigurationParser: def apply(file: File): LaunchConfiguration = Using(newReader(file))(apply) def apply(s: String): LaunchConfiguration = Using(new StringReader(s))(apply) def apply(reader: Reader): LaunchConfiguration = Using(new BufferedReader(reader))(apply) private def apply(in: BufferedReader): LaunchConfiguration = processSections(processLines(readLine(in, Nil, 0))) private final def readLine(in: BufferedReader, accum: List[Line], index: Int): List[Line] = in.readLine match case null => accum.reverse case line => readLine(in, ParseLine(line, index) ::: accum, index + 1) private def newReader(file: File) = new InputStreamReader(new FileInputStream(file), "UTF-8") def readRepositoriesConfig(file: File): List[Repository.Repository] = Using(newReader(file))(readRepositoriesConfig) def readRepositoriesConfig(reader: Reader): List[Repository.Repository] = Using(new BufferedReader(reader))(readRepositoriesConfig) def readRepositoriesConfig(s: String): List[Repository.Repository] = Using(new StringReader(s))(readRepositoriesConfig) private def readRepositoriesConfig(in: BufferedReader): List[Repository.Repository] = processRepositoriesConfig(processLines(readLine(in, Nil, 0))) def processRepositoriesConfig(sections: SectionMap): List[Repository.Repository] = processSection(sections, "repositories", getRepositories)._1 // section -> configuration instance processing def processSections(sections: SectionMap): LaunchConfiguration = val ((scalaVersion, scalaClassifiers), m1) = processSection(sections, "scala", getScala) val ((app, appClassifiers), m2) = processSection(m1, "app", getApplication) val (defaultRepositories, m3) = processSection(m2, "repositories", getRepositories) val (boot, m4) = processSection(m3, "boot", getBoot) val (logging, m5) = processSection(m4, "log", getLogging) val (properties, m6) = processSection(m5, "app-properties", getAppProperties) val ((ivyHome, checksums, isOverrideRepos, rConfigFile), m7) = processSection(m6, "ivy", getIvy) val (serverOptions, m8) = processSection(m7, "server", getServer) check(m8, "section") val classifiers = Classifiers(scalaClassifiers, appClassifiers) val repositories = rConfigFile map readRepositoriesConfig getOrElse defaultRepositories val ivyOptions = IvyOptions(ivyHome, classifiers, repositories, checksums, isOverrideRepos) // TODO - Read server properties... new LaunchConfiguration(scalaVersion, ivyOptions, app, boot, logging, properties, serverOptions) def getScala(m: LabelMap) = val (scalaVersion, m1) = getVersion(m, "Scala version", "scala.version") val (scalaClassifiers, m2) = getClassifiers(m1, "Scala classifiers") check(m2, "label") (scalaVersion, scalaClassifiers) def getClassifiers(m: LabelMap, label: String): (Value[List[String]], LabelMap) = process(m, "classifiers", processClassifiers(label)) def processClassifiers(label: String)(value: Option[String]): Value[List[String]] = value.map(readValue[List[String]](label)) getOrElse Value.Explicit(Nil) def getVersion(m: LabelMap, label: String, defaultName: String): (Value[String], LabelMap) = process(m, "version", processVersion(label, defaultName)) def processVersion(label: String, defaultName: String)(value: Option[String]): Value[String] = value.map(readValue[String](label)).getOrElse(Value.Implicit(defaultName, None)) def getName( m: LabelMap, label: String, defaultName: String, defaultValue: String ): (Value[String], LabelMap) = process(m, "name", processName(label, defaultName, defaultValue)) def processName(label: String, defaultName: String, defaultValue: String)( value: Option[String] ): Value[String] = value.map(readValue[String](label)).getOrElse(Value.Implicit(defaultName, Some(defaultValue))) def readValue[T](label: String)(implicit read: String => T): String => Value[T] = value0 => val value = substituteVariables(value0) if isEmpty(value) then Pre.error(label + " cannot be empty (omit declaration to use the default)") try parsePropertyValue(label, value)(Value.readImplied[T]) catch case _: BootException => Value.Explicit(read(value)) @nowarn def processSection[T](sections: SectionMap, name: String, f: LabelMap => T) = process[String, LabelMap, T](sections, name, m => f(m.default(x => None))) def process[K, V, T](sections: ListMap[K, V], name: K, f: V => T): (T, ListMap[K, V]) = (f(sections(name)), sections - name) def check(map: ListMap[String, ?], label: String): Unit = if map.isEmpty then () else Pre.error(map.keys.mkString("Invalid " + label + "(s): ", ",", "")) def check[T](label: String, pair: (T, ListMap[String, ?])): T = check(pair._2, label); pair._1 def id(map: LabelMap, name: String, default: String): (String, LabelMap) = (substituteVariables(orElse(getOrNone(map, name), default)), map - name) def getOrNone[K, V](map: ListMap[K, Option[V]], k: K) = orElse(map.get(k), None) def ids(map: LabelMap, name: String, default: List[String]) = val result = map(name) map ConfigurationParser.ids (orElse(result, default), map - name) def bool(map: LabelMap, name: String, default: Boolean): (Boolean, LabelMap) = val (b, m) = id(map, name, default.toString) (toBoolean(b), m) def toFiles(paths: List[String]): List[File] = paths.map(toFile) def toFile(path: String): File = new File( substituteVariables(path).replace('/', File.separatorChar) ) // if the path is relative, it will be resolved by Launch later def file(map: LabelMap, name: String, default: File): (File, LabelMap) = (orElse(getOrNone(map, name).map(toFile), default), map - name) def optfile(map: LabelMap, name: String): (Option[File], LabelMap) = (getOrNone(map, name).map(toFile), map - name) def getIvy(m: LabelMap): (Option[File], List[String], Boolean, Option[File]) = val (ivyHome, m1) = optfile(m, "ivy-home") val (checksums, m2) = ids(m1, "checksums", BootConfiguration.DefaultChecksums) val (repoConfigOverride, m3) = optfile(m2, "repository-override") val repoConfigOverrideExists = repoConfigOverride.exists(_.exists()) val (configuredOverrideRepos, m4) = bool(m3, "override-build-repos", false) val overrideRepos = configuredOverrideRepos || repoConfigOverrideExists val (repoConfig, m5) = optfile(m4, "repository-config") check(m5, "label") val effectiveRepoConfig = if repoConfigOverrideExists then repoConfigOverride else repoConfig (ivyHome, checksums, overrideRepos, effectiveRepoConfig filter (_.exists)) def getBoot(m: LabelMap): BootSetup = val (dir, m1) = file(m, "directory", toFile("project/boot")) val (props, m2) = file(m1, "properties", toFile("project/build.properties")) val (search, m3) = getSearch(m2, props) val (enableQuick, m4) = bool(m3, "quick-option", false) val (promptFill, m5) = bool(m4, "prompt-fill", false) val (promptCreate, m6) = id(m5, "prompt-create", "") val (lock, m7) = bool(m6, "lock", true) check(m7, "label") BootSetup(dir, lock, props, search, promptCreate, enableQuick, promptFill) def getLogging(m: LabelMap): Logging = check("label", process(m, "level", getLevel)) def getLevel(m: Option[String]) = m.map(LogLevel.apply).getOrElse(new Logging(LogLevel.Info)) def getSearch(m: LabelMap, defaultPath: File): (Search, LabelMap) = ids(m, "search", Nil) match case (Nil, newM) => (Search.none, newM) case (tpe :: Nil, newM) => (Search(tpe, List(defaultPath)), newM) case (tpe :: paths, newM) => (Search(tpe, toFiles(paths)), newM) def getApplication(m: LabelMap): (Application, Value[List[String]]) = val (org, m1) = id(m, "org", BootConfiguration.SbtOrg) val (name, m2) = id(m1, "name", "sbt") val (appName, _) = getName(m1, name + " name", name + ".name", name) val (rev, m3) = getVersion(m2, name + " version", name + ".version") val (main, m4) = id(m3, "class", "xsbt.Main") val (components, m5) = ids(m4, "components", List("default")) val (crossVersioned, m6) = id(m5, "cross-versioned", CrossVersionUtil.binaryString) val (resources, m7) = ids(m6, "resources", Nil) val (classifiers, m8) = getClassifiers(m7, "Application classifiers") check(m8, "label") val classpathExtra = toArray(toFiles(resources)) val app = new Application( org, appName, rev, main, components, LaunchCrossVersion(crossVersioned), classpathExtra ) (app, classifiers) def getServer(m: LabelMap): (Option[ServerConfiguration]) = val (lock, m1) = optfile(m, "lock") // TODO - JVM args val (args, m2) = optfile(m1, "jvmargs") val (props, _) = optfile(m2, "jvmprops") lock map { file => ServerConfiguration(file, args, props) } def getRepositories(m: LabelMap): List[Repository.Repository] = import Repository.{ Ivy, Maven, Predefined } val BootOnly = "bootOnly" val MvnComp = "mavenCompatible" val DescriptorOptional = "descriptorOptional" val DontCheckConsistency = "skipConsistencyCheck" val AllowInsecureProtocol = "allowInsecureProtocol" val OptSet = Set(BootOnly, MvnComp, DescriptorOptional, DontCheckConsistency, AllowInsecureProtocol) m.toList.map { case (key, None) => Predefined(key) case (key, Some(BootOnly)) => Predefined(key, true) case (key, Some(value)) => val r = trim(substituteVariables(value).split(",", 8)) val url = try new URL(r(0)) catch case e: MalformedURLException => Pre.error("invalid URL specified for '" + key + "': " + e.getMessage) val (optionPart, patterns) = r.tail.partition(OptSet.contains(_)) val options = ( optionPart.contains(BootOnly), optionPart.contains(MvnComp), optionPart.contains(DescriptorOptional), optionPart.contains(DontCheckConsistency), optionPart.contains(AllowInsecureProtocol) ) (patterns, options) match case (both :: Nil, (bo, mc, dso, cc, ip)) => Ivy( key, url, both, both, mavenCompatible = mc, bootOnly = bo, descriptorOptional = dso, skipConsistencyCheck = cc, allowInsecureProtocol = ip ) case (ivy :: art :: Nil, (bo, mc, dso, cc, ip)) => Ivy( key, url, ivy, art, mavenCompatible = mc, bootOnly = bo, descriptorOptional = dso, skipConsistencyCheck = cc, allowInsecureProtocol = ip ) case (Nil, (true, false, false, _, ip)) => Maven(key, url, bootOnly = true, allowInsecureProtocol = ip) case (Nil, (false, false, false, false, ip)) => Maven(key, url, allowInsecureProtocol = ip) case _ => Pre.error("could not parse %s: %s".format(key, value)) } def getAppProperties(m: LabelMap): List[AppProperty] = m.toList.flatMap: case (name, Some(value)) => val map = ListMap(trim(value.split(",")).map(parsePropertyDefinition(name))*) List(AppProperty(name)(map.get("quick"), map.get("new"), map.get("fill"))) case _ => Nil def parsePropertyDefinition(name: String)(value: String) = value.split("=", 2) match case Array(mode, value) => (mode, parsePropertyValue(name, value)(defineProperty(name))) case x => Pre.error("invalid property definition '" + x + "' for property '" + name + "'") def defineProperty( name: String )(action: String, requiredArg: String, optionalArg: Option[String]) = action match case "prompt" => PropertyInit.PromptProperty(requiredArg, optionalArg) case "set" => PropertyInit.SetProperty(requiredArg) case _ => Pre.error("unknown action '" + action + "' for property '" + name + "'") private lazy val propertyPattern = Pattern.compile("""(.+)\((.*)\)(?:\[(.*)\])?""") // examples: prompt(Version)[1.0] or set(1.0) def parsePropertyValue[T](name: String, definition: String)( f: (String, String, Option[String]) => T ): T = val m = propertyPattern.matcher(definition) if !m.matches() then Pre.error("invalid property definition '" + definition + "' for property '" + name + "'") val optionalArg = m.group(3) f(m.group(1), m.group(2), if optionalArg eq null then None else Some(optionalArg)) type LabelMap = ListMap[String, Option[String]] // section-name -> label -> value type SectionMap = ListMap[String, LabelMap] @nowarn def processLines(lines: List[Line]): SectionMap = type State = (SectionMap, Option[String]) val s: State = lines.foldLeft( (ListMap.empty.default(x => ListMap.empty[String, Option[String]]), None): State ) { case (x, Line.Comment) => x case ((map, _), s: Line.Section) => (map, Some(s.name)) case ((_, None), l: Line.Labeled) => Pre.error("label " + l.label + " is not in a section") case ((map, s @ Some(section)), l: Line.Labeled) => val sMap = map(section) if sMap.contains(l.label) then Pre.error("duplicate label '" + l.label + "' in section '" + section + "'") else (map(section) = (sMap(l.label) = l.value), s) } s._1 enum Line: case Labeled(val label: String, val value: Option[String]) case Section(val name: String) case Comment class ParseException(val content: String, val line: Int, val col: Int, val msg: String) extends BootException( "[" + (line + 1) + ", " + (col + 1) + "]" + msg + "\n" + content + "\n" + List .fill(col)(" ") .mkString + "^" ) object ParseLine: def apply(content: String, line: Int) = def error(col: Int, msg: String) = throw new ParseException(content, line, col, msg) def check(condition: Boolean)(col: Int, msg: String) = if condition then () else error(col, msg) val trimmed = trimLeading(content) def section = val closing = trimmed.indexOf(']', 1) check(closing > 0)(content.length, "expected ']', found end of line") val extra = trimmed.substring(closing + 1) val trimmedExtra = trimLeading(extra) check(isEmpty(trimmedExtra))( content.length - trimmedExtra.length, "expected end of line, found '" + extra + "'" ) Line.Section(trimmed.substring(1, closing).trim) def labeled = trimmed.split(":", 2) match case Array(label, value) => val trimmedValue = value.trim check(isNonEmpty(trimmedValue))( content.indexOf(':'), "value for '" + label + "' was empty" ) Line.Labeled(label, Some(trimmedValue)) case x => Line.Labeled(x.mkString, None) if isEmpty(trimmed) then Nil else val processed = trimmed.charAt(0) match case '#' => Line.Comment case '[' => section case _ => labeled processed :: Nil sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/CoursierUpdate.scala000066400000000000000000000337111522561027200321120ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import Pre.* import coursier.* import coursier.cache.{ CacheDefaults, FileCache } import coursier.core.{ Publication, Repository } import coursier.credentials.DirectCredentials import coursier.ivy.IvyRepository import coursier.maven.MavenRepository import coursier.params.ResolutionParams import java.io.{ File, FileWriter, PrintWriter } import java.nio.file.{ Files, StandardCopyOption } import java.util.Properties import java.util.regex.Pattern import BootConfiguration.* import UpdateTarget.* import scala.annotation.nowarn class CousierUpdate(config: UpdateConfiguration): import config.{ bootDirectory, getScalaVersion, resolutionCacheBase, scalaVersion, scalaOrg } private def logFile = new File(bootDirectory, UpdateLogName) private val logWriter = new PrintWriter(new FileWriter(logFile)) private def defaultCacheLocation: File = def absoluteFile(path: String): File = new File(path).getAbsoluteFile() def windowsCacheDirectory: File = // Per discussion in https://github.com/dirs-dev/directories-jvm/issues/43, // LOCALAPPDATA environment variable may NOT represent the one-true // Known Folders API (https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid) // in case the user happened to have set the LOCALAPPDATA environmental variable. // Given that there's no reliable way of accessing this API from JVM, I think it's actually // better to use the LOCALAPPDATA as the first place to look. // When it is not found, it will fall back to $HOME/AppData/Local. // For the purpose of picking the Coursier cache directory, it's better to be // fast, reliable, and predictable rather than strict adherence to Microsoft. val base = sys.env .get("LOCALAPPDATA") .map(absoluteFile) .getOrElse(new File(new File(absoluteFile(sys.props("user.home")), "AppData"), "Local")) new File(new File(new File(base, "Coursier"), "Cache"), "v1") sys.props .get("sbt.coursier.home") .map(home => new File(absoluteFile(home), "cache")) .orElse(sys.env.get("COURSIER_CACHE").map(absoluteFile)) .orElse(sys.props.get("coursier.cache").map(absoluteFile)) match case Some(dir) => dir case _ => if isWindows then windowsCacheDirectory else CacheDefaults.location private lazy val coursierCache = import coursier.util.Task val credentials = bootCredentials val cache = credentials.foldLeft(FileCache(defaultCacheLocation)(using Task.sync)) { _.addCredentials(_) } cache def apply(target: UpdateTarget, reason: String): UpdateResult = try update(target, reason) catch case e: Throwable => e.printStackTrace(logWriter) log("[error] [launcher] " + e.toString) new UpdateResult(false, None, None) finally delete(resolutionCacheBase) private def update(target: UpdateTarget, reason: String): UpdateResult = val deps = target match case u: UpdateScala => val scalaVersion = getScalaVersion val scalaOrgString = if scalaOrg != ScalaOrg then scalaOrg + " " else "" Console.err.println( s"[info] [launcher] getting ${scalaOrgString}Scala $scalaVersion ${reason}..." ) scalaVersion match case sv if sv.startsWith("2.") => withPublication( Dependency( Module(Organization(scalaOrg), ModuleName(CompilerModuleName)), scalaVersion ), u.classifiers ) ::: withPublication( Dependency( Module(Organization(scalaOrg), ModuleName(LibraryModuleName)), scalaVersion ), u.classifiers ) case sv if sv.startsWith("3.") => withPublication( Dependency( Module(Organization(scalaOrg), ModuleName(Compiler3ModuleName)), scalaVersion ), u.classifiers ) ::: withPublication( Dependency( Module(Organization(scalaOrg), ModuleName(Library3ModuleName)), scalaVersion ), u.classifiers ) case _ => sys.error("unsupported Scala version " + scalaVersion) case u: UpdateApp => val app = u.id val resolvedName = (app.crossVersioned, scalaVersion) match case (xsbti.CrossValue.Full, Some(sv)) => app.getName + "_" + sv case (xsbti.CrossValue.Binary, Some(sv)) => app.getName + "_" + CrossVersionUtil.binaryScalaVersion(sv) case _ => app.getName Console.err.println( s"[info] [launcher] getting ${app.groupID} $resolvedName ${app.getVersion} $reason (this may take some time)..." ) withPublication( Dependency( Module(Organization(app.groupID), ModuleName(resolvedName)), app.getVersion ), u.classifiers ) ::: (scalaVersion match case Some(sv) if sv.startsWith("3.") => withPublication( Dependency( Module(Organization(scalaOrg), ModuleName(Library3ModuleName)), sv ), u.classifiers ) case Some(sv) if sv.startsWith("2.") => withPublication( Dependency( Module(Organization(scalaOrg), ModuleName(LibraryModuleName)), sv ), u.classifiers ) case _ => Nil) update(target, deps) private def detectScalaVersion(dependencySet: Set[Dependency]): Option[String] = def detectScalaVersion3: Option[String] = (dependencySet collect { case d: Dependency if d.module == Module(Organization(scalaOrg), ModuleName(Library3ModuleName)) => d.version }).headOption def detectScalaVersion2: Option[String] = (dependencySet collect { case d: Dependency if d.module == Module(Organization(scalaOrg), ModuleName(LibraryModuleName)) => d.version }).headOption detectScalaVersion3.orElse(detectScalaVersion2) /** Runs the resolve and retrieve for the given moduleID, which has had its dependencies added already. */ private def update( target: UpdateTarget, deps: List[Dependency] ): UpdateResult = val repos = config.repositories.flatMap(toCoursierRepository) val params = scalaVersion match case Some(sv) if sv != "auto" => ResolutionParams() .withScalaVersion(sv) .withForceScalaVersion(true) case _ => detectScalaVersion(deps.toSet) match case Some(sv) => ResolutionParams() .withScalaVersion(sv) .withForceScalaVersion(true) case _ => ResolutionParams() val r: Resolution = Resolve() .withCache(coursierCache) .addDependencies(deps*) .withRepositories(repos) .withResolutionParams(params) .run() val actualScalaVersion = detectScalaVersion(r.dependencySet.set) val retrieveDir = target match case _: UpdateScala => new File(new File(bootDirectory, baseDirectoryName(scalaOrg, scalaVersion)), "lib") case u: UpdateApp => new File( new File(bootDirectory, baseDirectoryName(scalaOrg, actualScalaVersion)), appDirectoryName(u.id.toID, File.separator) ) val isScala = target match case _: UpdateScala => true case _: UpdateApp => false val depVersion: Option[String] = target match case _: UpdateScala => scalaVersion case u: UpdateApp => Some(Value.get(u.id.version)) val isSbt = target match case _: UpdateScala => false case u: UpdateApp => Some(Value.get(u.id.name)) == Some("sbt") val isZeroDot = depVersion match case Some(v) => v.startsWith("0.") case _ => false if !retrieveDir.exists then Files.createDirectories(retrieveDir.toPath) val downloadedJars = Fetch() .withCache(coursierCache) .addDependencies(deps*) .withRepositories(repos) .withResolutionParams(params) .run() downloadedJars foreach { downloaded => val t = if isScala then val name = downloaded.getName match case n if n.startsWith("scala-compiler") => "scala-compiler.jar" case n if n.startsWith("scala-library") => "scala-library.jar" case n if n.startsWith("scala-reflect") => "scala-reflect.jar" case n => n new File(retrieveDir, name) else if isSbt && downloaded.getName == "interface.jar" && isZeroDot then val componentDir = new File(retrieveDir, "xsbti") Files.createDirectories(componentDir.toPath) new File(componentDir, downloaded.getName) else val name = downloaded.getName match // https://github.com/sbt/sbt/issues/6432 // sbt expects test-interface JAR to be called test-interface-1.0.jar with // version number, but sometimes it doesn't have it. case "test-interface.jar" => if Pattern.matches("""[0-9.]+""", downloaded.getParentFile.getName) then "test-interface-" + downloaded.getParentFile.getName + ".jar" else "test-interface-0.0.jar" case n => n new File(retrieveDir, name) val isSkip = isScala && (downloaded.getName match case n if n.startsWith("compiler-interface") => true case n if n.startsWith("util-interface") => true case _ => false) if isSkip then () else Files.copy(downloaded.toPath, t.toPath, StandardCopyOption.REPLACE_EXISTING) () } new UpdateResult(true, actualScalaVersion, depVersion) def withPublication(d: Dependency, classifiers: List[String]): List[Dependency] = if classifiers.isEmpty then List(d) else classifiers.map(c => d.withPublication(Publication.empty.withClassifier(Classifier(c)))) def bootCredentials = val optionProps = Option(System.getProperty("sbt.boot.credentials")) orElse Option(System.getenv("SBT_CREDENTIALS")) map (path => Pre.readProperties(new File(substituteTilde(path))) ) def extractCredentials( keys: (String, String, String, String) )(props: Properties): Option[DirectCredentials] = val List(realm, host, user, password) = keys.productIterator.map(key => props.getProperty(key.toString)).toList if host != null && user != null && password != null then Some( DirectCredentials() .withHost(host) .withUsername(user) .withPassword(password) .withRealm(Option(realm).filter(_.nonEmpty)) .withHttpsOnly(false) .withMatchHost(true) ) else None (optionProps match case Some(props) => extractCredentials(("realm", "host", "user", "password"))(props) case None => None ).toList ::: (extractCredentials( ("sbt.boot.realm", "sbt.boot.host", "sbt.boot.user", "sbt.boot.password") )( System.getProperties )).toList @nowarn def toCoursierRepository(repo: xsbti.Repository): Seq[Repository] = import xsbti.Predefined.* repo match case m: xsbti.MavenRepository => mavenRepository(m.url.toString) :: Nil case i: xsbti.IvyRepository => ivyRepository( i.id, i.url.toString, i.ivyPattern, i.artifactPattern, i.mavenCompatible, i.descriptorOptional, i.skipConsistencyCheck, i.allowInsecureProtocol ) :: Nil case p: xsbti.PredefinedRepository => p.id match case Local => localRepository :: Nil case MavenLocal => val localDir = new File(new File(new File(sys.props("user.home")), ".m2"), "repository") mavenRepository(localDir.toPath.toUri.toString) :: Nil case MavenCentral => Repositories.central :: Nil case SonatypeOSSReleases => Repositories.sonatype("releases") :: Nil case SonatypeOSSSnapshots => Repositories.sonatype("snapshots") :: Nil case Jcenter | ScalaToolsReleases | ScalaToolsSnapshots => log( s"[warn] [launcher] ${p.id} is deprecated or no longer available; remove from repositories" ) Nil private def mavenRepository(root0: String): MavenRepository = val root = if root0.endsWith("/") then root0 else root0 + "/" MavenRepository(root) /** Uses the pattern defined in BuildConfiguration to download sbt from Google code. */ @nowarn private def ivyRepository( id: String, base: String, ivyPattern: String, artifactPattern: String, mavenCompatible: Boolean, descriptorOptional: Boolean, skipConsistencyCheck: Boolean, allowInsecureProtocol: Boolean ): IvyRepository = IvyRepository .parse( base + artifactPattern, Some(base + ivyPattern), ) .toOption .get private def localRepository: IvyRepository = val localDir = new File(new File(new File(sys.props("user.home")), ".ivy2"), "local") val root0 = localDir.toPath.toUri.toASCIIString val root = if root0.endsWith("/") then root0 else root0 + "/" IvyRepository .parse( root + LocalArtifactPattern, Some(root + LocalIvyPattern) ) .toOption .get /** Logs the given message to a file and to the console. */ private def log(msg: String) = try logWriter.println(msg) catch case e: Exception => Console.err.println("[error] [launcher] error writing to update log file: " + e.toString) Console.err.println(msg) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/Create.scala000066400000000000000000000042631522561027200303570ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import Pre.* import java.io.File import java.util.{ Locale, Properties } import scala.collection.immutable.List import PropertyInit.* object Initialize: lazy val selectCreate = (_: AppProperty).create lazy val selectQuick = (_: AppProperty).quick lazy val selectFill = (_: AppProperty).fill def create( file: File, promptCreate: String, enableQuick: Boolean, spec: List[AppProperty] ): Unit = readLine(promptCreate + " (y/N" + (if enableQuick then "/s" else "") + ") ") match case None => declined("") case Some(line) => line.toLowerCase(Locale.ENGLISH) match case "y" | "yes" => process(file, spec, selectCreate) case "s" => process(file, spec, selectQuick) case "n" | "no" | "" => declined("") case x => Console.err.println(" '" + x + "' not understood.") create(file, promptCreate, enableQuick, spec) def fill(file: File, spec: List[AppProperty]): Unit = process(file, spec, selectFill) def process( file: File, appProperties: List[AppProperty], select: AppProperty => Option[PropertyInit] ): Unit = val properties = readProperties(file) val uninitialized = for ( property <- appProperties; init <- select(property) if properties.getProperty(property.name) == null ) yield initialize(properties, property.name, init) if !uninitialized.isEmpty then writeProperties(properties, file, "") def initialize(properties: Properties, name: String, init: PropertyInit): Unit = init match case set: SetProperty => properties.setProperty(name, set.value) case prompt: PromptProperty => def noValue = declined("no value provided for " + prompt.label) readLine(prompt.label + prompt.default.toList.map(" [" + _ + "]").mkString + ": ") match case None => noValue case Some(line) => val value = if isEmpty(line) then orElse(prompt.default, noValue) else line properties.setProperty(name, value) () sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/Enumeration.scala000066400000000000000000000020751522561027200314410ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import Pre.* import scala.collection.immutable.List class Enumeration extends Serializable: def elements: List[Value] = members private lazy val members: List[Value] = val c = getClass val correspondingFields = ListMap(c.getDeclaredFields.map(f => (f.getName, f))*) c.getMethods.toList flatMap { method => if method.getParameterTypes.length == 0 && classOf[Value].isAssignableFrom( method.getReturnType ) then for ( field <- correspondingFields.get(method.getName) if field.getType == method.getReturnType ) yield method.invoke(this).asInstanceOf[Value] else Nil } def value(s: String) = new Value(s, 0) def value(s: String, i: Int) = new Value(s, i) final class Value(override val toString: String, val id: Int) extends Serializable def toValue(s: String): Value = elements .find(_.toString == s) .getOrElse(error("expected one of " + elements.mkString(",") + " (got: " + s + ")")) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/Exceptions.scala000066400000000000000000000006041522561027200312700ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot // The exception to use when an error occurs at the launcher level (and not a nested exception). // This indicates overrides toString because the exception class name is not needed to understand // the error message. class BootException(override val toString: String) extends RuntimeException(toString) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/FilteredLoader.scala000066400000000000000000000033061522561027200320360ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import BootConfiguration.{ FjbgPackage, IvyPackage, SbtBootPackage, ScalaPackage } import scala.collection.immutable.LazyList /** * A custom class loader to ensure the main part of sbt doesn't load any Scala or * Ivy classes from the jar containing the loader. */ private[boot] final class BootFilteredLoader(parent: ClassLoader) extends ClassLoader(parent): @throws(classOf[ClassNotFoundException]) override final def loadClass(className: String, resolve: Boolean): Class[?] = // note that we allow xsbti.* if className.startsWith(ScalaPackage) || className.startsWith(IvyPackage) || className .startsWith(SbtBootPackage) || className.startsWith(FjbgPackage) then throw new ClassNotFoundException(className) else super.loadClass(className, resolve) override def getResources(name: String) = excludedLoader.getResources(name) override def getResource(name: String) = excludedLoader.getResource(name) // the loader to use when a resource is excluded. This needs to be at least parent.getParent so that it skips parent. parent contains // resources included in the launcher, which need to be ignored. Now that the launcher can be unrooted (not the application entry point), // this needs to be the Java extension loader (the loader with getParent == null) private val excludedLoader = Loaders(parent.getParent).head object Loaders: def apply(loader: ClassLoader): LazyList[ClassLoader] = def loaders(loader: ClassLoader, accum: LazyList[ClassLoader]): LazyList[ClassLoader] = if loader eq null then accum else loaders(loader.getParent, loader #:: accum) loaders(loader, LazyList.empty) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/Find.scala000066400000000000000000000041541522561027200300330ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import Pre.* import java.io.File import java.net.URI import scala.collection.immutable.List object Find: def apply(config: LaunchConfiguration, currentDirectory: File) = (new Find(config))(currentDirectory) class Find(config: LaunchConfiguration): import config.boot.search def apply(currentDirectory: File) = val current = currentDirectory.getCanonicalFile assert(current.isDirectory) lazy val fromRoot = path(current, Nil).filter(hasProject).map(_.getCanonicalFile) val found: Option[File] = search.tpe match case Search.RootFirst => fromRoot.headOption case Search.Nearest => fromRoot.lastOption case Search.Only => if hasProject(current) then Some(current) else fromRoot match case Nil => Some(current) case head :: Nil => Some(head) case _ => Console.err.println( "[error] [launcher] search method is 'only' and multiple ancestor directories match:\n\t" + fromRoot .mkString("\n\t") ) Boot.exit(1) None case _ => Some(current) val baseDirectory = orElse(found, current) System.setProperty("user.dir", baseDirectory.getAbsolutePath) (ResolvePaths(config, baseDirectory), baseDirectory) private def hasProject(f: File) = f.isDirectory && search.paths.forall(p => ResolvePaths(f, p).exists) private def path(f: File, acc: List[File]): List[File] = if f eq null then acc else path(f.getParentFile, f :: acc) object ResolvePaths: def apply(config: LaunchConfiguration, baseDirectory: File): LaunchConfiguration = config.map(f => apply(baseDirectory, f)) def apply(baseDirectory: File, f: File): File = if f.isAbsolute then f else assert( baseDirectory.isDirectory ) // if base directory is not a directory, URI.resolve will not work properly val uri = new URI(null, null, f.getPath, null) new File(baseDirectory.toURI.resolve(uri)) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/JAnsi.scala000066400000000000000000000024011522561027200301500ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import xsbt.boot.Pre.* object JAnsi: def uninstall(loader: ClassLoader): Unit = callJAnsi("systemUninstall", loader) def install(loader: ClassLoader): Unit = callJAnsi("systemInstall", loader) private def callJAnsi(methodName: String, loader: ClassLoader): Unit = if isWindows && !isCygwin then callJAnsiMethod(methodName, loader) private def callJAnsiMethod(methodName: String, loader: ClassLoader): Unit = try val c = Class.forName(Seq("org", "fusesource", "jansi", "AnsiConsole").mkString("."), true, loader) c.getMethod(methodName).invoke(null) () catch case _: ClassNotFoundException => /* The below code intentionally traps everything. It technically shouldn't trap the * non-StackOverflowError VirtualMachineErrors and AWTError would be weird, but this is PermGen * mitigation code that should not render sbt completely unusable if jansi initialization fails. * [From Mark Harrah, https://github.com/sbt/sbt/pull/633#issuecomment-11957578]. */ case ex: Throwable => Console.err.println( "[error] [launcher] Jansi found on class path but initialization failed: " + ex ) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/Launch.scala000066400000000000000000000574111522561027200303710ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import Pre.* import BootConfiguration.{ CompilerModuleName, JAnsiVersion, LibraryModuleName } import java.io.File import java.net.{ URI, URL, URLClassLoader } import java.util.concurrent.Callable import java.util.concurrent.atomic.AtomicReference import scala.collection.immutable.List import scala.annotation.{ nowarn, tailrec } import ConfigurationStorageState.* import UpdateTarget.* class LauncherArguments(val args: List[String], val isLocate: Boolean, val isExportRt: Boolean) object Launch: def apply(arguments: LauncherArguments): Option[Int] = apply((new File("")).getAbsoluteFile, arguments) def apply(currentDirectory: File, arguments: LauncherArguments): Option[Int] = if arguments.isExportRt then if arguments.args.size != 1 then sys.error("destination expected: --export-rt ") exportRt(arguments.args.head) else val (configLocation, newArgs2, state) = Configuration.find(arguments.args, currentDirectory) val config = state match case SerializedFile => LaunchConfiguration.restore(configLocation) case PropertiesFile => parseAndInitializeConfig(configLocation, currentDirectory) if arguments.isLocate then if !newArgs2.isEmpty then // TODO - Print the arguments without exploding proguard size. Console.err.println("[warn] [launcher] --locate option ignores arguments") locate(currentDirectory, config) else // First check to see if there are java system properties we need to set. Then launch the application. updateProperties(config) launch(run(Launcher(config)))(makeRunConfig(currentDirectory, config, newArgs2)) def exportRt(destination: String): Option[Int] = import java.nio.file.* import java.util.HashMap val fileSystem = FileSystems.getFileSystem(URI.create("jrt:/")) val path: Path = fileSystem.getPath("/modules") val destPath: Path = Paths.get(destination) val uri = URI.create("jar:" + destPath.toUri()) val env = new HashMap[String, String]() env.put("create", "true") val zipfs = FileSystems.newFileSystem(uri, env) try val iterator = Files.list(path).iterator() while iterator.hasNext() do val next = iterator.next() IO.copyDirectory(next, zipfs.getPath("/")) finally zipfs.close Some(0) /** Locate a server, print where it is, and exit. */ def locate(currentDirectory: File, config: LaunchConfiguration): Option[Int] = config.serverConfig match case Some(_) => val uri = ServerLocator.locate(currentDirectory, config) Console.err.println(uri.toASCIIString) Some(0) case None => sys.error(s"${config.app.groupID}-${config.app.main} is not configured as a server.") /** * Some hackery to allow sys.props to be configured via a file. If this launch config has * a valid file configured, we load the properties and and apply them to this jvm. */ def updateProperties(config: LaunchConfiguration): Unit = config.serverConfig match case Some(config) => config.jvmPropsFile match case Some(file) if file.exists => try setSystemProperties(readProperties(file)) catch case e: Exception => throw new RuntimeException(s"unable to load server properties file: ${file}", e) case _ => case None => /** Parses the configuration *and* runs the initialization code that will remove variable references. */ def parseAndInitializeConfig(configLocation: URL, currentDirectory: File): LaunchConfiguration = val (parsed, _) = parseConfiguration(configLocation, currentDirectory) resolveConfig(parsed) /** Parse configuration and return it and the baseDirectory of the launch. */ def parseConfiguration(configLocation: URL, currentDirectory: File): (LaunchConfiguration, File) = Find(Configuration.parse(configLocation, currentDirectory), currentDirectory) /** Setups the Initialize object so we can fill in system properties in the configuration */ def resolveConfig(parsed: LaunchConfiguration): LaunchConfiguration = // Set up initialize. val propertiesFile = parsed.boot.properties import parsed.boot.{ enableQuick, promptCreate, promptFill } if isNonEmpty(promptCreate) && !propertiesFile.exists then Initialize.create(propertiesFile, promptCreate, enableQuick, parsed.appProperties) else if promptFill then Initialize.fill(propertiesFile, parsed.appProperties) parsed.logging.debug("parsed configuration: " + parsed) val resolved = ResolveValues(parsed) resolved.logging.debug("resolved configuration: " + resolved) resolved /** Create run configuration we'll use to launch the app. */ def makeRunConfig( currentDirectory: File, config: LaunchConfiguration, arguments: List[String] ): RunConfiguration = new RunConfiguration(config.getScalaVersion, config.app.toID, currentDirectory, arguments) /** The actual mechanism used to run a launched application. */ def run(launcher: xsbti.Launcher)(config: RunConfiguration): xsbti.MainResult = import config.* val appProvider: xsbti.AppProvider = launcher.app(app, orNull(scalaVersion)) // takes ~40 ms when no update is required val appConfig: xsbti.AppConfiguration = new AppConfiguration(toArray(arguments), workingDirectory, appProvider) // TODO - Jansi probably should be configurable via some other mechanism... JAnsi.install(launcher.topLoader) try val main = appProvider.newMain() try withContextLoader(appProvider.loader)(main.run(appConfig)) catch case e: xsbti.FullReload => if e.clean then delete(launcher.bootDirectory) else () throw e finally JAnsi.uninstall(launcher.topLoader) @tailrec final def launch( run: RunConfiguration => xsbti.MainResult )(config: RunConfiguration): Option[Int] = run(config) match case e: xsbti.Exit => Some(e.code) case _: xsbti.Continue => None case r: xsbti.Reboot => launch(run)( new RunConfiguration(Option(r.scalaVersion), r.app, r.baseDirectory, r.arguments.toList) ) case x => throw new BootException( "invalid main result: " + x + (if x eq null then "" else " (class: " + x.getClass + ")") ) private def withContextLoader[T](loader: ClassLoader)(eval: => T): T = val oldLoader = Thread.currentThread.getContextClassLoader Thread.currentThread.setContextClassLoader(loader) try eval finally Thread.currentThread.setContextClassLoader(oldLoader) // Cache of classes for lookup later. val ServerMainClass = classOf[xsbti.ServerMain] val AppMainClass = classOf[xsbti.AppMain] final class RunConfiguration( val scalaVersion: Option[String], val app: xsbti.ApplicationID, val workingDirectory: File, val arguments: List[String] ) import BootConfiguration.{ appDirectoryName, baseDirectoryName, extractScalaVersion, ScalaDirectoryName, TestLoadScala2Classes, TestLoadScala3Classes, ScalaOrg } class Launch private[xsbt] ( val bootDirectory: File, val lockBoot: Boolean, val ivyOptions: IvyOptions ) extends xsbti.Launcher: import ivyOptions.{ checksums as checksumsList, classifiers, repositories } bootDirectory.mkdirs def getScala(version: String): xsbti.ScalaProvider = getScala(version, "") def getScala(version: String, reason: String): xsbti.ScalaProvider = getScala(version, reason, ScalaOrg) def getScala(version: String, reason: String, scalaOrg: String) = scalaProviders((scalaOrg, version), reason) def app(id: xsbti.ApplicationID, version: String): xsbti.AppProvider = app(id, Option(version)) def app(id: xsbti.ApplicationID, scalaVersion: Option[String]): xsbti.AppProvider = getAppProvider(id, scalaVersion, false) val bootLoader = new BootFilteredLoader(getClass.getClassLoader) @nowarn private val initLoader: ClassLoader = if isWindows && !isCygwin then val version = sys.props.get(Configuration.SbtVersionProperty) orElse Configuration.guessSbtVersion if version.fold(false)(_.startsWith("0.")) then jansiLoader(bootLoader) else bootLoader else bootLoader private val scalaProviderClassLoader = new AtomicReference(initLoader) def topLoader: ClassLoader = scalaProviderClassLoader.get() private val scalaProviders = new Cache[(String, String), String, xsbti.ScalaProvider]((x, y) => getScalaProvider(x._1, x._2, y, scalaProviderClassLoader.get) ) val updateLockFile = if lockBoot then Some(new File(bootDirectory, "sbt.boot.lock")) else None def globalLock: xsbti.GlobalLock = Locks def ivyHome = orNull(ivyOptions.ivyHome) def ivyRepositories = (repositories: List[xsbti.Repository]).toArray def appRepositories = ((repositories filterNot (_.bootOnly)): List[xsbti.Repository]).toArray def isOverrideRepositories: Boolean = ivyOptions.isOverrideRepositories def checksums = checksumsList.toArray[String] // JAnsi needs to be shared between Scala and the application so there aren't two competing versions @deprecated("sbt handles jansi management itself", "1.1.6") def jansiLoader(parent: ClassLoader): ClassLoader = val id = AppID( Seq("org", "fusesource", "jansi").mkString("."), "jansi", JAnsiVersion, "", toArray(Nil), xsbti.CrossValue.Disabled, array() ) val jansiHome = appDirectory(new File(bootDirectory, baseDirectoryName(ScalaOrg, None)), id) val module = appModule(id, None, false, "jansi") def makeLoader(): ClassLoader = val urls = toURLs(wrapNull(jansiHome.listFiles(JarFilter))) val loader = new URLClassLoader(urls, bootLoader) checkLoader( loader, module, Seq("org", "fusesource", "jansi", "internal", "WindowsSupport").mkString(".") :: Nil, loader ) val existingLoader = if jansiHome.exists then try Some(makeLoader()) catch case _: Exception => None else None existingLoader.getOrElse { update(module, "") makeLoader() } def checkLoader[T]( loader: ClassLoader, module: ModuleDefinition, testClasses: Seq[String], ifValid: T ): T = val missing = getMissing(loader, testClasses) if missing.isEmpty then ifValid else module.retrieveCorrupt(missing) private def makeConfiguration( scalaOrg: String, version: Option[String] ): UpdateConfiguration = new UpdateConfiguration( bootDirectory, ivyOptions.ivyHome, scalaOrg, version, repositories, checksumsList ) final def getAppProvider( id: xsbti.ApplicationID, explicitScalaVersion: Option[String], forceAppUpdate: Boolean ): xsbti.AppProvider = locked( new Callable[xsbti.AppProvider]: def call = getAppProvider0(id, explicitScalaVersion, forceAppUpdate) ) @tailrec private final def getAppProvider0( id: xsbti.ApplicationID, explicitScalaVersion0: Option[String], forceAppUpdate: Boolean ): xsbti.AppProvider = val explicitScalaVersion = explicitScalaVersion0 match case Some(sv) => Some(sv) case _ => // https://github.com/sbt/sbt/issues/6587 // set the Scala version of sbt 1.4.x series to 2.12.12 explicitly // since util-interface depends on Scala 2.13 by mistake // https://github.com/sbt/sbt/blob/v1.4.0/project/Dependencies.scala if id.groupID() == "org.scala-sbt" && id.name() == "sbt" && id.version().startsWith("1.4.") then Some("2.12.12") else None val app = appModule(id, explicitScalaVersion, true, "app") /** Replace the version of an ApplicationID with the given one, if set. */ def resolveId(appVersion: Option[String], id: xsbti.ApplicationID) = appVersion map { v => import id.* AppID( groupID(), name(), v, mainClass(), mainComponents(), crossVersionedValue(), classpathExtra() ) } getOrElse id val baseDirs = (resolvedVersion: Option[String]) => (base: File) => appBaseDirs(base, resolveId(resolvedVersion, id)) def retrieve() = val (appv, sv) = update(app, "") val scalaVersion = strictOr(explicitScalaVersion, sv) new RetrievedModule(true, app, sv, appv, baseDirs(appv)(scalaHome(ScalaOrg, scalaVersion))) val retrievedApp = if forceAppUpdate then retrieve() else existing(app, ScalaOrg, explicitScalaVersion, baseDirs(None)) getOrElse retrieve() val testInterface = java.util.regex.Pattern.compile("test-interface-[0-9.]+\\.jar") retrievedApp.fullClasspath.find(f => testInterface.matcher(f.getName).find()).foreach { f => scalaProviderClassLoader.set(new TestInterfaceLoader(f, initLoader, topLoader)) } // https://github.com/sbt/sbt/issues/4955 // When sbt core artifacts are not properly downloaded, this the point that fails with "No Scala version specified or detected" val scalaVersion = getOrError( strictOr(explicitScalaVersion, retrievedApp.detectedScalaVersion), s"""sbt/sbt#4955! |sbt launcher is unable to detect the Scala version for ${id.groupID}:${id.name}; |this likely indicates an incomplete artifact resolution and/or corrupt boot cache (~/.sbt/boot/). |the following is the full classpath that was retrieved for ${id.groupID}:${id.name}: |""".stripMargin + retrievedApp.fullClasspath.toList .map(x => " * " + x.getAbsolutePath) .mkString(System.lineSeparator) ) val scalaProvider = getScala(scalaVersion, "(for " + id.name + ")") val resolvedId = resolveId(retrievedApp.resolvedAppVersion, id) val (missing, appProvider) = checkedAppProvider(resolvedId, retrievedApp, scalaProvider) if missing.isEmpty then appProvider else if retrievedApp.fresh then app.retrieveCorrupt(missing) else getAppProvider0(resolvedId, explicitScalaVersion, true) def scalaHome(scalaOrg: String, scalaVersion: Option[String]): File = new File(bootDirectory, baseDirectoryName(scalaOrg, scalaVersion)) def appHome(id: xsbti.ApplicationID, scalaVersion: Option[String]): File = appDirectory(scalaHome(ScalaOrg, scalaVersion), id) def checkedAppProvider( id: xsbti.ApplicationID, module: RetrievedModule, scalaProvider: xsbti.ScalaProvider ): (Iterable[String], xsbti.AppProvider) = val p = appProvider(id, module, scalaProvider, appHome(id, Some(scalaProvider.version))) val missing = getMissing(p.loader, id.mainClass :: Nil) (missing, p) private def locked[T](c: Callable[T]): T = Locks(orNull(updateLockFile), c) def getScalaProvider( scalaOrg: String, scalaVersion: String, reason: String, classLoader: ClassLoader ): xsbti.ScalaProvider = locked( new Callable[xsbti.ScalaProvider]: def call = getScalaProvider0(scalaOrg, scalaVersion, reason, classLoader) ) private final def getScalaProvider0( scalaOrg: String, scalaVersion: String, reason: String, classLoader: ClassLoader ) = val scalaM = scalaModule(scalaOrg, scalaVersion) val (_, lib) = scalaDirs(scalaM, scalaOrg, scalaVersion) val baseDirs = lib :: Nil def testLoadScalaClasses = if scalaVersion.startsWith("2.") then TestLoadScala2Classes else TestLoadScala3Classes def provider(retrieved: RetrievedModule): xsbti.ScalaProvider = val p = scalaProvider(scalaVersion, retrieved, classLoader, lib) checkLoader(p.loader, retrieved.definition, testLoadScalaClasses, p) existing(scalaM, scalaOrg, Some(scalaVersion), _ => baseDirs) flatMap { mod => try Some(provider(mod)) catch case _: Exception => None } getOrElse { val (_, scalaVersion) = update(scalaM, reason) provider(new RetrievedModule(true, scalaM, scalaVersion, baseDirs)) } def existing( module: ModuleDefinition, scalaOrg: String, explicitScalaVersion: Option[String], baseDirs: File => List[File] ): Option[RetrievedModule] = val filter = new java.io.FileFilter: val explicitName = explicitScalaVersion.map(sv => baseDirectoryName(scalaOrg, Some(sv))) def accept(file: File) = file.isDirectory && explicitName.forall(_ == file.getName) val retrieved = wrapNull(bootDirectory.listFiles(filter)) flatMap { scalaDir => val appDir = directory(scalaDir, module.target) if appDir.exists then new RetrievedModule(false, module, extractScalaVersion(scalaDir), baseDirs(scalaDir)) :: Nil else Nil } retrieved.headOption def directory(scalaDir: File, target: UpdateTarget): File = target match case _: UpdateScala => scalaDir case ua: UpdateApp => appDirectory(scalaDir, ua.id.toID) def appBaseDirs(scalaHome: File, id: xsbti.ApplicationID): List[File] = val appHome = appDirectory(scalaHome, id) val components = componentProvider(appHome) appHome :: id.mainComponents.map(components.componentLocation).toList def appDirectory(base: File, id: xsbti.ApplicationID): File = new File(base, appDirectoryName(id, File.separator)) @nowarn def scalaDirs(module: ModuleDefinition, scalaOrg: String, scalaVersion: String): (File, File) = val scalaHome = new File(bootDirectory, baseDirectoryName(scalaOrg, Some(scalaVersion))) val libDirectory = new File(scalaHome, ScalaDirectoryName) (scalaHome, libDirectory) def appProvider( appID: xsbti.ApplicationID, app: RetrievedModule, scalaProvider0: xsbti.ScalaProvider, appHome: File ): xsbti.AppProvider = new xsbti.AppProvider: import Launch.AppMainClass val scalaProvider = scalaProvider0 val id = appID def mainClasspath = app.fullClasspath lazy val loader = app.createLoader(scalaProvider.loader) // TODO - For some reason we can't call this from vanilla scala. We get a // no such method exception UNLESS we're in the same project. lazy val entryPoint: Class[?] = val c = Class.forName(id.mainClass, true, loader) if ServerApplication.isServerApplication(c) then c else if classOf[xsbti.AppMain].isAssignableFrom(c) then c else if PlainApplication.isPlainApplication(c) then c else sys.error( s"${c} is not an instance of xsbti.AppMain, xsbti.ServerMain nor does it have one of these static methods:\n" + " * void main(String[] args)\n * int main(String[] args)\n * xsbti.Exit main(String[] args)\n" ) // Deprecated API. Remove when we can. def mainClass: Class[? <: xsbti.AppMain] = entryPoint.asSubclass(AppMainClass) def newMain(): xsbti.AppMain = if ServerApplication.isServerApplication(entryPoint) then ServerApplication(this) else if AppMainClass.isAssignableFrom(entryPoint) then mainClass.getDeclaredConstructor().newInstance() else if PlainApplication.isPlainApplication(entryPoint) then PlainApplication(entryPoint) else throw new IncompatibleClassChangeError( s"main class ${entryPoint.getName} is not an instance of xsbti.AppMain, xsbti.ServerMain nor does it have a valid `main` method." ) lazy val components = componentProvider(appHome) def componentProvider(appHome: File) = new ComponentProvider(appHome, lockBoot) def scalaProvider( scalaVersion: String, module: RetrievedModule, parentLoader: ClassLoader, scalaLibDir: File ): xsbti.ScalaProvider = new xsbti.ExtendedScalaProvider: def launcher: Launch = Launch.this def version = scalaVersion private object LoaderInit: val (library, other) = module.fullClasspath.partition(_.getName.startsWith("scala-library")) val libraryLoader = new LibraryClassLoader(toURLs(library), parentLoader, scalaVersion) val fullLoader = new URLClassLoader(toURLs(other), libraryLoader) override def loaderLibraryOnly(): ClassLoader = LoaderInit.libraryLoader override def loader(): ClassLoader = LoaderInit.fullLoader def compilerJar = new File(scalaLibDir, CompilerModuleName + ".jar") def libraryJar = new File(scalaLibDir, LibraryModuleName + ".jar") def jars = module.fullClasspath def app(id: xsbti.ApplicationID) = Launch.this.app(id, Some(scalaVersion)) def appModule( id: xsbti.ApplicationID, scalaVersion: Option[String], getClassifiers: Boolean, tpe: String ): ModuleDefinition = new ModuleDefinition( configuration = makeConfiguration(ScalaOrg, scalaVersion), target = new UpdateApp( Application(id), if getClassifiers then Value.get(classifiers.app) else Nil, tpe ), failLabel = id.name + " " + id.version, extraClasspath = id.classpathExtra ) def scalaModule(org: String, version: String): ModuleDefinition = new ModuleDefinition( configuration = makeConfiguration(org, Some(version)), target = new UpdateScala(Value.get(classifiers.forScala)), failLabel = "Scala " + version, extraClasspath = array() ) /** Returns the resolved appVersion (if this was an App), as well as the scalaVersion. */ def update(mm: ModuleDefinition, reason: String): (Option[String], Option[String]) = val result = (new Update(mm.configuration))(mm.target, reason) if result.success then result.appVersion -> result.scalaVersion else mm.retrieveFailed object Launcher: def apply(bootDirectory: File, repositories: List[Repository.Repository]): xsbti.Launcher = apply( bootDirectory, IvyOptions( None, Classifiers(Nil, Nil), repositories, BootConfiguration.DefaultChecksums, false ) ) def apply(bootDirectory: File, ivyOptions: IvyOptions): xsbti.Launcher = apply(bootDirectory, ivyOptions, GetLocks.find) def apply(bootDirectory: File, ivyOptions: IvyOptions, locks: xsbti.GlobalLock): xsbti.Launcher = new Launch(bootDirectory, true, ivyOptions): override def globalLock = locks def apply(explicit: LaunchConfiguration): xsbti.Launcher = new Launch(explicit.boot.directory, explicit.boot.lock, explicit.ivyConfiguration) def defaultAppProvider(baseDirectory: File): xsbti.AppProvider = getAppProvider(baseDirectory, Configuration.configurationOnClasspath) def getAppProvider(baseDirectory: File, configLocation: URL): xsbti.AppProvider = val parsed = ResolvePaths(Configuration.parse(configLocation, baseDirectory), baseDirectory) Initialize.process(parsed.boot.properties, parsed.appProperties, Initialize.selectQuick) val config = ResolveValues(parsed) val launcher = apply(config) launcher.app(config.app.toID, orNull(config.getScalaVersion)) // Do not change this without testing sbt 1.3.13 // https://github.com/sbt/sbt/blob/v1.3.13/main/src/main/scala/sbt/internal/XMainConfiguration.scala#L32-L33 // https://github.com/sbt/sbt/issues/6405 @nowarn private[boot] class TestInterfaceLoader(jar: File, parent: ClassLoader, topLoader: ClassLoader) extends URLClassLoader(Array(jar.toURI.toURL), topLoader) class ComponentProvider(baseDirectory: File, lockBoot: Boolean) extends xsbti.ComponentProvider: def componentLocation(id: String): File = new File(baseDirectory, id) def component(id: String) = wrapNull(componentLocation(id).listFiles).filter(_.isFile) def defineComponent(id: String, files: Array[File]) = val location = componentLocation(id) if location.exists then throw new BootException( "cannot redefine component. ID: " + id + ", files: " + files.mkString(",") ) else Copy(files.toList, location) () def addToComponent(id: String, files: Array[File]): Boolean = Copy(files.toList, componentLocation(id)) def lockFile = if lockBoot then ComponentProvider.lockFile(baseDirectory) else null // null for the Java interface object ComponentProvider: def lockFile(baseDirectory: File) = baseDirectory.mkdirs() new File(baseDirectory, "sbt.components.lock") sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/LaunchConfiguration.scala000066400000000000000000000223411522561027200331130ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import Pre.* import java.io.File import java.net.URL import scala.annotation.nowarn import scala.collection.immutable.List //TODO: use copy constructor, check size change final case class LaunchConfiguration( scalaVersion: Value[String], ivyConfiguration: IvyOptions, app: Application, boot: BootSetup, logging: Logging, appProperties: List[AppProperty], serverConfig: Option[ServerConfiguration] ): def isServer: Boolean = serverConfig.isDefined def getScalaVersion = val sv = Value.get(scalaVersion) if sv == "auto" then None else Some(sv) def withScalaVersion(newScalaVersion: String) = LaunchConfiguration( Value.Explicit(newScalaVersion), ivyConfiguration, app, boot, logging, appProperties, serverConfig ) def withApp(app: Application) = LaunchConfiguration( scalaVersion, ivyConfiguration, app, boot, logging, appProperties, serverConfig ) def withAppVersion(newAppVersion: String) = LaunchConfiguration( scalaVersion, ivyConfiguration, app.withVersion(Value.Explicit(newAppVersion)), boot, logging, appProperties, serverConfig ) // TODO: withExplicit def withNameAndVersions( newScalaVersion: String, newAppVersion: String, newAppName: String, classifiers0: Classifiers ) = LaunchConfiguration( Value.Explicit(newScalaVersion), ivyConfiguration.copy(classifiers = classifiers0), app.withVersion(Value.Explicit(newAppVersion)).withName(Value.Explicit(newAppName)), boot, logging, appProperties, serverConfig ) def map(f: File => File) = LaunchConfiguration( scalaVersion, ivyConfiguration.map(f), app.map(f), boot.map(f), logging, appProperties, serverConfig.map(_.map(f)) ) object LaunchConfiguration: // Saves a launch configuration into a file. This is only safe if it is loaded by the *same* launcher version. def save(config: LaunchConfiguration, f: File): Unit = val out = new java.io.ObjectOutputStream(new java.io.FileOutputStream(f)) try out.writeObject(config) finally out.close() // Restores a launch configuration from a file. This is only safe if it is loaded by the *same* launcher version. def restore(url: URL): LaunchConfiguration = val in = new java.io.ObjectInputStream(url.openConnection.getInputStream) try in.readObject.asInstanceOf[LaunchConfiguration] finally in.close() final case class ServerConfiguration( lockFile: File, jvmArgs: Option[File], jvmPropsFile: Option[File] ): def map(f: File => File) = ServerConfiguration(f(lockFile), jvmArgs map f, jvmPropsFile map f) final case class IvyOptions( ivyHome: Option[File], classifiers: Classifiers, repositories: List[Repository.Repository], checksums: List[String], isOverrideRepositories: Boolean ): def map(f: File => File) = IvyOptions(ivyHome.map(f), classifiers, repositories, checksums, isOverrideRepositories) enum Value[A1]: case Explicit(value: A1) extends Value[A1] case Implicit(name: String, default: Option[A1]) extends Value[A1] override def toString: String = this match case Explicit(value) => value.toString() case Implicit(name, default) => name + (default match case Some(d) => "[" + d + "]" case None => "") object Value: def get[A1](v: Value[A1]): A1 = v match case e: Explicit[A1] => e.value; case _ => throw new BootException("unresolved version: " + v) def readImplied[A1](s: String, name: String, default: Option[String])(using read: String => A1 ): Value[A1] = if s == "read" then Value.Implicit(name, default map read) else Pre.error("expected 'read', got '" + s + "'") end Value final case class Classifiers(forScala: Value[List[String]], app: Value[List[String]]) object Classifiers: def apply(forScala: List[String], app: List[String]): Classifiers = Classifiers(Value.Explicit(forScala), Value.Explicit(app)) object LaunchCrossVersion: def apply(s: String): xsbti.CrossValue = s match case _ if CrossVersionUtil.isFull(s) => xsbti.CrossValue.Full case _ if CrossVersionUtil.isBinary(s) => xsbti.CrossValue.Binary case _ if CrossVersionUtil.isDisabled(s) => xsbti.CrossValue.Disabled case x => Pre.error("unknown value '" + x + "' for property 'cross-versioned'") final case class Application( groupID: String, name: Value[String], version: Value[String], main: String, components: List[String], crossVersioned: xsbti.CrossValue, classpathExtra: Array[File] ): def getName = Value.get(name) def withName(newName: Value[String]) = Application(groupID, newName, version, main, components, crossVersioned, classpathExtra) def getVersion = Value.get(version) def withVersion(newVersion: Value[String]) = Application(groupID, name, newVersion, main, components, crossVersioned, classpathExtra) def toID = AppID(groupID, getName, getVersion, main, toArray(components), crossVersioned, classpathExtra) def map(f: File => File) = Application(groupID, name, version, main, components, crossVersioned, classpathExtra.map(f)) final case class AppID( groupID: String, name: String, version: String, mainClass: String, mainComponents: Array[String], crossVersionedValue: xsbti.CrossValue, classpathExtra: Array[File] ) extends xsbti.ApplicationID: def crossVersioned: Boolean = crossVersionedValue != xsbti.CrossValue.Disabled object Application: def apply(id: xsbti.ApplicationID): Application = import id.* Application( groupID, Value.Explicit(name), Value.Explicit(version), mainClass, mainComponents.toList, safeCrossVersionedValue(id), classpathExtra ) @nowarn private def safeCrossVersionedValue(id: xsbti.ApplicationID): xsbti.CrossValue = try id.crossVersionedValue catch case _: AbstractMethodError => // Before 0.13 this method did not exist on application, so we need to provide a default value // in the event we're dealing with an older Application. if id.crossVersioned then xsbti.CrossValue.Binary else xsbti.CrossValue.Disabled object Repository: trait Repository extends xsbti.Repository: def bootOnly: Boolean final case class Maven( id: String, url: URL, bootOnly: Boolean = false, allowInsecureProtocol: Boolean = false ) extends xsbti.MavenRepository with Repository final case class Ivy( id: String, url: URL, ivyPattern: String, artifactPattern: String, mavenCompatible: Boolean, bootOnly: Boolean = false, descriptorOptional: Boolean = false, skipConsistencyCheck: Boolean = false, allowInsecureProtocol: Boolean = false ) extends xsbti.IvyRepository with Repository final case class Predefined(id: xsbti.Predefined, bootOnly: Boolean = false) extends xsbti.PredefinedRepository with Repository object Predefined: def apply(s: String): Predefined = new Predefined(xsbti.Predefined.toValue(s), false) def apply(s: String, bootOnly: Boolean): Predefined = new Predefined(xsbti.Predefined.toValue(s), bootOnly) def isMavenLocal(repo: xsbti.Repository) = repo match case p: xsbti.PredefinedRepository => p.id == xsbti.Predefined.MavenLocal; case _ => false def defaults: List[xsbti.Repository] = xsbti.Predefined.values.map(x => Predefined(x, false)).toList final case class Search(tpe: Search.Value, paths: List[File]) object Search extends Enumeration: def none = Search(Current, Nil) val Only = value("only") val RootFirst = value("root-first") val Nearest = value("nearest") val Current = value("none") def apply(s: String, paths: List[File]): Search = Search(toValue(s), paths) final case class BootSetup( directory: File, lock: Boolean, properties: File, search: Search, promptCreate: String, enableQuick: Boolean, promptFill: Boolean ): def map(f: File => File) = BootSetup(f(directory), lock, f(properties), search, promptCreate, enableQuick, promptFill) final case class AppProperty(name: String)( val quick: Option[PropertyInit], val create: Option[PropertyInit], val fill: Option[PropertyInit] ) enum PropertyInit: case SetProperty(val value: String) case PromptProperty(val label: String, val default: Option[String]) final class Logging(level: LogLevel.Value) extends Serializable: def log(s: => String, at: LogLevel.Value) = if level.id <= at.id then stream(at).println("[" + at + "] " + s) def debug(s: => String) = log(s, LogLevel.Debug) private def stream(at: LogLevel.Value) = if at == LogLevel.Error then System.err else System.out object LogLevel extends Enumeration: val Debug = value("debug", 0) val Info = value("info", 1) val Warn = value("warn", 2) val Error = value("error", 3) def apply(s: String): Logging = new Logging(toValue(s)) final class AppConfiguration( val arguments: Array[String], val baseDirectory: File, val provider: xsbti.AppProvider ) extends xsbti.AppConfiguration sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/LibraryClassLoader.scala000066400000000000000000000006101522561027200326650ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.net.{ URL, URLClassLoader } final class LibraryClassLoader(urls: Array[URL], parent: ClassLoader, val scalaVersion: String) extends URLClassLoader(urls, parent) with xsbti.LibraryClassLoader: override def toString: String = s"LibraryClassLoader(jar = ${urls.head}, parent = $parent)" sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/ListMap.scala000066400000000000000000000027771522561027200305350ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import Pre.* import scala.collection.Iterable import scala.collection.immutable.List // preserves iteration order sealed class ListMap[K, V] private (backing: List[(K, V)]) extends Iterable[(K, V)] // use Iterable because Traversable.toStream loops : import ListMap.remove def update(k: K, v: V) = this.+((k, v)) def +(pair: (K, V)) = copy(pair :: remove(backing, pair._1)) def -(k: K) = copy(remove(backing, k)) def get(k: K): Option[V] = backing.find(_._1 == k).map(_._2) def keys: List[K] = backing.reverse.map(_._1) def apply(k: K): V = getOrError(get(k), "Key " + k + " not found") def contains(k: K): Boolean = get(k).isDefined def iterator = backing.reverse.iterator override def isEmpty: Boolean = backing.isEmpty override def toList = backing.reverse override def toSeq: Seq[(K, V)] = toList protected def copy(newBacking: List[(K, V)]): ListMap[K, V] = new ListMap(newBacking) def default(defaultF: K => V): ListMap[K, V] = new ListMap[K, V](backing): override def apply(k: K) = super.get(k).getOrElse(defaultF(k)) override def copy(newBacking: List[(K, V)]) = super.copy(newBacking).default(defaultF) override def toString = backing.mkString("ListMap(", ",", ")") object ListMap: def apply[K, V](pairs: (K, V)*) = new ListMap[K, V](pairs.toList.distinct) def empty[K, V] = new ListMap[K, V](Nil) private def remove[K, V](backing: List[(K, V)], k: K) = backing.filter(_._1 != k) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/Locks.scala000066400000000000000000000073711522561027200302320ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.io.{ File, FileOutputStream, IOException } import java.nio.channels.FileChannel import java.util.concurrent.Callable import scala.collection.immutable.List import scala.annotation.tailrec object GetLocks: /** * Searches for Locks in parent class loaders before returning Locks from this class loader. * Normal class loading doesn't work because the launcher class loader hides xsbt classes. */ def find: xsbti.GlobalLock = Loaders(getClass.getClassLoader.getParent).flatMap(tryGet).headOption.getOrElse(Locks) private def tryGet(loader: ClassLoader): List[xsbti.GlobalLock] = try getLocks0(loader) :: Nil catch case _: ClassNotFoundException => Nil private def getLocks0(loader: ClassLoader) = Class .forName("xsbt.boot.Locks$", true, loader) .getField("MODULE$") .get(null) .asInstanceOf[xsbti.GlobalLock] // gets a file lock by first getting a JVM-wide lock. object Locks extends xsbti.GlobalLock: private val locks = new Cache[File, Unit, GlobalLock]((f, _) => new GlobalLock(f)) def apply[T](file: File, action: Callable[T]): T = if file eq null then action.call else apply0(file, action) private def apply0[T](file: File, action: Callable[T]): T = val lock = synchronized { file.getParentFile.mkdirs() try file.createNewFile() catch case e: IOException => throw new IOException(s"failed to create lock file $file", e) locks(file.getCanonicalFile, ()) } lock.withLock(action) private class GlobalLock(file: File): private var fileLocked = false def withLock[T](run: Callable[T]): T = synchronized { if fileLocked then run.call else fileLocked = true try ignoringDeadlockAvoided(run) finally fileLocked = false } // https://github.com/sbt/sbt/issues/650 // This approach means a real deadlock won't be detected @tailrec private def ignoringDeadlockAvoided[T](run: Callable[T]): T = val result = try Some(withFileLock(run)) catch case i: IOException if isDeadlockAvoided(i) => // there should be a timeout to the deadlock avoidance, so this is just a backup Thread.sleep(200) None result match // workaround for no tailrec optimization in the above try/catch case Some(t) => t case None => ignoringDeadlockAvoided(run) // The actual message is not specified by FileChannel.lock, so this may need to be adjusted for different JVMs private def isDeadlockAvoided(i: IOException): Boolean = i.getMessage == "Resource deadlock avoided" private def withFileLock[T](run: Callable[T]): T = def withChannelRetries(retries: Int)(channel: FileChannel): T = try withChannel(channel) catch case i: InternalLockNPE => if retries > 0 then withChannelRetries(retries - 1)(channel) else throw i def withChannel(channel: FileChannel) = val freeLock = try channel.tryLock catch case e: NullPointerException => throw new InternalLockNPE(e) if freeLock eq null then Console.err.println("[info] waiting for lock on " + file + " to be available..."); val lock = try channel.lock catch case e: NullPointerException => throw new InternalLockNPE(e) try run.call finally lock.release() else try run.call finally freeLock.release() Using(new FileOutputStream(file).getChannel)(withChannelRetries(5)) private final class InternalLockNPE(cause: Exception) extends RuntimeException(cause) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/ModuleDefinition.scala000066400000000000000000000034111522561027200324040ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import Pre.* import java.io.File import java.net.URLClassLoader import UpdateTarget.* final class ModuleDefinition( val configuration: UpdateConfiguration, val extraClasspath: Array[File], val target: UpdateTarget, val failLabel: String ): def retrieveFailed: Nothing = fail("") def retrieveCorrupt(missing: Iterable[String]): Nothing = fail(": missing " + missing.mkString(", ")) private def fail(extra: String) = throw new xsbti.RetrieveException(versionString, "could not retrieve " + failLabel + extra) private def versionString: String = target match case _: UpdateScala => configuration.getScalaVersion; case a: UpdateApp => Value.get(a.id.version) final class RetrievedModule( val fresh: Boolean, val definition: ModuleDefinition, val detectedScalaVersion: Option[String], val resolvedAppVersion: Option[String], val baseDirectories: List[File] ): /** Use this constructor only when the module exists already, or when its version is not dynamic (so its resolved version would be the same) */ def this( fresh: Boolean, definition: ModuleDefinition, detectedScalaVersion: Option[String], baseDirectories: List[File] ) = this(fresh, definition, detectedScalaVersion, None, baseDirectories) lazy val monkeys: Array[File] = sys.props.get("sbt.launcher.cp.prepend").toArray.flatMap(ms => ms.split(",").map(new File(_))) lazy val classpath: Array[File] = getJars(baseDirectories) lazy val fullClasspath: Array[File] = concat(concat(monkeys, classpath), definition.extraClasspath) def createLoader(parentLoader: ClassLoader): ClassLoader = new URLClassLoader(toURLs(fullClasspath), parentLoader) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/ParallelExecution.scala000066400000000000000000000007031522561027200325670ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.util.concurrent.Executors import scala.concurrent.ExecutionContext private[xsbt] object ParallelExecution: protected[xsbt] val executionContext = // ExecutionContext.fromExecutor(Executors.newCachedThreadPool()) ExecutionContext.fromExecutor( Executors.newFixedThreadPool( Runtime.getRuntime.availableProcessors() ) ) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/PlainApplication.scala000066400000000000000000000037641522561027200324100ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt package boot /** A wrapper around 'raw' static methods to meet the sbt application interface. */ class PlainApplication private (mainMethod: java.lang.reflect.Method) extends xsbti.AppMain: override def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = // TODO - Figure out if the main method returns an Int... val IntClass = classOf[Int] val ExitClass = classOf[xsbti.Exit] // It seems we may need to wrap exceptions here... try mainMethod.getReturnType match case ExitClass => mainMethod.invoke(null, configuration.arguments).asInstanceOf[xsbti.Exit] case IntClass => PlainApplication.Exit(mainMethod.invoke(null, configuration.arguments).asInstanceOf[Int]) case _ => // Here we still invoke, but return 0 if sucessful (no exceptions). mainMethod.invoke(null, configuration.arguments) PlainApplication.Exit(0) catch // This is only thrown if the underlying reflective call throws. // Let's expose the underlying error. case e: java.lang.reflect.InvocationTargetException if e.getCause != null => throw e.getCause /** An object that lets us detect compatible "plain" applications and launch them reflectively. */ object PlainApplication: def isPlainApplication(clazz: Class[?]): Boolean = findMainMethod(clazz).isDefined def apply(clazz: Class[?]): xsbti.AppMain = findMainMethod(clazz) match case Some(method) => new PlainApplication(method) case None => sys.error("class: " + clazz + " does not have a main method!") private def findMainMethod(clazz: Class[?]): Option[java.lang.reflect.Method] = try val method = clazz.getMethod("main", classOf[Array[String]]) if java.lang.reflect.Modifier.isStatic(method.getModifiers) then Some(method) else None catch case _: NoSuchMethodException => None case class Exit(code: Int) extends xsbti.Exit sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/Pre.scala000066400000000000000000000106421522561027200277000ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.io.{ File, FileFilter } import java.net.URL import java.util.Locale import scala.collection.immutable.List import scala.reflect.ClassTag object Pre: def readLine(prompt: String): Option[String] = val c = System.console() if c eq null then None else Option(c.readLine(prompt)) def trimLeading(line: String) = def newStart(i: Int): Int = if i >= line.length || !Character.isWhitespace(line.charAt(i)) then i else newStart(i + 1) line.substring(newStart(0)) def isEmpty(line: String) = line.length == 0 def isNonEmpty(line: String) = line.length > 0 def assert(condition: Boolean, msg: => String): Unit = if !condition then throw new AssertionError(msg) def assert(condition: Boolean): Unit = assert(condition, "assertion failed") def require(condition: Boolean, msg: => String): Unit = if !condition then throw new IllegalArgumentException(msg) def error(msg: String): Nothing = throw new BootException(prefixError(msg)) def declined(msg: String): Nothing = throw new BootException(msg) def prefixError(msg: String): String = "error during sbt launcher: " + msg def toBoolean(s: String) = java.lang.Boolean.parseBoolean(s) def toArray[T: ClassTag](list: List[T]) = val arr = new Array[T](list.length) def copy(i: Int, rem: List[T]): Unit = if i < arr.length then arr(i) = rem.head copy(i + 1, rem.tail) copy(0, list) arr /* These exist in order to avoid bringing in dependencies on RichInt and ArrayBuffer, among others. */ def concat(a: Array[File], b: Array[File]): Array[File] = val n = new Array[File](a.length + b.length) java.lang.System.arraycopy(a, 0, n, 0, a.length) java.lang.System.arraycopy(b, 0, n, a.length, b.length) n def array(files: File*): Array[File] = toArray(files.toList) /* Saves creating a closure for default if it has already been evaluated*/ def orElse[T](opt: Option[T], default: T) = if opt.isDefined then opt.get else default def wrapNull(a: Array[File]): Array[File] = if a == null then new Array[File](0) else a def const[B](b: B): Any => B = _ => b def strictOr[T](a: Option[T], b: Option[T]): Option[T] = a match case None => b; case _ => a def getOrError[T](a: Option[T], msg: String): T = a match case None => error(msg); case Some(x) => x def orNull[T >: Null](t: Option[T]): T = t match case None => null; case Some(x) => x def getJars(directories: List[File]): Array[File] = toArray(directories.flatMap(directory => wrapNull(directory.listFiles(JarFilter)))) object JarFilter extends FileFilter: def accept(file: File) = !file.isDirectory && file.getName.endsWith(".jar") def getMissing(loader: ClassLoader, classes: Iterable[String]): Iterable[String] = def classMissing(c: String) = try Class.forName(c, false, loader); false catch case _: ClassNotFoundException => true classes.toList.filter(classMissing) def toURLs(files: Array[File]): Array[URL] = files.map(_.toURI.toURL) def toFile(url: URL): File = try new File(url.toURI) catch case _: java.net.URISyntaxException => new File(url.getPath) def delete(f: File): Unit = if f.isDirectory then val fs = f.listFiles() if fs ne null then fs foreach delete if f.exists then f.delete() () final val isWindows: Boolean = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows") final val isCygwin: Boolean = isWindows && java.lang.Boolean.getBoolean("sbt.cygwin") private[boot] def substituteTilde(path: String): String = path.replaceFirst("^~(/|\\\\|$)", System.getProperty("user.home") + "$1") import java.util.Properties import java.io.{ FileInputStream, FileOutputStream } private[boot] def readProperties(propertiesFile: File) = val properties = new Properties if propertiesFile.exists then Using(new FileInputStream(propertiesFile))(properties.load) properties private[boot] def writeProperties(properties: Properties, file: File, msg: String): Unit = file.getParentFile.mkdirs() Using(new FileOutputStream(file))(out => properties.store(out, msg)) private[boot] def setSystemProperties(properties: Properties): Unit = val nameItr = properties.stringPropertyNames.iterator while nameItr.hasNext do val propName = nameItr.next System.setProperty(propName, properties.getProperty(propName)) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/ResolveValues.scala000066400000000000000000000031131522561027200317440ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import Pre.* object ResolveValues: def apply(conf: LaunchConfiguration): LaunchConfiguration = (new ResolveValues(conf))() private def trim(s: String): Option[String] = if s eq null then None else notEmpty(s.trim) private def notEmpty(s: String): Option[String] = if isEmpty(s) then None else Some(s) import ResolveValues.trim final class ResolveValues(conf: LaunchConfiguration): private def propertiesFile = conf.boot.properties private lazy val properties = readProperties(propertiesFile) def apply(): LaunchConfiguration = import conf.* val scalaVersion = resolve(conf.scalaVersion) val appVersion = resolve(app.version) val appName = resolve(app.name) val classifiers = resolveClassifiers(ivyConfiguration.classifiers) withNameAndVersions(scalaVersion, appVersion, appName, classifiers) def resolveClassifiers(classifiers: Classifiers): Classifiers = import ConfigurationParser.readIDs // the added "" ensures that the main jars are retrieved val scalaClassifiers = "" :: resolve(classifiers.forScala) val appClassifiers = "" :: resolve(classifiers.app) Classifiers(Value.Explicit(scalaClassifiers), Value.Explicit(appClassifiers)) def resolve[T](v: Value[T])(using read: String => T): T = v match case e: Value.Explicit[?] => e.value case i: Value.Implicit[?] => trim(properties.getProperty(i.name)) .map(read) .orElse(i.default) .getOrElse(sys.error("no " + i.name + " specified in " + propertiesFile)) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/ServerApplication.scala000066400000000000000000000214521522561027200326050ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt package boot import java.io.File import java.net.URI import java.io.IOException import Pre.* import scala.annotation.tailrec /** A wrapper around 'raw' static methods to meet the sbt application interface. */ class ServerApplication private (provider: xsbti.AppProvider) extends xsbti.AppMain: import ServerApplication.* override def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = val serverMain = provider.entryPoint.asSubclass(ServerMainClass).getDeclaredConstructor().newInstance() val server = serverMain.start(configuration) Console.err.println(s"${SERVER_SYNCH_TEXT}${server.uri}") server.awaitTermination() /** An object that lets us detect compatible "plain" applications and launch them reflectively. */ object ServerApplication: val SERVER_SYNCH_TEXT = "[SERVER-URI]" val ServerMainClass = classOf[xsbti.ServerMain] // TODO - We should also adapt friendly static methods into servers, perhaps... // We could even structurally type things that have a uri + awaitTermination method... def isServerApplication(clazz: Class[?]): Boolean = ServerMainClass.isAssignableFrom(clazz) def apply(provider: xsbti.AppProvider): xsbti.AppMain = new ServerApplication(provider) object ServerLocator: // TODO - Probably want to drop this to reduce classfile size private def locked[U](file: File)(f: => U): U = Locks( file, new java.util.concurrent.Callable[U]: def call(): U = f ) // We use the lock file they give us to write the server info. However, // it seems we cannot both use the server info file for locking *and* // read from it successfully. Locking seems to blank the file. SO, we create // another file near the info file to lock.a def makeLockFile(f: File): File = new File(f.getParentFile, s"${f.getName}.lock") // Launch the process and read the port... def locate(currentDirectory: File, config: LaunchConfiguration): URI = config.serverConfig match case None => sys.error("no server lock file configured. cannot locate server.") case Some(sc) => locked(makeLockFile(sc.lockFile)) { readProperties(sc.lockFile) match case Some(uri) if isReachable(uri) => uri case _ => val uri = ServerLauncher.startServer(currentDirectory, config) writeProperties(sc.lockFile, uri) uri } private val SERVER_URI_PROPERTY = "server.uri" def readProperties(f: File): Option[java.net.URI] = try val props = Pre.readProperties(f) props.getProperty(SERVER_URI_PROPERTY) match case null => None case uri => Some(new java.net.URI(uri)) catch case _: IOException => None def writeProperties(f: File, uri: URI): Unit = val props = new java.util.Properties props.setProperty(SERVER_URI_PROPERTY, uri.toASCIIString) val df = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ") df.setTimeZone(java.util.TimeZone.getTimeZone("UTC")) Pre.writeProperties(props, f, s"Server Startup at ${df.format(new java.util.Date)}") def isReachable(uri: java.net.URI): Boolean = try // TODO - For now we assume if we can connect, it means // that the server is working... val socket = new java.net.Socket(uri.getHost, uri.getPort) try socket.isConnected finally socket.close() catch case _: IOException => false /** A helper class that dumps incoming values into a print stream. */ class StreamDumper(in: java.io.BufferedReader, out: java.io.PrintStream) extends Thread: // Don't block the application for this thread. setDaemon(true) val endTime = new java.util.concurrent.atomic.AtomicLong(Long.MaxValue) override def run(): Unit = def read(): Unit = if endTime.get > System.currentTimeMillis then in.readLine match case null => () case line => out.println(line) out.flush() read() read() out.close() def close(waitForErrors: Boolean): Unit = // closing "in" blocks forever on Windows, so don't do it; // just wait a couple seconds to read more stuff if there is // any stuff. if waitForErrors then endTime.set(System.currentTimeMillis + 5000) // at this point we'd rather the dumper thread run // before we check whether to sleep Thread.`yield`() // let ourselves read more (thread should exit on earlier of endTime or EOF) while isAlive() && (endTime.get > System.currentTimeMillis) do Thread.sleep(50) else endTime.set(System.currentTimeMillis) object ServerLauncher: import ServerApplication.SERVER_SYNCH_TEXT def startServer(currentDirectory: File, config: LaunchConfiguration): URI = val serverConfig = config.serverConfig match case Some(c) => c case None => throw new RuntimeException( "logic failure: attempting to start a server that isn't configured to be a server. please report a bug." ) val launchConfig = java.io.File.createTempFile("sbtlaunch", "config") if System.getenv("SBT_SERVER_SAVE_TEMPS") eq null then launchConfig.deleteOnExit() LaunchConfiguration.save(config, launchConfig) val jvmArgs: List[String] = serverConfig.jvmArgs map readLines match case Some(args) => args case None => Nil val cmd: List[String] = ("java" :: jvmArgs) ++ ("-jar" :: defaultLauncherLookup.getCanonicalPath :: s"@load:${launchConfig.toURI.toURL.toString}" :: Nil) launchProcessAndGetUri(cmd, currentDirectory) // Here we try to isolate all the stupidity of dealing with Java processes. def launchProcessAndGetUri(cmd: List[String], cwd: File): URI = // TODO - Handle windows path stupidity in arguments. val pb = new java.lang.ProcessBuilder() pb.command(cmd*) pb.directory(cwd) val process = pb.start() // First we need to grab all the input streams, and close the ones we don't care about. process.getOutputStream.close() val stderr = process.getErrorStream val stdout = process.getInputStream // Now we start dumping out errors. val errorDumper = new StreamDumper( new java.io.BufferedReader(new java.io.InputStreamReader(stderr)), System.err ) errorDumper.start() // Now we look for the URI synch value, and then make sure we close the output files. try readUntilSynch(new java.io.BufferedReader(new java.io.InputStreamReader(stdout))) match case Some(uri) => uri case _ => // attempt to get rid of the server (helps prevent hanging / stuck locks, // though this is not reliable) try process.destroy() catch case _: Exception => // block a second to try to get stuff from stderr errorDumper.close(waitForErrors = true) sys.error(s"failed to start server process in ${pb.directory} command line ${pb.command}") finally errorDumper.close(waitForErrors = false) stdout.close() // Do not close stderr here because on Windows that will block, // and since the child process has no reason to exit, it may // block forever. errorDumper.close() instead owns the problem // of deciding what to do with stderr. object ServerUriLine: def unapply(in: String): Option[URI] = if in.startsWith(SERVER_SYNCH_TEXT) then Some(new URI(in.substring(SERVER_SYNCH_TEXT.size))) else None /** Reads an input steam until it hits the server synch text and server URI. */ def readUntilSynch(in: java.io.BufferedReader): Option[URI] = @tailrec def read(): Option[URI] = in.readLine match case null => None case ServerUriLine(uri) => Some(uri) case _ => read() try read() finally in.close() /** Reads all the lines in a file. If it doesn't exist, returns an empty list. Forces UTF-8 strings. */ def readLines(f: File): List[String] = if !f.exists then Nil else val reader = new java.io.BufferedReader( new java.io.InputStreamReader(new java.io.FileInputStream(f), "UTF-8") ) @tailrec def read(current: List[String]): List[String] = reader.readLine match case null => current.reverse case line => read(line :: current) try read(Nil) finally reader.close() def defaultLauncherLookup: File = try val classInLauncher = classOf[AppConfiguration] val fileOpt = for domain <- Option(classInLauncher.getProtectionDomain) source <- Option(domain.getCodeSource) location = source.getLocation yield toFile(location) fileOpt.getOrElse( throw new RuntimeException("could not inspect protection domain or code source") ) catch case e: Throwable => throw new RuntimeException("unable to find sbt-launch.jar.", e) sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/Update.scala000066400000000000000000000025201522561027200303700ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.io.File enum UpdateTarget: case UpdateScala(classifiers: List[String]) case UpdateApp(id: Application, classifiers: List[String], override val tpe: String) def tpe: String = this match case UpdateScala(_) => "scala" case UpdateApp(_, _, tpe) => tpe final class UpdateConfiguration( val bootDirectory: File, val ivyHome: Option[File], val scalaOrg: String, val scalaVersion: Option[String], val repositories: List[xsbti.Repository], val checksums: List[String] ): val resolutionCacheBase = new File(bootDirectory, "resolution-cache") def getScalaVersion = scalaVersion match case Some(sv) => sv; case None => "" final class UpdateResult( val success: Boolean, val scalaVersion: Option[String], val appVersion: Option[String] ) /** Ensures that the Scala and application jars exist for the given versions or else downloads them. */ final class Update(config: UpdateConfiguration): import config.bootDirectory bootDirectory.mkdirs lazy val coursierUpdate = new CousierUpdate(config) /** The main entry point of this class for use by the Update module. It runs Ivy */ def apply(target: UpdateTarget, reason: String): UpdateResult = coursierUpdate(target, reason) end Update sbt-launcher-ebbd426/launcher-implementation/src/main/scala/xsbt/boot/Using.scala000066400000000000000000000021051522561027200302320ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.io.{ Closeable, File, FileInputStream, FileOutputStream, InputStream, OutputStream } object Using: def apply[R <: Closeable, T](create: R)(f: R => T): T = withResource(create)(f) def withResource[R <: Closeable, T](r: R)(f: R => T): T = try f(r) finally r.close() object Copy: def apply(files: List[File], toDirectory: File): Boolean = files.map(file => apply(file, toDirectory)).contains(true) def apply(file: File, toDirectory: File): Boolean = toDirectory.mkdirs() val to = new File(toDirectory, file.getName) val missing = !to.exists if missing then Using(new FileInputStream(file)) { in => Using(new FileOutputStream(to)) { out => transfer(in, out) } } missing def transfer(in: InputStream, out: OutputStream): Unit = val buffer = new Array[Byte](8192) def next(): Unit = val read = in.read(buffer) if read > 0 then out.write(buffer, 0, read) next() next() sbt-launcher-ebbd426/launcher-implementation/src/test/000077500000000000000000000000001522561027200231675ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/test/scala/000077500000000000000000000000001522561027200242525ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/test/scala/CacheTest.scala000066400000000000000000000007171522561027200271270ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import org.scalacheck.* import Prop.* object CacheTest extends Properties("Cache"): property("Cache") = Prop.forAll { (_: Int, keys: List[Int], map: Int => Int) => val cache = new Cache((i: Int, _: Unit) => map(i)) def toProperty(key: Int) = ("Key " + key) |: ("Value: " + map(key)) |: (cache.apply(key, ()) == map(key)) Prop.all(keys.map(toProperty)*) } sbt-launcher-ebbd426/launcher-implementation/src/test/scala/ConfigurationParserTest.scala000066400000000000000000000172521522561027200321120ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.io.File import java.net.URI import java.nio.file.Files object ConfigurationParserTest extends verify.BasicTestSuite: test("repository override activation combines the configured value and file existence") { val directory = Files.createTempDirectory("launcher-repository-override-test").toFile val repositoryConfig = new File(directory, "repositories") val repositoryOverride = new File(directory, "repositories_force") Files.write(repositoryConfig.toPath, "[repositories]\n local\n".getBytes("UTF-8")) try val cases = List( (false, false, false, repositoryConfig), (true, false, true, repositoryConfig), (false, true, true, repositoryOverride), (true, true, true, repositoryOverride) ) cases.foreach { case (configuredOverride, overrideFileExists, expectedOverride, expectedFile) => if overrideFileExists then Files.write( repositoryOverride.toPath, "[repositories]\n maven-central\n".getBytes("UTF-8") ) else Files.deleteIfExists(repositoryOverride.toPath) val ivy = ListMap[String, Option[String]]( "override-build-repos" -> Option(configuredOverride.toString), "repository-config" -> Option(repositoryConfig.getAbsolutePath), "repository-override" -> Option(repositoryOverride.getAbsolutePath) ).default(_ => None) val (_, _, actualOverride, actualFile) = (new ConfigurationParser).getIvy(ivy) assert(actualOverride == expectedOverride) assert(actualFile.contains(expectedFile)) } finally Files.deleteIfExists(repositoryOverride.toPath) Files.deleteIfExists(repositoryConfig.toPath) Files.deleteIfExists(directory.toPath) } test("Configuration parser should correct parse bootOnly") { repoFileContains( """|[repositories] | local: bootOnly""".stripMargin, Repository.Predefined("local", true) ) repoFileContains( """|[repositories] | local""".stripMargin, Repository.Predefined("local", false) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org""".stripMargin, Repository.Maven("id", new URI("https://repo1.maven.org").toURL, false) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, bootOnly""".stripMargin, Repository.Maven("id", new URI("https://repo1.maven.org").toURL, true) ) repoFileContains( """|[repositories] | id: http://repo1.maven.org, bootOnly, allowInsecureProtocol""".stripMargin, Repository.Maven("id", new URI("http://repo1.maven.org").toURL, true, true) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath]""".stripMargin, Repository .Ivy("id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[orgPath]", false, false) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath], mavenCompatible""".stripMargin, Repository .Ivy("id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[orgPath]", true, false) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath], mavenCompatible, bootOnly""".stripMargin, Repository .Ivy("id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[orgPath]", true, true) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath], bootOnly, mavenCompatible""".stripMargin, Repository .Ivy("id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[orgPath]", true, true) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath], bootOnly""".stripMargin, Repository .Ivy("id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[orgPath]", false, true) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath], [artPath]""".stripMargin, Repository .Ivy("id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[artPath]", false, false) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath], [artPath], descriptorOptional""".stripMargin, Repository.Ivy( "id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[artPath]", false, false, true, false ) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath], [artPath], descriptorOptional, skipConsistencyCheck""".stripMargin, Repository.Ivy( "id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[artPath]", false, false, true, true ) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath], [artPath], skipConsistencyCheck, descriptorOptional""".stripMargin, Repository.Ivy( "id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[artPath]", false, false, true, true ) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath], [artPath], skipConsistencyCheck, descriptorOptional, mavenCompatible, bootOnly""".stripMargin, Repository.Ivy( "id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[artPath]", true, true, true, true ) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath], [artPath], bootOnly""".stripMargin, Repository .Ivy("id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[artPath]", false, true) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath], [artPath], bootOnly, mavenCompatible""".stripMargin, Repository .Ivy("id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[artPath]", true, true) ) repoFileContains( """|[repositories] | id: https://repo1.maven.org, [orgPath], [artPath], mavenCompatible, bootOnly""".stripMargin, Repository .Ivy("id", new URI("https://repo1.maven.org").toURL, "[orgPath]", "[artPath]", true, true) ) repoFileContains( """|[repositories] | id: http://repo1.maven.org, [orgPath], [artPath], mavenCompatible, bootOnly, allowInsecureProtocol""".stripMargin, Repository.Ivy( "id", new URI("http://repo1.maven.org").toURL, "[orgPath]", "[artPath]", mavenCompatible = true, bootOnly = true, allowInsecureProtocol = true ) ) } def repoFileContains(file: String, repo: Repository.Repository) = assert(loadRepoFile(file).contains(repo)) def loadRepoFile(file: String) = (new ConfigurationParser).readRepositoriesConfig(file) sbt-launcher-ebbd426/launcher-implementation/src/test/scala/EnumerationTest.scala000066400000000000000000000033331522561027200304070ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import org.scalacheck.* import Prop.{ Exception as _, * } object EnumerationTest extends Properties("Enumeration"): property("MultiEnum.toValue") = checkToValue(MultiEnum, multiElements*) property("MultiEnum.elements") = checkElements(MultiEnum, multiElements*) property("EmptyEnum.toValue") = checkToValue(EmptyEnum) property("EmptyEnum.elements") = EmptyEnum.elements.isEmpty property("SingleEnum.toValue") = checkToValue(SingleEnum, singleElements) property("SingleEnum.elements") = checkElements(SingleEnum, singleElements) def singleElements = ("A", SingleEnum.a) def multiElements = import MultiEnum.{ a, b, c } List(("A" -> a), ("B" -> b), ("C" -> c)) def checkElements(`enum`: Enumeration, mapped: (String, Enumeration#Value)*) = val elements = `enum`.elements ("elements: " + elements) |: (mapped.forall { case (_, v) => elements.contains(v) } && (elements.length == mapped.length)) def checkToValue(`enum`: Enumeration, mapped: (String, Enumeration#Value)*) = def invalid(s: String) = ("valueOf(" + s + ")") |: Prop.throws(classOf[Exception])(`enum`.toValue(s)) def valid(s: String, expected: Enumeration#Value) = ("valueOf(" + s + ")") |: ("Expected " + expected) |: (`enum`.toValue(s) == expected) val map = Map(mapped*) Prop.forAll((s: String) => map.get(s) match case Some(v) => valid(s, v) case None => invalid(s) ) object MultiEnum extends Enumeration: val a = value("A") val b = value("B") val c = value("C") object SingleEnum extends Enumeration: val a = value("A") object EmptyEnum extends Enumeration sbt-launcher-ebbd426/launcher-implementation/src/test/scala/ListMapTest.scala000066400000000000000000000027401522561027200274730ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import org.scalacheck.* object ListMapProperties extends Properties("ListMap"): given Arbitrary[ListMap[Int, Int]] = Arbitrary( for (list <- Arbitrary.arbitrary[List[(Int, Int)]]) yield ListMap(list*) ) property("ListMap from List contains all members of that List") = Prop.forAll { (list: List[(Int, Int)]) => val map = ListMap(list*) list forall { entry => map.contains(entry._1) } } property("contains added entry") = Prop.forAll { (map: ListMap[Int, Int], key: Int, value: Int) => { (map + (key -> value)).contains(key) } && { (map + (key -> value))(key) == value } && { (map + (key -> value)).get(key) == Some(value) } } property("remove") = Prop.forAll { (map: ListMap[Int, Int], key: Int) => { Prop.throws(classOf[Exception])((map - key)(key)) } && { !(map - key).contains(key) } && { (map - key).get(key).isEmpty } } property("empty") = Prop.forAll { (key: Int) => { Prop.throws(classOf[Exception])(ListMap.empty(key)) } { !ListMap.empty.contains(key) } && { ListMap.empty.get(key).isEmpty } } object ListMapEmpty extends Properties("ListMap.empty"): import ListMap.empty property("isEmpty") = empty.isEmpty property("toList.isEmpty") = empty.toList.isEmpty property("toSeq.isEmpty") = empty.toSeq.isEmpty property("keys.isEmpty") = empty.keys.isEmpty property("iterator.isEmpty") = empty.iterator.isEmpty sbt-launcher-ebbd426/launcher-implementation/src/test/scala/LocksTest.scala000066400000000000000000000041651522561027200272000ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import org.scalacheck.* import Prop.* import java.io.File import sbt.io.IO.withTemporaryDirectory /** * These mainly test that things work in the uncontested case and that no OverlappingFileLockExceptions occur. * There is no real locking testing, just the coordination of locking. */ object LocksTest extends Properties("Locks"): property("Lock in nonexisting directory") = spec { withTemporaryDirectory { dir => val lockFile = new File(dir, "doesntexist/lock") Locks(lockFile, callTrue) } } property("Uncontested re-entrant lock") = spec { withTemporaryDirectory { dir => val lockFile = new File(dir, "lock") Locks(lockFile, callLocked(lockFile)) && Locks(lockFile, callLocked(lockFile)) } } property("Uncontested double lock") = spec { withTemporaryDirectory { dir => val lockFileA = new File(dir, "lockA") val lockFileB = new File(dir, "lockB") Locks(lockFileA, callLocked(lockFileB)) && Locks(lockFileB, callLocked(lockFileA)) } } property("Contested single lock") = spec { withTemporaryDirectory { dir => val lockFile = new File(dir, "lock") forkFold(2000) { _ => Locks(lockFile, callTrue) } } } private def spec(f: => Boolean): Prop = Prop { _ => Result(if f then True else False) } private def call[T](impl: => T) = new java.util.concurrent.Callable[T]: def call = impl private def callLocked(lockFile: File) = call { Locks(lockFile, callTrue) } private lazy val callTrue = call { true } private def forkFold(n: Int)(impl: Int => Boolean): Boolean = forkWait(n)(impl).foldLeft(true) { _ && _ } private def forkWait(n: Int)(impl: Int => Boolean): Iterable[Boolean] = import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Await import scala.concurrent.duration.Duration.Inf // TODO - Don't wait forever... val futures = (0 until n).map { i => Future { impl(i) } } futures.toList.map(f => Await.result(f, Inf)) sbt-launcher-ebbd426/launcher-implementation/src/test/scala/PreTest.scala000066400000000000000000000045361522561027200266550ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.io.File import java.util.Arrays.equals as arrEquals import org.scalacheck.* object PreTest extends Properties("Pre"): import Pre.* property("isEmpty") = Prop.forAll((s: String) => (s.isEmpty == isEmpty(s))) property("isNonEmpty") = Prop.forAll((s: String) => (isEmpty(s) != isNonEmpty(s))) property("assert true") = assert(true); true property("assert false") = Prop.throws(classOf[AssertionError])(assert(false)) property("assert true with message") = Prop.forAll { (s: String) => assert(true, s); true } property("assert false with message") = Prop.forAll((s: String) => Prop.throws(classOf[AssertionError])(assert(false, s))) property("require false") = Prop.forAll((s: String) => Prop.throws(classOf[IllegalArgumentException])(require(false, s))) property("require true") = Prop.forAll { (s: String) => require(true, s); true } property("error") = Prop.forAll((s: String) => Prop.throws(classOf[BootException])(error(s))) property("toBoolean") = Prop.forAll((s: String) => trap(toBoolean(s)) == trap(java.lang.Boolean.parseBoolean(s))) property("toArray") = Prop.forAll((list: List[Int]) => arrEquals(list.toArray, toArray(list))) property("toArray") = Prop.forAll((list: List[String]) => objArrEquals(list.toArray, toArray(list))) property("concat") = Prop.forAll(genFiles, genFiles) { (a: Array[File], b: Array[File]) => (a ++ b) sameElements concat(a, b) } property("array") = Prop.forAll(genFiles) { (a: Array[File]) => array(a.toList*) sameElements Array(a*) } property("substituteTilde") = val userHome = System.getProperty("user.home") assert(substituteTilde("~/path") == s"$userHome/path") assert(substituteTilde("~\\") == s"$userHome\\") assert(substituteTilde("~") == userHome) assert(substituteTilde("~x") == "~x") assert(substituteTilde("x~/") == "x~/") true given Arbitrary[File] = Arbitrary { for (i <- Arbitrary.arbitrary[Int]) yield new File(i.toString) } val genFiles: Gen[Array[File]] = Arbitrary.arbitrary[Array[File]] def trap[T](t: => T): Option[T] = try Some(t) catch case _: Exception => None private def objArrEquals[T <: AnyRef](a: Array[T], b: Array[T]): Boolean = arrEquals(a.asInstanceOf[Array[AnyRef]], b.asInstanceOf[Array[AnyRef]]) sbt-launcher-ebbd426/launcher-implementation/src/test/scala/ScalaProviderTest.scala000066400000000000000000000125611522561027200306620ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.io.{ File, InputStream } import java.util.Properties import xsbti.{ Repository as _, Launcher as _, * } import LaunchTest.* import sbt.io.IO.{ createDirectory, touch, withTemporaryDirectory } object ScalaProviderTest extends verify.BasicTestSuite: test("Launch should provide ClassLoader for Scala 2.10.7") { checkScalaLoader("2.10.7") } test("Launch should provide ClassLoader for Scala 2.11.12") { checkScalaLoader("2.11.12") } test("Launch should provide ClassLoader for Scala 2.12.17") { checkScalaLoader("2.12.17") } test("Launch should provide ClassLoader for Scala 2.13.9") { checkScalaLoader("2.13.9") } // Scala version detection picks up 2.13 // test("Launch should provide ClassLoader for Scala 3.2.0") { // checkScalaLoader("3.2.0") // } test( "Launch should successfully load an application from local repository and run it with correct arguments" ) { assert(checkLoad(List("test"), "xsbt.boot.test.ArgumentTest").asInstanceOf[Exit].code == 0) intercept[RuntimeException] { checkLoad(List(), "xsbt.boot.test.ArgumentTest") () } } test( "Launch should successfully load an plain application from local repository and run it with correct arguments" ) { assert(checkLoad(List("test"), "xsbt.boot.test.PlainArgumentTest").asInstanceOf[Exit].code == 0) intercept[RuntimeException] { checkLoad(List(), "xsbt.boot.test.PlainArgumentTest") () } } test("Launch should successfully load an application instead of the plain application") { assert(checkLoad(List(), "xsbt.boot.test.PriorityTest").asInstanceOf[Exit].code == 0) } test( "Launch should successfully load an application from local repository and run it with correct sbt version" ) { assert( checkLoad(List(AppVersion), "xsbt.boot.test.AppVersionTest").asInstanceOf[Exit].code == 0 ) } test("Launch should add extra resources to the classpath") { assert( checkLoad(testResources, "xsbt.boot.test.ExtraTest", createExtra).asInstanceOf[Exit].code == 0 ) } def checkLoad(arguments: List[String], mainClassName: String): MainResult = checkLoad(arguments, mainClassName, _ => Array[File]()) def checkLoad( arguments: List[String], mainClassName: String, extra: File => Array[File] ): MainResult = withTemporaryDirectory { currentDirectory => withLauncher { launcher => Launch.run(launcher)( new RunConfiguration( Some(LaunchTest.getScalaVersion), LaunchTest.testApp(mainClassName, extra(currentDirectory)).toID, currentDirectory, arguments ) ) } } private def testResources = List("test-resourceA", "a/b/test-resourceB", "sub/test-resource") private def createExtra(currentDirectory: File) = val resourceDirectory = new File(currentDirectory, "resources") createDirectory(resourceDirectory) testResources.foreach(resource => touch(new File(resourceDirectory, resource.replace('/', File.separatorChar))) ) Array(resourceDirectory) private def checkScalaLoader(version: String) = withLauncher(checkLauncher(version, version)) private def checkLauncher(version: String, versionValue: String)(launcher: xsbti.Launcher) = import scala.reflect.Selectable.reflectiveSelectable val provider = launcher.getScala(version) val loader = provider.loader // ensure that this loader can load Scala classes by trying scala.ScalaObject. tryScala(loader, loader.getParent) assert(getScalaVersion(loader) == versionValue) val libraryLoader = provider.loader.getParent // Test the structural type libraryLoader match case x: (ClassLoader & LibraryLoader) @unchecked => assert(x.scalaVersion == version) tryScala(libraryLoader, libraryLoader) private def tryScala(loader: ClassLoader, libraryLoader: ClassLoader) = assert(Class.forName("scala.Product", false, loader).getClassLoader == libraryLoader) type LibraryLoader = { def scalaVersion: String } object LaunchTest: def testApp(main: String): Application = testApp(main, Array[File]()) def testApp(main: String, extra: Array[File]): Application = Application( "org.scala-sbt", Value.Explicit("launch-test"), Value.Explicit(AppVersion), main, Nil, CrossValue.Disabled, extra ) import Predefined.* def testRepositories = List(Local, MavenCentral, SonatypeOSSSnapshots).map(Repository.Predefined(_)) def withLauncher[T](f: xsbti.Launcher => T): T = withTemporaryDirectory { bootDirectory => f(Launcher(bootDirectory, testRepositories)) } def getScalaVersion: String = "3.7.2" // getScalaVersion(getClass.getClassLoader) def getScalaVersion(loader: ClassLoader): String = getProperty(loader, "library.properties", "version.number") lazy val AppVersion = getProperty(getClass.getClassLoader, "sbt.launcher.version.properties", "version") private def getProperty(loader: ClassLoader, res: String, prop: String) = loadProperties(loader.getResourceAsStream(res)).getProperty(prop) private def loadProperties(propertiesStream: InputStream): Properties = val properties = new Properties try properties.load(propertiesStream) finally propertiesStream.close() properties sbt-launcher-ebbd426/launcher-implementation/src/test/scala/ServerLocatorTest.scala000066400000000000000000000032261522561027200307140ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import java.io.File import sbt.io.IO.withTemporaryDirectory object ServerLocatorTest extends verify.BasicTestSuite: // TODO - Maybe use scalacheck to randomnly generate URIs test("ServerLocator read and write server URI properties") { withTemporaryDirectory { dir => val propFile = new File(dir, "server.properties") val expected = new java.net.URI("http://localhost:8080") ServerLocator.writeProperties(propFile, expected) assert(ServerLocator.readProperties(propFile) == Some(expected)) } } test("ServerLocator detect listening ports") { val serverSocket = new java.net.ServerSocket(0) object serverThread extends Thread: override def run(): Unit = // Accept one connection. val result = serverSocket.accept() result.close() serverSocket.close() serverThread.start() val uri = new java.net.URI( s"http://${serverSocket.getInetAddress.getHostAddress}:${serverSocket.getLocalPort}" ) assert(ServerLocator.isReachable(uri)) } test("ServerLauncher detect start URI from reader") { val expected = new java.net.URI("http://localhost:8080") val input = s"""|Some random text |to start the server |${ServerApplication.SERVER_SYNCH_TEXT}${expected.toASCIIString} |Some more output.""".stripMargin val inputStream = new java.io.BufferedReader(new java.io.StringReader(input)) val result = try ServerLauncher.readUntilSynch(inputStream) finally inputStream.close() assert(result == Some(expected)) } sbt-launcher-ebbd426/launcher-implementation/src/test/scala/URITests.scala000066400000000000000000000045451522561027200267510ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import org.scalacheck.* import Prop.* import Configuration.* import java.io.File import java.net.URI object URITests extends Properties("URI Tests"): // Need a platform-specific root, otherwise URI will not be absolute (e.g. if we use a "/a/b/c" path in Windows) // Note: // If I use "C:" instead of "/C:", then isAbsolute == true for the resulting URI, but resolve is broken: // e.g. scala> new URI("file", "c:/a/b'/has spaces", null).resolve("a") broken // res0: java.net.URI = a // scala> new URI("file", "/c:/a/b'/has spaces", null).resolve("a") working // res1: java.net.URI = file:/c:/a/b'/a val Root = if xsbt.boot.Pre.isWindows then "/C:/" else "/" val FileProtocol = "file" property("directoryURI adds trailing slash") = secure { val dirURI = directoryURI(new File(Root + "a/b/c")) val directURI = filePathURI(Root + "a/b/c/") dirURI == directURI } property("directoryURI preserves trailing slash") = secure { directoryURI(new File(Root + "a/b/c/")) == filePathURI(Root + "a/b/c/") } property("filePathURI encodes spaces") = secure { val decoded = "has spaces" val encoded = "has%20spaces" val fpURI = filePathURI(decoded) val directURI = new URI(encoded) s"filePathURI: $fpURI" |: s"direct URI: $directURI" |: s"getPath: ${fpURI.getPath}" |: s"getRawPath: ${fpURI.getRawPath}" |: (fpURI == directURI) && (fpURI.getPath == decoded) && (fpURI.getRawPath == encoded) } property("filePathURI and File.toURI agree for absolute file") = secure { val s = Root + "a/b'/has spaces" val viaPath = filePathURI(s) val viaFile = new File(s).toURI s"via path: $viaPath" |: s"via file: $viaFile" |: (viaPath == viaFile) } property("filePathURI supports URIs") = secure { val s = s"file://${Root}is/a/uri/with%20spaces" val decoded = Root + "is/a/uri/with spaces" val encoded = Root + "is/a/uri/with%20spaces" val fpURI = filePathURI(s) val directURI = new URI(s) s"filePathURI: $fpURI" |: s"direct URI: $directURI" |: s"getPath: ${fpURI.getPath}" |: s"getRawPath: ${fpURI.getRawPath}" |: (fpURI == directURI) && (fpURI.getPath == decoded) && (fpURI.getRawPath == encoded) } sbt-launcher-ebbd426/launcher-implementation/src/test/scala/VersionParts.scala000066400000000000000000000041301522561027200277140ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot import org.scalacheck.* import Prop.* object VersionParts extends Properties("VersionParts"): property("Valid version, no qualifier") = Prop.forAll { (x0: Int, y0: Int, z0: Int) => val (x, y, z) = (norm(x0), norm(y0), norm(z0)) val str = s"$x.$y.$z" val expected = s"$x.$y.$z" :: s"$x.$y" :: "" :: Nil check(str, expected) } property("Valid version with qualifier") = Prop.forAll { (x0: Int, y0: Int, z0: Int, q0: String) => val (x, y, z, q) = (norm(x0), norm(y0), norm(z0), normS(q0)) val str = s"$x.$y.$z-$q" val expected = s"$x.$y.$z-$q" :: s"$x.$y.$z" :: s"$x.$y" :: "" :: Nil check(str, expected) } property("Invalid version") = Prop.forAll { (x0: Int, y0: Int, z0: Int, q0: String) => val (x, y, z, q) = (norm(x0), norm(y0), norm(z0), normS(q0)) val strings = x.toString :: s"$x.$y" :: s"$x.$y-$q" :: s"$x.$y.$z.$q" :: Nil all(strings.map(str => check(str, Configuration.noMatchParts))*) } private def check(versionString: String, expectedParts: List[String]) = def printParts(s: List[String]): String = s.map("'" + _ + "'").mkString("(", ", ", ")") val actual = Configuration.versionParts(versionString) s"Version string '$versionString'" |: s"Expected '${printParts(expectedParts)}'" |: s"Actual'${printParts(actual)}'" |: (actual == expectedParts) // Make `i` non-negative private def norm(i: Int): Int = if i == Int.MinValue then Int.MaxValue else math.abs(i) // Make `s` non-empty and suitable for java.util.regex input private def normS(s: String): String = val filtered = s filter validChar if filtered.isEmpty then "q" else filtered // strip whitespace and characters not supported by Pattern private def validChar(c: Char) = !java.lang.Character.isWhitespace(c) && !java.lang.Character.isISOControl(c) && !Character.isHighSurrogate(c) && !Character.isLowSurrogate(c) sbt-launcher-ebbd426/launcher-implementation/src/test/scala/xsbt/000077500000000000000000000000001522561027200252325ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/test/scala/xsbt/boot/000077500000000000000000000000001522561027200261755ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-implementation/src/test/scala/xsbt/boot/BootExitSuppressionTest.scala000066400000000000000000000020771522561027200340600ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot object BootExitSuppressionTest extends verify.BasicTestSuite: private def withExitSuppressed[T](body: => T): T = val key = "sbt.boot.exit" val prev = sys.props.get(key) System.setProperty(key, "false") try body finally prev match case None => System.clearProperty(key) case Some(v) => System.setProperty(key, v) test("Boot.main does not terminate the JVM for --launcher-version when suppressed") { withExitSuppressed { Boot.main(Array("--launcher-version")) // If sys.exit was called, the test JVM would be terminated. } } test("Boot.main does not terminate the JVM on error path when suppressed") { withExitSuppressed { // This argument triggers an error path in the launcher (missing/invalid launch configuration). // We mainly care that it does not hard-exit the embedding JVM. Boot.main(Array("--bad-flag-that-will-error")) // If sys.exit was called, the test JVM would be terminated. } } sbt-launcher-ebbd426/launcher-interface/000077500000000000000000000000001522561027200203345ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-interface/NOTICE000066400000000000000000000002001522561027200212300ustar00rootroot00000000000000Simple Build Tool: Launcher Interface Component Copyright 2009, 2010 Mark Harrah Licensed under BSD-style license (see LICENSE)sbt-launcher-ebbd426/launcher-interface/src/000077500000000000000000000000001522561027200211235ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-interface/src/main/000077500000000000000000000000001522561027200220475ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-interface/src/main/java/000077500000000000000000000000001522561027200227705ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/000077500000000000000000000000001522561027200241215ustar00rootroot00000000000000sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/AppConfiguration.java000066400000000000000000000003511522561027200302330ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; import java.io.File; public interface AppConfiguration { public String[] arguments(); public File baseDirectory(); public AppProvider provider(); }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/AppMain.java000066400000000000000000000016341522561027200263150ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; /** * The main entry interface for launching applications. Classes which implement this interface * can be launched via the sbt launcher. * * In addition, classes can be adapted into this interface by the launcher if they have a static method * matching one of these signatures: * * - public static void main(String[] args) * - public static int main(String[] args) * - public static xsbti.Exit main(String[] args) * */ public interface AppMain { /** Run the application and return the result. * * @param configuration The configuration used to run the application. Includes arguments and access to launcher features. * @return * The result of running this app. * Note: the result can be things like "Please reboot this application". */ public MainResult run(AppConfiguration configuration); }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/AppProvider.java000066400000000000000000000037601522561027200272250ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; import java.io.File; /** * This represents an interface that can generate applications or servers. * * This provider grants access to launcher related features associated with * the id. */ public interface AppProvider { /** Returns the ScalaProvider that this AppProvider will use. */ public ScalaProvider scalaProvider(); /** The ID of the application that will be created by 'newMain' or 'mainClass'.*/ public ApplicationID id(); /** The classloader used to load this application. */ public ClassLoader loader(); /** Loads the class for the entry point for the application given by 'id'. * This method will return the same class every invocation. * That is, the ClassLoader is not recreated each call. * @deprecated("use entryPoint instead") * * Note: This will throw an exception if the launched application does not extend AppMain. */ @Deprecated public Class mainClass(); /** Loads the class for the entry point for the application given by 'id'. * This method will return the same class every invocation. * That is, the ClassLoader is not recreated each call. */ public Class entryPoint(); /** Creates a new instance of the entry point of the application given by 'id'. * It is NOT guaranteed that newMain().getClass() == mainClass(). * The sbt launcher can wrap generic static main methods. In this case, there will be a wrapper class, * and you must use the `entryPoint` method. * @throws IncompatibleClassChangeError if the configuration used for this Application does not * represent a launched application. */ public AppMain newMain(); /** The classpath from which the main class is loaded, excluding Scala jars.*/ public File[] mainClasspath(); /** Returns a mechanism you can use to install/find/resolve components. * A component is just a related group of files. */ public ComponentProvider components(); } sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/ApplicationID.java000066400000000000000000000027061522561027200274510ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; import java.io.File; /** * This represents an identification for the sbt launcher to load and run * an sbt launched application using ivy. */ public interface ApplicationID { /** * @return * The Ivy orgnaization / Maven groupId where we can find the application to launch. */ public String groupID(); /** * @return * The ivy module name / Maven artifactId where we can find the application to launch. */ public String name(); /** * @return * The ivy/maven version of the module we should resolve. */ public String version(); /** * @return * The fully qualified name of the class that extends xsbti.AppMain */ public String mainClass(); /** * @return * Additional ivy components we should resolve with the main application artifacts. */ public String[] mainComponents(); /** * @deprecated * This method is no longer used if the crossVersionedValue method is available. * * @return * True if the application is cross-versioned by binary-compatible version string, * False if there is no cross-versioning. */ @Deprecated public boolean crossVersioned(); /** * * @since 0.13.0 * @return * The type of cross-versioning the launcher should use to resolve this artifact. */ public CrossValue crossVersionedValue(); /** Files to add to the application classpath. */ public File[] classpathExtra(); }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/ComponentProvider.java000066400000000000000000000033141522561027200304420ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; import java.io.File; /** * A service to locate, install and modify "Components". * * A component is essentially a directory and a set of files attached to a unique string id. */ public interface ComponentProvider { /** * @param id The component's id string. * @return * The "working directory" or base directory for the component. You should perform temporary work here for the component. */ public File componentLocation(String id); /** * Grab the current component definition. * * @param componentID The component's id string. * @return * The set of files attached to this component. */ public File[] component(String componentID); /** * This will define a new component using the files passed in. * * Note: The component will copy/move the files into a cache location. You should not use them directly, but * look them up using the `component` method. * * @param componentID The component's id string * @param components The set of files which defines the component. */ public void defineComponent(String componentID, File[] components); /** * Modify an existing component by adding files to it. * * @param componentID The component's id string * @param components The set of new files to add to the component. * @return true if any files were copied and false otherwise. * */ public boolean addToComponent(String componentID, File[] components); /** * @return The lockfile you should use to ensure your component cache does not become corrupted. * May return null if there is no lockfile for this provider. */ public File lockFile(); }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/Continue.java000066400000000000000000000005261522561027200265530ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; /** A launched application returns an instance of this class in order to communicate to the launcher * that the application's main thread is finished and the launcher's work is complete, but it should not exit.*/ public interface Continue extends MainResult {}sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/CrossValue.java000066400000000000000000000002061522561027200270500ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; public enum CrossValue { Disabled, Full, Binary; } sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/Exit.java000066400000000000000000000005261522561027200257000ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; /** * A launched application returns an instance of this class in order to communicate to the launcher * that the application finished and the launcher should exit with the given exit code. */ public interface Exit extends MainResult { public int code(); }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/ExtendedScalaProvider.java000066400000000000000000000004441522561027200312050ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; public interface ExtendedScalaProvider extends ScalaProvider { /** A ClassLoader that loads the classes from scala-library.jar. It will be the parent of `loader` .*/ public ClassLoader loaderLibraryOnly(); } sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/FullReload.java000066400000000000000000000007731522561027200270240ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; public final class FullReload extends RuntimeException { private final String[] arguments; private final boolean clean; public FullReload(String[] arguments) { this.arguments = arguments; this.clean = false; } public FullReload(String[] arguments, boolean clean) { this.arguments = arguments; this.clean = clean; } public boolean clean() { return clean; } public String[] arguments() { return arguments; } }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/GlobalLock.java000066400000000000000000000003421522561027200267740ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; import java.io.File; import java.util.concurrent.Callable; public interface GlobalLock { public T apply(File lockFile, Callable run); }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/IvyRepository.java000066400000000000000000000005501522561027200276330ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; import java.net.URL; public interface IvyRepository extends Repository { String id(); URL url(); String ivyPattern(); String artifactPattern(); boolean mavenCompatible(); boolean skipConsistencyCheck(); boolean descriptorOptional(); boolean allowInsecureProtocol(); } sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/Launcher.java000066400000000000000000000047331522561027200265340ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; import java.io.File; public interface Launcher { public static final int InterfaceVersion = 1; public ScalaProvider getScala(String version); public ScalaProvider getScala(String version, String reason); public ScalaProvider getScala(String version, String reason, String scalaOrg); /** * returns an `AppProvider` which is able to resolve an application * and instantiate its `xsbti.Main` in a new classloader. * See [AppProvider] for more details. * @param id The artifact coordinates of the application. * @param version The version to resolve */ public AppProvider app(ApplicationID id, String version); /** * This returns the "top" classloader for a launched application. This classlaoder * lives somewhere *above* that used for the application. This classloader * is used for doing any sort of JNA/native library loads so that downstream * loaders can share native libraries rather than run into "load-once" restrictions. */ public ClassLoader topLoader(); /** * Return the global lock for interacting with the file system. * * A mechanism to do file-based locking correctly on the JVM. See * the [[GlobalLock]] class for more details. */ public GlobalLock globalLock(); /** Value of the `sbt.boot.dir` property, or the default * boot configuration defined in `boot.directory`. */ public File bootDirectory(); /** Configured launcher repositories. These repositories * are the same ones used to load the launcher. */ public xsbti.Repository[] ivyRepositories(); /** These are the repositories configured by this launcher * which should be used by the application when resolving * further artifacts. */ public xsbti.Repository[] appRepositories(); /** The user has configured the launcher with the only repositories * it wants to use for this applciation. */ public boolean isOverrideRepositories(); /** * The value of `ivy.ivy-home` of the boot properties file. * This defaults to the `sbt.ivy.home` property, or `~/.ivy2`. * Use this setting in an application when using Ivy to resolve * more artifacts. * * @return a file, or null if not set. */ public File ivyHome(); /** An array of the checksums that should be checked when retreiving artifacts. * Configured via the the `ivy.checksums` section of the boot configuration. * Defaults to sha1, md5 or the value of the `sbt.checksums` property. */ public String[] checksums(); } sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/LibraryClassLoader.java000066400000000000000000000003651522561027200305110ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; import java.io.File; /** * Marker interface for classloader with just scala-library. */ public interface LibraryClassLoader { public String scalaVersion(); } sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/MainResult.java000066400000000000000000000006671522561027200270600ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; /** * A launched application should return an instance of this from its 'run' method * to communicate to the launcher what should be done now that the application * has completed. This interface should be treated as 'sealed', with Exit and Reboot the only * direct subtypes. * * @see xsbti.Exit * @see xsbti.Reboot */ public interface MainResult {}sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/Manage.java000066400000000000000000000001671522561027200261600ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; enum Manage { Nop, Clean, Refresh; }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/MavenRepository.java000066400000000000000000000003341522561027200301320ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; import java.net.URL; public interface MavenRepository extends Repository { String id(); URL url(); boolean allowInsecureProtocol(); }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/Predefined.java000066400000000000000000000021001522561027200270220ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; public enum Predefined { Local("local"), MavenLocal("maven-local"), MavenCentral("maven-central"), /** * @deprecated use {@link #SonatypeOSSReleases} instead. */ @Deprecated ScalaToolsReleases("scala-tools-releases"), /** * @deprecated use {@link #SonatypeOSSSnapshots} instead. */ @Deprecated ScalaToolsSnapshots("scala-tools-snapshots"), SonatypeOSSReleases("sonatype-oss-releases"), SonatypeOSSSnapshots("sonatype-oss-snapshots"), /** * @deprecated */ @Deprecated Jcenter("jcenter"); private final String label; private Predefined(String label) { this.label = label; } public String toString() { return label; } public static Predefined toValue(String s) { for (Predefined p : values()) if (s.equals(p.toString())) return p; StringBuilder msg = new StringBuilder("Expected one of "); for (Predefined p : values()) msg.append(p.toString()).append(", "); msg.append("got '").append(s).append("'."); throw new RuntimeException(msg.toString()); } }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/PredefinedRepository.java000066400000000000000000000002411522561027200311260ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; public interface PredefinedRepository extends Repository { Predefined id(); } sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/Reboot.java000066400000000000000000000010741522561027200262200ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; import java.io.File; /** * A launched application returns an instance of this class in order to communicate to the launcher * that the application should be restarted. Different versions of the application and Scala can be used. * The application can be given different arguments as well as a new working directory. */ public interface Reboot extends MainResult { public String[] arguments(); public File baseDirectory(); public String scalaVersion(); public ApplicationID app(); }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/Repository.java000066400000000000000000000001601522561027200271400ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; public interface Repository {}sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/RetrieveException.java000066400000000000000000000005031522561027200304260ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; public final class RetrieveException extends RuntimeException { private final String version; public RetrieveException(String version, String msg) { super(msg); this.version = version; } public String version() { return version; } }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/ScalaProvider.java000066400000000000000000000020771522561027200275300ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; import java.io.File; /** Provides access to the jars and classes for a particular version of Scala.*/ public interface ScalaProvider { public Launcher launcher(); /** The version of Scala this instance provides.*/ public String version(); /** A ClassLoader that loads the classes from scala-library.jar and scala-compiler.jar.*/ public ClassLoader loader(); /** Returns the scala-library.jar and scala-compiler.jar for this version of Scala. */ public File[] jars(); /**@deprecated Only `jars` can be reliably provided for modularized Scala. (Since 0.13.0) */ @Deprecated public File libraryJar(); /**@deprecated Only `jars` can be reliably provided for modularized Scala. (Since 0.13.0) */ @Deprecated public File compilerJar(); /** Creates an application provider that will use 'loader()' as the parent ClassLoader for * the application given by 'id'. This method will retrieve the application if it has not already * been retrieved.*/ public AppProvider app(ApplicationID id); } sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/Server.java000066400000000000000000000024641522561027200262400ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; /** A running server. * * A class implementing this must: * * 1. Expose an HTTP port that clients can connect to, returned via the uri method. * 2. Accept HTTP HEAD requests against the returned URI. These are used as "ping" messages to ensure * a server is still alive, when new clients connect. * 3. Create a new thread to execute its service * 4. Block the calling thread until the server is shutdown via awaitTermination() */ public interface Server { /** * @return * A URI denoting the Port which clients can connect to. * * Note: we use a URI so that the server can bind to different IP addresses (even a public one) if desired. * Note: To verify that a server is "up", the sbt launcher will attempt to connect to * this URI's address and port with a socket. If the connection is accepted, the server is assumed to * be working. */ public java.net.URI uri(); /** * This should block the calling thread until the server is shutdown. * * @return * The result that should occur from the server. * Can be: * - xsbti.Exit: Shutdown this launch * - xsbti.Reboot: Restart the server * * */ public xsbti.MainResult awaitTermination(); }sbt-launcher-ebbd426/launcher-interface/src/main/java/xsbti/ServerMain.java000066400000000000000000000011431522561027200270360ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbti; /** The main entry point for a launched service. This allows applciations * to instantiate server instances. */ public interface ServerMain { /** * This method should launch one or more thread(s) which run the service. After the service has * been started, it should return the port/URI it is listening for connections on. * * @param configuration * The configuration used to launch this service. * @return * A running server. */ public Server start(AppConfiguration configuration); }sbt-launcher-ebbd426/licenses/000077500000000000000000000000001522561027200164025ustar00rootroot00000000000000sbt-launcher-ebbd426/licenses/LICENSE_Apache000066400000000000000000000261361522561027200206600ustar00rootroot00000000000000 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. sbt-launcher-ebbd426/licenses/LICENSE_Scala000066400000000000000000000033051522561027200205130ustar00rootroot00000000000000SCALA LICENSE Copyright (c) 2002-2008 EPFL, Lausanne, unless otherwise specified. All rights reserved. This software was developed by the Programming Methods Laboratory of the Swiss Federal Institute of Technology (EPFL), Lausanne, Switzerland. Permission to use, copy, modify, and distribute this software in source or binary form for any purpose with or without fee is hereby granted, provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the EPFL 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 REGENTS 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 REGENTS 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.sbt-launcher-ebbd426/project/000077500000000000000000000000001522561027200162435ustar00rootroot00000000000000sbt-launcher-ebbd426/project/Deps.scala000066400000000000000000000012241522561027200201420ustar00rootroot00000000000000import sbt.* import sbt.Keys.* object Deps { def lib(m: ModuleID) = libraryDependencies += m lazy val sbtIo = "org.scala-sbt" %% "io" % "1.10.5" lazy val scalacheck = "org.scalacheck" %% "scalacheck" % "1.19.0" lazy val junit = "junit" % "junit" % "4.13.2" lazy val verify = "com.eed3si9n.verify" %% "verify" % "1.0.0" val coursierVersion = "2.1.23" lazy val coursier = ("io.get-coursier" %% "coursier" % coursierVersion) .cross(CrossVersion.for3Use2_13) .exclude("org.codehaus.plexus", "plexus-archiver") .exclude("org.codehaus.plexus", "plexus-container-default") .exclude("org.codehaus.plexus", "plexus-container-default") } sbt-launcher-ebbd426/project/Release.scala000066400000000000000000000004341522561027200206310ustar00rootroot00000000000000import com.typesafe.sbt.JavaVersionCheckPlugin.autoImport.* import sbt.* object Release { def settings: Seq[Setting[?]] = javaVersionCheckSettings // Validation for java verison def javaVersionCheckSettings = Seq( javaVersionCheck / javaVersionPrefix := Some("1.8") ) } sbt-launcher-ebbd426/project/Transform.scala000066400000000000000000000072261522561027200212320ustar00rootroot00000000000000import sbt.* import Keys.* import Path.* object Transform { lazy val transformSources = TaskKey[Seq[File]]("transform-sources") lazy val inputSourceDirectories = SettingKey[Seq[File]]("input-source-directories") lazy val inputSourceDirectory = SettingKey[File]("input-source-directory") lazy val inputSources = TaskKey[Seq[File]]("input-sources") lazy val sourceProperties = TaskKey[Map[String, String]]("source-properties") lazy val transformResources = TaskKey[Seq[File]]("transform-resources") lazy val inputResourceDirectories = SettingKey[Seq[File]]("input-resource-directories") lazy val inputResourceDirectory = SettingKey[File]("input-resource-directory") lazy val inputResources = TaskKey[Seq[File]]("input-resources") lazy val resourceProperties = TaskKey[Map[String, String]]("resource-properties") lazy val conscriptConfigs = TaskKey[Unit]("conscript-configs") def transSourceSettings = Seq( inputSourceDirectory := sourceDirectory.value / "input_sources", inputSourceDirectories := Seq(inputSourceDirectory.value), inputSources := (inputSourceDirectories.value ** (-DirectoryFilter)).get(), transformSources / fileMappings := transformSourceMappings.value, transformSources := { (transformSources / fileMappings).value.map { case (in, out) => transform(in, out, sourceProperties.value) } }, sourceGenerators += transformSources.taskValue ) def transformSourceMappings = Def.task { val ss = inputSources.value val sdirs = inputSourceDirectories.value val sm = sourceManaged.value (ss --- sdirs).pair(rebase(sdirs, sm) | flat(sm)).toSeq } def configSettings = transResourceSettings ++ Seq( resourceProperties := { Map( "org" -> organization.value, "sbt.version" -> version.value, "sbt.name" -> name.value, "scala.version" -> scalaVersion.value, "repositories" -> repositories(isSnapshot.value).mkString(IO.Newline) ) } ) def transResourceSettings = Seq( inputResourceDirectory := sourceDirectory.value / "input_resources", inputResourceDirectories := Seq(inputResourceDirectory.value), inputResources := (inputResourceDirectories.value ** (-DirectoryFilter)).get(), transformResources / fileMappings := transformResourceMappings.value, transformResources := { (transformResources / fileMappings).value.map { case (in, out) => transform(in, out, resourceProperties.value) } }, resourceGenerators += transformResources.taskValue ) def transformResourceMappings = Def.task { val rs = inputResources.value val rdirs = inputResourceDirectories.value val rm = resourceManaged.value (rs --- rdirs).pair(rebase(rdirs, rm) | flat(rm)).toSeq } def transform(in: File, out: File, map: Map[String, String]): File = { def get(key: String): String = map.getOrElse(key, sys.error("No value defined for key '" + key + "'")) val newString = Property.replaceAllIn(IO.read(in), mtch => get(mtch.group(1))) if (Some(newString) != read(out)) IO.write(out, newString) out } def read(file: File): Option[String] = try { Some(IO.read(file)) } catch { case _: java.io.IOException => None } lazy val Property = """\$\{\{([\w.-]+)\}\}""".r def repositories(isSnapshot: Boolean) = Releases :: (if (isSnapshot) Snapshots :: Nil else Nil) lazy val Releases = typesafeRepository("releases") lazy val Snapshots = typesafeRepository("snapshots") def typesafeRepository(status: String) = """ typesafe-ivy-%s: https://repo.typesafe.com/typesafe/ivy-% a } val formatter = new java.text.SimpleDateFormat("yyyyMMdd'T'HHmmss") formatter.setTimeZone(TimeZone.getTimeZone("GMT")) val timestamp = formatter.format(new Date) val content = versionLine(version) + "\ntimestamp=" + timestamp val f = dir / fileName if ( !f.exists || f.lastModified < lastCompilationTime(analysis) || !containsVersion(f, version) ) { s.log.info("Writing version information to " + f + " :\n" + content) IO.write(f, content) } f :: Nil } def versionLine(version: String): String = "version=" + version def containsVersion(propFile: File, version: String): Boolean = IO.read(propFile).contains(versionLine(version)) } object Licensed { lazy val notice = SettingKey[File]("notice") lazy val extractLicenses = TaskKey[Seq[File]]("extract-licenses") lazy val seeRegex = """\(see (.*?)\)""".r def licensePath(base: File, str: String): File = { val path = base / str; if (path.exists) path else sys.error("Referenced license '" + str + "' not found at " + path) } def seePaths(base: File, noticeString: String): Seq[File] = seeRegex.findAllIn(noticeString).matchData.map(d => licensePath(base, d.group(1))).toList def settings: Seq[Setting[?]] = Seq( notice := baseDirectory.value / "NOTICE", Compile / unmanagedResources ++= (notice.value +: extractLicenses.value), extractLicenses := extractLicenses0( (ThisBuild / baseDirectory).value, notice.value, streams.value ) ) def extractLicenses0(base: File, note: File, s: TaskStreams): Seq[File] = if (!note.exists) Nil else try { seePaths(base, IO.read(note)) } catch { case e: Exception => s.log.warn("Could not read NOTICE"); Nil } } sbt-launcher-ebbd426/project/build.properties000066400000000000000000000000241522561027200214540ustar00rootroot00000000000000sbt.version=1.12.11 sbt-launcher-ebbd426/project/plugins.sbt000066400000000000000000000010471522561027200204400ustar00rootroot00000000000000addSbtPlugin("com.typesafe.sbt" % "sbt-javaversioncheck" % "0.1.0") addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.1.1") addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.5") addSbtPlugin("com.github.sbt" % "sbt-proguard" % "0.5.0") addSbtPlugin("com.eed3si9n" % "sbt-nocomma" % "0.1.3") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.1") addSbtPlugin("io.get-coursier" % "sbt-shading" % "2.1.5") addSbtPlugin("com.github.sbt" % "sbt-header" % "5.11.0") scalacOptions += "-feature" sbt-launcher-ebbd426/test-sample/000077500000000000000000000000001522561027200170335ustar00rootroot00000000000000sbt-launcher-ebbd426/test-sample/src/000077500000000000000000000000001522561027200176225ustar00rootroot00000000000000sbt-launcher-ebbd426/test-sample/src/main/000077500000000000000000000000001522561027200205465ustar00rootroot00000000000000sbt-launcher-ebbd426/test-sample/src/main/scala/000077500000000000000000000000001522561027200216315ustar00rootroot00000000000000sbt-launcher-ebbd426/test-sample/src/main/scala/xsbt/000077500000000000000000000000001522561027200226115ustar00rootroot00000000000000sbt-launcher-ebbd426/test-sample/src/main/scala/xsbt/boot/000077500000000000000000000000001522561027200235545ustar00rootroot00000000000000sbt-launcher-ebbd426/test-sample/src/main/scala/xsbt/boot/test/000077500000000000000000000000001522561027200245335ustar00rootroot00000000000000sbt-launcher-ebbd426/test-sample/src/main/scala/xsbt/boot/test/Apps.scala000066400000000000000000000033221522561027200264430ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot.test import scala.annotation.nowarn class Exit(val code: Int) extends xsbti.Exit final class MainException(message: String) extends RuntimeException(message) final class ArgumentTest extends xsbti.AppMain: def run(configuration: xsbti.AppConfiguration) = if configuration.arguments.length == 0 then throw new MainException("Arguments were empty") else new Exit(0) class AppVersionTest extends xsbti.AppMain: def run(configuration: xsbti.AppConfiguration) = val expected = configuration.arguments.headOption.getOrElse("") if configuration.provider.id.version == expected then new Exit(0) else throw new MainException( "app version was " + configuration.provider.id.version + ", expected: " + expected ) class ExtraTest extends xsbti.AppMain: def run(configuration: xsbti.AppConfiguration): xsbti.Exit = configuration.arguments.foreach { arg => if getClass.getClassLoader.getResource(arg) eq null then throw new MainException("Could not find '" + arg + "'") } new Exit(0) class PriorityTest extends xsbti.AppMain: def run(configuration: xsbti.AppConfiguration): xsbti.Exit = PriorityTest.run(configuration) object PriorityTest: @nowarn def run(configuration: xsbti.AppConfiguration) = new Exit(0) def main(args: Array[String]): Unit = throw new MainException("This should not be called") object PlainArgumentTestWithReturn: def main(args: Array[String]): Unit = if args.length == 0 then 1 else 0 () object PlainArgumentTest: def main(args: Array[String]): Unit = if args.length == 0 then throw new MainException("Arguments were empty") else () sbt-launcher-ebbd426/test-sample/src/main/scala/xsbt/boot/test/Servers.scala000066400000000000000000000044121522561027200271720ustar00rootroot00000000000000/* * sbt * Licensed under Apache License 2.0 (see LICENSE) */ package xsbt.boot.test import java.net.Socket import java.net.SocketTimeoutException class EchoServer extends xsbti.ServerMain: def start(configuration: xsbti.AppConfiguration): xsbti.Server = object server extends xsbti.Server: // TODO - Start a server. val serverSocket = new java.net.ServerSocket(0) val port = serverSocket.getLocalPort val addr = serverSocket.getInetAddress.getHostAddress override val uri = new java.net.URI(s"http://${addr}:${port}") // Check for stop every second. serverSocket.setSoTimeout(1000) object serverThread extends Thread: private val running = new java.util.concurrent.atomic.AtomicBoolean(true) override def run(): Unit = while running.get do try val clientSocket = serverSocket.accept() // Handle client connections object clientSocketThread extends Thread: override def run(): Unit = echoTo(clientSocket) clientSocketThread.start() catch case _: SocketTimeoutException => // Ignore // Simple mechanism to dump input to output. private def echoTo(socket: Socket): Unit = val input = new java.io.BufferedReader(new java.io.InputStreamReader(socket.getInputStream)) val output = new java.io.BufferedWriter(new java.io.OutputStreamWriter(socket.getOutputStream)) import scala.util.control.Breaks.* try // Lame way to break out. breakable { def read(): Unit = input.readLine match case null => () case "kill" => running.set(false) serverSocket.close() break() case line => output.write(line) output.flush() read() read() } finally output.close() input.close() socket.close() // Start the thread immediately serverThread.start() override def awaitTermination(): xsbti.MainResult = serverThread.join() new Exit(0) server