changeset 8329:2a77fdc4417c

Allow source-last-modified != $py.class mtime (addresses #2862) We allow up to 2 seconds discrepancy in the logic that decides whether to re-compile .py source to $py.class files. This is to accommodate the rounding of last-modified time that occurs when storing and extracting files from a JAR, as during installation. We choose to round times down when JARring and allow 2 seconds positive difference in the check. This has benefits beyond #2862, in avoiding spurious recompilation, so is made a distinct commit. At the same time the implications of #2862 are arguably not addressed fully until the JAR cache is made user-local.
author Jeff Allen <ja.py@farowl.co.uk>
date Fri, 21 Feb 2020 08:32:01 +0000
parents 96bb13434427
children 33ea06302061
files Lib/test/test_import.py build.xml installer/src/java/org/python/util/install/JarInstaller.java src/org/python/core/imp.java
diffstat 4 files changed, 22 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/Lib/test/test_import.py
+++ b/Lib/test/test_import.py
@@ -142,9 +142,9 @@ class ImportTests(unittest.TestCase):
             # Write a Python file, make it read-only and import it
             with open(fname, 'w') as f:
                 f.write("x = 'original'\n")
-            # Tweak the mtime of the source to ensure pyc gets updated later
+            # Tweak the mtime of the source 10s later to ensure compiled looks out of date
             s = os.stat(fname)
-            os.utime(fname, (s.st_atime, s.st_mtime-100000000))
+            os.utime(fname, (s.st_atime, s.st_mtime+10000))
             os.chmod(fname, 0400)
             m1 = __import__(TESTFN)
             self.assertEqual(m1.x, 'original')
--- a/build.xml
+++ b/build.xml
@@ -1190,7 +1190,7 @@ The text for an official release would c
         </copy>
 
         <echo>building installer .jar file</echo>
-        <jar destfile="${dist.dir}/jython-installer.jar" update="${jar.update}">
+        <jar destfile="${dist.dir}/jython-installer.jar" update="${jar.update}" roundup="false">
             <fileset dir="${dist.dir}">
                 <exclude name="jython-installer.jar"/>
                 <exclude name="${jython.dev.jar}"/>
--- a/installer/src/java/org/python/util/install/JarInstaller.java
+++ b/installer/src/java/org/python/util/install/JarInstaller.java
@@ -49,7 +49,7 @@ public class JarInstaller {
      * <li>generate the start scripts
      * <li>run ensurepip if selected
      * </ul>
-     * 
+     *
      * @param targetDirectory
      * @param installationType
      */
@@ -102,8 +102,9 @@ public class JarInstaller {
                     }
                 }
                 // exclude build.xml when not installing source
-                if (!installationType.installSources() && zipEntryName.equals("build.xml"))
+                if (!installationType.installSources() && zipEntryName.equals("build.xml")) {
                     exclude = true;
+                }
                 // handle exclusion of core Lib files
                 if (!exclude) {
                     exclude = shouldExcludeFile(installationType,
@@ -182,9 +183,9 @@ public class JarInstaller {
         try {
             String command[];
             if (Installation.isWindows()) {
-                command = new String[]{ bindir.resolve("jython.exe").toString(), "-m", "ensurepip" };
+                command = new String[] {bindir.resolve("jython.exe").toString(), "-m", "ensurepip"};
             } else {
-                command = new String[]{ Paths.get(".", "jython").toString(), "-m", "ensurepip"};
+                command = new String[] {Paths.get(".", "jython").toString(), "-m", "ensurepip"};
             }
             ChildProcess childProcess = new ChildProcess(command);
             childProcess.setCWD(bindir);
--- a/src/org/python/core/imp.java
+++ b/src/org/python/core/imp.java
@@ -7,6 +7,7 @@ import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Date;
 import java.util.Map;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.logging.Level;
@@ -396,10 +397,16 @@ public class imp {
             }
         }
 
-        // Check source-last-modified time fossilised in the class file against that expected
+        /*
+         * The source-last-modified time is fossilised in the class file. The source may have been
+         * installed from a JAR, and this will have resulted in rounding of the last-modified time
+         * down (see build.xml::jar-sources) to the nearest 2 seconds.
+         */
         if (testing && sourceLastModified != NO_MTIME) {
-            long mtime = ar.getMTime();
-            if (sourceLastModified != mtime) {
+            long diff = ar.getMTime() - sourceLastModified;
+            if (diff > 2000L) { // = 2000 milliseconds
+                logger.log(Level.FINE, "# {0} time is {1} ms later than source",
+                        new Object[] {name, diff});
                 return null;
             }
         }
@@ -925,6 +932,10 @@ public class imp {
                         if (ret != null) {
                             return ret;
                         }
+                    } else {
+                        logger.log(Level.FINE,
+                                "# {0} dated ({1,date} {1,time,long}) < ({2,date} {2,time,long})",
+                                new Object[] {name, new Date(classTime), new Date(pyTime)});
                     }
                 }