summaryrefslogtreecommitdiff
path: root/src/library/scala/util/Properties.scala
blob: 101a6437ec1dd688a0c3a67feff9b6d569697e85 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2006-2015, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */


package scala
package util

import java.io.{ IOException, PrintWriter }
import java.util.jar.Attributes.{ Name => AttributeName }

/** Loads `library.properties` from the jar. */
object Properties extends PropertiesTrait {
  protected def propCategory    = "library"
  protected def pickJarBasedOn  = classOf[Option[_]]

  /** Scala manifest attributes.
   */
  val ScalaCompilerVersion = new AttributeName("Scala-Compiler-Version")
}

private[scala] trait PropertiesTrait {
  protected def propCategory: String      // specializes the remainder of the values
  protected def pickJarBasedOn: Class[_]  // props file comes from jar containing this

  /** The name of the properties file */
  protected val propFilename = "/" + propCategory + ".properties"

  /** The loaded properties */
  protected lazy val scalaProps: java.util.Properties = {
    val props = new java.util.Properties
    val stream = pickJarBasedOn getResourceAsStream propFilename
    if (stream ne null)
      quietlyDispose(props load stream, stream.close)

    props
  }

  private def quietlyDispose(action: => Unit, disposal: => Unit) =
    try     { action }
    finally {
        try     { disposal }
        catch   { case _: IOException => }
    }

  def propIsSet(name: String)                   = System.getProperty(name) != null
  def propIsSetTo(name: String, value: String)  = propOrNull(name) == value
  def propOrElse(name: String, alt: String)     = System.getProperty(name, alt)
  def propOrEmpty(name: String)                 = propOrElse(name, "")
  def propOrNull(name: String)                  = propOrElse(name, null)
  def propOrNone(name: String)                  = Option(propOrNull(name))
  def propOrFalse(name: String)                 = propOrNone(name) exists (x => List("yes", "on", "true") contains x.toLowerCase)
  def setProp(name: String, value: String)      = System.setProperty(name, value)
  def clearProp(name: String)                   = System.clearProperty(name)

  def envOrElse(name: String, alt: String)      = Option(System getenv name) getOrElse alt
  def envOrNone(name: String)                   = Option(System getenv name)

  def envOrSome(name: String, alt: Option[String])       = envOrNone(name) orElse alt

  // for values based on propFilename, falling back to System properties
  def scalaPropOrElse(name: String, alt: String): String = scalaPropOrNone(name).getOrElse(alt)
  def scalaPropOrEmpty(name: String): String             = scalaPropOrElse(name, "")
  def scalaPropOrNone(name: String): Option[String]      = Option(scalaProps.getProperty(name)).orElse(propOrNone("scala." + name))

  /** The numeric portion of the runtime Scala version, if this is a final
   *  release.  If for instance the versionString says "version 2.9.0.final",
   *  this would return Some("2.9.0").
   *
   *  @return Some(version) if this is a final release build, None if
   *  it is an RC, Beta, etc. or was built from source, or if the version
   *  cannot be read.
   */
  val releaseVersion =
    for {
      v <- scalaPropOrNone("maven.version.number")
      if !(v endsWith "-SNAPSHOT")
    } yield v

  /** The development Scala version, if this is not a final release.
   *  The precise contents are not guaranteed, but it aims to provide a
   *  unique repository identifier (currently the svn revision) in the
   *  fourth dotted segment if the running version was built from source.
   *
   *  @return Some(version) if this is a non-final version, None if this
   *  is a final release or the version cannot be read.
   */
  val developmentVersion =
    for {
      v <- scalaPropOrNone("maven.version.number")
      if v endsWith "-SNAPSHOT"
      ov <- scalaPropOrNone("version.number")
    } yield ov

  /** Either the development or release version if known, otherwise
   *  the empty string.
   */
  def versionNumberString = scalaPropOrEmpty("version.number")

  /** The version number of the jar this was loaded from plus "version " prefix,
   *  or "version (unknown)" if it cannot be determined.
   */
  val versionString         = "version " + scalaPropOrElse("version.number", "(unknown)")
  val copyrightString       = scalaPropOrElse("copyright.string", "Copyright 2002-2017, LAMP/EPFL and Lightbend, Inc.")

  /** This is the encoding to use reading in source files, overridden with -encoding.
   *  Note that it uses "prop" i.e. looks in the scala jar, not the system properties.
   */
  def sourceEncoding        = scalaPropOrElse("file.encoding", "UTF-8")
  def sourceReader          = scalaPropOrElse("source.reader", "scala.tools.nsc.io.SourceReader")

  /** This is the default text encoding, overridden (unreliably) with
   *  `JAVA_OPTS="-Dfile.encoding=Foo"`
   */
  def encodingString        = propOrElse("file.encoding", "UTF-8")

  /** The default end of line character.
   */
  def lineSeparator         = System.lineSeparator()

  /* Various well-known properties. */
  def javaClassPath         = propOrEmpty("java.class.path")
  def javaHome              = propOrEmpty("java.home")
  def javaVendor            = propOrEmpty("java.vendor")
  def javaVersion           = propOrEmpty("java.version")
  def javaVmInfo            = propOrEmpty("java.vm.info")
  def javaVmName            = propOrEmpty("java.vm.name")
  def javaVmVendor          = propOrEmpty("java.vm.vendor")
  def javaVmVersion         = propOrEmpty("java.vm.version")
  def javaSpecVersion       = propOrEmpty("java.specification.version")
  def javaSpecVendor        = propOrEmpty("java.specification.vendor")
  def javaSpecName          = propOrEmpty("java.specification.name")
  def osName                = propOrEmpty("os.name")
  def scalaHome             = propOrEmpty("scala.home")
  def tmpDir                = propOrEmpty("java.io.tmpdir")
  def userDir               = propOrEmpty("user.dir")
  def userHome              = propOrEmpty("user.home")
  def userName              = propOrEmpty("user.name")

  /* Some derived values. */
  /** Returns `true` iff the underlying operating system is a version of Microsoft Windows. */
  def isWin                 = osName startsWith "Windows"
  // See http://mail.openjdk.java.net/pipermail/macosx-port-dev/2012-November/005148.html for
  // the reason why we don't follow developer.apple.com/library/mac/#technotes/tn2002/tn2110.
  /** Returns `true` iff the underlying operating system is a version of Apple Mac OSX.  */
  def isMac                 = osName startsWith "Mac OS X"
  /** Returns `true` iff the underlying operating system is a Linux distribution. */
  def isLinux               = osName startsWith "Linux"

  /* Some runtime values. */
  private[scala] def isAvian = javaVmName contains "Avian"

  private[scala] def coloredOutputEnabled: Boolean = propOrElse("scala.color", "auto") match {
    case "auto" => System.console() != null && !isWin
    case a if a.toLowerCase() == "true" => true
    case _ => false
  }

  // This is looking for javac, tools.jar, etc.
  // Tries JDK_HOME first, then the more common but likely jre JAVA_HOME,
  // and finally the system property based javaHome.
  def jdkHome               = envOrElse("JDK_HOME", envOrElse("JAVA_HOME", javaHome))

  // private[scala] for 2.12
  private[this] def versionFor(command: String) = f"Scala $command $versionString -- $copyrightString"

  def versionMsg            = versionFor(propCategory)
  def scalaCmd              = if (isWin) "scala.bat" else "scala"
  def scalacCmd             = if (isWin) "scalac.bat" else "scalac"

  /** Compares the given specification version to the specification version of the platform.
   *
   *  @param version a specification version number (legacy forms acceptable)
   *  @return `true` if the specification version of the current runtime
   *    is equal to or higher than the version denoted by the given string.
   *  @throws NumberFormatException if the given string is not a version string
   *
   *  @example {{{
   *  // In this example, the runtime's Java specification is assumed to be at version 8.
   *  isJavaAtLeast("1.8")            // true
   *  isJavaAtLeast("8")              // true
   *  isJavaAtLeast("9")              // false
   *  isJavaAtLeast("9.1")            // false
   *  isJavaAtLeast("1.9")            // throws
   *  }}}
   */
  def isJavaAtLeast(version: String): Boolean = {
    def versionOf(s: String, depth: Int): (Int, String) =
      s.indexOf('.') match {
        case 0 =>
          (-2, s.substring(1))
        case 1 if depth == 0 && s.charAt(0) == '1' =>
          val r0 = s.substring(2)
          val (v, r) = versionOf(r0, 1)
          val n = if (v > 8 || r0.isEmpty) -2 else v   // accept 1.8, not 1.9 or 1.
          (n, r)
        case -1 =>
          val n = if (!s.isEmpty) s.toInt else if (depth == 0) -2 else 0
          (n, "")
        case i  =>
          val r = s.substring(i + 1)
          val n = if (depth < 2 && r.isEmpty) -2 else s.substring(0, i).toInt
          (n, r)
      }
    def compareVersions(s: String, v: String, depth: Int): Int = {
      if (depth >= 3) 0
      else {
        val (sn, srest) = versionOf(s, depth)
        val (vn, vrest) = versionOf(v, depth)
        if (vn < 0) -2
        else if (sn < vn) -1
        else if (sn > vn) 1
        else compareVersions(srest, vrest, depth + 1)
      }
    }
    compareVersions(javaSpecVersion, version, 0) match {
      case -2 => throw new NumberFormatException(s"Not a version: $version")
      case i  => i >= 0
    }
  }

  // provide a main method so version info can be obtained by running this
  def main(args: Array[String]) {
    val writer = new PrintWriter(Console.err, true)
    writer println versionMsg
  }
}