Previous section   Next section

Using Studio Source Control Hooks

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

Overview

To place a Caché development project under source control, do the following:
  • Represent units of code as XML files and write them to a document system. Caché considers a class definition, a routine, or a CSP file as a single unit called a document.
  • Place the XML files under source control.
  • Ensure that the XML files are kept synchronized with the Caché documents (and vice versa), and make sure that both are kept in the appropriate read-write state.
  • Ensure that you can perform source control activities from within Studio.
  • Ensure that Studio always has the same information that the source control system has as to the status of a document: whether the document has been checked out, and, if checked out, by whom.

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:
  • The %Studio.Extension.Base and %Studio.SourceControl.Base classes provide methods for basic document management. You can extend one of these classes to add menu items that act on Caché documents. These classes are discussed in the section “Creating and Activating a Source Control Class” in this book.
  • The $system.OBJ.Export function exports a Caché document to an XML file in the external document system. This XML file contains all the information needed to reconstruct the Caché document. For example, for a class document, the corresponding XML file is a text representation of the entire class definition, which includes all code, properties, comments, and so on.
  • The $system.OBJ.Load function loads an external XML file and overwrites the corresponding Caché document, if one exists.
  • The %RoutineMgr.TS class method returns the timestamp for a Caché document. This method also returns, by reference, the compile time for the Caché document, as the second argument.

Deciding How to Map Internal and External Names

Each document has two names:
  • An internal name, the name you use in the Open dialog box in Studio.
  • An external name, which should be the complete external file name, including path. Because of differences between supported Caché platforms, it is not possible to provide a meaningful default.
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:
  • Class files are in the cls subdirectory, which contains subdirectories corresponding to the package hierarchy of the classes.
  • .INT routines are in the int subdirectory.
  • .MAC routines are in the mac subdirectory.
  • CSP files are in the csp subdirectory, which contains subdirectories corresponding to the package hierarchy of the CSP files.
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.
Note:
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:
  • Empty Login and Logout methods that you can implement as needed. The variable $username records the current user. (In the Login method, the Username argument is provided for backward compatibility; it is recommended that you use the variable $username instead.)
  • Basic methods to indicate the status of a given Caché document: GetStatus, and IsInSourceControl. Implement these methods as needed.
  • Callback methods that are executed when a user in Studio performs some action on a Caché document. These methods include OnBeforeLoad, OnAfterLoad, OnBeforeCompile, OnAfterCompile, ItemIconState, and so on.
Note:
Studio class compilation can use multiple processes. Therefore, don't use properties of %Studio.Extension.Base, 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:
  • An XDATA block named
    Menu
    that defines an additional menu for Studio: Source Control. By default, this menu contains the menu items Check In, Check Out, Undo Check Out, Get Latest, and Add To Source Control. This XDATA block also defines additional menu items for the context menu in Studio.
    All these menu items call methods also defined in this class.
  • Methods named CheckIn, CheckOut, UndoCheckOut, GetLatest, and AddToSourceControl, which do nothing by default.
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:
    • If appropriate, import or export the Caché document to XML.
    • Call the appropriate function or method of your source control software, to act on the XML file.
    • Update internal information in Caché about the status of the given file.
    • Control whether the Caché document is editable.
    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 System Administration > Configuration > Additional Settings > Source Control on the Management Portal.
    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:
Note:
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.

Introduction

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:
  • Establishing and maintaining a relationship between each Caché document and a corresponding external file.
  • Keeping Caché up-to-date with the status of each file.
  • Appropriately controlling the read-write state of each Caché document.
  • Defining methods to check files in and out.
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
    'C:\sources\cls\User\LotteryActivity.xml'
    
    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.

Global

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.

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:
  • Studio calls the OnBeforeLoad method immediately before loading any Caché document into the work space.
  • Studio calls the OnAfterSave method immediately after saving any Caché document.
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
}
Note:
  • The method checks to see whether the external file exists yet. If the external file exists, the method compares its time stamp to the time stamp of the Caché document. If the external file is more recent than the Caché document, the method loads it.
    It is not strictly necessary to check the time stamps; this method could load the external document every time. The check is performed because it offers a performance improvement.
  • The method sets the relevant nodes of the
    ^MySourceControl
    global.
The OnAfterSave method is analogous, as you can see in the sample itself.
Note:
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
}
Notes:
  • This method uses the
    ^MySourceControl
    global to see whether the current user can actually check this Caché document in.
  • If the user can check the document out, the method does the following:
    • The method calls OnAfterSave to make sure that the Caché document and the external document are synchronized.
    • The method kills the appropriate node of the
      ^MySourceControl
      global to indicate that the document is now checked in.
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 chapter “Cache 2012.1” in the Caché Release Notes and Upgrade Checklist Archive.
Previous section   Next section