From 5a3b95cd6d728e5f0bfbb1560d35d7b10224ddac Mon Sep 17 00:00:00 2001 From: Stu Hood Date: Fri, 18 Sep 2015 13:03:57 -0700 Subject: [PATCH 1/4] Add support for parsing system classes. --- .../api/src/main/scala/sbt/ClassToAPI.scala | 2 +- util/io/src/main/scala/sbt/IO.scala | 26 ++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/compile/api/src/main/scala/sbt/ClassToAPI.scala b/compile/api/src/main/scala/sbt/ClassToAPI.scala index 37e8a5919..8511d3599 100644 --- a/compile/api/src/main/scala/sbt/ClassToAPI.scala +++ b/compile/api/src/main/scala/sbt/ClassToAPI.scala @@ -160,7 +160,7 @@ object ClassToAPI { } catch { case e: Throwable => throw new IllegalStateException( - s"Failed to parse class $c: this may mean your classfiles are corrupted. Please clean and try again.", + s"Failed to parse $c: this may mean your classfiles are corrupted. Please clean and try again.", e ) } diff --git a/util/io/src/main/scala/sbt/IO.scala b/util/io/src/main/scala/sbt/IO.scala index 348fd0ab3..64e515b97 100644 --- a/util/io/src/main/scala/sbt/IO.scala +++ b/util/io/src/main/scala/sbt/IO.scala @@ -38,14 +38,22 @@ object IO { /** * Returns a URL for the directory or jar containing the the class file `cl`. * If the location cannot be determined, an error is generated. - * Note that Java standard library classes typically do not have a location associated with them. */ - def classLocation(cl: Class[_]): URL = - { - val codeSource = cl.getProtectionDomain.getCodeSource - if (codeSource == null) sys.error("No class location for " + cl) - else codeSource.getLocation + def classLocation(cl: Class[_]): URL = { + val codeSource = cl.getProtectionDomain.getCodeSource + if (codeSource ne null) { + codeSource.getLocation + } else { + Option(ClassLoader.getSystemClassLoader.getResource(classfilePathForClassname(cl.getName))) + .flatMap { + // TODO: assuming that System-class-loaded classes are located in jars, which + // will cause this method to truncate to the jar + urlAsFile + }.getOrElse { + sys.error("No class location for " + cl) + }.toURI.toURL } + } /** * Returns the directory or jar file containing the the class file `cl`. @@ -88,6 +96,12 @@ object IO { case _ => None } + /** + * @return The path for the given classname. + * + * TODO: crossplatform + */ + def classfilePathForClassname(clsname: String): String = s"${clsname.replace('.', '/')}.class" private[this] def uriToFile(uriString: String): File = { val uri = new URI(uriString) From 198820bc535b00c146cd1a91fd309dfcf602af22 Mon Sep 17 00:00:00 2001 From: Stu Hood Date: Fri, 18 Sep 2015 17:15:52 -0700 Subject: [PATCH 2/4] Move io tests to subdirectory, and add a test for "System" class lookups. --- util/io/src/main/scala/sbt/IO.scala | 13 +++------ .../src/test/scala/{ => sbt}/CopySpec.scala | 0 .../FileUtilitiesSpecification.scala | 0 .../src/test/scala/sbt/IOSpecification.scala | 28 +++++++++++++++++++ .../{ => sbt}/NameFilterSpecification.scala | 0 .../{ => sbt}/RichURISpecification.scala | 0 .../src/test/scala/{ => sbt}/StashSpec.scala | 0 .../src/test/scala/{ => sbt}/WithFiles.scala | 0 8 files changed, 32 insertions(+), 9 deletions(-) rename util/io/src/test/scala/{ => sbt}/CopySpec.scala (100%) rename util/io/src/test/scala/{ => sbt}/FileUtilitiesSpecification.scala (100%) create mode 100644 util/io/src/test/scala/sbt/IOSpecification.scala rename util/io/src/test/scala/{ => sbt}/NameFilterSpecification.scala (100%) rename util/io/src/test/scala/{ => sbt}/RichURISpecification.scala (100%) rename util/io/src/test/scala/{ => sbt}/StashSpec.scala (100%) rename util/io/src/test/scala/{ => sbt}/WithFiles.scala (100%) diff --git a/util/io/src/main/scala/sbt/IO.scala b/util/io/src/main/scala/sbt/IO.scala index 64e515b97..407cde6ce 100644 --- a/util/io/src/main/scala/sbt/IO.scala +++ b/util/io/src/main/scala/sbt/IO.scala @@ -44,10 +44,11 @@ object IO { if (codeSource ne null) { codeSource.getLocation } else { - Option(ClassLoader.getSystemClassLoader.getResource(classfilePathForClassname(cl.getName))) + // NB: this assumes that System-class-loaded classes are located in jars, and thus relies on + // uses forward-slash-separated paths and `urlAsFile`'s truncation to the containing jar file + val clsfile = s"${cl.getName.replace('.', '/')}.class" + Option(ClassLoader.getSystemClassLoader.getResource(clsfile)) .flatMap { - // TODO: assuming that System-class-loaded classes are located in jars, which - // will cause this method to truncate to the jar urlAsFile }.getOrElse { sys.error("No class location for " + cl) @@ -96,12 +97,6 @@ object IO { case _ => None } - /** - * @return The path for the given classname. - * - * TODO: crossplatform - */ - def classfilePathForClassname(clsname: String): String = s"${clsname.replace('.', '/')}.class" private[this] def uriToFile(uriString: String): File = { val uri = new URI(uriString) diff --git a/util/io/src/test/scala/CopySpec.scala b/util/io/src/test/scala/sbt/CopySpec.scala similarity index 100% rename from util/io/src/test/scala/CopySpec.scala rename to util/io/src/test/scala/sbt/CopySpec.scala diff --git a/util/io/src/test/scala/FileUtilitiesSpecification.scala b/util/io/src/test/scala/sbt/FileUtilitiesSpecification.scala similarity index 100% rename from util/io/src/test/scala/FileUtilitiesSpecification.scala rename to util/io/src/test/scala/sbt/FileUtilitiesSpecification.scala diff --git a/util/io/src/test/scala/sbt/IOSpecification.scala b/util/io/src/test/scala/sbt/IOSpecification.scala new file mode 100644 index 000000000..ccffdc5c2 --- /dev/null +++ b/util/io/src/test/scala/sbt/IOSpecification.scala @@ -0,0 +1,28 @@ +/* sbt -- Simple Build Tool + * Copyright 2008 Mark Harrah */ + +package sbt + +import util.Try + +import org.scalacheck._ +import Prop._ + +object IOSpecification extends Properties("IO") { + property("classLocation able to determine containing directories") = + Prop.forAll(classes) { (c: Class[_]) => + Try(IO.classLocationFile(c)).toOption.exists { + case jar if jar.getName.endsWith(".jar") => jar.isFile + case dir => dir.isDirectory + } + } + + implicit def classes: Gen[Class[_]] = + Gen.oneOf( + this.getClass, + classOf[java.lang.Integer], + classOf[String], + classOf[Thread], + classOf[Properties] + ) +} diff --git a/util/io/src/test/scala/NameFilterSpecification.scala b/util/io/src/test/scala/sbt/NameFilterSpecification.scala similarity index 100% rename from util/io/src/test/scala/NameFilterSpecification.scala rename to util/io/src/test/scala/sbt/NameFilterSpecification.scala diff --git a/util/io/src/test/scala/RichURISpecification.scala b/util/io/src/test/scala/sbt/RichURISpecification.scala similarity index 100% rename from util/io/src/test/scala/RichURISpecification.scala rename to util/io/src/test/scala/sbt/RichURISpecification.scala diff --git a/util/io/src/test/scala/StashSpec.scala b/util/io/src/test/scala/sbt/StashSpec.scala similarity index 100% rename from util/io/src/test/scala/StashSpec.scala rename to util/io/src/test/scala/sbt/StashSpec.scala diff --git a/util/io/src/test/scala/WithFiles.scala b/util/io/src/test/scala/sbt/WithFiles.scala similarity index 100% rename from util/io/src/test/scala/WithFiles.scala rename to util/io/src/test/scala/sbt/WithFiles.scala From 9401d47e797ac3ca85ec3fd20037521338b65331 Mon Sep 17 00:00:00 2001 From: Stu Hood Date: Sun, 20 Sep 2015 08:42:10 -0700 Subject: [PATCH 3/4] Remove copyright from IOSpecification, and use a simpler Resource lookup. --- util/io/src/main/scala/sbt/IO.scala | 7 +++---- util/io/src/test/scala/sbt/IOSpecification.scala | 3 --- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/util/io/src/main/scala/sbt/IO.scala b/util/io/src/main/scala/sbt/IO.scala index 407cde6ce..50c6c6c65 100644 --- a/util/io/src/main/scala/sbt/IO.scala +++ b/util/io/src/main/scala/sbt/IO.scala @@ -44,10 +44,9 @@ object IO { if (codeSource ne null) { codeSource.getLocation } else { - // NB: this assumes that System-class-loaded classes are located in jars, and thus relies on - // uses forward-slash-separated paths and `urlAsFile`'s truncation to the containing jar file - val clsfile = s"${cl.getName.replace('.', '/')}.class" - Option(ClassLoader.getSystemClassLoader.getResource(clsfile)) + // NB: This assumes that classes without code sources are System classes, and thus located in + // jars. It assumes that `urlAsFile` will truncate to the containing jar file. + Option(cl.getResource(cl.getSimpleName + ".class")) .flatMap { urlAsFile }.getOrElse { diff --git a/util/io/src/test/scala/sbt/IOSpecification.scala b/util/io/src/test/scala/sbt/IOSpecification.scala index ccffdc5c2..882f147c5 100644 --- a/util/io/src/test/scala/sbt/IOSpecification.scala +++ b/util/io/src/test/scala/sbt/IOSpecification.scala @@ -1,6 +1,3 @@ -/* sbt -- Simple Build Tool - * Copyright 2008 Mark Harrah */ - package sbt import util.Try From 5fbfa7aeb039d4f62420755c43cedd7eb659484a Mon Sep 17 00:00:00 2001 From: Stu Hood Date: Sun, 20 Sep 2015 09:28:43 -0700 Subject: [PATCH 4/4] Move back to ClassLoader.getSystemClassloader --- util/io/src/main/scala/sbt/IO.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/io/src/main/scala/sbt/IO.scala b/util/io/src/main/scala/sbt/IO.scala index 50c6c6c65..0fd5f52ad 100644 --- a/util/io/src/main/scala/sbt/IO.scala +++ b/util/io/src/main/scala/sbt/IO.scala @@ -46,7 +46,8 @@ object IO { } else { // NB: This assumes that classes without code sources are System classes, and thus located in // jars. It assumes that `urlAsFile` will truncate to the containing jar file. - Option(cl.getResource(cl.getSimpleName + ".class")) + val clsfile = s"${cl.getName.replace('.', '/')}.class" + Option(ClassLoader.getSystemClassLoader.getResource(clsfile)) .flatMap { urlAsFile }.getOrElse {