Better auto project ID handling. Ref #776.

* Consolidate project ID validation and normalization into Project methods
* Provide an earlier and more detailed error message when the directory
  name can't be used for the project ID
This commit is contained in:
Mark Harrah 2013-06-23 19:57:30 -04:00
parent 6f0028e50d
commit ce1c8b0ebc
5 changed files with 31 additions and 5 deletions

View File

@ -5,6 +5,7 @@ package sbt
object StringUtilities
{
@deprecated("Different use cases require different normalization. Use Project.normalizeModuleID or normalizeProjectID instead.", "0.13.0")
def normalize(s: String) = s.toLowerCase.replaceAll("""\W+""", "-")
def nonEmpty(s: String, label: String)
{

View File

@ -923,7 +923,7 @@ object Classpaths
conflictWarning in GlobalScope :== ConflictWarning.default("global"),
conflictWarning := conflictWarning.value.copy(label = Reference.display(thisProjectRef.value)),
unmanagedBase := baseDirectory.value / "lib",
normalizedName := StringUtilities.normalize(name.value),
normalizedName := Project.normalizeModuleID(name.value),
isSnapshot <<= isSnapshot or version(_ endsWith "-SNAPSHOT"),
description <<= description or name,
homepage in GlobalScope :== None,

View File

@ -435,17 +435,26 @@ object Load
val loadedDefs = new sbt.LoadedDefinitions(defDir, Nil, plugs.loader, defs, loadedProjects, defNames)
new sbt.BuildUnit(uri, normBase, loadedDefs, plugs)
}
private[this] def autoID(localBase: File, context: PluginManagement.Context, existingIDs: Seq[String]): String =
{
import StringUtilities.{normalize => norm}
def normalizeID(f: File) = Project.normalizeProjectID(f.getName) match {
case Right(id) => id
case Left(msg) => error(autoIDError(f, msg))
}
def nthParentName(f: File, i: Int): String =
if(f eq null) Build.defaultID(localBase) else if(i <= 0) norm(f.getName) else nthParentName(f.getParentFile, i - 1)
if(f eq null) Build.defaultID(localBase) else if(i <= 0) normalizeID(f) else nthParentName(f.getParentFile, i - 1)
val pluginDepth = context.pluginProjectDepth
val postfix = "-build" * pluginDepth
val idBase = if(context.globalPluginProject) "global-plugins" else nthParentName(localBase, pluginDepth)
val tryID = idBase + postfix
if(existingIDs.contains(tryID)) Build.defaultID(localBase) else tryID
}
private[this] def autoIDError(base: File, reason: String): String =
"Could not derive root project ID from directory " + base.getAbsolutePath + ":\n" +
reason + "\nRename the directory or explicitly define a root project."
private[this] def projectsFromBuild(b: Build, base: File): Seq[Project] =
b.projectDefinitions(base).map(resolveBase(base))

View File

@ -157,10 +157,26 @@ object Project extends ProjectExtra
delegates: => Seq[ProjectReference] = Nil, settings: => Seq[Def.Setting[_]] = defaultSettings, configurations: Seq[Configuration] = Configurations.default,
auto: AddSettings = AddSettings.allDefaults): Project =
{
DefaultParsers.parse(id, DefaultParsers.ID).left.foreach(errMsg => sys.error("Invalid project ID: " + errMsg))
validProjectID(id).foreach(errMsg => sys.error("Invalid project ID: " + errMsg))
new ProjectDef[ProjectReference](id, base, aggregate, dependencies, delegates, settings, configurations, auto) with Project
}
/** Returns None if `id` is a valid Project ID or Some containing the parser error message if it is not.*/
def validProjectID(id: String): Option[String] = DefaultParsers.parse(id, DefaultParsers.ID).left.toOption
/** Constructs a valid Project ID based on `id` and returns it in Right or returns the error message in Left if one cannot be constructed.*/
def normalizeProjectID(id: String): Either[String, String] =
{
// TODO: better attempt
val attempt = id.toLowerCase.replaceAll("""\W+""", "-")
validProjectID(attempt).toLeft(attempt)
}
/** Normalize a String so that it is suitable for use as a dependency management module identifier.
* This is a best effort implementation, since valid characters are not documented or consistent.*/
def normalizeModuleID(id: String): String = id.toLowerCase.replaceAll("""\W+""", "-")
def resolved(id: String, base: File, aggregate: => Seq[ProjectRef], dependencies: => Seq[ResolvedClasspathDependency], delegates: => Seq[ProjectRef],
settings: Seq[Def.Setting[_]], configurations: Seq[Configuration], auto: AddSettings): ResolvedProject =
new ProjectDef[ProjectRef](id, base, aggregate, dependencies, delegates, settings, configurations, auto) with ResolvedProject

View File

@ -175,7 +175,7 @@ project/ directory.
val pom = pomFile(info)
val model = readPom(pom)
val n = StringUtilities.normalize(model.getName)
val n = Project.normalizeProjectID(model.getName)
val base = Option(model.getProjectDirectory) getOrElse info.base
val root = Project(n, base) settings( pomSettings(model) : _*)
val build = new Build { override def projects = Seq(root) }