#Dexterity Development Environments – Part 3: Single Code Base

David Meego - Click for blog homepageThis is the third article in my series about setting up and working with your Dexterity Development Environments.

If you have not read the previous articles, please do so first using the links below:

Disclaimer: This is the Musgravion principles of development machine setup. It is not the only method but it has worked for me for many years and has been fine tuned during my career. Feel free to use it if it works for you.

In the previous article we discussed setting up individual Dexterity development projects. In this article we discus working on multiple versions of your project at the same time and how to create a single code base.

Introduction

Working with a single code base allows you to keep the feature set and bug fixes the same across all of your supported versions. However, due to changes in the core Dynamics.dic dictionary and possibly changes in other third party products that you integrate with, you might need to have some different code for different versions.

A good example for me was when the application security model changed completely from optimistic user and company based to pessimistic task and role based between versions v9.0 and v10.0 of Microsoft Dynamics GP.  The code base in the Support Debugging Tool was the same for both versions, but worked differently based on the version.

Basics

To help the Dexterity code know what version it is compiling for you will need to have some constants that specify the version of the Dynamics. I use constants like WDC_PROD_MAJ, WDC_PROD_MIN and WDC_PROD_BUILD to store the Major and Minor version of Dynamics as well as the Build of my product. Rather than restarting build from 1 for each version, I use the same build number across all versions so that the build identifies the feature set and fixes.

It is assumed that you followed the standard upgrade procedures of checking in all resources and updating the index file, then using Update with Index to bring the resources into a clean Dynamics.dic dictionary of the new version. You can update the version constants and then compile your code. Don’t fix any compilation or trigger registration errors in the code yet. This makes sure that the starting code for a new version matches the previous versions.

To make the single code base concept work, there are some basic steps and rules required. These are:

  • Open all instances of Dexterity with the appropriate development dictionaries when developing.
  • Always develop code first in the oldest version. This is because the export and import formats sometimes change between versions and the new versions can import older formats, but older versions cannot import newer formats.
  • Whenever you check out a resource in one version, check it out in all other versions.
  • Manually create or update base resources in all products. Base resources are resources found under the Base heading in the tree of the Dexterity Resource Explorer window and include datatypes, fields and messages, etc. I just use the clipboard to make the same changes across all versions. It is possible to export and import base resources, but it is often slow to import and doing it manually is faster.
  • Once you have completed changes to any non-base resources (Tables, Forms, Reports and Scripts) in the oldest version, export them as text files to the Project Folder for that version. Then on each of the newer versions, import them and compile them (or compile all).
  • When adding features use worksets to keep all the new and updated resources (of all types) grouped together.

Note: It is possible to use a single project in your source code repository, but you will need to update the product version constants before you compile your code. This will avoid the step of copying between versions. You can also use branches in your repository for Team Foundation Server (TFS) Visual Studio Team Services (VSTS) to handle some differences.

However, If you are working with GP 2010 (v11.0), you will have to use a separate repository as they are some differences in the Dexterity export/import formats that will cause data loss.

Exceptions to the Rules

There are few exceptions that I have found where exporting and importing resources does not work and you have to repeat changes made across all versions manually. If the steps are a long process, record a macro so you can run the macro to repeat the changes in the other versions.

Here are the two situations I have come across.

  • A Command Form when you are set up to work with Area Pages. The way area pages work changed from v11.0 to v12.0 and there is a change in Dexterity to support this difference. The Area Page command from v12.0 onwards uses a Type of Content Page which does not exist in v11.0. Copying by Export and Import from v11.0 will lose this setting on later versions. Once I no longer support v11.0 this will not be an issue.
  • Forms will large amounts of windows and scripts. I have a very complex form which gets corrupted and loses scripts when using Export and Import. So I usually make the changes manually. If I have made a lot of changes I actually check in the finished file and copy it directly out of the source code repository into the other repositories. Then you can use the Fetch button to bring the new code into the other versions. Export and Import to repository work fine, when Export and Import to a folder does not. Dexterity bug?

Handling Differences

So now we need to look at how we can handle the differences that occur between versions.  This can be differences between Major versions, Minor versions or even between different builds. GP 2013, GP 2015 and GP 2016 all have R2 releases which have the same Major and Minor version numbers as the RTM releases. I don’t have different development projects for the R2 releases, but there are times where there are differences my code to handle changes in the R2 releases.

If you need to adjust code for a difference in a later version, you can update the code in the later version. However, once it is working, copy and paste the script changes back into your oldest version so you can then roll the changes to all versions using export and import.

There are a number methods having a single script with different code depending on version and these are discussed in the following sections.

Conditional Code

The simplest method is to use a conditional statement based on the WDC_PROD_MAJ constant.

if MBS_PROD_MAJ >= 12 then

elseif MBS_PROD_MAJ >= 14 then

else

end if;

Conditional Compilation

The Conditional Code example above can work for some situations, but fails when the version differences include different functions and procedures, changes in parameters, or references to newly added resources. All these situations will create compilation errors on one or the other version.

The solution is to use Dexterity’s Script Preprocessor commands to control which parts of the script compile for each version.

#if MBS_PROD_MAJ >= 12

#elseif MBS_PROD_MAJ >= 14

#else

#end if

To learn mode, in the Dexterity Help on the Index tab, search for “compiling” and double click on the section “script pre-processor”.

Table Differences

When there are differences in a table that has been attached to a form (such as added fields), Conditional Compilation can handle the changes in the scripts, but what can you do when a table has been added or removed.

The solution here is to move the code that uses the tables into a global function or procedure and then call the global script from the form scripts. By making the script global, you don’t need to have the tables attached to the form and so will avoid issues caused by a form referencing non-existent tables.

Dynamic Compilation

Another technique that can be useful is Dynamic Compilation using of pass-through sanScript with the execute() function.

An example of when this works well is when there is a change made in an R2 release. Because I wanted to avoid having separate installs for RTM vs R2 releases, using some dynamically compiled code provided the solution.

The specific script that caused my problem had an optional parameter added for GP 2013 R2 (v12.0). So I ended up using Conditional Compilation with three versions of the code; v11.0 without the parameter, v14.0 or later with the parameter and v12.0 using execute() to dynamically compile the code without the parameter. Without the dynamic compilation the code compiled against RTM would generate an error when executed against R2.

Resource Function Library

Another technique I wanted to cover in this article is using the Resource_ function library to check if a resource exists. You can use the Resource_GetID() and Resource_GetSubResourceName() function library calls allow you to check if a resource exists in a particular build.

This can be used with Conditional Code and/or Dynamic Compilation to adjust code based on a change added to dictionaries for the same version. I have used this to look for changes made in an R2 version so that I could add supported for a new feature. It was easier to actually look for the added resource related to the feature before using it … rather than checking dictionary build numbers.

New Resources

When new resources have been added to later versions and so are not available in the older versions, you can always create them yourself for the older version. I have used this to create constants that were added to Dynamics.dic so that I could use the constant in all versions of my code. For example when the Defaults_Read() and Defaults_Write commands where updated to use USER_INI and GLOBAL_INI constants. I created the matching constants in the older versions so I could use them without getting errors.

Wrapper Functions

There have been occasions where a function that is used regularly in my code has been changed in a later version of Dexterity or Dynamics. When these situations occur, I will create a wrapper function and change my code to call the wrapper function. The wrapper function itself will use Conditional Compilation to handle the changes.  For example when the Defaults_Read() and Defaults_Write commands where updated to use USER_INI and GLOBAL_INI constants. I created wrapper functions that would use the global or user Dex.ini settings file based on the settings passed and whether the version of Dexterity supported the functionality.

Conclusion

To summarize, there is no wrong or right technique as long as it works and the various methods and concepts presented here should allow you to maintain a single code base for your products.

Make sure you test the code in all versions including the R2 releases. That is why I currently have seven Microsoft Dynamics GP environments set up on my system even though I only develop in the four RTM releases.

Note that while the code base is shared, I do have separate source code repositories for each version, so it makes it easy to add support for new versions and retire old versions. I would recommend supporting the last three or four versions.

When GP 2018 is released, I will cease further development on GP 2010 code and that will just stay with whatever build it was up to at the time.

 

For more information, see the KB Article I created on the topic:

Stay tuned for the next article on automating chunking and distribution process and backing up.

David

18-May-2017: Update about using a single source code repository project to have a shared code base. Thanks to Mariano for his input.

This article was originally posted on http://www.winthropdc.com/blog.