Planners 2.0: Backward Compatibility
Thursday, March 17, 2005 11:10 AM
Well, Planners 2.0 is out, and although I didn't get much (or any) rest, the pressure is a little down. Even after the product has been signed off, there's a lot to be done: setting up order pages, uploading a trial version, registering the new version with some download sites, updating the product web pages, and so on.
As promised, I'll try and write a little about the process of building and releasing this version, in hope that this would help aspiring component developers prepare for what they're about to face. In this post, I'll focus on backward compatibility.
The three main aspects of backward compatibility, in any software product, are data, interface, and installation. The actual implications of these aspects depend on the type of product. In the case of a component library, these are:
- Data - code built with a previous version should be compatible with the current version. It should compile, and produce the same results at run-time. While bug-fixes may change the result of some code, known workarounds should still work.
- Interface - the programming interfaces should remain unmodified. Extensions to the interfaces, such as new components, methods, and properties, should follow the same design principles and models of the previous version.
- Installation - the installation should be aware of the previous version, and take steps to prevent incompatibilities.
The difference between data and interface compatibility is obvious in applications: data compatibility means the ability to view and edit data produced by a previous version, and interface compatibility means the user interface is similar enough to the previous version's interface so users don't have to learn everything from scratch. For code libraries, the line is somewhat blurry.
Data compatibility in Planners 2.0 is implemented at the source code level: application source code written for Planners 1.0 should compile without errors with Planners 2.0.
The level of code compatibility often depends on the scope of the release. For example, when Borland releases a new version of Delphi, the expected compatibility is at the source code level: code written for one version of Delphi is expected to work with later versions. When Borland releases a patch, the expected compatibility is at the binary level: units compiled with a specific version of Delphi are expected to work with the patched version. Borland tries to avoid DCU-breaking changes in patches so that component vendors won't have to distribute multiple versions of their products with every Delphi patch. Unfortunately, this sort of compatibility is not always possible: Delphi 8 incorrectly relied on internal data in the .NET Framework. When Microsoft released Service Pack 1 for .NET 1.1, it broke Delphi 8, and Borland had to release a DCU-breaking update.
In fact, Planners 2.0 has already benefited from Borland's patch policy. The Delphi 2005 packages and units have been compiled with Delphi 2005 Update 1, but are compatible with both Update 2 and with Delphi 2005 without any patches.
In Planners 2.0, we support application source code level compatibility, and - with a couple of exceptions - source code compatibility. This means that any code written using Planners 1.0 and using public or published properties, events, and methods will compile unmodified with Planners 2.0. Application code will not break.
Technically speaking, Planners 2.0 does introduce two breaking changes at the source code level: two properties (AutoSizeRows and RowCount) have been made read-only. These properties should never have been made writable, and modifying them in application code would produce unexpected results. Inherited components should be able to modify these properties, so we've added a couple of protected methods for this purpose. While strictly speaking this is a breaking change, the fact is that there probably isn't any code out there modifying these properties. It just wouldn't work.
While code compatibility can be preserved by not breaking existing code and by regression testing, interface compatibility is more of a design principle. When adding new components, properties, methods and events, we maintained interface compatibility by following several guidelines:
- Naming conventions - new elements follow the naming conventions used in the previous version.
- Element re-use - whenever possible, existing elements were used. For example, new components derive from the same base components included in the previous version.
- Design consistency - use the same design principles as the previous version. For example, in Planners 1.0, all controls derive from a single base component, TSPBasePlanner, which implements common logic. Derived controls override certain methods to implement their own custom behavior, such as drawing. Printing support in Planners 2.0 was implemented by adding a new method, Print, to TSPBasePlanner, which calls protected methods of derived classes to do the actual drawing.
- Extend, don't replace - whenever possible, new features were written as extensions to existing components, rather than creating new components.
Maintaining installation compatibility is always tricky. Application installations can usually replace the previous version, but are expected to keep data and settings from the previous version. Development tools and libraries, on the other hand, are expected to support multiple versions on the same machine. Developers usually have to maintain code written using previous versions of the tool, so multiple versions have to leave peacefully side-by-side. There are restrictions, though. In the case of Delphi components, for example, two sets of components with the same name cannot be loaded by the IDE (or, in Delphi 2005, by an IDE personality) at the same time. There are two ways of handling this restriction:
- A component package could rename all components with each version.
- Registration of components from a previous version could be replaced with the new version.
The former method is almost never used, as it would break code compatibility. Planners 2.0 uses the latter method, by removing the Planners 1.0 packages from Delphi and C++Builder's list of packages, and registering the new packages instead.
The installation program has to take any conflicting version into account. There are currently four conflicting versions of Planners out there, and the installation program has to know about all of them. There's Planners 1.0, Planners 2.0, the Planners 1.0 trial, and the Planners 2.0 trial. The Planners 2.0 installer performs three steps when registering the components: first, it registers the new components; then it removes registrations for Planners 1.0 components; finally, when installing the full product, it removes the registration for the Planners 2.0 trial version.
At no point during the installation is any previous version of Planners removed. No files are deleted, and no directories are removed from the system and IDE's search paths. Planners 2.0 installs to a different directory than Planners 1.0, so no files are overwritten.
While the unit names for Planners 2.0 have to remain the same as the 1.0 unit names (again, code compatibility), the package names have changed. The Planners package names indicate both the product version and the version of the supported IDE. Here are a few names for the Planners design-time package:
- DclSPPlan9020.bpl - Planners 2.0 on Delphi 2005.
- DclSPPlan7020.bpl - Planners 2.0 on Delphi 7.
- DclSPPlan7010.bpl - Planners 1.0 on Delphi 7.
... and so on.
An interesting problem was raised when determining names for the Planners assemblies. Planners supports VCL for .NET in addition to the VCL on Win32. While it's possible to use any file name for a .NET assembly, the common convention is to use names that are similar (if not identical) to the namespaces supported by the assembly. Further complications arose because of the need to support both Delphi 8 and Delphi 2005. Delphi's namespace model has changed in Delphi 2005: in Delphi 8, the namespace was identical to the unit name, while in Delphi 2005, the namespace is the full unit name minus the last identifier, separated by dots. The Delphi assemblies, such as Borland.Delphi.dll, have the same file name for both Delphi 8 and Delphi 2005, but are in fact different. We also had to maintain compatibility with the Delphi 8 assembly names in Planners 1.0.
The issue was resolved by using strong-named assemblies. Planners 2.0 includes three assembly files:
There are, in fact, two versions of each assembly: one for Delphi 8 and one for Delphi 2005. Both versions have the same file name, but are strong-named, so they have a different assembly name. The assembly names for ShorterPath.Planners.Vcl.dll, for example, are:
ShorterPath.Planners.Vcl, Version=126.96.36.199, Culture=neutral, PublicKeyToken=235f6e5b184e6f81
ShorterPath.Planners.Vcl, Version=188.8.131.52, Culture=neutral, PublicKeyToken=235f6e5b184e6f81
The difference is in the assembly version. We used the major version number, as well as the release and build numbers, to indicate the version of Planners. The minor version number indicates the version of Delphi used by the assembly. Another advantage of strong-naming our assemblies is the ability to install them into the Global Assembly Cache.
We took similar measures with the Planners help files. The Planners 2.0 help files have a different name than the Planners 1.0 help files. The installation program installs the new help files but does not remove the old help files. Another feature we added to the installer was the ability to select which versions of the Planners packages and help files are installed. We've even added an option to completely skip automatic registration of components and help files, allowing users to manually register only the items they want.
More to come
I'll be writing more about the issues we faced with this release and the solutions we chose. If you want to know more about the process, or have specific questions, leave a comment here (comments can only be added by viewing this page in a browser).