Development system with improved methods for recompiling dependent code modules5978585Abstract A development system providing methodology for improving system performance by decreasing recompilation of dependent source modules is described. During operation, the system keeps track of several pieces of information. From the outset, the system has kept track of the timestamps of sources (e.g., A.java file in a Java system) and compiled files thereof (e.g., A.class class file in a Java system). Examination of the timestamps leads to detection of those files which have been modified. The system will recompile A.java in the following circumstances: (1) A.class is not found, (2) A.java has a different timestamp, or (3) A.class has a different timestamp. Otherwise, the system does not invoke a recompile. If a recompilation in not required, the system then looks to the imports for determining whether the imports are consistent. Here, the system checks the timestamps of imports, possibly recompiling one or more of the modules providing the imports. If the imports have been recompiled, then the system must check the root module for determining whether it remains consistent with the imports--that is, whether A is still consistent with its imports. If not, the system recompiles the source file (e.g., A.java). Claims What is claimed is: Description COPYRIGHT NOTICE
______________________________________
class name {
. . .
}
______________________________________
The keyword class begins the class definition for a class named name. The variables and methods of the class are embraced by the curly brackets that begin and end the class definition block. A very simple "Point" class, for instance, may be declared as follows.
______________________________________
class Point {
public double x; /* instance variable */
public double y; /* instance variable */
______________________________________
This declaration serves as a template from which "Point" objects can be instantiated. Actual instantiation of an object occurs in a manner similar to that found in the C++ programming language. The data associated with a class or object is stored in variables; the behavior associated with a class or object is implemented with methods. Methods are similar to the functions or procedures in procedural languages such as C. For example, a variable which refers to a "Point" object can be declared as follows. Point myPoint; An instance of a point object is allocated as follows. myPoint=new Point(); Here, one can now access variables of the "Point" object, using familiar "dot" notation for referring to the names of the variables. myPoint.x=10; myPoint.y=20; Objects communicate by sending messages to each other. A recipient object responds to a message by selecting a particular method to execute. If one object wants another object to do some work on its behalf, for instance, the first object sends a message to the second object. The second object, in response, invokes the method which is appropriate for processing the message. The methods themselves, therefore, define the behavior of objects instantiated from a class. In particular, it is an object's methods which manipulate the object's data--its instance variables. The entry point of every Java application is its main method. When the user runs an application with the Java interpreter, he or she specifies the name of the class which is desired to be run. In response, the Java interpreter invokes the main method defined within that class. The main method controls the flow of the program, including allocating whatever resources are needed and invoking any other methods that provide the functionality for the application. Every Java application must contain a main method with the following prototype or signature (i.e., modifiers, name, and parameters). public static void main(String[ ] args) The method signature for the main method contains three modifiers: public, static, and void. The public modifier indicates that the main method can be called by any object. The static modifier indicates that the main method is a class method. The void modifier indicates that the main method returns no value. The main method in the Java language is similar to the main function in C and C++. When the Java interpreter executes an application (by being invoked upon the application's controlling class), it starts by calling the class's main method. The main method then calls all the other methods required to run your application. The Java Language is well documented in the technical, trade, and patent literature; see e.g., Gosling, J. et al., The Java Language Environment: A White Paper, Sun Microsystems Computer Company, October 1995. For a description of runtime execution of Java modules (including dynamic linking), see e.g., James Gosling, Bill Joy, and Guy Steel, The Java Language Specification, Chapter 12: Execution (particularly 12.2 Loading of Classes and Interfaces, and Linking of Classes and Interfaces), Addison Wesley, 1996. The disclosures of each of the foregoing are hereby incorporated by reference. D. Development System Shown in further detail in FIG. 2A, a Java development system 200 of the present invention includes a client 210 which employs a virtual machine 220 for executing programs. In particular, the client 210 executes a "compiled" (i.e., bytecode or pseudo-compiled) Java program 240, which has been created by compiling a Java source code program or script 205 with a Java compiler 230. Here, the Java source code program 205 is an application program written in the Java programming language; the pseudo-compiled program 240, on the other hand, comprises the bytecode emitted by the compiler 230. The virtual machine 220 includes a runtime interpreter for interpreting the Java bytecode program 240. During operation, the client 210 simply requests the virtual machine 220 to execute a particular Java compiled program. As shown in FIG. 2B, the virtual machine 220 comprises a class loader 221, a bytecode verifier 222, a bytecode interpreter 223, and runtime support libraries 224. The class loader 221 is responsible for unpacking the class file which has been requested by a client. Specifically, the loader will unpack different sections of a file and instantiate in-memory corresponding data structures. The class loader will invoke itself recursively for loading any superclasses of the current class which is being unpacked. The bytecode verifier 222 verifies the bytecode as follows. First, it checks whether the class has the correct access level. Since the class will access other classes for invoking their methods, the bytecode verifier must confirms that appropriate access is in place. Additionally, the bytecode verifier confirms that the bytecode which comprises the methods is not itself corrupt. In this regard, the bytecode verifier confirms that the bytecode does not change the state of the virtual machine (e.g., by manipulating pointers). Once the bytecode has been verified, a "class initializer" method is executed. It serves, in effect, as a constructor for the class. The initializer is not a constructor in the sense that it is used to construct an instance of a class--an object. The class initializer, in contrast, initializes the static variables of the class. These comprise the variables which are present only once (i.e., only one instance), for all objects of the class. Runtime support libraries 224 comprise functions (typically, written in C) which provide runtime support to the virtual machine, including memory management, synchronization, type checking, and interface invocation. At the client, runtime support libraries 224 are included as part of the virtual machine; the libraries are not downloaded with the Java application. The bytecode which is executed repeatedly calls into the runtime support libraries 224, for invoking various Java runtime functions. D. General development interface The present invention is embodied in a component-based, rapid application development (RAD) Java environment. Many of the traditional requirements of programming, particularly for GUI applications, are handled for the programmer automatically by the system. FIG. 3 illustrates a preferred interface of a Java-based visual development or programming environment 360 provided by the system. As shown, the programming environment 360 comprises a main window 361, a form 371, a editor windows (e.g., query descriptor editor window 381), and an object manager or "inspector" window 391 (with object explorer 397). The main window 361 itself comprises main menu 362, tool bar buttons 363, and component palette 364. Main menu 362 lists user-selectable commands, in a conventional manner. For instance, the main menu invokes File, Edit, View submenus, and the like. Each submenu lists particular choices which the user can select. Working in conjunction with the main menu, toolbar 363 provides the user with shortcuts to the most common commands from the main menu. The toolbar is configurable by the user for including icons for most of the menu commands. Each editor, such as editor 381, is a full-featured editor that provides access to text in a given application project, such as Java code or SQL statements. Forms, such as form 371, are the focal point of nearly every application which one develops in the environment. In typical operation, the user employs the form like a canvas, placing and arranging "components" on it to design the parts of one's user interface. The components themselves are the basic building blocks of applications developed within the environment. Available components appear on the component palette 364, which is displayed as part of the main window 361. The form can be thought of as a component that contains other components. One form serves as the main form for the application; its components interact with other forms and their components to create the interface for an application under development. In this manner, the main form serves as the main interface for an application, while other forms typically serve as dialog boxes, data entry screens, and the like. During "design" mode operation of the system, the user can change the properties of the form, including resizing the form and moving it anywhere on screen. The form itself includes standard features such as a control menu, minimize and maximize buttons, title bar, and resizeable borders. The user can change these features, as well as other "properties" of the form, by using the object inspector window 391 to edit the form during design time. Thus, properties define a component's appearance and behavior. Components are the elements which a user employs to build his or her applications. They include all of the visible parts of an application, such as dialog boxes and buttons, as well as those which are not visible while the application is running (e.g., system timers). In the programming environment 360, components are grouped functionally on different pages of the component palette 364. Each functional group is identified by a tab member, which includes a label indicating the particular nature of the group. For example, components that represent the Microsoft Windows common dialog boxes are grouped on the "Dialogs" page of the palette. The palette can incorporate user-created custom controls, which the user installs onto the palette. Additionally, the user can install third-party components. The object inspector window 391 enables the user to easily customize the way a component appears and behaves in the application under development. The inspector 391 comprises a properties page 394 having object property fields (e.g., field 392); an events page 393 displays, in a similar manner, object method fields. The object property field 392 shows the type and value of the currently-selected property. The properties page 394, therefore, serves to list the attributes of a component placed on a form (or the form itself) which can be customized. The events page, on the other hand, lists "event handlers" for a particular component. Event handlers are specialized procedures which may include user-provided program code. The following description will focus on those features of the development system 200 which are helpful for understanding methodology of the present invention providing improved system performance by reducing recompilation of source modules during development of a program. Methodology for Improving Recompilation in a Java-Based Environment A. Overview Certain modifications should not trigger a recompilation of the client modules. In accordance with the present invention, the following modifications do not trigger a recompilation of the client modules: (1) comment editing, (2) implementation change with no signature change, and (3) adding new fields or new (non-overloaded) methods to a class. When a Java class is extended (i.e., in a child class) or its implementation changes, on the other hand, the system recompiles the affected class. Note, however, that this does not require recompilation of those classes which are just importing the class. Consider a change to a source module consisting of only changing the implementation of a particular method--that is, a change which does not affect the methods interface or signature. In such a case, there is no reason to recompile the clients of that method since the change does not affect them. As another example, consider a modification which adds new methods to a class, such as a Java class. The clients which do not see these new methods or fields have no reason to be recompiled, unless such clients derive new classes from the affected class (to avoid name collision). In the currently-preferred embodiment, the system does not attempt to perform incremental recompilation of a given source module. With present-day hardware, together with the structure of Java code itself, recompilation of a given source module is fast, generally on the order of less than one second. Accordingly, the design complexity and runtime overhead which would be incurred for incrementally compiling within a given module does not justify the effort. Instead, the focus is on minimizing the various modules which need to be recompiled, as a typical program generally comprises a multitude of such modules. Any given source file generally does not exist in isolation, however. Java applications of even modest complexity require dozens or even hundreds of different source modules, all which must exist within a hierarchy (dependency) framework. B. Method operation During general operation, the system keeps track of several pieces of information. From the outset, the system has kept track of the timestamps of sources (e.g., A.java file in a Java system) and compiled files thereof (e.g,. A.class class file in a Java system). Examination of the timestamps leads to detection of those files which have been modified. The system will recompile A.java in the following circumstances: (1) A.class is not found, (2) A.java has a different timestamp, or (3) A.class has a different timestamp. Otherwise, the system does not invoke a recompile. If a recompilation in not required, the system then looks to the imports for determining whether the imports are consistent. Here, the system checks the timestamps of imports, possibly recompiling one or more of the modules providing the imports. If the imports have been recompiled, then the system must check the root module for determining whether it remains consistent with the imports--that is, whether A is still consistent with its imports. If not, the system recompiles A.java. FIGS. 4A-C presents a detailed flowchart illustrating a dependency checking method 400 constructed in accordance with the present invention. The method begins with examination of the root (main) source file, as shown at step 401. At steps 402-404, the method will apply a series of tests against the module (and dependent modules during subsequent iterations of this recursive method) for determining whether recompilation is necessary. Specifically, at step 402, the source file's timestamp is examined to determine whether it has changed in a conventional manner, for determining files which have changed (i.e., have been edited by a user) since the last compilation. If the source itself has changed, the system recompiles the class, as indicated by the jump to step 411. If the time/date has not changed, on the other hand, the method examines the compiled file (e.g., Java class file) corresponding to the source, at step 403. If no such file is found, then the system will proceed to compile the source for generating an initial class file, as shown from the jump from step 403 ("false" case) to step 411. If the class file is already present but its state has changed apart from recompilation of the source (e.g., someone has copied a class file to the location), tested as step 404, the system recompiles, for ensuring consistency between the changed class file and the underlying source. During the recompilation at step 411, the compiler calls back into the dependency checker to determine whether each import must also be recompiled or, instead, the existing class file is used to compile the importing module. If nothing has changed, the system can proceed directly to step 413, without compiling the current module, for examining all the imports for the class. Specifically, the method will recursively apply the just-described technique for the imports. A loop is established at step 413 to loop through all imports, thereby recursively invoking the method, beginning at step 402. After conclusion of this phase, one or more imports might have required recompilation (tested at step 414). In such a case, the system still must check that the root class remains consistent with the imports (tested at step 415), once the imports have been recompiled. Specifically at this point, if the root class extends from those imports and the imports have really changed (i.e., a change in the sum of all exported signatures), the system must then recompile, as indicated by the jump from step 415 to step 405. Otherwise, the system continues on to step 421 for applying fine-grain consistency checking. With fine-grain dependency checking, the system examines dependencies at the object level. Consider, for instance, the following example.
______________________________________
A.java C.java
______________________________________
. . . C.x public static int x;
new .fwdarw. public static int
______________________________________
y;
Inserting y in module C should not trigger a recompilation of A. Changing type or accessibility of x should trigger a recompilation of A, however. A.class does not contain any information about the accessibility of C.x, just the name and type of C.x. If the static modifier in the declaration of x is removed, the system must detect whether A is inconsistent. It must be recompiled (and an error will be emitted). For each class, the system keeps a list of exported names--symbols available for use by other modules--together with a fingerprint or signature. The signature is a hash code representing the type (or signature) and accessibility of each name. For a method, the signature does not reflect the implementation of the method but, instead, the prototype signature (i.e., method name plus modifiers and parameter information) for the method. If the signature remains unchanged, modules which are dependent on that method need not be recompiled. Also, the system keeps a list of imported classes (with timestamps) and for each class, a list of imported names with fingerprints, representing the type and accessibility of the name at the time of the last compilation of A. In the currently-preferred embodiment, these lists of imported and exported names (with fingerprints and timestamps) for all the classes in the same package are stored together in one file per package. These dependency files are generated during the first build of the class and are updated as recompilations occur. The methodology of the present invention is applied to the root class and applied recursively to all imports of the root (without use of "make" files). In an exemplary embodiment, the signature is computed as a cyclic redundancy checksum (CRC) value. As with any computed checksum, the possibility of collision exits--that is, that two separate methods yield the same checksum value. In such a case, however, the collision (which is a rare event) can be dealt with at runtime, when the incompatibility will be detected by the runtime system. Upon encountering such an error, the system can at that time simply recompile. The system proceeds as follows. If a symbol in an imported class is available for use (i.e., exported from its own class) in the current module under examination (tested at step 422), the system computes a signature which specifies the symbol's object (or method) type (i.e., class from which it was created), including the accessibility of that object's fields, as shown at step 423. Otherwise (i.e., "false" at step 422), the method proceeds to step 425, for testing whether to loop on any remaining symbols. In Java, there are two basic types of symbols: fields and methods. Fields represent variables such as instance variables and static variables. Methods, on the other hand, represent the programmer-assigned name for each class method. At the point of performing consistency checking for each symbol, the system must discern that the symbol is in fact being used and, if used, whether it has changed. The class file, since it only defines the name of the symbol, does not provide this information itself (i.e., it only includes the name which is to be linked). For a method symbol, for instance, the computed signature includes information about the method's name and parameter information, together with a list of exceptions which can be thrown by the method. Based on this information, the system can compute a signature or fingerprint which fully characterizes each object or method. In this manner, when a file has changed, the system can examine signatures for determining whether individual object or methods have changed (i.e., an object's or method's fingerprint has changed since last compile). When a change is detected, tested at step 424, the method at that point can invoke the compiler for recompiling the current module (which is dependent on the changed symbol). The fine-grain consistency examination is continued for any remaining symbols, tested at step 425. If no imported symbols have changed and all symbols which are used from the imported classes have been examined, the method will conclude, as shown by the jump for the "false" case of step 425. With fine-grain dependency checking, therefore, the system has discerned not only that two class are dependent but, also, exactly which symbols used between the classes have changed. While the invention is described in some detail with specific reference to a single preferred embodiment and certain alternatives, there is no intent to limit the invention to that particular embodiment or those specific alternatives. Thus, the true scope of the present invention is not limited to any one of the foregoing exemplary embodiments but is instead defined by the appended claims.
|
Same subclass Same class Consider this |
||||||||||
