mirror of https://github.com/sbt/sbt.git
218 lines
9.0 KiB
Scala
218 lines
9.0 KiB
Scala
/* sbt -- Simple Build Tool
|
|
* Copyright 2008 Mark Harrah, David MacIver
|
|
*/
|
|
package sbt
|
|
|
|
import scala.reflect.Manifest
|
|
|
|
import scala.collection.Map
|
|
trait BasicEnvironment extends Environment
|
|
{
|
|
protected def log: Logger
|
|
/** The location of the properties file that backs the user-defined properties. */
|
|
def envBackingPath: Path
|
|
/** The environment from which user-defined properties inherit (if enabled). */
|
|
protected def parentEnvironment: Option[BasicEnvironment] = None
|
|
/** The identifier used in messages to refer to this environment. */
|
|
def environmentLabel = envBackingPath.absolutePath
|
|
|
|
private[this] var isModified = false
|
|
private[sbt] def setEnvironmentModified(modified: Boolean) { synchronized { isModified = modified } }
|
|
private[this] def isEnvironmentModified = synchronized { isModified }
|
|
|
|
|
|
implicit val IntFormat: Format[Int] = new SimpleFormat[Int] { def fromString(s: String) = java.lang.Integer.parseInt(s) }
|
|
implicit val LongFormat: Format[Long] = new SimpleFormat[Long] { def fromString(s: String) = java.lang.Long.parseLong(s) }
|
|
implicit val DoubleFormat: Format[Double] = new SimpleFormat[Double] { def fromString(s: String) = java.lang.Double.parseDouble(s) }
|
|
implicit val BooleanFormat: Format[Boolean] = new SimpleFormat[Boolean] { def fromString(s: String) = java.lang.Boolean.valueOf(s).booleanValue }
|
|
implicit val StringFormat: Format[String] = Format.string
|
|
val NonEmptyStringFormat: Format[String] = new SimpleFormat[String]
|
|
{
|
|
def fromString(s: String) =
|
|
{
|
|
val trimmed = s.trim
|
|
if(trimmed.isEmpty)
|
|
error("The empty string is not allowed.")
|
|
trimmed
|
|
}
|
|
}
|
|
implicit val VersionFormat: Format[Version] =
|
|
new SimpleFormat[Version]
|
|
{
|
|
def fromString(s: String) = Version.fromString(s).fold(msg => error(msg), x => x)
|
|
}
|
|
implicit val FileFormat = Format.file
|
|
|
|
|
|
/** Implementation of 'Property' for user-defined properties. */
|
|
private[sbt] class UserProperty[T](lazyDefaultValue: => Option[T], format: Format[T], inheritEnabled: Boolean,
|
|
inheritFirst: Boolean, private[BasicEnvironment] val manifest: Manifest[T]) extends Property[T]
|
|
{
|
|
/** The name of this property is used for persistence in the properties file and as an identifier in messages.*/
|
|
lazy val name = propertyMap.find( p => p._2 eq this ).map(_._1)
|
|
/** Gets the name of this property or an alternative if the name is not available.*/
|
|
private def nameString = name.getOrElse("<unnamed>")
|
|
/** The lazily evaluated default value for this property.*/
|
|
private lazy val defaultValue = lazyDefaultValue
|
|
/** The explicitly set value for this property.*/
|
|
private[BasicEnvironment] var explicitValue =
|
|
{
|
|
def initialValue = for(n <- name; stringValue <- initialValues.get(n)) yield format.fromString(stringValue)
|
|
new LazyVar[Option[T]](initialValue) // ensure propertyMap is initialized before a read occurs
|
|
}
|
|
def update(v: T): Unit = synchronized { explicitValue() = Some(v); setEnvironmentModified(true) }
|
|
def resolve: PropertyResolution[T] =
|
|
synchronized
|
|
{
|
|
if(inheritFirst) resolveInheritFirst
|
|
else resolveDefaultFirst
|
|
}
|
|
private def resolveInheritFirst =
|
|
explicitValue() match
|
|
{
|
|
case Some(v) => DefinedValue(v, false, false)
|
|
case None =>
|
|
val inherited = inheritedValue
|
|
// note that the following means the default value will not be used if an exception occurs inheriting
|
|
inherited orElse getDefault(inherited)
|
|
}
|
|
private def resolveDefaultFirst =
|
|
explicitValue() match
|
|
{
|
|
case Some(v) => DefinedValue(v, false, false)
|
|
case None => getDefault(inheritedValue)
|
|
}
|
|
private def getDefault(orElse: => PropertyResolution[T]): PropertyResolution[T] =
|
|
try
|
|
{
|
|
defaultValue match
|
|
{
|
|
case Some(v) => DefinedValue(v, false, true)
|
|
case None => orElse
|
|
}
|
|
} catch { case e: Exception =>
|
|
ResolutionException("Error while evaluating default value for property", Some(e))
|
|
}
|
|
|
|
private def inheritedValue: PropertyResolution[T] =
|
|
{
|
|
val propOption = if(inheritEnabled) parentProperty else None
|
|
propOption match
|
|
{
|
|
case Some(prop) => tryToInherit(prop)
|
|
case None => UndefinedValue(nameString, environmentLabel)
|
|
}
|
|
}
|
|
private def parentProperty = for(parent <- parentEnvironment; n <- name; prop <- parent.propertyMap.get(n)) yield prop
|
|
|
|
private def tryToInherit[R](prop: BasicEnvironment#UserProperty[R]): PropertyResolution[T] =
|
|
{
|
|
if(prop.manifest <:< manifest)
|
|
markInherited(prop.resolve.asInstanceOf[PropertyResolution[T]])
|
|
else
|
|
ResolutionException("Could not inherit property '" + nameString + "' from '" + environmentLabel + "':\n" +
|
|
"\t Property had type " + prop.manifest + ", expected type " + manifest, None)
|
|
}
|
|
private def markInherited(result: PropertyResolution[T]) =
|
|
result match
|
|
{
|
|
case DefinedValue(v, isInherited, isDefault) => DefinedValue(v, true, isDefault)
|
|
case x => x
|
|
}
|
|
|
|
override def toString = nameString + "=" + resolve
|
|
|
|
/** Gets the explicitly set value converted to a 'String'.*/
|
|
private[sbt] def getStringValue: Option[String] = explicitValue().map(format.toString)
|
|
/** Explicitly sets the value for this property by converting the given string value.*/
|
|
private[sbt] def setStringValue(s: String) { update(format.fromString(s)) }
|
|
}
|
|
/** Implementation of 'Property' for system properties (i.e. System.getProperty/setProperty) */
|
|
private class SystemProperty[T](val name: String, lazyDefaultValue: => Option[T], val format: Format[T]) extends Property[T]
|
|
{
|
|
def resolve =
|
|
{
|
|
val rawValue = System.getProperty(name)
|
|
if(rawValue == null)
|
|
notFound
|
|
else
|
|
try
|
|
DefinedValue(format.fromString(rawValue), false, false)
|
|
catch {
|
|
case e: Exception => ResolutionException("Error parsing system property '" + name + "': " + e.toString, Some(e))
|
|
}
|
|
}
|
|
/** Handles resolution when the property has no explicit value. If there is a default value, that is returned,
|
|
* otherwise, UndefinedValue is returned.*/
|
|
private def notFound =
|
|
{
|
|
defaultValue match
|
|
{
|
|
case Some(dv) =>
|
|
{
|
|
log.debug("System property '" + name + "' does not exist, using provided default.")
|
|
DefinedValue(dv, false, true)
|
|
}
|
|
case None => UndefinedValue(name, environmentLabel)
|
|
}
|
|
}
|
|
protected lazy val defaultValue = lazyDefaultValue
|
|
def update(t: T)
|
|
{
|
|
try System.setProperty(name, format.toString(t))
|
|
catch {
|
|
case e: Exception =>
|
|
log.trace(e)
|
|
log.warn("Error setting system property '" + name + "': " + e.toString)
|
|
}
|
|
}
|
|
override def toString = name + "=" + resolve
|
|
}
|
|
|
|
def system[T](propertyName: String)(implicit format: Format[T]): Property[T] =
|
|
new SystemProperty[T](propertyName, None, format)
|
|
def systemOptional[T](propertyName: String, defaultValue: => T)(implicit format: Format[T]): Property[T] =
|
|
new SystemProperty[T](propertyName, Some(defaultValue), format)
|
|
|
|
def property[T](implicit manifest: Manifest[T], format: Format[T]): Property[T] =
|
|
new UserProperty[T](None, format, true, false, manifest)
|
|
def propertyLocal[T](implicit manifest: Manifest[T], format: Format[T]): Property[T] =
|
|
new UserProperty[T](None, format, false, false, manifest)
|
|
def propertyOptional[T](defaultValue: => T)(implicit manifest: Manifest[T], format: Format[T]): Property[T] =
|
|
propertyOptional(defaultValue, false)(manifest, format)
|
|
def propertyOptional[T](defaultValue: => T, inheritFirst: Boolean)(implicit manifest: Manifest[T], format: Format[T]): Property[T] =
|
|
new UserProperty[T](Some(defaultValue), format, true, inheritFirst, manifest)
|
|
|
|
private type AnyUserProperty = UserProperty[_]
|
|
/** Maps property name to property. The map is constructed by reflecting vals defined on this object,
|
|
* so it should not be referenced during initialization or else subclass properties will be missed.**/
|
|
private lazy val propertyMap: Map[String, AnyUserProperty] =
|
|
{
|
|
log.debug("Discovering properties")
|
|
val propertyMap = new scala.collection.mutable.HashMap[String, AnyUserProperty]
|
|
// AnyProperty is required because the return type of the property*[T] methods is Property[T]
|
|
// and so the vals we are looking for have type Property[T] and not UserProperty[T]
|
|
// We then only keep instances of UserProperty
|
|
val vals = Environment.reflectiveMappings(this, classOf[Property[_]])
|
|
for( (name, property: AnyUserProperty) <- vals)
|
|
propertyMap(name) = property
|
|
propertyMap //.readOnly (not currently in 2.8)
|
|
}
|
|
private val initialValues: Map[String, String] = MapIO.readStrings(environmentLabel, envBackingPath)
|
|
|
|
def propertyNames: Iterable[String] = propertyMap.keys.toList
|
|
def getPropertyNamed(name: String): Option[UserProperty[_]] = propertyMap.get(name)
|
|
def propertyNamed(name: String): UserProperty[_] = propertyMap(name)
|
|
def saveEnvironment()
|
|
{
|
|
if(isEnvironmentModified)
|
|
{
|
|
val properties = new java.util.Properties
|
|
for( (name, variable) <- propertyMap; stringValue <- variable.getStringValue)
|
|
properties.setProperty(name, stringValue)
|
|
IO.write(properties, "Project properties", envBackingPath)
|
|
setEnvironmentModified(false)
|
|
}
|
|
}
|
|
private[sbt] def uninitializedProperties: Iterable[(String, Property[_])] = propertyMap.filter(_._2.get.isEmpty)
|
|
} |