From: <fwi...@us...> - 2009-03-06 04:15:56
|
Revision: 6071 http://jython.svn.sourceforge.net/jython/?rev=6071&view=rev Author: fwierzbicki Date: 2009-03-06 04:15:54 +0000 (Fri, 06 Mar 2009) Log Message: ----------- Added mtime to the compiled Jython classes to match CPython behavior. Fixes: http://bugs.jython.org/issue1567212 and http://bugs.jython.org/issue1024 Modified Paths: -------------- trunk/jython/src/org/python/compiler/ClassFile.java trunk/jython/src/org/python/compiler/Module.java trunk/jython/src/org/python/core/imp.java trunk/jython/src/org/python/modules/imp.java Added Paths: ----------- trunk/jython/Lib/test/test_compile_jy.py trunk/jython/src/org/python/compiler/MTime.java trunk/jython/src/org/python/core/AnnotationReader.java Removed Paths: ------------- trunk/jython/src/org/python/core/APIReader.java Added: trunk/jython/Lib/test/test_compile_jy.py =================================================================== --- trunk/jython/Lib/test/test_compile_jy.py (rev 0) +++ trunk/jython/Lib/test/test_compile_jy.py 2009-03-06 04:15:54 UTC (rev 6071) @@ -0,0 +1,57 @@ +import unittest +import os +import sys +import shutil +import __builtin__ +import py_compile +from test.test_support import run_unittest, TESTFN, is_jython + +class TestMtime(unittest.TestCase): + + def test_mtime_compile(self): + """ + This test exercises the mtime annotation that is now stored in Jython + compiled files. CPython already stores an mtime in its pyc files. To + exercise this functionality, I am writing a py file, compiling it, + setting the os modified time to a very low value on the compiled file, + then changing the py file after a small sleep. On CPython, this would + still cause a re-compile. In Jython before this fix it would not. + See http://bugs.jython.org/issue1024 + """ + + import time + os.mkdir(TESTFN) + try: + mod = "mod1" + source_path = os.path.join(TESTFN, "%s.py" % mod) + if is_jython: + compiled_path = os.path.join(TESTFN, "%s$py.class" % mod) + else: + compiled_path = os.path.join(TESTFN, "%s.pyc" % mod) + fp = open(source_path, "w") + fp.write("def foo(): return 'first'\n") + fp.close() + py_compile.compile(source_path) + + #sleep so that the internal mtime is older for the next source write. + time.sleep(1) + + fp = open(source_path, "w") + fp.write("def foo(): return 'second'\n") + fp.close() + + # make sure the source file's mtime is artificially younger than + # the compiled path's mtime. + os.utime(source_path, (1,1)) + + sys.path.append(TESTFN) + import mod1 + self.assertEquals(mod1.foo(), 'second') + finally: + shutil.rmtree(TESTFN) + +def test_main(): + run_unittest(TestMtime) + +if __name__ == "__main__": + test_main() Modified: trunk/jython/src/org/python/compiler/ClassFile.java =================================================================== --- trunk/jython/src/org/python/compiler/ClassFile.java 2009-03-05 22:07:35 UTC (rev 6070) +++ trunk/jython/src/org/python/compiler/ClassFile.java 2009-03-06 04:15:54 UTC (rev 6071) @@ -19,6 +19,7 @@ { ClassWriter cw; int access; + long mtime; public String name; String superclass; String sfilename; @@ -37,14 +38,19 @@ } public ClassFile(String name) { - this(name, "java/lang/Object", Opcodes.ACC_SYNCHRONIZED | Opcodes.ACC_PUBLIC); + this(name, "java/lang/Object", Opcodes.ACC_SYNCHRONIZED | Opcodes.ACC_PUBLIC, + org.python.core.imp.NO_MTIME); } public ClassFile(String name, String superclass, int access) { + this(name, superclass, access, org.python.core.imp.NO_MTIME); + } + public ClassFile(String name, String superclass, int access, long mtime) { this.name = fixName(name); this.superclass = fixName(superclass); this.interfaces = new String[0]; this.access = access; + this.mtime = mtime; cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); methodVisitors = Collections.synchronizedList(new ArrayList()); @@ -103,6 +109,10 @@ av.visit("value", new Integer(org.python.core.imp.APIVersion)); av.visitEnd(); + av = cw.visitAnnotation("Lorg/python/compiler/MTime;", true); + av.visit("value", new Long(mtime)); + av.visitEnd(); + if (sfilename != null) { cw.visitSource(sfilename, null); } Added: trunk/jython/src/org/python/compiler/MTime.java =================================================================== --- trunk/jython/src/org/python/compiler/MTime.java (rev 0) +++ trunk/jython/src/org/python/compiler/MTime.java 2009-03-06 04:15:54 UTC (rev 6071) @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2008 Jython Developers + * Licensed to PSF under a Contributor Agreement. + */ +package org.python.compiler; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface MTime { + long value(); +} Modified: trunk/jython/src/org/python/compiler/Module.java =================================================================== --- trunk/jython/src/org/python/compiler/Module.java 2009-03-05 22:07:35 UTC (rev 6070) +++ trunk/jython/src/org/python/compiler/Module.java 2009-03-06 04:15:54 UTC (rev 6071) @@ -296,11 +296,17 @@ public boolean linenumbers; Future futures; Hashtable scopes; + long mtime; public Module(String name, String filename, boolean linenumbers) { + this(name, filename, linenumbers, org.python.core.imp.NO_MTIME); + } + + public Module(String name, String filename, boolean linenumbers, long mtime) { this.linenumbers = linenumbers; + this.mtime = mtime; classfile = new ClassFile(name, "org/python/core/PyFunctionTable", - ACC_SYNCHRONIZED | ACC_PUBLIC); + ACC_SYNCHRONIZED | ACC_PUBLIC, mtime); constants = new Hashtable(); sfilename = filename; if (filename != null) @@ -313,7 +319,7 @@ } public Module(String name) { - this(name, name+".py", true); + this(name, name+".py", true, org.python.core.imp.NO_MTIME); } // This block of code handles the pool of Python Constants @@ -633,14 +639,22 @@ } throw new ParseException(msg,node); } + public static void compile(mod node, OutputStream ostream, + String name, String filename, + boolean linenumbers, boolean printResults, + CompilerFlags cflags) + throws Exception + { + compile(node, ostream, name, filename, linenumbers, printResults, cflags, org.python.core.imp.NO_MTIME); + } public static void compile(mod node, OutputStream ostream, String name, String filename, boolean linenumbers, boolean printResults, - CompilerFlags cflags) + CompilerFlags cflags, long mtime) throws Exception { - Module module = new Module(name, filename, linenumbers); + Module module = new Module(name, filename, linenumbers, mtime); if (cflags == null) { cflags = new CompilerFlags(); } Deleted: trunk/jython/src/org/python/core/APIReader.java =================================================================== --- trunk/jython/src/org/python/core/APIReader.java 2009-03-05 22:07:35 UTC (rev 6070) +++ trunk/jython/src/org/python/core/APIReader.java 2009-03-06 04:15:54 UTC (rev 6071) @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2008 Jython Developers - * Licensed to PSF under a Contributor Agreement. - */ -package org.python.core; - -import java.io.IOException; - -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.commons.EmptyVisitor; - -/** - * This class reads a classfile from a byte array and pulls out the value of the class annotation - * for APIVersion, which can then be retrieved by a call to getVersion(). - * - * Hopefully the use of ClassReader in this implementation is not too expensive. I suspect it is not - * since EmptyVisitor is just a bag of empty methods so shouldn't cost too much. If it turns out to - * cost too much, we will want to implement a special purpose ClassReader that only reads out the - * APIVersion annotation I think. - */ -public class APIReader extends EmptyVisitor { - - private boolean nextVisitIsVersion = false; - - private int version = -1; - - /** - * Reads the classfile bytecode in data and to extract the version. - * @throws IOException - if the classfile is malformed. - */ - public APIReader(byte[] data) throws IOException { - ClassReader r; - try { - r = new ClassReader(data); - } catch (ArrayIndexOutOfBoundsException e) { - IOException ioe = new IOException("Malformed bytecode: not enough data"); - ioe.initCause(e);// IOException didn't grow a constructor that could take a cause till - // 1.6, so do it the old fashioned way - throw ioe; - } - r.accept(this, 0); - } - - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - nextVisitIsVersion = desc.equals("Lorg/python/compiler/APIVersion;"); - return this; - } - - public void visit(String name, Object value) { - if (nextVisitIsVersion) { - version = (Integer)value; - nextVisitIsVersion = false; - } - } - - public int getVersion() { - return version; - } -} Copied: trunk/jython/src/org/python/core/AnnotationReader.java (from rev 6064, trunk/jython/src/org/python/core/APIReader.java) =================================================================== --- trunk/jython/src/org/python/core/AnnotationReader.java (rev 0) +++ trunk/jython/src/org/python/core/AnnotationReader.java 2009-03-06 04:15:54 UTC (rev 6071) @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2008 Jython Developers + * Licensed to PSF under a Contributor Agreement. + */ +package org.python.core; + +import java.io.IOException; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.commons.EmptyVisitor; + +/** + * This class reads a classfile from a byte array and pulls out the value of the class annotation + * for APIVersion, which can then be retrieved by a call to getVersion(). + * + * Hopefully the use of ClassReader in this implementation is not too expensive. I suspect it is not + * since EmptyVisitor is just a bag of empty methods so shouldn't cost too much. If it turns out to + * cost too much, we will want to implement a special purpose ClassReader that only reads out the + * APIVersion annotation I think. + */ +public class AnnotationReader extends EmptyVisitor { + + private boolean nextVisitIsVersion = false; + private boolean nextVisitIsMTime = false; + + private int version = -1; + private long mtime = -1; + + /** + * Reads the classfile bytecode in data and to extract the version. + * @throws IOException - if the classfile is malformed. + */ + public AnnotationReader(byte[] data) throws IOException { + ClassReader r; + try { + r = new ClassReader(data); + } catch (ArrayIndexOutOfBoundsException e) { + IOException ioe = new IOException("Malformed bytecode: not enough data"); + ioe.initCause(e);// IOException didn't grow a constructor that could take a cause till + // 1.6, so do it the old fashioned way + throw ioe; + } + r.accept(this, 0); + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + nextVisitIsVersion = desc.equals("Lorg/python/compiler/APIVersion;"); + nextVisitIsMTime = desc.equals("Lorg/python/compiler/MTime;"); + return this; + } + + public void visit(String name, Object value) { + if (nextVisitIsVersion) { + version = (Integer)value; + nextVisitIsVersion = false; + } else if (nextVisitIsMTime) { + mtime = (Long)value; + nextVisitIsVersion = false; + } + } + + public int getVersion() { + return version; + } + + public long getMTime() { + return mtime; + } +} Modified: trunk/jython/src/org/python/core/imp.java =================================================================== --- trunk/jython/src/org/python/core/imp.java 2009-03-05 22:07:35 UTC (rev 6070) +++ trunk/jython/src/org/python/core/imp.java 2009-03-06 04:15:54 UTC (rev 6071) @@ -22,6 +22,8 @@ public static final int APIVersion = 17; + public static final int NO_MTIME = -1; + // This should change to 0 for Python 2.7 and 3.0 see PEP 328 public static final int DEFAULT_LEVEL = -1; @@ -85,12 +87,17 @@ throw Py.IOError(ioe); } } + static PyObject createFromPyClass(String name, InputStream fp, boolean testing, + String sourceName, String compiledName) { + return createFromPyClass(name, fp, testing, sourceName, compiledName, NO_MTIME); + } + static PyObject createFromPyClass(String name, InputStream fp, boolean testing, - String sourceName, String compiledName) { + String sourceName, String compiledName, long mtime) { byte[] data = null; try { - data = readCode(name, fp, testing); + data = readCode(name, fp, testing, mtime); } catch (IOException ioe) { if (!testing) { throw Py.ImportError(ioe.getMessage() + "[name=" + name + ", source=" + sourceName @@ -118,9 +125,13 @@ } public static byte[] readCode(String name, InputStream fp, boolean testing) throws IOException { + return readCode(name, fp, testing, NO_MTIME); + } + + public static byte[] readCode(String name, InputStream fp, boolean testing, long mtime) throws IOException { byte[] data = readBytes(fp); int api; - APIReader ar = new APIReader(data); + AnnotationReader ar = new AnnotationReader(data); api = ar.getVersion(); if (api != APIVersion) { if (testing) { @@ -130,6 +141,12 @@ + APIVersion + ") in: " + name); } } + if (testing && mtime != NO_MTIME) { + long time = ar.getMTime(); + if (mtime != time) { + return null; + } + } return data; } @@ -146,7 +163,8 @@ if (sourceFilename == null) { sourceFilename = file.toString(); } - return compileSource(name, makeStream(file), sourceFilename); + long mtime = file.lastModified(); + return compileSource(name, makeStream(file), sourceFilename, mtime); } public static String makeCompiledFilename(String filename) { @@ -199,6 +217,10 @@ } public static byte[] compileSource(String name, InputStream fp, String filename) { + return compileSource(name, fp, filename, NO_MTIME); + } + + public static byte[] compileSource(String name, InputStream fp, String filename, long mtime) { ByteArrayOutputStream ofp = new ByteArrayOutputStream(); try { if(filename == null) { @@ -210,7 +232,7 @@ } finally { fp.close(); } - Module.compile(node, ofp, name + "$py", filename, true, false, null); + Module.compile(node, ofp, name + "$py", filename, true, false, null, mtime); return ofp.toByteArray(); } catch(Throwable t) { throw ParserFacade.fixParseError(null, t, filename); @@ -218,12 +240,17 @@ } public static PyObject createFromSource(String name, InputStream fp, String filename) { - return createFromSource(name, fp, filename, null); + return createFromSource(name, fp, filename, null, NO_MTIME); } public static PyObject createFromSource(String name, InputStream fp, String filename, String outFilename) { - byte[] bytes = compileSource(name, fp, filename); + return createFromSource(name, fp, filename, outFilename, NO_MTIME); + } + + public static PyObject createFromSource(String name, InputStream fp, + String filename, String outFilename, long mtime) { + byte[] bytes = compileSource(name, fp, filename, mtime); outFilename = cacheCompiledSource(filename, outFilename, bytes); Py.writeComment(IMPORT_LOG, "'" + name + "' as " + filename); @@ -433,20 +460,20 @@ } if (sourceFile.isFile() && caseok(sourceFile, sourceName)) { + long pyTime = sourceFile.lastModified(); if (compiledFile.isFile() && caseok(compiledFile, compiledName)) { Py.writeDebug(IMPORT_LOG, "trying precompiled " + compiledFile.getPath()); - long pyTime = sourceFile.lastModified(); long classTime = compiledFile.lastModified(); if (classTime >= pyTime) { PyObject ret = createFromPyClass(modName, makeStream(compiledFile), true, - displaySourceName, displayCompiledName); + displaySourceName, displayCompiledName, pyTime); if (ret != null) { return ret; } } } return createFromSource(modName, makeStream(sourceFile), displaySourceName, - compiledFile.getPath()); + compiledFile.getPath(), pyTime); } // If no source, try loading precompiled Modified: trunk/jython/src/org/python/modules/imp.java =================================================================== --- trunk/jython/src/org/python/modules/imp.java 2009-03-05 22:07:35 UTC (rev 6070) +++ trunk/jython/src/org/python/modules/imp.java 2009-03-06 04:15:54 UTC (rev 6071) @@ -207,10 +207,18 @@ } else if (name.equals("__init__")) { name = new File(sys.getCurrentWorkingDir()).getName(); } + + File fp = new File(resolvedFilename); + long mtime = -1; + if (fp.isFile()) { + mtime = fp.lastModified(); + } + mod = org.python.core.imp.createFromSource(name.intern(), (InputStream)o, filename.toString(), - compiledName); + compiledName, + mtime); break; case PY_COMPILED: compiledName = filename.toString(); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |