@@ -8,12 +8,13 @@ package tools
8
8
package nsc
9
9
10
10
import java .io .{ File , FileOutputStream , PrintWriter , IOException , FileNotFoundException }
11
+ import java .net .URL
11
12
import java .nio .charset .{ Charset , CharsetDecoder , IllegalCharsetNameException , UnsupportedCharsetException }
12
13
import scala .compat .Platform .currentTime
13
14
import scala .collection .{ mutable , immutable }
14
15
import io .{ SourceReader , AbstractFile , Path }
15
16
import reporters .{ Reporter , ConsoleReporter }
16
- import util .{ ClassPath , StatisticsInfo , returning , stackTraceString }
17
+ import util .{ ClassPath , MergedClassPath , StatisticsInfo , returning , stackTraceString }
17
18
import scala .reflect .ClassTag
18
19
import scala .reflect .internal .util .{ OffsetPosition , SourceFile , NoSourceFile , BatchSourceFile , ScriptSourceFile }
19
20
import scala .reflect .internal .pickling .{ PickleBuffer , PickleFormat }
@@ -841,6 +842,150 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
841
842
} reverse
842
843
}
843
844
845
+ // ------------ REPL utilities ---------------------------------
846
+
847
+ /** Extend classpath of `platform` and rescan updated packages. */
848
+ def extendCompilerClassPath (urls : URL * ): Unit = {
849
+ val newClassPath = platform.classPath.mergeUrlsIntoClassPath(urls : _* )
850
+ platform.currentClassPath = Some (newClassPath)
851
+ // Reload all specified jars into this compiler instance
852
+ invalidateClassPathEntries(urls.map(_.getPath): _* )
853
+ }
854
+
855
+ // ------------ Invalidations ---------------------------------
856
+
857
+ /** Is given package class a system package class that cannot be invalidated?
858
+ */
859
+ private def isSystemPackageClass (pkg : Symbol ) =
860
+ pkg == RootClass || (pkg.hasTransOwner(definitions.ScalaPackageClass ) && ! pkg.hasTransOwner(this .rootMirror.staticPackage(" scala.tools" ).moduleClass.asClass))
861
+
862
+ /** Invalidates packages that contain classes defined in a classpath entry, and
863
+ * rescans that entry.
864
+ *
865
+ * First, the classpath entry referred to by one of the `paths` is rescanned,
866
+ * so that any new files or changes in subpackages are picked up.
867
+ * Second, any packages for which one of the following conditions is met is invalidated:
868
+ * - the classpath entry contained during the last compilation run now contains classfiles
869
+ * that represent a member in the package;
870
+ * - the classpath entry now contains classfiles that represent a member in the package;
871
+ * - the set of subpackages has changed.
872
+ *
873
+ * The invalidated packages are reset in their entirety; all member classes and member packages
874
+ * are re-accessed using the new classpath.
875
+ *
876
+ * System packages that the compiler needs to access as part of standard definitions
877
+ * are not invalidated. A system package is:
878
+ * Any package rooted in "scala", with the exception of packages rooted in "scala.tools".
879
+ *
880
+ * @param paths Fully-qualified names that refer to directories or jar files that are
881
+ * entries on the classpath.
882
+ */
883
+ def invalidateClassPathEntries (paths : String * ): Unit = {
884
+ implicit object ClassPathOrdering extends Ordering [PlatformClassPath ] {
885
+ def compare (a: PlatformClassPath , b: PlatformClassPath ) = a.asClasspathString compare b.asClasspathString
886
+ }
887
+ val invalidated, failed = new mutable.ListBuffer [ClassSymbol ]
888
+ classPath match {
889
+ case cp : MergedClassPath [_] =>
890
+ def assoc (path : String ): List [(PlatformClassPath , PlatformClassPath )] = {
891
+ val dir = AbstractFile .getDirectory(path)
892
+ val canonical = dir.canonicalPath
893
+ def matchesCanonical (e : ClassPath [_]) = e.origin match {
894
+ case Some (opath) =>
895
+ AbstractFile .getDirectory(opath).canonicalPath == canonical
896
+ case None =>
897
+ false
898
+ }
899
+ cp.entries find matchesCanonical match {
900
+ case Some (oldEntry) =>
901
+ List (oldEntry -> cp.context.newClassPath(dir))
902
+ case None =>
903
+ error(s " Error adding entry to classpath. During invalidation, no entry named $path in classpath $classPath" )
904
+ List ()
905
+ }
906
+ }
907
+ val subst = immutable.TreeMap (paths flatMap assoc : _* )
908
+ if (subst.nonEmpty) {
909
+ platform updateClassPath subst
910
+ informProgress(s " classpath updated on entries [ ${subst.keys mkString " ," }] " )
911
+ def mkClassPath (elems : Iterable [PlatformClassPath ]): PlatformClassPath =
912
+ if (elems.size == 1 ) elems.head
913
+ else new MergedClassPath (elems, classPath.context)
914
+ val oldEntries = mkClassPath(subst.keys)
915
+ val newEntries = mkClassPath(subst.values)
916
+ mergeNewEntries(newEntries, RootClass , Some (classPath), Some (oldEntries), invalidated, failed)
917
+ }
918
+ }
919
+ def show (msg : String , syms : scala.collection.Traversable [Symbol ]) =
920
+ if (syms.nonEmpty)
921
+ informProgress(s " $msg: ${syms map (_.fullName) mkString " ," }" )
922
+ show(" invalidated packages" , invalidated)
923
+ show(" could not invalidate system packages" , failed)
924
+ }
925
+
926
+ /** Merges new classpath entries into the symbol table
927
+ *
928
+ * @param newEntries The new classpath entries
929
+ * @param root The root symbol to be resynced (a package class)
930
+ * @param allEntries Optionally, the corresponding package in the complete current classpath
931
+ * @param oldEntries Optionally, the corresponding package in the old classpath entries
932
+ * @param invalidated A listbuffer collecting the invalidated package classes
933
+ * @param failed A listbuffer collecting system package classes which could not be invalidated
934
+ *
935
+ * The merging strategy is determined by the absence or presence of classes and packages.
936
+ *
937
+ * If either oldEntries or newEntries contains classes, root is invalidated provided that a corresponding package
938
+ * exists in allEntries. Otherwise it is removed.
939
+ * Otherwise, the action is determined by the following matrix, with columns:
940
+ *
941
+ * old sym action
942
+ * + + recurse into all child packages of newEntries
943
+ * - + invalidate root
944
+ * - - create and enter root
945
+ *
946
+ * Here, old means classpath, and sym means symboltable. + is presence of an entry in its column, - is absence.
947
+ */
948
+ private def mergeNewEntries (newEntries : PlatformClassPath , root : ClassSymbol ,
949
+ allEntries : OptClassPath , oldEntries : OptClassPath ,
950
+ invalidated : mutable.ListBuffer [ClassSymbol ], failed : mutable.ListBuffer [ClassSymbol ]) {
951
+ ifDebug(informProgress(s " syncing $root, $oldEntries -> $newEntries" ))
952
+
953
+ val getName : ClassPath [AbstractFile ] => String = (_.name)
954
+ def hasClasses (cp : OptClassPath ) = cp.isDefined && cp.get.classes.nonEmpty
955
+ def invalidateOrRemove (root : ClassSymbol ) = {
956
+ allEntries match {
957
+ case Some (cp) => root setInfo new loaders.PackageLoader (cp)
958
+ case None => root.owner.info.decls unlink root.sourceModule
959
+ }
960
+ invalidated += root
961
+ }
962
+ def subPackage (cp : PlatformClassPath , name : String ): OptClassPath =
963
+ cp.packages find (cp1 => getName(cp1) == name)
964
+
965
+ val classesFound = hasClasses(oldEntries) || newEntries.classes.nonEmpty
966
+ if (classesFound && ! isSystemPackageClass(root)) {
967
+ invalidateOrRemove(root)
968
+ } else {
969
+ if (classesFound) {
970
+ if (root.isRoot) invalidateOrRemove(EmptyPackageClass )
971
+ else failed += root
972
+ }
973
+ if (! oldEntries.isDefined) invalidateOrRemove(root)
974
+ else
975
+ for (pstr <- newEntries.packages.map(getName)) {
976
+ val pname = newTermName(pstr)
977
+ val pkg = (root.info decl pname) orElse {
978
+ // package does not exist in symbol table, create symbol to track it
979
+ assert(! subPackage(oldEntries.get, pstr).isDefined)
980
+ loaders.enterPackage(root, pstr, new loaders.PackageLoader (allEntries.get))
981
+ }
982
+ mergeNewEntries(subPackage(newEntries, pstr).get, pkg.moduleClass.asClass,
983
+ subPackage(allEntries.get, pstr), subPackage(oldEntries.get, pstr),
984
+ invalidated, failed)
985
+ }
986
+ }
987
+ }
988
+
844
989
// ----------- Runs ---------------------------------------
845
990
846
991
private var curRun : Run = null
0 commit comments