Using Caché Studio
Using Studio Source Control Hooks
[Back] [Next]

To place Caché code under source control, you need to connect Caché Studio to a third-party source control system. This appendix describes how to do this. It discusses the following topics:

To place a Caché development project under source control, do the following:
Caché Documents
A Caché document is a class definition, a routine, an include file, or a CSP file. Caché records information about each Caché document, such as whether it has changed since the last compilation. Your source control system treats each Caché document as a separate unit. The state of a document is shown by an icon in the document window.
In Caché, you work within one namespace at a time. The same is true for your source control system.
Tools for Managing Documents and Files
Caché provides the following tools for managing Caché documents and external files:
Deciding How to Map Internal and External Names
Each document has two names:
You will set up a bidirectional mapping between the internal names and the external names. In practice, deciding how to do this may be one of the most challenging parts of creating a source control interface. This mapping is customer-specific and should be considered carefully.
You want the source control tool to group similar items. For example, the sample uses the following directory structure:
For example, the external name for the class MyApp.Addresses.HomeAddress is C:\sources\cls\MyApp\Addresses\HomeAddress.xml.
This approach might be problematic if you had large numbers of routines. In such a case, you might prefer to group routines into subdirectories in some manner, perhaps by function.
Creating and Activating a Source Control Class
This section describes the basic requirements for creating and activating a source control class.
Extending Studio
Caché provides classes that you can use to add menu items to Studio. To add a source control menu to Studio, you would use either %Studio.Extension.Base or %Studio.SourceControl.Base.
Limit on how many menus you can add to Studio: You can add up two menus with 19 menu items each.
The %Studio.Extension.Base class provides the following methods, which all use the internal name of the Caché document:
Studio compiles processes in separate threads. If you set properties in %Studio.Extension.Base, they may not be accessible in subsequent calls, as they may be running in different object instances. Do not use a properties to pass information from MenuItem to OnBeforeCompile. Instead, use a temporary global.
The %Studio.SourceControl.Base class is a subclass of the preceding class. %Studio.SourceControl.Base provides the following additional elements:
To extend Studio, you define a new class that extends one of these classes. As you see in Activating a Source Control Class,” the Management Portal provides a way to indicate which extension class is currently active in a given namespace. If an extension class is active in a given namespace, and if that class defines an XDATA menu block, those menu items are added to Studio.
Creating a Source Control Class
To create a source control class, do the following:
  1. If you started with %Studio.Extension.Base, create an XDATA block named Menu in your subclass. (Copy and paste from %Studio.SourceControl.Base to start this.)
  2. Implement the methods of this class as needed: AddToSourceControl, CheckIn, CheckOut, and so on. These methods would typically do the following, at a minimum:
    The details depend upon the source control system. The sample demonstrates some useful techniques. See the section Sample Source Control Class in this book.
  3. Implement the GetStatus method of your source control class. This is required. You might also need to implement the IsInSourceControl method, if the default implementation is not suitable.
Activating a Source Control Class
To activate a source control class for a given namespace, do the following:
  1. Use the Management Portal to specify which extension class, if any, Studio should use for a given namespace. To specify the class to use:
    1. Navigate to the [Home] > [Configuration] > [Source Control Settings] page of the Management Portal. (Select System Administration > Configuration > Additional Settings > Source Control.)
    2. On the left, select the namespace to which this setting should apply.
    3. Select the name of the extension class to use (or select NONE) and select OK.
      This list includes all compiled subclasses of %Studio.Extension.Base.
  2. If Studio is currently open, close it and reopen it, or switch to another namespace and then switch back.
Accessing Your Source Control System
The API for your source control system provides methods or functions to perform source control activities such as checking files out. Your source control class will need to make the appropriate calls to this API, and the Caché server will need to be able to locate the shared library or other file that defines the API itself.
If the source control system provides a COM interface, you can generate a set of Caché wrapper classes that you can use to call methods in that interface. To do so, you use the Caché Activate Wizard in Studio. Given an interface and the name of the package to contain the classes, the wizard generates the classes. For information, see Using the Caché ActiveX Gateway.
Also, it is important to remember that Caché will execute the source control commands on the Caché server. This means that your XML files will be on the Caché server, and your file mapping must work on the operating system used on that server.
Example 1
For the following fragment, we have used the Caché Activate Wizard to generate wrapper methods for the API for VSS. Then we can include code like the following within your source control methods:
 do ..VSSFile.CheckIn(..VSSFile.LocalSpec,Description)
The details depend on the source control software, its API, and your needs.
Example 2
The following fragment uses a Windows command-line interface to check out a file. In this example, the source control system is Perforce:
/// Check this routine/class/csp file out of source control.
Method CheckOut(IntName As %String, Description As %String) As %Status
  Set file=..ExternalName(IntName)
  If file="" Quit $$$OK
 Set cmd="p4 edit """_file_""""

  #; execute the actual command
  Set sc=..RunCmd(cmd)
  If $$$ISERR(sc) Quit sc

  #; If the file still does not exist or
  #; if it is not writable then checkout failed
  If '##class(%File).Exists(file)||(##class(%File).ReadOnly(file)) {
    Quit $$$ERROR($$$GeneralError,
                  "Failure: '"_IntName_"' not writeable in file sys")

  #; make sure we have latest version
  Set sc=..OnBeforeLoad(IntName)
  If $$$ISERR(sc) Quit sc

  Quit sc
In this example, RunCmd is another method, which executes the given command and does some generic error checking. (RunCmd issues the OS command via the $ZF(-1) interface.)
Also, this CheckOut method calls the OnBeforeLoad method, which ensures that the Caché document and the external XML file are synchronized.
Sample Source Control Class
The SAMPLES namespace provides a sample source control class, Studio.SourceControl.Example. This section shows how this sample works. The following topics are discussed:
The class in your SAMPLES namespace could be slightly different from the examples shown here. In particular, some of the line breaks have been adjusted for readability in this document.
Studio.SourceControl.Example is a partial example that does not make any calls to a source control system. It simply maintains external XML files that such a system would use. Despite this simplification, however, the sample demonstrates all the following:
Note that the sample does not modify the read-write state of the external files; the source control system would be responsible for that. Also, the sample implements only the Check In and Check Out methods.
To try this example in the SAMPLES namespace, do the following:
  1. Use the Management Portal to enable this source control class (Studio.SourceControl.Example), as described earlier in Activating a Source Control Class.”
  2. In the Studio Workspace window, double-click a Caché document. Notice a message like the following in the Output window:
    File C:\sources\cls\User\LotteryUser.xml not found, skipping import
  3. Edit the document (for example by adding a comment).
  4. Select File —> Save. You will see a message like the following in the Output window:
    Exported 'User.LotteryActivity.CLS' to file
    At this step, you have implicitly added the Caché document to the source control system.
  5. Try to make another edit. The Studio displays a dialog box that asks if you want to check the file out. Select No. Notice that the Caché document remains read-only.
  6. Select Source Control —> Check Out and then select Yes. You can now edit the Caché document.
  7. Select Source Control —> Check In and then select Yes. The Caché document is now read-only again.
Other menu items on the Source Control menu do nothing, because the sample implements only the Check In and Check Out methods.
The Studio.SourceControl.Example sample uses a global to record any needed persistent information. Methods in this class maintain and use the ^MySourceControl global, which has the following structure:
Node Contents
^MySourceControl("base") The absolute path of the directory that will store the XML files. The default is C:\sources\
^MySourceControl(0,IntName), where IntName is the internal name of a Caché file The date and time when the corresponding external file was last modified
^MySourceControl(1,IntName) The date and time when this Caché document was last modified
^MySourceControl(2,IntName) The name of the user who has this Caché document checked out, if any
This global is purely a sample and is used only by this class.
Determining the External Names
If you enable the Studio.SourceControl.Example class, it maintains external XML files that correspond to any Caché document that you load or create. It writes these files to the directory C:\sources\ by default, as described in Deciding How to Map Internal and External Names.” For example, the external name for the class MyApp.Addresses.HomeAddress is C:\sources\cls\MyApp\Addresses\HomeAddress.xml.
Within the sample, the ExternalName method determines the external file name for any Caché document. This method is as follows:
Method ExternalName(IntName As %String) As %String
 Set name=$piece(IntName,".",1,$length(IntName,".")-1)
 Set ext=$zconvert($piece(IntName,".",$length(IntName,".")),"l")
 If name="" Quit ""
 Set filename=ext_"\"_$translate(name,".","\")_".xml"
 Quit $get(^MySourceControl("base"),"C:\sources\")_filename
The sample is suitable only for Windows, of course. The implementation of this method would need to be different on UNIX® or OpenVMS.
Synchronizing the Caché Document and the External File
Two methods are responsible for ensuring that the Caché document and the corresponding XML file are kept synchronized with each other:
In the sample, the OnBeforeLoad method is as follows:
Method OnBeforeLoad(IntName As %String) As %Status
 Set filename=..ExternalName(IntName)
 If filename="" Quit $$$OK

 #; If no file then skip the import
 If '##class(%File).Exists(filename) {
     Write !,"File ",filename," not found, skipping import"
     Quit $$$OK

 #; If the timestamp on the file is the same as the last time
 #; it was imported, then do nothing
 If ##class(%File).GetFileDateModified(filename)=
                   $get(^MySourceControl(0,IntName)) {
     Quit $$$OK

 #; Call the function to do the load
 Set sc=$system.OBJ.Load(filename,"-l-d")
 If $$$ISOK(sc) {
 Write !,"Imported '",IntName,"' from file '",filename,"'"
 Set ^MySourceControl(0,IntName)=##class(%File).GetFileDateModified(filename)
 Set ^MySourceControl(1,IntName)=##class(%RoutineMgr).TS(IntName)
 } Else {
 Do $SYSTEM.Status.DecomposeStatus(sc,.errors,"d")
 Quit sc
The OnAfterSave method is analogous, as you can see in the sample itself.
Not only does Studio call these methods automatically as noted above, we will call these methods whenever we need to ensure that the Caché document and the external document are synchronized.
Controlling the Status of the Caché Document
The GetStatus method of your source control class is responsible for returning information about the status of the given Caché document. This method has the following signature:
Method GetStatus(IntName As %String,
                 ByRef IsInSourceControl As %Boolean,
                 ByRef Editable As %Boolean,
                 ByRef IsCheckedOut As %Boolean,
                 ByRef UserCheckedOut As %String) As %Status
Studio calls this method at various times when you work with a Caché document. It uses this method to determine if a Caché document is read-only, for example. When you implement a source control class, you must implement this method appropriately.
In the sample, this method is implemented as follows:
Method GetStatus(IntName As %String,
                 ByRef IsInSourceControl As %Boolean,
                 ByRef Editable As %Boolean,
                 ByRef IsCheckedOut As %Boolean,
                 ByRef UserCheckedOut As %String) As %Status
 Set Editable=0,IsCheckedOut=0,UserCheckedOut=""
 Set filename=..ExternalName(IntName)
 Set IsInSourceControl=(filename'=""&&(##class(%document).Exists(filename)))
 If 'IsInSourceControl Set Editable=1 Quit $$$OK

 If $data(^MySourceControl(2,IntName))
 {Set IsCheckedOut=1
 Set UserCheckedOut=$listget(^MySourceControl(2,IntName))}

 If IsCheckedOut,UserCheckedOut=..Username Set Editable=1
 Quit ..OnBeforeLoad(IntName)
Here is how this method works:
  1. It first initializes all the arguments that it returns by reference.
  2. The method then checks to see whether the external document exists yet; if it does not, the Caché document should be editable.
  3. The method then checks the ^MySourceControl global to see if anyone has checked this document out. If so, and if that user is the current user, the document is editable. If the document is checked out to a different user, it is uneditable to the current user.
  4. Finally, the method calls the OnBeforeLoad method, which was described earlier in this document. This step ensures that the Caché document and the external XML file are synchronized and that the relevant nodes of the ^MySourceControl global get set.
Source Control Actions
The sample implements methods for the two most basic source actions: check in and check out.
The CheckIn method is as follows:
Method CheckIn(IntName As %String, Description As %String) As %Status
 #; See if we have it checked out
 If '$data(^MySourceControl(2,IntName)) {
   Quit $$$ERROR($$$GeneralError,"You cannot check in an item
                                  you have not checked out")
 If $listget(^MySourceControl(2,IntName))'=..Username {
   Quit $$$ERROR($$$GeneralError,"User '"_
         $listget(^MySourceControl(2,IntName))_"'has this item checked out")

 #; Write out the latest version
 Set sc=..OnAfterSave(IntName)
 If $$$ISERR(sc) Quit sc

 #; Remove the global to show that we have checked it in
 Kill ^MySourceControl(2,IntName)
 Quit $$$OK
The CheckOut method is analogous.
These methods could be extended to include the appropriate calls to a third-party source control system.
Other Details
By default, the method IsInSourceControl calls the GetStatus method and gets the needed information from there.
In the sample, the method IsInSourceControl returns true for all internal names; recall that all documents are assumed to be under source control.
A class definition can be changed when you compile it, because compilation can update the storage information. Accordingly, the sample implements the OnAfterCompile method. This method just calls the OnAfterSave method, because it needs the same logic as that method provides; specifically, it needs to check whether the Caché document has changed and if so, save the XML file again.
We do not recommend using process private globals in source control hooks because processes may not run in the same thread. For more information, see the section Routine Compilation Now Uses Multiple Jobs in the Caché 2012.1 Upgrade Checklist