RickStrahl / wwDotnetBridge

.NET Interop for Visual FoxPro made easy
http://west-wind.com/wwDotnetBridge.aspx
MIT License
74 stars 35 forks source link

wwDotnetCoreBridge.GetDotnetCoreRuntimeFolder() isn't agnostic of SET EXACT and don't get the latest release. #28

Closed mauricioulla closed 1 year ago

mauricioulla commented 1 year ago

Hi Rick!

The Problem

Considers the followings Net Core versions installed in C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App:

Then, when I create my wwDotNetCoreBridge with CREATEOBJECT("wwDotnetCoreBridge", myParam), I am using myParam for lcRuntimeFolder parameter of Init().

Empty version case

When myParam is empty => fine the version that I get is 7.0.1, great the highest!

Exact version case

When myParam is exactly a installed SDK version => Works fine, for instance with "3.1.12", "5.0.9", "5.0.17", here everything is ok.

Latest release version case

But, what happens when I want the latest release of a specific SDK versions? Lets say using the Partial Version mode:

When myParam is "3.1" => I get 7.0.1 when I expected 3.1.32! When myParam is "5.0" => I get 7.0.1 too when I expected 5.0.17! Same thing when myParam is "6.0" => again, I get 7.0.1 when I expected 6.0.12!

Debugging the code, I found that my application execute SET EXACT ON very early and before (hundreds of lines before) my wwDotNetCoreBridge instance creation.

So, I implemented the following workaround:

LOCAL m.setExact AS String
m.setExact = SET("Exact")
SET EXACT OFF
CREATEOBJECT("wwDotnetCoreBridge", myParam)
SET EXACT &setExact

After that, When myParam is "6.0" => I get 6.0.12, good! But when myParam is "5.0" => I get 5.0.10, oh! oh! oh! ... I expected 5.0.17! Ok, trying with myParam as "3.1" => I get 3.1.11, when I expected 3.1.32... something weird here. Then, if in the future MSFT release another release of 6.0 SDK, lets say a 6.0.22, and my computer would has the 6.0.12 and 6.0.22 releases, wwDotnetCoreBridge will load the 6.0.12 release when I would want 6.0.22... Thats my real case today.

My suggestion

May be, in the line 2018 of wwDotnetBridge.PRG you could replace:

   lnIndex = ASCAN(laFolders,lcVersion,1)

with something like that, using bit 2 of nFlags parameter in ASCAN() to override the system Exact setting:

   lnIndex = ASCAN(laFolders,lcVersion,1,-1,1,4)

Something like that would avoid to implement a workaround like mine, but this is insufficient because doesn't fix the multiple releases case. :-(

Idea for the multiple releases case

I think that here, once that you found the subset of folder of the SDK version, you need order them and deal with the right order because are strings and not numbers. ;-)

I hope was clear.

RickStrahl commented 1 year ago

Yeah I see the problem - semantic versioning is not easy to compare because it's not string comparable - you have to actually compare each version by parsing out the number pairs. This is relatively easy to do with the .NET version function, but this method is called before .NET is loaded so we can't use that unfortunately.

For now I'm making two changes:

This improves things significantly and works even around some of the case I didn't expect to work (ie. 7.1.1 and 7.13.2)

This demonstrates some of the ordering issue:

image

For now the EXACT fix and the order reversal produce better results though and as long as numbers stay below .10 the (which they usually do) I think this won't actually be an issue.

RickStrahl commented 1 year ago

Ok spent a little more time doing a proper update for this so this should work correctly now:

************************************************************************
*  FindDotnetCoreRuntime
****************************************
***  Function: Finds the .NET Core Runtime Path
***    Assume:
***      Pass: lcVersion
***            Modes:
***            Full Path: C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\2.2.5
***            Version only: 2.2.5
***            Partial version 2.1  or 2.2 or 3.0
***    Return: Runtime Folder path or empty string
************************************************************************
FUNCTION GetDotnetCoreRuntimeFolder(lcVersion)
LOCAL lcFolder, lnFolderId, lnResult, lcOutput, lcFolderName, lnX, lnIndex
LOCAL lcVersion, lnParts, lcBasePath
LOCAL laParts[1], laFolders[1,4], laVersions[1,1]

IF !EMPTY(lcVersion) AND DIRECTORY(lcVersion)
    RETURN ADDBS(lcVersion)
ENDIF

DECLARE INTEGER SHGetFolderPath IN Shell32.dll ;
      INTEGER Hwnd, INTEGER nFolder, INTEGER Token, INTEGER Flags, STRING @cPath

lnFolderId = 0x0026  && Program Files
lcOutput = repl(CHR(0),256)
lnResult = SHGetFolderPath(_VFP.hWnd,lnFolderId,0,0,@lcOutput)
IF lnResult = 0
   lcOutput = STRTRAN(lcOutput,CHR(0),"") + "\"
ELSE
   lcOutput = ""
ENDIF

IF EMPTY(lcVersion)
  lcVersion = ""
ENDIF

lcBasePath = ADDBS(lcOutput) + "dotnet\shared\Microsoft.NETCore.App\"

IF !EMPTY(lcVersion)
   *** Check for exact folder match
   IF DIRECTORY(lcVersion)
      RETURN ADDBS(lcFolder)
   ENDIF

   *** Check for exact version match
   IF DIRECTORY(lcBasePath + lcVersion)
      RETURN lcBasePath + lcVersion + "\"
   ENDIF        
ENDIF

*** We have to determine the highest matching version for partial version supplied
*** We capture folder names and pad out version parts in order to sort properly
*** then pick the highest value
lnFolders = ADIR(laFolders,lcBasePath + lcVersion + "*.*","D")
IF lnFolders < 1
   RETURN ""
ENDIF

*** Reformat folders with left padding to 3 characters and find the largest version
DIMENSION laVersions[lnFolders,2]

FOR lnX = 1 TO lnFolders
   lcVersion = laFolders[lnX,1]
   DIMENSION laParts[1]
   lnParts= ALINES(laParts, lcVersion, 1, '.')
   lcAdjusted = PADL(INT(VAL(laParts[1])),3,'0')
   IF(lnParts > 1)
       lcAdjusted = lcAdjusted + "." + PADL(INT(VAL(laParts[2])),3,'0') 
   ENDIF       
   IF(lnParts > 2)
       lcAdjusted = lcAdjusted + "." + PADL(INT(VAL(laParts[3])),3,'0') 
   ENDIF       
   IF(lnParts > 3)
     lcAdjusted = lcAdjusted + "." + PADL(INT(VAL(laParts[4])),3,'0') 
   ENDIF       
   laVersions[lnx, 1] = lcAdjusted
   laVersions[lnx, 2] = lcVersion   
ENDFOR

ASORT(laVersions,1,ALEN(laVersions,1),1)

lcFullVersion = laVersions[1,2]
RETURN lcBasePath + lcFullVersion + "\"

Code basically modifies the version strings into padded number pairs that are sortable and then picks the highest version for the match. As before exact version or path match supersedes the lookup.

Here's what this now looks like:

image

And it's now picking up version 7.13.1 over 7.2.1.

This will work for version parts up to .999 which should be enough :smile:

mauricioulla commented 1 year ago

Great! Padding each version part with zeros was my after-think I wrote the issue.

You read my mind! 😆