diff --git a/ivy/src/main/scala/sbt/StringUtilities.scala b/ivy/src/main/scala/sbt/StringUtilities.scala index 17f6e75a9..b86427449 100644 --- a/ivy/src/main/scala/sbt/StringUtilities.scala +++ b/ivy/src/main/scala/sbt/StringUtilities.scala @@ -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) { diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 1c235736b..b48b40c28 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -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, diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index 12398a0d0..6f1420206 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -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)) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index bd2b753b6..5f62dfbfa 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -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 diff --git a/src/sphinx/Extending/Build-Loaders.rst b/src/sphinx/Extending/Build-Loaders.rst index 0f1468429..d8ceed18a 100644 --- a/src/sphinx/Extending/Build-Loaders.rst +++ b/src/sphinx/Extending/Build-Loaders.rst @@ -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) }